Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
122 0 0
NodeJS实现简易区块链
" V9 N& L- {; Y( G1 o: V- D6 s( U之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。: ?$ W7 D: q0 ]2 F4 w- M$ {& U
但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
8 j4 u+ z. x8 {$ k  C. B想了想,就动手做试试咯。% ?3 f- a" L3 w* g5 E/ ^" e  ?# [2 g- w  D
?查看全部教程 / 阅读原文?
1 q. o% H, h# D/ g: S% D4 u7 R' P+ P技术调研
5 U* m! q# S9 p' u5 l+ p  x/ R2 h在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。% ?- L0 E, T7 s2 p+ t
结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。
; _6 Y7 k0 K5 v( x) w% s9 ^! [区块定义
' P) M, i6 R4 Z# Y; P为了方便表示区块,将其封装为一个class,它没有任何方法:& {) U5 c6 s3 d2 T4 [9 i
/**, K$ N  h& r5 r* y) `2 J! N0 S
* 区块信息的结构化定义/ ~* I; M; I) t& I; ]  H' B
*/( `7 l( `& N; ~% P' W. a& \
class Block {  b* l0 ?0 R0 \' @
  /**
  r3 J: V4 d& q   * 构造函数
, G8 @. _& |/ k: E   * @param {Number} index & f2 T) C. H( B; x( v* f% `$ W9 e# x
   * @param {String} previousHash $ l& ~8 z, ~1 j, [8 k
   * @param {Number} timestamp 9 Z! ^9 r; P/ |% E4 V& l
   * @param {*} data
; m) i! [/ G: L& I( I- n% _  w" S; X   * @param {String} hash 8 u& ^4 X3 e( |" p
   */; y; W7 o/ x7 L9 w" B
  constructor(index, previousHash, timestamp, data, hash) {
5 K* ]+ d1 z& ]: ]    this.index = index // 区块的位置' _: N. q3 c  l+ }$ M
    this.previousHash = previousHash + '' // 前一个区块的hash
) d) @+ G; ^- W1 a0 ~    this.timestamp = timestamp // 生成区块时候的时间戳, o( h- k$ y+ r/ B
    this.data = data // 区块本身携带的数据! y' t2 b0 n' t( [
    this.hash = hash + '' // 区块根据自身信息和规则生成的hash+ w3 S) q1 v; s) q
  }  ~6 N3 O' B# ^2 J
}
3 @) b1 F. Y4 d至于怎么生成hash,这里采用的规则比较简单:
$ b# D  Y. J/ v5 j# u% D6 P拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash& d! b: P1 s6 @1 l' p
- V. f* T$ I, ~9 \2 m; t
为了方便,会引入一个加密库:
0 v8 D; G+ P! b- w( R- k% D8 \( j5 |" }const CryptoJS = require('crypto-js')' c+ G9 v* C8 M, J. ^) O
链结构定义
- e; C) I4 u+ y# n1 @9 z很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:
9 h. H) o& o" p, b8 h9 c; Y7 I按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算/ i$ m1 n/ q! |. d

9 U' B5 P' M9 M1. 起源块" O' _0 L; k4 ?5 i" ~
起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
1 {, ~5 m8 w& l5 Pclass BlockChain {
% G- h5 k2 [# v& j) m% E# g  constructor() {
' U' `$ s/ @) {0 {& E  |    this.blocks = [this.getGenesisBlock()]& k, T& M. m2 t$ `
  }" L. _& Y* Z6 W# E" b/ L
  /**
, [6 S6 `) ?. i0 l" W. D/ k   * 创建区块链起源块, 此块是硬编码1 z5 N. ?  c3 f# S! y$ D, [
   */* I' F4 e7 H0 N, }2 V
  getGenesisBlock() {
7 g$ c! X, x9 ~    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')* l* w* O0 l' F# R
  }$ A+ }) ]! e; m
}: Y8 Y% l6 u( e' k0 p
2. 计算下一个区块
$ i+ q' _4 ~$ l7 U6 e: f( \BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
8 J1 _2 A9 |7 H9 M// 方法都是BlockChain对象方法
8 L# a$ z( V" O! n( \2 t  /**
' M$ C. D6 L1 q   * 根据信息计算hash值
3 B/ ^' s! v  r  R4 @/ a# R" G9 u   */% Y& t' Q; d+ K' {% Z" \
  calcuteHash(index, previousHash, timestamp, data) {% z  j; E! }/ m& W' f  V, f/ D
    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''
5 W; j# m; Y7 W3 P9 g" \7 K1 C  }* U$ u  ?8 G& F$ d8 s
  /**& q" C6 _! L; V! k8 Q
   * 得到区块链中最后一个块节点
" C" r( `# O6 G: [! F0 K   */
' {1 I. Q- T" l  getLatestBlock() {! K( n* u6 C6 p% e( l- J
    return this.blocks[this.blocks.length - 1]
% I+ d; [# H( }% M3 W  B1 M  }  E* v  ]/ _; R3 C
  /**
" U' T. v0 S0 U7 W" i   * 计算当前链表的下一个区块% Z- o4 g9 a) Q+ j1 s- H- G
   * @param {*} blockData
; O' m4 y. A3 \6 t   */
8 ~& ^4 a: H7 x8 R  generateNextBlock(blockData) {
2 F9 z) ~& B' c7 V6 d' @- z9 M    const previousBlock = this.getLatestBlock()
9 m2 X* i* R+ F. ^" X; p    const nextIndex = previousBlock.index + 1- D6 N2 w7 I7 W& O
    const nextTimeStamp = new Date().getTime()
/ l0 r/ y) q1 r4 N- J1 i    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)$ A. Q* S, k% Z9 ?$ y( E4 I; X9 q
    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)0 P  L& x! D: K" G; J
  }' Z; z+ G4 L/ A
3. 插入区块+ S; l/ i/ ^9 a" {# N/ F
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。
, {( [( A. d: X. D& l  /**
: w* q: S5 W# }" g9 W* H   * 向区块链添加新节点( O# |: {( l" v. D% k
   * @param {Block} newBlock + v5 M% j) w+ ~) H$ e% t, x) u( j
   */
2 Y; ?0 o7 G# l9 H* F  addBlock(newBlock) {, K7 ]! h4 ?& h* k3 W- ^
    // 合法区块
0 L9 O; Z0 J0 \8 ]    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {  {: D, ?4 U  q
      this.blocks.push(newBlock)
7 _- e* R; |) ~' K5 j4 t      return true  " w) J  s' [/ B* L3 I7 z0 {- Z
    }
/ Z+ M( a+ v/ [    return false
% n7 ~' z- ]& J0 t; ^/ @! Z  }- O% T, W' L/ M1 `# V7 }' b# J
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:
# t" H8 _3 G. O! R5 H判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成
5 Y7 p# k8 k' j. C1 e9 _
6 h  B$ E; n! ]  T7 T' S  /**
7 Z  H" {% ?7 z- e+ U: H  ~   * 判断新加入的块是否合法4 `1 O* o, _( v1 ]. _$ ]$ _/ @
   * @param {Block} newBlock * Z* s  @  r" E. W3 u2 |
   * @param {Block} previousBlock
6 |/ w( p$ i( h   */
+ S9 x8 W4 s/ N  isValidNewBlock(newBlock, previousBlock) {
5 j% C/ O* u8 S+ q1 Q; y* V% H  _    if(0 N# r( m4 F6 L. P3 x
      !(newBlock instanceof Block) ||
5 x5 [# s7 g  s5 {, \      !(previousBlock instanceof Block)
2 t& `8 ?- r+ |" l    ) {2 w0 p3 G/ l: w1 y' e$ y! j; r/ B
      return false( Z( a! d5 E9 u/ _% P
    }8 _+ R. D* j& I, H$ O+ O
    // 判断index
  d7 j6 j) j, K0 d' ]  z    if(newBlock.index !== previousBlock.index + 1) { ! y% }0 u' j4 f1 l% Y
      return false) e  n4 m/ T! U( e7 p
    }7 \. s+ P0 D& ~* O5 u$ i6 [, r! Z
    // 判断hash值) p2 o6 ^  _* e6 ?# i" p; d
    if(newBlock.previousHash !== previousBlock.hash) {
% e! E. j& i" q+ Q7 r      return false
# f# E" j4 @7 M+ y; c, `    }
! k  N- C" g; z, y* u    // 计算新块的hash值是否符合规则
+ u7 ^, w( V# R4 f, t    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) {
" `' E# F& z% Z. h& T      return false
' q) ]% N0 U; e1 L$ y* Z, E7 k    }& }8 |. n# Q% o: t1 o6 p
    return true: a! {! s- }2 F% B
  }- }% T# P: E: Z  r$ |
4. 批量插入
) p$ {6 h2 V: y; B: b3 J批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。$ a1 [- b: e  g, V& z- Q. f+ ]
在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。! x. ~' r- Z5 L% x
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链8 |0 h$ X5 O3 W/ W8 f
插入新链的方法如下:
1 T' O  ~6 h  s& g8 N' t  /**
! w* {- k. A8 L! g  Q" {   * 插入新链表
8 b8 F1 L2 j! {! \7 u) w   * @param {Array} newChain
- u! ?% H" A8 |- W   */+ J: L: O8 h1 w( l
  addChain(newChain) {
4 K. x2 S/ k" G8 b2 W+ k! J    if(this.isValidNewChain(newChain)) {4 V% M' E, f# P* p, D7 b( x
      const index = newChain[0].index
& P5 Z; t9 o  w4 {" ]& E      this.blocks.splice(index)* k' `9 R0 C1 o9 j
      this.blocks = this.blocks.concat(newChain)
) w: Z# ]- l5 _: n      return true
$ k( u  Y+ n; Q+ }) {; N" J    }% ~9 z) g7 h. t- A% R: |
    return false* k+ ^& S! B- g! Q, @
  }) N1 O0 O. U3 v7 g4 [4 g
实现上面所述逻辑的方法如下:: @7 x& p, s# F- w9 C$ [
  /**
# c2 K1 F. `$ `5 a% R   * 判断新插入的区块链是否合法而且可以覆盖原来的节点
. P  ^9 s; B+ O9 E   * @param {Array} newChain
! u) l3 w9 A) g7 O7 f7 d3 I# Z   */
0 ?0 l* h( Q4 i; |  J# k- ?  isValidNewChain(newChain) {+ b/ Z; c5 U) a5 {! t- K: h0 k
    if(Array.isArray(newChain) === false || newChain.length === 0) {( ^8 {7 i* O2 g% G1 T4 B
      return false1 B5 O; [! @" d' }2 K) i
    }
" H9 g" q- o3 \  V! A    let newChainLength = newChain.length,) W2 G3 J% C  Y& o5 x5 a
      firstBlock = newChain[0]
* X7 W" ~, q8 X    // 硬编码的起源块不能改变4 c2 Z+ }+ x' u, Y5 q5 S5 h; y  o
    if(firstBlock.index === 0) {
% G7 q  \' R- Y2 M$ J+ g0 E      return false" W5 F/ j; \, P; |7 W/ ]
    }: b# \* L# i7 ~' L
    // 移植新的链的长度
2 T- _% |7 o/ d  `$ D5. 为什么需要批量插入?
* a; H5 |" f# f) _/ n+ p我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。
( d9 o8 f' x$ NA与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。
  ~2 O( X" A1 R" {而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。6 r" W# _, H5 P2 m% w
效果测试
! i* h: c6 l( y7 o! n5 D2 t8 l虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:; o! S0 F9 y7 I1 r. C$ i7 ^0 v  C  i. v
5 n' _1 ~' w$ V+ _3 E
红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
0 f! f& I6 Z$ {3 @1 W8 v$ W全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1