Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
461 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
; F2 b! U& U4 L8 Z6 _$ ~    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。+ p9 X  K. U- C5 b4 Q( k7 o/ a
    准备工作* C2 }0 o. H3 Q& J
    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。
  K- f5 b9 a  G" B1 R    如果你不知道哈希值是什么,这里有一个解释。
) i" ?& }# s, ~; R" k6 P    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。
) Y& Q& \/ v. x' H    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:# y; g$ g; d+ l7 R
    pipinstallFlask==0.12.2requests==2.18.46 [; [2 p& ^0 T, |8 ]) q
    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
9 U1 }( F) R! t. s% l% N    一、开始创建BlockChain; y0 q0 @1 N5 I7 ?  p
    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
6 m3 ?8 L- J( B6 c+ a1 U* r+ P    BlockChain类; _$ u& @3 f! Q  e2 D0 H& M9 Z
    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。$ ?' s# @; \- Q! F) `9 H
    以下是BlockChain类的框架:
% e1 i! O8 \' j- q4 _" [  
  1. classBlockchain(object):0 }! K1 W( m, z% C8 E8 \. U" T
  2.     def__init__(self):+ X& x  Y7 v; o( k6 Q3 a
  3.     self.chain=[]
    ; S  V8 M* D3 ~/ C- q4 }
  4.     self.current_transactions=[]
    ' ~$ F4 p4 \$ {9 J0 f$ u4 w
  5.     defnew_block(self):" M& n- r$ S, E5 D. [: l
  6.     #CreatesanewBlockandaddsittothechain4 N5 x, c  M( d4 U
  7.     pass
    2 o& j- I  @( M! o5 g' {9 V' \
  8.     defnew_transaction(self):
    * ?( J2 _. a" c0 V7 o. R, F
  9.     #Addsanewtransactiontothelistoftransactions/ ?: z- X5 B) x: ^$ s9 u
  10.     pass; k/ D- B1 ~8 l1 E* P
  11.     @staticmethod
    & L& C8 J9 |) e' Z& i& j
  12.     defhash(block):3 X7 I: b+ }  R2 Y% \* T
  13.     #HashesaBlock# \& ?3 g, r3 R2 I
  14.     pass# v5 w2 A/ r4 k5 u. D" t2 X
  15.     @property  F. D0 n) I4 }# x4 Q/ l0 K1 C
  16.     deflast_block(self):
    2 W- U. O8 ]& H3 m
  17.     #ReturnsthelastBlockinthechain
    , h+ i3 x# {' a
  18.     pass
复制代码

# Z! V0 X6 Y5 i8 p9 d    -我们的区块链类的蓝图-4 b- H- c2 t& X  @* V4 R
    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。
/ j2 ?6 r* ?, h( F7 m! |    块结构
0 E) [1 i# x6 R: K. l( \' N    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。) f8 |( ?; ^5 w' ?( @. K
    以下是一个区块结构:  `8 V- E$ w  g5 K4 C; y1 p: e
   
  1. block={
    ; F3 z; \6 f9 w8 l
  2.     'index':1,
    ; \3 A) s& O8 O7 K& k
  3.     'timestamp':1506057125.900785,3 v- {: q+ i6 ~/ x8 F" N! {
  4.     'transactions':[
    " R2 G& T' H5 Q
  5.     {8 R: [! R; P$ W" d  z" J% Q- j
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",8 v4 j' ~( J$ B: {! |$ }
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",* o! m. K" v' `
  8.     'amount':5,+ v" O( \3 T* c, L0 A
  9.     }
    # B+ i! X/ v& Q* d2 x: y
  10.     ],
    ; ]* x* ~+ i& g$ N& `5 i, y$ E
  11.     'proof':324984774000,' X5 H9 V; q1 c8 t9 B7 P  A$ ~
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    ! [, f! A* Z. n0 P6 r0 b/ `
  13.     }
复制代码

8 J/ l, I: Q+ O5 P$ P    -链上一个区块的例子-
* f' o) f/ P7 }- u2 V    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。8 L1 x6 h, u4 ^, [: ~9 T
    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。
9 u; d6 r8 W/ d8 M8 e) T8 r" h    加入交易( j8 d, G0 k9 K" x5 C% ]
    接下来我们需要添加一个交易,来完善下new_transaction()方法。6 R7 U6 y' |) {! E
  
  1. classBlockchain(object):. `1 x- E7 c5 t$ P. O
  2.     ...
    ! s5 w  h0 }3 e
  3.     defnew_transaction(self,sender,recipient,amount):
    / v( b" W1 _0 K. `6 K% g9 z: \6 ]
  4.     """$ g6 _3 O% u7 d6 K; Y7 _. }8 I
  5.     生成新交易信息,信息将加入到下一个待挖的区块中
    3 U. M" B# s3 K; [
  6.     :paramsender:AddressoftheSender$ K$ T. ^* [8 T
  7.     :paramrecipient:AddressoftheRecipient' z9 D0 S+ x+ J" [# S% J2 [
  8.     :paramamount:Amount! ?8 i  ]; b$ e, E. ~$ z+ R1 F
  9.     :return:TheindexoftheBlockthatwillholdthistransaction
    ' ?. `# w! U" I1 j$ \5 H+ F) L# H+ _
  10.     """
    & ~: N1 ]& x' E1 W
  11.     self.current_transactions.append({" i! C% h( ~7 q; ?6 u" D5 r
  12.     'sender':sender,9 W7 i! S  Y% ^$ H/ q$ M0 u0 C
  13.     'recipient':recipient,
      G. F8 u. H$ d9 T
  14.     'amount':amount,6 O0 R9 q5 M, j  i0 Q! K
  15.     })2 r% V, ^  F8 Z& ~; p
  16.     returnself.last_block['index']+1
复制代码

- G) o* w7 k/ G& y5 ?; ~    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
7 W6 F0 U7 F6 y# Y( {+ G4 x    创建区块
# U: V/ |) F3 o( z! ~    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。
9 {2 a! Z  l  E: L- U8 U$ y4 _    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。% ?+ ^* m8 H7 U6 p
    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
8 S. \$ I2 g; i, G9 }& K/ R+ I6 R  
  1. importhashlib3 V( o8 I8 v3 m3 a% J
  2.     importjson. p8 ~1 ~2 k( `/ g. t! H( ~
  3.     fromtimeimporttime
    3 W5 J( W. D" z: [6 c. l
  4.     classBlockchain(object):# I) p, _. G# l4 ?
  5.     def__init__(self):: Y4 B& o" H: S) S( Z; p
  6.     self.current_transactions=[]
    ! }1 p# ^; |' e! u9 e' O' d! u
  7.     self.chain=[]; H8 k5 M2 C( J% z4 C* j
  8.     #Createthegenesisblock' @0 D% ?5 R  g4 A5 o0 e' W
  9.     self.new_block(previous_hash=1,proof=100), w2 p1 K. y3 {3 S+ y7 T4 v* ^6 h
  10.     defnew_block(self,proof,previous_hash=None):  o2 E1 H- e3 H) i! i; i
  11.     """
    - ?2 T5 n9 i" d0 m+ }
  12.     生成新块
    $ [- Z. }/ ]' }( M' K9 R" P
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm
    1 v& m7 _- Q2 z6 y. V
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    : d% K5 `& ]  {+ G2 r
  15.     :return:NewBlock. N' ~- I, e3 w" Q. l/ \/ h& y
  16.     """  C( |0 o- o/ g
  17.     block={, h* P, @' c% X& |# _, n6 `
  18.     'index':len(self.chain)+1,
    2 q) {. t+ E/ i0 ?; k0 r+ X
  19.     'timestamp':time(),
    2 a/ ^+ [! `' B' ?9 ^
  20.     'transactions':self.current_transactions,
    9 q% M% O$ D# `  J8 w, z
  21.     'proof':proof,( H2 B- L9 J, d' Z2 [& X
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),
    ( N2 [1 [& W7 U2 X
  23.     }' a7 j- S9 }) V
  24.     #Resetthecurrentlistoftransactions
    * u! S% f7 h- f' G, L+ j
  25.     self.current_transactions=[]* i$ {; \: S7 l
  26.     self.chain.append(block)
    2 m. g* a9 B7 P- B2 y4 h7 ^
  27.     returnblock
    % `; r- _: {2 d0 q5 R
  28.     defnew_transaction(self,sender,recipient,amount):
    2 w) A7 t" d, Q, _9 b2 ?3 P% z
  29.     """
    # q9 t2 g, G3 g( r1 v- W4 B8 o2 M8 R
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中
    " r) H; X/ T" }2 K. W6 J
  31.     :paramsender:AddressoftheSender
    1 M/ p9 k. m( g3 ~2 D7 K/ e# A, k
  32.     :paramrecipient:AddressoftheRecipient, J" i+ y7 r, L, k5 i" B/ v# s
  33.     :paramamount:Amount6 n6 n: r( G  r$ }+ u; a
  34.     :return:TheindexoftheBlockthatwillholdthistransaction
    4 u# r$ V/ G6 s+ O
  35.     """% N! m) u0 q, i  b! B, s
  36.     self.current_transactions.append({- Y4 z8 Q  n0 R$ N0 e
  37.     'sender':sender,/ A5 y( c! t* r! g
  38.     'recipient':recipient,4 q0 x4 [( Z9 N9 C8 _% M+ ]
  39.     'amount':amount,
    / M6 @3 j# \6 `1 @4 l% |! j  x
  40.     })
    + O5 |8 U" k& \, V5 D- Z9 \
  41.     returnself.last_block['index']+1$ J/ j: p; J- K2 j6 G
  42.     @property5 O0 O( P% I* n  ?* {4 |
  43.     deflast_block(self):
    ! `( t. r; v& d/ B" f/ J) L4 [
  44.     returnself.chain[-1]. p4 N' J+ m3 v: C7 j  Z
  45.     @staticmethod4 A* Y0 M8 g5 F  P+ m
  46.     defhash(block):
    1 [5 w( S% I8 q% ^& g
  47.     """5 c! l. c( ]5 q
  48.     生成块的SHA-256hash值
    0 |  `' ?6 i. n+ @) M
  49.     :paramblock:Block
    / B( l3 `0 \; q$ L' y
  50.     :return:) j: N! Z2 D2 h" ?7 ^
  51.     """
复制代码

9 U/ J1 y0 E# S+ l0 |' j    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
" ?8 i% A" E% H    block_string=json.dumps(block,sort_keys=True).encode()
' i; a% t  C- ]$ }, U    returnhashlib.sha256(block_string).hexdigest()' E% A. B0 m/ d4 L# N& i5 i
    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
. I; c# m9 q' P0 e2 L    理解工作量证明% T, J( f# U1 g$ j
    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
( L) k4 v! I  d1 J& G: H( p) d- W    为了方便理解,我们举个例子:1 I, l. S2 Q4 t; T4 P6 q6 x) a
    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:* L. P5 j; y. Y& W" I1 t
  1.   fromhashlibimportsha256
    0 X. O& H0 h! W; e2 F7 [& |8 s7 O; c
  2.     x=5
    ' H  }* x5 ]! [" y& \/ ]$ S, g
  3.     y=0#y未知  i- k8 ?$ U/ p7 }, ?0 B6 p; d
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":/ C) R* E1 G8 {6 ~) m$ N9 B. }
  5.     y+=17 A1 s, S  z; @( z
  6.     print(f'Thesolutionisy={y}')
复制代码
& k. V( k& p- x
    结果y=21,因为:6 L3 Y5 Y: C+ I  a$ w' R
    hash(5*21)=1253e9373e...5e3600155e860
- R4 J" C- x  [- x: v3 Y5 p    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。0 l  ^/ d) u9 v
    当然,在网络上非常容易验证这个结果。# k2 p6 a, ]7 o) I8 ^9 b8 X
    实现工作量证明
; O2 A7 b  ^9 u    让我们来实现一个相似PoW算法,规则是:
- \' [; _  M& E; W" A0 V    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。! N* v) \1 n: D' q1 Q/ B4 |
  
  1. importhashlib
    ) e1 v6 v6 ]6 L; o8 U0 l6 j
  2.     importjson
    $ q- D$ J3 p* V  i6 S0 C
  3.     fromtimeimporttime% b/ I5 ]/ ^, l& y- G3 @
  4.     fromuuidimportuuid41 N2 w% c' A# K# O+ u6 I
  5.     classBlockchain(object):' U1 S/ B5 W5 \- W
  6.     ...( t2 S8 ?; w& f
  7.     defproof_of_work(self,last_proof):  w# W3 K9 T- m  q- d3 D/ A  g
  8.     """
    , O  d. \$ J2 [  a9 V* c8 x/ Y3 l
  9.     简单的工作量证明:1 N8 i# D- h* c$ a9 m7 ^
  10.     -查找一个p'使得hash(pp')以4个0开头* w- H" m' h5 K) n. N7 r- A
  11.     -p是上一个块的证明,p'是当前的证明
    ! H/ ]" q$ A/ ~& s
  12.     :paramlast_proof:. C6 P% k0 b$ R+ y5 a0 @
  13.     :return:- ]3 g7 |. w& K: T  [
  14.     """
    ! a. v7 N6 |" e" f( W! N
  15.     proof=0% R- n" S4 k7 G2 f1 D( X
  16.     whileself.valid_proof(last_proof,proof)isFalse:
    6 n; g9 A! G, T& m& d- ?
  17.     proof+=1$ Q! h/ G; R& L! u3 o5 @9 l1 d! n
  18.     returnproof% T3 U7 n5 u/ D+ u* n
  19.     @staticmethod
    5 F; k# b9 `; L' K4 C& k2 a
  20.     defvalid_proof(last_proof,proof):
    3 t! o) v& a1 W: p; Q& D
  21.     """
    % y% w# k; i& Q' ?! H
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?4 Y# D  I# X* ~8 {6 \, I  p8 c
  23.     :paramlast_proof:PreviousProof6 U$ J- b. F3 n4 t) p
  24.     :paramproof:CurrentProof
    $ O7 v! t1 k) ?( {' ]7 \
  25.     :return:Trueifcorrect,Falseifnot./ e% M2 B9 d& q* x! j3 O! \
  26.     """
    ; l2 u  B% H. [/ {2 S2 i
  27.     guess=f'{last_proof}{proof}'.encode()3 v" ~: M4 b: r" M' h  T+ X
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    ) P) i  g4 X" w! Z; c3 C# Y
  29.     returnguess_hash[:4]=="0000": f3 ^2 B% D# {3 e; [6 l
  30.     ```
复制代码
9 R1 A6 {% G4 L. c( A
    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。! ]% H% q$ X3 T, n) p; D2 y
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。% |  e+ ]" x2 o& K! D* c( f
    ##二、BlockChain作为API接口
$ \3 m2 q/ h$ e0 W& X4 U    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
2 |- X' f9 A* i! X8 w. W6 w    我们将创建三个接口:& @1 N* y9 B2 f$ a- @7 \5 U' V
   
  1. ```/transactions/new```创建一个交易并添加到区块+ Q$ _( O7 }& D& z
  2.     ```/mine```告诉服务器去挖掘新的区块
    + P/ @5 H: e2 C- O3 N6 |8 ?
  3.     ```/chain```返回整个区块链
    + J4 q1 P* g  P# G8 C
  4.     ###创建节点
    , ?. l+ ]. o4 M) d  [
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:) I" q* Q2 ^! U9 q
  6.     importhashlib; S+ [/ f+ w8 L& l
  7.     importjson% p* [: R3 x- D2 {
  8.     fromtextwrapimportdedent
    % c2 d$ e! b3 Q  ]
  9.     fromtimeimporttime$ \% }; h' u% }! R" P; m  C, {8 ]7 L) z
  10.     fromuuidimportuuid4
      q' d- O9 \8 ?- _' }% X( y
  11.     fromflaskimportFlask
    + c9 G2 k8 m/ M' J- h% u
  12.     classBlockchain(object):
    7 |5 ?/ J7 C" \2 W9 ?  S3 u  [9 Q
  13.     …& I+ h) D3 N' V# [7 T# c# i
  14.     InstantiateourNode6 V, e' N: }; _6 Z& q& {* Z4 z: C
  15.     app=Flask(name)% c+ G6 Q& r' `+ Z3 t( ^1 Q5 c
  16.     Generateagloballyuniqueaddressforthisnode& x4 g+ @2 i, h+ G5 N" a% `
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)5 l& e/ c- q3 L  N. P
  18.     InstantiatetheBlockchain# H. A/ C' T( L4 y0 {  N$ }+ A
  19.     blockchain=Blockchain()* u9 E( v( i/ G6 a& ~
  20.     @app.route(’/mine’,methods=[‘GET’])3 E& m. @8 i- W1 S
  21.     defmine():
    " w) n  e/ \' ]% k  e
  22.     return“We’llmineanewBlock”
    1 H$ w, d5 j( e- `! Y
  23.     @app.route(’/transactions/new’,methods=[‘POST’])2 k) ?# k5 p& r) Q+ P- O
  24.     defnew_transaction():
    / I; y3 _/ [- o( ?
  25.     return“We’lladdanewtransaction”
    " u* C' y5 S' A/ s( c0 X/ n! R; F
  26.     @app.route(’/chain’,methods=[‘GET’])% |; g( H- b* s( v- X2 X
  27.     deffull_chain():8 H! x5 n8 y) y) u4 B4 B
  28.     response={3 o" m1 f, w7 g; F: |( f$ ~
  29.     ‘chain’:blockchain.chain,7 G9 L; |5 D2 k( I0 z. w/ J  z/ ~9 I; s
  30.     ‘length’:len(blockchain.chain),& Q, K; o: d! g/ ^
  31.     }4 C3 Z: S0 K7 j8 e
  32.     returnjsonify(response),200
    ! w; s+ j& K$ Q$ R% |% B  O
  33.     ifname==‘main’:
    . w; R1 y6 q0 v4 j1 w' \) B
  34.     app.run(host=‘0.0.0.0’,port=5000)
    5 T8 \" Z; o1 j" p* V; s% ?+ k
  35.     ```- I0 w" p" U& i6 P; A' n
  36.     简单的说明一下以上代码:
    7 q0 v: ^( q2 f
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。- @( z+ q6 b& G! S. T; G. [
  38.     第18行:为节点创建一个随机的名字。4 X2 H) w4 g% S+ d" R
  39.     第21行:实例Blockchain类。
    9 d4 w. [- |& W! c* K: J" Y* Z
  40.     第24–26行:创建/mineGET接口。6 {' _; D  E% k9 H* f
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
    ' v" \; |; b5 n
  42.     第32–38行:创建/chain接口,返回整个区块链。
    ! \. f- j; v0 z: D  R8 V5 {$ D
  43.     第40–41行:服务运行在端口5000上。# O( v0 F' l. I
  44.     发送交易4 F6 L* `1 {8 S" H; E. r
  45.     发送到节点的交易数据结构如下:  u* U" n$ X- p2 u
  46.     {
    # i% K! y6 Q, A& j1 N6 K
  47.     "sender":"myaddress",* Z; P6 o6 k, K2 s
  48.     "recipient":"someoneelse'saddress",
    + B# O5 ]+ f% M( }
  49.     "amount":5
    2 B( y0 r1 v+ _+ i
  50.     }
    & h; h! E  u1 ~% j. ~* _& r9 D/ W
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:8 D( |2 }# q9 I# X# m( |4 r
  52.     importhashlib
    8 X( `2 m8 c0 w+ B& O
  53.     importjson
    * x9 K) i$ u) g/ }! v& X
  54.     fromtextwrapimportdedent
    , t# ?: a% t5 R4 f) t
  55.     fromtimeimporttime* l! Z: d$ {! z0 c) L) a
  56.     fromuuidimportuuid41 M3 D5 c9 n2 J
  57.     fromflaskimportFlask,jsonify,request+ g" Y4 A9 x( Z; M# A$ S# P
  58.     ...
    ( \( Y2 I# v; M$ [3 n  Y! I3 J
  59.     @app.route('/transactions/new',methods=['POST'])% e7 T; ~% r0 h3 W
  60.     defnew_transaction():
    - K8 b- {, Y( c( h( Q) K
  61.     values=request.get_json()
    2 D  I1 O) U9 ]) L- c! W
  62.     #CheckthattherequiredfieldsareinthePOST'eddata
    3 j+ _% I! m# [; f4 L3 [
  63.     required=['sender','recipient','amount']1 x0 v" l, I8 t) A$ f) t
  64.     ifnotall(kinvaluesforkinrequired):6 P/ O/ g+ G% t  p( Z5 Z2 M
  65.     return'Missingvalues',400
    # y' M) T; V; ?0 ?) t& Y
  66.     #CreateanewTransaction$ ^+ \4 o" |% N8 [+ I" l! [5 v5 H, K
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])1 i" U' U' G" B4 @6 f5 L* O
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}2 B- ~, V! V- F) J$ @
  69.     returnjsonify(response),201
    ) W  T  d; n3 N! ?4 P2 q8 }7 Q. T
  70.     ```
    & a, ~2 G  _. Q, G* }3 r
  71.     -创建交易的方法-7 n# ?/ A% m6 N
  72.     ###挖矿# s; S. B" _9 H* f
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:
    # p( d2 l& m# U  H4 J5 r4 i  V
  74.     计算工作量证明PoW5 ^& W3 }( u6 a- c- I) f
  75.     通过新增一个交易授予矿工(自己)一个币
    4 e- f) Y3 c+ v; [$ [0 ~
  76.     构造新区块并将其添加到链中# {6 a# ^: ^# H! y' U$ g
  77.     importhashlib0 \% ]  Z" O7 m$ _! ]
  78.     importjson6 s) n9 @& G; V  R3 h9 u
  79.     fromtimeimporttime! ~3 f$ ]$ a; }; Y/ Z/ f+ D9 s. h
  80.     fromuuidimportuuid49 E- V2 r- X: g* o* X
  81.     fromflaskimportFlask,jsonify,request' r8 T( O! ]  Z4 c
  82.     …
    : e0 @! X# g) s6 g4 ?3 k( M7 J
  83.     @app.route(’/mine’,methods=[‘GET’])% o. |. L/ {5 G
  84.     defmine():
    2 a( ]; O- ?7 v' J: d
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…! e' I, n8 [$ O/ ^" [" i* K
  86.     last_block=blockchain.last_block6 d3 e/ y4 F) F( }/ b& f# k
  87.     last_proof=last_block[‘proof’]
      p  S$ ?6 n# o4 `+ Q; c- h" ^3 u
  88.     proof=blockchain.proof_of_work(last_proof)& A$ |5 G, R0 k( h0 W5 b& g
  89.     #给工作量证明的节点提供奖励.
    3 Z& a; I- r1 j; k
  90.     #发送者为"0"表明是新挖出的币.2 B& h* O' o# f& S
  91.     blockchain.new_transaction(* }+ n: C( c  C, c  O7 u! k# Y9 A0 J
  92.     sender="0",$ |' j! Z1 R4 n" c: L
  93.     recipient=node_identifier,
    , U! g# Y9 n/ i
  94.     amount=1,0 `7 t/ u$ B" }2 L4 f, g6 S: _4 Q9 I
  95.     )% U' y2 \7 R3 ]. a
  96.     #ForgethenewBlockbyaddingittothechain9 F) ]# h2 M" V" f( F2 n
  97.     previous_hash=blockchain.hash(last_block)
    6 Q+ D' y+ |5 O' n
  98.     block=blockchain.new_block(proof,previous_hash)6 e& B- p8 u/ O+ d- F/ U
  99.     response={3 v$ [  I0 v$ T0 n7 q6 }/ f2 P
  100.     'message':"NewBlockForged",
    " ?3 p2 _5 n# S4 o9 y( @
  101.     'index':block['index'],1 ^6 `6 _8 C% h8 n
  102.     'transactions':block['transactions'],* j) s3 @% T! j* @5 q
  103.     'proof':block['proof'],
      R; r6 N6 [  J( G. @+ K
  104.     'previous_hash':block['previous_hash'],
    : Q: c* H+ K- v
  105.     }
      u- E4 }5 ^4 I+ }
  106.     returnjsonify(response),200- C$ v1 P/ E% D+ p  k( I5 P
  107.     ```
复制代码
* i+ t1 }: s! L% D* F* }; C
    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。8 H# ~, G& Z& `2 ]
    三、运行区块链. @8 t* u8 W; z( T
    你可以使用cURL或Postman去和API进行交互
, Q" Z, E9 ?; n    启动server:. f' K1 C- N9 H  j
    $pythonblockchain.py
3 b, D$ R5 f5 T/ P, ?    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)9 h' B! v8 M0 ^+ n( }8 V" i  V3 x
    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:2 F0 t* D. N. E5 S9 ~9 B
    -使用Postman以创建一个GET请求-
% d9 K% q0 q& ~" N2 ]8 u    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:0 s. ~& X8 V# c- |/ h! s8 t
    -使用Postman以创建一个POST请求-! K8 h9 {5 I1 E
    如果不是使用Postman,则用一下的cURL语句也是一样的:
; i) U9 `& d( [0 q- k9 G2 ]    $curl-XPOST-H"Content-Type:application/json"-d'{+ F5 @8 i# {% M" j# `$ w. n
    "sender":"d4ee26eee15148ee92c6cd394edd974e",
' @0 x, f2 c1 b- Y    "recipient":"someone-other-address",4 z  H/ q* p2 v2 ?
    "amount":5
" h) v& {2 l2 g% i% d$ i! `+ `3 ~4 l    }'"http://localhost:5000/transactions/new"
5 e* ^, E6 ~2 `( ]    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
' a  w! G$ y! f9 \, h   
  1. {/ g6 |- S* I$ p* e1 ]
  2.     "chain":[8 B7 q" h0 V& k. V: g* a
  3.     {& m3 I, l3 r3 C, `8 x: Y# l9 p
  4.     "index":1,# x& n+ D* ]- a5 T" g
  5.     "previous_hash":1,
    / a8 S: e' d' H+ ]& O3 |& r& F
  6.     "proof":100,3 M3 q' |3 q& e% ?2 \. N
  7.     "timestamp":1506280650.770839,7 O4 O( G4 f8 C
  8.     "transactions":[]
    - y) C" R3 f9 h+ O+ c
  9.     },
    & S! R/ ^6 ~& P  a. M0 b! Y/ h
  10.     {- x3 {% Q/ y0 l
  11.     "index":2,
    2 K+ X1 f' V; L) j. ?2 ~3 I
  12.     "previous_hash":"c099bc...bfb7",4 R3 |7 ^  P* c! l! o) h, F" b
  13.     "proof":35293,
    & T# r; e, S5 z$ a, o6 @
  14.     "timestamp":1506280664.717925,! `# T" K/ U9 g  [
  15.     "transactions":[5 K6 J& [2 N7 f( H/ ?
  16.     {
    , _- t1 K7 }* B5 m" g! V& f0 M
  17.     "amount":1,
    / o" h6 h/ o. U+ R% G9 y
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    4 N; h8 x: F1 x0 N5 b9 x: F+ c1 z
  19.     "sender":"0"
    2 ^2 S  E3 n  q7 f
  20.     }
    ( p5 |$ S1 F2 t6 {1 s
  21.     ]
    5 U* Z8 P+ D( t; s' |$ o
  22.     },
    $ ]) a1 Q6 t% e& t3 M: G
  23.     {/ z8 [. H' _( ~2 e8 T. W1 A1 u5 w4 W
  24.     "index":3,
    ; N2 n% j8 x' q
  25.     "previous_hash":"eff91a...10f2",1 c+ F# x  F7 H" X
  26.     "proof":35089,
    / \) T' U* E9 @' p7 b
  27.     "timestamp":1506280666.1086972,
    2 W9 u/ x3 Y& X- c7 [& m1 Q" F
  28.     "transactions":[
    & \4 W  Q# j8 T- Z9 }! r9 G
  29.     {
    $ Y& Y1 ?  d8 Z/ Q) P, N: O6 C4 U, P
  30.     "amount":1,( |; @( o6 s& T0 P2 T3 k: }5 ?
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",$ J% a- f2 R! w2 s# f$ s& r
  32.     "sender":"0"
    1 J, n; r; X$ r, p4 T
  33.     }$ V6 T- |1 H. ^! W
  34.     ]
    2 @0 H1 z% v2 U% Y
  35.     }8 y9 D3 s% w4 Q8 H* b& o& Q
  36.     ],# |( u9 F0 h# f# K
  37.     "length":34 _7 c5 I1 S4 I
  38.     }
复制代码
4 p9 ]4 p- Y3 S( c9 o' i
    四、一致性(共识)
8 v9 @3 d; Z0 \! [/ T$ w, P    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。
7 U! l+ |: S% k    注册节点
" }* Q; t" [3 w8 l* ^% M" z    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
4 @' y; \$ j" S. E    /nodes/register接收URL形式的新节点列表6 s) R# h1 a3 h+ k7 a) {6 ^
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链0 G1 b: }4 V. {1 G& `7 K
    我们修改下Blockchain的init函数并提供一个注册节点方法:
0 }$ M6 ]6 o7 Q- {4 B  
  1. ...  G( Y5 b0 G- h: x( O
  2.     fromurllib.parseimporturlparse( B8 c! ?* \3 r+ T+ Z0 {
  3.     ...
    # y3 L) s6 F* l) r  d
  4.     classBlockchain(object):
    # P  P6 ~; d% J; j+ u
  5.     def__init__(self):
    / ~: P& g6 O+ f" m; F/ T) n
  6.     ...5 |2 f. \8 t9 L4 t  L9 ]
  7.     self.nodes=set()( r2 r  X" ~9 `% |7 Y
  8.     ...& {) L' v5 U3 @# W/ ]8 I' v
  9.     defregister_node(self,address):
    8 o& N9 Q  Y" k0 B, P$ b* b; `
  10.     """6 R. w2 g+ J4 M9 j5 i3 A
  11.     Addanewnodetothelistofnodes
    . q: C7 Q# v& _' X2 e( }/ B
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
    + q# J# k9 Q, k- _
  13.     :return:None
    # [. G1 ^, b% I+ h( x8 ]2 h$ J3 m
  14.     """
    3 N3 \- s& F" ^& K
  15.     parsed_url=urlparse(address)9 S  g$ y* O9 H1 f
  16.     self.nodes.add(parsed_url.netloc)
    $ g* Q* G+ }' u0 k8 }
  17.     ```
    - ~6 e$ Z7 T7 p7 I( T; N6 s
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。; t1 D! \7 f. e2 h- y+ c
  19.     ###实现共识算法
    2 q0 \1 ~4 s0 w6 o0 |; e4 j; Q
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。+ k% N# _4 n0 C0 o2 K
  21.     …- O* L! \# @0 L8 d4 U
  22.     importrequests# p$ `. o) I, q0 A6 m
  23.     classBlockchain(object)1 l: O8 U& N8 i' k6 q# ^
  24.     …
      j) P% B+ b8 d3 }! D7 e
  25.     defvalid_chain(self,chain):$ K) x# T8 P  b
  26.     """+ Z( Z% r* s7 Z# Q& L
  27.     Determineifagivenblockchainisvalid
    + s* H, W* R% G9 V* I- T
  28.     :paramchain:Ablockchain
    ' i$ t# G+ m& M/ ~
  29.     :return:Trueifvalid,Falseifnot/ _1 W9 ~2 _7 y  \% Z5 ^5 [  N
  30.     """
      w( [4 {( t% ]
  31.     last_block=chain[0]; \. q% e6 q4 t# K( }0 q
  32.     current_index=1! [. ?; [$ d# ^- W! e6 h, z6 W7 s
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):
    - |% O  n2 _: O8 E4 v
  34.     max_length=length
    4 Z* t/ |4 @, H
  35.     new_chain=chain
    4 j1 d8 o0 m1 F: n# t5 P$ F1 {
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours! J6 u1 ?5 D0 ?' z( R; ]5 P
  37.     ifnew_chain:3 `* g9 o( P  b' E- S
  38.     self.chain=new_chain! i4 w! c3 I3 R5 o2 G
  39.     returnTrue8 ~8 g# l, i- K9 Z0 u# T
  40.     returnFalse
      V2 N2 E6 l1 ?3 g( \; t% x
  41.     ```
复制代码

& Z# D. \: i! l" O5 [    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。! z, c* d$ v) [# H
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。& @& [: _' ~  p8 u
    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。6 R9 M, E1 R  _7 Y  T
   
  1. @app.route('/nodes/register',methods=['POST'])
    : Y7 `7 i6 N, M
  2.     defregister_nodes():
    % \, f8 I. ~" [0 x7 V/ x
  3.     values=request.get_json()/ D, s' m# V1 H- R, v6 m
  4.     nodes=values.get('nodes')
    1 e9 ^6 Q7 }7 q4 s) a, H) C
  5.     ifnodesisNone:
    6 q8 ~; L: Y( ]5 Y3 A
  6.     return"Error:Pleasesupplyavalidlistofnodes",400
    1 \- j( j- v4 d! \$ c
  7.     fornodeinnodes:
    ! y, D7 R5 @1 m$ O* }/ ?; b
  8.     blockchain.register_node(node). B3 S$ I$ S+ C* \6 R4 C
  9.     response={
    % J* L( B4 ?8 s2 b
  10.     'message':'Newnodeshavebeenadded',- _+ C/ v2 c" q0 R
  11.     'total_nodes':list(blockchain.nodes),
    # Q% E, d! R) z! P+ u% O- X& b) i
  12.     }& z4 K- D/ q6 n: `4 u' J
  13.     returnjsonify(response),2012 R+ J# p- u4 [2 x7 R6 u6 M
  14.     @app.route('/nodes/resolve',methods=['GET'])
    ( Q( w& a0 z* z0 S
  15.     defconsensus():
    8 f4 N; ]$ s3 z/ ]- Y
  16.     replaced=blockchain.resolve_conflicts()
    ' M" T+ s; G3 O
  17.     ifreplaced:
    - k5 u! d' ]1 J4 C" X
  18.     response={6 l* ?' x* F+ H' V# W
  19.     'message':'Ourchainwasreplaced',
    & s8 h) ]& u* B' B
  20.     'new_chain':blockchain.chain
    ! }0 }. ]* q, h; p/ Y% ^- q
  21.     }; c1 `9 l1 z+ N3 F4 g; ]3 T
  22.     else:+ `& l  `) ~* l
  23.     response={4 M2 `2 c# t, M
  24.     'message':'Ourchainisauthoritative',+ c- O, V# u' s5 ~
  25.     'chain':blockchain.chain; n3 _3 Z, K/ o7 G
  26.     }! a6 T, O4 a0 [, A
  27.     returnjsonify(response),2008 [+ Q3 I* ~& J1 r) D3 ~
  28.     ```
复制代码

0 t* X/ \9 ~8 \* w1 K8 v1 E    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
+ O6 j: R" y$ X, P
! @! ^! V( L. h, S% }% \/ k    -注册一个新的节点-
. W0 p' U, {) o* n8 Q    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7