Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
152 0 0
NodeJS实现简易区块链
) @5 D7 |  F9 }+ O之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。
. h4 r) m2 b- e- L但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
! }. `! m, R/ A  V+ o+ z想了想,就动手做试试咯。
- m* _+ z" @) t8 j?查看全部教程 / 阅读原文?/ `" @: C! K8 d, e0 Q
技术调研
( {6 v6 y) E4 ^- X/ Q在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。0 C6 Y( X8 {/ p
结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。# l6 k1 }9 a0 L3 w+ d# m6 S
区块定义
& W5 N" a5 H0 e为了方便表示区块,将其封装为一个class,它没有任何方法:; D- j1 Y5 a* L/ S
/**
' l( i; y3 l4 T, d% Y* I# I" l * 区块信息的结构化定义
; m0 W: D5 {( H( I% m# Z7 j */
  g8 M0 w; A% s3 V9 R2 G( `class Block {$ O9 L8 K! T1 ]  x  c+ |
  /**; C! n3 r$ S; l. A2 ?3 ?
   * 构造函数: C& F$ ]# [8 K% K
   * @param {Number} index $ N; N! P6 B* T0 Q/ m
   * @param {String} previousHash
1 g% O9 ~. r6 ?6 a5 a   * @param {Number} timestamp
' b1 Y4 r* g6 q" f   * @param {*} data
: R3 H% \. Y* Q! d+ I% l% T1 [   * @param {String} hash " b# c9 ]0 ]% _* |1 V' V$ K+ e
   */
& c3 ^# [. F" _+ g, l9 I; Y' |  constructor(index, previousHash, timestamp, data, hash) {
# t& D6 b. c# Q/ @% m! Z& P6 R    this.index = index // 区块的位置0 i. d  q4 X5 j0 b9 ?$ `) b
    this.previousHash = previousHash + '' // 前一个区块的hash
1 H  h& K% {  F7 |    this.timestamp = timestamp // 生成区块时候的时间戳
2 E7 _' B5 f$ s! o! J3 S2 n; Q    this.data = data // 区块本身携带的数据
1 E  t% I8 E) ~7 a$ @    this.hash = hash + '' // 区块根据自身信息和规则生成的hash) _2 G! C4 K) J. S. k" o" S
  }) x0 L7 [9 o! [. M
}; u" D+ k" ?* T4 ]
至于怎么生成hash,这里采用的规则比较简单:3 L5 z" g1 m# {& r7 r$ u5 A
拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash- I: k! P! `  T! B2 W

0 E: c8 l% ?4 E5 M% ^8 y7 D为了方便,会引入一个加密库:
7 \0 r) T+ B; O; cconst CryptoJS = require('crypto-js')- }* j+ v7 y2 F7 m9 z3 z) y
链结构定义( m7 i* L! l% ?2 @
很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:: O* F" w( G5 Q- Z; O$ @* N
按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算
7 [6 t: ^( X2 d9 O/ }! j9 t: C% `) G
1. 起源块
& x8 M2 f8 i) H; h- V& t起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
0 e1 P  w* S6 z+ h0 C/ [class BlockChain {
! R: h& T1 e3 Y8 D  constructor() {3 Q/ g2 T. M- F& F
    this.blocks = [this.getGenesisBlock()]
5 w( {/ k( U7 w0 O  }' D) F$ m5 \8 m" G* b: P. s1 I
  /**$ A, s; l* V- y8 Y  O7 p* P
   * 创建区块链起源块, 此块是硬编码9 \4 v; n5 I; Z* h! H# F
   */
# c. s) ]2 r+ u5 m5 i  getGenesisBlock() {8 A7 h9 o" ~9 A1 |, v  d- L1 |4 t
    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
6 I6 _1 J& }7 B/ P8 v# P1 F  }
8 O' @. ^* k% w6 E}
* z  Y' a) N; t3 m1 a& z2. 计算下一个区块
7 y6 |% h1 P5 W( M& X* sBlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
, R$ p0 p( ?0 [4 ?; R// 方法都是BlockChain对象方法
" A; M* e# ^. v8 s5 P9 o. W  /**
9 [- o; k6 g% Q6 C8 A   * 根据信息计算hash值/ J( f" q% N( i2 d! ~, R! A( ?
   */
9 r  ?; l0 W% [; N  calcuteHash(index, previousHash, timestamp, data) {
: U) u- q. Q4 y2 C# X$ E& [# i% O0 m    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''
; X  l5 H$ ^$ s/ A  }9 R+ u4 E. s2 K( i+ J
  /**
+ Q" @' W9 o  Y" e   * 得到区块链中最后一个块节点& G8 P7 j' v, L( R9 }) z* }
   */) L, b0 [% v0 E, U" ^
  getLatestBlock() {; y% Q5 a" {& x5 J8 c
    return this.blocks[this.blocks.length - 1]
4 }6 x% {% L6 Y* s7 b  }
! s) n0 g2 E+ j* B. R0 S( c1 W- S8 k  /**1 K, \. S7 M4 E2 Q8 U' O
   * 计算当前链表的下一个区块8 c$ b6 r/ N. Z0 i8 G
   * @param {*} blockData ( o/ a9 m/ f2 e
   */" I9 `6 ?, q; H8 {* R5 A4 G. M
  generateNextBlock(blockData) {4 T& K* x0 j' M0 X) t4 z7 Y
    const previousBlock = this.getLatestBlock()
/ L, |. S4 M: {/ ~    const nextIndex = previousBlock.index + 13 ]) I1 b& Q4 s. r( b
    const nextTimeStamp = new Date().getTime()) s  `0 J" u8 _, V
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
. x1 S* k  P) @6 P! C    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)6 T/ z+ t1 w0 b* E- J* }2 E# m/ D( q
  }
0 E; V. e7 h7 r3. 插入区块; H+ y( o- P8 v. H9 j1 t$ S
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。
1 B* ]6 o+ g: y, u  /**
7 G% Z' D0 F! z0 m, C( _8 ?2 N. H   * 向区块链添加新节点
0 v5 y9 W9 \- ?' a$ g6 }  E7 G/ p   * @param {Block} newBlock
0 s$ K+ i. ^6 \+ J8 B+ S# v   */2 x: I# t: e5 o% K. \
  addBlock(newBlock) {
5 p" S% \' B8 c5 r    // 合法区块9 R+ B6 d$ {( R( x; O- Z  C6 V
    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
& X2 \4 ^" k/ T+ ^      this.blocks.push(newBlock)
" V! [5 Q. E" u1 h" m+ v      return true  ' h6 R$ v2 U4 q
    }' D) D) v" @% p2 W5 @: f- j
    return false3 M* \5 }7 Q" [: }, T$ |) R
  }' `$ n6 r6 H' }; \
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:: B1 ?6 C' ~) {% G' X
判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成8 W8 P! F9 V, x6 `( w: C& U

2 f( f& e8 A. R1 j) Q1 Q" \  /**4 b7 y7 z4 f$ K+ ]1 ~
   * 判断新加入的块是否合法5 n  G, T- P& j, C
   * @param {Block} newBlock 6 y- s! z1 t' e  [2 C
   * @param {Block} previousBlock - g& _4 o% \8 G5 ]$ w8 o- v
   */1 H7 W8 R1 R; z- o
  isValidNewBlock(newBlock, previousBlock) {
1 y, l0 {; g; Y7 _% v    if(3 i. s. g* g: a2 e
      !(newBlock instanceof Block) ||
* x; {6 N8 F2 D) U' V4 h      !(previousBlock instanceof Block)+ H) i  Y# t+ W, U8 |
    ) {! j7 L: H2 r  F7 p. d( p
      return false1 b$ v1 l( o) `/ }
    }
: a" u5 o9 P3 A) {' B    // 判断index' l- _/ r" p8 i/ Q: L2 d, `; O* W
    if(newBlock.index !== previousBlock.index + 1) { 9 X- p1 {) d! [$ t- d* t0 H
      return false- p  ?" K. z) E- u
    }; q$ u6 O3 @& [) L
    // 判断hash值
0 g  y5 E; e% K* x    if(newBlock.previousHash !== previousBlock.hash) { : w4 Q- G& i& j/ V& w& J  Q
      return false
, i+ b- m+ T9 Z0 c) |: F( f    }) z0 }9 P" q, u9 A) ^
    // 计算新块的hash值是否符合规则
: W4 ~, Z# Q8 b    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) { . i; E. o8 m. W( }1 B
      return false* Z4 s: H1 K9 s- |  E
    }
# i2 v/ O  e$ G; K5 T) m    return true
, ?  v- {# g. A2 _% U1 D" Y$ L  }9 x8 c5 @1 T$ {  s" W  O
4. 批量插入
7 L2 W- e5 }, R# }1 H; {+ z批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。
4 ?* ^  ]* m+ _在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。7 k& x! R1 u3 {) H% B
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链9 N- s0 d- F# t4 c9 ^3 m
插入新链的方法如下:# B2 ^, k3 v* w2 i& B
  /**8 k+ A1 O# q1 G5 `, r2 y8 g3 z( p
   * 插入新链表* d% b2 d6 i8 E7 k
   * @param {Array} newChain
! W% B! i9 O- ^) u5 g   */
% W4 A& X* B" m# `8 M8 j- ^  addChain(newChain) {: A1 M6 u0 W4 I% j8 k
    if(this.isValidNewChain(newChain)) {
4 Q! @( N6 \+ d      const index = newChain[0].index( f9 ~, |% D* \+ b
      this.blocks.splice(index)# L" H7 D" G5 V! j
      this.blocks = this.blocks.concat(newChain)
9 r9 x7 L! g2 ~0 O# E+ y3 `: x      return true
0 h+ M8 V- J# S) _+ L$ \    }1 A7 t* r7 d1 v- i  c9 s# F
    return false
" x' M8 U6 C, K+ n% @6 s1 L# Y  }( ]) J' u$ q, I
实现上面所述逻辑的方法如下:
  ]1 X0 j* B, D7 A  /**1 e8 S1 o' K5 ^! g# x% V& V5 _7 y
   * 判断新插入的区块链是否合法而且可以覆盖原来的节点7 X6 ~4 F! R9 H; I& F3 l& _( N
   * @param {Array} newChain 2 w: w: u9 F# o2 U9 M8 J: t
   */
  k. C4 T- J8 x5 i! T6 G3 O  isValidNewChain(newChain) {
8 a5 n5 R4 k# K8 I; T    if(Array.isArray(newChain) === false || newChain.length === 0) {
, t, S; ?0 p3 I& n- t7 e3 F      return false
6 D/ [( z# o* F    }6 M% w& m3 M+ P* }* \
    let newChainLength = newChain.length,
7 O: x) A  i+ O; w+ E' I; g  c      firstBlock = newChain[0]
( q' f6 `0 H% s& C6 M# C    // 硬编码的起源块不能改变
1 t; I( D  L6 n    if(firstBlock.index === 0) {
+ w8 M  S6 K$ y7 x4 u6 w      return false
# H% H6 ]' G1 x5 ?) z    }& Y# \9 S/ A5 g7 g3 {
    // 移植新的链的长度 7 a6 K$ P1 ?# d% |
5. 为什么需要批量插入?
0 I) m9 R& [- j我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。# p$ H# A8 M: J) M4 @4 L
A与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。: J& c  ^$ X% b/ [- r! g1 \
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。
% h$ y* a8 Z+ c# H- u效果测试
- s  V8 @6 h# U2 K0 R( G( w, R虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
, j' Y9 d, u! C5 s5 K
, w  z0 V! F/ J9 j; U红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。7 t! H1 X7 P0 w  K- I
全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1