Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

NodeJS实现简易区块链

r8kao8k8
190 0 0
NodeJS实现简易区块链
" T1 o7 d: u6 U: p之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。
) A( f, y$ X3 h+ T7 h但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
! F1 F# J' x3 ?. p4 i- r想了想,就动手做试试咯。: L0 R5 ]( {/ _3 q
?查看全部教程 / 阅读原文?
6 K) I5 _& n9 p6 l1 F8 ~技术调研
% ~) ^% [- d! _# Y) t在google上搜了搜,发现有个项目不错: https://github.com/lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。0 N% I$ ^( ~+ A, Z0 n
结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。
' L' E/ |" Z  z' s5 t+ f% R8 X区块定义
" I. P2 G9 j+ `为了方便表示区块,将其封装为一个class,它没有任何方法:
; O6 v% v# g! @$ _; @! z/**
6 u# G9 X/ }5 c0 X * 区块信息的结构化定义7 E  F9 x' [1 C+ }% M
*/
. w6 d2 o2 e5 \" Tclass Block {
2 |# ?9 H# {) s" w- Z8 f7 K  /**4 T, G" S1 }( C
   * 构造函数& M8 j+ d$ T7 `6 M5 P& ]
   * @param {Number} index 2 B7 C+ T' U4 L3 L: \/ X( `3 ]' H
   * @param {String} previousHash 3 Z/ }5 h3 ~0 I& H. i' x
   * @param {Number} timestamp
6 S1 {& b- N- C6 Z- [; O" P2 l8 y   * @param {*} data
7 s6 g3 `* ~5 D2 J   * @param {String} hash
2 U. R2 E( a& ~& o; ^; u   */6 c* x' h: J4 O' y8 S
  constructor(index, previousHash, timestamp, data, hash) {
; B6 o* j4 S$ d$ T    this.index = index // 区块的位置
5 T7 I& ]: ~6 b% V* V5 Y/ ]( S5 f' i    this.previousHash = previousHash + '' // 前一个区块的hash1 z6 n$ D# t6 `0 V% n- ]% C( `
    this.timestamp = timestamp // 生成区块时候的时间戳
+ \( G/ d" w2 l3 C) d    this.data = data // 区块本身携带的数据) o& t) R+ Y7 B
    this.hash = hash + '' // 区块根据自身信息和规则生成的hash
( A) ?1 ], {, @3 o, R1 A$ b  }
  Z& H$ ^6 d9 i* i7 Y9 u  V6 {}" z6 B) s9 v  f  B( O5 [
至于怎么生成hash,这里采用的规则比较简单:
: B5 n; o1 h7 O5 G/ V; m拼接index、previouHash、timestamp和data,将其字符串化利用sha256算法,计算出的记过就是hash& G# }* J4 E: ]" ]& Y
! b" R; R4 Z! k5 x/ E; ?
为了方便,会引入一个加密库:" j0 w' X# d9 U
const CryptoJS = require('crypto-js')' m) u( ?% b* m0 M
链结构定义
: r) G3 |( w" R/ D1 z$ Z很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:, q$ {! x1 E7 C
按照加密规则生成hash插入新块和检查操作批量插入块和检查操作以及可信度计算
1 ~8 y. ?" Z+ r4 y0 [3 p
+ C3 q- h# H  b3 }3 m1. 起源块: }7 P( O0 n7 V. A
起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。! X2 z. V! `  I0 _
class BlockChain {
  s0 x% }. @4 Y7 F2 f3 {# ]  constructor() {
$ a  Y7 |; H# O1 I+ v  D2 w$ ?    this.blocks = [this.getGenesisBlock()]
0 e- K% S3 n& @9 R, x1 v# Z/ O# ]- t  }
# m% G$ w/ F; j. X8 A5 t  /**
( c8 J( F8 d! y) k   * 创建区块链起源块, 此块是硬编码* N1 q: _0 z# F
   */
  ?& }% U1 p$ s0 J  getGenesisBlock() {9 R+ S4 `$ _2 z" l$ Z/ D* z
    return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4'). O. h) m- z7 h' A
  }
3 g$ D/ p0 }* P/ `, I) R}% K  M& m5 c7 [9 H" r9 @
2. 计算下一个区块! E2 J: K* q/ i8 s( T
BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。" P3 W0 C9 O' x8 G& K! _5 w0 R
// 方法都是BlockChain对象方法& {- e3 q8 M" F
  /**- E# w% ^; o5 ^! S; {9 J
   * 根据信息计算hash值
4 q. U% ?5 A* k2 u" N+ x8 `   *// J+ D, q; g5 D4 d) c3 k
  calcuteHash(index, previousHash, timestamp, data) {; p; N' ]/ w+ S" g$ W4 F% G1 }6 n
    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''- V" f/ l$ C1 }% G
  }
8 E& T" V% M5 A5 d  /**9 m* p$ D4 `0 c; J
   * 得到区块链中最后一个块节点3 s% b, w* E9 a3 D5 I
   */- T# F$ Y# V6 L' u/ ?' ~
  getLatestBlock() {" |, S% R' C5 w7 ~1 s' u
    return this.blocks[this.blocks.length - 1]/ u: i/ n$ I0 u' v. O( R$ B
  }
/ I- G" q& E) @# T" e; X/ \  /**
( L8 R6 L. Q! w4 z8 p7 U   * 计算当前链表的下一个区块
7 r5 U. Y0 }' S# k4 T+ a   * @param {*} blockData " I9 |7 }$ `9 g, i# h2 Q
   */6 m: k2 m# k5 }' l/ l, y1 S
  generateNextBlock(blockData) {
3 y; w- H; R$ L" p! u: g0 n    const previousBlock = this.getLatestBlock()
- v- J& M8 y( t7 @8 L3 y/ P    const nextIndex = previousBlock.index + 1
) ^6 a! e0 o0 w$ \0 y2 E6 C    const nextTimeStamp = new Date().getTime(). R4 A" ~2 Y5 q4 s# h
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
, ?, t( N( v% @4 u+ d, Y' e    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)& l" v. L( d4 M5 \* e
  }
# f( ?0 ^' ^- e( W3. 插入区块4 c  J7 d% U) P4 z9 Z
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。' O8 _7 O8 g0 G4 T' q, O
  /**
8 o" A8 \  N' R) }2 X9 Y; o   * 向区块链添加新节点, S" j# s0 @7 h# N# c( i
   * @param {Block} newBlock
4 V8 e( k$ o% B5 `3 {   */
! n- H& k5 W7 l3 ^# }" p- ]3 m! b  addBlock(newBlock) {
$ Y* L& O9 Z$ w. b4 r! U- f    // 合法区块
( K; i  v( H2 X& Q    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {& {6 T3 e/ b7 g+ D  L
      this.blocks.push(newBlock)# M* g+ {  `8 f, V- `
      return true  
( n8 K' S. T, @& z, D6 J# u    }
$ F1 Z" M6 C5 Y% P0 {  k& \# A    return false
* M7 W7 s* Q' l0 |: F  }3 @- h9 M6 b8 @6 L% M  [5 Z) i% j0 ]
检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:, P- A. ]4 H0 `8 s3 ~
判断新区块的index是否是递增的判断previousHash是否和前一个区块的hash相等判断新区块的hash是否按约束好的规则生成
4 P1 O4 L9 Q- X  j
# I" o$ U7 I9 h  u3 [7 s  /**
, o  I, N% r) D% b; X. B   * 判断新加入的块是否合法
- L5 B' S/ |$ h5 R: c/ `6 Z( Z   * @param {Block} newBlock 4 ?( O4 Q& ]- [; e
   * @param {Block} previousBlock
6 j3 D) J# J. z4 N8 A   */
  r) q3 S7 C' L5 a  isValidNewBlock(newBlock, previousBlock) {1 P6 ^% @6 v* _$ t. J; M
    if(
6 l4 a2 V7 ?) U$ `      !(newBlock instanceof Block) ||6 ], O( C. M8 V, Y+ E
      !(previousBlock instanceof Block)* T: Y8 [8 S) r
    ) {
* n& ^# U( M0 e! j5 {5 N7 y      return false% J9 E" m8 I2 E) b9 f
    }
/ v7 a* R+ J8 |! W8 V    // 判断index' Z' x! A  ~  ~3 ^: R# h
    if(newBlock.index !== previousBlock.index + 1) {
! Z5 T6 ~4 g4 `5 {8 I      return false- R' P) W- k/ v: q/ \0 L9 E) A
    }% P7 J1 Q7 D& n8 Q( b* o5 e
    // 判断hash值+ A6 l( w2 u# `9 J5 n
    if(newBlock.previousHash !== previousBlock.hash) {
$ p" P# I4 ^( y  V- ]      return false- Q+ w$ G" _0 n0 e; D, N
    }4 ^2 d# K- o4 T5 f) C6 J
    // 计算新块的hash值是否符合规则
: ^5 [- U# m" D/ Z# J) p7 d    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) {
/ m; h3 U5 i: k" d      return false
( r3 a* ?, v$ g1 c    }
  a" Y1 L/ V3 R" Z    return true
$ y# \7 D; S/ j( p8 I4 C  }
# W) y8 M2 d1 ^: c4. 批量插入
% W2 P( x. {4 X6 M8 h( t批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。
- r" {( I( |# M5 \" j- f在下标index的处理上,假设还是上面的情况,如果传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。. i% c) b) H4 _% K
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。如果 length© > length(A),那么即可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链
- h) M: P3 o% j( w. D插入新链的方法如下:* i9 q$ t/ ?% G9 x8 _
  /**
+ S6 o: Q) \- g0 n/ g   * 插入新链表
4 k$ M- H* f; e  y# ?, j   * @param {Array} newChain
0 J! b) t4 s5 ]+ j   */0 x: z/ m& |% c" T3 C4 O" i
  addChain(newChain) {
' ^' R% C2 Z- f0 m) k, a    if(this.isValidNewChain(newChain)) {2 u' Z  z4 l4 q  v$ q# v8 V
      const index = newChain[0].index
3 ^! |% U& a. i% |      this.blocks.splice(index)
! ?3 H7 O) F% D7 v7 z4 ?$ _' Z% S      this.blocks = this.blocks.concat(newChain)
' B5 S4 v6 \2 ]. Q8 y/ i6 i% \      return true8 w7 \+ A1 [8 _5 {: y/ V$ C
    }
. ~9 B6 z" T4 I1 N) m; c    return false
  E- Z2 c* y( L- n* y1 D  t  }
+ W: l2 V% r5 |3 |实现上面所述逻辑的方法如下:
- @1 C/ {7 H) E6 D+ H5 m- L" M  /**
7 S1 h# D& J2 u# ~. N% p! f   * 判断新插入的区块链是否合法而且可以覆盖原来的节点, I) m; C  R9 i! |
   * @param {Array} newChain
# s8 T0 R9 W# O/ l4 e' g; a- Y   */7 F) B2 m8 f- x$ G- E1 G
  isValidNewChain(newChain) {
2 S/ B& V- ~# m) ^9 D    if(Array.isArray(newChain) === false || newChain.length === 0) {
. c. _2 G1 P! m& d# a) M      return false
: Y' Z0 a) i! v; U    }4 ?) L% K: e/ V' G% d7 Y! Z
    let newChainLength = newChain.length,
% s# P0 W( P6 |% z% X5 e! J) b      firstBlock = newChain[0]
& n5 p. ], k; q8 ^* t    // 硬编码的起源块不能改变
4 L+ B8 n1 q/ a1 V0 u/ A  q    if(firstBlock.index === 0) {: X8 k5 j$ N% u( K/ i
      return false
/ h% Y% p3 e" F; u+ X    }/ C2 E9 A  t* ?$ _7 ]! `
    // 移植新的链的长度 & h  r! W6 `) X; e3 w! w
5. 为什么需要批量插入?: h9 B+ [* s; ?
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个用户A与B。
/ t* u  r" ]" [* u* f) NA与B同时拉取到已知链的数据,然后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经更新。; M& N" m: F% J8 Z2 j
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,更新后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。$ A2 S$ k5 S9 O$ T5 L' z0 |
效果测试9 N* C/ s" k1 Y; U% W; }: M
虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
( ^/ @# f: O8 G# g+ h
/ T# ]3 z  E% p红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
1 d' x( V: F; {全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
标签: NodeJS
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

r8kao8k8 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1