bitxhub/docs/develop/consensus_usage.md

8.7 KiB
Raw Blame History

共识算法插件化使用文档

在本教程中,你将构建一个完整功能的共识服务。过程中能学习基本的概念和具体使用细节。该示例将展示如何快速、轻松地接入自己的共识算法到BitXHub中来

1. 开发要求

  • 安装 go1.13+

  • 设置好$GOPATH等环境

2. 准备

为了更加便捷的开发共识服务接入到BitXHub中来,我们提供了一些接口和参数。

2.1 Order接口

我们规定了下面一些必要的接口,这些接口是BitXHub与共识服务的交互接口。

type Order interface {
	//开启共识服务
	Start() error

	//停止共识服务,关闭共识服务资源
	Stop()

	//交易发送到共识服务,共识服务将处理并打包该交易
	Prepare(tx *pb.Transaction) error

	//返回打包的区块
	Commit() chan *pb.Block

	//从网络接收到的共识消息
	Step(ctx context.Context, msg []byte) error

	//集群中产生了新Leader系统通过该接口判断共识服务是否正常
	Ready() bool

	//系统会通知该接口已经持久化的区块,
	ReportState(height uint64, hash types.Hash)

	//集群中可以正常工作的最少节点数量如在raft中要求正常节点数是N/2+1
	Quorum() uint64
}

2.2 Config参数

我们规定了下面一些参数可以从BitXHub传递给共识服务。

type Config struct {
   Id                 uint64 //节点ID
   RepoRoot           string //根路径
   StoragePath        string //存储文件路径
   PluginPath         string //插件文件路径
   PeerMgr            peermgr.PeerManager //网络模块组件
   PrivKey            crypto.PrivateKey //节点私钥
   Logger             logrus.FieldLogger //日志组件
   Nodes              map[uint64]types.Address //集群节点网络地址
   Applied            uint64 //当前区块高度
   Digest             string //当前区块哈希
   GetTransactionFunc func(hash types.Hash) (*pb.Transaction, error) //获取已经持久化的交易函数
   GetChainMetaFunc   func() *pb.ChainMeta //获取链的元信息函数,其中包括当前区块高度和区块哈希
}

2.3 Filter过滤器

为了更加方便过滤重复交易,我们提供了布隆过滤器的组件。

const (
   filterDbKey = "bloom_filter"
   m = 10000000 //filter的字节位数
   k = 4        //计算hash的次数
)

type ReqLookUp struct {
   filter       *bloom.BloomFilter //bloom过滤器
   storage storage.Storage //leveldb存储
   b       bytes.Buffer //filter缓存
}

3. 程序目的

本教程以开发一个简单Solo版本的共识算法为例。

3.1 开始编写你的程序

首先选择你的工程目录按照正常的GO程序的流程建立项目

$ go version // 确认你安装的GO版本
$ mkdir ${YOUR_PROJECT}
$ cd ${YOUR_PROJECT}
$ go mod init

3.2 Node对象

首先创建一个node.go文件这个文件是共识Plugin的核心和入口来看看Node具体结构

type Node struct {
   sync.RWMutex         
   height             uint64             // 当前区块高度
   pendingTxs         *list.List         //交易池
   commitC            chan *pb.Block     //区块channel
   logger             logrus.FieldLogger //日志
   reqLookUp          *order.ReqLookUp   //bloom过滤器
   getTransactionFunc func(hash types.Hash) (*pb.Transaction, error) //获取持久化的交易函数
   packSize  int           //出块的最大交易数量
   blockTick time.Duration //出块间隔

   ctx    context.Context 
   cancel context.CancelFunc 
}

然后应该提供一个Order的实例化的接口(类似于构造函数),具体代码如下:

func NewNode(opts ...order.Option) (order.Order, error) {
   //处理Order参数
   config, err := order.GenerateConfig(opts...)
   if err != nil {
      return nil, fmt.Errorf("generate config: %w", err)
   }

   //创建leveldb用于存储bloom filter
   storage, err := leveldb.New(config.StoragePath)
   if err != nil {
      return nil, fmt.Errorf("new leveldb: %w", err)
   }

   //创建bloom filter用于过滤重复交易
   reqLookUp, err := order.NewReqLookUp(storage)
   if err != nil {
      return nil, fmt.Errorf("new bloom filter: %w", err)
   }

   //创建node的上下文
   ctx, cancel := context.WithCancel(context.Background())
   return &Node{
      height:             config.Applied,//区块的当前高度
      pendingTxs:         list.New(),
      commitC:            make(chan *pb.Block, 1024),
      packSize:           500, //出块的最大交易数量可配置在order.toml中
      blockTick:          500 * time.Millisecond, //出块时间间隔可配置在order.toml中
      reqLookUp:          reqLookUp,
      getTransactionFunc: config.GetTransactionFunc,
      logger:             config.Logger,
      ctx:                ctx,
      cancel:             cancel,
   }, nil
}

3.3 Node主要方法

通过描述Node的主要方法介绍pending的交易是如何被打包到区块中以及如何与BitXHub系统进行交互。

3.3.1 Start方法

功能:定时在交易池中扫描交易并出块。

func (n *Node) Start() error {
   go n.execute()
   return nil
}

func (n *Node) execute(){
   //开启定时器定时扫描交易池中是否存在pending的交易
   ticker := time.NewTicker(n.blockTick)
   defer ticker.Stop()

   for {
      select {
      case <-ticker.C:
         n.Lock()
         l := n.pendingTxs.Len()
         if l == 0 {
            n.Unlock()
            continue
         }

         var size int
         if l > n.packSize {
            size = n.packSize
         } else {
            size = l
         }

	 //打包交易
         txs := make([]*pb.Transaction, 0, size)
         for i := 0; i < size; i++ {
            front := n.pendingTxs.Front()
            tx := front.Value.(*pb.Transaction)
            txs = append(txs, tx)
            n.pendingTxs.Remove(front)
         }

	//区块高度+1
         n.height++
         n.Unlock()
	 //区块出块
         block := &pb.Block{
            BlockHeader: &pb.BlockHeader{
               Version:   []byte("1.0.0"),
               Number:    n.height,
               Timestamp: time.Now().UnixNano(),
            },
            Transactions: txs,
         }
         n.commitC <- block
      case <-n.ctx.Done():
         ticker.Stop()
      }
   }
    return nil
}

3.3.2 Stop方法

功能:停止共识,释放共识相关资源。

func (n *Node) Stop() {
   n.cancel()
}

3.3.3 Prepare方法

功能:从BitXHub系统中传入交易,收集进交易池。

func (n *Node) Prepare(tx *pb.Transaction) error {
   hash := tx.TransactionHash
   //检查交易是否存在,防止交易二次打包
   if ok := n.reqLookUp.LookUp(hash.Bytes()); ok {
      if tx, _ := n.getTransactionFunc(hash); tx != nil {
         return nil
      }
   }

   //交易进入交易池
   n.pushBack(tx)
   return nil
}

3.3.4 Commit方法

功能:返回新区块的channel

func (n *Node) Commit() chan *pb.Block {
   return n.commitC
}

3.3.5 Step方法

功能:通过该接口接收共识的网络消息。


//由于示例是Solo的版本故具体不实现该方法
func (n *Node) Step(ctx context.Context, msg []byte) error {
   return nil
}

3.3.6 Ready方法

功能判断共识是否完成Leader是否完成选举。


//由于示例是Solo的版本单节点直接返回True
func (n *Node) Ready() bool {
   return true
}

3.3.7 ReportState方法

功能:新区块被持久化后,BitXHub会调用该接口通知共识服务

func (n *Node) ReportState(height uint64, hash types.Hash) {
   //每一个新区块被存储后持久化bloom filter
   if err := n.reqLookUp.Build(); err != nil {
      n.logger.Errorf("bloom filter persistence error", err)
   }
   //每十个区块做一个Check Point
   if height%10 == 0 {
      n.logger.WithFields(logrus.Fields{
         "height": height,
         "hash":   hash.ShortString(),
      }).Info("Report checkpoint")
   }
}

3.3.8 Quorum方法

功能集群中可以正常工作的最少节点数量比如在raft中要求正常节点数是N/2+1

//由于示例是Solo的版本直接返回1
func (n *Node) Quorum() uint64 {
   return 1
}

4. 接入共识算法

4.1 配置文件

可以通过配置order.toml文件,自定义你的共识算法。

[solo]
[solo.tx_pool]
   pack_size = 500 # 出块最大交易数
   block_tick = "500ms" # 出块间隔

4.2 插件化

共识算法插件化设计文档