用 60 行 Javascript 创建区块链
近年来,加密货币和区块链是两个新兴领域,所以今天,我将分享仅用 60 行代码在 Javascript 中创建区块链的方法。
YouTube 上也有我的完整教程。你可以去看看,了解更多详情。
另外,我的新文章发布了,快来看看吧!这篇文章讲的是为我们的区块链创建交易,这基本上是构建加密货币的第一步。
如果您对其中两篇已经很熟悉,可以考虑阅读第三篇关于如何创建 P2P 网络并发布加密货币的文章。不妨看看!
什么是区块链?
在编写任何代码之前,我们需要了解什么是区块链。从技术角度来看,区块链本质上只是一个包含对象的列表,其中包含一些基本信息,例如时间戳、交易、哈希值等。其数据必须是不可篡改且不可被黑客攻击的。以太坊、Cardano、Polkadot 等现代平台拥有更复杂的内容,但本文我们将简单介绍一下。
设置
我们在这个项目中使用 Node.js,因此如果您还没有安装它,请务必安装它。
在整篇文章中,我将使用面向对象的编程风格,所以我希望你了解有关它的基本知识。
创建一个块
正如我所说,块只是一个包含一些信息的对象,所以我们应该有一个Block
这样的类:
class Block {
constructor(timestamp = "", data = []) {
this.timestamp = timestamp;
// this.data should contain information like transactions.
this.data = data;
}
}
所以我们有 和timestamp
,data
但区块链需要不变性。我们可以通过使用哈希函数来获得这种效果,该函数会将区块中的所有属性进行哈希处理。我建议阅读维基百科上关于哈希函数的内容,它在区块链中起着至关重要的作用。简单来说,它接收一条消息并输出一条固定长度的“哈希”消息,消息的细微变化都会导致输出完全不同。
我正在使用该sha256
算法。为了实现它的哈希函数,我将使用 Nodejs 的内置crypto
包:
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");
上面的代码应该能满足我们的要求,但是如果你想知道它是如何工作的,请查看Node.js 关于哈希类的官方文档。
我们应该有类似这样的内容:
// Get the sha256 hash function.
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");
class Block {
constructor(timestamp = "", data = []) {
this.timestamp = timestamp;
this.data = data;
this.hash = this.getHash();
this.prevHash = ""; // previous block's hash
}
// Our hash function.
getHash() {
return SHA256(this.prevHash + this.timestamp + JSON.stringify(this.data));
}
}
因为每次任何东西发生变化时,SHA256 都会抛出一些完全不同的东西,这样就可以在某种程度上确保不变性。
该prevHash
属性在不变性方面也发挥着重要作用,它确保区块在区块链的整个生命周期内保持不变。它包含前一个区块的哈希值,因此您可以确保前一个区块的不变性,因为任何细微的变化都会导致当前区块的哈希值getHash
发生变化。您可以看到它是空的,但我们将在本文后面对其进行处理。
区块链
让我们转到区块链课程。
正如我所说的,区块链是一个包含区块的列表,因此我们可以有这样的基本形式:
class Blockchain {
constructor() {
// This property will contain all the blocks.
this.chain = [];
}
}
你必须有一个创世块,从技术上讲,它只是第一个块:
class Blockchain {
constructor() {
// Create our genesis block
this.chain = [new Block(Date.now().toString())];
}
}
为了方便起见,我将创建一个函数来获取最新的区块:
getLastBlock() {
return this.chain[this.chain.length - 1];
}
现在,我们应该有一种方法来向区块链添加一个区块。
addBlock(block) {
// Since we are adding a new block, prevHash will be the hash of the old latest block
block.prevHash = this.getLastBlock().hash;
// Since now prevHash has a value, we must reset the block's hash
block.hash = block.getHash();
// Object.freeze ensures immutability in our code
this.chain.push(Object.freeze(block));
}
验证
我们需要知道链是否仍然有效,因此我们需要一个方法来检查其有效性。如果一个区块的哈希值等于其哈希方法返回的值,则该链有效;而一个区块的prevHash
属性应该等于前一个区块的哈希值。
isValid(blockchain = this) {
// Iterate over the chain, we need to set i to 1 because there are nothing before the genesis block, so we start at the second block.
for (let i = 1; i < blockchain.chain.length; i++) {
const currentBlock = blockchain.chain[i];
const prevBlock = blockchain.chain[i-1];
// Check validation
if (currentBlock.hash !== currentBlock.getHash() || prevBlock.hash !== currentBlock.prevHash) {
return false;
}
}
return true;
}
当我们的区块链在 p2p 网络上运行的时候,这种方法将发挥非常重要的作用。
工作量证明
在点对点网络中,没有第三方系统来批准人们的行为,也没有任何共识机制,节点(简单来说就是人)会同意大多数人的意见,但其他人可能会成为攻击者并控制大多数人,所以我们需要一种共识机制。共识机制的存在并非完全是为了阻止攻击,而是为了让人们不成为攻击者。工作量证明就是其中之一。
在我们进一步讨论之前,系统的工作原理是让你增加一个称为 nonce 的值来获取以等于/与难度相关的多个零开头的哈希值。
PoW 有两个好处:它可以防止攻击者,因为单凭一己之力几乎不可能追上其他节点;它还提供挖矿奖励,这样人们就会尽量保持中立,而不是成为攻击者。我们将在下一篇文章中,在交易系统完善后,实现挖矿奖励。
我们可以通过向我们的区块添加mine
方法和属性来实现 PoW 系统:nonce
class Block {
constructor(timestamp = "", data = []) {
this.timestamp = timestamp;
this.data = data;
this.hash = this.getHash();
this.prevHash = ""; // previous block's hash
this.nonce = 0;
}
// Our hash function.
getHash() {
return SHA256(this.prevHash + this.timestamp + JSON.stringify(this.data) + this.nonce);
}
mine(difficulty) {
// Basically, it loops until our hash starts with
// the string 0...000 with length of <difficulty>.
while (!this.hash.startsWith(Array(difficulty + 1).join("0"))) {
// We increases our nonce so that we can get a whole different hash.
this.nonce++;
// Update our new hash with the new nonce value.
this.hash = this.getHash();
}
}
}
因为当我们改变块中的一个小细节时,哈希值将完全不同,所以我们只是一遍又一遍地增加随机数,直到哈希值与我们需要的哈希值匹配。
(请注意,比特币和其他货币通常使用不同的方式来检查难度,但我们保持简单)
转到Blockchain
类,我们应该创建一个难度属性:
this.difficulty = 1;
我将把它设置为 1,难度应该根据挖掘的区块数量进行更新。
addBlock
我们也必须从区块链更新方法:
addBlock(block) {
block.prevHash = this.getLastBlock().hash;
block.hash = block.getHash();
block.mine(this.difficulty);
this.chain.push(Object.freeze(block));
}
现在,所有区块在添加到链之前都需要被挖掘。
快速说明
为了保持简单,我在这个区块链中使用了工作量证明系统。需要注意的是,大多数现代区块链都使用一种更先进的系统,称为权益证明(或其许多升级版本)。
测试链条!
创建一个新文件,该文件将作为入口文件。
让我们使用刚刚创建的区块链吧!我JeChain
现在就调用它。
首先导出所需的类:
module.exports = { Block, Blockchain };
const { Block, Blockchain } = require("./your-blockchain-file.js");
const JeChain = new Blockchain();
// Add a new block
JeChain.addBlock(new Block(Date.now().toString(), { from: "John", to: "Bob", amount: 100 }));
// (This is just a fun example, real cryptocurrencies often have some more steps to implement).
// Prints out the updated chain
console.log(JeChain.chain);
它看起来应该是这样的:
第一个区块是我们的创世区块,第二个区块是添加的区块。
更新奖励:难度和区块时间
区块时间
区块时间是一个常数值,代表着区块被添加到链上的预计时间。比特币等平台的区块时间为 10 分钟,而以太坊的区块时间为 13 秒。
比特币的难度公式
比特币每挖出 2016 个区块,其难度就会更新一次。它使用以下公式计算新的难度:
old difficulty * (2016 blocks * 10 minutes) / mining time for the previous 2016 blocks
现在,让我们开始编码吧!
首先,我们必须先确定区块时间,我将其设置为 30 秒,相当于 30000 毫秒。我使用毫秒,因为它与 配合得更好Date.now()
。
this.blockTime = 30000;
(请注意,我们在课堂上进行编码Blockchain
)。
仅作为一个例子,我将创建自己的系统:如果区块时间小于区块开采的实际时间,则难度将增加 1,否则难度将减少。
addBlock(block) {
block.prevHash = this.getLastBlock().hash;
block.hash = block.getHash();
block.mine(this.difficulty);
this.chain.push(Object.freeze(block));
this.difficulty += Date.now() - parseInt(this.getLastBlock().timestamp) < this.blockTime ? 1 : -1;
}
重要提示!!!
由于我们之前检查难度的方式,这应该没问题。但是,最好使用难度值log16(difficulty)
而不是难度本身来检查难度,这样你就可以使用比特币的难度公式了。
不过,你可以想出自己的方案。你应该考虑在保证良好性能的同时,如何才能最大限度地保障安全。
源代码
您可以在此 repo 中获取完整的源代码:
荣誉奖
我从Simply Explained学到了很多关于区块链的知识。如果没有他们的视频帮助,这篇文章可能永远都不会出现。建议大家在 YouTube 上看看,他们有非常棒的区块链教程系列。
我也从这篇文章中获取了一些信息。快去看看吧!
题外话
我应该继续这个系列吗?如果是的话,我应该写些什么?权益证明?完整的加密货币?还是智能合约?请在评论区留言告诉我。