Trong bài hướng dẫn ngày hôm nay chúng ta sẽ đi từ những bước đầu tiên để xây dựng lên một mạng blockchain đơn giản bằng ngôn ngữ python. Giúp chúng ta sẽ có cái nhìn tổng quát nhất về cách thức một blockchain hoạt động.
Bằng cách sử dụng microframework Flask để tạo ra các điểm endpoint, sau đó chạy trên nhiều máy để tạo một mạng phi tập trung. Chúng ta sẽ học cách xây dựng một giao diện người dùng đơn giản để tương tác với blockchain và lưu trữ thông tin cho mọi trường hợp sử dụng, chẳng hạn như thanh toán ngang hàng, trò chuyện hoặc thương mại điện tử.
Mục tiêu là sẽ xây dựng một ứng dụng cho phép người dùng chia sẻ thông tin bằng cách đăng các bài post. Nội dung sẽ được lưu trữ trên blockchain, nên nó sẽ không thể thay đổi và tồn tại vĩnh viễn. Người dùng sẽ tương tác với ứng dụng thông qua một giao diện web đơn giản.
Chúng ta sẽ sử dụng cách tiếp cận từ dưới lên. Bắt đầu bằng cách xác định cấu trúc dữ liệu mà chúng tôi sẽ lưu trữ trong blockchain. Mỗi bài post sẽ bao gồm ba yếu tố cần thiết:
- Content
- Author
- Timestamp
Sau đây chúng ta sẽ đi luôn vào các bước thực hiện:
Lưu trữ các giao dịch trong các blocks
Ở đây chúng ta sẽ lưu trữ theo định dạng được sử dụng rộng rãi đó là JSON. Và dưới đây là những gì một bài post sẽ được lưu trữ trong blockchain:
1 2 3 4 5 6 | { "author": "some_author_name", "content": "Some thoughts that author wants to share", "timestamp": "The time at which the content was created" } |
Thuật ngữ data
trong blockchain thường thay thế bằng transaction
. Nên ở đây chúng ta thống nhất sử dụng thuật ngữ transaction
để chỉ data
trong ứng dụng này.
Các transaction sẽ được đóng gói thành block. Một block có thể chứa một hoặc nhiều transaction. Các blocks chứa các transactions được tạo thường xuyên và được thêm vào blockchain. Do có nhiều blocks nên mỗi block sẽ có một ID
duy nhất:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> <span class="token class-name">Block</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> index<span class="token punctuation">,</span> transactions<span class="token punctuation">,</span> timestamp<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Constructor cho một `Block` class. :param index: Chỉ số ID duy nhất của một block. :param transactions: Danh sách các transactions. :param timestamp: Thời gian tạo block. """</span> self<span class="token punctuation">.</span>index <span class="token operator">=</span> index self<span class="token punctuation">.</span>transactions <span class="token operator">=</span> transactions self<span class="token punctuation">.</span>timestamp <span class="token operator">=</span> timestamp |
Thêm chữ ký số vào các blocks
Để ngăn các dữ liệu giả mạo được lưu trữ trên bên trong các blocks và để phát hiện ra điều này chúng ta sẽ sử dụng hàm băm.
Hàm băm là một hàm lấy dữ liệu có kích thước bất kỳ và tạo ra dữ liệu có kích thước cố định, thường được sử dụng để xác định dữ liệu đầu vào. Hàm băm lý tưởng thường có những đặc điểm sau là:
- Dễ dàng để tính toán.
- Cùng một dữ liệu sẽ luôn dẫn đến cùng một giá trị băm.
- Phải có tính ngẫu nhiên thống nhất tức là ngay cả một thay đổi bit trong dữ liệu cũng sẽ thay đổi giá trị băm đáng kể.
Kết quả của những tính chất trên là:
- Hầu như không thể đoán được dữ liệu đầu vào được băm. (Cách duy nhất là thử tất cả các trường hợp đầu vào có thể)
- Nếu bạn biết cả đầu vào và giá trị băm, bạn chỉ cần truyền đầu vào qua hàm băm để xác minh giá trị băm được cung cấp có đúng hay không.
Có nhiều hàm băm phổ biến khác nhau. Đây là một ví dụ trong Python ta sử dụng hàm băm SHA-256
:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token operator">>></span><span class="token operator">></span> <span class="token keyword">from</span> hashlib <span class="token keyword">import</span> sha256 <span class="token operator">>></span><span class="token operator">></span> data <span class="token operator">=</span> b<span class="token string">"Some variable length data"</span> <span class="token operator">>></span><span class="token operator">></span> sha256<span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">.</span>hexdigest<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token string">'b919fbbcae38e2bdaebb6c04ed4098e5c70563d2dc51e085f784c058ff208516'</span> <span class="token operator">>></span><span class="token operator">></span> sha256<span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">.</span>hexdigest<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># bất kể bạn chạy nó bao nhiêu lần, kết quả sẽ là cùng một chuỗi 256 ký tự</span> <span class="token string">'b919fbbcae38e2bdaebb6c04ed4098e5c70563d2dc51e085f784c058ff208516'</span> <span class="token operator">>></span><span class="token operator">></span> data <span class="token operator">=</span> b<span class="token string">"Some variable length data2"</span> <span class="token comment"># Đã thêm một ký tự ở cuối.</span> <span class="token string">'9fcaab521baf8e83f07512a7de7a0f567f6eef2688e8b9490694ada0a3ddeec8'</span> <span class="token comment"># Note: giá trị băm đã thay đổi hoàn toàn !</span> |
Chúng ta sẽ lưu trữ giá trị băm của khối vào một trường bên trong đối tượng Block
và nó sẽ hoạt động giống như chữ ký số (hoặc chữ ký) của dữ liệu chứa trong đó:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">from</span> hashlib <span class="token keyword">import</span> sha256 <span class="token keyword">import</span> json <span class="token keyword">def</span> <span class="token function">compute_hash</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Chuyển đối tượng block thành dạng string JSON sau đó trả về giá trị băm. """</span> block_string <span class="token operator">=</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>self<span class="token punctuation">.</span>__dict__<span class="token punctuation">,</span> sort_keys<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span> <span class="token keyword">return</span> sha256<span class="token punctuation">(</span>block_string<span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>hexdigest<span class="token punctuation">(</span><span class="token punctuation">)</span> |
Nối các blocks thành chain(chuỗi)
Chúng ta cần một cách để đảm bảo rằng bất kỳ thay đổi nào trong các blocks trước đó sẽ làm mất hiệu lực toàn bộ chain. Cách làm của Bitcoin là tạo ra sự phụ thuộc giữa các khối liên tiếp bằng cách xâu chuỗi chúng với giá trị băm của block ngay trước. Có nghĩa là sẽ lưu giá trị băm của block trước đó trong block hiện tại khi thêm mới vào một trường có tên là previous_hash
.
Sẽ có câu hỏi: vậy còn block đầu tiên thì sao? block đó được gọi là block genesis và nó có thể được tạo thủ công hoặc thông qua một số logic nào đó.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <span class="token keyword">from</span> hashlib <span class="token keyword">import</span> sha256 <span class="token keyword">import</span> json <span class="token keyword">import</span> time <span class="token keyword">class</span> <span class="token class-name">Block</span><span class="token punctuation">:</span> def__init__<span class="token punctuation">(</span>self<span class="token punctuation">,</span> index<span class="token punctuation">,</span> transactions<span class="token punctuation">,</span> timestamp<span class="token punctuation">,</span> previous_hash<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Constructor cho một `Block` class. :param index: Chỉ số ID duy nhất của một block. :param transactions: Danh sách các transactions. :param timestamp: Thời gian tạo block. :param previous_hash: Chứa giá trị băm của block đứng trước trong chain. """</span> self<span class="token punctuation">.</span>index <span class="token operator">=</span> index self<span class="token punctuation">.</span>transactions <span class="token operator">=</span> transactions self<span class="token punctuation">.</span>timestamp <span class="token operator">=</span> timestamp self<span class="token punctuation">.</span>previous_hash <span class="token operator">=</span> previous_hash <span class="token comment"># thêm trường previous_hash</span> <span class="token keyword">def</span> <span class="token function">compute_hash</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Chuyển đối tượng block thành dạng string JSON sau đó trả về giá trị băm. """</span> block_string <span class="token operator">=</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>self<span class="token punctuation">.</span>__dict__<span class="token punctuation">,</span> sort_keys<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span> <span class="token keyword">return</span> sha256<span class="token punctuation">(</span>block_string<span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>hexdigest<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">Blockchain</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Constructor của class `Blockchain`. """</span> self<span class="token punctuation">.</span>chain <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> self<span class="token punctuation">.</span>create_genesis_block<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">create_genesis_block</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Hàm generate ra `block genesis` và thêm nó vào chain. Block này có index bằng 0, previous_hash là 0 và một giá trị băm hợp lệ. """</span> genesis_block <span class="token operator">=</span> Block<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"0"</span><span class="token punctuation">)</span> genesis_block<span class="token punctuation">.</span><span class="token builtin">hash</span> <span class="token operator">=</span> genesis_block<span class="token punctuation">.</span>compute_hash<span class="token punctuation">(</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>chain<span class="token punctuation">.</span>append<span class="token punctuation">(</span>genesis_block<span class="token punctuation">)</span> @<span class="token builtin">property</span> <span class="token keyword">def</span> <span class="token function">last_block</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Một cách đơn giản để lấy block cuối cùng trong chain. Chú ý chain sẽ luôn có một block đó chính là block genesis """</span> <span class="token keyword">return</span> self<span class="token punctuation">.</span>chain<span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> |
Bây giờ, nếu nội dung của bất kỳ khối nào trước đó thay đổi:
- Giá trị băm của khối trước đó sẽ thay đổi.
- Điều này sẽ dẫn đến sự không phù hợp với trường previous_hash trong khối tiếp theo.
- Vì dữ liệu đầu vào để tính toán giá trị băm của bất kỳ khối nào cũng bao gồm cả trường previous_hash, nên giá trị băm của khối tiếp theo cũng sẽ thay đổi.
Cuối cùng, toàn bộ chain theo khối thay thế bị vô hiệu và cách duy nhất để khắc phục là tính toán lại toàn bộ chain.
Triển khai thuật toán Proof-Of-Work(Bằng chứng công việc)
Dù vậy vẫn có một vấn đề đó là giá trị băm của tất cả các block tiếp theo có thể được tính lại khá dễ dàng để tạo ra một blockchain khác hợp lệ. Để ngăn chặn điều này, chúng ta có thể khai thác tính bất đối xứng của hàm băm mà chúng ta đã thảo luận ở trên để thực hiện nhiệm vụ tính toán giá trị băm khó khăn và ngẫu nhiên hơn. Điều này có nghĩa là: Thay vì chấp nhận bất kỳ giá trị băm nào cho block, chúng ta thêm một số ràng buộc cho nó. Hãy thêm một ràng buộc rằng giá trị băm của chúng ta sẽ bắt đầu bằng n các số 0 phía trước trong đó n là số nguyên dương.
Ở đây ta sẽ thêm một số dữ liệu giả mà ta có thể thay đổi. Ta sẽ thêm một trường mới là trường nonce . Số nonce là một số mà chúng ta có thể tiếp tục thay đổi cho đến khi chúng ta có được một hàm băm thỏa mãn ràng buộc. Việc nonce thỏa mãn các ràng buộc đóng vai trò là bằng chứng cho thấy một số tính toán đã được thực hiện. Kỹ thuật này là phiên bản đơn giản hóa của thuật toán Hashcash được sử dụng trong Bitcoin. Số lượng số 0 được chỉ định trong ràng buộc xác định độ khó của thuật toán PoW (số lượng số 0 càng lớn, càng khó để tìm ra nonce).
Ngoài ra do tính bất đối xứng nên Proof-of-work(PoW) khó tính toán nhưng rất dễ xác minh một khi bạn tìm ra nonce (bạn chỉ cần chạy lại hàm băm):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">Blockchain</span><span class="token punctuation">:</span> <span class="token comment"># độ khó của thuật toán POW</span> difficulty <span class="token operator">=</span> <span class="token number">2</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">def</span> <span class="token function">proof_of_work</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> block<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Hàm thử các giá trị khác nhau của nonce để lấy giá trị băm thỏa mãn """</span> block<span class="token punctuation">.</span>nonce <span class="token operator">=</span> <span class="token number">0</span> computed_hash <span class="token operator">=</span> block<span class="token punctuation">.</span>compute_hash<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">while</span> <span class="token operator">not</span> computed_hash<span class="token punctuation">.</span>startswith<span class="token punctuation">(</span><span class="token string">'0'</span> <span class="token operator">*</span> Blockchain<span class="token punctuation">.</span>difficulty<span class="token punctuation">)</span><span class="token punctuation">:</span> block<span class="token punctuation">.</span>nonce <span class="token operator">+=</span> <span class="token number">1</span> computed_hash <span class="token operator">=</span> block<span class="token punctuation">.</span>compute_hash<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">return</span> computed_hash |
Thêm block vào Chain
Để thêm một block vào chain, trước tiên chúng ta phải xác minh rằng:
- Dữ liệu không bị giả mạo (proof of work được cung cấp là chính xác).
- Thứ tự của các giao dịch được giữ nguyên.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <span class="token keyword">class</span> <span class="token class-name">Blockchain</span><span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">def</span> <span class="token function">add_block</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> block<span class="token punctuation">,</span> proof<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Chức năng thêm block vào chain sau khi xác minh. Xác minh bao gồm: * Check nếu proof is đúng. * Trường previous_hash đúng và là giá trị băm của block mới nhất trong chain. """</span> previous_hash <span class="token operator">=</span> self<span class="token punctuation">.</span>last_block<span class="token punctuation">.</span><span class="token builtin">hash</span> <span class="token keyword">if</span> previous_hash <span class="token operator">!=</span> block<span class="token punctuation">.</span>previous_hash<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token boolean">False</span> <span class="token keyword">if</span> <span class="token operator">not</span> Blockchain<span class="token punctuation">.</span>is_valid_proof<span class="token punctuation">(</span>block<span class="token punctuation">,</span> proof<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token boolean">False</span> block<span class="token punctuation">.</span><span class="token builtin">hash</span> <span class="token operator">=</span> proof self<span class="token punctuation">.</span>chain<span class="token punctuation">.</span>append<span class="token punctuation">(</span>block<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">True</span> <span class="token keyword">def</span> <span class="token function">is_valid_proof</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> block<span class="token punctuation">,</span> block_hash<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Kiểm tra xem block_hash có phải là giá trị băm hợp lệ của block hay không và thỏa mãn các tiêu chí độ khó. """</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>block_hash<span class="token punctuation">.</span>startswith<span class="token punctuation">(</span><span class="token string">'0'</span> <span class="token operator">*</span> Blockchain<span class="token punctuation">.</span>difficulty<span class="token punctuation">)</span> <span class="token operator">and</span> block_hash <span class="token operator">==</span> block<span class="token punctuation">.</span>compute_hash<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
Mining
Các transactions ban đầu sẽ được lưu trữ dưới dạng một nhóm các transactions chưa được xác nhận. Quá trình đưa các transactions chưa được xác nhận vào một block và tính toán POW được gọi là Mining các blocks. Khi nonce thỏa mãn các ràng buộc được tìm ra, chúng ta có thể nói rằng một block đã được mined và nó có thể được đưa vào blockchain.
Trong hầu hết các loại tiền điện tử (bao gồm Bitcoin), những người miners có thể được trao một số tiền điện tử như một phần thưởng cho việc sử dụng sức mạnh tính toán của họ để tính toán POW. Đây là function mining:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <span class="token keyword">class</span> <span class="token class-name">Blockchain</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> self<span class="token punctuation">.</span>unconfirmed_transactions <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token comment"># list transactions chưa được xác nhận</span> self<span class="token punctuation">.</span>chain <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> self<span class="token punctuation">.</span>create_genesis_block<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">def</span> <span class="token function">add_new_transaction</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> transaction<span class="token punctuation">)</span><span class="token punctuation">:</span> self<span class="token punctuation">.</span>unconfirmed_transactions<span class="token punctuation">.</span>append<span class="token punctuation">(</span>transaction<span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">mine</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Hàm thêm transactions pending vào blockchain bằng các thêm chúng vào block và đưa ra PoW. """</span> <span class="token keyword">if</span> <span class="token operator">not</span> self<span class="token punctuation">.</span>unconfirmed_transactions<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token boolean">False</span> last_block <span class="token operator">=</span> self<span class="token punctuation">.</span>last_block new_block <span class="token operator">=</span> Block<span class="token punctuation">(</span>index<span class="token operator">=</span>last_block<span class="token punctuation">.</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> transactions<span class="token operator">=</span>self<span class="token punctuation">.</span>unconfirmed_transactions<span class="token punctuation">,</span> timestamp<span class="token operator">=</span>time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> previous_hash<span class="token operator">=</span>last_block<span class="token punctuation">.</span><span class="token builtin">hash</span><span class="token punctuation">)</span> proof <span class="token operator">=</span> self<span class="token punctuation">.</span>proof_of_work<span class="token punctuation">(</span>new_block<span class="token punctuation">)</span> self<span class="token punctuation">.</span>add_block<span class="token punctuation">(</span>new_block<span class="token punctuation">,</span> proof<span class="token punctuation">)</span> self<span class="token punctuation">.</span>unconfirmed_transactions <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token keyword">return</span> new_block<span class="token punctuation">.</span>index |
Ok đây là code của phần xửa lý: source code
Tạo giao diện
Phần này chúng ta sẽ tạo giao diện cho node blockchain để tương tác với ứng dụng chúng ta vừa xây dựng. Ở đây sẽ sử dụng một microframework Python phổ biến được gọi là Flask để tạo API REST tương tác và gọi các actions khác nhau trong node blockchain của chúng ta. Nếu bạn đã từng làm việc với bất kỳ framework web nào trước đây, code dưới đây sẽ không khó theo dõi.
1 2 3 4 5 6 7 8 9 | <span class="token keyword">from</span> flask <span class="token keyword">import</span> Flask<span class="token punctuation">,</span> request <span class="token keyword">import</span> requests <span class="token comment"># Khởi tại ứng dụng flask</span> app <span class="token operator">=</span> Flask<span class="token punctuation">(</span>__name__<span class="token punctuation">)</span> <span class="token comment"># Khởi tạo object Blockchain</span> blockchain <span class="token operator">=</span> Blockchain<span class="token punctuation">(</span><span class="token punctuation">)</span> |
Chúng ta cần một route để ứng dụng có thể tạo mới một transaction và ở đây chính là bài post
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/new_transaction'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'POST'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">new_transaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> tx_data <span class="token operator">=</span> request<span class="token punctuation">.</span>get_json<span class="token punctuation">(</span><span class="token punctuation">)</span> required_fields <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"author"</span><span class="token punctuation">,</span> <span class="token string">"content"</span><span class="token punctuation">]</span> <span class="token keyword">for</span> field <span class="token keyword">in</span> required_fields<span class="token punctuation">:</span> <span class="token keyword">if</span> <span class="token operator">not</span> tx_data<span class="token punctuation">.</span>get<span class="token punctuation">(</span>field<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"Invalid transaction data"</span><span class="token punctuation">,</span> <span class="token number">404</span> tx_data<span class="token punctuation">[</span><span class="token string">"timestamp"</span><span class="token punctuation">]</span> <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> blockchain<span class="token punctuation">.</span>add_new_transaction<span class="token punctuation">(</span>tx_data<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"Success"</span><span class="token punctuation">,</span> <span class="token number">201</span> |
Sẽ có một route trả về bản sao của chain. Và route này ứng dụng sẽ sử dụng để để truy vấn tất cả dữ liệu cần hiển thị:
1 2 3 4 5 6 7 8 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/chain'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'GET'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">get_chain</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> chain_data <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token keyword">for</span> block <span class="token keyword">in</span> blockchain<span class="token punctuation">.</span>chain<span class="token punctuation">:</span> chain_data<span class="token punctuation">.</span>append<span class="token punctuation">(</span>block<span class="token punctuation">.</span>__dict__<span class="token punctuation">)</span> <span class="token keyword">return</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"length"</span><span class="token punctuation">:</span> <span class="token builtin">len</span><span class="token punctuation">(</span>chain_data<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"chain"</span><span class="token punctuation">:</span> chain_data<span class="token punctuation">}</span><span class="token punctuation">)</span> |
Còn đây là route để gửi một request yêu cầu mine – xác thực các transaction chưa được xác thực(nếu có). Chúng ta sẽ sử dụng nó để bắt đầu một lệnh mine từ chính ứng dụng của chúng ta:
1 2 3 4 5 6 7 8 9 10 11 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/mine'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'GET'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">mine_unconfirmed_transactions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> result <span class="token operator">=</span> blockchain<span class="token punctuation">.</span>mine<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">not</span> result<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"No transactions to mine"</span> <span class="token keyword">return</span> <span class="token string">"Block #{} is mined."</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/pending_tx'</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">get_pending_tx</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>blockchain<span class="token punctuation">.</span>unconfirmed_transactions<span class="token punctuation">)</span> |
Thiết lập cơ chế đồng thuận và phân tán
Có một vấn đề blockchain mà chúng ta đã triển khai đang chạy trên một máy tính. Mặc dù chúng ta đã liên kết các block bằng giá trị băm và áp dụng POW thì vẫn không thể tin tưởng vào một thực thể duy nhất (trong trường hợp ở đây là một máy duy nhất). Chúng ta cần dữ liệu được phân tán tức cần nhiều node để duy trì blockchain. Vì vậy, để chuyển từ một node đơn sang mạng ngang hàng, trước tiên chúng ta hãy tạo một cơ chế để cho một node mới có thể biết về các peers(đồng nghiệp) khác trong mạng:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <span class="token comment"># Chứa địa chỉ host của các thành viên tham gia khác của mạng</span> peers <span class="token operator">=</span> <span class="token builtin">set</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># route thêm một peer mới vào mạng</span> @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/register_node'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'POST'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">register_new_peers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># Địa chỉ host đến các node ngang hàng </span> node_address <span class="token operator">=</span> request<span class="token punctuation">.</span>get_json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">"node_address"</span><span class="token punctuation">]</span> <span class="token keyword">if</span> <span class="token operator">not</span> node_address<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"Invalid data"</span><span class="token punctuation">,</span> <span class="token number">400</span> <span class="token comment"># Thêm địa chỉ node vào danh sách</span> peers<span class="token punctuation">.</span>add<span class="token punctuation">(</span>node_address<span class="token punctuation">)</span> <span class="token comment"># Trả lại blockchain mới</span> <span class="token keyword">return</span> get_chain<span class="token punctuation">(</span><span class="token punctuation">)</span> @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/register_with'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'POST'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">register_with_existing_node</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Trong nội bộ gọi đến route `register_node` để đăng ký node hiện tại với node từ xa được chỉ định trong request và cập nhật lại mạng blockchain """</span> node_address <span class="token operator">=</span> request<span class="token punctuation">.</span>get_json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">"node_address"</span><span class="token punctuation">]</span> <span class="token keyword">if</span> <span class="token operator">not</span> node_address<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"Invalid data"</span><span class="token punctuation">,</span> <span class="token number">400</span> data <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"node_address"</span><span class="token punctuation">:</span> request<span class="token punctuation">.</span>host_url<span class="token punctuation">}</span> headers <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">"application/json"</span><span class="token punctuation">}</span> <span class="token comment"># Reuqest đăng ký với node từ xa và lấy thông tin</span> response <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>node_address <span class="token operator">+</span> <span class="token string">"/register_node"</span><span class="token punctuation">,</span> data<span class="token operator">=</span>json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">,</span> headers<span class="token operator">=</span>headers<span class="token punctuation">)</span> <span class="token keyword">if</span> response<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">:</span> <span class="token keyword">global</span> blockchain <span class="token keyword">global</span> peers <span class="token comment"># update chain và các peers</span> chain_dump <span class="token operator">=</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">'chain'</span><span class="token punctuation">]</span> blockchain <span class="token operator">=</span> create_chain_from_dump<span class="token punctuation">(</span>chain_dump<span class="token punctuation">)</span> peers<span class="token punctuation">.</span>update<span class="token punctuation">(</span>response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">'peers'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"Registration successful"</span><span class="token punctuation">,</span> <span class="token number">200</span> <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token comment"># Nếu có lỗi xảy ra, API sẽ trả lại response</span> <span class="token keyword">return</span> response<span class="token punctuation">.</span>content<span class="token punctuation">,</span> response<span class="token punctuation">.</span>status_code <span class="token keyword">def</span> <span class="token function">create_chain_from_dump</span><span class="token punctuation">(</span>chain_dump<span class="token punctuation">)</span><span class="token punctuation">:</span> blockchain <span class="token operator">=</span> Blockchain<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> idx<span class="token punctuation">,</span> block_data <span class="token keyword">in</span> <span class="token builtin">enumerate</span><span class="token punctuation">(</span>chain_dump<span class="token punctuation">)</span><span class="token punctuation">:</span> block <span class="token operator">=</span> Block<span class="token punctuation">(</span>block_data<span class="token punctuation">[</span><span class="token string">"index"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> block_data<span class="token punctuation">[</span><span class="token string">"transactions"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> block_data<span class="token punctuation">[</span><span class="token string">"timestamp"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> block_data<span class="token punctuation">[</span><span class="token string">"previous_hash"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> proof <span class="token operator">=</span> block_data<span class="token punctuation">[</span><span class="token string">'hash'</span><span class="token punctuation">]</span> <span class="token keyword">if</span> idx <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">:</span> added <span class="token operator">=</span> blockchain<span class="token punctuation">.</span>add_block<span class="token punctuation">(</span>block<span class="token punctuation">,</span> proof<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">not</span> added<span class="token punctuation">:</span> <span class="token keyword">raise</span> Exception<span class="token punctuation">(</span><span class="token string">"The chain dump is tampered!!"</span><span class="token punctuation">)</span> <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token comment"># block này là một block genesis nên không cần verification</span> blockchain<span class="token punctuation">.</span>chain<span class="token punctuation">.</span>append<span class="token punctuation">(</span>block<span class="token punctuation">)</span> <span class="token keyword">return</span> blockchain |
Một node mới tham gia vào mạng có thể gọi hàm register_with_existing_node
để đăng ký với các node hiện có trong mạng. Điều này sẽ giúp ích những điều sau:
- Request node từ xa để thêm một peer mới vào danh sách các peer đã có.
- Dễ dàng Khởi tạo blockchain của node mới bằng cách lấy của node từ xa.
- Đồng bộ hóa lại blockchain với mạng nếu node đó không kết nối với mạng nữa.
Tuy nhiên có một vấn đề với nhiều node do thao tác có chủ ý hoặc lý do vô ý (như độ trễ mạng), bản sao của chain một vài nút có thể khác nhau. Trong trường hợp đó các node cần phải đồng ý với một số phiên bản của chain để duy trì tính toàn vẹn của toàn bộ hệ thống. Nói cách khác, chúng ta cần đạt được sự đồng thuận.
Một thuật toán đồng thuận đơn giản có thể đồng ý với chain hợp lệ dài nhất khi các chain của các node tham gia trong mạng xuất xuất hiện rẽ nhánh. Lý do đằng sau phương pháp này là chain dài nhất chứng tỏ số lượng công việc được thực hiện nhiều nhất (hãy nhớ PoW rất khó tính):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <span class="token keyword">class</span> <span class="token class-name">Blockchain</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">def</span> <span class="token function">check_chain_validity</span><span class="token punctuation">(</span>cls<span class="token punctuation">,</span> chain<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Là một hàm helper để check nếu toàn bộ blockchain là đúng """</span> result <span class="token operator">=</span> <span class="token boolean">True</span> previous_hash <span class="token operator">=</span> <span class="token string">"0"</span> <span class="token comment"># Lặp lại qua tất cả các block</span> <span class="token keyword">for</span> block <span class="token keyword">in</span> chain<span class="token punctuation">:</span> block_hash <span class="token operator">=</span> block<span class="token punctuation">.</span><span class="token builtin">hash</span> <span class="token comment"># xóa trường đã băm để tính toán lại giá trị băm</span> <span class="token comment"># sử dụng `compute_hash` method.</span> <span class="token builtin">delattr</span><span class="token punctuation">(</span>block<span class="token punctuation">,</span> <span class="token string">"hash"</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">not</span> cls<span class="token punctuation">.</span>is_valid_proof<span class="token punctuation">(</span>block<span class="token punctuation">,</span> block<span class="token punctuation">.</span><span class="token builtin">hash</span><span class="token punctuation">)</span> <span class="token operator">or</span> previous_hash <span class="token operator">!=</span> block<span class="token punctuation">.</span>previous_hash<span class="token punctuation">:</span> result <span class="token operator">=</span> <span class="token boolean">False</span> <span class="token keyword">break</span> block<span class="token punctuation">.</span><span class="token builtin">hash</span><span class="token punctuation">,</span> previous_hash <span class="token operator">=</span> block_hash<span class="token punctuation">,</span> block_hash <span class="token keyword">return</span> result <span class="token keyword">def</span> <span class="token function">consensus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Thuật toán đồng thuận đơn giản ở đấy là nếu một chuỗi hợp lệ dài hơn được tìm thấy, chain của chúng ta sẽ thay thế bằng nó. """</span> <span class="token keyword">global</span> blockchain longest_chain <span class="token operator">=</span> <span class="token boolean">None</span> current_len <span class="token operator">=</span> <span class="token builtin">len</span><span class="token punctuation">(</span>blockchain<span class="token punctuation">.</span>chain<span class="token punctuation">)</span> <span class="token keyword">for</span> node <span class="token keyword">in</span> peers<span class="token punctuation">:</span> response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'{}/chain'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> length <span class="token operator">=</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">'length'</span><span class="token punctuation">]</span> chain <span class="token operator">=</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">'chain'</span><span class="token punctuation">]</span> <span class="token keyword">if</span> length <span class="token operator">></span> current_len <span class="token operator">and</span> blockchain<span class="token punctuation">.</span>check_chain_validity<span class="token punctuation">(</span>chain<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># Chain hợp lệ dài hơn được tìm thấy!</span> current_len <span class="token operator">=</span> length longest_chain <span class="token operator">=</span> chain <span class="token keyword">if</span> longest_chain<span class="token punctuation">:</span> blockchain <span class="token operator">=</span> longest_chain <span class="token keyword">return</span> <span class="token boolean">True</span> <span class="token keyword">return</span> <span class="token boolean">False</span> |
Tiếp đến chúng ta cần phát triển một cách để bất kỳ node nào thông báo cho mạng rằng nó đã mining ra một block để mọi người có thể cập nhật blockchain của họ và chuyển sang mining các block khác. Các node khác có thể chỉ cần xác minh PoW và thêm block mới được khai thác vào chain tương ứng của mình (hãy nhớ rằng việc xác minh là dễ dàng khi biết được nonce):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <span class="token comment"># Route này để thêm khối người khác vừa mined.</span> <span class="token comment"># Đầu tiên cần xac minh block và sau đó là thêm vào chain</span> @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/add_block'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'POST'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">verify_and_add_block</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> block_data <span class="token operator">=</span> request<span class="token punctuation">.</span>get_json<span class="token punctuation">(</span><span class="token punctuation">)</span> block <span class="token operator">=</span> Block<span class="token punctuation">(</span>block_data<span class="token punctuation">[</span><span class="token string">"index"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> block_data<span class="token punctuation">[</span><span class="token string">"transactions"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> block_data<span class="token punctuation">[</span><span class="token string">"timestamp"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> block_data<span class="token punctuation">[</span><span class="token string">"previous_hash"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> proof <span class="token operator">=</span> block_data<span class="token punctuation">[</span><span class="token string">'hash'</span><span class="token punctuation">]</span> added <span class="token operator">=</span> blockchain<span class="token punctuation">.</span>add_block<span class="token punctuation">(</span>block<span class="token punctuation">,</span> proof<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">not</span> added<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"The block was discarded by the node"</span><span class="token punctuation">,</span> <span class="token number">400</span> <span class="token keyword">return</span> <span class="token string">"Block added to the chain"</span><span class="token punctuation">,</span> <span class="token number">201</span> <span class="token keyword">def</span> <span class="token function">announce_new_block</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Một hàm thông báo cho mạng sau khi một block đã được mined. Các block khác chỉ có thể xác minh PoW và thêm nó vào chuỗi tương ứng. """</span> <span class="token keyword">for</span> peer <span class="token keyword">in</span> peers<span class="token punctuation">:</span> url <span class="token operator">=</span> <span class="token string">"{}add_block"</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>peer<span class="token punctuation">)</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>url<span class="token punctuation">,</span> data<span class="token operator">=</span>json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>block<span class="token punctuation">.</span>__dict__<span class="token punctuation">,</span> sort_keys<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
Hàm announce_new_block
nên được gọi sau mỗi block được mined bởi các node để các peer khác có thể thêm nó vào chain của họ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/mine'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'GET'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">mine_unconfirmed_transactions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> result <span class="token operator">=</span> blockchain<span class="token punctuation">.</span>mine<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">not</span> result<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"No transactions to mine"</span> <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token comment"># Đảm bảo chúng ta có chain dài nhất trước khi thông báo với mạng</span> chain_length <span class="token operator">=</span> <span class="token builtin">len</span><span class="token punctuation">(</span>blockchain<span class="token punctuation">.</span>chain<span class="token punctuation">)</span> consensus<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> chain_length <span class="token operator">==</span> <span class="token builtin">len</span><span class="token punctuation">(</span>blockchain<span class="token punctuation">.</span>chain<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># thông báo block được mined gần đây vào mạng</span> announce_new_block<span class="token punctuation">(</span>blockchain<span class="token punctuation">.</span>last_block<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"Block #{} is mined."</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>blockchain<span class="token punctuation">.</span>last_block<span class="token punctuation">.</span>index |
Xây dựng ứng dụng web
Vậy là máy chủ blockchain đã được thiết lập. Bạn có thể thấy source code ở đây.
Bây giờ là lúc bắt đầu phát triển giao diện của ứng dụng web. Chúng ta đã sử dụng template Jinja2 để hiển thị các view và một số CSS để làm cho mọi thứ trở nên đẹp mắt.
Ứng dụng cần kết nối với một node trong mạng blockchain để tìm nạp dữ liệu và cũng để gửi dữ liệu mới.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">import</span> datetime <span class="token keyword">import</span> json <span class="token keyword">import</span> requests <span class="token keyword">from</span> flask <span class="token keyword">import</span> render_template<span class="token punctuation">,</span> redirect<span class="token punctuation">,</span> request <span class="token keyword">from</span> app <span class="token keyword">import</span> app <span class="token comment"># Node mà chúng ta sẽ kết nối để fetch dữ liệu về</span> CONNECTED_NODE_ADDRESS <span class="token operator">=</span> <span class="token string">"http://127.0.0.1:8000"</span> posts <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> |
Hàm fetch_posts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token keyword">def</span> <span class="token function">fetch_posts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Hàm get chain từ node, phân tíc dữ liệu và lưu trữ cục bộ """</span> get_chain_address <span class="token operator">=</span> <span class="token string">"{}/chain"</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>CONNECTED_NODE_ADDRESS<span class="token punctuation">)</span> response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>get_chain_address<span class="token punctuation">)</span> <span class="token keyword">if</span> response<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">:</span> content <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> chain <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>response<span class="token punctuation">.</span>content<span class="token punctuation">)</span> <span class="token keyword">for</span> block <span class="token keyword">in</span> chain<span class="token punctuation">[</span><span class="token string">"chain"</span><span class="token punctuation">]</span><span class="token punctuation">:</span> <span class="token keyword">for</span> tx <span class="token keyword">in</span> block<span class="token punctuation">[</span><span class="token string">"transactions"</span><span class="token punctuation">]</span><span class="token punctuation">:</span> tx<span class="token punctuation">[</span><span class="token string">"index"</span><span class="token punctuation">]</span> <span class="token operator">=</span> block<span class="token punctuation">[</span><span class="token string">"index"</span><span class="token punctuation">]</span> tx<span class="token punctuation">[</span><span class="token string">"hash"</span><span class="token punctuation">]</span> <span class="token operator">=</span> block<span class="token punctuation">[</span><span class="token string">"previous_hash"</span><span class="token punctuation">]</span> content<span class="token punctuation">.</span>append<span class="token punctuation">(</span>tx<span class="token punctuation">)</span> <span class="token keyword">global</span> posts posts <span class="token operator">=</span> <span class="token builtin">sorted</span><span class="token punctuation">(</span>content<span class="token punctuation">,</span> key<span class="token operator">=</span><span class="token keyword">lambda</span> k<span class="token punctuation">:</span> k<span class="token punctuation">[</span><span class="token string">'timestamp'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> reverse<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span> |
Ứng dụng có một biểu mẫu HTML để nhập đầu vào của người dùng và sau đó thực hiện request POST đến node được kết nối để thêm transaction vào nhóm transactions chưa được xác nhận. Transaction sau đó được mining bởi mạng và cuối cùng được tìm nạp sau khi reload trang:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/submit'</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">'POST'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">submit_textarea</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Route để tạo transaction mới """</span> post_content <span class="token operator">=</span> request<span class="token punctuation">.</span>form<span class="token punctuation">[</span><span class="token string">"content"</span><span class="token punctuation">]</span> author <span class="token operator">=</span> request<span class="token punctuation">.</span>form<span class="token punctuation">[</span><span class="token string">"author"</span><span class="token punctuation">]</span> post_object <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'author'</span><span class="token punctuation">:</span> author<span class="token punctuation">,</span> <span class="token string">'content'</span><span class="token punctuation">:</span> post_content<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token comment"># Submit transaction</span> new_tx_address <span class="token operator">=</span> <span class="token string">"{}/new_transaction"</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>CONNECTED_NODE_ADDRESS<span class="token punctuation">)</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>new_tx_address<span class="token punctuation">,</span> json<span class="token operator">=</span>post_object<span class="token punctuation">,</span> headers<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">'Content-type'</span><span class="token punctuation">:</span> <span class="token string">'application/json'</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment"># Trả về homepage</span> <span class="token keyword">return</span> redirect<span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span> |
Chạy ứng dụng
Đến đây chúng ta đã hoàn thành! source code
Cìa đặt:
1 2 3 | $ <span class="token function">cd</span> python_blockchain_app $ pip <span class="token function">install</span> -r requirements.txt |
Chạy một node blockchain trên cổng 8000
1 2 3 | $ <span class="token function">export</span> FLASK_APP<span class="token operator">=</span>node_server.py $ flask run --port 8000 |
chạy ứng dụng web:
1 2 | $ python run_app.py |
Ứng dụng sẽ chạy tại http://localhost:5000 .
Chạy nhiều node
Ta sẽ chạy nhiều node bằng cách chạy trên các cổng khac nhau. Vả sử dụng register_with
để đăng ký thành một mạng ngang hàng:
1 2 3 4 5 6 | <span class="token comment"># already running</span> $ flask run --port 8000 <span class="token operator">&</span> <span class="token comment"># spinning up new nodes</span> $ flask run --port 8001 <span class="token operator">&</span> $ flask run --port 8002 <span class="token operator">&</span> |
1 2 3 4 5 6 7 8 9 10 | $ <span class="token function">curl</span> -X POST http://127.0.0.1:8001/register_with -H <span class="token string">'Content-Type: application/json'</span> -d <span class="token string">'{"node_address": "http://127.0.0.1:8000"}'</span> $ <span class="token function">curl</span> -X POST http://127.0.0.1:8002/register_with -H <span class="token string">'Content-Type: application/json'</span> -d <span class="token string">'{"node_address": "http://127.0.0.1:8000"}'</span> |
Bạn có thể chạy ứng dụng ( python run_app.py) và tạo transaction(đăng bài qua giao diện web) và khi bạn mined các transaction, tất cả các node trong mạng sẽ cập nhật chain. Và các node cũng có thể được kiểm tra bằng cách gọi bằng cURL hoặc Postman.
1 2 3 | $ <span class="token function">curl</span> -X GET http://localhost:8001/chain $ <span class="token function">curl</span> -X GET http://localhost:8002/chain |
Authenticate transactions
Bạn có thể đã nhận thấy một lỗ hổng trong ứng dụng là: Bất kỳ ai cũng có thể thay đổi name và post bất kỳ content nào. Ngoài ra post dễ bị giả mạo trong khi gửi transaction lên mạng blockchain. Một cách để giải quyết điều này là tạo ra tài khoản người dùng, bằng mật mã khóa công khai . Mỗi người dùng mới cần một public key và private key để có thể đăng post trong ứng dụng:
- Mỗi transaction mới được gửi (đăng post) được ký bằng private key của người dùng. Chữ ký này được thêm vào dữ liệu transaction cùng với thông tin người dùng.
- Trong giai đoạn xác minh khi mining các giao dịch, chúng ta có thể xác minh xem chủ sở hữu được yêu cầu của bài đăng có giống với chủ sở hữu được chỉ định trong dữ liệu transaction hay không và thông báo không được sửa đổi nếu không đúng. Điều này có thể được thực hiện bằng cách sử dụng chữ ký và public key của chủ sở hữu submit bài post.
kết luận
Hướng dẫn này bao gồm các nguyên tắc cơ bản của một Public blockchain. Nếu bạn đã theo dõi đến đấy thì giờ bạn đã có thể triển khai một blockchain từ đầu và xây dựng một ứng dụng đơn giản, cho phép người dùng chia sẻ thông tin trên blockchain. Mong rằng bài viết sẽ đem lại cho các bạn những kiến thức bổ ích rất vui và hẹn gặp lại ở những bài viết tiếp theo.