但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
准备工作" f W. F* q5 r/ U. C* B% I
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。: X& g+ C+ u7 m: F; v! [7 M! {
如果你不知道哈希值是什么,这里有一个解释。! S) B( r8 O# _( X& a# j
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
pipinstallFlask==0.12.2requests==2.18.4
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。! ^$ n0 ^2 b! ?/ p* N: g, r
一、开始创建BlockChain% o) K9 B8 P( F6 K. c
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。- g3 {7 H% `5 b# q- V
BlockChain类, @, o$ F; _. {0 P' ?8 S
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:( w p3 X$ Z4 ]2 q2 e2 ~
- classBlockchain(object):- ^7 {1 l+ k) N! V2 m% m* b
- def__init__(self):
- self.chain=[]) |; h1 F7 p1 c& `" f- l
- self.current_transactions=[]4 { k! R2 B0 I& n
- defnew_block(self):
- #CreatesanewBlockandaddsittothechain
- pass
- defnew_transaction(self):( K$ y' t9 ]2 A" a1 } j
- #Addsanewtransactiontothelistoftransactions$ f7 E$ F! l+ ^1 ^3 K
- pass
- @staticmethod
- defhash(block):& }5 @3 s0 [3 T4 ]0 u
- #HashesaBlock1 Q! O9 o" W6 G2 [2 [9 r" m9 i
- pass, l5 S; o6 t! [
- @property
- deflast_block(self):) A5 |! k2 G* _; B
- #ReturnsthelastBlockinthechain9 p. N/ k! ^) t; f& Z' ^& `6 U, P
- pass
-我们的区块链类的蓝图-" Q. K7 ^* L/ l+ I5 ~6 r
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
块结构2 o9 w$ _1 L" g; d1 C
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。/ \- \7 U& j, m1 F
以下是一个区块结构:
- block={
- 'index':1,
- 'timestamp':1506057125.900785,1 m/ ?9 P/ `1 Y3 i
- 'transactions':[) L- W8 C. E" m! I O4 v9 N
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
- 'amount':5,+ s) o" |7 f% h
- }# t, c0 B: k: C! `4 ^
- ],
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"3 X w; e" ]: u/ {) [' L+ q3 S7 x
- }
-链上一个区块的例子-( s6 G, A0 J9 m5 T; w% ?
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易+ c0 ?6 c6 e0 U) ]' }
接下来我们需要添加一个交易,来完善下new_transaction()方法。
- classBlockchain(object):
- ...% I9 L6 P# c6 `8 H$ g, D# C
- defnew_transaction(self,sender,recipient,amount):
- """
- 生成新交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender _" X6 q |8 h$ r6 Q' k. U( T& s& h
- :paramrecipient:AddressoftheRecipient. h8 [5 B- w; z V+ _
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction
- """+ I/ R4 K1 a; d
- self.current_transactions.append({
- 'sender':sender,8 \0 p9 F" c: Z( x& ]
- 'recipient':recipient,
- 'amount':amount,, ~, f2 D$ @5 j9 w; b* O
- })- @ g4 L8 c, r- z6 c1 B9 w
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。6 B' U" \/ D3 t+ S
创建区块8 Z2 R$ ~/ O% H c1 f
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。' N* X; M) D" h5 p
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。& |% j1 Q) Q1 j# \
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib! g2 g, f2 R. B; s! c5 Y
- importjson a' [9 I9 n$ j+ E0 B: A
- fromtimeimporttime
- classBlockchain(object):& W: A2 m6 C6 M' I% g
- def__init__(self):
- self.current_transactions=[]" J$ }( E# D2 G
- self.chain=[]
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100): v' I( U; r1 G# W, E2 p
- defnew_block(self,proof,previous_hash=None):% m/ B& ^7 O- f8 A8 W
- """
- 生成新块
- :paramproof:TheproofgivenbytheProofofWorkalgorithm! }1 e k) {8 F8 E
- :paramprevious_hash:(Optional)HashofpreviousBlock/ B. l" g. ~4 [
- :return:NewBlock1 [* c9 N" n% G# E" Q, q |
- """' F+ y, X& v, b5 m2 D& M& s& O' F
- block={
- 'index':len(self.chain)+1,
- 'timestamp':time(),- a5 n6 C! S2 M0 z6 I. r
- 'transactions':self.current_transactions,6 F9 A: D1 {3 @3 ^. n! l. y; }$ j: T. ^1 d
- 'proof':proof,
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),- q t X& W7 f7 o: J1 p6 }4 `
- }
- #Resetthecurrentlistoftransactions
- self.current_transactions=[]
- self.chain.append(block); x) h! i* }! x& \
- returnblock9 C* O" F0 _" y! r
- defnew_transaction(self,sender,recipient,amount):
- """/ y' h0 V+ a( j4 ^
- 生成新的交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction0 {0 ?. d* ~, v, e3 I5 j
- """
- self.current_transactions.append({% {! b7 v* m Y! w% v
- 'sender':sender,3 _6 f. r9 J9 _ H( C
- 'recipient':recipient,* `8 g- c* Z# I' b1 b5 n& @
- 'amount':amount,
- })9 {: _( r2 A3 J* w% F9 K' @
- returnself.last_block['index']+1; W0 n D4 @+ V' G3 t5 `
- @property
- deflast_block(self):
- returnself.chain[-1]$ g/ U5 Y- y8 G. G# t# c/ F- Y
- @staticmethod
- defhash(block):
- """+ F, J; _0 T s4 U
- 生成块的SHA-256hash值
- :paramblock:Block8 m$ }" J/ _7 c; g9 ]
- :return:+ Q) t" {. z! h! P
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。$ P6 |" V" S) r3 x
理解工作量证明3 s7 D" D, ?' p
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。( ]7 J6 L8 ^) g1 z" Z
为了方便理解,我们举个例子:6 ]; I/ f1 `+ S) i1 A6 `2 J
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:) c: L5 s& W5 d0 \/ ] Z) _
- fromhashlibimportsha256
- x=5: a( ?: |& L/ U
- y=0#y未知
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":2 L; Z) K3 a8 l+ {" C! s9 b) J
- y+=1# L2 L% z$ |( L. ]6 [
- print(f'Thesolutionisy={y}')
结果y=21,因为:% I6 j! |5 J; _8 c6 o
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
当然,在网络上非常容易验证这个结果。# ?5 ]1 [ G( H6 q& u
实现工作量证明
让我们来实现一个相似PoW算法,规则是:' n6 Z8 _) H' m7 S5 a8 w
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
- importhashlib
- importjson, g0 L3 |9 L! B) t, A. w
- fromtimeimporttime: ]( j0 Z7 A5 P: c
- fromuuidimportuuid4
- classBlockchain(object):
- ...
- defproof_of_work(self,last_proof):9 n1 y- F7 i) @! {4 f! Y
- """8 J- d" ^3 K- s* r
- 简单的工作量证明:$ i7 C( p* {/ ]8 l) T3 `$ P
- -查找一个p'使得hash(pp')以4个0开头# Z0 h2 J$ ?. W/ I* ]: t# D, [" h
- -p是上一个块的证明,p'是当前的证明3 B q) m# Z& U# h& a8 g
- :paramlast_proof:
- :return:
- """
- proof=09 H+ Q# f' X: O" t; ?7 m7 k% p
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=1- `7 \0 i( U2 s8 U) M
- returnproof
- @staticmethod
- defvalid_proof(last_proof,proof):
- """9 K9 J! H! _5 h z! O* _1 k& O3 k
- 验证证明:是否hash(last_proof,proof)以4个0开头?
- :paramlast_proof:PreviousProof
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot.! x3 T1 a* b9 `$ Y1 {
- """
- guess=f'{last_proof}{proof}'.encode()" P& b Q5 b, q6 X% F1 m: B
- guess_hash=hashlib.sha256(guess).hexdigest()8 W# g6 s3 F- e) M3 j* ~3 N
- returnguess_hash[:4]=="0000"4 `- M* |# y( A1 c/ S, J0 n
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。1 C- [1 w5 f* G3 K" K; k* X
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
##二、BlockChain作为API接口% a0 n, R2 ^8 Z% |+ g
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。% h I, T9 @; ~4 H; `1 H
我们将创建三个接口:& i' [: C8 W9 c
- ```/transactions/new```创建一个交易并添加到区块- c% U+ I# H: l% q K: m
- ```/mine```告诉服务器去挖掘新的区块7 ~* Y$ e) G8 U% x% f3 v( j- ~ e
- ```/chain```返回整个区块链" l9 T& O+ a( G/ l( s
- ###创建节点* L. [1 h1 m, }& I( J) G2 o
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
- importhashlib
- importjson/ V) D5 Q( ?9 E
- fromtextwrapimportdedent
- fromtimeimporttime2 U) s" h3 \5 c" X- F
- fromuuidimportuuid4
- fromflaskimportFlask
- classBlockchain(object):* l- f/ u9 X! J9 c) Z+ o% l
- …2 M6 j6 u* `) H7 |7 N4 ]4 i
- InstantiateourNode
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode3 t1 V- P9 W5 S
- node_identifier=str(uuid4()).replace(’-’,‘’)
- InstantiatetheBlockchain
- blockchain=Blockchain() n e7 O, |/ ^/ w1 Y$ `5 @" B
- @app.route(’/mine’,methods=[‘GET’])
- defmine():
- return“We’llmineanewBlock”
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():7 b: o1 l0 G# w
- return“We’lladdanewtransaction”4 z# H- D- [# w# S8 D( K
- @app.route(’/chain’,methods=[‘GET’])
- deffull_chain():/ [0 i- M3 p% W# f
- response={1 @+ I5 W1 U1 w! A( ]$ S
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),
- }$ N& q3 n6 t% `" e, f
- returnjsonify(response),200
- ifname==‘main’:
- app.run(host=‘0.0.0.0’,port=5000)# [5 V5 S7 C6 t3 P% {
- ```
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。2 t7 T6 a ]+ I! C- j
- 第18行:为节点创建一个随机的名字。
- 第21行:实例Blockchain类。
- 第24–26行:创建/mineGET接口。
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。* e# {. C7 G, V& @6 |
- 第32–38行:创建/chain接口,返回整个区块链。6 L$ t- f( A" O# ~- p
- 第40–41行:服务运行在端口5000上。
- 发送交易
- 发送到节点的交易数据结构如下:; d# H- {% r0 c! D3 x) m
- {6 F* |- _3 W2 v' O1 E/ h$ G) t4 U
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",
- "amount":50 D- |' y. a$ Z0 l3 d% v# z: F# b
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:
- importhashlib
- importjson
- fromtextwrapimportdedent. n& u8 i/ m! Q
- fromtimeimporttime& |. H9 u: O. g
- fromuuidimportuuid4- `3 ~1 g' K- V8 H3 V
- fromflaskimportFlask,jsonify,request
- ...5 W% u0 L% c" H' _# Q* `
- @app.route('/transactions/new',methods=['POST'])/ q% @" B& v) ?
- defnew_transaction():
- values=request.get_json()
- #CheckthattherequiredfieldsareinthePOST'eddata
- required=['sender','recipient','amount']! y* F j( ^ d# A* n; k
- ifnotall(kinvaluesforkinrequired):1 G. h& D2 a8 D" g5 [$ B
- return'Missingvalues',400! t/ M$ Z' u. e( Y+ e
- #CreateanewTransaction' D% M7 U0 N$ }) a% O
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}9 _+ e( @' e$ z d6 S! n R/ Q: d
- returnjsonify(response),201
- ```) Q7 p$ Y3 z% I0 K2 j {. ?
- -创建交易的方法-
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币2 N) X& T9 { D8 G
- 构造新区块并将其添加到链中9 Y8 i; w$ J% N' P+ v. x' @! k
- importhashlib/ f* \0 Y4 s* r% x" S& _
- importjson1 Z' R6 F& @8 t0 J3 E" f
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- …$ [( L* j. M2 l4 v: D
- @app.route(’/mine’,methods=[‘GET’]), H5 `# Z$ n4 O5 z! \
- defmine():
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block$ r9 N& O" L4 ?6 m
- last_proof=last_block[‘proof’]$ H# |* ^ [# O p+ g
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.& @, H) J Z* v9 b: q0 U) B: c
- blockchain.new_transaction(/ ]+ q W1 {6 d% O# q
- sender="0",
- recipient=node_identifier,8 ?$ _- n+ j& L% M! j" |
- amount=1,
- ), v1 I/ C, j' h( T1 ?3 F$ T
- #ForgethenewBlockbyaddingittothechain
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)4 A7 @; `0 V0 _4 y) n& \0 Y
- response={
- 'message':"NewBlockForged",. A8 l( b) i* |9 x$ j2 N5 i# ]- y
- 'index':block['index'],
- 'transactions':block['transactions'],
- 'proof':block['proof'],4 F& q+ {2 I" w1 W
- 'previous_hash':block['previous_hash'],
- }
- returnjsonify(response),200( L' ~) T3 M/ N6 ^/ ^! J
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
三、运行区块链+ H+ L- {# T/ f# a$ E
你可以使用cURL或Postman去和API进行交互
启动server:
$pythonblockchain.py
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-7 B, |1 `; [$ X4 z9 z; ^
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:7 F& o2 O0 }& x+ d4 b( h
-使用Postman以创建一个POST请求-# M% V4 h( \2 |2 O( ^1 P; k& }( T
如果不是使用Postman,则用一下的cURL语句也是一样的:
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e", N2 R1 ^) i; |( U
"recipient":"someone-other-address",/ f( d# N0 L* N/ G) E
"amount":5
}'"http://localhost:5000/transactions/new"
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:, k% Z; |+ `- Z5 _6 L5 T& Z
- {
- "chain":[
- {' g7 l# w4 _# G
- "index":1," k/ @8 z ~' b- X0 A, T( W1 Z
- "previous_hash":1,% c" x! `. {& y, G; K
- "proof":100,
- "timestamp":1506280650.770839,
- "transactions":[]
- },
- {- T6 X5 K. V8 c. R
- "index":2,8 g: C2 a5 i. m; d% \' g
- "previous_hash":"c099bc...bfb7"," C) O( s N; T) \
- "proof":35293,% ~ K N: D# T9 \ w7 Q
- "timestamp":1506280664.717925,8 C3 Y$ M) v5 w Y) l
- "transactions":[8 V) s: p# g! s5 |, L
- {5 m' k: C' r5 E
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",% r9 [3 N- R* `, o* m
- "sender":"0"1 X$ M: K, N6 C9 u& J) a0 k
- }) M6 H: Y2 n F0 K+ Z! `
- ]
- },
- {4 W$ o1 K8 ]" ~3 ]- t! H
- "index":3,9 ~" Z: C7 t+ j
- "previous_hash":"eff91a...10f2",; }4 j5 b) \# F0 Y/ ?* e
- "proof":35089,/ u0 n0 W* O0 K* i- U
- "timestamp":1506280666.1086972,
- "transactions":[
- {1 C# @4 j, |3 N/ i, I4 e0 I
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",! M F; ^1 r3 k* w
- "sender":"0"$ y8 ]& j7 B, i: B0 z8 I
- }+ h* `8 E" M! K6 \. E7 C
- ]
- }
- ],
- "length":3
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。) N$ E6 h! q. D/ J* i
注册节点
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:6 w7 W' c- C- C/ ^
/nodes/register接收URL形式的新节点列表( m& ^: Z8 S" n5 g; g c
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:( M/ ^0 Z& V- u6 ^% k
- ...$ ~' w' a: L- F. A ]1 ~* ^
- fromurllib.parseimporturlparse! D: y9 o( R5 d& z
- ...
- classBlockchain(object):& Y& w! d* S& M8 z. c
- def__init__(self):
- ...
- self.nodes=set()
- ...
- defregister_node(self,address):
- """
- Addanewnodetothelistofnodes
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'- \8 V! G" K& r9 s/ L
- :return:None! |1 ]3 h7 i' O/ S! {1 U. q7 W
- """9 Q7 V7 [6 K" S( E3 Z
- parsed_url=urlparse(address)- S1 R1 t: y+ H1 H$ S' u' Y
- self.nodes.add(parsed_url.netloc)$ P* a/ _. V$ |7 t( `7 G
- ```
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。' o X9 I V% y$ b! {% U/ J
- ###实现共识算法; I9 f4 K( h/ f: i
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。) v* O8 y H) h
- … j7 F! x) K0 O6 ~- q0 Z4 ]
- importrequests
- classBlockchain(object)" C' I; P. s4 j5 U
- …8 ~" Z; M. m2 u6 }+ j3 i) w- h8 A
- defvalid_chain(self,chain):' h3 r- Y+ R" D: t( [9 X1 w
- """
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot
- """
- last_block=chain[0]
- current_index=1; G- u, f/ N6 s, g
- whilecurrent_indexmax_lengthandself.valid_chain(chain): d. P/ u' C" ? Q
- max_length=length
- new_chain=chain
- #Replaceourchainifwediscoveredanew,validchainlongerthanours
- ifnew_chain:
- self.chain=new_chain) Z8 v6 ` Q1 S9 U, _" h0 R
- returnTrue
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。4 U+ S- @# R! N, ?( x1 @" ?
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。4 M6 C* `0 z; z7 X: t
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。1 r' d4 W% p2 r! o4 N, _ M6 O
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes():
- values=request.get_json()2 Z- U0 L9 g' A1 n t I5 V b
- nodes=values.get('nodes')
- ifnodesisNone:+ e' }7 w% ]2 T' q& f8 t
- return"Error:Pleasesupplyavalidlistofnodes",4005 ]7 `6 {1 C2 g: X
- fornodeinnodes:
- blockchain.register_node(node)7 C7 P- M+ v" z/ G0 O# F
- response={
- 'message':'Newnodeshavebeenadded',2 v7 T8 V6 }4 T% W) k6 a( r& G
- 'total_nodes':list(blockchain.nodes),
- }
- returnjsonify(response),201
- @app.route('/nodes/resolve',methods=['GET'])" {7 f. S* n& j E! Z7 X0 M$ {% Y& w
- defconsensus():
- replaced=blockchain.resolve_conflicts()
- ifreplaced:2 i* M8 `5 X" @; t9 I3 J) C
- response={
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain
- }) e' |9 T( _1 B
- else:+ V7 y; K3 e4 q# r/ f3 X! d6 {, B
- response={
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain
- }8 M8 ^/ H! v& |! j$ s
- returnjsonify(response),200+ \) D: \) D# J( }3 W
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
-注册一个新的节点-4 l, b$ a- k& O- y& h& E: z$ }0 m$ J
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。