Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
307 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
7 \9 m; A' |2 K+ M$ d+ p, K& x- U    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。4 z. c7 g" M" _; H6 ?3 ^& _
    准备工作! B/ g8 d1 k7 G1 W
    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
5 K8 }$ a& m# E7 x9 K* N    如果你不知道哈希值是什么,这里有一个解释。: r" ~3 Y3 U0 _) o( E. s) w$ y
    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
6 d; Y. q# B8 x+ _    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:$ n; R, s, [% p
    pipinstallFlask==0.12.2requests==2.18.4. q( L. D9 }4 T
    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
8 r/ B' I" F7 s$ A; _/ H    一、开始创建BlockChain
: l7 |! F3 G) y8 F2 x, T4 a    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。) [- @2 B- n4 ]5 k; P" n( v- s; A
    BlockChain类
( u7 V8 ?$ |' e    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
# E$ B+ k8 e$ g* @- S    以下是BlockChain类的框架:
# y' o. n/ N- v! ^  
  1. classBlockchain(object):
    7 I5 a% [4 r$ B' A! x6 p) M( K! S
  2.     def__init__(self):
    5 Q, K" j, _& K5 W# j
  3.     self.chain=[]$ D' W3 @. u: g3 t0 P4 i
  4.     self.current_transactions=[]
    & H: g; @, L; ~; ?( R
  5.     defnew_block(self):
    ( O! M* R6 y1 K3 h
  6.     #CreatesanewBlockandaddsittothechain
    0 b6 k) N& V( @# S, Z) L& w' x
  7.     pass- v* }, M$ {9 v2 X) v3 I8 Z/ [& N
  8.     defnew_transaction(self):
    0 F6 d- R# ~& w; z+ o$ j
  9.     #Addsanewtransactiontothelistoftransactions
    . x% p7 C( X/ D9 ?) p, o
  10.     pass' {. f# A* l7 x% C; ?' ^
  11.     @staticmethod- Z1 K* \7 _7 F! Y4 c
  12.     defhash(block):
    1 S! Q: B/ g0 j
  13.     #HashesaBlock) {2 D$ k- J0 L- I' v! X4 b0 g5 ]  G( ?
  14.     pass& b4 ?. v- A: h5 W, Z
  15.     @property* `" }, d1 [7 z0 ?4 ?3 X
  16.     deflast_block(self):3 z" P0 t. w1 |) N' i# b% b
  17.     #ReturnsthelastBlockinthechain
    7 x6 A+ ^+ Q3 J$ Y
  18.     pass
复制代码

! E. h8 E4 q6 j    -我们的区块链类的蓝图-
0 J4 [5 C1 I, W  u( A    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
  b* b+ w# d5 o  A. {  i6 M7 \    块结构
3 U! Z* `0 w8 O' t' r/ ~3 r, ]    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。- R$ Q. p. K2 g3 Y
    以下是一个区块结构:1 L8 a, c; c- G4 _( H
   
  1. block={; a1 e' c% [* F4 M
  2.     'index':1,
    + z0 n' W/ u1 `6 s6 s0 K* a
  3.     'timestamp':1506057125.900785,
    2 `" [( k9 w# L- z8 t9 n
  4.     'transactions':[
    6 C7 D* o$ M  i% V* q+ {8 y
  5.     {
    6 {; ?$ D, A0 T
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
      u& H: z3 r% T+ b( I
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
    9 c4 s$ t- V. }% q: g4 D  C. K4 B
  8.     'amount':5,
    7 f& `* _( b9 M+ T
  9.     }7 s1 ^& k' z8 j, C, c
  10.     ],
    , L& _0 y4 w2 T6 g; u
  11.     'proof':324984774000,
    # T# @8 X1 k4 g- V& w% Z
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", ~) L6 y* N' w0 e9 S
  13.     }
复制代码
* a2 c6 j* G8 f9 E9 r5 l- E+ C5 V
    -链上一个区块的例子-: ]& T' z+ D3 [' x, [& c4 Z$ i
    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。( Z1 |& C1 v, Q: f
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
- }4 i- ]4 h7 T1 N    加入交易- F! l1 ^* Z5 e' g2 V; f
    接下来我们需要添加一个交易,来完善下new_transaction()方法。1 y9 O" i/ k) H9 ~5 w
  
  1. classBlockchain(object):
    0 _$ B7 j, ?6 {, J
  2.     ...
    / l5 D& `+ B# z# y* w
  3.     defnew_transaction(self,sender,recipient,amount):
    9 K7 ~- ~+ u  g0 t7 n  D5 f
  4.     """& h! m- F1 |7 j& y
  5.     生成新交易信息,信息将加入到下一个待挖的区块中' q. m3 ^# q$ y) J  q
  6.     :paramsender:AddressoftheSender
    " b9 u# X  m' N5 E2 V( Q" a
  7.     :paramrecipient:AddressoftheRecipient
    0 ?! b3 q$ J, r
  8.     :paramamount:Amount
    ! g0 d4 O7 M9 o5 @! {8 `
  9.     :return:TheindexoftheBlockthatwillholdthistransaction/ y6 B4 F6 k; a) _7 l/ l9 _
  10.     """
    ; u1 M( e( j, {3 L0 W% C( W
  11.     self.current_transactions.append({4 x) e" N" A- ]! h$ ?
  12.     'sender':sender,
    3 l9 _3 o" @+ g% H% B2 O
  13.     'recipient':recipient,2 Q2 d( m" e- q0 K
  14.     'amount':amount,7 c7 R7 a8 J) o/ v7 q: E$ D, r; c
  15.     }), \; v: ^9 u1 A3 V% y0 t8 F
  16.     returnself.last_block['index']+1
复制代码
0 u- f  O/ c  c" ]$ q
    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。6 V2 p* r2 Y$ o) ?# b
    创建区块
# P& W9 X8 y! A% X0 ?& y/ `8 {    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
9 C6 S9 t7 Q: S$ |% M0 l0 U    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。1 b5 ?" S' v# X3 r( Q5 J- u- c
    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
8 Q* L8 Z% ?+ r# e$ |% z4 B  
  1. importhashlib
    , g& }! ^! p# Y
  2.     importjson. h3 ^* b: M& j2 ?
  3.     fromtimeimporttime! S% E3 r# _+ n" @8 {3 P; {
  4.     classBlockchain(object):' v: j9 f5 g+ H; z' j6 X
  5.     def__init__(self):
    ( p) X, {+ K4 E. t  b) {# L
  6.     self.current_transactions=[]- n5 S* [% B( _. c9 s7 f. w
  7.     self.chain=[]. p+ H2 \6 z1 y) z/ A4 [
  8.     #Createthegenesisblock! F- D2 ~+ F+ k. L+ }
  9.     self.new_block(previous_hash=1,proof=100)) P& @& Y6 {0 r) I
  10.     defnew_block(self,proof,previous_hash=None):
    - B2 u. D# e+ o  U! m. p
  11.     """
      Z$ T# U3 f" Q9 @! E
  12.     生成新块# l( ]& e. O  v/ i& {; _
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm
    1 a8 L4 M1 I6 k# }
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    - B& t3 k0 ~* L6 f4 Z$ z
  15.     :return:NewBlock' N0 r9 V; u% d% c. n% C
  16.     """
    6 T! I5 b9 X* \* ?2 o5 S( E& Y
  17.     block={
    4 }( q6 q' e  g
  18.     'index':len(self.chain)+1,* U5 y( P0 @3 n
  19.     'timestamp':time(),: |5 {/ W6 q3 {. M4 e% O3 G
  20.     'transactions':self.current_transactions,
    % Z$ P3 ~- {, D' N3 A
  21.     'proof':proof,  k4 E$ q# E) ]" `" P% b" N6 ]* V; u
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),
    ! N) p% t& e/ D( j* Y! u$ z8 ]
  23.     }
    ; B: B. B4 d' A" v/ h8 I
  24.     #Resetthecurrentlistoftransactions* M# U, e3 w, K. |$ H( e
  25.     self.current_transactions=[]
    8 d9 N9 A7 h1 u. B% x
  26.     self.chain.append(block). _8 g( G, Y& e7 n
  27.     returnblock
    3 e7 P* J$ ^( n
  28.     defnew_transaction(self,sender,recipient,amount):) }; p8 k( e  W
  29.     """6 X& R. n  H$ ]: X
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中" x; J9 q( d1 O
  31.     :paramsender:AddressoftheSender
    - m/ \5 i3 u6 q4 e- Z( \* s/ w5 c
  32.     :paramrecipient:AddressoftheRecipient
    6 x- a7 o+ K( L( F# H+ d
  33.     :paramamount:Amount
    & H; Z  \/ V) b4 ^7 s$ A
  34.     :return:TheindexoftheBlockthatwillholdthistransaction& ^# @$ [$ Q. p6 ^
  35.     """
    , f. |6 j2 K, U; d5 l: E* O( i
  36.     self.current_transactions.append({3 `; [1 D1 o& u+ Z
  37.     'sender':sender,
    $ x1 E; d; T  r6 n9 U
  38.     'recipient':recipient,  |1 {5 T! l0 F* N# ^
  39.     'amount':amount,
    ! G7 _6 _) X( d
  40.     })
    % g; ^! d" [- _7 M) f1 m: R- e) T- {( A
  41.     returnself.last_block['index']+1
    & m6 U# \8 r+ A1 \/ Z' Z
  42.     @property
    ! T* i" g# h0 a) m
  43.     deflast_block(self):
    6 ~1 i/ [. \& Y. N  G4 c" G
  44.     returnself.chain[-1]
    % _& Z6 i0 n; ?6 ]( o
  45.     @staticmethod8 D: {5 T9 ?! F( e4 j, p
  46.     defhash(block):5 x' _2 G" m- s1 ^  e4 H
  47.     """
    0 T" p& [, U0 X$ J
  48.     生成块的SHA-256hash值
      ~( z1 F2 H' z& R  z, [2 Y* U
  49.     :paramblock:Block
    . |( Z1 U/ }( {3 y; U: l
  50.     :return:& P) K! @1 s! ~: w, n/ Z
  51.     """
复制代码

2 s4 C' x$ x4 J" L# N. q    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
2 ^# j- c3 p: L! v- i' I" h6 F    block_string=json.dumps(block,sort_keys=True).encode()% J  I7 o1 f$ z- K1 A; s
    returnhashlib.sha256(block_string).hexdigest()9 z$ u2 b7 q& u4 F6 o1 |
    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。# c* \% C. e! f" t/ E
    理解工作量证明
4 G  \) O4 l- y    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
* o2 V8 ~/ d+ n! G+ N    为了方便理解,我们举个例子:
* P; Y$ E* @/ P2 r    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
4 R- ~' G  F$ y, ]* F8 \( x7 Y
  1.   fromhashlibimportsha256" i- n5 K: k/ O
  2.     x=5% w9 q  q/ C! M, T
  3.     y=0#y未知8 |1 l; x0 ~- D
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":: P6 S5 ?: L" q$ W) V
  5.     y+=19 J, X& T/ R6 O) D
  6.     print(f'Thesolutionisy={y}')
复制代码

: M( A; j6 J) r& p5 z9 F    结果y=21,因为:
( L3 z2 [; t) I$ \1 u! v( W    hash(5*21)=1253e9373e...5e3600155e860
0 E& R0 ~3 n$ T& I; i$ ~5 E0 q' h8 \    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
4 R- h  T5 F5 I" F9 n; y1 h( e    当然,在网络上非常容易验证这个结果。$ R0 ~7 l% y& v8 Y4 I( N
    实现工作量证明% g2 r0 `( ]+ U( S5 H
    让我们来实现一个相似PoW算法,规则是:
, m! h+ u  k8 n' s! d+ k3 m; V2 _: p    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。, S& C# p* }9 N3 t; x
  
  1. importhashlib
    ! `& s) L( \! V2 R
  2.     importjson
    + l! E  o4 |0 z6 ]: r* Y3 C5 g3 T9 i
  3.     fromtimeimporttime
    $ @7 h: Y( J/ E
  4.     fromuuidimportuuid48 h1 A- g- K( Y% O! }
  5.     classBlockchain(object):
    1 o9 }% T+ ]& B6 B9 }! J
  6.     ...8 e0 J( G% @6 Y$ }$ L& k% n
  7.     defproof_of_work(self,last_proof):, n+ N+ E& M5 E& z6 A
  8.     """
    . D" [. e# L2 k  r2 D
  9.     简单的工作量证明:
    ; `; }/ S5 r, Z0 o( g7 p
  10.     -查找一个p'使得hash(pp')以4个0开头
    7 E% n+ o4 d; x  a$ M
  11.     -p是上一个块的证明,p'是当前的证明
    5 G4 u) V* H' m! L8 T
  12.     :paramlast_proof:3 V! I0 B/ F' k  ?5 q' \! x1 f
  13.     :return:
    # {+ C& W% d+ I4 L% T
  14.     """
    : y/ F3 k  f" b+ _$ c
  15.     proof=0
    5 P$ l: l% V1 s3 u3 S+ P9 t
  16.     whileself.valid_proof(last_proof,proof)isFalse:
    $ r2 ^0 O' t" Y
  17.     proof+=1
    " n8 _, m0 t: h/ `' Z/ F4 }' c
  18.     returnproof
      ?$ `" e, Y/ ?4 J8 n* Y) D
  19.     @staticmethod" G# q: t1 i; g' l
  20.     defvalid_proof(last_proof,proof):
    $ {5 j: ]% m7 c/ t* o
  21.     """& w, x3 Z0 F6 Y, ~( L
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?
    1 @  d& `9 R! R  @: `- G
  23.     :paramlast_proof:PreviousProof4 E- L) Q0 ]2 @* n
  24.     :paramproof:CurrentProof% t: s+ @  R9 f* M  y: s
  25.     :return:Trueifcorrect,Falseifnot.. [" X; g) Z9 P: J) v
  26.     """4 i% @$ v8 r2 E' Z4 o7 e# D
  27.     guess=f'{last_proof}{proof}'.encode(): _7 q, n  E6 ]# a0 [5 e
  28.     guess_hash=hashlib.sha256(guess).hexdigest()0 n- Y" {  d5 ~! Q
  29.     returnguess_hash[:4]=="0000"" K( x5 o1 V- Q$ E! g, V
  30.     ```
复制代码
$ z- ]! N# F) q: G/ e% S
    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。
# t( E" V4 y9 ~    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
) O9 j2 K$ a/ R" |    ##二、BlockChain作为API接口7 ]. T* L' s  r  Q+ H
    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
$ l; p- y& U7 x" o3 B' @2 [/ p! [& v' G    我们将创建三个接口:
' _5 m# \7 T% z   
  1. ```/transactions/new```创建一个交易并添加到区块) i0 \6 ^/ w) a% ]: H" O! x
  2.     ```/mine```告诉服务器去挖掘新的区块
    7 G) Q4 Q  Q" }. R
  3.     ```/chain```返回整个区块链+ f* M. @+ A+ \1 U, z
  4.     ###创建节点
    0 D9 Q- ?0 g+ L) z7 j0 n
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
    3 u, H$ A- q7 B0 p+ S7 |1 n
  6.     importhashlib
    ) [( q9 p9 K9 J( v
  7.     importjson
    2 j, ?7 v, G2 L1 ?7 m4 z% d, x" E
  8.     fromtextwrapimportdedent
    0 I5 v0 j/ f  l* C$ V' n8 Y
  9.     fromtimeimporttime+ w( k2 q5 v3 R0 Q
  10.     fromuuidimportuuid4
    " }2 |+ \! b! R* _
  11.     fromflaskimportFlask( y2 \8 D, O) X$ x/ ^' m# u
  12.     classBlockchain(object):4 b) V$ H3 K0 h" ]; w, S
  13.     …
    ; H& j3 R  E$ ~$ `# x! a
  14.     InstantiateourNode4 e1 [+ z) J+ p  y# Y
  15.     app=Flask(name)
    2 ]8 T! l: _2 @- s3 `( F
  16.     Generateagloballyuniqueaddressforthisnode
    * z, [  g& k' d% M5 X7 _
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)/ r/ _4 z, j# n, T5 }" t$ S6 C9 R
  18.     InstantiatetheBlockchain2 c5 |# r' u0 E, c) @
  19.     blockchain=Blockchain()  W8 s0 Z. A2 j3 J% B
  20.     @app.route(’/mine’,methods=[‘GET’]). u7 q5 a* B8 x0 Q8 t4 W4 P
  21.     defmine():
    + }$ x) s3 h( R* J5 h. J
  22.     return“We’llmineanewBlock”2 w$ W, Y) I' N; _
  23.     @app.route(’/transactions/new’,methods=[‘POST’])1 k# }5 z/ p( \3 m
  24.     defnew_transaction():0 z- V, g% G, `" c; n
  25.     return“We’lladdanewtransaction”
    # i3 {5 y5 [* z% b
  26.     @app.route(’/chain’,methods=[‘GET’])
    5 s6 Y+ C$ }) H
  27.     deffull_chain():
    , v, k5 ?+ l+ k/ Y$ r) a: {, W
  28.     response={& F, q% ?9 C5 D; W0 ?- W3 I
  29.     ‘chain’:blockchain.chain,# P- U9 i5 m, `6 v9 w( c- M
  30.     ‘length’:len(blockchain.chain)," n) F6 i# S7 `) x. |/ h
  31.     }
    0 q) x+ ~7 f3 Q
  32.     returnjsonify(response),200+ N1 ~  [$ l: }  b/ G  P
  33.     ifname==‘main’:
    ; e( d' N3 L: W5 @' v1 y* v: \' u
  34.     app.run(host=‘0.0.0.0’,port=5000)
    % s/ Q9 g& t8 l5 H" j# j
  35.     ```4 V/ c4 t2 |$ `# t" |' V
  36.     简单的说明一下以上代码:3 ~" ^: G+ z  t& }7 E
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。
    % K: T" p1 N: g1 Q" A* E: N7 s% i
  38.     第18行:为节点创建一个随机的名字。/ _4 }( V: W+ x( @( ^
  39.     第21行:实例Blockchain类。& \1 ~2 m# {! |1 S
  40.     第24–26行:创建/mineGET接口。6 L) _) U  }$ D
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
    ( {* p: g8 y9 v) ~5 i& K2 }
  42.     第32–38行:创建/chain接口,返回整个区块链。4 }) H3 S6 w1 C/ {1 f8 X
  43.     第40–41行:服务运行在端口5000上。
    ( y- X5 G2 s  r! G+ `$ n7 `/ D
  44.     发送交易
    ' q& {$ l; a( Y) n4 e" \* M; y
  45.     发送到节点的交易数据结构如下:
    8 l* r! n( J) J
  46.     {
    0 V1 M, j% O6 w+ M$ A& Q/ v
  47.     "sender":"myaddress",
    1 D, X2 X% k! P2 c' I/ V: P
  48.     "recipient":"someoneelse'saddress",# ~- x6 M* ]7 t0 C% k9 ?
  49.     "amount":5* @8 {0 p+ \. l/ N1 J( I
  50.     }5 E- }6 Y4 {0 P* A% w# A4 h
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:
    + h5 R; U0 j1 C, f  q& O
  52.     importhashlib6 `, `7 V; s# f6 ~! j% T2 J4 R
  53.     importjson
    % `8 J% {7 {; b
  54.     fromtextwrapimportdedent
    4 t* L4 O: F* q6 ?% ~0 p9 M
  55.     fromtimeimporttime
    ! W$ q. I0 Y! c/ B! Y% y$ V/ q
  56.     fromuuidimportuuid41 q( b+ K! H& i! _4 A6 a
  57.     fromflaskimportFlask,jsonify,request
    % d7 i! Y# a6 a7 v6 M; N, I
  58.     ...
    : ?1 \- f6 _; R% N( J5 a; Y
  59.     @app.route('/transactions/new',methods=['POST'])
    + q1 A7 }* u; m: Y
  60.     defnew_transaction():
    8 B9 ]9 \: `+ h7 x
  61.     values=request.get_json()0 G/ r" V( }8 X( i( Z
  62.     #CheckthattherequiredfieldsareinthePOST'eddata: J  C! W  @+ {1 j; X; C0 F
  63.     required=['sender','recipient','amount']
    " z! N* U1 g3 d, h* R
  64.     ifnotall(kinvaluesforkinrequired):
    # U' H4 W! s, a$ r8 g
  65.     return'Missingvalues',400
    , H& F" I4 a8 b- ~7 i
  66.     #CreateanewTransaction- J7 R. E( y  F9 k
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
    $ d9 l. b2 }/ m* L! K
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}: e9 G$ F9 ^$ ~1 b! Z3 ?
  69.     returnjsonify(response),201
    5 W% M- w7 x1 C' ?3 p: \" I
  70.     ```
    6 c$ w; B! b8 ?& \5 b
  71.     -创建交易的方法-9 W: Z% U. B% u) }$ Z6 z
  72.     ###挖矿* `: `( _. O$ x2 K  [
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:: ^$ {" ~9 X# e7 ~; Y& g9 r8 o
  74.     计算工作量证明PoW& _& v; y# D4 C6 g
  75.     通过新增一个交易授予矿工(自己)一个币
    ; R  c( p/ _* X: w
  76.     构造新区块并将其添加到链中8 \9 ^+ i* y2 B( u% F: E
  77.     importhashlib' p3 x2 r4 g" O0 g8 z7 {
  78.     importjson7 k: ?6 p: _6 X- j
  79.     fromtimeimporttime& n9 b" D7 P% }3 T/ I: z% G3 z7 M
  80.     fromuuidimportuuid4
    - x( F% ~. @, t8 \! {6 U
  81.     fromflaskimportFlask,jsonify,request
    , T/ V6 u) J, U# j
  82.     …/ D; @5 T% P: Y$ E4 Q/ |/ {, ~
  83.     @app.route(’/mine’,methods=[‘GET’])
    ! H7 Z3 t, k4 A; @4 ]; W4 f
  84.     defmine():
    7 ]7 D9 B5 j( u  X6 ^. U
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…+ {% K) l4 T0 J5 n1 n* |
  86.     last_block=blockchain.last_block6 U8 N' z8 X$ ^( h' L5 z) A
  87.     last_proof=last_block[‘proof’]$ y/ ]1 \$ \- T! \
  88.     proof=blockchain.proof_of_work(last_proof)
    - p8 F- ?; L, @6 P# P! i
  89.     #给工作量证明的节点提供奖励.
    1 S: {6 y5 k5 A% z- b
  90.     #发送者为"0"表明是新挖出的币.+ l5 V, l: }/ Q/ h! k. E7 y
  91.     blockchain.new_transaction(
    ! [2 X* V, p2 X- j; ^6 k9 H
  92.     sender="0",
    6 ]2 k1 f3 g& [( H  q" O, c
  93.     recipient=node_identifier,9 i( ~  _: L8 w* Q
  94.     amount=1,& `' l' P  i9 C' I9 |
  95.     )
    6 _( u0 E3 |- e& ]5 _* q7 z5 w2 T
  96.     #ForgethenewBlockbyaddingittothechain
    / H$ O; m  F# w, S
  97.     previous_hash=blockchain.hash(last_block)3 [6 `5 S0 a+ G: E6 w$ A
  98.     block=blockchain.new_block(proof,previous_hash)
    " e) R/ P2 w( e# f
  99.     response={! s7 m& O: G2 ~
  100.     'message':"NewBlockForged",
    % J6 Z( o' K; B% O; u: c2 z
  101.     'index':block['index'],
    1 o0 J1 ?, R: z* k& f; }
  102.     'transactions':block['transactions'],* p, A; L) I% ^& I
  103.     'proof':block['proof'],
    2 L* }+ J3 T7 A0 k' {
  104.     'previous_hash':block['previous_hash'],) d, x; }6 z+ z
  105.     }
    $ n/ m5 v5 S4 V
  106.     returnjsonify(response),200
    # C3 \( Y& o3 K3 Y/ L* q
  107.     ```
复制代码

! z. E. n# b0 U    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
3 x3 \) Q1 v) }    三、运行区块链: x; g" ]9 r( d
    你可以使用cURL或Postman去和API进行交互
$ g1 ^6 i  C# t' u( `8 I3 `+ a* c    启动server:
2 M' @) L5 f8 T# E    $pythonblockchain.py
% C: F; s1 n( Y- j  [    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
9 N% E# v% i' ]6 s    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
4 T' {: m0 M. j' E7 U& O    -使用Postman以创建一个GET请求-$ ^& y% _/ p8 e2 Q
    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:' T. j+ I+ H. B" p
    -使用Postman以创建一个POST请求-$ n& Y2 p: L# r, ~  ~
    如果不是使用Postman,则用一下的cURL语句也是一样的:; w7 `/ I$ z( p3 {! z
    $curl-XPOST-H"Content-Type:application/json"-d'{
/ J; v7 K8 l& [- X' ?7 E9 O  j' W7 ]    "sender":"d4ee26eee15148ee92c6cd394edd974e",4 E3 H" X& j: [" W
    "recipient":"someone-other-address",
5 h" o" t" c0 e3 `) z! W# S    "amount":5: ?; ~# ^0 o+ I, c) b0 b
    }'"http://localhost:5000/transactions/new"/ a5 o( G% I3 t) s
    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
& [8 K  I6 w3 I  j( d3 w" R   
  1. {
    # s% |- K6 D# i# Z; P: e* w* y+ H
  2.     "chain":[/ z* S; N) W5 M% U
  3.     {
    , o& t4 X7 E3 L+ F) W
  4.     "index":1,
    4 A. b* G2 N" K! Q
  5.     "previous_hash":1,
    9 |- A- \% I0 R) {$ e, U8 ^
  6.     "proof":100,# Y* m6 a  F" V4 c) G  l! r
  7.     "timestamp":1506280650.770839,! c3 z* ]! F; U! @/ ^
  8.     "transactions":[]
    4 G2 w% w) o1 U4 U8 }9 G3 f
  9.     },- a: x2 \7 P2 e( P) J/ q8 @
  10.     {5 y6 C4 ]! h) X% A% ^3 m% a( Z
  11.     "index":2,
    ! H" c/ {% [) z  }8 u* j
  12.     "previous_hash":"c099bc...bfb7",
    0 @8 O. N) t, q9 C) n1 i- J
  13.     "proof":35293,
    9 W- K2 f0 @6 D+ j$ {' ^6 G  t( q2 E
  14.     "timestamp":1506280664.717925,
    % i. A" r- s, c. X, A* f# W6 q
  15.     "transactions":[% U& l$ E* }* F4 V1 k# a
  16.     {
    7 T5 a: l$ w5 r' R5 M
  17.     "amount":1,
    3 {6 ^1 Y- N( l* V' \: j, u4 A
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",$ V9 H% T2 V, ~( E: J2 \5 w+ j# y6 i
  19.     "sender":"0"
    5 c1 m+ d8 I  c% T7 J+ {
  20.     }; A1 F: K" u: R7 F
  21.     ]
    0 w: b6 y9 s  s9 _9 M
  22.     },: m5 o; K- R/ o' C! J( k# C
  23.     {+ \& X( Z2 Y; p/ y) ?; ~; q
  24.     "index":3,
    0 `* F' |0 w( i. f
  25.     "previous_hash":"eff91a...10f2",3 B1 L& k/ O7 k" ^. w
  26.     "proof":35089,/ O6 _1 Q& t2 q, h# `
  27.     "timestamp":1506280666.1086972,
    4 P4 r* D0 ^3 u* v8 l' L
  28.     "transactions":[& ]7 |/ O5 C5 c, \
  29.     {
    % Y: K5 f6 w' W
  30.     "amount":1,
    3 s! v8 X: l2 [, v6 r( x
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    ( q3 e8 a( R) S) |- `2 N
  32.     "sender":"0"
    9 l6 h0 O/ r/ J: _# G3 a
  33.     }$ j4 v# v, W6 z" z: H5 r$ ?+ ]
  34.     ]
    , N; k9 B" z' f+ @/ P. _7 C
  35.     }
    ( ~) ?* l% V. I/ X0 N
  36.     ],
    . q& O3 J& s4 B! C, \3 w4 _' ^
  37.     "length":3, v/ P+ n/ _" O6 f, V! v
  38.     }
复制代码
- S! A. s  J% s4 V  S: p
    四、一致性(共识)
; F& `8 ^/ y6 x4 O! w    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
6 K) ?* c. g) w# j! R. C' S    注册节点
3 y/ `$ h$ }7 V2 \; N    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:. ?6 _. |0 K: I- ~3 ~7 K# A" e
    /nodes/register接收URL形式的新节点列表% @4 C6 h1 B0 ?& L, l4 j/ r
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
' w! o8 m# F6 ?6 _! ~    我们修改下Blockchain的init函数并提供一个注册节点方法:
. x' b' [" A, j/ n) V  
  1. ...5 i; F$ O$ N( s( O
  2.     fromurllib.parseimporturlparse* V1 U% c) f0 M$ C2 F# U% I
  3.     ...0 a9 i5 t$ O, ?% O
  4.     classBlockchain(object):& ^: u3 K7 }. F* {: o5 k: T2 d( Y; e( A
  5.     def__init__(self):
    * Q3 q$ h/ E* [; |  ]' x4 f) A( f
  6.     ...
    + d* K- f, R; v. Y* k
  7.     self.nodes=set()8 h  v8 u& b1 s  t: r
  8.     ...4 V* ?% L! |2 k  G- K2 e% d5 k$ h
  9.     defregister_node(self,address):/ N- O3 j5 O; i- \! O
  10.     """
    - o% s. L! i0 N* l6 g5 N- k
  11.     Addanewnodetothelistofnodes
    , o6 b( J1 E! Q& @% T* ~* u
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'- a# g% G" J1 l! c4 Y; f' X* S
  13.     :return:None: y" L* x' ~- Q) v& r. U$ e" k1 X
  14.     """
    5 ?; [" o! @4 g
  15.     parsed_url=urlparse(address)2 m4 X' C  x% m1 h
  16.     self.nodes.add(parsed_url.netloc)+ _) N  u& d. E$ J7 {
  17.     ```/ _' @5 J* w+ J
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。
    ( B  o( B' t- X( H5 s4 S9 T
  19.     ###实现共识算法3 C& F: d. o0 k/ M
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
    : P, F' I' `* [4 Q/ d. Y* N
  21.     …
    . o) w8 o) b2 B  o$ c% ?( Y% S
  22.     importrequests
    1 R  U$ |# I) f
  23.     classBlockchain(object)6 G0 B! \; S$ o* i  e
  24.     …5 {. t% F/ ]. t' I) l9 k
  25.     defvalid_chain(self,chain):* I! M$ U8 J) Y! H
  26.     """
    4 }9 A6 p3 ~' H; ^4 Q. \! S
  27.     Determineifagivenblockchainisvalid
    " @  Q' v% h/ _" }2 V9 t& ^
  28.     :paramchain:Ablockchain! N" N" D. W) ~0 @) u! B
  29.     :return:Trueifvalid,Falseifnot; R. O% f- j. |, I
  30.     """
    3 u! c' ]9 Z8 F: U1 H+ q, T% _
  31.     last_block=chain[0]9 V$ `7 k' M1 `6 n" q) \
  32.     current_index=17 i0 {' ?9 G- Z
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):2 g! S7 _, _6 C" i5 N
  34.     max_length=length  o8 F) p6 o- H+ d: A
  35.     new_chain=chain
    8 F2 s6 F. `' z/ w0 `0 V
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours5 @; g* T" A! y: U* s" n
  37.     ifnew_chain:$ x- h% [* M, ~# B
  38.     self.chain=new_chain. z! I! n4 A+ p5 }6 l
  39.     returnTrue
    * C$ U% v& W4 O! m: a2 s5 V
  40.     returnFalse
    ) }4 L$ p- h  ^; x9 F4 \  c6 W
  41.     ```
复制代码

, S) ^7 n4 {, d, Y3 m$ |$ s    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。
. Y  p0 i8 _  U# Y    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。: J5 u4 k3 U4 J; E
    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。( W0 Z; Q4 a+ o6 n
   
  1. @app.route('/nodes/register',methods=['POST'])
    . t' b6 K" X* ^) w" D
  2.     defregister_nodes():& _  x) T! e3 l
  3.     values=request.get_json()
    4 @- [' F3 m1 A: _  ~( P
  4.     nodes=values.get('nodes')
    + x  G2 r) D8 o: v. |, E/ @# K' Y
  5.     ifnodesisNone:5 j+ q' R5 R9 y2 e; d$ t$ v
  6.     return"Error:Pleasesupplyavalidlistofnodes",400
    , @: f4 o& ^' j- [/ h9 x
  7.     fornodeinnodes:* S7 e) s! E6 r
  8.     blockchain.register_node(node)  G$ R8 x0 B: b  g2 U0 @  @
  9.     response={" w$ \+ z& W1 V+ S, Q1 S6 C3 j
  10.     'message':'Newnodeshavebeenadded',
    7 y  B3 n* J1 `1 Q
  11.     'total_nodes':list(blockchain.nodes),
    ( E/ K1 d3 V, _8 u9 R% c- x+ v
  12.     }0 L% c  i% Z) {1 T! z
  13.     returnjsonify(response),201
    3 R4 {' v( e" \" ~2 y: f
  14.     @app.route('/nodes/resolve',methods=['GET'])8 y: h7 B$ l- V$ r" Q
  15.     defconsensus():
    ; I; @  P0 N. @0 J4 l2 T( O( T
  16.     replaced=blockchain.resolve_conflicts()
    1 ~" U; W+ \, x9 z5 f
  17.     ifreplaced:5 x$ G( d- I  o
  18.     response={
    , m+ r/ q* ^2 T2 \# O0 j
  19.     'message':'Ourchainwasreplaced',
    . F$ S# p7 s. F% H2 ]& n
  20.     'new_chain':blockchain.chain
    2 V2 x9 x# t: Z0 c* b; d, t- Q! z. v2 h
  21.     }
    2 t; q  x" K/ o4 b" e
  22.     else:
    3 Y; c1 \6 w6 ]2 t9 ^, R* X8 S$ E* E4 W
  23.     response={5 _& `, G* q" o. ~# k% }+ p
  24.     'message':'Ourchainisauthoritative',, Z; A. |0 S! n& R
  25.     'chain':blockchain.chain
    0 a# ]" `) c7 R" F
  26.     }% O7 j+ k8 |0 Q9 O) J4 c
  27.     returnjsonify(response),200% ]2 j: e+ s: N9 P6 L" Z$ L
  28.     ```
复制代码

8 K, ~  z3 l/ z& K+ g& j    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。4 w+ K, z$ l, M8 |9 P& {3 j6 l

& ~& F2 U. |7 c4 U+ D- b* i    -注册一个新的节点-
# {6 }; a* \2 a& ?* u: G/ ~0 W* i    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7