但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
准备工作
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。9 B% ]. N) T- D7 g! y) M0 ^
如果你不知道哈希值是什么,这里有一个解释。. @/ K% m; E" \# {
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。: L# K* Z H9 g; V, a
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:/ l% G: Y# M& v, ] Z3 M
pipinstallFlask==0.12.2requests==2.18.4
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain1 M" {# s2 x( K9 v- p
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。& ?8 Q O# ~' C' e! ^3 d
BlockChain类6 b5 F3 m* ^, u/ B
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。% |( p# P x0 B9 x1 G' z: T( }
以下是BlockChain类的框架:* t: ]4 Q* D% ^) H
- classBlockchain(object):
- def__init__(self):, ~ V5 j/ N, T8 X
- self.chain=[]. }0 M/ y- ^+ r7 i, K
- self.current_transactions=[]
- defnew_block(self):
- #CreatesanewBlockandaddsittothechain. t0 j! P- U# ~2 E/ U9 h
- pass. L: b' z$ U* g6 e+ o6 D# K6 O( B
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions1 }: L+ _9 Q k1 b
- pass2 {# G3 g: O% J. [" g. }( {8 U. Z2 ]
- @staticmethod
- defhash(block):
- #HashesaBlock
- pass4 E ~% ?1 u0 P) b" }( @/ f) U/ b
- @property" G9 m. H& i) L# i5 F0 w
- deflast_block(self):
- #ReturnsthelastBlockinthechain
- pass
-我们的区块链类的蓝图-/ r+ P. h8 E# E( C/ \
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。% r5 p0 i5 i S2 E6 _2 M7 I
块结构$ ?/ u1 `3 [; m7 J/ @
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。- ^# G3 E4 A) E; b ?' h
以下是一个区块结构:
- block={; t n3 R: L1 l; R* q/ Q
- 'index':1,. \$ S9 B4 u7 G" Y8 u8 v+ X
- 'timestamp':1506057125.900785,
- 'transactions':[
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
- 'amount':5,9 e6 V4 h: e/ b
- }3 {3 W H! U2 t- ?! J
- ],
- 'proof':324984774000,5 h7 ?- |- p+ h' H4 a, I
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-3 t* Y2 d: t3 f5 L& F7 Q
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。' c% ]/ i4 @. K ~: D
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易+ A8 M9 \2 M0 Y# O, h
接下来我们需要添加一个交易,来完善下new_transaction()方法。
- classBlockchain(object): ]# T% l. K. R$ {. @" K
- ...
- defnew_transaction(self,sender,recipient,amount):
- """1 x0 M8 y2 M7 y6 ?$ C: c( ?4 [
- 生成新交易信息,信息将加入到下一个待挖的区块中2 H6 A' e0 `9 V' e" v
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount0 K4 d# V3 Q& N7 f( N6 W
- :return:TheindexoftheBlockthatwillholdthistransaction; A: d, K" G' I7 b! X4 P0 ~
- """
- self.current_transactions.append({' e. a% ?/ U" v" t0 w& ^$ A
- 'sender':sender,
- 'recipient':recipient,
- 'amount':amount,
- })3 X. y0 }4 S) C7 w
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块$ Z! j% H; k2 ^ T
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。- G$ i& a) P, K$ k' e7 B. C4 z' C
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:9 `( w. ~# n( n0 x# |
- importhashlib
- importjson0 `5 z o4 X5 l8 f9 M
- fromtimeimporttime) P7 k4 V+ K# [
- classBlockchain(object):1 w6 X4 H/ @* U) f
- def__init__(self):
- self.current_transactions=[]
- self.chain=[]
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100)
- defnew_block(self,proof,previous_hash=None):. A/ ]/ V# l6 S. m) f" T
- """( [5 A" t% ?$ p' h; b
- 生成新块8 K' j" n: K3 z/ k4 z5 Y$ m7 h
- :paramproof:TheproofgivenbytheProofofWorkalgorithm
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock4 [. L/ u& ~) ]" S
- """/ b" B1 J M, t& i/ N
- block={
- 'index':len(self.chain)+1,+ @6 Y8 \. N+ L% A S8 n: ^
- 'timestamp':time(),
- 'transactions':self.current_transactions,; u/ A$ o8 z) A
- 'proof':proof,
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),; T/ `6 S( a5 P
- }/ I1 B- H! d2 o
- #Resetthecurrentlistoftransactions
- self.current_transactions=[]
- self.chain.append(block)
- returnblock
- defnew_transaction(self,sender,recipient,amount):
- """
- 生成新的交易信息,信息将加入到下一个待挖的区块中6 X& [) u- S7 N0 x) c# H6 y
- :paramsender:AddressoftheSender2 V9 p6 q5 t$ o$ {% P
- :paramrecipient:AddressoftheRecipient, M9 @% h. Z; S5 u4 \
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction- [4 D; ^! r p0 Z1 }
- """+ G' J2 J5 N0 G/ K3 G# W( n
- self.current_transactions.append({$ T9 V, |3 h! H% I) j6 L( l5 E
- 'sender':sender,+ R/ P9 \9 ], ^ h/ h
- 'recipient':recipient,- e1 w- G [& q" t1 n
- 'amount':amount,
- })
- returnself.last_block['index']+1
- @property; F6 ` K$ [4 v4 I( u# v, B% ^0 T, \
- deflast_block(self):; Q& [" R. |0 v* n% S7 I `- Y: N0 D
- returnself.chain[-1]
- @staticmethod
- defhash(block):8 H7 A; Q/ v' F: v5 F7 }9 a4 n
- """
- 生成块的SHA-256hash值$ N1 ~7 B* \! |0 W" y" ]
- :paramblock:Block
- :return:
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes. ?* E7 E$ B2 d! ]
block_string=json.dumps(block,sort_keys=True).encode(). S. v+ k" L! |) b' O/ C' k8 h
returnhashlib.sha256(block_string).hexdigest()
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明2 J8 S; z( ~ C: {1 Z( b H. Y
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。: y/ V, x2 G! E+ k X
为了方便理解,我们举个例子:+ _1 O6 | Q$ k/ e) e. T
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
- fromhashlibimportsha256
- x=53 q/ a* J0 O% J) D6 j
- y=0#y未知' F8 b8 H: r! ], }% I- C3 U6 `
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
- y+=1
- print(f'Thesolutionisy={y}')
结果y=21,因为:
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
当然,在网络上非常容易验证这个结果。
实现工作量证明5 u& l) n A1 l4 n$ ]" N s3 p
让我们来实现一个相似PoW算法,规则是:$ g6 G" n1 o: v- o- f
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
- importhashlib5 n0 n% I6 G0 Y7 p5 k. b
- importjson
- fromtimeimporttime
- fromuuidimportuuid4
- classBlockchain(object):/ o& [! u* v T
- ...0 i5 n1 z* y e$ Y) z' c* ~
- defproof_of_work(self,last_proof):: z* i, x8 _9 q" f
- """
- 简单的工作量证明:2 e( E% A) P" H4 L8 G( K
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明
- :paramlast_proof:
- :return:
- """
- proof=0
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=1
- returnproof9 G5 g4 @. I. @
- @staticmethod
- defvalid_proof(last_proof,proof):. x, y) J D( {7 H( @2 r
- """& F5 V3 h$ p; ~6 E9 t- \9 ^
- 验证证明:是否hash(last_proof,proof)以4个0开头?
- :paramlast_proof:PreviousProof
- :paramproof:CurrentProof' D6 |+ X, X' a( t. @! R/ x. m
- :return:Trueifcorrect,Falseifnot.! x. Q+ }5 F3 B- F
- """
- guess=f'{last_proof}{proof}'.encode()6 C) E3 p( i8 j& U: t j
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000"2 r! S% c) p$ Q3 l8 {
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。$ {$ a" d8 Z5 i0 X3 ^
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。6 K) ~( Y' D( W$ j" | q( u
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。6 \, Z* z. }' c' K; W' e
我们将创建三个接口:
- ```/transactions/new```创建一个交易并添加到区块3 P' W9 N% Q w# R9 b
- ```/mine```告诉服务器去挖掘新的区块
- ```/chain```返回整个区块链
- ###创建节点 c: s3 t& ~: p% ~
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:: e; ^& O, ^" g/ I: d
- importhashlib* i9 F( A" @1 Q$ V
- importjson; h u8 Y. d3 g/ R4 _
- fromtextwrapimportdedent5 J* o) }6 {' g6 |6 o
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask( P" q K+ \4 J* R2 v. I* m3 ^
- classBlockchain(object):" d% k! o! V2 |+ q! L' D
- …3 h* i A4 {7 G) y3 G
- InstantiateourNode
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode" N" s1 Z: B4 H4 n' r
- node_identifier=str(uuid4()).replace(’-’,‘’)
- InstantiatetheBlockchain
- blockchain=Blockchain(). \' l; D; _7 o# O+ u
- @app.route(’/mine’,methods=[‘GET’])+ ], ~ m4 t/ x5 x4 T0 }4 n
- defmine():
- return“We’llmineanewBlock”
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])
- deffull_chain():
- response={
- ‘chain’:blockchain.chain,$ L. |5 D1 c9 T
- ‘length’:len(blockchain.chain), ?. Y5 f4 }! P; r d8 e8 x& `
- }
- returnjsonify(response),2008 U9 ?! ^/ D' s" W6 d b
- ifname==‘main’:$ P/ N: k- ]! O: P2 |3 f' ]
- app.run(host=‘0.0.0.0’,port=5000)
- ``` X3 ^, y& w8 G: Y8 @( Y" B Q; v+ f5 ~$ E
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。
- 第18行:为节点创建一个随机的名字。
- 第21行:实例Blockchain类。* s8 |' T( I; A5 Q% y, r( s+ j" ~
- 第24–26行:创建/mineGET接口。
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。3 U/ j" c1 ~4 N1 Y$ x# J3 `
- 第32–38行:创建/chain接口,返回整个区块链。0 ~# x1 f d/ K, q; V# L# k
- 第40–41行:服务运行在端口5000上。! l w5 J3 j2 i' L4 P
- 发送交易. U+ X) @# U& Z, |
- 发送到节点的交易数据结构如下:/ G* M" b: y& |: \5 L
- {. l( O" ]- D; Y* ?/ I- I
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",
- "amount":5' d6 S9 b: \9 _( q& K0 p+ P
- }2 I* E6 s0 G- i1 Z/ m9 j1 p
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:
- importhashlib! ]; p7 q: e' n1 w% ~
- importjson, |6 J2 A9 U8 p4 t
- fromtextwrapimportdedent
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request& f* ~4 `$ ~. ]' ^ a
- ...
- @app.route('/transactions/new',methods=['POST'])
- defnew_transaction():
- values=request.get_json()
- #CheckthattherequiredfieldsareinthePOST'eddata
- required=['sender','recipient','amount']" v5 i% ~) r' f& V) P4 U
- ifnotall(kinvaluesforkinrequired):
- return'Missingvalues',4000 G% M R! f8 X/ e3 g+ X' |
- #CreateanewTransaction
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}
- returnjsonify(response),201' I% ?* X) V: F) A8 j3 h3 p! \3 Z
- ```
- -创建交易的方法-* W9 }( s6 s) d |* M
- ###挖矿$ s. X% g$ @( f0 b9 Z! C$ D
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW/ ^4 R+ D2 E: M
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
- importhashlib
- importjson) N' y) x2 j+ t8 \; ]
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- …, t2 j; f9 O a" X9 ^( P3 m
- @app.route(’/mine’,methods=[‘GET’])
- defmine():
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block+ F9 o7 H3 M: N7 c0 _
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)5 D' m e7 q' _0 Z
- #给工作量证明的节点提供奖励.6 O7 a. y0 j# j9 b8 H! z/ H
- #发送者为"0"表明是新挖出的币.3 d2 Q! d, j5 n" [
- blockchain.new_transaction(
- sender="0",0 {4 R0 C4 k; G% Z1 l/ Y" ~
- recipient=node_identifier,1 {, B) H# C# e4 ] s* B
- amount=1,
- ). |1 H2 w0 [4 \/ c4 s3 i
- #ForgethenewBlockbyaddingittothechain
- previous_hash=blockchain.hash(last_block)( ^2 Q3 N5 t7 k7 `# S
- block=blockchain.new_block(proof,previous_hash)
- response={. V" O- Z# [$ F* O
- 'message':"NewBlockForged",
- 'index':block['index'],6 r, z2 G8 Q& y5 C# |7 l
- 'transactions':block['transactions'],
- 'proof':block['proof'],
- 'previous_hash':block['previous_hash'],
- }) j; d0 ~; {9 M2 j* {
- returnjsonify(response),200: A0 q: U) K: e, a2 J4 I
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。: r3 C/ @/ |2 i1 a/ ^/ @
三、运行区块链% e3 Z. k: P$ l; }. ^3 i! s
你可以使用cURL或Postman去和API进行交互. O1 F3 b/ S% D) n8 e1 z
启动server:
$pythonblockchain.py
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
-使用Postman以创建一个POST请求-$ {# ?3 A9 q! ]" |
如果不是使用Postman,则用一下的cURL语句也是一样的:1 q- D K' I' B/ \4 j" p2 o g2 i5 m
$curl-XPOST-H"Content-Type:application/json"-d'{3 S3 e1 B- _8 O( O' f' y
"sender":"d4ee26eee15148ee92c6cd394edd974e",5 L5 I: g& m: G- K! |
"recipient":"someone-other-address",
"amount":56 Y) ?% t7 B/ J
}'"http://localhost:5000/transactions/new"& B( t$ d) m% A
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:8 w! c7 N @7 ~; V
- {
- "chain":[
- {' y0 X7 ~+ j- c% H
- "index":1,7 s3 P! U! i, ^( C9 L- n& j
- "previous_hash":1,
- "proof":100,( T, }6 I, V7 ~- g6 g0 F/ T
- "timestamp":1506280650.770839,# A5 W- s: i$ A* X
- "transactions":[]
- },
- {
- "index":2,3 V8 e5 P; u2 `
- "previous_hash":"c099bc...bfb7",
- "proof":35293,
- "timestamp":1506280664.717925,+ O- _; z b# r2 J6 R) S7 x4 v
- "transactions":[
- {
- "amount":1,6 m6 f5 H9 O3 d* y
- "recipient":"8bbcb347e0634905b0cac7955bae152b",) P2 f% [1 P+ ?
- "sender":"0"
- }
- ]
- },
- {
- "index":3,
- "previous_hash":"eff91a...10f2",
- "proof":35089,$ B, Y" X; q+ A5 W" w: M
- "timestamp":1506280666.1086972,8 P0 c" ^/ A5 a& i/ i2 L$ p
- "transactions":[
- {+ w, Z! z6 {2 r" }. T) d7 f
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }$ U4 i6 K1 K3 \% @( e N" H
- ]& I7 }' I( S) i* j3 H& [
- }/ W% L; |4 M' U* Q: g
- ],
- "length":3
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。% U* ~; n3 M# g, W# b" S
注册节点 c+ T7 O8 e; P
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
/nodes/register接收URL形式的新节点列表
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:
- ...2 O5 A- m* a# M
- fromurllib.parseimporturlparse
- ...
- classBlockchain(object):
- def__init__(self):
- ...
- self.nodes=set()
- ...
- defregister_node(self,address):0 l1 [& M1 q% y' p
- """' m$ X @ M+ p5 U, t
- Addanewnodetothelistofnodes
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None$ H& u( `' t6 Z8 V1 k( Z0 R
- """
- parsed_url=urlparse(address)
- self.nodes.add(parsed_url.netloc): a6 w. e5 T. _! T3 k; P
- ```2 B0 { O/ t" b% {# s! D
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
- ###实现共识算法, v" x# s2 H3 U8 }; x6 k- c
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
- …
- importrequests- O3 Z7 F5 H- i" P3 Y4 \4 M
- classBlockchain(object)
- …6 u5 z1 u9 v) |
- defvalid_chain(self,chain):# D V% J$ O5 q2 K' z3 S0 J( _
- """
- Determineifagivenblockchainisvalid( K% J: f4 J( @% R3 E
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot, f* X4 R8 @1 V6 F1 _7 k* q' R1 C+ ~
- """
- last_block=chain[0]9 N) h5 s5 K8 s) I$ b
- current_index=10 C* A% d( g2 N) ?/ o
- whilecurrent_indexmax_lengthandself.valid_chain(chain):
- max_length=length: K2 v! N0 R1 T2 x3 x, W# H' V
- new_chain=chain1 H3 l/ \! D) t7 t6 h3 [
- #Replaceourchainifwediscoveredanew,validchainlongerthanours2 E5 v8 \ n" a a7 v8 {/ ^
- ifnew_chain:& J$ F" ^- K* e5 R. m3 H
- self.chain=new_chain7 {8 }* K3 n5 M. X% m1 j* m
- returnTrue
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。# | T1 C# ^/ F
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。" [0 ]$ ^# T* i7 I2 l. @
- @app.route('/nodes/register',methods=['POST']). Q9 G8 s u- W. w% P1 j: g
- defregister_nodes():
- values=request.get_json()
- nodes=values.get('nodes')3 C3 Z$ k ^* e: q, N
- ifnodesisNone:) L0 s. J2 w9 J$ t! w$ j/ n% _0 X
- return"Error:Pleasesupplyavalidlistofnodes",400
- fornodeinnodes:
- blockchain.register_node(node)
- response={; A7 P' r( v8 q! }" w
- 'message':'Newnodeshavebeenadded'," w+ t# k+ d- [% j) T, T$ j
- 'total_nodes':list(blockchain.nodes),
- }
- returnjsonify(response),201
- @app.route('/nodes/resolve',methods=['GET']). }1 H& A& s. t
- defconsensus():) F- h7 q9 L' M$ d( g
- replaced=blockchain.resolve_conflicts()5 L: _( B' D3 C5 I- k( l" z
- ifreplaced:6 I* [/ a7 K9 ~" c' U! q
- response={
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain/ Q, K; `) h9 C. `3 m, G
- }) u3 J |8 o" v$ [. @7 a
- else:/ U( A+ d) m6 Q6 s' O% \
- response={2 `, c5 ?8 O# x3 f2 c
- 'message':'Ourchainisauthoritative',: L+ y9 |+ {: ^0 E, T
- 'chain':blockchain.chain
- }$ ]1 H1 ~) f, U" e% ]& Z4 N8 h/ g
- returnjsonify(response),200
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。; U# d, {0 a; M+ |4 t
-注册一个新的节点-/ A" |' w, V* j2 E- ?
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。