Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
191 0 0
NodeJS实现简易区块链
2 w1 b7 n" S+ d3 ]* T之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。9 }( x; |2 p) i$ H0 e: \
但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
+ v! \8 Y$ }8 h想了想,就动手做试试咯。
3 H2 o# ?$ h& U7 N) g?查看全部教程 / 阅读原文?
& B7 i; C( _: I1 e, d. Z  p5 I技术调研2 _2 F3 }( o% J7 C9 N
在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。
) D5 ^" g9 W6 F' r6 n结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。+ T. U! ^6 @4 l; V% r! Y; @: {: f7 X
区块定义
* |0 N2 I; C" N. J# j) U* U为了方便表示区块,将其封装为一个class,它没有任何方法:
3 Y1 d( \6 p1 Z' f2 p! [- _! S/**
8 {9 n$ b% Q3 x8 B. U4 u * 区块信息的结构化定义5 }+ j7 I! ^' f, [# Q
*/
) ?/ @. s/ N% Z  s; Aclass Block {( t* v+ }: t% F' ]9 c
  /**
1 j4 h8 T2 x8 d# R  F   * 构造函数
: q( Y  l( r+ H) F   * @param {Number} index
8 ]7 h5 F7 B" ?( \5 O; ?   * @param {String} previousHash
: n; f' o- v& C& _  C' y   * @param {Number} timestamp
* I2 G+ N- i9 u) J1 S   * @param {*} data   c  ~% G( ~( f$ Y4 \
   * @param {String} hash 4 h7 Y2 W& W2 E$ S& |2 b& I
   */0 H! \. C/ @6 Y1 X: G* B, \( j' k
  constructor(index, previousHash, timestamp, data, hash) {
# l9 h2 R: e. T3 N- Y! G    this.index = index // 区块的位置3 L# K+ s5 |: R4 a6 ?
    this.previousHash = previousHash + '' // 前一个区块的hash$ d' V- ?9 I8 n7 x. M- h# ?# s
    this.timestamp = timestamp // 生成区块时候的时间戳
( x' `! h. j% Z: b+ O+ I6 G    this.data = data // 区块本身携带的数据
/ n8 ^+ ^' h' g9 `" k' a% Q    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
4 C; L6 J1 ?( o9 m. D1 U& ]  Q/ r. y  }
5 u! t" I! ^0 T}1 }' p: `5 m5 b8 w: `0 h
至于怎么生成hash,这里采用的规则比较简单:
. n/ n" x! P3 F9 t0 x6 ?" h! \拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash
0 s1 K8 Q- h* ?  z% X3 [, Q" |1 ~4 G& e0 C3 @1 j* h: t
为了方便,会引入一个加密库:* I- D7 d) s- o: h
const CryptoJS = require('crypto-js')0 c8 x' \0 b5 R3 K0 D) e8 s
链结构定义
# S% u' L9 D6 b0 g) S7 |+ p/ W$ S" P很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:. E7 W* Y  d4 h
按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算0 F) X% S3 L' Y3 v

: z( K6 \" }; {, N+ b  L$ j3 Q1. 起源块
. K8 h' ]- W" S/ e起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。0 \- m1 e; \8 D9 j
class BlockChain {
; c3 b; E0 G! J. a$ ~( x/ p  constructor() {- ?6 w( ^. Q  O% L, [( \
    this.blocks = [this.getGenesisBlock()]$ e6 \/ A  [1 Y' P; ^! i  h
  }
/ I8 J$ j. p* `  /**! M9 K' L# s9 E* ?7 i
   * 创建区块链起源块, 此块是硬编码
" \! I5 D9 w2 J5 f- |. w   */
( @2 h0 l7 q- k+ N  getGenesisBlock() {! x3 D& N' q8 |% H( a, E
    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
$ f1 x0 M) C6 o  }$ e! l/ g; w  [/ }% u7 _
}
, e7 w1 p  I8 ]4 m! l2. 计算下一个区块3 R2 {6 G* T$ z+ x+ d- A  S
BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
9 C) |4 W5 e2 r4 M+ ~7 s  z// 方法都是BlockChain对象方法
  l7 q$ Q' [9 M  /**
& G2 F, Q5 u. w4 V   * 根据信息计算hash值8 X3 a. m# }% K8 Y3 j
   */4 d4 C% u* ^' _; B( j
  calcuteHash(index, previousHash, timestamp, data) {
2 g4 b/ U/ p9 H3 N% Z! A    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''0 C9 K$ o- X5 S; Q7 Y, |3 L
  }
. d) J2 p) g! ^# c: H  /**% G1 q6 f0 R( i* |
   * 得到区块链中最后一个块节点
- {3 Y* h2 c; h9 h4 D2 k/ W   */
0 I5 m# R% N: C  getLatestBlock() {
" h/ Q1 ]+ b+ v8 s    return this.blocks[this.blocks.length - 1]+ R7 W4 K7 u. k3 S! e
  }) @# k; Q6 a+ V7 C5 e$ e( A
  /**
4 n# C! H2 C/ X/ O7 V9 I   * 计算当前链表的下一个区块( V3 d1 C2 `6 g0 }  y
   * @param {*} blockData
, n" k# Q# m% t   */& I# k6 P) q% N2 r
  generateNextBlock(blockData) {
& ~7 S1 ?/ x- j1 j! d: }# D    const previousBlock = this.getLatestBlock()- x& }+ p4 v$ {2 D
    const nextIndex = previousBlock.index + 1
  D: G( `& E3 J" p4 m    const nextTimeStamp = new Date().getTime()
# A1 V5 g' j* ?# Q2 D- Q) B! {    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
. k/ p2 N  n9 T( P5 \    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
6 k2 `- R: Y2 O' l" B7 q, C( V' W  }3 M" L. W6 q8 J7 _" U
3. 插入区块
2 F$ z, c' }9 W+ p5 A" b& A插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。
$ C/ o; i; `1 V8 D1 B  /**
2 A7 J: T* T' [6 ?   * 向区块链添加新节点/ \: g+ Z& o& l0 a
   * @param {Block} newBlock
) o, C+ _& B$ Q% P) P   */
% [1 ~. I; w; V5 `/ p  addBlock(newBlock) {
+ v; a" G/ ?. i+ y" _    // 合法区块
( _7 Z, [$ C" F+ t2 e. `! v# H! Z    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {+ A, |1 T5 f) q5 V$ d
      this.blocks.push(newBlock)
: Q9 \% w. [- N+ ~* v5 c& W! S      return true  
8 B" M  e! E" @* T; o9 A    }
0 V3 T3 o$ `. H. y% w( ~    return false$ U5 C% J# L: V, x  ~. G
  }% ^( Q: U  u. n, R* ^8 u
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:' N6 m* d, V  F; d& H
判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成& Y2 e) t3 P0 Q' J- v$ c* n
5 D4 r) b( K/ m2 H
  /**
3 Q* c2 W7 f5 c" I   * 判断新加入的块是否合法7 a: P. ?: q: t( d2 V- C& [( D; f6 H
   * @param {Block} newBlock ( ?# \& w2 g( z+ C
   * @param {Block} previousBlock
. Q6 }) l1 X; B. I7 b  C6 U+ h   */& }$ K/ u- S: l1 I' E
  isValidNewBlock(newBlock, previousBlock) {
! ^' E; l& _- ]. i4 B    if(
; |" v' C& n! Q, K/ s+ E      !(newBlock instanceof Block) ||  I& L8 I. a# z) S1 g7 ^5 p& H% O* w; }
      !(previousBlock instanceof Block)
) Y2 t, X. K6 ^9 B3 s    ) {0 H) L7 `! I. k+ @( M
      return false/ O1 w- ?2 J$ ?
    }
4 Q3 i8 }( U4 h3 W' _  f2 l; h    // 判断index
) K: y/ Y: h( k5 u- C    if(newBlock.index !== previousBlock.index + 1) {
7 M+ Q% g) z. W( g      return false
/ W# N/ z/ \/ {. S" X    }
* v! n1 U7 Z+ F4 S- F2 e    // 判断hash值
( `" ]% Q) x/ Z5 H) J# M. c! M    if(newBlock.previousHash !== previousBlock.hash) {
6 R. S$ I% g; d3 @  Y0 C- Q7 V/ V      return false; r2 A5 R" z( j* S% B5 R; O  e
    }0 U2 u7 n- M4 a5 m5 m6 }
    // 计算新块的hash值是否符合规则5 `* ^# g( L( L$ O" O( G1 H) K
    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) {
8 P9 z: ]  w9 ?2 H4 @4 p/ l' a      return false
7 T2 Z$ X9 j/ Y; U7 p* r    }
  p2 q! Y* `9 r0 F2 B% Y    return true
' H* {7 E% L8 o9 H  }$ J" r' y* L0 b9 a7 t
4. 批量插入$ ^0 |( c- T, ?3 H
批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。  t) B% ]1 v3 k) q5 p
在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。3 x* }% h7 D0 F0 _) ?" t
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链4 x9 n, F0 [4 S+ q
插入新链的方法如下:, R! O7 V% d, ]3 p& e" n+ O
  /**( \' t) r5 \3 \9 ^/ N% n0 ?
   * 插入新链表
$ V2 O6 g! _3 E/ J1 Z  b* h   * @param {Array} newChain   M7 O$ n1 f5 p5 [: E
   */
$ u6 T* s9 b2 R0 r9 |  addChain(newChain) {
) |% p( I9 ?( T2 B4 u    if(this.isValidNewChain(newChain)) {6 e1 P& q% h3 f; R3 d  w7 V' Q9 [5 ?
      const index = newChain[0].index) `5 Y. J, X6 K2 l
      this.blocks.splice(index); R2 q/ ~" N. I, C
      this.blocks = this.blocks.concat(newChain)2 Q; ?( x# G. a4 [8 g( {% m0 U, G
      return true% c/ O1 v0 L$ ^1 X8 a# R
    }0 a) u8 ~5 A6 u) E0 w
    return false
6 t" {: `6 i$ x# C$ @0 q& e  }3 B6 r) \) W8 _; G, B9 e% W: o
实现上面所述逻辑的方法如下:% v/ ]4 m5 s  e) y. |8 c
  /**
& u& \  A3 x  e% R9 G   * 判断新插入的区块链是否合法而且可以覆盖原来的节点
4 y8 e8 k! O9 e0 c. C% ?" ~   * @param {Array} newChain
& o" j: L2 p! u' l# Q$ a! k   */2 n+ Z. k. ^2 X! y/ A
  isValidNewChain(newChain) {
1 B9 P# I9 Q) m    if(Array.isArray(newChain) === false || newChain.length === 0) {
/ c2 Z# k2 [7 X" ?" x      return false
$ n) @. E) U. ^5 o% ~& a    }: ?8 x; v9 S$ Q* X0 u2 ]0 I8 h
    let newChainLength = newChain.length,
4 [: E5 y" v- b! h      firstBlock = newChain[0]; Z, F! \+ e% S. h9 j8 ^  ^
    // 硬编码的起源块不能改变
# w5 ]5 V9 `( w1 I    if(firstBlock.index === 0) {8 v  i1 c( i% m) `. u# U
      return false5 }/ g. b* t& n# j7 x1 n# W
    }  u" n! }9 a  j  L
    // 移植新的链的长度 & C" G! V9 `" ?- ?
5. 为什么需要批量插入?! n: E/ T4 z" v( o( f/ o& r, D
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。
1 {; |1 {9 ^: r# WA与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。6 K- A" O& I7 ^3 Y; X
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。0 G3 Z/ a( e# }+ M& }7 ?# c
效果测试
* x, r1 r8 [- M/ B4 w/ Z虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:; A9 s6 I* u5 t8 h4 _
5 b. `# u/ k1 a, N! w! u* s
红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
, T3 e4 A6 m* [4 P* H6 u( u* w全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1