Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
455 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
0 k% S0 K0 {+ f" P    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
! f4 k7 N. L+ E' a! D* f: x    准备工作" f  W. F* q5 r/ U. C* B% I
    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。: X& g+ C+ u7 m: F; v! [7 M! {
    如果你不知道哈希值是什么,这里有一个解释。! S) B( r8 O# _( X& a# j
    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
6 a' V. _7 B; j! H4 h    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
; o1 a5 `7 U4 I6 I    pipinstallFlask==0.12.2requests==2.18.4
8 B, q- \+ O* v. V: G8 n    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。! ^$ n0 ^2 b! ?/ p* N: g, r
    一、开始创建BlockChain% o) K9 B8 P( F6 K. c
    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。- g3 {7 H% `5 b# q- V
    BlockChain类, @, o$ F; _. {0 P' ?8 S
    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
1 P" [  w9 b* Q! ~: A# Y    以下是BlockChain类的框架:( w  p3 X$ Z4 ]2 q2 e2 ~
  
  1. classBlockchain(object):- ^7 {1 l+ k) N! V2 m% m* b
  2.     def__init__(self):
    0 n2 ^+ g# Y  l( E: c: _1 b9 x& L
  3.     self.chain=[]) |; h1 F7 p1 c& `" f- l
  4.     self.current_transactions=[]4 {  k! R2 B0 I& n
  5.     defnew_block(self):
    9 u! w5 m" ]7 M* [4 o6 P' P- e& b
  6.     #CreatesanewBlockandaddsittothechain
    & v' s$ x! m4 `: t. b( K5 K, w0 _  w* \
  7.     pass
    4 _8 V/ s& j1 A; D- f- ?) U
  8.     defnew_transaction(self):( K$ y' t9 ]2 A" a1 }  j
  9.     #Addsanewtransactiontothelistoftransactions$ f7 E$ F! l+ ^1 ^3 K
  10.     pass
    9 q2 i$ h( b/ D: T
  11.     @staticmethod
    , I* K4 l* u% ]* e7 E7 ?: l5 a3 V
  12.     defhash(block):& }5 @3 s0 [3 T4 ]0 u
  13.     #HashesaBlock1 Q! O9 o" W6 G2 [2 [9 r" m9 i
  14.     pass, l5 S; o6 t! [
  15.     @property
      ?/ Q& a9 D1 _
  16.     deflast_block(self):) A5 |! k2 G* _; B
  17.     #ReturnsthelastBlockinthechain9 p. N/ k! ^) t; f& Z' ^& `6 U, P
  18.     pass
复制代码

( O0 Z/ K5 Q3 ^6 b; [9 [    -我们的区块链类的蓝图-" Q. K7 ^* L/ l+ I5 ~6 r
    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
# q! L, X( k! w3 L3 L    块结构2 o9 w$ _1 L" g; d1 C
    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。/ \- \7 U& j, m1 F
    以下是一个区块结构:
! d9 D  ?6 j* J. K. d8 J: u+ ?   
  1. block={
    : O7 o# _) c4 z, w  X
  2.     'index':1,
      x6 J6 ^8 r  ~5 H# r3 d& x8 [- h
  3.     'timestamp':1506057125.900785,1 m/ ?9 P/ `1 Y3 i
  4.     'transactions':[) L- W8 C. E" m! I  O4 v9 N
  5.     {
    ) T4 j! p/ F3 p: {' w0 e
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    - \9 _) k5 j/ T! F; _
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
    5 S- e( j3 Y# f7 g
  8.     'amount':5,+ s) o" |7 f% h
  9.     }# t, c0 B: k: C! `4 ^
  10.     ],
    4 i, k& b, i% F7 K; _
  11.     'proof':324984774000,
    * ]% x/ C9 C+ r( R$ [- g
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"3 X  w; e" ]: u/ {) [' L+ q3 S7 x
  13.     }
复制代码
, A. C. O* ]. i0 U- n
    -链上一个区块的例子-( s6 G, A0 J9 m5 T; w% ?
    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
( @) v! u8 H4 Z$ ]8 n8 q/ F* d1 Y    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
# B! a1 @) R  d    加入交易+ c0 ?6 c6 e0 U) ]' }
    接下来我们需要添加一个交易,来完善下new_transaction()方法。
& F' J$ a  s2 e( c5 H8 G  
  1. classBlockchain(object):
    7 m, H1 s5 O! U! ~+ E% f' ~) `3 k6 [
  2.     ...% I9 L6 P# c6 `8 H$ g, D# C
  3.     defnew_transaction(self,sender,recipient,amount):
    ! M8 X' D0 p  q# y  y8 s& D& G! k
  4.     """
      h* ]( }, ]2 `
  5.     生成新交易信息,信息将加入到下一个待挖的区块中
    2 q# P  V' m  j4 p1 H7 S& M( K
  6.     :paramsender:AddressoftheSender  _" X6 q  |8 h$ r6 Q' k. U( T& s& h
  7.     :paramrecipient:AddressoftheRecipient. h8 [5 B- w; z  V+ _
  8.     :paramamount:Amount
    - N* [- Z) n6 \
  9.     :return:TheindexoftheBlockthatwillholdthistransaction
    ) x2 O* U' I9 A8 N/ M0 s2 K
  10.     """+ I/ R4 K1 a; d
  11.     self.current_transactions.append({
    . {, T+ b  x( |+ }8 e; }
  12.     'sender':sender,8 \0 p9 F" c: Z( x& ]
  13.     'recipient':recipient,
    ) v" D, \0 n  ^5 n# N7 E1 o8 B
  14.     'amount':amount,, ~, f2 D$ @5 j9 w; b* O
  15.     })- @  g4 L8 c, r- z6 c1 B9 w
  16.     returnself.last_block['index']+1
复制代码

: D3 \' I" K- _: i& N    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。6 B' U" \/ D3 t+ S
    创建区块8 Z2 R$ ~/ O% H  c1 f
    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。' N* X; M) D" h5 p
    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。& |% j1 Q) Q1 j# \
    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
3 D. E' u2 V; h. j0 q5 b' j  
  1. importhashlib! g2 g, f2 R. B; s! c5 Y
  2.     importjson  a' [9 I9 n$ j+ E0 B: A
  3.     fromtimeimporttime
    - A2 T! ]$ A+ m4 H7 K; l; n9 o
  4.     classBlockchain(object):& W: A2 m6 C6 M' I% g
  5.     def__init__(self):
      A8 r3 X: d4 X
  6.     self.current_transactions=[]" J$ }( E# D2 G
  7.     self.chain=[]
    7 N7 f1 b$ E7 }
  8.     #Createthegenesisblock
    2 C. i! V9 h( P) d! B
  9.     self.new_block(previous_hash=1,proof=100): v' I( U; r1 G# W, E2 p
  10.     defnew_block(self,proof,previous_hash=None):% m/ B& ^7 O- f8 A8 W
  11.     """
    ! b- x2 W) T* H3 O) @& c
  12.     生成新块
    & {7 Z  a. l" s+ {* J
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm! }1 e  k) {8 F8 E
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock/ B. l" g. ~4 [
  15.     :return:NewBlock1 [* c9 N" n% G# E" Q, q  |
  16.     """' F+ y, X& v, b5 m2 D& M& s& O' F
  17.     block={
    ; d$ m: K1 g- i, {" P$ ?
  18.     'index':len(self.chain)+1,
    1 C/ c. n0 ?3 R( j
  19.     'timestamp':time(),- a5 n6 C! S2 M0 z6 I. r
  20.     'transactions':self.current_transactions,6 F9 A: D1 {3 @3 ^. n! l. y; }$ j: T. ^1 d
  21.     'proof':proof,
    . R$ c+ \5 L* h
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),- q  t  X& W7 f7 o: J1 p6 }4 `
  23.     }
    & V6 B8 `* N% p9 I
  24.     #Resetthecurrentlistoftransactions
    . g8 C8 Z3 r1 H% C
  25.     self.current_transactions=[]
    $ V( _+ L0 L: \. ]
  26.     self.chain.append(block); x) h! i* }! x& \
  27.     returnblock9 C* O" F0 _" y! r
  28.     defnew_transaction(self,sender,recipient,amount):
    . w' Q7 e6 l% |' b- z% s  p$ F2 a
  29.     """/ y' h0 V+ a( j4 ^
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中
    + I+ \& _& K* v+ s
  31.     :paramsender:AddressoftheSender
    6 z$ f7 h: D4 Q$ Y- T# K
  32.     :paramrecipient:AddressoftheRecipient
    / N& b1 L7 i6 T/ d
  33.     :paramamount:Amount
    ( S( h3 B& H! _; }& Y; b
  34.     :return:TheindexoftheBlockthatwillholdthistransaction0 {0 ?. d* ~, v, e3 I5 j
  35.     """
    # C1 l2 e4 D- I4 H, E
  36.     self.current_transactions.append({% {! b7 v* m  Y! w% v
  37.     'sender':sender,3 _6 f. r9 J9 _  H( C
  38.     'recipient':recipient,* `8 g- c* Z# I' b1 b5 n& @
  39.     'amount':amount,
    2 h/ [; J9 r4 R/ M# V4 O( n9 D
  40.     })9 {: _( r2 A3 J* w% F9 K' @
  41.     returnself.last_block['index']+1; W0 n  D4 @+ V' G3 t5 `
  42.     @property
      k) H; S8 j4 H. S4 w% J( t6 Q
  43.     deflast_block(self):
    * f1 l1 M; j6 C/ f8 b) n* L
  44.     returnself.chain[-1]$ g/ U5 Y- y8 G. G# t# c/ F- Y
  45.     @staticmethod
    , M0 D7 h0 m6 ~* G
  46.     defhash(block):
    ' G- E4 Z/ Z. p$ p  J4 e  G- |
  47.     """+ F, J; _0 T  s4 U
  48.     生成块的SHA-256hash值
    3 @4 w2 Z- o3 ?/ c6 o
  49.     :paramblock:Block8 m$ }" J/ _7 c; g9 ]
  50.     :return:+ Q) t" {. z! h! P
  51.     """
复制代码

% W* L2 V' j/ l6 h& [1 N    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
" p, u1 b6 j6 {3 s+ E: M    block_string=json.dumps(block,sort_keys=True).encode()
" i. @  [/ |) w: A: A    returnhashlib.sha256(block_string).hexdigest()
, o2 U2 T* M& L% W0 Y  a$ ]( A    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。$ P6 |" V" S) r3 x
    理解工作量证明3 s7 D" D, ?' p
    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。( ]7 J6 L8 ^) g1 z" Z
    为了方便理解,我们举个例子:6 ]; I/ f1 `+ S) i1 A6 `2 J
    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:) c: L5 s& W5 d0 \/ ]  Z) _
  1.   fromhashlibimportsha256
    / I/ p+ [  \. ]6 C; a, u# f
  2.     x=5: a( ?: |& L/ U
  3.     y=0#y未知
    3 U8 [$ D" c* e
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":2 L; Z) K3 a8 l+ {" C! s9 b) J
  5.     y+=1# L2 L% z$ |( L. ]6 [
  6.     print(f'Thesolutionisy={y}')
复制代码
; L+ J  t. B$ O6 w6 R7 i+ ]7 k
    结果y=21,因为:% I6 j! |5 J; _8 c6 o
    hash(5*21)=1253e9373e...5e3600155e860
8 T$ S0 f6 L6 t- S) o    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
6 D, u: w8 S/ i) i* D& e    当然,在网络上非常容易验证这个结果。# ?5 ]1 [  G( H6 q& u
    实现工作量证明
) E0 L2 A+ v" I4 D' w; M    让我们来实现一个相似PoW算法,规则是:' n6 Z8 _) H' m7 S5 a8 w
    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
5 {* |- p) ^) F) h4 }! e, [  
  1. importhashlib
    3 g; q# K# O: C, Q7 f8 W- C) ~0 l
  2.     importjson, g0 L3 |9 L! B) t, A. w
  3.     fromtimeimporttime: ]( j0 Z7 A5 P: c
  4.     fromuuidimportuuid4
    , q, ?5 h8 o$ z4 X$ K  m
  5.     classBlockchain(object):
    ! [$ w3 [8 a0 j% N: U% T
  6.     ...
    6 a$ x0 I5 m0 y+ u2 c& M- Y( u
  7.     defproof_of_work(self,last_proof):9 n1 y- F7 i) @! {4 f! Y
  8.     """8 J- d" ^3 K- s* r
  9.     简单的工作量证明:$ i7 C( p* {/ ]8 l) T3 `$ P
  10.     -查找一个p'使得hash(pp')以4个0开头# Z0 h2 J$ ?. W/ I* ]: t# D, [" h
  11.     -p是上一个块的证明,p'是当前的证明3 B  q) m# Z& U# h& a8 g
  12.     :paramlast_proof:
    " }7 D! J# ]# K: z
  13.     :return:
    ( O! v/ Y+ w8 |8 X* C
  14.     """
    " C( I8 \" v7 r5 E! @
  15.     proof=09 H+ Q# f' X: O" t; ?7 m7 k% p
  16.     whileself.valid_proof(last_proof,proof)isFalse:
    * A0 e$ k, W, h+ \, B5 x
  17.     proof+=1- `7 \0 i( U2 s8 U) M
  18.     returnproof
    5 G3 k, g) x( g5 l2 i
  19.     @staticmethod
    ! {5 b1 m; |( O% A+ x5 t7 ]
  20.     defvalid_proof(last_proof,proof):
    0 B4 `/ I; d+ W1 {4 T$ a) t$ `
  21.     """9 K9 J! H! _5 h  z! O* _1 k& O3 k
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?
    - \) i& ^* v" D
  23.     :paramlast_proof:PreviousProof
    6 u: p# D: @+ j4 n
  24.     :paramproof:CurrentProof
    3 u* U- P+ F+ m: C3 A" Z
  25.     :return:Trueifcorrect,Falseifnot.! x3 T1 a* b9 `$ Y1 {
  26.     """
    1 A# k; `+ g2 F/ S! K
  27.     guess=f'{last_proof}{proof}'.encode()" P& b  Q5 b, q6 X% F1 m: B
  28.     guess_hash=hashlib.sha256(guess).hexdigest()8 W# g6 s3 F- e) M3 j* ~3 N
  29.     returnguess_hash[:4]=="0000"4 `- M* |# y( A1 c/ S, J0 n
  30.     ```
复制代码

# N: }$ ?. K4 \7 E+ \) [3 ^5 f    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。1 C- [1 w5 f* G3 K" K; k* X
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
0 ]: `- P% P$ R' j    ##二、BlockChain作为API接口% a0 n, R2 ^8 Z% |+ g
    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。% h  I, T9 @; ~4 H; `1 H
    我们将创建三个接口:& i' [: C8 W9 c
   
  1. ```/transactions/new```创建一个交易并添加到区块- c% U+ I# H: l% q  K: m
  2.     ```/mine```告诉服务器去挖掘新的区块7 ~* Y$ e) G8 U% x% f3 v( j- ~  e
  3.     ```/chain```返回整个区块链" l9 T& O+ a( G/ l( s
  4.     ###创建节点* L. [1 h1 m, }& I( J) G2 o
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
    ! J- c( l+ b# }1 f3 w1 P
  6.     importhashlib
    1 U/ t, f9 `7 U$ d& I
  7.     importjson/ V) D5 Q( ?9 E
  8.     fromtextwrapimportdedent
    # h0 \. F$ j. i* c* I
  9.     fromtimeimporttime2 U) s" h3 \5 c" X- F
  10.     fromuuidimportuuid4
    4 d# K% t  |3 w7 Z7 g
  11.     fromflaskimportFlask
    $ y4 e. L) p) _! M9 P* H
  12.     classBlockchain(object):* l- f/ u9 X! J9 c) Z+ o% l
  13.     …2 M6 j6 u* `) H7 |7 N4 ]4 i
  14.     InstantiateourNode
    ' b9 H+ Y  Z( i+ B! s' R
  15.     app=Flask(name)
    3 }1 D! G) ~) c& E% {
  16.     Generateagloballyuniqueaddressforthisnode3 t1 V- P9 W5 S
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)
    & J0 j7 z7 H$ v7 k
  18.     InstantiatetheBlockchain
    0 M; c+ c) p3 B/ z/ ]4 [+ _2 N
  19.     blockchain=Blockchain()  n  e7 O, |/ ^/ w1 Y$ `5 @" B
  20.     @app.route(’/mine’,methods=[‘GET’])
    ; R* P9 L9 V5 t  O4 Z
  21.     defmine():
    8 U4 N  W  o7 @! S4 \8 q1 l: V
  22.     return“We’llmineanewBlock”
    9 K! @1 ]4 D- H; B  W! |
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    ! Z1 _. R) x% |* O& q
  24.     defnew_transaction():7 b: o1 l0 G# w
  25.     return“We’lladdanewtransaction”4 z# H- D- [# w# S8 D( K
  26.     @app.route(’/chain’,methods=[‘GET’])
    7 Z. l* n9 a, o: p& l( `& g
  27.     deffull_chain():/ [0 i- M3 p% W# f
  28.     response={1 @+ I5 W1 U1 w! A( ]$ S
  29.     ‘chain’:blockchain.chain,
    7 ]  L# `, i: D
  30.     ‘length’:len(blockchain.chain),
    % M6 f& l& f/ ^: X* H6 [/ m: }. X
  31.     }$ N& q3 n6 t% `" e, f
  32.     returnjsonify(response),200
    - z- Q0 ]4 E' l/ I4 D
  33.     ifname==‘main’:
    ! ?5 _" X6 ?* q
  34.     app.run(host=‘0.0.0.0’,port=5000)# [5 V5 S7 C6 t3 P% {
  35.     ```
    8 X% H8 G) J3 K' l- Z2 z3 w, {
  36.     简单的说明一下以上代码:
    & V5 i4 B  S; O
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。2 t7 T6 a  ]+ I! C- j
  38.     第18行:为节点创建一个随机的名字。
    : U' k. ]& ^9 i3 Z
  39.     第21行:实例Blockchain类。
    $ ^% M( g- D# {( ^  o9 M# F- C( D
  40.     第24–26行:创建/mineGET接口。
    9 r9 D+ Z8 _% J8 W$ k2 d
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。* e# {. C7 G, V& @6 |
  42.     第32–38行:创建/chain接口,返回整个区块链。6 L$ t- f( A" O# ~- p
  43.     第40–41行:服务运行在端口5000上。
    ) e/ {5 b9 |! u' F' Q# |
  44.     发送交易
    1 j5 d4 X" d$ |( E9 r
  45.     发送到节点的交易数据结构如下:; d# H- {% r0 c! D3 x) m
  46.     {6 F* |- _3 W2 v' O1 E/ h$ G) t4 U
  47.     "sender":"myaddress",
    2 l( E# i! V$ X. F6 b
  48.     "recipient":"someoneelse'saddress",
    . U" |( O9 b/ L& F& V% p" H! m
  49.     "amount":50 D- |' y. a$ Z0 l3 d% v# z: F# b
  50.     }
      e( E. U5 ~& \& b; i( @
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:
    " L: r4 \3 R$ }3 j4 {
  52.     importhashlib
    8 W, N$ q; \! q
  53.     importjson
    ! }: T! H( R! E: I2 b) r3 a! _
  54.     fromtextwrapimportdedent. n& u8 i/ m! Q
  55.     fromtimeimporttime& |. H9 u: O. g
  56.     fromuuidimportuuid4- `3 ~1 g' K- V8 H3 V
  57.     fromflaskimportFlask,jsonify,request
    . Z1 I. T% Q. f" T; ~
  58.     ...5 W% u0 L% c" H' _# Q* `
  59.     @app.route('/transactions/new',methods=['POST'])/ q% @" B& v) ?
  60.     defnew_transaction():
    ! y' w' U. q( O  U3 a5 ]
  61.     values=request.get_json()
    ' E! R7 Z0 \, l+ n+ k/ o2 e$ X9 J
  62.     #CheckthattherequiredfieldsareinthePOST'eddata
    + ^3 U4 P7 k( s; H( K: j
  63.     required=['sender','recipient','amount']! y* F  j( ^  d# A* n; k
  64.     ifnotall(kinvaluesforkinrequired):1 G. h& D2 a8 D" g5 [$ B
  65.     return'Missingvalues',400! t/ M$ Z' u. e( Y+ e
  66.     #CreateanewTransaction' D% M7 U0 N$ }) a% O
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
    # [' M! t% R; l% l3 p
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}9 _+ e( @' e$ z  d6 S! n  R/ Q: d
  69.     returnjsonify(response),201
    6 P. V6 \$ [: t
  70.     ```) Q7 p$ Y3 z% I0 K2 j  {. ?
  71.     -创建交易的方法-
    # c, p4 A" x% u) p) N
  72.     ###挖矿
    ) K" c6 S  W7 N/ a8 `4 K7 W
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:
    * N# v' M+ u! k
  74.     计算工作量证明PoW
    , a7 ]+ t" S7 P4 a" |7 C
  75.     通过新增一个交易授予矿工(自己)一个币2 N) X& T9 {  D8 G
  76.     构造新区块并将其添加到链中9 Y8 i; w$ J% N' P+ v. x' @! k
  77.     importhashlib/ f* \0 Y4 s* r% x" S& _
  78.     importjson1 Z' R6 F& @8 t0 J3 E" f
  79.     fromtimeimporttime
    4 o! g. M4 N5 U2 Z( j
  80.     fromuuidimportuuid4
    : G2 o" b0 p2 N; ?  f+ j& V
  81.     fromflaskimportFlask,jsonify,request
    . c9 ]5 Z* ~2 r  p& _
  82.     …$ [( L* j. M2 l4 v: D
  83.     @app.route(’/mine’,methods=[‘GET’]), H5 `# Z$ n4 O5 z! \
  84.     defmine():
    2 M* e5 N; |- l; W8 F
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…
    5 d0 Z: `" w3 G" L8 \  _8 x! M
  86.     last_block=blockchain.last_block$ r9 N& O" L4 ?6 m
  87.     last_proof=last_block[‘proof’]$ H# |* ^  [# O  p+ g
  88.     proof=blockchain.proof_of_work(last_proof)
    ; G, ^7 h/ `! e' t- k" O- e
  89.     #给工作量证明的节点提供奖励.
    2 l$ }' V" @, i, w; Y; u) |- ]
  90.     #发送者为"0"表明是新挖出的币.& @, H) J  Z* v9 b: q0 U) B: c
  91.     blockchain.new_transaction(/ ]+ q  W1 {6 d% O# q
  92.     sender="0",
    * I+ X" h0 q' y) k# ^
  93.     recipient=node_identifier,8 ?$ _- n+ j& L% M! j" |
  94.     amount=1,
    3 c6 O( L3 u1 ]) y" h- G
  95.     ), v1 I/ C, j' h( T1 ?3 F$ T
  96.     #ForgethenewBlockbyaddingittothechain
    : `8 s* y+ u( _7 y* x. V  e+ C
  97.     previous_hash=blockchain.hash(last_block)
    2 U& g  z+ O* E, h
  98.     block=blockchain.new_block(proof,previous_hash)4 A7 @; `0 V0 _4 y) n& \0 Y
  99.     response={
    * \8 @; `2 e  T1 f$ Y( G
  100.     'message':"NewBlockForged",. A8 l( b) i* |9 x$ j2 N5 i# ]- y
  101.     'index':block['index'],
    * P2 Q6 V$ F- _
  102.     'transactions':block['transactions'],
    " k+ a7 [* k" m: j0 V9 b
  103.     'proof':block['proof'],4 F& q+ {2 I" w1 W
  104.     'previous_hash':block['previous_hash'],
    ' J% W' o- d; C* Z
  105.     }
    & j3 o2 @! W$ a  ]3 l
  106.     returnjsonify(response),200( L' ~) T3 M/ N6 ^/ ^! J
  107.     ```
复制代码

2 e. r9 p$ m* K( D; T% t) z    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
. d4 s" [/ x* q$ O# h9 x- d1 m0 t2 w    三、运行区块链+ H+ L- {# T/ f# a$ E
    你可以使用cURL或Postman去和API进行交互
4 p8 G; J5 l) e- H) |! S3 X    启动server:
% W3 Y+ d" w$ j/ \$ z' M7 s    $pythonblockchain.py
. {; i6 s2 N# |6 ?* j1 B( r    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
3 G# N6 k9 ~2 G    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
7 }( |' m/ {6 q. Z( n: f# }2 |    -使用Postman以创建一个GET请求-7 B, |1 `; [$ X4 z9 z; ^
    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:7 F& o2 O0 }& x+ d4 b( h
    -使用Postman以创建一个POST请求-# M% V4 h( \2 |2 O( ^1 P; k& }( T
    如果不是使用Postman,则用一下的cURL语句也是一样的:
( u/ J* ^3 \' t6 j: I- ~5 m+ H    $curl-XPOST-H"Content-Type:application/json"-d'{
& Z+ a% n! c, Y5 J2 z2 j    "sender":"d4ee26eee15148ee92c6cd394edd974e",  N2 R1 ^) i; |( U
    "recipient":"someone-other-address",/ f( d# N0 L* N/ G) E
    "amount":5
3 T1 |4 d$ B8 t* y$ J) \    }'"http://localhost:5000/transactions/new"
1 R4 ?% {' Q0 B2 c* ]    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:, k% Z; |+ `- Z5 _6 L5 T& Z
   
  1. {
    , J2 ]& ^# _; B6 H, m) K( g
  2.     "chain":[
    # \5 A$ Z$ G3 S1 O: L" {
  3.     {' g7 l# w4 _# G
  4.     "index":1," k/ @8 z  ~' b- X0 A, T( W1 Z
  5.     "previous_hash":1,% c" x! `. {& y, G; K
  6.     "proof":100,
    4 I0 t6 A% q" E: @$ j2 \, c
  7.     "timestamp":1506280650.770839,
    ) |7 w$ |% A& z  h
  8.     "transactions":[]
    : {% c5 X: |5 P6 b( U1 u
  9.     },
    , F. t6 i' y3 _  ]% s6 w" _
  10.     {- T6 X5 K. V8 c. R
  11.     "index":2,8 g: C2 a5 i. m; d% \' g
  12.     "previous_hash":"c099bc...bfb7"," C) O( s  N; T) \
  13.     "proof":35293,% ~  K  N: D# T9 \  w7 Q
  14.     "timestamp":1506280664.717925,8 C3 Y$ M) v5 w  Y) l
  15.     "transactions":[8 V) s: p# g! s5 |, L
  16.     {5 m' k: C' r5 E
  17.     "amount":1,
    ! O" o- v* E3 {* ]/ g1 w$ N
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",% r9 [3 N- R* `, o* m
  19.     "sender":"0"1 X$ M: K, N6 C9 u& J) a0 k
  20.     }) M6 H: Y2 n  F0 K+ Z! `
  21.     ]
    % I6 B2 x7 }* b* [2 W& Y
  22.     },
    ; `* T2 ?6 O4 |* {9 a$ z/ O4 D
  23.     {4 W$ o1 K8 ]" ~3 ]- t! H
  24.     "index":3,9 ~" Z: C7 t+ j
  25.     "previous_hash":"eff91a...10f2",; }4 j5 b) \# F0 Y/ ?* e
  26.     "proof":35089,/ u0 n0 W* O0 K* i- U
  27.     "timestamp":1506280666.1086972,
    : A5 m8 Q( X- u- [  G8 L
  28.     "transactions":[
    , N. V1 x3 Z) _  Q3 r, s
  29.     {1 C# @4 j, |3 N/ i, I4 e0 I
  30.     "amount":1,
    ) m* v0 q# t: g5 b, o
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",! M  F; ^1 r3 k* w
  32.     "sender":"0"$ y8 ]& j7 B, i: B0 z8 I
  33.     }+ h* `8 E" M! K6 \. E7 C
  34.     ]
    ( `% V; v$ E+ m' |
  35.     }
    & W& [' t2 a, R- p
  36.     ],
    + i$ J+ F% w" r( @  b$ a) T3 }
  37.     "length":3
    : a9 h3 W; M" ~: P* P
  38.     }
复制代码

' Q- U( p! w9 C4 _5 H    四、一致性(共识)
  y% X. x" _# \2 i2 d9 k6 \* D% N    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。) N$ E6 h! q. D/ J* i
    注册节点
/ t9 T( r+ u% X3 D( w* v0 E    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:6 w7 W' c- C- C/ ^
    /nodes/register接收URL形式的新节点列表( m& ^: Z8 S" n5 g; g  c
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
: m. Y. {+ I1 w. @/ _" B, }# H$ A    我们修改下Blockchain的init函数并提供一个注册节点方法:( M/ ^0 Z& V- u6 ^% k
  
  1. ...$ ~' w' a: L- F. A  ]1 ~* ^
  2.     fromurllib.parseimporturlparse! D: y9 o( R5 d& z
  3.     ...
    4 V/ N0 }4 D+ j  }/ Z8 ]- H( S) Y9 N
  4.     classBlockchain(object):& Y& w! d* S& M8 z. c
  5.     def__init__(self):
    $ {2 J+ `+ x2 m$ V# Z  f5 N
  6.     ...
      R1 G5 M- G4 x. p* n
  7.     self.nodes=set()
    5 a& W4 o3 t& ]. o. d: z* q
  8.     ...
      r4 E9 I! t% i3 z
  9.     defregister_node(self,address):
    2 ]8 u2 U& O2 j) S, t5 e6 P0 c
  10.     """
    8 m% [* M7 |3 F9 X9 ~  O( Q
  11.     Addanewnodetothelistofnodes
    ) q; V1 o2 Q& V* s8 f) r3 l0 |. D6 B0 m
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'- \8 V! G" K& r9 s/ L
  13.     :return:None! |1 ]3 h7 i' O/ S! {1 U. q7 W
  14.     """9 Q7 V7 [6 K" S( E3 Z
  15.     parsed_url=urlparse(address)- S1 R1 t: y+ H1 H$ S' u' Y
  16.     self.nodes.add(parsed_url.netloc)$ P* a/ _. V$ |7 t( `7 G
  17.     ```
    8 U+ I, d% T3 t+ `/ p, x
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。' o  X9 I  V% y$ b! {% U/ J
  19.     ###实现共识算法; I9 f4 K( h/ f: i
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。) v* O8 y  H) h
  21.     …  j7 F! x) K0 O6 ~- q0 Z4 ]
  22.     importrequests
    ; }# y, q6 N" ]( w6 W
  23.     classBlockchain(object)" C' I; P. s4 j5 U
  24.     …8 ~" Z; M. m2 u6 }+ j3 i) w- h8 A
  25.     defvalid_chain(self,chain):' h3 r- Y+ R" D: t( [9 X1 w
  26.     """
    " b' m; ?2 W; O$ h2 h9 F" D' x
  27.     Determineifagivenblockchainisvalid
    9 v) n, n% l; P  t6 d
  28.     :paramchain:Ablockchain
    . m9 U# L6 r3 H. t
  29.     :return:Trueifvalid,Falseifnot
    ) ^. K% T8 a2 w3 a; ]/ B' R; J
  30.     """
    2 V& l) u3 j. M* n3 g( r6 P
  31.     last_block=chain[0]
    $ |; J3 J' a3 s  C: H" g
  32.     current_index=1; G- u, f/ N6 s, g
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):  d. P/ u' C" ?  Q
  34.     max_length=length
    " Y. R$ v8 O0 G6 a* i
  35.     new_chain=chain
    5 Q% S& I. l" W" c; X6 y( ^
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours
    - N, z7 I! K; n! p! x
  37.     ifnew_chain:
    ( q) {# I% a( f: @
  38.     self.chain=new_chain) Z8 v6 `  Q1 S9 U, _" h0 R
  39.     returnTrue
    : h3 Q6 }7 E$ l2 L/ w# z9 y! E
  40.     returnFalse
    2 U3 n2 ]* R2 H" a) e; |
  41.     ```
复制代码

/ u) S$ f* h. a2 K    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。4 U+ S- @# R! N, ?( x1 @" ?
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。4 M6 C* `0 z; z7 X: t
    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。1 r' d4 W% p2 r! o4 N, _  M6 O
   
  1. @app.route('/nodes/register',methods=['POST'])
    ! d; @. b7 ]- [- Z' J* W
  2.     defregister_nodes():
    ' b9 N% h( n; @
  3.     values=request.get_json()2 Z- U0 L9 g' A1 n  t  I5 V  b
  4.     nodes=values.get('nodes')
    ; z- R3 W3 t7 t$ Y9 @6 L
  5.     ifnodesisNone:+ e' }7 w% ]2 T' q& f8 t
  6.     return"Error:Pleasesupplyavalidlistofnodes",4005 ]7 `6 {1 C2 g: X
  7.     fornodeinnodes:
    1 L8 C% N' ^8 Q% q& w
  8.     blockchain.register_node(node)7 C7 P- M+ v" z/ G0 O# F
  9.     response={
    $ a! A5 {8 S7 @8 _! w
  10.     'message':'Newnodeshavebeenadded',2 v7 T8 V6 }4 T% W) k6 a( r& G
  11.     'total_nodes':list(blockchain.nodes),
    9 i$ J% O2 X; I+ t
  12.     }
    ' ^- m! o2 d& E9 W) G$ c
  13.     returnjsonify(response),201
    7 `# `' h6 B6 Q/ Z1 q1 w9 v
  14.     @app.route('/nodes/resolve',methods=['GET'])" {7 f. S* n& j  E! Z7 X0 M$ {% Y& w
  15.     defconsensus():
    & O8 C  G2 I+ `9 {: W
  16.     replaced=blockchain.resolve_conflicts()
    . E" s+ k$ Y( T: P
  17.     ifreplaced:2 i* M8 `5 X" @; t9 I3 J) C
  18.     response={
    $ d( W+ g6 E4 M$ s0 s& [; _+ p
  19.     'message':'Ourchainwasreplaced',
    - ]* f  K3 _* k9 W* p( A
  20.     'new_chain':blockchain.chain
    8 @' L, Q4 ]( t' W  P8 O* j
  21.     }) e' |9 T( _1 B
  22.     else:+ V7 y; K3 e4 q# r/ f3 X! d6 {, B
  23.     response={
      B6 G. a, R6 p" J! P% j: ]% K
  24.     'message':'Ourchainisauthoritative',
    9 Z! v) j" d3 Z$ f9 P. ^. O
  25.     'chain':blockchain.chain
    ; X$ Y! r" U* h0 y/ a% z7 h
  26.     }8 M8 ^/ H! v& |! j$ s
  27.     returnjsonify(response),200+ \) D: \) D# J( }3 W
  28.     ```
复制代码
) q# S9 b- u2 o+ s; v8 ]
    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
6 K2 s5 r+ ^( V. a$ P7 e7 H
5 g  q* Z* r  c  t8 k8 `0 r1 G    -注册一个新的节点-4 l, b$ a- k& O- y& h& E: z$ }0 m$ J
    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7