但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
准备工作
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:6 ?! q5 K2 z8 ~# y+ \6 ~! D
pipinstallFlask==0.12.2requests==2.18.4
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain) y/ B; y, E/ t" e
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
BlockChain类" \9 m' x' s2 }( ~+ l: r$ n/ m
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。# x7 p b9 U2 y8 n
以下是BlockChain类的框架:
- classBlockchain(object):" L8 n! B* o3 T. v7 w& I3 }5 @
- def__init__(self):
- self.chain=[]
- self.current_transactions=[]) c! }; Z& h) Z" p4 M
- defnew_block(self):
- #CreatesanewBlockandaddsittothechain! W: |* P0 m5 ^0 C
- pass4 h( y' d4 b% N
- defnew_transaction(self):; _1 z4 v& g3 E1 w& O8 r
- #Addsanewtransactiontothelistoftransactions
- pass4 U4 B6 x9 o% T1 I6 ~( ]
- @staticmethod: Y( W G' H; {% T
- defhash(block):0 J( {! R, E5 o3 Z+ F% ?" F$ \
- #HashesaBlock2 Q, A8 b6 s5 q
- pass
- @property3 m; H y2 f8 {$ n' F* |
- deflast_block(self):
- #ReturnsthelastBlockinthechain! x+ B# n0 s) a4 n F6 K5 ~. g
- pass
-我们的区块链类的蓝图-, |5 O, L- F; m7 n- B
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。9 ~, W7 d+ E0 \+ [
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。6 H3 o& l4 Z2 L w$ ~
以下是一个区块结构:
- block={
- 'index':1,. J' |& T+ U/ j
- 'timestamp':1506057125.900785,
- 'transactions':[* u! Y# p" x7 {+ }/ t, `
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",+ o9 }7 q1 J- |
- 'amount':5,
- }5 z1 t5 {- _& d1 ^0 E+ N
- ],
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-# h) n9 U+ B! r- t) Z6 ]9 |
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。" y* I+ n) c, B% Z) a/ T0 x
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易1 p& c8 \* a" n5 E
接下来我们需要添加一个交易,来完善下new_transaction()方法。
- classBlockchain(object):
- ...
- defnew_transaction(self,sender,recipient,amount):* _ I' M' u2 z" j
- """
- 生成新交易信息,信息将加入到下一个待挖的区块中) p; X% B' }, s' l& H) {( V
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient* O( s. x. B/ V/ @" g+ Q
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction
- """
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,; t" W: f( ?1 W$ n2 b
- 'amount':amount,
- })8 H: q; P2 u, H3 Y1 X% x
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块( E/ y3 ~7 X8 O/ d, O
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。0 N( `' h0 J% S+ j t; E: C! J
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib
- importjson" V/ X F! b' m7 ~
- fromtimeimporttime
- classBlockchain(object):" F: d6 T! R* x, f
- def__init__(self):
- self.current_transactions=[]+ @+ [0 x7 ]# i7 p% g% o8 p
- self.chain=[]. \- T. S. u4 X( b& R
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100)* W% d! \, y- a
- defnew_block(self,proof,previous_hash=None):
- """
- 生成新块. i/ d$ e+ {* W" P
- :paramproof:TheproofgivenbytheProofofWorkalgorithm% ^) ]5 m3 X8 e B- N5 c
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock
- """
- block={- S6 D- r* |8 ~: {: X* N2 a; V
- 'index':len(self.chain)+1,9 P; |' t$ }7 K, d2 k8 P
- 'timestamp':time(),
- 'transactions':self.current_transactions,: }6 J5 O" a) i
- 'proof':proof,$ H: Q4 {( Z2 G; T
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),5 t) \3 I; A3 g, Z" ]% U
- }# D% v- X+ r1 q h( M' f& ~
- #Resetthecurrentlistoftransactions& ?( {& h( E6 O& d) n9 V9 w
- self.current_transactions=[]
- self.chain.append(block)
- returnblock
- defnew_transaction(self,sender,recipient,amount):- h- r( D: ]5 ]$ O
- """
- 生成新的交易信息,信息将加入到下一个待挖的区块中8 m8 K! ]2 P9 U
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient% t( c d+ W. n
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction7 @6 ^: x4 a" \- ^9 F5 S# g
- """' p; @" k- f1 S8 t* ]
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,* A5 h) e% r3 k: E
- 'amount':amount,
- })5 u5 y% ?: }5 r/ n/ k/ B6 h
- returnself.last_block['index']+1
- @property
- deflast_block(self):0 J9 d' T1 Q! b, F
- returnself.chain[-1]* m) x! X# |3 b u( C
- @staticmethod
- defhash(block):. C4 a$ e8 q' \ q$ [( Z+ \: f& \
- """% ^& P) B) `! E5 m' W5 K' y$ F6 u$ M
- 生成块的SHA-256hash值
- :paramblock:Block# x! u1 d# t6 Q' W7 L8 q
- :return:
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes- ~4 |0 J( i0 g' T) m7 R" ^3 q* |
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()$ y. c( J6 |/ t
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。) T/ Y/ q! |8 I8 k- |( }
理解工作量证明) i; e: n/ ]/ `6 r! L# S
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。9 h( _5 i8 L* l1 y* X, G" V
为了方便理解,我们举个例子:. R% Y& I" I& m3 f5 p
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:, i6 c: |6 N" ~6 | F( m. ]( ]4 r
- fromhashlibimportsha256
- x=5
- y=0#y未知) {1 l6 j1 W8 E5 r. ~3 I( J( @1 H
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":' R+ z" x& z1 Y" [
- y+=1
- print(f'Thesolutionisy={y}')
结果y=21,因为:
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。( V5 I! k' P( z# b* j/ i
当然,在网络上非常容易验证这个结果。
实现工作量证明( u# }& O- j2 e7 A4 `
让我们来实现一个相似PoW算法,规则是:3 G6 v' O, k# F" Y4 ^- o1 y1 ^* w
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。$ \& r7 e2 a0 W
- importhashlib6 w; J0 r6 n, b8 x& I
- importjson" f$ B5 K( z# ^; y5 t
- fromtimeimporttime& ?3 v- {8 f# r
- fromuuidimportuuid4
- classBlockchain(object):3 {3 L5 U- m9 U* x" X
- ...& x2 Q4 @* h- `7 `+ w, F
- defproof_of_work(self,last_proof):
- """
- 简单的工作量证明:8 R" n2 x; H, g) i' R7 e
- -查找一个p'使得hash(pp')以4个0开头 s7 e5 x7 U v( H1 C. ]
- -p是上一个块的证明,p'是当前的证明2 e- A) w: o8 H
- :paramlast_proof:+ U r% ?$ g! o6 \) ]) L1 J) ]! X
- :return:
- """, s7 G$ n! N& H- P- ~2 R) w' ?
- proof=0
- whileself.valid_proof(last_proof,proof)isFalse:1 p7 o7 @7 H% s( F! c9 ]' i
- proof+=1% z" Q2 I: q$ V% d
- returnproof
- @staticmethod3 z+ M0 F+ f' I4 C
- defvalid_proof(last_proof,proof):
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?( X. y* S6 i+ q
- :paramlast_proof:PreviousProof
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot.
- """! f# {: E @, a2 a, u% Z
- guess=f'{last_proof}{proof}'.encode()! @1 }# e( B2 G; l' `& G+ w y/ [' s$ D
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000"0 p F' I. S- v& W. w5 J
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。, n0 s3 X8 x5 J
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。: }5 W3 }5 [# }3 x
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。" o" p$ ~1 N* v, Q3 d2 f
我们将创建三个接口:
- ```/transactions/new```创建一个交易并添加到区块
- ```/mine```告诉服务器去挖掘新的区块, l5 P! Z& v8 r& ~% F, v! U
- ```/chain```返回整个区块链" j7 N g- R8 `
- ###创建节点1 L9 H6 i! n9 m7 z0 L3 D3 w% K6 H
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
- importhashlib
- importjson
- fromtextwrapimportdedent
- fromtimeimporttime+ Z k0 }7 B- } }6 O, d
- fromuuidimportuuid4
- fromflaskimportFlask
- classBlockchain(object):3 W! f: d& z# n' g; d- B8 n
- …+ v" m$ x0 Q, f2 I
- InstantiateourNode* ?4 b- c$ B- Q9 w3 F; g, f' a
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode: L9 f1 {) T. @
- node_identifier=str(uuid4()).replace(’-’,‘’), ^9 ?: S8 D+ G u
- InstantiatetheBlockchain+ [& q1 D' G* k1 F0 d. J
- blockchain=Blockchain()
- @app.route(’/mine’,methods=[‘GET’])7 ?; y9 g& @7 W5 h
- defmine():
- return“We’llmineanewBlock”* Z S# c5 `4 n- v" I& a- _
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():2 C8 N& D+ _7 C, r8 O
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])
- deffull_chain():
- response={
- ‘chain’:blockchain.chain,( U' p: Y( h& E( f
- ‘length’:len(blockchain.chain),
- }8 v- t( U! g4 ^4 Q; ]* z( c t
- returnjsonify(response),200
- ifname==‘main’:
- app.run(host=‘0.0.0.0’,port=5000)* V& A9 {5 P1 B/ T; X0 W
- ```* s% H! g3 j+ m
- 简单的说明一下以上代码:. c3 G4 R6 n) B2 ^# p- p
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。. N T0 Q& q' m$ ` p7 } M
- 第18行:为节点创建一个随机的名字。
- 第21行:实例Blockchain类。# b4 `& m% f2 y! w
- 第24–26行:创建/mineGET接口。1 o ^' U. Z7 \$ x# Z- e |
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。& _. @5 Q' H! z- L
- 第32–38行:创建/chain接口,返回整个区块链。) \$ X# D* F4 c' z' h$ V
- 第40–41行:服务运行在端口5000上。; _/ W( v0 ]. }$ K i
- 发送交易, h, l7 Z8 G! X0 |+ U8 d
- 发送到节点的交易数据结构如下:
- {3 a' E% v9 R7 l$ S3 h' u5 T
- "sender":"myaddress",: h8 z8 Z# Y2 o
- "recipient":"someoneelse'saddress",: a$ H& [! d% S8 ~# [- J$ E
- "amount":55 B2 x0 B1 r3 H
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:) k/ k! u7 a/ q) C- v+ j* e! B) S! D
- importhashlib
- importjson
- fromtextwrapimportdedent
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request! T. I" w6 |2 W+ j* E" i
- ...# R$ R {; T8 ` A& _
- @app.route('/transactions/new',methods=['POST'])6 ^' P$ v- q7 U
- defnew_transaction():
- values=request.get_json()
- #CheckthattherequiredfieldsareinthePOST'eddata# w4 D0 e X! ~2 r4 U9 E4 F
- required=['sender','recipient','amount']4 n2 [* C3 Y: c1 v3 Y8 x
- ifnotall(kinvaluesforkinrequired):- Z/ D/ t$ g. ?+ O
- return'Missingvalues',4005 a5 a$ F- e, w* a7 @$ _! H6 V( H
- #CreateanewTransaction
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}" Z; J2 e Z" e" m
- returnjsonify(response),201) m1 ^& b9 L9 g1 {. h
- ```
- -创建交易的方法-! r1 c% t0 N. F" @
- ###挖矿 t+ ^" ^4 y% s1 a
- 挖矿正是神奇所在,它很简单,做了一下三件事:6 {! j* }3 }, R5 ~/ t4 h
- 计算工作量证明PoW) l7 [6 s# W2 S$ k9 a3 ^0 L
- 通过新增一个交易授予矿工(自己)一个币* C6 K7 V }1 S
- 构造新区块并将其添加到链中- W# I' Q% D* x: D
- importhashlib
- importjson
- fromtimeimporttime+ B, G. X4 X% K* u1 @
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- …
- @app.route(’/mine’,methods=[‘GET’])' B5 n. n, |2 y i1 z6 v: k0 E
- defmine():
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.
- blockchain.new_transaction(9 P9 Y2 K& {+ K4 X
- sender="0",* Y3 _( k! ~- L
- recipient=node_identifier,
- amount=1,; U/ E) @2 t, c( d8 ]7 d
- )
- #ForgethenewBlockbyaddingittothechain
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)$ }5 l$ {9 s( _
- response={% @' P, L4 P1 L( D: G0 Z! v2 V* B
- 'message':"NewBlockForged", G: l* H8 s4 v5 F( W; \ {
- 'index':block['index'],
- 'transactions':block['transactions'],
- 'proof':block['proof'],7 Y3 D7 W. n. k& X4 [8 J
- 'previous_hash':block['previous_hash'],- Z6 O8 Y1 n* B# x
- }
- returnjsonify(response),200
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
三、运行区块链
你可以使用cURL或Postman去和API进行交互
启动server:% J) o! A3 P! H( \/ |
$pythonblockchain.py& k0 Z& y+ z$ j; E: h) U
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿: I3 ]0 `, F- b- V
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:2 Q, k+ r: r8 c! X7 X! Q9 }# Q
-使用Postman以创建一个POST请求-/ @) o7 W6 `- _" E" I
如果不是使用Postman,则用一下的cURL语句也是一样的:. w( y8 E9 M" v4 k/ f
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e",
"recipient":"someone-other-address",
"amount":5
}'"http://localhost:5000/transactions/new"8 z. A; `' _. Y7 ~* V
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:" P/ V; U8 u& v4 C8 @
- {
- "chain":[' X- V6 `7 h \7 }: u
- {
- "index":1,
- "previous_hash":1,
- "proof":100,
- "timestamp":1506280650.770839,' }( ?1 c. Q& |' R; E! \
- "transactions":[]% S8 [ @+ _# g- [
- },
- {+ \5 [! T" ^ ? i
- "index":2,
- "previous_hash":"c099bc...bfb7",. `5 T2 r7 i; Z0 v" f
- "proof":35293,
- "timestamp":1506280664.717925," G) t9 p. U. e% C
- "transactions":[ r8 p" e1 R9 d
- {
- "amount":1,- b! }8 h$ T: c% e* w. ]* i# B
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }
- ]5 p0 O s2 t3 p6 |: Q
- },
- {/ j, e! ]4 p4 j5 {! N) o% D
- "index":3,
- "previous_hash":"eff91a...10f2",# f# n1 e: v" j: @' V% r/ _
- "proof":35089,
- "timestamp":1506280666.1086972,
- "transactions":[; |* g1 Q! Y' H( }' @
- {9 j" P- H+ b; j' _- y. B- S! {
- "amount":1,/ U# S! `. p7 }& P4 O5 Y0 j& b
- "recipient":"8bbcb347e0634905b0cac7955bae152b", \( w6 _. W1 }
- "sender":"0"5 q8 L; `3 W' \2 B/ T
- }# O: [6 E% z- o
- ]
- }
- ]," |3 U( z% ?$ i4 M$ V C
- "length":3
- }
四、一致性(共识). S2 B. B" ?, U+ j6 O7 U
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
注册节点8 o+ {2 L2 O1 j+ G/ }+ j
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:1 H+ U: y- l8 K I7 E$ O* C
/nodes/register接收URL形式的新节点列表. h; ]' n1 X& l7 O
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:9 v7 @. z7 C4 }, U6 I# m
- ...2 t+ B7 I! f! G [1 n! }+ E- x
- fromurllib.parseimporturlparse! O# L- X% u* Z2 v- Z- Q
- ...( W- Q, q3 {7 X7 a' U* J
- classBlockchain(object):8 K C% c$ W# W6 Y+ r0 b& r+ J
- def__init__(self):5 U$ {3 m# `! u6 E& @! Y8 h
- ...
- self.nodes=set()
- ...
- defregister_node(self,address):# `7 [: w& {0 [2 J/ t# {) a
- """
- Addanewnodetothelistofnodes- C! U: O3 | A9 |- a1 t
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000') c) l& Y2 N% ~. x7 c
- :return:None
- """
- parsed_url=urlparse(address)
- self.nodes.add(parsed_url.netloc)% Z; c3 W; R' c
- ```" b1 f1 l1 G+ }: U- Y
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
- …# w6 I1 ?6 f8 G; |
- importrequests
- classBlockchain(object)4 `, R& R4 Z9 z( x: x
- …
- defvalid_chain(self,chain):
- """1 j& F G' K! l- @; T" h3 k2 o
- Determineifagivenblockchainisvalid/ V2 N& K4 W, C8 h
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot4 \ |/ u5 v, u* r
- """% X7 `! C+ p0 W) S
- last_block=chain[0]8 j( L0 u6 Q4 y9 ~" x
- current_index=1
- whilecurrent_indexmax_lengthandself.valid_chain(chain):7 W% z0 [% |# Z) ?& W; H
- max_length=length
- new_chain=chain
- #Replaceourchainifwediscoveredanew,validchainlongerthanours6 ?% |; C( `* N# E M2 \+ b
- ifnew_chain:/ h' e6 j M. x3 m1 X. y
- self.chain=new_chain
- returnTrue
- returnFalse8 G' G8 { m5 o& ]
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。6 P1 I- \3 w7 { N, |
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。' F+ {: Z" c Z% j
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。9 A. b, P. c( D$ S# j/ U1 D- M
- @app.route('/nodes/register',methods=['POST'])# c1 n6 h7 b' O1 R1 \
- defregister_nodes():
- values=request.get_json()
- nodes=values.get('nodes')
- ifnodesisNone:
- return"Error:Pleasesupplyavalidlistofnodes",400
- fornodeinnodes:1 v3 N1 V% p" ~" O
- blockchain.register_node(node)
- response={
- 'message':'Newnodeshavebeenadded',
- 'total_nodes':list(blockchain.nodes),1 P. L/ p1 Q1 O5 B' O8 p, T7 S
- }
- returnjsonify(response),201
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():
- replaced=blockchain.resolve_conflicts()( _' F2 E" G* [+ z
- ifreplaced:0 w2 ~4 W# P. I/ K) a- m+ ?
- response={9 ?# E& h$ R# |( t" {8 l
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain" V+ a' L% i$ k1 Y8 s
- }
- else:
- response={
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain
- }
- returnjsonify(response),200
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。: u* U6 ^. F7 O0 ^
: L* I6 \7 ?7 c6 S
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。