Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
453 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。: Y, L7 b! W1 z2 a, z
    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
' \2 Z; Y/ i! y9 ^- r: {    准备工作
5 F* e. E4 l+ |( |    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。  M9 x3 A* {$ ~) w" ^8 n: c
    如果你不知道哈希值是什么,这里有一个解释。
& K9 e6 x4 }3 a4 c    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
/ ]  c/ i/ g9 ]( H$ O    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:; [! O3 ]; T% r' h8 x* R! G; W
    pipinstallFlask==0.12.2requests==2.18.4
6 ^- n7 L, y# l$ e& u, n0 ]. r9 `    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。# w$ o' [- e' L1 A- ]# {
    一、开始创建BlockChain% c4 d  m/ H. A' l" F1 h
    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。+ F# t: L4 e9 b, t+ S8 ?
    BlockChain类
: }, x, a# X8 u& b9 x    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。& {8 |; ~5 M/ T& t- B" v; f
    以下是BlockChain类的框架:
7 ]+ R7 a* F& }: k' L1 E& P  
  1. classBlockchain(object):8 U  a. R" V/ S' o- S4 F
  2.     def__init__(self):
    0 x, W8 g) x/ A9 ~
  3.     self.chain=[]9 S, \8 c' i' D; X5 |7 o% J1 `# F
  4.     self.current_transactions=[]
    , p1 K4 P0 S0 V/ M6 K
  5.     defnew_block(self):
    & U: o' F. b8 s% z& E) s0 {( O  i
  6.     #CreatesanewBlockandaddsittothechain
    # J7 j% ~0 Q2 R& x2 e5 o
  7.     pass
    $ S6 j: _' @) S  r, N. `! h
  8.     defnew_transaction(self):9 C( H$ g/ u6 I. T
  9.     #Addsanewtransactiontothelistoftransactions
    " W. u- i6 }& r# o1 S2 Q4 }7 ~
  10.     pass
    5 |# p3 ]6 ?5 ]/ k4 g, p' B
  11.     @staticmethod
    , m: k1 @7 ~5 b! ?! P; ^
  12.     defhash(block):" D  P  F+ d. }! X$ D
  13.     #HashesaBlock
    0 r) [7 N% |- |2 O1 R
  14.     pass6 M; @3 Y1 @. C8 ^6 H. Z
  15.     @property! \" N) u3 m( Y
  16.     deflast_block(self):
    & N7 k5 e" K; m/ ?. ^4 Q- H
  17.     #ReturnsthelastBlockinthechain' T% W6 B- r$ A) C  `0 }
  18.     pass
复制代码
' n' q9 i) C( q0 C' I
    -我们的区块链类的蓝图-
7 B7 P" [. G1 @! H. p' W    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。, L- f) N. w4 ?3 O) `
    块结构
4 l  w- y! l) `2 X3 L    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。/ J5 f, C6 i: d% F2 p9 Q
    以下是一个区块结构:$ ?: y: s9 l7 {7 D" a
   
  1. block={9 y& K+ Q9 \1 g* ^  u' \; I. q
  2.     'index':1,! v, K3 u6 a; u2 j1 A
  3.     'timestamp':1506057125.900785,) w2 H) ]0 o$ i
  4.     'transactions':[
    2 z# I9 H. \2 X7 q2 V
  5.     {
    * v% |. U0 e" l6 W- K5 u1 K
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    0 P; Y' Z) `; k
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",9 S( f" a/ `2 F3 c
  8.     'amount':5,
      h9 P4 @1 w, f
  9.     }
    : H) g5 X) Z% j$ Z* `! Y! P, Y
  10.     ],$ F* e4 c. T+ X! Z: D& i
  11.     'proof':324984774000,2 v4 q* |- _- D8 [
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    / F* P' s2 X. E% m) n
  13.     }
复制代码

, j7 A+ ?* T0 s' s/ W    -链上一个区块的例子-
% {6 `# K8 g  a, v/ J    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。+ C; A8 `% P# @+ d$ a9 t; [: ?0 I5 @' k
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。' i0 r8 [: m& w4 Y, k
    加入交易: f9 J3 L. |( f1 s1 S; T
    接下来我们需要添加一个交易,来完善下new_transaction()方法。
, D7 |3 h+ Q9 E! I" L7 I; c0 |  
  1. classBlockchain(object):
    ( p, s7 ~5 a4 p  H# P( w: l
  2.     ...
    1 n/ O: ?+ o+ ?1 x1 p
  3.     defnew_transaction(self,sender,recipient,amount):0 ]. u# o% e1 ?1 D. ^- m! p( j
  4.     """3 k3 T% y+ q  i- F
  5.     生成新交易信息,信息将加入到下一个待挖的区块中; S& g3 F9 k. O- M+ p3 e
  6.     :paramsender:AddressoftheSender
    6 ^# e( v: x' c+ A
  7.     :paramrecipient:AddressoftheRecipient1 J& a7 O) i: S& }- b
  8.     :paramamount:Amount) H2 A5 d( t7 |2 H
  9.     :return:TheindexoftheBlockthatwillholdthistransaction3 b! j9 W: l9 _
  10.     """4 g/ a2 w# m- T7 G& O. I
  11.     self.current_transactions.append({
    - S0 j( K. h. ?: v8 }; P3 l* F
  12.     'sender':sender,
    ' c$ f; F2 g9 H8 ?3 m6 f( [
  13.     'recipient':recipient,
    % C' }, ]$ |% q  u9 u4 O3 W
  14.     'amount':amount,5 \* g' d9 B( F
  15.     })7 c* _! T8 `. w$ m& ^7 y0 l. A+ i
  16.     returnself.last_block['index']+1
复制代码

2 M5 {0 B" j$ m* f9 X    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
& f5 l5 s; `: A' H- P$ i    创建区块
# K& r9 N, m4 M- ^5 X    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
4 P" |! Z& T( r1 D2 p& |. e    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
7 M2 u: {' C% R" B5 o" g3 D    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:3 o! h, L) B# |7 K- Z
  
  1. importhashlib8 v) A% C% d3 f7 @
  2.     importjson* }: F" Z+ ]& J, _' Q1 |( i
  3.     fromtimeimporttime1 h( c$ Q8 G7 e  v( V
  4.     classBlockchain(object):- a2 ~7 {; T0 L5 D% _
  5.     def__init__(self):
    , M. u5 U, A: k; A1 x
  6.     self.current_transactions=[]7 J8 \5 [# X! |
  7.     self.chain=[]
    6 k/ |' i6 C' n3 a( n3 C
  8.     #Createthegenesisblock
    + [6 S' Q, B1 t& I9 Q
  9.     self.new_block(previous_hash=1,proof=100)
    : p  [  }# C- j4 g# V
  10.     defnew_block(self,proof,previous_hash=None):% {4 M, @! j7 K4 [5 N. K
  11.     """1 H1 N* V$ o+ z7 g$ D; r
  12.     生成新块- G5 v, e! i- J2 T
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm
    & V6 V8 G3 ?. _0 f1 n0 ~
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    & @6 O$ n) B% t$ C* t7 Z) m
  15.     :return:NewBlock/ x% E, I- l( k2 _+ r* P
  16.     """
    ' h* Y, A5 @) C" {+ ~# o6 [
  17.     block={
      F9 Q5 m, w1 A( @: O7 I# x
  18.     'index':len(self.chain)+1,4 e" Q4 |1 ]3 _: s3 P& J+ W
  19.     'timestamp':time(),: [( F5 B# z1 A- {" J
  20.     'transactions':self.current_transactions,- }3 e8 a" d% T4 K
  21.     'proof':proof,6 _! u0 M! f; B! j7 e3 `* I
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),
    + }  q/ T9 t: i1 I
  23.     }
    1 |" O4 W+ \) Q; \& N
  24.     #Resetthecurrentlistoftransactions6 U+ o6 n7 N  y( Y. _
  25.     self.current_transactions=[]$ f9 B0 a: `7 Y, p" V
  26.     self.chain.append(block)
    ' |$ s1 a" O- d3 M5 m/ r- `
  27.     returnblock. m! v# P! L3 ^* d- x
  28.     defnew_transaction(self,sender,recipient,amount):
    2 G, V" }% d% p4 l
  29.     """
    / u0 w# {9 F& O( _1 [/ m
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中4 N. {1 T, F- S6 T+ l- t$ \2 P
  31.     :paramsender:AddressoftheSender7 q; O- u- _- _$ u/ R; X+ P$ [& ]
  32.     :paramrecipient:AddressoftheRecipient7 H7 e, }) [  X
  33.     :paramamount:Amount5 O/ p3 {6 [* x  g
  34.     :return:TheindexoftheBlockthatwillholdthistransaction
    , Y1 A4 F* k9 ^2 J
  35.     """
    * K0 H7 d  h, m5 r5 ?: ?
  36.     self.current_transactions.append({
    ; o, i% g% B# u  q. m) H6 [# r; ]
  37.     'sender':sender,
    % N/ B5 @) O9 l1 j( f
  38.     'recipient':recipient,
    : F% i6 }% g, L' U/ a& Y
  39.     'amount':amount,+ \) m6 J& I/ y! a* g$ R0 f1 v9 b: q
  40.     })
    ' n/ S6 n- ^5 F7 H; K. @
  41.     returnself.last_block['index']+1
    ( y" N) @5 e; I$ J" ]  {% r
  42.     @property0 z! @, s* T4 M$ G1 v# u/ s7 W" _" ?
  43.     deflast_block(self):2 n3 e* ^/ t* l; v
  44.     returnself.chain[-1]
    / h* f3 r% ?7 H$ X
  45.     @staticmethod5 ]% o4 P! Q& u  U/ k* s
  46.     defhash(block):" m; j) S0 _5 D5 @1 @
  47.     """
    3 _( y( d6 Q7 c/ i
  48.     生成块的SHA-256hash值
    3 n  _+ [. {. j1 G  t
  49.     :paramblock:Block. A3 @& V/ s4 L" o6 b4 G  r
  50.     :return:
    # `. f  H: d  f3 B" B8 N9 o
  51.     """
复制代码
4 A$ L8 Z6 n5 h8 f8 ^( ]- ?3 |
    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
; j) ?7 F4 ?; P+ y5 R& t+ v    block_string=json.dumps(block,sort_keys=True).encode()
0 d  h1 t9 @2 b- u) Z    returnhashlib.sha256(block_string).hexdigest()
# j) t' }, G, H' Z; E1 H    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
4 w" ?- P% a7 I) @1 |    理解工作量证明
: b* E# L7 v5 U+ n( @3 ~    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。; P; r$ I# i6 u  y5 c0 [& k. R
    为了方便理解,我们举个例子:  g/ y  d9 ^9 G
    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
) [) e/ Z- U( [
  1.   fromhashlibimportsha256
    7 ~8 \! W$ R# W) s
  2.     x=5! I( K5 d0 U" Q+ k
  3.     y=0#y未知6 o, f3 }& G, C* x8 ]
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":4 `+ w1 r3 c  i% G9 |" n$ N- f
  5.     y+=1
    : T) n, u. U# D
  6.     print(f'Thesolutionisy={y}')
复制代码

- c1 d# R# y! `" r. Q    结果y=21,因为:2 c! G9 \6 A/ b0 x5 Y+ T) g
    hash(5*21)=1253e9373e...5e3600155e8600 H: o  P/ u  y( [+ m. D
    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
! ?# b- w4 s3 y+ R% E    当然,在网络上非常容易验证这个结果。6 @! w6 o7 |+ X5 e$ Q9 m# m
    实现工作量证明
) m. \2 A: ^( d; X    让我们来实现一个相似PoW算法,规则是:
# Y; M- _9 a* U* M$ @2 o    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。0 B4 I. k3 A1 v* U( ^" k0 f+ s' T
  
  1. importhashlib- G9 Q9 q% e+ v+ R, ^
  2.     importjson
    0 `1 ?6 v8 G* S/ ]7 N1 O7 \& r' W
  3.     fromtimeimporttime
    8 }" n( z" f% s) i
  4.     fromuuidimportuuid49 h6 S8 ]8 F6 D: U: r+ A
  5.     classBlockchain(object):
    - [1 l* C- Y! W, l
  6.     ...
    # Q; j% G4 c9 n5 Q1 V! s6 V4 s
  7.     defproof_of_work(self,last_proof):
    ) c# _! l" P& M& F8 B
  8.     """! }0 _# @# q, o7 Q; r, t
  9.     简单的工作量证明:" R  T) M+ R* }$ P
  10.     -查找一个p'使得hash(pp')以4个0开头
    5 e' F7 K- c, x- U! Z2 t
  11.     -p是上一个块的证明,p'是当前的证明
    & h7 ?0 _5 Y; [  i
  12.     :paramlast_proof:
    ! p4 Y* `) k  g" u- N% a5 ^
  13.     :return:. G4 n! P# T: I  D3 a  `
  14.     """# X- p; y. k2 y6 [5 N; h1 T( S4 s
  15.     proof=05 q# U: A5 [7 ^+ x7 _8 i
  16.     whileself.valid_proof(last_proof,proof)isFalse:+ |5 V1 @5 O2 c2 {# O6 `$ V
  17.     proof+=1$ y/ q. b7 }4 C8 m
  18.     returnproof/ L) h6 K0 Q8 z
  19.     @staticmethod
    * v  G! m6 M" [0 Z- b
  20.     defvalid_proof(last_proof,proof):
    ; y+ T+ q! e' |$ H% D" c/ w: A( e- S
  21.     """! Z. G4 J6 X1 W. L
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?( N5 n2 n2 H) w/ n
  23.     :paramlast_proof:PreviousProof! g  }* l3 ], p! N
  24.     :paramproof:CurrentProof4 b1 D$ g- z% G- a6 k* |
  25.     :return:Trueifcorrect,Falseifnot.
    6 ]! O  z2 \! K! T
  26.     """
    : \+ _# _3 D' v* d! h1 W
  27.     guess=f'{last_proof}{proof}'.encode()
    ) \. I; e! V6 [
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    7 F" V, a8 \0 U6 F( s
  29.     returnguess_hash[:4]=="0000"
    5 }0 U# u, S- N1 V
  30.     ```
复制代码
/ N" x8 Q& z2 W* o1 N+ k
    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。9 l# L8 J: Y) h, D. Q
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
6 w8 z3 M: ]  P' ~    ##二、BlockChain作为API接口
! R) ?4 \( O1 x    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
$ l/ N% |8 ~) s# r/ T    我们将创建三个接口:2 n1 C! ?( ~8 X& C0 O
   
  1. ```/transactions/new```创建一个交易并添加到区块: m! H* g5 W& ], y0 {$ o
  2.     ```/mine```告诉服务器去挖掘新的区块
    # ^3 u3 M. t5 ~  v% H8 G: _
  3.     ```/chain```返回整个区块链
    4 o- X0 R/ ~2 r7 g  L5 s! m3 h: R
  4.     ###创建节点
    5 d9 [7 Z$ p3 }, V" q! ~, K
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
    / }9 S$ T( ]3 N
  6.     importhashlib
    3 x5 v6 A% H* k* c: T( B% T
  7.     importjson
    , m) r# Q, a1 A8 P$ o7 n# C
  8.     fromtextwrapimportdedent7 m. {) F, x5 k$ c6 E6 [
  9.     fromtimeimporttime0 o  E; f+ l* Z2 l' ]' H( X
  10.     fromuuidimportuuid4
      X+ Z, y; x5 g1 I
  11.     fromflaskimportFlask
    2 O/ l! l+ l9 v+ Q
  12.     classBlockchain(object):
    9 E: R7 Y- Y" I$ Y
  13.     …8 t% g$ h$ m' Q5 j# f# J$ X9 U7 }
  14.     InstantiateourNode, a% [$ C, o* O, ]0 s
  15.     app=Flask(name)8 X; J" X$ K/ P. @& D4 W
  16.     Generateagloballyuniqueaddressforthisnode
    ' |  d3 i: Z* |. H
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)  d& W, s  O% j" e) q+ M* ~
  18.     InstantiatetheBlockchain8 o) Q& E+ T8 V, M2 ]
  19.     blockchain=Blockchain()
    , u* |( @' L: ]" q
  20.     @app.route(’/mine’,methods=[‘GET’])
    , ?' l5 i6 o6 {& H0 @
  21.     defmine():
    ' E  m! B- q' h$ v9 \" k
  22.     return“We’llmineanewBlock”
    ! x8 D/ S- s2 a6 r: N# T
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    % ^' Z! z$ K# m  U# H& r
  24.     defnew_transaction():
    6 S2 ^* P( B3 n1 `. n& q3 O
  25.     return“We’lladdanewtransaction”7 X5 |# O4 @- n) c9 d) L9 p9 i) @  n
  26.     @app.route(’/chain’,methods=[‘GET’])
    7 D- _5 b; T  @; g
  27.     deffull_chain():. n; {! e; K+ t) A% X2 F+ \
  28.     response={
    # N, p5 B8 P  p" g
  29.     ‘chain’:blockchain.chain,3 L; D8 R$ j/ E. z1 v0 w
  30.     ‘length’:len(blockchain.chain),  W% m8 i3 q( {/ }
  31.     }5 m, K9 F- ~2 g5 g, O' o/ U
  32.     returnjsonify(response),200% D. k* ~, E1 s) ?; T# ^+ x, X: A. t
  33.     ifname==‘main’:
    6 |- x9 R$ e5 l* P6 v% c) r7 E- |
  34.     app.run(host=‘0.0.0.0’,port=5000)
    / D5 x5 M3 n4 ~5 _
  35.     ```) R% N/ ]& [% V+ e/ B/ s
  36.     简单的说明一下以上代码:
    4 a! X* E7 G# Q
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。8 y, B$ u+ v; ~
  38.     第18行:为节点创建一个随机的名字。& G, E9 s: B  }# V, o, j5 {: k3 y
  39.     第21行:实例Blockchain类。
    # i9 q* e$ h) C& u; P
  40.     第24–26行:创建/mineGET接口。
    " h7 u4 o% x1 X# w
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
    # h' y* x& r2 X! I
  42.     第32–38行:创建/chain接口,返回整个区块链。
    $ f+ b8 D9 V; s  O) Z% N
  43.     第40–41行:服务运行在端口5000上。# J) i8 l5 C" \* J! H
  44.     发送交易2 l  Y/ g. E! V5 L4 N
  45.     发送到节点的交易数据结构如下:
    ! |) B% }1 O8 F9 @* C- g' ]$ N
  46.     {4 ]7 R2 g5 e  {. `8 A1 B* H
  47.     "sender":"myaddress",
      S2 o- z. M2 @5 T- ~* h- w: l( m
  48.     "recipient":"someoneelse'saddress",
    & R( `' v! o$ ]- \5 Y: \" n
  49.     "amount":5
    4 r# q+ [' W# A- N. I1 G3 p. h
  50.     }
      E6 ~# k0 O. G# ?, @1 s8 y
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:* J" q( m0 m5 j$ A4 p
  52.     importhashlib
      d% Y" _8 y8 c( b; ^$ l8 C; P. ?, \
  53.     importjson$ t$ a) [( b6 r, E
  54.     fromtextwrapimportdedent7 `$ v+ E) q: H
  55.     fromtimeimporttime" i2 |; z# ]+ D- ]( x
  56.     fromuuidimportuuid43 c1 F1 E0 j% L+ ]8 F8 }, Z4 P5 ]
  57.     fromflaskimportFlask,jsonify,request
    4 e# J( Y- D! b8 B! r) @
  58.     ...3 M7 v# l5 w6 T( m0 x
  59.     @app.route('/transactions/new',methods=['POST'])
    + l/ @& B" Z. l' ~  S
  60.     defnew_transaction():
    , P( X" G8 U8 U) D7 L' X- S+ x/ L
  61.     values=request.get_json()
    $ L& ~/ q% O4 k+ k+ L
  62.     #CheckthattherequiredfieldsareinthePOST'eddata
    5 l' b( N% q3 N4 ^& ~3 P! |" Q
  63.     required=['sender','recipient','amount']0 [0 `3 j4 P3 j6 ]; U* `3 v
  64.     ifnotall(kinvaluesforkinrequired):
    % W" i6 a$ G  ^
  65.     return'Missingvalues',400
    - G6 z3 c) i7 m8 e4 w
  66.     #CreateanewTransaction
    - B2 ^/ ?. a& R1 X- a- K5 g% l
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
    , F$ ]* M# P5 w( E0 W
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}& l3 E' x* o0 O3 V% @; H! V( z: I
  69.     returnjsonify(response),2011 g) V9 `/ t4 @: C/ h) y, u
  70.     ```
    % p# g1 W0 X9 {% y5 ~
  71.     -创建交易的方法-0 C3 q3 o9 c$ U2 ~
  72.     ###挖矿
    / ^/ _- e& ?, M0 d% [( ?
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:
    # }6 W! C, l8 j
  74.     计算工作量证明PoW
      X, c6 K7 a( A" F% }
  75.     通过新增一个交易授予矿工(自己)一个币" g8 A$ a) t. G  [9 w1 t
  76.     构造新区块并将其添加到链中
    6 d+ j( \. `: \% }
  77.     importhashlib  H7 E% o8 I$ j( Q9 h
  78.     importjson
    ) J8 R/ @- F# V6 Q
  79.     fromtimeimporttime# i( |7 S, ^( l& R2 A
  80.     fromuuidimportuuid4* I6 A1 T3 I' G! s/ @# ?0 l/ j
  81.     fromflaskimportFlask,jsonify,request' ]7 g9 H; Q4 N* ]! @/ d( N3 Z
  82.     …1 o; @' ?' m3 c9 A# F5 A$ q( L/ [
  83.     @app.route(’/mine’,methods=[‘GET’])! g- b1 d  X! x& L
  84.     defmine():
    " O& B5 U. R  _# A
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…
    * m3 r4 K2 r: A" R& W
  86.     last_block=blockchain.last_block
    ; A/ q) _# {% q& M' v, D: N/ i  r
  87.     last_proof=last_block[‘proof’]4 n2 |2 R6 I! ^2 ~( |2 t+ n
  88.     proof=blockchain.proof_of_work(last_proof)- @4 l$ M& |% Z1 A
  89.     #给工作量证明的节点提供奖励.1 O; B9 ]# {9 |' D
  90.     #发送者为"0"表明是新挖出的币.
    , A& g1 L2 K2 L: A/ A
  91.     blockchain.new_transaction(7 Z- G0 T1 ~3 f# q! K
  92.     sender="0",$ A! @  U/ z7 ~/ k( F
  93.     recipient=node_identifier,) G' V# y9 r1 X/ S  f
  94.     amount=1,2 z; U. K, w3 v0 ?
  95.     )6 u* H* }$ L' H2 S4 ~
  96.     #ForgethenewBlockbyaddingittothechain
    8 K1 \0 b- h; S+ e8 m
  97.     previous_hash=blockchain.hash(last_block)
    1 A. j8 _1 o- _" s9 c) z# t
  98.     block=blockchain.new_block(proof,previous_hash)
    / }2 J& t6 M% y& B0 e
  99.     response={
    # N% \! M: L5 I: }  Z
  100.     'message':"NewBlockForged",
    5 @2 j9 }1 G9 b, d; U( H
  101.     'index':block['index'],
    4 o& t, _7 h& L1 @- L
  102.     'transactions':block['transactions'],5 {2 o2 G$ @/ P
  103.     'proof':block['proof'],
    3 I1 i1 W' b0 f8 W
  104.     'previous_hash':block['previous_hash']," O8 p! ]5 b7 p5 d% m" c
  105.     }% E: x8 _5 M9 {" P$ R
  106.     returnjsonify(response),200, {3 v9 R' Q' x) H! }
  107.     ```
复制代码

6 S" e. s5 c2 w0 O, x1 a    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
/ W  V) O* u% e# Z$ N; y    三、运行区块链( ~4 v% q7 K/ @2 D8 _$ x
    你可以使用cURL或Postman去和API进行交互
! n" h: ?; `* p+ {, K    启动server:
0 z' i- C7 Z# k4 f* s9 W    $pythonblockchain.py
8 Z* `! e5 J! v    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)7 d% ~3 M3 S2 W( O; B
    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
; i& Y: x5 w+ Z' K" r    -使用Postman以创建一个GET请求-. |8 A) z$ {/ T# O! U# h8 f( B) B
    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:# f- B* L$ [- {  ]- W+ {2 n0 C0 y7 ]8 c" @
    -使用Postman以创建一个POST请求-
: W; o" R& v7 T* }    如果不是使用Postman,则用一下的cURL语句也是一样的:% H. ]( |: K2 F2 K
    $curl-XPOST-H"Content-Type:application/json"-d'{5 U6 A5 ~9 c6 \1 f, S
    "sender":"d4ee26eee15148ee92c6cd394edd974e",3 }$ o2 d9 X  j. ^# t4 h; X
    "recipient":"someone-other-address",' V6 y! p' k* f) r9 `4 @( b
    "amount":5
6 s4 E* Z( G7 E, p' h9 t: ~/ g3 A    }'"http://localhost:5000/transactions/new"
; w8 I3 x( c) V) r( d: b    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:! h, G+ j/ ~- m- N" ]+ j
   
  1. {
    2 x  B5 E7 a  U  {
  2.     "chain":[
    0 `2 l8 K( \2 Z
  3.     {7 M4 e: J2 `2 F: ~- f
  4.     "index":1,
    + P; X% Y0 o3 e: U/ B2 [) W
  5.     "previous_hash":1,
    % F; ?' k2 N9 y
  6.     "proof":100,
    # _5 V; ^1 D8 H$ i
  7.     "timestamp":1506280650.770839,
    ) m! J, ?% H# p: i& J, ?
  8.     "transactions":[]
    ! @: v4 b5 r# Z* o& D
  9.     },4 Y4 g; V. V9 E/ w
  10.     {% n, q- E, O8 `6 U+ U
  11.     "index":2,
    - H' |6 \4 h. T. i' a, c: O
  12.     "previous_hash":"c099bc...bfb7",, }9 s' `4 A, b$ r/ B. L5 B
  13.     "proof":35293,- T9 v" k+ x+ O" j4 S4 c
  14.     "timestamp":1506280664.717925,* p. p8 D. a4 @& h% @  ^
  15.     "transactions":[
    # o3 @; M, ~8 O: F
  16.     {
    6 \4 V3 {7 d- w( L5 M
  17.     "amount":1,
    % W5 t  ^& V: |* s4 i  ]3 i
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    " r- @' z% W. a1 n6 s8 H/ f
  19.     "sender":"0"/ Y/ `2 @- Z( Y# C$ K" D/ n
  20.     }
    ' i3 ^; @' R8 n' c% P
  21.     ]2 s" e3 V8 Y# b6 ?0 }- b
  22.     },& N( b7 N( \- T5 w
  23.     {
    ( _/ A( ?- \4 i2 D
  24.     "index":3,8 a3 W% i7 ~  n  ]/ b4 P& \4 {
  25.     "previous_hash":"eff91a...10f2",
    0 ~. Y8 H# O6 J' U  p/ r
  26.     "proof":35089,
    - I$ O# V; c$ l. q! X0 `
  27.     "timestamp":1506280666.1086972,3 N; C. j7 \& o
  28.     "transactions":[0 k$ Y/ f/ ~" `9 @2 R& x( h  F" R" G
  29.     {
    ( t$ Q* l# k2 k6 p3 B
  30.     "amount":1,
    ! C; H3 T8 ?. M' X
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    , P" l3 @3 J; V$ l( i% r% c( E
  32.     "sender":"0"
    & v- W3 B6 ^7 ]- K$ n
  33.     }
    ! J% a+ N2 [( N8 c4 d* U' U
  34.     ]
    : e! ^7 W; ?) U$ l: B" y2 g
  35.     }
    # A/ J! z9 f! O! B0 \5 m
  36.     ],
    0 R6 ^7 M% X# b* Y7 `; q$ \( F
  37.     "length":3% F% u9 {# g* d* \9 r* y
  38.     }
复制代码
4 F' i. x' F3 z5 l/ C+ s4 ]- }
    四、一致性(共识)& Z# W( h3 R# {* s
    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。, W1 @& g6 o! `- V4 M6 f" J
    注册节点& _9 {, P: N! g4 |7 ^4 u
    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
* f- J5 k5 ^; ~; m5 a3 H/ E    /nodes/register接收URL形式的新节点列表
4 B/ {' y; }& r2 `" Y" Z* f    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链0 K6 L% A7 U9 ~* e& k
    我们修改下Blockchain的init函数并提供一个注册节点方法:
2 ]  |; G! r) c- _/ Y, ~" O  
  1. ...
    5 p2 q( Q# u& Q1 X/ ]
  2.     fromurllib.parseimporturlparse
    4 S, l" W; G; C
  3.     ...
    ( J7 ~* m1 n  c2 p
  4.     classBlockchain(object):8 h8 X9 j' O8 g* u7 P. E5 w
  5.     def__init__(self):
    - ?% u. ^, D; c, X  d  j( ^
  6.     ...
    + L  N) N0 U3 [7 l
  7.     self.nodes=set()% Q: Q, @  \5 j. e* {( D) M2 N, f
  8.     ...! M$ C: A3 M- j  \4 g' X
  9.     defregister_node(self,address):) y* E* b; f9 X' s1 ^
  10.     """
    , V9 s* x% ^" i; e" N$ r/ V
  11.     Addanewnodetothelistofnodes
      o; }$ L1 p" x; ~/ ?* b$ }% ?# h
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
    7 b+ ?' \3 \  J9 |' W8 o! P1 b
  13.     :return:None
    5 Q! J7 }, C3 K; _2 M* F
  14.     """0 L. @0 U2 ^  @& F3 |
  15.     parsed_url=urlparse(address). O( ?1 D) i0 V9 Q* O) ^
  16.     self.nodes.add(parsed_url.netloc): t& h) ~# M4 L' e% c
  17.     ```
    4 b! Z# L, w+ w: [2 e! B
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
    . R% l" O6 l. ~+ Q3 j( b
  19.     ###实现共识算法
    9 h. c' F! p6 `7 O
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。, m9 e, C& H! A( P3 Q$ S
  21.     …0 ]- i6 g& ]4 M2 l! \3 k
  22.     importrequests
    8 A$ j0 g  V3 G3 f, n  H
  23.     classBlockchain(object)
    3 {: f7 j1 i2 [4 ~3 @# f
  24.     …! r, H. J& i0 F' M
  25.     defvalid_chain(self,chain):4 O4 g0 I3 n* S, ]$ p/ [
  26.     """
    4 Q7 }7 G8 ]0 x5 [5 j
  27.     Determineifagivenblockchainisvalid
    : \0 f. z1 I* E! |4 {6 w
  28.     :paramchain:Ablockchain
    6 K$ Z6 \7 c* H' W+ s
  29.     :return:Trueifvalid,Falseifnot
    5 Y3 c2 Z& U& N: H! Z
  30.     """; ~- ?4 }0 z! k6 B' p" F
  31.     last_block=chain[0]7 }$ b2 n6 h+ h+ C& Q
  32.     current_index=1- T7 ^; c' t; B4 O
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):
    , x( O/ h: L0 x5 _  }2 n2 }+ \
  34.     max_length=length
    6 J+ W1 c" T4 p4 u0 B+ {
  35.     new_chain=chain
    8 n; f$ v0 _8 B" M. L
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours4 k' D+ l  ~7 h- S
  37.     ifnew_chain:
    2 J2 C1 s) o- k- ~  I5 Q
  38.     self.chain=new_chain
    ) F* y4 z3 d9 B0 ]5 k7 t
  39.     returnTrue
    7 ~6 o3 g' C. l' v' |
  40.     returnFalse* B# c( r1 @& x6 Q! O' e8 }0 h4 n5 {
  41.     ```
复制代码

4 \" y9 x2 W5 |0 M0 e    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。& |' x3 h8 y$ x$ O' c
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。' G" W2 ~/ Q2 S0 w: t7 ~
    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。' a! a! K) E0 }, }" n: L
   
  1. @app.route('/nodes/register',methods=['POST'])
    ' O  U/ e! A4 @# E! q4 W/ ^! M
  2.     defregister_nodes():
    * h3 b: t4 n% a! c  Y# M8 e2 |
  3.     values=request.get_json()
    + K/ |; r- w  t
  4.     nodes=values.get('nodes')
    1 g+ L5 t; A" W' z
  5.     ifnodesisNone:$ B! H: w4 j! p& c! h: u; D
  6.     return"Error:Pleasesupplyavalidlistofnodes",400  _: T9 ~' D4 w: Z$ h6 _1 L. A
  7.     fornodeinnodes:+ z0 m! G' ]+ r9 P) s. E
  8.     blockchain.register_node(node)
    & o2 p* y7 w( S' c* }
  9.     response={
    / a6 `+ X8 r/ N2 F( A0 u# s
  10.     'message':'Newnodeshavebeenadded',
    ) q0 k( Y2 A% c5 t2 s
  11.     'total_nodes':list(blockchain.nodes),
    ) i6 ^! p& V# i& F
  12.     }) P, z: W$ o# c" ]4 `4 e7 R
  13.     returnjsonify(response),201
    # T" c# E, T, Y% Q9 M( m# ]* b
  14.     @app.route('/nodes/resolve',methods=['GET'])
    + {6 K* a, \5 a9 t) B1 H
  15.     defconsensus():
    . E! r) Q1 z6 A4 M
  16.     replaced=blockchain.resolve_conflicts()
    ( K' f7 C; B/ s5 O/ }3 _
  17.     ifreplaced:: K6 g! x1 G5 s- U
  18.     response={9 H1 Z4 ]0 O; _. |
  19.     'message':'Ourchainwasreplaced',
    6 R4 N( L" J0 [- L
  20.     'new_chain':blockchain.chain
    7 D/ B2 x" b! j- D
  21.     }
    . C8 b3 A& G+ y/ V
  22.     else:
    " l5 b$ ?( \; w+ X
  23.     response={
    4 o  A/ d5 m; ?. N# D, ]
  24.     'message':'Ourchainisauthoritative',
    . Z1 O: z3 D5 K: y6 p# N4 k
  25.     'chain':blockchain.chain+ f( L' k7 {8 R" [7 L8 G8 x! q- i3 }
  26.     }# u: Z( C- D# S. o( t- B6 @2 o
  27.     returnjsonify(response),200, M1 F. i9 h# E
  28.     ```
复制代码
2 O9 ?* q3 C+ O
    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。1 H4 H% L2 {2 F" o2 i0 g- s

& ?+ h9 |0 ^  i, v6 Y/ L    -注册一个新的节点-1 V7 I' Z( q6 Z
    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7