Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
311 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。8 a) r: J$ C0 x
    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。& {2 E9 i% X9 j9 Y& f8 v0 _
    准备工作" z& S2 g5 y( P2 c2 k: ]
    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。9 I6 Z& o: I" }" k/ @
    如果你不知道哈希值是什么,这里有一个解释。& P2 w# N. @# ?: K- t3 P5 \
    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
; W1 [" O* K4 E    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
; A4 h  V7 c8 z# `& i: V    pipinstallFlask==0.12.2requests==2.18.4/ T0 o% g5 `# L
    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
2 T. ^" V0 @! z    一、开始创建BlockChain
) F) D1 z2 N$ ]2 P3 d    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
8 O" u4 o8 ~  S6 B) a    BlockChain类. K$ ^1 ?# i! B0 `& u
    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
8 D  y( \5 Q3 H" ~1 q# V' ~    以下是BlockChain类的框架:
! d. |1 J2 ?% F  |) {) |+ e  
  1. classBlockchain(object):* p! ^! Y# Z6 L; ~( i3 {/ j0 D7 {
  2.     def__init__(self):
      g! w6 H! A+ K9 M8 x8 V, L! \- j6 r
  3.     self.chain=[]% F/ |' H4 t3 Y  W! B. Q6 o
  4.     self.current_transactions=[]$ Z( h1 x8 w" t. ~# G
  5.     defnew_block(self):8 Z) ~4 K, }9 A2 M) V$ |5 j
  6.     #CreatesanewBlockandaddsittothechain
    6 m$ f5 ]6 C6 ?: n
  7.     pass& ~8 Y7 `5 e8 y; ?
  8.     defnew_transaction(self):
    ! [( v3 T$ l; x2 q+ W5 N
  9.     #Addsanewtransactiontothelistoftransactions
    4 ~6 I& F% I3 m
  10.     pass
      P0 L% i) M: L# t" t
  11.     @staticmethod
    2 z6 |& n) I( U% p2 X
  12.     defhash(block):  K. q! W+ Z& X: m
  13.     #HashesaBlock. C$ _- L" i2 o- A: P) B2 ?5 U% v. f8 L
  14.     pass  b$ E+ L4 f+ w/ O
  15.     @property4 H+ q9 C/ P! G1 ^: d
  16.     deflast_block(self):
    ' {( ?9 k% N6 X4 i  S
  17.     #ReturnsthelastBlockinthechain
    6 x) q: W  S5 E: W, i# p
  18.     pass
复制代码
1 S+ i, P. j' K% w. v4 ^" u
    -我们的区块链类的蓝图-
( U, a6 f  u6 ]( ~7 D    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
5 F* O  k0 B. ?+ E    块结构
9 |$ R; p% R. O' E0 I0 W    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。5 q# [% b, X- p$ q' O
    以下是一个区块结构:
+ e8 S# r" k1 s( b   
  1. block={
      H" y; d% p# N$ T$ Z
  2.     'index':1,) s# t, E/ X2 N& g" N
  3.     'timestamp':1506057125.900785,' t, u# A+ g, I* }# |+ m& k
  4.     'transactions':[
    / F' s- T$ A* e3 d8 O
  5.     {
    9 t+ _$ o3 ?  e: L; g
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    ' j6 A8 l/ p) {/ r% \0 M: Q- y
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f"," S. n2 D" ]# i3 T
  8.     'amount':5,: T7 p3 ]) k0 l$ t
  9.     }6 b, |! m/ v' P' g7 A8 b' h
  10.     ],
    7 y# K# J  f. ^) X: ]2 p9 `' C
  11.     'proof':324984774000,
    ) d; K2 C- U0 F; ^+ m3 G3 l
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", \6 u$ \' e3 ^" ]3 \
  13.     }
复制代码
9 [, I- n1 \# s& `+ y
    -链上一个区块的例子-
5 ?: T; w8 i; T" B! O    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
1 _9 C/ x$ R6 {( a    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
1 K# S/ ]! l/ w/ e+ L. i2 @    加入交易  X" Z2 _) c+ {5 y
    接下来我们需要添加一个交易,来完善下new_transaction()方法。+ A+ Y) A  O5 C/ i- n  Y) m' {
  
  1. classBlockchain(object):
    8 I; w4 r  I* G  j, i
  2.     ...% w2 l% O! l! {5 M( ?8 U6 Q
  3.     defnew_transaction(self,sender,recipient,amount):
    7 Y* [/ r1 f& _3 d# L# y/ w
  4.     """
      c( @, u8 _8 s
  5.     生成新交易信息,信息将加入到下一个待挖的区块中( \! N% O! e9 r- g9 Y
  6.     :paramsender:AddressoftheSender& e* H4 u! m+ ~5 d4 ~
  7.     :paramrecipient:AddressoftheRecipient7 u* }5 X# n, `
  8.     :paramamount:Amount
    ; o5 _9 ], ~) O# u5 ?$ ~
  9.     :return:TheindexoftheBlockthatwillholdthistransaction3 @- j4 E1 ^, \( O
  10.     """
    . c5 ^, R% Y5 [) U5 B4 p$ X6 v
  11.     self.current_transactions.append({
    " d2 ~& f  B9 s& D
  12.     'sender':sender,
      S7 E1 I4 x6 K+ R
  13.     'recipient':recipient,
    + L# a1 |5 I3 {; T) a
  14.     'amount':amount,
    6 U* O8 E" ?' {" T
  15.     })
    6 R+ ?# D' o7 ?& f& w
  16.     returnself.last_block['index']+1
复制代码
3 S' c. P& s  F% w! h0 f+ d9 s! p
    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。. W9 V' S( t+ o8 S
    创建区块; s. V* z1 G- G' e
    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
7 O, b( U& R  w. I3 j    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
8 S% O& D- x1 V# s    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
4 `  Z7 \. G% [+ ?! [1 M  
  1. importhashlib3 [& ?' I, ]8 Z4 p
  2.     importjson
    / n# A7 T# N" e0 j
  3.     fromtimeimporttime2 p4 Z* L+ r4 \2 u4 q& \
  4.     classBlockchain(object):/ H5 {/ E5 _: W  r( H
  5.     def__init__(self):  K4 K8 S1 Q8 C4 X: |
  6.     self.current_transactions=[]
    3 \2 o* i% t! J! V0 u) G
  7.     self.chain=[]7 e6 U, Z+ C+ ?' w3 Z: K% I" H
  8.     #Createthegenesisblock
    1 F, T$ K8 I7 X" g/ ~" |
  9.     self.new_block(previous_hash=1,proof=100); Y6 @# _$ n1 ?( [
  10.     defnew_block(self,proof,previous_hash=None):
    4 V/ A1 C, U5 h- w  @$ M
  11.     """7 e, |7 u- B5 K- L/ `, G( I, w
  12.     生成新块7 d  S  O$ R" \
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm; y! X" c0 _& v
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    % S+ u* a, E/ j
  15.     :return:NewBlock3 M& X" j9 R4 r1 Y, G* l
  16.     """8 N& p- O% J8 q& C
  17.     block={0 n. t& h0 m) E- [. [( r
  18.     'index':len(self.chain)+1,
    5 w5 }) r* l' o- A
  19.     'timestamp':time(),
    & Y3 Q, f+ r6 t+ Q
  20.     'transactions':self.current_transactions,% J- i4 l" B# E- D/ x0 A) k
  21.     'proof':proof,5 H; z* ]: L4 Q$ X% L9 z5 q
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),
    ; D4 [$ s! K5 d: T, `( a' A
  23.     }( Y" q& ]% x: z0 B4 i6 B
  24.     #Resetthecurrentlistoftransactions+ `' U/ |) f7 z# J( K
  25.     self.current_transactions=[]
    " T2 K. Z! r$ ]/ e4 M. \% i" T/ y% f
  26.     self.chain.append(block)
    6 j5 G9 t0 i  N% \
  27.     returnblock
    2 A, {. @) z# ]! S
  28.     defnew_transaction(self,sender,recipient,amount):
    / k# K! A* F3 b7 J% T  Q: P
  29.     """6 J3 M" a. H) T5 @
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中
    4 D9 d" P0 S4 _; }$ @7 e
  31.     :paramsender:AddressoftheSender
    : d7 _' M5 T: ]8 E* U- d
  32.     :paramrecipient:AddressoftheRecipient
    + y: d7 K$ P, b* c" T7 F
  33.     :paramamount:Amount
    0 ^/ p# D; e; C
  34.     :return:TheindexoftheBlockthatwillholdthistransaction
    ) `3 v  D  N, u# c' w
  35.     """) D3 \0 {* ]* \, N+ }' ^0 t
  36.     self.current_transactions.append({8 i+ ^' y' H* \: E% h3 [$ |9 N
  37.     'sender':sender,; C, @2 U' ?  T$ F6 K& E0 e
  38.     'recipient':recipient,) J% o0 P8 b; k: y) ]1 t2 s1 X# [3 [
  39.     'amount':amount,
    $ [5 L: G$ O2 x& X6 z
  40.     })
      i9 z3 U) f6 ?- {6 t
  41.     returnself.last_block['index']+1
    * o8 X) M* h- q! j+ h' Y. ^
  42.     @property, ]# N4 z$ Y) D3 s
  43.     deflast_block(self):
    4 A3 d6 B3 _8 a5 a
  44.     returnself.chain[-1]
    ! _* x# K5 b' _
  45.     @staticmethod
    - K" K2 ?& P6 q0 |+ R) o, d
  46.     defhash(block):
    $ _0 ^$ o6 p7 q4 g& ~
  47.     """# q. }0 U. q+ ?; N0 o
  48.     生成块的SHA-256hash值" p/ M" F) V% S4 e' Q; i6 @
  49.     :paramblock:Block* Y  W# {. `' g7 ]
  50.     :return:
    0 r) i! x; ]5 F& N$ m
  51.     """
复制代码

( m; F1 P6 p& J4 W  q6 {& Z    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes4 |1 U- F8 L7 e9 c! o7 z
    block_string=json.dumps(block,sort_keys=True).encode()
, z; j1 k" a8 q9 H! g1 r    returnhashlib.sha256(block_string).hexdigest()
- e5 }* l: w; C! z0 n7 `" j    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。2 G9 E; z0 ?9 ^; H" b( W+ |" G
    理解工作量证明
1 d1 Q* l7 K1 I! c- d    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。, b* t$ b- r% x; f
    为了方便理解,我们举个例子:
5 b$ q- d7 I/ i1 k$ y    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
# F/ z% A+ L/ ?4 H! n5 X
  1.   fromhashlibimportsha256
    % x& M: I7 `4 i7 a2 u
  2.     x=5
    - O  |9 g$ z8 `) w1 q8 Y/ Y& q
  3.     y=0#y未知
    9 x. ]4 b# O: H) K4 P
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":1 C& K6 c* @$ }& E; G! O
  5.     y+=1
    : b4 w$ }& n% K  r
  6.     print(f'Thesolutionisy={y}')
复制代码

3 a) p! C4 e5 h7 n    结果y=21,因为:- ~! c1 u* Z2 D! p" V
    hash(5*21)=1253e9373e...5e3600155e860
  I5 }6 O& |0 P! v# F    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。. _9 D% X* R, V9 K: d7 ]1 i7 T
    当然,在网络上非常容易验证这个结果。
. w. P5 B8 y% X; t9 k1 g    实现工作量证明$ y- E, M% q8 K( J: e
    让我们来实现一个相似PoW算法,规则是:! s& F6 X, n( {4 n- Q. S  z
    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。, {  H( q0 r1 r; ~! [; x$ t4 c
  
  1. importhashlib9 r" y2 q! G7 H: _- p
  2.     importjson
    & n" R/ ^, r2 A% N/ U
  3.     fromtimeimporttime" Y2 W+ G( O; Z0 }# I& S! g
  4.     fromuuidimportuuid4' J% ^5 i9 g3 H( G, ^- B
  5.     classBlockchain(object):- R4 k" F" O" H. S; o. V  J
  6.     ...  ^8 H5 z  E* W! V
  7.     defproof_of_work(self,last_proof):& I; k9 L& g5 P, E; f
  8.     """+ O! [8 v8 E& [9 L0 x7 Q
  9.     简单的工作量证明:' Y6 ^( l' s3 c1 _5 l
  10.     -查找一个p'使得hash(pp')以4个0开头
    + l% g- n% B! I
  11.     -p是上一个块的证明,p'是当前的证明
    : ^5 r5 c5 G7 E9 ?6 Q
  12.     :paramlast_proof:5 @1 R8 M' y( t6 _
  13.     :return:
    $ o' C6 ~+ P! P: y4 d( `
  14.     """. Z$ X( F4 Q+ i' T
  15.     proof=0
    & j- E  T+ a9 f( A$ C. F& O, ]9 d
  16.     whileself.valid_proof(last_proof,proof)isFalse:# O* ^0 x8 ^. x% i9 J' c. r
  17.     proof+=1
    1 d. j, j& E+ b! @0 Q# f% b6 M
  18.     returnproof
    % }( E6 f0 W, f
  19.     @staticmethod
    0 p, R( I0 b0 m- Y
  20.     defvalid_proof(last_proof,proof):: X- w1 U' d9 O' _* e8 A4 P
  21.     """
    + ]/ Q  N& G2 _; n" a
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?/ c, R6 D9 m5 M
  23.     :paramlast_proof:PreviousProof
    / H5 Z, ?' e# L
  24.     :paramproof:CurrentProof
    6 Y1 f# |6 r# Z4 b! l+ k
  25.     :return:Trueifcorrect,Falseifnot.
    ; _4 Z* c  b* s* ~% u
  26.     """
    : S( C$ E1 Y# p; o( o3 A/ Q
  27.     guess=f'{last_proof}{proof}'.encode()/ z' H( ^5 H) w$ w/ [4 F# w; u
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    ; }& _, r; [% ?" A! |4 d
  29.     returnguess_hash[:4]=="0000"
    5 w" f) K8 n; Z1 g2 G* ~# G' @: s, j
  30.     ```
复制代码
, V5 I2 m. H. |; L5 X" K
    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。+ a. F; b! ?" i9 I
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
* R( A! u- a- F( K    ##二、BlockChain作为API接口
/ Y7 d- \; |. M" V9 o    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。* o; X( E" y, Z
    我们将创建三个接口:9 f" r) k6 W- L$ u
   
  1. ```/transactions/new```创建一个交易并添加到区块
    # E; x4 F% j  g
  2.     ```/mine```告诉服务器去挖掘新的区块( D. {5 r, n3 o& g  ]
  3.     ```/chain```返回整个区块链
    # e  U/ c2 r) X
  4.     ###创建节点
    ! P) y$ B- @7 a, m
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:3 E& t5 H! v$ C
  6.     importhashlib; |. V$ ?% V1 f7 v3 v3 K1 n2 `
  7.     importjson
    ) C4 _- D- T6 m! c5 P9 n
  8.     fromtextwrapimportdedent; b2 p9 m3 \& t- z
  9.     fromtimeimporttime
    ' z  X8 b+ t5 @  G
  10.     fromuuidimportuuid4
    6 T( R! ?+ `& w2 L! y, I& R
  11.     fromflaskimportFlask- Z6 n0 S: J5 m& C: I  Q* ]
  12.     classBlockchain(object):' Z% S  k( p; E( }8 e4 i1 m
  13.     …) q% J; Y) `* C
  14.     InstantiateourNode- v" A: q8 ^$ f
  15.     app=Flask(name)$ d; f/ k  a" Q" A  A
  16.     Generateagloballyuniqueaddressforthisnode
    ! Z  t" m4 D$ x
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)
    & \9 c: x3 I6 _  S8 ?/ u# E9 M
  18.     InstantiatetheBlockchain' ]- ]. U* ^! F& V8 _
  19.     blockchain=Blockchain()1 b, _8 P9 }0 n( o  ~% H
  20.     @app.route(’/mine’,methods=[‘GET’])
    & ?( ?! y% c8 J7 R. ^* Q$ m. D. j
  21.     defmine():
    6 ~. l) l% a/ K# K- F# b& E
  22.     return“We’llmineanewBlock”9 _/ {0 u7 a& j% E
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    5 w$ g  o/ j+ n  t$ B4 d* R+ ]+ \
  24.     defnew_transaction():
    , Z4 t4 I( Z& A0 D4 O8 C
  25.     return“We’lladdanewtransaction”
    5 l" i! L. j/ N' l* u- N2 b
  26.     @app.route(’/chain’,methods=[‘GET’])7 p5 G5 S7 ]0 E# w  m' Z: R
  27.     deffull_chain():
    % e2 J# k- w5 W
  28.     response={
    * [3 w' O/ u! b2 V* z0 x
  29.     ‘chain’:blockchain.chain,
    9 z/ |6 m) Q( M, J& O$ o
  30.     ‘length’:len(blockchain.chain),* w5 _2 t  T: l  N
  31.     }6 v; u( p; V2 E# \, N. F
  32.     returnjsonify(response),200
    ; `1 s5 v) H1 j/ Q
  33.     ifname==‘main’:! P* g4 _+ Y" E0 x; C2 X1 e2 j
  34.     app.run(host=‘0.0.0.0’,port=5000). r' p7 v/ u; a' z9 J0 ~
  35.     ```
    $ \. k+ v8 M5 \2 e
  36.     简单的说明一下以上代码:
      N0 l* M& Z+ t) a+ L1 ~
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。
    ; s9 M/ N! k* N7 [0 I, A
  38.     第18行:为节点创建一个随机的名字。- q" b6 V8 B* k% L1 _
  39.     第21行:实例Blockchain类。1 l  S4 T* ?$ x3 z. d5 h! C
  40.     第24–26行:创建/mineGET接口。5 P% q' ~' f* w* j. f3 C6 x
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。; p4 ~/ ~+ s$ g: p: \3 W2 L0 v1 \
  42.     第32–38行:创建/chain接口,返回整个区块链。
    ' y- |7 h5 L  Y% B: ^
  43.     第40–41行:服务运行在端口5000上。
      [: Z+ t* ]+ |8 @% \( Q
  44.     发送交易, i5 J* {$ t, G; J  D' w0 v1 ^6 P0 V
  45.     发送到节点的交易数据结构如下:  m) ]' i5 X  a0 R' ~
  46.     {
    ; v4 [5 i) `# S
  47.     "sender":"myaddress",
      V7 E0 H* E8 z$ L" A+ g
  48.     "recipient":"someoneelse'saddress",
    4 X6 V0 {- C5 d: U/ G+ s4 o5 o
  49.     "amount":5
    - k4 \3 U2 A6 h
  50.     }
    . z. L5 f1 L) ?. q0 O+ }9 h+ G
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:
    . P0 R0 r' ]" s' B, O
  52.     importhashlib: W( d6 P* W" o1 ^/ e
  53.     importjson' B9 ?0 I7 P. {# d7 B, X
  54.     fromtextwrapimportdedent. H# Z7 `' H2 c0 \
  55.     fromtimeimporttime
    " F) O( w7 {) }6 ~  ?) Q
  56.     fromuuidimportuuid4
    ) Q1 [' x: z1 M2 b6 z  @$ ^; `3 j& R) \
  57.     fromflaskimportFlask,jsonify,request. k9 b( b- ?- l' T5 s2 K6 }. Q5 [0 M
  58.     ...
    % r- F7 c5 w! T; Y) I  ~- g
  59.     @app.route('/transactions/new',methods=['POST'])
    ' N! `- \1 ?& |
  60.     defnew_transaction():% O' a: M+ T8 _
  61.     values=request.get_json()* G- O5 P6 I* B, [9 H
  62.     #CheckthattherequiredfieldsareinthePOST'eddata/ j& n$ e/ O6 k/ w* {. Q0 j
  63.     required=['sender','recipient','amount']' G+ P& A5 b. Q
  64.     ifnotall(kinvaluesforkinrequired):) Q+ k0 _0 G# {. p6 P2 o
  65.     return'Missingvalues',400
    ( H# o" K. g+ }1 y3 f/ {3 x* N
  66.     #CreateanewTransaction# d# r" K% e; A
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount']); w, c7 t- X# f. d" u0 m
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}
    % B0 ?3 r# a, H
  69.     returnjsonify(response),2013 U% b8 \9 ?+ g2 i( o, c- S
  70.     ```# m3 o) A9 K: T, O: n6 K* w. z( a
  71.     -创建交易的方法-* |- n# t4 V8 g" h5 q
  72.     ###挖矿
    . x4 ]  |, ]/ d  }/ b9 z+ _" [
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:
    / P  F, M5 G( X, ?& }( [1 |
  74.     计算工作量证明PoW
    - p, ?+ Y/ J4 O( K2 p+ ]2 J
  75.     通过新增一个交易授予矿工(自己)一个币6 y6 z% Q7 z* \+ ]. a, y+ q* E
  76.     构造新区块并将其添加到链中6 W7 y. k' F3 T2 v' P8 H2 F8 c) W
  77.     importhashlib
    3 _- a  k2 [# }  C( S- J1 P
  78.     importjson
    8 u/ l: X) `9 t8 d* e3 V
  79.     fromtimeimporttime
    ; K. `6 }0 e, h
  80.     fromuuidimportuuid4
    & Z9 \% a: y, x4 Z' C
  81.     fromflaskimportFlask,jsonify,request
    # {3 Y, W1 a3 H2 @: P  Y( v
  82.     …
    ; E9 J# _7 g0 `; D5 W6 F/ c
  83.     @app.route(’/mine’,methods=[‘GET’])
    - J+ L3 p; C/ x" v( ~8 s
  84.     defmine():7 j& ~& M3 e3 h1 h, f. k
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…
    1 P  r, t& e5 W( }  x+ A
  86.     last_block=blockchain.last_block
    ) d2 i! `8 N6 b) d. w
  87.     last_proof=last_block[‘proof’]
    0 T7 L( l7 s$ ^
  88.     proof=blockchain.proof_of_work(last_proof)6 C  g8 ]7 `: P3 x9 m+ ^
  89.     #给工作量证明的节点提供奖励.
    ) O0 b# T4 y8 o
  90.     #发送者为"0"表明是新挖出的币.
    " K3 G- V3 V9 E" B1 P
  91.     blockchain.new_transaction(* B) }5 t9 j! Z8 H1 t8 A
  92.     sender="0",: z# ?; x1 G4 {" C
  93.     recipient=node_identifier,1 Q# R$ I4 i* W. ^2 W6 ]
  94.     amount=1,
    6 Z6 I( [, v  Q  Z$ M! l
  95.     )
    8 S, x- i, G9 N
  96.     #ForgethenewBlockbyaddingittothechain5 p. ~7 N  r. \3 g
  97.     previous_hash=blockchain.hash(last_block)
    ; e0 H" W- V0 t0 v& [
  98.     block=blockchain.new_block(proof,previous_hash)+ O1 U+ {  N8 p9 R1 L7 L3 X
  99.     response={
    0 @* \7 w: {. z: l; D/ {
  100.     'message':"NewBlockForged",* `" M2 `; O6 i! w. C
  101.     'index':block['index']," K0 |" S+ Y8 v& V4 u
  102.     'transactions':block['transactions'],# Y0 M. c, [# I  q: c
  103.     'proof':block['proof'],! h4 [# t! I- c& L- y
  104.     'previous_hash':block['previous_hash'],
      e2 E) G/ J; [9 H6 P0 t" g
  105.     }
    ) ~: I, e7 o  C
  106.     returnjsonify(response),200
    7 F2 X3 r/ S: T9 R% z: d1 k, H- {5 Z
  107.     ```
复制代码

, F$ D6 p- \! M9 e! Q8 W6 Y$ [    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。
& R# L$ u; e% E3 j+ G    三、运行区块链
. `& @8 f! [3 ]% L* j    你可以使用cURL或Postman去和API进行交互  }- n& e) k- }% [2 a
    启动server:
* Q$ c/ z" r$ C5 H7 C& r    $pythonblockchain.py4 w# C4 _' W" E2 B! H1 Z) Y5 k
    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)' M- d  G* g0 _+ i" F) ^) r
    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:/ l# m! E. e$ S3 u9 P# Y- x
    -使用Postman以创建一个GET请求-& S) A& a* O6 @
    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:5 R! m2 U) H5 d7 t
    -使用Postman以创建一个POST请求-4 j7 a! i  l1 A5 l. ?
    如果不是使用Postman,则用一下的cURL语句也是一样的:- Y2 Q; w4 E5 }  Y; g; h% X
    $curl-XPOST-H"Content-Type:application/json"-d'{7 j& Y* t* t# z' w. s9 m
    "sender":"d4ee26eee15148ee92c6cd394edd974e",
; f2 w% }( H- }1 [    "recipient":"someone-other-address",
# [) {( ]% u5 U+ E1 t    "amount":5
  I( T* H* b" s# j    }'"http://localhost:5000/transactions/new"
/ k4 \/ f1 M  z7 O    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
" }# O) _  t; L& U* D   
  1. {
    , J: H( s0 z! K1 r4 v
  2.     "chain":[9 d5 o" g% H% @, M: X8 A
  3.     {+ j+ f+ R0 c- i, [" L
  4.     "index":1,
    - ~6 d9 N0 f' ^& t3 k
  5.     "previous_hash":1,' n/ w' E' E4 e: ]5 j9 U& ]' R
  6.     "proof":100,8 j! A' \0 B8 v5 A+ Z
  7.     "timestamp":1506280650.770839,
    ! ~% X6 ~  L  p; U8 @
  8.     "transactions":[]) z/ v, ^6 c5 A3 F5 x4 g1 m
  9.     },
    3 x$ I; A9 F" c+ c4 J
  10.     {! y* z1 k5 Y' ?& X- T; ?
  11.     "index":2,2 s( A3 c0 K! ]( Q, }# ^- U
  12.     "previous_hash":"c099bc...bfb7",
    , n; @) a& f5 x( e% Y5 U) ^  E
  13.     "proof":35293,
    : {% h3 r( G3 \% k  M4 h
  14.     "timestamp":1506280664.717925,
    4 V4 F4 b7 N! ~* r5 T
  15.     "transactions":[
    5 p& [$ f( w' b0 f/ m: L5 L7 ]
  16.     {
    + I' H; c' r3 z5 Z1 a
  17.     "amount":1,! X$ ]+ ]( V: u+ ~0 V4 Y
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    " F, ^, a# L0 B* l# Z# |( I( _" F
  19.     "sender":"0"$ f! u( S0 ], G7 f2 i/ r5 [
  20.     }6 t# n1 @! W0 o1 v+ ]# i: O$ F
  21.     ]6 B: o: S3 ?& l$ I! A
  22.     },
    , N* L. M$ `' P; [
  23.     {) s, e, b+ k; {$ c; w
  24.     "index":3,0 `0 {+ P- }. R3 |/ S$ f5 g1 p
  25.     "previous_hash":"eff91a...10f2",
    - K8 K' K0 u* `# G0 s. S
  26.     "proof":35089,; {7 B4 ?0 }1 G( {" |
  27.     "timestamp":1506280666.1086972,
    + v% p+ Z: R8 i3 B& J" O
  28.     "transactions":[
    , v$ r+ M& t, t
  29.     {
    . n; `( D9 }% t. N. T
  30.     "amount":1,
    % x1 q) l/ d0 B3 v; `$ ]
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    - |0 O& r. k! \0 Z; a+ j  v
  32.     "sender":"0"' }+ ?( ~1 ?1 K; r3 X$ `
  33.     }( W* ?2 m* L) V5 D$ S: ]
  34.     ]
    " ^) _. M9 Q& P  t8 y: v# u5 @
  35.     }' c  [% |* |- u, B, T
  36.     ],
    , T! a3 r( z! H/ p' \
  37.     "length":3
    " ]" f8 l/ q! V- v: K2 g
  38.     }
复制代码

8 _0 E- W# z& T5 Q+ \    四、一致性(共识)
) @8 Q9 S8 x& z9 F8 x+ a    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
9 B6 o% a5 `& ]' }9 i  y6 t( ~    注册节点
+ U6 m! I% [5 u# V' [    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
4 u8 a9 d0 Q- I) ?" T    /nodes/register接收URL形式的新节点列表5 G. [6 P& t& ]* \; K: {8 ?
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链; r1 ~6 }% n6 c7 f9 T+ H$ c
    我们修改下Blockchain的init函数并提供一个注册节点方法:& j8 w  M7 n# T2 m3 y1 ~7 g
  
  1. ...
    4 x7 t$ a6 K, h7 }: b* V
  2.     fromurllib.parseimporturlparse+ e, j4 N, K* ~+ |6 l) r
  3.     ...
    , }. W/ O/ t! a1 z# u% b. r
  4.     classBlockchain(object):: o! x2 _, H$ T. J5 B6 d3 o
  5.     def__init__(self):3 f  r. J6 h% D; e
  6.     ...
    $ a5 }- s& c; R
  7.     self.nodes=set()
    9 g# P) W3 d2 w; I
  8.     ..." X5 ?7 N8 J6 Y( J+ u
  9.     defregister_node(self,address):
    8 c) X* W$ W, K1 K
  10.     """4 n1 @) ~4 }; h1 G( z1 C
  11.     Addanewnodetothelistofnodes- @2 H6 x) p( \! i6 u. j( I
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'' L' ?* v6 c& O# Q4 {2 h
  13.     :return:None# B4 x$ v' S$ E- @0 q9 |4 @
  14.     """
    1 X( w6 S" ?) V1 f' B
  15.     parsed_url=urlparse(address)  h7 ?( i) J+ a  ^0 y
  16.     self.nodes.add(parsed_url.netloc), p) d/ }% i) @2 P& R5 j
  17.     ```) c! l+ T4 X8 M4 w! T; ]
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。7 B9 Z6 _# Q  A# o9 ]" b- a. u
  19.     ###实现共识算法
    & Q& C9 J. Z% }8 z" @, ~
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
    ' h2 o6 l* O( {0 C2 d
  21.     …
    6 m  v' L5 Z0 o1 m
  22.     importrequests5 s) M' H9 _) Y! H  Z; r, k
  23.     classBlockchain(object). D* @2 V, u5 t# m/ G' X; y8 d
  24.     …
      m5 y* G! h( q9 j! l* x
  25.     defvalid_chain(self,chain):# j: Q/ r: L5 R2 ?
  26.     """
    9 D! O- {$ p! V4 J: W
  27.     Determineifagivenblockchainisvalid) n2 _3 \. u( M5 q: T4 A. _& m8 [# ?
  28.     :paramchain:Ablockchain
    . G- S: r, o* W! [& }( \
  29.     :return:Trueifvalid,Falseifnot. c1 ?- b9 Z3 P' d0 X$ f9 f
  30.     """' g+ o  T1 Q- S- z$ f. ~9 C
  31.     last_block=chain[0]/ E% u" {9 g2 Y, F( m( z. [' P
  32.     current_index=1! f: i. k7 ?" I* W& C9 C
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):9 ^+ b+ h" [( Q& x3 j
  34.     max_length=length$ @- y, d  r2 W9 Y
  35.     new_chain=chain3 T  w* _5 p3 ^
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours
    / z  S( K7 E; `1 X' |# f) B. V0 S
  37.     ifnew_chain:
    3 D6 B7 G. L1 M% V) F
  38.     self.chain=new_chain
    5 W' z. z& V* ]" {
  39.     returnTrue' U3 U4 j' d4 H2 s1 j  ?' `
  40.     returnFalse
    1 L: y! Q! ^0 c& d  w4 v& m. _! K
  41.     ```
复制代码
! u. V) l( c  m2 ?' n, }  R
    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。& r8 _; C5 ~3 h$ ]% a: T8 N
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。: L  ~' k$ V% S4 j2 W% U* j
    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。
# j* a% H# G: H* D* z6 x2 |. T   
  1. @app.route('/nodes/register',methods=['POST'])
    9 m0 [5 L8 R6 p. ?
  2.     defregister_nodes():
    $ N9 l, r5 D6 X2 ]; V
  3.     values=request.get_json()  S1 n$ Z0 O6 F, C# L
  4.     nodes=values.get('nodes')& Z8 `/ @, \$ K5 }$ Q+ w
  5.     ifnodesisNone:
    2 f9 C. D3 L1 ]
  6.     return"Error:Pleasesupplyavalidlistofnodes",400' h+ X2 M# r# \+ H0 }. ^& {, H% J
  7.     fornodeinnodes:
    " q5 ?% {, M4 W& J. d
  8.     blockchain.register_node(node)
    0 F- q/ B$ k: p4 f, x
  9.     response={
    # Y8 \& H$ v0 W5 k
  10.     'message':'Newnodeshavebeenadded',
    8 m' h  I+ c5 w, E
  11.     'total_nodes':list(blockchain.nodes),
    4 E: m; ?  n$ \( Z
  12.     }
    5 x5 P6 l; h: D* ^: j) `
  13.     returnjsonify(response),201, Z! d& k! [! h' K
  14.     @app.route('/nodes/resolve',methods=['GET'])
    6 J' g4 H! n/ W/ Y; e$ m
  15.     defconsensus():
    1 v7 I2 b! t9 v3 v
  16.     replaced=blockchain.resolve_conflicts()4 I( `, o) q1 h# S7 S2 }# O
  17.     ifreplaced:
    " M* u6 T, \& F
  18.     response={
    ; K# H+ O* @& W; J
  19.     'message':'Ourchainwasreplaced',
    * C7 o+ W. K) _
  20.     'new_chain':blockchain.chain
    & p( ?* U  a' P! h" {! y6 S' ?5 H
  21.     }
    % p0 `8 k8 v# ]3 S
  22.     else:
    - c2 v5 A' Y! k5 S
  23.     response={
    * D8 C3 ]/ Y/ y  y% L, @$ s! u
  24.     'message':'Ourchainisauthoritative',. o& h' ?: k( N# G2 h- L
  25.     'chain':blockchain.chain, [, N5 `4 v* I- u8 r- g" |7 k" [
  26.     }
    & N+ u" M. p0 V1 _/ R: V* k
  27.     returnjsonify(response),200
    ! U( p; Z4 f! Z! {
  28.     ```
复制代码
/ t7 c5 t+ x3 z& n
    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
5 b( Z2 d7 m8 _2 K* g % S- X$ c7 f  k
    -注册一个新的节点-
0 l4 C8 o' o. n% ]$ w) I' T    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7