Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
313 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。' w/ d+ z/ b9 f1 P. A& U$ n, j
    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。% q+ j! ]  V2 n+ |
    准备工作" Y# ?. |. X9 v
    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
5 X3 n/ U# O! M9 C- g# g& Q  N    如果你不知道哈希值是什么,这里有一个解释。
. C1 g- f2 a! i/ @% I3 `    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
* c  k0 g" w6 ^. Y2 y( E    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
, w) K( q" B  _0 T$ B    pipinstallFlask==0.12.2requests==2.18.4- z0 }4 {9 O* P% V
    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。6 c" c+ ~( r0 ~
    一、开始创建BlockChain
) U# K! D3 C. a9 x+ c    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。: q# V; H2 F. o0 Y$ X" A) W5 C' x
    BlockChain类& G% L! c+ e/ `) {8 p3 [
    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。8 b7 e' ^8 o: e9 \( u
    以下是BlockChain类的框架:
! e# m1 Z- _4 g- j0 x4 Z  
  1. classBlockchain(object):
    8 B1 k: m' U( C7 ]
  2.     def__init__(self):! t; ]1 ~5 ^0 m5 S
  3.     self.chain=[]
    7 h9 @3 d" ^% A8 ?3 j' a9 |( }9 b
  4.     self.current_transactions=[]
    - t# e2 z- \8 X
  5.     defnew_block(self):" p  h7 e& ], ^+ U/ {
  6.     #CreatesanewBlockandaddsittothechain0 p( a/ ^9 J3 N( Y3 J
  7.     pass
    5 S6 y2 b6 a/ l2 ]3 u% S4 G2 U
  8.     defnew_transaction(self):
    % |' z! p8 ^6 }* p* _/ N
  9.     #Addsanewtransactiontothelistoftransactions
    2 M. t5 w% }8 N+ g+ c
  10.     pass+ V% _& `& |% H1 B+ B% J, L
  11.     @staticmethod7 j% M6 Y7 T4 M8 P6 C: ?
  12.     defhash(block):
    ' A2 M8 `; X2 `2 b; V5 _
  13.     #HashesaBlock
    ; @  v& @0 y7 Y! T1 ]2 _  N
  14.     pass
    4 q" V' F% P" s2 Y) r& Y/ s
  15.     @property
    6 S) @0 J, a( c, @" F
  16.     deflast_block(self):; U: N# y( l0 _3 q( x$ y
  17.     #ReturnsthelastBlockinthechain
    2 I% N; P2 \- G5 E1 i& }# n
  18.     pass
复制代码
3 h1 x8 {4 @9 {
    -我们的区块链类的蓝图-; H  V$ `* ]- P7 N  R# j
    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。# f! n3 X( C/ F; ?
    块结构
+ D8 k) d# g0 Q( T  j6 O    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。
+ q- \8 ?+ w- y6 ^; v+ l1 |0 c: o    以下是一个区块结构:, E- P% @; ]9 u8 Q
   
  1. block={
    + N5 L9 L7 s! Q# E0 n$ T: ?
  2.     'index':1,
    3 P. b( V2 W2 h8 Q0 Z
  3.     'timestamp':1506057125.900785,
    - r+ l4 r% B0 z% z8 M
  4.     'transactions':[6 I6 E2 n% u' j. U& r
  5.     {# y$ Y& W, s" t7 s9 g1 F' W
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    % i$ Q- g) ^! h
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
    2 ], `  e1 k& R* W3 T, Y. B- k
  8.     'amount':5,: w& f6 l+ U" M& H6 W  L
  9.     }7 I" d/ t" f- O8 C9 L+ `
  10.     ],. F; v: e3 u8 z- e. @; t* R& \* T
  11.     'proof':324984774000,
    7 T% H4 I/ ^' o* S( I3 `: S5 e4 k
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"# p* m. o) ~: V: }
  13.     }
复制代码

% I( _8 h( H! E; e3 N$ C    -链上一个区块的例子-
: D# J( ]4 q+ _- W; Z    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。7 \7 `7 k% A+ |/ o
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
% d  g/ Z9 m' ]    加入交易% s: n6 U, r7 a9 x+ ?
    接下来我们需要添加一个交易,来完善下new_transaction()方法。
1 D5 H, C' C% r4 h1 ^8 T6 a/ L  
  1. classBlockchain(object):0 J4 y  S# P* H% Y3 ^* _3 A% E
  2.     ...* P; Z9 V  J; C: e, t, f
  3.     defnew_transaction(self,sender,recipient,amount):+ e  J/ P1 P1 N% j7 ^/ o
  4.     """3 V# ]' @' m: C  i+ }
  5.     生成新交易信息,信息将加入到下一个待挖的区块中0 A' r) c, \6 e
  6.     :paramsender:AddressoftheSender! F$ x- M4 O$ \6 U9 S( k  c
  7.     :paramrecipient:AddressoftheRecipient2 H  l( f1 h0 p& @; \
  8.     :paramamount:Amount
    . Q$ I4 \- Y- C& k
  9.     :return:TheindexoftheBlockthatwillholdthistransaction' {7 F5 E: f7 t+ w) ~4 k& K
  10.     """
    4 F! `- v3 ~8 `) b1 K
  11.     self.current_transactions.append({4 o) I6 T  N, h$ _. Y/ k
  12.     'sender':sender,' C. _+ w1 \* O8 x; a' u% X+ H
  13.     'recipient':recipient,
    5 |( s/ r+ X7 V- f: L
  14.     'amount':amount,
    9 d' a4 ?- X  ]* b# x: F9 Q
  15.     })0 _$ @: ]- Z& N: B/ w* D1 ?
  16.     returnself.last_block['index']+1
复制代码
) Z. b/ |; ]5 m
    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
- G3 D6 f  o) g1 O# |' E    创建区块# j: X; v5 @. B" l) i
    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
! O5 m2 H( Z4 A' {& I; `    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
0 r8 n8 z: @4 L! i2 d. |6 T    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
' s. L* e- r1 E  
  1. importhashlib- j. F' K( f# `' V3 W9 F
  2.     importjson
    3 E$ a/ G& h; d2 }0 M2 D7 L: g
  3.     fromtimeimporttime
    9 ]9 G2 S/ y3 k3 |
  4.     classBlockchain(object):
    , L& R, t% ?( ^; i: M) I
  5.     def__init__(self):
    3 H1 z' J: E+ p0 B
  6.     self.current_transactions=[]
    / Q/ i0 S9 S8 Z# r
  7.     self.chain=[]+ |. ?, Z6 @4 ~% J
  8.     #Createthegenesisblock2 z3 t( m6 q: x  O6 }
  9.     self.new_block(previous_hash=1,proof=100)
    . b- ]- M% [8 m! q
  10.     defnew_block(self,proof,previous_hash=None):- i  ?! m' y: N( K
  11.     """
    & h% G2 {4 z% M, y
  12.     生成新块
    7 b, @2 H2 K6 t" I+ W0 m. f
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm. m9 Z# t8 i$ R# B- P
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    $ R3 B6 \7 ]% \( V# F
  15.     :return:NewBlock3 b4 J* V& b; P; `! @. L
  16.     """
    # _5 a# t( b3 Z/ s  ]  u
  17.     block={
    * B" p* [- x) H1 v) Q
  18.     'index':len(self.chain)+1,
    * X9 Y$ i% H+ n8 j
  19.     'timestamp':time(),
    / S" q6 }! S& c2 ~- R
  20.     'transactions':self.current_transactions,7 p% C1 r3 j4 x+ I- ~2 V7 C
  21.     'proof':proof,
      n5 F+ \2 Q4 \1 |, j2 n
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),4 y- G, R% F" _6 a: V+ ^: s
  23.     }! P7 n# E3 Q% K4 j
  24.     #Resetthecurrentlistoftransactions
    ! V; O% z- o- l$ @0 d% G5 `- d
  25.     self.current_transactions=[]
    + Q- ~/ j8 Q" c6 }
  26.     self.chain.append(block)
    6 Q) a' q2 X3 }5 w; J' {
  27.     returnblock. a! C7 ~  n6 ]' |( O
  28.     defnew_transaction(self,sender,recipient,amount):8 [3 K  S; A0 f- _6 q* p" I# o& `
  29.     """( T/ x* K7 u7 e8 Y
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中: y" }2 C: G  \$ f. Z
  31.     :paramsender:AddressoftheSender3 x* Q( S/ d' {  y0 q
  32.     :paramrecipient:AddressoftheRecipient4 h- D  H$ T# j: R+ \
  33.     :paramamount:Amount  F5 u6 g7 c. H  n2 _# A$ u) K
  34.     :return:TheindexoftheBlockthatwillholdthistransaction9 z; w' P# W6 A7 [: D, A
  35.     """
    ; t1 N7 x6 Z. e" Z: H. Z, g/ n2 Y
  36.     self.current_transactions.append({
    , N! A7 T1 R) Q: @
  37.     'sender':sender,
    . p- n' \5 Z" t; ^. Z/ ]( f
  38.     'recipient':recipient,& u& q' ]0 K; @& }/ N6 }
  39.     'amount':amount,  o, j: d( k) M# b% r2 h$ E7 a4 r
  40.     })
    2 L7 V; k- \% u4 I$ ~: b: x
  41.     returnself.last_block['index']+19 }  ]2 [1 ]; N, v) o, l0 [, w9 [
  42.     @property
    : z2 }  E$ o5 b. M8 A7 r6 g# ?
  43.     deflast_block(self):
    : ?% d- q1 h  Q5 b
  44.     returnself.chain[-1]5 d3 N4 q6 E% C1 ?; S7 a
  45.     @staticmethod" Y. ^% e4 R9 O' j3 B! `
  46.     defhash(block):
    ! X/ R/ K" E; {/ i: R/ l% J
  47.     """
    / t' X# C. N" o+ i) u* a& l( u* n7 D5 U
  48.     生成块的SHA-256hash值
    7 b, L6 \+ f$ w3 M; s
  49.     :paramblock:Block8 `+ G# l0 J0 C! i7 i
  50.     :return:  B5 f9 I7 ]0 [' }+ m
  51.     """
复制代码
/ g5 `& g8 x, {1 i. a4 y% q
    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes- P' y# B1 N3 l
    block_string=json.dumps(block,sort_keys=True).encode()3 ?& t6 V# O3 V+ o+ A, _( b
    returnhashlib.sha256(block_string).hexdigest()- f% j% x# B, z5 s
    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。4 ^, n) u& ~& l! P. ^( A' e
    理解工作量证明
# {& P' ~9 c! ]6 ~  O  h0 f    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
4 Z% J! Q. o1 o# O* t    为了方便理解,我们举个例子:
3 H7 ]) J/ g7 X3 r. j    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
' a; y3 T8 o4 K2 f
  1.   fromhashlibimportsha2567 C" Y+ s% E8 f$ i
  2.     x=58 U9 m, r: n& S) T8 K% S
  3.     y=0#y未知
      C$ L+ N6 {5 i
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":3 v1 ?$ x" Q. ~; |+ R
  5.     y+=1* O( U! m2 ^7 L* O
  6.     print(f'Thesolutionisy={y}')
复制代码

% ?! ^1 R& \3 n' O  w, S( }8 U    结果y=21,因为:5 ]( v; _: l7 ]" X8 q
    hash(5*21)=1253e9373e...5e3600155e860
7 e' P' z& X& ^7 u* O2 H, x  T    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。( m8 ^$ ?) {" U  G, X
    当然,在网络上非常容易验证这个结果。
- K( ]* `) e" w  q    实现工作量证明- y3 H( \0 }( V% A
    让我们来实现一个相似PoW算法,规则是:# y2 r0 E( l) e4 k+ w  H* G  g6 w
    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。
% h7 U+ M: F! h; x  
  1. importhashlib
    . R* g0 l) w9 i* o4 V0 u- Q
  2.     importjson$ ]' R% S, m: p  i+ \# M3 o
  3.     fromtimeimporttime; _8 R! a1 g. R5 P$ Q
  4.     fromuuidimportuuid4! [8 D; g* V% ~) l5 H7 C
  5.     classBlockchain(object):! F& ^/ t: {6 T8 p: L' k2 F
  6.     ...7 p6 o6 w" p. L+ e
  7.     defproof_of_work(self,last_proof):# L) q9 ~* [4 T' Z0 k
  8.     """2 ]: N5 C- O0 S! M
  9.     简单的工作量证明:0 s+ {* U  G% G0 i
  10.     -查找一个p'使得hash(pp')以4个0开头
    & M0 j! F% a1 ]6 ?
  11.     -p是上一个块的证明,p'是当前的证明- C  E7 b6 E. B5 Y
  12.     :paramlast_proof:9 w$ Y4 l: B. e' n# G
  13.     :return:
    2 V$ l, k2 @: {% C2 Q6 U
  14.     """6 }! K- q0 C5 B% x% G4 q5 I
  15.     proof=0
    ' Y" O. E/ }9 t* Q$ F* W* ]; g  }
  16.     whileself.valid_proof(last_proof,proof)isFalse:$ N# ]* P  \3 Y' `, A
  17.     proof+=1
    7 g3 n7 i( a$ s! P6 y* K9 S4 X
  18.     returnproof
    9 J8 |' r; ^. H6 _# W, X
  19.     @staticmethod
    * Y5 ^7 b' E9 ^! L9 [( i5 Q
  20.     defvalid_proof(last_proof,proof):, R% Z/ q% _0 D9 r- K  R0 T# _
  21.     """; b( e& T- h; w* c
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?
    % Y$ t4 j- `) J- P: P
  23.     :paramlast_proof:PreviousProof" F5 R; `' b0 x' m
  24.     :paramproof:CurrentProof1 V) X& f+ @2 ?$ T0 P& c
  25.     :return:Trueifcorrect,Falseifnot.
    9 |- x. o- s5 a. k7 w
  26.     """
    - Y6 b7 G4 A+ [5 c1 M% S
  27.     guess=f'{last_proof}{proof}'.encode()
    6 _9 G4 j; \. _/ @
  28.     guess_hash=hashlib.sha256(guess).hexdigest()9 i( T1 N# c. L/ D# h
  29.     returnguess_hash[:4]=="0000"
      I8 E: W9 y6 l
  30.     ```
复制代码

* f6 P- k* ~4 C+ \+ h0 Y, g8 w    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。
& w. d5 E/ J4 f4 i. Y# T    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。4 X4 r! B5 ^. _0 }
    ##二、BlockChain作为API接口- T- ?4 P/ a6 n) c3 s
    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
, g' H4 d5 G. M8 v. Y    我们将创建三个接口:
& z0 h8 j' C: Q, [   
  1. ```/transactions/new```创建一个交易并添加到区块# q) _2 l5 L2 _# \& F. Q
  2.     ```/mine```告诉服务器去挖掘新的区块  g  Y; Q7 D8 {  K
  3.     ```/chain```返回整个区块链
    4 T& ]) Q" Q- @! G: r
  4.     ###创建节点+ S% O, A) S8 y- y" n5 U7 L" C
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
    ! [8 c2 s# K! `. m- z% |$ t( L* y
  6.     importhashlib
    - e' M5 ]& {3 W
  7.     importjson
    + ?& N/ m& z$ ~  f; H: [( |/ y
  8.     fromtextwrapimportdedent8 _3 p/ E" J/ b! ^2 b* n
  9.     fromtimeimporttime* s5 y- J( d; L9 @6 H
  10.     fromuuidimportuuid4! x7 j! e( K0 j8 Z, }- K; Y
  11.     fromflaskimportFlask
    : R/ w7 }9 p/ x) s
  12.     classBlockchain(object):
    $ E( o- p8 t* v9 d1 r
  13.     …
    % {1 @/ g4 g/ [8 g; D& O: A
  14.     InstantiateourNode
    ! ~; G2 m7 K1 t# p& N; M" |! |. _
  15.     app=Flask(name)
    $ k* @6 l: w7 B
  16.     Generateagloballyuniqueaddressforthisnode
    5 D# J6 N0 Q8 W: a5 R/ D: l
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)
    ) m) ?6 X0 J6 p  H  V
  18.     InstantiatetheBlockchain
    - g7 J: p; z9 V: d3 `
  19.     blockchain=Blockchain()4 Z2 A3 E6 x1 J7 e& D
  20.     @app.route(’/mine’,methods=[‘GET’])0 D& d2 G6 Q2 D+ h
  21.     defmine():$ r2 s% h! F/ B5 S4 i
  22.     return“We’llmineanewBlock”& o! P- q* G/ V+ r  L  u% B5 C5 A
  23.     @app.route(’/transactions/new’,methods=[‘POST’])- x" x0 ~3 Q( Z* k
  24.     defnew_transaction():3 a1 f$ U: C% B4 k+ q! X
  25.     return“We’lladdanewtransaction”; q9 V) l4 K4 I+ c: g3 v+ r
  26.     @app.route(’/chain’,methods=[‘GET’])0 L( y) I0 p  x4 t3 i
  27.     deffull_chain():8 C% _6 H5 ~5 j  X  |" s
  28.     response={
      ?0 z0 ]- T; y$ H* [& J8 i1 I
  29.     ‘chain’:blockchain.chain,
    8 k  I1 j1 M! X+ Y
  30.     ‘length’:len(blockchain.chain),
    ' D- B; E6 L$ j- X' E9 T
  31.     }
    ! W  v7 E, _5 m4 T
  32.     returnjsonify(response),200
    9 W' ]3 q1 F3 Q4 Z
  33.     ifname==‘main’:
    * P/ v4 z/ z1 T
  34.     app.run(host=‘0.0.0.0’,port=5000)4 ^: K3 w! O* B- S- x
  35.     ```
    ; ?5 N( m3 y8 Z( h3 f
  36.     简单的说明一下以上代码:0 p3 `5 O' a6 l5 N* p; S8 h* s
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。* S& i  F6 m2 s# `
  38.     第18行:为节点创建一个随机的名字。  T4 c- T& f: }/ o: h
  39.     第21行:实例Blockchain类。
    + K  ~3 b+ ]0 {# l/ t8 m
  40.     第24–26行:创建/mineGET接口。
    : d% k' o+ T! |0 B! U
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
    ! b- ^) f6 f+ ?5 N; m6 V' q) k
  42.     第32–38行:创建/chain接口,返回整个区块链。
    , V/ D0 Q& m6 s+ O! ]7 G: ~
  43.     第40–41行:服务运行在端口5000上。
    3 s+ I5 I- f; w2 Q& h! n
  44.     发送交易
    9 H1 l0 r, @  H, X4 s
  45.     发送到节点的交易数据结构如下:3 m1 B' A7 c& z; \5 B0 Z" ?
  46.     {
    ' {3 |5 G" c. D- s
  47.     "sender":"myaddress",5 q0 Z) i& [# Y, `$ r5 q2 @
  48.     "recipient":"someoneelse'saddress",
    % l) j& B6 e$ a# E" _
  49.     "amount":5# v+ i% O0 D0 \; j$ a  D+ L
  50.     }
    7 \$ V, X9 ^. a8 k2 c$ S
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:
    " ~8 C1 A' R9 {$ c5 [
  52.     importhashlib/ ]6 _5 l0 z! U% o! @% G; _2 i
  53.     importjson
    ( }3 o6 M) e5 G
  54.     fromtextwrapimportdedent
    7 n- t/ {3 l9 E2 ~# u
  55.     fromtimeimporttime2 b6 s) ]1 J2 j& N4 M+ s/ L
  56.     fromuuidimportuuid45 N1 U. U' }) d: X8 G# a$ z
  57.     fromflaskimportFlask,jsonify,request7 K) R1 K! |" r, x
  58.     ...
    " z0 q* x- K4 ]  S! ]
  59.     @app.route('/transactions/new',methods=['POST'])1 h3 d( i& i/ C" N# y
  60.     defnew_transaction():/ n7 n( m5 O& B+ k+ G) ]( U& s1 C
  61.     values=request.get_json()
    " ?/ K1 J( ~9 ~
  62.     #CheckthattherequiredfieldsareinthePOST'eddata. g5 z6 {# Y5 T  y
  63.     required=['sender','recipient','amount']" A9 J2 _8 F! |5 {
  64.     ifnotall(kinvaluesforkinrequired):
    ' L( D& O8 @* B' ~
  65.     return'Missingvalues',4007 J/ Z# Z7 {/ T, o( c5 M8 g8 {
  66.     #CreateanewTransaction
    3 y0 n+ t& I% ]2 Q" i1 F0 d
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])
    ) [7 D' U8 K9 c
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}% g6 p2 J3 J3 l6 e- z" R# ~
  69.     returnjsonify(response),201
    # I, t" [7 n5 f8 X
  70.     ```
    , n$ @! h& ~1 N8 S
  71.     -创建交易的方法-
    ) V* ]4 b- e' n% w& X7 p. m
  72.     ###挖矿: G, a1 }" B2 m; f( s2 I+ ~# r3 Z
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:$ ]5 i! {+ f1 K# F; T! I
  74.     计算工作量证明PoW
    9 [6 U' X6 t  j: g$ r
  75.     通过新增一个交易授予矿工(自己)一个币
    8 b7 a' m  ]9 M- V, Z$ T& c5 L
  76.     构造新区块并将其添加到链中
    # t. m( m/ G5 N; G* U' G$ h
  77.     importhashlib
    * e* L6 H" ^$ D/ ~
  78.     importjson9 i, U0 ]; ?. s: q: k' c
  79.     fromtimeimporttime2 S# n0 v% }, m- _2 Q. W0 M
  80.     fromuuidimportuuid4
    4 i& Q) c* e8 z$ M* Q) o
  81.     fromflaskimportFlask,jsonify,request
    4 e3 L7 `& ]8 _! I/ ?
  82.     …1 N$ Q4 R# o9 v' u8 |. b) K
  83.     @app.route(’/mine’,methods=[‘GET’])
    " O7 t! r/ m( A4 M
  84.     defmine():& [4 u% w7 f( Q+ [9 F9 s
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…
    % F9 Y% m/ ?6 p, l: ]8 r% a. V
  86.     last_block=blockchain.last_block
    7 S9 `# `& I3 Q1 B; {1 z: ]) }9 v
  87.     last_proof=last_block[‘proof’]6 ?+ m5 o8 ?# w
  88.     proof=blockchain.proof_of_work(last_proof)
    " V: D; p" a1 }7 Y& g$ {; y3 `
  89.     #给工作量证明的节点提供奖励.6 Y1 D) l( F7 R: V. P9 p# t
  90.     #发送者为"0"表明是新挖出的币.
    ( N2 _9 O" s7 x
  91.     blockchain.new_transaction(* w7 P" {) @2 }+ E
  92.     sender="0",. U  d9 z" J; }3 `% _
  93.     recipient=node_identifier,
    / b( G* ]! F  v% f5 k- U
  94.     amount=1,
    1 t, @9 w* V7 }& J$ E$ u/ y7 g
  95.     )- L: R8 y0 [' r" I
  96.     #ForgethenewBlockbyaddingittothechain
    ; b9 H' k8 N$ z( {& |+ r+ K' ^
  97.     previous_hash=blockchain.hash(last_block)
    8 f3 X/ o# O9 A0 u7 ~& D
  98.     block=blockchain.new_block(proof,previous_hash)
    7 F8 f) ^% [0 z* |0 D  Z
  99.     response={
    8 S$ u& H) }: q) u* v0 [' \- p, g' u6 T2 H
  100.     'message':"NewBlockForged",
    # ~. e% j- g& |; }
  101.     'index':block['index'],' M1 f  M! t8 }# l) f# G' O6 C
  102.     'transactions':block['transactions'],
    / _# {. y  d6 J; |
  103.     'proof':block['proof'],
    - e( ]1 `+ i  O% E5 {4 D7 j; E
  104.     'previous_hash':block['previous_hash'],3 b0 V! w( I9 Y- _. \' ]
  105.     }$ N- g: Y  s% g- X0 P4 W
  106.     returnjsonify(response),200) t9 O9 i3 s! o" O) }5 v, V) W
  107.     ```
复制代码

: k/ B7 E. R' y6 ~8 O    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。1 k8 _8 e0 j& O$ [9 R" L
    三、运行区块链
9 S, f, ^$ W5 [    你可以使用cURL或Postman去和API进行交互  Y. j, {- C6 |5 v/ t
    启动server:
) h" F0 z# K% c0 A    $pythonblockchain.py# R0 B4 i3 s& W* o7 k3 l! |5 }7 k
    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)" J$ Z( c5 Y- O( s& r- }
    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
& n7 v3 t+ W, ^% ]    -使用Postman以创建一个GET请求-0 h! f8 c! w* o: ~( R3 F
    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
' ]' n# W0 H% i8 ]( G% m! L    -使用Postman以创建一个POST请求-
& p; C9 A7 d% N. P# i0 k    如果不是使用Postman,则用一下的cURL语句也是一样的:
; h# T" e' K9 S* S7 S    $curl-XPOST-H"Content-Type:application/json"-d'{4 ?- m7 v7 j% \
    "sender":"d4ee26eee15148ee92c6cd394edd974e",
" T) K" V) S$ x- A* a- e    "recipient":"someone-other-address",
' g6 @) x% U3 ~& ^; ]8 K    "amount":5
/ |% C0 }& k# L0 l3 H; W& H! P    }'"http://localhost:5000/transactions/new"* F) j2 I5 S- K$ d$ Y* d
    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:- K/ a* n6 @' g. V) b' l+ u' b
   
  1. {( r; B4 ^# g  e- j% `; z3 Z: h
  2.     "chain":[
    / o) m( f5 f: E0 `. H; w, n, T6 z! ?- |
  3.     {' X3 v: y: Q+ D5 }& u
  4.     "index":1,* p" Q! z- c! e" ~, w! o  {& I
  5.     "previous_hash":1,+ y* t; s9 p6 x) B: R
  6.     "proof":100,& _) I% o- ~) g4 J) k; W; q! Y
  7.     "timestamp":1506280650.770839,
    ! A% ?- e4 @  J8 `: S- n. ?
  8.     "transactions":[]$ ^# @  r* f2 ?" T: D' i) ?0 p
  9.     },7 [* y: Z% O4 q, X
  10.     {1 X0 X4 F3 {1 V8 h/ U( [
  11.     "index":2,9 A( d$ V5 v7 _! T0 O
  12.     "previous_hash":"c099bc...bfb7",
    1 V) z" d, H& I
  13.     "proof":35293,
      w9 N; k9 \4 }% o
  14.     "timestamp":1506280664.717925,+ [7 o6 Z$ a0 _( B0 q9 o7 V  I
  15.     "transactions":[8 m/ B8 X; Q: D% `
  16.     {
    7 W: N% c+ F2 c& P' v( k* @# v2 {
  17.     "amount":1,# v: |% P" R. L$ t  N
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",, X3 r" O2 o" D/ u
  19.     "sender":"0"6 U" M  M: t/ `& ~3 J
  20.     }9 f+ k* Y) n' k1 d5 ]+ z
  21.     ]' C6 J3 L$ g- R2 ?$ ]9 D3 s4 P* Q
  22.     },) l- C2 l. X  ^( ^  r4 z' l
  23.     {; h; e# x/ D0 F7 a
  24.     "index":3,
    0 v9 G7 B! z  ^! }" z! E
  25.     "previous_hash":"eff91a...10f2",3 q  A* Q2 f. r  x
  26.     "proof":35089,8 U  r, Y% \# Q0 Z
  27.     "timestamp":1506280666.1086972,
    0 h( |" C9 T1 E+ P% U+ I
  28.     "transactions":[9 d. K7 n" r7 Q5 U7 b+ L3 |
  29.     {7 D: Y& t, e5 n3 f& \; p0 N
  30.     "amount":1,3 u* O2 i8 X$ l# d0 s
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    7 Z+ D3 C1 R" ]+ g% a% P
  32.     "sender":"0"
    : Q2 d$ A2 |8 S* r% u4 \
  33.     }0 N5 i: N7 q* S9 L
  34.     ]
    1 |5 x3 D7 }5 Y. U% n
  35.     }
    - K  z8 b8 s% t
  36.     ],
    ! }7 |0 E$ U+ E( M
  37.     "length":3  ~# U/ y4 g, ~' Q, F/ d8 u
  38.     }
复制代码

3 u% Q" j2 }. Q8 [# O( _8 v    四、一致性(共识)
7 a% e3 g/ f  P$ o0 J; I    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
8 I2 l2 Y+ s. w/ O7 Y+ H    注册节点
8 U; {+ D0 v/ j9 V    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:! c7 p4 _4 g$ W  W
    /nodes/register接收URL形式的新节点列表4 T! i  t' F8 ]: J; V4 o0 Z3 O) {2 s
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
+ `' L* L* A& S    我们修改下Blockchain的init函数并提供一个注册节点方法:
! l: f4 O7 @; s# x) O- J* ?3 V* s  
  1. ...
    4 N9 P" ?( S  @# h6 z
  2.     fromurllib.parseimporturlparse6 V) |1 y3 b2 x% H5 b& T  I* ~
  3.     ..." p1 C( w( Z, ?
  4.     classBlockchain(object):4 g' ?3 q( U' V
  5.     def__init__(self):
    ! Q7 p" w5 l" M  G$ k3 o8 m
  6.     ...
    7 ]3 o$ ^+ u/ G5 D2 M" W, t
  7.     self.nodes=set()
    ; |3 w- s# n5 k4 V
  8.     .../ I+ p- t8 x" X/ x+ w, n, l) n3 U: B
  9.     defregister_node(self,address):& E' n4 q! R2 {* m4 I
  10.     """
    / f0 O" \, n! [. v( [
  11.     Addanewnodetothelistofnodes3 }$ L" E+ j# S9 f+ y0 q, S
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
    9 d+ Z0 q0 d8 j2 [
  13.     :return:None
    ; D' S" t8 r, h1 l& j2 t6 A
  14.     """
    - i, K- r$ c1 t+ x- b
  15.     parsed_url=urlparse(address)
    . l9 u) R  x* p9 j
  16.     self.nodes.add(parsed_url.netloc)
    0 p  \7 q! W2 R
  17.     ```
    # @) ~8 h& Q' U6 Q5 E2 \2 q. `' i" U
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。! B7 W! l, X% |2 u( D+ M. p
  19.     ###实现共识算法
    2 r& H; A! Y; N
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。# |" G, V  R: o; D3 c, }! K! s
  21.     …
      m: R" L# H+ T" u  I
  22.     importrequests
    1 l# y' N- j6 Q
  23.     classBlockchain(object)
    & W) h) n' `7 _( D
  24.     …
    6 {3 q4 h# M( P3 f; d% c/ Z
  25.     defvalid_chain(self,chain):$ L" _4 Z4 }- R9 D& {; {: ]2 y
  26.     """9 M6 U! ~: o1 e  s) a4 j; }$ a- `
  27.     Determineifagivenblockchainisvalid
    ( ?; I# t, B6 h7 W
  28.     :paramchain:Ablockchain7 v% ~5 H( s  N  Q
  29.     :return:Trueifvalid,Falseifnot
    $ z) a% w$ R- D$ D4 q, F
  30.     """2 v+ j1 I; G' E- u3 w4 g5 O
  31.     last_block=chain[0]9 t8 J) ^. O7 o3 _: Q2 V
  32.     current_index=14 [5 u; }( m8 c% M
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):- ]7 Q1 c& [2 ]
  34.     max_length=length
    ; C. b! h1 k9 P! p
  35.     new_chain=chain/ r& y0 Z- t4 }; Q0 v5 y( K6 D
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours& E" L: X. [' s' `- ^. h
  37.     ifnew_chain:
    . v. A; R6 f4 G9 `
  38.     self.chain=new_chain9 C( U+ V. k! u1 d
  39.     returnTrue9 A- ^9 v  A0 \- D
  40.     returnFalse
    " ]3 P9 Y# o" ~, D, n+ Y5 T/ D
  41.     ```
复制代码
) G3 j; v4 q  o  Y- m/ j
    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。  a5 Q; h+ L9 `) H* E" S
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
1 I: q( I3 Q9 P/ A    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。
* G- j$ y9 ~) L# R0 b   
  1. @app.route('/nodes/register',methods=['POST'])' f* \% g0 _5 u6 v4 L1 ^4 }
  2.     defregister_nodes():% _3 C3 v/ q" z1 s9 ~
  3.     values=request.get_json()5 r" m9 q+ ]3 n) n
  4.     nodes=values.get('nodes')" e, L+ B3 x3 E+ U0 c2 H/ w! l
  5.     ifnodesisNone:
    4 v$ s' Y% w: F. @/ Y6 x  {( G/ q
  6.     return"Error:Pleasesupplyavalidlistofnodes",400
    : d; c" c% t( s+ k, Z: ?+ M( U  g; y
  7.     fornodeinnodes:4 L  i, e5 N: E, C+ h; C* i
  8.     blockchain.register_node(node)/ d+ J: D4 ^% [$ @# C$ r
  9.     response={" E& W% K0 H" G% v0 [
  10.     'message':'Newnodeshavebeenadded',
    $ A0 R: Q. o5 a4 {
  11.     'total_nodes':list(blockchain.nodes),2 |3 h5 W1 S* x1 n# B
  12.     }% F* U. T$ k/ U1 ^6 D) D: q
  13.     returnjsonify(response),201& ?4 S" L+ F& q* \0 N
  14.     @app.route('/nodes/resolve',methods=['GET'])  Y3 t5 R, ?- f# L  o% S- ^
  15.     defconsensus():
    7 X1 L4 N9 D0 r/ f
  16.     replaced=blockchain.resolve_conflicts()
    7 b" _/ y# A. r' I! R7 V
  17.     ifreplaced:+ n+ c9 n4 {2 @* w6 \% j
  18.     response={
    . q" @/ @6 x2 b0 m+ A+ g4 a1 M
  19.     'message':'Ourchainwasreplaced',
    0 s/ ^1 t* p3 T5 \2 Z- r+ i& u
  20.     'new_chain':blockchain.chain3 |& u5 ~9 F3 N& ^( P! x. j$ q6 y
  21.     }
    * T8 ?* v$ i2 m  n  D
  22.     else:
    " j8 F% h& e- c! ^
  23.     response={4 D- f( S) y4 k! x; |6 U/ B4 S, J
  24.     'message':'Ourchainisauthoritative',
    6 A7 i! u" Q( P! D7 _
  25.     'chain':blockchain.chain" {/ q4 R9 w3 }, s
  26.     }& P2 i$ f) a, E. o2 A1 I: o& G
  27.     returnjsonify(response),200
    ' C2 h/ |, x$ D  K& V" q+ e: i
  28.     ```
复制代码

4 N* O) O4 t/ E# ~    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
6 }. `- s' z) W4 l( b
* Q8 W  U: @8 X    -注册一个新的节点-
# G# W% J: [7 ]2 p' b    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7