但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。7 O0 n. W J; C+ `) ^0 \+ `0 t
准备工作
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。( G7 y0 e a) a i5 v: U3 L
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:7 y* d9 B! {) D0 y; b& y0 Y
pipinstallFlask==0.12.2requests==2.18.4
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain r t- Q' E1 X- ^
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
BlockChain类
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:
- classBlockchain(object):
- def__init__(self):& l: d, q; _* j, x% {
- self.chain=[]
- self.current_transactions=[]
- defnew_block(self):* d8 W1 {9 t9 J( e" l8 D
- #CreatesanewBlockandaddsittothechain" D! E, ^2 E; | _5 ]4 @ y/ Q
- pass- I! P( v4 S! b8 o
- defnew_transaction(self):8 w9 e' Q% k' S9 w! n
- #Addsanewtransactiontothelistoftransactions
- pass* l- H ~- k) f9 @# E* m
- @staticmethod
- defhash(block):8 @. u0 g. a" U- U! A2 v* G! H# F
- #HashesaBlock
- pass+ C) b$ h1 r$ u5 P
- @property* u# H, o; A/ x) l
- deflast_block(self):2 d% f4 y* {) a. q& X$ w
- #ReturnsthelastBlockinthechain& s6 k X) p& l% @
- pass
-我们的区块链类的蓝图-
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。) Z8 h) v; D- r8 g* B% |' j
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。. k$ g! R" v7 w3 K( x; G6 U
以下是一个区块结构:
- block={- b: A* y; r- u. X" M' V
- 'index':1,0 p% Z6 \! M9 Y: ^" n
- 'timestamp':1506057125.900785,) ]( j. i) [9 o
- 'transactions':[
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",9 d5 s2 {1 t8 P1 w6 \& C
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",4 w6 g8 }2 H Q4 {4 ^' l
- 'amount':5,. w: X- E+ t% `/ R. _5 c3 |1 l
- }
- ],, C4 H: Q1 v5 O6 l* G1 `7 a" W7 |& C
- 'proof':324984774000,% L( {: j1 S$ o4 Y
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。. I' T8 |; h# H4 k* S
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易, l! ?. C, I1 h5 s) M- |7 Q
接下来我们需要添加一个交易,来完善下new_transaction()方法。3 |3 h5 @" e# {8 E, `2 g5 e
- classBlockchain(object):
- ...
- defnew_transaction(self,sender,recipient,amount):1 t7 M: r; n; x: c: O9 n$ l' U
- """" L, M8 R" O1 S2 ?; s
- 生成新交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount% R9 M) }1 _* v
- :return:TheindexoftheBlockthatwillholdthistransaction
- """9 ?# |! Q: G9 B
- self.current_transactions.append({$ z4 w e7 B; H
- 'sender':sender,
- 'recipient':recipient,
- 'amount':amount,
- })
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。 [+ J8 V& k' {% B7 z" h% i7 p
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。& [+ g: A* o- m+ x% `4 O2 ^
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法: m* F& c* z1 @- |# C
- importhashlib; p- h+ y0 X' }
- importjson& p7 o5 ^8 p) ^& r
- fromtimeimporttime' P+ ?& X9 j3 j* c9 `
- classBlockchain(object):
- def__init__(self):
- self.current_transactions=[]
- self.chain=[]
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100)! u( y `/ w# |# F p# I. f0 \
- defnew_block(self,proof,previous_hash=None):
- """
- 生成新块2 V4 M% L1 U/ }4 Z+ z
- :paramproof:TheproofgivenbytheProofofWorkalgorithm
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock" O- B! Y5 l- {. E8 U3 r
- """
- block={8 h+ W; o4 X2 J; [; |7 D
- 'index':len(self.chain)+1,7 A: U+ a) E- t: ?2 \7 T
- 'timestamp':time(),
- 'transactions':self.current_transactions,5 s1 F: o9 Z( n% U# ]8 R `$ i& d
- 'proof':proof,: e3 ~# h7 R% }
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),
- }
- #Resetthecurrentlistoftransactions
- self.current_transactions=[]
- self.chain.append(block)
- returnblock. p! L* g7 D& i- n
- defnew_transaction(self,sender,recipient,amount):5 D) H0 L% {3 g6 A
- """! e8 J6 X- q0 Q2 ]6 Y j
- 生成新的交易信息,信息将加入到下一个待挖的区块中, J8 A4 ^$ R3 W4 A, B9 l0 s+ g
- :paramsender:AddressoftheSender/ [! t: T* k( ^7 O# v5 y) x
- :paramrecipient:AddressoftheRecipient9 o1 d, q! L) d3 H8 W
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction4 u# r# P- K4 r$ p4 h
- """
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,) t: |* L. m2 {% x8 s% X
- 'amount':amount,
- })
- returnself.last_block['index']+1- O" H7 D0 }0 Z K
- @property! m2 l) `' F4 w7 H" } X' j3 e
- deflast_block(self):
- returnself.chain[-1]
- @staticmethod# A! V0 l3 p! Y1 D% h) d9 b
- defhash(block):' j! ]" U' I' L* {5 T, Z+ Y
- """0 ?/ P& c& L6 S3 E* @! A7 g5 u
- 生成块的SHA-256hash值) l0 m+ A$ h/ e" Q5 s# V2 r$ r" v7 O
- :paramblock:Block
- :return:
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes4 O ^/ h1 V4 x% X1 Y
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
为了方便理解,我们举个例子:
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:2 M* ^- _: C" R' Y9 V
- fromhashlibimportsha256
- x=5! u% h! @3 E* H
- y=0#y未知! j5 Z, t1 d2 |; J- l8 L
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
- y+=1
- print(f'Thesolutionisy={y}')
结果y=21,因为:( P7 w7 b3 Z# q/ Y
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
当然,在网络上非常容易验证这个结果。" R5 |8 S) d5 s; ]; S$ }1 @. u
实现工作量证明
让我们来实现一个相似PoW算法,规则是:3 Z+ e- S. C# p" e9 i0 H' i+ v, `
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
- importhashlib
- importjson! `9 I( Q5 Q r# `& w. n
- fromtimeimporttime
- fromuuidimportuuid49 Y/ Q: ]; v0 F- H) w. Z
- classBlockchain(object):
- ...
- defproof_of_work(self,last_proof):
- """3 j, h: g: d' Q3 R
- 简单的工作量证明:0 Q9 q* j0 r. ]5 W
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明3 [" X* P; K$ v s2 W6 C
- :paramlast_proof:3 Y# e+ E1 w& X: |5 G5 x
- :return:
- """. _9 w5 g+ k7 L: d7 ^
- proof=07 l3 Q/ p3 f, @0 O. x* W4 {
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=1
- returnproof
- @staticmethod
- defvalid_proof(last_proof,proof):
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?: X. k- x o- O) t1 q) l
- :paramlast_proof:PreviousProof$ k$ m. C) {: h& Q- h# g4 N6 g
- :paramproof:CurrentProof) O9 a2 _8 Q6 _! f, R% m6 I$ f
- :return:Trueifcorrect,Falseifnot.
- """
- guess=f'{last_proof}{proof}'.encode()& p" L+ y$ E% g0 }% j/ F
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000"1 J: ^; V: d/ h7 i1 C4 |0 t
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。 ~% ?- \5 }/ [) H, _; D
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。2 G5 y4 o: l% r: P8 i& x4 C
##二、BlockChain作为API接口% [0 A% o4 a! {( v5 F E+ U5 o7 g8 ?
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。) `$ q1 g: w- j" ~: q$ w
我们将创建三个接口:
- ```/transactions/new```创建一个交易并添加到区块 |- `7 `6 L) R5 r& i0 _8 s
- ```/mine```告诉服务器去挖掘新的区块1 }) e, [" b9 H/ Q5 K
- ```/chain```返回整个区块链1 |/ Q+ C8 a c( H" a/ K
- ###创建节点9 e m+ ^2 ]6 }# @
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
- importhashlib
- importjson9 j5 a. H# F" J9 G% j- Z, ~- h
- fromtextwrapimportdedent
- fromtimeimporttime
- fromuuidimportuuid4$ w% s# c5 g0 j3 d8 P- f1 v* l
- fromflaskimportFlask
- classBlockchain(object):
- …* ?4 P% c Y7 k
- InstantiateourNode
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode
- node_identifier=str(uuid4()).replace(’-’,‘’)4 [2 W& K O; p3 ^' ~' m5 [" u9 z
- InstantiatetheBlockchain$ I. {/ C$ b U9 p
- blockchain=Blockchain()
- @app.route(’/mine’,methods=[‘GET’])& t, b* m8 k/ n0 C( E" d8 O4 g
- defmine():
- return“We’llmineanewBlock”
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():
- return“We’lladdanewtransaction”0 g% w: E" ?6 @; O7 \+ {
- @app.route(’/chain’,methods=[‘GET’]); J4 \' r w! A7 T' ~" ^' G
- deffull_chain():6 @7 w! K7 a6 q' ?7 {1 c
- response={# I6 n6 S+ G5 m9 M) B2 u3 ?
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),
- }
- returnjsonify(response),200 I: g' q3 d7 B2 e- `3 W
- ifname==‘main’:
- app.run(host=‘0.0.0.0’,port=5000)! n5 ?. }# i p& @- a7 l
- ```
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。
- 第18行:为节点创建一个随机的名字。
- 第21行:实例Blockchain类。* l" \' m( L* Z% d$ t1 s
- 第24–26行:创建/mineGET接口。
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。/ P$ J% b0 ^8 q5 e% H
- 第32–38行:创建/chain接口,返回整个区块链。
- 第40–41行:服务运行在端口5000上。& q7 M' ]0 Q# p
- 发送交易
- 发送到节点的交易数据结构如下:: [' M% B2 {4 Q
- {
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",6 _, q* Z5 @9 j1 `* i; k' B
- "amount":5
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:( W. S& C h- a
- importhashlib: Z, {2 r$ d7 u1 f6 y7 d
- importjson9 e+ J" A+ u, ] a
- fromtextwrapimportdedent
- fromtimeimporttime8 s' R0 A' f3 y0 j! B" m
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- ...
- @app.route('/transactions/new',methods=['POST'])
- defnew_transaction():
- values=request.get_json()$ x# I6 r3 g* Y+ O) T
- #CheckthattherequiredfieldsareinthePOST'eddata) |: ^% K$ F" \ |2 Z& V$ D
- required=['sender','recipient','amount']
- ifnotall(kinvaluesforkinrequired):
- return'Missingvalues',4004 {. Z" g4 M5 ^4 O# m3 b
- #CreateanewTransaction4 a' h" F$ k2 \* u y9 k
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount']): x$ W0 F) o; P
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}
- returnjsonify(response),2010 X0 O+ g; _. i) \
- ```9 o6 v0 H$ T, V
- -创建交易的方法-+ v6 I, q3 c$ x+ F' z
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW1 U# k. z0 E0 a: D- a" |
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中* `' P! N. g" [: N& Z( N) o
- importhashlib
- importjson
- fromtimeimporttime6 m, J; R( c$ E% j9 l; m
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- …' I7 Z. O' @0 I9 b; N. g
- @app.route(’/mine’,methods=[‘GET’])
- defmine():$ m! ~2 r3 ]0 O2 Y
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block+ [- D1 y" j) A7 d+ H4 [4 n
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.
- blockchain.new_transaction(
- sender="0",: b& x0 A$ ~% j9 u7 h6 w8 Q
- recipient=node_identifier,0 i- E1 w, P6 z: `4 c( u/ u, f
- amount=1,
- )
- #ForgethenewBlockbyaddingittothechain& K& |9 E% U ^( V# z6 B
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)
- response={
- 'message':"NewBlockForged", w& E- ^: f6 u
- 'index':block['index'],
- 'transactions':block['transactions'],
- 'proof':block['proof'],6 @8 s+ _# Y4 h
- 'previous_hash':block['previous_hash'],# P( o4 e9 j4 e6 `9 D
- }% O8 j" n6 k% K+ |! `) ^' |
- returnjsonify(response),200
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
三、运行区块链5 T# f" g1 G1 d( R) o7 o6 i" u
你可以使用cURL或Postman去和API进行交互
启动server:
$pythonblockchain.py! I& o- ^! ?; k; Q
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit) b8 n. ~2 A: K1 g4 O
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:/ D: [! J6 e" D/ Z! _2 N. a
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
-使用Postman以创建一个POST请求-
如果不是使用Postman,则用一下的cURL语句也是一样的:
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e",; G& q+ b) O- e$ z( G M; H
"recipient":"someone-other-address",7 ^, Z# ]+ A6 }9 D6 W8 F
"amount":5
}'"http://localhost:5000/transactions/new"# `6 G8 M% ~; q; d
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:0 o6 t: s2 _% C6 _
- {8 C% R* ~1 m5 F
- "chain":[
- {
- "index":1,
- "previous_hash":1,
- "proof":100,
- "timestamp":1506280650.770839,9 {0 x' x1 g# F* x+ S: _) f; O
- "transactions":[]
- },
- {# S& A$ i* `% d7 z
- "index":2,$ d* o; S- N' l: a: I% \% `
- "previous_hash":"c099bc...bfb7",. |* k) m, ] S% J
- "proof":35293,* |8 W) i" x* B; h9 F+ f7 i$ X
- "timestamp":1506280664.717925,
- "transactions":[
- {
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",+ I7 @7 ^) B6 X9 l7 {; |
- "sender":"0"
- }
- ]. y# ~; X" y6 t( g8 |; h4 i
- },
- {( ~( O! b1 {4 {, O9 h
- "index":3,3 F; Q3 a' a4 H* K
- "previous_hash":"eff91a...10f2",. G( b/ ^' c, {7 d* u
- "proof":35089,
- "timestamp":1506280666.1086972,' m( ?, c- U4 F5 g
- "transactions":[# d7 O' k2 I& a) q
- {/ h# `1 T9 Q7 R2 _; m
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }' ~ y* U3 Z! O) u8 e* y/ l
- ]
- }
- ],( j$ Z# K) V+ R4 Y
- "length":3# w# _# x. B; I1 c
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
注册节点
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:3 y) {, c1 b# {) \% F/ a& B
/nodes/register接收URL形式的新节点列表
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:8 Y) M7 p# m% w, [& m/ p$ b# }% }$ j
- ...
- fromurllib.parseimporturlparse
- ...
- classBlockchain(object):: L8 X7 @1 o* a' n9 X& e2 {
- def__init__(self):0 ]5 m( I. M$ F% a7 J0 f
- ...- J/ H1 r+ z# v! Z$ z' A# Y9 @
- self.nodes=set() w D3 L3 C6 _5 S7 P$ l+ \& V
- ..., Y% @3 _) B( I
- defregister_node(self,address):
- """# {+ @: `" L5 N
- Addanewnodetothelistofnodes
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None* L* x6 _# F' G% S! F2 T: N
- """; Y$ W5 x# y4 R. m
- parsed_url=urlparse(address)% a, n! L; d0 @- g- w5 [
- self.nodes.add(parsed_url.netloc)
- ```1 I( [$ X' I8 y
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。3 ]3 _0 V4 d7 Y
- ###实现共识算法& s `7 m. [: B
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
- …! a" b$ b; g |9 A+ S2 `( U4 Y
- importrequests$ _! E3 C- z/ f
- classBlockchain(object)! P% z/ u& j+ x" Y2 _6 l& h
- …/ A2 b4 `8 l9 Q% d
- defvalid_chain(self,chain):
- """) D* A. l3 {7 T& p5 A8 }
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain* z9 h+ x% F" O$ Z
- :return:Trueifvalid,Falseifnot2 ~! J. r; V# w! G4 a
- """* \8 m" L5 T! r
- last_block=chain[0]) w5 w3 Q( c' Z% u( @, o( v% U
- current_index=1 z7 [' E. o2 J+ ^4 Q
- whilecurrent_indexmax_lengthandself.valid_chain(chain):$ l1 M4 U( F3 J
- max_length=length
- new_chain=chain, t' F8 d% ]- H( C: [. A
- #Replaceourchainifwediscoveredanew,validchainlongerthanours) c1 ^# Y- z- k7 R c( y- u# z
- ifnew_chain:3 W3 t7 Z- Z+ O1 @
- self.chain=new_chain
- returnTrue
- returnFalse: ~, {- h q, q; O8 \
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。: [" K5 \- [9 C' o0 a% _
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。8 F3 x" H1 M6 b
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes():0 c% g- w3 O, z- G! w0 f
- values=request.get_json()5 {/ p3 T9 ]! [' K
- nodes=values.get('nodes')3 b) q+ F+ J4 l
- ifnodesisNone:. J: |6 _( e$ |* @$ e$ l+ F
- return"Error:Pleasesupplyavalidlistofnodes",4005 }3 w4 R& S0 F l& F+ y) n
- fornodeinnodes:7 k; f* A7 q+ b( R8 C
- blockchain.register_node(node)
- response={: W* l2 p2 Q& a' D: L# l0 r' A
- 'message':'Newnodeshavebeenadded',
- 'total_nodes':list(blockchain.nodes),, g2 \( s) U1 K, s
- }
- returnjsonify(response),2018 R* s9 g; R- g) G- u
- @app.route('/nodes/resolve',methods=['GET'])! L& T) Q. g1 X z* l: F* k! B! \
- defconsensus():9 u. l* J! P! O
- replaced=blockchain.resolve_conflicts(). S7 R0 |' \) a
- ifreplaced:" ^5 Y5 C8 Y5 J- u+ c6 B" _& ~
- response={4 d2 q+ |& j( y" O P5 u6 ^) @7 ]
- 'message':'Ourchainwasreplaced',! C- A$ e7 ?3 Y5 k
- 'new_chain':blockchain.chain0 t; b2 A8 I j5 d z
- }
- else:$ y& \2 K0 s) @! ] ^0 {' y
- response={( F* F& h) [! o g! Y& N3 J
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain
- }2 I% I; h' B* D# E6 W
- returnjsonify(response),200; \3 U X5 f& q) @9 B( P# p0 v( W
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
-注册一个新的节点-: I( d% A5 P, \2 w
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。