但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。8 |: V$ M4 C* u1 w3 ~
准备工作& U a( L; P+ b: `7 V5 r% H8 z
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。$ b p: j- p8 B" B( u) ^" y
如果你不知道哈希值是什么,这里有一个解释。) P3 c7 U: R& x3 v% ^# k
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。 t8 h5 G( \/ G# [" }
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
pipinstallFlask==0.12.2requests==2.18.4
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。% O' Q* a2 C6 ?, S
BlockChain类7 m* K8 G1 |3 r9 h
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:
- classBlockchain(object):
- def__init__(self):/ K b$ {5 W9 j8 I* o
- self.chain=[]
- self.current_transactions=[]8 D, z4 U |3 p0 j( A& I
- defnew_block(self):
- #CreatesanewBlockandaddsittothechain
- pass
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions1 o" @# c, q, t
- pass
- @staticmethod5 D& `2 R; e. C$ x9 y* ^& I
- defhash(block):, ?+ X0 \/ k% u4 ^/ }) d
- #HashesaBlock; }3 I" M6 j2 L7 F% e* B. L; L
- pass
- @property+ F8 ]+ m/ R$ x8 W5 b& L2 Z/ ?+ z2 O
- deflast_block(self):
- #ReturnsthelastBlockinthechain# j' F9 D p6 z4 b
- pass
-我们的区块链类的蓝图-3 _3 r" U% E2 b
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。! V: g( ?. ?" Q. D
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。
以下是一个区块结构:1 H; H R; F* k( F: x5 M t/ Q% N
- block={
- 'index':1,
- 'timestamp':1506057125.900785,3 i0 ^0 x+ K1 A( K6 _
- 'transactions':[" q, x2 D% v- h
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",, G8 \, Z O% |. G+ G; d
- 'amount':5,
- }8 ~; S- \: v8 l' E
- ],& q5 ?! {+ S5 A& l
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。' P y5 p; @% Y0 f$ ?7 {
加入交易. Y. ~! I8 R w& a& r7 W' w2 g9 Q
接下来我们需要添加一个交易,来完善下new_transaction()方法。3 R5 ^* A; _+ ]
- classBlockchain(object):
- ...$ E. d* j& {9 A6 K
- defnew_transaction(self,sender,recipient,amount):3 R# q; L5 |3 s) v* O H8 s# ]
- """
- 生成新交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount- M6 u/ `: @* ]3 h `, f3 I: }
- :return:TheindexoftheBlockthatwillholdthistransaction
- """. @ `: @, B! o, Y/ c' g
- self.current_transactions.append({
- 'sender':sender,4 u+ }2 {4 X$ \# n
- 'recipient':recipient,
- 'amount':amount,
- })
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块5 R8 f4 }+ A1 T5 ~2 Q4 E/ C
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。+ v4 i3 b$ V+ }8 A4 M- k
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。1 j9 i7 Z$ s: e% _3 ~, F, V
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:; Q* o: a3 e* A, p& D
- importhashlib
- importjson* q1 F: Y* t. L3 f: J$ n: y. z
- fromtimeimporttime
- classBlockchain(object):& @% B9 g2 ]9 u* t
- def__init__(self):
- self.current_transactions=[]5 e' `% a" J# A
- self.chain=[]
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100)
- defnew_block(self,proof,previous_hash=None):, _5 r2 `7 K8 K% F3 o
- """
- 生成新块0 w% i1 }0 |- P
- :paramproof:TheproofgivenbytheProofofWorkalgorithm/ X% i2 M s( j+ K q) y
- :paramprevious_hash:(Optional)HashofpreviousBlock. `- ]* a8 w/ Z0 N8 C0 X
- :return:NewBlock
- """: v x: b, f$ h' k) ~- w
- block={
- 'index':len(self.chain)+1,
- 'timestamp':time(),2 u- b9 A4 V( o$ l9 X0 H4 M+ }
- 'transactions':self.current_transactions,3 o. e1 o T3 B& c
- 'proof':proof,
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),
- }# _ [/ Y1 y; e3 J
- #Resetthecurrentlistoftransactions+ t7 m% I$ N% I: b$ i! N# ^
- self.current_transactions=[]
- self.chain.append(block)
- returnblock
- defnew_transaction(self,sender,recipient,amount):
- """
- 生成新的交易信息,信息将加入到下一个待挖的区块中4 Z0 K5 n: X) Y/ J8 `0 N& n3 b
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount" l9 _/ z" G) F
- :return:TheindexoftheBlockthatwillholdthistransaction, l8 H5 s, ~7 A+ i% \ m1 ]
- """
- self.current_transactions.append({" B5 `3 I) [0 z C1 b
- 'sender':sender,
- 'recipient':recipient,
- 'amount':amount,9 w/ ` M- x, H8 g! `
- })
- returnself.last_block['index']+1; ~) o* V9 E, E# Y
- @property
- deflast_block(self):5 o: z& q) f% e- @
- returnself.chain[-1]8 V' F, O% r5 M1 o- k
- @staticmethod8 x0 ?: o: K6 O2 G# N
- defhash(block):! s- s( X, J4 b4 g9 V% u
- """# |5 F J/ n' Z$ B+ b
- 生成块的SHA-256hash值
- :paramblock:Block7 R, \ ^2 I5 Q. O8 V; j$ B$ a( n1 q
- :return:; c, D/ D* m9 n8 I: M( o" T$ y+ c
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes' D9 p# K4 v0 v& w
block_string=json.dumps(block,sort_keys=True).encode()) K0 G5 C1 O6 j6 [5 n( j
returnhashlib.sha256(block_string).hexdigest()
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。3 D+ Y) `# b# N7 x0 m6 `9 |: R
为了方便理解,我们举个例子:1 [- z! V n0 d
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
- fromhashlibimportsha256
- x=5
- y=0#y未知: I" D6 o6 f0 B4 H3 l. K
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
- y+=1
- print(f'Thesolutionisy={y}')
结果y=21,因为:
hash(5*21)=1253e9373e...5e3600155e8604 H2 Y$ }1 x8 {" ]5 W5 ^
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。2 \6 a1 e# E Q! l) S- Y2 W5 O9 t
当然,在网络上非常容易验证这个结果。
实现工作量证明
让我们来实现一个相似PoW算法,规则是:2 H0 p2 g5 L/ \9 ~7 M' R
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
- importhashlib9 x" S) ^0 x8 R7 X% P
- importjson
- fromtimeimporttime
- fromuuidimportuuid4
- classBlockchain(object):. h- d" p1 |, ]0 n1 F8 O' q* |4 V
- ...
- defproof_of_work(self,last_proof):
- """% d' d1 Y" g6 M# x7 i- s
- 简单的工作量证明:
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明, n& ~% I) g: V
- :paramlast_proof:8 w' J" x) X/ j
- :return:6 b. e+ |( L- v! ]& D
- """
- proof=0% W g. ^& p7 K L' c0 F
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=1 e# |. b: B" U! d; b; i* l- M
- returnproof6 I/ Q3 B2 {8 c6 C2 L) A. m
- @staticmethod
- defvalid_proof(last_proof,proof):
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?
- :paramlast_proof:PreviousProof
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot.; V5 f. N9 m- }4 U7 r$ f+ g
- """( h: Y! R2 {, q M: J8 m
- guess=f'{last_proof}{proof}'.encode(); s" Z' G3 C* X' X+ I
- guess_hash=hashlib.sha256(guess).hexdigest()6 T0 H8 [& J4 B* [, L6 g% m
- returnguess_hash[:4]=="0000"& h# y8 U% P- F y+ w: V, e1 I
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。6 y& v/ \0 \* L# C9 g6 s
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
我们将创建三个接口:+ c& i3 F* m3 q2 D( z" s
- ```/transactions/new```创建一个交易并添加到区块
- ```/mine```告诉服务器去挖掘新的区块" b2 p( H) n5 g4 k4 z; H' @! D7 |
- ```/chain```返回整个区块链
- ###创建节点
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:9 Q7 @$ [0 `$ l7 K0 d- I& b
- importhashlib
- importjson$ e9 r4 |. ` S2 G+ o1 ~
- fromtextwrapimportdedent( |& F& K2 M' I$ r; ]/ q
- fromtimeimporttime
- fromuuidimportuuid4 ^0 L7 P6 `0 ^% ^! a) [( K& H# p
- fromflaskimportFlask
- classBlockchain(object):
- …3 I+ }6 F- e2 h6 d
- InstantiateourNode
- app=Flask(name)2 T8 e3 a7 Y7 B1 x
- Generateagloballyuniqueaddressforthisnode8 l+ O6 H+ l u* T! j- q$ J* c( U
- node_identifier=str(uuid4()).replace(’-’,‘’)
- InstantiatetheBlockchain
- blockchain=Blockchain()' j$ j+ [! ?; m0 V! _: `) R: g+ ], |
- @app.route(’/mine’,methods=[‘GET’])
- defmine():& ~6 {! K8 |3 a* a# b
- return“We’llmineanewBlock”
- @app.route(’/transactions/new’,methods=[‘POST’])5 {; Z" P7 G: f0 Q% F5 _$ u
- defnew_transaction():) s& i& P- i( n# f8 t- k0 y% n' F
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])
- deffull_chain():" S; e* I6 [! X2 D& A
- response={
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),
- }1 f; N- c: x, }' J+ u5 I
- returnjsonify(response),200
- ifname==‘main’:4 M2 r3 [& z; x+ t. m
- app.run(host=‘0.0.0.0’,port=5000)6 E# m' z: k: X: T( L3 [" X5 G" ]
- ```8 \1 r5 \7 h) i1 [) p
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。, {( ]$ f: D: Y9 n7 u7 k+ E! C# i
- 第18行:为节点创建一个随机的名字。! c' x9 P& m$ y6 d2 u' I
- 第21行:实例Blockchain类。/ V2 N& S" E1 m& u+ y
- 第24–26行:创建/mineGET接口。
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。+ ?. [ J2 N; M' @, v6 z# [! X
- 第32–38行:创建/chain接口,返回整个区块链。
- 第40–41行:服务运行在端口5000上。+ A3 A( O! y, B3 L
- 发送交易6 Q( z3 _# {2 V- c
- 发送到节点的交易数据结构如下:/ u+ ^- O) C" d* ~1 |) {" q( F
- {
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",' w8 U$ z; z, I! G: l) g
- "amount":5
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:( r3 F0 t, J1 x9 w5 x
- importhashlib
- importjson
- fromtextwrapimportdedent/ V& Q8 c U# \4 U
- fromtimeimporttime/ x# [# T8 @$ u* [
- fromuuidimportuuid4, ]& v# o" ?/ @8 e* e
- fromflaskimportFlask,jsonify,request4 }6 t6 a$ U# J5 r; J/ ?" c
- ...
- @app.route('/transactions/new',methods=['POST'])* x# P6 u* j$ m2 h; e: ^0 m
- defnew_transaction():, j: B4 F0 u {/ t* u2 R% ]) T' [
- values=request.get_json()/ _. K0 r" M) U4 O6 H3 z
- #CheckthattherequiredfieldsareinthePOST'eddata
- required=['sender','recipient','amount']) \8 L3 S. U: W
- ifnotall(kinvaluesforkinrequired):6 l& z, x. {1 [7 V4 U$ ~3 o
- return'Missingvalues',400) n1 j; v" F! G
- #CreateanewTransaction. s0 S0 n$ t7 j
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])8 p5 ~& i, L. a) Q' _3 P
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}
- returnjsonify(response),201
- ```
- -创建交易的方法-
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:; \6 K# V% ^4 u0 ?9 }- K
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
- importhashlib4 D' d- [& T, w" F% b; C. F. |
- importjson. @! u6 |6 d( Q2 {! w
- fromtimeimporttime
- fromuuidimportuuid4 O! m, R# |* |! l1 G! d( D. S
- fromflaskimportFlask,jsonify,request
- …( w# h ]6 p# x: Z# x* u2 y
- @app.route(’/mine’,methods=[‘GET’])
- defmine():
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block; Y4 t9 x! k/ _) h' c# v% @- K
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.
- blockchain.new_transaction(
- sender="0",1 }" y8 v) c5 R9 V2 `
- recipient=node_identifier,
- amount=1,/ K$ n) f( }$ P2 t: \9 A) C+ W! L
- )0 w) F5 J, q, y5 x
- #ForgethenewBlockbyaddingittothechain
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)) g0 q- M. {& j5 A
- response={
- 'message':"NewBlockForged",
- 'index':block['index'],
- 'transactions':block['transactions'],
- 'proof':block['proof'], b# ^, b. \* p/ K3 e4 p+ R6 t
- 'previous_hash':block['previous_hash'],
- }! d5 ]% T" j7 _
- returnjsonify(response),2005 R+ m# A% K X8 P
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
三、运行区块链8 F$ K. M$ @+ k# ?7 o2 ]
你可以使用cURL或Postman去和API进行交互2 a7 z# p5 x! O: K
启动server:9 H0 C& i! H( w/ _& h* t( o
$pythonblockchain.py+ Y& O8 T2 Q- j+ f
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
-使用Postman以创建一个POST请求-
如果不是使用Postman,则用一下的cURL语句也是一样的:/ ~# j2 P2 f$ W* ?
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e",: c$ N! \8 f* S& M# `0 z
"recipient":"someone-other-address",
"amount":55 S4 D/ ~( n m+ [
}'"http://localhost:5000/transactions/new"
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
- {7 y8 {+ B* _5 m9 O
- "chain":[
- {5 X! e7 b6 Z5 ]. c( l0 w5 B. g2 C/ M
- "index":1,
- "previous_hash":1,
- "proof":100,
- "timestamp":1506280650.770839,3 o* Y$ ^: k. R! k
- "transactions":[]/ ^8 `* Y$ O8 D1 Y& k
- },; ^& Q2 D5 m q, S1 w, _
- {
- "index":2,' l) P4 X9 j5 y% F3 k7 n* Z( l
- "previous_hash":"c099bc...bfb7",
- "proof":35293,
- "timestamp":1506280664.717925,
- "transactions":[, X; r. v! P/ l# I/ F+ G
- {
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }
- ]
- },. ]/ N! n/ L- ]/ D1 B( a
- {& s+ F& o; h0 S) o- f9 u. O6 J
- "index":3,
- "previous_hash":"eff91a...10f2",4 I5 p |/ {. Q% `9 Q' \- t1 t" r
- "proof":35089,2 [; ]! s2 E$ v
- "timestamp":1506280666.1086972,
- "transactions":[( m8 ?' j. R) h; k- E3 T
- {9 u: D* m- d; U5 n
- "amount":1,( G& D* b! N# n! b
- "recipient":"8bbcb347e0634905b0cac7955bae152b",& a* k' p3 }: U s8 q1 g
- "sender":"0"
- }
- ]
- }! _% F7 d$ F$ a9 D! j/ P* R! ]
- ],3 Y' d+ E, E8 e7 F4 p
- "length":35 \$ b4 I m1 N% ?6 d' i
- }
四、一致性(共识); i( x: K" B/ W% {0 s
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
注册节点# g: V" a1 y k$ u1 K+ e! {6 A
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
/nodes/register接收URL形式的新节点列表; I' w7 X6 X) H) P: s$ o6 Q+ W
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:, a0 y3 H- Y3 R1 m& J
- ...
- fromurllib.parseimporturlparse- E1 t/ |8 S6 a$ h+ t- f% E
- ...& ^2 G% V. r8 i
- classBlockchain(object):6 A! K* K6 _2 j0 k( | A, ?9 v/ V
- def__init__(self):
- ...* U9 Y* B5 D% I
- self.nodes=set()
- ..., h6 r0 R) t6 h8 c7 e4 A' N; t
- defregister_node(self,address):* N5 s6 O2 z# p0 o
- """2 k" r# y- _2 K- l1 ^
- Addanewnodetothelistofnodes
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None
- """
- parsed_url=urlparse(address)
- self.nodes.add(parsed_url.netloc)
- ```
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。, k% d W1 `$ c6 q' a0 s
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。" p4 I5 [# |3 h2 z% S# z9 ]
- …
- importrequests
- classBlockchain(object)
- …
- defvalid_chain(self,chain):
- """
- Determineifagivenblockchainisvalid; P% A, w4 q8 [+ J3 n9 B
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot
- """5 ^0 `+ _8 o: W6 ^& R, o$ E% W
- last_block=chain[0]
- current_index=11 F3 E& w$ T1 v& A! w6 ^# s
- whilecurrent_indexmax_lengthandself.valid_chain(chain):% ]3 n$ |0 o+ V2 t# k
- max_length=length
- new_chain=chain- ]2 P- k; B5 c& ~
- #Replaceourchainifwediscoveredanew,validchainlongerthanours
- ifnew_chain:% k/ c2 B; c" _
- self.chain=new_chain
- returnTrue7 {& n8 b5 o& l- j" Z
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。+ ^4 G' F- b: ?
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。0 M) d( g* x) W+ _9 H8 T. x
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。! y$ a+ u2 h; Y( Z' i% @
- @app.route('/nodes/register',methods=['POST']). n( C/ i" @, _9 r+ n
- defregister_nodes():. J$ N; x& C1 O% j* v* {# @% T6 ]1 w
- values=request.get_json()) _1 `* |6 l/ r8 {5 r* V
- nodes=values.get('nodes')6 Z( \) o1 n/ T2 B5 l, a
- ifnodesisNone:9 m! E0 v. e" t0 d
- return"Error:Pleasesupplyavalidlistofnodes",400' q/ E! E6 W0 g R3 u
- fornodeinnodes:# v$ G( R7 Q4 H
- blockchain.register_node(node)% N' ?( z5 n8 s+ _
- response={
- 'message':'Newnodeshavebeenadded',3 J( R& ~" D$ p2 x- K
- 'total_nodes':list(blockchain.nodes),
- }+ t3 l5 W, ?* ^6 P
- returnjsonify(response),201
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():
- replaced=blockchain.resolve_conflicts()& b% i5 h' s, _) I: f
- ifreplaced:% M) s) Z7 i( F" X$ C& D
- response={' ^+ @; r) K" y+ l
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain+ a& o& Z2 `3 o" |: L2 ^( f
- }' ^' `2 f) C& K: o* H( D: R/ A
- else:
- response={) I6 Z) C9 o9 W
- 'message':'Ourchainisauthoritative',( O4 x( d7 {! K K- O' Y
- 'chain':blockchain.chain
- }5 ~/ g }: s9 N1 j. E0 |. D
- returnjsonify(response),200
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。3 R9 c' {) @& H) n$ @
# Q5 t; ^9 k2 v! w" o! u T
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。