从零开始构建一个区块链(四): 共识

共识是区块链系统里非常重要的组成部分,是一个区块链系统能够顺利运行的基础。只有所有节点对区块链达成了共识,这个区块链系统才是一个健康的系统。最近比特币的分叉就是共识没有统一的结果。

这篇文章主要讨论如何添加peer节点以及当节点区块数据不一致时如何解决冲突,达成一致的问题。

一、Blockchain类扩展

想要解决和其他节点的区块冲突问题,首先需要能够获取其他节点的数据。我们在Blockchain类里添加一个nodes属性。

    this.nodes = new Set();

这里使用集合,即使重复添加也不受影响。

添加一个节点注册的方法(需要使用到urlparse模块):

  registerNode(address) {
    const parsedURL = URLparse(address);
    this.nodes.add(`${parsedURL.host}:${parsedURL.port}`);
  }

添加一个冲突解决的方法。这个方法背后的逻辑时,本节点会轮询this.nodes里节点的区块信息,如果有区块的长度大于本节点的长度并且区块都有效的话,那就是用对方的区块信息替换本节点的区块。默认来说,最长的区块链包含了最多的工作量,大家都只承认最长的区块链就好了。这也是比特币中使用的机制。代码如下:

  async resolveConflicts() {
    const neighbours = this.nodes;
    let maxLength = this.chain.length;
    for (let node of neighbours) {
      const response = await axios.get(`http://${node}/chain`)

      if (response.status === 200) {
        const chain = response.data.chain;
        const length = response.data.length;
        console.log('chain', chain)
        if ( length > maxLength && this.isChainValid(chain) ) {
          maxLength = length;
          this.chain = chain;
          return true
        }
      }
      return false 
    }
  }

这里使用了axios模块以及async await的异步机制。

另外Blockchain类的方法isChainValid也做了一些修改。

完整代码见Github [liangpeili/testcoin](liangpeili/testcoin)。


二、 添加路由

1. 添加节点注册路由

通过POST请求添加其他节点。

app.post('/nodes/register', jsonParser, (req, res) => {
  const nodes = req.body.nodes;
  if (nodes === null) {
    res.status(400).send('Error: Please supply a valid list of nodes.')
  }
  for (let node of nodes) {
    console.log('node', node)
    testCoin.registerNode(node);
  }
  const response = {
    message: 'New nodes have been added',
    totalNodes: Array.from(testCoin.nodes)
  }
  res.send(response)
})

2. 添加冲突解决路由

返回本节点区块链是否被替换,注意这里也使用了async/await。

app.get('/nodes/resolve', async (req, res) => {
  const replaced = await testCoin.resolveConflicts();
  console.log('replaced', replaced)
  let response = {
    'message': 'Our chain is authoritative',
    'new_chain': testCoin.chain
  }

  if (replaced) {
    response.message = 'Our chain was replaced';
  }
  res.send(response);
})


三、测试

我们在3001端口起一个服务,并且使用localhost:3001/mine来挖几个区块。

目前3001端口的区块链服务总共有5个区块。

3000端口的服务区块长度为:

我们先在3000端口的服务上注册节点信息。

返回数据为:

此时调用解决冲突的api:

可以看到3000端口的区块链已经被替换了。“Our chain was replaced”。本节点已经和其他节点的最长区块链达成共识。

本篇完整代码可参考[liangpeili/testcoin](liangpeili/testcoin),有问题也可以留言。

《从零开始构建一个区块链》到现在为止总共写了四篇,涉及加密函数、POW、共识等基础知识,如果能完整的把代码敲下来肯定可以收获对区块链的基本理解。目前决定暂停该系列的更新,接下来会整理出UC Berkeley 的区块链课程笔记,敬请关注!


参考:

learn-blockchains-by-building-one

编辑于 2017-11-27

文章被以下专栏收录