背景介绍
很多朋友吐槽区块链没有应用场景,实际上是因为你并没有真正理解区块链的能力,或者没有找对方向。所有需要存储不可变数据的场景,都可以使用区块链来实现。
其中最普遍的一个场景是区块链溯源系统。在建材、食品和运输等行业都有广泛的应用。
之所以出这期内容,是因为前段时间有个朋友的公司决定开发一套区块链溯源系统,他从网上查阅了大量资料,但不知道如何动手。我也发现了这个痛点,那就是网络上有很多讲解区块链概念的内容,但是真正落实到写代码的环节,就很少有人去讲了。
所以这也算是我发现的一个痛点吧。所以这节课我会带大家手写一个简单的区块链,帮大家真正理解区块链的技术实现。
关于区块链的具体概念我就不讲了,可以去看我之前的视频。
这里我们直接聚焦如何自己写一个区块链。在很多人的印象里,区块链是一个极度复杂的东西,但实际上并没有你想象中那么复杂。我早在 2019 年的时候就带领团队用 Java 开发过一个基于区块链的大型建筑工地建材溯源系统。所以我对区块链中的各种技术细节了如指掌。也明白区块链并非极度复杂。
首先,我们知道区块链是由一系列不可篡改的数据块组成的,这些数据块通过哈希指针彼此相连,最终组成一个链,就是区块链。我们这节课的目标就是先实现一个基本的区块链。
定义区块
组成区块链的单位是区块,所以我们要先来完成区块的代码。区块可以抽象成一个 class
,叫做 Block
。
Block
会包含一些基本的属性,以下是它的属性:
- index:索引。
- timestamp:时间戳。
- data:存储的数据。
- previousHash:前一个区块的 Hash。
- hash:自己的 Hash。
- nonce:用于工作量证明的随机数。
Block
还应该包含几个必备的方法,比如计算哈希值的方法、挖掘新区块的方法等。以下是它的方法:
- calculateHash:计算当前区块的哈希值,返回 Hash 字符串。
- mineBlock:挖掘新区块,参数是挖矿难度,类型是 number。
这是它的 UML 定义:
uml
+-----------------+
| Block |
+-----------------+
| - index: number |
| - timestamp: string |
| - data: any |
| - previousHash: string |
| - hash: string |
| - nonce: number |
+-----------------+
| + constructor(index: number, timestamp: string, data: any, previousHash: string) |
| + calculateHash(): string |
| + mineBlock(difficulty: number): void |
+-----------------+
以下是代码实现:
ts
// 定义区块类
class Block {
index: number;
timestamp: string;
data: any;
previousHash: string;
hash: string;
nonce: number;
constructor(index: number, timestamp: string, data: any, previousHash = "") {
this.index = index; // 区块在链中的位置
this.timestamp = timestamp; // 区块创建的时间戳
this.data = data; // 区块存储的数据,例如交易信息
this.previousHash = previousHash; // 前一个区块的哈希值
this.hash = this.calculateHash(); // 计算当前区块的哈希值
this.nonce = 0; // 随机数,用于工作量证明算法
}
// 计算当前区块的哈希值
calculateHash() {
return SHA256(
this.index +
this.previousHash +
this.timestamp +
JSON.stringify(this.data) +
this.nonce
).toString();
}
// 挖掘新区块的方法
mineBlock(difficulty: number) {
// 循环直到找到一个合适的哈希值,它的前 N 个字符等于 0,N 取决于难度值
while (
this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")
) {
this.nonce++;
this.hash = this.calculateHash();
}
console.log("Block mined: " + this.hash);
}
}
定义区块链
接下来我们要创建一个 Blockchain
的类,它表示区块链本身。
Blockchain
也具有几个基础的属性:
- chain:存储区块的数组。
- diffculty:设定挖掘新区块的难度。
- height:区块链的高度。
我们需要在区块链中定义一些方法来实现一些必备的功能,比如如何添加新的区块、如何验证链上数据是否被篡改等。以下是它的方法:
- createGenesisBlock:生成创世区块。
- getLatestBlock:获取链上最新区块。
- addBlock:添加新区块到链上。
- isChainValid:用来校验整个区块链的完整性。
这是它的 UML 定义:
uml
+---------------------+
| Blockchain |
+---------------------+
| - chain: Block[] |
| - difficulty: number |
| - height: number |
+---------------------+
| + constructor() |
| + createGenesisBlock(): Block |
| + getLatestBlock(): Block |
| + addBlock(newBlock: Block): void |
| + isChainValid(): boolean |
+---------------------+
以下是代码实现:
ts
// 定义区块链类
class Blockchain {
chain: Block[]; // 区块链
difficulty: number; // 挖掘新区块的难度
height: number; // 区块链的高度
constructor() {
this.chain = [this.createGenesisBlock()]; // 初始化区块链,创建创世区块
this.difficulty = 2; // 设置挖掘新区块的难度
this.height = 1; // 设置区块链的高度
}
// 创建创世区块的方法,创世区块是区块链的第一个区块
createGenesisBlock() {
return new Block(0, "01/01/2020", "Genesis Block", "0");
}
// 获取区块链中最后一个区块的方法
getLatestBlock() {
return this.chain[this.chain.length - 1];
}
// 添加新区块到区块链的方法
addBlock(newBlock: Block) {
newBlock.previousHash = this.getLatestBlock().hash; // 设置新区块的前置哈希值
newBlock.mineBlock(this.difficulty); // 挖掘新区块
this.chain.push(newBlock); // 将新区块添加到链上
this.height++; // 更新区块链的高度
}
// 验证区块链是否有效的方法
isChainValid() {
for (let i = 1; i < this.chain.length; i++) {
const currentBlock = this.chain[i]; // 当前区块
const previousBlock = this.chain[i - 1]; // 前一个区块
// 检验区块的哈希值是否正确
if (currentBlock.hash !== currentBlock.calculateHash()) {
return false;
}
// 检验区块的前置哈希值是否等于前一个区块的哈希值
if (currentBlock.previousHash !== previousBlock.hash) {
return false;
}
}
return true;
}
}
挖掘区块和验证区块
在实现完成 Block
和 Blockchain
之后,我们开始实例化区块链,并且尝试挖掘区块,并且验证区块的正确性。
ts
// 在区块链中添加并挖掘两个新的区块,然后检查链是否有效
let myBlockchain = new Blockchain();
console.log("Mining block 1...");
myBlockchain.addBlock(new Block(1, "10/01/2020", { amount: 4 }));
console.log("Mining block 2...");
myBlockchain.addBlock(new Block(2, "12/01/2020", { amount: 8 }));
console.log("Blockchain valid? " + myBlockchain.isChainValid());
然后我们再来体验一下区块链的不可篡改性的特点。我们把链中的某个一区块的数据进行修改,再来验证一下。
ts
// 修改区块链中的数据,然后检查链是否有效
myBlockchain.chain[1].data = { amount: 100 };
myBlockchain.chain[1].hash = myBlockchain.chain[1].calculateHash();
console.log("Blockchain valid? " + myBlockchain.isChainValid());
总结
可以看到我们只用了一百多行就实现了区块链的核心部分。实际上一个成熟的区块链会远比这个要复杂得多。像比特币这种比较简单的区块链,源码大概只有 10 到 30 万行左右的规模,对于大多数研发团队来说,都完全有能力独立研发这种规模的区块链。像以太坊这种非常复杂的区块链,源码规模就比较大了,因为它包含了 EVM 虚拟机和智能合约编译器,大概有几十万到一两百万。不过它们所使用的语言都是表达行较弱的语言,比特币用的是 C++,以太坊主要有两个版本,一个是 Go、一个是 Rust。这些语言都比较容易增加代码行数。如果用 NodeJS 来实现的话,代码量可能会减少 1/3 以上。
这期内容主要就是帮助大家理解区块链的核心原理。所以这个例子具备了麻雀虽小、五脏俱全的特征。虽然现在的区块链已经可以运行起来了,但还不足以投入到生产环境去运行。如果大家对区块链技术感兴趣的话,我后面还会继续出几期内容,来不断完善这个区块链。
如果文章对你有帮助,欢迎添加作者的微信:LZQ20130415
,进群交流学习。