但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
准备工作
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。' n7 ], H- D: \, _) U7 ]
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:0 A& U5 F5 ^& t3 }% j% D
pipinstallFlask==0.12.2requests==2.18.4* F0 b( i6 |' e
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。( W( e, }' g9 |
一、开始创建BlockChain
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。- K! e% ?/ Q- U3 k ]
BlockChain类
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:
- classBlockchain(object):
- def__init__(self):
- self.chain=[]7 R. h/ Y/ d' ]2 O$ P1 @
- self.current_transactions=[]
- defnew_block(self):8 W+ b, P# @' v- ~6 h. K( D
- #CreatesanewBlockandaddsittothechain
- pass
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions/ ?5 n. v" l. _$ I3 O, O* {
- pass
- @staticmethod% m6 J# @/ K! C2 t
- defhash(block):
- #HashesaBlock6 C1 O, ]$ v i, I# C! @ m
- pass
- @property
- deflast_block(self):
- #ReturnsthelastBlockinthechain0 \' T* C6 ?/ ?+ A+ w# }: f
- pass
-我们的区块链类的蓝图-
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。3 O# e5 r2 Q$ w' _5 M$ b
以下是一个区块结构:" }8 n( ?; Q# E& s, \- k( X9 Q
- block={
- 'index':1,. a3 |4 m4 i" {1 E' \; B+ q
- 'timestamp':1506057125.900785,( |$ _! H2 q2 y) b$ y$ [
- 'transactions':[) m1 X0 n' g( J% j; d
- {
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
- 'amount':5,9 r2 [, t6 N! w1 P& B# X; Z
- }
- ],
- 'proof':324984774000,* ~. c5 I7 J$ Z: l) c5 O
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-' @9 P6 q. T* W- [
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易6 d4 Z( R0 \; }2 p2 H3 M
接下来我们需要添加一个交易,来完善下new_transaction()方法。
- classBlockchain(object):
- ...# P; R* j. t( f$ v8 e' ^
- defnew_transaction(self,sender,recipient,amount):8 N# u3 Q3 V& c- c* M+ y/ g
- """
- 生成新交易信息,信息将加入到下一个待挖的区块中4 Y+ c3 h2 J% y
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction
- """3 c; C K* n0 [$ W v) I: ]( _
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,8 u+ L' r1 |% L+ e1 X Z! O, b
- 'amount':amount,
- })2 Q# O/ j' u; x* F8 Y( H$ P
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。' W8 T# z$ v, L9 ~7 \) @
创建区块5 {) y3 W- v( }* n* t. `
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。* ?; \ N' v0 n( L% X; S
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。1 B$ A1 P. ~- p$ [2 J
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib
- importjson0 A, ^9 O3 X1 u& z! R7 w
- fromtimeimporttime x9 \6 @" S% p2 u7 F% r9 b
- classBlockchain(object):
- def__init__(self):
- self.current_transactions=[]# R2 {; [/ j: X" _
- self.chain=[]
- #Createthegenesisblock; c5 X6 x# p: J) }3 J- r
- self.new_block(previous_hash=1,proof=100)- H' b( A' F/ R; \, s
- defnew_block(self,proof,previous_hash=None):
- """
- 生成新块( o4 `6 V: Z+ C& G. z' ^9 C0 V/ ~3 o
- :paramproof:TheproofgivenbytheProofofWorkalgorithm
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock
- """/ m+ k, h) j& H( O5 p4 l2 M
- block={/ K3 n& U% [' a% [9 b' Q! T
- 'index':len(self.chain)+1,
- 'timestamp':time(),8 u8 O3 K& J6 R& ?3 i0 }
- 'transactions':self.current_transactions,( V5 a U$ ~0 v
- 'proof':proof,
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),
- }% e+ N' Z, @* w; {& K. r, I
- #Resetthecurrentlistoftransactions! z, |4 q; G# t' ]8 D( w& V/ t
- self.current_transactions=[]
- self.chain.append(block)
- returnblock+ ~& ^$ C& h& D* S! v3 o
- defnew_transaction(self,sender,recipient,amount):
- """) P% F x* P) ]) {' h& d
- 生成新的交易信息,信息将加入到下一个待挖的区块中9 a& W" f3 D0 [1 o7 A3 f- f
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient5 g5 J; M, z" K% X" ^
- :paramamount:Amount9 f$ M6 [/ z* s! D
- :return:TheindexoftheBlockthatwillholdthistransaction
- """
- self.current_transactions.append({, E4 l7 F( V z: J: U3 s
- 'sender':sender,6 |7 K" N: F( a, w, e. f2 I
- 'recipient':recipient,$ ~" [: c; `% c; P0 A! w4 \
- 'amount':amount,
- })1 n8 O w. M& X- r' n' x
- returnself.last_block['index']+10 Z) J9 `7 |/ F
- @property' H4 Q" f4 v8 W* R6 Z6 u$ m
- deflast_block(self):
- returnself.chain[-1]
- @staticmethod
- defhash(block):9 ^ F( @) x N' T3 y, w% t. ]# _
- """' t7 r% g# R) q9 b# p/ x
- 生成块的SHA-256hash值, }: |; \; M8 c; y
- :paramblock:Block& }' |' @$ J- H/ `2 W6 H- v; k5 v
- :return:
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
block_string=json.dumps(block,sort_keys=True).encode()! E/ p" A0 | B& u" j% J9 F
returnhashlib.sha256(block_string).hexdigest()
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明+ U0 }- k+ o1 w) ~3 [
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
为了方便理解,我们举个例子:! Z( M9 z1 ~( n' i1 H
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:( o, `+ [- x3 c. c" Y
- fromhashlibimportsha256
- x=5
- y=0#y未知/ _- `( n* c& K4 q$ n
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
- y+=1" A4 D5 D6 Y- D+ k" T8 V( L& H
- print(f'Thesolutionisy={y}')
结果y=21,因为:6 Q! Q+ [0 a& w- {
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。* u" z' w; E, ^5 n% \- _% `1 F
当然,在网络上非常容易验证这个结果。4 k1 \6 C) r5 L
实现工作量证明+ C% L+ ?9 f1 }* M( U) ?
让我们来实现一个相似PoW算法,规则是:
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。* p) ?" P) o: E9 [+ a/ r
- importhashlib
- importjson
- fromtimeimporttime8 m: n% q$ x" {, H
- fromuuidimportuuid4! `0 |5 {+ K8 ^$ E, p/ @6 R; m
- classBlockchain(object):2 y8 Y6 y5 a1 P/ Q3 s
- ...
- defproof_of_work(self,last_proof):( W1 [& Z/ V# J9 I$ ]
- """
- 简单的工作量证明:7 S+ b4 _$ j2 A6 J6 X3 c. c
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明
- :paramlast_proof:2 `% s. P" z3 c6 O
- :return:
- """3 O- K& h0 s) n u$ f# t* S
- proof=0% l& M) d3 {3 w
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=1# Y2 }8 Y, r9 ~ q A$ S s
- returnproof5 t& w* H$ B( h6 K) W
- @staticmethod
- defvalid_proof(last_proof,proof):
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?3 h0 y6 m! B- R* e- k
- :paramlast_proof:PreviousProof1 y3 H: b" ~% r7 L4 C) J" s* y: {
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot.
- """
- guess=f'{last_proof}{proof}'.encode()! f. o. {, X- ]' \, \
- guess_hash=hashlib.sha256(guess).hexdigest()$ j" ^2 e1 W: g; J3 R
- returnguess_hash[:4]=="0000"8 s/ S5 U+ K% p& a, J. V
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。+ v& o; [% I7 w8 Z; v3 w
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。' o% w2 |+ o0 w
我们将创建三个接口:1 A& Z- K5 T+ _3 \9 \# z0 }
- ```/transactions/new```创建一个交易并添加到区块
- ```/mine```告诉服务器去挖掘新的区块 d: g6 t1 }! Z* w2 c
- ```/chain```返回整个区块链
- ###创建节点. T& [6 Q* j- ]8 |
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:9 q) s: K I$ f0 C4 P$ v5 e. v
- importhashlib
- importjson
- fromtextwrapimportdedent+ V/ c5 {/ X% q4 @- X
- fromtimeimporttime0 m+ J+ L4 k) s% b8 J. Z+ C7 r4 i
- fromuuidimportuuid4- a1 @0 z! i, K% I" J
- fromflaskimportFlask( K; A4 {% b: J5 l/ g: n. G
- classBlockchain(object):, Y M0 i4 t9 j
- …' }* ?+ O3 u- }6 t8 _7 ~" B
- InstantiateourNode! p5 C; j( x* i* G9 [) ^' i5 F! J
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode- S |$ l7 B+ @: x7 k, B
- node_identifier=str(uuid4()).replace(’-’,‘’)
- InstantiatetheBlockchain" I/ y, U1 e8 |6 R( U
- blockchain=Blockchain()
- @app.route(’/mine’,methods=[‘GET’])
- defmine():
- return“We’llmineanewBlock”5 W. e3 ~& a: l6 {8 W b
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():
- return“We’lladdanewtransaction”2 V( F3 p0 k: j
- @app.route(’/chain’,methods=[‘GET’])
- deffull_chain():
- response={
- ‘chain’:blockchain.chain,
- ‘length’:len(blockchain.chain),. S) o( B+ A ]3 H5 v! r) I
- }) T5 w9 c1 ^3 Q# w& ]4 ]( k' q0 q$ o0 l
- returnjsonify(response),200
- ifname==‘main’:8 ~; E2 }4 x, I! F; p9 O, R( j% U
- app.run(host=‘0.0.0.0’,port=5000)
- ```
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。
- 第18行:为节点创建一个随机的名字。
- 第21行:实例Blockchain类。* O$ O, t5 r! q* c* Q
- 第24–26行:创建/mineGET接口。) y1 w2 _! r' E
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。( w' @6 {' x3 R
- 第32–38行:创建/chain接口,返回整个区块链。* r% _: M; Y# p4 s6 T- C
- 第40–41行:服务运行在端口5000上。
- 发送交易
- 发送到节点的交易数据结构如下:
- {' t$ _( e& G4 x3 B
- "sender":"myaddress",0 I# |& x3 D0 n, N- O4 @
- "recipient":"someoneelse'saddress",
- "amount":5/ c, L5 h3 A# i/ h* x( s& E
- }% a( U8 D" k2 {+ S+ r, h; c
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:& b- r4 f8 L. J6 H9 \3 V
- importhashlib' S- ^1 H# h, P. w" z. S" R- D
- importjson" C" H- w! i; G3 {4 |: Y/ V
- fromtextwrapimportdedent8 K) m, v' M) |$ I5 K
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request
- ..., l9 k Y3 t* i- e4 K$ k, v# r% M
- @app.route('/transactions/new',methods=['POST'])- F1 e$ E: }! ~! @2 L+ n" n
- defnew_transaction():
- values=request.get_json()
- #CheckthattherequiredfieldsareinthePOST'eddata) O) G$ ~5 r) j% N$ w
- required=['sender','recipient','amount']9 Y1 H. p5 z. k
- ifnotall(kinvaluesforkinrequired):
- return'Missingvalues',400 s: j; W5 w" O* m6 g5 M8 o/ ~/ E
- #CreateanewTransaction
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])) z, P$ _) N- G4 W# B' {6 B
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}
- returnjsonify(response),201
- ```
- -创建交易的方法-% e8 A9 b2 B$ t% D
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW0 c6 D$ Y4 V1 R; y# D W
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
- importhashlib; y" s6 y# |+ F* C
- importjson5 q- r6 P. {: n) q0 t
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask,jsonify,request+ b3 W3 I7 O: _
- …2 x8 G: _1 c' V
- @app.route(’/mine’,methods=[‘GET’])
- defmine():
- #Weruntheproofofworkalgorithmtogetthenextproof…
- last_block=blockchain.last_block) B# d7 A8 _ p( R1 O9 K
- last_proof=last_block[‘proof’]6 r8 o% Q6 v2 H. |" k
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.2 O& y" |$ Q! u8 _
- blockchain.new_transaction(8 r; ]2 M2 O, o9 E* {
- sender="0",
- recipient=node_identifier,3 |3 C& ~) A7 k9 x! {. m
- amount=1,( f+ ^! b4 K# _1 q2 C4 W
- )
- #ForgethenewBlockbyaddingittothechain5 U |2 b8 @/ N
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash). i4 ~7 m' X# t
- response={
- 'message':"NewBlockForged",# z0 C5 {7 K b; E0 V
- 'index':block['index'],
- 'transactions':block['transactions'],
- 'proof':block['proof'],
- 'previous_hash':block['previous_hash'],5 S" r- T. b( B# k& @9 M. _# V- v
- }
- returnjsonify(response),2009 c. d4 r( J- @' n9 d: C% A( O3 X$ u
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。" [, e+ [9 C/ S# |9 H, j& M( C$ x# V
三、运行区块链# ?. f* C, k& \7 J8 [ j
你可以使用cURL或Postman去和API进行交互( t2 g; Y2 ]" @6 D
启动server:
$pythonblockchain.py
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)4 K2 V" Q4 k z% `( {: A+ S
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
-使用Postman以创建一个POST请求-
如果不是使用Postman,则用一下的cURL语句也是一样的:
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e",; b. w( P3 d: a* Q7 l
"recipient":"someone-other-address",2 x, p- V8 y. w* v- i: Y6 r2 s5 B( j# [/ `
"amount":5
}'"http://localhost:5000/transactions/new"$ u* U' |5 N3 z c% ? i l+ U, g
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:4 L4 i$ Z$ B6 b( [" {
- {" ~3 H( L3 ^$ `2 l1 L6 D
- "chain":[8 U |7 E8 U; ^+ U" M2 q' [5 l
- {4 s, z8 g' s, }7 M1 ^0 R- `. `1 E
- "index":1,' ]; Y# _& z# u& ~; y' I
- "previous_hash":1,
- "proof":100,
- "timestamp":1506280650.770839,' |1 |, y, ~$ \2 Q+ p7 ~$ v
- "transactions":[]
- },
- {5 y- O1 ^9 K4 r) |3 S0 Y
- "index":2,
- "previous_hash":"c099bc...bfb7",
- "proof":35293,( W+ ~) w* V( Y z+ d; V, [2 {" ~
- "timestamp":1506280664.717925,; p7 Z( Y1 R: B# ?9 h8 F
- "transactions":[
- {- j+ @7 z( W/ N( A
- "amount":1,# d/ c. \, _: S9 N/ A
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"& b8 \2 U% Z4 L4 U( d% U
- }) I( Z5 F7 \7 w- }
- ]
- },# A/ ?! n& W" A* F% b' k
- {0 s0 l. ^3 Y7 W4 C2 s9 j
- "index":3,
- "previous_hash":"eff91a...10f2",5 }* S& x) E1 t4 L* e
- "proof":35089,
- "timestamp":1506280666.1086972,
- "transactions":[" ^' i4 \9 n4 P6 R+ |% v9 q6 `: M
- {( a8 |: x& Z& Q; H# o
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }
- ]
- }% j1 o Q6 c5 |0 U
- ],
- "length":3. P$ y7 k( i" Y! g$ e
- }
四、一致性(共识)) N' t' h9 u! Q+ E7 V3 i$ u# Q
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。5 C: r6 G; ^/ `& I: Y
注册节点$ Y. H+ c9 ~: U* P. f. Z! _
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
/nodes/register接收URL形式的新节点列表
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
我们修改下Blockchain的init函数并提供一个注册节点方法:
- ...
- fromurllib.parseimporturlparse2 E, c- m3 r* @( R g
- ...
- classBlockchain(object):/ [5 Z: X8 {, g& F2 S# g+ q
- def__init__(self):
- ...2 ~( E0 u+ e6 ^# N+ B
- self.nodes=set()
- ...9 I$ z" t+ S& I2 e; f Q
- defregister_node(self,address):/ \) f) v2 a/ G9 W& L2 k" x
- """/ t& s9 r' Z& f7 O# u% _
- Addanewnodetothelistofnodes
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000') Z, c5 s: E) c I5 L1 g5 G& ^
- :return:None1 U' S# O* V. G8 U4 n
- """4 ~1 U! u5 l2 ~. j- }$ x& |
- parsed_url=urlparse(address). E) e, b/ `4 U% ?$ {: y$ q% Y
- self.nodes.add(parsed_url.netloc)' b+ f+ H$ {3 f X- _0 a! m( x, }
- ```4 W! ^( M1 }) d' ~4 w
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
- …& t6 N" o r8 ~: J q" U
- importrequests: v% ]$ W1 Q% x5 d# {9 ^6 c
- classBlockchain(object)
- …
- defvalid_chain(self,chain):
- """
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot1 m- {, z4 L: P( @
- """
- last_block=chain[0]
- current_index=15 j0 R: R, ]0 Z: {7 R+ A3 \
- whilecurrent_indexmax_lengthandself.valid_chain(chain):; S+ E8 R5 w- G5 m" A4 R
- max_length=length% F5 I h2 ?0 n4 o
- new_chain=chain3 ]& A: a+ W/ j4 E' M
- #Replaceourchainifwediscoveredanew,validchainlongerthanours. z' \$ S1 q9 W7 R# [+ M
- ifnew_chain:& N9 f6 m8 @9 G
- self.chain=new_chain
- returnTrue0 Q1 C q4 H, x; O
- returnFalse1 ^( A ^( ?' Q, O5 P
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。9 L6 \/ o1 t) g
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes():. Q. M5 }# ^, V. y: }+ Q
- values=request.get_json()8 ]8 ]( g7 F- ? Y
- nodes=values.get('nodes')
- ifnodesisNone:( `' o/ y7 i+ j
- return"Error:Pleasesupplyavalidlistofnodes",4007 O8 ?) A9 t1 g; Q$ N
- fornodeinnodes:! A; e: I: ^; m3 x0 P' \1 s) a. ]" k& ?$ u
- blockchain.register_node(node)8 G' N; B7 j) j3 e) g8 S
- response={# v# D" C) A7 @1 w/ @" W0 [
- 'message':'Newnodeshavebeenadded',# e& I# ~, M5 q) L" x8 i2 H
- 'total_nodes':list(blockchain.nodes),$ T9 @4 b1 m3 G9 S4 F; z
- }
- returnjsonify(response),201% f) U6 F4 h$ [: m
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():5 [) s& l$ P4 g4 ~
- replaced=blockchain.resolve_conflicts()
- ifreplaced:
- response={
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain
- }
- else:
- response={, [$ Y$ k5 ?, b o2 k: D
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain+ d: _- m7 s. l3 I$ W, U
- }
- returnjsonify(response),200
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
* o* F5 Y! K+ N% a( x. Y( k2 n2 x
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。