Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
118 0 0
NodeJS实现简易区块链9 M; X* m4 t2 Z  _/ e, b- @7 \
之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。
5 e" D6 k/ V" _. a( u# i但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
  x! u9 w2 ?- P( {$ x( u  ~想了想,就动手做试试咯。! _' A5 L* K  V
?查看全部教程 / 阅读原文?) E5 i, x4 K- R% d
技术调研
4 y5 \- F( o! I# @! `' Y在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。
8 O: l. t  q9 E% T7 |4 O' N结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。2 H3 N4 Z; D) k' j" i' O
区块定义) U# |' F/ z& r: p% j
为了方便表示区块,将其封装为一个class,它没有任何方法:( N0 t4 T+ m2 l- L- r
/**1 k" z$ e1 C( B) F0 O7 r
* 区块信息的结构化定义
: Y9 n' X, s: y. m: r' j! H */
" G0 e, j) p- P/ f. K; f# }class Block {1 [4 I9 g  X: o- K
  /**
" W7 i7 p- _# d' |% N   * 构造函数5 ^0 U2 Z7 b- y- e. o" t
   * @param {Number} index 5 n" I3 ?: v' A0 L
   * @param {String} previousHash 2 c( O: ~" `! M" t" _4 o7 x
   * @param {Number} timestamp 8 K8 y$ }  s0 W1 T2 e( K  X- Z
   * @param {*} data / W, T" j: c) Y7 U" Y1 D9 Q
   * @param {String} hash
1 j! z9 ?' k3 E9 u* i+ i   */4 b! Y6 y: f: S
  constructor(index, previousHash, timestamp, data, hash) {5 `9 F2 w  I7 p/ }6 D+ b
    this.index = index // 区块的位置
7 k& T8 J/ r+ R3 _    this.previousHash = previousHash + '' // 前一个区块的hash
8 L( u5 D! i8 x; k2 l, ?+ f    this.timestamp = timestamp // 生成区块时候的时间戳7 Z% I% N/ K+ p: n  J% U
    this.data = data // 区块本身携带的数据
$ m. c& v: L8 G; E: T- r    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
, o! N- x  a, T1 z0 m- \2 H. m  }
! T* d1 Z) |3 }2 `( V8 Z}" j  c; q# Y: ^* \4 D$ d
至于怎么生成hash,这里采用的规则比较简单:
" S( K: Z: Q2 b: n3 H/ z拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash
! M, W$ ]2 [* [$ p# Y- s! r) p2 D2 }; j, B8 y9 p
为了方便,会引入一个加密库:
1 ~5 P1 h; S$ Z# l! C& pconst CryptoJS = require('crypto-js')3 M  j5 p7 K+ q  ^! z
链结构定义
; [" Q' V) j; @" o很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:
/ L. A3 B6 I& j. ]% a) r; A按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算$ s# W9 p+ i6 G! q9 c
1 {/ e, r3 s& N6 I6 z, y
1. 起源块
+ r6 x* K) L6 \8 \起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
! A8 ~  D# _  i* G& q, sclass BlockChain {
; d1 }! \( y- L* \  constructor() {% B6 B! `6 l( ?$ d8 v+ O2 h
    this.blocks = [this.getGenesisBlock()]
! M* n4 y8 x& ~+ T' }1 C  }( g" z! P1 Y* O
  /**
; U1 }+ W1 [% ~  M! J8 n   * 创建区块链起源块, 此块是硬编码, T/ [" g2 y. c# p2 D
   */! L: d3 ~+ a' z5 q0 Y' N7 D* a; h
  getGenesisBlock() {
& H9 ^8 p' S7 {6 l( p    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
; T4 [+ ^- w; B6 a# T  }
' S9 v6 o2 z: L2 I) {}) B* n0 t/ H1 K) X- a4 U4 Q
2. 计算下一个区块& n  h6 A/ d* c' [
BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。7 ?  Z& t' l. c, f8 {8 u
// 方法都是BlockChain对象方法5 {. F* G3 r- F% v2 n
  /**
' N: G) d+ ^& ~) Y0 l   * 根据信息计算hash值
# y, U# k. R1 m/ c/ ~0 \   */- Q, a# Z7 f9 u) i# I6 V) O
  calcuteHash(index, previousHash, timestamp, data) {3 u4 E* A* j2 h$ t1 q6 H' m
    return CryptoJS.SHA256(index + previousHash + timestamp + data) + '', Z4 p' Y/ n  L
  }- ~2 M' @4 G0 q: y
  /**
* C6 Q4 Y2 v" R: `" l# }" Q   * 得到区块链中最后一个块节点
0 E. b2 f, |; f/ M1 z2 B$ F6 n   */
# l4 B' ^" K6 m  getLatestBlock() {* c* q9 C! ]3 T
    return this.blocks[this.blocks.length - 1]
3 m- G  \8 m) C- F9 x7 \! P  }8 X' f- ?) F( S6 t5 g* F& z9 F
  /**; R+ N2 R. k4 w" n
   * 计算当前链表的下一个区块4 j9 a6 K3 q( M3 g5 ]
   * @param {*} blockData * k- M4 f: O* d: T) l! n0 j$ q& P
   */
; |1 J. u4 C0 y' R/ j# `4 D8 P7 k  generateNextBlock(blockData) {4 v" w6 T: H) @. T! a+ |
    const previousBlock = this.getLatestBlock()
3 v- b9 b( a3 i' h& C) _    const nextIndex = previousBlock.index + 19 }8 l2 M' ~0 ^6 |# _4 [/ w( ?2 f' w
    const nextTimeStamp = new Date().getTime()
% P7 \  ~' @5 M; b    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData); J+ ]( h* i! R4 E/ f
    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)0 j1 V! z; M9 Z$ i7 }; _- N
  }
0 a' y+ D1 Z* X8 t" k" d3. 插入区块4 A9 r; p. p# `% t- m# x8 o$ l2 ^8 ^
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。, x, m/ n! L+ e) x
  /**4 f3 }; O2 y! b+ z; N2 X" p: Z+ q) G& y
   * 向区块链添加新节点/ I: d6 b- j' H) U8 M2 `
   * @param {Block} newBlock
) M+ h) A+ [% ~+ ?   */1 C* I( i& Z2 e* c8 ~7 p3 n
  addBlock(newBlock) {
9 |8 q& ?3 @  R4 R: i    // 合法区块, l& R. I4 o7 y* p: z# a
    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
5 L# d# m6 f' S      this.blocks.push(newBlock)3 Y4 l/ g/ Y- z6 d
      return true  
" g( G" t, u8 }6 v    }
* D" x. }& S' y    return false6 s$ M! g! a8 M3 P& ]7 ?- g, Y+ S& L: F
  }. J  V! l. o0 p* B2 i- @& T# w$ T0 B" h
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:
; `0 G0 \- H! A, D/ X8 G/ ^" z! F# e" L判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成
0 D+ ?! k2 K9 w+ I
# n( v/ G; m" P1 v  /**
) I% {  ?( [9 w9 Q+ Q   * 判断新加入的块是否合法  j$ ~6 P8 `2 j$ E' x, o
   * @param {Block} newBlock 6 h1 V/ X* W! v$ W- J
   * @param {Block} previousBlock % [9 N! V) ^3 u, [6 Y) n
   */' {/ D$ ^; e- k) @) I, r
  isValidNewBlock(newBlock, previousBlock) {; _* \5 X: p3 J
    if(
2 h1 l7 W6 H) V* J- M; a      !(newBlock instanceof Block) ||2 o) Y6 |3 m  Z7 a  V
      !(previousBlock instanceof Block)
) @- T  M1 H% K& _% y* ?    ) {0 D! U9 M$ ?1 T& @# Z
      return false$ n- m2 P! {& S0 @+ v8 |
    }" h! w% m9 ^4 q2 D
    // 判断index
4 E5 t( N5 Y/ r$ [( g1 T# z    if(newBlock.index !== previousBlock.index + 1) { % P+ {, p1 M* v( _' A7 ^$ _
      return false* c8 Z( O; ^+ G- b0 n
    }9 W2 ~# {0 I( Y
    // 判断hash值$ H! t; L! c8 ^1 o& z+ i) U
    if(newBlock.previousHash !== previousBlock.hash) { 8 _0 T, d) P) i' ^( ?6 c; S7 @
      return false
, z% p- n. A- q) P5 n7 ~, M    }
3 t( b4 w- @) q0 T    // 计算新块的hash值是否符合规则* u- G- R% Q6 J8 r2 E: a* H$ f
    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) { ( d  N/ p. x9 _: u' m7 K
      return false  e1 I/ T( t+ E! d. m, r! |
    }8 |5 c: {, k6 h4 m( q# G
    return true
; i/ y$ F2 c7 x1 o9 o8 O  }9 n% @& [/ }) o6 l8 K2 m% r# _% M
4. 批量插入
* S# {) E4 `8 z, Y5 e: t1 G批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。% v. L" [& n7 r2 \6 h- F
在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。, R2 j% A6 ?1 f. l7 v2 v' ~
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链
& h. }& B! `* E7 r4 R& T& G4 V, i插入新链的方法如下:, N5 d0 G( L  o$ n+ N, l
  /**. Q  v: S- j2 o# T0 u9 E
   * 插入新链表
) S7 D4 T' h# O. b) k% P: x4 k   * @param {Array} newChain
2 c- ?+ `5 p6 P- A8 x   */
" w3 j" h9 V0 V! F$ Z* N  addChain(newChain) {0 c  ~( i  t. G0 ?6 A. u, N
    if(this.isValidNewChain(newChain)) {
2 [9 k" T0 Z* d, `' Z( f      const index = newChain[0].index" _" ]# j4 {: m/ `( m2 J6 c
      this.blocks.splice(index)1 m" [4 c% e/ D* l6 l
      this.blocks = this.blocks.concat(newChain); N: R" p! g. @, \) _4 k$ e
      return true
8 o0 I3 q! s6 q. C1 O% j    }/ p. b& r. K' T( a8 F
    return false
/ T) \; x: I* e7 n; w0 R/ u  A3 N  }% H6 X9 `5 s& Z% D
实现上面所述逻辑的方法如下:
: R! e: o+ R3 [  /**. L1 p5 M# C) D, Q) p3 t
   * 判断新插入的区块链是否合法而且可以覆盖原来的节点( h8 {1 g3 K* I* C  ]1 Y
   * @param {Array} newChain
& F( |( L" w. ?3 Q; B% C   */
+ w, y1 ]! E' B7 [$ g) Z  isValidNewChain(newChain) {" e3 ]& y9 T/ s
    if(Array.isArray(newChain) === false || newChain.length === 0) {
' \6 X, B& n- J$ @" w' x3 O      return false
7 |) t! ^5 q* P9 F6 x4 n" F    }: H. v3 `/ y( _/ y; Q1 I- ~
    let newChainLength = newChain.length,
- j3 O/ x4 ?7 z. r, j9 q0 e      firstBlock = newChain[0]
* {: V+ B% k# H9 U    // 硬编码的起源块不能改变
3 l2 i$ ?* J* \3 Z    if(firstBlock.index === 0) {
- ]0 Z  f; @2 }( |      return false; j7 r9 }- ~- |! I4 K& D2 Y  o2 T
    }4 L' ?  }) W* x/ d- K9 S, c1 ?
    // 移植新的链的长度
5 N; ~3 Y, c* p3 W( k; n: c5. 为什么需要批量插入?+ P, _# G8 z* g& h5 ?6 D
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。
% W3 W" J1 `9 R; o5 t  _) `A与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。* k& H# z* Q: t
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。
+ Q- X) e7 m; ?2 y& L8 u效果测试( ^4 D1 p7 e# A; U1 ~
虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:; e2 C& @2 O( [7 D

8 ?7 T$ h9 A! f3 _9 h* J红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
1 W& w: Y" N! _全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1