但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。% q+ j! ] V2 n+ |
准备工作" Y# ?. |. X9 v
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
pipinstallFlask==0.12.2requests==2.18.4- z0 }4 {9 O* P% V
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。6 c" c+ ~( r0 ~
一、开始创建BlockChain
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。: q# V; H2 F. o0 Y$ X" A) W5 C' x
BlockChain类& G% L! c+ e/ `) {8 p3 [
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。8 b7 e' ^8 o: e9 \( u
以下是BlockChain类的框架:
- classBlockchain(object):
- def__init__(self):! t; ]1 ~5 ^0 m5 S
- self.chain=[]
- self.current_transactions=[]
- defnew_block(self):" p h7 e& ], ^+ U/ {
- #CreatesanewBlockandaddsittothechain0 p( a/ ^9 J3 N( Y3 J
- pass
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions
- pass+ V% _& `& |% H1 B+ B% J, L
- @staticmethod7 j% M6 Y7 T4 M8 P6 C: ?
- defhash(block):
- #HashesaBlock
- pass
- @property
- deflast_block(self):; U: N# y( l0 _3 q( x$ y
- #ReturnsthelastBlockinthechain
- pass
-我们的区块链类的蓝图-; H V$ `* ]- P7 N R# j
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。# f! n3 X( C/ F; ?
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。
以下是一个区块结构:, E- P% @; ]9 u8 Q
- block={
- 'index':1,
- 'timestamp':1506057125.900785,
- 'transactions':[6 I6 E2 n% u' j. U& r
- {# y$ Y& W, s" t7 s9 g1 F' W
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
- 'amount':5,: w& f6 l+ U" M& H6 W L
- }7 I" d/ t" f- O8 C9 L+ `
- ],. F; v: e3 u8 z- e. @; t* R& \* T
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"# p* m. o) ~: V: }
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。7 \7 `7 k% A+ |/ o
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易% s: n6 U, r7 a9 x+ ?
接下来我们需要添加一个交易,来完善下new_transaction()方法。
- classBlockchain(object):0 J4 y S# P* H% Y3 ^* _3 A% E
- ...* P; Z9 V J; C: e, t, f
- defnew_transaction(self,sender,recipient,amount):+ e J/ P1 P1 N% j7 ^/ o
- """3 V# ]' @' m: C i+ }
- 生成新交易信息,信息将加入到下一个待挖的区块中0 A' r) c, \6 e
- :paramsender:AddressoftheSender! F$ x- M4 O$ \6 U9 S( k c
- :paramrecipient:AddressoftheRecipient2 H l( f1 h0 p& @; \
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction' {7 F5 E: f7 t+ w) ~4 k& K
- """
- self.current_transactions.append({4 o) I6 T N, h$ _. Y/ k
- 'sender':sender,' C. _+ w1 \* O8 x; a' u% X+ H
- 'recipient':recipient,
- 'amount':amount,
- })0 _$ @: ]- Z& N: B/ w* D1 ?
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块# j: X; v5 @. B" l) i
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib- j. F' K( f# `' V3 W9 F
- importjson
- fromtimeimporttime
- classBlockchain(object):
- def__init__(self):
- self.current_transactions=[]
- self.chain=[]+ |. ?, Z6 @4 ~% J
- #Createthegenesisblock2 z3 t( m6 q: x O6 }
- self.new_block(previous_hash=1,proof=100)
- defnew_block(self,proof,previous_hash=None):- i ?! m' y: N( K
- """
- 生成新块
- :paramproof:TheproofgivenbytheProofofWorkalgorithm. m9 Z# t8 i$ R# B- P
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock3 b4 J* V& b; P; `! @. L
- """
- block={
- 'index':len(self.chain)+1,
- 'timestamp':time(),
- 'transactions':self.current_transactions,7 p% C1 r3 j4 x+ I- ~2 V7 C
- 'proof':proof,
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),4 y- G, R% F" _6 a: V+ ^: s
- }! P7 n# E3 Q% K4 j
- #Resetthecurrentlistoftransactions
- self.current_transactions=[]
- self.chain.append(block)
- returnblock. a! C7 ~ n6 ]' |( O
- defnew_transaction(self,sender,recipient,amount):8 [3 K S; A0 f- _6 q* p" I# o& `
- """( T/ x* K7 u7 e8 Y
- 生成新的交易信息,信息将加入到下一个待挖的区块中: y" }2 C: G \$ f. Z
- :paramsender:AddressoftheSender3 x* Q( S/ d' { y0 q
- :paramrecipient:AddressoftheRecipient4 h- D H$ T# j: R+ \
- :paramamount:Amount F5 u6 g7 c. H n2 _# A$ u) K
- :return:TheindexoftheBlockthatwillholdthistransaction9 z; w' P# W6 A7 [: D, A
- """
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,& u& q' ]0 K; @& }/ N6 }
- 'amount':amount, o, j: d( k) M# b% r2 h$ E7 a4 r
- })
- returnself.last_block['index']+19 } ]2 [1 ]; N, v) o, l0 [, w9 [
- @property
- deflast_block(self):
- returnself.chain[-1]5 d3 N4 q6 E% C1 ?; S7 a
- @staticmethod" Y. ^% e4 R9 O' j3 B! `
- defhash(block):
- """
- 生成块的SHA-256hash值
- :paramblock:Block8 `+ G# l0 J0 C! i7 i
- :return: B5 f9 I7 ]0 [' }+ m
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes- P' y# B1 N3 l
block_string=json.dumps(block,sort_keys=True).encode()3 ?& t6 V# O3 V+ o+ A, _( b
returnhashlib.sha256(block_string).hexdigest()- f% j% x# B, z5 s
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。4 ^, n) u& ~& l! P. ^( A' e
理解工作量证明
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
为了方便理解,我们举个例子:
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
- fromhashlibimportsha2567 C" Y+ s% E8 f$ i
- x=58 U9 m, r: n& S) T8 K% S
- y=0#y未知
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":3 v1 ?$ x" Q. ~; |+ R
- y+=1* O( U! m2 ^7 L* O
- print(f'Thesolutionisy={y}')
结果y=21,因为:5 ]( v; _: l7 ]" X8 q
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。( m8 ^$ ?) {" U G, X
当然,在网络上非常容易验证这个结果。
实现工作量证明- y3 H( \0 }( V% A
让我们来实现一个相似PoW算法,规则是:# y2 r0 E( l) e4 k+ w H* G g6 w
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
- importhashlib
- importjson$ ]' R% S, m: p i+ \# M3 o
- fromtimeimporttime; _8 R! a1 g. R5 P$ Q
- fromuuidimportuuid4! [8 D; g* V% ~) l5 H7 C
- classBlockchain(object):! F& ^/ t: {6 T8 p: L' k2 F
- ...7 p6 o6 w" p. L+ e
- defproof_of_work(self,last_proof):# L) q9 ~* [4 T' Z0 k
- """2 ]: N5 C- O0 S! M
- 简单的工作量证明:0 s+ {* U G% G0 i
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明- C E7 b6 E. B5 Y
- :paramlast_proof:9 w$ Y4 l: B. e' n# G
- :return:
- """6 }! K- q0 C5 B% x% G4 q5 I
- proof=0
- whileself.valid_proof(last_proof,proof)isFalse:$ N# ]* P \3 Y' `, A
- proof+=1
- returnproof
- @staticmethod
- defvalid_proof(last_proof,proof):, R% Z/ q% _0 D9 r- K R0 T# _
- """; b( e& T- h; w* c
- 验证证明:是否hash(last_proof,proof)以4个0开头?
- :paramlast_proof:PreviousProof" F5 R; `' b0 x' m
- :paramproof:CurrentProof1 V) X& f+ @2 ?$ T0 P& c
- :return:Trueifcorrect,Falseifnot.
- """
- guess=f'{last_proof}{proof}'.encode()
- guess_hash=hashlib.sha256(guess).hexdigest()9 i( T1 N# c. L/ D# h
- returnguess_hash[:4]=="0000"
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。4 X4 r! B5 ^. _0 }
##二、BlockChain作为API接口- T- ?4 P/ a6 n) c3 s
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
我们将创建三个接口:
- ```/transactions/new```创建一个交易并添加到区块# q) _2 l5 L2 _# \& F. Q
- ```/mine```告诉服务器去挖掘新的区块 g Y; Q7 D8 { K
- ```/chain```返回整个区块链
- ###创建节点+ S% O, A) S8 y- y" n5 U7 L" C
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
- importhashlib
- importjson
- fromtextwrapimportdedent8 _3 p/ E" J/ b! ^2 b* n
- fromtimeimporttime* s5 y- J( d; L9 @6 H
- fromuuidimportuuid4! x7 j! e( K0 j8 Z, }- K; Y
- fromflaskimportFlask
- classBlockchain(object):
- …
- InstantiateourNode
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode
- node_identifier=str(uuid4()).replace(’-’,‘’)
- InstantiatetheBlockchain
- blockchain=Blockchain()4 Z2 A3 E6 x1 J7 e& D
- @app.route(’/mine’,methods=[‘GET’])0 D& d2 G6 Q2 D+ h
- defmine():$ r2 s% h! F/ B5 S4 i
- return“We’llmineanewBlock”& o! P- q* G/ V+ r L u% B5 C5 A
- @app.route(’/transactions/new’,methods=[‘POST’])- x" x0 ~3 Q( Z* k
- defnew_transaction():3 a1 f$ U: C% B4 k+ q! X
- return“We’lladdanewtransaction”; q9 V) l4 K4 I+ c: g3 v+ r
- @app.route(’/chain’,methods=[‘GET’])0 L( y) I0 p x4 t3 i
- deffull_chain():8 C% _6 H5 ~5 j X |" s
- response={
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),
- }
- returnjsonify(response),200
- ifname==‘main’:
- app.run(host=‘0.0.0.0’,port=5000)4 ^: K3 w! O* B- S- x
- ```
- 简单的说明一下以上代码:0 p3 `5 O' a6 l5 N* p; S8 h* s
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。* S& i F6 m2 s# `
- 第18行:为节点创建一个随机的名字。 T4 c- T& f: }/ o: h
- 第21行:实例Blockchain类。
- 第24–26行:创建/mineGET接口。
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
- 第32–38行:创建/chain接口,返回整个区块链。
- 第40–41行:服务运行在端口5000上。
- 发送交易
- 发送到节点的交易数据结构如下:3 m1 B' A7 c& z; \5 B0 Z" ?
- {
- "sender":"myaddress",5 q0 Z) i& [# Y, `$ r5 q2 @
- "recipient":"someoneelse'saddress",
- "amount":5# v+ i% O0 D0 \; j$ a D+ L
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:
- importhashlib/ ]6 _5 l0 z! U% o! @% G; _2 i
- importjson
- fromtextwrapimportdedent
- fromtimeimporttime2 b6 s) ]1 J2 j& N4 M+ s/ L
- fromuuidimportuuid45 N1 U. U' }) d: X8 G# a$ z
- fromflaskimportFlask,jsonify,request7 K) R1 K! |" r, x
- ...
- @app.route('/transactions/new',methods=['POST'])1 h3 d( i& i/ C" N# y
- defnew_transaction():/ n7 n( m5 O& B+ k+ G) ]( U& s1 C
- values=request.get_json()
- #CheckthattherequiredfieldsareinthePOST'eddata. g5 z6 {# Y5 T y
- required=['sender','recipient','amount']" A9 J2 _8 F! |5 {
- ifnotall(kinvaluesforkinrequired):
- return'Missingvalues',4007 J/ Z# Z7 {/ T, o( c5 M8 g8 {
- #CreateanewTransaction
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}% g6 p2 J3 J3 l6 e- z" R# ~
- returnjsonify(response),201
- ```
- -创建交易的方法-
- ###挖矿: G, a1 }" B2 m; f( s2 I+ ~# r3 Z
- 挖矿正是神奇所在,它很简单,做了一下三件事:$ ]5 i! {+ f1 K# F; T! I
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
- importhashlib
- importjson9 i, U0 ]; ?. s: q: k' c
- fromtimeimporttime2 S# n0 v% }, m- _2 Q. W0 M
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- …1 N$ Q4 R# o9 v' u8 |. b) K
- @app.route(’/mine’,methods=[‘GET’])
- defmine():& [4 u% w7 f( Q+ [9 F9 s
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block
- last_proof=last_block[‘proof’]6 ?+ m5 o8 ?# w
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.6 Y1 D) l( F7 R: V. P9 p# t
- #发送者为"0"表明是新挖出的币.
- blockchain.new_transaction(* w7 P" {) @2 }+ E
- sender="0",. U d9 z" J; }3 `% _
- recipient=node_identifier,
- amount=1,
- )- L: R8 y0 [' r" I
- #ForgethenewBlockbyaddingittothechain
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)
- response={
- 'message':"NewBlockForged",
- 'index':block['index'],' M1 f M! t8 }# l) f# G' O6 C
- 'transactions':block['transactions'],
- 'proof':block['proof'],
- 'previous_hash':block['previous_hash'],3 b0 V! w( I9 Y- _. \' ]
- }$ N- g: Y s% g- X0 P4 W
- returnjsonify(response),200) t9 O9 i3 s! o" O) }5 v, V) W
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。1 k8 _8 e0 j& O$ [9 R" L
三、运行区块链
你可以使用cURL或Postman去和API进行交互 Y. j, {- C6 |5 v/ t
启动server:
$pythonblockchain.py# R0 B4 i3 s& W* o7 k3 l! |5 }7 k
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)" J$ Z( c5 Y- O( s& r- }
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-0 h! f8 c! w* o: ~( R3 F
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
-使用Postman以创建一个POST请求-
如果不是使用Postman,则用一下的cURL语句也是一样的:
$curl-XPOST-H"Content-Type:application/json"-d'{4 ?- m7 v7 j% \
"sender":"d4ee26eee15148ee92c6cd394edd974e",
"recipient":"someone-other-address",
"amount":5
}'"http://localhost:5000/transactions/new"* F) j2 I5 S- K$ d$ Y* d
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:- K/ a* n6 @' g. V) b' l+ u' b
- {( r; B4 ^# g e- j% `; z3 Z: h
- "chain":[
- {' X3 v: y: Q+ D5 }& u
- "index":1,* p" Q! z- c! e" ~, w! o {& I
- "previous_hash":1,+ y* t; s9 p6 x) B: R
- "proof":100,& _) I% o- ~) g4 J) k; W; q! Y
- "timestamp":1506280650.770839,
- "transactions":[]$ ^# @ r* f2 ?" T: D' i) ?0 p
- },7 [* y: Z% O4 q, X
- {1 X0 X4 F3 {1 V8 h/ U( [
- "index":2,9 A( d$ V5 v7 _! T0 O
- "previous_hash":"c099bc...bfb7",
- "proof":35293,
- "timestamp":1506280664.717925,+ [7 o6 Z$ a0 _( B0 q9 o7 V I
- "transactions":[8 m/ B8 X; Q: D% `
- {
- "amount":1,# v: |% P" R. L$ t N
- "recipient":"8bbcb347e0634905b0cac7955bae152b",, X3 r" O2 o" D/ u
- "sender":"0"6 U" M M: t/ `& ~3 J
- }9 f+ k* Y) n' k1 d5 ]+ z
- ]' C6 J3 L$ g- R2 ?$ ]9 D3 s4 P* Q
- },) l- C2 l. X ^( ^ r4 z' l
- {; h; e# x/ D0 F7 a
- "index":3,
- "previous_hash":"eff91a...10f2",3 q A* Q2 f. r x
- "proof":35089,8 U r, Y% \# Q0 Z
- "timestamp":1506280666.1086972,
- "transactions":[9 d. K7 n" r7 Q5 U7 b+ L3 |
- {7 D: Y& t, e5 n3 f& \; p0 N
- "amount":1,3 u* O2 i8 X$ l# d0 s
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }0 N5 i: N7 q* S9 L
- ]
- }
- ],
- "length":3 ~# U/ y4 g, ~' Q, F/ d8 u
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
注册节点
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:! c7 p4 _4 g$ W W
/nodes/register接收URL形式的新节点列表4 T! i t' F8 ]: J; V4 o0 Z3 O) {2 s
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:
- ...
- fromurllib.parseimporturlparse6 V) |1 y3 b2 x% H5 b& T I* ~
- ..." p1 C( w( Z, ?
- classBlockchain(object):4 g' ?3 q( U' V
- def__init__(self):
- ...
- self.nodes=set()
- .../ I+ p- t8 x" X/ x+ w, n, l) n3 U: B
- defregister_node(self,address):& E' n4 q! R2 {* m4 I
- """
- Addanewnodetothelistofnodes3 }$ L" E+ j# S9 f+ y0 q, S
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None
- """
- parsed_url=urlparse(address)
- self.nodes.add(parsed_url.netloc)
- ```
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。! B7 W! l, X% |2 u( D+ M. p
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。# |" G, V R: o; D3 c, }! K! s
- …
- importrequests
- classBlockchain(object)
- …
- defvalid_chain(self,chain):$ L" _4 Z4 }- R9 D& {; {: ]2 y
- """9 M6 U! ~: o1 e s) a4 j; }$ a- `
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain7 v% ~5 H( s N Q
- :return:Trueifvalid,Falseifnot
- """2 v+ j1 I; G' E- u3 w4 g5 O
- last_block=chain[0]9 t8 J) ^. O7 o3 _: Q2 V
- current_index=14 [5 u; }( m8 c% M
- whilecurrent_indexmax_lengthandself.valid_chain(chain):- ]7 Q1 c& [2 ]
- max_length=length
- new_chain=chain/ r& y0 Z- t4 }; Q0 v5 y( K6 D
- #Replaceourchainifwediscoveredanew,validchainlongerthanours& E" L: X. [' s' `- ^. h
- ifnew_chain:
- self.chain=new_chain9 C( U+ V. k! u1 d
- returnTrue9 A- ^9 v A0 \- D
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。 a5 Q; h+ L9 `) H* E" S
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。
- @app.route('/nodes/register',methods=['POST'])' f* \% g0 _5 u6 v4 L1 ^4 }
- defregister_nodes():% _3 C3 v/ q" z1 s9 ~
- values=request.get_json()5 r" m9 q+ ]3 n) n
- nodes=values.get('nodes')" e, L+ B3 x3 E+ U0 c2 H/ w! l
- ifnodesisNone:
- return"Error:Pleasesupplyavalidlistofnodes",400
- fornodeinnodes:4 L i, e5 N: E, C+ h; C* i
- blockchain.register_node(node)/ d+ J: D4 ^% [$ @# C$ r
- response={" E& W% K0 H" G% v0 [
- 'message':'Newnodeshavebeenadded',
- 'total_nodes':list(blockchain.nodes),2 |3 h5 W1 S* x1 n# B
- }% F* U. T$ k/ U1 ^6 D) D: q
- returnjsonify(response),201& ?4 S" L+ F& q* \0 N
- @app.route('/nodes/resolve',methods=['GET']) Y3 t5 R, ?- f# L o% S- ^
- defconsensus():
- replaced=blockchain.resolve_conflicts()
- ifreplaced:+ n+ c9 n4 {2 @* w6 \% j
- response={
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain3 |& u5 ~9 F3 N& ^( P! x. j$ q6 y
- }
- else:
- response={4 D- f( S) y4 k! x; |6 U/ B4 S, J
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain" {/ q4 R9 w3 }, s
- }& P2 i$ f) a, E. o2 A1 I: o& G
- returnjsonify(response),200
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。