Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

用 Python 从零开始创建区块链

江左没浪
495 3 0
我们都对比特币的崛起感到惊讶惊奇,并且想知道其背后的技术——区块链是如何实现的。
, ]( Y' D  c  [+ D9 P. f/ F    但是完全搞懂区块链并非易事,至少对我来讲是这样。我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。
$ e$ s# w9 H6 b$ E7 u& K+ Q$ m    准备工作
- w) ~5 r' w! B/ \2 U    我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。- u6 Q2 ^( m, B6 b0 G2 K- z
    如果你不知道哈希值是什么,这里有一个解释。
8 ~# t% x2 w$ I5 {0 N8 T    这份指南的目标人群:阅读这篇文章,要求读者对Python有基本的了解,能编写基本的Python代码,并且需要对HTTP请求有基本的理解。' @' A( j( m8 r- s- k4 V" F. U
    环境准备:确保已经安装Python3.6+,pip,Flask,requests。安装方法:! x; b6 [! G: G4 Q# J9 z/ t: e
    pipinstallFlask==0.12.2requests==2.18.4
: X* q, H8 d2 @    同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。
8 l) t& ]3 o0 E' Y* A5 H    一、开始创建BlockChain" a% O/ H3 W  R% g
    打开你最喜欢的文本编辑器或者IDE,比如PyCharm,新建一个文件blockchain.py,本文所有的代码都写在这一个文件中。如果你丢了它,你总是可以引用源代码。
" F( R: c( r! |  b    BlockChain类* e# ]' t: w6 M' X5 M; I* X5 S
    首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。; n% l. |) Y. r3 n7 F
    以下是BlockChain类的框架:1 n7 }- J9 W+ Z& O" v' B
  
  1. classBlockchain(object):
    7 d4 h! t4 |& t& B, W+ p
  2.     def__init__(self):
    ) k" X$ T9 l- H/ V/ J+ s, m
  3.     self.chain=[]! b1 {0 v9 L+ B) H0 G# g: }
  4.     self.current_transactions=[]
    $ \+ d% t5 t& r5 x: z# X! ?
  5.     defnew_block(self):+ B2 v' _. e+ N5 j9 C. s) y
  6.     #CreatesanewBlockandaddsittothechain
    0 c4 ]4 J7 e5 J; m1 C
  7.     pass
    & I& H" C: @2 i, m
  8.     defnew_transaction(self):( {) C3 P! g9 Z  H+ y1 w/ ~
  9.     #Addsanewtransactiontothelistoftransactions! _! @$ r, J. l" _
  10.     pass/ P9 e9 ]0 C5 ~
  11.     @staticmethod
    2 n& o% d  o" g3 W5 v& I. r& J
  12.     defhash(block):  D/ ~7 z% x' i6 `, P& ~
  13.     #HashesaBlock+ U3 r; r9 w5 U0 ]8 d7 ~( O  M
  14.     pass: X0 T) f5 F  a- W5 X- Y
  15.     @property6 {- v5 |' s0 u2 K6 s0 b  u! z
  16.     deflast_block(self):
    0 n' g3 i* B) A& |. b( C0 H
  17.     #ReturnsthelastBlockinthechain
    % m" U. @7 C0 Q, D) A+ P0 R
  18.     pass
复制代码
1 H8 J" w* h( L2 H
    -我们的区块链类的蓝图-3 |5 ~, |# t3 }% M
    Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。' p8 K! \4 u! x0 p  m1 b& c
    块结构
! N; b6 u5 v0 X    每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。7 F" }# L8 T/ b1 v6 H/ w# e
    以下是一个区块结构:' `5 L2 Z7 ]& }6 i9 C8 n) e- V& b$ U
   
  1. block={
      f- a) r0 Y" v) Q
  2.     'index':1,- d8 d! L) f% P! Q: r/ u
  3.     'timestamp':1506057125.900785,
    1 W; f! f% U7 k+ x. Q5 \
  4.     'transactions':[
    - I3 g% ~! ?$ i
  5.     {2 i* |' M& t! ?. a/ d% p& F3 T/ S
  6.     'sender':"8527147fe1f5426f9dd545de4b27ee00",
    " {& \8 O1 r, t+ }; o7 ?, w$ R. g
  7.     'recipient':"a77f5cdfa2934df3954a5c7c7da5df1f",
    " F0 l5 \) z# {* w: P! F6 p7 O* T
  8.     'amount':5,
    / t8 I6 d$ [8 ~* k8 w
  9.     }. @6 T, q! x# V! S* ^$ b
  10.     ],/ T" F# y- t, v1 b) Z
  11.     'proof':324984774000,3 {3 D. t1 {$ F( a+ Z# y. i  @
  12.     'previous_hash':"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    % `7 S7 u0 c+ W8 |0 D- |/ `
  13.     }
复制代码
+ b4 q& P6 i+ b+ U! I% S
    -链上一个区块的例子-/ [! h! m7 O' G2 p
    到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。
  _& @7 h1 y! z9 c    确定这有用吗?嗯,你可以花点彻底了解它——这可是区块链背后的核心理念。# }" x* Q6 q6 X& M. R
    加入交易! H% z9 b. r8 k# v( j0 K
    接下来我们需要添加一个交易,来完善下new_transaction()方法。' x; Z: G* K" v: o: I
  
  1. classBlockchain(object):
    / o, h  U/ r7 m7 }
  2.     ...: B- b( \) z& q9 t* D1 e1 L9 t
  3.     defnew_transaction(self,sender,recipient,amount):; y' u9 F/ C5 s; v" u' y1 H& d
  4.     """" k7 B2 B0 G7 C# Y( B; {
  5.     生成新交易信息,信息将加入到下一个待挖的区块中
    ) M: `' H1 {" e% f: T
  6.     :paramsender:AddressoftheSender. ^, t, y! y; R) h. M! p9 ~
  7.     :paramrecipient:AddressoftheRecipient6 O, ?3 P+ E; v
  8.     :paramamount:Amount
    0 k7 z7 N. l$ d3 G& N
  9.     :return:TheindexoftheBlockthatwillholdthistransaction
    ) p- w6 N' _2 Y
  10.     """
    0 ^" [" t3 ^8 n# A% F2 }$ d
  11.     self.current_transactions.append({
    / W( Y! A% M" r# i% X# V+ d
  12.     'sender':sender,( e4 `; ?, A/ [
  13.     'recipient':recipient,8 T$ R7 D6 Q& e1 C' x) B5 {
  14.     'amount':amount,
    , n+ i7 _. P( y5 Z4 G' v2 o
  15.     })4 {* i! u# ~8 ~1 ?) u* @
  16.     returnself.last_block['index']+1
复制代码

) \9 y3 c4 I, k. ?& }4 `    new_transaction()方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。
& B2 Y& m* F: v0 D    创建区块
) m  c6 ~' S) q5 ^    当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个“工作量证明”。+ |4 k4 d! H+ U, n/ p+ N
    每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。
, b1 ^  y. }6 H- }6 K    为了构造创世块,我们还需要完善new_block()、new_transaction()和hash()方法:
5 T2 {6 Y0 o/ h. q, K+ T  
  1. importhashlib* v. O( }% Y4 A! U) }) O2 w
  2.     importjson" T( L" L; e' t. t$ t
  3.     fromtimeimporttime! z0 i+ @) j  n) t
  4.     classBlockchain(object):
    1 J9 h# \; ~2 W
  5.     def__init__(self):
    5 W8 R5 l! z! b% i# H
  6.     self.current_transactions=[]! ^# A. r# Y* _
  7.     self.chain=[]
    + g6 O! w' D4 M6 r; [  _9 K
  8.     #Createthegenesisblock6 T. E* u  k/ `0 i3 I4 M
  9.     self.new_block(previous_hash=1,proof=100), M+ h( L* ], g" w" n7 W' K5 q" _
  10.     defnew_block(self,proof,previous_hash=None):
    " F: t( r# b! [" o, g. R; K' Y$ O
  11.     """
    2 H, J8 _4 z  ]! c' U
  12.     生成新块
    2 I/ T4 `7 l5 z9 n; v% e
  13.     :paramproof:TheproofgivenbytheProofofWorkalgorithm. Z7 |, d. `$ Y  O: W
  14.     :paramprevious_hash:(Optional)HashofpreviousBlock
    / ^( a# t5 y( ^+ H6 d: j) D
  15.     :return:NewBlock
    ' N$ }7 B2 h: Q4 V3 U
  16.     """, d% l3 {3 k0 l2 ^$ f
  17.     block={3 [9 r  a8 C$ C# W5 e0 o$ K, A
  18.     'index':len(self.chain)+1,
      q4 y/ J6 Z9 \; l
  19.     'timestamp':time(),2 [- ]4 w* C. _6 D4 N! H
  20.     'transactions':self.current_transactions,
    3 o( K" k! @) z# d
  21.     'proof':proof,
    , a0 I2 h# G6 M9 K6 ~8 b0 z
  22.     'previous_hash':previous_hashorself.hash(self.chain[-1]),
    + B* A. {# D" Q! A# Y$ k! Z
  23.     }) a9 z7 t/ V: H# i  y
  24.     #Resetthecurrentlistoftransactions
    ! N2 D# E( d( I7 c
  25.     self.current_transactions=[]+ Y3 }6 J: Q, Y  x- ]4 @% U
  26.     self.chain.append(block)
    % E# I+ M8 @4 a8 `* `; _& B# Z
  27.     returnblock' ]/ A+ G- u; }6 ]
  28.     defnew_transaction(self,sender,recipient,amount):/ z# ]6 j# H" X% ]5 o
  29.     """" d0 c. I' }; [6 j  q3 J7 n
  30.     生成新的交易信息,信息将加入到下一个待挖的区块中/ N* L; l5 E' U7 N
  31.     :paramsender:AddressoftheSender9 i* M8 T1 x  W$ B4 J1 |8 L% {- M
  32.     :paramrecipient:AddressoftheRecipient/ q1 M( T* F: E0 O
  33.     :paramamount:Amount3 \" h) Z* [. M; V; _; J$ ]+ J& o
  34.     :return:TheindexoftheBlockthatwillholdthistransaction
    " K! B- O- m+ ~4 X
  35.     """
    2 x" ~$ g0 A7 J7 J5 _
  36.     self.current_transactions.append({
    ! v. W5 F. }& k. Q( T
  37.     'sender':sender,. l& H+ S3 F  `% [6 o+ Z. {9 X9 V
  38.     'recipient':recipient,! H% @& F% C) S* C! ]( n
  39.     'amount':amount,) p9 m" j8 {6 p* ^7 y
  40.     })
    ' O8 e, |! [2 h6 Z2 K$ F7 r
  41.     returnself.last_block['index']+1
    5 j& o1 x/ s" d2 R* S' Y9 w. A
  42.     @property3 l; C+ \9 [- g! ~9 s
  43.     deflast_block(self):
    8 I% U2 f! f6 A, j6 r1 J& t
  44.     returnself.chain[-1]
    ; C$ z) {; _! b/ l: P" x4 ^
  45.     @staticmethod
    ( k! ~# C( R, _* x, F6 ]# c
  46.     defhash(block):* I5 |+ h, Z2 g# T8 Y! V1 H
  47.     """7 i/ A% [: M* p% m7 g+ J" _) d3 ?& v
  48.     生成块的SHA-256hash值& D. x1 B; _' l4 C
  49.     :paramblock:Block
    / p# A: v! W0 }5 f3 l. l- c3 C
  50.     :return:
    7 g0 l9 J( f! {9 B1 q% J. T
  51.     """
复制代码

& t; ^* @/ A. U; G/ _2 m5 u    #WemustmakesurethattheDictionaryisOrdered,orwe'llhaveinconsistenthashes
: L# U( F/ B8 r! A1 [5 h9 U: Y    block_string=json.dumps(block,sort_keys=True).encode()0 L$ i+ u7 U0 {# X0 l
    returnhashlib.sha256(block_string).hexdigest()
1 ], T9 F4 X0 x" N6 p+ e    上述应该是非常直接的——我已经添加了一些注释和字符来帮助大家理解。当表达出我们的区块链时,我们也快要完成了。但现在,你肯定在疑惑新区块是如何被创建、伪造或是挖出的。
' p( ]  P# {7 j4 n    理解工作量证明
/ f5 ?8 p" A& g. S    新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
3 _8 U8 \, ]6 r# J# w    为了方便理解,我们举个例子:
' s7 L5 [; X" J    假设一个整数x乘以另一个整数y的积的Hash值必须以0结尾,即hash(x*y)=ac23dc…0。设变量x=5,求y的值?用Python实现如下:
# n' I: ], U' ~
  1.   fromhashlibimportsha256% D: t6 B0 L. C5 S( s- w" X2 m
  2.     x=5
    & x+ D+ p( Y8 q7 ~, g- @( z
  3.     y=0#y未知
    " s$ ]+ c, J6 _$ a5 K
  4.     whilesha256(f'{x*y}'.encode()).hexdigest()[-1]!="0":
    . D2 f/ }9 V- m  Y
  5.     y+=17 g) k2 G$ R  I: n% ~3 N7 ]) x4 B
  6.     print(f'Thesolutionisy={y}')
复制代码

. z9 |0 P  l* a) U. P2 B. e    结果y=21,因为:- Q5 j; y: E3 g6 C  N* q5 O
    hash(5*21)=1253e9373e...5e3600155e860
5 l% ?1 D# V# |8 m8 f/ T) s" d8 ^1 Z    在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。3 g2 A0 Y3 c% E$ ]2 x1 }$ H& V
    当然,在网络上非常容易验证这个结果。' r  K1 U$ Y' t# J9 k; q6 _
    实现工作量证明
/ }$ [* Y* a: N- B6 n; }' }    让我们来实现一个相似PoW算法,规则是:8 K& `+ I$ V4 `! d
    寻找一个数p,使得它与前一个区块的proof拼接成的字符串的Hash值以4个零开头。# X! n0 T5 d$ V. `8 Y% W
  
  1. importhashlib. N5 q* Q: Y1 }' ^8 S
  2.     importjson
    $ v3 a+ F9 H! s
  3.     fromtimeimporttime
    8 u, \; ?. {# T/ W$ B
  4.     fromuuidimportuuid48 D) _% k5 o& Y) B5 r+ m4 I$ A
  5.     classBlockchain(object):9 n& X; n( Y! Y2 w; ?
  6.     ...; u1 \- G( v+ \$ T# P
  7.     defproof_of_work(self,last_proof):
    + g% a. @4 c- b. @
  8.     """
    $ ~; J7 a% X+ f0 W0 x
  9.     简单的工作量证明:$ o2 J+ t; X+ {7 c; n  @0 W9 g) Q
  10.     -查找一个p'使得hash(pp')以4个0开头
    . i& h- I, V1 B2 k; ]7 I
  11.     -p是上一个块的证明,p'是当前的证明3 s& a' C% Z* y7 X' r
  12.     :paramlast_proof:
    - E- k$ `0 r4 a# q$ i
  13.     :return:' V5 y0 l% G/ N: r% k
  14.     """
    % R( h& @; I+ L  f
  15.     proof=0" b7 Q- H  q( l$ U7 f, Y
  16.     whileself.valid_proof(last_proof,proof)isFalse:% K/ c4 l7 _% e* H" P6 z& o
  17.     proof+=1
    $ D/ I2 l% ^$ y: z( p0 _- u
  18.     returnproof
    % }/ p1 C$ |5 p/ K/ w
  19.     @staticmethod
    ! n9 l# H. {- i* y
  20.     defvalid_proof(last_proof,proof):& u# }/ ^/ {& N, d2 ]
  21.     """
    ' b  G9 p+ p; x+ E; J5 b
  22.     验证证明:是否hash(last_proof,proof)以4个0开头?
    7 r3 P& ^( F( h( Q: i( X# M
  23.     :paramlast_proof:PreviousProof
    % @8 p8 y, j# O* v8 }9 l
  24.     :paramproof:CurrentProof; N8 _' C& e8 E: Z& ^
  25.     :return:Trueifcorrect,Falseifnot.3 V! e; L( h+ n0 k+ }$ A
  26.     """
    7 r9 J+ d8 Z$ i2 C7 |
  27.     guess=f'{last_proof}{proof}'.encode()5 ?+ [& p  o. S1 q
  28.     guess_hash=hashlib.sha256(guess).hexdigest()
    ! }7 v! _, ?) U, [
  29.     returnguess_hash[:4]=="0000"
    % P) H) K' g- _: L
  30.     ```
复制代码
8 {  \8 e# M& a+ H  s1 n7 O5 X
    衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。0 m3 w7 D% N6 N% m
    现在Blockchain类基本已经完成了,接下来使用HTTPrequests来进行交互。
6 d; v) _, a3 I$ Q: j" s    ##二、BlockChain作为API接口4 i% a- ?  f: c# X( P' `2 f
    我们将使用PythonFlask框架,这是一个轻量Web应用框架,它方便将网络请求映射到Python函数,现在我们来让Blockchain运行在基于Flaskweb上。
; m# C; d( V* I: p    我们将创建三个接口:. _* M" k/ j$ _+ e/ \: b5 k
   
  1. ```/transactions/new```创建一个交易并添加到区块
    . i2 b( ~* E1 ]8 r
  2.     ```/mine```告诉服务器去挖掘新的区块
    + H7 i- D! n* i9 b; l
  3.     ```/chain```返回整个区块链/ F* X) k1 i; K  n8 z4 ~# i! M
  4.     ###创建节点$ K( w; g8 s$ t
  5.     我们的Flask服务器将扮演区块链网络中的一个节点。我们先添加一些框架代码:
    , Q+ T! `* d$ ?% ?
  6.     importhashlib& ~$ l2 s* R8 J9 j3 F( V# i) d
  7.     importjson
    2 K0 U) c$ t8 X6 r; Q' e* M) b
  8.     fromtextwrapimportdedent
    ( X0 m/ A8 l/ T; x  l- T" \3 E% P
  9.     fromtimeimporttime
    , ?  L% h' o2 z6 m
  10.     fromuuidimportuuid4
    5 f, ^; J# f( j! }! C9 A
  11.     fromflaskimportFlask
    ' w, d& m4 h( }' C
  12.     classBlockchain(object):* l7 R- A) [4 f2 b, K# U
  13.     …
      p9 C) q9 z. \  K7 D1 U
  14.     InstantiateourNode
    / _* a. T) k& @2 @" F
  15.     app=Flask(name)
    & i; {/ g( p& c9 X# _# c4 n+ U
  16.     Generateagloballyuniqueaddressforthisnode& j" G3 Y8 R4 V
  17.     node_identifier=str(uuid4()).replace(’-’,‘’)
    " n3 f5 W1 L  ~: g( r5 I4 d
  18.     InstantiatetheBlockchain$ U9 C/ B7 H3 Q" |8 w7 J
  19.     blockchain=Blockchain()
    4 C1 M; g: ^/ V% \
  20.     @app.route(’/mine’,methods=[‘GET’])) k' d5 L1 y0 {! j* b5 D4 L
  21.     defmine():
    ; r0 d! S. K0 e/ D7 `6 w* x
  22.     return“We’llmineanewBlock”  V0 ^8 X+ C& m0 b: a
  23.     @app.route(’/transactions/new’,methods=[‘POST’])
    5 v7 z7 Q) E1 k8 g: s7 a/ C
  24.     defnew_transaction():
    # ?$ L# ^0 `9 Z7 V( [& z. e6 M: D
  25.     return“We’lladdanewtransaction”
    5 w- A: C5 }& n7 M1 d% o2 L9 e; `6 h
  26.     @app.route(’/chain’,methods=[‘GET’])8 Y$ W# S9 ~# B  s2 i# g
  27.     deffull_chain():! [3 @1 w0 R/ Y9 Z7 {
  28.     response={- }4 j8 n# N' ^$ ~6 T: g
  29.     ‘chain’:blockchain.chain,; O6 m" v' g$ r
  30.     ‘length’:len(blockchain.chain),
    , j+ X. Y8 i, o
  31.     }. N  v& j1 `  _
  32.     returnjsonify(response),2007 Z' `; J9 _: K* l
  33.     ifname==‘main’:, g6 i" o6 ?/ @9 \2 n
  34.     app.run(host=‘0.0.0.0’,port=5000)5 Q9 v7 F* d4 B& X4 G
  35.     ```3 a+ V& ]; ]2 I) [% y
  36.     简单的说明一下以上代码:
    ) o# b4 A( T- v* B) e9 J
  37.     第15行:创建一个节点。在这里阅读更多关于Flask的东西。9 f: W% x9 Z2 w& p" a  m1 _
  38.     第18行:为节点创建一个随机的名字。4 i: T4 d2 {6 K& P+ m# H/ N
  39.     第21行:实例Blockchain类。
    / w* e' O, |+ Y2 e5 R
  40.     第24–26行:创建/mineGET接口。; [7 u7 N# `4 G" x3 l# K$ j
  41.     第28–30行:创建/transactions/newPOST接口,可以给接口发送交易数据。
    8 s" c: b; {' P7 G8 E3 z& m; u$ ]( t3 B8 A
  42.     第32–38行:创建/chain接口,返回整个区块链。; C8 d  I- _/ M
  43.     第40–41行:服务运行在端口5000上。
    - u7 M. U, \4 ~+ d/ }$ p2 g% Z
  44.     发送交易& }" C' v0 z8 P& C6 g8 ~/ X4 f
  45.     发送到节点的交易数据结构如下:
    3 S5 Z9 k6 v; b! L
  46.     {  Q6 a" o+ S8 d( X; E: z0 U
  47.     "sender":"myaddress",* y8 o" e  \0 E
  48.     "recipient":"someoneelse'saddress",: c/ _/ ~0 v) o" Z
  49.     "amount":57 i& y. I, E; L1 f  Q; C
  50.     }
      t4 K1 I' U& U% D. j
  51.     之前已经有添加交易的方法,基于接口来添加交易就很简单了:$ |: K7 ~4 L  n4 |
  52.     importhashlib+ X2 \: w( V3 t6 O, D' n! F
  53.     importjson4 E* }7 K) p9 Y- Q# \0 S# W
  54.     fromtextwrapimportdedent2 s- u- V- L; x9 \) l6 i
  55.     fromtimeimporttime
    # R/ I$ Y8 K9 _7 _9 F/ P0 h% n
  56.     fromuuidimportuuid4- x; i) F: o( U, V( \! l/ }: y
  57.     fromflaskimportFlask,jsonify,request3 T* M- i7 J' G& A' O1 T
  58.     ...- A3 w, r% Q  f) m" T
  59.     @app.route('/transactions/new',methods=['POST'])
    " s" e0 }( p# `7 u3 d4 b: r+ B8 `* g
  60.     defnew_transaction():% C7 E. j, v( h- H) Y( k) H
  61.     values=request.get_json()
    5 f9 Z- x% n: q* |, V
  62.     #CheckthattherequiredfieldsareinthePOST'eddata8 v8 Q$ b  ]4 {7 P
  63.     required=['sender','recipient','amount']+ e; Q6 t* \# r. i
  64.     ifnotall(kinvaluesforkinrequired):6 G2 K: e2 Y/ v
  65.     return'Missingvalues',400
    0 C7 S8 p; P/ j4 g0 u8 V
  66.     #CreateanewTransaction/ F& N% V1 g1 C; ?: d9 Y' U
  67.     index=blockchain.new_transaction(values['sender'],values['recipient'],values['amount'])/ U1 n, x+ u( A$ ?; J' p2 n
  68.     response={'message':f'TransactionwillbeaddedtoBlock{index}'}: b  o9 v4 @; d2 a
  69.     returnjsonify(response),201
    2 M% u2 H# s* \' W( P% J7 M
  70.     ```# S: s3 |* P% c* F2 c; }, |
  71.     -创建交易的方法-' i! M2 h! |/ Y0 @; n3 a; v1 w
  72.     ###挖矿
    7 \6 `/ F; v( }  `
  73.     挖矿正是神奇所在,它很简单,做了一下三件事:! z1 d2 P3 p: Z
  74.     计算工作量证明PoW8 `0 N0 s$ y1 ?
  75.     通过新增一个交易授予矿工(自己)一个币
    & ?8 m- p) ?. Q9 v' P$ w- ]
  76.     构造新区块并将其添加到链中" y' Y' l9 v- o, ?1 P
  77.     importhashlib% R! j; T4 @2 p: G, n( g4 X
  78.     importjson$ T# y, m# |1 [! H; ~/ u5 y
  79.     fromtimeimporttime7 @2 F1 e) x) J& R* N
  80.     fromuuidimportuuid4& e6 N3 n0 L1 A$ @' T  z6 {
  81.     fromflaskimportFlask,jsonify,request
    7 {8 P) S4 y3 H0 L) O7 I
  82.     …: v4 C4 r( J- c- u/ t% e7 V2 ?: g. ?) n
  83.     @app.route(’/mine’,methods=[‘GET’]); C# b. z0 e& P. K6 c+ Z8 r
  84.     defmine():
    % z+ i" B' ]- r- z7 f* i6 ^
  85.     #Weruntheproofofworkalgorithmtogetthenextproof…' }0 @! y+ Q/ T/ o2 T+ B; q
  86.     last_block=blockchain.last_block
    ( U8 p+ v( H% N$ Q) N
  87.     last_proof=last_block[‘proof’]
    2 F% s* A( E( @: P$ ~2 m0 {
  88.     proof=blockchain.proof_of_work(last_proof)
    3 y7 e0 n; }4 S/ S
  89.     #给工作量证明的节点提供奖励.0 |$ B6 D6 G" z  i' [
  90.     #发送者为"0"表明是新挖出的币.& a) d) X2 k' @, E" j
  91.     blockchain.new_transaction(
    ) u( Y. Z# u' y& O0 J$ ?2 t1 W5 y
  92.     sender="0",  _# b6 f' O7 c2 v5 L# h; p8 ?
  93.     recipient=node_identifier,0 N1 y  |2 X6 ^$ o9 T
  94.     amount=1,
    7 L, ^( n* _) p: @# W6 j/ O/ P
  95.     )
    . A2 q4 E% O0 \: s  R
  96.     #ForgethenewBlockbyaddingittothechain
    # v0 A! y) ]' j
  97.     previous_hash=blockchain.hash(last_block)* f5 u& O0 S4 q9 _( M& ]  z
  98.     block=blockchain.new_block(proof,previous_hash)
      c( o. l$ Z% N" M
  99.     response={
    # f$ R, [* ~( T' F5 g( Q( m
  100.     'message':"NewBlockForged",
    ) x! q" W8 p8 L, g7 v" X
  101.     'index':block['index'],
    ; |2 m# ~" a& c) a
  102.     'transactions':block['transactions'],
    , x! f- V8 ~3 U1 t
  103.     'proof':block['proof'],
    # [# M$ Q! N. U4 v: M6 L. w
  104.     'previous_hash':block['previous_hash'],
    % y6 h  W. Y7 ^# ~. r
  105.     }
    0 F  j% {4 Y+ @. b' u4 J
  106.     returnjsonify(response),200
    , B9 r& B5 T; ?9 D& B
  107.     ```
复制代码
" K. V+ P1 @# U/ _6 q5 d% i* `; i% Y, V/ k
    注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。% u' c/ h8 v. ?0 N; L, y
    三、运行区块链
( h% z9 A' O5 Q7 l. |/ q2 o$ m9 |    你可以使用cURL或Postman去和API进行交互
$ E8 `" @* K/ h+ Q1 [" I$ G4 k5 p    启动server:
6 u( A" u- G% i- @3 t7 |5 B    $pythonblockchain.py
" ]( m, ~# W0 |    *Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)
! Z' n5 Y; ^1 t8 k4 m* w( u9 ]. G    让我们通过发送GET请求到http://localhost:5000/mine来进行挖矿:
) L) J3 j5 |0 v& j) C    -使用Postman以创建一个GET请求-
! A9 J+ ?: k' y    通过发送POST请求到http://localhost:5000/transactions/new,添加一个新交易:# c" H( p- ~5 a" k: e  _
    -使用Postman以创建一个POST请求-
4 X. W$ y) l/ y6 E    如果不是使用Postman,则用一下的cURL语句也是一样的:6 F% R( w5 g+ L' O0 T
    $curl-XPOST-H"Content-Type:application/json"-d'{9 w/ Y7 g+ ~6 ^  Q
    "sender":"d4ee26eee15148ee92c6cd394edd974e",/ u# O1 I0 ]; Y, s6 |; v2 k
    "recipient":"someone-other-address",' {. T$ N$ c. J+ }) b  |. G, ]: g+ Y
    "amount":5
& C6 q) j: @- w) }    }'"http://localhost:5000/transactions/new"
1 F4 J  Q. W- [5 x    在挖了两次矿之后,就有3个块了,通过请求http://localhost:5000/chain可以得到所有的块信息:
: d0 l, z1 A# T1 _   
  1. {+ ?! g9 x) I% _- Q+ a% [1 d
  2.     "chain":[0 ]+ M9 R/ E0 P/ I" N
  3.     {
    1 m" |; `: q+ P4 K
  4.     "index":1,
    / z' \8 H4 F* N! s/ y; X: I1 B. ^; q
  5.     "previous_hash":1,
    + i: D4 Y; T5 r+ K% X
  6.     "proof":100,/ \! @9 L1 n0 J3 o+ _
  7.     "timestamp":1506280650.770839,
    + f  p1 B4 P0 _6 W
  8.     "transactions":[]/ J" P, N5 I) f
  9.     },6 C: Z# N# ^* f1 h# K
  10.     {
    0 }1 N* V0 l# k  B, j8 M* o
  11.     "index":2,4 m+ X4 K, l) u2 M7 t
  12.     "previous_hash":"c099bc...bfb7",: B' `! P. m2 b7 m- m; C
  13.     "proof":35293,
    ! W" C) n' n) n2 n  V6 ~  r! l
  14.     "timestamp":1506280664.717925,) S3 q  e8 p% J7 m$ D1 k1 H& a
  15.     "transactions":[! ^; H8 F) H$ P* {. |2 ]
  16.     {
    $ w7 `. C' b1 V1 x4 }5 C
  17.     "amount":1,
    # V' L, U  G' z  [2 ]) Q2 ~6 I
  18.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    3 z7 \9 u" X$ K; }8 O
  19.     "sender":"0"0 x7 F  N: G0 U  P2 a
  20.     }. `' D  m# t4 j5 x+ l
  21.     ]. B5 k5 x) S. `  J. M4 X5 x
  22.     },
    * N5 w) S2 I( Q  ^2 Y
  23.     {2 n# u$ W( r$ n  p9 ~) r/ u; m
  24.     "index":3,- i1 |! Y& Z& Q, N: n5 q  \
  25.     "previous_hash":"eff91a...10f2",3 x! E2 R( g" Z, d& `: B
  26.     "proof":35089,0 k. D) M1 i0 U
  27.     "timestamp":1506280666.1086972,* a$ n# ^# Z  S: V
  28.     "transactions":[
    6 o4 L8 O$ t" n9 ^- E- w
  29.     {, `+ D9 R5 M* c" U0 a& }: I) P
  30.     "amount":1,
    2 m8 V( m$ v3 {  e6 \* a- r1 b- p
  31.     "recipient":"8bbcb347e0634905b0cac7955bae152b",
    4 z3 U- i6 {6 C
  32.     "sender":"0"9 x4 r! y  ~% ~: H+ r' r" `
  33.     }3 O+ _- k+ A) [/ O, f6 }  f% U3 d7 c- f
  34.     ]8 t" R3 R; P3 ^* ^
  35.     }
      n& Z7 C# y9 @
  36.     ],4 V/ c: z/ @- g/ a6 k
  37.     "length":3# w! Z$ |( Q. o  B0 T
  38.     }
复制代码

) i7 x% N- v4 p1 |; M( I4 m9 I4 G    四、一致性(共识)
1 C3 b. q/ m: x    非常棒,我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。! r+ f! q* q: T! d4 \- Z1 Q1 @
    注册节点
1 C0 j& t9 d% L* C$ o+ k) b: J* D    在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
( V8 j" |) [- N    /nodes/register接收URL形式的新节点列表* B1 K8 {+ X# e$ f8 u6 c
    /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链
) j; ~3 c# m1 b: i2 `" v$ w    我们修改下Blockchain的init函数并提供一个注册节点方法:
9 T) l+ _* a# G' }  
  1. ...! w; }7 r! z+ Y  {& h: P
  2.     fromurllib.parseimporturlparse: c3 T0 M9 }  \7 ?
  3.     ...; O8 V! j( Z0 @% Y8 e4 A
  4.     classBlockchain(object):
    # w: Y; c4 ?) M9 F& P/ O
  5.     def__init__(self):- n$ O. ~, I' A( W% ]
  6.     ...
      x  j4 W' t$ r! ^9 Q5 D
  7.     self.nodes=set()
    6 y: t' {# s" ]0 d/ ^
  8.     ...8 m: q- S+ D/ I- p7 h' b7 m
  9.     defregister_node(self,address):
    $ y* ^7 H3 {5 Q3 w& @& f3 U
  10.     """: d1 d6 {# z' O- d9 L) m* b: p# i
  11.     Addanewnodetothelistofnodes& m$ T6 E2 e: d) S
  12.     :paramaddress:Addressofnode.Eg.'http://192.168.0.5:5000'
    4 r6 W. T; H! @& e, b- S
  13.     :return:None
    / q: g. I) H# g4 n% I. |
  14.     """( l! u- n, ?* y- `8 i0 b
  15.     parsed_url=urlparse(address)" V: M3 X% L( m3 Q- u; {3 d9 D
  16.     self.nodes.add(parsed_url.netloc)
    0 z* z) c3 G* [0 B
  17.     ```
    1 B' \* T" ]+ O, y  U
  18.     我们用set()来储存节点,这是一种避免重复添加节点的简单方法——这意味着无论我们添加特定节点多少次,它实际上都只添加了一次。5 D, Q( ]5 Q5 G) U3 O% ]
  19.     ###实现共识算法! S7 N, L, \7 U8 N2 Z/ Z/ ?5 k
  20.     前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。我们使用以下的算法,来达到网络中的共识。
    + @5 y$ ~' f- }
  21.     …1 f7 M! ]5 `; N0 m
  22.     importrequests
    4 G, T& f9 o1 }' K
  23.     classBlockchain(object)0 M! f' ]$ q* X0 `
  24.     …4 D4 L. f; j0 e' W, [- Z: ?
  25.     defvalid_chain(self,chain):/ f  ?: l9 Y: I, ?3 K. f" G8 q1 l
  26.     """/ l$ _& J+ [9 R+ N# ?
  27.     Determineifagivenblockchainisvalid" R8 ~2 ^4 X: [# s$ Q& m, u9 ?# N. g
  28.     :paramchain:Ablockchain
    1 O; j' j. I7 {6 f7 G& T- t
  29.     :return:Trueifvalid,Falseifnot
    2 `, |8 {  W& Q0 s9 F" u
  30.     """
    ! h! n/ Q# y' H. l5 d/ G
  31.     last_block=chain[0]
    2 i# W  l( U5 E8 f8 B
  32.     current_index=10 \1 ~3 K9 @" k" P
  33.     whilecurrent_indexmax_lengthandself.valid_chain(chain):" X& [9 f# O5 D) B; D9 q4 C
  34.     max_length=length5 c6 X* \6 ]5 v2 b4 a+ l" A: q% j
  35.     new_chain=chain
    0 h8 c# |( ~3 \* r, A
  36.     #Replaceourchainifwediscoveredanew,validchainlongerthanours* Y9 i+ [$ @7 b0 @$ e
  37.     ifnew_chain:
    9 r3 E* c: Y' ]9 W2 F4 }
  38.     self.chain=new_chain( B% Z3 F) ?. f- I3 h% o
  39.     returnTrue3 F7 W- J) f  Q) A* y5 a
  40.     returnFalse$ z2 X, B' _: F! V: g3 \' t! D
  41.     ```
复制代码
+ ~  Z# y$ l2 _7 ~) ~
    第1个方法valid_chain()用来检查是否是有效链,遍历每个块验证hash和proof。& I- N5 K6 c$ E% a0 [
    第2个方法resolve_conflicts()用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性,如果发现有效更长链,就替换掉自己的链。
7 y; D0 v* X9 @) Q  G. @/ T& X    让我们添加两个路由,一个用来注册节点,一个用来解决冲突。
0 T+ R, A5 R2 {   
  1. @app.route('/nodes/register',methods=['POST'])8 \% @( s( M$ P0 e7 V
  2.     defregister_nodes():3 c7 K1 j6 N0 y4 f( |6 d% s
  3.     values=request.get_json()
    9 o, }" l* x9 W: d' @# B; b  e6 y
  4.     nodes=values.get('nodes')
    0 l8 [# ^$ C6 h) ]: ^+ o: D
  5.     ifnodesisNone:
      I" u" Q1 E5 o' Z) p' `0 l
  6.     return"Error:Pleasesupplyavalidlistofnodes",400
    $ E' j/ i# R4 q
  7.     fornodeinnodes:
    # F& r8 q4 b. N! e1 p
  8.     blockchain.register_node(node)" ~$ E: v7 m- p* j8 I! N
  9.     response={
    8 X! V3 p& c, E
  10.     'message':'Newnodeshavebeenadded',
    " i) y  @$ u* X5 ?# \; J0 B
  11.     'total_nodes':list(blockchain.nodes),
    / h4 u; @: A7 o' _: v/ d" A
  12.     }
    2 |2 x9 R, h2 n. P( q8 C* L. |
  13.     returnjsonify(response),201
    . Z/ e+ h6 g+ B1 e$ e1 h
  14.     @app.route('/nodes/resolve',methods=['GET'])
      Z" }; P' W$ L! R8 i
  15.     defconsensus():
    5 m" S  e6 v  Q
  16.     replaced=blockchain.resolve_conflicts()% O, P# t& G; _3 S
  17.     ifreplaced:
    ; E  t: ~: J5 D+ B. d- L
  18.     response={; Q1 Q! r5 t! G( j8 a2 _
  19.     'message':'Ourchainwasreplaced',
    1 }2 v9 D  g  }, e  y7 [
  20.     'new_chain':blockchain.chain0 o( C8 t, I! N7 A/ T! |! j7 }3 a* _
  21.     }% s9 H0 Y  m& }
  22.     else:
    4 h5 h4 u1 g' B5 w) t8 {0 e
  23.     response={
    & ]5 t4 K( p! V! U0 u3 e' o& l
  24.     'message':'Ourchainisauthoritative',+ Q% e+ i* d1 K2 p5 |
  25.     'chain':blockchain.chain, q6 M, p- }+ z! l. e7 e
  26.     }
    $ D, O0 X+ b( Q2 J- K. f3 Q0 b
  27.     returnjsonify(response),200* X6 {$ L. K6 C* ?: c7 ~8 c
  28.     ```
复制代码
, `5 c; Q; [2 X/ N
    你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:```http://localhost:5000```和```http://localhost:5001```。
1 [' ]- b4 x' q8 i5 j) ~/ H # `' N7 j7 ]' R$ z
    -注册一个新的节点-
, h2 C/ R. i" E    然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问GET/nodes/resolve,这时节点1的链会通过共识算法被节点2的链取代。
标签: Python
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

江左没浪 小学生
  • 粉丝

    18

  • 关注

    0

  • 主题

    7