meshplus/bitxhub

Add POW consensus

sandyzhou opened this issue · 2 comments

Add a POW consensus implementation.

Please refer to the document about how to develop a consensus plugin in BitXHub:
https://meshplus.github.io/bitxhub/bitxhub/dev/consensus/

描述

为BitXHub增加一个POW共识算法插件

标准

  1. 根据BitXHub中现有的共识接口,实现POW共识算法插件
  2. 完善的单元测试

赛题解读

BitXHub采用插件机制,实现了灵活的共识算法的适配,目前支持RAFT和RBFT。根据BitXHub的共识插件机制,实现一个POW共识算法插件。

解题思路

BitXHub的共识插件的写法可以参考:
https://meshplus.github.io/bitxhub/bitxhub/dev/consensus/
https://meshplus.github.io/bitxhub/bitxhub/design/consensus_plugin/

POW共识可参考:
https://github.com/ethereum/go-ethereum/tree/master/consensus

新增PoW共识算法总共分为以下几个流程:

满分100分,各个步骤得分如下:

1. 区块结构修改,增加diffcultly,nonce等字段;(10分)
2. 创世配置文件,配置初始难度值,难度调整周期等;(10分)
3. pow出块算法,采用CPU单线程出块;(20分)
4. 验证区块,验证区块的有效性;(30分)
5. 处理分叉,最长链原则,需要砍块,删状态;(30分)

下面我们对其进行简单阐述与说明。

1. 环境准备

为了更好的部署安装体验,我们建议您选用CentOS 8.2、Ubuntu 16.04和MacOS 10.15来进行部署安装。具体的环境配置教程参考bitxhub环境准备.

2. 中继链部署

==推荐方式:源码编译==

# 1. 首先拉取bitxhub项目源代码
git clone https://github.com/meshplus/bitxhub.git
# 2. 进入bitxhub目录,切换到指定的分支或版本后编译bitxhub二进制
cd bitxhub && git checkout v1.6.2 && make build
# 注意⚠️:首次编译需要在build之前先执行 make prepare 完成依赖安装
# 编译完成后可以在项目的bin目录下看到刚刚生成的bitxhub二进制文件
# 3. 接下来需要编译共识插件,进入到 internal/plugins 目录进行编译
cd internal/plugins && make plugins
# 编译完成后可以在项目的internal/plugins/build目录下看到刚刚生成的共识插件文件,raft.so和solo.so

中继链部署教程参考: bitxhub中继链部署

3. 开发与调试BitXHub

下载examples_bitxhub_v1.6.2.tar.gz, 并将node1的相关证书与私钥拷贝到Bitxhub/config文件目录下:

$ cp -r ~/Desktop/examples_bitxhub_v1.6.2/node1/* ./config 
$ make build  
$ cd internal/plugins && make plugins
$ cp build/* ../../config/plugins 

./config文件夹如下所示:

.
├── README.md
├── bitxhub.toml //中继链相关配置
├── certs		//节点证书
│   ├── …………
├── key.json
├── logs
│   ├── …………
├── order.toml	//共识插件相关配置
├── plugins		//共识插件,需要添加pow
│   ├── raft.so		
│   └── solo.so
├── start.sh		//启动脚本
└── storage
    ├── …………

8 directories, 18 files

IDE调试时,环境配置设置为:./bitxhub --repo ./config start即可。

共识插件调试需要更改bitxhub.toml 的order字段。

[order]
  plugin = "plugins/raft.so"

4. 新增PoW共识算法流程

1. 区块结构修改,增加diffcultly,nonce等字段

  • 下载Bitxhub-model(git clone https://github.com/meshplus/bitxhub-model.git),**注意将其放入bitxhub项目同级目录**。修改`Block.pb`文件:

    /*This is just an example, you can choose any type to fill the variable‘s filed. 
    Compared with block_hash, pow_block_hash lack receipt root.
    */
    syntax = "proto3";
    
    package pb;
    
    import "github.com/gogo/protobuf/gogoproto/gogo.proto";
    
    message Block {
        BlockHeader block_header = 1;
        bytes transactions = 2 [(gogoproto.customtype) = "Transactions"]; // transaction set
        bytes block_hash = 3 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash"];
        bytes signature = 4;
        bytes extra = 5;
        bytes pow_block_hash = 6 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash"];
    }
    
    message BlockHeader {
        uint64 number = 1;
        bytes state_root = 2 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash"];
        bytes tx_root = 3 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash"];
        bytes receipt_root = 4 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash"];
        bytes parent_hash = 5 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash"];
        int64 timestamp = 6;
        bytes version = 7;
        bytes Bloom = 8 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Bloom"];
        uint64 difficulty = 9;
        uint64 blockNonce = 10;
    }
  • 编译proto(注意将bitxhub-model项目放到$GOPATH并切换到bitxhub-model/pb目录下执行以下命令)

    	protoc -I=. \
    	-I${GOPATH}/src \
    	--gogofaster_out=:. \
    	block.proto

    如果遇到以下报错:

    - block.proto:5:1: Import "github.com/gogo/protobuf/gogoproto/gogo.proto" was not found or had errors.

    执行go get命令导入所需的工具即可,如:

    $ go get github.com/gogo/protobuf/gogoproto
  • 修改bitxhubgo.mod文件

    replace github.com/meshplus/bitxhub-model => ../bitxhub-model
    

2. 创世配置文件,配置初始难度值,难度调整周期等;

  • 新建pow文件夹,仿照solo模式进行开发

    .//bitxhub/pkg/order
    ├── build
    ├── config.go
    ├── etcdraft
    ├── filter.go
    ├── filter_test.go
    ├── mempool
    ├── pow
    │   ├── config.go
    │   └── node.go  
    │   ├── node_test.go
    │   └── testdata
    ├── solo
    │   ├── config.go
    │   ├── node.go
    │   ├── node_test.go
    │   └── testdata
    └── syncer
  • 修改Start接口,负责接受交易并开启mine

    func (n *Node) Start() error {
      //receive tx -> send txs to TxCache
    	go n.txCache.ListenEvent()
      //handle input request
      go n.run()
    	return nil
    }
  • 实现难度值调整

    //TODO: Calculate the difficulty.
    func CalcDifficulty(state mempool.ChainState) uint64 {
    	return 1
    }

3. pow出块算法,采用CPU单线程出块;

  • 开启共识模块,共识模块启动流程如下图所示

order start

交易通过api交由共识模块,经过不同的共识算法后,将区块放入commitC中,执行模块将对区块数据进行验证,并构成不通话发的merkle root(包括TxRoot、StateRoot和RecipetRoot),最终将区块存储到存储模块,并将交易交由路由模块。

以下是一个Bitxhub实现pow算法的demo(注意以下并非全部代码,只是搭建了框架,开发者可对其进行完善或重构)。

/*This is just an example, you can choose any method to implement the mine function.
*/

func (n *Node) run() {
	var (
		abort = make(chan struct{})
	)

	for {
		select {
		case <-n.ctx.Done():
			n.Stop()

		case txSet := <-n.txCache.TxSetC:
			// start batch timer when this node receives the first transaction
			if !n.batchMgr.IsBatchTimerActive() {
				n.batchMgr.StartBatchTimer()
			}
			if batch := n.mempool.ProcessTransactions(txSet.TxList, true, true); batch != nil {
				n.batchMgr.StopBatchTimer()
				n.proposeC <- batch
			}

		case <-n.batchMgr.BatchTimeoutEvent():
			n.batchMgr.StopBatchTimer()
			n.logger.Debug("Batch timer expired, try to create a batch")
			if n.mempool.HasPendingRequest() {
				if batch := n.mempool.GenerateBlock(); batch != nil {
					n.postProposal(batch)
				}
			} else {
				n.logger.Debug("The length of priorityIndex is 0, skip the batch timer")
			}

		case state := <-n.stateC:
			if state.Height%10 == 0 {
				n.logger.WithFields(logrus.Fields{
					"height": state.Height,
					"hash":   state.BlockHash.String(),
				}).Info("Report checkpoint")
			}
			n.mempool.CommitTransactions(state)

		case proposal := <-n.proposeC:
			n.logger.WithFields(logrus.Fields{
				"proposal_height": proposal.Height,
				"tx_count":        len(proposal.TxList),
			}).Debugf("Receive proposal from mempool")

			if proposal.Height != n.lastExec+1 {
				n.logger.Warningf("Expects to execute seq=%d, but get seq=%d, ignore it", n.lastExec+1, proposal.Height)
				return
			}
			n.logger.Infof("======== Call execute, height=%d", proposal.Height)
			mineBlock := &pb.Block{
				BlockHeader: &pb.BlockHeader{
					Version:    []byte("1.0.0"),
					Number:     proposal.Height,
					Timestamp:  time.Now().Unix(),
					Difficulty: n.difficulty,
					BlockNonce: 0,
				},
				Transactions: proposal.TxList,
			}

			localList := make([]bool, len(proposal.TxList))
			for i := 0; i < len(proposal.TxList); i++ {
				localList[i] = true
			}
			l2Root, err := n.buildTxMerkleTree(mineBlock.Transactions)
			if err != nil {
				panic(err)
			}
			mineBlock.BlockHeader.TxRoot = l2Root
			mineBlock.BlockHeader.ParentHash = n.getChainMetaFunc().BlockHash
			mineBlock.PowBlockHash = mineBlock.Hash()
			n.mine(mineBlock, localList, abort, 0)
			break
		}
	}

}
  • 实现mine()方法
/*This is just an example, you can choose any method to implement the mine function.
*/

func (n *Node) mine(block *pb.Block, localList []bool, abort chan struct{}, nonce uint64) {
search:
	for {
		select {
		case <-abort:
			// Mining terminated, update stats and abort
			n.logger.Infof("======== POW nonce search aborted.start next term mine========")

			// TODO: start next mining.

			break
		default:
			diff := big.NewInt(int64(block.BlockHeader.Difficulty))
			// Calculate target by difficulty.
			// two256 is the maximum number in 256bit.
			target := new(big.Int).Div(two256, diff)
			result := CalcBlockNonce(block.PowBlockHash, nonce)
			
			// if result < target, mine success.
			if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
				n.logger.WithFields(logrus.Fields{
					"pow nonce found and reported": nonce,
				})
				executeEvent := &pb.CommitEvent{
					Block:     block,
					LocalList: localList,
				}
				n.commitC <- executeEvent
				n.lastExec++
				n.logger.WithFields(logrus.Fields{
					"new block height = ": n.lastExec,
				})
				break search
			}
			nonce++
		}
	}
}

4. 验证区块,验证区块的有效性;

  • 实现verifyBlock方法

5. 处理分叉,最长链原则,需要砍块,删状态。

TODO