Python視頻教程之用Django實現一個可運行的區塊鏈應用分享
對數字貨幣的崛起感到新奇的我們,并且想知道其背后的技術——區塊鏈是怎樣實現的。

但是完全搞懂區塊鏈并非易事,我喜歡在實踐中學習,通過寫代碼來學習技術會掌握得更牢固。通過構建一個區塊鏈可以加深對區塊鏈的理解。下面內容對Python有基本的理解,能讀寫基本的Python,并且需要對HTTP請求有基本的了解。
我們知道區塊鏈是由區塊的記錄構成的不可變、有序的鏈結構,記錄可以是交易、文件或任何你想要的數據,重要的是它們是通過哈希值(hashes)鏈接起來的。
環境準備環境準備,確保已經安裝Python3.5,pip,django,requests,urllib,json,hashlib安裝方法:
pipinstalldjangorequests
同時還需要一個HTTP客戶端,比如Postman,cURL或其它客戶端,本文以Postman為例。
開始創建Blockchain通過django-adminstartprojectblock創建一個block的項目,在項目中創建一個demo項目django-adminstartprojectdemo,目錄結構:
Blockchain類在views中創建一個Blockchain類,在構造函數中創建了兩個列表,一個用于儲存區塊鏈,一個用于儲存交易。
以下是Blockchain類的框架:
class
Blockchain
(
object
):
def
__init__(
self
):
self
.chain=[]
self
.current_transactions=[]
def
new_block(
self
):
#CreatesanewBlockandaddsittothechain
pass
def
new_transaction(
self
):
#Addsanewtransactiontothelistoftransactions
pass
@staticmethod
def
hash(block):
#HashesaBlock
pass
@property
def
last_block(
self
):
#ReturnsthelastBlockinthechain
pass
Blockchain類用來管理鏈條,它能存儲交易,加入新塊等,下面我們來進一步完善這些方法。
塊結構每個區塊包含屬性:索引(index),Unix時間戳(timestamp),交易列表(transactions),工作量證明(稍后解釋)以及前一個區塊的Hash值。
以下是一個區塊的結構:
block={
'index'
:
1
,
'timestamp'
:
1506057125.900785
,
'transactions'
:[
{
'sender'
:
"8527147fe1f5426f9dd545de4b27ee00"
,
'recipient'
:
"a77f5cdfa2934df3954a5c7c7da5df1f"
,
'amount'
:
5
,
}
],
'proof'
:
324984774000
,
'previous_hash'
:
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
到這里,區塊鏈的概念就清楚了,每個新的區塊都包含上一個區塊的Hash,這是關鍵的一點,它保障了區塊鏈不可變性。如果攻擊者破壞了前面的某個區塊,那么后面所有區塊的Hash都會變得不正確。不理解的話,慢慢消化。
加入交易接下來我們需要添加一個交易,來完善下new_transaction方法
class
Blockchain
(
object
):
...
def
new_transaction(
self
,sender,recipient,amount):
"""
生成新交易信息,信息將加入到下一個待挖的區塊中
:paramsender:<str>AddressoftheSender
:paramrecipient:<str>AddressoftheRecipient
:paramamount:<int>Amount
:return:<int>TheindexoftheBlockthatwillholdthistransaction
"""
self
.current_transactions.append({
'sender'
:sender,
'recipient'
:recipient,
'amount'
:amount,
})
return
self
.last_block[
'index'
]+
1
方法向列表中添加一個交易記錄,并返回該記錄將被添加到的區塊(下一個待挖掘的區塊)的索引,等下在用戶提交交易時會有用。
創建新塊
當Blockchain實例化后,我們需要構造一個創世塊(沒有前區塊的第一個區塊),并且給它加上一個工作量證明。每個區塊都需要經過工作量證明,俗稱挖礦,稍后會繼續講解。
為了構造創世塊,我們還需要完善newblock(),newtransaction()和hash()方法:
class
Blockchain
(
object
):
def
__init__(
self
):
self
.chain=[]
self
.current_transactions=[]
self
.new_block(previous_hash=
1
,proof=
100
)
self
.nodes=
set
()
def
new_block(
self
,proof,previous_hash=
None
):
block={
'index'
:len(
self
.chain)+
1
,
'timestamp'
:time(),
'transactions'
:
self
.current_transactions,
'proof'
:proof,
'previous_hash'
:previous_hash
or
self
.hash(
self
.chain[-
1
]),
}
self
.current_transactions=[]
self
.chain.append(block)
return
block
def
new_transaction(
self
,sender,recipient,amount):
self
.current_transactions.append({
'sender'
:sender,
'recipient'
:recipient,
'amount'
:amount,
})
return
self
.last_block[
'index'
]+
1
@staticmethod
def
hash(block):
block_string=json.dumps(block,sort_keys=
True
).encode()
return
hashlib.sha256(block_string).hexdigest()
通過上面的代碼和注釋可以對區塊鏈有直觀的了解,接下來我們看看區塊是怎么挖出來的。
理解工作量證明新的區塊依賴工作量證明算法(PoW)來構造。PoW的目標是找出一個符合特定條件的數字,這個數字很難計算出來,但容易驗證。這就是工作量證明的核心思想。
為了方便理解,舉個例子:
假設一個整數x乘以另一個整數y的積的Hash值必須以0結尾,即hash(x*y)=ac23dc…0。設變量x=5,求y的值?
用Python實現如下:
from
hashlib
import
sha256
x=
5
y=
0
while
sha256(str(x*y).encode()).hexdigest()[:
4
]!=
"0000"
:
y+=
1
(y,sha256(str(x*y).encode()).hexdigest()[:
4
])
(y)
在比特幣中,使用稱為Hashcash的工作量證明算法,它和上面的問題很類似。礦工們為了爭奪創建區塊的權利而爭相計算結果。通常,計算難度與目標字符串需要滿足的特定字符的數量成正比,礦工算出結果后,會獲得比特幣獎勵。當然,在網絡上非常容易驗證這個結果。
實現工作量證明讓我們來實現一個相似PoW算法,規則是:尋找一個數p,使得它與前一個區塊的proof拼接成的字符串的Hash值以4個零開頭。
import
hashlib
import
json
from
time
import
time
from
uuid
import
uuid4
class
Blockchain
(
object
):
...
def
last_block(
self
):
return
self
.chain[-
1
]
def
proof_of_work(
self
,last_proof):
proof=
0
while
self
.valid_proof(last_proof,proof)
is
False
:
proof+=
1
return
proof
@staticmethod
def
valid_proof(last_proof,proof):
guess=str(last_proof*proof).encode()
guess_hash=hashlib.sha256(guess).hexdigest()
return
guess_hash[:
5
]==
"00000"
衡量算法復雜度的辦法是修改零開頭的個數。使用4個來用于演示,你會發現多一個零都會大大增加計算出結果所需的時間。
現在Blockchain類基本已經完成了,接下來使用HTTPrequests來進行交互。
Blockchain作為API接口我們將使用Pythondjango框架,這是一個輕量Web應用框架,它方便將網絡請求映射到Python函數,現在我們來讓來試一下:
我們將創建三個接口:
/transactions/new創建一個交易并添加到區塊
/mine告訴服務器去挖掘新的區塊
/chain返回整個區塊鏈
創建節點我們的“djangoweb服務器”將扮演區塊鏈網絡中的一個節點。我們先添加一些框架代碼:
node_identifier=str(uuid4()).replace(
'-'
,
''
)
#InstantiatetheBlockchain
blockchain=
Blockchain
()
def
mine(request):
last_block=blockchain.last_block
last_proof=last_block[
'proof'
]
proof=blockchain.proof_of_work(last_proof)
(proof)
blockchain.new_transaction(
sender=
"0"
,
recipient=node_identifier,
amount=
1
,
)
#ForgethenewBlockbyaddingittothechain
block=blockchain.new_block(proof)
response={
'message'
:
"NewBlockForged"
,
'index'
:block[
'index'
],
'transactions'
:block[
'transactions'
],
'proof'
:block[
'proof'
],
'previous_hash'
:block[
'previous_hash'
],
}
(response)
return
HttpResponse
(json.dumps(response))
def
new_transaction(request):
values=json.loads(request.body.decode(
'utf-8'
))
required=[
'sender'
,
'recipient'
,
'amount'
]
if
not
all(k
in
values
for
k
in
required):
return
'Missingvalues'
index=blockchain.new_transaction(values[
'sender'
],values[
'recipient'
],values[
'amount'
])
(index)
response={
'message'
:
'TransactionwillbeaddedtoBlock%s'
%index}
return
HttpResponse
(json.dumps(response))
def
full_chain(request):
response={
'chain'
:blockchain.chain,
'length'
:len(blockchain.chain),
}
return
HttpResponse
(json.dumps(response))
添加url路由節點:運行服務
from
demo
import
views
urlpatterns=[
url(r
'^admin/'
,admin.site.urls),
url(r
'^mine'
,views.mine),
url(r
'^transactions/new/'
,views.new_transaction),
url(r
'^chain/'
,views.full_chain),
url(r
'^register'
,views.register_nodes),
url(r
'^resolve'
,views.consensus),
]
運行服務
pythonmanage.pyrunserver
127.0
.
0.1
:
8000
發送交易發送到節點的交易數據,結構如下:
{
"sender"
:
"myaddress"
,
"recipient"
:
"someoneelse'saddress"
,
"amount"
:
5
}
向服務添加一個交易
挖礦挖礦正是神奇所在,它很簡單,做了一下三件事:
1、計算工作量證明PoW
2、通過新增一個交易授予礦工(自己)一個幣
3、構造新區塊并將其添加到鏈中
def
proof_of_work(
self
,last_proof):
proof=
0
while
self
.valid_proof(last_proof,proof)
is
False
:
proof+=
1
return
proof
@staticmethod
def
valid_proof(last_proof,proof):
guess=str(last_proof*proof).encode()
guess_hash=hashlib.sha256(guess).hexdigest()
return
guess_hash[:
5
]==
"00000"
注意交易的接收者是我們自己的服務器節點,我們做的大部分工作都只是圍繞Blockchain類方法進行交互。到此,我們的區塊鏈就算完成了,我們來實際運行下
運行區塊鏈使用Postman去和API進行交互
讓我們通過請求http://127.0.0.1:8000/mine來進行挖礦
在挖了兩次礦之后,就有3個塊了,通過請求http://localhost:8000/chain可以得到所有的塊信息。
{
"chain"
:[
{
"transactions"
:[],
"proof"
:
100
,
"timestamp"
:
1520314374.7261052
,
"index"
:
1
,
"previous_hash"
:
1
},
{
"transactions"
:[
{
"sender"
:
"0"
,
"recipient"
:
"27d4aae55b2848dcae52bc722d86e0c3"
,
"amount"
:
1
}
],
"proof"
:
1771087
,
"timestamp"
:
1520314389.5019505
,
"index"
:
2
,
"previous_hash"
:
"32fa73f48240160257e95fdf8422c6df734b5d7e8ceb69a41a5578643c1d36fb"
},
{
"transactions"
:[
{
"sender"
:
"d4ee26eee15148ee92c6cd394edd9705"
,
"recipient"
:
"5"
,
"amount"
:
500
},
{
"sender"
:
"0"
,
"recipient"
:
"27d4aae55b2848dcae52bc722d86e0c3"
,
"amount"
:
1
}
],
"proof"
:
100
,
"timestamp"
:
1520314592.4745598
,
"index"
:
3
,
"previous_hash"
:
"e6b1be488e0ed20fe3ec51135e5fafb4dfffaa28a190967106a5dd3e89e4b3aa"
}
],
"length"
:
3
}
一致性(共識)我們已經有了一個基本的區塊鏈可以接受交易和挖礦。但是區塊鏈系統應該是分布式的。既然是分布式的,那么我們究竟拿什么保證所有節點有同樣的鏈呢?這就是一致性問題,我們要想在網絡上有多個節點,就必須實現一個一致性的算法。
注冊節點在實現一致性算法之前,我們需要找到一種方式讓一個節點知道它相鄰的節點。每個節點都需要保存一份包含網絡中其它節點的記錄。因此讓我們新增幾個接口:
1、/register接收URL形式的新節點列表
2、/resolve執行一致性算法,解決任何沖突,確保節點擁有正確的鏈我們修改下Blockchain的init函數并提供一個注冊節點方法:
from
urllib.parse
import
urlparse
...
class
Blockchain
(
object
):
def
__init__(
self
):
...
self
.nodes=
set
()
...
def
register_node(
self
,address):
parsed_url=urlparse(address)
self
.nodes.add(parsed_url.netloc)
我們用set來儲存節點,這是一種避免重復添加節點的簡單方法。
實現共識算法前面提到,沖突是指不同的節點擁有不同的鏈,為了解決這個問題,規定最長的、有效的鏈才是最終的鏈,換句話說,網絡中有效最長鏈才是實際的鏈。
我們使用一下的算法,來達到網絡中的共識
class
Blockchain
(
object
):
def
__init__(
self
):
...
def
valid_chain(
self
,chain):
last_block=chain[
0
]
current_index=
1
while
current_index<len(chain):
block=chain[current_index]
if
block[
'previous_hash'
]!=
self
.hash(last_block):
return
False
#CheckthattheProofofWorkiscorrect
if
not
self
.valid_proof(last_block[
'proof'
],block[
'proof'
]):
return
False
last_block=block
current_index+=
1
return
True
def
resolve_conflicts(
self
):
neighbours=
self
.nodes
new_chain=
None
max_length=len(
self
.chain)
for
node
in
neighbours:
response=requests.
get
(
'http://%s/chain'
%node)
if
response.status_code==
200
:
length=json.loads(response)[
'length'
]
chain=json.loads(response)[
'chain'
]
#Checkifthelengthislongerandthechainisvalid
if
length>max_length
and
self
.valid_chain(chain):
max_length=length
new_chain=chain
#Replaceourchainifwediscoveredanew,validchainlongerthanours
if
new_chain:
self
.chain=new_chain
return
True
return
False
第一個方法valid_chain()用來檢查是否是有效鏈,遍歷每個塊驗證hash和proof.
第2個方法resolve_conflicts()用來解決沖突,遍歷所有的鄰居節點,并用上一個方法檢查鏈的有效性,如果發現有效更長鏈,就替換掉自己的鏈
在url中添加兩個路由,一個用來注冊節點,一個用來解決沖突。
from
demo
import
views
urlpatterns=[
url(r
'^register'
,views.register_nodes),
url(r
'^resolve'
,views.consensus),
]
你可以在不同的機器運行節點,或在一臺機機開啟不同的網絡端口來模擬多節點的網絡,這里在同一臺機器開啟不同的端口演示,在不同的終端運行一下命令,就啟動了兩個節點:http://127.0.0.1:8000和http://127.0.0.1:8100
然后在節點8100節點上挖兩個塊,確保是更長的鏈,然后在節點8000節點上訪問接口/resolve,這時節點8100的鏈會通過共識算法被節點8000節點的鏈取代。
最后想要了解更多關于Python發展前景趨勢,請關注扣丁學堂python培訓官網、微信等平臺,扣丁學堂IT職業在線學習教育平臺為您提供最新的Python視頻教程系統,通過千鋒扣丁學堂金牌講師在線錄制的Python視頻教程課程,讓你快速掌握Python從入門到精通開發實戰技能。扣丁學堂Python技術交流群:816572891。
*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。











