但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
准备工作' z4 ]) a/ X8 ^) {: O& }6 G3 w
我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
如果你不知道哈希值是什么,这里有一个解释。
这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
pipinstallFlask==0.12.2requests==2.18.4) m* j% h, o7 T# y- }! c
同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
一、开始创建BlockChain9 g( g% K' h K( W( ~" q. j: c
打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。3 I% `# N' x8 P, g. Q1 X% l
BlockChain类
首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
以下是BlockChain类的框架:. c1 e# c1 v7 N
- classBlockchain(object):
- def__init__(self):
- self.chain=[]
- self.current_transactions=[]
- defnew_block(self):
- #CreatesanewBlockandaddsittothechain
- pass K! \$ {7 t3 S0 u W7 a' t
- defnew_transaction(self):: I9 p1 S9 y- |1 P9 ]- m& P9 e
- #Addsanewtransactiontothelistoftransactions
- pass
- @staticmethod& M! m- _; a. o# d
- defhash(block):
- #HashesaBlock
- pass6 C- Z5 q& Z' F6 F; W9 i
- @property8 N4 S0 w! [5 D
- deflast_block(self):
- #ReturnsthelastBlockinthechain
- pass
-我们的区块链类的蓝图-
Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。3 s" D8 w% F5 U; A
块结构2 N8 B! g" D2 o( A, a' h
每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。" r( w4 e4 ~2 t
以下是一个区块结构:9 E* G4 k- H- m- T( ~3 G
- block={
- 'index':1,
- 'timestamp':1506057125.900785,, d8 F2 p+ u( N1 @& \4 g
- 'transactions':[
- {6 F7 Z# o9 T, ~& C/ I% Y# Z5 d
- 'sender':"8527147fe1f5426f9dd545de4b27ee00",
- 'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
- 'amount':5,1 h; o) m5 T* D# r {6 d' c
- }
- ],
- 'proof':324984774000,
- 'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"& C* @) Q6 B# m4 s& U
- }
-链上一个区块的例子-
到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。$ F5 Q7 a) |6 n: B3 e
确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。' D+ Y0 r, f' B) a; s1 D2 m" P' E
加入交易
接下来我们需要添加一个交易,来完善下new_transaction()方法。9 X( a8 M; m5 g" f5 s$ s
- classBlockchain(object):4 E G1 N) _. l, a; ~5 q
- ...2 k8 B% T$ y! ~
- defnew_transaction(self,sender,recipient,amount):
- """) U1 x7 W/ W4 ^7 ~5 {) P
- 生成新交易信息,信息将加入到下一个待挖的区块中
- :paramsender:AddressoftheSender$ Z2 u8 E P: [6 N8 H7 n+ t4 N0 p/ s
- :paramrecipient:AddressoftheRecipient1 E* w9 h0 X% s: n3 N
- :paramamount:Amount
- :return:TheindexoftheBlockthatwillholdthistransaction. L) Z$ F {4 V4 {
- """
- self.current_transactions.append({# E! S& H5 }2 m. q% F( H" n
- 'sender':sender,
- 'recipient':recipient,2 s2 a& N- U0 o" g
- 'amount':amount,
- })
- returnself.last_block['index']+1
new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
创建区块+ D3 x7 S) B V
当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
- importhashlib
- importjson
- fromtimeimporttime
- classBlockchain(object):4 y6 ^1 b. @7 M8 C
- def__init__(self): D% I+ f' Q, c' W9 M) a
- self.current_transactions=[]* c& ]; V' L, U. O. Y' C* Q, b
- self.chain=[]
- #Createthegenesisblock
- self.new_block(previous_hash=1,proof=100)5 M& _/ b6 f1 H( n' r+ J. U
- defnew_block(self,proof,previous_hash=None):
- """$ ?/ C4 Q8 K; H0 v9 m x
- 生成新块
- :paramproof:TheproofgivenbytheProofofWorkalgorithm4 A* B& O* O! {8 A
- :paramprevious_hash:(Optional)HashofpreviousBlock
- :return:NewBlock4 @* E) o$ }% C2 Y! L
- """
- block={9 ` ]! ^3 C8 C5 ?8 e2 Z: G+ u
- 'index':len(self.chain)+1,
- 'timestamp':time(),4 e, C" C4 {5 d+ Z6 M! G1 L- |
- 'transactions':self.current_transactions,# U0 w/ p0 T1 N7 V1 O) Y, f' s
- 'proof':proof,
- 'previous_hash':previous_hashorself.hash(self.chain[-1]),
- }
- #Resetthecurrentlistoftransactions
- self.current_transactions=[]: Q& Y( [6 ] h/ z/ W
- self.chain.append(block)0 E! J( ?" R' L( H
- returnblock. F# _# J+ v9 l7 m: s1 m$ o0 y
- defnew_transaction(self,sender,recipient,amount):
- """
- 生成新的交易信息,信息将加入到下一个待挖的区块中; \9 g& I5 k/ }5 P! n" A5 E
- :paramsender:AddressoftheSender# i6 P4 H& T9 f& ]* N7 V! ?9 |
- :paramrecipient:AddressoftheRecipient
- :paramamount:Amount! X/ G+ `4 `$ ]
- :return:TheindexoftheBlockthatwillholdthistransaction
- """
- self.current_transactions.append({
- 'sender':sender,
- 'recipient':recipient,; U" u4 q: e( e; v9 Q% `
- 'amount':amount,
- })
- returnself.last_block['index']+1
- @property
- deflast_block(self):& C' x; p# R9 ]5 j0 D
- returnself.chain[-1]
- @staticmethod% Z3 y. v, M! {/ D2 Q% M
- defhash(block):
- """1 D* g1 n- [) k
- 生成块的SHA-256hash值
- :paramblock:Block
- :return:! V& m$ a7 s+ R$ n/ t+ a2 K/ o: G
- """
#WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes& ]# Q: @8 D( w( w$ j
block_string=json.dumps(block,sort_keys=True).encode()
returnhashlib.sha256(block_string).hexdigest()4 Y2 @* i! s- r, @- R& C
上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
理解工作量证明& x7 X" e" w2 u
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。5 @7 w! h* q& C$ |1 c% ?' G
为了方便理解,我们举个例子:
假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
- fromhashlibimportsha256# Y5 s9 j/ E& G* J: ]
- x=5; A" P2 [, |0 ]3 ?+ {5 b
- y=0#y未知- a$ `, x# J/ s9 C, F- |
- whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
- y+=1
- print(f'Thesolutionisy={y}')
结果y=21,因为:
hash(5*21)=1253e9373e...5e3600155e860
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
当然,在网络上非常容易验证这个结果。4 O$ @2 V ^. ]3 ]; y
实现工作量证明0 F: ^* c9 A; X
让我们来实现一个相似PoW算法,规则是:
寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。3 p b0 s# H. H. u9 q* S5 ]" p+ S
- importhashlib# }7 V" O- A1 M `% L, v
- importjson8 J: B1 p6 ^9 M! q6 b2 b: u+ V
- fromtimeimporttime
- fromuuidimportuuid4
- classBlockchain(object):+ `9 z' X1 N, \" {
- ...
- defproof_of_work(self,last_proof):, _* J; H7 m# I# s! g( W
- """- \% M$ l F/ x! Q% ~
- 简单的工作量证明:
- -查找一个p'使得hash(pp')以4个0开头
- -p是上一个块的证明,p'是当前的证明+ }2 v: U/ f- ?. F6 w* L) I
- :paramlast_proof:2 @$ d: b% G9 K
- :return:
- """
- proof=0
- whileself.valid_proof(last_proof,proof)isFalse:
- proof+=10 g8 B& \. g$ ]& m3 B1 y# j
- returnproof
- @staticmethod( I) x6 _: d/ S% W, @
- defvalid_proof(last_proof,proof):& j# n9 F9 h' @
- """8 j. t/ A6 t. R# y1 t
- 验证证明:是否hash(last_proof,proof)以4个0开头?! q) s! Y* S2 n: N5 B2 K. w0 t
- :paramlast_proof:PreviousProof6 @" I; x3 l. }+ X6 l0 X5 b [
- :paramproof:CurrentProof
- :return:Trueifcorrect,Falseifnot.: R$ ]# R: w. j8 y
- """% Z4 _% ^9 D9 t4 t* a
- guess=f'{last_proof}{proof}'.encode()
- guess_hash=hashlib.sha256(guess).hexdigest()
- returnguess_hash[:4]=="0000" w, t; I5 r2 O/ v
- ```
衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。1 ~2 H2 q( F! f4 Q$ {8 c) c" b, R
现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。$ e7 i% s8 A/ i+ d3 R9 N
##二、BlockChain作为API接口
我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。2 k4 |5 w* O: D3 |- |
我们将创建三个接口:
- ```/transactions/new```创建一个交易并添加到区块
- ```/mine```告诉服务器去挖掘新的区块3 x+ K+ C9 z4 t3 \ `
- ```/chain```返回整个区块链2 D4 o/ }8 V) b! j x
- ###创建节点1 h1 G H% h6 j2 O, H
- 我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:6 e' w- K4 H& G [
- importhashlib. q/ r& p; k2 i0 f0 L. e
- importjson
- fromtextwrapimportdedent
- fromtimeimporttime
- fromuuidimportuuid4
- fromflaskimportFlask
- classBlockchain(object):, ^. {9 a \) M7 ~$ A/ ]* ~$ q
- …
- InstantiateourNode
- app=Flask(name)
- Generateagloballyuniqueaddressforthisnode
- node_identifier=str(uuid4()).replace(’-’,‘’): R1 M+ i% |% u0 E
- InstantiatetheBlockchain8 V% }. W6 O8 U1 E: Q/ E6 `" U
- blockchain=Blockchain()
- @app.route(’/mine’,methods=[‘GET’])
- defmine():7 t) U t! H J1 e- H
- return“We’llmineanewBlock”$ T7 X1 s- K1 [' D" D }
- @app.route(’/transactions/new’,methods=[‘POST’])
- defnew_transaction():
- return“We’lladdanewtransaction”
- @app.route(’/chain’,methods=[‘GET’])& }/ r" ?( |. E4 m# C0 w0 D. r
- deffull_chain():( I) U1 g. p- l+ W
- response={7 A# [( H* o- l# {' J
- ‘chain’:blockchain.chain,: k* ]2 M. W- ?, D3 p& O! v$ _& r D, c; T
- ‘length’:len(blockchain.chain),
- }
- returnjsonify(response),200
- ifname==‘main’:- ~5 R4 N/ z9 ^' y6 l9 s/ l
- app.run(host=‘0.0.0.0’,port=5000)- O5 g, ] t( G# e
- ```
- 简单的说明一下以上代码:& E4 K$ l; N$ E. Q8 R. X
- 第15行:创建一个节点。在这里阅读更多关于Flask的东西。
- 第18行:为节点创建一个随机的名字。
- 第21行:实例Blockchain类。' _9 F5 I: \2 t% o: S( x S8 n* c
- 第24–26行:创建/mineGET接口。
- 第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。+ ~9 O; k- T7 I& l4 Y
- 第32–38行:创建/chain接口,返回整个区块链。! l9 ?. R' w1 b# A! u
- 第40–41行:服务运行在端口5000上。( m$ |5 s) F% G* z
- 发送交易6 x0 A( f b% F9 w% N& h
- 发送到节点的交易数据结构如下:7 X6 @& R' Y' Y" r, Q0 n! d& C) J
- {
- "sender":"myaddress",
- "recipient":"someoneelse'saddress",
- "amount":5
- }
- 之前已经有添加交易的方法,基于接口来添加交易就很简单了:
- importhashlib- Z. ?. P* E4 y- r( M4 ^4 K
- importjson, G8 d# v! P; H( ]- s8 f& H( j
- fromtextwrapimportdedent
- fromtimeimporttime# }* D7 r( w$ J, `3 U6 [
- fromuuidimportuuid4, _! O6 Z* _$ Z8 q8 W- I( p' d
- fromflaskimportFlask,jsonify,request
- ...9 t6 W/ }* }! t% }- D' m2 o
- @app.route('/transactions/new',methods=['POST'])
- defnew_transaction():
- values=request.get_json()2 {/ W' U* w0 C. q& F- C' k
- #CheckthattherequiredfieldsareinthePOST'eddata! ~0 Z) ^/ a; R7 N! r+ p# U3 z+ P
- required=['sender','recipient','amount']2 L7 m; g' ]9 |9 Y& [9 A. o
- ifnotall(kinvaluesforkinrequired):
- return'Missingvalues',4007 Y2 ]; Q! T0 W. z+ i
- #CreateanewTransaction
- index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])4 [# O1 c8 x( F8 s1 J, ], Y
- response={'message':f'TransactionwillbeaddedtoBlock{index}'}
- returnjsonify(response),201
- ```( H Q; x$ \7 t0 |4 N0 p
- -创建交易的方法-
- ###挖矿
- 挖矿正是神奇所在,它很简单,做了一下三件事:" }6 Q, m6 k6 W
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
- importhashlib2 w! V% q8 n1 N# D* }# P, S
- importjson. B" R! C8 f3 V0 ~4 b# \
- fromtimeimporttime
- fromuuidimportuuid4" @* b7 u2 [+ B. q, R: _
- fromflaskimportFlask,jsonify,request
- …
- @app.route(’/mine’,methods=[‘GET’])
- defmine():. s U- g( y! n/ G6 \; E
- #Weruntheproofofworkalgorithmtogetthenextproof…2 ~$ N; a7 b* r0 }
- last_block=blockchain.last_block
- last_proof=last_block[‘proof’]
- proof=blockchain.proof_of_work(last_proof)
- #给工作量证明的节点提供奖励.
- #发送者为"0"表明是新挖出的币.
- blockchain.new_transaction(8 K7 E* U$ H+ P4 Q: g, c5 t
- sender="0"," |. {- T" x( {
- recipient=node_identifier,
- amount=1,5 h- l! ~# O( W" T/ [& x7 K' y
- ) o4 t, k- s- h' K
- #ForgethenewBlockbyaddingittothechain% T" H% Z- `7 U: ]- o! @
- previous_hash=blockchain.hash(last_block)
- block=blockchain.new_block(proof,previous_hash): u1 L3 s! a/ g9 |9 B& s; a
- response={; \$ b# E) d+ I" K# @( x: V
- 'message':"NewBlockForged",
- 'index':block['index'],( z1 o3 Q" C6 d- X
- 'transactions':block['transactions'],- M! d' ^, E6 ]6 J0 F0 J7 W
- 'proof':block['proof'],
- 'previous_hash':block['previous_hash'],
- }. Y' x3 b) g; i8 A g8 o3 p
- returnjsonify(response),200# j# f; j2 ?1 s' h( ]3 \
- ```
注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。7 ^0 ]( ?0 p& T& C! P# \
三、运行区块链
你可以使用cURL或Postman去和API进行交互+ ?$ c7 W3 W- T9 A
启动server:
$pythonblockchain.py. |7 S" c$ e5 h/ E6 C# z( p' y
*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
-使用Postman以创建一个GET请求-! S n8 y( [% x4 @
通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
-使用Postman以创建一个POST请求-
如果不是使用Postman,则用一下的cURL语句也是一样的:9 U1 K% z# Y$ Y, I7 l# m
$curl-XPOST-H"Content-Type:application/json"-d'{
"sender":"d4ee26eee15148ee92c6cd394edd974e",( C" z6 k/ c, V% v
"recipient":"someone-other-address",
"amount":5* I3 h3 ~# Y: t
}'"http://localhost:5000/transactions/new"
在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:3 E5 o$ W! a- ~" ^
- {
- "chain":[
- {
- "index":1,
- "previous_hash":1,6 L* F* s- x* |( e
- "proof":100,
- "timestamp":1506280650.770839,' V# A$ E) O1 T: `! D1 n" h2 |) c
- "transactions":[]
- },' w/ {* }/ r' L8 h" u
- {
- "index":2,6 L$ G' u# \4 `+ F% L/ q" N
- "previous_hash":"c099bc...bfb7",. \! o6 |+ w# }+ \) S6 z+ d
- "proof":35293,
- "timestamp":1506280664.717925,1 O& n/ p. b( O0 L
- "transactions":[
- {' g9 y5 C+ h6 T
- "amount":1,
- "recipient":"8bbcb347e0634905b0cac7955bae152b",
- "sender":"0"' L% H \. O0 q- C8 Z: b8 M" K
- }0 U, T! M; a! U, c7 D2 F6 K/ G
- ]
- },
- {. Y/ D3 E& k- g3 Y0 R$ |
- "index":3,
- "previous_hash":"eff91a...10f2", M6 b, [ U7 Y0 E* `
- "proof":35089,
- "timestamp":1506280666.1086972,
- "transactions":[
- {
- "amount":1,9 m2 [1 G* T7 y$ {
- "recipient":"8bbcb347e0634905b0cac7955bae152b",7 Z$ r" i% _* I
- "sender":"0", o$ P3 C" N( m* V. M
- }: H* P- s& L; l! m2 p3 O3 Z5 h
- ]
- }
- ],
- "length":3/ V1 c- T! G* F
- }
四、一致性(共识)
非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。7 e! k! a( C: a3 P0 G) k% ?4 n* ~
注册节点: K6 j+ `: i r4 W
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
/nodes/register接收URL形式的新节点列表$ D+ @2 T; |- j: `! D! I
/nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链# h" {# [/ z9 T: Y$ t
我们修改下Blockchain的init函数并提供一个注册节点方法:- H* Q4 ^7 r& i) M9 S) a3 l9 T
- ...
- fromurllib.parseimporturlparse
- .... S# p" R: h5 c4 `% a5 {+ Q
- classBlockchain(object):
- def__init__(self):7 d5 @1 @1 W$ {* E1 p
- ...
- self.nodes=set()
- ...
- defregister_node(self,address):
- """5 P1 o2 k/ ^, ?) |3 z7 G6 \
- Addanewnodetothelistofnodes4 R- X' ^' D, K5 X3 c, I& h
- :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
- :return:None2 m. G2 K7 u" e/ j. Z0 S
- """
- parsed_url=urlparse(address), D6 s0 o: ^1 H; q* V8 K
- self.nodes.add(parsed_url.netloc)
- ```
- 我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。# H8 e9 e9 q8 K* Y7 u
- ###实现共识算法
- 前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
- …8 C# U& R/ D3 {, u& { m$ _
- importrequests
- classBlockchain(object)8 X5 O8 {( f+ q7 M: Z8 E" a5 w
- …- C/ {$ T, w" [" V& @7 S- Y: C
- defvalid_chain(self,chain):
- """
- Determineifagivenblockchainisvalid
- :paramchain:Ablockchain
- :return:Trueifvalid,Falseifnot) V @) t( X2 t' d/ \
- """
- last_block=chain[0]
- current_index=1
- whilecurrent_indexmax_lengthandself.valid_chain(chain):
- max_length=length
- new_chain=chain
- #Replaceourchainifwediscoveredanew,validchainlongerthanours" ^3 ] `9 _2 ~
- ifnew_chain:' k8 g O+ s* J
- self.chain=new_chain
- returnTrue
- returnFalse
- ```
第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。
第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
让我们添加两个路由,一个用来注册节点,一个用来解决冲突。6 n, ?+ O C1 p4 d: {4 v! ?* B
- @app.route('/nodes/register',methods=['POST'])
- defregister_nodes(): Y* T# ?7 \3 u, p* I6 o
- values=request.get_json()( }, K. ~" z/ V) I
- nodes=values.get('nodes')( b& a4 J: Y* f$ x5 h
- ifnodesisNone:( A# A4 U4 @1 v8 C1 C
- return"Error:Pleasesupplyavalidlistofnodes",4000 }2 q2 Y0 q3 F: _% r
- fornodeinnodes:( G2 Z; M3 y* p
- blockchain.register_node(node)& b4 A6 O1 Z# }5 T* \$ y0 u1 {8 Y
- response={
- 'message':'Newnodeshavebeenadded',
- 'total_nodes':list(blockchain.nodes),0 P6 W4 f# Z" g- @/ j
- }) D- f8 |- f- `3 S. Z. l, }/ g
- returnjsonify(response),201
- @app.route('/nodes/resolve',methods=['GET'])
- defconsensus():
- replaced=blockchain.resolve_conflicts()( }9 f$ }$ i) F
- ifreplaced:
- response={
- 'message':'Ourchainwasreplaced',- x! D& a1 O6 f4 Q6 W7 a0 \+ m$ d9 Q
- 'new_chain':blockchain.chain# w+ B( d6 o1 j
- }( d, a( c" L6 J0 k: F
- else:" u- q; {! V+ T* z
- response={8 `# o# b7 x; y$ j4 s
- 'message':'Ourchainisauthoritative',
- 'chain':blockchain.chain
- }; a1 u$ R: h$ t8 M# @( A/ o
- returnjsonify(response),200- \ e% {2 x7 r! b" M7 z, J
- ```
你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
. B1 I; R+ d1 u1 h7 c
-注册一个新的节点-
然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。