但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
准备工作+ F% J0 d5 o5 ~5 s% ^9 l2 h" J; Q
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。; Z- ]% d; j' ] L
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:( F" O" R6 Z* g: w
pipinstallFlask==0.12.2requests==2.18.4
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。7 @9 d5 O+ a8 E L- m
一、开始创建BlockChain! C% ]; Q! Y3 _
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
BlockChain类+ O$ Y$ q& j6 F- d
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:! n T" o ^/ i( V# }
- classBlockchain(object):4 n. B3 J& S/ ^4 j
- def__init__(self):
- self.chain=[]4 A4 @ Q( F, T; q- w
- self.current_transactions=[]/ D& f( ~* e; X3 D; Q( `+ T; F
- defnew_block(self):6 q* {+ i2 \/ _' [; `) m( _. G: V! A
- #CreatesanewBlockandaddsittothechain2 E: C2 i$ ? l
- pass% X0 @+ i; O6 G$ l# A/ S1 p1 I: D3 z
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions
- pass+ i2 d1 z4 M2 o8 {; w
- @staticmethod* `2 ^: }4 g T1 ~8 j/ z/ Y4 X
- defhash(block):& A% D/ b- g1 Y) c
- #HashesaBlock
- pass' p: ^; B4 U4 f. y4 |$ |( j& ~
- @property: @( o W6 `: Y/ l& U7 c" ?: z- U+ y, A
- deflast_block(self):
- #ReturnsthelastBlockinthechain% n4 u) P5 k: F3 Z+ p
- pass
-我们的区块链类的蓝图-- w2 }# ~* G: z. b N5 q
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。# p- {) @ p4 b& h: n
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。
以下是一个区块结构:8 ]) J% b9 S* ^- B9 [1 O4 _
- block={/ i6 u/ q7 r6 C. H
- 'index':1,. v4 i- K, b6 R: O$ r# z( `
- 'timestamp':1506057125.900785,9 m* A5 e6 J' @6 x5 [ f n9 u4 G& w
- 'transactions':[8 y$ S; q6 }4 k: F
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",( Y0 ?- z; Q, T
- 'amount':5,
- }
- ],% e$ X. a/ e' d+ y, o
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。. {: m5 O: F* w+ K( n! e9 r6 Z- l
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。( ~. f( y6 t' c/ [. e) B
加入交易: E2 A3 }( m! w0 v7 s! S
接下来我们需要添加一个交易,来完善下new_transaction()方法。4 e9 w, P+ q# |" ^
- classBlockchain(object):: X( w* L L+ k1 v0 Y$ B' h: O
- ...2 J, T5 b- b+ X( m' s+ \( k
- defnew_transaction(self,sender,recipient,amount):3 n ~* S6 S) s
- """& Q9 y7 y; l2 M, O1 o" \
- 生成新交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender' s/ _/ w/ G! B# Y
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount3 |: y" k0 J; P
- :return:TheindexoftheBlockthatwillholdthistransaction, @; Q. `) T U) h5 r
- """
- self.current_transactions.append({
- 'sender':sender,6 J1 O( {1 E1 L& a$ p
- 'recipient':recipient,
- 'amount':amount,
- })
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块3 Z7 P/ r3 O2 E" ]% H
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。# d3 r% _, B. |* @2 y, e/ f
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib
- importjson
- fromtimeimporttime
- classBlockchain(object):: P9 x5 t5 t5 }$ H1 @
- def__init__(self):1 y9 @% m j/ U
- self.current_transactions=[]; f- L& L$ b' h0 Z
- self.chain=[]
- #Createthegenesisblock; w7 J5 n- ^4 |4 ~* Y) H
- self.new_block(previous_hash=1,proof=100)
- defnew_block(self,proof,previous_hash=None):- h$ W# v- |( d1 ~3 c: S
- """
- 生成新块
- :paramproof:TheproofgivenbytheProofofWorkalgorithm( R# b( |$ B# C4 U- O+ e/ g W$ `, A
- :paramprevious_hash:(Optional)HashofpreviousBlock% r5 S/ M1 K& T
- :return:NewBlock3 q$ j3 x4 E! }$ [ B3 R+ @5 |7 V. e# X8 z
- """' ?9 p0 P1 g8 ]* G# T. d- J
- block={- z6 p5 ?6 _ L7 z* B: C; E8 a( T0 |
- 'index':len(self.chain)+1,$ [5 R. Q6 n$ y1 E# k5 ~+ h
- 'timestamp':time(),
- 'transactions':self.current_transactions,
- 'proof':proof,1 N- ]' U$ r3 l6 t! b- T
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),- U: y$ r7 U* b% E* d* |! t# x
- }6 |' c& e- H0 v4 y1 V" ]
- #Resetthecurrentlistoftransactions0 n8 x* c4 _- p7 ?4 j$ V2 M/ _
- self.current_transactions=[]
- self.chain.append(block)
- returnblock
- defnew_transaction(self,sender,recipient,amount):
- """/ @7 u1 R! J7 A8 |7 ?/ B
- 生成新的交易信息,信息将加入到下一个待挖的区块中3 L: y; q3 P* l0 p
- :paramsender:AddressoftheSender/ x2 @- S) c+ X* l4 |
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction! c/ h. H8 q5 G$ B9 m, y. G1 r: O
- """
- self.current_transactions.append({
- 'sender':sender,3 M4 l D9 `( A! W0 s- f! j: ]3 u8 I5 _
- 'recipient':recipient,
- 'amount':amount,
- })
- returnself.last_block['index']+1/ B4 d# f0 \/ ^' n
- @property: p/ O0 U( {" v% ~* n2 K0 S. y
- deflast_block(self): G. N/ ]7 v9 n' u
- returnself.chain[-1]% G$ M2 R4 i3 P h1 c2 Q
- @staticmethod
- defhash(block):% P, S3 `, |* Q' J5 ?& t4 r
- """* ] {# t6 b2 A, l
- 生成块的SHA-256hash值( {0 e( `' n; E8 G& \7 H( k
- :paramblock:Block
- :return:
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes( ?7 l0 w* p+ x6 K
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()7 ]4 u N" L# u1 M6 F. Z& _
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。: D5 p0 J+ z! W5 h
为了方便理解,我们举个例子:; E* K/ f* s1 j: t
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:7 W- A9 t) J" C# H* A( c: E0 d' ?
- fromhashlibimportsha256# U7 e4 d6 i" _/ F
- x=5' j* L4 N$ C! V3 \$ }( @
- y=0#y未知
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
- y+=1, [+ r+ ^1 i% v, J" v4 W7 l; t
- print(f'Thesolutionisy={y}')
结果y=21,因为:
hash(5*21)=1253e9373e...5e3600155e8608 o9 }: S2 @1 D$ f5 A
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。% U) o9 Y7 J) V& v( c
当然,在网络上非常容易验证这个结果。5 k8 p- t# q$ b, R; M
实现工作量证明7 x0 R( X) Z: g0 y+ j. K: U) F$ B; D
让我们来实现一个相似PoW算法,规则是:
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
- importhashlib
- importjson
- fromtimeimporttime
- fromuuidimportuuid4$ b4 o7 U& G( _1 |+ X$ x
- classBlockchain(object):
- ...& v1 R3 j3 k9 }1 i
- defproof_of_work(self,last_proof):
- """0 p7 [9 p% ~. [
- 简单的工作量证明:3 y) A- ~9 g# f6 l. h
- -查找一个p'使得hash(pp')以4个0开头& R' |; m5 b/ V( J9 s8 x0 B# \
- -p是上一个块的证明,p'是当前的证明( M* b* K8 K7 u7 e( \: i
- :paramlast_proof:
- :return:
- """
- proof=08 F w' F9 ~! |$ n$ V
- whileself.valid_proof(last_proof,proof)isFalse:3 R+ P" T7 s1 ]4 z
- proof+=1 v5 V* @5 F& T+ ?$ y/ a' K) z4 J7 F
- returnproof; ]: I! |. D' ]' D
- @staticmethod7 L" b& v- k2 G, Q
- defvalid_proof(last_proof,proof):& G* P' X2 D) k7 a! G( a5 G
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?; T& M- b! H h3 ^) J" f! l9 M
- :paramlast_proof:PreviousProof5 o* W Y4 q: b& q5 _, m r
- :paramproof:CurrentProof2 L. U5 k5 G x" S
- :return:Trueifcorrect,Falseifnot.: d( ^8 U# M2 e# K
- """" H, |. a8 g" O0 r6 m/ P0 |
- guess=f'{last_proof}{proof}'.encode()
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000"$ j% P( k% U/ J1 |7 c/ L. @! ?
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。& I& z5 d1 Y+ M
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。( O) Y6 v9 a0 I& z3 |- P' H
我们将创建三个接口:% ]. k5 O$ V: w; D
- ```/transactions/new```创建一个交易并添加到区块2 @' _& X* \. e) s) D
- ```/mine```告诉服务器去挖掘新的区块
- ```/chain```返回整个区块链! H4 p$ E; d3 h) H0 }
- ###创建节点$ B* A* |; z2 T6 m
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
- importhashlib& k; }9 D3 V* L
- importjson. y( Z( n# I1 \
- fromtextwrapimportdedent
- fromtimeimporttime
- fromuuidimportuuid4+ l# ^( s$ r. X" o) s; C, J
- fromflaskimportFlask
- classBlockchain(object):
- …
- InstantiateourNode+ n, T2 S' j1 A. h8 W' D
- app=Flask(name)/ T% p# l# y- |7 N1 l1 ]8 ~5 O
- Generateagloballyuniqueaddressforthisnode
- node_identifier=str(uuid4()).replace(’-’,‘’)3 Z' E# @# X2 _, R
- InstantiatetheBlockchain) f! L1 L# C6 b4 G8 ~
- blockchain=Blockchain()
- @app.route(’/mine’,methods=[‘GET’])
- defmine():& g+ ]4 [& t( l* u6 p! Q
- return“We’llmineanewBlock”- I! b) h0 M- j; R) _$ ]! s
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])" C# ~5 u$ B) Y2 L& [
- deffull_chain():9 n" c/ M$ V# n# [+ r
- response={
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),
- }0 O" R* n2 d& M) V2 R* q) T
- returnjsonify(response),200
- ifname==‘main’:9 k( u$ N* j9 s0 | A* K+ A
- app.run(host=‘0.0.0.0’,port=5000)$ F+ E$ k/ q. t8 T9 ^7 ]
- ```9 P' i2 D; _9 d# a) Z! O" r% j
- 简单的说明一下以上代码:* \7 H/ W' M* `2 v
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。. h" C. _5 i# h. A2 x8 W! F
- 第18行:为节点创建一个随机的名字。' j4 ?1 ^: b. n2 {$ y) l
- 第21行:实例Blockchain类。8 l' g% f# ~- t/ {
- 第24–26行:创建/mineGET接口。+ Y7 ?/ d* Z& y. q$ w% W
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。- A; g7 v: ] C( m5 j
- 第32–38行:创建/chain接口,返回整个区块链。
- 第40–41行:服务运行在端口5000上。
- 发送交易
- 发送到节点的交易数据结构如下:
- {
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",
- "amount":5( t% m; m8 V" }- J$ q! O; L
- }, s! s" N& T- U1 v% L
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:
- importhashlib
- importjson
- fromtextwrapimportdedent
- fromtimeimporttime
- fromuuidimportuuid40 ?0 Q1 X/ d3 o) T
- fromflaskimportFlask,jsonify,request
- ...
- @app.route('/transactions/new',methods=['POST'])
- defnew_transaction():
- values=request.get_json()/ ~; q8 @! b8 M( T
- #CheckthattherequiredfieldsareinthePOST'eddata4 Y: ^" `* R2 E" i
- required=['sender','recipient','amount']" [( H5 y9 \- w" r
- ifnotall(kinvaluesforkinrequired):
- return'Missingvalues',400
- #CreateanewTransaction
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])" m/ g. [2 T% X7 \8 ^
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}9 O4 ?; i; F0 }& a
- returnjsonify(response),201
- ```
- -创建交易的方法-# r% T0 z7 x; k3 w Y8 \
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
- importhashlib
- importjson5 [: Z1 d, e+ Y5 S9 R
- fromtimeimporttime
- fromuuidimportuuid4" m1 x& @0 |* T. C% P* Y2 o
- fromflaskimportFlask,jsonify,request
- …* S# H) r% _ H, u, _! P/ P# h
- @app.route(’/mine’,methods=[‘GET’])
- defmine():- _1 w* Q4 J @3 h
- #Weruntheproofofworkalgorithmtogetthenextproof…! `! e5 _4 \* T5 X6 @- P+ I
- last_block=blockchain.last_block
- last_proof=last_block[‘proof’]9 r1 ] g& V$ u( O) p; |1 ^+ Y6 k
- proof=blockchain.proof_of_work(last_proof)! z! s- h& [' E% A6 p& g$ G
- #给工作量证明的节点提供奖励. S; U8 O; G; ~, y
- #发送者为"0"表明是新挖出的币.. W% \! Q$ y7 _+ m W2 H4 Y3 x
- blockchain.new_transaction(! U" F1 w v# V- R# t! Z
- sender="0",$ s% G$ `' c0 T3 [$ ]' |1 Z, N
- recipient=node_identifier,
- amount=1,
- )# `$ o: v: K, b9 H9 d M2 A
- #ForgethenewBlockbyaddingittothechain* V; {8 i5 k' |5 _
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)
- response={' Y( @, k" @5 ]8 A7 @
- 'message':"NewBlockForged",# O: m% n2 W& U) A* q! @
- 'index':block['index'],' c! e. }4 F+ o5 | w/ X
- 'transactions':block['transactions'],9 w# t H: l0 x H1 i$ T6 C( J R
- 'proof':block['proof'],0 x: |# w: `4 [& P: S0 }" @
- 'previous_hash':block['previous_hash'],8 q2 o: H8 _% S* y E/ v
- }8 E! C6 f; y. t, z/ {! e" |6 t% m
- returnjsonify(response),200
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
三、运行区块链8 _7 i9 b) n J. l8 Z
你可以使用cURL或Postman去和API进行交互4 ? @% _3 C" p
启动server:# C7 V$ A* N3 s* F: R' O
$pythonblockchain.py" S% M4 X& T& b
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:3 Y. ~9 G+ H! d4 H c
-使用Postman以创建一个POST请求-
如果不是使用Postman,则用一下的cURL语句也是一样的:. p& g3 t# O! f
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e",
"recipient":"someone-other-address"," @$ ?+ j1 }. E8 d" x) {0 j E
"amount":5
}'"http://localhost:5000/transactions/new"' t; _+ `; d c
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
- {# P1 E \) R! s1 ]
- "chain":[
- {
- "index":1,9 s, d% w \4 I$ U
- "previous_hash":1,* t: j% \7 O( R( @% w1 Z, H4 X
- "proof":100,' f8 _3 k- `$ N* e* D
- "timestamp":1506280650.770839,7 X+ {& m% b/ ?1 Y0 y
- "transactions":[]( Z G" W1 Y% y, y
- },
- {6 `8 M$ H& g2 _; | f l; \2 F
- "index":2,( R0 f7 d4 C# K5 S$ F' ]2 g
- "previous_hash":"c099bc...bfb7",# r r: G! S8 R. N+ \/ L
- "proof":35293,$ Q8 c7 j+ G& X* T& p
- "timestamp":1506280664.717925, e. e6 b" f- s3 x V( h* B
- "transactions":[! [& s0 Q4 c3 g7 r8 b
- {' i" n; s5 P: {- S4 P
- "amount":1,' c# X# @" v, B: g. y8 G. k/ @! B7 Q. E' K
- "recipient":"8bbcb347e0634905b0cac7955bae152b",8 t) f$ ^1 h* H8 p( h# S6 p* b
- "sender":"0"/ X2 `) Y+ H/ F- k9 Q2 j6 @5 q- g9 Y5 M% r& A
- }% i3 N, ` w$ c7 T' L: Y# y
- ]
- },# x' l3 ?4 f4 f+ D6 u/ C& I
- {
- "index":3,; ^2 O4 L6 x, `/ d% k' o; \
- "previous_hash":"eff91a...10f2",! i. S+ T$ \3 k( X/ u0 [- Q5 X/ W# ?
- "proof":35089,
- "timestamp":1506280666.1086972,
- "transactions":[
- {+ n# _# }. e, B. x; M
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",6 D- K" ^5 ?4 s* V# _9 w/ z
- "sender":"0"- s3 J( O5 r& g9 s. B, H
- }6 Q3 \# Y0 ?4 T" D J
- ]
- }) O5 Y: n4 y! [: A
- ],
- "length":3
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。& z% U7 Q( m# j" t) ^) l. r
注册节点) I8 X3 ?8 n) q, W
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:) ?- s; o0 m0 ~$ M0 u
/nodes/register接收URL形式的新节点列表2 T# F) y, G9 n- a- }
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链* h6 y' d1 C" b7 w; T
我们修改下Blockchain的init函数并提供一个注册节点方法:0 T6 C& F5 P N3 `8 i
- ...
- fromurllib.parseimporturlparse
- ...% \6 K6 F) @0 T) A& U2 A7 J0 ] u
- classBlockchain(object):$ a3 Q. q( _1 P7 _, s' b! a n
- def__init__(self):0 T( C: ~: t1 Z
- ...( A7 A! {; x& i
- self.nodes=set()% S- e# b* ^- h; @+ w$ X8 {' W
- ...
- defregister_node(self,address):0 r S& j& D# f/ j" @7 k
- """$ I* G# D7 @& u1 B% ]
- Addanewnodetothelistofnodes) D) T3 ?7 S/ B3 L
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None
- """
- parsed_url=urlparse(address)* h9 l% V( w- \' E
- self.nodes.add(parsed_url.netloc)
- ```
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。' {; S! C7 n0 f+ I9 v0 Y6 v2 N
- …6 w: B" j& v+ F0 p
- importrequests }" N- t/ k2 ^* D
- classBlockchain(object)
- …
- defvalid_chain(self,chain):
- """! a9 V- `" H: l9 y1 O
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain4 R+ Z5 N( d M: ~
- :return:Trueifvalid,Falseifnot
- """
- last_block=chain[0]! U( J- @: G# j) J* K
- current_index=18 J2 a. Q' F$ T
- whilecurrent_indexmax_lengthandself.valid_chain(chain):1 v. Y8 k" O) e
- max_length=length) {! P! C7 s" P( ^7 u2 \# W
- new_chain=chain3 O$ n/ f# x) ?8 x+ |
- #Replaceourchainifwediscoveredanew,validchainlongerthanours
- ifnew_chain:6 x$ i3 n B7 J" \. A9 k0 n
- self.chain=new_chain
- returnTrue! G: V, S g0 F* {0 H- ^! h
- returnFalse1 h8 t! @6 v1 L/ @' y! R" {) D
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。3 V5 }4 g: d6 J! f1 k% @
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。8 N; y- [& d& }
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。& q3 l7 P8 k$ N5 g# V' H1 y( |% D
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes():& q) `2 r5 h0 Q# y
- values=request.get_json()
- nodes=values.get('nodes')
- ifnodesisNone:. q3 K" W0 P* H; L
- return"Error:Pleasesupplyavalidlistofnodes",400
- fornodeinnodes:/ K2 q( t0 c. m- _' p
- blockchain.register_node(node)
- response={: p3 ~ e7 d- `( B
- 'message':'Newnodeshavebeenadded',) l. E0 ^5 d+ a8 e
- 'total_nodes':list(blockchain.nodes),
- }0 [' E! {8 P& |; k6 ~
- returnjsonify(response),201
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():+ j1 D7 w9 T3 m! @* C0 E) A' U
- replaced=blockchain.resolve_conflicts()# {0 |+ b9 X/ ]) i5 e
- ifreplaced:0 W6 V' x6 d6 W3 ]# o
- response={3 V% M: k% R+ V# ?) b! D
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain/ ~1 L2 x" N2 q8 S L
- }: p- e' K1 e! b8 i& i, W" W
- else:* S. S3 S. s* S+ b1 E5 C$ t6 B
- response={
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain2 [! k7 M* V* n
- }
- returnjsonify(response),200* c, C& F7 [3 y( u; s
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。