相信你和我一樣對數(shù)字貨幣的崛起感到新奇,并且想知道其背后的技術(shù)——區(qū)塊鏈?zhǔn)窃鯓訉?shí)現(xiàn)的。
但是理解區(qū)塊鏈并非易事,至少對于我來說是如此。晦澀難懂的視頻、漏洞百出的教程以及示例的匱乏令我倍受挫折。
我喜歡在實(shí)踐中學(xué)習(xí),通過寫代碼來學(xué)習(xí)技術(shù)會掌握得更牢固。如果你也這樣做,那么讀完本文,你將獲得一個(gè)可用的區(qū)塊鏈以及對區(qū)塊鏈的深刻理解。
開始之前...
首先你需要知道區(qū)塊鏈?zhǔn)怯杀环Q為區(qū)塊的記錄構(gòu)成的不可變的、有序的鏈?zhǔn)浇Y(jié)構(gòu),這些記錄可以是交易、文件或任何你想要的數(shù)據(jù),最重要的是它們是通過 Hash 連接起來的。
如果你不了解 Hash,這里有個(gè)例子
其次,你需要安裝 Python3.6+,F(xiàn)lask,Request
pip installFlask==0.12.2requests==2.18.4
同時(shí)你還需要一個(gè) HTTP 客戶端,比如 Postman,cURL 或任何其它客戶端。
最終的源代碼在這里:
第一步: 打造一個(gè) Blockchain
新建一個(gè)文件 blockchain.py,本文所有的代碼都寫在這一個(gè)文件中。首先創(chuàng)建一個(gè) Blockchain 類,在構(gòu)造函數(shù)中我們創(chuàng)建了兩個(gè)列表,一個(gè)用于儲存區(qū)塊鏈,一個(gè)用于儲存交易。
classBlockchain(object):
def__init__(self):
self.chain=[]
self.current_transactions=[]
defnew_block(self):
# Creates a new Block and adds it to the chain
pass
defnew_transaction(self):
# Adds a new transaction to the list of transactions
pass
@staticmethod
defhash(block):
# Hashes a Block
pass
@property
deflast_block(self):
# Returns the last Block in the chain
pass
一個(gè)區(qū)塊有五個(gè)基本屬性:index,timestamp(in Unix time),transaction 列表,工作量證明(稍后解釋)以及前一個(gè)區(qū)塊的 Hash 值。
block={
'index':1,
'timestamp':1506057125.900785,
'transactions':[
{
'sender':"8527147fe1f5426f9dd545de4b27ee00",
'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
'amount':5,
}
],
'proof':324984774000,
'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
到這里,區(qū)塊鏈的概念應(yīng)該比較清楚了:每個(gè)新的區(qū)塊都會包含上一個(gè)區(qū)塊的 Hash 值。這一點(diǎn)非常關(guān)鍵,它是區(qū)塊鏈不可變性的根本保障。如果攻擊者破壞了前面的某個(gè)區(qū)塊,那么后面所有區(qū)塊的 Hash 都會變得不正確。不理解?慢慢消化~
我們需要一個(gè)向區(qū)塊添加交易的方法:
classBlockchain(object):
...
defnew_transaction(self,sender,recipient,amount):
"""
Creates a new transaction to go into the next mined Block
:param sender:
:param recipient:
:param amount:
:return:
"""
self.current_transactions.append({
'sender':sender,
'recipient':recipient,
'amount':amount,
})
returnself.last_block['index']+1
new_transaction() 方法向列表中添加一個(gè)交易記錄,并返回該記錄將被添加到的區(qū)塊——下一個(gè)待挖掘的區(qū)塊——的索引,稍后在用戶提交交易時(shí)會有用。
當(dāng) Blockchain 實(shí)例化后,我們需要?jiǎng)?chuàng)建一個(gè)初始的區(qū)塊(創(chuàng)世塊),并且給它預(yù)設(shè)一個(gè)工作量證明。
除了添加創(chuàng)世塊的代碼,我們還需要補(bǔ)充 newblock(), newtransaction() 和 hash() 方法:
importhashlib
importjson
fromtimeimporttime
classBlockchain(object):
def__init__(self):
self.current_transactions=[]
self.chain=[]
# Create the genesis block
self.new_block(previous_hash=1,proof=100)
defnew_block(self,proof,previous_hash=None):
block={
'index':len(self.chain)+1,
'timestamp':time(),
'transactions':self.current_transactions,
'proof':proof,
'previous_hash':previous_hashorself.hash(self.chain[-1]),
}
# Reset the current list of transactions
self.current_transactions=[]
self.chain.append(block)
returnblock
defnew_transaction(self,sender,recipient,amount):
self.current_transactions.append({
'sender':sender,
'recipient':recipient,
'amount':amount,
})
returnself.last_block['index']+1
@property
deflast_block(self):
returnself.chain[-1]
@staticmethod
defhash(block):
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()
上面的代碼應(yīng)該很直觀,我們基本上有了區(qū)塊鏈的雛形。但此時(shí)你肯定很想知道一個(gè)區(qū)塊究竟是怎樣被創(chuàng)建或挖掘出來的。
新的區(qū)塊來自工作量證明(PoW)算法。PoW 的目標(biāo)是計(jì)算出一個(gè)符合特定條件的數(shù)字,這個(gè)數(shù)字對于所有人而言必須在計(jì)算上非常困難,但易于驗(yàn)證。這就是工作量證明的核心思想。
舉個(gè)例子:
假設(shè)一個(gè)整數(shù) x 乘以另一個(gè)整數(shù) y 的積的 Hash 值必須以 0 結(jié)尾,即 hash(x * y) = ac23dc...0。設(shè) x = 5,求 y?
fromhashlibimportsha256
x=5
y=0# We don't know what y should be yet...
whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
y+=1
print(f'The solution is y = {y}')
結(jié)果是 y = 21 // hash(5 * 21) = 1253e9373e...5e3600155e860
在比特幣中,工作量證明算法被稱為 Hashcash,它和上面的問題很相似,只不過計(jì)算難度非常大。這就是礦工們?yōu)榱藸帄Z創(chuàng)建區(qū)塊的權(quán)利而爭相計(jì)算的問題。通常,計(jì)算難度與目標(biāo)字符串需要滿足的特定字符的數(shù)量成正比,礦工算出結(jié)果后,就會獲得一定數(shù)量的比特幣獎(jiǎng)勵(lì)(通過交易)。
網(wǎng)絡(luò)要驗(yàn)證結(jié)果,當(dāng)然非常容易。
讓我們來實(shí)現(xiàn)一個(gè) PoW 算法,和上面的例子非常相似,規(guī)則是:尋找一個(gè)數(shù) p,使得它與前一個(gè)區(qū)塊的 proof 拼接成的字符串的 Hash 值以 4 個(gè)零開頭。
importhashlib
importjson
fromtimeimporttime
fromuuidimportuuid4
classBlockchain(object):
...
defproof_of_work(self,last_proof):
proof=0
whileself.valid_proof(last_proof,proof)isFalse:
proof+=1
returnproof
@staticmethod
defvalid_proof(last_proof,proof):
guess=f'{last_proof}{proof}'.encode()
guess_hash=hashlib.sha256(guess).hexdigest()
returnguess_hash[:4]=="0000"
衡量算法復(fù)雜度的辦法是修改零的個(gè)數(shù)。4 個(gè)零足夠用于演示了,你會發(fā)現(xiàn)哪怕多一個(gè)零都會大大增加計(jì)算出結(jié)果所需的時(shí)間。
我們的 Blockchain 基本已經(jīng)完成了,接下來我們將使用 HTTP requests 來與之交互。
第二步:作為 API 的 Blockchain
我們將使用 Flask 框架,它十分輕量并且很容易將網(wǎng)絡(luò)請求映射到 Python 函數(shù)。
我們將創(chuàng)建三個(gè)接口:
/transactions/new創(chuàng)建一個(gè)交易并添加到區(qū)塊
/mine告訴服務(wù)器去挖掘新的區(qū)塊
/chain返回整個(gè)區(qū)塊鏈
我們的服務(wù)器將扮演區(qū)塊鏈網(wǎng)絡(luò)中的一個(gè)節(jié)點(diǎn)。我們先添加一些常規(guī)代碼:
importhashlib
importjson
fromtextwrapimportdedent
fromtimeimporttime
fromuuidimportuuid4
fromflaskimportFlask,jsonify,request
classBlockchain(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'])
defmine():
return"We'll mine a new Block"
@app.route('/transactions/new',methods=['POST'])
defnew_transaction():
return"We'll add a new transaction"
@app.route('/chain',methods=['GET'])
deffull_chain():
response={
'chain':blockchain.chain,
'length':len(blockchain.chain),
}
returnjsonify(response),200
if__name__=='__main__':
app.run(host='127.0.0.1',port=5000)
這是用戶發(fā)起交易時(shí)發(fā)送到服務(wù)器的請求:
{
"sender":"my address",
"recipient":"someone else's address",
"amount":5
}
我們已經(jīng)有了向區(qū)塊添加交易的方法,因此剩下的部分就很簡單了:
@app.route('/transactions/new',methods=['POST'])
defnew_transaction():
values=request.get_json()
# Check that the required fields are in the POST'ed data
required=['sender','recipient','amount']
ifnotall(kinvaluesforkinrequired):
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}'}
returnjsonify(response),201
挖掘端正是奇跡發(fā)生的地方,它只做三件事:計(jì)算 PoW;通過新增一個(gè)交易授予礦工一定數(shù)量的比特幣;構(gòu)造新的區(qū)塊并將其添加到區(qū)塊鏈中。
@app.route('/mine',methods=['GET'])
defmine():
# 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
block=blockchain.new_block(proof)
response={
'message':"New Block Forged",
'index':block['index'],
'transactions':block['transactions'],
'proof':block['proof'],
'previous_hash':block['previous_hash'],
}
returnjsonify(response),200
需注意交易的接收者是我們自己的服務(wù)器節(jié)點(diǎn),目前我們做的大部分事情都只是圍繞 Blockchain 類進(jìn)行交互。到此,我們的區(qū)塊鏈就算完成了。
第三步:交互演示
使用 Postman 演示,略。
第四步:一致性
這真的很棒,我們已經(jīng)有了一個(gè)基本的區(qū)塊鏈可以添加交易和挖礦。但是,整個(gè)區(qū)塊鏈系統(tǒng)必須是分布式的。既然是分布式的,那么我們究竟拿什么保證所有節(jié)點(diǎn)運(yùn)行在同一條鏈上呢?這就是一致性問題,我們要想在網(wǎng)絡(luò)中添加新的節(jié)點(diǎn),就必須實(shí)現(xiàn)保證一致性的算法。
在實(shí)現(xiàn)一致性算法之前,我們需要找到一種方式讓一個(gè)節(jié)點(diǎn)知道它相鄰的節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)都需要保存一份包含網(wǎng)絡(luò)中其它節(jié)點(diǎn)的記錄。讓我們新增幾個(gè)接口:
1./nodes/register接收以URL的形式表示的新節(jié)點(diǎn)的列表
2./nodes/resolve用于執(zhí)行一致性算法,用于解決任何沖突,確保節(jié)點(diǎn)擁有正確的鏈
...
fromurllib.parseimporturlparse
...
classBlockchain(object):
def__init__(self):
...
self.nodes=set()
...
defregister_node(self,address):
parsed_url=urlparse(address)
self.nodes.add(parsed_url.netloc)
注意到我們用 set 來儲存節(jié)點(diǎn),這是一種避免重復(fù)添加節(jié)點(diǎn)的簡便方法。
前面提到的沖突是指不同的節(jié)點(diǎn)擁有的鏈存在差異,要解決這個(gè)問題,我們規(guī)定最長的合規(guī)的鏈就是最有效的鏈,換句話說,只有最長且合規(guī)的鏈才是實(shí)際存在的鏈。
讓我們再添加兩個(gè)方法,一個(gè)用于添加相鄰節(jié)點(diǎn),另一個(gè)用于解決沖突。
...
importrequests
classBlockchain(object)
...
defvalid_chain(self,chain):
last_block=chain[0]
current_index=1
whilecurrent_index
block=chain[current_index]
print(f'{last_block}')
print(f'{block}')
print("n-----------n")
# Check that the hash of the block is correct
ifblock['previous_hash']!=self.hash(last_block):
returnFalse
# Check that the Proof of Work is correct
ifnotself.valid_proof(last_block['proof'],block['proof']):
returnFalse
last_block=block
current_index+=1
returnTrue
defresolve_conflicts(self):
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
fornodeinneighbours:
response=requests.get(f'http://{node}/chain')
ifresponse.status_code==200:
length=response.json()['length']
chain=response.json()['chain']
# Check if the length is longer and the chain is valid
iflength>max_lengthandself.valid_chain(chain):
max_length=length
new_chain=chain
# Replace our chain if we discovered a new, valid chain longer than ours
ifnew_chain:
self.chain=new_chain
returnTrue
returnFalse
現(xiàn)在你可以新開一臺機(jī)器,或者在本機(jī)上開啟不同的網(wǎng)絡(luò)接口來模擬多節(jié)點(diǎn)的網(wǎng)絡(luò),或者邀請一些朋友一起來測試你的區(qū)塊鏈。
我希望本文能激勵(lì)你創(chuàng)造更多新東西。我之所以對數(shù)字貨幣入迷,是因?yàn)槲蚁嘈艆^(qū)塊鏈會很快改變我們看待事物的方式,包括經(jīng)濟(jì)、政府、檔案管理等。
新聞熱點(diǎn)
疑難解答
圖片精選