但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。& {2 E9 i% X9 j9 Y& f8 v0 _
准备工作" z& S2 g5 y( P2 c2 k: ]
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。9 I6 Z& o: I" }" k/ @
如果你不知道哈希值是什么,这里有一个解释。& P2 w# N. @# ?: K- t3 P5 \
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
pipinstallFlask==0.12.2requests==2.18.4/ T0 o% g5 `# L
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
BlockChain类. K$ ^1 ?# i! B0 `& u
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:
- classBlockchain(object):* p! ^! Y# Z6 L; ~( i3 {/ j0 D7 {
- def__init__(self):
- self.chain=[]% F/ |' H4 t3 Y W! B. Q6 o
- self.current_transactions=[]$ Z( h1 x8 w" t. ~# G
- defnew_block(self):8 Z) ~4 K, }9 A2 M) V$ |5 j
- #CreatesanewBlockandaddsittothechain
- pass& ~8 Y7 `5 e8 y; ?
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions
- pass
- @staticmethod
- defhash(block): K. q! W+ Z& X: m
- #HashesaBlock. C$ _- L" i2 o- A: P) B2 ?5 U% v. f8 L
- pass b$ E+ L4 f+ w/ O
- @property4 H+ q9 C/ P! G1 ^: d
- deflast_block(self):
- #ReturnsthelastBlockinthechain
- pass
-我们的区块链类的蓝图-
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。5 q# [% b, X- p$ q' O
以下是一个区块结构:
- block={
- 'index':1,) s# t, E/ X2 N& g" N
- 'timestamp':1506057125.900785,' t, u# A+ g, I* }# |+ m& k
- 'transactions':[
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f"," S. n2 D" ]# i3 T
- 'amount':5,: T7 p3 ]) k0 l$ t
- }6 b, |! m/ v' P' g7 A8 b' h
- ],
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", \6 u$ \' e3 ^" ]3 \
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易 X" Z2 _) c+ {5 y
接下来我们需要添加一个交易,来完善下new_transaction()方法。+ A+ Y) A O5 C/ i- n Y) m' {
- classBlockchain(object):
- ...% w2 l% O! l! {5 M( ?8 U6 Q
- defnew_transaction(self,sender,recipient,amount):
- """
- 生成新交易信息,信息将加入到下一个待挖的区块中( \! N% O! e9 r- g9 Y
- :paramsender:AddressoftheSender& e* H4 u! m+ ~5 d4 ~
- :paramrecipient:AddressoftheRecipient7 u* }5 X# n, `
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction3 @- j4 E1 ^, \( O
- """
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,
- 'amount':amount,
- })
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。. W9 V' S( t+ o8 S
创建区块; s. V* z1 G- G' e
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib3 [& ?' I, ]8 Z4 p
- importjson
- fromtimeimporttime2 p4 Z* L+ r4 \2 u4 q& \
- classBlockchain(object):/ H5 {/ E5 _: W r( H
- def__init__(self): K4 K8 S1 Q8 C4 X: |
- self.current_transactions=[]
- self.chain=[]7 e6 U, Z+ C+ ?' w3 Z: K% I" H
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100); Y6 @# _$ n1 ?( [
- defnew_block(self,proof,previous_hash=None):
- """7 e, |7 u- B5 K- L/ `, G( I, w
- 生成新块7 d S O$ R" \
- :paramproof:TheproofgivenbytheProofofWorkalgorithm; y! X" c0 _& v
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock3 M& X" j9 R4 r1 Y, G* l
- """8 N& p- O% J8 q& C
- block={0 n. t& h0 m) E- [. [( r
- 'index':len(self.chain)+1,
- 'timestamp':time(),
- 'transactions':self.current_transactions,% J- i4 l" B# E- D/ x0 A) k
- 'proof':proof,5 H; z* ]: L4 Q$ X% L9 z5 q
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),
- }( Y" q& ]% x: z0 B4 i6 B
- #Resetthecurrentlistoftransactions+ `' U/ |) f7 z# J( K
- self.current_transactions=[]
- self.chain.append(block)
- returnblock
- defnew_transaction(self,sender,recipient,amount):
- """6 J3 M" a. H) T5 @
- 生成新的交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction
- """) D3 \0 {* ]* \, N+ }' ^0 t
- self.current_transactions.append({8 i+ ^' y' H* \: E% h3 [$ |9 N
- 'sender':sender,; C, @2 U' ? T$ F6 K& E0 e
- 'recipient':recipient,) J% o0 P8 b; k: y) ]1 t2 s1 X# [3 [
- 'amount':amount,
- })
- returnself.last_block['index']+1
- @property, ]# N4 z$ Y) D3 s
- deflast_block(self):
- returnself.chain[-1]
- @staticmethod
- defhash(block):
- """# q. }0 U. q+ ?; N0 o
- 生成块的SHA-256hash值" p/ M" F) V% S4 e' Q; i6 @
- :paramblock:Block* Y W# {. `' g7 ]
- :return:
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes4 |1 U- F8 L7 e9 c! o7 z
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。2 G9 E; z0 ?9 ^; H" b( W+ |" G
理解工作量证明
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。, b* t$ b- r% x; f
为了方便理解,我们举个例子:
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
- fromhashlibimportsha256
- x=5
- y=0#y未知
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":1 C& K6 c* @$ }& E; G! O
- y+=1
- print(f'Thesolutionisy={y}')
结果y=21,因为:- ~! c1 u* Z2 D! p" V
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。. _9 D% X* R, V9 K: d7 ]1 i7 T
当然,在网络上非常容易验证这个结果。
实现工作量证明$ y- E, M% q8 K( J: e
让我们来实现一个相似PoW算法,规则是:! s& F6 X, n( {4 n- Q. S z
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。, { H( q0 r1 r; ~! [; x$ t4 c
- importhashlib9 r" y2 q! G7 H: _- p
- importjson
- fromtimeimporttime" Y2 W+ G( O; Z0 }# I& S! g
- fromuuidimportuuid4' J% ^5 i9 g3 H( G, ^- B
- classBlockchain(object):- R4 k" F" O" H. S; o. V J
- ... ^8 H5 z E* W! V
- defproof_of_work(self,last_proof):& I; k9 L& g5 P, E; f
- """+ O! [8 v8 E& [9 L0 x7 Q
- 简单的工作量证明:' Y6 ^( l' s3 c1 _5 l
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明
- :paramlast_proof:5 @1 R8 M' y( t6 _
- :return:
- """. Z$ X( F4 Q+ i' T
- proof=0
- whileself.valid_proof(last_proof,proof)isFalse:# O* ^0 x8 ^. x% i9 J' c. r
- proof+=1
- returnproof
- @staticmethod
- defvalid_proof(last_proof,proof):: X- w1 U' d9 O' _* e8 A4 P
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?/ c, R6 D9 m5 M
- :paramlast_proof:PreviousProof
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot.
- """
- guess=f'{last_proof}{proof}'.encode()/ z' H( ^5 H) w$ w/ [4 F# w; u
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000"
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。+ a. F; b! ?" i9 I
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。* o; X( E" y, Z
我们将创建三个接口:9 f" r) k6 W- L$ u
- ```/transactions/new```创建一个交易并添加到区块
- ```/mine```告诉服务器去挖掘新的区块( D. {5 r, n3 o& g ]
- ```/chain```返回整个区块链
- ###创建节点
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:3 E& t5 H! v$ C
- importhashlib; |. V$ ?% V1 f7 v3 v3 K1 n2 `
- importjson
- fromtextwrapimportdedent; b2 p9 m3 \& t- z
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask- Z6 n0 S: J5 m& C: I Q* ]
- classBlockchain(object):' Z% S k( p; E( }8 e4 i1 m
- …) q% J; Y) `* C
- InstantiateourNode- v" A: q8 ^$ f
- app=Flask(name)$ d; f/ k a" Q" A A
- Generateagloballyuniqueaddressforthisnode
- node_identifier=str(uuid4()).replace(’-’,‘’)
- InstantiatetheBlockchain' ]- ]. U* ^! F& V8 _
- blockchain=Blockchain()1 b, _8 P9 }0 n( o ~% H
- @app.route(’/mine’,methods=[‘GET’])
- defmine():
- return“We’llmineanewBlock”9 _/ {0 u7 a& j% E
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])7 p5 G5 S7 ]0 E# w m' Z: R
- deffull_chain():
- response={
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),* w5 _2 t T: l N
- }6 v; u( p; V2 E# \, N. F
- returnjsonify(response),200
- ifname==‘main’:! P* g4 _+ Y" E0 x; C2 X1 e2 j
- app.run(host=‘0.0.0.0’,port=5000). r' p7 v/ u; a' z9 J0 ~
- ```
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。
- 第18行:为节点创建一个随机的名字。- q" b6 V8 B* k% L1 _
- 第21行:实例Blockchain类。1 l S4 T* ?$ x3 z. d5 h! C
- 第24–26行:创建/mineGET接口。5 P% q' ~' f* w* j. f3 C6 x
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。; p4 ~/ ~+ s$ g: p: \3 W2 L0 v1 \
- 第32–38行:创建/chain接口,返回整个区块链。
- 第40–41行:服务运行在端口5000上。
- 发送交易, i5 J* {$ t, G; J D' w0 v1 ^6 P0 V
- 发送到节点的交易数据结构如下: m) ]' i5 X a0 R' ~
- {
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",
- "amount":5
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:
- importhashlib: W( d6 P* W" o1 ^/ e
- importjson' B9 ?0 I7 P. {# d7 B, X
- fromtextwrapimportdedent. H# Z7 `' H2 c0 \
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request. k9 b( b- ?- l' T5 s2 K6 }. Q5 [0 M
- ...
- @app.route('/transactions/new',methods=['POST'])
- defnew_transaction():% O' a: M+ T8 _
- values=request.get_json()* G- O5 P6 I* B, [9 H
- #CheckthattherequiredfieldsareinthePOST'eddata/ j& n$ e/ O6 k/ w* {. Q0 j
- required=['sender','recipient','amount']' G+ P& A5 b. Q
- ifnotall(kinvaluesforkinrequired):) Q+ k0 _0 G# {. p6 P2 o
- return'Missingvalues',400
- #CreateanewTransaction# d# r" K% e; A
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount']); w, c7 t- X# f. d" u0 m
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}
- returnjsonify(response),2013 U% b8 \9 ?+ g2 i( o, c- S
- ```# m3 o) A9 K: T, O: n6 K* w. z( a
- -创建交易的方法-* |- n# t4 V8 g" h5 q
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币6 y6 z% Q7 z* \+ ]. a, y+ q* E
- 构造新区块并将其添加到链中6 W7 y. k' F3 T2 v' P8 H2 F8 c) W
- importhashlib
- importjson
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- …
- @app.route(’/mine’,methods=[‘GET’])
- defmine():7 j& ~& M3 e3 h1 h, f. k
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)6 C g8 ]7 `: P3 x9 m+ ^
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.
- blockchain.new_transaction(* B) }5 t9 j! Z8 H1 t8 A
- sender="0",: z# ?; x1 G4 {" C
- recipient=node_identifier,1 Q# R$ I4 i* W. ^2 W6 ]
- amount=1,
- )
- #ForgethenewBlockbyaddingittothechain5 p. ~7 N r. \3 g
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)+ O1 U+ { N8 p9 R1 L7 L3 X
- response={
- 'message':"NewBlockForged",* `" M2 `; O6 i! w. C
- 'index':block['index']," K0 |" S+ Y8 v& V4 u
- 'transactions':block['transactions'],# Y0 M. c, [# I q: c
- 'proof':block['proof'],! h4 [# t! I- c& L- y
- 'previous_hash':block['previous_hash'],
- }
- returnjsonify(response),200
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
三、运行区块链
你可以使用cURL或Postman去和API进行交互 }- n& e) k- }% [2 a
启动server:
$pythonblockchain.py4 w# C4 _' W" E2 B! H1 Z) Y5 k
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)' M- d G* g0 _+ i" F) ^) r
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:/ l# m! E. e$ S3 u9 P# Y- x
-使用Postman以创建一个GET请求-& S) A& a* O6 @
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:5 R! m2 U) H5 d7 t
-使用Postman以创建一个POST请求-4 j7 a! i l1 A5 l. ?
如果不是使用Postman,则用一下的cURL语句也是一样的:- Y2 Q; w4 E5 } Y; g; h% X
$curl-XPOST-H"Content-Type:application/json"-d'{7 j& Y* t* t# z' w. s9 m
"sender":"d4ee26eee15148ee92c6cd394edd974e",
"recipient":"someone-other-address",
"amount":5
}'"http://localhost:5000/transactions/new"
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
- {
- "chain":[9 d5 o" g% H% @, M: X8 A
- {+ j+ f+ R0 c- i, [" L
- "index":1,
- "previous_hash":1,' n/ w' E' E4 e: ]5 j9 U& ]' R
- "proof":100,8 j! A' \0 B8 v5 A+ Z
- "timestamp":1506280650.770839,
- "transactions":[]) z/ v, ^6 c5 A3 F5 x4 g1 m
- },
- {! y* z1 k5 Y' ?& X- T; ?
- "index":2,2 s( A3 c0 K! ]( Q, }# ^- U
- "previous_hash":"c099bc...bfb7",
- "proof":35293,
- "timestamp":1506280664.717925,
- "transactions":[
- {
- "amount":1,! X$ ]+ ]( V: u+ ~0 V4 Y
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"$ f! u( S0 ], G7 f2 i/ r5 [
- }6 t# n1 @! W0 o1 v+ ]# i: O$ F
- ]6 B: o: S3 ?& l$ I! A
- },
- {) s, e, b+ k; {$ c; w
- "index":3,0 `0 {+ P- }. R3 |/ S$ f5 g1 p
- "previous_hash":"eff91a...10f2",
- "proof":35089,; {7 B4 ?0 }1 G( {" |
- "timestamp":1506280666.1086972,
- "transactions":[
- {
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"' }+ ?( ~1 ?1 K; r3 X$ `
- }( W* ?2 m* L) V5 D$ S: ]
- ]
- }' c [% |* |- u, B, T
- ],
- "length":3
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
注册节点
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
/nodes/register接收URL形式的新节点列表5 G. [6 P& t& ]* \; K: {8 ?
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链; r1 ~6 }% n6 c7 f9 T+ H$ c
我们修改下Blockchain的init函数并提供一个注册节点方法:& j8 w M7 n# T2 m3 y1 ~7 g
- ...
- fromurllib.parseimporturlparse+ e, j4 N, K* ~+ |6 l) r
- ...
- classBlockchain(object):: o! x2 _, H$ T. J5 B6 d3 o
- def__init__(self):3 f r. J6 h% D; e
- ...
- self.nodes=set()
- ..." X5 ?7 N8 J6 Y( J+ u
- defregister_node(self,address):
- """4 n1 @) ~4 }; h1 G( z1 C
- Addanewnodetothelistofnodes- @2 H6 x) p( \! i6 u. j( I
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'' L' ?* v6 c& O# Q4 {2 h
- :return:None# B4 x$ v' S$ E- @0 q9 |4 @
- """
- parsed_url=urlparse(address) h7 ?( i) J+ a ^0 y
- self.nodes.add(parsed_url.netloc), p) d/ }% i) @2 P& R5 j
- ```) c! l+ T4 X8 M4 w! T; ]
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。7 B9 Z6 _# Q A# o9 ]" b- a. u
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
- …
- importrequests5 s) M' H9 _) Y! H Z; r, k
- classBlockchain(object). D* @2 V, u5 t# m/ G' X; y8 d
- …
- defvalid_chain(self,chain):# j: Q/ r: L5 R2 ?
- """
- Determineifagivenblockchainisvalid) n2 _3 \. u( M5 q: T4 A. _& m8 [# ?
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot. c1 ?- b9 Z3 P' d0 X$ f9 f
- """' g+ o T1 Q- S- z$ f. ~9 C
- last_block=chain[0]/ E% u" {9 g2 Y, F( m( z. [' P
- current_index=1! f: i. k7 ?" I* W& C9 C
- whilecurrent_indexmax_lengthandself.valid_chain(chain):9 ^+ b+ h" [( Q& x3 j
- max_length=length$ @- y, d r2 W9 Y
- new_chain=chain3 T w* _5 p3 ^
- #Replaceourchainifwediscoveredanew,validchainlongerthanours
- ifnew_chain:
- self.chain=new_chain
- returnTrue' U3 U4 j' d4 H2 s1 j ?' `
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。& r8 _; C5 ~3 h$ ]% a: T8 N
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。: L ~' k$ V% S4 j2 W% U* j
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes():
- values=request.get_json() S1 n$ Z0 O6 F, C# L
- nodes=values.get('nodes')& Z8 `/ @, \$ K5 }$ Q+ w
- ifnodesisNone:
- return"Error:Pleasesupplyavalidlistofnodes",400' h+ X2 M# r# \+ H0 }. ^& {, H% J
- fornodeinnodes:
- blockchain.register_node(node)
- response={
- 'message':'Newnodeshavebeenadded',
- 'total_nodes':list(blockchain.nodes),
- }
- returnjsonify(response),201, Z! d& k! [! h' K
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():
- replaced=blockchain.resolve_conflicts()4 I( `, o) q1 h# S7 S2 }# O
- ifreplaced:
- response={
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain
- }
- else:
- response={
- 'message':'Ourchainisauthoritative',. o& h' ?: k( N# G2 h- L
- 'chain':blockchain.chain, [, N5 `4 v* I- u8 r- g" |7 k" [
- }
- returnjsonify(response),200
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
% S- X$ c7 f k
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。