您现在的位置是:首页 >学无止境 >JS 实现区块链同步和共识网站首页学无止境

JS 实现区块链同步和共识

GoldenaArcher 2024-06-14 17:17:21
简介JS 实现区块链同步和共识

JS 实现区块链同步和共识

之前实现了区块链和去中心化网络,这里实现区块链的同步和共识,不过本质上来说使用的的方法和 register & broadcast 的方法是一样的。

这个也是目前学习中倒数第二篇笔记了,最后两个部分学完,blockchain 就暂时放一放继续回到前端的学习去了。

同步网络

这里一步会做到的就是当下一个 transaction/block 被添加到网络中某一个 blockchain 上时,其他的 node 也会收到消息,并且同步更新自己 node 上的 blockchain。

同步 transaction

重构创建新的 transaction

要做到这一步首先需要 refactor 一下当前的 blockchain 中,transaction 的实现。这一步主要是将原来的函数拆分成两个,修改前的代码为:

createNewTransaction = (amount: number, sender: string, recipient: string) => {
  const newTransaction = {
    amount,
    sender,
    recipient,
  };

  this.pendingTransactions.push(newTransaction);

  return this.getLastBlock()['index'] + 1;
};

修改后的部分为:

createNewTransaction = (
  amount: number,
  sender: string,
  recipient: string
): Transaction => {
  // interface 也需要同样更新
  const newTransaction: Transaction = {
    amount,
    sender,
    recipient,
    transactionId: crypto.randomUUID().split('-').join(''),
  };

  return newTransaction;
};

addToPendingTransactions = (transaction: Transaction): number => {
  this.pendingTransactions.push(transaction);
  return this.getLastBlock().index + 1;
};

这样当某一个 node 创建了新的 transaction 之后,当前结点就可以将其 broadcast 到整个 network 上实现同步。

实现 broadcast transaction 的功能

就跟将 node 加到 network 中一样的实现步骤,首先从 request 中获取对应的信息,随后调用 createNewTransaction 去获取创建成功的 transaction。随后循环遍历 network 中的所有 nodes,每个 node 都会将最新的 transaction 推到自己 node 中,blockchain 中的 pending transactions 中。

app.post('/transaction/broadcast', (req, res) => {
  const { amount, sender, recipient } = req.body;

  const newTransaction: Transaction = bitcoin.createNewTransaction(
    amount,
    sender,
    recipient
  );

  bitcoin.addToPendingTransactions(newTransaction);

  const reqPromises: RequestPromise<any>[] = bitcoin.networkNodes.map(
    (networkNodeUrl) => {
      const reqOptions = {
        uri: networkNodeUrl + '/transaction',
        method: 'POST',
        body: { newTransaction },
        json: true,
      };

      return rp(reqOptions);
    }
  );

  Promise.all(reqPromises).then(() => {
    res.json({ message: 'Transaction creatd and broadcasted successfully.' });
  });
});

更新 post transaction

这个变动比较小,只需要获取新的 transaction,调用之前写的 append 功能即可。

app.post('/transaction', (req, res) => {
  const { newTransaction } = req.body;

  const blockIdx = bitcoin.addToPendingTransactions(newTransaction);

  res.json({ message: `Transaction will be added in block ${blockIdx}.` });
});

测试新的 transaction

这里依旧用 postman 进行测试,依旧是在 1 个 node 上添加新的 transaction,随后在其他的结点上查看。

首先确定当前 network 上已经有其他的 nodes 进行关联:

在这里插入图片描述

随后 3331 新添一个 transaction:

在这里插入图片描述

3331、3332 和 3333 上都显示出了新的 transaction:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

随后再通过 3332 添加一个 transaction:

在这里插入图片描述

可以发现 3331、3332、3333 上也都出现了最新的 transaction:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

同步 mine

和同步 transaction 的操作差不多,这里也会创建一个新的 block,然后遍历所有的 nodes,将其推到 block 中。

接受新的 block

这里的实现就是从 request 中获取 block 信息,并且做一个简单的验证,如果验证通过得啊,就将当前 block 推到 blockchain 中:

app.post('/recieve-new-block', (req, res) => {
  const newBlock: Block = req.body.newBlock;
  const lastBlock = bitcoin.getLastBlock();
  const correctHash = lastBlock.hash === newBlock.previousBlockHash,
    correctIndex = lastBlock.index === newBlock.index - 1;

  if (correctHash && correctIndex) {
    bitcoin.chain.push(newBlock);
    bitcoin.pendingTransactions = [];
    res.json({ message: 'New block recieved and accepted.', newBlock });
  } else {
    res.json({ message: 'New block rejected.', newBlock });
  }
});

修改 mine

mine 这部分主要时添加了一个遍历调用上面创建的 endpoint 的部分,以及现在奖励矿工的部分放到了 pending transaction 中,而不是直接添加到当前 block 里:

app.get('/mine', (req, res) => {
  const lastBlock = bitcoin.getLastBlock();
  const prevBlockHash = lastBlock.hash;
  const currBlockData = {
    transactions: bitcoin.pendingTransactions,
    index: lastBlock.index + 1,
  };
  const nonce = bitcoin.proofOfWork(prevBlockHash, currBlockData);
  const blockHash = bitcoin.hashBlock(prevBlockHash, currBlockData, nonce);

  bitcoin.createNewTransaction(12.5, '00', nodeAddress);

  const newBlock = bitcoin.createNewBlock(nonce, prevBlockHash, blockHash);

  const reqPromises = bitcoin.networkNodes.map((networkNodeUrl) => {
    const reqOptions = {
      uri: networkNodeUrl + '/recieve-new-block',
      method: 'POST',
      body: { newBlock },
      json: true,
    };

    return rp(reqOptions);
  });

  Promise.all(reqPromises)
    .then(() => {
      const reqOptions = {
        uri: bitcoin.currentNodeUrl + '/transaction/broadcast',
        method: 'POST',
        body: { amount: 12.5, sender: '00', recipient: nodeAddress },
        json: true,
      };

      return rp(reqOptions);
    })
    .then(() => {
      res.json({ message: 'New block mined successfully', block: newBlock });
    });
});

测试 mine

这里依旧用 3331 去挖一个新的 block:

在这里插入图片描述

随后查看 3331、3332 和 3333 是否都同步了:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

共识 consensus

consensus 是每个 node 都需要承认当前的 transaction/block 是 valid 的过程,简单的说就是认证。

新增验证功能

新增到 blockchain 中的函数如下:

validateChain = (blockchain: Block[]): boolean => {
  for (let i = 1; i < blockchain.length; i++) {
    const currBlock = blockchain[i],
      prevBlock = blockchain[i - 1];

    if (currBlock.previousBlockHash !== prevBlock.hash) return false;

    const blockHash = this.hashBlock(
      prevBlock.hash,
      {
        transactions: currBlock.transactions,
        index: currBlock.index,
      },
      currBlock.nonce
    );

    if (blockHash.substring(0, 4) !== '0000') return false;
  }

  const genesisBlock = blockchain[0];
  const { nonce, hash, previousBlockHash, transactions } = genesisBlock;

  return (
    nonce === 100 &&
    previousBlockHash === '0' &&
    hash === '0' &&
    transactions.length === 0
  );
};

这个函数主要实现了这么几个功能:

  1. 验证从第 2 个 block 开始,所有存在于 blockchain 中的 block 是否合法

    合法的 block 必须要满足:

    1. 当前 block 中存储的 prevBlockHash 需要与上一个 block 存储的 hash 值一致
    2. 当前 block 重新计算出来的 hash 值是一个合法的 blockchain hash,即前四个数字为 0000
  2. 验证 genesis block 是否合法

    这个部分就基于当前实现 hardcode 了

这个感兴趣的可以自己生成一些数据进行操作修改,这里提供一下我当时用来做测试的简单数据:

const bc1 = {
  chain: [
    {
      index: 1,
      timestamp: 1683430683516,
      transactions: [],
      nonce: 100,
      hash: '0',
      previousBlockHash: '0',
    },
    {
      index: 2,
      timestamp: 1683430713676,
      transactions: [
        {
          amount: 10,
          sender: 'sender a',
          recipient: 'miner a',
          transactionId: 'b60d9820fa5b41678a2ddd7e362ce677',
        },
      ],
      nonce: 57094,
      hash: '00008d14fdea3462ba42d5877404d0e79031b37efe0f836c4ed498521fbe6718',
      previousBlockHash: '0',
    },
    {
      index: 3,
      timestamp: 1683430741200,
      transactions: [
        {
          amount: 12.5,
          sender: '00',
          recipient: '0a574140dc034e0aa95d2f94202be1c9',
          transactionId: 'a950c175607545f393b2818c7dc8783e',
        },
        {
          amount: 10,
          sender: 'sender b',
          recipient: 'miner b',
          transactionId: '153b8d642e974c7a8141582f432b1333',
        },
      ],
      nonce: 23143,
      hash: '0000508c0ecea10829d6c757aa8d29d6198561cac666496add65dffbcdc9899d',
      previousBlockHash:
        '00008d14fdea3462ba42d5877404d0e79031b37efe0f836c4ed498521fbe6718',
    },
  ],
  pendingTransactions: [
    {
      amount: 12.5,
      sender: '00',
      recipient: '0a574140dc034e0aa95d2f94202be1c9',
      transactionId: 'a6da7fea6cba47fa996bdd001f56b11a',
    },
  ],
  currentNodeUrl: 'http://localhost:3331',
  networkNodes: [],
};

创建 consensus endpoint

这里要做的步骤是:

  1. 获取当前所有 network 上所有 nodes 的 blockchains
  2. 遍历获取的 blockchains,以最长的那条 blockchain 作为 single source of truth
app.get('/consensus', (req, res) => {
  const reqPromises = bitcoin.networkNodes.map((newtorkNodeUrl) => {
    const reqOptions = {
      uri: newtorkNodeUrl + '/blockchain',
      method: 'GET',
      json: true,
    };

    return rp(reqOptions);
  });

  Promise.all(reqPromises).then((blockchains) => {
    const currChainLen = bitcoin.chain.length;
    let maxChainLen = currChainLen,
      newLongestChain = null,
      newPendingTransactions = null;
    blockchains.forEach((blockchain: Blockchain) => {
      if (blockchain.chain.length > maxChainLen) {
        maxChainLen = blockchain.chain.length;
        newLongestChain = blockchain.chain;
        newPendingTransactions = blockchain.pendingTransactions;
      }
    });

    const isValidateChain = newLongestChain
      ? bitcoin.validateChain(newLongestChain)
      : false;

    if (!newLongestChain || (newLongestChain && !isValidateChain)) {
      res.json({
        message: 'Current chain has not been replaced.',
        chain: bitcoin.chain,
      });

      return;
    }

    if (newLongestChain && isValidateChain) {
      bitcoin.chain = newLongestChain;
      res.json({
        message: 'Current chain has been replaced.',
        chain: bitcoin.chain,
      });
    }
  });
});

测试 consensus 部分:

⚠️:刚开始错误使用了 post 去实现,应该是 get,代码部分改了,postman 测试这里暂时不改了

3331、3332、3333 这三个 nodes 运行 consensus 都不会有任何的区别,因为 3 条 blockchain 都是一致的:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

但是这个时候如果有新的 node 加到了 network 中,那么这个 node 上所留有的就是一个空的 blockchain:

在这里插入图片描述

再运行 consensus 就会使得当前 node 上的 blockchain 被当前网络中最长的 blockchain 所取代:

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。