聖誕節竟然還要寫code,只好在今天run起自己的區塊鏈網路,浪費電力報復社會。
既然這次的鐵人賽題目取叫區塊鏈報明牌,為了不要太名不符實,就把這個區塊鏈叫做報明牌鏈吧!接下來幾天會在根據比特幣論文繼續完善,並加入一些額外功能,來完成在Day1說的第一部分目標。
請注意這只是個學習性質為主的demo,離真正能當作實務應用等級的區塊鏈差非常遠,真正的實作大家可以參考官方bitcoin的原始碼。
先說一下目前的區塊鏈網路雖然可以work,不過還不符合比特幣論文中所題的網路流程,我本來想說在聖誕節搞場大的,結果今天寫不完,讓我在花個幾天完善它。
身為一個碼農要做P2P網路自然要先找library來用,google了一下發現了一個叫pyp2p的library,範例還寫的超簡單的,看來今天能輕鬆解決了,喔不跑不起來,看一下狀態已經不再開發。又找到了一個pydevp2p,而且還是乙太幣python實作用的P2P library,結果文件狀態是fail的,範例也寫得很複雜,難道我今天要糗大了嗎?最後找到了Berry College的The P2P Framework,500行的程式碼實現的P2P框架,終於看得懂了,碼農的日常就是這樣吧。
The P2P Framework架構(圖源參考自[1])
這個library功能很簡單,其實也是server-client架構,只是每個節點同時都是server跟client,server收其他所有節點的請求、client也跟所有節點要服務。
因為這裡的實作沒有Tracker之類紀錄網路上IP的東西,所以直接把網路中的node手動加進來:
# 在自己的電腦上的port開個節點
# 'xxx.xxx.xxx.xxx'填P2P網路中的其他ip:port
# 也可以在自己的電腦上開兩個port來用
# 開不起來可能是因為port有程式在用可以換一個
# 或檢查看看防火牆之類的設定
node = BTPeer(0, 4444)
node.addpeer(1, 'xxx.xxx.xxx.xxx', 4444)
node.addpeer(2, 'xxx.xxx.xxx.xxx', 4445)
node.addpeer(3, 'xxx.xxx.xxx.xxx', 4446)
....
修改先前的兩個函式broadcast跟trade,讓他們可以在P2P網路上廣播:
# 之前的所有str()都改用json.dumps(),方便傳遞資料
# broadcast 修改為 block_broadcast
# 將區塊昭告天下
def block_broadcast(self, new_block):
todelete = []
for pid in self.peers:
isconnected = False
try:
self.__debug( 'Check live %s' % pid )
host,port = self.peers[pid]
peerconn = BTPeerConnection( pid, host, port, debug=self.debug )
peerconn.senddata( 'REBL', json.dumps(new_block, encoding='latin1') )
isconnected = True
except:
todelete.append( pid )
if isconnected:
peerconn.close()
self.peerlock.acquire()
try:
for pid in todelete:
if pid in self.peers: del self.peers[pid]
finally:
self.peerlock.release()
# 談好一筆交易了
def trade_broadcast(self, pre_tran, data, pre_sk, now_vk):
transaction = {
'owner_vk': now_vk,
'preowner_vk': pre_tran['owner_vk'],
'data': data,
'signature': ecdsa.SigningKey.from_pem(pre_sk).sign(str(pre_tran)+now_vk)
}
try:
# 交易談好了
# 現在確認上一個transaction的擁有者有沒有偷改他以前的交易內容
ecdsa.VerifyingKey.from_pem(pre_tran['owner_vk']).verify(transaction['signature'], str(pre_tran) + now_vk)
todelete = []
for pid in self.peers:
isconnected = False
try:
self.__debug( 'Check live %s' % pid )
host,port = self.peers[pid]
peerconn = BTPeerConnection( pid, host, port, debug=self.debug )
peerconn.senddata( 'RETR', json.dumps(transaction, encoding='latin1'))
isconnected = True
except:
todelete.append( pid )
if isconnected:
peerconn.close()
self.peerlock.acquire()
try:
for pid in todelete:
if pid in self.peers: del self.peers[pid]
finally:
self.peerlock.release()
except:
print('有騙子?!')
修改add_tran為recv_tran,並把broadcast原本的一部分功能放到recv_block內:
# recv_tran
def recv_tran( self , peerconn, msgdata):
block = {
'prev_Hash': None,
'nonce': None,
'timestamp': int(time.time()),
'Txs': []
}
block['Txs'].append(json.loads(msgdata))
self.proof_of_work(block)
# recv_block
def recv_block( self , peerconn, msgdata):
new_block = json.loads(msgdata)
# 先確定區塊的合法性
# 才能確定區塊是不是算力大的一方來的
if new_block['timestamp'] < self.blockchain[-1]['timestamp']:
print("node %s:區塊不合法,應按照時間生成" % self.myid)
return
if new_block['pre_Hash'] != hashlib.sha256(str(self.blockchain[-1])).hexdigest():
print("node %s:區塊不合法,前一個hash值對不起來" % self.myid)
return
if hashlib.sha256(str(new_block).decode('utf-8')).hexdigest()[0:4] != '0000':
print("node %s:區塊不合法,hash值不符合proof of work的規定" % self.myid)
return
check_legel = True
# 第j的區塊的第k個transaction
for j in range(len(self.blockchain)):
for k in range(len(self.blockchain[j]['Txs'])):
for l in range(len(new_block['Txs'])):
if self.blockchain[j]['Txs'][k]['preowner_vk'].decode('utf-8') == new_block['Txs'][l]['preowner_vk'].decode('utf-8'):
check_legel = False
if check_legel == True:
self.blockchain.append(new_block)
print("node %s:紀錄了新block中的所有交易" % self.myid)
else:
print("node %s:抱歉,這筆交易不合規定,不能重複交易" % self.myid)
將上面的function都註冊到node的recv_block裡
self.addhandler('RETR', self.recv_tran)
self.addhandler('REBL', self.recv_block)
self.blockchain = [Genesis_block]
現在區塊鏈網路動起來!:
# 該node只發起交易之後其實沒參與網路運作
node = BTPeer(0, 4444)
node.addpeer(1, 'xxx.xxx.xxx.xxx', 4444)
node.addpeer(2, 'xxx.xxx.xxx.xxx', 4445)
node.addpeer(3, 'xxx.xxx.xxx.xxx', 4446)
# 不厭其煩的強調,真實情境不要把在用的私鑰放到瀏覽器上
node.trade_broadcast(Genesis_block['Txs'][0], '1000萬拿去收好', '-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIGqXRqNjZns1TJ1CfayizUPcpZop00KWWj0+fOy/WwqtoAcGBSuBBAAK\noUQDQgAEZUBDWMgG3dTAzKcvMbw1IkJiLbtFq/AyLIMsKpz2v2mc3e3QJUM/scUR\nMzoXPDSPftfU2CT6f4K0saWZsstAWg==\n-----END EC PRIVATE KEY-----', '')
另外兩個node的狀況:
# 假設是4445port的node先計算完成
node = BTPeer(0, 4445).mainloop()
node.addpeer(1, 'xxx.xxx.xxx.xxx', 4444)
node.addpeer(2, 'xxx.xxx.xxx.xxx', 4445)
node.addpeer(3, 'xxx.xxx.xxx.xxx', 4446)
node.mainloop()
'''
4445先計算完成
node xxx.xxx.xxx.xxx:4445 計算合法區塊成功
node xxx.xxx.xxx.xxx:4445:紀錄了新block中的所有交易
4446算完的廣播進來了 可是來不及了
node xxx.xxx.xxx.xxx:4445:區塊不合法,前一個hash值對不起來
'''
node = BTPeer(0, 4446).mainloop()
node.addpeer(1, 'xxx.xxx.xxx.xxx', 4444)
node.addpeer(2, 'xxx.xxx.xxx.xxx', 4445)
node.addpeer(3, 'xxx.xxx.xxx.xxx', 4446)
node.mainloop()
'''
收到4445來的廣播
node xxx.xxx.xxx.xxx:4446:紀錄了新block中的所有交易
4446計算完成 可是來不及了
node xxx.xxx.xxx.xxx:4446 計算合法區塊成功
node xxx.xxx.xxx.xxx:4446:區塊不合法,前一個hash值對不起來
'''
在github放有完整的範例程式提供,可以直接執行參考,可以把debug設成1以方便理解流程。
跟大家分享一個小故事,python的創始人Guido van Rossum就是為了打發聖誕節才開始寫python的,雖然我這種渣渣不能比啦,不過箇中的喜悅想必就是如此吧,帶著哀傷的喜悅阿。
《Bitcoin: A Peer-to-Peer Electronic Cash System》
https://bitcoin.org/bitcoin.pdf
[1]The P2P Framework
http://cs.berry.edu/~nhamid/p2p/framework-python.html