NodeJS实现简易区块链
之前因为课程要求,基于Nodejs做了一个实现简易区块链。要求非常简单,结构体记录区块结构,顺便能向链中插入新的区块就可。
但是假如要支持多客户使用,就需要考虑“可信度”的问题。那么按照区块链要求,链上的数据不能被篡改,除非算力超过除了攻击者本身之外其他所以机器的算力。
想了想,就动手做试试咯。
??查看一律教程 / 阅读原文??
技术调研
在google上搜了搜,发现有个项目不错: lhartikk/naivechain 。大概只有200行,但是其中几十行都是关于搭建ws和http服务器,美中不足的是没有实现批量插入区块链和计算可信度。
结合这个项目,基本上可以确定每个区块会封装成一个class(结构化表示),区块链也封装成一个class,再对外暴露接口。
区块定义
为了方便表示区块,将其封装为一个class,它没有任何方法:
/** * 区块信息的结构化定义 */class Block { /** * 构造函数 * @param {Number} index * @param {String} previousHash * @param {Number} timestamp * @param {*} data * @param {String} hash */ constructor(index, previousHash, timestamp, data, hash) { this.index = index // 区块的位置 this.previousHash = previousHash + '' // 前一个区块的hash this.timestamp = timestamp // 生成区块时候的时间戳 this.data = data // 区块本身携带的数据 this.hash = hash + '' // 区块根据自身信息和规则生成的hash }}至于怎样生成hash,这里采用的规则比较简单:
- 拼接index、previouHash、timestamp和data,将其字符串化
- 利用sha256算法,计算出的记过就是hash
为了方便,会引入一个加密库:
const CryptoJS = require('crypto-js')链结构定义
很多区块链接在一起,就组成了一条链。这条链,也用class来表示。并且其中实现了很多方法:
- 按照加密规则生成hash
- 插入新块和检查操作
- 批量插入块和检查操作以及可信度计算
1. 起源块
起源块是“硬编码”,由于它前面没数据呀。并且规定它不能被篡改,即不能强制覆盖。我们在构造函数中,直接将生成的起源块放入链中。
class BlockChain { constructor() { this.blocks = [this.getGenesisBlock()] } /** * 创立区块链起源块, 此块是硬编码 */ getGenesisBlock() { return new Block(0, '0', 1552801194452, 'genesis block', '810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4') }}2. 计算下一个区块
BlockChain对象可以根据当前链,自动计算下一个区块。并且与客户传来的区块信息比较,假如一样,说明合法,可以插入;否则,客户的区块就是非法的,不允许插入。
// 方法都是BlockChain对象方法 /** * 根据信息计算hash值 */ calcuteHash(index, previousHash, timestamp, data) { return CryptoJS.SHA256(index + previousHash + timestamp + data) + '' } /** * 得到区块链中最后一个块节点 */ getLatestBlock() { return this.blocks[this.blocks.length - 1] } /** * 计算当前链表的下一个区块 * @param {*} blockData */ generateNextBlock(blockData) { const previousBlock = this.getLatestBlock() const nextIndex = previousBlock.index + 1 const nextTimeStamp = new Date().getTime() const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData) return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash) }3. 插入区块
插入区块的时候,需要检查当前块能否合法,假如合法,那么插入并且返回true;否则返回false。
/** * 向区块链增加新节点 * @param {Block} newBlock */ addBlock(newBlock) { // 合法区块 if(this.isValidNewBlock(newBlock, this.getLatestBlock())) { this.blocks.push(newBlock) return true } return false }检查的逻辑就就放在了 isValidNewBlock 方法中, 它主要完成3件事情:
- 判断新区块的index能否是递增的
- 判断previousHash能否和前一个区块的hash相等
- 判断新区块的hash能否按束缚好的规则生成
/** * 判断新加入的块能否合法 * @param {Block} newBlock * @param {Block} previousBlock */ isValidNewBlock(newBlock, previousBlock) { if( !(newBlock instanceof Block) || !(previousBlock instanceof Block) ) { return false } // 判断index if(newBlock.index !== previousBlock.index + 1) { return false } // 判断hash值 if(newBlock.previousHash !== previousBlock.hash) { return false } // 计算新块的hash值能否符合规则 if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) { return false } return true }4. 批量插入
批量插入的逻辑比较复杂,比方当前链上有4个区块的下标是:0->1->2->3。除了起源块0不能被覆盖,当插入一条新的下标为“1->2->3->4”的链时候,即可以替换原来的区块。最终结果是:0->1->2->3->4。
在下标index的解决上,假设还是上面的情况,假如传入的链的下标是从大于4的整数开始,显然无法拼接原来的区块链的下标,直接扔掉。
但是如何保证可信度呢?就是当新链(B链)替换原来的链(A链)后,生成新的链(C链)。假如 length(C) > length(A),那么就可覆盖要替换的部分。 这就保证了,只有在算力超过所有算力50%的时候,才能篡改这条链 。
插入新链的方法如下:
/** * 插入新链表 * @param {Array} newChain */ addChain(newChain) { if(this.isValidNewChain(newChain)) { const index = newChain[0].index this.blocks.splice(index) this.blocks = this.blocks.concat(newChain) return true } return false }实现上面所述逻辑的方法如下:
/** * 判断新插入的区块链能否合法而且可以覆盖原来的节点 * @param {Array} newChain */ isValidNewChain(newChain) { if(Array.isArray(newChain) === false || newChain.length === 0) { return false } let newChainLength = newChain.length, firstBlock = newChain[0] // 硬编码的起源块不能改变 if(firstBlock.index === 0) { return false } // 移植新的链的长度 <= 现有链的长度 // 新的链不可信 if(newChainLength + firstBlock.index <= this.blocks.length) { return false } // 下面检查新的链是否移植 // 以及新的链的每个节点能否符合规则 if(!this.isValidNewBlock(firstBlock, this.blocks[firstBlock.index - 1])) { return false } for(let i = 1; i < newChainLength; ++i) { if(!this.isValidNewBlock(newChain[i], newChain[i - 1])) { return false } } return true }5. 为什么需要批量插入?
我当时很奇怪,为什么需要“批量插入”这个方法。后来想明白了(希望没想错)。假设服务器S,以及两个客户A与B。
A与B同时拉取到已知链的数据,而后各自生成。A网速较快,但是算力低,就生成了1个区块,放入了S上。注意:此时S上的区块已经升级。
而B比较惨了,它在本地生成了2个区块,但是受限于网速,只能等网速恢复了传入区块。这时候,按照规则,它是可以覆盖的(算力高嘛)。所以这种情况下,服务器S接受到B的2个区块,升级后的链长度是3(算上起源块),并且A的那个区块已经被覆盖了。
效果测试
尽管没有写服务器,但是还是模拟了上面讲述的第5种情况。代码在 test.js 文件中,直接run就可。看下效果截图吧:
image.png
红线上面就是先算出来的,红线下面就是被算力更高的用户端篡改后的区块链。具体模拟过程可以看代码,这里不再冗赘了。
一律代码在都放在: dongyuanxin/node-blockchain
更多系列文章
?在GitHub上收藏/订阅?
《前台知识体系》
- JavaScript基础知识梳理(上)
- JavaScript基础知识梳理(下)
- ES6重难点整理
- 谈谈promise/async/await的执行顺序与V8引擎的BUG
- 前台面试中常考的源码实现
- Flex上手与实战
- ……
《设计模式手册》
- 单例模式
- 策略模式
- 代理商模式
- 迭代器模式
- 订阅-发布模式
- 桥接模式
- 备忘录模式
- 模板模式
- 笼统工厂模式
- ……
《Webpack4渐进式教程》
- webpack4 系列教程(二): 编译 ES6
- webpack4 系列教程(三): 多页面处理方案–提取公共代码
- webpack4 系列教程(四): 单页面处理方案–代码分割和懒加载
- webpack4 系列教程(五): 解决 CSS
- webpack4 系列教程(八): JS Tree Shaking
- webpack4 系列教程(十二):解决第三方 JavaScript 库
- webpack4 系列教程(十五):开发模式与 webpack-dev-server
- ……
?在GitHub上收藏/订阅?
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » NodeJS实现简易区块链