Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
121 0 0
NodeJS实现简易区块链- B4 @$ M$ p! v, H
之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。' X6 _! y& t; O! P! c0 h
但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
9 O6 n0 K; A# V0 e想了想,就动手做试试咯。
# e0 \( F9 z% u( [) ]?查看全部教程 / 阅读原文?
* u7 F  {2 J7 M$ g- x3 N  s技术调研1 q5 y1 r& p. b
在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。
' H' a, S1 k# q9 x3 r3 B结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。& w4 W: i9 q2 E- g7 @0 f
区块定义* w' G( d; w# U& z/ V
为了方便表示区块,将其封装为一个class,它没有任何方法:( h9 b) q. c- a" V
/**
: Z) t# e; _9 H4 } * 区块信息的结构化定义* p' e9 ^$ i9 h4 s5 C3 t+ f5 T
*// P( t  u# x  N4 Y
class Block {' n, @- Z: R) r0 r
  /**7 |, v  L6 z7 b' \
   * 构造函数
, A, w2 o4 [7 l   * @param {Number} index : c) ]* y8 _7 o; `* `8 j
   * @param {String} previousHash
/ c, U1 F+ h/ ^( m( B   * @param {Number} timestamp ' y5 }& u3 [' e8 I0 h$ u! v1 A
   * @param {*} data
; o  ?8 y; T4 Q2 d$ p   * @param {String} hash ( R. H* w7 }4 `  j: f
   */% M  |' G2 Q" J* V& s
  constructor(index, previousHash, timestamp, data, hash) {: L5 M9 ]" ?6 ?2 `
    this.index = index // 区块的位置; C) U$ w8 |0 u/ ~
    this.previousHash = previousHash + '' // 前一个区块的hash, R' ^$ {4 F3 j2 g" N; X/ a2 }
    this.timestamp = timestamp // 生成区块时候的时间戳
- Z, {, c) K0 _. o9 S4 e    this.data = data // 区块本身携带的数据
% b5 k8 `- U7 ?; G2 j  i    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
) T* k5 ^" \$ z: o; o4 J  }  j( r) w& \, |
}& w) S. i: C! g
至于怎么生成hash,这里采用的规则比较简单:
- J3 Q$ i& j7 H拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash
% }3 \8 K6 L1 A) r  u/ d! U) d
! n6 P, c. z) L4 @% G' i3 Y' j为了方便,会引入一个加密库:1 Z8 G  Q# s, L/ T' D& {8 Z5 C3 l2 h
const CryptoJS = require('crypto-js')
3 V2 x5 g# U9 a9 Q9 ^/ i# I( E链结构定义0 V. N; r) q5 Q4 w9 l
很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:
( T; O1 _& f3 E" C4 P, c按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算9 l' q7 s3 n7 p- y, k( }& _5 t

, @5 f" \2 s! ]8 w& F- S1. 起源块2 Z+ H- F6 W7 @  c1 h
起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
9 D! }) n' s7 `0 B$ c5 mclass BlockChain {2 W$ t2 j4 b9 X  s) f
  constructor() {
! j" ?  t# K( ^  A, q+ B' m    this.blocks = [this.getGenesisBlock()]: [  Z' A3 W3 q8 D
  }# ^0 @, O* M0 B" g; ^0 R6 V6 ]
  /**; r* H6 r/ [9 W3 C/ w5 C
   * 创建区块链起源块, 此块是硬编码5 I* }& D/ e' v" v2 z' `: q5 J4 d
   */( k) _8 e* R# V8 ^2 v( |& c) T: R8 }
  getGenesisBlock() {+ C& @( O* }6 K0 F
    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
4 m+ j" C6 Z7 Q; U3 G  }' J- V' b; t! H- ~. Y
}
6 E4 B/ G0 x; A. S4 K6 L2. 计算下一个区块
3 r9 q* m+ }- G2 v3 bBlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
# s9 G3 p% v! E+ r5 d// 方法都是BlockChain对象方法1 R# b1 e$ y3 I, @% i
  /**
# h8 _. f+ t/ j; X$ I   * 根据信息计算hash值
. g/ o; V2 O  s5 X- |0 _* c   */
1 }2 R0 W' Y5 D/ K; I* l5 O  calcuteHash(index, previousHash, timestamp, data) {
. l8 q6 w7 E/ B/ ^! v    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''
$ ~2 D7 U( Q& m  D0 T. f  }0 c- r) Q5 d) @$ ]
  /**: P/ g: j/ B, P. ?3 \; K
   * 得到区块链中最后一个块节点
! V' N2 w9 |- w$ g' `3 u   */2 ]% q' R2 `3 z7 [9 Z
  getLatestBlock() {
- D, K% n7 ~3 @5 E    return this.blocks[this.blocks.length - 1]. p; i: ?+ ~! Z$ b% f7 m5 }5 }9 B
  }8 ]* A" `, d2 n5 I; i& w- ]! q* u
  /**% W. x* v, t$ ~! U  N
   * 计算当前链表的下一个区块
6 s: a( m6 X, O% S4 E& _* t   * @param {*} blockData " A& f- M- P  ?0 g
   */* U8 s! `8 a+ m# @2 p7 y- p
  generateNextBlock(blockData) {. C' u+ i9 _8 S9 ]" g
    const previousBlock = this.getLatestBlock(); P% Z; b4 J8 ^- l
    const nextIndex = previousBlock.index + 1) H5 I8 U4 j: O$ _
    const nextTimeStamp = new Date().getTime()& O$ |5 h. u( }6 _
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
) ]* I2 {" h! o4 }    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
2 r) _" v: X/ a  }
) |( K2 v" r8 s6 H* O: q) a3. 插入区块
  K% z$ r. K1 M4 {& Q8 U* T1 A3 H插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。6 \: j* @7 D7 H- n- d% l) {+ k
  /**
' j4 P1 `5 {  X5 _* Q   * 向区块链添加新节点# n; x1 f; Q7 a7 Q; t, y) R( J
   * @param {Block} newBlock : g9 P7 o7 ^* `- n# H
   */
$ {& E; L- o6 o  addBlock(newBlock) {
9 `# V5 s1 o# q; k" `    // 合法区块
2 \' n1 I. B; W    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
$ T+ \5 T" B- |/ [2 `      this.blocks.push(newBlock)
# N. ~, n) m& _$ `+ p      return true  5 O3 S! q( D( C3 z* T
    }
  E$ v+ o4 k: A# q. g- K    return false
' T7 o- A/ O# z  z  }) t& g; P2 B6 P6 W9 J
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:
8 L* J, y+ E! a1 Q7 n8 F  i判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成8 [8 M: H* D4 L! y- Z. k4 U9 i  i
9 V1 m$ d, a" O$ C2 e
  /**
% y0 t5 K; C) m. U0 @, W0 U   * 判断新加入的块是否合法4 Y) l; w2 I3 u: E5 s- {3 D. v
   * @param {Block} newBlock
# q7 U5 V) |- V! d5 `   * @param {Block} previousBlock 7 a; X) w! ]2 ?% ]4 P" E4 Q  ?
   */0 w8 B( ^: n( i) n) O5 x
  isValidNewBlock(newBlock, previousBlock) {/ U) V) l/ p: c# a3 q1 c) T
    if(5 h3 k  d% D0 z+ J$ Y% r! D+ e2 v
      !(newBlock instanceof Block) ||% S$ {3 @8 t8 n; h& b
      !(previousBlock instanceof Block), H" m+ D. ]0 o6 N+ p6 w
    ) {& N/ ]" t* O1 X, w
      return false
0 c8 U8 w$ S4 \4 t) B" C    }. v( Y( @4 o" E) R: u
    // 判断index
* w$ i% x& E3 v6 k- m0 L    if(newBlock.index !== previousBlock.index + 1) { 6 w) K/ W+ J& U
      return false
" S" @& m) h+ f7 S    }
* W3 a/ K$ N) f5 h    // 判断hash值
8 x7 i9 M* `) g/ D& [    if(newBlock.previousHash !== previousBlock.hash) { - z) B, X! E; x0 O' S) H
      return false( j2 {+ q. Q: H; o
    }
! R7 _* f/ C' q% G. O# M: r' D    // 计算新块的hash值是否符合规则1 m+ A; d2 y  A( ?4 x, b+ y3 s
    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) {
5 R' ]6 i- z' \      return false
% L% R- Q, Q0 g- m8 y( l    }% E/ \, c$ U5 ^3 b8 D/ p! Y
    return true
) ~2 D$ a2 }) t! m, S  }7 n" _. u. H) @6 h$ O% d
4. 批量插入$ p0 Y9 [* w: k) H5 g" }" t
批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。9 G, M9 V" o  q$ q. X* ?
在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。
2 k. o; z. d, v/ j" J但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链
2 b; i# k+ t3 r' U  P! b插入新链的方法如下:( G! v3 i. V- I& @; T3 W7 e
  /**
7 h. y8 X1 B/ Q6 A  M$ B   * 插入新链表
# K% B: f" K' ~   * @param {Array} newChain 9 N/ F/ `6 E, Q  d2 x  _1 s
   */
' F/ r  e6 w' O! I7 \  addChain(newChain) {
2 F+ {1 z8 t3 ^$ }    if(this.isValidNewChain(newChain)) {
! C3 @* I# Y1 l4 \" _      const index = newChain[0].index
+ p. a$ o* c  C9 U      this.blocks.splice(index)
1 t1 r, m: i  o* C# Y      this.blocks = this.blocks.concat(newChain)
6 l# U9 w) [/ p7 d& w( E4 s6 Z      return true
/ }) @1 O; y/ a! `- U; r    }
5 c% ]1 y, y* `1 w( N! K    return false
/ N  U4 S* n2 j' V" i1 g  }
. m0 [) J* a% ~! ?( G+ r% f实现上面所述逻辑的方法如下:
. i, i8 j$ S% @" m  /**
2 o0 X$ h' S4 d) C* o( z5 |7 [9 ^/ z   * 判断新插入的区块链是否合法而且可以覆盖原来的节点) o/ O  Z3 A2 e. l$ h
   * @param {Array} newChain
: G5 X% h# v4 C8 |! a4 w   */
  J2 k5 \' b, {. D5 L0 _  isValidNewChain(newChain) {
- L9 B! R2 P& ~, P. {$ ?- @    if(Array.isArray(newChain) === false || newChain.length === 0) {
+ |6 p8 \( I$ C( N/ j# J      return false: d9 {* f: ?% p& D& V' l2 q
    }+ b1 Q6 E# h6 @: B
    let newChainLength = newChain.length,
) b" J! x5 T1 a0 y, U* e# S2 T      firstBlock = newChain[0]! h' i! g/ V) k) o8 f
    // 硬编码的起源块不能改变6 A! W1 J7 q: c+ c
    if(firstBlock.index === 0) {
- y, ~1 J  Y- g, R- F2 X      return false" }7 R8 p1 y, W
    }( U9 A8 ~6 O, }: @
    // 移植新的链的长度 . a7 K  m/ z9 s4 W
5. 为什么需要批量插入?/ k5 _+ y# T! Q4 o/ ^
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。, k. N& @& z& R, B  {. K
A与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。$ Z- D+ f3 Q+ g: ]$ y2 V0 p5 m
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。
% |. o- K6 Q- Q$ M/ g( }+ ^1 W效果测试
3 T& d  G. a+ \8 w5 q) N. S虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
" y# \, }( A/ p- u, F! Q8 q# D- y
. U, m+ i+ Z% x# D, V* K  F红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。% `' Q, l% h. o& v4 A! K. d0 [
全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1