Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
176 0 0
NodeJS实现简易区块链
2 t" H& F: v' {# b& P3 S: I之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。* N9 r- k1 x- m$ D9 N
但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。+ w5 k( @- b6 ~0 T4 u* A! R4 K
想了想,就动手做试试咯。9 B+ @7 I" r* Z/ I5 Y
?查看全部教程 / 阅读原文?4 B# W  e" Z" L) m  _8 {) b, t
技术调研. K, _8 t2 U& }- ~
在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。
. i4 K6 G. _  B1 q7 B结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。' ^) ]5 l1 S6 a; R' x1 E" J
区块定义
$ y& s9 L, j+ ?为了方便表示区块,将其封装为一个class,它没有任何方法:
# t% m. Z- P' l$ A! @( d/**! r8 ~3 h% c+ D6 Z; o: [6 V
* 区块信息的结构化定义
. F4 @- J) P1 Q  I1 W */, P" y# e$ t  I
class Block {
9 `) T  h/ C( r7 C' d# G1 n4 \  /**
: N- F: `/ h# m   * 构造函数
, w4 x, D3 s/ v% s7 J   * @param {Number} index
$ P6 [( K  E( p$ t% m" f6 r7 T   * @param {String} previousHash & e. ~" _; m/ c2 H# M
   * @param {Number} timestamp 3 Q$ m/ e# r7 N# w7 D
   * @param {*} data
+ l: Y1 v! R0 j4 s, g, Z9 b5 M   * @param {String} hash
) @. _  P4 c3 Z   */* j, T0 F) j1 U  |# `, V* s
  constructor(index, previousHash, timestamp, data, hash) {
# J3 B) b: n3 Y6 {  F    this.index = index // 区块的位置
% E1 a+ l- b8 O8 P    this.previousHash = previousHash + '' // 前一个区块的hash! V* R. |# Y, K+ {- `
    this.timestamp = timestamp // 生成区块时候的时间戳
) W; ~  u1 D  U3 o2 y    this.data = data // 区块本身携带的数据
" _; |8 ^! [5 d% O    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
( K0 k* y! A1 V& I4 ]8 q  }- D0 L/ L' q- |6 t9 ]
}) o+ v* W4 U4 }$ e& O
至于怎么生成hash,这里采用的规则比较简单:
( P" {- F9 e  w$ P. ~5 H% U4 |拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash% V# f' _% R, {7 `8 {0 B

" V. {! T% V' p' d, ^! ?% G为了方便,会引入一个加密库:
* Y5 u9 O6 @7 ^+ n* o- W7 x( s# g5 Rconst CryptoJS = require('crypto-js')
: v, P5 r  d7 H链结构定义
  U% j: Y8 V3 m2 C, h( |很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:6 W# o, X+ I* p# @
按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算
( X1 E) e+ L( W  K; V4 h3 W) k+ ~4 {
1. 起源块
/ ^0 }6 V) q. r$ q- ?起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。( u( X# q6 K& D) }
class BlockChain {$ V7 f  G  v& k9 o% m2 ?+ e4 X
  constructor() {) I1 R$ z, q1 _( ~
    this.blocks = [this.getGenesisBlock()]+ ~: c4 Y% ]7 T2 J
  }, H- Q% G7 _; b$ t1 z
  /**; n8 ^- t, Y+ S: N8 u
   * 创建区块链起源块, 此块是硬编码! `: N; P8 \1 W2 f
   */
+ o" n3 S- s8 @  getGenesisBlock() {
5 |8 [% G% l/ b4 j: I- J) |/ S1 `    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')2 k# o) g7 n7 r/ t# k
  }
0 Q* j: g" e9 N" A}
9 H0 c! H9 ]. f+ L2. 计算下一个区块7 Z7 k; Z: w- J* ?& e3 }7 s: D
BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
) S/ z; T8 X. G5 h* A& Z3 R/ z// 方法都是BlockChain对象方法
$ W6 h( |+ H' j7 T' b7 i5 q/ q  /**0 a# o8 K2 k! E9 V: L* w# j  q" h
   * 根据信息计算hash值
9 c$ ~  F: f" [! R* G   */
# ~% `6 ~& Y$ Z& k  calcuteHash(index, previousHash, timestamp, data) {
6 \/ N6 t7 F, Z4 t    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''
& j7 Z2 E" p+ T  }
# \) J0 h8 _# G/ l' D! e; T  /**' O* M5 j  ]4 Q. D5 X8 A
   * 得到区块链中最后一个块节点
  Y( x7 G5 J1 @# L+ U8 K! p9 `+ i   */  p' w( Z- s6 b; x; H( j
  getLatestBlock() {
$ }* z' b" R. G/ ]  V    return this.blocks[this.blocks.length - 1]2 G, \. A" s' i
  }
" \5 _4 V1 K1 ^  Y  /**
* ^0 \! Y. f* R   * 计算当前链表的下一个区块0 I& n" b2 S& h: J
   * @param {*} blockData ! D5 @8 B* C4 w1 z
   */8 W2 Y1 F" |& Q, K: |! x8 a( r
  generateNextBlock(blockData) {
- p* T9 J( @' _. E% H# W    const previousBlock = this.getLatestBlock()! q1 A/ s0 g2 `1 c2 Z; S) d
    const nextIndex = previousBlock.index + 14 z/ W4 q3 b7 |: X" N6 H
    const nextTimeStamp = new Date().getTime(); E2 |; U4 q2 }7 B7 ^) T* s
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
6 D' d- S9 j! A! C    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash); S: k; k) v9 E$ {0 F1 A* H
  }
2 u  P4 w* g6 u2 e' V* C- }( C3. 插入区块: F& b* E" X$ D7 R
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。. U4 [: r) _4 a2 t% D* y
  /**
$ {& B' b( x  H7 u   * 向区块链添加新节点
. {6 P1 l" X2 F. O0 i/ |# a* _( A. I   * @param {Block} newBlock
0 y, t2 k3 r# u8 I/ C: _( S   */
0 i( w$ Z$ }2 U/ |$ l! u  addBlock(newBlock) {
0 x6 q" }4 K' Q7 ~    // 合法区块: w0 T9 D9 _! U0 N3 |/ [
    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
6 J1 o# C& |- ~      this.blocks.push(newBlock)# O/ n/ s8 _2 n0 w6 V0 D0 }% V( X
      return true  + R+ p' q1 e  b3 t
    }
7 w/ L* H1 C2 p    return false
. Z: [8 p+ s8 r( p! m+ {, P2 C! L  }' o: t4 B& w, W! G3 v4 U
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:! }3 n, _7 c1 P9 g6 T4 [3 y+ \. Y
判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成8 J: P6 K8 J. r* S+ E, {
; M$ X2 U, \9 m1 o0 T
  /**& ?/ }9 A% H) b8 h* Z9 ^. w* H
   * 判断新加入的块是否合法; I' M; X* R  ^5 }& h- x. V
   * @param {Block} newBlock
+ K7 Z4 B  L. `0 [   * @param {Block} previousBlock
4 W: Z. P9 b4 U4 M   */' \' {- f# K2 q9 [; e' ?- f
  isValidNewBlock(newBlock, previousBlock) {( S& Q" O$ I$ R3 U! {1 Q$ d/ j
    if(- D, v0 O5 a0 D% _0 m1 B+ {# Z: P
      !(newBlock instanceof Block) ||
$ p8 `* `9 A. I. P8 o$ u& U* P& S      !(previousBlock instanceof Block)
4 a- X+ I; p" y' v: q7 F; q    ) {
: l; L. h/ T+ M9 i8 C      return false
* P7 y& k/ Y, F3 @    }5 P" O, S% }6 c' r& z4 u6 ~
    // 判断index
2 ^1 ^: t% x, h+ d% P7 b    if(newBlock.index !== previousBlock.index + 1) { / m3 S; M! c2 [7 o: l
      return false
% n; M$ E0 H* Y- ?" A" Y    }# H4 G/ o: f  Y8 V+ B3 n
    // 判断hash值
4 @6 m* ~* Q! r6 `) @# `' Y    if(newBlock.previousHash !== previousBlock.hash) {
& Y' A) w+ U. V+ B      return false
% h' H9 J. b1 [4 [( L, _) ^$ j    }
: R- m, v7 w. c  z" i    // 计算新块的hash值是否符合规则
/ Z' X' ^3 ?0 O    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) {
% W+ H  G, Q# o* r3 ^0 D9 L; e      return false
2 S) l/ C2 ?; ]    }: F9 i; H. p3 T3 O
    return true9 m+ U$ v( V' o  V: _! p# R) J9 P. |
  }
* Q) w& [" ~8 P  \  D/ i4. 批量插入: @( v; ~$ J  Q% S4 _4 C# V
批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。
: O. H# F+ V8 A7 Z( Z/ M1 C* M在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。1 x( \+ n- g( @# U6 U
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链
, Q. q% @7 p7 R+ s插入新链的方法如下:  i; E$ @" ^$ }4 u
  /**2 W7 H& E% W% O- L3 G
   * 插入新链表
$ F% Q! W4 P7 a( U   * @param {Array} newChain 6 u4 f9 H8 W& N' w
   */+ w6 k, w* x: W
  addChain(newChain) {
$ o$ i. \, ^7 a; v) M; C! b) D    if(this.isValidNewChain(newChain)) {* z4 S5 Q. j. y+ O
      const index = newChain[0].index
7 n# Q6 Z) w: Q8 R      this.blocks.splice(index): m$ C1 P2 k/ _, {
      this.blocks = this.blocks.concat(newChain)
0 m. Q$ _" @& B/ F4 _! H      return true
- _6 S8 u+ |1 e    }
2 V6 @% U0 M6 m9 o! R    return false
8 H) w* x2 e+ D  }# h9 y1 Y, j  u- H8 q
实现上面所述逻辑的方法如下:
1 K' g5 E  Y1 d) N2 t) r  /**
$ v+ Q+ [, L8 w! T* b- `   * 判断新插入的区块链是否合法而且可以覆盖原来的节点
- V! ^0 p! k# j% p7 t6 M1 K   * @param {Array} newChain " L4 x7 t3 r: ?# {/ }" Z# `
   */! \; z# B: h( k
  isValidNewChain(newChain) {. E4 L& }+ g+ ]* |1 m+ A
    if(Array.isArray(newChain) === false || newChain.length === 0) {. s7 n# d  x( }; N  j) }
      return false  f1 E( n: V# y
    }- A4 ^# E9 z$ i6 X+ u& x+ p1 {
    let newChainLength = newChain.length,
) i- P* `( S1 k. j" [7 C      firstBlock = newChain[0]
( p8 E& d  k# H5 {    // 硬编码的起源块不能改变1 c7 f. d! G- [, J3 G4 c% |* D
    if(firstBlock.index === 0) {
6 E3 {" @+ T3 x3 ^      return false- o. w  _; g6 B
    }3 y, o. b+ u% o5 c4 r+ l
    // 移植新的链的长度 ) r8 e* T6 F3 j7 g
5. 为什么需要批量插入?# O0 F9 f+ n8 a5 G5 N
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。
  K& I4 H$ j9 I9 L4 ~A与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。2 N+ F: d$ w& X# C0 _% d
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。
( V, l$ [. G" i% F5 g8 y" L效果测试
) M% a" U% i2 f4 F0 I& D3 T" g  c虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
$ Q7 v: ?3 e) ]3 u' \' f/ g3 I
; U0 d! d# b% Y6 I  i) ?. b红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
' P5 n( b6 i# V$ O, x9 J+ @6 K( K全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1