摘要:首先,區塊鏈是一系列稱作區塊的結構順序鏈接而成的不可改變的記錄。表示一個區塊鏈我們創建一個類,其構造器會創建兩個列表,一個存儲區塊鏈,另一個存儲交易。我們幾乎完成了表示一個區塊鏈的工作。
目前大多數對于區塊鏈的文章都是停留在概念性的描述,大肆宣揚其顛覆性,本文則反其道行之,以一個程序員的視角,通過300行代碼,快速實現了一個區塊鏈原型。雖然沒有覆蓋區塊鏈的全部內容(如Merkle樹),但對于理解區塊鏈的核心技術仍大有裨益。
----譯者注
能夠點進這篇文章,說明你也像我一樣對加密貨幣的興起十分激動,并想了解加密貨幣的支撐技術---區塊鏈是如何工作的。
但理解區塊鏈并不那么輕松,至少對我來說如此。我看了很多相關的視頻和教程,卻沮喪地發現實例真是太少了。
我喜歡通過實踐學習。這種方式使我在代碼層面思考問題,并發現關鍵所在。如果你和我一樣,那么在本文結尾你將構建一個功能完備的區塊鏈并對其工作機制有深刻的理解。
首先,區塊鏈是一系列稱作區塊(Block)的結構順序鏈接而成的不可改變的記錄。塊中可以包含交易記錄、文件或者其他任何你想存儲的數據。需要注意的是塊與塊之間通過hash值鏈接。如果你不清楚hash是什么,請參考What Are Hash Functions。
本文適合哪些人看?你應該懂得一些基本的Python知識,同時也應該對HTTP請求有所理解,因為我們的區塊鏈是運行在HTTP協議之上的。
我需要準備什么?請確保Python3.6及以上版本和pip工具已經安裝。還需要安裝Flask和requests庫。
pip install Flask==0.12.2 requests==2.18.4
對了,你還需要一個HTTP客戶端,比如Postman或者cURL,當然,其他的也可以。
最終的代碼哪里可以獲取?點擊這里
第一步:構建區塊鏈創建一個新的Python文件,名為blockchain.py,我們所有的邏輯都在一個文件完成。
表示一個區塊鏈我們創建一個BlockChain類,其構造器會創建兩個列表,一個存儲區塊鏈,另一個存儲交易。下面是我們這個類的第一個版本:
class Blockchain(object): def __init__(self): self.chain = [] self.current_transactions = [] def new_block(self): # Creates a new Block and adds it to the chain pass def new_transaction(self): # Adds a new transaction to the list of transactions pass @staticmethod def hash(block): # Hashes a Block pass @property def last_block(self): # Returns the last Block in the chain pass
我們的BlockChain類負責管理整個區塊鏈,它會存儲交易并為新增區塊等操作提供輔助方法。下面,我們來實現這些方法。
區塊是什么每個區塊都有一個索引(index),一個時間戳(timestamp),一系列交易,一個工作量證明(稍后詳述)和前置區塊的哈希值。下面是單個區塊的一個簡單實例:
block = { "index": 1, "timestamp": 1506057125.900785, "transactions": [ { "sender": "8527147fe1f5426f9dd545de4b27ee00", "recipient": "a77f5cdfa2934df3954a5c7c7da5df1f", "amount": 5, } ], "proof": 324984774000, "previous_hash": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" }
顯而易見,所有的區塊會構成一條鏈---因為每個區塊都保存了前一區塊的hash值。這就是區塊鏈不可篡改的重要原因:如果攻擊者損壞了某一區塊,那么后面所有的區塊都會作廢。
如果你不明白上面的話,請花一些時間理解,因為這是區塊鏈的核心思想。
我們需要一個方法來向區塊中添加交易記錄,這里命名為new_transaction(),代碼寫的十分直白易懂:
class Blockchain(object): ... def new_transaction(self, sender, recipient, amount): """ Creates a new transaction to go into the next mined Block :param sender:Address of the Sender :param recipient: Address of the Recipient :param amount: Amount :return: The index of the Block that will hold this transaction """ self.current_transactions.append({ "sender": sender, "recipient": recipient, "amount": amount, }) return self.last_block["index"] + 1
在new_transaction()方法將交易添加進區塊之后,區塊索引將會被返回,該區塊將可能被開采為鏈的最新區塊,這在之后用戶提交交易的時候十分有用。
創建新區塊當BlockChain類初始化的時候,我們需要產生一個創世區塊(genesis block,即沒有前置區塊的區塊)作為區塊鏈的第一個區塊。我們還需要添加一個proof字段在創世區塊中作為挖礦的結果(或者說本次工作量的證明),我們將在后文繼續討論挖礦。
除了產生創世區塊,我們還需要完成一些其他輔助方法(new_block(),new_transaction()和hash()):
import hashlib import json from time import time class Blockchain(object): def __init__(self): self.current_transactions = [] self.chain = [] # Create the genesis block self.new_block(previous_hash=1, proof=100) def new_block(self, proof, previous_hash=None): """ Create a new Block in the Blockchain :param proof:The proof given by the Proof of Work algorithm :param previous_hash: (Optional) Hash of previous Block :return: New Block """ block = { "index": len(self.chain) + 1, "timestamp": time(), "transactions": self.current_transactions, "proof": proof, "previous_hash": previous_hash or self.hash(self.chain[-1]), } # Reset the current list of transactions self.current_transactions = [] self.chain.append(block) return block def new_transaction(self, sender, recipient, amount): """ Creates a new transaction to go into the next mined Block :param sender: Address of the Sender :param recipient: Address of the Recipient :param amount: Amount :return: The index of the Block that will hold this transaction """ self.current_transactions.append({ "sender": sender, "recipient": recipient, "amount": amount, }) return self.last_block["index"] + 1 @property def last_block(self): return self.chain[-1] @staticmethod def hash(block): """ Creates a SHA-256 hash of a Block :param block: Block :return: """ # We must make sure that the Dictionary is Ordered, or we"ll have inconsistent hashes block_string = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest()
上面的代碼十分直白,我還添加了一些注釋幫助理解。我們幾乎完成了表示一個區塊鏈的工作。但此時,你應該思考下一個區塊是如何產生或者說被開采出來的。
理解工作量證明機制(Proof of Work)工作量證明(PoW)算法是用來產生或開采區塊的一種機制,PoW的目標是找到一個符合要求的數字,從算力的角度來說這個數字對任何人來說都很難找到卻十分容易驗證(是否符合要求)。這就是PoW算法的核心思想。
我們舉一個非常簡單的例子來幫助理解:
假定我們需要找到一個整數y,使得他和整數x的乘積的哈希值以0結尾,即hash(x*y) = ac23dc...0。如果x=5那么用Python實現如下:
from hashlib import sha256 x = 5 y = 0 # We don"t know what y should be yet... while sha256(f"{x*y}".encode()).hexdigest()[-1] != "0": y += 1 print(f"The solution is y = {y}")
第一個符合要求的數是y=21,因為:
hash(5 * 21) = 1253e9373e...5e3600155e860
在比特幣世界中,PoW算法被稱為哈希現金(Hashcash),它和我們上面的例子沒有本質區別。在這算法中,礦工們開始了解決問題的競賽,優勝者可以產生一個新的區塊。通常來說,難度取決于限制字符的數量。礦工將會因為找到一個合法的解答收到一些比特幣作為獎勵,整個比特幣網絡能夠很容易驗證礦工挖掘的區塊是否合法有效。
實現基本的PoW算法下面來為我們的區塊鏈實現一個類似的算法,我們的規則將會和上面的例子十分接近:找到一個數p,使得它與前置區塊的哈希值由4個0開頭。
import hashlib import json from time import time from uuid import uuid4 class Blockchain(object): ... def proof_of_work(self, last_proof): """ Simple Proof of Work Algorithm: - Find a number p" such that hash(pp") contains leading 4 zeroes, where p is the previous p" - p is the previous proof, and p" is the new proof :param last_proof::return: """ proof = 0 while self.valid_proof(last_proof, proof) is False: proof += 1 return proof @staticmethod def valid_proof(last_proof, proof): """ Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes? :param last_proof: Previous Proof :param proof: Current Proof :return: True if correct, False if not. """ guess = f"{last_proof}{proof}".encode() guess_hash = hashlib.sha256(guess).hexdigest() return guess_hash[:4] == "0000"
我們可以通過設置前導0的個數調整算法的難度,但4個足夠了,你會發現增加一個0會使找到一個答案的時間大大增加。我們的類幾乎完成了,現在我們將通過HTTP請求與區塊鏈進行交互。
第二步:將區塊鏈作為API我們將使用Flask,它是一個輕量級的框架,可以很容易將一個網絡節點映射為Python函數,這讓我們可以通過HTTP請求與區塊鏈交互。我們將創建以下方法:
/transactions/new建立一個新的區塊。
/mine告訴服務器開采一個新的區塊
/chain返回整個區塊鏈
設置Flask我們的每個服務器將對應區塊鏈網絡中的一個單一節點。下面是樣板代碼:
import hashlib import json from textwrap import dedent from time import time from uuid import uuid4 from flask import Flask class Blockchain(object): ... # Instantiate our Node app = Flask(__name__) # Generate a globally unique address for this node node_identifier = str(uuid4()).replace("-", "") # Instantiate the Blockchain blockchain = Blockchain() @app.route("/mine", methods=["GET"]) def mine(): return "We"ll mine a new Block" @app.route("/transactions/new", methods=["POST"]) def new_transaction(): return "We"ll add a new transaction" @app.route("/chain", methods=["GET"]) def full_chain(): response = { "chain": blockchain.chain, "length": len(blockchain.chain), } return jsonify(response), 200 if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)
下面是簡單的解釋:
15行:實例化節點,關于Flask點擊Quick Start
18行:為節點創建一個隨機名字
21行:實例化BlockChain類
24-26行:創建/mine節點,這是一個GET請求。
28-30行:創建/transactions/new節點,因為需要發送數據,所以是POST請求。
32-38行:創建/chain節點,返回整個區塊鏈
40-41行:運行服務器5000端口
交易節點用戶會想服務器發送交易請求,格式類似下面這樣:
{ "sender": "my address", "recipient": "someone else"s address", "amount": 5 }
因為我們已經實現了將交易加入區塊的方法,所以剩余部分十分容易:
import hashlib import json from textwrap import dedent from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @app.route("/transactions/new", methods=["POST"]) def new_transaction(): values = request.get_json() # Check that the required fields are in the POST"ed data required = ["sender", "recipient", "amount"] if not all(k in values for k in required): return "Missing values", 400 # Create a new Transaction index = blockchain.new_transaction(values["sender"], values["recipient"], values["amount"]) response = {"message": f"Transaction will be added to Block {index}"} return jsonify(response), 201挖礦節點
挖礦節點很簡單但也很神奇,他需要完成以下任務:
計算執行PoW算法
通過添加一筆交易獎勵礦工1比特幣
產生新的區塊并添加入鏈
import hashlib import json from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @app.route("/mine", methods=["GET"]) def mine(): # We run the proof of work algorithm to get the next proof... last_block = blockchain.last_block last_proof = last_block["proof"] proof = blockchain.proof_of_work(last_proof) # We must receive a reward for finding the proof. # The sender is "0" to signify that this node has mined a new coin. blockchain.new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Forge the new Block by adding it to the chain previous_hash = blockchain.hash(last_block) block = blockchain.new_block(proof, previous_hash) response = { "message": "New Block Forged", "index": block["index"], "transactions": block["transactions"], "proof": block["proof"], "previous_hash": block["previous_hash"], } return jsonify(response), 200
需要注意的是接受被開采區塊的地址就是我們的節點,并且我們的大部分工作就是和BlockChain類的方法交互。我們已經完成了這部分,現在可以開始和區塊鏈交互了。
第三步:和區塊鏈交互你可以使用簡單古老的cURL或者Postman來和這些網絡中的API交互,首先啟動服務器:
$ python blockchain.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
讓我們發送一個GET請求來開采一個區塊:
http://localhost:5000/mine
再向http://localhost:5000/transactions/new發送一個POST請求,參數是JSON格式的交易數據:
如果不想用Postman,cURL也可以做到:
$ curl -X POST -H "Content-Type: application/json" -d "{ "sender": "d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address", "amount": 5 }" "http://localhost:5000/transactions/new"
我重啟了服務器并開采了兩個區塊,所以現在總共有3個了,通過http://localhost:5000/chain節點可以獲取整個區塊:
{ "chain": [ { "index": 1, "previous_hash": 1, "proof": 100, "timestamp": 1506280650.770839, "transactions": [] }, { "index": 2, "previous_hash": "c099bc...bfb7", "proof": 35293, "timestamp": 1506280664.717925, "transactions": [ { "amount": 1, "recipient": "8bbcb347e0634905b0cac7955bae152b", "sender": "0" } ] }, { "index": 3, "previous_hash": "eff91a...10f2", "proof": 35089, "timestamp": 1506280666.1086972, "transactions": [ { "amount": 1, "recipient": "8bbcb347e0634905b0cac7955bae152b", "sender": "0" } ] } ], "length": 3 }第四步:共識機制
我們已經擁有了一個能接收交易的初級區塊鏈,并且能夠開采出新的區塊。但整個區塊鏈最核心的是去中心化,如果去中心了,我們又如何保證所有節點對應的是統一區塊鏈呢?這就是共識問題,如果我們希望網絡中有不止一個節點,就必須實現共識算法。
注冊新節點在實現共識算法之前,我們需要讓節點知道有其他節點加入了網絡。網絡中的每一個節點應該存留其他全部節點的注冊表,因此我們需要一些其他的服務器節點:
/nodes/register用來從URL中接收一系列節點
/nodes/resolve實現共識算法,并解決沖突以保證節點擁有正確的鏈
我們需要修改BlockChain類的構造器并提供一個方法來注冊節點:
... from urllib.parse import urlparse ... class Blockchain(object): def __init__(self): ... self.nodes = set() ... def register_node(self, address): """ Add a new node to the list of nodes :param address:Address of node. Eg. "http://192.168.0.5:5000" :return: None """ parsed_url = urlparse(address) self.nodes.add(parsed_url.netloc)
現在可以使用set()來存儲節點列表。這保證了節點的添加是冪等的,即一個節點無論添加多少次只會出現一次。
實現共識算法當一個節點的區塊鏈和另一節點的區塊鏈不同時,沖突就發生了。為了解決這個問題,我們需要制定規則:最長有效鏈最有權威性,即網絡中最長的那條鏈是真正的區塊鏈。使用這個算法,我們能夠達成大多數節點的一致。
... import requests class Blockchain(object) ... def valid_chain(self, chain): """ Determine if a given blockchain is valid :param chain:A blockchain :return:
True if valid, False if not """ last_block = chain[0] current_index = 1 while current_index < len(chain): block = chain[current_index] print(f"{last_block}") print(f"{block}") print(" ----------- ") # Check that the hash of the block is correct if block["previous_hash"] != self.hash(last_block): return False # Check that the Proof of Work is correct if not self.valid_proof(last_block["proof"], block["proof"]): return False last_block = block current_index += 1 return True def resolve_conflicts(self): """ This is our Consensus Algorithm, it resolves conflicts by replacing our chain with the longest one in the network. :return: True if our chain was replaced, False if not """ neighbours = self.nodes new_chain = None # We"re only looking for chains longer than ours max_length = len(self.chain) # Grab and verify the chains from all the nodes in our network for node in neighbours: response = requests.get(f"http://{node}/chain") if response.status_code == 200: length = response.json()["length"] chain = response.json()["chain"] # Check if the length is longer and the chain is valid if length > max_length and self.valid_chain(chain): max_length = length new_chain = chain # Replace our chain if we discovered a new, valid chain longer than ours if new_chain: self.chain = new_chain return True return False
valid_chain()通過遍歷每一區塊并檢查proof和hash的正確與否判斷鏈的有效性。resolve_conflicts()遍歷所有節點,并通過上面的方法驗證其有效性。如果一個有效鏈的長度大于當前鏈,那么當前鏈將會被替換。現在添加兩個API端口,一個用來添加節點,一個用來解決沖突:
@app.route("/nodes/register", methods=["POST"]) def register_nodes(): values = request.get_json() nodes = values.get("nodes") if nodes is None: return "Error: Please supply a valid list of nodes", 400 for node in nodes: blockchain.register_node(node) response = { "message": "New nodes have been added", "total_nodes": list(blockchain.nodes), } return jsonify(response), 201 @app.route("/nodes/resolve", methods=["GET"]) def consensus(): replaced = blockchain.resolve_conflicts() if replaced: response = { "message": "Our chain was replaced", "new_chain": blockchain.chain } else: response = { "message": "Our chain is authoritative", "chain": blockchain.chain } return jsonify(response), 200
現在你可以用不同的計算機來構建網絡中的這些節點,當然也可以用同一機器的不同端口。例如,將5001端口也注冊進區塊鏈網絡:
現在,如果我在第二個節點開采一個新的區塊,當我在節點1調用GET /nodes/resolve的時候,共識算法會保證鏈被更新到現有網絡中的最長鏈:
現在你可以找一些朋友來和你一起測試這個區塊鏈了。
后記我希望這篇文章能夠激發你的靈感,畢竟我對加密貨幣十分狂熱,我相信他會改變我們對金融、政府和記錄存儲的思考方式。
Update:我計劃寫這個話題的第二部分,我將進一步拓展這個區塊鏈并支持交易驗證( Transaction Validation Mechanism),同時也將討論如何將你的區塊鏈產業化。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44700.html
摘要:我同時也建立了一個基于瀏覽器的版本安裝命令行工具在此之前請先安裝然后在你的命令行中運行以下指令你應該會看到和一個提示。 原文:How does blockchain really work? I built an app to show you.作者:Sean Han譯者:JeLewine 根據維基百科,區塊鏈是: 一個用于維護不斷增長的記錄列表的分布式數據庫,我們稱之為區塊鏈。 這聽...
摘要:我同時也建立了一個基于瀏覽器的版本安裝命令行工具在此之前請先安裝然后在你的命令行中運行以下指令你應該會看到和一個提示。 原文:How does blockchain really work? I built an app to show you.作者:Sean Han譯者:JeLewine 根據維基百科,區塊鏈是: 一個用于維護不斷增長的記錄列表的分布式數據庫,我們稱之為區塊鏈。 這聽...
摘要:作者原文第一部分應用混沌工程理論到區塊鏈框架。你可以抗議混沌環境在像與這種權限不足的公共區塊鏈網絡上是否存在。在之后這些被稱之為混沌工程。混沌原則開始進入正式規范。名字是混沌工程通過實驗建立對系統行為的信心。 作者 Vipin Bharathan原文:https://medium.com/@vipinsun/... 第一部分. 應用混沌工程理論到區塊鏈框架。 混沌與工程兩個字是沒有什么...
閱讀 1841·2021-08-19 11:12
閱讀 1418·2021-07-25 21:37
閱讀 980·2019-08-30 14:07
閱讀 1260·2019-08-30 13:12
閱讀 645·2019-08-30 11:00
閱讀 3523·2019-08-29 16:28
閱讀 982·2019-08-29 15:33
閱讀 2960·2019-08-26 13:40