深度理解区块链原理:用 Node.js 手写区块链

背景介绍

很多朋友吐槽区块链没有应用场景,实际上是因为你并没有真正理解区块链的能力,或者没有找对方向。所有需要存储不可变数据的场景,都可以使用区块链来实现。

其中最普遍的一个场景是区块链溯源系统。在建材、食品和运输等行业都有广泛的应用。

之所以出这期内容,是因为前段时间有个朋友的公司决定开发一套区块链溯源系统,他从网上查阅了大量资料,但不知道如何动手。我也发现了这个痛点,那就是网络上有很多讲解区块链概念的内容,但是真正落实到写代码的环节,就很少有人去讲了。

所以这也算是我发现的一个痛点吧。所以这节课我会带大家手写一个简单的区块链,帮大家真正理解区块链的技术实现。

关于区块链的具体概念我就不讲了,可以去看我之前的视频。

这里我们直接聚焦如何自己写一个区块链。在很多人的印象里,区块链是一个极度复杂的东西,但实际上并没有你想象中那么复杂。我早在 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;
  }
}

挖掘区块和验证区块

在实现完成 BlockBlockchain 之后,我们开始实例化区块链,并且尝试挖掘区块,并且验证区块的正确性。

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,进群交流学习。

相关推荐
Tech Synapse41 分钟前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴41 分钟前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since811921 小时前
[ruby on rails] 安装docker
后端·docker·ruby on rails
代码吐槽菌3 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm
豌豆花下猫3 小时前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai
YMWM_3 小时前
第一章 Go语言简介
开发语言·后端·golang
码蜂窝编程官方3 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
hummhumm4 小时前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
J老熊4 小时前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
AuroraI'ncoding4 小时前
时间请求参数、响应
java·后端·spring