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共识算法插件
标准
- 根据BitXHub中现有的共识接口,实现POW共识算法插件
- 完善的单元测试
赛题解读
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
-
修改
bitxhub
的go.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
接口,负责接受交易并开启minefunc (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单线程出块;
- 开启共识模块,共识模块启动流程如下图所示
交易通过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