跳转到主要内容
Chinese, Simplified

如何使用 JavaScript 创建一个简单的区块链

[区块链] 是我们在未来十年左右能想到的最大机会集。
~鲍勃·格雷菲尔德


如果您从事科技行业,那么几乎可以肯定您听说过区块链技术。区块链是所有加密货币和大多数去中心化应用程序背后的基础技术。它们也被认为是这一代人最重要的发明之一,并迅速在大众中流行起来。
区块链是跟踪网络中交换的所有数字数据或资产的技术,该记录称为“分类帐”。每个交换的数据都是“交易”,每个经过验证的交易都会作为一个区块添加到分类帐中。将块想象成一个包含一些数据的盒子,这些块中的多个被放在一起形成一个区块链。同理,区块链可以被想象成一个容纳多个盒子的容器。
今天我们将通过在 JavaScript 中构建区块链来了解它是如何工作的。但是在我们开始编程之前,我们为什么要首先学习它呢?


为什么是区块链?


区块链始于 2008 年,作为一种存储和保护数字货币的方式。这是中本聪为比特币提出的提案的一部分。而比特币是区块链网络的第一个应用。它的主要好处之一是,如果没有所有相关方或节点之间的协议,就不能更改记录的信息。
其他好处包括:

  • 去中心化:交易发生在计算机网络上。
  • 不变性:如果一个事务被创建,它就不能被修改
  • 开放:所有交易对所有节点都是可见的。
  • 安全性:由于加密功能,区块链几乎总是安全的

现在,我们了解了它的特性,让我们开始使用 NodeJS 从头开始​​构建我们的区块链。


先决条件


要遵循和理解本教程,您应该熟悉以下内容:

  • JavaScript 中的类和其他 ES6 特性的工作知识。
  • 机器上安装了 NodeJS。


开始使用块


我们之前提到块是一个包含一些有用信息的框。我喜欢将区块链视为 LinkedList(数据结构),将区块链中的每个块视为 LinkedList 中的一个节点。它可以表示为 JavaScript 中的对象,其中包含以下属性:

  • 记录在区块链上的数据,例如交易数据。
  • 块哈希——它是使用加密技术生成的块的 ID。
  • 链中前一个块的哈希。它被记录在每个区块中,以将其链接到链上并提高其安全性。
  • 创建块并将其添加到区块链时的时间戳。

工作量证明 (PoW),这是推导出当前区块哈希的努力量(我们将使用这种共识,因为权益证明超出了本文的范围)。


定义具有上述属性的块类。

 

class Block {
  constructor(data, previousHash) {
    this.data = data;
    this.hash = "";
    this.previousHash = previousHash;
    this.timestamp = new Date();
    this.pow = 0;
  }
}

计算块的哈希


块的哈希是使用加密技术生成的标识符。 我们将通过使用 SHA256 算法对前一个块哈希、当前块数据、时间戳和 PoW 进行哈希处理来获得块哈希。 我们将使用crypto,一个用于散列数据的NodeJS内置库。

const calculateHash = (block) => {
  const data = JSON.stringify(block.data);
  const blockData =
    data +
    block.previousHash +
    block.timestamp.toISOString() +
    block.pow.toString();
  return createHash("sha256").update(blockData).digest("hex");
};

在上面的代码中,我们做了以下事情:

  • 将块的数据转换为 JSON 格式,以便我们可以将其与其他信息作为字符串连接。
  • 连接块的先前哈希、数据、时间戳和工作证明 (PoW)。
  • 使用 SHA256 算法为较早的连接生成哈希。
  • 返回以 16 为底的散列结果,小写字母表示 A-F。


挖掘新区块


挖掘新块涉及使用一定数量的前导零(0)生成块的散列。前导零的数量由当前区块链的难度级别提供。这意味着如果区块链的难度为 3,我们必须生成一个以三个零“000”开头的块,例如“000f34abad……”。
由于我们从块的内容中派生了哈希,我们无法更改内容,但我们当然可以增加工作量证明 (PoW) 值,直到我们满足挖掘条件。
为了实现这一点,我们将为 Block 类创建一个 mine() 方法,该方法将不断增加 PoW 值并计算块哈希,直到我们得到一个有效的哈希。

 

class Block {
  // constructor we used earlier
  mine(difficulty) {
    const regex = new RegExp(`^(0){${difficulty}}.*`);
    while (!this.hash.match(regex)) {
      this.pow++;
      this.hash = calculateHash(this);
    }
  }
}

//alternatively we can do this:

// Block.prototype.mine = (difficulty) => {
//   const regex = new RegExp(`^(0){${difficulty}}.*`);
//   while (!this.hash.match(regex)) {
//     this.pow++;
//     this.hash = calculateHash(this);
//   }
// };

定义区块链类


正如我们之前提到的,区块链是多个区块的集合,区块链类将具有三个属性,即创世区块、包含链中其他区块的数组以及表示难度级别的数字。 创世块是添加到区块链的第一个块。

 

class Blockchain {
  constructor(genesisBlock, chain, difficulty) {
    this.genesisBlock = genesisBlock;
    this.chain = chain;
    this.difficulty = difficulty;
  }
  static create(difficulty) {
    const genesisBlock = new Block(null); //the genesis block has no data i.e. null
    return new Blockchain(genesisBlock, [genesisBlock], difficulty);
  }
}

我们还在区块链内部声明了一个静态方法,这样我们就可以直接使用 const blockchain = Blockchain.create(2) 这样的难度来初始化区块链——将创建一个难度为 2 的区块链实例以及一个创世块。


向区块链添加新区块


我们已经成功地为我们的块实现了计算它们的哈希和自己挖掘的功能。 现在让我们在 Blockchain 类中定义一个方法来将新块添加到链属性。

Blockchain.prototype.addBlock = (from, to, amount) => {
  const blockData = { from, to, amount };
  const lastBlock = this.chain[this.chain.length - 1];
  const newBlock = new Block(blockData, lastBlock.hash);
  newBlock.mine(this.difficulty);
  this.chain.push(newBlock);
}

这里我们将 addBlock 方法添加到 Blockchain 类的原型中。 它类似于直接在类中定义 addBlock 方法。
addBlock 方法说明:

  • 从参数中收集交易的详细信息(发送方、接收方和转账金额)。
  • 使用交易详细信息创建一个新块。
  • 使用 Block 类的 mine 方法挖掘新块。
  • 将新创建的区块推送到区块链的链属性。

验证区块链


现在我们已经实现了区块链的所有功能,我们需要检查区块链的真实性,以便我们可以验证区块链没有被篡改。 我们将 isValid 方法添加到区块链原型中。

 

Blockchain.prototype.isValid = () => {
  if (this.chain.length === 1) return true;
  for (let index = 1; index < this.chain.length; index++) {
  const currentBlock = this.chain[index];
  const previousBlock = this.chain[index - 1];
  if (
    currentBlock.hash !== calculateHash(currentBlock) ||
    previousBlock.hash !== currentBlock.previousHash
  )
    return false;
  }
  return true;
}

在这里,我们重新计算了链上每个块的哈希值,并将它们与其中存储的哈希 id 进行比较,并比较下一个块的 previousHash 属性应该等于当前块的哈希 ID。 由于哈希是使用区块的内容计算出来的,内容稍有变化就会产生完全不同的哈希值。


测试区块链


由于我们有一个功能齐全的区块链,让我们测试一下到目前为止我们已经实现的所有功能。 在文件中添加测试函数并使用 node <filename.js> 从命令行运行它。

 

function () {
  const blockchain = Blockchain.create(2); // difficulty increases exponentially with each increase
  blockchain.addBlock("Alice", "Bob", 5);
  blockchain.addBlock("John", "Doe", 100);
  console.log(blockchain);
  console.log(blockchain.isValid()); // true - since we haven't tampered with it
  blockchain.chain[1].data.amount = 200; // tampering with the blockchain
  console.log(blockchain.isValid()); // false - tampered with the blockchain
})()

结果应该类似于下图,但由于时间戳会不同,因此预计哈希值会有所不同。

奖励:出块时间和难度调整


块时间是挖掘后将新块添加到链中所需的估计时间。它是一个常数值。一些常见平台的出块时间是比特币 10 分钟,以太坊大约 13 秒。


比特币根据所花费的时间为每个 2016 年开采的区块调整其区块时间。以每 10 分钟一个区块的理想速度,2016 个区块将需要两周时间才能找到。如果之前的 2016 块需要两个多星期才能找到,则难度会降低,否则会增加。难度的变化与前一个 2016 年区块所花费的超过或不到两周的时间成正比。是这样的 →

新难度=旧难度*(2016个区块*10分钟)/前2016个区块的挖矿时间

new difficulty = old difficulty * (2016 blocks * 10 minutes) / mining time of the previous 2016 blocks


对于我们的简单区块链,如果新块花费的时间超过块时间,我们将调整难度。如果需要更多时间,我们将其减少 1,否则将其增加 1。


我们将宣布我们的阻塞时间为 10 秒或 10000 毫秒。在 Blockchain 构造函数上添加 blockTime 属性,并给它一个固定值,例如 10000。然后编辑 addBlock 方法以在每次交易后调整难度。

注意:不要忘记将 blockTime 属性添加到区块链构造函数中。 否则,代码会抛出错误。


结论


今天我们学习了区块链是如何在底层工作的,以及如何使用 JavaScript 从头开始创建我们的区块链。 源代码可作为 GitHub Gist 获得,包括难度调整。 GitHub 存储库中提供了更具交互性的代码,它使用 TypeScript 在终端中运行程序来与我们今天创建的区块链进行交互。

原文:https://levelup.gitconnected.com/learn-blockchain-by-building-it-f2f8cc…

本文:https://jiagoushi.pro/node/2038

Tags
 
Article
知识星球
 
微信公众号
 
视频号