JS 实现区块链分布式网络

news2024/11/23 1:56:37

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 上最后的 block
  const 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. 创建新的 block
  const 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 values
    this.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,
    // 我没加过 A
    nodeNotAlreadyPresent = !bitcoin.networkNodes.includes(newNodeUrl),
    // 我不是 A
    notCurrentNode = 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;
  // 如果 A 不存在于群组中,先将 A 加到群组里
  if (!bitcoin.networkNodes.includes(newNodeUrl))
    bitcoin.networkNodes.push(newNodeUrl);

  const regNodesPromises: RequestPromise<any>[] = [];

  // 通知群组中的其他成员,将 A 加到它们的通讯录中
  bitcoin.networkNodes.forEach((networkNodeUrl) => {
    // register node
    const requestOptions = {
      uri: networkNodeUrl + '/register-node',
      method: 'POST',
      body: { newNodeUrl },
      json: true,
    };

    regNodesPromises.push(rp(requestOptions));

    Promise.all(regNodesPromises)
      .then((data) => {
        // 成功了之后,A 需要将群组中的成员加到自己的通讯录中
        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.coloradmin.cn/o/494921.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字节跳动ByteHouse与亚马逊云科技携手打造新一代云数仓服务

随着全球化的发展&#xff0c;越来越多的中国企业开始涉足海外市场&#xff0c;开展跨境业务。在这个过程中&#xff0c;强大的数据分析能力是出海企业不可或缺的重要一环。通过有效的数据分析&#xff0c;能帮助企业更好地了解全球市场对产品的需求便于调整产品战略&#xff0…

微服务---RabbitMQ进阶(消息可靠性,延迟队列,惰性队列,集群部署)

RabbitMQ进阶(消息可靠性,延迟队列,惰性队列,集群部署) 消息队列在使用过程中&#xff0c;面临着很多实际问题需要思考&#xff1a; 1.消息可靠性 消息从发送&#xff0c;到消费者接收&#xff0c;会经理多个过程&#xff1a; 其中的每一步都可能导致消息丢失&#xff0c;常见…

Python每日一练(20230506) 存在重复元素I、II、III

目录 1. 存在重复元素 Contains Duplicate I 2. 存在重复元素 Contains Duplicate II 3. 存在重复元素 Contains Duplicate III &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 存在重…

项目管理-数据管理能力DCMM模型

DCMM 数据管理能力成熟度评估模型简介 DCMM&#xff08;Data Management Capability Maturity Assessment Model&#xff0c;数据管理能力成熟度评估模型&#xff09;是我国首个数据管理领域国家标准&#xff0c;将组织内部数据能力划分为八个重要组成部分&#xff0c;描述了每…

重新定义座舱智能化的下半场?谁能抓住弯道超车的窗口期

2020年&#xff0c;高通8155上车之前&#xff0c;行业的定义更多是4G联网互联网生态&#xff08;智能手机的复刻&#xff09;&#xff0c;以斑马智行为代表&#xff1b;而随着集成异构计算、高性能AI引擎&#xff08;8TOPS算力&#xff09;的高通8155密集上车&#xff0c;驱动行…

车载多屏互动联动动画版本同屏幕大小情况方案设计--众筹项目

hi&#xff0c;粉丝朋友们&#xff1a; 背景及成果展示 本节带大家来开始学习多屏幕互动的动画版本设计&#xff0c;回忆一下我们已经在之前blog和wms课程中学习了多屏互动的非动画版本如下&#xff1a; 再来看看今天我们想要实现有动画版本的成果&#xff1a; 是不是相比之…

多维时序 | MATLAB实现基于VMD-SSA-LSSVM、SSA-LSSVM、VMD-LSSVM、LSSVM的多变量时间序列预测对比

多维时序 | MATLAB实现基于VMD-SSA-LSSVM、SSA-LSSVM、VMD-LSSVM、LSSVM的多变量时间序列预测对比 目录 多维时序 | MATLAB实现基于VMD-SSA-LSSVM、SSA-LSSVM、VMD-LSSVM、LSSVM的多变量时间序列预测对比预测效果基本介绍程序设计学习总结参考资料 预测效果 基本介绍 多维时序 …

全景环视搭载率突破30%,本土供应商在细分市场突围而出

随着行泊一体、AVP等功能成为智能驾驶赛道新周期的主角&#xff0c;背后支撑落地的全景环视&#xff08;也称为360环视&#xff09;方案也不再是传统功能定义场景&#xff08;为驾驶员提供泊车及盲区辅助&#xff09;下的应用&#xff0c;同时&#xff0c;环视与周视的硬件复用…

【Mybatis-Plus笔记01】整合Springboot实现基础配置和增删改查案例

【Mybatis-Plus笔记01】整合Springboot实现基础配置和增删改查案例 【一】Mybatis-Plus的简单介绍【1】MP的特特性有哪些【2】MP的框架结构 【二】MP的使用案例&#xff08;1&#xff09;准备开发环境&#xff08;2&#xff09;添加pom依赖&#xff08;3&#xff09;编写yml配置…

基于SpringBoot+Vue实现的体检录入系统

【简介】 本体检信息录入系统采用前端&#xff1a;vue&#xff1b;后端&#xff1a;springbootmybatis-plusredismysql技术架构开发&#xff0c;前后端分离&#xff0c;容易上手。除了基本的体检结果查询、录入及导出外&#xff0c;在录入中还能对录入信息进行智能计算。 【功…

LeetCode:20. 有效的括号

20. 有效的括号 1&#xff09;题目2&#xff09;思路3&#xff09;代码4&#xff09;结果 1&#xff09;题目 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1…

Multi-Head self-Attention结构细节

先验知识&#xff1a; Self-Attention结构细节及计算过程https://blog.csdn.net/weixin_54039182/article/details/130515594?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22130515594%22%2C%22source%22%3A%22weixin_54039182…

分布式夺命12连问

分布式理论 1. 说说CAP原则&#xff1f; CAP原则又称CAP定理&#xff0c;指的是在一个分布式系统中&#xff0c;Consistency&#xff08;一致性&#xff09;、 Availability&#xff08;可用性&#xff09;、Partition tolerance&#xff08;分区容错性&#xff09;这3个基本…

Zotero 抓取知网文献失败解决办法梳理(针对问题:更新Translator无效,更新茉莉花插件无效,卸载Zotero无效,使用学校VPN访问知网)

Zotero 抓取知网文献失败解决办法梳理&#xff08;针对问题&#xff1a;更新Translator无效&#xff0c;更新茉莉花插件无效&#xff0c;卸载Zotero无效&#xff0c;使用学校VPN访问知网&#xff09; 问题背景解决方案一解决方案二解决方案三 说在前面&#xff1a;解决方案一和…

idea 远程debug阿里云服务器springboot项目

文章目录 前言意见和建议技术要点小试牛刀通信端口放行Idea增加remote启动项服务端JAR增加参数并启动本地项目启动远程debugpostman测试debug 前言 在实际的生产中不免会出现系统问题&#xff0c;有的在测试环境发现&#xff0c;有的在预发布环境发现&#xff0c;更有甚者在生…

播放卡顿分析

看下这个M3U8请求&#xff0c;时间间隔超过duration的时长。ts的duration是11S M3U8内容更新慢&#xff1f;

JavaScript:二叉树(前序遍历,中序遍历,后序遍历,递归法,统一迭代法)

文章目录 二叉树递归法迭代法 144. 二叉树的前序遍历 - 力扣&#xff08;LeetCode&#xff09;二叉树的递归遍历递归法作图分析代码和思路分析 二叉树的迭代遍历前序遍历迭代分析代码及思路分析 94. 二叉树的中序遍历递归法作图举例递归流程 迭代法代码 145. 二叉树的后序遍历 …

虹科分享|便携式数据包捕获解决方案的发展

有人说&#xff0c;未来就在眼前。如果我们看看过去十年中开发出的物联网技术&#xff0c;我们的确没发反驳他们。21世纪的技术繁荣改变了我们的生活&#xff0c;和彼此之间的交流方式。 比如说我们正在研究的MAREA项目&#xff0c;我们甚至可以说我们正在见证历史。 这是一个…

晚唐诗人杜荀鹤及其十首古诗赏析

一、关于出身的传说 他出身寒微。曾数次赴长安应考&#xff0c;不第还山。相传他是杜牧出妾之子。他诗语言通俗、风格清新&#xff0c;后人称“杜荀鹤体”。他就是晚唐诗人杜荀鹤。 据说&#xff0c;杜牧在会昌末年任池州刺史时&#xff0c;妾程氏有孕&#xff0c;为杜妻所逐&…

翻译|英译汉|汉译英|11:30-11:50+8:40-10:00

英译汉&#xff1a;逐句翻译、注意用词、确保大体通顺。 目录 一、解题技巧 &#xff08;一&#xff09;词语翻译 1. 词的选用 2. 词性转换 &#xff08;1&#xff09;英译汉中的词性转换 &#xff08;2&#xff09;汉译英中的词性转换 3. 增词法 4. 减词法 &#xff…