但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。+ p9 X K. U- C5 b4 Q( k7 o/ a
准备工作* C2 }0 o. H3 Q& J
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:# y; g$ g; d+ l7 R
pipinstallFlask==0.12.2requests==2.18.46 [; [2 p& ^0 T, |8 ]) q
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain; y0 q0 @1 N5 I7 ? p
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
BlockChain类; _$ u& @3 f! Q e2 D0 H& M9 Z
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。$ ?' s# @; \- Q! F) `9 H
以下是BlockChain类的框架:
- classBlockchain(object):0 }! K1 W( m, z% C8 E8 \. U" T
- def__init__(self):+ X& x Y7 v; o( k6 Q3 a
- self.chain=[]
- self.current_transactions=[]
- defnew_block(self):" M& n- r$ S, E5 D. [: l
- #CreatesanewBlockandaddsittothechain4 N5 x, c M( d4 U
- pass
- defnew_transaction(self):
- #Addsanewtransactiontothelistoftransactions/ ?: z- X5 B) x: ^$ s9 u
- pass; k/ D- B1 ~8 l1 E* P
- @staticmethod
- defhash(block):3 X7 I: b+ } R2 Y% \* T
- #HashesaBlock# \& ?3 g, r3 R2 I
- pass# v5 w2 A/ r4 k5 u. D" t2 X
- @property F. D0 n) I4 }# x4 Q/ l0 K1 C
- deflast_block(self):
- #ReturnsthelastBlockinthechain
- pass
-我们的区块链类的蓝图-4 b- H- c2 t& X @* V4 R
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
块结构
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。) f8 |( ?; ^5 w' ?( @. K
以下是一个区块结构: `8 V- E$ w g5 K4 C; y1 p: e
- block={
- 'index':1,
- 'timestamp':1506057125.900785,3 v- {: q+ i6 ~/ x8 F" N! {
- 'transactions':[
- {8 R: [! R; P$ W" d z" J% Q- j
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",8 v4 j' ~( J$ B: {! |$ }
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",* o! m. K" v' `
- 'amount':5,+ v" O( \3 T* c, L0 A
- }
- ],
- 'proof':324984774000,' X5 H9 V; q1 c8 t9 B7 P A$ ~
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。8 L1 x6 h, u4 ^, [: ~9 T
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
加入交易( j8 d, G0 k9 K" x5 C% ]
接下来我们需要添加一个交易,来完善下new_transaction()方法。6 R7 U6 y' |) {! E
- classBlockchain(object):. `1 x- E7 c5 t$ P. O
- ...
- defnew_transaction(self,sender,recipient,amount):
- """$ g6 _3 O% u7 d6 K; Y7 _. }8 I
- 生成新交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender$ K$ T. ^* [8 T
- :paramrecipient:AddressoftheRecipient' z9 D0 S+ x+ J" [# S% J2 [
- :paramamount:Amount! ?8 i ]; b$ e, E. ~$ z+ R1 F
- :return:TheindexoftheBlockthatwillholdthistransaction
- """
- self.current_transactions.append({" i! C% h( ~7 q; ?6 u" D5 r
- 'sender':sender,9 W7 i! S Y% ^$ H/ q$ M0 u0 C
- 'recipient':recipient,
- 'amount':amount,6 O0 R9 q5 M, j i0 Q! K
- })2 r% V, ^ F8 Z& ~; p
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。% ?+ ^* m8 H7 U6 p
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib3 V( o8 I8 v3 m3 a% J
- importjson. p8 ~1 ~2 k( `/ g. t! H( ~
- fromtimeimporttime
- classBlockchain(object):# I) p, _. G# l4 ?
- def__init__(self):: Y4 B& o" H: S) S( Z; p
- self.current_transactions=[]
- self.chain=[]; H8 k5 M2 C( J% z4 C* j
- #Createthegenesisblock' @0 D% ?5 R g4 A5 o0 e' W
- self.new_block(previous_hash=1,proof=100), w2 p1 K. y3 {3 S+ y7 T4 v* ^6 h
- defnew_block(self,proof,previous_hash=None): o2 E1 H- e3 H) i! i; i
- """
- 生成新块
- :paramproof:TheproofgivenbytheProofofWorkalgorithm
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock. N' ~- I, e3 w" Q. l/ \/ h& y
- """ C( |0 o- o/ g
- block={, h* P, @' c% X& |# _, n6 `
- 'index':len(self.chain)+1,
- 'timestamp':time(),
- 'transactions':self.current_transactions,
- 'proof':proof,( H2 B- L9 J, d' Z2 [& X
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),
- }' a7 j- S9 }) V
- #Resetthecurrentlistoftransactions
- self.current_transactions=[]* i$ {; \: S7 l
- self.chain.append(block)
- returnblock
- defnew_transaction(self,sender,recipient,amount):
- """
- 生成新的交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender
- :paramrecipient:AddressoftheRecipient, J" i+ y7 r, L, k5 i" B/ v# s
- :paramamount:Amount6 n6 n: r( G r$ }+ u; a
- :return:TheindexoftheBlockthatwillholdthistransaction
- """% N! m) u0 q, i b! B, s
- self.current_transactions.append({- Y4 z8 Q n0 R$ N0 e
- 'sender':sender,/ A5 y( c! t* r! g
- 'recipient':recipient,4 q0 x4 [( Z9 N9 C8 _% M+ ]
- 'amount':amount,
- })
- returnself.last_block['index']+1$ J/ j: p; J- K2 j6 G
- @property5 O0 O( P% I* n ?* {4 |
- deflast_block(self):
- returnself.chain[-1]. p4 N' J+ m3 v: C7 j Z
- @staticmethod4 A* Y0 M8 g5 F P+ m
- defhash(block):
- """5 c! l. c( ]5 q
- 生成块的SHA-256hash值
- :paramblock:Block
- :return:) j: N! Z2 D2 h" ?7 ^
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()' E% A. B0 m/ d4 L# N& i5 i
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明% T, J( f# U1 g$ j
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
为了方便理解,我们举个例子:1 I, l. S2 Q4 t; T4 P6 q6 x) a
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:* L. P5 j; y. Y& W" I1 t
- fromhashlibimportsha256
- x=5
- y=0#y未知 i- k8 ?$ U/ p7 }, ?0 B6 p; d
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":/ C) R* E1 G8 {6 ~) m$ N9 B. }
- y+=17 A1 s, S z; @( z
- print(f'Thesolutionisy={y}')
结果y=21,因为:6 L3 Y5 Y: C+ I a$ w' R
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。0 l ^/ d) u9 v
当然,在网络上非常容易验证这个结果。# k2 p6 a, ]7 o) I8 ^9 b8 X
实现工作量证明
让我们来实现一个相似PoW算法,规则是:
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。! N* v) \1 n: D' q1 Q/ B4 |
- importhashlib
- importjson
- fromtimeimporttime% b/ I5 ]/ ^, l& y- G3 @
- fromuuidimportuuid41 N2 w% c' A# K# O+ u6 I
- classBlockchain(object):' U1 S/ B5 W5 \- W
- ...( t2 S8 ?; w& f
- defproof_of_work(self,last_proof): w# W3 K9 T- m q- d3 D/ A g
- """
- 简单的工作量证明:1 N8 i# D- h* c$ a9 m7 ^
- -查找一个p'使得hash(pp')以4个0开头* w- H" m' h5 K) n. N7 r- A
- -p是上一个块的证明,p'是当前的证明
- :paramlast_proof:. C6 P% k0 b$ R+ y5 a0 @
- :return:- ]3 g7 |. w& K: T [
- """
- proof=0% R- n" S4 k7 G2 f1 D( X
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=1$ Q! h/ G; R& L! u3 o5 @9 l1 d! n
- returnproof% T3 U7 n5 u/ D+ u* n
- @staticmethod
- defvalid_proof(last_proof,proof):
- """
- 验证证明:是否hash(last_proof,proof)以4个0开头?4 Y# D I# X* ~8 {6 \, I p8 c
- :paramlast_proof:PreviousProof6 U$ J- b. F3 n4 t) p
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot./ e% M2 B9 d& q* x! j3 O! \
- """
- guess=f'{last_proof}{proof}'.encode()3 v" ~: M4 b: r" M' h T+ X
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000": f3 ^2 B% D# {3 e; [6 l
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。! ]% H% q$ X3 T, n) p; D2 y
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。% | e+ ]" x2 o& K! D* c( f
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
我们将创建三个接口:& @1 N* y9 B2 f$ a- @7 \5 U' V
- ```/transactions/new```创建一个交易并添加到区块+ Q$ _( O7 }& D& z
- ```/mine```告诉服务器去挖掘新的区块
- ```/chain```返回整个区块链
- ###创建节点
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:) I" q* Q2 ^! U9 q
- importhashlib; S+ [/ f+ w8 L& l
- importjson% p* [: R3 x- D2 {
- fromtextwrapimportdedent
- fromtimeimporttime$ \% }; h' u% }! R" P; m C, {8 ]7 L) z
- fromuuidimportuuid4
- fromflaskimportFlask
- classBlockchain(object):
- …& I+ h) D3 N' V# [7 T# c# i
- InstantiateourNode6 V, e' N: }; _6 Z& q& {* Z4 z: C
- app=Flask(name)% c+ G6 Q& r' `+ Z3 t( ^1 Q5 c
- Generateagloballyuniqueaddressforthisnode& x4 g+ @2 i, h+ G5 N" a% `
- node_identifier=str(uuid4()).replace(’-’,‘’)5 l& e/ c- q3 L N. P
- InstantiatetheBlockchain# H. A/ C' T( L4 y0 { N$ }+ A
- blockchain=Blockchain()* u9 E( v( i/ G6 a& ~
- @app.route(’/mine’,methods=[‘GET’])3 E& m. @8 i- W1 S
- defmine():
- return“We’llmineanewBlock”
- @app.route(’/transactions/new’,methods=[‘POST’])2 k) ?# k5 p& r) Q+ P- O
- defnew_transaction():
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])% |; g( H- b* s( v- X2 X
- deffull_chain():8 H! x5 n8 y) y) u4 B4 B
- response={3 o" m1 f, w7 g; F: |( f$ ~
- ‘chain’:blockchain.chain,7 G9 L; |5 D2 k( I0 z. w/ J z/ ~9 I; s
- ‘length’:len(blockchain.chain),& Q, K; o: d! g/ ^
- }4 C3 Z: S0 K7 j8 e
- returnjsonify(response),200
- ifname==‘main’:
- app.run(host=‘0.0.0.0’,port=5000)
- ```- I0 w" p" U& i6 P; A' n
- 简单的说明一下以上代码:
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。- @( z+ q6 b& G! S. T; G. [
- 第18行:为节点创建一个随机的名字。4 X2 H) w4 g% S+ d" R
- 第21行:实例Blockchain类。
- 第24–26行:创建/mineGET接口。6 {' _; D E% k9 H* f
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
- 第32–38行:创建/chain接口,返回整个区块链。
- 第40–41行:服务运行在端口5000上。# O( v0 F' l. I
- 发送交易4 F6 L* `1 {8 S" H; E. r
- 发送到节点的交易数据结构如下: u* U" n$ X- p2 u
- {
- "sender":"myaddress",* Z; P6 o6 k, K2 s
- "recipient":"someoneelse'saddress",
- "amount":5
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:8 D( |2 }# q9 I# X# m( |4 r
- importhashlib
- importjson
- fromtextwrapimportdedent
- fromtimeimporttime* l! Z: d$ {! z0 c) L) a
- fromuuidimportuuid41 M3 D5 c9 n2 J
- fromflaskimportFlask,jsonify,request+ g" Y4 A9 x( Z; M# A$ S# P
- ...
- @app.route('/transactions/new',methods=['POST'])% e7 T; ~% r0 h3 W
- defnew_transaction():
- values=request.get_json()
- #CheckthattherequiredfieldsareinthePOST'eddata
- required=['sender','recipient','amount']1 x0 v" l, I8 t) A$ f) t
- ifnotall(kinvaluesforkinrequired):6 P/ O/ g+ G% t p( Z5 Z2 M
- return'Missingvalues',400
- #CreateanewTransaction$ ^+ \4 o" |% N8 [+ I" l! [5 v5 H, K
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])1 i" U' U' G" B4 @6 f5 L* O
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}2 B- ~, V! V- F) J$ @
- returnjsonify(response),201
- ```
- -创建交易的方法-7 n# ?/ A% m6 N
- ###挖矿# s; S. B" _9 H* f
- 挖矿正是神奇所在,它很简单,做了一下三件事:
- 计算工作量证明PoW5 ^& W3 }( u6 a- c- I) f
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中# {6 a# ^: ^# H! y' U$ g
- importhashlib0 \% ] Z" O7 m$ _! ]
- importjson6 s) n9 @& G; V R3 h9 u
- fromtimeimporttime! ~3 f$ ]$ a; }; Y/ Z/ f+ D9 s. h
- fromuuidimportuuid49 E- V2 r- X: g* o* X
- fromflaskimportFlask,jsonify,request' r8 T( O! ] Z4 c
- …
- @app.route(’/mine’,methods=[‘GET’])% o. |. L/ {5 G
- defmine():
- #Weruntheproofofworkalgorithmtogetthenextproof…! e' I, n8 [$ O/ ^" [" i* K
- last_block=blockchain.last_block6 d3 e/ y4 F) F( }/ b& f# k
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)& A$ |5 G, R0 k( h0 W5 b& g
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.2 B& h* O' o# f& S
- blockchain.new_transaction(* }+ n: C( c C, c O7 u! k# Y9 A0 J
- sender="0",$ |' j! Z1 R4 n" c: L
- recipient=node_identifier,
- amount=1,0 `7 t/ u$ B" }2 L4 f, g6 S: _4 Q9 I
- )% U' y2 \7 R3 ]. a
- #ForgethenewBlockbyaddingittothechain9 F) ]# h2 M" V" f( F2 n
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash)6 e& B- p8 u/ O+ d- F/ U
- response={3 v$ [ I0 v$ T0 n7 q6 }/ f2 P
- 'message':"NewBlockForged",
- 'index':block['index'],1 ^6 `6 _8 C% h8 n
- 'transactions':block['transactions'],* j) s3 @% T! j* @5 q
- 'proof':block['proof'],
- 'previous_hash':block['previous_hash'],
- }
- returnjsonify(response),200- C$ v1 P/ E% D+ p k( I5 P
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。8 H# ~, G& Z& `2 ]
三、运行区块链. @8 t* u8 W; z( T
你可以使用cURL或Postman去和API进行交互
启动server:. f' K1 C- N9 H j
$pythonblockchain.py
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)9 h' B! v8 M0 ^+ n( }8 V" i V3 x
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:2 F0 t* D. N. E5 S9 ~9 B
-使用Postman以创建一个GET请求-
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:0 s. ~& X8 V# c- |/ h! s8 t
-使用Postman以创建一个POST请求-! K8 h9 {5 I1 E
如果不是使用Postman,则用一下的cURL语句也是一样的:
$curl-XPOST-H"Content-Type:application/json"-d'{+ F5 @8 i# {% M" j# `$ w. n
"sender":"d4ee26eee15148ee92c6cd394edd974e",
"recipient":"someone-other-address",4 z H/ q* p2 v2 ?
"amount":5
}'"http://localhost:5000/transactions/new"
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
- {/ g6 |- S* I$ p* e1 ]
- "chain":[8 B7 q" h0 V& k. V: g* a
- {& m3 I, l3 r3 C, `8 x: Y# l9 p
- "index":1,# x& n+ D* ]- a5 T" g
- "previous_hash":1,
- "proof":100,3 M3 q' |3 q& e% ?2 \. N
- "timestamp":1506280650.770839,7 O4 O( G4 f8 C
- "transactions":[]
- },
- {- x3 {% Q/ y0 l
- "index":2,
- "previous_hash":"c099bc...bfb7",4 R3 |7 ^ P* c! l! o) h, F" b
- "proof":35293,
- "timestamp":1506280664.717925,! `# T" K/ U9 g [
- "transactions":[5 K6 J& [2 N7 f( H/ ?
- {
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"
- }
- ]
- },
- {/ z8 [. H' _( ~2 e8 T. W1 A1 u5 w4 W
- "index":3,
- "previous_hash":"eff91a...10f2",1 c+ F# x F7 H" X
- "proof":35089,
- "timestamp":1506280666.1086972,
- "transactions":[
- {
- "amount":1,( |; @( o6 s& T0 P2 T3 k: }5 ?
- "recipient":"8bbcb347e0634905b0cac7955bae152b",$ J% a- f2 R! w2 s# f$ s& r
- "sender":"0"
- }$ V6 T- |1 H. ^! W
- ]
- }8 y9 D3 s% w4 Q8 H* b& o& Q
- ],# |( u9 F0 h# f# K
- "length":34 _7 c5 I1 S4 I
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
注册节点
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
/nodes/register接收URL形式的新节点列表6 s) R# h1 a3 h+ k7 a) {6 ^
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链0 G1 b: }4 V. {1 G& `7 K
我们修改下Blockchain的init函数并提供一个注册节点方法:
- ... G( Y5 b0 G- h: x( O
- fromurllib.parseimporturlparse( B8 c! ?* \3 r+ T+ Z0 {
- ...
- classBlockchain(object):
- def__init__(self):
- ...5 |2 f. \8 t9 L4 t L9 ]
- self.nodes=set()( r2 r X" ~9 `% |7 Y
- ...& {) L' v5 U3 @# W/ ]8 I' v
- defregister_node(self,address):
- """6 R. w2 g+ J4 M9 j5 i3 A
- Addanewnodetothelistofnodes
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None
- """
- parsed_url=urlparse(address)9 S g$ y* O9 H1 f
- self.nodes.add(parsed_url.netloc)
- ```
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。; t1 D! \7 f. e2 h- y+ c
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。+ k% N# _4 n0 C0 o2 K
- …- O* L! \# @0 L8 d4 U
- importrequests# p$ `. o) I, q0 A6 m
- classBlockchain(object)1 l: O8 U& N8 i' k6 q# ^
- …
- defvalid_chain(self,chain):$ K) x# T8 P b
- """+ Z( Z% r* s7 Z# Q& L
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot/ _1 W9 ~2 _7 y \% Z5 ^5 [ N
- """
- last_block=chain[0]; \. q% e6 q4 t# K( }0 q
- current_index=1! [. ?; [$ d# ^- W! e6 h, z6 W7 s
- whilecurrent_indexmax_lengthandself.valid_chain(chain):
- max_length=length
- new_chain=chain
- #Replaceourchainifwediscoveredanew,validchainlongerthanours! J6 u1 ?5 D0 ?' z( R; ]5 P
- ifnew_chain:3 `* g9 o( P b' E- S
- self.chain=new_chain! i4 w! c3 I3 R5 o2 G
- returnTrue8 ~8 g# l, i- K9 Z0 u# T
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。! z, c* d$ v) [# H
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。& @& [: _' ~ p8 u
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。6 R9 M, E1 R _7 Y T
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes():
- values=request.get_json()/ D, s' m# V1 H- R, v6 m
- nodes=values.get('nodes')
- ifnodesisNone:
- return"Error:Pleasesupplyavalidlistofnodes",400
- fornodeinnodes:
- blockchain.register_node(node). B3 S$ I$ S+ C* \6 R4 C
- response={
- 'message':'Newnodeshavebeenadded',- _+ C/ v2 c" q0 R
- 'total_nodes':list(blockchain.nodes),
- }& z4 K- D/ q6 n: `4 u' J
- returnjsonify(response),2012 R+ J# p- u4 [2 x7 R6 u6 M
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():
- replaced=blockchain.resolve_conflicts()
- ifreplaced:
- response={6 l* ?' x* F+ H' V# W
- 'message':'Ourchainwasreplaced',
- 'new_chain':blockchain.chain
- }; c1 `9 l1 z+ N3 F4 g; ]3 T
- else:+ `& l `) ~* l
- response={4 M2 `2 c# t, M
- 'message':'Ourchainisauthoritative',+ c- O, V# u' s5 ~
- 'chain':blockchain.chain; n3 _3 Z, K/ o7 G
- }! a6 T, O4 a0 [, A
- returnjsonify(response),2008 [+ Q3 I* ~& J1 r) D3 ~
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。