Merge pull request #152 from meshplus/docs/make-documents-better
docs(docs): move documents in wiki to the document station
This commit is contained in:
commit
18263042a8
|
@ -3,38 +3,44 @@ module.exports = {
|
|||
description: 'BitXHub部署文档、使用文档、设计文档站',
|
||||
themeConfig: {
|
||||
sidebar: [
|
||||
// {
|
||||
// title: '版本信息',
|
||||
// path: '/version/'
|
||||
// },
|
||||
{
|
||||
title: '快速开始',
|
||||
path: '/quick/'
|
||||
path: '/quick/',
|
||||
},
|
||||
{
|
||||
title: '部署文档',
|
||||
path: '/deploy/',
|
||||
},
|
||||
{
|
||||
title: '使用文档',
|
||||
path: '/usage/',
|
||||
},
|
||||
{
|
||||
title: '开发文档',
|
||||
path: '/develop/',
|
||||
children: [
|
||||
{
|
||||
title: '共识算法插件化使用文档',
|
||||
path: '/develop/consensus_usage'
|
||||
},
|
||||
{
|
||||
title: '共识算法插件化设计文档',
|
||||
path: '/develop/consensus_design'
|
||||
},
|
||||
{
|
||||
title: '跨链合约编写规范',
|
||||
path: '/develop/interchain_contract'
|
||||
},
|
||||
{
|
||||
title: '验证引擎规则编写规范',
|
||||
path: '/develop/rule'
|
||||
}
|
||||
],
|
||||
},
|
||||
// {
|
||||
// title: '使用文档',
|
||||
// path: '/usage/',
|
||||
// children: [
|
||||
// {
|
||||
// title: '部署文档',
|
||||
// path: '/usage/deploy',
|
||||
// }
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
title: 'FAQ',
|
||||
path: '/faq/',
|
||||
},
|
||||
// {
|
||||
// title: '开发文档',
|
||||
// path: '/develop/',
|
||||
// children: [
|
||||
// {
|
||||
// title: '共识模块开发',
|
||||
// path: '/develop/order'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# 共识算法插件化设计文档
|
||||
|
||||
## 1. 项目结构
|
||||
|
||||
该项目为BitXHub提供共识算法的插件化,具体项目结构如下:
|
||||
|
||||
```none
|
||||
|
||||
./
|
||||
├── Makefile //编译文件
|
||||
├── README.md
|
||||
├── build
|
||||
│ └── consensus.so //编译后的共识算法二进制插件
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── order.toml //共识配置文件
|
||||
└── consensus //共识算法项目代码
|
||||
├── config.go
|
||||
├── node.go
|
||||
└── stack.go
|
||||
```
|
||||
|
||||
其中注意在`go.mod`中需要引用BitXHub项目源码,需要让该插件项目与BitXHub在同一目录下(建议在$GOPATH路径下)。
|
||||
|
||||
```none
|
||||
replace github.com/meshplus/bitxhub => ../bitxhub/
|
||||
```
|
||||
|
||||
## 2. 编译Plugin
|
||||
|
||||
我们采用GO语言提供的插件模式,实现`BitXHub`对于Plugin的动态加载。
|
||||
|
||||
编写`Makefile`编译文件:
|
||||
|
||||
```shell
|
||||
SHELL := /bin/bash
|
||||
CURRENT_PATH = $(shell pwd)
|
||||
GO = GO111MODULE=on go
|
||||
plugin:
|
||||
@mkdir -p build
|
||||
$(GO) build --buildmode=plugin -o build/consensus.so consensus/*.go
|
||||
```
|
||||
|
||||
运行下面的命令,能够得到 `consensus.so`文件。
|
||||
|
||||
```shell
|
||||
$ make plugin
|
||||
```
|
||||
|
||||
修改节点的`bitxhub.toml`
|
||||
|
||||
```none
|
||||
[order]
|
||||
plugin = "plugins/consensus.so"
|
||||
```
|
||||
|
||||
将你编写的动态链接文件和`order.toml`文件,分别放到节点的plugins文件夹和配置文件下。
|
||||
|
||||
```none
|
||||
./
|
||||
├── api
|
||||
├── bitxhub.toml
|
||||
├── certs
|
||||
│ ├── agency.cert
|
||||
│ ├── ca.cert
|
||||
│ ├── node.cert
|
||||
│ └── node.priv
|
||||
├── key.json
|
||||
├── logs
|
||||
├── network.toml
|
||||
├── order.toml //共识算法配置文件
|
||||
├── plugins
|
||||
│ ├── consensus.so //共识算法插件
|
||||
├── start.sh
|
||||
└── storage
|
||||
```
|
||||
|
||||
结合我们提供的`BitXHub`中继链,就能接入到跨链平台来。
|
|
@ -0,0 +1,346 @@
|
|||
# 共识算法插件化使用文档
|
||||
|
||||
在本教程中,你将构建一个完整功能的共识服务。过程中能学习基本的概念和具体使用细节。该示例将展示如何快速、轻松地**接入自己的共识算法到BitXHub中来**。
|
||||
|
||||
## 1. 开发要求
|
||||
|
||||
- 安装 [__go1.13+__](https://golang.org/doc/install)
|
||||
|
||||
- 设置好$GOPATH等环境
|
||||
|
||||
## 2. 准备
|
||||
|
||||
为了更加便捷的开发共识服务接入到`BitXHub`中来,我们提供了一些接口和参数。
|
||||
|
||||
### 2.1 Order接口
|
||||
|
||||
我们规定了下面一些必要的接口,这些接口是`BitXHub`与共识服务的交互接口。
|
||||
|
||||
```go
|
||||
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传递给共识服务。
|
||||
|
||||
```go
|
||||
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过滤器
|
||||
|
||||
为了更加方便过滤重复交易,我们提供了布隆过滤器的组件。
|
||||
|
||||
```go
|
||||
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程序的流程建立项目
|
||||
|
||||
```shell
|
||||
$ go version // 确认你安装的GO版本
|
||||
$ mkdir ${YOUR_PROJECT}
|
||||
$ cd ${YOUR_PROJECT}
|
||||
$ go mod init
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 3.2 Node对象
|
||||
|
||||
首先创建一个`node.go`文件,这个文件是共识Plugin的核心和入口,来看看`Node`具体结构
|
||||
|
||||
```go
|
||||
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`的实例化的接口(类似于构造函数),具体代码如下:
|
||||
|
||||
```go
|
||||
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方法
|
||||
|
||||
功能:定时在交易池中扫描交易并出块。
|
||||
|
||||
```go
|
||||
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方法
|
||||
|
||||
功能:停止共识,释放共识相关资源。
|
||||
|
||||
```go
|
||||
func (n *Node) Stop() {
|
||||
n.cancel()
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.3 Prepare方法
|
||||
|
||||
功能:从`BitXHub`系统中传入交易,收集进交易池。
|
||||
|
||||
```go
|
||||
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`。
|
||||
|
||||
```go
|
||||
func (n *Node) Commit() chan *pb.Block {
|
||||
return n.commitC
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 3.3.5 Step方法
|
||||
|
||||
功能:通过该接口接收共识的网络消息。
|
||||
|
||||
```go
|
||||
|
||||
//由于示例是Solo的版本,故具体不实现该方法
|
||||
func (n *Node) Step(ctx context.Context, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.6 Ready方法
|
||||
|
||||
功能:判断共识是否完成,Leader是否完成选举。
|
||||
|
||||
```go
|
||||
|
||||
//由于示例是Solo的版本,单节点直接返回True
|
||||
func (n *Node) Ready() bool {
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.7 ReportState方法
|
||||
|
||||
功能:新区块被持久化后,`BitXHub`会调用该接口通知共识服务
|
||||
|
||||
```go
|
||||
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)。
|
||||
|
||||
```go
|
||||
//由于示例是Solo的版本,直接返回1
|
||||
func (n *Node) Quorum() uint64 {
|
||||
return 1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 4. 接入共识算法
|
||||
|
||||
### 4.1 配置文件
|
||||
|
||||
可以通过配置`order.toml`文件,自定义你的共识算法。
|
||||
|
||||
```none
|
||||
[solo]
|
||||
[solo.tx_pool]
|
||||
pack_size = 500 # 出块最大交易数
|
||||
block_tick = "500ms" # 出块间隔
|
||||
```
|
||||
|
||||
### 4.2 插件化
|
||||
[共识算法插件化设计文档](https://github.com/meshplus/bitxhub/wiki/%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95%E6%8F%92%E4%BB%B6%E5%8C%96%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3)
|
||||
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
# 跨链合约编写规范
|
||||
|
||||
## 跨链合约设计说明
|
||||
|
||||
按照跨链合约的设计,我们需要在有跨链需求的应用链上部署两种合约。一个合约负责对接跨链网关Pier,为跨链管理合约Broker;一个合约负责具体的业务场景,为业务合约。业务合约需要跨链时,要统一将跨链请求提交到Broker合约上,Broker统一和Pier进行交互。一个Broker合约可以负责对接多个业务合约。
|
||||
|
||||
同时为了简化Broker的编写,我们设计了Broker合约和业务合约的相应接口。
|
||||
|
||||
### Broker 合约接口
|
||||
|
||||
```java
|
||||
public interface Broker {
|
||||
// 提供给业务合约注册。注册且审核通过的业务合约才能调用Broker合约的跨链接口
|
||||
register(string id) Response
|
||||
|
||||
// 提供给管理员审核已经注册的业务合约
|
||||
audit(string id, bool status) Response
|
||||
|
||||
// getInnerMeta 是获取跨链请求相关的Meta信息的接口。以Broker所在的区块链为目的链的一系列跨链请求的序号信息。如果Broker在A链,则可能有多条链和A进行跨链,如B->A:3; C->A:5。返回的map中,key值为来源链ID,value对应该来源链已发送的最新的跨链请求的序号,如{B:3, C:5}。
|
||||
getInnerMeta() Response
|
||||
|
||||
// getOuterMeta 是获取跨链请求相关的Meta信息的接口。以Broker所在的区块链为来源链的一系列跨链请求的序号信息。如果以Broker在A链,则A可能和多条链进行跨链,如A->B:3; A->C:5。返回的map中,key值为目的链ID,value对应已发送到该目的链的最新跨链请求的序号,如{B:3, C:5}。
|
||||
getOuterMeta() Response
|
||||
|
||||
// getCallbackMeta 是获取跨链请求相关的Meta信息的接口。以Broker所在的区块链为来源链的一系列跨链请求的序号信息。如果Broker在A链,则A可能和多条链进行跨链,如A->B:3; A->C:5;同时由于跨链请求中支持回调操作,即A->B->A为一次完整的跨链操作,我们需要记录回调请求的序号信息,如A->B->:2; A->C—>A:4。返回的map中,key值为目的链ID,value对应到该目的链最新的带回调跨链请求的序号,如{B:2, C:4}。(注意 callbackMeta序号可能和outMeta是不一致的,这是由于由A发出的跨链请求部分是没有回调的)
|
||||
getCallbackMeta() Response
|
||||
|
||||
// getInMessage 查询历史跨链请求。查询键值中srcChainID指定来源链,idx指定序号,查询结果为以Broker所在的区块链作为目的链的跨链请求。
|
||||
getInMessage(string srcChainID, uint64 idx) Response
|
||||
|
||||
// getOutMessage 查询历史跨链请求。查询键值中dstChainID指定目的链,idx指定序号,查询结果为以Broker所在的区块链作为来源链的跨链请求。
|
||||
getOutMessage(string dstChainID, uint64 idx) Response
|
||||
|
||||
// 提供给业务合约发起跨链资产交换的接口
|
||||
InterchainTransferInvoke(string dstChainID, string destAddr, string args) Response
|
||||
|
||||
// 提供给业务合约发起跨链数据交换的接口
|
||||
InterchainDataSwapInvoke(string dstChainID, string destAddr, string key) Response
|
||||
|
||||
// 提供给业务合约发起通用的跨链交易的接口
|
||||
InterchainInvoke(string dstChainID, string sourceAddr, string destAddr, string func, string args, string callback) Response
|
||||
|
||||
// 提供给跨链网关调用的接口,跨链网关收到跨链充值的请求时候调用
|
||||
interchainCharge(string srcChainID, uint64 index, string destAddr, string sender, string receiver, uint64 amount) Response
|
||||
|
||||
// 提供给跨链网关调用的接口,跨链网关收到跨链转账执行结果时调用
|
||||
interchainConfirm(string srcChainID, uint64 index, string destAddr, bool status, string sender, uint64 amount) Response
|
||||
|
||||
// 提供给跨链网关调用的接口,跨链网关收到跨链数据交换请求时调用
|
||||
interchainGet(string srcChainID, uint64 index, string destAddr, string key) Response
|
||||
|
||||
// 提供给跨链网关调用的接口,跨链网关收到跨链数据交换执行结果时调用
|
||||
interchainSet(string srcChainID, uint64 index, string destAddr, string key, string value) Response
|
||||
|
||||
// 提供给合约部署初始化使用
|
||||
initialize() Response
|
||||
}
|
||||
```
|
||||
|
||||
#### 重要接口说明
|
||||
|
||||
1. `InterchainInvoke` 接口
|
||||
|
||||
改接口是实现通用的跨链调用的接口。接受的参数有:目的链ID,发起跨链交易的业务合约地址或ID,目的链业务合约地址或ID,跨链调用的函数名,该函数的参数,回调函数名。
|
||||
|
||||
Broker会记录跨链交易相应的元信息,并对跨链交易进行编号,保证跨链交易有序进行。并且抛出跨链事件,以通知跨链网关跨链交易的产生。
|
||||
|
||||
2. `InterchainTransferInvoke` 接口
|
||||
|
||||
是对`InterchainInvoke` 接口的封装,专门用于发起一次跨链转账业务。接受参数有:目的链ID,目的链业务合约地址或ID,发起转账账户名,接受转账账户名,转账金额。
|
||||
|
||||
3. `InterchainDataSwapInvoke` 接口
|
||||
|
||||
是对InterchainInvoke 接口的封装,专门用于发起一次跨链数据交换业务。接受参数有:目的链ID,目的链业务合约地址或ID,Key值。
|
||||
|
||||
4. `interchainCharge` 接口
|
||||
|
||||
接受跨链转账的接口。由Broker合约根据业务合约地址或ID对业务合约进行充值操作,并记录相应的元信息。接受参数有:来源链ID,交易序号,目的业务合约ID,发起账户名,接收账户名,转账金额。
|
||||
|
||||
5. `interchainConfirm` 接口
|
||||
|
||||
接受跨链转账的接口。由Broker合约根据业务合约地址或ID对业务合约进行充值操作,并记录相应的元信息。接受参数有:来源链ID,交易序号,目的业务合约ID,跨链转账交易状态,接收账户名,转账金额。
|
||||
|
||||
Broker需要根据跨链交易的状态决定是否进行回滚操作,并记录相应元信息。
|
||||
|
||||
6. `interchainGet` 接口
|
||||
|
||||
接受跨链数据获取的接口。接受参数有:来源链ID,交易序号,目的业务合约ID,Key值。
|
||||
|
||||
7. `interchainSet` 接口
|
||||
|
||||
接受跨链数据回写的接口。接受参数有:来源链ID,交易序号,目的业务合约ID,Key值,value值。
|
||||
|
||||
### 业务合约接口
|
||||
|
||||
业务合约现阶段分为资产类和数据交换类的业务合约,由于资产类的有操作原子性和安全性的考虑,需要的接口实现上比数据交换类的业务合约更复杂。
|
||||
|
||||
#### Transfer 合约
|
||||
|
||||
```java
|
||||
public interface Transfer {
|
||||
// 发起一笔跨链交易的接口
|
||||
transfer(string dstChainID, string destAddr, string sender, string receiver, string amount) Response
|
||||
|
||||
// 提供给Broker合约收到跨链充值所调用的接口
|
||||
interchainCharge(string sender, string receiver, uint64 val) Response
|
||||
|
||||
// 跨链交易失败之后,提供给Broker合约进行回滚的接口
|
||||
interchainRollback(string sender, uint64 val) Response
|
||||
}
|
||||
```
|
||||
|
||||
#### DataSwapper合约
|
||||
|
||||
```java
|
||||
public interface DataSwapper {
|
||||
// 发起一个跨链获取数据交易的接口
|
||||
get(string dstChainID, string dstAddr, string key) Response
|
||||
|
||||
// 提供给Broker合约调用,当Broker收到跨链获取数据的请求时取数据的接口
|
||||
interchainGet(string key) Response
|
||||
|
||||
// 跨链获取到的数据回写的接口
|
||||
interchainSet(string key, string value) Response
|
||||
}
|
||||
```
|
||||
|
||||
## 具体实现
|
||||
|
||||
对于想要接入到我们的跨链平台中的Fabric区块链,我们已经有提供跨链管理合约Broker和相应的Plugin,你只需要对你的业务合约进行一定的改造便可拥有跨链功能。
|
||||
|
||||
**如果是其他应用链,你可以根据我们的设计思路自行开发跨链管理合约以及相应的Plugin。**
|
||||
|
||||
现在我们已经有Solidity版本和chaincode版本编写的跨链合约样例实现,具体说明如下:
|
||||
|
||||
* [Solidity 跨链合约实现]()
|
||||
* [chaincode 跨链合约实现]()
|
||||
|
||||
如果你需要新的语言编写合约,你可以按照我们的设计思路和参考实现进行进一步的开发。
|
||||
|
||||
### 改造业务合约
|
||||
|
||||
本章主要说明在Fabric应用链上,如何使用我们提供的跨链管理合约Broker,在你已有业务合约的基础上添加接口,以或得跨链能力。
|
||||
|
||||
#### 业务合约Demo
|
||||
|
||||
假设你已经有了一个简单的KV存储的业务合约,代码如下:
|
||||
|
||||
```go
|
||||
type KVStore struct{}
|
||||
|
||||
func (s *KVStore) Init(stub shim.ChaincodeStubInterface) peer.Response {
|
||||
return shim.Success(nil)
|
||||
}
|
||||
|
||||
func (s *KVStore) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
|
||||
function, args := stub.GetFunctionAndParameters()
|
||||
|
||||
fmt.Printf("invoke: %s\n", function)
|
||||
switch function {
|
||||
case "get":
|
||||
return s.get(stub, args)
|
||||
case "set":
|
||||
return s.set(stub, args)
|
||||
default:
|
||||
return shim.Error("invalid function: " + function + ", args: " + strings.Join(args, ","))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KVStore) get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
|
||||
// args[0]: key
|
||||
value, err := stub.GetState(args[0])
|
||||
if err != nil {
|
||||
return shim.Error(err.Error())
|
||||
}
|
||||
|
||||
return shim.Success(value)
|
||||
}
|
||||
|
||||
// get is business function which will invoke the to,tid,id
|
||||
func (s *KVStore) set(stub shim.ChaincodeStubInterface, args []string) peer.Response {
|
||||
if len(args) != 2 {
|
||||
return shim.Error("incorrect number of arguments")
|
||||
}
|
||||
|
||||
err := stub.PutState(args[0], []byte(args[1]))
|
||||
if err != nil {
|
||||
return shim.Error(err.Error())
|
||||
}
|
||||
|
||||
return shim.Success(nil)
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := shim.Start(new(KVStore))
|
||||
if err != nil {
|
||||
fmt.Printf("Error starting chaincode: %s", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在你想在这个合约的基础上增加一个跨链获取数据的功能,如果使用我们的跨链管理合约提供的接口,很简单的增加几个接口就可以了。
|
||||
|
||||
##### 发起跨链数据交换的接口
|
||||
|
||||
为了方便用户使用,我们在原来获取数据的接口基础增加这个功能:
|
||||
|
||||
```go
|
||||
const (
|
||||
channelID = "mychannel"
|
||||
brokerContractName = "broker"
|
||||
interchainInvokeFunc = "InterchainDataSwapInvoke"
|
||||
)
|
||||
|
||||
func (s *KVStore) get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
|
||||
switch len(args) {
|
||||
case 1:
|
||||
// args[0]: key
|
||||
value, err := stub.GetState(args[0])
|
||||
if err != nil {
|
||||
return shim.Error(err.Error())
|
||||
}
|
||||
|
||||
return shim.Success(value)
|
||||
case 3:
|
||||
// args[0]: destination appchain id
|
||||
// args[1]: destination contract address
|
||||
// args[2]: key
|
||||
b := util.ToChaincodeArgs(interchainInvokeFunc, args[0], args[1], args[2])
|
||||
response := stub.InvokeChaincode(brokerContractName, b, channelID)
|
||||
|
||||
if response.Status != shim.OK {
|
||||
return shim.Error(fmt.Errorf("invoke broker chaincode %s error: %s", brokerContractName, response.Message).Error())
|
||||
}
|
||||
|
||||
return shim.Success(nil)
|
||||
default:
|
||||
return shim.Error("incorrect number of arguments")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
由于我们的跨链管理合约一旦部署之后,chaincode name和所在的channel和跨链接口都是不变的,所以在业务变量中直接使用常量指定Broker合约的相关信息。
|
||||
|
||||
```go
|
||||
b := util.ToChaincodeArgs(interchainInvokeFunc, args[0], args[1], args[2])
|
||||
response := stub.InvokeChaincode(brokerContractName, b, channelID)
|
||||
```
|
||||
|
||||
这两行代码调用了我们的跨链管理合约,只需要提供参数:目的链ID,目的链上业务合约的地址,想要获取数据的`Key`值。
|
||||
|
||||
##### 跨链获取的接口
|
||||
|
||||
```go
|
||||
func (s *KVStore) interchainGet(stub shim.ChaincodeStubInterface, args []string) peer.Response {
|
||||
if len(args) != 2 {
|
||||
return shim.Error("incorrect number of arguments")
|
||||
}
|
||||
|
||||
value, err := stub.GetState(args[0])
|
||||
if err != nil {
|
||||
return shim.Error(err.Error())
|
||||
}
|
||||
|
||||
return shim.Success(value)
|
||||
}
|
||||
```
|
||||
|
||||
`interchainGet` 接受参数 `key`,在本合约中查询该`Key`值对应的`value`,并返回。该接口提供给`Broker`合约进行跨链获取数据的调用。
|
||||
|
||||
##### 跨链回写的接口
|
||||
|
||||
```go
|
||||
func (s *KVStore) interchainSet(stub shim.ChaincodeStubInterface, args []string) peer.Response {
|
||||
if len(args) != 2 {
|
||||
return shim.Error("incorrect number of arguments")
|
||||
}
|
||||
|
||||
err := stub.PutState(args[0], []byte(args[1]))
|
||||
if err != nil {
|
||||
return shim.Error(err.Error())
|
||||
}
|
||||
|
||||
return shim.Success(nil)
|
||||
}
|
||||
```
|
||||
|
||||
`interchainSet` 接受参数 `key`,在本合约中设置`Key`值对应的`value`。该接口提供给`Broker`合约回写跨链获取数据的时候进行调用。
|
||||
|
||||
### 总结
|
||||
|
||||
经过上面的改造,你的业务合约已经具备跨链获取数据的功能了,完整的代码可以参数[这里](https://github.com/meshplus/pier-client-fabric/tree/master/example)
|
|
@ -0,0 +1,166 @@
|
|||
# 验证引擎规则编写规范
|
||||
|
||||
## 智能合约
|
||||
|
||||
BitXHub提供wasm虚拟机来运行验证规则的智能合约,应用链方可以灵活自主实时的部署和更新验证规则的智能合约。
|
||||
|
||||
验证规则的智能合约的主要目的是:
|
||||
|
||||
1. 解析IBTP的Proof字段,不同的应用链在Pier发送IBTP时会对自身的验证信息做不同形式的封装,智能合约需要对封装的Proof进行解析,得到需要验签的参数;
|
||||
|
||||
1. 对得到的验签的参数采用对应的签名算法进行验签。
|
||||
|
||||
BitXHub采用wasmer作为wasm的虚拟机,因此验证规则智能合约需要遵循wasi的规范,验证规则合约需要使用指定的方法名。规则合约的编写者可以通过以下两种方式来进行合约的编写,入口函数为verify:
|
||||
|
||||
3. 编写智能合约时可以引入BitXHub提供给wasm的签名函数。
|
||||
|
||||
```rust
|
||||
|
||||
// BitXHub provides ecdsa signature verify method
|
||||
|
||||
// Take this as an example
|
||||
|
||||
extern {
|
||||
|
||||
fn ecdsa_verify(...) -> ... ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
fn verify(...) -> ... {
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
4. 应用链方在合约中自己编写验签方法,并编译成wasm智能合约。
|
||||
|
||||
## 规则部署
|
||||
|
||||
应用链在注册到BitXHub以后,需要将自身对跨链交易的背书验证规则注册到BitXHub上,不然跨链交易的请求是无法通过的。所以应用链在注册到BitXHub后想要进行跨链请求就必须将自身的验证规则合约部署到BitXHub上。
|
||||
|
||||
首先应用链需要通过部署合约的方式将验证规则的合约部署到BitXHub上,部署完后WASM虚拟机会返回一个合约的地址,这个地址就是验证引擎需要调用的地址。客户端在拿到部署合约的结果(验证规则的地址)后。需要调用BitXHub的内置合约,把验证规则的地址和自己应用链的ID关联起来。
|
||||
|
||||
之后每一次跨链交易产生,验证引擎就会通过这个合约地址拿到验证规则合约,将合约载入到虚拟机中对验证信息进行校验。
|
||||
|
||||
|
||||
## 规则调用
|
||||
|
||||
当BitXHub处理一条跨链交易的时候,需要通过验证引擎验证交易的有效性,验证引擎会根据来源链的ID找到对应的验证规则,加载到WASM虚拟机中,WASM虚拟机会通过验证的入口函数(Verify)调用验证规则的智能合约。运行验证规则,返回验证结果。
|
||||
|
||||
|
||||
## 证书签名
|
||||
|
||||
一般来说,验证签名需要证书或者公钥,验证引擎的背书公钥和证书是在应用链注册到BitXHub中的时候上传到BitXHub中的。
|
||||
|
||||
## 合约编写
|
||||
|
||||
### 参数传递
|
||||
|
||||
由于wasm虚拟机对传入传出的数据类型有限制,现行支持的类型不足以支持将验证信息直接传入到wasm虚拟机中,为了使wasm合约编写者能对验证信息直接读写,BitXHub提供了一套验证引擎合约的模板,模板对各个入口进行封装,用户只需要在入口函数中添加自己的验证逻辑即可。
|
||||
|
||||
wasm现阶段不支持对字符串或者是byte数组的输入输出,所以如何将证明信息传入到wasm虚拟机需要使用特定的方法。BitXHub的模板提供了对虚拟机内存访问的接口,通过该接口可以在虚拟机外部直接读写虚拟机的内存,从而变相的达到传入传出字符串的目的。
|
||||
|
||||
```rust
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
pub extern fn allocate(size: usize) -> *mut c_void {
|
||||
|
||||
let mut buffer = Vec::with_capacity(size);
|
||||
|
||||
let pointer = buffer.as_mut_ptr();
|
||||
|
||||
mem::forget(buffer);
|
||||
|
||||
|
||||
|
||||
pointer as *mut c_void
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
pub extern fn deallocate(pointer: *mut c_void, capacity: usize) {
|
||||
|
||||
unsafe {
|
||||
|
||||
let _ = Vec::from_raw_parts(pointer, 0, capacity);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 引用BitXHub提供的方法
|
||||
|
||||
由于wasm交叉编译对一些动态链接库的支持性低,许多合约编写者可能无法直接调用高级语言的算法库进行合约编写。BitXHub提供了一些基础的密码学的库方便智能合约的编写。
|
||||
|
||||
原理介绍:
|
||||
|
||||
wasm虚拟机允许外部import函数让wasm来调用,本质上是通过cgo的方式,将执行函数的入口指针提供给wasm虚拟机,这样wasm就可以调用Go的函数了。
|
||||
|
||||
虚拟机的内存是Go程序内存的一部分,虚拟机内部无法访问到Go程序的内存,所以wasm函数返回的指针都是基于虚拟机内存的偏移量,这就造成了Go的函数无法通过wasm函数返回的指针直接访问到指针对应的变量。我们这边的解决方案是将虚拟机内存的起始地址作为所有import函数的上下文,这样所有import函数都可以通过这个上下文和wasm函数返回的指针来定位变量的地址了。
|
||||
|
||||
```go
|
||||
|
||||
data := ctx.Data().(map[int]int)
|
||||
|
||||
memory := ctx.Memory()
|
||||
|
||||
signature := memory.Data()[sig_ptr : sig_ptr+70]
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 验证引擎与WASM虚拟机
|
||||
|
||||
### WASM虚拟机实例
|
||||
|
||||
当BitXHub的Executor创建时,验证引擎的实例也就被创建了。当每一次有跨链交易的请求到达时,验证引擎会根据交易来源链的ID拿到存储在链上的验证规则合约,然后会实例化一个WASM的虚拟机,将该验证规则合约加载到虚拟机中。
|
||||
|
||||
由于BitXHub本身提供很多密码学的方法,当WASM虚拟机实例化之前需要将这些方法import进来,从而可以提供给合约进行调用。
|
||||
|
||||
```go
|
||||
|
||||
imports, _ := wasm.NewImports().Append("*", *, C.*)
|
||||
|
||||
instance, _ := wasm.NewInstanceWithImports(bytes, imports)
|
||||
|
||||
```
|
||||
|
||||
### 验证引擎传参
|
||||
|
||||
验证引擎需要将IBTP解析出的Proof字段传输到WASM中以便合约能够执行,由于WASM虚拟机对传入数据的类型有非常严格的限制,现在只支持整形和浮点参数的传入,所以字符串和byte数组的信息无法直接被合约方法调用。
|
||||
|
||||
验证引擎首先需要根据传入参数的大小长度在WASM的虚拟机中划分出一块内存作为输入读取内存,并获得输入读取内存的指针,并且划分出一块内存作为输出读取内存,同样的获得输出内存指针。指针都可以用i32的整型数据变量来表示。
|
||||
|
||||
内存的划分和释放方法由BitXHub提供的合约模板提供,入口函数为allocate和deallocate。
|
||||
|
||||
|
||||
### 验证引擎验证
|
||||
|
||||
根据验证引擎传参章节的描述,验证引擎在虚拟机划出输入输出的内存以后,就会调用验证规则的入口函数start,将输入输出的内存指针作为穿参让入口函数执行。
|
||||
|
||||
合约模板已经帮合约编写者处理好了参数在wasm中的输入输出问题。合约编写者可以直接拿到Proof字段和Validator的信息,只需要对这些信息进行验证规则的编写即可。
|
||||
|
||||
```rust
|
||||
|
||||
pub fn verify<'a>(proof: &[u8], validators: &Vec<&'a str>) -> bool {
|
||||
|
||||
// Code your rule here with variables: proof and validators
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
|
||||
```
|
|
@ -0,0 +1,155 @@
|
|||
# BitXHub使用文档
|
||||
|
||||
## 准备工作
|
||||
|
||||
BitXHub使用[**golang**](https://golang.org/doc/install)开发,版本要求1.13以上。
|
||||
|
||||
如果你想在本地或者服务器上运行BitXHub,你需要在机器上安装有以下的依赖:
|
||||
|
||||
[__packr__](https://github.com/gobuffalo/packr)、[__gomock__](https://gist.github.com/thiagozs/4276432d12c2e5b152ea15b3f8b0012e#installation)、[__mockgen__](https://gist.github.com/thiagozs/4276432d12c2e5b152ea15b3f8b0012e#installation)
|
||||
|
||||
可以通过下面的命令,快速安装依赖:
|
||||
|
||||
```bash
|
||||
bash scripts/prepare.sh
|
||||
```
|
||||
|
||||
## 编译
|
||||
|
||||
编译bitxhub:
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
编译完后,会在`bin`目录下生成一个`bitxhub`二进制文件。
|
||||
|
||||
使用`version`命令进行验证:
|
||||
|
||||
```bash
|
||||
$ ./bin/bitxhub version
|
||||
BitXHub version: 1.0.0-master-2bb82e8
|
||||
App build date: 2020-03-31T00:02:19
|
||||
System version: darwin/amd64
|
||||
Golang version: go1.13.8
|
||||
```
|
||||
|
||||
如果想直接安装`bitxhub`到可执行环境,可以使用`install`命令:
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
## 启动solo模式bitxhub
|
||||
|
||||
使用下面的命令即可启动一个单节点的bitxhub:
|
||||
|
||||
```bash
|
||||
cd scripts
|
||||
bash solo.sh
|
||||
```
|
||||
|
||||
启动成功会打印出BitXHub的ASCII字体:
|
||||
<p>
|
||||
<img src="https://user-images.githubusercontent.com/29200902/77936225-30cc6400-72e5-11ea-9499-1ead165c5495.png" width="50%" />
|
||||
</p>
|
||||
|
||||
## 本地快速启动4节点
|
||||
|
||||
本地快速启动脚本依赖于[tmux](https://github.com/tmux/tmux/wiki),需要提前进行安装。
|
||||
|
||||
使用下面的命令克隆项目:
|
||||
|
||||
```shell
|
||||
git clone git@github.com:meshplus/bitxhub.git
|
||||
```
|
||||
|
||||
BitXHub还依赖于一些小工具,使用下面的命令进行安装:
|
||||
|
||||
```shell
|
||||
cd bitxhub
|
||||
bash scripts/prepare.sh
|
||||
```
|
||||
|
||||
最后,运行下面的命令即可运行一个四节点的BitXHub中继链:
|
||||
|
||||
```shell
|
||||
make cluster
|
||||
```
|
||||
|
||||
启动成功会在四个窗格中分别打印出BitXHub的ASCII字体。
|
||||
|
||||
**注意:** `make cluster`启动会使用`tmux`进行分屏,所以在命令执行过程中,最好不要进行终端切换。
|
||||
|
||||
## 自定义启动
|
||||
|
||||
首先,使用项目提供的配置脚本快速生成配置文件:
|
||||
|
||||
```shell
|
||||
cd bitxhub/scripts
|
||||
bash config.sh <number> // number是节点数量,
|
||||
```
|
||||
|
||||
上面命令以生成4个节点配置作为例子,会在当前目录下的build文件夹下生成如下文件:
|
||||
|
||||
```shell
|
||||
.
|
||||
├── addresses
|
||||
├── agency.cert
|
||||
├── agency.priv
|
||||
├── bitxhub
|
||||
├── ca.cert
|
||||
├── ca.priv
|
||||
├── node1
|
||||
├── node2
|
||||
├── node3
|
||||
├── node4
|
||||
├── pids
|
||||
└── raft.so
|
||||
```
|
||||
|
||||
node1-node4下的文件信息如下:
|
||||
|
||||
```shell
|
||||
.
|
||||
├── api
|
||||
├── bitxhub.toml
|
||||
├── certs
|
||||
├── network.toml
|
||||
├── order.toml
|
||||
├── plugins
|
||||
└── start.sh
|
||||
```
|
||||
|
||||
### 修改端口信息
|
||||
|
||||
端口的配置主要在`bitxhub.toml`文件中。
|
||||
|
||||
`port.grpc` 修改节点的grpc端口
|
||||
|
||||
`port.gateway` 修改节点的grpc gateway端口
|
||||
|
||||
`port.pprof` 修改节点的pprof端口
|
||||
|
||||
### 修改初始化账号
|
||||
|
||||
`addresses`文件中记录了各节点的地址,将里面的地址填写到`genesis.addresses`中即可。
|
||||
|
||||
### 修改网络信息
|
||||
网络配置修改在`network.toml`中。
|
||||
|
||||
`N`字段修改为节点数量,默认是4个。
|
||||
|
||||
`id`字段代表了节点的顺序id,范围在1-N,节点间不能重复。
|
||||
|
||||
`addr`和`id`分别是各节点的地址和id,其中`/ip4/`后填写节点所在服务器的ip地址,`/tcp/`后填写节点的libp2p端口(端口不重复即可),`/p2p/`后填写Libp2p的id,具体的值从pids文件中按照顺序获取。
|
||||
|
||||
### 启动
|
||||
|
||||
启动前,需要将build目录下的bitxhub二进制拷贝到node1-node4目录下,将build目录下的raft.so插件拷贝到node1-node4下的plugins下。
|
||||
|
||||
分别进入到node1-node4目录下,执行以下命令即可启动:
|
||||
|
||||
```shell
|
||||
bash start.sh
|
||||
```
|
Loading…
Reference in New Issue