一个叫木头,一个叫马尾

使用Node.js构建一个简单的加密货币区块链

文章译自: How To Build A Simple Cryptocurrency Blockchain In Node.js[1],作者 Alfrick Opidi。

摘要:本文演示了如何使用JavaScript中的类(class)和Node.js创建一个简单的加密货币,称为 smashingCoin。你不妨动手试试 —— 它比想象的要简单。


加密货币的空前崛起,以及支撑它的区块链技术,已经风靡全球 —— 从十多年前作为学术概念的卑微起步,到目前在各个行业越来越多的应用。

区块链技术之所以受到广泛关注,是因为它能够增强无信任环境下的安全性,实施去中心化,并使流程变得高效。

传统上,Python一直是区块链开发事实上的编程语言。然而,随着这一神奇技术的扩散,开发选择也越来越多 —— Node.js也没有落下。

本教程将讲述如何使用Node.js构建一个简单的加密货币区块链。它不会太过花哨,但足以帮助你了解区块链的工作原理。

我把这种简单的加密货币称为 砸钱币(smashingCoin)。(原文发布的网站叫 smashingmagazine)

如果你是一名 JavaScript 开发人员,想在加密货币这个新兴领域一跃而起,这篇文章将使你具备必要的入门技能。或者,如果你对加密货币世界中的事情是如何运作的感到好奇,那么本教程可能有助于回答你的一些问题。

先决条件

要成功地按照本教程操作,你需要具备以下条件:

让我们开始吧。。

区块链是什么

区块链是推动比特币和以太坊等数字货币的技术。它是一种创新的分布式公共记账技术,它保持着一个不断增长的记录列表,称为区块,这些记录使用加密技术安全连接。

区块链 这个词之所以得名,是因为它保存交易数据的方式,即以 区块 的方式保存,区块之间相互连接,形成一条。区块链的规模会随着所进行的交易数量的增加而增长。

任何有效的交易数据都会被记录到区块链网络中,而区块链网络受参与者规定的点对点规则的约束。例如,这种数据可以包含区块的 "价值",如数字货币、交易记录(如各方交换商品和服务时),或权利特权(如链所记录的是所有权信息时)。

除了交易数据外,每个区块可能包含自己的加密哈希值(一个独特的标识符或数字足迹)、自己的nonce值(在加密计算中使用一次的任意随机数)、前一个区块的哈希值和最近认证交易的时间戳。

由于每一个新区块都应该指向上一个区块,如果一个区块在没有包含上一个区块的正确哈希值的情况下被纳入链中,可能会导致整个区块链无效。这种不可变属性是区块链安全的关键。

此外,各种类型的共识协议通常被应用于维护区块链的真实性上。共识协议用来确保所有参与者同意网络验证的交易。

例如,常用的共识协议是工作量证明(proof of work),其目的是确定一个数字,在完成一定的计算工作后,找到一个复杂数学问题的解决方案。

工作量证明的主要思想是,区块链网络中的任何参与者都应该发现这个数字很难识别,但很容易验证。因此,它阻止了垃圾信息(spamming)和篡改区块链结构的行为。

对大多数加密货币而言,在区块链上添加一个新的区块需要解决一个复杂的数学方程,随着时间的推移,这个方程的难度会随着区块链的发展而增加。因此,任何通过解决这个问题来证明自己做了工作的人都会得到数字货币的补偿,这个过程被称为 "挖矿"。

怎么创建一个区块

现在,在介绍完区块链技术及其工作原理后,让我们看看如何在创建区块时应用这些概念。如前所述,区块是相互连接形成区块链的。

为了创建smashingCoin货币,我将使用 JavaScript类[3],类的概念在ES6中被引入。

准备好了吗?

开始动手吧。。

下面是 CryptoBlock 类的代码:

const SHA256 = require("crypto-js/sha256");
class CryptoBlock {
  constructor(index, timestamp, data, precedingHash = " ") {
    this.index = index;
    this.timestamp = timestamp;
    this.data = data;
    this.precedingHash = precedingHash;
    this.hash = this.computeHash();
  }
  computeHash() {
    return SHA256(
      this.index +
        this.precedingHash +
        this.timestamp +
        JSON.stringify(this.data)
    ).toString();
  }
}

从上面的代码中可以看到,我创建了 CryptoBlock 类,并为它添加了 constructor() 方法 —— 就像在任何其他JavaScript类中所做的那样。然后,为了初始化它的属性,我为构造方法分配了以下参数。

参数名 解释
index 该数字具有惟一性,可以追踪整个区块链中每个区块的位置。
timestamp 它记录了每笔完成的交易的发生时间。
data 它提供了有关已完成交易的数据,如发件人的详细信息、收件人的详细信息和交易数量。
precedingHash 它指向区块链中前一个区块的哈希值,这对维护区块链的完整性很重要。

此外,我还使用了 computeHash 方法,根据块的属性计算出块的哈希值。

如你所见,我导入了 crypto-js JavaScript库[4],并使用它的 crypto-js/sha256 模块来计算每个块的哈希值。由于该模块返回的是一个数字对象,我使用了 toString()方法将其转换为字符串。

要将crypto-js库添加到你的项目中,请进入终端并运行以下命令,使用npm安装:

npm install --save crypto-js

运行上面的命令后,包含库和其他必要文件的node_modules目录将被添加到你的项目文件夹中。

怎么创建一个区块链

正如前面所解释的,区块链技术是基于所有区块之间相互链结的概念。因此,让我们创建一个 CryptoBlockchain 类,它将负责处理整个链的操作。丑媳妇要见公婆了。

CryptoBlockchain类将提供辅助方法来维护区块链的操作,完成不同的任务,例如创建新的区块并将其添加到链上。

下面是 CryptoBlockchain 类的代码:

class CryptoBlockchain {
  constructor() {
    this.blockchain = [this.startGenesisBlock()];
  }
  startGenesisBlock() {
    return new CryptoBlock(0"01/01/2020""Initial Block in the Chain""0");
  }
  obtainLatestBlock() {
    return this.blockchain[this.blockchain.length - 1];
  }
  addNewBlock(newBlock) {
    newBlock.precedingHash = this.obtainLatestBlock().hash;
    newBlock.hash = newBlock.computeHash();
    this.blockchain.push(newBlock);
  }
}

我们谈谈构成 CryptoBlockchain 类的每个辅助方法的作用。

1. 构造方法

这个方法用来实例化区块链。在构造函数里面,我创建了 blockchain 属性,它指向了一个区块数组。注意,我传递了 startGenesisBlock() 方法,该方法在链中创建了初始区块(创世块)。

2. 构造创世块(Genesis Block)

在区块链中,创世区块指的是网络上创建的第一个区块。每当一个区块与链的其他部分整合时,都应该指向前一个区块。

相反,对于初始区块而言,它没有任何前一个区块可以指向。因此,创世区块通常被硬编码到区块链中。这样一来,后续的区块就可以在其上创建。它的索引通常为0。

我使用 startGenesisBlock() 方法创建了创世块。注意,我使用前面创建的CryptoBlock类创建了它,并传递了 indextimestampdataprecedingHash参数。

3. 获取最近的区块

获取区块链中最新的区块,有助于确保当前区块的哈希值指向上一个区块的哈希值 —— 从而维护链的完整性。

我使用 obtainLatestBlock() 方法来得到它。

4. 添加新的区块

我使用addNewBlock()方法将一个新的区块添加到链中。为了达到这个目的,我将新区块的前一个哈希值(precedingHash)设置为等于链中最后一个区块的哈希值 —— 从而确保链是防篡改的。

由于新区块的属性会随着每一次新的计算而改变,所以再次计算它的加密哈希值很重要。更新其哈希值后,新区块被推送到区块链数组中。

实际上,在区块链上添加一个新的区块并不是那么容易,因为要经过好几道检查。尽管如此,对于这种简单的加密货币来说,它足以证明区块链的实际工作原理。

测试该区块链

现在,让我们测试一下我们的简单区块链,看看它是否有效。

下面是代码:

let smashingCoin = new CryptoBlockchain();
smashingCoin.addNewBlock(
  new CryptoBlock(1"01/06/2020", {
    sender"Iris Ljesnjanin",
    recipient"Cosima Mielke",
    quantity50,
  })
);
smashingCoin.addNewBlock(
  new CryptoBlock(2"01/07/2020", {
    sender"Vitaly Friedman",
    recipient"Ricardo Gimenes",
    quantity100,
  })
);
console.log(JSON.stringify(smashingCoin, null4));

从上面的代码中可以看到,我创建了一个新的CryptoBlockchain类的实例,并将其命名为smashingCoin。然后,我使用一些随意的值向区块链中添加了两个区块。对于data参数,我使用了一个对象,并添加了发送者的详细信息、接收者的详细信息和交易数量。

在终端上运行这些代码,我得到的输出是这样的:

检查我们的区块链能否正常工作
检查我们的区块链能否正常工作

这就是smashingCoin的样子!它是一个包含blockchain属性的对象,该属性又是一个包含链中所有区块的数组。正如你在上图中看到的,每个区块都会引用前一个区块的哈希值。例如,第二个区块引用了第一个区块的哈希值。在测试并看到我们的区块链工作后,让我们再添加一些功能来增强smashingCoin的特性。

怎么验证区块链的完整性

如前所述,区块链的一个关键特征是,一旦一个区块被添加到链上,就不能改变,否则会使链上其他部分的完整性失效。

因此,为了验证区块链的完整性,我将在CryptoBlockchain类中添加一个checkChainValidity()方法。

哈希值对于确保区块链的有效性和安全性至关重要,因为区块内容的任何变化都将导致产生一个全新的哈希值,并使区块链失效。

因此,checkChainValidity()方法将利用if语句来验证每个区块的哈希值是否被篡改。从第一个创建的区块开始,它会循环检查整个区块链并检查其有效性。需要注意的是,由于创世区块是硬编码的,所以它不会被检查。

同时,该方法将验证每两个连续区块的哈希值是否相互指向。如果区块链的完整性没有被破坏,则返回true;否则,如果出现任何异常情况,则返回false。

下面是代码:

checkChainValidity() {
  for (let i = 1; i < this.blockchain.length; i++) {
    const currentBlock = this.blockchain[i];
    const precedingBlock = this.blockchain[i - 1];

    if (currentBlock.hash !== currentBlock.computeHash()) {
      return false;
    }
    if (currentBlock.precedingHash !== precedingBlock.hash) return false;
  }
  return true;
}

怎么添加工作量证明

如前所述,工作量证明是为了增加挖矿或向区块链添加新区块所需难度而应用的一个概念。

对于smashingCoin,我会采用一个简单的算法,阻止人们轻易生成新的区块,或者在区块链上制造大量垃圾。

所以,在CryptoBlock类中,我会添加另一个方法,叫做proofOfWork()。本质上,这个简单的算法识别了一个数字,作为difficulty属性传递,这样每个区块的哈希值都包含与这个difficulty级别对应的前导零(leading zeros)。

确保每个区块的哈希值都以难度等级(difficulty)中设定的零的数目开始,这需要大量的计算能力。难度越高,挖掘新区块所需的时间就越长。

此外,我会在每一个哈希块中加入一个随机的nonce值,这样,当进行rehash时,仍然可以满足难度等级限制。

下面是代码:

proofOfWork(difficulty) {
  while (
    this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")
  ) {
    this.nonce++;
    this.hash = this.computeHash();
  }
}

再来,这里是更新后的 computeHash() 方法,其中包含了 nonce 变量:

computeHash() {
  return SHA256(
    this.index +
      this.precedingHash +
      this.timestamp +
      JSON.stringify(this.data) +
      this.nonce
  ).toString();
}

另外,为了实现新区块生成过程中的工作量证明机制,我将在addNewBlock()方法中加入proofOfWork()方法的调用:

addNewBlock(newBlock) {
  newBlock.precedingHash = this.obtainLatestBlock().hash;
  //newBlock.hash = newBlock.computeHash();
  newBlock.proofOfWork(this.difficulty);
  this.blockchain.push(newBlock);
}

汇总

以下是使用Node.js构建smashingCoin加密货币的全部代码:

const SHA256 = require("crypto-js/sha256");
class CryptoBlock {
  constructor(index, timestamp, data, precedingHash = " ") {
    this.index = index;
    this.timestamp = timestamp;
    this.data = data;
    this.precedingHash = precedingHash;
    this.hash = this.computeHash();
    this.nonce = 0;
  }

  computeHash() {
    return SHA256(
      this.index +
        this.precedingHash +
        this.timestamp +
        JSON.stringify(this.data) +
        this.nonce
    ).toString();
  }

  proofOfWork(difficulty) {
    while (
      this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")
    ) {
      this.nonce++;
      this.hash = this.computeHash();
    }
  }
}

class CryptoBlockchain {
  constructor() {
    this.blockchain = [this.startGenesisBlock()];
    this.difficulty = 4;
  }
  startGenesisBlock() {
    return new CryptoBlock(0"01/01/2020""Initial Block in the Chain""0");
  }

  obtainLatestBlock() {
    return this.blockchain[this.blockchain.length - 1];
  }
  addNewBlock(newBlock) {
    newBlock.precedingHash = this.obtainLatestBlock().hash;
    //newBlock.hash = newBlock.computeHash();
    newBlock.proofOfWork(this.difficulty);
    this.blockchain.push(newBlock);
  }

  checkChainValidity() {
    for (let i = 1; i < this.blockchain.length; i++) {
      const currentBlock = this.blockchain[i];
      const precedingBlock = this.blockchain[i - 1];

      if (currentBlock.hash !== currentBlock.computeHash()) {
        return false;
      }
      if (currentBlock.precedingHash !== precedingBlock.hash) return false;
    }
    return true;
  }
}

let smashingCoin = new CryptoBlockchain();

console.log("smashingCoin mining in progress....");
smashingCoin.addNewBlock(
  new CryptoBlock(1"01/06/2020", {
    sender"Iris Ljesnjanin",
    recipient"Cosima Mielke",
    quantity50,
  })
);

smashingCoin.addNewBlock(
  new CryptoBlock(2"01/07/2020", {
    sender"Vitaly Friedman",
    recipient"Ricardo Gimenes",
    quantity100,
  })
);

console.log(JSON.stringify(smashingCoin, null4));

如果在终端上运行这些代码,得到的输出将类似这样:

总于得到了我们  加密货币!
总于得到了我们 smashingCoin 加密货币!

如上图所示,现在的哈希值从四个零开始,这与工作证明机制中设定的难度等级相对应。

总结

就是这样! 这就是你如何使用Node.js构建一个简单的加密货币区块链方式。

当然,smashingCoin加密货币远未完成。事实上,如果你对它不做更多的改进而直接发布,它是不可能满足当前市场对安全、可靠、直观的数字货币的要求的 —— 你会成为唯一一个使用它的人!

尽管如此,我希望本教程能让你掌握一些基本技能,让你在惊心动魄的加密世界里得以一展身手。

[1]

How To Build A Simple Cryptocurrency Blockchain In Node.js: https://www.smashingmagazine.com/2020/02/cryptocurrency-blockchain-node-js/

[2]

Node.js下载地址: https://nodejs.org/en/

[3]

JavaScript Classes: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

[4]

crypto-js JavaScript library: https://www.npmjs.com/package/crypto-js