Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
386 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
/ Y$ a3 r" ?% M% q! \7 P    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
* U( U  v4 Y) ~4 x    准备工作
0 F* F& B2 m  ?' s3 A! \    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
. v( g* P! \0 @    如果你不知道哈希值是什么,这里有一个解释。
* J/ {: b3 m4 W5 W    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
4 |% X2 @8 r% t% ~; }8 E. A    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:6 ?! q5 K2 z8 ~# y+ \6 ~! D
    pipinstallFlask==0.12.2requests==2.18.4
4 Z7 G  K  ^9 I! n+ K0 W2 {    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
1 i# l9 s/ y" c    一、开始创建BlockChain) y/ B; y, E/ t" e
    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
4 U4 H/ s" w( m$ a, y    BlockChain类" \9 m' x' s2 }( ~+ l: r$ n/ m
    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。# x7 p  b9 U2 y8 n
    以下是BlockChain类的框架:
' }5 b% V$ q# I& I- w  
  1. classBlockchain(object):" L8 n! B* o3 T. v7 w& I3 }5 @
  2.     def__init__(self):
    3 L$ L; |% M: K- i. d
  3.     self.chain=[]
    # y4 o* T0 J4 f4 J2 y) Z
  4.     self.current_transactions=[]) c! }; Z& h) Z" p4 M
  5.     defnew_block(self):
    . k) u; b' [# n  B7 N
  6.     #CreatesanewBlockandaddsittothechain! W: |* P0 m5 ^0 C
  7.     pass4 h( y' d4 b% N
  8.     defnew_transaction(self):; _1 z4 v& g3 E1 w& O8 r
  9.     #Addsanewtransactiontothelistoftransactions
    : E' Q4 K' v; G) B% n( |3 c
  10.     pass4 U4 B6 x9 o% T1 I6 ~( ]
  11.     @staticmethod: Y( W  G' H; {% T
  12.     defhash(block):0 J( {! R, E5 o3 Z+ F% ?" F$ \
  13.     #HashesaBlock2 Q, A8 b6 s5 q
  14.     pass
    , V* I3 r. t3 M. M+ {" J
  15.     @property3 m; H  y2 f8 {$ n' F* |
  16.     deflast_block(self):
    4 |" X+ t! i/ `0 J  A; G9 g! Q! S* W
  17.     #ReturnsthelastBlockinthechain! x+ B# n0 s) a4 n  F6 K5 ~. g
  18.     pass
复制代码
, x2 N. F& F( |5 j- L
    -我们的区块链类的蓝图-, |5 O, L- F; m7 n- B
    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。9 ~, W7 d+ E0 \+ [
    块结构
+ r6 J4 l  c& r9 x    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。6 H3 o& l4 Z2 L  w$ ~
    以下是一个区块结构:
5 O0 m: c& |' q( t: M$ h. N4 e2 G   
  1. block={
    * z7 g4 L7 L! k4 b; k1 g
  2.     'index':1,. J' |& T+ U/ j
  3.     'timestamp':1506057125.900785,
    3 e2 F6 {+ y. I6 p4 S6 q1 c
  4.     'transactions':[* u! Y# p" x7 {+ }/ t, `
  5.     {
    3 }1 u. g- P6 R5 T
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    & d7 i1 z" o9 k, N& t6 J$ c+ u6 i
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",+ o9 }7 q1 J- |
  8.     'amount':5,
    3 f9 t0 n) o8 E" \1 c/ Y
  9.     }5 z1 t5 {- _& d1 ^0 E+ N
  10.     ],
    7 t4 M& h+ S: j( I9 _
  11.     'proof':324984774000,
    - N, y2 _. _; Q% W. G8 A- }9 q- d7 q
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    6 s( g! n) C3 ]1 m. F+ E/ y1 g8 \
  13.     }
复制代码
7 ?0 x8 X: Z* O! W) ~* I
    -链上一个区块的例子-# h) n9 U+ B! r- t) Z6 ]9 |
    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。" y* I+ n) c, B% Z) a/ T0 x
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
6 `- L( U/ r$ v4 Q: r5 s    加入交易1 p& c8 \* a" n5 E
    接下来我们需要添加一个交易,来完善下new_transaction()方法。
4 t3 ?+ y6 x& O( r  
  1. classBlockchain(object):
    2 b' q, Z- t( O: A- P# H9 K" s9 [
  2.     ...
    ) ~4 K+ `1 k2 j6 J  F4 t8 s9 m5 g3 P
  3.     defnew_transaction(self,sender,recipient,amount):* _  I' M' u2 z" j
  4.     """
    / e. v' V* r1 ~1 l" O4 V( _
  5.     生成新交易信息,信息将加入到下一个待挖的区块中) p; X% B' }, s' l& H) {( V
  6.     :paramsender:AddressoftheSender
    ) p' `( g& D8 R& y2 o
  7.     :paramrecipient:AddressoftheRecipient* O( s. x. B/ V/ @" g+ Q
  8.     :paramamount:Amount
    7 {) G/ J6 N9 o: H
  9.     :return:TheindexoftheBlockthatwillholdthistransaction
    : b# V; D2 I7 l  F5 q0 w( B
  10.     """
    . X0 k! b& v7 P& {
  11.     self.current_transactions.append({
    - {- l; y4 y0 y8 J6 _
  12.     'sender':sender,
    0 _0 q6 Y+ L1 W# ^1 b" [2 @; |' P
  13.     'recipient':recipient,; t" W: f( ?1 W$ n2 b
  14.     'amount':amount,
    5 G3 w* f8 r7 ^% {
  15.     })8 H: q; P2 u, H3 Y1 X% x
  16.     returnself.last_block['index']+1
复制代码

2 w) T6 v6 T3 Y    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
; z3 ]0 |" O" e    创建区块( E/ y3 ~7 X8 O/ d, O
    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
# ]+ Z( y% Z% f% A! p1 k/ H    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。0 N( `' h0 J% S+ j  t; E: C! J
    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
/ @; g/ Q/ U8 @0 i9 b  
  1. importhashlib
    & A9 E  c8 ^0 H: f5 B3 a
  2.     importjson" V/ X  F! b' m7 ~
  3.     fromtimeimporttime
    0 e" Z3 y9 r% G2 v4 ]7 [# b
  4.     classBlockchain(object):" F: d6 T! R* x, f
  5.     def__init__(self):
    * ~- m. P& o% \3 q; F
  6.     self.current_transactions=[]+ @+ [0 x7 ]# i7 p% g% o8 p
  7.     self.chain=[]. \- T. S. u4 X( b& R
  8.     #Createthegenesisblock
    1 v! B& E0 z8 W5 V$ q
  9.     self.new_block(previous_hash=1,proof=100)* W% d! \, y- a
  10.     defnew_block(self,proof,previous_hash=None):
    4 d7 _# A6 o4 P: H
  11.     """
    ! Z$ N3 V' Y) }! b( P& v: U
  12.     生成新块. i/ d$ e+ {* W" P
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm% ^) ]5 m3 X8 e  B- N5 c
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    # _& T1 l: I/ j3 K: j5 C
  15.     :return:NewBlock
    ; `' E1 o4 }* p% X. {
  16.     """
    2 [! E5 [2 L; U" I# E3 h. e. |
  17.     block={- S6 D- r* |8 ~: {: X* N2 a; V
  18.     'index':len(self.chain)+1,9 P; |' t$ }7 K, d2 k8 P
  19.     'timestamp':time(),
    , z. A  V2 H, `) M7 h* s; @
  20.     'transactions':self.current_transactions,: }6 J5 O" a) i
  21.     'proof':proof,$ H: Q4 {( Z2 G; T
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),5 t) \3 I; A3 g, Z" ]% U
  23.     }# D% v- X+ r1 q  h( M' f& ~
  24.     #Resetthecurrentlistoftransactions& ?( {& h( E6 O& d) n9 V9 w
  25.     self.current_transactions=[]
    ; D7 O8 g3 T" J' z. [3 g) c3 T/ b
  26.     self.chain.append(block)
    1 F; B8 ^/ y5 T
  27.     returnblock
    & i( R6 _) K8 T; C+ ^
  28.     defnew_transaction(self,sender,recipient,amount):- h- r( D: ]5 ]$ O
  29.     """
    # @$ S2 X, w9 E; Z& P
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中8 m8 K! ]2 P9 U
  31.     :paramsender:AddressoftheSender
    1 v5 G/ v% K6 i$ R& Q
  32.     :paramrecipient:AddressoftheRecipient% t( c  d+ W. n
  33.     :paramamount:Amount
    + I- R+ w' u( ^9 X$ n/ U& _8 A/ z! \
  34.     :return:TheindexoftheBlockthatwillholdthistransaction7 @6 ^: x4 a" \- ^9 F5 S# g
  35.     """' p; @" k- f1 S8 t* ]
  36.     self.current_transactions.append({
    3 \* {+ E4 J/ l; ~/ F
  37.     'sender':sender,
    ' ?: R8 s- k) a# J/ Q2 v
  38.     'recipient':recipient,* A5 h) e% r3 k: E
  39.     'amount':amount,
    - S) V; b! x  V8 v5 s; ?' Q4 m) [
  40.     })5 u5 y% ?: }5 r/ n/ k/ B6 h
  41.     returnself.last_block['index']+1
    ; J3 R. V( a9 F
  42.     @property
    / F  f+ H5 [2 h9 L5 P  E1 @5 r7 j
  43.     deflast_block(self):0 J9 d' T1 Q! b, F
  44.     returnself.chain[-1]* m) x! X# |3 b  u( C
  45.     @staticmethod
    9 `1 ?4 `, \0 |' D0 ?4 _( \- M  i6 E
  46.     defhash(block):. C4 a$ e8 q' \  q$ [( Z+ \: f& \
  47.     """% ^& P) B) `! E5 m' W5 K' y$ F6 u$ M
  48.     生成块的SHA-256hash值
    5 z" e) d1 o/ N5 _8 R; d4 N
  49.     :paramblock:Block# x! u1 d# t6 Q' W7 L8 q
  50.     :return:
    + n6 }* v( ]! h$ P
  51.     """
复制代码

& A$ x1 K" ?2 |  k    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes- ~4 |0 J( i0 g' T) m7 R" ^3 q* |
    block_string=json.dumps(block,sort_keys=True).encode()
2 K* n* O, I5 P/ _9 T' n7 A    returnhashlib.sha256(block_string).hexdigest()$ y. c( J6 |/ t
    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。) T/ Y/ q! |8 I8 k- |( }
    理解工作量证明) i; e: n/ ]/ `6 r! L# S
    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。9 h( _5 i8 L* l1 y* X, G" V
    为了方便理解,我们举个例子:. R% Y& I" I& m3 f5 p
    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:, i6 c: |6 N" ~6 |  F( m. ]( ]4 r
  1.   fromhashlibimportsha256
    1 D2 E( v1 Q! l1 D5 V
  2.     x=5
    # _: c- v# C# u- \9 Y& s
  3.     y=0#y未知) {1 l6 j1 W8 E5 r. ~3 I( J( @1 H
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":' R+ z" x& z1 Y" [
  5.     y+=1
    ) |* n- D6 H# h, E, \/ d' O
  6.     print(f'Thesolutionisy={y}')
复制代码

$ Q5 G8 g: k  M4 [7 ^/ ]( Q    结果y=21,因为:
, I1 Q/ X; _, K3 I    hash(5*21)=1253e9373e...5e3600155e860
& l, t% ?- n/ F5 W  n    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。( V5 I! k' P( z# b* j/ i
    当然,在网络上非常容易验证这个结果。
  _. i6 ?5 f2 v* {3 e    实现工作量证明( u# }& O- j2 e7 A4 `
    让我们来实现一个相似PoW算法,规则是:3 G6 v' O, k# F" Y4 ^- o1 y1 ^* w
    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。$ \& r7 e2 a0 W
  
  1. importhashlib6 w; J0 r6 n, b8 x& I
  2.     importjson" f$ B5 K( z# ^; y5 t
  3.     fromtimeimporttime& ?3 v- {8 f# r
  4.     fromuuidimportuuid4
    5 p) w& X; g$ w9 w: r- }2 O1 ?
  5.     classBlockchain(object):3 {3 L5 U- m9 U* x" X
  6.     ...& x2 Q4 @* h- `7 `+ w, F
  7.     defproof_of_work(self,last_proof):
    4 z# |4 o6 e0 U' O& x4 t
  8.     """
    $ K  Y) a1 \; g- H, @
  9.     简单的工作量证明:8 R" n2 x; H, g) i' R7 e
  10.     -查找一个p'使得hash(pp')以4个0开头  s7 e5 x7 U  v( H1 C. ]
  11.     -p是上一个块的证明,p'是当前的证明2 e- A) w: o8 H
  12.     :paramlast_proof:+ U  r% ?$ g! o6 \) ]) L1 J) ]! X
  13.     :return:
      [! P; x' T' `: X
  14.     """, s7 G$ n! N& H- P- ~2 R) w' ?
  15.     proof=0
    9 u4 E7 c- _6 y0 p* b
  16.     whileself.valid_proof(last_proof,proof)isFalse:1 p7 o7 @7 H% s( F! c9 ]' i
  17.     proof+=1% z" Q2 I: q$ V% d
  18.     returnproof
    7 u6 G6 W5 D0 T2 Z2 q' K6 D  T6 |
  19.     @staticmethod3 z+ M0 F+ f' I4 C
  20.     defvalid_proof(last_proof,proof):
    * t, I% D) U! V7 j7 X1 p+ C( k  N' }
  21.     """
    9 H; L, Y2 A3 }- I) D: u4 \$ Y
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?( X. y* S6 i+ q
  23.     :paramlast_proof:PreviousProof
    0 n) b  y5 k6 n3 E: K  N$ f) ?7 d
  24.     :paramproof:CurrentProof
    $ g9 X/ r9 K- }9 p
  25.     :return:Trueifcorrect,Falseifnot.
    ' j9 d+ V8 |3 }4 |  y2 E# A; z
  26.     """! f# {: E  @, a2 a, u% Z
  27.     guess=f'{last_proof}{proof}'.encode()! @1 }# e( B2 G; l' `& G+ w  y/ [' s$ D
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    0 X4 d" @! Z9 h8 t% b* E0 k
  29.     returnguess_hash[:4]=="0000"0 p  F' I. S- v& W. w5 J
  30.     ```
复制代码

( Y- I+ m- B6 L$ c, @! ]; m    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。, n0 s3 X8 x5 J
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。: }5 W3 }5 [# }3 x
    ##二、BlockChain作为API接口
3 k6 f9 f: ?' I7 y    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。" o" p$ ~1 N* v, Q3 d2 f
    我们将创建三个接口:
* l4 E( F; _, C$ p& F   
  1. ```/transactions/new```创建一个交易并添加到区块
    2 F$ g% F- u7 d$ @) {7 S
  2.     ```/mine```告诉服务器去挖掘新的区块, l5 P! Z& v8 r& ~% F, v! U
  3.     ```/chain```返回整个区块链" j7 N  g- R8 `
  4.     ###创建节点1 L9 H6 i! n9 m7 z0 L3 D3 w% K6 H
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
    " P6 J* k( ~. T" P, Y
  6.     importhashlib
    ! J+ \2 P* r1 ]; G3 W# b
  7.     importjson
    ; i" V2 z% N# m3 x2 F& l. t
  8.     fromtextwrapimportdedent
    * z9 k$ U9 ~" N  d/ c( ~6 y
  9.     fromtimeimporttime+ Z  k0 }7 B- }  }6 O, d
  10.     fromuuidimportuuid4
    " l% k# Z3 q. v. b: t7 N9 @
  11.     fromflaskimportFlask
    9 {8 o. P# i% Y( b( o( A, X$ f7 L
  12.     classBlockchain(object):3 W! f: d& z# n' g; d- B8 n
  13.     …+ v" m$ x0 Q, f2 I
  14.     InstantiateourNode* ?4 b- c$ B- Q9 w3 F; g, f' a
  15.     app=Flask(name)
    , r& S6 W2 ~8 |7 \
  16.     Generateagloballyuniqueaddressforthisnode: L9 f1 {) T. @
  17.     node_identifier=str(uuid4()).replace(’-’,‘’), ^9 ?: S8 D+ G  u
  18.     InstantiatetheBlockchain+ [& q1 D' G* k1 F0 d. J
  19.     blockchain=Blockchain()
    ) H7 z" ?1 X. j& [
  20.     @app.route(’/mine’,methods=[‘GET’])7 ?; y9 g& @7 W5 h
  21.     defmine():
    # b7 R0 @% J3 [6 d3 c5 c9 |
  22.     return“We’llmineanewBlock”* Z  S# c5 `4 n- v" I& a- _
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    4 C# c# Z; R1 q: |$ `
  24.     defnew_transaction():2 C8 N& D+ _7 C, r8 O
  25.     return“We’lladdanewtransaction”
    + e- X- W3 \# _/ t- I& K
  26.     @app.route(’/chain’,methods=[‘GET’])
    # p1 e  @4 a6 a- i3 d- b) V% H
  27.     deffull_chain():
    ! H7 ~) X6 Y( K2 g, v  @
  28.     response={
    0 {. i6 D/ d& V: U! p2 N' _
  29.     ‘chain’:blockchain.chain,( U' p: Y( h& E( f
  30.     ‘length’:len(blockchain.chain),
    8 n! Q5 b, ?1 V, j" E+ [
  31.     }8 v- t( U! g4 ^4 Q; ]* z( c  t
  32.     returnjsonify(response),200
    4 D* X7 _: G$ k" u
  33.     ifname==‘main’:
    ' B3 O% F, @$ n1 S  a7 @! @$ _0 g
  34.     app.run(host=‘0.0.0.0’,port=5000)* V& A9 {5 P1 B/ T; X0 W
  35.     ```* s% H! g3 j+ m
  36.     简单的说明一下以上代码:. c3 G4 R6 n) B2 ^# p- p
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。. N  T0 Q& q' m$ `  p7 }  M
  38.     第18行:为节点创建一个随机的名字。
    * ]* g3 A3 k/ ]% G% G" C) ^
  39.     第21行:实例Blockchain类。# b4 `& m% f2 y! w
  40.     第24–26行:创建/mineGET接口。1 o  ^' U. Z7 \$ x# Z- e  |
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。& _. @5 Q' H! z- L
  42.     第32–38行:创建/chain接口,返回整个区块链。) \$ X# D* F4 c' z' h$ V
  43.     第40–41行:服务运行在端口5000上。; _/ W( v0 ]. }$ K  i
  44.     发送交易, h, l7 Z8 G! X0 |+ U8 d
  45.     发送到节点的交易数据结构如下:
    7 [: a) s8 Z6 b  U
  46.     {3 a' E% v9 R7 l$ S3 h' u5 T
  47.     "sender":"myaddress",: h8 z8 Z# Y2 o
  48.     "recipient":"someoneelse'saddress",: a$ H& [! d% S8 ~# [- J$ E
  49.     "amount":55 B2 x0 B1 r3 H
  50.     }
    ' Q% _& x: k9 H" i0 f+ I3 V
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:) k/ k! u7 a/ q) C- v+ j* e! B) S! D
  52.     importhashlib
    * m1 j2 A8 C& u8 }' b; x+ R
  53.     importjson
    * \, n( s" ~) Q
  54.     fromtextwrapimportdedent
    7 q7 {. k" @% e- ?( ~' [
  55.     fromtimeimporttime
    6 ^0 o0 I7 U( ~. N: v5 G" e. A
  56.     fromuuidimportuuid4
    3 G; H+ P3 k) O( x
  57.     fromflaskimportFlask,jsonify,request! T. I" w6 |2 W+ j* E" i
  58.     ...# R$ R  {; T8 `  A& _
  59.     @app.route('/transactions/new',methods=['POST'])6 ^' P$ v- q7 U
  60.     defnew_transaction():
      d; j3 E; V5 ~. B( Y
  61.     values=request.get_json()
    % x9 |  u( L$ _( E2 g1 r4 o
  62.     #CheckthattherequiredfieldsareinthePOST'eddata# w4 D0 e  X! ~2 r4 U9 E4 F
  63.     required=['sender','recipient','amount']4 n2 [* C3 Y: c1 v3 Y8 x
  64.     ifnotall(kinvaluesforkinrequired):- Z/ D/ t$ g. ?+ O
  65.     return'Missingvalues',4005 a5 a$ F- e, w* a7 @$ _! H6 V( H
  66.     #CreateanewTransaction
    ' d% b* B  O% j+ h2 ^; j
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
    + E1 g# Z- m$ O. {  ?% z4 k! v& ?
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}" Z; J2 e  Z" e" m
  69.     returnjsonify(response),201) m1 ^& b9 L9 g1 {. h
  70.     ```
    # Z2 n) X; c; J# A* W* Y5 Z6 k
  71.     -创建交易的方法-! r1 c% t0 N. F" @
  72.     ###挖矿  t+ ^" ^4 y% s1 a
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:6 {! j* }3 }, R5 ~/ t4 h
  74.     计算工作量证明PoW) l7 [6 s# W2 S$ k9 a3 ^0 L
  75.     通过新增一个交易授予矿工(自己)一个币* C6 K7 V  }1 S
  76.     构造新区块并将其添加到链中- W# I' Q% D* x: D
  77.     importhashlib
    " x2 i0 ^& b% O! i2 q
  78.     importjson
    ! {2 K# _9 Q/ N! w. p3 {
  79.     fromtimeimporttime+ B, G. X4 X% K* u1 @
  80.     fromuuidimportuuid4
    3 R8 ~- K. ?. p9 t) C6 _" W
  81.     fromflaskimportFlask,jsonify,request
    9 u3 s* U! H& \2 c- e" {1 X
  82.     …
    + C1 Y* s! n  f& t' R! S5 n/ M
  83.     @app.route(’/mine’,methods=[‘GET’])' B5 n. n, |2 y  i1 z6 v: k0 E
  84.     defmine():
    , i( h' Z* L; N4 Y+ }8 {; g2 }3 O
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…
    4 X$ L3 d4 \* D: f
  86.     last_block=blockchain.last_block
    , R4 `, y+ N+ c7 k% X( O. |1 T
  87.     last_proof=last_block[‘proof’]
    " \" f* U" o4 J2 b; _
  88.     proof=blockchain.proof_of_work(last_proof)
    ; b3 k0 z6 y( d0 J) {+ Y1 r
  89.     #给工作量证明的节点提供奖励.
    2 \# e* ^7 x' B* I# ?/ h% ^# O
  90.     #发送者为"0"表明是新挖出的币.
    1 c5 v' X" c- G/ @  l" g
  91.     blockchain.new_transaction(9 P9 Y2 K& {+ K4 X
  92.     sender="0",* Y3 _( k! ~- L
  93.     recipient=node_identifier,
    " S6 q6 Z4 B0 y$ R9 Z
  94.     amount=1,; U/ E) @2 t, c( d8 ]7 d
  95.     )
    ( M2 y1 ]. C3 }: [9 t, J) J
  96.     #ForgethenewBlockbyaddingittothechain
    # @5 g# @; s# [7 \2 U# x( S
  97.     previous_hash=blockchain.hash(last_block)
    5 ~% ~$ z  ^+ c* j7 u4 y- u
  98.     block=blockchain.new_block(proof,previous_hash)$ }5 l$ {9 s( _
  99.     response={% @' P, L4 P1 L( D: G0 Z! v2 V* B
  100.     'message':"NewBlockForged",  G: l* H8 s4 v5 F( W; \  {
  101.     'index':block['index'],
    . I9 I3 w- G. g# M
  102.     'transactions':block['transactions'],
    & \. v! M) J; m& A" w2 m
  103.     'proof':block['proof'],7 Y3 D7 W. n. k& X4 [8 J
  104.     'previous_hash':block['previous_hash'],- Z6 O8 Y1 n* B# x
  105.     }
    $ f  h' Y4 V# ]  ?  r
  106.     returnjsonify(response),200
    % n- a) ~& |7 F
  107.     ```
复制代码
1 f5 c/ T" I: i+ m% I! C
    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
( W" ?$ Q" c# v% r; b    三、运行区块链
! W( z$ U, J& Z! {- v4 t    你可以使用cURL或Postman去和API进行交互
) P# a3 Z+ E- H7 t; ?8 N* n/ s    启动server:% J) o! A3 P! H( \/ |
    $pythonblockchain.py& k0 Z& y+ z$ j; E: h) U
    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
+ e! {+ L. z7 E3 m) `$ z, `    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:  I3 ]0 `, F- b- V
    -使用Postman以创建一个GET请求-
7 w6 c# ^* j1 S% O3 L    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:2 Q, k+ r: r8 c! X7 X! Q9 }# Q
    -使用Postman以创建一个POST请求-/ @) o7 W6 `- _" E" I
    如果不是使用Postman,则用一下的cURL语句也是一样的:. w( y8 E9 M" v4 k/ f
    $curl-XPOST-H"Content-Type:application/json"-d'{
9 [0 F0 v" V- o9 ?3 f8 S* p    "sender":"d4ee26eee15148ee92c6cd394edd974e",
, c( ]6 @6 A# C* H    "recipient":"someone-other-address",
, D7 D/ e: i2 h3 n. x* c6 |+ ~    "amount":5
# \! j( ^9 n2 R" f    }'"http://localhost:5000/transactions/new"8 z. A; `' _. Y7 ~* V
    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:" P/ V; U8 u& v4 C8 @
   
  1. {
    2 c: i) Y8 g9 `5 [- R: U
  2.     "chain":[' X- V6 `7 h  \7 }: u
  3.     {
    " O2 a0 p+ C7 r4 ]" \+ Z# _' u
  4.     "index":1,
    8 F# D; k: k) y# S$ c% N
  5.     "previous_hash":1,
    * c5 P5 R  h6 o: P: H
  6.     "proof":100,
    ' W! n! M( B3 L- T2 \! D. `! d) T# w
  7.     "timestamp":1506280650.770839,' }( ?1 c. Q& |' R; E! \
  8.     "transactions":[]% S8 [  @+ _# g- [
  9.     },
    * o# m( d3 W2 \& R
  10.     {+ \5 [! T" ^  ?  i
  11.     "index":2,
    3 `& t; Q  ^. H! X; I, c" P
  12.     "previous_hash":"c099bc...bfb7",. `5 T2 r7 i; Z0 v" f
  13.     "proof":35293,
    ( `' Z; K* _7 y
  14.     "timestamp":1506280664.717925," G) t9 p. U. e% C
  15.     "transactions":[  r8 p" e1 R9 d
  16.     {
    / [: _$ ~  d, j7 f
  17.     "amount":1,- b! }8 h$ T: c% e* w. ]* i# B
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    2 f) N% Q' A% X: T  ^3 v) b  E
  19.     "sender":"0"
    7 r# L% |" u6 U* K2 _* L+ t; Z
  20.     }
    1 e, s$ [( |6 m
  21.     ]5 p0 O  s2 t3 p6 |: Q
  22.     },
    8 k( A9 U. B/ S+ Q/ y
  23.     {/ j, e! ]4 p4 j5 {! N) o% D
  24.     "index":3,
    + p3 w) D4 I& w
  25.     "previous_hash":"eff91a...10f2",# f# n1 e: v" j: @' V% r/ _
  26.     "proof":35089,
    8 y. u, s2 M$ H8 {( M
  27.     "timestamp":1506280666.1086972,
    / z+ f2 X. l+ K6 m( B, d
  28.     "transactions":[; |* g1 Q! Y' H( }' @
  29.     {9 j" P- H+ b; j' _- y. B- S! {
  30.     "amount":1,/ U# S! `. p7 }& P4 O5 Y0 j& b
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",  \( w6 _. W1 }
  32.     "sender":"0"5 q8 L; `3 W' \2 B/ T
  33.     }# O: [6 E% z- o
  34.     ]
    * T% C9 [- R0 ~$ N
  35.     }
    + p' g. v1 b5 V
  36.     ]," |3 U( z% ?$ i4 M$ V  C
  37.     "length":3
    ! ~8 ?  q9 w; n- \; x+ }
  38.     }
复制代码
5 N" T" x7 x. f5 b, `/ N$ o
    四、一致性(共识). S2 B. B" ?, U+ j6 O7 U
    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
; C# ?; L# g( ~4 d7 C9 P/ ~. k& s    注册节点8 o+ {2 L2 O1 j+ G/ }+ j
    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:1 H+ U: y- l8 K  I7 E$ O* C
    /nodes/register接收URL形式的新节点列表. h; ]' n1 X& l7 O
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
) a& i  `- ?& w3 w0 V7 O    我们修改下Blockchain的init函数并提供一个注册节点方法:9 v7 @. z7 C4 }, U6 I# m
  
  1. ...2 t+ B7 I! f! G  [1 n! }+ E- x
  2.     fromurllib.parseimporturlparse! O# L- X% u* Z2 v- Z- Q
  3.     ...( W- Q, q3 {7 X7 a' U* J
  4.     classBlockchain(object):8 K  C% c$ W# W6 Y+ r0 b& r+ J
  5.     def__init__(self):5 U$ {3 m# `! u6 E& @! Y8 h
  6.     ...
    7 B# z! }$ y/ T! o6 _
  7.     self.nodes=set()
    2 r: ^1 L7 ?# j. g
  8.     ...
    5 c! I( q4 z  H
  9.     defregister_node(self,address):# `7 [: w& {0 [2 J/ t# {) a
  10.     """
    ' t9 Q" W) {5 Z3 v) v$ N
  11.     Addanewnodetothelistofnodes- C! U: O3 |  A9 |- a1 t
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000') c) l& Y2 N% ~. x7 c
  13.     :return:None
    4 [) v2 Q# c6 [! X9 k- W: D, p% _
  14.     """
    5 X+ t5 _1 z8 [3 w. ^
  15.     parsed_url=urlparse(address)
    5 m% Z5 H( ?% p" `
  16.     self.nodes.add(parsed_url.netloc)% Z; c3 W; R' c
  17.     ```" b1 f1 l1 G+ }: U- Y
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
    & A6 Z- O' V0 V
  19.     ###实现共识算法
    - s+ R! ^- n4 |4 q. l; V/ I. z$ a
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
    " r' m8 E1 ?$ r( T
  21.     …# w6 I1 ?6 f8 G; |
  22.     importrequests
    # e) [( s" T% a' i+ o
  23.     classBlockchain(object)4 `, R& R4 Z9 z( x: x
  24.     …
    2 Z. ^1 X5 Q! \$ i, L5 k
  25.     defvalid_chain(self,chain):
    * U! n; P2 ~2 n! _5 W9 N
  26.     """1 j& F  G' K! l- @; T" h3 k2 o
  27.     Determineifagivenblockchainisvalid/ V2 N& K4 W, C8 h
  28.     :paramchain:Ablockchain
    , ?; k& r3 G+ f
  29.     :return:Trueifvalid,Falseifnot4 \  |/ u5 v, u* r
  30.     """% X7 `! C+ p0 W) S
  31.     last_block=chain[0]8 j( L0 u6 Q4 y9 ~" x
  32.     current_index=1
    # `7 Y0 Q* I# @, i2 e+ ]" Q, ]/ s
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):7 W% z0 [% |# Z) ?& W; H
  34.     max_length=length
    3 p2 {: e/ h6 n7 i- w
  35.     new_chain=chain
    8 w* b+ W- u3 L
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours6 ?% |; C( `* N# E  M2 \+ b
  37.     ifnew_chain:/ h' e6 j  M. x3 m1 X. y
  38.     self.chain=new_chain
    & o+ y6 _9 {( T) a  H9 @5 v5 T8 J
  39.     returnTrue
    , w; T& u% d0 {6 Q4 D
  40.     returnFalse8 G' G8 {  m5 o& ]
  41.     ```
复制代码

0 y: t! Y7 s/ f9 ?4 U7 B) T    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。6 P1 I- \3 w7 {  N, |
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。' F+ {: Z" c  Z% j
    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。9 A. b, P. c( D$ S# j/ U1 D- M
   
  1. @app.route('/nodes/register',methods=['POST'])# c1 n6 h7 b' O1 R1 \
  2.     defregister_nodes():
    : e6 J: t4 M4 N% _0 `, w' {, [
  3.     values=request.get_json()
    * }: R' [; c/ m8 ^7 H
  4.     nodes=values.get('nodes')
    8 Y8 ^7 E" J; e* j+ x
  5.     ifnodesisNone:
    8 [, w/ J2 o! X: \- N; _; F
  6.     return"Error:Pleasesupplyavalidlistofnodes",400
    6 H. ]) m. M# D3 I
  7.     fornodeinnodes:1 v3 N1 V% p" ~" O
  8.     blockchain.register_node(node)
    + E' C2 r1 W, }% G/ D
  9.     response={
    / ~. \% N7 l+ q, s% V) a  A
  10.     'message':'Newnodeshavebeenadded',
    ) H  ]) k- ~# x/ @- f3 {8 z
  11.     'total_nodes':list(blockchain.nodes),1 P. L/ p1 Q1 O5 B' O8 p, T7 S
  12.     }
    5 ~' n% L) C' e
  13.     returnjsonify(response),201
    ! f( _7 X8 ]: u. V
  14.     @app.route('/nodes/resolve',methods=['GET'])
    : P7 U( R6 b/ `& e. u" z* B
  15.     defconsensus():
    : u4 `- i8 W4 {0 r# w, Z# h
  16.     replaced=blockchain.resolve_conflicts()( _' F2 E" G* [+ z
  17.     ifreplaced:0 w2 ~4 W# P. I/ K) a- m+ ?
  18.     response={9 ?# E& h$ R# |( t" {8 l
  19.     'message':'Ourchainwasreplaced',
    / l' ]& r- b# \8 E  y
  20.     'new_chain':blockchain.chain" V+ a' L% i$ k1 Y8 s
  21.     }
    $ J% s$ N: K$ r0 ?
  22.     else:
    ' ?7 W* k) ]* o5 L' a% l% e
  23.     response={
    % B3 p4 @) o- }! |% B; ]
  24.     'message':'Ourchainisauthoritative',
    . F$ X' V( R6 K
  25.     'chain':blockchain.chain
    4 R3 O6 \* c* J4 T2 q) ~+ T
  26.     }
    * [/ S* A0 v+ u4 A
  27.     returnjsonify(response),200
    & C. h8 R- K4 `0 r- t$ u
  28.     ```
复制代码
7 @6 A- g& e4 `! @/ g- s7 t- o" m7 e
    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。: u* U6 ^. F7 O0 ^
: L* I6 \7 ?7 c6 S
    -注册一个新的节点-
; K; W; z8 C* B( h" _7 i1 \    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7