JS 实现区块链分布式网络

news/2024/12/22 15:25:20/

JS 实现区块链网络

这里主要 JS 实现区块链 实现的部分继续下去,对 Blockchain 这个对象有一些修改,如果使用 TS 的话可能要修改对应的 interface,但是如果是 JS 的话就无所谓了。

需要安装的依赖有:

  • express

  • body-parser

    现在的 express 已经不内置 body-parser,需要作为单独的依赖下载

  • request

    不下载会报错,是使用 request-promise 所需要的依赖

  • request-promise

可选的依赖包包括:

  • concurrently
  • nodemon

这两个主要为了方便热更新,详情参考:TypeScript 服务端热更新

⚠️:request 和 request-promise 已经 deprecated 了,具体 reference 可以参考 Request’s Past, Present and Future,以及 request 的代替品可以在这里查看:Alternative libraries to request

实现网络

单独的一个节点所要提供的功能有:

  • 返回当前的 blockchain
  • 添加新的交易
  • 挖矿

package.json 的配置就不多提了,我是 yarn+concurrently+nodemon 的搭配。

基础设定如下:

  • index.ts

    import express from 'express';
    import bodyParser from 'body-parser';const bitcoin = new Blockchain();const app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));app.listen(3331, () => {console.log(`Listening on port 3331...`);
    });
    

这个时候服务器就起来了,是时候实现对应的 API 功能了。

get blockchain

第一个 API 的实现特别简单,只需要返回当前的 blockchain 即可:

app.get('/blockchain', (req, res) => {res.send(bitcoin);
});

post transaction

这里是创建 transaction 的 API,基础的业务逻辑是从 request body 中获取交易的数额和交易双方的信息,随后创建一个新的 transaction,实现方法如下:

app.post('/transaction', (req, res) => {const { amount, sender, recipient } = req.body;const blockIdx: number = bitcoin.createNewTransaction(amount,sender,recipient);res.json({ message: `transaction will be added in block ${blockIdx}` });
});

测试结果如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

⚠️:重复一下,在当前 block 没有被 mine 之前,所有的 transaction 都会被推到当前 block 中。

get mine

这是整个单节点上业务逻辑最复杂的一部分,同样也需要修改一下 blockchain 的实现(如果用的是 ts)。

要成功的挖出当前的 block,基础的业务逻辑如下:

  1. 获取整个 blockchain 上最后一个 block
  2. mine 这个 block,一直获取到正确的 nonce
  3. 创建一个新的交易去奖励当前的矿工
  4. 创建一个新的 block

当然,这里所需的功能在 blockchain 对象中已经实现了。

接下来就根据上面的步骤实现挖矿的功能:

// 我没有用 uuid,而是直接使用内置的 crypto 去实现生成随机的 uuid 的功能
import crypto from 'crypto';const nodeAddress = crypto.randomUUID().split('-').join('');app.get('/mine', (req, res) => {// 1. 获取当前 blockchain 上最后的 blockconst lastBlock = bitcoin.getLastBlock();// 2. 开始 mine,一直到获取正确的 nonce//    2.1 在 mine 之前也需要获取对应的数据const prevBlockHash = lastBlock.hash;//    这里是更新的地方,之前的 blockData 的数据结构 为 Transaction | Transaction[],这里更新一下const currBlockData = {transactions: bitcoin.pendingTransactions,index: lastBlock.index + 1,};const nonce = bitcoin.proofOfWork(prevBlockHash, currBlockData);const blockHash = bitcoin.hashBlock(prevBlockHash, currBlockData, nonce);// 3. 创建新的 transaction 去奖励当前的矿工//    这里的收件方为当前 network 地址(随机生成)bitcoin.createNewTransaction(12.5, '00', nodeAddress);// 4. 创建新的 blockconst newBlock = bitcoin.createNewBlock(nonce, prevBlockHash, blockHash);res.json({ message: 'New block mined successfully', block: newBlock });
});

blockchain 修改的部分为:

interface BlockData {index: number;transactions: Transaction[];
}// 修改 currBlockData 的数据类型就好,其他地方不用变
class Blockchain {hashBlock = (prevBlockHash: string,currBlockData: BlockData,nonce: number) => {// ...};proofOfWork = (prevBlockHash: string, currBlockData: BlockData) => {// ...};
}

这样就实现完了,沿用上面的结果,测试如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

⚠️:每次 mine 都至少会创建 1 个 transaction,就是奖励当前挖矿人的奖励

实现去中心化区块链网络

区块链的一个特点就在于每一个结点都可以成为一个中心,所以每个结点都能够访问其他的结点:

在这里插入图片描述

捋一下要做到这一点的流程:

  1. 每一个结点都需要群组去保存所有链接的结点

  2. 当一个新的结点 A 加入这个家庭的时候,它要通知其他的结点,将结点 A 加到其他结点已经存在的群组中

  3. 当其他的结点成功注册了结点 A,将 A 加到了自己的通讯录中

  4. 结点 A 也需要将其他的结点加到自己的通讯录中

接下来就根据这个逻辑去实现去中心化网络

修改配置和数据结构

首先修改 blockchain 对象,当前 blockchain 应该获取当前的网络,并能够存储相关联的网络,修改如下:

const currentNodeUrl = process.argv[3];class Blockchain {// ...currentNodeUrl: string;networkNodes: string[];constructor() {this.chain = [];this.pendingTransactions = [];// arbitrary valuesthis.createNewBlock(100, '0', '0');this.currentNodeUrl = currentNodeUrl;this.networkNodes = [];}
}

这里将会使用 process.argv[3] 直接获取当前 node 的 url,因此,下一步就是修改配置文件,将 node url 作为命令行参数:

{"scripts": {"node_1": "concurrently \"tsc -w\" \"nodemon dist/index.js 3331 http://localhost:3331\"","node_2": "concurrently \"tsc -w\" \"nodemon dist/index.js 3332 http://localhost:3332\"","node_3": "concurrently \"tsc -w\" \"nodemon dist/index.js 3333 http://localhost:3333\"","node_4": "concurrently \"tsc -w\" \"nodemon dist/index.js 3334 http://localhost:3334\"","node_5": "concurrently \"tsc -w\" \"nodemon dist/index.js 3335 http://localhost:3335\""}
}

again,这里用了 ts+nodemon+concurrently,所以这么修改的,如果不用一样的配置,可能需要自己想办法修改一下参数的问题。

运行结果如下:

在这里插入图片描述

这样当前网络上就有 5 个不相关联的结点正在运行了。

post register node

这一步其实是实现的步骤 3,也就是其他结点接受一个参数,并且将结点 A 加到自己的通讯录中。对于当前结点来说,它只需要知道 A 的地址,并且判断:

  1. 这个我是不是 A
  2. 我是不是已经加过 A 了

如果二者都不满足,那么当前结点就将 A 加入通讯录中。

app.post('/register-node', (req, res) => {const newNodeUrl = req.body.newNodeUrl,// 我没加过 AnodeNotAlreadyPresent = !bitcoin.networkNodes.includes(newNodeUrl),// 我不是 AnotCurrentNode = bitcoin.currentNodeUrl !== newNodeUrl;if (nodeNotAlreadyPresent && notCurrentNode)bitcoin.networkNodes.push(newNodeUrl);// 这个也可以加到 if 里面,else 里面的信息表示没有已经存在或是自己res.json({ message: 'New node registered successfully.' });
});

在这里插入图片描述

在这里插入图片描述

这里 3331 收到了 3333,所以 3331 会将 3333 加到自己的通讯录中,而 3333 暂时还没有将 3331 加到自己的通讯录中。

post register nodes bulk

这一步是滴 4 步,即其他的结点已经将 A 加到通讯录中了,A 也要将其他的结点加到通讯录中,所以这里接受的参数是一个数组。

实现如下:

app.post('/register-nodes-bulk', (req, res) => {const allNetworkNodes: string[] = req.body.allNetworkNodes;allNetworkNodes.forEach((networkNodeUrl) => {const nodeNotAlreadyPresent =!bitcoin.networkNodes.includes(networkNodeUrl),notCurrentNode = bitcoin.currentNodeUrl !== networkNodeUrl;if (nodeNotAlreadyPresent && notCurrentNode)bitcoin.networkNodes.push(networkNodeUrl);});res.json({ message: 'Bult registration successful.' });
});

测试如下:

在这里插入图片描述

在这里插入图片描述

这两个细节都完成了,现在可以跳回去补全第 2 步了。

post register & broadcast

实现如下:

app.post('/register-and-broadcast-node', (req, res) => {const newNodeUrl = req.body.newNodeUrl;if (!bitcoin.networkNodes.includes(newNodeUrl))bitcoin.networkNodes.push(newNodeUrl);const regNodesPromises: RequestPromise<any>[] = [];bitcoin.networkNodes.forEach((networkNodeUrl) => {// register nodeconst requestOptions = {uri: networkNodeUrl + '/register-node',method: 'POST',body: { newNodeUrl },json: true,};regNodesPromises.push(rp(requestOptions));});Promise.all(regNodesPromises).then((data) => {const bulkRegisterOptions = {uri: newNodeUrl + '/register-nodes-bulk',method: 'POST',body: {allNetworkNodes: [...bitcoin.networkNodes, bitcoin.currentNodeUrl],},json: true,};return rp(bulkRegisterOptions);}).then((data) => {res.json({ message: 'New node registered with network successfully.' });});
});

测试结果如下:

在这里插入图片描述

这里 3331 和 3332 作为两个单独的结点被连接在了一起了,从逻辑上可以理解声 3332 加入到了 3331 的群组中。

在这里插入图片描述

3331 所在群组通过判断,发现 3332 不在自己的群组中,所以它们决定把 3332 加到各自成员中的通讯录中(即所有群组成员都调用一次 /register-node)。

在这里插入图片描述

成功哦那个 3332 将组群内的成员加到自己的通讯录中,最后返回调用成功。

这个时候再尝试让 3333 加入 3332(即 3332 和 3331)所在的群组中:

在这里插入图片描述

同样的步骤:

3333 先尝试加入这个家庭:

在这里插入图片描述

3331 和 3332 接收了新成员:

在这里插入图片描述

成功后 3333 将 3331 和 3332 加入到自己的通讯录中,完成加入。

现在的问题就是,每一个结点有着单独一个 blockchain 的 instance,而在现实生活中,所有的网络节点都在同一个区块链上工作。

下一步就会尝试解决这个问题。


http://www.ppmy.cn/news/69876.html

相关文章

Windows cmd命令行计划任务使用详解

哈喽&#xff0c;大家好&#xff0c;我是[有勇气的牛排]&#xff08;全网同名&#xff09;&#x1f42e;&#x1f42e;&#x1f42e; 有问题的小伙伴欢迎在文末[评论&#xff0c;点赞、收藏]是对我最大的支持&#xff01;&#xff01;&#xff01;。 https://www.couragestea…

postman接口自动化测试

Postman除了前面介绍的一些功能&#xff0c;还有其他一些小功能在日常接口测试或许用得上。今天&#xff0c;我们就来盘点一下&#xff0c;如下所示&#xff1a; 1.数据驱动 想要批量执行接口用例&#xff0c;我们一般会将对应的接口用例放在同一个Collection中&#xff0c;然…

剑指 Offer 58 - II. 左旋转字符串(3种方法)

题目&#xff1a; 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。 请定义一个函数实现字符串左旋转操作的功能。 比如&#xff0c;输入字符串"abcdefg"和数字2&#xff0c;该函数将返回左旋转两位得到的结果"cdefgab"。 示例 1&…

pcap文件格式

在通过使用wireshark工具抓取主机不同网段的数据包时&#xff0c;把抓到的数据包保存起来会发现生成的文件是.pcap文件&#xff0c;此篇博客主要介绍pcap文件的格式&#xff0c;并利用C语言的结构体知识来初窥探数据包数据。 pcap文件格式 pcap文件数据结构如下图所示&#x…

Cordova webapp实战开发:(5)如何写一个Andorid下自动更新的插件?

在 《Cordova webapp实战开发&#xff1a;&#xff08;4&#xff09;Android环境搭建》中我们搭建好了开发环境&#xff0c;也给大家布置了调用插件的预习作业&#xff0c;做得如何了呢&#xff1f;今天我们来学一下如何自己从头建立一个Andorid下的cordova插件。 本次练习你能…

智能的本质人工智能与机器人领域的64个大问题阅读笔记(三)

目录 机器智能提高到人类的水平或者人类智能下降到机器的水平&#xff0c;都可以到达图灵点。 或许图灵测试是一个自我实现的预言&#xff1a;我们&#xff08;声称&#xff09;在打造“聪明”机器的同时&#xff0c;我们也在把人变笨。 不长脑的机器和不思考的人没什么两样&…

Kubernetes架构与组件详解

Kubernetes架构与组件详解 一、Kubernetes 简介1 什么是 Kubernetes2 Kubernetes 的优势3 Kubernetes 的应用场景 二、Kubernetes 架构1 Kubernetes 架构概述2 Kubernetes 架构组件介绍2.1 控制面板组件2.2 API Server2.3 etcd2.4 kubelet2.5 kube-proxy2.6 CNI 插件2.7 容器运…

API常用类

Java API概述 API(Application Programming Interface) 应用程序编程接口 是对Java预先定义的类或接口功能和函数功能的说明文档,目的是提供给开发人员进行使用帮助说明 基本数据类型封装类 基本数据类型包装类byte ByteshortShortcharCharacterint Integerl…