前言
注:文末有源码地址
"凡是可以用 JavaScript 来写的应用,最终都会用 JavaScript 来写。" ——Atwood定律
【本文重心】: 如何以最简代码打造一款区块链
,基于NodeJS如何开发一款区块链公有链
本文将以解读源码
的形式,让你有如下收获:
- 本文最终完成的效果是怎样的?
- 区块链中的区块是什么意思?
- 区块是如何构成区块链的?
- 最终实现上述功能的代码有多简洁?
- 如何基于NodeJS开发区块链?
最终效果
- 如果你想立即看到效果,请按照文末的实践步骤(仅需三步),你将基于NodeJS开发你的第一款区块链可视化的应用。
即: 每隔2秒,一个新的区块
被挖出,并缀连到前一个区块的尾部,如此形成区块链
,同时打印出来。
![](https://img.haomeiwen.com/i3030499/3554a8a8c8b17aed.gif)
- 如果你想了解如何从零开始,下面将带你手把手,一步一步实现上述效果
正文 - 源码详解
一. 区块链中的区块
什么意思,如何创建一个区块?
class Block {
/**
* 构造函数
* @param {Number} height
* @param {String} previousHash
* @param {Number} timestamp
* @param {*} data
* @param {String} hash
*/
constructor(height, previousHash, timestamp, data, hash) {
this.height = height
this.previousHash = previousHash + ''
this.timestamp = timestamp
this.data = data
this.hash = hash + ''
}
//根据上一个区块生成下一个区块
static generateBlock(blockData, previousBlock) {
const nextHeight = previousBlock.height + 1;
const nextTimeStamp = new Date().getTime();
const nextHash = CryptoJS.SHA256(nextHeight + previousBlock.hash + nextTimeStamp + blockData) + '';
return new Block(nextHeight, previousBlock.hash, nextTimeStamp, blockData, nextHash);
}
}
如上所见,区块的数据结构主要由以下几个字段:
-
height
: 当前这个区块的高度(所谓的高度,就是这个区块被挖出来的顺序,第一个,第二个...) -
previousHash
: 前一个区块的hash
(你可以简单认为hash
就是一个类似身份证号的唯一识别码) -
timestamp
: 即当前时间戳(用来指明当前高度的区块被挖出的时间) -
data
: 区块除了上述区块头
中的字段之外,在区块体
中需要存放这一小段时间内该链上的事件
/交易
,这些事件
/交易
放在data字段中,当该区块被添加到区块链上之后,这些事件、交易就跟着区块
被写入了区块链
,从此具有了可追溯
,不可篡改
等特性 -
hash
:即当前区块
将所有的区块头
和区块体
的数据打包后,计算出的一个唯一识别码。
三. 上述生成的区块如何构成区块链的?
其实根据上述区块间缀连
的关系,已经知道了区块通过hash
前后相连,逐步形成了区块链
。
下面通过源码解读,区块间如何通过hash相连,又有哪些需要注意的地方?
这就是一个区块链最简最朴实无华且枯燥
的样子:
class BlockChain {
constructor() {
this.blocks = [];
}
}
现在我希望将一个有一个的区块逐步缀连到前一个区块上,即用previousHash
字段指明前一区块是谁!那么问题来了,第一个区块的previousHash
是什么?也就是第一个区块该跟在谁后面?此刻什么都没有,难道跟着上帝么?
没错!就是跟着上帝
!即区块链中的上帝,也就是固化在源码中,所谓的创世区块
:
getGenesisBlock() {
return new Block(0, '0', 1595490064640, 'GenesisBlock', '0000000000000000000d87bedef9550a014af9a3af74b791d84d049cc3ca85f4')
}
如上所见,创世区块做了这么几件事:
-
height
: 将高度设定为0 -
previousHash
: 将创世区块的前一hash置为0 -
timestamp
: 取当前时间戳:1595490064640(即2020-08-03 18:00)) -
data
: 区块的数据体中,目前就是朴实无华的一句话,你写什么都OK! -
hash
: 根据上述字段计算出来的一个唯一的识别码
现在有了创世区块
了,那么接下来的第一个区块
怎么生成呢?源码在此:
generateNextBlock(blockData) {
const previousBlock = this.getLatestBlock()
const nextIndex = previousBlock.height + 1
const nextTimeStamp = new Date().getTime()
const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
}
如上可见,后继区块使用到了前一区块的高度height
和哈希值hash
。
当区块链发现有一个新的区块被挖出,且需要添加到该链的尾部时,必须得验证该块是否合乎要求:
isValidNewBlock(newBlock, previousBlock) {
if(
!(newBlock instanceof Block) ||
!(previousBlock instanceof Block)
) {
return false
}
// 判断height
if(newBlock.height !== previousBlock.height + 1) {
return false
}
// 判断hash值
if(newBlock.previousHash !== previousBlock.hash) {
return false
}
// 计算新块的hash值是否符合规则
if(this.calcuteHash(newBlock.height, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) {
return false
}
return true
}
由上可见,判断一个新区块能否被添加到该区块链的依据有如下四点:
- 当前挖出的新区块
newBlock
和取出的上一区块previousBlock
是否是合法的区块? -
newBlock
的高度,是否比previousBlock
的高度刚好大1? -
newblock
的前一哈希:previousHash
,是否刚好是previousBlock
哈希:hash
? -
newBlock
的hash
是否是根据该区块的所有信息按固定规范加密生成的?
只有当上述几点全部满足,该新区块newBlock
才能被加入该区块链,否则该区块将成为垃圾区块,游离于三界之外!
在判定区块合法之后,现在将其加入区块链:
addBlock(newBlock) {
if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
this.blocks.push(newBlock)
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.height === 0) {
return false
}
// 移植新的链的长度 <= 现有链的长度
// 新的链不可信
if(newChainLength + firstBlock.height <= this.blocks.length) {
return false
}
// 下面检查新的链能否移植
// 以及新的链的每个节点是否符合规则
if(!this.isValidNewBlock(firstBlock, this.blocks[firstBlock.height - 1])) {
return false
}
for(let i = 1; i < newChainLength; ++i) {
if(!this.isValidNewBlock(newChain[i], newChain[i - 1])) {
return false
}
}
return true
}
/**
* 插入新链表
* @param {Array} newChain
*/
addChain(newChain) {
if(this.isValidNewChain(newChain)) {
const height = newChain[0].height
this.blocks.splice(height)
this.blocks = this.blocks.concat(newChain)
return true
}
return false
}
获取完整源码: Github
快速上手/如何运行
- 下载源码:
git clone https://github.com/stevekeol/YunDang-Chain
- 切换至对应目录,并安装依赖项:
cd yundang-chain/demo && npm install
- 运行:
npm start
总结
本文仅以近乎于Demo的形式,最简的展示了:
- 何为
代码层面
的区块链
- 如何以
最简代码
打造区块链
作者简介
近期已提上日程的计划是打造一款 基于NodeJS的全节点公有链: YunDang-Chain
。
我们将带你手把手实现
它,参考 项目源码ReadMe,其中有个人微信
和进群方式
。
网友评论