NodeJS实现简易区块链
r8kao8k8
发表于 2023-1-2 20:25:20
152
0
0
之前由于课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块即可。
但是如果要支持多用户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其余所以机器的算力。
想了想,就动手做试试咯。
?查看全部教程 / 阅读原文?/ `" @: C! K8 d, e0 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
区块定义
为了方便表示区块,将其封装为一个class,它没有任何方法:; D- j1 Y5 a* L/ S
/**
* 区块信息的结构化定义
*/
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
* @param {Number} timestamp
* @param {*} data
* @param {String} hash " b# c9 ]0 ]% _* |1 V' V$ K+ e
*/
constructor(index, previousHash, timestamp, data, hash) {
this.index = index // 区块的位置0 i. d q4 X5 j0 b9 ?$ `) b
this.previousHash = previousHash + '' // 前一个区块的hash
this.timestamp = timestamp // 生成区块时候的时间戳
this.data = data // 区块本身携带的数据
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
为了方便,会引入一个加密库:
const 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插入新块和检查操作批量插入块和检查操作以及可信度计算
/ }! j9 t: C% `) G
1. 起源块
起源块是“硬编码”,因为它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
class BlockChain {
constructor() {3 Q/ g2 T. M- F& F
this.blocks = [this.getGenesisBlock()]
}' 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
*/
getGenesisBlock() {8 A7 h9 o" ~9 A1 |, v d- L1 |4 t
return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')
}
}
2. 计算下一个区块
BlockChain对象可以根据当前链,自动计算下一个区块。并且与用户传来的区块信息比较,如果一样,说明合法,可以插入;否则,用户的区块就是非法的,不允许插入。
// 方法都是BlockChain对象方法
/**
* 根据信息计算hash值/ J( f" q% N( i2 d! ~, R! A( ?
*/
calcuteHash(index, previousHash, timestamp, data) {
return CryptoJS.SHA256(index + previousHash + timestamp + data) + ''
}9 R+ u4 E. s2 K( i+ J
/**
* 得到区块链中最后一个块节点& 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]
}
/**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()
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)
return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)6 T/ z+ t1 w0 b* E- J* }2 E# m/ D( q
}
3. 插入区块; H+ y( o- P8 v. H9 j1 t$ S
插入区块的时候,需要检查当前块是否合法,如果合法,那么插入并且返回true;否则返回false。
/**
* 向区块链添加新节点
* @param {Block} newBlock
*/2 x: I# t: e5 o% K. \
addBlock(newBlock) {
// 合法区块9 R+ B6 d$ {( R( x; O- Z C6 V
if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
this.blocks.push(newBlock)
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
/**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) {
if(3 i. s. g* g: a2 e
!(newBlock instanceof Block) ||
!(previousBlock instanceof Block)+ H) i Y# t+ W, U8 |
) {! j7 L: H2 r F7 p. d( p
return false1 b$ v1 l( o) `/ }
}
// 判断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值
if(newBlock.previousHash !== previousBlock.hash) { : w4 Q- G& i& j/ V& w& J Q
return false
}) z0 }9 P" q, u9 A) ^
// 计算新块的hash值是否符合规则
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
}
return true
}9 x8 c5 @1 T$ { s" W O
4. 批量插入
批量插入的逻辑比较复杂,比如当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,就可以替换原来的区块。最终结果是:0->1->2->3->4。
在下标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
*/
addChain(newChain) {: A1 M6 u0 W4 I% j8 k
if(this.isValidNewChain(newChain)) {
const index = newChain[0].index( f9 ~, |% D* \+ b
this.blocks.splice(index)# L" H7 D" G5 V! j
this.blocks = this.blocks.concat(newChain)
return true
}1 A7 t* r7 d1 v- i c9 s# F
return false
}( ]) J' u$ q, I
实现上面所述逻辑的方法如下:
/**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
*/
isValidNewChain(newChain) {
if(Array.isArray(newChain) === false || newChain.length === 0) {
return false
}6 M% w& m3 M+ P* }* \
let newChainLength = newChain.length,
firstBlock = newChain[0]
// 硬编码的起源块不能改变
if(firstBlock.index === 0) {
return false
}& Y# \9 S/ A5 g7 g3 {
// 移植新的链的长度 7 a6 K$ P1 ?# d% |
5. 为什么需要批量插入?
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器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的那个区块已经被覆盖了。
效果测试
虽然没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run即可。看下效果截图吧:
红线上面就是先算出来的,红线下面就是被算力更高的客户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。7 t! H1 X7 P0 w K- I
全部代码在都放在: https://github.com/dongyuanxin/node-blockchain
成为第一个吐槽的人