Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
197 0 0
NodeJS实现简易区块链6 ]) s( w2 j, j) b9 A
之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。' n; r* {6 j& Q
但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
. i& D6 G& a1 M5 f( Y7 d6 ]% c. a! ?想了想,就动手做试试咯。3 u0 Q% q4 k9 L6 X
?查看全部教程 / 阅读原文?
/ G' X- `  M  _/ z# J& R5 Z4 m技术调研
; l# V! m$ C! h& k, ]1 T在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。9 S  v6 _1 u% j
结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。
6 Q8 {3 W5 M1 {% I区块定义
0 Q8 G6 v  U: q. l% s* T* t0 H为了方便表示区块,将其封装为一个class,它没有任何方法:7 m5 w) X. S& @6 g4 k
/**' C& r) A3 B3 E' T& q/ ^( n
* 区块信息的结构化定义
7 d" v+ G4 A. d+ g0 f+ K: d */8 p8 q( C* W* E; H8 w
class Block {% ]0 z' Q1 C0 e: X# V. m
  /**
* o& Z5 K1 Z; b  G; F5 d   * 构造函数) w5 A) s0 x) O  F3 f& g
   * @param {Number} index * H$ k4 v0 r: i2 s
   * @param {String} previousHash ! R  k* o% e3 @, C3 `+ n
   * @param {Number} timestamp ( X( c* b3 e3 s
   * @param {*} data
4 Y9 z& m: A$ Y5 N* T   * @param {String} hash + B* ]1 C9 j# I) a( H
   */
+ a" ^9 Z3 v3 b: ]3 [" t& S7 [- g  constructor(index, previousHash, timestamp, data, hash) {/ Q6 L) M8 {7 S" I' C& l/ O
    this.index = index // 区块的位置
  M1 G8 F9 i8 f  H7 B: G" j    this.previousHash = previousHash + '' // 前一个区块的hash5 i+ @+ K. f0 U. B. d6 a
    this.timestamp = timestamp // 生成区块时候的时间戳! V) K. K3 M4 C9 D1 U' E: j# x
    this.data = data // 区块本身携带的数据
3 i8 `0 D) S: J# }7 t    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
+ r7 W9 ~; H7 a" B8 p3 ?1 y  }
5 M& F& S0 d( x# |! a7 M}
, Z+ X: o! k0 g9 x至于怎么生成hash,这里采用的规则比较简单:
/ e9 }8 c8 Q+ s拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash7 m: j: I6 M+ u& o2 e; Y
$ x% g( q% v: S/ k# Y
为了方便,会引入一个加密库:
# T4 s* `8 b% i- j9 _const CryptoJS = require('crypto-js')
/ x7 H: j* H& A9 T3 L/ w+ T链结构定义' u/ k( r: Y. m2 j) e5 N2 J
很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:
6 k" x, s! t2 [7 i按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算0 k, o1 v' `+ B. y0 Y9 O7 e# Y3 ~8 r
) z7 t; X/ [+ s7 ]$ K# D& |
1. 起源块
4 v. e* M% e) a% n3 p起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
% T9 g: U& A6 I$ g: l+ Sclass BlockChain {) H0 W" `6 P3 ]  ?9 Y( z, b# l( `
  constructor() {
7 N( o0 `# C! p. V; }; x    this.blocks = [this.getGenesisBlock()]
; d; K8 E- g6 @5 {  x4 w2 b  }
+ F# B$ C# \% T- `  /**& ]6 G/ ~, S; [, g! Q% z
   * 创建区块链起源块, 此块是硬编码
/ X, h: v7 g; W   */
" d) }: d' c0 P3 ]4 E) J* h! d  getGenesisBlock() {
$ `+ g  b/ @: l/ Z8 D, g, G+ ~    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
$ ]: k* ~* n, Q3 S' |! J  }  O) c# @; t% H, A7 g, f; i
}
8 b, i: T( ~6 y" [  m0 W2. 计算下一个区块# G' G6 ^# e' n' r
BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。( T! Q2 Q5 F! F7 O3 w4 `) l" c% w& K
// 方法都是BlockChain对象方法2 `. ~9 @* N) p# b( }+ f* W6 V
  /**
; R, B- R* J( {  I  [   * 根据信息计算hash值
3 V2 y- ]5 b5 m) e& R0 p( U# J   */; H# w& S7 g0 ^' ~
  calcuteHash(index, previousHash, timestamp, data) {
3 O9 }4 A0 D# x    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''8 `& S3 g& w; q, K- y  _
  }( E# m0 W  k6 e  J1 u0 r" `) v
  /**4 |' n4 q  x7 @* c
   * 得到区块链中最后一个块节点" s/ l* R3 c7 w
   */" D" Q+ K. ?! s  s) b
  getLatestBlock() {
/ T' q) W, }7 a1 i2 Z    return this.blocks[this.blocks.length - 1]
* w2 m8 V% e* K  }
# Q6 I( J  j0 f' R/ \! L  /**
! U/ ^' M$ U0 l" \. P4 T  C( ?   * 计算当前链表的下一个区块
2 a7 }& z% X7 d8 s   * @param {*} blockData   d8 u) U0 j' q6 j9 {: e9 p
   */1 N$ T, P  M% {% [) p. d: m0 G
  generateNextBlock(blockData) {
6 X: b+ A+ s6 D) n7 P    const previousBlock = this.getLatestBlock()
' b5 [! ~# k- h1 D+ ~% _9 m    const nextIndex = previousBlock.index + 1
) f" }. t6 R6 D$ f' ]. j4 G# O  c    const nextTimeStamp = new Date().getTime()
/ D: \0 x, A) P( o6 r- x' j+ E/ O- U    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
, o( \/ a; [- m    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
, G5 K7 n3 x" M% o; F* D7 a  }* ]: K7 L9 d7 y1 v, A
3. 插入区块5 G/ n2 b7 `: _5 j7 y' v; b
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。
1 Y. d/ V2 X8 Q; Z4 `  /**. Z6 P7 N7 `3 ]* ?/ ^
   * 向区块链添加新节点( t3 V5 _! ?# r
   * @param {Block} newBlock
- K( M; t# ~+ a' a   */
( l( Y, {# Y) J% b, q7 L7 u  addBlock(newBlock) {; ?! d/ b5 n' g2 v) V% L
    // 合法区块
1 t8 L% v; Y$ t! G; r6 L9 ?    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {! |8 c* p6 A' i! g; r% u
      this.blocks.push(newBlock): {8 f5 |) A! u1 ]' w1 y
      return true  
% i$ F# x3 K# L9 x' _* G$ q    }, E$ w, N# I# j3 J$ }
    return false, `: s& y0 ^# F& y- E9 }) [  a2 S
  }
$ Q' w1 l# n" L6 x检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:
1 h# S1 T9 U! {: v! [判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成6 F' w) C8 z5 w% [/ q) I# w
5 o+ @4 `* X& t( W- r0 v2 u+ A
  /**
; i/ b# O; \: V$ I   * 判断新加入的块是否合法
+ Z8 `) `  `* z0 s) ?# G   * @param {Block} newBlock ) _+ f! a$ l2 V$ J" m" I
   * @param {Block} previousBlock
& h" S; F) u* N8 O   */
+ Q9 f6 R/ ~: o9 P- X# F  isValidNewBlock(newBlock, previousBlock) {3 c6 R$ G1 `2 m
    if(3 s; l) p% Q8 c5 n
      !(newBlock instanceof Block) ||
- N9 A& p' l$ O      !(previousBlock instanceof Block)
! E* J# D+ h) e: w) z    ) {& F+ @- D4 D# H, ?1 I( j$ k
      return false8 J$ L, M5 }( n5 ?% a
    }
6 r$ E/ ?: s1 J7 B8 ]# z) i    // 判断index: i3 \) h# j& w& l5 o
    if(newBlock.index !== previousBlock.index + 1) { , [& H3 O" [; D' f7 @3 E
      return false
! I5 Z: G" p- O$ z/ u    }
  ^2 }, A" q# Z& w1 H. s    // 判断hash值
! l, g2 c) {  ^    if(newBlock.previousHash !== previousBlock.hash) {
# I  K8 T) V" O, i" h      return false
9 |3 k4 l  {# }/ B4 S  m2 V* u7 f    }' E1 l! E. v+ x* i$ O3 c
    // 计算新块的hash值是否符合规则  W! L: x$ `( N: ~
    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) { 9 k' K! r8 h& I" Z
      return false
/ f' f  Q% \6 L, W. T- y, z- K    }
$ h* z1 d! r, h4 Q/ f% X$ k- w    return true
; q5 d% r0 [8 N0 _: g  }( }3 D: [' v9 K( A  s
4. 批量插入, j* E; G7 F9 K" F
批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。( u6 y) U5 u4 R* ?  m, S6 ]/ F
在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。8 v+ g+ r0 M# {& q
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链
1 p. J& \/ h' D" W插入新链的方法如下:/ c7 u, y, U" F  ~# D1 o
  /**
2 D5 p; l' d! O  F8 h5 G   * 插入新链表
8 B1 A; v. d$ h7 j# S   * @param {Array} newChain 9 U" E  ~: {# g
   */6 y0 y/ i2 W6 U- _, p
  addChain(newChain) {! O, j/ x9 m/ R6 g- O& b+ A) e" z
    if(this.isValidNewChain(newChain)) {) b2 z3 c/ Q: {
      const index = newChain[0].index) W+ S: g9 m6 u$ b/ F
      this.blocks.splice(index)
, X) m4 h: P# @- {# Y! j      this.blocks = this.blocks.concat(newChain)
. s' n* \4 g9 z1 Q% E5 c- q      return true9 e% {: n. u+ E* R2 D8 @0 r
    }0 ^( O. b, J- F  |5 s4 R# c3 Y1 n
    return false
& q& k! h; n, q3 n8 L' ^  }% {$ x) v+ S/ m+ \7 H
实现上面所述逻辑的方法如下:
! Z) i; q2 _' T* W. Z  /**9 I$ R8 \/ h1 A; j
   * 判断新插入的区块链是否合法而且可以覆盖原来的节点
5 H" W0 ^: z7 g6 m, Q$ H   * @param {Array} newChain
# t. D3 @, A: N" H7 m5 L   */  P9 y, c1 e0 I$ A! v
  isValidNewChain(newChain) {
# V! x* m+ s7 D! W. ?0 J% ~    if(Array.isArray(newChain) === false || newChain.length === 0) {/ _1 |5 R8 D# M
      return false- \; W, ^% }$ Q5 h5 s
    }% u1 m% Y4 h; M. u
    let newChainLength = newChain.length,3 S. c: k4 s; D
      firstBlock = newChain[0]9 ^( l4 n6 _+ N% A
    // 硬编码的起源块不能改变
* @0 P6 N* p" z    if(firstBlock.index === 0) {
, P5 m7 ?% a0 e/ L  s  U0 o9 k      return false: A8 Y3 O  t8 j* [; Z$ y& X9 g
    }/ `# y+ T3 ]+ ?" o
    // 移植新的链的长度 ) @- A' @" y- O  O
5. 为什么需要批量插入?
2 R- x5 w* C7 C: I我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。9 e. Z: w# S9 i
A与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。+ q4 S+ i7 b; {" M9 ?+ E/ n
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。. F  x; R3 m7 o
效果测试
5 j- k- f7 t% {8 e2 A1 K虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
6 Y9 K  d/ z7 @7 D
( l3 i; W$ M. s8 ?: A" s1 R; g红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。  m& h4 Z- F! t# R
全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1