Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
286 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
# V* K5 m. A, L! ?- k& z! i* g    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
, i0 E) k. ?* j- d    准备工作
& @" \0 ~! T  A) f" r    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(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
6 ?, G5 [- ?4 k    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
1 U- }& g2 c# N% P    一、开始创建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
  
  1. classBlockchain(object):
    ) f# M* u. R$ M1 _4 f# l
  2.     def__init__(self):, ~  V5 j/ N, T8 X
  3.     self.chain=[]. }0 M/ y- ^+ r7 i, K
  4.     self.current_transactions=[]
      k' k3 P0 k4 N+ g6 Q
  5.     defnew_block(self):
    - D# u1 f9 K9 \( ~5 [$ S
  6.     #CreatesanewBlockandaddsittothechain. t0 j! P- U# ~2 E/ U9 h
  7.     pass. L: b' z$ U* g6 e+ o6 D# K6 O( B
  8.     defnew_transaction(self):
      Y1 f) ]) h! q& u
  9.     #Addsanewtransactiontothelistoftransactions1 }: L+ _9 Q  k1 b
  10.     pass2 {# G3 g: O% J. [" g. }( {8 U. Z2 ]
  11.     @staticmethod
    - @4 R4 l2 A* Y, q
  12.     defhash(block):
    3 S4 W( N) N" H0 }7 n  w7 G/ i
  13.     #HashesaBlock
    # O' U- k; x1 ]# a* g) L
  14.     pass4 E  ~% ?1 u0 P) b" }( @/ f) U/ b
  15.     @property" G9 m. H& i) L# i5 F0 w
  16.     deflast_block(self):
    " k, Y1 }2 _( H& P+ z+ g
  17.     #ReturnsthelastBlockinthechain
    7 ^: g9 ?( r9 M% ^) P1 ^2 U
  18.     pass
复制代码
. n' O: f. _, \! f3 G
    -我们的区块链类的蓝图-/ 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
    以下是一个区块结构:
. o6 ~4 f/ Z* i+ h- M   
  1. block={; t  n3 R: L1 l; R* q/ Q
  2.     'index':1,. \$ S9 B4 u7 G" Y8 u8 v+ X
  3.     'timestamp':1506057125.900785,
    ' K# `. L! ?/ z* q# B
  4.     'transactions':[
    ! E" [7 E2 I# ]
  5.     {
    & ?0 L% h8 g2 c
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    0 i, b. f( D6 _$ l  F8 d  c
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
    # X  J" B" g! J8 x+ {0 G, v
  8.     'amount':5,9 e6 V4 h: e/ b
  9.     }3 {3 W  H! U2 t- ?! J
  10.     ],
    6 E8 {: `! O. J0 ?$ v3 d" t/ ?! G: _1 U
  11.     'proof':324984774000,5 h7 ?- |- p+ h' H4 a, I
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
      ?- a' Y4 _1 ]5 `; l& ]
  13.     }
复制代码
7 E) M& v; d! d: F
    -链上一个区块的例子-3 t* Y2 d: t3 f5 L& F7 Q
    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。' c% ]/ i4 @. K  ~: D
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
! R, G. j& O$ C( h! ~    加入交易+ A8 M9 \2 M0 Y# O, h
    接下来我们需要添加一个交易,来完善下new_transaction()方法。
. u0 K1 P6 d8 ?) ?6 \  
  1. classBlockchain(object):  ]# T% l. K. R$ {. @" K
  2.     ...
    , N: `6 Y) w  J& f. Q+ i" ^: U
  3.     defnew_transaction(self,sender,recipient,amount):
    7 Q( k/ S8 z( U2 h  w
  4.     """1 x0 M8 y2 M7 y6 ?$ C: c( ?4 [
  5.     生成新交易信息,信息将加入到下一个待挖的区块中2 H6 A' e0 `9 V' e" v
  6.     :paramsender:AddressoftheSender
    % e5 {0 @+ t3 Q4 a% l4 a3 r; B
  7.     :paramrecipient:AddressoftheRecipient
    % h. }! T% m# t2 V
  8.     :paramamount:Amount0 K4 d# V3 Q& N7 f( N6 W
  9.     :return:TheindexoftheBlockthatwillholdthistransaction; A: d, K" G' I7 b! X4 P0 ~
  10.     """
    4 o5 W0 J) ]4 f+ T* R  }' d" D
  11.     self.current_transactions.append({' e. a% ?/ U" v" t0 w& ^$ A
  12.     'sender':sender,
    ; T$ w8 a  E7 Q3 r/ x+ z
  13.     'recipient':recipient,
    2 {7 X  Z4 u8 c% R& |" I8 ?' F
  14.     'amount':amount,
    9 H& j% Y/ o/ n6 B) V$ `
  15.     })3 X. y0 }4 S) C7 w
  16.     returnself.last_block['index']+1
复制代码

; S4 _. O0 x: x1 t    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
( v% F+ f+ m" D) e( }- x    创建区块$ Z! j% H; k2 ^  T
    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。- G$ i& a) P, K$ k' e7 B. C4 z' C
    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
, j! Q) G8 X# U# ~    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:9 `( w. ~# n( n0 x# |
  
  1. importhashlib
    ' N5 I" A/ {4 f3 A
  2.     importjson0 `5 z  o4 X5 l8 f9 M
  3.     fromtimeimporttime) P7 k4 V+ K# [
  4.     classBlockchain(object):1 w6 X4 H/ @* U) f
  5.     def__init__(self):
    4 |8 L" u. c5 T" Z3 @, ~' M
  6.     self.current_transactions=[]
    3 ~; N: ~+ u" \! v
  7.     self.chain=[]
    0 g% O1 v! W- G$ a
  8.     #Createthegenesisblock
    : T4 }1 v4 ?  n1 H
  9.     self.new_block(previous_hash=1,proof=100)
      ]+ f$ b% u( h+ _
  10.     defnew_block(self,proof,previous_hash=None):. A/ ]/ V# l6 S. m) f" T
  11.     """( [5 A" t% ?$ p' h; b
  12.     生成新块8 K' j" n: K3 z/ k4 z5 Y$ m7 h
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm
    - s$ y$ z" j& W& r: K1 [! R
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    ( a  d. l4 r" ?5 a. Y! \! V/ P6 v
  15.     :return:NewBlock4 [. L/ u& ~) ]" S
  16.     """/ b" B1 J  M, t& i/ N
  17.     block={
    ; n1 U/ m* B2 M) d/ F
  18.     'index':len(self.chain)+1,+ @6 Y8 \. N+ L% A  S8 n: ^
  19.     'timestamp':time(),
    , T/ f* N2 q: m* ~: _
  20.     'transactions':self.current_transactions,; u/ A$ o8 z) A
  21.     'proof':proof,
    : f5 Q. P7 p/ L! Y
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),; T/ `6 S( a5 P
  23.     }/ I1 B- H! d2 o
  24.     #Resetthecurrentlistoftransactions
    4 F5 l) Q0 k7 U- j
  25.     self.current_transactions=[]
    ' F5 w% V# ]4 Z4 D) B
  26.     self.chain.append(block)
    + p5 ^" R: R' q% `
  27.     returnblock
    : T9 l  P, m/ G. M% w$ a) g
  28.     defnew_transaction(self,sender,recipient,amount):
    + J4 N+ L: A3 @/ A
  29.     """
    " N" r- N: X! l7 A2 m7 Y) c$ O
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中6 X& [) u- S7 N0 x) c# H6 y
  31.     :paramsender:AddressoftheSender2 V9 p6 q5 t$ o$ {% P
  32.     :paramrecipient:AddressoftheRecipient, M9 @% h. Z; S5 u4 \
  33.     :paramamount:Amount
    . u! q8 c. P5 w5 d5 H' N
  34.     :return:TheindexoftheBlockthatwillholdthistransaction- [4 D; ^! r  p0 Z1 }
  35.     """+ G' J2 J5 N0 G/ K3 G# W( n
  36.     self.current_transactions.append({$ T9 V, |3 h! H% I) j6 L( l5 E
  37.     'sender':sender,+ R/ P9 \9 ], ^  h/ h
  38.     'recipient':recipient,- e1 w- G  [& q" t1 n
  39.     'amount':amount,
      y" z  P3 v' m& ?6 C
  40.     })
    - W3 ?& ~: V6 i+ b, ?
  41.     returnself.last_block['index']+1
    8 u) ]0 \% t) k- `# Z* R
  42.     @property; F6 `  K$ [4 v4 I( u# v, B% ^0 T, \
  43.     deflast_block(self):; Q& [" R. |0 v* n% S7 I  `- Y: N0 D
  44.     returnself.chain[-1]
    ) S1 Q9 k, X6 f+ d, P
  45.     @staticmethod
    4 c1 z' V* F' J) ]6 }5 q( b+ I
  46.     defhash(block):8 H7 A; Q/ v' F: v5 F7 }9 a4 n
  47.     """
    ; k+ h, R  L' H, p, f
  48.     生成块的SHA-256hash值$ N1 ~7 B* \! |0 W" y" ]
  49.     :paramblock:Block
    & d1 L. L2 H8 u9 u
  50.     :return:
    ; }7 w/ a0 D7 P2 `
  51.     """
复制代码

6 j% l/ ^  k8 x  n0 V    #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()
* b$ w  F6 V% _& Y% p* x1 I6 K    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
5 @5 i" Q( O( I8 ?* L- l% B    理解工作量证明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实现如下:
  D5 a  i! E; g2 @- ~/ [- F- C
  1.   fromhashlibimportsha256
    * d1 U/ Y% I. \1 G1 Y6 y2 ~
  2.     x=53 q/ a* J0 O% J) D6 j
  3.     y=0#y未知' F8 b8 H: r! ], }% I- C3 U6 `
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
    ( G- S4 q& b$ j3 A6 l
  5.     y+=1
    / y, ?- f5 J  s
  6.     print(f'Thesolutionisy={y}')
复制代码

4 D7 }7 A" m0 `* V! G- i    结果y=21,因为:
$ f( s+ c- w3 L3 }0 y    hash(5*21)=1253e9373e...5e3600155e860
2 X' ^" K; f$ y5 b* G7 u    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
+ P% b( b  y* O8 C7 ~    当然,在网络上非常容易验证这个结果。
4 i' x6 K& z9 V, x$ _( ?2 u$ Q    实现工作量证明5 u& l) n  A1 l4 n$ ]" N  s3 p
    让我们来实现一个相似PoW算法,规则是:$ g6 G" n1 o: v- o- f
    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
9 }! b0 k/ }' y  
  1. importhashlib5 n0 n% I6 G0 Y7 p5 k. b
  2.     importjson
    . p! E* O  u) d  g2 c
  3.     fromtimeimporttime
    8 Q) i8 _7 N" f& W
  4.     fromuuidimportuuid4
    5 D2 `5 j8 {3 O5 L- G: ^
  5.     classBlockchain(object):/ o& [! u* v  T
  6.     ...0 i5 n1 z* y  e$ Y) z' c* ~
  7.     defproof_of_work(self,last_proof):: z* i, x8 _9 q" f
  8.     """
    , x# F5 }: x* Y/ a! @# c
  9.     简单的工作量证明:2 e( E% A) P" H4 L8 G( K
  10.     -查找一个p'使得hash(pp')以4个0开头
    3 m% }: v& _, d4 B) P6 z
  11.     -p是上一个块的证明,p'是当前的证明
    3 _+ u0 z& e/ j! |; p( h, C
  12.     :paramlast_proof:
    , D8 W% p" Y, ~& K3 c2 ?* S3 X9 Q
  13.     :return:
    ! x0 `9 b. B$ i' p1 T) b! b
  14.     """
    0 z1 f9 K- B4 ?0 Q; i
  15.     proof=0
    + @( J; g( ?8 k: K/ S) Y; i
  16.     whileself.valid_proof(last_proof,proof)isFalse:
    1 f* [( Q& b  [, W4 r5 a+ E+ H- _
  17.     proof+=1
    ) l: n5 l0 ^: X: v, Q& I
  18.     returnproof9 G5 g4 @. I. @
  19.     @staticmethod
    9 m4 m$ w3 i- B- b8 _& \% F4 }  q
  20.     defvalid_proof(last_proof,proof):. x, y) J  D( {7 H( @2 r
  21.     """& F5 V3 h$ p; ~6 E9 t- \9 ^
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?
    & f+ K3 b; g9 E0 {
  23.     :paramlast_proof:PreviousProof
    ) Q7 @% U& A- n9 p
  24.     :paramproof:CurrentProof' D6 |+ X, X' a( t. @! R/ x. m
  25.     :return:Trueifcorrect,Falseifnot.! x. Q+ }5 F3 B- F
  26.     """
    # ~' `/ [: F/ C7 I* _
  27.     guess=f'{last_proof}{proof}'.encode()6 C) E3 p( i8 j& U: t  j
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    5 I4 c: f7 z1 I+ A2 k% f7 Z( {6 }
  29.     returnguess_hash[:4]=="0000"2 r! S% c) p$ Q3 l8 {
  30.     ```
复制代码
  b& |* }! \5 d; K
    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。$ {$ a" d8 Z5 i0 X3 ^
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。6 K) ~( Y' D( W$ j" |  q( u
    ##二、BlockChain作为API接口
2 H/ y6 }% E* E& t: t    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。6 \, Z* z. }' c' K; W' e
    我们将创建三个接口:
) T. Q  J: n' l% M4 m! G4 D: {   
  1. ```/transactions/new```创建一个交易并添加到区块3 P' W9 N% Q  w# R9 b
  2.     ```/mine```告诉服务器去挖掘新的区块
    & x2 B4 P4 p) L% x
  3.     ```/chain```返回整个区块链
    ; \; h2 y6 K# m
  4.     ###创建节点  c: s3 t& ~: p% ~
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:: e; ^& O, ^" g/ I: d
  6.     importhashlib* i9 F( A" @1 Q$ V
  7.     importjson; h  u8 Y. d3 g/ R4 _
  8.     fromtextwrapimportdedent5 J* o) }6 {' g6 |6 o
  9.     fromtimeimporttime
    ) {* I7 t  L8 y" g4 _
  10.     fromuuidimportuuid4
    * N5 `- X! c3 Y" ~2 R/ F
  11.     fromflaskimportFlask( P" q  K+ \4 J* R2 v. I* m3 ^
  12.     classBlockchain(object):" d% k! o! V2 |+ q! L' D
  13.     …3 h* i  A4 {7 G) y3 G
  14.     InstantiateourNode
    5 r* w" b4 h+ w; K$ o$ C* v2 S8 F
  15.     app=Flask(name)
    . T: X$ [) `, K. `% \9 ^% u
  16.     Generateagloballyuniqueaddressforthisnode" N" s1 Z: B4 H4 n' r
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)
    # @) }. X$ y( Z+ m# q' ~
  18.     InstantiatetheBlockchain
    8 T3 h! _  M: E1 W0 f8 o* L" z
  19.     blockchain=Blockchain(). \' l; D; _7 o# O+ u
  20.     @app.route(’/mine’,methods=[‘GET’])+ ], ~  m4 t/ x5 x4 T0 }4 n
  21.     defmine():
    3 r( {+ N5 Y5 y  [
  22.     return“We’llmineanewBlock”
    ' Z8 r1 x  {3 K+ ~6 s$ ?
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    6 a- o% X6 b8 u; ~6 W- x5 e
  24.     defnew_transaction():
    1 Z& F# u  P* F3 ?
  25.     return“We’lladdanewtransaction”
    ) G0 V3 T4 U" F. B7 W' S9 d2 D
  26.     @app.route(’/chain’,methods=[‘GET’])
    ) N+ X! _: g3 |% D
  27.     deffull_chain():
    * ]7 c6 u0 i/ v8 X6 T0 B* y% c
  28.     response={
    $ Y& K& R7 s- ?2 V8 ~
  29.     ‘chain’:blockchain.chain,$ L. |5 D1 c9 T
  30.     ‘length’:len(blockchain.chain),  ?. Y5 f4 }! P; r  d8 e8 x& `
  31.     }
    2 O& z2 \& P; a5 V( X" O. K
  32.     returnjsonify(response),2008 U9 ?! ^/ D' s" W6 d  b
  33.     ifname==‘main’:$ P/ N: k- ]! O: P2 |3 f' ]
  34.     app.run(host=‘0.0.0.0’,port=5000)
    / x1 {8 p1 H7 O- n& {2 f- g
  35.     ```  X3 ^, y& w8 G: Y8 @( Y" B  Q; v+ f5 ~$ E
  36.     简单的说明一下以上代码:
    $ L& N$ A% |+ U- H/ ]' a
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。
    " K0 I" g1 R- C6 x
  38.     第18行:为节点创建一个随机的名字。
    9 S3 w- e  I4 j" k3 ^1 D# g0 ~
  39.     第21行:实例Blockchain类。* s8 |' T( I; A5 Q% y, r( s+ j" ~
  40.     第24–26行:创建/mineGET接口。
    0 P0 u+ \2 h1 C' V: a, E/ L) D
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。3 U/ j" c1 ~4 N1 Y$ x# J3 `
  42.     第32–38行:创建/chain接口,返回整个区块链。0 ~# x1 f  d/ K, q; V# L# k
  43.     第40–41行:服务运行在端口5000上。! l  w5 J3 j2 i' L4 P
  44.     发送交易. U+ X) @# U& Z, |
  45.     发送到节点的交易数据结构如下:/ G* M" b: y& |: \5 L
  46.     {. l( O" ]- D; Y* ?/ I- I
  47.     "sender":"myaddress",
    - A7 l- |7 W1 e$ J2 X9 ^' s8 _
  48.     "recipient":"someoneelse'saddress",
    5 Y3 e! a% [6 O0 p1 i8 Y6 o: A
  49.     "amount":5' d6 S9 b: \9 _( q& K0 p+ P
  50.     }2 I* E6 s0 G- i1 Z/ m9 j1 p
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:
    " |: j+ i9 w- {) y' G& t
  52.     importhashlib! ]; p7 q: e' n1 w% ~
  53.     importjson, |6 J2 A9 U8 p4 t
  54.     fromtextwrapimportdedent
    # ^7 j- M& V7 _# A9 D, B9 l2 c
  55.     fromtimeimporttime
    5 r; R: n# a! h* ?; T, x
  56.     fromuuidimportuuid4
    ' `& ?8 L+ S7 b# Y
  57.     fromflaskimportFlask,jsonify,request& f* ~4 `$ ~. ]' ^  a
  58.     ...
    - G8 U# ^/ `/ D  \
  59.     @app.route('/transactions/new',methods=['POST'])
    ) I  D! O  [. v3 r) b
  60.     defnew_transaction():
      S# K7 O+ N3 U  z) H
  61.     values=request.get_json()
    8 I# X! _7 O- D7 F, w
  62.     #CheckthattherequiredfieldsareinthePOST'eddata
    0 ]; l) e& }( C+ S* E7 A# B3 o
  63.     required=['sender','recipient','amount']" v5 i% ~) r' f& V) P4 U
  64.     ifnotall(kinvaluesforkinrequired):
      Y  W; {9 t+ k8 }
  65.     return'Missingvalues',4000 G% M  R! f8 X/ e3 g+ X' |
  66.     #CreateanewTransaction
    4 w$ }) U9 j& ?7 E" i
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
    2 ]! X; Z3 G$ [+ [: v
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}
    3 E, ]6 Y- ^3 O7 U: A
  69.     returnjsonify(response),201' I% ?* X) V: F) A8 j3 h3 p! \3 Z
  70.     ```
    4 v" }" m- B( H! s7 A
  71.     -创建交易的方法-* W9 }( s6 s) d  |* M
  72.     ###挖矿$ s. X% g$ @( f0 b9 Z! C$ D
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:
    1 r/ G4 P! ^+ `5 h+ w9 X
  74.     计算工作量证明PoW/ ^4 R+ D2 E: M
  75.     通过新增一个交易授予矿工(自己)一个币
    ' p' F. W3 z" f, C7 J: `6 U
  76.     构造新区块并将其添加到链中
    7 Q  e4 i  T; n2 a0 [% x
  77.     importhashlib
    + l: M$ l0 `! o5 ?( K
  78.     importjson) N' y) x2 j+ t8 \; ]
  79.     fromtimeimporttime
    4 H7 I# b& H$ e! @- V
  80.     fromuuidimportuuid4
    & e& c# A) P9 b  g& h+ h. [6 |
  81.     fromflaskimportFlask,jsonify,request
    ( z' s, N1 H; F- B4 W
  82.     …, t2 j; f9 O  a" X9 ^( P3 m
  83.     @app.route(’/mine’,methods=[‘GET’])
    " B  ~# \* ~" w; j% y- |1 _( k
  84.     defmine():
    4 ]  J. m  c- |, S( W$ F. @
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…
    " B' _8 C/ a, g" L" P+ d9 I! ^, q
  86.     last_block=blockchain.last_block+ F9 o7 H3 M: N7 c0 _
  87.     last_proof=last_block[‘proof’]
    - ]" L- V7 C+ _4 m0 [
  88.     proof=blockchain.proof_of_work(last_proof)5 D' m  e7 q' _0 Z
  89.     #给工作量证明的节点提供奖励.6 O7 a. y0 j# j9 b8 H! z/ H
  90.     #发送者为"0"表明是新挖出的币.3 d2 Q! d, j5 n" [
  91.     blockchain.new_transaction(
    % o7 ], \! _; O' W
  92.     sender="0",0 {4 R0 C4 k; G% Z1 l/ Y" ~
  93.     recipient=node_identifier,1 {, B) H# C# e4 ]  s* B
  94.     amount=1,
    ! ^( h$ r+ _: n
  95.     ). |1 H2 w0 [4 \/ c4 s3 i
  96.     #ForgethenewBlockbyaddingittothechain
    ; l& ]0 Z( e) B% h5 H9 U7 u  [+ R  [
  97.     previous_hash=blockchain.hash(last_block)( ^2 Q3 N5 t7 k7 `# S
  98.     block=blockchain.new_block(proof,previous_hash)
    ! r# f5 Q3 T- L5 l2 X' S
  99.     response={. V" O- Z# [$ F* O
  100.     'message':"NewBlockForged",
    # k6 B+ o# h1 V8 P
  101.     'index':block['index'],6 r, z2 G8 Q& y5 C# |7 l
  102.     'transactions':block['transactions'],
    ; z2 z  r5 z* u6 ~! q$ C- Z
  103.     'proof':block['proof'],
    1 I( [) A- @; P0 j" F: N# j
  104.     'previous_hash':block['previous_hash'],
    ( E! p: \% L# a# U: I+ y6 M! R
  105.     }) j; d0 ~; {9 M2 j* {
  106.     returnjsonify(response),200: A0 q: U) K: e, a2 J4 I
  107.     ```
复制代码
+ B! {" @+ J$ g; i/ c( S7 _0 |
    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕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:
, `( ^4 S: u) F% Y1 }. u& `    $pythonblockchain.py
, ~( N. p/ I6 x    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
% l/ ^! h8 _* O, N+ f% b, p- o    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
: I  E+ x  b) a2 R. |) }1 T    -使用Postman以创建一个GET请求-
0 W' r" }9 y/ ?* y8 N3 o    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
' a) t1 ^8 w. x" h) h8 Y  |, S1 w    -使用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",
* B9 A' _4 t; t% v0 N% u# y1 T8 T+ [    "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
   
  1. {
    8 ~. P4 k& |) n1 H+ N
  2.     "chain":[
    - k6 d) E; ~( X& m5 {
  3.     {' y0 X7 ~+ j- c% H
  4.     "index":1,7 s3 P! U! i, ^( C9 L- n& j
  5.     "previous_hash":1,
    : m. f$ R7 A7 ~. f( i- W
  6.     "proof":100,( T, }6 I, V7 ~- g6 g0 F/ T
  7.     "timestamp":1506280650.770839,# A5 W- s: i$ A* X
  8.     "transactions":[]
    & a. K* n! P9 I% @) S; Y
  9.     },
    6 f* Y. p% }5 B
  10.     {
    6 v9 l& ~* [; s6 @- i+ e$ o, q) l
  11.     "index":2,3 V8 e5 P; u2 `
  12.     "previous_hash":"c099bc...bfb7",
    $ `- y8 D9 ^# k
  13.     "proof":35293,
    6 x) }5 M" e, z6 G
  14.     "timestamp":1506280664.717925,+ O- _; z  b# r2 J6 R) S7 x4 v
  15.     "transactions":[
    ) K7 g& B/ U: n0 _( }
  16.     {
    0 M; r0 o! w8 [( U
  17.     "amount":1,6 m6 f5 H9 O3 d* y
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",) P2 f% [1 P+ ?
  19.     "sender":"0"
    ! Z! f* V! O0 z0 F
  20.     }
    0 E; D) M6 d: ~" {. F
  21.     ]
      P- Y0 Y$ Q. i" ~/ [8 u; X
  22.     },
    $ j* x. A' ~+ {, N7 W7 p
  23.     {
    - c1 G. [% v3 w% n* A# A7 }5 \
  24.     "index":3,
    % [, r+ k/ X% u7 z" ~) |/ ^9 y% x( h
  25.     "previous_hash":"eff91a...10f2",
    % Q. Q3 }& U# I( b: j1 C
  26.     "proof":35089,$ B, Y" X; q+ A5 W" w: M
  27.     "timestamp":1506280666.1086972,8 P0 c" ^/ A5 a& i/ i2 L$ p
  28.     "transactions":[
    3 g" N( G& j+ m+ s
  29.     {+ w, Z! z6 {2 r" }. T) d7 f
  30.     "amount":1,
    + G- r8 K! f* T9 V" s* R& s
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    + Y# r4 l3 h* V. ~9 w1 L* t6 B
  32.     "sender":"0"
    1 `0 E. |; H! f8 y9 j! Z
  33.     }$ U4 i6 K1 K3 \% @( e  N" H
  34.     ]& I7 }' I( S) i* j3 H& [
  35.     }/ W% L; |4 M' U* Q: g
  36.     ],
    % \6 w5 z9 d9 r  T
  37.     "length":3
    + }. ?# ~. E* Z$ M  J. G7 A4 l2 W
  38.     }
复制代码

% Q1 ~8 k" `" N4 T* w4 z    四、一致性(共识)
% A  O  C* K1 I: n* g    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。% U* ~; n3 M# g, W# b" S
    注册节点  c+ T7 O8 e; P
    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
; T4 |# P) z9 c- R& I( E7 F    /nodes/register接收URL形式的新节点列表
# x0 u8 a9 {$ W  X1 l5 W( J$ E    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
) h8 O  ?1 L. a* s0 P* r+ d    我们修改下Blockchain的init函数并提供一个注册节点方法:
& x* K9 `% i' b( @% B4 G  
  1. ...2 O5 A- m* a# M
  2.     fromurllib.parseimporturlparse
      F, }& _; g+ A
  3.     ...
    0 L1 F5 e: E# J' |7 o
  4.     classBlockchain(object):
    ' L% q$ F3 K" j% s6 H4 u+ G
  5.     def__init__(self):
    7 F+ \! X. k! h* t, n' Z1 C; m. J
  6.     ...
    / {5 ^( F) {' t# Y- K4 H5 l- g
  7.     self.nodes=set()
    $ v- O8 D) h, G* R: s1 W. B. P5 G( v
  8.     ...
      ~2 F' c. O# ]* m8 @3 ~' t7 p, C
  9.     defregister_node(self,address):0 l1 [& M1 q% y' p
  10.     """' m$ X  @  M+ p5 U, t
  11.     Addanewnodetothelistofnodes
    + B# O* ^# w" j+ O0 y
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
    3 q, ?, s& C. [+ p
  13.     :return:None$ H& u( `' t6 Z8 V1 k( Z0 R
  14.     """
    . c- f. R3 j, F* T+ X' c7 j9 O
  15.     parsed_url=urlparse(address)
    # k4 L( o4 b$ k2 i
  16.     self.nodes.add(parsed_url.netloc): a6 w. e5 T. _! T3 k; P
  17.     ```2 B0 {  O/ t" b% {# s! D
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
    8 I; r: n  y& n' M; V  V7 t$ s
  19.     ###实现共识算法, v" x# s2 H3 U8 }; x6 k- c
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
    ) G1 ~; b* W/ I
  21.     …
    6 _6 r0 {& |  f: F
  22.     importrequests- O3 Z7 F5 H- i" P3 Y4 \4 M
  23.     classBlockchain(object)
    5 F- Q3 V) u" r' P
  24.     …6 u5 z1 u9 v) |
  25.     defvalid_chain(self,chain):# D  V% J$ O5 q2 K' z3 S0 J( _
  26.     """
    7 `; H; i. L( ]6 B
  27.     Determineifagivenblockchainisvalid( K% J: f4 J( @% R3 E
  28.     :paramchain:Ablockchain
    " A" m5 z! q' e  r
  29.     :return:Trueifvalid,Falseifnot, f* X4 R8 @1 V6 F1 _7 k* q' R1 C+ ~
  30.     """
    ; f: i2 F% ?0 ~& c& m0 ]$ z. p! ^
  31.     last_block=chain[0]9 N) h5 s5 K8 s) I$ b
  32.     current_index=10 C* A% d( g2 N) ?/ o
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):
    " I4 `* G9 M# D! m9 L! z: x
  34.     max_length=length: K2 v! N0 R1 T2 x3 x, W# H' V
  35.     new_chain=chain1 H3 l/ \! D) t7 t6 h3 [
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours2 E5 v8 \  n" a  a7 v8 {/ ^
  37.     ifnew_chain:& J$ F" ^- K* e5 R. m3 H
  38.     self.chain=new_chain7 {8 }* K3 n5 M. X% m1 j* m
  39.     returnTrue
      a) U+ e% D1 [- p. A* c# r9 s, _& x
  40.     returnFalse
    : J3 C* c7 F/ ~/ B" [2 p9 l: v
  41.     ```
复制代码
* W+ i$ V2 n0 P
    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。# |  T1 C# ^/ F
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
5 i) f" u" H  i& N5 d6 g! F; R6 s    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。" [0 ]$ ^# T* i7 I2 l. @
   
  1. @app.route('/nodes/register',methods=['POST']). Q9 G8 s  u- W. w% P1 j: g
  2.     defregister_nodes():
    " V. Y( P* _. x2 W: p9 ]
  3.     values=request.get_json()
    6 {9 P9 t" L  p7 g& g
  4.     nodes=values.get('nodes')3 C3 Z$ k  ^* e: q, N
  5.     ifnodesisNone:) L0 s. J2 w9 J$ t! w$ j/ n% _0 X
  6.     return"Error:Pleasesupplyavalidlistofnodes",400
    # i  L; W  ^6 `5 I
  7.     fornodeinnodes:
    ( A' w, d& R& n! a
  8.     blockchain.register_node(node)
    ' m4 o; P2 f7 ~2 _  L/ B/ v( ]- b
  9.     response={; A7 P' r( v8 q! }" w
  10.     'message':'Newnodeshavebeenadded'," w+ t# k+ d- [% j) T, T$ j
  11.     'total_nodes':list(blockchain.nodes),
    ( C$ W+ [& a; w2 O$ q+ i! D+ G7 ~
  12.     }
    2 Z' S" c( d% z5 m2 X
  13.     returnjsonify(response),201
    6 h4 K9 b" j6 D' c1 T
  14.     @app.route('/nodes/resolve',methods=['GET']). }1 H& A& s. t
  15.     defconsensus():) F- h7 q9 L' M$ d( g
  16.     replaced=blockchain.resolve_conflicts()5 L: _( B' D3 C5 I- k( l" z
  17.     ifreplaced:6 I* [/ a7 K9 ~" c' U! q
  18.     response={
    8 L; B) F5 A7 `" H  p* ^: }) V
  19.     'message':'Ourchainwasreplaced',
    6 [7 J) Q; K+ i$ ]
  20.     'new_chain':blockchain.chain/ Q, K; `) h9 C. `3 m, G
  21.     }) u3 J  |8 o" v$ [. @7 a
  22.     else:/ U( A+ d) m6 Q6 s' O% \
  23.     response={2 `, c5 ?8 O# x3 f2 c
  24.     'message':'Ourchainisauthoritative',: L+ y9 |+ {: ^0 E, T
  25.     'chain':blockchain.chain
    & j7 @8 m8 \+ h  [! }2 I
  26.     }$ ]1 H1 ~) f, U" e% ]& Z4 N8 h/ g
  27.     returnjsonify(response),200
    - s1 i% _" X' O, }" q. Z
  28.     ```
复制代码
( z) [1 g. I* Q% J" {1 Q
    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。; U# d, {0 a; M+ |4 t

3 g' X# K5 D$ f( z, c- z    -注册一个新的节点-/ A" |' w, V* j2 E- ?
    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7