Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
454 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
/ N, n  X8 i# r$ S    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
/ B0 u/ G+ A& b# D: Z    准备工作' z4 ]) a/ X8 ^) {: O& }6 G3 w
    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
9 ]$ X) p& d" K0 Q& w! W    如果你不知道哈希值是什么,这里有一个解释。
4 u/ j  ^' G1 ~' A    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
& P. [& `3 j2 L7 D7 v  n6 j    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:
" G, r' e0 k) i; B7 `    pipinstallFlask==0.12.2requests==2.18.4) m* j% h, o7 T# y- }! c
    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
. D- @- E& V7 }( ?+ Q: I3 P    一、开始创建BlockChain9 g( g% K' h  K( W( ~" q. j: c
    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。3 I% `# N' x8 P, g. Q1 X% l
    BlockChain类
  X, i; U9 ^# B    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
7 ^, r' B9 r  Q. T" ~% U) a    以下是BlockChain类的框架:. c1 e# c1 v7 N
  
  1. classBlockchain(object):
    5 N# b# [+ y# ]
  2.     def__init__(self):
    1 [+ z3 l1 s, b' t4 e- E* J  |, R
  3.     self.chain=[]
    6 q& B7 P" L' k$ f
  4.     self.current_transactions=[]
    % Y9 O& Z0 m8 w! X
  5.     defnew_block(self):
    0 ]* l( K, K2 G' J
  6.     #CreatesanewBlockandaddsittothechain
    / }; Y7 o/ j, F9 @' E, |
  7.     pass  K! \$ {7 t3 S0 u  W7 a' t
  8.     defnew_transaction(self):: I9 p1 S9 y- |1 P9 ]- m& P9 e
  9.     #Addsanewtransactiontothelistoftransactions
    * i- e2 r& w' M
  10.     pass
    ' p/ {, b& R0 S" n( Y, o
  11.     @staticmethod& M! m- _; a. o# d
  12.     defhash(block):
    # T( E" Z; |+ M) m' s0 S
  13.     #HashesaBlock
    8 g3 _9 V: [1 t4 T# X  j; f8 c2 p
  14.     pass6 C- Z5 q& Z' F6 F; W9 i
  15.     @property8 N4 S0 w! [5 D
  16.     deflast_block(self):
    + W! J  u" ~/ T6 W6 V6 [
  17.     #ReturnsthelastBlockinthechain
    % w& O8 N! B$ P
  18.     pass
复制代码
8 J/ s+ f- }2 ^. F* o
    -我们的区块链类的蓝图-
8 l- i6 e; V# ]. c  A    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。3 s" D8 w% F5 U; A
    块结构2 N8 B! g" D2 o( A, a' h
    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。" r( w4 e4 ~2 t
    以下是一个区块结构:9 E* G4 k- H- m- T( ~3 G
   
  1. block={
    ! v) {# z; |( w3 W/ S! E8 v* k3 W
  2.     'index':1,
    ' M  d+ v; j  h4 C: {
  3.     'timestamp':1506057125.900785,, d8 F2 p+ u( N1 @& \4 g
  4.     'transactions':[
    : V3 l0 ~3 l6 Z. U, W$ U% s
  5.     {6 F7 Z# o9 T, ~& C/ I% Y# Z5 d
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    9 Q$ V. i4 q3 N9 o8 B% {( P
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
    ' Y8 b& x  I+ b, \* l' Z
  8.     'amount':5,1 h; o) m5 T* D# r  {6 d' c
  9.     }
    . E/ [& g0 G& j9 d! \
  10.     ],
      V/ |: g6 l5 P' R$ O
  11.     'proof':324984774000,
    $ @9 {, q' ^& f) H0 k1 A: k$ \: u- ^
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"& C* @) Q6 B# m4 s& U
  13.     }
复制代码
" i9 e+ |+ g& d  h. K( S; Z( o1 r
    -链上一个区块的例子-
$ z6 Y! @1 y' p! I  f; U    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。$ F5 Q7 a) |6 n: B3 e
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。' D+ Y0 r, f' B) a; s1 D2 m" P' E
    加入交易
+ L8 Q& z& o3 P8 s/ ~  ?3 S5 S/ |2 N    接下来我们需要添加一个交易,来完善下new_transaction()方法。9 X( a8 M; m5 g" f5 s$ s
  
  1. classBlockchain(object):4 E  G1 N) _. l, a; ~5 q
  2.     ...2 k8 B% T$ y! ~
  3.     defnew_transaction(self,sender,recipient,amount):
    , w% T2 y+ e$ q* x3 X' |
  4.     """) U1 x7 W/ W4 ^7 ~5 {) P
  5.     生成新交易信息,信息将加入到下一个待挖的区块中
    9 ?" _5 Z/ d  e: A/ V3 [' C; n
  6.     :paramsender:AddressoftheSender$ Z2 u8 E  P: [6 N8 H7 n+ t4 N0 p/ s
  7.     :paramrecipient:AddressoftheRecipient1 E* w9 h0 X% s: n3 N
  8.     :paramamount:Amount
    3 V& D# W/ [+ ~7 m# P
  9.     :return:TheindexoftheBlockthatwillholdthistransaction. L) Z$ F  {4 V4 {
  10.     """
    5 m) O* `% J: ]
  11.     self.current_transactions.append({# E! S& H5 }2 m. q% F( H" n
  12.     'sender':sender,
    6 S& r' E, W- Y- m# V
  13.     'recipient':recipient,2 s2 a& N- U0 o" g
  14.     'amount':amount,
    ; A6 ^* @2 {- `& d$ V
  15.     })
    $ {9 J  Y8 ]" M8 p
  16.     returnself.last_block['index']+1
复制代码

. D+ v7 ]5 l. m- K5 J0 G    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
) B7 I; ~+ ]) w3 M    创建区块+ D3 x7 S) B  V
    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
7 Y- j! X/ K6 Q    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
& N8 w( L" l2 {' ]1 f' Z" d    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
5 h: s3 k7 f4 o2 a8 p3 |  
  1. importhashlib
    ) g- a* r8 m7 N& r/ U
  2.     importjson
    % S  p; u; C8 j- _
  3.     fromtimeimporttime
    9 k- U. f7 }- S, Y" _
  4.     classBlockchain(object):4 y6 ^1 b. @7 M8 C
  5.     def__init__(self):  D% I+ f' Q, c' W9 M) a
  6.     self.current_transactions=[]* c& ]; V' L, U. O. Y' C* Q, b
  7.     self.chain=[]
    ( A5 y3 z- D. ?% @1 o/ q$ J% `% I+ \
  8.     #Createthegenesisblock
    ! ]3 p/ Q) |+ u7 `5 d8 @
  9.     self.new_block(previous_hash=1,proof=100)5 M& _/ b6 f1 H( n' r+ J. U
  10.     defnew_block(self,proof,previous_hash=None):
    % A# \; _( V6 b4 b
  11.     """$ ?/ C4 Q8 K; H0 v9 m  x
  12.     生成新块
    2 H$ z  h+ `1 m/ Y% v# l  z( U
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm4 A* B& O* O! {8 A
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    6 d: V/ Z# \+ |
  15.     :return:NewBlock4 @* E) o$ }% C2 Y! L
  16.     """
    / u  j- Y& ?3 z( C, Q: X
  17.     block={9 `  ]! ^3 C8 C5 ?8 e2 Z: G+ u
  18.     'index':len(self.chain)+1,
    3 Q) G  a$ L2 Q! l
  19.     'timestamp':time(),4 e, C" C4 {5 d+ Z6 M! G1 L- |
  20.     'transactions':self.current_transactions,# U0 w/ p0 T1 N7 V1 O) Y, f' s
  21.     'proof':proof,
    4 [  j& F) y$ U) Z" a9 a/ Y
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),
    & p9 r& L1 @7 Q! e; o: H8 j/ o: Q& K1 b
  23.     }
    # Q: Q* r; @+ |# j+ q) z+ A
  24.     #Resetthecurrentlistoftransactions
    4 a6 g! k% A. L/ H2 \& Z
  25.     self.current_transactions=[]: Q& Y( [6 ]  h/ z/ W
  26.     self.chain.append(block)0 E! J( ?" R' L( H
  27.     returnblock. F# _# J+ v9 l7 m: s1 m$ o0 y
  28.     defnew_transaction(self,sender,recipient,amount):
    5 |- q# F7 s) x6 k: o5 |
  29.     """
    ) R8 Z2 F3 c; @/ V- U0 x
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中; \9 g& I5 k/ }5 P! n" A5 E
  31.     :paramsender:AddressoftheSender# i6 P4 H& T9 f& ]* N7 V! ?9 |
  32.     :paramrecipient:AddressoftheRecipient
    ) N5 N& b$ A* n6 F" j
  33.     :paramamount:Amount! X/ G+ `4 `$ ]
  34.     :return:TheindexoftheBlockthatwillholdthistransaction
    5 R. }( ]3 R2 Q
  35.     """
    , V& a# @( L! ?" C' @! o
  36.     self.current_transactions.append({
    $ `& y& V' P) [: p2 z
  37.     'sender':sender,
    ! g9 Q& C* K7 c2 J7 A
  38.     'recipient':recipient,; U" u4 q: e( e; v9 Q% `
  39.     'amount':amount,
    ! d. o( h- Z- |0 g! K
  40.     })
    + A, H* Y. H& q9 T( ?  z! a0 U3 f
  41.     returnself.last_block['index']+1
    / z% h5 O* c/ D1 U/ x$ ?
  42.     @property
    6 Z9 ^& F# Y7 o& P: j2 }
  43.     deflast_block(self):& C' x; p# R9 ]5 j0 D
  44.     returnself.chain[-1]
    5 h* T3 D. \5 B. {' |" z& ]0 i. n
  45.     @staticmethod% Z3 y. v, M! {/ D2 Q% M
  46.     defhash(block):
    4 W+ w* p* ]3 U
  47.     """1 D* g1 n- [) k
  48.     生成块的SHA-256hash值
    4 W! m. k$ a8 P- }3 e9 L9 z4 [, Q
  49.     :paramblock:Block
    ! P. \! k' q' `8 g( F$ P' c3 |
  50.     :return:! V& m$ a7 s+ R$ n/ t+ a2 K/ o: G
  51.     """
复制代码
' {( G, {; \2 \! B4 f( j& v. _0 c
    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes& ]# Q: @8 D( w( w$ j
    block_string=json.dumps(block,sort_keys=True).encode()
9 _# l3 w( T; O. L3 n2 V1 x    returnhashlib.sha256(block_string).hexdigest()4 Y2 @* i! s- r, @- R& C
    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
! h; {3 o3 y8 y2 w0 ]" J$ e- ]4 E2 B    理解工作量证明& x7 X" e" w2 u
    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。5 @7 w! h* q& C$ |1 c% ?' G
    为了方便理解,我们举个例子:
' [5 P5 t2 e, L- U$ d; G    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
' L$ N8 {  l" l' ~
  1.   fromhashlibimportsha256# Y5 s9 j/ E& G* J: ]
  2.     x=5; A" P2 [, |0 ]3 ?+ {5 b
  3.     y=0#y未知- a$ `, x# J/ s9 C, F- |
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
    9 D! }- m0 K! T, ?; k0 p: L
  5.     y+=1
    ! n1 X! p. b3 {# ]" }7 l) @
  6.     print(f'Thesolutionisy={y}')
复制代码
' J3 T, [% M) ~0 n
    结果y=21,因为:
8 q  B7 l7 F/ p+ l, h; [, M) H    hash(5*21)=1253e9373e...5e3600155e860
9 A# U' X& H2 j% I  Q7 W    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
0 Z- A- \; Z7 l) L( f7 K    当然,在网络上非常容易验证这个结果。4 O$ @2 V  ^. ]3 ]; y
    实现工作量证明0 F: ^* c9 A; X
    让我们来实现一个相似PoW算法,规则是:
$ H! w+ U% H2 M; h, T" A& i    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。3 p  b0 s# H. H. u9 q* S5 ]" p+ S
  
  1. importhashlib# }7 V" O- A1 M  `% L, v
  2.     importjson8 J: B1 p6 ^9 M! q6 b2 b: u+ V
  3.     fromtimeimporttime
    # T2 d2 U6 F3 Z, v9 {4 g
  4.     fromuuidimportuuid4
    : `2 F5 D5 A" ~7 z8 S* u8 m
  5.     classBlockchain(object):+ `9 z' X1 N, \" {
  6.     ...
    4 L/ T. r& M' x1 Z1 |
  7.     defproof_of_work(self,last_proof):, _* J; H7 m# I# s! g( W
  8.     """- \% M$ l  F/ x! Q% ~
  9.     简单的工作量证明:
    1 W" D- [; h+ S4 ]4 k0 b3 b* m! F
  10.     -查找一个p'使得hash(pp')以4个0开头
    4 z1 {& y( m/ ]
  11.     -p是上一个块的证明,p'是当前的证明+ }2 v: U/ f- ?. F6 w* L) I
  12.     :paramlast_proof:2 @$ d: b% G9 K
  13.     :return:
    $ f8 e! k. i3 R5 ^
  14.     """
    9 U% ~+ A! T. f& S  ?
  15.     proof=0
    8 W- O2 f! }; H3 S$ U/ \( _* G7 A
  16.     whileself.valid_proof(last_proof,proof)isFalse:
    1 J  E$ P' L3 V9 A+ C+ x' L' e
  17.     proof+=10 g8 B& \. g$ ]& m3 B1 y# j
  18.     returnproof
    ; D* P' s2 e% i/ G# J
  19.     @staticmethod( I) x6 _: d/ S% W, @
  20.     defvalid_proof(last_proof,proof):& j# n9 F9 h' @
  21.     """8 j. t/ A6 t. R# y1 t
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?! q) s! Y* S2 n: N5 B2 K. w0 t
  23.     :paramlast_proof:PreviousProof6 @" I; x3 l. }+ X6 l0 X5 b  [
  24.     :paramproof:CurrentProof
    2 b. X2 A& P& N" |
  25.     :return:Trueifcorrect,Falseifnot.: R$ ]# R: w. j8 y
  26.     """% Z4 _% ^9 D9 t4 t* a
  27.     guess=f'{last_proof}{proof}'.encode()
    % y, v0 s0 j- S$ G% F- d; z1 h
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    3 I% x# K4 D2 ~, N; H7 p
  29.     returnguess_hash[:4]=="0000"  w, t; I5 r2 O/ v
  30.     ```
复制代码

; J& j. b# ^. \1 {- [' X    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。1 ~2 H2 q( F! f4 Q$ {8 c) c" b, R
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。$ e7 i% s8 A/ i+ d3 R9 N
    ##二、BlockChain作为API接口
2 u! [4 t$ o% u0 V    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。2 k4 |5 w* O: D3 |- |
    我们将创建三个接口:
9 q, a3 ]4 T' E3 u   
  1. ```/transactions/new```创建一个交易并添加到区块
    9 }$ U! l3 @+ r* I8 P
  2.     ```/mine```告诉服务器去挖掘新的区块3 x+ K+ C9 z4 t3 \  `
  3.     ```/chain```返回整个区块链2 D4 o/ }8 V) b! j  x
  4.     ###创建节点1 h1 G  H% h6 j2 O, H
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:6 e' w- K4 H& G  [
  6.     importhashlib. q/ r& p; k2 i0 f0 L. e
  7.     importjson
    6 ?  A0 z% }, |
  8.     fromtextwrapimportdedent
    ' z9 c$ O0 n$ z' A) o
  9.     fromtimeimporttime
    9 k; f! M4 @" R
  10.     fromuuidimportuuid4
    ( l2 o7 W* e" c8 T* a
  11.     fromflaskimportFlask
    ' U. p1 t  W8 w  ?. d
  12.     classBlockchain(object):, ^. {9 a  \) M7 ~$ A/ ]* ~$ q
  13.     …
    & D8 F$ {& |- o0 _" P' ^
  14.     InstantiateourNode
    " N7 k( a" j( m; }8 I: R2 c$ }4 O
  15.     app=Flask(name)
    " T+ `5 O4 M. ]5 n1 Y3 c& l( G6 S
  16.     Generateagloballyuniqueaddressforthisnode
    / l4 D  V* b' x+ m7 y
  17.     node_identifier=str(uuid4()).replace(’-’,‘’): R1 M+ i% |% u0 E
  18.     InstantiatetheBlockchain8 V% }. W6 O8 U1 E: Q/ E6 `" U
  19.     blockchain=Blockchain()
    8 S% k9 L" k& R. {) X) D; w2 r
  20.     @app.route(’/mine’,methods=[‘GET’])
    % g* c$ I1 @/ [+ U& k& k: p
  21.     defmine():7 t) U  t! H  J1 e- H
  22.     return“We’llmineanewBlock”$ T7 X1 s- K1 [' D" D  }
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    3 T) k- a" t) O0 @6 }
  24.     defnew_transaction():
    + t# \3 H8 s, L0 Q- p1 Y8 n' w
  25.     return“We’lladdanewtransaction”
    9 Y1 D+ S& D" N) m7 a
  26.     @app.route(’/chain’,methods=[‘GET’])& }/ r" ?( |. E4 m# C0 w0 D. r
  27.     deffull_chain():( I) U1 g. p- l+ W
  28.     response={7 A# [( H* o- l# {' J
  29.     ‘chain’:blockchain.chain,: k* ]2 M. W- ?, D3 p& O! v$ _& r  D, c; T
  30.     ‘length’:len(blockchain.chain),
    * ?, Q2 Y9 O* J" D/ o
  31.     }
    7 M! y# a) C7 g/ A8 s
  32.     returnjsonify(response),200
    2 G) \  _3 X, O" e% w  j
  33.     ifname==‘main’:- ~5 R4 N/ z9 ^' y6 l9 s/ l
  34.     app.run(host=‘0.0.0.0’,port=5000)- O5 g, ]  t( G# e
  35.     ```
      _" Y0 `3 A. k! k. |0 ]
  36.     简单的说明一下以上代码:& E4 K$ l; N$ E. Q8 R. X
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。
    0 O! `/ c+ u8 S$ S% x7 [  V
  38.     第18行:为节点创建一个随机的名字。
    ( _5 n- H1 m1 I+ O2 z' u
  39.     第21行:实例Blockchain类。' _9 F5 I: \2 t% o: S( x  S8 n* c
  40.     第24–26行:创建/mineGET接口。
    7 F  _% j2 U& S# x& B% H
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。+ ~9 O; k- T7 I& l4 Y
  42.     第32–38行:创建/chain接口,返回整个区块链。! l9 ?. R' w1 b# A! u
  43.     第40–41行:服务运行在端口5000上。( m$ |5 s) F% G* z
  44.     发送交易6 x0 A( f  b% F9 w% N& h
  45.     发送到节点的交易数据结构如下:7 X6 @& R' Y' Y" r, Q0 n! d& C) J
  46.     {
    3 m' z/ C  a6 L- |! f+ R
  47.     "sender":"myaddress",
    ; S& H+ J5 U( ]  s7 }0 l
  48.     "recipient":"someoneelse'saddress",
    2 q( f" Z4 W, `. o" k
  49.     "amount":5
    ( o6 H' }3 z- N& v. Q
  50.     }
    % t  H$ F. w& w* w% I: r: s
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:
    ' u9 L% I7 j) q7 U
  52.     importhashlib- Z. ?. P* E4 y- r( M4 ^4 K
  53.     importjson, G8 d# v! P; H( ]- s8 f& H( j
  54.     fromtextwrapimportdedent
    + k, ?; l+ p/ j! X; l  R
  55.     fromtimeimporttime# }* D7 r( w$ J, `3 U6 [
  56.     fromuuidimportuuid4, _! O6 Z* _$ Z8 q8 W- I( p' d
  57.     fromflaskimportFlask,jsonify,request
    8 O! V* `& S' t4 _8 d8 J/ b8 ?
  58.     ...9 t6 W/ }* }! t% }- D' m2 o
  59.     @app.route('/transactions/new',methods=['POST'])
    & _2 e3 Z% ]1 u& P
  60.     defnew_transaction():
      @& u4 r/ B: F$ f
  61.     values=request.get_json()2 {/ W' U* w0 C. q& F- C' k
  62.     #CheckthattherequiredfieldsareinthePOST'eddata! ~0 Z) ^/ a; R7 N! r+ p# U3 z+ P
  63.     required=['sender','recipient','amount']2 L7 m; g' ]9 |9 Y& [9 A. o
  64.     ifnotall(kinvaluesforkinrequired):
    ) U' S: T; B' I$ z. z6 @2 l( X* X
  65.     return'Missingvalues',4007 Y2 ]; Q! T0 W. z+ i
  66.     #CreateanewTransaction
    ! Z! c" B, t% d+ e
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])4 [# O1 c8 x( F8 s1 J, ], Y
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}
    ( a/ c# d, Z+ l6 E5 m1 G. J
  69.     returnjsonify(response),201
    4 d4 Q0 v- q0 g# ^9 v
  70.     ```( H  Q; x$ \7 t0 |4 N0 p
  71.     -创建交易的方法-
    1 F& [$ J4 r' [7 u
  72.     ###挖矿
    9 N1 m, y7 Z9 T/ _5 Q( R
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:" }6 Q, m6 k6 W
  74.     计算工作量证明PoW
    $ B1 v! s7 ^0 G! V- ~
  75.     通过新增一个交易授予矿工(自己)一个币
    * j8 f2 N9 {* i1 [
  76.     构造新区块并将其添加到链中
    / ^0 y0 F: K; k1 i8 I
  77.     importhashlib2 w! V% q8 n1 N# D* }# P, S
  78.     importjson. B" R! C8 f3 V0 ~4 b# \
  79.     fromtimeimporttime
    . G% z* n" R; H0 A. s
  80.     fromuuidimportuuid4" @* b7 u2 [+ B. q, R: _
  81.     fromflaskimportFlask,jsonify,request
    3 n9 Y% q2 ?0 \, a2 }( `
  82.     …
    ! p. Y1 J& w9 E* Q4 y
  83.     @app.route(’/mine’,methods=[‘GET’])
    9 O$ g( k- K6 M. n% r
  84.     defmine():. s  U- g( y! n/ G6 \; E
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…2 ~$ N; a7 b* r0 }
  86.     last_block=blockchain.last_block
    . F. H0 H) f2 p5 ]9 D4 t
  87.     last_proof=last_block[‘proof’]
    " x; s6 ]& X6 f' i3 l: c( R! ^
  88.     proof=blockchain.proof_of_work(last_proof)
    ; T' H3 Z, [' |
  89.     #给工作量证明的节点提供奖励.
      L  W2 p' }4 l8 Z$ M5 y! K* Z2 n
  90.     #发送者为"0"表明是新挖出的币.
    - ~5 \# k& c( H" J. e
  91.     blockchain.new_transaction(8 K7 E* U$ H+ P4 Q: g, c5 t
  92.     sender="0"," |. {- T" x( {
  93.     recipient=node_identifier,
    4 w/ ^! J' S& e3 f+ S  N$ i
  94.     amount=1,5 h- l! ~# O( W" T/ [& x7 K' y
  95.     )  o4 t, k- s- h' K
  96.     #ForgethenewBlockbyaddingittothechain% T" H% Z- `7 U: ]- o! @
  97.     previous_hash=blockchain.hash(last_block)
    " y& v2 k8 M2 X2 g
  98.     block=blockchain.new_block(proof,previous_hash): u1 L3 s! a/ g9 |9 B& s; a
  99.     response={; \$ b# E) d+ I" K# @( x: V
  100.     'message':"NewBlockForged",
    1 k8 v. \% F1 I6 f1 f) P
  101.     'index':block['index'],( z1 o3 Q" C6 d- X
  102.     'transactions':block['transactions'],- M! d' ^, E6 ]6 J0 F0 J7 W
  103.     'proof':block['proof'],
    * ~3 ~( E. i, I; B
  104.     'previous_hash':block['previous_hash'],
    3 ?$ v* Q% f2 R% c9 j# p
  105.     }. Y' x3 b) g; i8 A  g8 o3 p
  106.     returnjsonify(response),200# j# f; j2 ?1 s' h( ]3 \
  107.     ```
复制代码
1 e& Q1 I- _& o6 s4 Z
    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。7 ^0 ]( ?0 p& T& C! P# \
    三、运行区块链
+ ~# d( n5 O/ X; H( o' `; n$ b    你可以使用cURL或Postman去和API进行交互+ ?$ c7 W3 W- T9 A
    启动server:
" k  Z* b1 S5 F% ^    $pythonblockchain.py. |7 S" c$ e5 h/ E6 C# z( p' y
    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
- e4 m3 N2 |/ s    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
& d- M" o! D, I# L, q1 q    -使用Postman以创建一个GET请求-! S  n8 y( [% x4 @
    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:
! O2 o& @# q$ _    -使用Postman以创建一个POST请求-
! |1 e6 R( v- S8 W# \    如果不是使用Postman,则用一下的cURL语句也是一样的:9 U1 K% z# Y$ Y, I7 l# m
    $curl-XPOST-H"Content-Type:application/json"-d'{
7 G# J. v* i3 q' |- F% a9 D    "sender":"d4ee26eee15148ee92c6cd394edd974e",( C" z6 k/ c, V% v
    "recipient":"someone-other-address",
' ?; {  _) d7 L: @. J1 n7 B4 U9 i& J    "amount":5* I3 h3 ~# Y: t
    }'"http://localhost:5000/transactions/new"
, k# r7 E) w! |" p! s* M    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:3 E5 o$ W! a- ~" ^
   
  1. {
    3 p9 j% S8 M' h5 J4 U
  2.     "chain":[
    * n" `5 N) P! ]3 E* f
  3.     {
    * d$ Z) ?3 b( P% ]4 b5 B# j
  4.     "index":1,
    + U9 d6 Q- b( T' R# K) [4 A
  5.     "previous_hash":1,6 L* F* s- x* |( e
  6.     "proof":100,
    / z% y: j( |+ g- R5 x$ [
  7.     "timestamp":1506280650.770839,' V# A$ E) O1 T: `! D1 n" h2 |) c
  8.     "transactions":[]
    ( U* a, l/ P. [4 v, }2 W& Z
  9.     },' w/ {* }/ r' L8 h" u
  10.     {
    - F( ^. B1 ~7 b  u
  11.     "index":2,6 L$ G' u# \4 `+ F% L/ q" N
  12.     "previous_hash":"c099bc...bfb7",. \! o6 |+ w# }+ \) S6 z+ d
  13.     "proof":35293,
    ; B* l6 y+ x" u1 Z! E5 A' [
  14.     "timestamp":1506280664.717925,1 O& n/ p. b( O0 L
  15.     "transactions":[
    ! _$ B4 p1 `, }. g$ L
  16.     {' g9 y5 C+ h6 T
  17.     "amount":1,
    ' S9 `# d+ |$ {7 {  Z% }
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
      J6 ?6 `+ a  Z1 ~3 ~6 s9 L+ @
  19.     "sender":"0"' L% H  \. O0 q- C8 Z: b8 M" K
  20.     }0 U, T! M; a! U, c7 D2 F6 K/ G
  21.     ]
    2 e  i3 f0 J6 y: @
  22.     },
    ; w6 ]' `+ |. z. {
  23.     {. Y/ D3 E& k- g3 Y0 R$ |
  24.     "index":3,
    % U! s% r/ Q, U7 b4 ^7 p, X  d6 z7 G
  25.     "previous_hash":"eff91a...10f2",  M6 b, [  U7 Y0 E* `
  26.     "proof":35089,
    : F8 P% v6 T/ w' ^' j' H, l$ E
  27.     "timestamp":1506280666.1086972,
    + e. ]; O3 G. h% t( U) U* h
  28.     "transactions":[
    8 |& S4 c5 J* r8 M& p, q& R. f
  29.     {
    - a/ C6 d/ k( J; ?5 ?
  30.     "amount":1,9 m2 [1 G* T7 y$ {
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",7 Z$ r" i% _* I
  32.     "sender":"0", o$ P3 C" N( m* V. M
  33.     }: H* P- s& L; l! m2 p3 O3 Z5 h
  34.     ]
    # X5 ?- O. ^9 a8 Q
  35.     }
    . K- L, z+ }6 H
  36.     ],
    3 S" O4 b. L0 k
  37.     "length":3/ V1 c- T! G* F
  38.     }
复制代码
: t. X( B9 ^. S6 I
    四、一致性(共识)
# O9 n* ~- U/ N( ?+ y) S    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。7 e! k! a( C: a3 P0 G) k% ?4 n* ~
    注册节点: K6 j+ `: i  r4 W
    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
: g' a# R) f; x* q5 ^    /nodes/register接收URL形式的新节点列表$ D+ @2 T; |- j: `! D! I
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链# h" {# [/ z9 T: Y$ t
    我们修改下Blockchain的init函数并提供一个注册节点方法:- H* Q4 ^7 r& i) M9 S) a3 l9 T
  
  1. ...
    * i3 \( J, H' u5 Z7 U: W7 q
  2.     fromurllib.parseimporturlparse
    " B( s* \0 Q, _: Y% y
  3.     .... S# p" R: h5 c4 `% a5 {+ Q
  4.     classBlockchain(object):
    $ R3 \+ ~+ `/ N) k9 j+ k6 D$ G
  5.     def__init__(self):7 d5 @1 @1 W$ {* E1 p
  6.     ...
    1 m, i2 j% U: {3 W+ r5 ]
  7.     self.nodes=set()
    4 [4 _  S' v. a: r6 I3 F
  8.     ...
    - \- }  k; {) V1 b7 P# V2 @
  9.     defregister_node(self,address):
    ( r' x; O! M0 q* z
  10.     """5 P1 o2 k/ ^, ?) |3 z7 G6 \
  11.     Addanewnodetothelistofnodes4 R- X' ^' D, K5 X3 c, I& h
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
    6 f3 t% I5 N) I4 i
  13.     :return:None2 m. G2 K7 u" e/ j. Z0 S
  14.     """
    7 ?0 s* M+ q% P2 @/ E1 u% }( q, I3 f
  15.     parsed_url=urlparse(address), D6 s0 o: ^1 H; q* V8 K
  16.     self.nodes.add(parsed_url.netloc)
    ! N( C" F% x! S9 ]$ k, i6 K* m1 `
  17.     ```
    ! {6 e: u* I" M5 B- Q9 k
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。# H8 e9 e9 q8 K* Y7 u
  19.     ###实现共识算法
    " f+ n" ^( L2 k( t- B; g8 [! D
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
      r$ z4 {9 ^- H
  21.     …8 C# U& R/ D3 {, u& {  m$ _
  22.     importrequests
    , ~# q0 G) m' s- D
  23.     classBlockchain(object)8 X5 O8 {( f+ q7 M: Z8 E" a5 w
  24.     …- C/ {$ T, w" [" V& @7 S- Y: C
  25.     defvalid_chain(self,chain):
    6 M+ h# W- Y. P
  26.     """
    0 G) }8 g- r& z6 Y( _
  27.     Determineifagivenblockchainisvalid
    ( h' Y( B% S9 @4 M3 n6 Z' ^
  28.     :paramchain:Ablockchain
    % u; \! f% |8 Z' b! }
  29.     :return:Trueifvalid,Falseifnot) V  @) t( X2 t' d/ \
  30.     """
    , [, o, E9 K4 ~* W1 M# X
  31.     last_block=chain[0]
    7 U7 ]8 t5 \4 c3 a: x% g3 ]
  32.     current_index=1
    / t2 m1 I. F' M5 {& [3 E
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):
    ( s- i  [0 O/ P5 `1 A1 K! `
  34.     max_length=length
    & @, D$ E5 s! s- W
  35.     new_chain=chain
    + a+ V6 E$ [6 A. q- j- ?0 o
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours" ^3 ]  `9 _2 ~
  37.     ifnew_chain:' k8 g  O+ s* J
  38.     self.chain=new_chain
    4 w) P$ Y1 b; ~3 Z3 M8 ~/ r# z( \7 [
  39.     returnTrue
    * K' c* \0 K  L, P. y4 t+ ]1 w
  40.     returnFalse
    . c4 x# f# C' f7 W) v: ^# C  G
  41.     ```
复制代码

1 b3 X6 Q9 a& ]% t( I( I% m8 w    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。
( r$ K( v8 X* N; ]( F' R' R4 Q    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
4 _0 p& i) X6 s9 }3 B8 g7 r2 J# C    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。6 n, ?+ O  C1 p4 d: {4 v! ?* B
   
  1. @app.route('/nodes/register',methods=['POST'])
    - V9 ~) \# k( |8 F
  2.     defregister_nodes():  Y* T# ?7 \3 u, p* I6 o
  3.     values=request.get_json()( }, K. ~" z/ V) I
  4.     nodes=values.get('nodes')( b& a4 J: Y* f$ x5 h
  5.     ifnodesisNone:( A# A4 U4 @1 v8 C1 C
  6.     return"Error:Pleasesupplyavalidlistofnodes",4000 }2 q2 Y0 q3 F: _% r
  7.     fornodeinnodes:( G2 Z; M3 y* p
  8.     blockchain.register_node(node)& b4 A6 O1 Z# }5 T* \$ y0 u1 {8 Y
  9.     response={
    " x9 n2 j* T0 e- Y
  10.     'message':'Newnodeshavebeenadded',
    / O* n0 K: }) k' p) m) M, O: X" `
  11.     'total_nodes':list(blockchain.nodes),0 P6 W4 f# Z" g- @/ j
  12.     }) D- f8 |- f- `3 S. Z. l, }/ g
  13.     returnjsonify(response),201
    * a* e' f7 Q( R
  14.     @app.route('/nodes/resolve',methods=['GET'])
    # v8 d$ x  ^) J" s6 _
  15.     defconsensus():
    9 P9 b4 W/ G0 Z& i! t
  16.     replaced=blockchain.resolve_conflicts()( }9 f$ }$ i) F
  17.     ifreplaced:
    ; ]) J1 {' `/ B! W
  18.     response={
    - O" g; V+ p* v" q& w# A
  19.     'message':'Ourchainwasreplaced',- x! D& a1 O6 f4 Q6 W7 a0 \+ m$ d9 Q
  20.     'new_chain':blockchain.chain# w+ B( d6 o1 j
  21.     }( d, a( c" L6 J0 k: F
  22.     else:" u- q; {! V+ T* z
  23.     response={8 `# o# b7 x; y$ j4 s
  24.     'message':'Ourchainisauthoritative',
    ( y: Z$ s4 ]. o2 {% W0 W( d% O* I
  25.     'chain':blockchain.chain
    5 D. ^4 b7 q  O( K1 E. Z
  26.     }; a1 u$ R: h$ t8 M# @( A/ o
  27.     returnjsonify(response),200- \  e% {2 x7 r! b" M7 z, J
  28.     ```
复制代码

% J' x7 R* y8 w6 R6 ?: ]' J    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
  q, E9 Y7 o- T0 B% m7 P; Y0 t . B1 I; R+ d1 u1 h7 c
    -注册一个新的节点-
" u+ N$ G3 C4 }, q    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7