Giải ngố Blockchain #01: Lập trình blockchain với Python

Giải ngố Blockchain #01: Lập trình blockchain với Python
Photo by Shubham Dhage / Unsplash

Blockchain là công nghệ đang ngày càng trở nên phổ biến trong các ứng dụng thương mại điện tử, tài chính và rất nhiều lĩnh vực khác. Tuy nhiên, việc hiểu rõ cách hoạt động của blockchain và cách triển khai nó vẫn là một thách thức đối với nhiều người. Trong bài viết này, chúng ta sẽ tìm hiểu các khái niệm cơ bản của blockchain và cách xây dựng một blockchain đơn giản bằng ngôn ngữ lập trình Python.  Bài viết này sẽ hữu ích cho những người mới bắt đầu quan tâm đến blockchain và muốn hiểu rõ hơn về cách hoạt động của nó. Ngoài ra, việc xây dựng một blockchain đơn giản bằng Python cũng là bước tiếp cận tốt để bắt đầu tìm hiểu về các ứng dụng và triển khai thực tế của blockchain.

Blockchain là gì?

Đầu tiên ta cần xét đến khái niệm blockchain (chuỗi khối) có nghĩa là gì? Về cơ bản thì blockchain chính là chain-of-block, là môt chuối các khối được nối liên tiếp với nhau.

Theo Wikipedia, blockchain được định nghĩa như sau [1]:

A blockchain is a distributed ledger with growing lists of records (blocks) that are securely linked together via cryptographic hashes.

Ta sẽ bàn về distributed ledgersecurely linked ở phần sau. Nhưng ở đây chúng ta cần tránh một sự nhầm lẫn hay gặp đó là Satoshi Nakatomo không phải là người đã khám phá ra khái niệm blockchain. Mà người đặt nền móng cho blockchain là hai nhà khoa học Stuart HaberW. Scott Stornetta khi họ đã trình bày hầu hết các ý tưởng quan trọng của blockchain trong bài báo khoa học nổi tiếng được xuất bản năm 1991: "How to time-stamp a digital document" [2].

Vậy trong mỗi block của blockchain có gì? Câu trả lời chính xác nhất thì đó chính là dữ liệu, bất kỳ thứ gì chúng ta có thể mô tả được dưới dạng dữ liệu số đều có thể lưu trữ trong blockchain. Ví dụ đó là thể là dữ liệu của một giao dịch tiền tệ (transaction), bản ghi nhớ, bài viết, hình ảnh,... Mỗi blockchain luôn được thiết kế để lưu trữ một loại dữ liệu gì đó phục vụ mục đích mà nó được phát triển.

Tại sao thuật toán mã hóa lại quan trọng?

Đầu tiên ta sẽ cần nói về việc mã hóa trong blockchain. Như chúng ta đã biết các block (khối) trong blockchain phải được securely linked. Giống như cấu trúc dữ liệu linked list, để kết nối các khối rời rạc với nhau thì trước hết ta phải biết được địa chỉ định danh của các block đó. Mà địa chỉ này phải đảm bảo được tính toàn vẹn của dữ liệu, tức là mỗi địa chỉ luôn đại diện cho một khối dữ liệu cụ thể. Hai khối dữ liệu khác nhau có thể phân biệt được bằng địa chỉ của chúng. Do đó, dùng thuật toán mã hóa hoặc các hàm băm với tỉ lệ đụng độ thấp (low collision rate) là một giải pháp tốt. Ví dụ ta có các block với dữ liệu và mã băm SHA-256 tương ứng như sau:

Lưu ý mã hash SHA-256 có đến 256 bit (64 ký tự), mã hash trong hình đã được cắt bớt để dễ hình dung.

Có thể thấy, bây giờ mỗi block không chỉ có transaction data mà còn có thêm một mã hash. Mã này có thể xem là mã định danh của từng block bởi vì tỉ lệ đụng độ (collision rate) hay nói nôm na là tỉ lệ trùng của thuật toán SHA-256 là cực kỳ thấp [3]. Giờ đây ta có thể dễ dàng link các khối với nhau thông qua mã hash này bằng cách gắn thêm thông tin previous_hash cho mỗi block. Tức là ta có thể truy ra block ngay liền trước block hiện tại trong blockchain bằng mã previous_hash. Như ví dụ bên dưới, ta có thể tìm được block số 2 bằng cách tìm mã previous_hash được lưu tại block số 3. Do block đầu tiên không có block ở liền trước nó nên ta có thể định nghĩa thông tin previous_hash bằng hằng số -1, block này thường được gọi là Genesis Block.

Ngoài việc dùng để link các block với nhau thì mã băm này còn một lợi ích nữa đó là đảm bảo tính toàn vẹn của dữ liệu. Chỉ cần thay đổi duy nhất 1 bit trong dữ liệu thì thuật toán SHA-256 sẽ cho ra một mã băm hoàn toàn khác (avalance effect), cho nên ta có thể dùng nó để kiểm tra xem dữ liệu có bị thay đổi, xâm phạm hay không. Việc đảm bảo tính toàn vẹn của dữ liệu là một tiêu chí rất quan trọng của blockchain. Ví dụ 2 bản ghi dữ liệu sau chỉ khác nhau ở một số duy nhất là block, tuy nhiên khi thực hiện thuật toán SHA-256 sẽ ra hai kết quả rất khác:

{
   "block": 3,
      "trasaction": {
         "sender": "Nam",
         "recipient": "Nga",
         "amount": 1.0
      }
}

SHA-256: 2010ECE8D55B4C8D74D411398805C680AD67B9D6B63EC5299FA0D689B95CC39D
{
   "block": 2,
      "trasaction": {
         "sender": "Nam",
         "recipient": "Nga",
         "amount": 1.0
      }
}

SHA-256: B686C1C65B2400CFBED6819F4B315B4969B883D2A0C68ADD5382A59FE9C01997

Ngoài ra, một điều khác khiến SHA-256 được dùng nhiều đó là tốc độ xử lý và tính toán của thuật toán này rất nhanh.

Immutable Ledger

Nếu với thuật toán mã hóa (hash) ta có thể đảm bảo dữ liệu của từng block không thể bị thay đổi, xâm phạm. Thì ở mức độ chuỗi khối, nếu người quản lý chuỗi cố tình thêm một khối mới chứa dữ liệu độc hai (malicious block) thì sao?

Malicious block có thể chứa dữ liệu độc hại, những transaction không được cấp phép,...

Nếu coi chuỗi khối là một cuốn số ghi nhận tất cả các transaction, thì bất kỳ ai sở hữu cuốn cuốn sổ này đều có thể thao túng tất cả dữ liệu. Điều này tương tự với cách ngân hàng truyền thống sử dụng các cơ sở dữ liệu tập trung. Nếu như hacker truy cập được vào cơ sở dữ liệu này thì hoàn toàn có thể thay đổi dữ liệu để đánh cắp tài sản rất dễ dàng.

Để giải quyết vấn đề này, blockchain sẽ cho phép tất cả người dùng ở trên mạng lưới của mình có thể giữ một bản sổ cái (ledger). Tức là bất kỳ ai kết nối vào mạng cũng được giữ một bản copy của blockchain (nói là bản copy nhưng thật ra tất cả đều là bản chính). Khi có bất kỳ giao dịch nào được thêm vào blockchain thì tất cả người dùng đều phải thực hiện ghi nhận giao dịch đó vào cuốn sổ cái của mình. Khi đó, nếu có một malicious nào được bất kỳ ai thêm vào sổ cái của riêng họ thì cũng rất khó vượt qua được quy trình kiểm tra chéo trên mạng ngang hàng. Do đó, cơ chế này được gọi là immutable ledger (sổ cái không thể thay đổi).

Distributed P2P Network

Mạng phân tán ngang hàng hay distributed peer-to-peer network không phải là một khái niệm mới. Như chúng ta có thể thấy, khi bất kỳ máy tính nào tham gia một mạng blockchain thì máy tính đó phải luôn luôn kết nối với mạng lưới để liên tục cập nhật chain của mình. Khi đó tất cả các máy tính trở thành các node trong mạng ngang hàng. Gọi là mạng ngang hàng vì trong mạng này tất cả các node đều có vai trò và trách nhiệm bình đẳng với nhau, không có bất kỳ node nào là node cordinator như trong các kiến trúc master-slave.

Trong mạng này, khi có bất kỳ giao dịch nào hợp lệ được ghi nhận (mining thành công) tại một node thì node đó sẽ thực hiện broadcast thông tin về giao dịch đó cho toàn bộ mạng thông qua một peer-to-peer protocol. Các node khác khi nhận được thông tin sẽ thực hiện ghi nhận giao dịch đó vào chain của mình. Khi đó, nếu có một cuộc tấn công bằng cách cố tình thay đổi giao dịch trên chain của mình thì node đó được gọi là malicious node. Để xác thực xem một chain có phải là bản gốc hay bản đã bị chỉnh sửa hay không thì rất đơn giản, chỉ cần sử dụng phương pháp đối chiếu chain đó với đa số. Chúng ta sẽ bàn sâu hơn về vấn đề này ơ phần cơ chế đồng thuận, nhưng nói chung là chain nào được nhiều node ghi nhận hơn thì đó là chain gốc, ngược lại thì là malicious chain.

Mining a block

Vậy thuật ngữ "đào" trong blockchain là gì? Thực chất "đào" là từ chỉ hành động thêm một block mới vào blockchain. Block đó có thể chứa một hoặc nhiều transaction. Ví dụ ta có một transaction mới như sau:

{
    "sender": "Nam",
    "recipient": "Thanh",
    "amount": 2.5
}

Lúc này ta cần phải thêm transaction này vào một block sau đó thêm block này vào blockchain. Tuy nhiên nếu chỉ mã hóa transaction này thành mã hash và gắn nó vào chuỗi hiện tại bằng previous_hash thì có vẻ quá đơn giản, bất kỳ ai cũng có thể làm được. Do đó, trong một mạng blockchain, nếu muốn xác nhận vào thêm một transaction vào block thì ta phải giải quyết một bài toán có độ phức tạp (algorithm complexity) cao. Bài toán này được thiết kế để các node trong mạng cạnh tranh với nhau, node nào giải xong bài toán này trước thì sẽ được thưởng vì đã thêm thành công block mới vào chain.

Vậy đó là bài toán gì? Như chúng ta đã biết, tất cả dữ liệu của một block sẽ được mã hóa thành một mã hash SHA-256. Vì mã hash SHA-256 là một hexadecimal number (một số trong hệ cơ số 16), nên bài toán được đặt ra là phải làm sao để mã hash của tất cả các block luôn nằm trong một khoảng nhất định. Mỗi mã hash có đến 256 bit mà mỗi ký tự trong hệ cơ số 16 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, A, B, C, D, E, F) cần 4 bit, nên tối đa mã hash này sẽ có 64 ký tự. Dễ dàng nhận thấy mã hex nhỏ nhất và lớn nhất trong trường hợp này lần lượt là:

min: 0000000000000000000000000000000000000000000000000000000000000000
max: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Ta hoàn toàn có thể định nghĩa một khoảng mã hex nhất định được phép sử dụng bằng cách thêm boundary hex. Như ví dụ bên dưới, ta có boundary hex là 0000FFFFFF... Ta sẽ định nghĩa bất kì mã hex nào lớn hơn min và nhỏ hơn boundary hex đều được chấp nhận, còn ngược lại thì không.

Tuy nhiên, dữ liệu trong block là cố định, tức là chỉ có thể cho ra một mã hash duy nhất. Làm sao để kiểm soát giá trị hash này và đưa chúng về khoảng được chấp nhận. Từ đó, ta định nghĩa thêm một trường dữ liệu khác trong block đó là nonce. Nonce (number used only once) là một trường dữ liệu được kiểm soát hoàn toàn bởi thuật toán và không hề ảnh hưởng đến các trường còn lại của dữ liệu. Nonce chỉ có vai trò duy nhất là giúp điều chỉnh mã hash theo ý muốn của chúng ta bằng cách thay đổi giá trị nonce này. Do dó, để giải quyết bài toán mining ở trên, các miners cần phải tìm ra một giá trị nonce sao cho khi thêm giá trị này vào dữ liệu của block và mã hóa lại thì mã hash sẽ nằm trong khoảng được chấp nhận (allowed range). Điều này đòi hỏi các miners phải thử nghiệm hàng triệu hoặc thậm chí hàng tỉ giá trị nonce khác nhau để tìm ra giá trị thích hợp.

Miner phải thử nghiệm rất rất nhiều lần để tìm ra giá trị nonce phù hợp.

Nếu một miner tìm thấy giá trị nonce phù hợp, họ sẽ thông báo cho các node khác trên mạng rằng họ đã tìm thấy giải pháp và đưa ra bằng chứng cho đề xuất thêm một block mới vào blockchain. Các node khác sẽ xác minh giải pháp của miner bằng cách kiểm tra xem hash của block mới có thỏa mãn yêu cầu hay không. Nếu đúng, các node khác sẽ đồng ý với đề xuất và thêm block mới vào blockchain của họ. Phương pháp này nổi tiếng với cái tên là cơ chế đồng thuận proof-of-work.

Với cách thức hoạt động này, blockchain đảm bảo tính an toàn, bảo mật và không thể chỉnh sửa được của các giao dịch. Việc giải quyết bài toán mining giúp bảo vệ blockchain tránh khỏi các cuộc tấn công như double spending hay các hành động gian lận khác. Ngoài ra, việc thực hiện bài toán mining cũng là cách để phân phối cryptocurrency mới, là nguồn thu nhập cho các miners trên mạng.

Cơ chế đồng thuận (Consensus Protocol)

Byzantine Fault Tolerance

Việc giải quyết bài toán mining chỉ là một phần của việc đảm bảo tính an toàn của mạng blockchain. Một thách thức khác trong việc xây dựng một mạng blockchain là đảm bảo tính toàn vẹn của dữ liệu trên mạng trong trường hợp có sự cố xảy ra. Chẳng hạn, nếu một số node trên mạng có hành vi gian lận hoặc bị lỗi, thì làm sao mạng blockchain có thể đảm bảo rằng dữ liệu vẫn được duy trì một cách chính xác?

Để giải quyết vấn đề này, một khái niệm quan trọng trong lý thuyết mạng là Byzantine Fault Tolerance (BFT) được sử dụng. BFT là khả năng của một hệ thống mạng để hoạt động đúng và đủ hiệu quả, ngay cả khi một số node trong mạng bị lỗi hoặc có hành vi gian lận.

Trong một mạng blockchain, Byzantine Fault Tolerance được đạt được bằng cách sử dụng các cơ chế đồng thuận (consensus protocol). Consensus protocol là một hệ thống các quy tắc và thuật toán được sử dụng để đồng bộ hóa các hoạt động và quyết định trong một mạng blockchain. Consensus protocol cũng được sử dụng để bảo vệ mạng blockchain khỏi các tấn công từ các hacker và kẻ tấn công khác.

Consensus Protocol

Một số mạng blockchain sử dụng thuật toán Proof of Stake (PoS) để đảm bảo tính toàn vẹn của dữ liệu trên mạng. Trong thuật toán PoS, các node trên mạng được yêu cầu phải giữ (stake) một số tiền cryptocurrency để trở thành validator và xác minh các giao dịch trên mạng. Các validator được chọn ngẫu nhiên để tham gia xác minh các giao dịch và đưa ra quyết định cho phép thêm các block mới vào blockchain. Việc chọn ngẫu nhiên các validator này đảm bảo tính công bằng và đồng đều trong việc kiểm soát mạng. Nếu các validator không đồng ý với quyết định đưa ra, các node khác trên mạng sẽ chọn một validator khác để đưa ra quyết định. Điều này giúp đảm bảo tính toàn vẹn của dữ liệu trên mạng blockchain, ngay cả khi một số validator có hành vi gian lận hoặc bị lỗi.

Một thuật toán phân tán khác được sử dụng trong mạng blockchain chính là Proof of Work (PoW) như đã đề cập. Trong thuật toán này, các node trên mạng cạnh tranh với nhau để giải quyết bài toán mining, và node nào giải quyết bài toán đầu tiên sẽ được thưởng bằng một số tiền cryptocurrency. Tuy nhiên, việc giải quyết bài toán này rất tốn kém về tài nguyên và năng lượng, và có thể dẫn đến một số node bị loại khỏi mạng blockchain.

%51\%% Attack

Một trong những cách phổ biến để tấn công mạng blockchain là tấn công %51\%% Attack. Kẻ tấn công này sẽ cố gắng kiểm soát hơn %50\%% các node trên mạng blockchain và sử dụng quyền kiểm soát đó để thực hiện các giao dịch không hợp lệ hoặc thay đổi dữ liệu trên blockchain. Điều này có thể gây ra sự mất độc lập của mạng blockchain và làm suy yếu tính toàn vẹn của dữ liệu trên mạng.

Tuy nhiên, với sự phát triển của các Consensus Protocol như Proof of Work (PoW), Proof of Stake (PoS) và Delegated Proof of Stake (DPoS), các tấn công %51\%% Attack đã trở nên khó hơn. Trong Proof of Work, việc đào coin của mạng blockchain được thực hiện bằng cách sử dụng năng lượng tính toán của các máy tính đào coin để giải quyết các bài toán phức tạp. Điều này làm cho việc kiểm soát hơn %50\%% các node trên mạng trở nên rất đắt đỏ và khó khăn.

Trong Proof of Stake và Delegated Proof of Stake, các validator trên mạng blockchain được chọn dựa trên số tiền mà họ nắm giữ trong mạng. Điều này đảm bảo rằng các validator có lợi ích với tính toàn vẹn của dữ liệu trên mạng và sẽ không có lợi ích nào trong việc phá hoại mạng blockchain.

Hạn chế

Tuy nhiên, các Consensus Protocol cũng có nhược điểm của chúng. Ví dụ, trong Proof of Work, việc đào coin tiêu tốn rất nhiều năng lượng và có thể gây ra tác động tiêu cực đến môi trường. Trong Proof of Stake, các validator có lợi ích với tính toàn vẹn của dữ liệu trên mạng có thể được tập trung quá nhiều, dẫn đến tình trạng tập trung quá mức và suy yếu tính phi tập trung của mạng blockchain.

Tóm lại, Consensus Protocol là một phần quan trọng trong việc đảm bảo tính an toàn và tính toàn vẹn của mạng blockchain (Byzantine Fault Tolerance). Các Consensus Protocol như Proof of Work, Proof of Stake và Delegated Proof of Stake được sử dụng để đảm bảo rằng các hoạt động trên mạng blockchain được đồng bộ hóa và được thực hiện đúng cách. Ngoài ra, các Consensus Protocol cũng giúp bảo vệ mạng blockchain khỏi các tấn công từ các hacker và kẻ tấn công khác.

Mặc dù các Consensus Protocol có nhược điểm của riêng chúng, nhưng vẫn được coi là các giải pháp an toàn và tin cậy để xử lý các hoạt động trên mạng blockchain. Các nhà phát triển blockchain đang tiếp tục nghiên cứu và phát triển các Consensus Protocol mới để cải thiện tính bảo mật và tính hiệu quả của mạng blockchain.

Lập trình blockchain đơn giản với Python

Để xây dựng một chương trình blockchain với các chức năng cơ bản, đầu tiên ta hãy tạo một lớp có tên Blockchain và một import một số thư viện cần thiết như sau:

Chức năng đầu tiên cần hiện thực đó là thêm một block vào mạng. Mỗi block của chúng ta sẽ chứa các thông tin bao gồm index, timestamp, nonce, previous_hash, và để đơn giản trường data hiện tại ta chỉ cần lưu một chuỗi là "This is a block". Ví dụ block đầu tiên (genesis block) của chúng ta sẽ được thể hiện như sau:

{
    "index": 1,
    "timestamp": "2023-02-22 20:33:32.362287",
    "nonce": 0,
    "data": "This is a block",
    "previous_hash":"0",
    "hash":"7dd23dbc895a79f40d5651382a43f3ce0408a4bde547c00ae0fb5ae58b631abf"
}

Đoạn code sau sẽ hiện thực việc tạo block như trên với hai tham số là nonceprevious_hash. Sau khi có hàm new_block thì ta thực hiện mining genesis block để thêm vào chain.

Một hàm quan trọng khác cần có khi lập trình blockchain đó là hàm băm (hash). Chỉ đơn giản bằng cách sử dụng thư viện hashlib ta có thể lấy được mã hash SHA-256 như bên dưới:

Việc tiếp theo là viết hàm tìm kiếm nonce, hay proof-of-work. Vì sự liên hệ giữa nonce và mã hash là hoàn toàn ngẫu nhiên nên ta không có cách nào khác ngoài việc tìm kiếm tuần tự cho đến khi nào thành công thì thôi. Để đơn giản, thuật toán proof-of-work của chúng ta chỉ cần tìm các giá trị hash nhỏ hơn 0000FFFFFF.. và lớn hơn 00000000... Do bất kì mã hex nào bắt đầu với 4 số 0 (0000xxxx...) đều nhỏ hơn 0000FFFFF... nên chúng ta chỉ cần kiểm tra xem mã hex đó có chưa đủ 4 số 0 ở đầu hay không mà thôi.

Tiếp theo ta cần phải viết hàm kiểm tra xem chain có hợp lệ hay không. Với mức độ hiện tại, một chain hợp lệ chỉ cần là một chain với tất cả các block đáp ứng hai tiêu chí: liên kết giữa hai block là toàn vẹn, giá trị nonce chính xác. Hàm này được hiện thực như sau:

Như vậy, ta đã hoàn thành các chức năng cơ bản nhất của một blockchain. Tiếp theo, chỉ cần viết REST API để test các hàm này thông qua webapp đơn giản. Chúng ta sẽ sử dụng FastAPI để hiện thực API này:

Để chạy được API này, ta chỉ việc cài đặt FastAPI và Uvicorn và thực hiện command line như sau:

# Install FastAPI and uvicorn
$ pip install fastapi uvicorn

# Run the server
$ uvicorn blockchain:app

Như chúng ta có thể thấy, API trên chỉ có 3 endpoint chính đó là:

  • /mine_block: Mining một block mới với dữ liệu là "This is a block".
  • /get-chain: Trả về kết quả là toàn bộ chain.
  • /is-valid: Kiểm tra xem có chain hợp lệ hay không.
Sử dụng cURL để test các API endpoint

Ngoài ra, ta cũng có thể render một HTML page đơn giản để test các chức năng này thông qua endpoint /.

Khi đó ta có thể thực hiện test trực tiếp thông qua web browser:

Mine block
Get full chain
Check valid chain. Có thể thấy API trả về "All good. The Blockchain is valid".

Kết

Trong bài viết này, chúng ta đã tìm hiểu các khái niệm quan trọng của blockchain từ thuật toán mã hóa, immutable ledger đến consensus protocol vầ thực hiện cài đặt một số chức năng cơ bản. Ở phần sau, chúng ta sẽ tiếp tục hiện thực việc thêm transaction vào block và cơ chế đồng thuận trên mạng phân tán ngang hàng thông qua một ứng dụng cụ thể của blockchain đó là cryptocurrency. Toàn bộ mã nguồn trong bài viết này có thể được tìm thấy ở đây: https://github.com/vndee/blockchain-demo

Tài liệu tham khảo