构建区块链在上一章中,我们构建了区块链数据结构的开端。在本章中,我们将构建一个允许我们与区块链交互的 API。为了构建 API,我们将使用 Express.js 库创建一个服务器,然后我们将构建三个不同的端点,允许我们与区块链交互
让我们开始从头开始构建 API。在本章中,我们将介绍以下主题:
让我们开始构建 API 或服务器,以便与区块链数据结构交互。我们将在一个新文件中构建 API,并将其放入dev
文件夹中。让我们创建一个新文件并将其命名为api.js
;我们将在这里构建整个 API:
现在,我们将使用一个名为Express.js
的库来构建服务器或 API。让我们按照下面提到的步骤来安装它:
Express.js npm
,然后点击第一个链接(https://www.npmjs.com/package/express 。这将带您进入下一页:现在,我们在项目中将 Express 库作为一个依赖项
使用 Express 非常简单:让我们看看如何使用它:
api.js
文件中:var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('Hello World')
})
app.listen(3000)
如您所见,在我们的文件顶部,我们需要express
,我们刚刚下载的库,然后我们创建一个app
。这个app
将帮助我们处理不同的端点或不同的路由。
例如,我们有一个get
端点,它就是/
。有了这个端点,我们将返回Hello World
的响应。整个服务器正在监听端口3000
。
node dev/api.js
get
端点路由来测试这一点,该路由将只是端口3000
上的本地主机。localhost:3000
。在这里,您将看到文本 Hello World:Hello World
更改为Hello Coding JavaScript!
:var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('Hello Coding JavaScript!')
})
app.listen(3000)
node dev/api.js
好了!使用 Express 非常简单直接。我们将使用 Express.js 库构建所有端点。
在本节中,我们将继续构建区块链 API,然后我们将首先在 API 中构建以下三个端点:
/blockchain
,它允许我们获取整个区块链,以便我们可以查看其中的数据。/transaction
,它允许我们创建一个新的事务。/mine
,这将允许我们使用上一章中提出的proofOfWork
方法挖掘一个新块。这将是一个非常强大的端点,构建起来会很有趣。这基本上是我们的 BaseCu 链 API 的基础。在dev/networkNode.js
文件中,我们定义这些端点如下:
const express = require('express');
const app = express();
app.get('/blockchain', function (req, res) {
});
app.post('/transaction', function(req, res) {
});
app.get('/mine', function(req, res) {
});
app,listen(3000);
现在,我们想做的另一件事是对listen
方法进行一些修改:
app,listen(3000, function(){
console.log('listening on port 3000...');
});
我们在这个方法中添加了另一个参数,它是一个函数。在这个函数中,我们只需要打印出Listening on port 3000
字符串。我们这样做的原因是,当我们的端口实际运行时,我们将看到这个文本。让我们到终端再次运行我们的api.js
文件:
如您所见,前面的屏幕截图显示我们正在收听端口3000
。每当我们看到此文本时,我们就知道我们的服务器正在运行。
在本节中,我们将对我们的环境进行改进,使我们的开发过程更加简单。我们要做的第一件事是安装一个名为nodemon
的新软件包。在我们终端的blockchain
目录中,我们将写入npm i nodemon --save
命令:
每当我们对其中一个文件进行更改并保存它时,此 nodemon 库将自动为我们重新启动服务器,这样我们就不必在每次更改时来回地从终端到代码重新启动服务器。
要使用 nodemon,我们将打开我们的package.json
文件。在上面写着"scripts"
的地方,我们将添加一个新脚本:
{
"name": "javaScript-blockchain",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --watch dev -e js dev/api.js"
}
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.3",
"nodemon": "^1.17.3",
"sha256": "^0.2.0"
}
}
我们添加了"start": "nodemon --watch dev -e js dev/api.js"
。这意味着当我们运行start
命令时,我们希望nodemon
监视我们的dev
文件夹,并监视我们所有的 JavaScript 文件。每当这些 JS 文件中的一个被更改并保存时,我们希望 nodemon 为我们重新启动我们的dev/api.js
文件。保存package.json
文件。现在,每当我们在dev
文件夹中进行更改并保存它时,我们的服务器就会自动重启。让我们来测试一下。
我们去候机楼吧。我们的服务器现在应该使用 nodemon:
我们已经使用npm start
命令启动了服务器。您可以看到,这是在端口3000
上侦听。每当我们对其中一个 JS 文件进行更改并保存它时,我们都会看到服务器自动重启:
正如您所看到的,服务器正在再次监听端口3000
,这只是一个工具,我们使用它来简化开发。现在,我们想要使用的另一个工具是 Postman
Postman 工具允许我们调用任何 post 端点,并通过请求将数据发送到这些端点。让我们了解如何安装它:
前往https://www.getpostman.com 下载应用程序。一旦你下载了应用程序,我们就可以进行一个小测试,看看如何使用这个邮递员应用程序到达我们的/transaction
端点。
下载后打开邮递员应用程序。您将看到类似于以下屏幕截图的内容:
http://localhost:3000/transaction
发出发帖请求:/transaction
端点是否工作,让我们在输出中返回一些内容。在我们的/transaction
端点中,我们添加了以下行:app.post('/transaction', function(req, res) {
res.send('It works!!!');
});
It works!!!
返回给我们。单击 Send 按钮,您将获得输出,如以下屏幕截图所示:post
端点,我们希望向其发送数据。例如,当我们点击/transaction
端点时,我们希望创建一个新的事务。因此,我们必须将事务数据,如交易金额、发送方和接收方发送到/transaction
端点。我们可以通过邮递员来完成这项工作,实际上非常简单。我们要做的是在我们的 post 请求中发送一些信息。您可以通过单击“正文”选项卡来执行此操作:amount
作为20
比特币,地址为sender
,地址为recipient
。Remember that everything has to be in JSON format, so we need all of our quotations to be in double quotes or the terms won't work.
req.body
。req.body
只是我们在 JSON 对象中创建的信息:app.post('/transaction', function(req, res) {
console.log(req.body);
res.send(`The amount of the transaction is ${req.body.amount}
bitcoin.`);
});
如您所见,我们也在响应中发送了一些不同的信息。我们在 back ticks 中添加了一个句子,并且我们还使用了一些字符串插值${req.body.amount}
,这将只返回amount
${req.body.amount}
能够工作,我们需要安装另一个库来访问这些信息。让我们回到终点站;我们将退出当前正在侦听端口3000
的进程,并将安装一个名为body-parser
的包:npm start
再次启动服务器。body-parser
时,我们只想在我们导入app
的行之后的文件顶部导入它:const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
为了使用这个body-parser
库,我们添加了下面两行。这两行代码都在说明,如果请求包含 JSON 数据或表单数据,我们只需要解析该数据,以便在任何端点中访问它。因此,对于我们命中的任何端点,我们的数据都将首先通过body-parser
以便我们可以访问数据,然后在接收数据的任何端点中使用。
body-parser
,我们应该能够访问金额。让我们保存api.js
文件并尝试发送请求,如下所示:成功了!我们得到返回的字符串,表示交易金额为 20 比特币。
在我们的终端中,由于我们注销了整个req.body
,我们可以看到关于金额、发送者和接收者的全部信息显示:
伟大的现在,需要注意的一点是,在本章的其余部分中,您应该始终让服务器运行,这意味着您应该始终运行npm start
命令,以便我们可以使用我们的 API,访问不同的端点,并测试它是否工作。
让我们继续构建我们的区块链 API。在本节中,我们将与/blockchain
端点进行交互。这意味着我们必须从blockchain.js
文件导入区块链:
const Blockchain = require('./blockchain');
我们现在已经导入了我们的区块链数据结构或区块链构造函数。接下来,我们想制作一个区块链实例。我们可以这样做:
const bitcoin = new Blockchain();
现在我们有一个区块链构造器函数的实例,我们称它为bitcoin
。你可以随意调用它,但为了简单起见,我将把它称为bitcoin
。
让我们在/blockchain
终点站的基础上再接再厉。该端点要做的就是将我们的整个区块链发送回调用该端点的人。为此,我们将添加一行以发送响应:
app.get('/blockchain', function(req, res) {
res.send(bitcoin);
});
信不信由你,这就是我们要为这个终点所做的一切
现在,我们可以在浏览器中使用该端点来测试该端点是否工作:
localhost:3000/blockchain
:如您所见,我们以更可读的格式返回数据,即 JSON 格式。你可以看到我们有chain
,其中有一个项目——我们的创世纪区块——我们还有pendingTransaction
区块。这很酷,我们可以看出我们的/blockchain
端点正在工作,因为我们得到了整个区块链
在本节中,我们将构建事务端点。让我们按照下面提到的步骤进行操作:
npm start
命令来实现这一点。api.js
文件并构建事务端点。首先,取出我们前面在/transaction
端点中添加的示例代码,在我们的区块链中创建一个新交易。为此,我们将使用我们在第 2 章中构建的blockchain.js
文件中的createNewTransaction
方法构建区块链。createNewTransaction
方法包含三个参数:amount
、sender
和recipient
:Blockchain.prototype.createNewTransaction = function(amount, sender, recipient) {
const newTransaction = {
amount: amount,
sender: sender,
recipient: recipient
};
this.pendingTransactions.push(newTransaction);
return this.getLastBlock()['index'] + 1;
};
/transaction
端点中,我们将添加以下行:app.post('/transaction', function(req, res) {
const blockIndex = bitcoin.createNewTransaction(req.body.amount,
req.body.sender, req.body.recipient)
});
req.body
发送进来的。结果将保存在blockIndex
中,这就是我们将发送回调用此端点的人的内容。我们将以note
的形式发回:app.post('/transaction', function(req, res) {
const blockIndex = bitcoin.createNewTransaction(req.body.amount,
req.body.sender, req.body.recipient)
res.json({ note:`Transaction will be added in block
${blockIndex}.`});
});
如您所见,注释将告诉我们该事务将添加到哪个块。我们使用了字符串插值来传递blockIndex
值。让我们保存此文件并使用 Postman 测试此端点。
现在,让我们转到 Postman 并应用类似于我们前面设置的设置:
我们已经选择了 POST 请求,我们的目标是/transaction
端点。在 Body 选项卡中,我们选中了 raw,文本被选择为 JSON 格式。我们已经在 JSON 对象中传递了amount
、sender
和recipient
的值,这将是我们的req.body
,我们将在这个对象上发送所有的事务数据。在/transaction
端点中提到的req.body
帮助下,我们可以访问金额、发件人地址和收件人。
现在让我们测试这个端点:
如您所见,当我们单击 Postman 上的 Send 按钮时,我们得到了输出,因为事务将添加到块 2 中。我们在这里得到区块 2 的原因是,当我们启动区块链时,已经创建了一个区块,这创建了 genesis 区块。因此,该事务被添加到块 2。
我们可以测试的另一种方法是点击我们的/blockchain
端点,以确保该端点正常工作。当我们点击该端点时,我们应该期望将整个区块链返回给我们。在该区块链中,应该有一个区块——我们的 genesis 区块——并且应该有一个未决交易,即我们刚刚创建的交易。到浏览器localhost:3000/blockchain
让我们看一下:
如你所见,整个对象就是我们的整个区块链——第一部分是我们拥有 genesis 区块的链,第二部分是我们刚刚创建的未决交易。我们的/transaction
端点工作正常。
让我们为区块链 API 构建最终端点:mine 端点,这将挖掘并创建一个新块:
blockchain.js
文件中已经定义的createNewBlock
方法。让我们转到api.js
文件,在/mine
端点内创建一个新块:app.get('/mine', function(req, res) {
const newBlock = bitcoin.createNewBlock();
});
createNewBlock
方法包含三个参数:nonce
、previousBlockHash
和hash
:Blockchain.prototype.createNewBlock = function(nonce, previousBlockHash, hash) {
const newBlock = {
index: this.chain.length + 1,
timestamp: Date.now(),
transactions: this.pendingTransactions,
nonce: nonce,
hash: hash,
previousBlockHash: previousBlockHash
};
this.pendingTransactions = [];
this.chain.push(newBlock);
return newBlock;
};
app.get('/mine', function(req, res) {
const lastBlock = bitcoin.getLastBlock();
const previousBlockHash = lastBlock['hash'];
如您所见,我们已经创建了lastBlock
,这是我们链中的最后一个区块,或者是我们新区块的前一个区块。为了得到最后一个区块的hash
,我们创建了previousBlockHash
。有了这些,我们现在可以得到我们的previousBlockHash
,这是我们接下来的createNewBlock
方法所需要的参数之一。
nonce
。要为我们的块生成一个nonce
,我们需要生成一个proofOfWork
,它是我们在blockchain.js
文件中创建的:Blockchain.prototype.proofOfWork = function(previousBlockHash, currentBlockData) {
let nonce = 0;
let hash = this.hashBlock(previousBlockHash, currentBlockData,
nonce);
while (hash.substring(0, 4) !== '0000') {
nonce++;
hash = this.hashBlock(previousBlockHash, currentBlockData,
nonce);
}
return nonce;
};
/mine
端点中,我们将添加以下行:const nonce = bitcoin.proofOfWork(previousBlockHash, currentBlockData);
proofOfWork
方法,我们将得到一个nonce
返回给我们。让我们将其保存为我们的nonce
变量。我们的proofOfWork
方法包含两个参数:previousBlockHash
(我们已经有了)和currentBlockData
。让我们来定义我们的currentBlockData
:const currentBlockData = {
transactions: bitcoin.pendingTransactions,
index: lastBlock['index'] + 1
};
我们把我们的currentBlockData
作为一个对象,它由数据组成。这个数据将只包括这个块中的transactions
和一个index
,它是我们将要创建的新块的索引;我们的lastBlock
加 1 的索引。currentBlockData
对象就是这个新区块中的transactions
和它的index
,通过这个,我们现在可以计算我们的nonce
,就像我们的previousBlockHash
和currentBlockData
一样。
createNewBlock
方法必须接受的最后一个参数是这个新块的hash
,现在让我们计算一下。为了创建这个新块的散列,我们将使用我们的hashBlock
方法。我们将在/mine
端点中添加以下行:const blockHash = bitcoin.hashBlock(previousBlockHash, currentBlockData, nonce);
如您所知,我们已经在blockchain.js
文件中创建了hashBlock
方法。此方法接受三个参数:previousBlockHash
、currentBlockData
和nonce
。我们已经有了所有这些参数,所以我们调用它并将结果保存在一个名为blockHash
的变量中
createNewBlock
方法所需的所有参数,所以让我们分配这些参数:const newBlock = bitcoin.createNewBlock(nonce, previousBlockHash, blockHash);
这里发生的事情真是太棒了。如你所见,创建这个新区块需要进行许多不同的计算,我们可以通过使用区块链数据结构进行所有这些计算。这是一个非常强大的数据结构,我们的区块链现在可以通过使用proofOfWork
挖掘新的区块,这与许多其他区块链的功能类似。
/mine
端点中添加以下行:res.json({
note: "New block mined successfully",
block: newBlock
});
我们只是寄回一张便条,上面写着新区块开采成功,同时也写着我们刚刚创建的newBlock
。现在,发回这个newBlock
不会以任何方式影响我们的区块链。我们正在发回newBlock
以便创建或开采这个新区块的人知道它是什么样子。
/mine
端点内,我们将创建一个新交易:bitcoin.createNewTransaction(12.5, "00", nodeAddress);
目前,2018 年,在真正的比特币区块链中挖掘新区块将获得 12.5 比特币奖励。为了与真正的比特币保持一致,我们也将获得我们的奖励12.5
比特币。作为发送方地址,我们已经输入了值00
,。这样,每当我们查看网络上的交易时,我们都知道如果交易是从地址00
进行的,这是一种挖掘奖励。
现在我们只需要一个收件人的地址,nodeAddress
。我们需要向挖掘新区块的人发送12.5
比特币——但我们如何才能找到它?好吧,我们将向我们当前所在的节点发送此奖励,这是我们正在处理的整个 API 文件。我们可以将整个 API 视为比特币区块链中的网络节点
在未来的章节中,我们将有多个 API 实例,它们将作为大清洁区块链中的不同网络节点。现在,每当我们访问我们创建的任何端点时,我们总是只与这个网络节点通信。然而,由于我们知道所有区块链技术都是分布式,并且托管在许多不同的网络节点上,因此我们将继续创建更多的网络节点。但目前,我们的整个区块链仅托管在这一个网络节点上。
现在,每当我们到达/mine
端点时,我们都希望奖励这个节点挖掘新块。为了给这个节点它应得的12.5
比特币奖励,我们需要一个地址来发送比特币,现在让我们为这个节点创建一个地址。
要创建此节点的地址,我们将使用终端导入名为uuid
的新库:
输入npm i uuid --save
命令并点击回车后,将添加包。您可以使用npm start
命令再次启动服务器。
现在,让我们在api.js
文件的顶部导入新的uuid
库:
const uuid = require('uuid/v1');
如您所见,我们已经导入了uuid
库的版本 1。这个库为我们创建了一个唯一的随机字符串,我们将使用这个字符串作为这个网络节点的地址。为此,我们将添加以下行:
const nodeAddress = uuid().split('-').join('');
我们想对从这个库中获得的字符串进行修改的一件事是,存在两个破折号——我们不希望地址中有任何破折号。在这里,我们只需在所有破折号上拆分该字符串,然后用传入的空字符串重新连接它。我们将得到的nodeAddress
是一个随机字符串,它保证在很大程度上是唯一的。我们真的希望这个字符串是唯一的,因为我们不希望有两个具有相同地址的节点,否则我们将最终向错误的人发送比特币,这将是不好的。现在我们可以简单地传递这个nodeAddress
变量输入到我们的createNewTransaction
方法中。
在下一节中,我们将测试我们的/mine
端点,以及/transaction
和/blockchain
端点,以确保它们都正常工作和交互。
在本节中,我们将测试我们的/mine
端点,以及/transaction
和/blockchain
端点,以确保一切都能很好地协同工作
在我们测试之前,最好去掉proofOfWork
方法中的console.log
语句。这是因为拥有它只会让你的程序更难工作,因此需要更多的时间来计算。
首先,让我们测试上一节中刚刚构建的/mine
端点。让我们转到浏览器,点击localhost:3000/blockchain
:
现在,我们拥有整个区块链,其中链中有一个区块——我们的 genesis 区块——我们也没有未决交易。
现在,让我们打开另一个选项卡并点击/mine
端点。这将为我们挖掘并创建一个新区块:
我们注意到新块挖掘成功。我们还得到了新块,我们可以看到块上的所有数据。它有一个散列,还有前一个块的散列,即 genesis 块,以及其中的一个事务。你可能会想,我们没有创建一个交易,那么这个交易是从哪里来的?这个交易实际上是我们放入我们的端点的挖掘奖励,它有12.5
比特币挖掘奖励交易。看起来我们的挖掘端点工作得很好。
为了测试并确保我们确实创建了这个新块,我们可以返回到/blockchain
端点并刷新页面:
我们现在有两个区块链:一个是创世纪区块,另一个是我们刚刚创建的区块。第二个区块中也有交易,它有奖励。
让我们再挖一块来测试一下。前往我们的/mine
端点并刷新它:
我们刚刚挖掘了另一个区块,这是我们的第三个区块。我们可以看到,我们得到了timestamp
和另一个交易,这是挖掘奖励,我们还有剩余的数据。现在让我们回到/blockchain
端点并刷新它:
如你所见,我们有三个街区。区块 3 是我们刚刚创建的,其中有我们的采矿奖励交易。另外需要注意的是,我们的previousBlockHash
实际上与区块 2 的hash
一致。这有助于保护我们的区块链,这很好。
现在,让我们使用/transaction
端点创建一些事务。为此,请转到 Postman,确保设置与之前相同,并进行以下更改:
我们已将amount
设置为1000
比特币。我们将保留发件人和收件人地址,但您可以将其更改为任何您想要的地址。一旦我们发布到/transaction
端点,作为回报,我们应该得到文本事务,它将被添加到块 4 中,我们做到了。这个事务被添加到块 4 中,因为我们的链中已经有三个块。
让我们做另一个示例事务。在这里,我们将把amount
改为50
比特币,并对发送方和接收方的地址进行一些更改。因此,当我们发送此请求时,我们应该得到相同的响应:将在块 4 中添加交易。这是因为我们尚未挖掘新块。让我们尝试一下:
成功了。现在,让我们回头再看看我们的整个区块链。这一次,我们应该期望得到相同的区块链和我们刚刚创建的两个未决交易。让我们刷新页面并查看输出:
您会注意到这有三个块和两个挂起的事务。现在,如果我们转到/mine
端点并刷新页面,这两个挂起的事务将添加到块 4:
我们成功地挖掘了一个新区块。它有我们的数据,还有三个事务。前两个事务是我们在 Postman 中创建的,第三个是我们的挖掘奖励事务。现在,如果我们返回/blockchain
端点并刷新它,我们将看到两个挂起的事务消失了,并且它们已添加到块 4:
如您所见,block 4 包含所有三个事务,我们的pendingTransactions
现在为空。结果很好。现在,我鼓励您创建更多的事务并挖掘另一个块,以确保一切正常工作。
通过构建整个 API 和区块链并真正理解代码的工作原理,理解区块链技术的实际工作原理就变得容易多了,而且您还意识到其中很多其实并不那么复杂。
在测试这些端点的任何时候,如果对文件进行更改并保存,则服务器将重新启动。这将产生一个新的区块链实例,这意味着到目前为止您创建的所有内容都将被清除。
在本章中,我们学习了如何在项目中设置 Express.js,以及如何使用它构建 API/服务器。然后我们安装了 Postman,并了解了如何使用它来测试端点。在此之后,我们继续构建服务器的各种端点,并测试这些端点,以验证它们是否正常工作
在下一章中,我们将创建一个节点网络或分布式网络来承载我们的区块链,就像在现实世界中承载的一样。