Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
115 0 0
NodeJS实现简易区块链
, v  ~; a0 t6 W2 @% @之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。
: U# C3 H- C' O3 D但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。' T% M# }; J+ [% f4 d% ?# N' G: ~
想了想,就动手做试试咯。. \5 d7 n+ l5 R& H
?查看全部教程 / 阅读原文?/ z, }' J3 h9 o) g; K9 d% K0 n% P
技术调研
6 b: Q1 n' n5 P在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。# R8 ?7 R, d: M
结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。
: u7 q0 |0 t3 f* e* `6 c4 G2 M区块定义( S2 b3 d' R/ \2 ?# B2 o  ~1 t5 L
为了方便表示区块,将其封装为一个class,它没有任何方法:1 Z# D( W* t: B
/**
8 @3 G: I+ ?/ u& z8 W& e% G * 区块信息的结构化定义  C& }$ h$ V2 Y  W5 R. Y
*/6 Q$ |  a# Z  M' Y( L4 j7 Z
class Block {4 M4 w7 [' B* P. J. ]
  /**
% e2 |9 F4 v9 ^   * 构造函数' y  U: G/ b. f5 k" L6 \/ H
   * @param {Number} index
' E4 s1 q. O% v1 D   * @param {String} previousHash 0 w7 u/ d. X; J: Y$ P7 w
   * @param {Number} timestamp " P6 G4 B1 J) l
   * @param {*} data 9 d# l+ b, O9 i8 G8 R. ]
   * @param {String} hash
, J# I7 I- b( Q" W2 x9 Z' A   */# t* J4 f2 k, u) ?
  constructor(index, previousHash, timestamp, data, hash) {! u* G# F% s  v/ s. z
    this.index = index // 区块的位置0 b. G: J! [. U+ j
    this.previousHash = previousHash + '' // 前一个区块的hash7 E0 P8 \& m# b1 H7 Z/ T( K% p
    this.timestamp = timestamp // 生成区块时候的时间戳/ G( J" S" b! \" c- F
    this.data = data // 区块本身携带的数据; W5 x9 o( F3 |4 h
    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
0 @) l( e( @  h  }
. O  G6 y: T" r3 d}. i- Z* h, ?2 M  |
至于怎么生成hash,这里采用的规则比较简单:
/ R! A4 }1 n9 Q+ s& J拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash' f' O. k4 \# D0 R; H" d

* @/ C% _6 v5 s4 ?& O1 z+ N为了方便,会引入一个加密库:
0 d1 V6 F4 s9 |const CryptoJS = require('crypto-js')  |3 z8 a; T+ o: H
链结构定义
: a8 [1 Z* J' B3 J很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:+ F" {; V/ l( H* F
按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算) Y2 U) A) E# q2 L% Y% X. ?* v
8 i/ n$ a$ m% \9 x& s
1. 起源块# v: X8 s* O8 R6 W7 u% m
起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。( V7 O; d' w& ]2 Y
class BlockChain {
* L! h* e2 c8 I% d  constructor() {! q7 W# z; [+ u+ g  t) F
    this.blocks = [this.getGenesisBlock()]
) t$ g# v6 L) J6 L! ]3 {  }7 F& X# T4 R+ X  t' h( T
  /**
" \- I% x  ~# ^: O) q9 G8 o   * 创建区块链起源块, 此块是硬编码
7 _# H; D& T0 k0 T- t2 I9 P   */9 b2 y6 }; T* q3 b( P  N0 u
  getGenesisBlock() {, {& ^$ U; P8 c  m- S. ~$ V; s) l
    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
% b0 i5 a- R- J; y; `1 ?, R  }
4 d: X( y; H) T  I2 |0 |}0 E6 O, a2 N6 ?2 M& }
2. 计算下一个区块
; N2 \) }* P$ x" N$ qBlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
5 x" z, G+ R7 w' H// 方法都是BlockChain对象方法) N7 e" v9 ^  U7 I8 B* [- ~6 G
  /**
  Z+ a' F$ S5 p+ p$ k1 w   * 根据信息计算hash值5 D$ s- }6 Y9 f: A, x
   */
; G0 M+ W% P3 e/ r0 l$ l! j5 b  calcuteHash(index, previousHash, timestamp, data) {
# @) }+ z& k1 @8 j% _1 n& Q2 f    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''
5 X% W- ~8 q: B4 q  }) y, u8 E* B( G2 L4 p7 u
  /**
; B5 M9 L7 l% ~" X   * 得到区块链中最后一个块节点# N5 `$ g( o8 G- M" J7 s/ o
   */
( `: J; C+ \! n3 O- L$ H  getLatestBlock() {! H# \8 p! J$ V% B0 G4 |- w7 _
    return this.blocks[this.blocks.length - 1]% F- B* C3 g7 n6 Q
  }( C  w" x5 i; k! t' [
  /**, t' f2 @; X( C* ~7 ]4 s) F
   * 计算当前链表的下一个区块) e7 W5 _) J* J
   * @param {*} blockData
% Z0 ^' s4 s# R/ C  l' D$ a   */
. X- W" N2 }' x3 x* g  generateNextBlock(blockData) {! ]$ ~/ `% \8 O5 X3 ^
    const previousBlock = this.getLatestBlock()
  B/ D& b1 g% K6 z- ?  d    const nextIndex = previousBlock.index + 16 G& F2 L' ~6 w
    const nextTimeStamp = new Date().getTime()' d/ r$ a) w  S/ u1 R* m  G
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)3 ]# H9 v2 L( ^3 i9 d' y8 q
    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
+ R& A3 i# @" |2 L) p  }7 ~$ H" a, z8 Y9 x) O
3. 插入区块, I. {' j/ ]& Y( O3 ]! G) T
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。
6 m$ r5 }1 o# M6 T8 |: {  /**
# V4 n8 i( T) Q0 M/ H   * 向区块链添加新节点/ n  J) H3 p1 |
   * @param {Block} newBlock
# p0 h1 `- f* `   */: g3 _, Q2 _* H  L* Z7 c$ ]
  addBlock(newBlock) {1 _: m: O/ z8 U7 l
    // 合法区块7 Y0 x- ^: p2 {: l1 P3 a
    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
0 c) N$ ^" e, _: c( L  U      this.blocks.push(newBlock)
5 o$ p# G4 v1 v      return true  
/ Y1 ~8 X8 E7 {5 ]( Q0 P) c    }
1 H# h  Y9 Z  e& ?    return false
6 x1 m" `" e3 t% c5 @: v  }$ W0 Q5 g% m, B
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:
7 K/ J2 L! G5 j判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成
! N& _) q8 u5 h* P6 e3 R: U* N& `
/ x. l  p/ p; p3 O% Z  /**4 N9 {+ E/ L" c
   * 判断新加入的块是否合法
! ~$ i% X! X2 d) g' v   * @param {Block} newBlock + @' p/ Q& I( V9 b' q& K8 {6 N: M
   * @param {Block} previousBlock
# Q; p& e) X3 Z( S2 e; U) B5 h   */" l* m. z9 z0 P8 q$ P  L7 n
  isValidNewBlock(newBlock, previousBlock) {
8 S0 X+ _# l& S7 _& X    if(
* c: ?( ]" r/ N- q$ k      !(newBlock instanceof Block) ||8 z( s' p: k9 y- c5 u
      !(previousBlock instanceof Block): W* B+ v4 g' Y. k8 X
    ) {
4 a7 w; k; a9 z- \. [4 R' ?5 m- z      return false
* P' l5 M/ _6 P9 L; A+ A* v( J* l    }
9 o  ~6 F8 V& g    // 判断index& u( h4 L. S0 E' i  `# y2 y# E7 l
    if(newBlock.index !== previousBlock.index + 1) {
+ L6 s6 D/ D: q      return false8 y5 x5 j8 T2 o" x& |4 i
    }" m& V$ q- q5 {% T9 A) I
    // 判断hash值: l9 d! G5 D0 u2 |" e, s9 C) _
    if(newBlock.previousHash !== previousBlock.hash) {
# S' Y9 h7 j: }6 v' t: y1 e' ]6 q      return false
1 U% S) C; S* b4 K  M+ U    }
3 S$ ]  W' N0 o- J' r; e8 V! M    // 计算新块的hash值是否符合规则
! t9 a- X) J3 ^% y4 R    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) { # i3 I) b9 c/ F$ U& a5 I
      return false6 W( ~% D3 ]: u
    }8 E: j* g) q) p6 g: o- \! G) P
    return true, x5 M/ [9 j! b0 U
  }
$ y1 v) c/ r: X2 C& S  V4. 批量插入# x1 e0 m: ]( ~6 x: S4 m- u
批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。
2 O- l7 q6 W7 j7 S1 d0 P( K在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。
1 ?. `& A: Q9 h+ e/ B但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链9 A, \3 b) ~  R* t3 c
插入新链的方法如下:- b- m* s# Q5 m. T7 [: D; }# z
  /**
9 s: d7 m5 L% S   * 插入新链表
& I# J5 ?% F5 @: {* j: v   * @param {Array} newChain , x9 e- q3 e, P% X5 f
   */4 S% H4 `( I6 Y" Y' C
  addChain(newChain) {
2 H6 f- c0 C7 w    if(this.isValidNewChain(newChain)) {
$ O! E8 n( b& D& O. D      const index = newChain[0].index# A/ a) S' y9 @1 u8 m6 o8 f
      this.blocks.splice(index)
# a% n; J4 P7 @" s+ T. }      this.blocks = this.blocks.concat(newChain)
# z1 V/ r$ i; V2 L8 ^6 l      return true
* x# Q( Q+ l6 j# y/ t% `' ?    }
: e6 h1 [1 J: c9 }+ |    return false1 a, Y2 n- u( x9 F
  }
5 z) ]# s2 b6 m6 a8 I* G: `( @实现上面所述逻辑的方法如下:
* H+ Z5 M9 |  Z$ K* h6 F! y+ M  /**; X7 V9 k& S7 ]5 V$ z
   * 判断新插入的区块链是否合法而且可以覆盖原来的节点
2 h  v6 D0 |3 k$ b. c   * @param {Array} newChain
/ L& o' C! U" o   */
. f& s) u: B; V" F  isValidNewChain(newChain) {) |* b% `8 |$ P, M( M7 p
    if(Array.isArray(newChain) === false || newChain.length === 0) {
/ v+ p6 E' L$ B) c, Y$ T' E      return false9 ]2 R+ j% ]1 P5 s
    }
: K, |5 J! i  O% ]: c( H    let newChainLength = newChain.length,
3 c  _) G- J5 o  j      firstBlock = newChain[0]  Y1 ?( T% S9 v! K  r4 `
    // 硬编码的起源块不能改变  o9 a- [; c  i- @! m4 S
    if(firstBlock.index === 0) {: W" Z$ p' h1 P, R; ^4 Q
      return false
( c/ M5 {" }8 R4 I! P) r    }
5 c8 k' K$ v" ]7 i' r' P    // 移植新的链的长度
4 L; r0 O  n# Y5. 为什么需要批量插入?
( X+ E5 ?/ l0 v  D, ?我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。- h2 v; d! F) o- c& }) e4 V$ A6 v
A与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。
- z5 d( X$ Z  J8 |" ^而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。
1 M; k& S3 ^  L. K3 O, B' l3 O# ]效果测试6 N0 K3 f3 G0 ?
虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
; t8 K' z! N! }3 v
; S4 Y  W7 |2 i, Q  V9 E# g& N8 Q! a' |红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
5 X. W4 c, C+ M$ N, q' H2 F全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1