XuperChain文档

简介

XuperChain是 XuperChain 体系下的第一个开源项目,是构建超级联盟网络的底层方案。

其主要特点是高性能,通过原创的XuperModel模型,真正实现了智能合约的并行执行和验证,通过自研的WASM虚拟机,做到了指令集级别的极致优化。

在架构方面,其可插拔、插件化的设计使得用户可以方便选择适合自己业务场景的解决方案,通过独有的XuperBridge技术,可插拔多语言虚拟机,从而支持丰富的合约开发语言。

在网络能力方面,XuperChain具备全球化部署能力,节点通信基于加密的P2P网络,支持广域网超大规模节点,且底层账本支持分叉管理,自动收敛一致性,TDPOS算法确保了大规模节点下的快速共识。在账号安全方面,XuperChain内置了多私钥保护的账号体系,支持权重累计、集合运算等灵活的策略。

XuperChain架构

XuperChain架构

模块

模块

特性

存储

XuperChain的底层存储基于KV数据库,存储的数据包括区块数据、交易数据、账号 余额、DPOS投票数据、合约账号数据、智能合约数据等,上链的数据全部持久化到底 层存储。不同的链,存储独立。底层存储支持可插拔,从而可以满足不同的业务场景

网络

负责交易数据的网络传播和广播、节点发现和维护。以P2P通信为基础,实现全分布式 结构化拓扑网络结构,数据传输全程加密。局域网穿透技术采用NAT方案,同一条流保 持长连接且复用。多条链复用同一个p2p网络

共识

共识模块用于解决交易上链顺序问题,过滤无效交易并达成全网一致。XuperChain实 现了更加高效的DPOS共识算法。支持可插拔,从而可以支持不同的业务场景

密码学

用于构造和验证区块、交易的完整性,采用非对称加密算法生成公私钥、地址。匿名性 较好。支持可插拔,从而可以支持不同的业务场景

智能合约

自研并实现了一套智能合约虚拟机XVM,支持丰富的开发语言,智能合约之间并发执行, 支持执行消耗资源,避免恶意攻击

提案

一种解决系统升级问题的机制。比如修改区块大小,升级共识算法。提案整个过程涉及 到发起提案、参与投票、投票生效三个阶段

账号与权限

为了满足合约调用的权限控制,保证XuperChain网络的健康运转,自研并实现了一套 基于账号的去中心化的合约权限系统。支持权重累计、集合运算等灵活的策略,可以满足 不同的业务场景

核心数据结构

背景

众所周知,程序=数据结构+算法,了解一个程序的数据结构有助于掌握一个程序的关键设计。本文从背景、功能以及各个字段的用意来剖析XuperChain底层核心数据结构,从而方便XuperChain开发者以及用户更深入地了解XuperChain底层框架的核心数据结构的设计缘由,有助于提高XuperChain开发者更高效的开发,有助于XuperChain用户更好的使用XuperChain来服务自己的业务。

核心数据结构

涉及到的核心数据结构包括:区块、交易、UTXO、读写集。

区块

  • 背景:所谓区块链,简单来说就是不同的区块以DAG方式链接起来形成的链。因此,区块是区块链的基本单元。

  • 功能:区块是区块链的基本单元,通常为了提高区块链网络的吞吐,矿工会在一个区块中打包若干个交易。一个区块通常由区块头以及区块体组成。

_images/block-img.png
  • 代码:区块的Proto如下

 1message InternalBlock {
 2    // block version
 3    // 区块版本
 4    int32 version = 1;
 5    // Random number used to avoid replay attacks
 6    // 随机数,用来避免重放攻击
 7    int32 nonce = 2;
 8    // blockid generate the hash sign of the block used by sha256
 9    // 区块的唯一标识
10    bytes blockid = 3;
11    // pre_hash is the parent blockid of the block
12    // 区块的前置依赖区块ID
13    bytes pre_hash = 4;
14    // The miner id
15    // 矿工ID
16    bytes proposer = 5;
17    // 矿工对区块的签名
18    // The sign which miner signed: blockid + nonce + timestamp
19    bytes sign = 6;
20    // The pk of the miner
21    // 矿工公钥
22    bytes pubkey = 7;
23    // The Merkle Tree root
24    // 默克尔树树根
25    bytes merkle_root = 8;
26    // The height of the blockchain
27    // 区块所在高度
28    int64 height = 9;
29    // Timestamp of the block
30    // 打包区块的时间戳
31    int64 timestamp = 10;
32    // Transactions of the block, only txid stored on kv, the detail information
33    // stored in another table
34    // 交易内容
35    repeated Transaction transactions = 11;
36    // The transaction count of the block
37    // 区块中包含的交易数量
38    int32 tx_count = 12;
39    // 所有交易hash的merkle tree
40    repeated bytes merkle_tree = 13;
41    // 采用DPOS共识算法时,当前是第几轮
42    int64 curTerm = 16;
43    int64 curBlockNum = 17;
44    // 区块中执行失败的交易以及对应的失败原因
45    map<string, string> failed_txs = 18; // txid -> failed reason
46    // 采用POW共识算法时,对应的挖矿难度值
47    int32 targetBits = 19;
48    // 下面的属性会动态变化
49    // If the block is on the trunk
50    // 该区块是否在主干上
51    bool in_trunk = 14;
52    // Next next block which on trunk
53    // 当前区块的后继区块ID
54    bytes next_hash = 15;
55}

交易

  • 背景:区块链网络中的每个节点都是一个状态机,为了给每个节点传递状态,系统引入了交易,作为区块链网络状态更改的最小操作单元。

  • 功能:通常表现为普通转账以及智能合约调用。

  • 代码:交易的Proto如下

 1message Transaction {
 2    // txid is the id of this transaction
 3    // 交易的唯一标识
 4    bytes txid = 1;
 5    // the blockid the transaction belong to
 6    // 交易被打包在哪个区块中
 7    bytes blockid = 2;
 8    // Transaction input list
 9    // UTXO来源
10    repeated TxInput tx_inputs = 3;
11    // Transaction output list
12    // UTXO去处
13    repeated TxOutput tx_outputs = 4;
14    // Transaction description or system contract
15    // 交易内容描述或系统合约
16    bytes desc = 6;
17    // Mining rewards
18    // 矿工奖励
19    bool coinbase = 7;
20    // Random number used to avoid replay attacks
21    // 随机数
22    string nonce = 8;
23    // Timestamp to launch the transaction
24    // 发起交易的时间戳
25    int64 timestamp = 9;
26    // tx format version; tx格式版本号
27    int32 version = 10;
28    // auto generated tx
29    // 该交易是否属于系统自动生成的交易
30    bool autogen = 11;
31    // 读写集中的读集
32    repeated TxInputExt tx_inputs_ext = 23;
33    // 读写集中的写集
34    repeated TxOutputExt tx_outputs_ext = 24;
35    // 该交易包含的合约调用请求
36    repeated InvokeRequest contract_requests = 25;
37    // 权限系统新增字段
38    // 交易发起者, 可以是一个Address或者一个Account
39    string initiator = 26;
40    // 交易发起需要被收集签名的AddressURL集合信息,包括用于utxo转账和用于合约调用
41    repeated string auth_require = 27;
42    // 交易发起者对交易元数据签名,签名的内容包括auth_require字段
43    repeated SignatureInfo initiator_signs = 28;
44    // 收集到的签名
45    repeated SignatureInfo auth_require_signs = 29;
46    // 节点收到tx的时间戳,不参与签名
47    int64 received_timestamp = 30;
48    // 统一签名(支持多重签名/环签名等,与initiator_signs/auth_require_signs不同时使用)
49    XuperSignature xuper_sign = 31;
50}

UTXO

  • 背景:区块链中比较常见的两种操作,包括普通转账以及合约调用,这两种操作都涉及到了数据状态的引用以及更新。为了描述普通转账涉及到的数据状态的引用以及更新,引入了UTXO(Unspent Transaction Output)。

  • 功能:一种记账方式,用来描述普通转账时涉及到的数据状态的引用以及更新。通常由转账来源数据(UtxoInput)以及转账去处数据(UtxoOutput)组成。

_images/tx-img.png
  • 代码:UTXO的Proto如下

 1message Utxo {
 2    // 转账数量
 3    bytes amount = 1;
 4    // 转给谁
 5    bytes toAddr = 2;
 6    // 转给谁的公钥
 7    bytes toPubkey = 3;
 8    // 该Utxo属于哪一个交易
 9    bytes refTxid = 4;
10    // 该Utxo数据哪一个交易的哪一个offset
11    int32 refOffset = 5;
12}
13// UtxoInput query info to query utxos
14// UTXO的转账来源
15message UtxoInput {
16    Header header = 1;
17    // which bcname to select
18    // UTXO来源属于哪一条链
19    string bcname = 2;
20    // address to select
21    // UTXO来源属于哪个address
22    string address = 3;
23    // publickey of the address
24    // UTXO来源对应的公钥
25    string publickey = 4;
26    // totalNeed refer the total need utxos to select
27    // 需要的UTXO总额
28    string totalNeed = 5;
29    // userSign of input
30    // UTXO来源的签名
31    bytes userSign = 7;
32    // need lock
33    // 该UTXO是否需要锁定(内存级别锁定)
34    bool needLock = 8;
35}
36// UtxoOutput query results
37// UTXO的转账去处
38message UtxoOutput {
39    Header header = 1;
40    // utxo list
41    // UTXO去处
42    repeated Utxo utxoList = 2;
43    // total selected amount
44    // UTXO去处总额
45    string totalSelected = 3;
46}

读写集

  • 背景:区块链中比较常见的两种操作,包括普通转账以及合约调用,这两种操作都涉及到了数据状态的引用以及更新。为了描述合约调用涉及到的数据状态的引用以及更新,引入了读写集。

  • 功能:一种用来描述合约调用时涉及到的数据状态的引用以及更新的技术。通常由读集(TxInputExt)以及写集(TxOutputExt)组成。

_images/xupermodel.png
  • 代码:读写集的Proto如下

 1// 扩展输入
 2message TxInputExt {
 3    // 读集属于哪一个bucket
 4    string bucket = 1;
 5    // 读集对应的key
 6    bytes key = 2;
 7    // 读集属于哪一个txid
 8    bytes ref_txid = 3;
 9    // 读集属于哪一个txid的哪一个offset
10    int32 ref_offset = 4;
11}
12// 扩展输出
13message TxOutputExt {
14    // 写集属于哪一个bucket
15    string bucket = 1;
16    // 写集对应的key
17    bytes key = 2;
18    // 写集对应的value
19    bytes value = 3;
20}

智能合约

自研并实现了一套智能合约虚拟机XVM。特点如下:

  1. 合约状态数据与合约代码运行环境分离,从而能够支持多语言虚拟机且各种合约虚拟机只需要做纯粹的无状态合约代码执行;

  2. 支持执行消耗资源,避免恶意攻击;

  3. 支持丰富的智能合约开发语言,比如go,Solitidy,C/C++,Java等;

  4. 利用读写集确保普通合约调用支持并发执行,充分利用计算机多核特性;

权限系统

实现一个去中心化,区块链内置的合约账号权限系统。特点如下:

  1. 支持多种权限模型,比如背书数、背书率、AK集合、CA鉴权、社区治理等;

  2. 支持完善的账号权限管理,比如账号的创建、添加和删除AK、设置AK权重、权限模型;

  3. 支持设置合约调用权限,添加和删除AK、设置AK权重、权限模型;

隐私和保密

XuperChain支持多种隐私保护和保密机制,包括但不限于:

  1. 数据在p2p网络中采用ECDH加密传输,保障区块链数据的安全性;

  2. 通过助记词技术,在用户私钥丢失的情况下可以恢复;

  3. 多私钥保护的账号体系;

  4. 基于椭圆曲线算法的公私钥加密和签名体系;

性能

交易处理速度:达到9万TPS

  1. 默认采用DPOS作为共识算法;

  2. 交易处理充分利用计算机多核,支持并发执行;

  3. 智能合约通过读写集技术能够支持并发执行;

可信账本

XuperChain 可信账本基于 XuperChain 和百度Mesatee技术,实现了合约数据加密存储及链上密文运算等功能。具备性能高、编程复杂度低等特点。

  1. 密文存储:可信账本提供数据加解密功能,用户可将数据加密存储在合约中。

  2. 权限管理:支持数据拥有者对数据所属权和使用权的细粒度管理。

  3. 基本运算:支持密文加法,减法以及乘法,比较运算等,统一称为 隐私计算 算子。同时可以使用mesatee-core-standalone快速扩展算子,这些基本运算几乎可以满足用户对任何复杂计算功能的需求。

总结

XuperChain是百度自研的一套区块链解决方案,采用经典的UTXO记账模式,并且支持丰富的智能合约开发语言,交易处理支持并发执行,拥有完善的账号与权限体系,采用DPOS作为共识算法,交易处理速度可达到9万TPS。

本章节将指导您获取XuperChain的代码并部署一个基础的可用环境,还会展示一些基本操作

XuperChain环境部署

准备环境

XuperChain主要由Golang开发,需要首先准备编译运行的环境

  • 安装go语言编译环境,版本为1.11或更高
  • 安装git
    • 下载地址:git

编译XuperChain

1cd src/github.com/xuperchain/xuperchain
2make
  • 在output目录得到产出xchain和xchain-cli

注解

可能需要配置go语言环境变量($GOROOT, $PATH)

GOPATH问题报错(go1.11版本之后无需关注)
  • 在1.11版本之前需要配置。配置成以下形式:

  • export GOPATH=xxx/github.com/xuperchain/xuperchain

GCC版本需要升级到4或5以上

XuperChain基本操作

在output下,主要目录有data, logs, conf, plugins等, 二进制文件有xchain,xchain-cli

各目录的功能如下表:

目录名

功能

output/

节点根目录

├─ conf

xchain.yaml: xchain服务的配置信息(注意端口冲突) plugins.conf: 插件的配置信息

├─ data

数据的存放目录,创世块信息,以及共识和合约的样例

│ ··· ├─ blockchain

账本目录

│ ··· ├─ keys

此节点的地址,具有全局唯一性

│ ··· ├─ netkeys

此节点的网络标识ID,具有全局唯一性

│ ··· └─ config

包括创始的共识,初始的资源数,矿工奖励机制等

├─ logs

程序日志目录

├─ plugins

so扩展的存放目录

├─ xchain

xchain服务的二进制文件

├─ xchain-cli

xchain客户端工具

└─ wasm2c

wasm工具(智能合约会用到)

部署xchain服务

创建链

在启动xchain服务之前,我们首先需要创建一条链(创世区块),xchain客户端工具提供了此功能

1# 创建xuper链
2./xchain-cli createChain
创建链

创建链

这样我们就使用 config/xuper.json 中的配置创建了一条链(此时 data/blockchain 中会生成 xuper 目录,里面即是我们创建的链的账本等文件)

启动服务

启动服务命令十分简单,还可以配合多种参数使用,详见命令行的 -h 输出

1# 启动服务节点
2nohup ./xchain &

确认服务状态

按照默认配置,xchain服务会监听37101端口,可以使用如下命令查看xchain服务的运行状态

1# check服务运行状况
2./xchain-cli status -H 127.0.0.1:37101
查看状态

查看运行状态

基本功能的使用

创建新账号

xchain中,账号分为普通账号和“合约账号”,这里先介绍普通账号的创建,命令如下

1# 创建普通用户, 包含地址,公钥,私钥
2./xchain-cli account newkeys --output data/bob
3# 在bob目录下会看到文件address,publickey,privatekey生成
创建账号

创建账号

查询资源余额

对于普通账号,可使用如下命令查询账号资源余额,其中 -H 参数为xchain服务的地址

1./xchain-cli account balance --keys data/keys -H 127.0.0.1:37101
查询余额

查询余额

转账

转账操作需要提供源账号的私钥目录,也就类似“1.2.4.1”中生成的目录,这里注意到并不需要提供目标账号的任何密钥,只需要提供地址即可

1# --keys 从此地址 转给 --to地址 --amount 钱
2./xchain-cli transfer --to czojZcZ6cHSiDVJ4jFoZMB1PjKnfUiuFQ --amount 10 --keys data/keys/ -H 127.0.0.1:37101

命令执行的返回是转账操作的交易id(txid)

普通转账

普通转账操作

查询交易信息

通过以下命令可以查询交易的信息,包括交易状态、交易的源和目标账号、交易的金额、所在的区块(如果已上链)等内容

1# 可查询上一步生成的txid的交易信息
2./xchain-cli tx query cbbda2606837c950160e99480049e2aec3e60689a280b68a2d253fdd8a6ce931 -H 127.0.0.1:37101
查询交易

查询交易

查询block信息

通过blockid可以查询区块的相关信息,包括区块内打包的交易、所在链的高度、前驱/后继区块的id等内容

1# 可查询上一步交易所在的block id信息
2./xchain-cli block 0354240c8335e10d8b48d76c0584e29ab604cfdb7b421d973f01a2a49bb67fee -H 127.0.0.1:37101
查询区块

查询区块

发起多重签名交易

对于需要多个账号签名才可以生效的交易,需要先发起多重签名交易,收集需要的签名,然后再发出

对需要收集签名的账号地址,需要事先维护在一个文件中(假定名为addr_list),每个地址一行

1YDYBchKWXpG7HSkHy4YoyzTJnd3hTFBgG
2ZAmWoJViiNn5pKz32m2MVgmPnSpgLia7z

假设要发起一笔转账操作

1# 从账号发起
2./xchain-cli multisig gen --to czojZcZ6cHSiDVJ4jFoZMB1PjKnfUiuFQ --amount 100 -A addr_list
3# 从合约账号发起
4./xchain-cli multisig gen --to czojZcZ6cHSiDVJ4jFoZMB1PjKnfUiuFQ --amount 100 -A addr_list --from XC11111111111111@xuper

这样会生成一个tx.out文件,包含了需发起的交易内容

1# 各方在签名之前可以check 原始交易是否ok,查看visual.out
2./xchain-cli multisig check --input tx.out --output visual.out

然后收集需要的签名

1# 首先需要发起者自己的签名
2./xchain-cli multisig sign --tx tx.out --output my.sign
3# 假设addr_list中的地址对应的私钥存放在alice、bob中
4./xchain-cli multisig sign --keys data/account/alice --tx tx.out --output alice.sign
5./xchain-cli multisig sign --keys data/account/bob --tx tx.out --output bob.sign

最后将交易和收集好的签名发出

1# send后第一个参数是发起者的签名文件,第二个参数是需要收集的签名文件,均为逗号分割
2./xchain-cli multisig send --tx tx.out my.sign alice.sign,bob.sign

XuperModel

XuperChain能够支持合约链内并行的很大的原因是由于其底层自研的XuperModel数据模型。

XuperModel是一个带版本的存储模型,支持读写集生成。该模型是比特币utxo模型的一个演变。在比特币的utxo模型中,每个交易都需要在输入字段中引用早期交易的输出,以证明资金来源。同样,在XuperModel中,每个事务读取的数据需要引用上一个事务写入的数据。在XuperModel中,事务的输入表示在执行智能合约期间读取的数据源,即事务的输出来源。事务的输出表示事务写入状态数据库的数据,这些数据在未来事务执行智能合约时将被引用,如下图所示:

XuperModel事务

XuperModel事务

为了在运行时获取合约的读写集,在预执行每个合约时XuperModel为其提供智能缓存。该缓存对状态数据库是只读的,它可以为合约的预执行生成读写集和结果。验证合约时,验证节点根据事务内容初始化缓存实例。节点将再次执行一次合约,但此时合约只能从读集读取数据。同样,写入数据也会在写入集中生效。当验证完生成的写集和事务携带的写集一致时合约验证通过,将事务写入账本,cache的原理如下所示,图中左边部分是合约预执行时的示意图,右边部分是合约验证时的示意图:

XuperModel合约验证

XuperModel合约验证

XuperBridge

内核调用设计

XuperBridge为所有合约提供统一的合约接口,从抽象方式上类似于linux内核对应于应用程序,内核代码是一份,应用程序可以用各种语言实现,比如go,c。类比到合约上就是各种合约的功能,如KV访问,QueryBlock, QueryTx等,这些请求都会通过跟xchain通信的方式来执行,这样在其上实现的各种合约虚拟机只需要做纯粹的无状态合约代码执行。

XuperBridge

XuperBridge

合约与xchain进程的双向通信

xchain进程需要调用合约虚拟机来执行具体的合约代码,合约虚拟机也需要跟xchain进程通信来进行具体的系统调用,如KV获取等,这是一个双向通信的过程。

合约双向通信

合约双向通信

这种双向通信在不同虚拟机里面有不同的实现,

  • 在native合约里面由于合约是跑在docker容器里面的独立进程,因此牵扯到跨进程通信,这里选用了unix socket作为跨进程通信的传输层,xchain在启动合约进程的时候把syscall的socket地址以及合约进程的socket地址传递给合约进程,合约进程一方面监听在unix socket上等待xchain调用自己运行合约代码,另一方面通过xchain的unix socket创建一个指向xchain syscall服务的grpc客户端来进行系统调用。

合约socket

合约socket

  • 在WASM虚拟机里面情况有所不同,WASM虚拟机是以library的方式链接到xchain二进制里面,所以虚拟机和xchain在一个进程空间,通信是在xchain和WASM虚拟机之间进行的,这里牵扯到xchain的数据跟虚拟机里面数据的交换,在实现上是通过WASM自己的模块机制实现的,xchain实现了一个虚拟的WASM模块,合约代码执行到外部模块调用的时候就转到对应的xchain函数调用,由于xchain和合约代码的地址空间不一样,还是牵扯到序列化和反序列化的动作。

WASM合约

WASM合约

PB接口

合约暴露的代码接口

1service NativeCode {
2    rpc Call(CallRequest) returns (CallResponse);
3}

xchain暴露的syscall接口

 1service Syscall {
 2    // KV service
 3    rpc PutObject(PutRequest) returns (PutResponse);
 4    rpc GetObject(GetRequest) returns (GetResponse);
 5    rpc DeleteObject(DeleteRequest) returns (DeleteResponse);
 6    rpc NewIterator(IteratorRequest) returns (IteratorResponse);
 7
 8    // Chain service
 9    rpc QueryTx(QueryTxRequest) returns (QueryTxResponse);
10    rpc QueryBlock(QueryBlockRequest) returns (QueryBlockResponse);
11    rpc Transfer(TransferRequest) returns (TransferResponse);
12}

KV接口与读写集

合约每次执行的产出为一系列KV操作的读写集,读写集的概念详细见 XuperModel

KV的接口:

  • GetObject(key)

  • PutObject(key, value)

  • DeleteObject(key)

  • NewIterator(start, limit)

各个接口对读写集的影响:

  • Get会生成一个读请求

  • Put会产生一个读加一个写

  • Delete会产生一个读加一个特殊的写(TODO)

  • Iterator会对迭代的key产生读

效果:

  • 读请求不会读到最新的其他tx带来的变更

  • 读请求会读到最新的自己的写请求(包括删除)的变更

  • 写请求在提交前不会被其他合约读到

  • 新写入的会被迭代器读到

合约上下文

每次合约运行都会有一个伴随合约执行的上下文(context)对象,context里面保存了合约的kv cache对象,运行参数,输出结果等,context用于隔离多个合约的执行,也便于合约的并发执行。

Context的创建和销毁

context在合约虚拟机每次执行合约的时候创建。 每个context都有一个context id,这个id由合约虚拟机维护,在xchain启动的时候置0,每次创建一个context对象加1,合约虚拟机保存了context id到context对象的映射。 context id会传递给合约虚拟机,在Docker里面即是合约进程,在之后的合约发起KV调用过程中需要带上这个context id来标识本次合约调用以找到对应的context对象。

context的销毁时机比较重要,因为我们还需要从context对象里面获取合约执行过程中的Response以及读写集,因此有两种解决方案,一种是由调用合约的地方管理,这个是xuper3里面做的,一种是统一销毁,这个是目前的做法,在打包成块结束调用Finalize的时候统一销毁所有在这个块里面的合约context对象。

合约上下文的操作

  • NewContext,创建一个context,需要合约的参数等信息。

  • Invoke,运行一个context,这一步是执行合约的过程,合约执行的结果会存储在context里面。

  • Release,销毁context,context持有的所有资源得到释放。

XVM虚拟机

背景

XVM为合约提供一个稳定的沙盒运行环境,有如下目标:

  • 隔离性,合约运行环境和xchain运行环境互不影响,合约的崩溃不影响xchain。

  • 确定性,合约可以访问链上资源,但不能访问宿主机资源,保证在确定的输入下有确定的输出

  • 可停止性,设置资源quota,合约对资源的使用超quota自动停止

  • 可以统计合约的资源使用情况,如CPU,内存等

  • 运行速度尽量快。

WASM简介

WASM是WebAssembly的缩写,是一种运行在浏览器上的字节码,用于解决js在浏览器上的性能不足的问题。 WASM的指令跟机器码很相似,因此很多高级语言如C,C++,Go,rust等都可以编译成WASM字节码从而可以运行在浏览器上。 很多性能相关的模块可以通过用C/C++来编写,再编译成WASM来提高性能,如视频解码器,运行在网页的游戏引擎,React的虚拟Dom渲染算法等。

WASM本身只是一个指令集,并没有限定运行环境,因此只要实现相应的解释器,WASM也可以运行在非浏览器环境。 xchain的WASM合约正是这样的应用场景,通过用C++,go等高级语言来编写智能合约,再编译成WASM字节码,最后由XVM虚拟机来运行。 XVM虚拟机在这里就提供了一个WASM的运行环境。

WASM字节码编译加载流程

WASM字节码的运行有两种方式,一种是解释执行,一种是编译成本地指令后再运行。 前者针对每条指令挨个解释执行,后者通过把WASM指令映射到本地指令如(x86)来执行,解释执行优点是启动快,缺点是运行慢,编译执行由于有一个预先编译的过程因此启动速度比较慢,但运行速度很快。

XVM选用的是编译执行模式。

XVM编译加载流程

XVM编译加载流程

字节码编译

用户通过c++编写智能合约,通过emcc编译器生成wasm字节码,xvm加载字节码,生成加入了指令资源统计的代码以及一些运行时库符号查找的机制,最后编译成本地指令来运行。

c++合约代码

1int add(int a, int b) {
2        return a + b;
3}

编译后的WASM文本表示

1(module
2(func $add (param i32 i32) (result i32)
3    local.get 0
4    local.get 1
5    i32.add)
6(export "_add" (func $add)))

XVM编译WASM到c,最后再生成动态链接库。

 1static u32 _add(wasm_rt_handle_t* h, u32 p0, u32 p1) {
 2    FUNC_PROLOGUE;
 3    u32 i0, i1;
 4    ADD_AND_CHECK_GAS(3);
 5    i0 = p0;
 6    i1 = p1;
 7    i0 += i1;
 8    FUNC_EPILOGUE;
 9    return i0;
10}
11/* export: '_add' */
12u32 (*export__add)(wasm_rt_handle_t*, u32, u32);
13
14static void init_exports(wasm_rt_handle_t* h) {
15    /* export: '_add' */
16    export__add = (&_add);
17}

加载运行

在了解如何加载运行之前先看下如何使用xvm来发起对合约的调用,首先生成Code对象,Code对象管理静态的指令代码以及合约所需要的符号解析器Resolver。 之后就可以通过实例化Context对象来发起一次合约调用,GasLimit等参数就是在这里传入的。Code和Context的关系类似Docker里面的镜像和容器的关系, 一个是静态的,一个是动态的。

 1func run(modulePath string, method string, args []string) error {
 2    code, err := exec.NewCode(modulePath, emscripten.NewResolver())
 3    if err != nil {
 4            return err
 5    }
 6    defer code.Release()
 7
 8    ctx, err := exec.NewContext(code, exec.DefaultContextConfig())
 9    if err != nil {
10            return err
11    }
12    ret, err := ctx.Exec(method, []int64{int64(argc), int64(argv)})
13    fmt.Println(ret)
14    return err
15}

转换后的c代码最终会编译成一个动态链接库来给XVM运行时来使用,在每个生成的动态链接库里面都有如下初始化函数。 这个初始化函数会自动对wasm里面的各个模块进行初始化,包括全局变量、内存、table、外部符号解析等。

 1typedef struct {
 2    void* user_ctx;
 3    wasm_rt_gas_t gas;
 4    u32 g0;
 5    uint32_t call_stack_depth;
 6}wasm_rt_handle_t;
 7
 8
 9void* new_handle(void* user_ctx) {
10    wasm_rt_handle_t* h = (*g_rt_ops.wasm_rt_malloc)(user_ctx, sizeof(wasm_rt_handle_t));
11    (h->user_ctx) = user_ctx;
12    init_globals(h);
13    init_memory(h);
14    init_table(h);
15    return h;
16}

语言运行环境

c++运行环境

c++因为没有runtime,因此运行环境相对比较简单,只需要设置基础的堆栈分布以及一些系统函数还有emscripten的运行时函数即可。

c++合约的内存分布

c++合约的内存分布

c++合约的内存分布

普通调用如何在xvm解释

xvm符号解析

xvm符号解析

go运行环境

go合约运行时结构

go合约运行时结构

XuperBridge对接

XVM跟XuperBridge对接主要靠两个函数

  • call_method,这个函数向Bridge传递需要调用的方法和参数

  • fetch_response,这个函数向Bridge获取上次调用的结果

 1extern "C" uint32_t call_method(const char* method, uint32_t method_len,
 2                            const char* request, uint32_t request_len);
 3extern "C" uint32_t fetch_response(char* response, uint32_t response_len);
 4
 5static bool syscall_raw(const std::string& method, const std::string& request,
 6                        std::string* response) {
 7    uint32_t response_len;
 8    response_len = call_method(method.data(), uint32_t(method.size()),
 9                            request.data(), uint32_t(request.size()));
10    if (response_len <= 0) {
11        return true;
12    }
13    response->resize(response_len + 1, 0);
14    uint32_t success;
15    success = fetch_response(&(*response)[0u], response_len);
16    return success == 1;
17}

资源消耗统计

考虑到大部分指令都是顺序执行的,因此不需要在每个指令后面加上gas统计指令, 只需要在control block最开头加上gas统计指令,所谓control block指的是loop, if等会引起跳转的指令。

c++代码

 1extern int get(void);
 2extern void print(int);
 3
 4int main() {
 5        int i = get();
 6        int n = get();
 7        if (i < n) {
 8                i += 1;
 9                print(i);
10        }
11        print(n);
12}

编译后生成的wast代码

 1(func (;2;) (type 1) (result i32)
 2    (local i32 i32)
 3    call 1
 4    local.tee 0
 5    call 1
 6    local.tee 1
 7    i32.lt_s
 8    if  ;; label = @1
 9        local.get 0
10        i32.const 1
11        i32.add
12        call 0
13    end
14    local.get 1
15    call 0
16    i32.const 0)

生成的带统计指令的c代码

 1static u32 wasm__main(wasm_rt_handle_t* h) {
 2    u32 l0 = 0, l1 = 0;
 3    FUNC_PROLOGUE;
 4    u32 i0, i1;
 5    ADD_AND_CHECK_GAS(11);
 6    i0 = wasm_env__get(h);
 7    l0 = i0;
 8    i1 = wasm_env__get(h);
 9    l1 = i1;
10    i0 = (u32)((s32)i0 < (s32)i1);
11    if (i0) {
12        ADD_AND_CHECK_GAS(6);
13        i0 = l0;
14        i1 = 1u;
15        i0 += i1;
16        wasm_env__print(h, i0);
17    }
18    ADD_AND_CHECK_GAS(5);
19    i0 = l1;
20    wasm_env__print(h, i0);
21    i0 = 0u;
22    FUNC_EPILOGUE;
23    return i0;
24}

账号权限控制模型

背景

XuperChain 需要一套去中心化的,内置的权限系统 为了实现这个目标,我们借鉴了业界很多现有系统如Ethereum、EOS、Fabric 的优点,设计一个基于账号的合约权限系统

名词解释

  • AK(Access Key):具体的一个address,由密码学算法生成一组公私钥对,然后将公钥用指定编码方式压缩为一个地址。

  • 账号(Account): 在 XuperChain 上部署合约需要有账号, 账号可以绑定一组AK(公钥),并且AK可以有不同的权重。 账号的名字具有唯一性。

  • 合约 (Contract): 一段部署在区块链上的可执行字节码,合约的运行会更新区块链的状态。我们允许一个账号部署多个合约。合约的名字具有唯一性。

模型简介

系统会首先识别用户,然后根据被操作对象的ACL的信息来决定用户能否对其进行哪些操作

ACL简介

ACL简介

  • 个人账号AK:是指一个具体的地址Addr
    • 用户的创建是离线的行为,可以通过命令行工具或者API进行创建

  • 合约账号: XuperChain 智能合约的管理单元。
    • 账号的创建:
      • 任何账号或者AK都可以调用系统级智能合约创建账号

      • 创建账号需要指定账号对应的拥有者的地址集,如果一个账号中只有一个地址, 那么这个Addr对账号完全控制;

      • 创建账号需要指定ACL控制策略,用于账号其他管理动作的权限控制;

      • 创建账号需要消耗账号资源;

    • 账号名命名规则;
      • 合约账号由三部分组成,分为前缀,中间部分,后缀。

      • 前缀为XC,后缀为@链名

      • 中间部分为16个数字组成。

      • 在创建合约账号的时候,只需要传入16位数字,在使用合约账号的时候,使用完整的账号。

    • 账号管理:依地址集合据创建时指定的地址集和权限策略,管理账号其他操作
      • 账号股东剔除和加入

      • 账号资产转账

      • 创建合约,创建智能合约需要消耗账号资源,先将utxo资源打到账号下,通过消耗账号的utxo资源创建合约,验证的逻辑需要走账号的ACL控制

      • 合约Method权限模型管理

    • 智能合约: XuperChain 中的一个具体的合约,属于某个账号
      • 账号所属人员允许在账号内部署合约

      • 账号所属人员可以定义合约管理的权限模型

      • 设置合约方法的权限模型,合约内有一个权限表,记录:{ contract.method,permission_model}

    • 合约命名规则:长度为4~16个字符(包括4和16),首字母可选项为[a-ZA-Z_],末尾字符可选项为[a-zA-Z0-9_],中间部分的字符可选项为[a-zA-Z_.]

实现功能

主要有两个功能:账号权限管理、合约权限管理

  1. 账号权限管理 账号的创建、添加和删除AK、设置AK权重、权限模型

  2. 合约权限管理 设置合约调用权限,支持2种权限模型:
    1. 背书阈值:在名单中的AK或Account签名且他们的权重值加起来超过一定阈值,就可以调用合约

    2. AK集合: 定义多组AK集合,集合内的AK需要全部签名,集合间只要有一个集合有全部签名即可

系统设计

ACL架构

ACL架构

ACL数据结构说明

 1// --------   Account and Permission Section -------
 2enum PermissionRule {
 3    NULL = 0;             // 无权限控制
 4    SIGN_THRESHOLD = 1;   // 签名阈值策略
 5    SIGN_AKSET = 2;       // AKSet签名策略
 6    SIGN_RATE = 3;        // 签名率策略
 7    SIGN_SUM = 4;         // 签名个数策略
 8    CA_SERVER = 5;        // CA服务器鉴权
 9    COMMUNITY_VOTE = 6;   // 社区治理
10}
11
12message PermissionModel {
13    PermissionRule rule = 1;
14    double acceptValue = 2;    // 取决于用哪种rule, 可以表示签名率,签名数或权重阈值
15}
16
17// AK集的表示方法
18message AkSet {
19    repeated string aks = 1; //一堆公钥
20}
21
22message AkSets {
23    map<string, AkSet> sets = 1;   // 公钥or账号名集
24    string expression = 2;      // 表达式,一期不支持表达式,默认集合内是and,集合间是or
25}
26
27// Acl实际使用的结构
28message Acl {
29    PermissionModel  pm = 1;             // 采用的权限模型
30    map<string, double>  aksWeight = 2;  // 公钥or账号名  -> 权重
31    AkSets akSets = 3;
32}

签名阈值策略: Sum{Weight(AK_i) , if sign_ok(AK_i)}  >=  acceptValue

系统合约接口

合约接口

用途

NewAccountMethod

创建新的账号

SetAccountACLMethod

更新账号的ACL

SetMethodACLMethod

更新合约Method的ACL

样例

acl模型如下:

 1{
 2    "pm": {
 3        "rule": 1,
 4
 5        "acceptValue": 1.0
 6    },
 7    "aksWeight": {
 8        "AK1": 1.0,
 9        "AK2": 1.0
10    }
11}
  • 其中rule=1表示签名阈值策略,rule=2表示AKSet签名策略

  • 签名的ak对应的weight值加起来>acceptValue,则符合要求

XuperChain p2p网络

p2p网络概述

依据p2p网络中节点相互之间如何联系,可以将p2p网络简单区分为无结构和结构化两大类:

  1. 非结构化p2p网络:这种p2p网络是最普通的,没有对结构做特别的设计。优点在于结构简单易于组建,网络局部区域内个体可以任意分布。对于节点的加入和离开网络也表现地非常稳定,比特币网络使用的就是无结构化的网络。但是这种网络主要有3个缺点,一是公网网络拥塞时传输效率低,二是存在泛洪循环,三是消息风暴问题。

  2. 结构化p2p网络:这种p2p网络的结构经过精心设计,目的是为了增加路由效率,提高查询数据的效率,结构化p2p最普遍的实现方案是使用分布式哈希表(DHT),以太坊网络中使用的就是结构化的网络。

互联网的发展速度远远超过人们的预期,人们在制定网络协议之初没有考虑过网络规模会获得如此迅速的增长,导致ip地址的短缺。NAT技术通过将局域网内的主机地址映射为互联网上的有效ip地址,实现了网络地址的复用,从而部分解决了ip地址短缺的问题。网络中大部分用户处于各类NAT设备之后,导致在p2p网络中两个节点之间直接建立udp或者tcp链接难度比较大,应运而生的是NAT穿透技术。目前主要有两种途径,一种称为打洞,即UDP Punch技术;另一种是利用NAT设备的管理接口,称为UPnP技术。

XuperChain p2p网络

XuperChain 的p2p网络是可插拔的,目前支持libp2p模式和基于GRRC模式,libp2p使用KAD进行节点的路由管理,支持NAT穿透,主要用于公开网络的场景,节点规模可以达到万级;基于GRPC模式的p2p网络支持路由的自定义、节点的动态加入退出等功能,主要用于联盟链场景。

通过xchian.yaml中p2p module配置,选择p2p网络的模式。

XuperChain 定义了自己的协议类型 XuperProtocolID = "/xuper/2.0.0" ,所有的 XuperChain 网络节点除了基础的消息类型外还会监听并处理这个协议的网络消息。

XuperChain p2p消息

XuperChain 消息采用Protobuf定义,整个消息包括2部分,分别是消息头 MessageHeader 和消息体 MessageData ,具体如下所示:

_images/p2p-msg.png

其proto消息定义如下:

 1// XuperMessage is the message of Xuper p2p server
 2message XuperMessage {
 3    // MessageHeader is the message header of Xuper p2p server
 4    message MessageHeader {
 5        string version = 1;
 6        // dataCheckSum is the message data checksum, it can be used check where the message have been received
 7        string logid = 2;
 8        string from = 3;
 9        string bcname = 4;
10        MessageType type = 5;
11        uint32 dataCheckSum = 6;
12        ErrorType errorType = 7;
13    }
14    // MessageData is the message data of Xuper p2p server
15    message MessageData {
16        // msgInfo is the message infomation, use protobuf coding style
17        bytes msgInfo = 3;
18    }
19    MessageHeader Header = 1;
20    MessageData Data = 2;
21}

模块交互图

XuperChain p2p网络模块与其他模块的交互如图所示,以libp2p模式为例:

_images/p2p-relation.png

上图左边是Xuper的启动流程,其中InitP2PServer的流程为启动P2P的核心流程,如右半部分所示,右半部分主要包括4个阶段,分别为:

  1. InitP2pInstance:创建libp2p host实例

  2. SetXuperStreamHandler:初始化p2p通信消息protocols,XuperProtocol为Xuper节点之间进行消息通信和消息处理的核心逻辑。

  3. InitKadDht: 初始化libp2p KadDht,通过设置的bootstrap节点,建立自己的kad dht。

  4. InitStreams: 前一步已经建立了自己的kad dht,下一步就是与这些邻近的节点之间建立通信流,通过libp2p的NewStream接口实现通信流建立。

至此,Xuper的p2p连接建立完毕。

交易消息处理流程

用户提交的交易消息在 XuperChain 网络中传输的处理流程如下所示:

_images/p2p-flow.png

用户通过RPC将交易提交到网络中,交易执行成功后会通过p2p模块广播给网络中的其他节点。

身份认证

背景

Xuperchain节点之间存在双重身份:P2P节点ID和Xuperchain address,为了解决节点间的身份互信,防止中间人攻击和消息篡改,节点间需要一种身份认证机制,可以证明对称节点声明的XChain address是真实有效的

名词解释

  • Xuperchain address:当前节点的address,一般为data/keys/address

  • P2P节点ID:当前节点P2P的peer.ID

P2P建立连接过程

连接建立时序

连接建立时序

实现过程

  • 新建的net.Stream连接,已经完成了ECDH密钥协商流程,因此此时节点间已经是加密连接。

  • 连接建立后,增加一步身份认证流程,如果通过,则stream建立成功,加入到streamPool中

其中,身份认证流程如下:

  • 身份认证流程通过开关控制,可开启和关闭 DefaultIsAuthentication: true or false

  • 身份验证支持XChain address的验证方式

  • 如果开启身份验证,则身份验证不通过的Stream直接关闭

  • 身份验证是使用XChain的私钥对PeerID+XChain地址的SHA256哈希值进行签名,并将PeerID、Xuperchain公钥、Xuperchain地址、签名数据一起传递给对方进行验证

主要结构修改点

1// stream 增加authenticate接口
2func (s *Stream) Authenticate() error {}
3
4// 收到身份验证消息后的回调处理函数接口
5
6func (p *P2PServerV2) handleGetAuthentication(ctx context.Context, msg *xuper_p2p.XuperMessage) (*xuper_p2p.XuperMessage, error) {}

提案和投票机制

_images/proposal-1.png

图1:提案和投票机制示意图

提案和投票机制是区块链系统实现自我进化的关键。系统首次上线后难免遇到很多问题,我们提供提案/投票机制为区块链的社区治理提供便利的工具,以保证未来系统的可持续发展。具体实现方法如下:

Step1:提案者(proposer) 通过发起一个事务声明一个可调用的合约,并约定提案的投票截止高度,生效高度; Step2:投票者(voter) 通过发起一个事务来对提案投票,当达到系统约定的投票率并且账本达到合约的生效高度后,合约就会自动被调用; Step3:为了防止机制被滥用,被投票的事务的需要冻结参与者的一笔燃料,直到合约生效后解冻。

共识可升级

_images/proposal-2.png

图2:XuperChain提案机制进行共识升级

XuperChain提供可插拔共识机制,通过提案和投票机制,升级共识算法或者参数。图2简要说明了如何使用XuperChain的提案机制进行共识升级。

系统参数可升级

通过提案和投票机制,区块链自身的运行参数也是可升级的。包括:block大小、交易大小、挖矿奖励金额和衰减速度等。

下面通过一个例子来说明,假设一条链,最开始用的是POW共识,创始块如下:

 1{
 2    "version" : "1",
 3    "predistribution": [
 4        {}
 5    ],
 6    "maxblocksize" : "128",
 7    "award" : "1000000",
 8    "decimals" : "8",
 9    "award_decay": {
10        "height_gap": 31536000,
11        "ratio": 0.5
12    },
13    "genesis_consensus": {
14        "name": "pow",
15        "config": {
16                "defaultTarget": "19",      # 默认难度19个0 bits前缀
17                "adjustHeightGap": "10",    # 每10个区块调整一次难度
18                "expectedPeriod": "15",     # 期望15秒一个区块
19                "maxTarget": "22"
20        }
21    }
22}

然后,我们想将其共识切换到TDPOS共识。

步骤1:由提案者发起提案,提案没有额外的代价,通过命令行的desc选项指向提案用的json即可。 提案json的内容如下:

 1{
 2    "module": "proposal",
 3    "method": "Propose",
 4    "args" : {
 5        "min_vote_percent": 51,     # 当投票者冻结的资产占全链的51%以上时生效提案
 6        "stop_vote_height": 120     # 停止计票的高度是:120
 7    },
 8    "trigger": {
 9        "height": 130,              # 提案生效高度是:130
10        "module": "consensus",
11        "method": "update_consensus",
12        "args" : {
13            "name": "tdpos",
14            "config": {
15                "proposer_num":"3",
16                "period":"3000",
17                "term_gap":"60000",
18                "alternate_interval": "3000",
19                "term_interval": "6000",
20                "block_num":"10",
21                "vote_unit_price":"1",
22                "init_proposer": {
23                    "1": ["dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN", "f3prTg9itaZY6m48wXXikXdcxiByW7zgk", "U9sKwFmgJVfzgWcfAG47dKn1kLQTqeZN3"]
24                }
25            }
26        }
27    }
28}

把上面的json保存在myprop.json, 然后运行:

1./xchain-cli transfer --to `cat data/keys/address` --desc ./myprop.json --amount 1

得到一个txid,此处为 67cc7cd23b7fcbe0a4919d5c581b3fda759da13cdd97414afa7539e221727594

然后,通过

1./xchain-cli tx query 67cc7cd23b7fcbe0a4919d5c581b3fda759da13cdd97414afa7539e221727594

确认该交易已经上链(标志是blockid不为空了)

步骤2:可以对这个提案投票。投票需要冻结自己资产,并且冻结高度必须大于停止计票的高度。

1./xchain-cli vote –amount 100000000 –frozen 121 67cc7cd23b7fcbe0a4919d5c581b3fda759da13cdd97414afa7539e221727594

注解

注意:冻结高度121需要大于提案停止计票高度120, 否则是无效投票。

另外,累计投票金额数量必须大于全链总量的51% (51%是提案json中指定的,但是最小不能少于50%)

1./xchain-cli account balance –Z # 可以查看自己被冻结的资产总量。
2./xchain-cli status --host localhost:37301  | grep -i total # 查询全链的资产总量。

步骤3:最后,等到当前生效高度到达,会发现共识已经切换到TDPOS了。

1./xchain-cli tdpos status

此命令可以查看tdpos状态。

密码学和隐私保护

背景

密码学技术是区块链的核心基础技术之一,承担着区块链不可篡改和去中心化验证等特性的底层支撑。在 XuperChain 中,密码学技术广泛应用在账户体系、交易签名、数据隐私保护等方面,主要以ECC(椭圆曲线密码体系)以及多种Hash散列算法为基础,发展出的一个单独的模块。

密码学基础

哈希函数

加密哈希函数(Hash Function) 是适用于密码学的哈希散列函数,是现代密码学的基本工具。它是一种数学算法,将任意大小的数据(通常称为“消息”)映射到固定大小的二进制串(称之为“散列值”,“散列”或“消息摘要”),并且是单向的功能,即一种实际上不可逆转的功能。理想情况下,查找生成给定哈希的消息的唯一方法是尝试对可能的输入进行暴力搜索,以查看它们是否产生匹配,或使用匹配哈希的彩虹表。

  • MD5 : 摘要长度为128bit,由于容易受到碰撞攻击,目前使用越来越少。

  • SHA256 : SHA系列哈希算法由美国国家安全局制定,具有多个hash算法标准,可以产生160~512bit不等的哈希摘要。目前在区块链中使用较多的是SHA256,摘要长度为256bit,具有较高的抗碰撞攻击安全性。

  • RIPEMD-160 : 产生长度为160bit的摘要串。 相比于美国国家安全局设计的SHA-1和SHA-2算法,RIPEMD-160的设计原理是开放的。

关于一些典型的Hash算法的对比,可以参考 这里

ECC

构建区块链的去中心化交易,需要一种加密算法,使交易发起人使用持有的密钥对交易数据进行数字签名,而交易验证者只需要知道交易发起人的公开信息,即可对交易有效性进行验证,确定该交易确实来自交易发起者。这种场景在密码学中称之为`公开密钥加密`,也称之为非对称密钥加密。

常见的公开密钥算法如RSA、ECC(Elliptic Curve Cryptography,缩写为 ECC)等,RSA起步较早,此前在非对称加密领域使用范围最广,例如目前的SSL证书大多采用RSA算法。而在ECC算法问世后,由于在抗攻击性、资源消耗等方面相比RSA具有更好的表现,其使用也越来越广泛。

公钥密码算法一般都基于一个数学难题,比如RSA的依据是给定两个数p, q很容易相乘得到N, 当N足够大时,对N进行因式分解则相对困难的多。ECC是建立在基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点P,一个整数k,求解Q=kP很容易;给定一个点P、Q,知道Q=kP,求整数k却是一个难题。具体的理论知识可以参考 椭圆曲线密码学

椭圆曲线密码学包含了多种密码学算法,下面列出在 XuperChain 中涉及到的一些算法:

  • ECIES (Elliptic Curve Integrated Encryption Scheme): 椭圆曲线集成加密算法,主要用于基于椭圆曲线的数据加解密。

  • ECDH (Elliptic Curve Diffie–Hellman): 基于Diffie–Hellman算法的一种密钥协商算法,定义了双方如何安全的生成和交换密钥。

  • ECDSA (Elliptic Curve Digital Signature Algorithm): 是使用椭圆曲线密码学实现的DSA(数字签名算法),一般发起人对消息摘要使用私钥签名,验证者可以通过公钥对签名有效性进行验证。

椭圆曲线算法由于采用的椭圆曲线的不同,具有多种不同的算法标准,典型的如:

  • NIST标准,典型的曲线如P-256/P-384/P-521等;

  • SECG标准,典型的如Secp256k1/Secp256r1/ secp192k1/ secp192r1等;

  • ECC25519,主要指Ed25519数字签名和Curve25519密钥协商标准等;

  • 国产密码算法,中国国家密码局制定的密码学算法标准,典型的如SM2/3/4等。

多重签名和环签名

多重签名是指在数字签名中,有时需要多个用户对同一个交易进行签名和认证,例如某些合约账户下的数据需要多个人授权才能修改或转账。

在密码学中,通过多重签名可以将多个用户的授权签名信息压缩在同一个签名中,这样相比于每个用户产生一个签名的数据体量会小很多,因此其验签计算、网络传输的资源开销也会少很多。

环签名是一种数字签名技术,环签名的一个安全属性是无法通过计算还原出一组用户中具体使用私钥签名的用户。也就是说,使用环签名技术可以使一组用户中的某一个人对消息进行签名,而并不会泄露签名者是这组用户中的哪个人。环签名与组签名类似,但在两个关键方面有所不同:第一,单个签名具有匿名性; 第二,任何一批用户都可以作为一个组使用,无需额外设置。

在实际使用中,多重签名主要用作多人实名授权的交易,通过产生更小的签名数据提升网络传输和计算效率,而环签名则主要用于对交易隐私保护和匿名性有要求的交易场景中。

XuperChain 中密码学的使用

密码学作为区块链系统的底层基础技术,在很多方面都会使用到。这里介绍几个 XuperChain 中几个密码学典型的使用场景。

用户公私钥账户

XuperChain 的用户账户体系基于非对称公私钥对,每个用户账户对应这一组公私钥对,并采用一定的哈希算法将公钥摘要成一个字符串作为用户账户地址(address)。

考虑到密钥不具备可读性,为了帮助用户保存密钥, XuperChain 实现了BIP39提案的助记词技术。

  • 助记词的生成过程:首先生成一个长度在128~256bit之间的随机熵,由此在助记词表中选出对应的单词列表,形成助记词。

  • 助记词产生私钥:使用基于口令的密钥派生算法PBKDF2,将上述生成的助记词和用户指定的密钥作为密钥派生算法参数,生成长度为512bit的种子,以此种子作为生成密钥的随机参数,便产生了了从助记词生成的私钥。

  • 通过助记词恢复密钥:由于用户持有生成密钥的助记词和口令,因此在用户私钥遗忘或丢失时,可以通过同样的助记词和口令,执行助记词产生私钥的过程,从而恢复出账户密钥。

数据签名

XuperChain 中,每个交易都需要交易发起人以及交易背书人的签名;在每个块生成时,也需要加上打包块的节点的签名。

  • 交易签名: 基于交易数据摘要,会包含交易输入输出、合约调用、合约读写集、发起人和背书人信息等,并将交易数据序列化后的字节数组使用双重SHA256得到摘要数据,最后对摘要数据用ECDSA或其他数字签名算法产生交易签名。

  • 块签名:基于区块数据摘要,会包含区块元信息如前序块Hash值、交易Merkle树根、打包时间、出块节点等数据,并在序列化后使用双重SHA256得到摘要数据,最后对摘要数据用ECDSA或其他数字签名算法产生区块签名。

_images/crypto-2.png

通信加密

XuperChain 底层采用P2P网络传播交易和区块数据,在一些许可区块链网络场景中,需要对节点间的数据传输进行加密提升安全和隐私性,因此 XuperChain 的P2P连接支持基于ECDH的密钥交换算法的TLS连接。

ECDH的原理是交换双方可以在不共享任何秘密的情况下协商出一个密钥,双方只要知道对方的公钥,就能和自己的私钥通过计算得出同一份数据,而这份数据就可以作为双方接下来对称加密的密钥。

  • 第一阶段是Propose阶段,这一阶段,对等节点间互相交换双方永久公钥PK。

  • 第二阶段是Exchange阶段,本质是基于ECDH的密钥交换。双方通过ECC算法随机生成一组临时密钥对(tempPK, tempSK),然后用自己的永久私钥对临时公钥tempPK进行签名并交换。这时,双方可以通过第一步的公钥进行验签,同时拿到供本次会话使用的临时公钥。使用临时公钥的好处是一话一密,即使本次会话密钥泄露也不会导致以前的加密数据被破解。ECDH算法使得双方通过对方的公钥和自己的私钥,可以获得一致的共享密钥SharedKey。

  • 第三阶段是Verify阶段。双方使用ShareKey产生两组密钥Key1,Key2分别作为读写密钥,并使用支持的对称加密算法(AES/blowfish)加密传输第一步中发送给对方的Nonce,而接收方则使用刚才协商的密钥对数据解密,并验证Nonce是不是等于第一步中自己发送给对方的值。

通过这三次握手,双方建立了加密通信通道,并且节点间通信加密信道满足一话一密的高安全等级。

密码学模块

Crypto Provider Interface

密码学作为区块链系统的底层技术,相对比较独立。考虑到 XuperChain 作为区块链底层系统方案的模块化目标,我们将密码学相关的功能设计成一个单独的模块,并通过插件化技术实现了模块可插拔、插件可替换。

因此, XuperChain 首先抽象出了统一的密码学相关的功能,并在此之上定义了统一的密码学接口,我们称之为Crypto Provider Interface,并通过CryptoClient接口向上层区块链系统系统密码学功能。CryptoClient目前由一组接口构成:

1// CryptoClient is the interface of all Crypto functions
2type CryptoClient interface {
3    CryptoCore
4    KeyUtils
5    AccountUtils
6    MultiSig
7}

整个CryptoClient由四部分功能接口组成:

  • CryptoCore : 主要提供包括加解密、签名等密码学核心功能;

  • KeyUtils : 主要提供公私钥相关工具,例如密钥对象和JSON、文件格式之间的转换等;

  • AccountUtils : 主要提供账户相关的功能接口,例如创建账户、助记词导出私钥等;

  • MultiSig : 主要提供多重签名、环签名相关功能接口。

密码学插件

由于抽象出了统一的密码学模块和接口,在此基础上实现插件化就比较容易。目前 XuperChain 已经实现了包括 Nist P256 + ECDSA/Schnorr 以及 国密 等多种密码学插件,并且已经开源了 Nist P256 + ECDSA/Schnorr*和*国密 算法实现,并分别提供了密码学插件。

为了方便框架使用密码学插件, XuperChain 在 crypto/client 包中封装了一层密码学插件管理器,支持创建指定类型的密码学对象,或者通过公私钥自动识别需要加载的插件类型。通过密码学插件管理器,可以支持隔绝框架对密码学插件的感知,对上层框架提供一种无缝的使用体验。

XuperChain 中默认密码学插件使用的是Nist P256 + ECDSA,在不额外指定的情况下, XuperChain 启动后会加载默认密码学插件。

之前说过,通过密码学插件管理器可以按照公私钥自动识别需要加载的插件类型,那么 XuperChain 如何根据密钥来判断应该使用哪种密码学插件呢?其实,不同的密码学插件是通过密钥中的曲线类型来确定的,目前系统中定义了三种不同的曲线类型:

  • P-256 : 使用Nist P256+ECDSA的默认插件;

  • P-256-SN : 使用Nist P256 + Schnorr签名的插件,可以提供更高的签名验签性能;

  • SM2-P-256 : 使用SM2/3/4的国密插件,符合中国国家密码局制定的密码学标准。

实际使用中,可以通过创建链时的配置中的密码学类型指定使用哪种密码学插件,以schnorr签名为例,在创世块配置中添加下述配置即可:

1"crypto": "schnorr"

在cli命令行工具中已经支持了通过命令行参数 --cryptotype 指定密码学插件的类型,例如需要创建一个使用Nist P256 + Schnorr的密码学插件的用户账户,可以使用下述命令行:

1./xchain-cli account newkeys --output data/tmpkey --cryptotype schnorr

国密插件使用方法

目前我们开源的国密密码学插件支持SM2/SM3/SM4算法,并且国密插件支持对Nist P256算法生成的地址和签名数据进行验签。

如果要创建一个基于国密算法的链,需要完成以下几个步骤:

  1. 首先确认使用了v3.7以上的XuperChain版本,并且编译产出中包含了*plugins/crypto/crypto-gm.so.1.0.0*,在插件配置*conf/plugins.conf*中确认包含如下配置内容:

 1{
 2"crypto":[
 3    ....,
 4    {
 5        "subtype": "gm",
 6        "path": "plugins/crypto/crypto-gm.so.1.0.0",
 7        "version": "1.0.0",
 8        "ondemand": false
 9    }]10    ....
11}

2. 首先对每个节点创建一个基于国密算法生成的节点私钥和地址。 注意,data/keys*目录原来保存的是默认密码学插件生成的私钥,可以删除原私钥目录,或者使用-f*参数强制覆盖原私钥。如果不希望覆盖原节点密钥,可以在*--output*参数后面指定新的私钥目录。私钥生成后,可以查看私钥文件,其中*Curvname*应该是**SM2-P-256**。

1./xchain-cli account newkeys --output data/keys --cryptotype gm
  1. 修改待创建链的创世块配置,通过**crypto**字段指定默认密码学插件为国密插件,并修改矿工地址为新生成的国密私钥对应的地址。一般默认创世块配置位于*data/config/xuper.json*,修改创世块配置:

 1{
 2    ...,
 3    "predistribution": [
 4        {
 5            "address": "此处替换为国密address",
 6            "quota": "100000000000000000000"
 7        }
 8    ],
 9    "crypto": "gm",
10    ...,
11    "genesis_consensus": {
12        "name": "tdpos",
13        "config": {
14            ...,
15            "init_proposer": {
16                "1": [
17                    "此处替换为国密address"
18                ]
19            }
20        }
21    }
22}
  1. 创建链并启动:

1./xchain-cli createChain
2nohup ./xchain &
  1. 使用命令行进行操作时,需要通过*--cryptotype*参数指定加密类型为国密,例如transfer命令。

1./xchain-cli transfer --to alice --amount 1 --keys data/keys --cryptotype gm
  1. 目前国密只支持使用Go SDK调用,后续会支持更多SDK。

插件机制

可插拔架构

XuperChain 从设计之初就以高性能、可插拔的区块链底层技术架构为目标,因此整个 XuperChain 在模块化、接口化设计上做了很多的抽象工作。而插件化机制就是服务于 XuperChain 可插拔的架构目标,使得所有模块具有同样的可插拔机制,并能满足对模块插件的加载、替换等生命周期的管理。

通过插件化机制可以实现如下架构优点:

  • 代码解耦 :插件化机制使 XuperChain 的架构框架与各个模块的实现相解耦,模块统一抽象出基本数据结构与框架的交互接口,模块只要符合统一接口即可做到插拔替换。

  • 高可扩展 :用户可以自己实现符合业务需求的模块插件,直接替换插件配置就可以实现业务扩展。

  • 发布灵活 :插件可以单独发布,配合插件生命周期管理甚至可以实现插件的单独更新,而作为插件的开发者也可以自由选择开源发布或者只发布插件二进制文件。

插件框架设计

插件框架用以根据需求创建插件实例,考虑到 XuperChain 单进程多链、插件多实例多版本等需求,整体设计

_images/extension.png

模块和插件定义

XuperChain 中,一种 模块 是指:包含一组相同数据结构和接口的代码集合,能实现相对独立的功能。

一个模块可以有多种实现,每种实现形成一个 插件

模块和插件具有如下约束:

  • 同一种模块,需要抽象出公共数据接口和接口方法。

  • 该模块的所有插件,需要实现定义的所有公共接口,并不包含定义接口以外的public接口。

  • 每个插件需要实现一个 GetInstance 接口,该接口创建并返回一个插件对象引用,该插件对象包含插件定义的所有公共接口。

因此,我们可以在框架中定义一组公共的数据结构和接口,例如:

1package kvstore
2
3type KVStore interface {
4    Init(string)
5    Get(string) (string, error)
6    Set(string, string) error
7}

并在插件代码中,引用并实现该公共接口定义,例如:

 1import "framework/kvstore"
 2
 3type KVMem struct {
 4    meta    kvstore.Meta
 5    data    map[string]string
 6    rwmutex sync.RWMutex
 7}
 8
 9// 每个插件必须包含此方法,返回一个插件对象
10func GetInstance() interface{} {
11    kvmem := KVMem{}
12    return &kvmem
13}
14
15// 插件需要实现接口定义中的所有方法
16func (ys *YourKVStore) Init(conf string) {
17    // Your code here
18}
19
20func (ys *YourKVStore) Get(key string) (string, error) {
21    // Your code here
22}
23
24func (ys *YourKVStore) Set(key string, value string) error {
25    // Your code here
26}

配置化管理

插件通过配置文件组织可选插件以及 插件子类型插件路径版本 等信息。考虑到同一个链中可能需要创建某个插件的多种实例,因此所有的插件都以数组的方式声明该插件不同的子插件类型对应的链接库地址。

举例如下:

 1{
 2    "kvstore":[
 3        {
 4            "subtype": "Memory",
 5            "path": "plugins/kv-memory.so.1.0.1",
 6            "version": "1.0.1",
 7            "ondemand": false
 8        },
 9        {
10            "subtype": "Json",
11            "path": "plugins/kv-json.so.1.0.0",
12            "version": "1.0.0",
13            "ondemand": false
14        }
15    ],
16    "crypto":[
17        {
18            "subtype": "GuoMi",
19            "path": "plugins/crypto/crypto-gm.so.1.1.0",
20            "version": "1.1.0",
21            "ondemand": false
22        },
23    ]
24}

PluginMgr

PluginMgr定义了插件管理的对外接口。

1// 根据插件配置文件初始化插件管理对象
2func CreateMgr(confPath string) (pm *PluginMgr, err error);
3
4// 指定插件名称和插件子类型,获取该插件的一个实例
5func (pm *PluginMgr) CreatePluginInstance(name string, subtype string)

需要插件功能的主逻辑中,要通过 CreateMgr 创建一个PluginMgr的实例,该实例会根据传入的配置文件创建插件实例。

PluginMgr使用

每个模块可以定义自己的实例创建方法,并可以自行确定是否使用默认模块,或使用插件化的模块。

 1func NewKVStore(pm *pluginmgr.PluginMgr, subType string) (store KVStore, err error) {
 2    var iface interface{}
 3    iface, err = pm.CreatePluginInstance(KV_PLUGIN_NAME, subType)
 4    if err != nil {
 5        return
 6    }
 7
 8    if iface != nil {
 9        // registered external plugin
10        store = iface.(KVStore)
11    } else {
12        // no plugin registered, use default one
13        store = new(KVText)
14    }
15    return
16}

XuperChain 的插件

目前,插件化机制已经在 XuperChain 中应用于包括密码学、共识、KV引擎等多个核心模块中,初步实现了插件的解耦和可扩展性目标。

以密码学为例,通过插件机制,我们可以实现多套不同的密码学算法的封装,目前 XuperChain 已经实现了包括Nist P256、Schnorr签名、国密算法等多个不同的密码学插件,并支持代码和二进制产出的独立发布。

当然,目前插件机制是基于go plugin的实现,限于go plugin本身实现上的一些局限性,插件机制也具有如下需要改进的地方:

  • 跨平台支持:目前尚不支持Windows系统的插件化,只支持Mac/Linux系统。

  • 依赖版本限制:插件的依赖库版本和框架的依赖库版本不能有任何的差别,否则会加载失败。

相信在后续 XuperChain 迭代过程中,上述问题也会得到解决。

XuperChain 共识框架

区块链共识机制概述

区块链系统多数采用去中心化的分布式设计,节点是分散在各处,系统需要设计一套完善的制度,以维护系统的执行顺序与公平性,统一区块链的版本,并奖励提供资源维护区块链的使用者,以及惩罚恶意的危害者。这样的制度,必须依赖某种方式来证明,是由谁取得了一个区块链的打包权(或称记帐权),并且可以获取打包这一个区块的奖励;又或者是谁意图进行危害,就会获得一定的惩罚,这些都是区块链系统的共识机制需要解决的问题。

随着区块链应用落地场景越来越多,很多适应不同应用场景的共识算法先后被提出。但是在当前的技术背景下,功能过于全面的共识算法无法真正可用。在新一代区块链共识机制的设计过程中,根据实际应用场景,有的放矢的选择去中心化、节能、安全等设计原则,对一些原则支持强弱进行取舍,将一定程度上提升系统的整体运行效率。

我们 XuperChain 设计上是一个通用的区块链框架,用户可以方便地进行二次开发定制。 XuperChain 的共识模块设计上是一个能够复用底层共识安全的共识框架,用户基于这样的框架可以轻松地定义其自己的链,而不需要考虑底层的共识安全和网络安全。

XuperChain 共识框架概览

_images/consensus-1.png

上图是 XuperChain 共识模块的整体架构图,自底向上主要包括3层:

1. 共识公共组件层:该层主要是不同共识可以共用的组件,包括共识公共节点Consensus、Interface、Chained-BFT、GPS原子钟等,它可以为链提供底层的共识安全性保障;
2. 共识类型层:中间层为 XuperChain 以及支持或者即将支持的共识类型,主要包括TDPoS、Pow、授权共识等,基于底层的共识安全能力。在这一层,用户可以定义有自己特色的共识类型,如类似TDPoS这种选举机制的共识,也可以定义Stakeing等的相关逻辑;
3. 可插拔共识层:最上层是可插拔共识的运行态,包括Step Consensus 和Pluggable Consensus两个实例,该层主要负责维护了链从创建到当前高度的共识的所有升级历史。 XuperChain 的共识升级主要依赖系统的提案和投票机制,详情请查看 提案和投票机制文档

XuperChain 共识矩阵

目前 XuperChain 主要支持了四种共识,总结如下:

共识算法名称

是否基于chained-BFT

确认效率

XPoA

3个区块后

XPoS

3个区块后

PoW

经验值

Single

当即确认

XuperChain 共识主流程

XuperChain 的是一个多链架构,其中单个链的主要逻辑在 core/xchaincore.go 文件中,其中与共识模块交互的函数主要有2个,分别是 Miner()SendBlock()

  1. Miner(): 这个函数的主要功能有2点,首先判断自己是否为当前的矿工,当判断自己是矿工时需要进行区块打包。

  2. SendBlock(): 这个函数是节点收到区块时的核心处理逻辑,当节点收到一个区块时会调用共识模块的相关接口进行区块有效性的验证,当验证通过后会将区块写入到账本中。

  1. 用户提交交易到网络,交易执行完后会进入未确认状态,并记录在交易的未确认列表中TxPool中;

  2. 节点的Miner流程通过访问Consensus模块判断自己是否为当前的矿工;

  3. 当节点判断自己是矿工时需要从TxPool中拉取交易并进行区块的打包;

  4. 当矿工完成打包后会将区块广播给其他的验证节点,同时会通过步骤7将区块写入到账本;

  5. 如果某个时刻其他节点判断自己是矿工,同样地会按照上述1-5流程进行区块打包,打包完后会将区块广播给该节点;

  6. 节点收到区块后,会调用consensus模块进行区块的有效性验证;

  7. 矿工打包完后或者验证节点收到一个有效的区块后,将区块写入账本;

接口介绍

整个共识框架主要有2套接口,分别是共识基础接口和共识安全接口,适用的场景不同。

场景一:用户希望定义自己的共识功能并独立负责共识安全;那么用户仅需要实现共识基础接口; 场景二:用户希望定义自己的共识功能,但是希望框架底层能帮助保证共识安全;那么用户需要实现共识基础接口和共识安全接口;

共识基础接口

共识基础接口是共识模块的核心接口,是与core模块交互的主要部分。其中最核心的部分主要是 CompeteMasterCheckMinerMatch 两个。 CompeteMaster 是一个节点判断自己是否为主的主要逻辑, CheckMinerMatch 是节点收到一个区块验证其区块有效性的主要逻辑。

 1// consensus/base/consensusinterface.go
 2type ConsensusInterface interface {
 3    Type() string
 4    Version() int64
 5    InitCurrent(block *pb.InternalBlock) error
 6    Configure(xlog log.Logger, cfg *config.NodeConfig, consCfg map[string]interface{},
 7        extParams map[string]interface{}) error
 8    CompeteMaster(height int64) (bool, bool)
 9    CheckMinerMatch(header *pb.Header, in *pb.InternalBlock) (bool, error)
10    ProcessBeforeMiner(timestamp int64) (map[string]interface{}, bool)
11    ProcessConfirmBlock(block *pb.InternalBlock) error
12    GetCoreMiners() []*MinerInfo
13    GetStatus() *ConsensusStatus
14}

共识安全接口

共识安全接口是保证底层共识安全的核心接口,共识框架底层支持了 Hotstuff 算法的的高性能的共识安全模块 Chained-BFT 。暴露出了 PacemakerInterfaceExternalInterface 接口,其中 PacemakerInterfaceChained-BFT 的活性保证,此外为了扩展 Chained-BFT 安全模块能够应用于更多的仲裁类型, 底层 Chained-BFT 设计上不需要理解仲裁的具体内容,通过 ExternalInterface 会与外层的共识进行通信,接口的具体定义如下,更详细的内容可以参见 Chained-BFT 的介绍。

 1// consensus/common/chainedbft/liveness/pacemaker_interface.go
 2// PacemakerInterface is the interface of Pacemaker. It responsible for generating a new round.
 3// We assume Pacemaker in all correct replicas will have synchronized leadership after GST.
 4// Safty is entirely decoupled from liveness by any potential instantiation of Packmaker.
 5// Different consensus have different pacemaker implement
 6type PacemakerInterface interface {
 7    // NextNewView sends new view msg to next leader
 8    // It used while leader changed.
 9    NextNewView(viewNum int64, proposer, preProposer string) error
10    // NextNewProposal generate new proposal directly while the leader haven't changed.
11    NextNewProposal(proposalID []byte, data interface{}) error
12    // UpdateQCHigh update QuorumCert high of this node.
13    //UpdateQCHigh() error
14    // CurretQCHigh return current QuorumCert high of this node.
15    CurrentQCHigh(proposalID []byte) (*pb.QuorumCert, error)
16    // CurrentView return current vie of this node.
17    CurrentView() int64
18    // UpdateValidatorSet update the validator set of BFT
19    UpdateValidatorSet(validators []*cons_base.CandidateInfo) error
20}
21// consensus/common/chainedbft/external/external_interface.go
22// ExternalInterface is the interface that chainedbft can communicate with external interface
23// external consensus need to implements this.
24type ExternalInterface interface {
25    // CallPreQc call external consensus for the PreQc with the given Qc
26    //  PreQc is the the given QC's ProposalMsg's JustifyQC
27    CallPreQc(*pb.QuorumCert) (*pb.QuorumCert, error)
28    // CallProposalMsg call external consensus for the marshal format of proposalMsg's parent block
29    CallPreProposalMsg([]byte) ([]byte, error)
30    // CallPrePreProposalMsg call external consensus for the marshal format of proposalMsg's grandpa's block
31    CallPrePreProposalMsg([]byte) ([]byte, error)
32    // CallVerifyQc call external consensus for proposalMsg verify with the given QC
33    CallVerifyQc(*pb.QuorumCert) (bool, error)
34    // CallProposalMsgWithProposalID call  external consensus for proposalMsg  with the given ProposalID
35    CallProposalMsgWithProposalID([]byte) ([]byte, error)
36    // IsFirstProposal return true if current proposal is the first proposal of bft
37    // First proposal could have empty or nil PreQC
38    IsFirstProposal(*pb.QuorumCert) (bool, error)
39}

Chained-BFT共识公共组件

概述

在 ` XuperChain 共识框架 <consensus.html>`_ 一文中介绍了 XuperChain 底层有一个共识的公共组件叫chained-bft,其是Hotstuff算法的实现。HotStuff是一种简洁而优雅的bft改进算法。它具有以下优点:

  • 它的设计中将liveness和safty解耦开来,使得非常方便与其他的共识进行扩展;

  • 将bft过程拆解成3阶段,每个阶段都是o(n)的通信;

  • 它允许一个节点处于不同的view,并且将view的切换与区块结合起来,使得其能够实现异步共识,进一步提升共识的效率。

这样一个chained-bft可以在给定主集合的场景下确保网络的共识安全性,并且通过与外层共识配合工作实现共识的活性保证。

Xuperchain现有基于Chained-BFT的共识算法

目前基于chained-BFT的算法有:XPoA、XPoS

chained-BFT中区块需要在三个块后才被最终一致确认

HotStuff中明确了基本算法Basic HotStuff和链式算法Chained-HotStuff的概念,其中Chained-HotStuff是Basic HotStuff的流水线链式结构,也是 XuperChain chained-BFT的算法原型。

和PBFT一样,Basic HotStuff也被分成了三个阶段: Prepare、Pre-Commit、Commit,如下图所示。

_images/chained_bft.jpg

Chained-Hotstuff并发操作了原本的Basic HotStuff,将上一视图在所在阶段的信息发给下一视图的Leader,从而形成了流水线操作,增加效率。

正因为Basic HotStuff分为三个阶段,在完成三阶段的信息过程后,才最终形成一执行,即在当前视图在三个视图之后才最终完成了信息收集与执行。在chained-BFT中,视图与区块高度是一致绑定的,因此一个区块需要在三个区块后才被确认。

核心数据结构

 1enum QCState {
 2    NEW_VIEW = 0;
 3    PREPARE = 1;
 4    PRE_COMMIT = 2;
 5    COMMIT = 3;
 6    DECIDE = 4;
 7}
 8// QuorumCert is a data type that combines a collection of signatures from replicas.
 9message QuorumCert {
10    // The id of block this QC certified.
11    bytes BlockId = 1;
12    // The current type of this QC certified.
13    // the type contains `NEW_VIEW`, `PREPARE`,`PRE_COMMIT`, `COMMIT`, `DECIDE`.
14    State Type = 2;
15    // The view number of this QC certified.
16    int64 ViewNumber = 3;
17    // SignInfos is the signs of the leader gathered from replicas
18    // of a specifically certType.
19    QCSignInfos SignInfos  = 4;
20}
21// QCSignInfos is the signs of the leader gathered from replicas of a specifically certType.
22// A slice of signs is used at present.
23// TODO zq: It will be change to Threshold-Signatures
24// after Crypto lib support Threshold-Signatures.
25message QCSignInfos {
26    // QCSignInfos
27    map<string, SignInfo> QCSignInfos = 1;
28}
29// SignInfo is the signature information of the
30message SignInfo {
31string Address = 1;
32string PublicKey = 2;
33bytes  Sign = 3;
34}
35// ChainedBftMessage is the message of the protocal
36// In hotstuff, there are two kinds of messages, "NEW_VIEW_Message" and "QC_Message".
37// In XuperChain, there is only one kind of message, "NEW_VIEW. The "QC_Message" is resuded with "BroadcastBlock" message.
38message ChainedBftMessage {
39    // Message Type
40    QCState Type = 1;
41    // Justify is the QC of the leader gathered, send to next leader.
42    QuorumCert Justify = 2;
43}
44// ChainedBftMessage is the vote message of
45message ChainedBftVoteMessage {
46    // The id of block this message vote for.
47    bytes BlockId = 1;
48    // Replica will sign the QCMessage if the QuorumCert if valid.
49    SignInfo signature = 2;
50}

整个chained-bft中主要包括三部分,分别是状态机 SmrSafetyRulesPacemakerInterface

Smr

Smr是 chained-bft 的核心实例。他的主要的作用有以下几点:

  1. 维护节点链的chained-bft共识状态机;

  2. 在外层共识的驱动下发起 NewViewNewProposal 等消息并更新本地状态;

  3. 处理其他验证节点的消息并更新本地状态;

_images/chained-bft.png

Safety Rule

Safety Rule 是一个验证节点是否要接受一个新的Proposal的安全性规则,主要有三条:

  1. 判断当前Proposal的View值是否大于本地locked Proposal的View值;

  2. 验证当前Proposal中上一个Proposal的投票信息有效性和投票个数是否大于系统矿工数目的2/3;

  3. 验证当前Proposal的ProposalMsg是否有效;

当一个验证节点收到一个新的提案时,如果满足上述 Safety Rule 的认证,则会给这个提案进行投票,否则拒绝这次提案。

PacemakerInterface

Hotstuff算法的一大特点就是将共识的liveness和safety分开。PacemakerInterface是Hotstuff算法Pacemaker的接口定义,外层共识通过实现这些接口,可以推进内层共识的状态轮转。不同的外层共识可以有不同的实现。目前 XuperChain 已经实现了DPoS+Hotstuff,具体的方案如下所示:

_images/pacemaker.png

XPoS共识

介绍

XPoS是 XuperChain 的一种改进型的DPoS算法,他是在一段预设的时间长度(一轮区块生产周期)内选择若干个验证节点,同时将这样一轮区块生产周期分为N个时间段, 这若干个候选节点按照约定的时间段协议协同挖矿的一种算法。在选定验证节点集合后,XPoS通过Chained-BFT算法来保证轮值期间的安全性。 总结一下,整个XPoS主要包括2大阶段:

  1. 验证人选举:通过pos相关选举规则选出一个验证者集合;

  2. 验证人轮值:验证者集合按照约定的协议规则进行区块生产;

候选人选举

节点角色

_images/tdpos.png

在XPoS中,网络中的节点有三种角色,分别是“普通选民”、“候选人”、“验证者”:

  1. 选民:所有节点拥有选民的角色,可以对候选节点进行投票;

  2. 候选人:需要参与验证人竞选的节点通过注册机制成为候选人,通过注销机制退出验证人竞选;

  3. 验证人:每轮第一个节点进行检票,检票最高的topK候选人集合成为该轮的验证人,被选举出的每一轮区块生产周期的验证者集合,负责该轮区块的生产和验证,某个时间片内,会有一个矿工进行区块打包,其余的节点会对该区块进行验证。

网络中的三种角色之间是可以相互转换的,转换规则如下:

  1. 所有地址都具有选民的特性,可以对候选人进行投票;

  2. 选民经过“候选人提名”提名接口成为候选人,参与竞选;

  3. 候选人经过“候选人退选”注销接口退出竞选;

  4. 候选人经过检票产出验证者,得票topK的候选人当选验证者;

  5. 验证者轮值完恢复候选人或者选民角色;

提名规则

节点想要参与竞选,需要先被提名为候选人,只有被提名的地址才能接受投票。为了收敛候选人集合,并一定程度上增加候选人参与的门槛,提名为候选人会有很多规则,主要有以下几点:

  1. 提名候选人需要冻结燃料,并且金额不小于系统总金额的十万分之一;

  2. 该燃料会被一直冻结,直到节点退出竞选;

  3. 提名支持自提和他提,即允许第三方节点对候选人进行提名;

  4. 被提名者需要知晓自己被提名,需要对提名交易进行背书;

选举规则

候选人被提名后,会形成一个候选人池子,投票需要针对该池子内部的节点进行。XPoS的投票也有很多规则,主要有以下几点:

  1. 任何地址都可以进行投票,投票需要冻结燃料,投票的票数取决于共识配置中每一票的金额,票数 = 冻结金额 / 投票单价;

  2. 该燃料会被一直冻结,直到该投票被撤销;

  3. 投票采用博尔达计分法,支持一票多投,每一票最多投给设置的验证者个数,每一票中投给不同候选人的票数相同;

候选人轮值

每一轮开始的第一个区块会自动触发检票的交易,该交易会进行下一轮候选人的检票,被选举出的节点会按照既定的时间片协同出块,每一个区块都会请求所有验证节点的验证。XPoS的时间片切分如下图所示:

_images/tdpos-slice.png

为了降低切主时容易造成分叉,XPoS将出块间隔分成了3个,如上图所示:

  • t1:同一轮内同一个矿工的出块间隔;

  • t2:同一轮内切换矿工时的出块间隔,需要为t1的整数倍;

  • t3:不同轮间切换时的出块间隔,需要为t1的整数倍;

拜占庭容错

XPoS验证节点轮值过程中,采取了 Chained-Bft 防止矿工节点的作恶。

技术细节

XPoS实现主要在 consensus/tdpos 路径下,其主要是通过智能合约的方式实现的,主要有以下几个合约方法:

1voteMethod = "vote"
2// 候选人投票撤销
3revokeVoteMethod = "revoke_vote"
4// 候选人提名
5nominateCandidateMethod = "nominate_candidate"
6// 候选人罢黜
7revokeCandidateMethod = "revoke_candidate"
8// 验证人生成
9checkvValidaterMethod = "check_validater"

核心接口如下:

 1func (tp *TDpos) runVote(desc *contract.TxDesc, block *pb.InternalBlock) error {
 2    // ......
 3    return nil
 4}
 5func (tp *TDpos) runRevokeVote(desc *contract.TxDesc, block *pb.InternalBlock) error {
 6    // ......
 7    return nil
 8}
 9func (tp *TDpos) runNominateCandidate(desc *contract.TxDesc, block *pb.InternalBlock) error {
10    // ......
11    return nil
12}
13func (tp *TDpos) runRevokeCandidate(desc *contract.TxDesc, block *pb.InternalBlock) error {
14    // ......
15    return nil
16}
17func (tp *TDpos) runCheckValidater(desc *contract.TxDesc, block *pb.InternalBlock) error {
18    // ......
19    return nil
20}

XPoA共识

介绍

XPoA是 XuperChain 对PoA的一种实现,其基本思想是在节点中动态设定一组验证节点,验证节点组在预设的时间段内进行组内轮流出块(称之为轮值),即其余节点在某特定验证节点V出块的时间段内统一将交易发送给V,交易由该验证节点V打包成区块。

XPoA支持动态变更验证节点,可以通过指令修改现有的验证节点组,包括对当前验证节点组进行删除和添加操作。在该算法中,预设时间段包括确定单个区块的出块时间,以及验证节点单次轮值出块数量。 同样,XPoA通过Chained-BFT算法来保证轮值期间的安全性。

详细操作请参考 XPoA使用文档

技术细节

在XPoA中,网络中的节点有两种角色,分别是“普通节点”和“验证节点”:

  1. 普通节点:普通节点仅对验证节点进行验证,计算当前时间点下验证节点地址是否于计算结果吻合。

  2. 验证节点:进行区块打包工作;在更改验证节点组过程中,多数验证节点需确定更改结果添加和删除操作方能生效。

修改验证组规则

验证组信息通过合约调用进行修改,流程主要有以下几点:

  1. 在收到该信息后,验证节点通过签名信息确认交易真实性

  2. 验证节点在UtxoVM中进行系统调用并更新当前验证人集合读写集

  3. 验证人集合并不会立即影响当前共识,在三个区块后集合才能生效

验证节点间轮值

每一轮的时间由配置xuper.json指定,在单轮时间段内,区块打包由目前验证节点组中的节点按顺序轮流完成。在通过合约发起验证节点变更后,变更会在三个区块后才触发,然后验证节点按照新的验证组继续进行轮值。

https://raw.githubusercontent.com/aucusaga/LearnXuperchainPicRep/master/XPoA/XPoA.jpg

调度代码具体实现如下:

func (xpoa *XPoa) minerScheduling(timestamp int64) (term int64, pos int64, blockPos int64) {
            ...
            // 每一轮的时间
            termTime := xpoa.xpoaConf.period * int64(len(xpoa.proposerInfos)) * xpoa.xpoaConf.blockNum
            // 每个矿工轮值时间
            posTime := xpoa.xpoaConf.period * xpoa.xpoaConf.blockNum
    // 当前轮数
            term = (timestamp-xpoa.termTimestamp)/termTime + 1
    // 本轮已过时间
            resTime := (timestamp - xpoa.termTimestamp) - (term-1)*termTime
            // 当前验证节点所属位置
    pos = resTime / posTime
    // 当前验证节点所处轮值时间已过时间
            resTime = resTime - (resTime/posTime)*posTime
    // 当前验证节点已出块数量
            blockPos = resTime/xpoa.xpoaConf.period + 1
            ...
            return
    }

调度流程如下:

https://raw.githubusercontent.com/aucusaga/LearnXuperchainPicRep/master/XPoA/minerScheduling.jpg

拜占庭容错

XPoA验证节点轮值过程中,采取了 Chained-Bft 防止矿工节点的作恶。

整体代码

XPoA实现主要在 consensus/xpoa 路径下,其主要是通过智能合约的方式实现的,合约在 contractsdk/cpp/example/xpoa_validates/src 路径下,主要有以下几个合约方法:

 1/*XPoA添加一个新的候选人节点*/
 2DEFINE_METHOD(Hello, add_validate) {
 3    ...
 4}
 5/*XPoA删除一个候选人节点*/
 6DEFINE_METHOD(Hello, del_validate) {
 7    ...
 8}
 9/*XPoA更新一个候选人节点信息*/
10DEFINE_METHOD(Hello, update_validate) {
11    ...
12}
13/*查询当前候选人节点信息*/
14DEFINE_METHOD(Hello, get_validates) {
15    ...
16}

核心接口如下:

 1func (xpoa *XPoa) minerScheduling(timestamp int64) (term int64, pos int64, blockPos int64) {
 2    // 轮值时间调度计算规则
 3    ...
 4    return
 5}
 6func (xpoa *XPoa) getCurrentValidates() ([]*cons_base.CandidateInfo, int64, int64, error) {
 7    // 获取当前验证组信息,若无法查询则使用xuper.json初始化值
 8    ...
 9    return candidateInfos.Proposers, confirmedTime, confirmedHeight, nil
10}
11func (xpoa *XPoa) updateValidates(curHeight int64) (bool, error) {
12    // 查询当前验证组,判断当前时间点是否需要更新验证组
13    ...
14    return true, nil
15}
16func (xpoa *XPoa) updateViews(viewNum int64) error {
17    // 获取当前验证节点以及下一验证节点,创建下一轮新视图
18    ...
19    return xpoa.bftPaceMaker.NextNewView(viewNum, nextProposer, proposer)
20}
21func (xpoa *XPoa) getProposerWithTime(timestamp, height int64) (string, error) {
22    // 根据当前时间戳计算当前验证节点是谁并返回其地址
23    ...
24    return xpoa.proposerInfos[pos].Address, nil
25}

Single及PoW共识

介绍

Single以及PoW属于不同类型的区块链共识算法。其中,PoW(Proof Of Work,工作量证明)是通过解决一道特定的问题从而达成共识的区块链共识算法;而Single亦称为授权共识,在一个区块链网络中授权固定的address来记账本。Single一般在测试环境中使用,不适合大规模的应用环境。PoW适用于公有链应用场景。

算法流程

Single共识

  • 对于矿工:Single是固定 address 周期性出块,因此在调用 CompeteMaster 的时候主要判断当前时间与上一次出块时间间隔是否达到一个周期;

  • 对于验证节点:验证节点除了密码学方面必要的验证之外,还会验证矿工与本地记录的矿工是否一致;

Pow共识

  • 对于矿工:每次调用 CompeteMaster 都返回 true,表明每次调用 CompeteMaster 的结果都是矿工该出块了;

  • 对于验证节点:验证节点除了密码学方面必要的验证之外,还会验证区块的难度值是否符合要求;

在 XuperChain 中使用Single或PoW共识

只需修改 data/config 中的创世块配置即可指定使用共识

使用Single共识的创世块配置

 1{
 2    "version" : "1",
 3    "consensus" : {
 4        # 共识算法类型
 5        "type"  : "single",
 6        # 指定出块的address
 7        "miner" : "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN"
 8    },
 9    # 预分配
10    "predistribution":[
11        {
12            "address" : "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
13            "quota" : "100000000000000000000"
14        }
15    ],
16    # 区块大小限制
17    "maxblocksize" : "128",
18    # 出块周期
19    "period" : "3000",
20    # 出块奖励
21    "award" : "428100000000",
22    # 精度
23    "decimals" : "8",
24    # 出块奖励衰减系数
25    "award_decay": {
26        "height_gap": 31536000,
27        "ratio": 1
28    },
29    # 系统权限相关配置
30    "permission": {
31        "CreateAccount" : { "rule" : "NULL", "acl": {}},
32        "SetAccountAcl": { "rule" : "NULL", "acl": {}},
33        "SetContractMethodAcl": { "rule" : "NULL", "acl": {}}
34    }
35}

使用PoW共识的创世块配置

 1{
 2    "version" : "1",
 3    # 预分配
 4    "predistribution":[
 5        {
 6            "address" : "Y4TmpfV4pvhYT5W17J7TqHSLo6cqq23x3",
 7            "quota" : "1000000000000000"
 8        }
 9    ],
10    "maxblocksize" : "128",
11    "award" : "1000000",
12    "decimals" : "8",
13    "award_decay": {
14        "height_gap": 31536000,
15        "ratio": 0.5
16    },
17    "genesis_consensus":{
18        "name": "pow",
19        "config": {
20                # 默认难度值
21                "defaultTarget": "19",
22                # 每隔10个区块做一次难度调整
23                "adjustHeightGap": "10",
24                "expectedPeriod": "15",
25                "maxTarget": "22"
26        }
27    }
28}

关键技术

Single共识的原理简单,不再赘述。

PoW共识

解决一道难题过程,执行流程如下:

  • step1 每隔一个周期判断是否接收到新的区块。若是,跳出解决难题流程,若不是,进行 step2

  • step2 判断当前计算难度值是否符合要求。若是,跳出难题解决流程,若不是难度值加1,继续 step1

伪代码如下:

 1// 在每次挖矿时,设置为true
 2// StartPowMinning
 3for {
 4    // 每隔round次数,判断是否接收到新的区块,避免与网络其他节点不同步
 5    if gussCount % round == 0 && !l.IsEnablePowMinning() {
 6        break
 7    }
 8    // 判断当前计算难度值是否符合要求
 9    if valid = IsProofed(block.Blockid, targetBits); !valid {
10        guessNonce += 1
11        block.Nonce = guessNonce
12        block.Blockid, err = MakeBlockID(block)
13        if err != nil {
14            return nil, err
15        }
16        guessCount++
17        continue
18    }
19    break
20}
21// valid为false说明还没挖到块
22// l.IsEnablePowMinning() == true  --> 自己挖出块
23// l.IsEnablePowMinning() == false --> 被中断
24if !valid && !l.IsEnablePowMinning() {
25    l.xlog.Debug("I have been interrupted from a remote node, because it has a higher block")
26    return nil, ErrMinerInterrupt
27}

计算当前区块难度值过程,执行流程如下:

  • step1 判断当前区块所在高度是否比较小。若是,直接复用默认的难度值,跳出计算区块难度值过程,若不是,继续 step2

  • step2 获取当前区块的前一个区块的难度值;

  • step3 判断当前区块是否在下一个难度调整周期范围内。若是,继续 step4 ;若不是,继续 step5

  • step4 获取当前区块的前一个区块的难度值,并计算经历N个区块,预期/实际消耗的时间,并根据公式调整难度值,跳出计算区块难度值过程;

  • step5 如果当前区块所在高度在下一次区块难度调整的周期范围内,直接复用前一个区块的难度值,跳出计算区块难度值过程;

伪代码如下:

 1func (pc *PowConsensus) calDifficulty(curBlock *pb.InternalBlock) int32 {
 2    // 如果当前区块所在高度比较小,直接复用默认的难度值
 3    if curBlock.Height <= int64(pc.config.adjustHeightGap) {
 4        return pc.config.defaultTarget
 5    }
 6    height := curBlock.Height
 7    preBlock, err := pc.getPrevBlock(curBlock, 1)
 8    if err != nil {
 9        pc.log.Warn("query prev block failed", "err", err, "height", height-1)
10        return pc.config.defaultTarget
11    }
12    // 获取当前区块前一个区块的难度值
13    prevTargetBits := pc.getTargetBitsFromBlock(preBlock)
14    // 如果当前区块所在高度恰好是难度值调整所在的高度周期
15    if height%int64(pc.config.adjustHeightGap) == 0 {
16        farBlock, err := pc.getPrevBlock(curBlock, pc.config.adjustHeightGap)
17        if err != nil {
18            pc.log.Warn("query far block failed", "err", err, "height", height-int64(pc.config.adjustHeightGap))
19            return pc.config.defaultTarget
20        }
21        // 经历N个区块,预期消耗的时间
22        expectedTimeSpan := pc.config.expectedPeriod * (pc.config.adjustHeightGap - 1)
23        // 经历N个区块,实际消耗的时间
24        actualTimeSpan := int32((preBlock.Timestamp - farBlock.Timestamp) / 1e9)
25        pc.log.Info("timespan diff", "expectedTimeSpan", expectedTimeSpan, "actualTimeSpan", actualTimeSpan)
26        //at most adjust two bits, left or right direction
27        // 避免难度值调整太快,防止恶意攻击
28        if actualTimeSpan < expectedTimeSpan/4 {
29            actualTimeSpan = expectedTimeSpan / 4
30        }
31        if actualTimeSpan > expectedTimeSpan*4 {
32            actualTimeSpan = expectedTimeSpan * 4
33        }
34        difficulty := big.NewInt(1)
35        difficulty.Lsh(difficulty, uint(prevTargetBits))
36        difficulty.Mul(difficulty, big.NewInt(int64(expectedTimeSpan)))
37        difficulty.Div(difficulty, big.NewInt(int64(actualTimeSpan)))
38        newTargetBits := int32(difficulty.BitLen() - 1)
39        if newTargetBits > pc.config.maxTarget {
40            pc.log.Info("retarget", "newTargetBits", newTargetBits)
41            newTargetBits = pc.config.maxTarget
42        }
43        pc.log.Info("adjust targetBits", "height", height, "targetBits", newTargetBits, "prevTargetBits", prevTargetBits)
44        return newTargetBits
45    } else {
46        // 如果当前区块所在高度在下一次区块难度调整的周期范围内,直接复用前一个区块的难度值
47        pc.log.Info("prev targetBits", "prevTargetBits", prevTargetBits)
48        return prevTargetBits
49    }
50}

XuperChain 监管机制

监管机制概述

XuperChain 是一个具备政府监管能力的区块链系统。在设计上我们需要充分考虑监管和安全问题,做到安全可控。基于此我们 XuperChain 底层设计了一个监管合约的机制,通过该机制, XuperChain 具备了对链上用户的实名、交易的安全检查等监管能力。

XuperChain 在初始化时候,可以通过创世块配置的方式,配置这条链是否需要支持监管类型。对于配置了监管合约的链,这个链上所有的事务发起,无论是转账还是合约调用,系统会默认插入监管合约的执行,执行结果体现在读写集中,执行过程不消耗用户资源,执行结果所有节点可验证。

目前 XuperChain 支持的监管合约主要有以下几个:

  • 实名制合约: identity

  • DApp封禁合约: banned

  • 合规性检查合约: complianceCheck

  • 交易封禁合约: forbidden

下面将会以实名合约为例对监管合约的使用步骤进行说明

监管机制使用说明

创世块配置

创世块配置新增 reserved_contracts 配置,内容如下:

1"reserved_contracts": [
2    {
3        "module_name": "wasm",
4        "contract_name": "identity",
5        "method_name": "verify",
6        "args":{}
7    }
8]

这个配置中配置了 identity 监管合约。

搭建网络

搭建网络的方式与以前的方式没有区别,用户可以依据需求选择搭建单节点网络还是多节点网络。

搭建网络参见如下链接: 单节点网络搭建 多节点网络搭建

部署Reserved合约

1. 编译实名合约

实名合约代码路径如下:core/contractsdk/cpp/reserved/identity.cc

实名合约实名的对象是一个具体的ak。

1cd ./contractsdk/cpp
2cp reserved/identity.cc example
3./build.sh

编译好的产出为 ./build 文件夹下的identity.wasm文件。

2. 创建合约账户

在XuperChain中所有的合约都是部署在具体的某个账户下的,所以,为了部署实名合约,我们需要首先创建一个合约账户,注意,这里账户的拥有者可以修改其内合约Method的ACL权限管理策略,通过这种机制实现对谁可以添加实名状态和删除实名状态的控制。 这里是由 XuperChain 的 多节点网络搭建 支持的。

1# 快速创建合约方式:
2./xchain-cli account new --account 1111111111111111

3. 部署实名合约

部署合约需要消耗资源,所以先给上述合约账户转移一笔资源,然后在合约内部署上面的合约:

1# 1  转移资源
2./xchain-cli transfer --to XC1111111111111111@xuper --amount 100000
3# 2 部署实名合约
4# 通过 -a 的creator参数,可以初始化被实名的ak。
5./xchain-cli wasm deploy --account XC1111111111111111@xuper --cname identity -H localhost:37101 identity.wasm -a '{"creator":"addr1"}'

注解

上述实名合约初始化的被实名的address需要和实名合约添加实名信息保持相同,否则会由于初始实名的ak和添加实名权控不一致而导致系统无法添加新的实名状态。

Reserved合约调用

实名合约部署完成后,就可以进行实名合约信息的添加和删除了

1. 添加实名信息

合约调用json文件如下:

1{
2    "module_name": "wasm",
3    "contract_name": "identity",
4    "method_name": "register_aks",
5    "args":{
6        "aks":"ak1,ak2"
7    }
8}

具体步骤如下:

1# 1: 生成原始交易
2./xchain-cli multisig gen --desc identity_add.json --host localhost:37101 --fee 1000 --output tx_add.out
3# 2: 本地签名
4./xchain-cli multisig sign --output tx_add_my.sign --tx tx_add.out
5# 3: 交易发送
6./xchain-cli multisig send tx_add_my.sign --host localhost:37101 --tx tx_add.out

2. 删除实名信息

合约调用json文件如下:

1{
2    "module_name": "wasm",
3    "contract_name": "identity",
4    "method_name": "unregister_aks",
5    "args":{
6        "aks":"ak1,ak2"
7    }
8}

具体步骤如下:

1# 1: 生成原始交易
2./xchain-cli multisig gen --desc identity_del.json --host localhost:37101 --fee 1000 --output tx_del.out
3# 2: 本地签名
4./xchain-cli multisig sign --output tx_del_my.sign --tx tx_del.out
5# 3: 交易发送
6./xchain-cli multisig send tx_del_my.sign tx_del_compliance_sign.out --host localhost:37101 --tx tx_del.out

3. 实名信息验证

当用户向网络发起事务请求时,网络会验证交易中的 initiatorauth_require 字段是否都经过实名,如果都经过实名,则通过,否则,失败。

多盘散列

背景

区块链中的账本数据通常是只增不减,而单盘存储容量有上限。目前单盘最高容量是14TB左右,需要花费4000块钱;以太坊账本数据已经超过1TB,即使是在区块大小上精打细算的比特币账本也有0.5TB左右。区块链账本数据不断增加,单盘容量上限成为区块链持续发展的天花板。 目前对leveldb的多盘扩展方案,大部分是采用了多个leveldb实例的方式,也就是每个盘一个单独的leveldb实例。这种做法的好处是简单,不需要修改leveldb底层代码,缺点是牺牲了多行原子写入的功能。在区块链的应用场景中,我们是需要这种多个写入操作原子性的。所以选择了改leveldb底层模型的技术路线。

LevelDB数据模型分析

  • Log文件:写Memtable前会先写Log文件,Log通过append的方式顺序写入。Log的存在使得机器宕机导致的内存数据丢失得以恢复;

  • Manifest文件:Manifest文件中记录SST文件在不同Level的分布,单个SST文件的最大最小key,以及其他一些LevelDB需要的元信息;

  • Current文件:LevelDB启动时的首要任务就是找到当前的Manifest,而Manifest可能有多个。Current文件简单的记录了当前Manifest的文件名;

以上3种文件可以称之为元数据文件,它们占用的存储空间通常是几十MB,最多不会超过1GB

  • SST文件:磁盘数据存储文件。分为Level 0到Level N多层,每一层包含多个SST文件;单个SST文件容量随层次增加成倍增长;文件内数据有序;其中Level0的SST文件由Immutable直接Dump产生,其他Level的SST文件由其上一层的文件和本层文件归并产生;SST文件在归并过程中顺序写生成,生成后仅可能在之后的归并中被删除,而不会有任何的修改操作。

_images/leveldb.png

核心改造点

Leveldb的数据主要是存储在SST(Sorted String Table)文件中,写放大的产生就是由于compact的时候需要顺序读取Level-N中的sst文件,写出到Level-N+1的sst文件中。 我们将SST文件分散在多块盘上存储,具体的方法是根据sst的编号做取模散列,取模的底数是盘的个数, 理论上数据量和IO压力会均匀分散在多块盘上。

举个例子,假设某sst文件名是12345.ldb,而节点机器有3块盘用于存储(/disk1, /disk2, /disk3),那么就将改sst文件放置在 (12345 % 3) + 1, 也就是disk1盘上

_images/leveldb2.png

使用方式

leveldb.OpenFile 有两个参数,一个是db文件夹路径path,一个是打开参数Options; 如果要使用多盘存储,调用者需要设置 Options.DataPaths 参数,它是一个[]string 数组,声明了各个盘的文件夹路径,可参考 配置多盘存储

扩容问题

假设本来是N块盘,扩容后是(N+M)块盘。对于已有的sst文件,因为取模的底数变了, 可能会出现按照原有的取模散列不命中的情况。 规则是:

  • 对于读Open,先按照 (N+M) 取模去Open,如果不存在,则遍历各盘直到能Open到相应的文件,由于Open并不是频繁操作,代价可接受,且SST的编号是唯一且递增的,所以不存在读取脏数据的问题;

  • 对于写Open,就按照 (N+M) 取模,因为写Open一定是生成新的文件。

随着Compact的不断进行,整个数据文件的分布会越来越趋向于均匀分布在 (N+M) 个盘,扩容完成。

实验

写入测试可参考代码 kv/mstorage/test/test_write.go
读取测试可参考代码 kv/mstorage/test/test_read.go

平行链与群组

背景

XuperChain 具备平行链特性,能够实现业务的混部,确保整体架构性能上可以水平扩展;

同时,平行链还具备群组特性,能够一定程度上实现平行链隐私数据的隔离,只有群组内的节点才能有这个平行链的数据。

术语

  • 平行链 :相对于主链而言,运行在 XuperChain 中的用户级区块链实例,用户通过调用主链的智能合约创建。功能与主链无区别,全网节点均可以获取平行链账本数据,实现整体架构水平可扩展。

  • 群组 :作用于平行链,具备群组特性的平行链,只有特定节点才拥有该平行链的账本数据。群组具备的特性包括私密性、动态性。

架构

平行链的群组特性通过白名单机制来实现,在网络层进行过滤。平行链的群组架构,如下图

_images/parallel.png

设计思路

  1. 如何要支持群组,需要在xuper链部署一个系统合约:GroupChain(一个网络有且仅有一个)

    这样是为了保证兼容性,如果没有部署这个GroupChain合约,那么行为和旧版本一致。

    <平行链名字,IP> → Address

    为什么把IP放在Key中,是为了方便做过滤的时候查找更快,直接Get。

    平行链名字作为前缀,方便列出这条链的所有合法成员节点。

    备注:此处IP是代指一个TCP协议定位符,可以是libp2p风格的URL。

  2. 查询特定的链是否具备群组关系

    Case1: 部分链希望有群组特性,即只有特定的节点才能同步账本数据;

    Case2: 剩下的链还是期望所有节点都参与同步、验证区块;

    基于以上两种场景,需要增加一层映射,即<平行链,是否支持群组>

    如果每次转发都Lookup数据库过滤IP,性能有影响,可以考虑在p2p中维护一个Cache;

  3. 通过这个智能合约接口,可以修改(address, IP)的映射关系

    合约的Owner(GroupChain这个合约的Owner)可以添加或删除address

    节点也可以后期自己修改IP(节点有权更换自己的IP),合约里面会判断Initiator()字段和address是否一致,确保每个address只能修改自己的IP

  4. 平行链中转消息的时候,必须确保目的IP在智能合约的映射表中存在

    如果每次转发都Lookup数据库过滤IP,性能有影响,可以考虑在p2p中维护一个Cache;

XuperChain 跨链技术

背景

近年来,随着区块链行业的蓬勃发展,产生了很多区块链系统,这些系统底层协议各不相同。并且随着区块链被被纳入新基建的范畴, 如火如荼的区块链改造运动更加活跃,正在形成一个个新的数据孤岛无论这些孤岛是基于相同的底层系统还是不同的底层系统,其数据互通都非常困困难,因此,迫切需要一个解决方案能够系统地解决多链之间融合的问题,从而实现不同链之间地价值互通。

什么是跨链

跨链(Cross-Chain),简单来说就是通过一定的技术措施实现相对独立的不同区块链系统之间数据和资产的链接互通。常见的跨链解决方案一般是针对资产类,实现不同链之间任意数据的跨链互通难度会更高

常见的跨链互操作类型

跨链资产互换
_images/cross_chain_1.png
跨链资产转移
_images/cross_chain_2.png
跨链任意数据
_images/cross_chain_3.png

主流跨链方案

跨链技术的主要挑战

  • 生效的原子性 对两个网络分别发起交易tx1、交易tx2,如何保证要么都不生效,要么都生效。可以通过经典的两阶段提交来完成, 两个交易互为锚点,锚点超过一段时间仍然无效,就可以发起对交易的回滚。

  • 锚点有效性校验 所有节点对锚点的有效性达成没有分歧的判断,不能依赖远程访问证明,由于网络不稳定因素可能会造成验证难的问题。

  • 合约验证的不确定性 一般的区块链系统,每个节点都会验证交易,以防止作恶。所以如果一个合约嵌入了外部链的合约调用,那么在验证阶段,如何确保各个节点验证的结果的一致性,是否会依赖外链的稳定性,这些问题都需要解决。

主流的跨链方案

一般地通过哈希时间锁可以保证跨链交易的原子性。对于两个比较独立的区块链系统,一般的需要依赖一个第三方组件实现信息的交互。根据第三方组件是否执行验证可以分为两种模式,分为公证人模式、侧链/中继模式和哈希锁定模式。根据不同链的交易是异步生效还是同步生效,可以分为异步模式和同步模式。

公证人模式

由一个或者一组节点作为公证人参与到两条链中,进行双方交易的收集和验证。其优点是简单,缺点是弱中心化,如下图所示:

_images/cross_chain_4.png
侧链/中继模式

侧链/中继链模式是2014年BlockStream提出的一种跨链方案。其与公证人模式最大的区别在于其验证是在目标链进行,通过双向锚定的方式实现资产在不同链之间的转移。目标链的验证方式各不相同,比如BTC-Relay使用的是SPV技术。

_images/cross_chain_5.png
异步模式

以太坊上很多预言机类的DApp的实现方式采用的是异步模式。简单来说,是指发起调用的是一个函数,处理调用结果的是另一个回调函数,如下图所示:

_images/cross_chain_6.png

异步调用场景下,一个完整流程需要3笔交易:首先发送交易tx1到A链,A链代码执行到跨链调用会发出一个事件,并且声明了回调函数。

由一个中间件(单例)订阅到事件后,发起对B链调用的交易tx2,tx2上链成功后, 中间件在触发回调函数调用Tx3.

异步的缺点是业务逻辑被迫拆成很多碎片,交互次数多,编程不友好。

同步模式

同步模式目前业界没有比较统一的方案,一般地区块链的上的合约在每个节点都会重复执行,如何保证重复执行的结果确定性,且具备幂等性、无副作用是一个难解决的问题。但是现在很多区块链系统采取的是预执行再提交的方式,比如 XuperChain 和Fabric,基于这种事务模式比较容易同步模式的跨链,下面是同步模式的示意图:

_images/cross_chain_7.png

XuperChain 跨链方案

XuperChain 是一个易于编程的区块链框架,可以方便地定制新的功能。基于此框架, XuperChain 设计了一个轻量级的跨链方案,通过合约和一些基础库组成了一个完整的跨链方案,适用于不同的跨链应用场景。

通过对用户实际需求的调研,我们抽象出三种不同的跨链场景,分别是只读事务场景跨链、非事务场景跨链和事务场景跨链。并通过上述合约和基础库的能力设计了满足不同场景的解决方案,下面会进行详细的介绍。

跨链域名解析

跨链涉及到不同链资源的互操作,由于不同的链的协议各部相同,为了实现对不同链的资源进行统一定义, XuperChain 定义了如下跨链寻址协议,结合着链名解析合约,便可以实现对任何协议链的统一资源寻址。

跨链寻址协议
1    [chain_scheme:][//chain_name][path][?query]
  • chain_scheme:跨链链类型标识符,用于标识链的具体类型,比如: xuper 表示所跨链链接的链的类型是 XuperChain 。

  • chain_name:所跨链的链名,定位某一个具体的链,同一Scheme下链名是唯一的;

  • path:不同Scheme的链类型扩展字段;

  • query:用于定位某个链内的某项资源,比如对于 XuperChain ,声明调用的合约名、方法名和参数等信息;

    下面举例说明:

    xuper://chain1?module=wasm&bcname=xuper&contract_name=counter&method_name=increase
    xuper: 表示被访问的目标链为XuperChain;
    chain1: 表示被访问的目标链的链名;
    module: 表示被访问目标xuper链被访问合约的vm为wasm;
    bcname: 表示被访问目标xuper链的子链名;
    contract_name: 表示被访问目标xuper链合约名;
    method_name:表示被访问目标xuper链合约方法名;
    

目前 XuperChain 仅开源了 XuperChain 搭建的网络之间的跨链互操作,后续会逐步开源与其他链之间的互操作能力。

链名解析合约

为了实现跨链请求,仅有上面的链名跨链寻址协议是不够的,在跨链发起的原链上还需要部署一个链名解析合约,该合约目前也已开源 链名解析合约

 1// 注册一个网络,同时包含网络初始meta信息
 2RegisterChain(name, chain_meta)
 3// 更新一个网络的meta信息,比如修改网络的背书策略
 4UpdateChain(name, chain_meta)
 5// 根据网络名解析所有背书节点
 6Resolve(chain_name) *CrossQueryMeta
 7// 插入一个背书节点
 8AddEndorsor(chain_name, info)
 9// 更新背书节点信息
10UpdateEndorsor(chain_name, address, info)
11// 删除背书节点
12DeleteEndorsor(chain_name, address)
13// 目标网络meta信息
14message CrossChainMeta {
15        string type = 1;
16        int64 min_endorsor_num = 2;
17}
18// 目标网络背书信息
19message CrossEndorsor {
20        string address = 1;
21        string pub_key = 2;
22        tring host = 3;   // ip+port
23}
24// 目标网络CrossQuery完整信息
25message CrossQueryMeta {
26        CrossChainMeta chain_meta = 1;
27        repeated CrossEndorsor endorsors = 2;
28}

只读事务场景跨链

整体方案

在大部分使用场景下,用户仅希望从一个链能够查询到另一个链的数据,并且能够满足稳定性、幂等性和安全性,我们称为只读事务跨链。

只读事务跨链的典型场景包括身份认证、资质认证等;

为了满足上述要求,我们采取了目标链背书的方式,如下图所示:

_images/cross_chain_9.png

完整的步骤如下:

  1. 用户在原链发起预执行,合约内部调用”cross_query”原语,访问目标链Hello()合约;

  2. 原链内部解析到”cross_query”原语后会调用跨链寻址合约解析目标链链接和背书信息;

  3. 原链访问目标链的背书服务;

  4. 目标链背书服务内部访问Hello()合约,得到Response并进行签名,返回给原链;

  5. 原链继续执行合约,并返回预执行结果给用户;

  6. 用户组装完整的交易提交给原链网络;

背书和验证

XuperChain 的合约采用的是先预执行再提交的二阶段处理过程,参看 (https://xuperchain.readthedocs.io/zh/latest/design_documents/XuperModel.html)。

预执行阶段,原链节点会远程访问目标链背书服务,背书服务会预执行该合约并且对合约结果进行签名背书,原链会将该合约的结果和背书信息写进写集中一个特殊的bucket中。

提交阶段,不需要再进行远程调用,原链节点在验证合约时,首先会解析出该特殊的写集中的跨链结果和背书签名信息,以填充合约执行的上下文环境,当执行到只读跨链原语时,原链节点不再进行远程调用,而是验证预执行的背书签名,当签名满足背书策略的要求后,就直接使用写集中的跨链合约结果继续后续的合约逻辑,否则验证失败。

非事务场景跨链

什么是非事务性场景跨链

非事务场景跨链是指跨链行为并不是要求不同链上的交易具备事务性,即不要求不同链上的跨链交易同时生效或同时失败,跨链交易只对当时被跨链的交易状态进行确认,并根据目标交易状态执行交易后续操作。

举例来讲,在A链上发起的交易tx1执行成功后,发起B链上的交易tx2,但tx2执行失败并不会影响tx1,即不会发生tx1回滚。这种存在依赖的交易,但不保证多个交易执行满足事务性的场景,是典型的非事务跨链。

非事务跨链的典型场景比如身份验证、预言机、资产跨链转移等等,在技术上主要强调跨链交易的存在性证明,但对跨链交易的原子性没有要求。

整体方案

非事务跨链典型技术是通过中继实现对目标链数据的数据同步和交易存在性验证,类似BTCRelay。在 XuperChain 中,我们也实现了一套中继机制,通过对目标链的区块头同步,以及梅克尔验证技术,可以做到在原链上直接验证目标链的交易是否存在。

如下图是中继机制的整体结构,主要分为以下两个部分:
  • 链上中继合约(XuperRelayer):部署在原链上的一个智能合约,会保存目标链的所有块头信息,并提供基于梅克尔证明的目标链交易存在性验证。

  • 中继同步进程(Relay Process):在目标链和原链之间,会有一个或多个中继进程,持续监听目标链的最新出块信息,并抽取出块头信息写入

_images/cross_chain_10.png

以一个典型的资产转移的场景为例,首先在B链上发起tx1交易,然后原链上发起tx2交易。tx2交易成功的前提条件是确认tx1交易已经生效。因此整个跨链交易生效流程:

  1. 用户首先在目标链提交tx1交易;

  2. 当tx1交易打包在块中后,中继同步进程会将块头同步到原链上,并保存在链上中继合约中;

  3. 用户在原链上发起资产转移交易tx2,同时在合约参数中填写tx1的信息以及tx1的梅克尔路径;

  4. 原链中的用户合约通过跨合约调用调用链上中继合约,进行梅克尔验证,如果证明tx1确实存在在目标链中,则返回验证成功,否则返回验证失败;

  5. 用户合约在拿到tx1验证成功的结果后,执行后续资产转移操作,并返回结果。

梅克尔证明

在 XuperChain 中,区块头保存了块中所有交易的txid构造出的梅克尔树的树根。下图是一颗区块中的梅克尔树举例,区块中的8个交易是梅克尔树的叶子节点,按照交易顺序,每两个相邻的交易的txid通过SHA256计算出梅克尔树的父节点id,依次向上直到计算出了梅克尔树根。在这个树形结构中,任何一个节点的id都直接和自己的父子节点相关,因此树中任何一个节点数据的修改,都会导致整个梅克尔树的树根变化。

_images/cross_chain_11.png

交易的验证方只需要知道待验证交易tx1的txid,tx1所在块的block1,并知道从tx1所在梅克尔树的叶子节点开始到梅克尔树根的路径上所有兄弟节点的hash值,就可以计算出梅克尔树根。例如我们要验证梅克尔树中绿色节点9Dog的存在性,那么我们同时给出从9Dog到梅克尔树根的路径上的所有兄弟节点数组[1FXq, ec20, 8f74],则从下而上:

1    Hash(1FXq+9Dog) ⇒ 781a
2Hash(ec20+781a) ⇒ 5c71
3Hash(5c71+8f74) ⇒ 6c0a

在得到计算出的根Hash后,我们从链上中继合约中获取之前已经保存的block1对应的区块头中的梅克尔树根,如果也是6c0a则证明验证通过,否则验证不通过。

链上中继合约

链上中继合约是跨链存在性证明的核心,提供对目标链中的区块头同步存储,并通过梅克尔验证技术提供交易查询接口。链上中继合约主要解决三个问题,交易的真实性、及时性证明、区块头分叉管理。

  • 真实性证明:即证明交易确实存在,主要通过Merkle Proof证明交易是否真的存在于链中以及是否在主干上。

  • 及时性证明:即证明交易是否确认,跟目标链采用的共识算法相关。对于同步Byzantine类共识算法,及时性是内置的,只要交易在区块头中,那么交易必定在原链处于上链状态。对于PoW类共识算法,一般要求交易所在块距主干最新高度相差6个块以上。

  • 区块头分叉管理:对同步的区块头按照树形结构维护依赖关系,并根据目标链的主干选择算法即使调整目标链的主干区块状态。

目前的跨链实现支持了对 XuperChain 同构链的链上中继合约,代码位于 https://github.com/xuperchain/xuperchain/tree/master/core/contractsdk/cpp/example/xuper_relayer 目录中。主要实现了对XPoS共识的区块头分叉管理和存在性证明,目前该功能仍处于实验状态,后续版本中会持续增加新的共识和异构链的支持。

合约主要接口如下:

接口名

参数

说明

initialize

合约初始化函数

initAnchorBlockHe ader

blockHeader: 区块头数据

初始化锚点区块头,锚点区块头一般是初始化后写入的第一个区块头,目 标链不一定需要从创世块开始同步,因此有了锚点区块的概念。

putBlockHeader

blockHeader: 区块头数据

写入一个新区块头信息,会自动验证区块头数据是否正确并进行分叉管理

verifyTx

blockid: 待验证交易的区块id txid:待验证的交易id proofPath:待验证交易的梅克尔路 径上的兄弟节点hash txIndex:待验证交易在区块所有交 易中的序号

交易验证接口,通过梅克尔证明验证他链上的交易id是否真实有效

printBlockHeader

blockid: 区块id

打印指定区块id的区块头信息

中继同步进程

中继同步进程相对简单,主要通过对目标链的出块监听,将最新区块中的区块头信息抽取出来,并同步到原链中。 中继同步进程的代码位于https://github.com/xuperchain/xuperchain/tree/master/core/cmd/relayer。

事务场景跨链

可信账本

背景

在大数据和人工智能时代,数据的广泛链接和应用导致隐私问题日益凸显。数据流通可以产生巨大的价值,而阻碍数据流通的主要因素就是数据隐私问题。保护数据安全既是对像百度这样的科技公司的要求,也是行业的底线。

为实现数据流通过程中的隐私保护, XuperChain 可信账本采用了Intel SGX和同态加密等多种技术,实现了数据隐私计算、数据权限管理和数据存证溯源功能。

名词解释

可信账本:基于百度 XuperChain 和Mesatee技术,支持合约数据加密存储及链上密文运算等功能。

TEE:可信执行环境(TEE)是CPU的安全区域,它可以保护在其内部加载的代码和数据的机密性与完整性。

SGX:Software Guard Extensions(SGX)是Intel推出的基于Intel CPU的硬件安全机制。

Mesatee:Memory Safe TEE(Mesatee)是百度基于Intel SGX设计的内存安全的可信安全计算服务框架。

架构设计

下图是可信账本的系统架构设计

_images/trusted_ledger.png

TEESDK是与TEE服务请求的入口,将编译为动态链接库被 XuperChain 调用,实现链上的隐私计算。

可信账本目前支持数据加密存储、数据权限管理、秘钥托管和基本的密文计算功能,此部分代码暂未开源。

重要接口和数据结构

TEESDK

// 提交任务到TEE服务,返回计算结果
func (s *TEEClient) Submit(method string, cipher string) (string, error)

Xuperchain

// contractSDK
// TEE隐私计算接口,供合约调用
bool binary_ops(const std::string op, const Operand &left_op,
                                const Operand &right_op, std::map<std::string, std::string> *result)
bool authorize(const AuthInfo &auth, std::map<std::string, std::string> *result)

// contract/teevm
// 解析合约调用为可信账本调用
func (tf *TrustFunctionResolver) ResolveFunc(module, name string) (interface{}, bool)
// 调用TEESDK插件执行链上隐私计算
func (tf *TrustFunctionResolver) tfcall(ctx exec.Context, inptr, inlen,
                                                                                                                                                                outpptr, outlenptr uint32) uint32

// 可信账本配置,要设置TEESDK插件地址及相关配置地址
type TEEConfig struct {
   Enable     bool   `yaml:"enable"`     // enable: on or off to enable private ledger
   PluginPath string `yaml:"pluginPath"` // path to dynamic library
   ConfigPath string `yaml:"configPath"` // config path for the dynamic
}

应用场景

秘钥托管

利用TEE托管私钥,保证私钥无法被外部访问。同时支持利用SGX的sealing技术进行私钥的持久化存储。

链上隐私计算

可信账本可以实现链上密文存储和计算。例如, 姚氏百万富翁问题, 安全ID求交等。在政务数据共享领域,经常有数据安全交换的需求,希望实现所谓的”可以分享数据,但是不能篡改数据“,本质上就是如何保证数据所有权的前提下,挖掘数据的价值,放心地让其他方使用自身数据;

可信随机数

利用TEE可以生成可信随机数,联盟链节点之间可利用可信随机数进行共识。

合约账号

访问控制列表(ACL)

如果把合约账号当作一家股份制公司,那么ACL便是公司股东投票的机制,ACL可以规定合约账号背后各“股东”账号的权重,只有当“股东”签名的权重之和大于设定阈值时操作才会有效地进行。

XuperChain 中ACL配置格式如下:

 1{
 2    "pm": {
 3        "rule": 1,              # rule=1表示签名阈值策略,rule=2表示AKSet签名策略
 4        "acceptValue": 0.6      # acceptValue为签名需达到的阈值
 5    },
 6    "aksWeight": {              # aksWeight里规定了每个地址对应账号签名的权重
 7        "AK1": 0.3,
 8        "AK2": 0.3
 9    }
10}

了解了访问控制列表的概念,下面我们就来演示一下如何创建一个合约账号

合约账号创建

Xchain的客户端工具提供了新建账号的功能,基本用法如下:

1xchain-cli account new --desc account.des

这里的 account.des 就是创建账号所需要的配置了,内容如下:

1{
2    "module_name": "xkernel",
3    "method_name": "NewAccount",
4    "args" : {
5        "account_name": "1111111111111111",  # 说明:账号名称是16位数字组成的字符串
6        # acl 中的内容注意转义
7        "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 0.6},\"aksWeight\": {\"AK1\": 0.3,\"AK2\": 0.3}}"
8    }
9}

命令运行后就会调用xchain的系统合约功能 NewAccount 创建一个名为 XC1111111111111111@xuper (如果链名字为xuper)的账号

新建合约账号

新建合约账号

除了上述方法,我们还提供了一个比较简易的方式来创建合约账号,命令如下:

1xchain-cli account new --account 1111111111111111 # 16位数字组成的字符串

上述命令也会创建一个名为 XC1111111111111111@xuper 的账号,由于我们没有制定ACL的具体内容,其ACL被赋值为默认状态,即背后有权限的账号只有当前节点上默认账号一个(地址默认位于 data/keys/address)

注解

创建合约账号的操作需要提供手续费,需要按照命令行运行结果给出的数值,添加一个不小于它的费用(使用 --fee 参数)

合约账号基本操作

查询账号ACL

XuperChain的客户端工具提供了ACL查询功能,只需如下命令

1xchain-cli acl query --account XC1111111111111111@xuper # account参数为合约账号名称
查询合约账号ACL

查询合约账号ACL

查询账号余额

合约账号查询余额和普通账号类似,只是命令行的参数有些许变化

1./xchain-cli account balance XC1111111111111111@xuper -H 127.0.0.1:37101

使用此命令即可查询`XC1111111111111111@xuper`的余额

查询合约账号

查询合约账号余额

修改账号ACL

修改ACL的配置和创建账号的配置类似

1{
2    "module_name": "xkernel",
3    "method_name": "SetAccountAcl",  # 这里的方法有了变更
4    "args" : {
5        "account_name": "XC1111111111111111@xuper", #account_name在此处一定要写成XC.....@xuper的形式
6        # acl字段为要修改成的新ACL
7        "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 0.6},\"aksWeight\": {\"AK3\": 0.3,\"AK4\": 0.3}}"
8    }
9}

修改ACL的操作,需要符合当前ACL中设置的规则,即需要具有足够权重的账号签名。

需要新建文件添加需要签名的地址,默认acl文件路径是:./data/acl/addrs 。 ./data/acl/addrs 示例: XC9999999999999999@xuper/9LArZSMrrRorV7T6h5T32PVUrmdcYLbug XC9999999999999999@xuper/gLAdZSMtkforV7T6h5TA14VUrfdcYLbuy

我们首先生成一个多重签名的交易

1./xchain-cli multisig gen --desc acl_new.json --from XC1111111111111111@xuper
生成多重签名交易

成多重签名交易

这样就会生成一个默认为`tx.out`的文件,之后使用原ACL中的账号对其进行签名

1./xchain-cli multisig sign --keys data/account/AK1 --output AK1.sign
2./xchain-cli multisig sign --keys data/account/AK2 --output AK2.sign
签名交易

签名交易

最后把生成的`tx.out`发出去

1./xchain-cli multisig send --tx tx.out AK1.sign,AK2.sign AK1.sign,AK2.sign
发送交易

发送交易

至此便完成了ACL的修改

多节点部署

在阅读本节前,请先阅读“快速入门”,当中介绍了创建单节点网络的创建,在该基础上,搭建一个SINGLE共识的多节点网络,其他节点只要新增p2p网络bootNodes配置即可。如果你想搭建一个TDPoS共识的链,仅需要修改创世块参数中“genesis_consensus”配置参数即可。下面将详细介绍相关操作步骤。

p2p网络配置

我们以搭建3个节点的网络为例来说明(其实搭建更多节点的原理是一致的),首先需要有一个节点作为种子节点“bootNode”,其他节点通过该种子节点的链接地址“netURL”加入网络:

对于bootNode节点,我们需要先获取它的netURL,具体命令如下:

1./xchain-cli netURL get -H 127.0.0.1:37101

如果不是以默认配置启动的,我们需要先生成它的netURL,然后再获取

1./xchain-cli netURL gen         #重新生成本地节点的网络私钥
2./xchain-cli netURL preview     #显示本地节点的p2p地址

通过上述操作,我们会得到该节点的链接地址: /ip4/127.0.0.1/tcp/47101/p2p/QmVxeNubpg1ZQjQT8W5yZC9fD7ZB1ViArwvyGUB53sqf8e

对其他的节点,我们需要修改其服务配置 conf/xchain.yaml 中p2p一节

1p2p:
2    module: p2pv2
3    // port是节点p2p网络监听的默认端口,如果在一台机器上部署注意端口配置不要冲突,
4    // node1配置的是47101,node2和node3可以分别设置为47102和47103
5    port: 47102
6    // 节点加入网络所连接的种子节点的链接信息,
7    bootNodes:
8    - "/ip4/127.0.0.1/tcp/47101/p2p/QmVxeNubpg1ZQjQT8W5yZC9fD7ZB1ViArwvyGUB53sqf8e"

注解

需要注意的是,如果其他节点分布在不同的机器之上,需要把种子节点的netURL中的127.0.0.1改为种子节点的实际物理ip;

修改完其他节点的配置后,即可在每一个节点使用相同配置创建链,然后分别启动bootNode和其他节点,即完成了多节点环境的部署

这里可以使用系统状态的命令检查环境是否正常

1./xchain-cli status -H 127.0.0.1:37101

通过变更 -H 参数,查看每个节点的状态,若所有节点高度都是一致变化的,则证明环境部署成功

搭建TDPoS共识网络

XuperChain系统支持可插拔共识,通过修改创世块的参数,可以创建一个以TDPoS为共识的链。

下面创世块配置(一般位于 core/data/config/xuper.json)和单节点创世块配置的区别在于创世共识参数genesis_consensus的config配置,各个配置参数详解配置说明如下所示:

 1{
 2    "version" : "1",
 3    "predistribution":[
 4        {
 5            "address" : "mahtKhdV5SZP4FveEBzX7j6FgUGfBS9om",
 6            "quota" : "100000000000000000000"
 7        }
 8    ],
 9    "maxblocksize" : "128",
10    "award" : "1000000",
11    "decimals" : "8",
12    "award_decay": {
13        "height_gap": 31536000,
14        "ratio": 1
15    },
16    "genesis_consensus": {
17        "name": "tdpos",
18        "config": {
19            # tdpos共识初始时间,声明tdpos共识的起始时间戳,建议设置为一个刚过去不旧的时间戳
20            "timestamp": "1548123921000000000",
21            # 每一轮选举出的矿工数,如果某一轮的投票不足以选出足够的矿工数则默认复用前一轮的矿工
22            "proposer_num":"3",
23            # 每个矿工连续出块的出块间隔
24            "period":"3000",
25            # 每一轮内切换矿工时的时间间隔,需要为period的整数倍
26            "alternate_interval":"6000",
27            # 切换轮时的出块间隔,即下一轮第一个矿工出第一个块距离上一轮矿工出最后一个块的时间间隔,需要为period的整数配
28            "term_interval":"9000",
29            # 每一轮内每个矿工轮值任期内连续出块的个数
30            "block_num":"200",
31            # 为被提名的候选人投票时,每一票单价,即一票等于多少Xuper
32            "vote_unit_price":"1",
33            # 指定第一轮初始矿工,矿工个数需要符合proposer_num指定的个数,所指定的初始矿工需要在网络中存在,不然系统轮到该节点出块时会没有节点出块
34            "init_proposer": {
35                "1":["RU7Qv3CrecW5waKc1ZWYnEuTdJNjHc43u","XpQXiBNo1eHRQpD9UbzBisTPXojpyzkxn","SDCBba3GVYU7s2VYQVrhMGLet6bobNzbM"]
36            }
37        }
38    }
39}

修改完每个节点的创世块配置后,需要确认各节点的 data/blockchain 目录下内容为空。然后重新按照上一节的步骤,在各节点上创建链,启动所有节点,即完成TDPoS共识的环境部署。

选举TDPOS候选人

选举候选人包括提名和投票两个环节,具体操作和 发起提案 类似

提名候选人

首先需要准备一个提名的配置,json格式

 1{
 2    "module": "tdpos",
 3    "method": "nominate_candidate",
 4    "args": {
 5        # 此字段为要提名的候选人的地址
 6        "candidate": "kJFcY3FjmNU8xk6cRzHvTPmChUQ3SBGVE",
 7        # 此字段为候选人节点的netURL
 8        "neturl": "/ip4/10.0.4.6/tcp/47101/p2p/QmRmdBSyHpKPvhsvmyys8f1jDM4x1S9cbCwZaBMqMKjwhV"
 9    }
10}

然后将这个json文件(假定文件名为nominate.json)通过多重签名命令发出。提名候选人的操作需要提名者和被提名候选人的两个签名(如果是自己提名自己,那么就只需要一个签名了)

首先要准备一个需收集签名的地址列表,可以参考 发起多重签名交易

1YDYBchKWXpG7HSkHy4YoyzTJnd3hTFBgG
2kJFcY3FjmNU8xk6cRzHvTPmChUQ3SBGVE

然后生成一个提名交易, XuperChain 上进行候选人提名需要冻结大于链上资产总量的十万分之一的utxo(当前的总资产可以通过 status查询命令 查看结果的utxoTotal字段)

1# 这里转账的目标地址可以任意,转给自己也可以,注意冻结参数为-1,表示永久冻结
2./xchain-cli multisig gen --to=dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN --desc=nominate.json --amount=10000000000000000 --frozen -1 -A addr_list --output nominate.tx

命令会生成交易内容,然后对其进行签名

1# 提名者签名
2./xchain-cli multisig sign --tx nominate.tx --output nominate.sign --keys path/to/nominate
3# 候选人签名
4./xchain-cli multisig sign --tx nominate.tx --output candidate.sign --keys path/to/candidate

然后将生成的交易发送

1# send 后面的签名有两个参数,第一个为发起方的签名,第二个为需要收集的签名(列表逗号分隔)
2./xchain-cli multisig send --tx nominate.tx nominate.sign nominate.sign,candidate.sign

发送交易会返回一个txid,这里需要记录下来,后面可能会用到

投票

投票的配置也是一个json格式

1{
2    "module": "tdpos",
3    "method": "vote",
4    "args": {
5        # 提名过的address
6        "candidates":["RU7Qv3CrecW5waKc1ZWYnEuTdJNjHc43u"]
7    }
8}

同样使用转账的命令发出,注意投票的utxo需要永久冻结。

1# 同样,转账目标地址可任意填写,转给自己也可以
2./xchain-cli transfer --to=dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN --desc=vote.json --amount=1 --frozen -1

根据共识算法配置的候选人集合大小(上面配置中的"proposer_num"字段,假设为n),每一轮出块结束后系统都会查看被提名的候选人数目是否达到n,如果没有达到则继续按上一轮的顺序出块;如果达到n则会统计得票靠前的n个节点为新一轮的矿工集合

注解

细心的读者可能已经发现这些配置文件的json key 都类似,可以参考 xuperchain/core/contract/contract.go 中TxDesc的定义

撤销提名 && 撤销投票

Json格式的配置又来了

1{
2    "module":"proposal",
3    "method": "Thaw",
4    "args" : {
5        # 此处为提名或者投票时的txid,且address与提名或者投票时需要相同
6        "txid":"02cd75a721f2589a3ff6768b49650b46fa0b042f970df935b4d28a15aa19e49a"
7    }
8}

然后使用转账操作发出(注意address一致),撤销提名/投票后,当时被冻结的资产会解冻,可以继续使用了

1./xchain-cli transfer --to=dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN --desc=thaw.json --amount=1

TDPOS结果查询

XuperChain 的客户端提供了这一功能

  1. 查询候选人信息

./xchain-cli tdpos query-candidates
  1. 查看某一轮的出块顺序

./xchain-cli tdpos query-checkResult -t=30
  1. 查询提名信息:某地址发起提名的记录

./xchain-cli tdpos query-nominate-records -a=dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN
  1. 被提名查询:某个候选人被提名的记录

./xchain-cli tdpos query-nominee-record -a=RU7Qv3CrecW5waKc1ZWYnEuTdJNjHc43u
  1. 某选民的有效投票记录

./xchain-cli tdpos query-vote-records -a=dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN
  1. 某候选人被投票记录

./xchain-cli tdpos query-voted-records -a=RU7Qv3CrecW5waKc1ZWYnEuTdJNjHc43u

各种查询命令的详细参数列表可以通过 ./xchain-cli tdpos -h 查询

常见问题

  • 端口冲突:注意如果在一台机器上部署多个节点,各个节点的RPC监听端口以及p2p监听端口都需要设置地不相同,避免冲突;

  • 节点公私钥和节点netUrl冲突:注意网络中不同节点./data/keys下的文件和./data/netkeys下的内容都应该不一样,这两个文件夹是节点在网络中的唯一标识,每个节点需要独自生成,否则网络启动异常;

  • 启动时链接bootNodes节点失败:注意要先将bootNodes节点启动,再起动其他节点,否则会因为加入网络失败而启动失败;

  • 遇到The gas you cousume is: XXXX, You need add fee 通过加--fee XXXX 参数附加资源;

创建合约

XuperChain 支持丰富的智能合约开发语言,比如go,Solitidy,C++,Java等。

阅读本节前,请先确保完成 XuperChain环境部署

编写合约

可以根据合约示例代码,编写自己的合约

部署 wasm合约

  1. 编译合约

    对于C++合约,已提供编译脚本,位于 contractsdk/cpp/build.sh。

    需要注意的是,脚本依赖从hub.baidubce.com拉取的docker镜像,请在编译前确认docker相关环境是可用的

  2. 部署合约

    部署合约的操作需要由合约账号完成,部署操作同样需要支付手续费,操作前需要确保合约账号下有足够的余额

    $ ./xchain-cli wasm deploy --account XC1111111111111111@xuper  -a '{"creator":"XC1111111111111111@xuper"}' --cname counter ../core/contractsdk/cpp/build/counter.wasm
    

    运行时会提示手续费的数目,使用 --fee 参数传入即可

  1. 合约调用

    $ ./xchain-cli wasm invoke --method increase -a '{"key":"test"}' counter --fee 100
    The gas you cousume is: 93
    The fee you pay is: 100
    Tx id: 141e4c1fb99566ce4b6ba32fa92af73c0e9857189debf773cf5753d64e1416a7
    
    $ ./xchain-cli wasm query --method get -a '{"key":"test"}' counter
    contract response: 1
    

部署native合约

如果本地搭建 XuperChain 环境,在部署、调用 native 合约之前,请先查看`conf/xchain.yaml` 中native一节,确保native合约功能开启。

 1# 管理native合约的配置
 2native:
 3    enable: true
 4
 5    # docker相关配置
 6    docker:
 7        enable:false
 8        # 合约运行的镜像名字
 9        imageName: "docker.io/centos:7.5.1804"
10        # cpu核数限制,可以为小数
11        cpus: 1
12        # 内存大小限制
13        memory: "1G"
14    # 停止合约的等待秒数,超时强制杀死
15    stopTimeout: 3
  1. 编译合约 - Golang

    编译native合约时,只要保持环境和编译XuperChain源码时一致即可,我们以 contractsdk/go/example 中的 counter 合约为例

    cd contractsdk/go/example/counter
    go build
    
  2. 编译合约 - Java

    我们以contractsdk/java/example中的counter合约为例

    $ cd contractsdk/java/example/counter
    $ mvn package
    
  3. 部署合约

    部署native合约。针对不同语言实现的合约,主要通过 --runtime 字段进行区分

    # 部署golang native合约
    $ ./xchain-cli native deploy --account XC1111111111111111@xuper --fee 15587517 --runtime go -a '{"creator":"XC1111111111111111@xuper"}'   --cname golangcounter ../core/contractsdk/go/example/counter/counter
     contract response: ok
     The gas you cousume is: 14311874
     The fee you pay is: 15587517
     Tx id: af0d46f6df2edba4d9d9d07e1db457e5267274b1c9fe0611bb994c0aa7931933
    
    # 部署java native合约
    $ ./xchain-cli native deploy --account XC1111111111111111@xuper --fee 15587517 --runtime java   --cname javacounter ../core/contractsdk/java/example/counter/target/counter-0.1.0-jar-with-dependencies.jar
     The gas you cousume is: 14311876
     The fee you pay is: 15587517
     Tx id: 875d2c9129973a1c64811d7a5a55ca80743102abc30d19f012656fa52ee0f4f7
    
  4. 合约调用

    针对不同语言实现的 native合约,调用方式相同。通过合约名直接发起合约调用和查询

    # 调用golang native合约,Increase方法,golangcounter为合约名
    $ ./xchain-cli native invoke --method Increase -a '{"key":"test"}' golangcounter
    
    # 调用golang native合约,Get方法,golangcounter为合约名
    $ ./xchain-cli native query --method Get -a '{"key":"test"}' golangcounter
    contract response: 1
    
    # 调用java native合约,increase方法,javacounter为合约名
    $ ./xchain-cli native invoke --method increase -a '{"key":"test"}' javacounter --fee 10
    
    # 调用java native合约,get方法,javacounter为合约名
    $ ./xchain-cli native query --method get -a '{"key":"test"}' javacounter
      contract response: 1
    

部署solidity合约

如果本地搭建 XuperChain 环境,在部署、调用solidity合约之前,请先查看`conf/xchain.yaml` 中evm一节,确保evm合约功能开启。

1# evm合约配置
2evm:
3    driver: "evm"
4    enable: true
  1. 编译合约 - Solidity

    使用 solc 编译solidity合约。安装 solc 编译器,请参见**https://solidity-cn.readthedocs.io/zh/latest/installing-solidity.html**。

    solc --version
    // solc, the solidity compiler commandline interface
    // Version: 0.5.9+commit.c68bc34e.Darwin.appleclang
    

    我们以contractsdk/evm/example中的counter合约为例

    cd core/contractsdk/evm/example/counter
    // 通过solc编译合约源码
    solc --bin --abi Counter.sol -o .
    // 合约二进制文件和abi文件分别存放在当前目录下,Counter.bin和Counter.abi。
    
  2. 部署合约

    部署solidity合约。

    ./xchain-cli evm deploy --account XC1111111111111111@xuper --cname counterevm  --fee 5200000 ../core/contractsdk/evm/example/counter/Counter.bin --abi ../core/contractsdk/evm/example/counter/Counter.abi
     contract response: ok
     The gas you cousume is: 1789
     The fee you pay is: 22787517
     Tx id: 78469246d86a92ad47e5c15991a55978075902809346e48533e09a8eb0e3a7e4
    
    • --abi Counter.abi :表示部署需要使用的abi文件,用于合约方法参数编解码

    • ``-a ``:如果合约需要构造函数,通过-a进行指定。与c++、golang等合约的部署和调用方式相同。

  1. 合约调用

    调用solidity合约。通过合约名直接发起合约调用和查询。

    # 调用solidity合约,increase方法,counterevm为合约名
    $ ./xchain-cli evm invoke --method increase -a '{"key":"test"}' counterevm --fee 22787517
    
    # 调用solidity合约,get方法,counterevm为合约名
    $ ./xchain-cli evm query --method get -a '{"key":"test"}' counterevm
    # 调用结果,其中0表示返回值的次序,1为返回值
    # key,value: 0 1
    
  2. XuperChain 账户与EVM账户地址转换

    XuperChain 有普通地址、合约账户以及合约名,这三类账户在EVM运行时需要转换为以太坊的地址类型(16进制编码字符串,形如0x1234567890abcdef1234567890abcdef12345678格式)。 XuperChain 提供了上述三个地址与EVM地址类型转换工具。

    # xchain合约账户地址转evm地址,contract-account表示 XuperChain 合约账户
    ./xchain-cli evm addr-trans -t x2e -f XC1111111111111113@xuper
    result, 3131313231313131313131313131313131313133    contract-account
    
    # evm地址转xchain合约账户,contract-account表示 XuperChain 合约账户
    ./xchain-cli evm addr-trans -t e2x -f 3131313231313131313131313131313131313133
    result, XC1111111111111113@xuper     contract-account
    
    # xchain普通账户地址转evm地址,xchain表示 XuperChain 普通账户
    ./xchain-cli evm addr-trans -t e2x -f 93F86A462A3174C7AD1281BCF400A9F18D244E06
    result, dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN   xchain
    
    # xchain普通账户地址转evm地址,xchain表示 XuperChain 普通账户
    ./xchain-cli evm addr-trans -t x2e -f dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN
    result, 93F86A462A3174C7AD1281BCF400A9F18D244E06   xchain
    
    # xchain合约名地址转evm地址,contract-name表示 XuperChain 合约名
    ./xchain-cli evm addr-trans -t x2e -f storagedata11
    result, 313131312D2D2D73746F72616765646174613131   contract-name
    
    # evm地址转xchain合约名,contract-name表示 XuperChain 合约名
    ./xchain-cli evm addr-trans -t e2x -f 313131312D2D2D73746F72616765646174613131
    result, storagedata11   contract-name
    
    • x2e :表示 XuperChain 地址转换为EVM地址

    • e2x :表示EVM地址转换为 XuperChain 地址。

合约升级

XuperChain 支持合约升级,在使用合约升级功能之前需要修改 conf/xchain.yaml,开启合约升级功能

# 合约通用配置
contract:
    enableUpgrade: true
合约升级与合约部署的命令十分类似,区别在于
  1. 不需要指定 runtime

  2. 不需要指定初始化参数

以升级 wasm 的 counter 合约为例

./xchain-cli wasm upgrade --account XC1111111111111111@xuper --cname counter ../core/contractsdk/cpp/build/counter.wasm

设置合约方法的ACL

  1. 准备desc文件setMethodACL.desc

    {
        "module_name": "xkernel",
        "method_name": "SetMethodAcl",
        "args" : {
            "contract_name": "counter",
            "method_name": "increase",
            "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 1.0},\"aksWeight\": {\"UU4kyZcQinAMsBSPRLUA34ebXrfZtB4Z8\": 1}}"
            }
    }
    

    参数说明:

    • module_name: 模块名称,用固定值xkernel

    • method_name :方法名称,用固定值SetMethodAcl

    • contract_name:合约名称

    • method_name:合约方法名称

    • acl:合约方法的acl

  2. 设置合约方法ACL

    设置合约方法ACL的操作,需符合合约账号的ACL,在3.2节,使用 XC1111111111111111@xuper 部署的counter合约,合约账号ACL里 只有1个AK,所以在data/acl/addrs中添加1行,如果合约账号ACL里有多个AK,则填写多行。

    echo "XC1111111111111111@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN" > data/acl/addrs
    

    执行如下命令,设置ACL:

    ./xchain-cli multisig gen --desc ./setMethodACL.desc --fee 1 -H 127.0.0.1:37101
    ./xchain-cli multisig sign --output sign.out
    ./xchain-cli multisig send sign.out sign.out -H 127.0.0.1:37101
    
  3. 查看合约方法ACL

    [work@]$ deploy-env -> ./xchain-cli acl query --contract counter --method increase -H :37101
    # 执行结果
    # {
    #   "pm": {
    #     "rule": 1,
    #     "acceptValue": 1
    #   },
    #   "aksWeight": {
    #     "UU4kyZcQinAMsBSPRLUA34ebXrfZtB4Z8": 1
    #   }
    # }
    

发起提案

XuperChain中有多种提案-投票操作场景,但原理都是一致的,我们以通过提案更改共识算法(single改为tdpos)来介绍具体的操作流程

部署一个Single共识的 XuperChain 环境已经在“快速入门”一节有介绍

首先我们需要准备一个tdpos共识的配置,包括出块时间、代表名单等(假设文件名为proposal.json)

 1{
 2    "module": "proposal",
 3    "method": "Propose",
 4    "args" : {
 5        "min_vote_percent": 51,                 # 生效的资源比例
 6        "stop_vote_height": 800                 # 计票截至的高度
 7    },
 8    "trigger": {
 9        "height": 1000,                         # 期望生效的高度
10        "module": "consensus",
11        "method": "update_consensus",
12        "args" : {
13            "name": "tdpos",
14            "config": {
15                "version":"2",
16                "proposer_num":"2",             # 代表个数
17                "period":"3000",
18                "alternate_interval":"6000",
19                "term_interval":"9000",
20                "block_num":"20",
21                "vote_unit_price":"1",
22                "init_proposer": {              # 出块的代表名单
23                    "1":["dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN", "U5SHuTiiSP1JAAHVMknqrm66QXk2VhXsK"]
24                }
25            }
26        }
27    }
28}

需要注意的是当前的区块高度,来设置合理的截至计票高度和生效高度。然后在矿工节点下,执行给自己转账的操作,并在 --desc 参数里传入提案

1./xchain-cli transfer --to dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN --desc proposal.json --amount 100

运行后会得到本次提案的交易id,需要记录下来供投票使用

对提案进行投票操作由如下命令执行

1./xchain-cli vote f26d670b695d9fd5da503a34d130ef19e738b35e031b18b70ad4cbbf6dfe2656 --frozen 1100 --amount 100002825031900000000

这里需要注意进行投票的节点需要有矿工账号的密钥对,以及 --frozen 参数的冻结高度大于提案生效的高度。因为最终通过的规则是投票资源大于总资源的51%,所以需要初始token量最多的矿工账号来进行投票,并保证token数符合要求。

如此进行后,等到区块出到设定的生效高度,便完成了提案-投票的整个流程。其他场景的提案机制都是类似的,仅是json配置文件不同而已。

配置变更

配置多盘存储

由区块链本身特点决定的,区块链服务启动后需要的存储空间会逐渐变多,即使交易不频繁,每到固定出块时间也会占用少量的存储空间。XuperChain提供了一种可以将存储路径配置在多个磁盘上的功能,来更好地支持单个磁盘存储空间不充裕的场景。

位于代码目录下的 core/conf/xchain.yaml ,包含了大部分XuperChain服务启动的配置项,其中有磁盘相关的章节

1# 数据存储路径
2datapath: ./data/blockchain
3
4# 多盘存储的路径
5datapathOthers:
6    - /ssd1/blockchain
7    - /ssd2/blockchain
8    - /ssd3/blockchain

只需将“多盘存储路径”部分去掉注释,并在conf/plugins.conf更改kv插件配置,便可以灵活配置多个数据存储位置。 更换kv插件的具体方法为:修改conf/plugins.conf文件,将kv项下default中的path改为plugins/kv/kv-ldb-multi.so.1.0.0即可。

替换扩展插件

XuperChain采用了动态链接库的方式实现了加密、共识算法等扩展插件,可以根据实际使用场景进行替换。

插件目录位于 plugins ,对应的配置文件为 conf/plugins.conf (json格式)

 1{
 2    "crypto":[{
 3        "subtype": "default",
 4        "path": "plugins/crypto/crypto-default.so.1.0.0",
 5        "version": "1.0.0",
 6        "ondemand": false
 7    },{
 8        "subtype": "schnorr",
 9        "path": "plugins/crypto/crypto-schnorr.so.1.0.0",
10        "version": "1.0.0",
11        "ondemand": false
12    }]
13    # ......
14}

需要替换插件则修改对应的 .so 文件路径即可

使用平行链与群组

创建平行链

现在 XuperChain 中创建平行链的方式是:发起一个系统智能合约,发到xuper链。

当前xchain.yaml有两个配置项:

1Kernel:
2    # minNewChainAmount 设置创建平行链时最少要转多少utxo(门槛)到同链名的address
3    minNewChainAmount: “100”
4    # newChainWhiteList 有权创建平行链的address白名单
5    newChainWhiteList:
6        - dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN: true

创建平行链的json文件(模版),如下:

1{
2    "Module": "kernel",
3    "Method": "CreateBlockChain",
4    "Args": {
5        "name": "HelloChain",
6        "data": "{\"version\": \"1\", \"consensus\": {\"miner\":\"dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN\", \"type\":\"single\"},\"predistribution\":[{\"address\": \"dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN\",\"quota\": \"1000000000000000\"}],\"maxblocksize\": \"128\",\"period\": \"3000\",\"award\": \"1000000\"}"
7    }
8}

使用如下指令即可创建平行链(需要在主链操作,转了100个主链的token到平行链同名的address,作为创建链的代价):

1./xchain-cli transfer --to HelloChain --amount 100 --desc createChain.json

获取group_chain合约

XuperChain 提供了默认的群组合约(group_chain)的实现,路径为 core/contractsdk/cpp/example/group_chain.cc 。在 core/contractsdk/cpp 目录下执行 sh build.sh 即可编译生成 group_chain.wasm ,即可使用 group_chain.wasm 实现群组合约的部署。

创建群组

如果希望创建的平行链只在自己希望的小范围使用,那么可以参考此节配置群组功能

当前 XuperChain 中创建群组的方式是:在xuper链上部署GroupChain智能合约,将节点白名单加到GroupChain合约中。

在创世块中配置群组合约配置:

1{
2    "group_chain_contract": {
3        "module_name": "wasm",
4        "contract_name": "group_chain",
5        "method_name": "list",
6        "args":{}
7    }
8}

如果需要确保HelloChain具备群组属性,且白名单为<ip1,addr1>,<ip2,addr2>,其他节点不能获取这条平行链的信息,可以按如下操作。 此处: ip1,ip2是指从xchain-cli netURL preview 获取的形如"/ip4/127.0.0.1/tcp/47101/p2p/QmVxeNubpg1ZQjQT8W5yZC9fD7ZB1ViArwvyGUB53sqf8e"的IP地址; add1,add2 是指节点的公钥地址:data/keys/addresss

step1: 在xuper链部署GroupChain合约

1# 需要使用合约账号,部署编译好的合约文件
2./xchain-cli wasm deploy --account XC1111111111111111@xuper --cname group_chain ./group_chain.wasm --fee xxx

step2: 调用GroupChain合约的AddNode方法将<ip1,add1>,<ip2,add2>加入白名单

1./xchain-cli wasm invoke group_chain --method addNode -a '{"bcname":"HelloChain", "ip":"ip1", "address":"addr1"}'
2./xchain-cli wasm invoke group_chain --method addNode -a '{"bcname":"HelloChain", "ip":"ip2", "address":"addr2"}'

step3: 调用GroupChain合约的AddChain确保HelloChain具备群组特性

1./xchain-cli wasm invoke group_chain --method addChain -a '{"bcname":"HelloChain"}'

至此即完成了群组的设置,只有<ip1,add1>,<ip2,add2>两个节点可以获取平行链HelloChain的内容了。

使用事件订阅功能

在使用 XuperChain 的过程中,可能会遇到一些异步的情况:比如执行合约的时候,构造的交易是否合法我们可以实时获知,但具体什么时候真正的被节点打包上链就不一样了。当然还有其他类似的场景,针对这种问题,我们引入了事件订阅机制。

事件订阅的接口

订阅的接口十分简单,只有Subscribe一个

1service EventService {
2    rpc Subscribe (SubscribeRequest) returns (stream Event);
3}

其中订阅接口的SubscribeRequest格式如下:

1message SubscribeRequest {
2    SubscribeType type = 1;
3    bytes filter = 2;
4}
5
6enum SubscribeType {
7    // 区块事件,payload为BlockFilter
8    BLOCK = 0;
9}

请求里的filter用来设置事件过滤参数,是一段序列化的proto对象,因为订阅不同内容需要的参数不尽相同

BLOCK 事件的过滤参数如下:

 1message BlockFilter {
 2    string bcname = 1;
 3    BlockRange range = 2;
 4    bool exclude_tx = 3;
 5    bool exclude_tx_event = 4;
 6    string contract = 10;
 7    string event_name = 11;
 8    string initiator = 12;
 9    string auth_require = 13;
10    string from_addr = 14;
11    string to_addr = 15;
12}
13
14message BlockRange {
15    string start = 1;
16    string end = 2;
17}

其中各个字段的说明如下:

  • bcname 链名,必填字段

  • range 指定起始订阅位置和结束位置,如果没有指定则默认从当前最新区块开始,持续订阅。

  • exclude_tx 是否去掉FilteredTransaction数据

  • exclude_tx_event 是否去掉ContractEvent数据

  • contract 匹配合约名字,为空的话匹配所有合约

  • event_name 匹配合约事件名字,为空的话匹配所有合约事件name

  • initiator 匹配交易发起者地址,为空的话匹配所有交易发起者

  • auth_require 匹配交易的auth_require中的任何一个地址,为空匹配所有

  • from_addr 匹配转账发起者地址,为空的话匹配所有转账发起者

  • to_addr 匹配转账接受者地址,为空的匹配所有转账接受者

BlockRange 字段意义:

  • 如果 start_numend_num 都为空,则表示从当前最新区块开始,持续订阅最新区块。

  • 如果 start_num 为空, end_num 不为空,则表示从当前最新区块开始,订阅到指定区块,如果``end_num``小与当前区块则什么也不做。

  • 如果 start_num 不为空, end_num 为空,则从 start_num 开始持续订阅。

  • 如果 start_numend_num 都不为空,按照指定区块范围订阅,左闭右开。

注解

需要注意的是过滤字段都是正则表达式,如果需要全匹配名字为 counter 的合约,contract 字段需要为 ^counter$ , 不能为 counter ,这么写会匹配到名为 counter1 的合约。

订阅返回的内容格式均为Event,事件的详细内容会放在payload里

1message Event {
2    bytes payload = 1;
3}

订阅 BLOCK 事件时,填充如下内容:

 1message ContractEvent {
 2    string contract = 1;
 3    string name = 2;
 4    bytes body = 3;
 5}
 6
 7message FilteredTransaction {
 8    string txid = 1;
 9    repeated ContractEvent events = 2;
10}
11
12message FilteredBlock {
13    string bcname = 1;
14    string blockid = 2;
15    int64 block_height = 3;
16    repeated FilteredTransaction txs = 4;
17}

当然,订阅RPC接口断开的时候,订阅行为也会停止

使用事件订阅

使用前,请检查xchain的配置conf/xchain.yaml,确保有如下配置:

1# 事件订阅相关配置
2event:
3    enable: true
4    # 每个ip的最大订阅连接数,为0的话不限连接数
5    addrMaxConn: 5

使用命令行订阅事件

xchain-cliwatch 指令可以用来监听事件,命令行参数的说明如下:

  • -f, --filter 过滤器字段,JSON格式的,字段解释见 message BlockFilter

  • --oneline 是否将事件打印在一行,方便命令行解析

  • --skip-empty-tx 默认watch命令会打印所有的block,即使block里面没有交易,这么做是为了方面做断点记录,--skip-empty-tx 参数可以不打印不包含交易的block

如下是一些例子

  1. 订阅所有的新块

1./xchain-cli watch
  1. 订阅名字为 counter 的合约

1./xchain-cli watch -f '{"contract":"^counter$"}'
  1. 订阅 counter 合约的 increase 合约事件

1./xchain-cli watch -f '{"contract":"^counter$", "event_name":"^increase$"}'
  1. 订阅区块高度从100开始的事件(断点续传)

1./xchain-cli watch -f '{"range":{"start":"100"}}'
  1. 订阅区块高度区间为[100, 200)的事件

1./xchain-cli watch -f '{"range":{"start":"100", "end":"200"}}'

只读跨链场景使用文档

跨链的背景知识可以参考 XuperChain 的技术文档 ` XuperChain 跨链技术 <../design_documents/cross_chain.html>`_ ,这里介绍一下 XuperChain 跨链只读使用说明。

B网络搭建

搭建B网络,设置背书配置为true,部署被调用合约B,例如counter:

11. xchain.yaml增加配置: enableXEndorser: true
22. 配置节点背书公私钥,./data/endorser/keys 目录下,增加背书地址及公私钥;

A网络搭建

搭建A网络,部署跨链寻址合约,部署测试合约A用于查看B网络的counter合约;

部署查询合约A

查询合约可参考:core/contractsdk/cpp/example/cross_query_demo/src/main.cc

1DEFINE_METHOD(Hello, cross_query) {
2        xchain::Context* ctx = self.context();
3        xchain::Response response;
4        // 合约SDK 增加cross query方法
5        ctx->cross_query("xuper://mainnet.xuper?module=wasm&bcname=xuper&contract_name=counter&method_name=get", {{"key", "zq"}}, &response);
6        *ctx->mutable_response() = response;
7}

部署跨链寻址合约

XuperChain 提供了默认的寻址合约(crossQueryNaming)的实现,路径为core/contractsdk/cpp/example/naming/src/naming.cc。在 core/contractsdk/cpp 目录下执行 sh build.sh 即可编译生成 naming.wasm ,即可使用 naming.wasm实现寻址合约的部署。

创建合约账户

./xchain-cli account new --account 1111111111111111 --fee 1000
./xchain-cli transfer --to XC1111111111111111@xuper --amount 9999999999999999    ##给合约账号充钱

部署wasm

#注意:合约名必须为crossQueryNaming

./xchain-cli wasm deploy -n crossQueryNaming ./naming.wasm  --account XC1111111111111111@xuper --fee xxxxx

注册链名

./xchain-cli wasm invoke crossQueryNaming --method RegisterChain -a '{"name":"mainnet.xuper","type":"xuper", "min_endorsor_num":"2"}' --fee 888
  • name:B网络.链名

  • type:链的种类, XuperChain

  • min_endorsor_num: 表示背书个数

添加信任节点

#注意此处address及pub_key在./data/endorser/keys下

./xchain-cli wasm invoke crossQueryNaming --method AddEndorsor -a '{"name":"mainnet.xuper", "address":"bobfffff", "host":"ip1:port1", "pub_key":"xxxxx"}' --fee 555

./xchain-cli wasm invoke crossQueryNaming --method AddEndorsor -a '{"name":"mainnet.xuper", "address":"alicefffff", "host":"ip2:port2", "pub_key":"yyyyy"}' --fee 555
  • address:背书地址

  • host:背书节点ip:port

  • pub_key: 背书公钥

链名解析

./xchain-cli wasm query crossQueryNaming --method Resolve -a '{"name":"mainnet.xuper"}'

跨链查询

B网络调用counter

在B网络调用counter合约,自增key值并查询自增后的结果:

./xchain-cli wasm invoke counter --method increase -a '{"key":"zq"}'--fee 100-H=ipB:portB
./xchain-cli wasm invoke counter --method get -a '{"key":"zq"}'--fee 100-H=ipB:portB

A网络调用crossQueryNaming

在A网络调用crossQueryNaming查询,即实现跨链查询B网络xuper链上counter合约key值结果:

./xchain-cli wasm invoke cross_query_demo --method cross_query -H=ipA:portA

非事务场景跨链使用文档

跨链的背景知识可以参考 XuperChain 的设计文档 ` XuperChain 跨链技术 <../design_documents/cross_chain.html>`_ ,这里介绍一下 XuperChain 自带的跨链工具的使用方式。

中继同步合约

合约的代码在 xuperchain/core/contractsdk/cpp/example/xuper_relayer.cc 中,主要功能是存储其他链上的有前后关系的区块在合约中,并可以查询指定交易在所存区块中是否存在、是否合法。

  • initAnchorBlockHeader:此方法用于存储第一个区块,每个合约只能调用一次,存储时不需要检查区块的合法性

  • putBlockHeader:此方法用于存储后继的区块,调用次数无限制,但存储的区块需是某个已存区块的后继块

  • verifyTx:此方法用于校验指定的交易是否存在且合法,需要提供交易所在区块和梅克尔路径信息

  • printBlockHeader:调用此方法可使用区块id获取存储块的内容

合约使用方法

合约部署调用在 创建合约 中有提到,中继同步合约在部署上和其他合约无异,但调用过程中可能有些不同的地方。

中继同步合约在调用过程中往往需要传入区块内容的参数,这个参数以字符串形式传入,解析时则以序列化Protobuf来处理

1std::unique_ptr<relayer::InternalBlock> anchorBlockHeader(
2    new relayer::InternalBlock);
3std::string anchorBlockHeaderStr = ctx->arg("blockHeader");
4bool succ = anchorBlockHeader->ParseFromString(anchorBlockHeaderStr);

这里如果在命令行中操作就会遇到一些麻烦,在命令行中调用,blockHeader字段需要使用base64编码放在json中;更多的是序列化的InternalBlock数据从哪里来。

proto文件位于 xuperchain/core/contractsdk/cpp/example/xuper_relayer/src/relayer.proto,其中的InternalBlock和 xuperchain/core/pb/xchain.proto XuperChain 的InternalBlock定义是一致的,我们可以使用 XuperChain 提供的RPC接口

 1rpc GetBlock(BlockID) returns (Block);
 2message BlockID {
 3    Header header = 4;
 4    string bcname = 1;
 5    bytes blockid = 2;
 6    // if need content
 7    bool need_content = 3; //是否需要内容
 8}
 9message Block {
10    Header header = 1;
11    string bcname = 2;
12    bytes blockid = 3;
13    enum EBlockStatus {
14        ERROR = 0;
15        TRUNK = 1;
16        BRANCH = 2;
17        NOEXIST = 3;
18    }
19    EBlockStatus status = 4;
20    InternalBlock block = 5;
21}

来获取InternalBlock内容,注意输入中的 need_content 需要置为true。

对于查询交易的方法,除了所在区块id外,还需要传入一个“梅克尔路径”。对于梅克尔树的定义,这里不再赘述,可参考 Merkle_tree ,在梅克尔树中,待验证交易的txid是存在于某个叶子结点中的,我们需要给出此叶子结点合并计算hash至根节点的路径,以下图为例

_images/merkle_tree.png

假如我们需要验证node8的合法性,我们需要给出从node8计算出root的所有节点,即node7、node8与node4,他们的相对顺序需要按照二叉树的中序遍历位置给出,即我们需要如下构造输入参数

1{
2    "proofPath": "[node7 hash],[node8 hash],[node4 hash]",
3    "txIndex": 1
4}

其中的hash字段使用逗号分隔,待验证的id在其中的位置需要给出。

中继同步进程

中继同步进程的代码位于 xuperchain/core/cmd/relayer 中,是“自动”调用中继同步合约的一个工具,运行配置文件如下:此方法用于

 1anchorBlockHeight: 20
 2chains:
 3srcChain:
 4    rpcAddr: "remotehost:37101"
 5    bcname: "xuper"
 6dstChain:
 7    rpcAddr: "localhost:37101"
 8    bcname: "xuper"
 9    keys: "./data/keys"
10    contractConfig:
11        moduleName: "wasm"
12        contractName: "relayer"
13        updateMethod: "putBlockHeader"
14        anchorMethod: "initAnchorBlockHeader"

其中的srcChain是需要存储的区块信息的来源链,dstChain是部署中继合约的目标链,使用中继同步进程需要事先部署完成中继合约,并将合约的名称填入contractName字段。anchorBlockHeight表示是从来源链中的高度20的区块开始同步,需要视具体情况修改。

运行除了配置文件不需要额外参数,不过需要注意的是,同步区块的过程是自动的,如果您试图从较小的高度同步一个已经很高的链,请做好接收大量数据的准备。

搭建XPoA共识的 XuperChain 网络

XPoA是为许可链设计的共识算法,XPoA共识算法的原理可以参考 XuperChain 的设计文档 XPoA技术文档 。许可链指的是所有参与链系统的节点,都需经过许可,未经过许可的节点不可接入系统。下面介绍下如何搭建一个XPoA共识的 XuperChain 网络。

搭建XPoA共识网络

p2p网络配置

以搭建3节点网络为例,拷贝xuperchain编译产出的output到node1~node3。每个节点需修改配置文件 conf/xchain.yaml 中p2p一节,使用p2pv1,p2pv1是为许可链设计的p2p网络插件。

p2p:
  module: p2pv1
  # port是节点p2p网络监听的默认端口,如果在一台机器上部署注意端口配置不要冲突,
  # node1配置的是47101,node2、node3可以分别设置为47102、47103
  port: 47101
  # 不使用证书
  isUseCert: false
  # 配置网络中所有节点的neturl, 格式ip:port, 也加上本节点的neturl
  staticNodes:
    xuper:
      - "127.0.0.1:47101"
      - "127.0.0.1:47102"
      - "127.0.0.1:47103"

注意,如果节点分布在不同的机器之上,需要把网络地址中的本地ip改为机器的实际ip。

更新各节点的keys

由于节点目录下的keys都是默认的,node1保持不变,更新node2、node3的keys。更新前需手动删掉data/keys目录。更新keys命令如下:

./xchain-cli account newkeys

配置创世块

XuperChain系统支持可插拔共识,通过修改创世块的参数,可以创建一个以XPoA为共识的链。创世块配置位于 data/config/xuper.json ,修改genesis_consensus一节。各个配置参数详解配置说明如下所示:

{
    "version" : "1",
    "predistribution":[
        {
            "address" : "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
            "quota" : "100000000000000000000"
        }
    ],
    "maxblocksize" : "128",
    "award" : "1000000",
    "decimals" : "8",
    "award_decay": {
        "height_gap": 31536000,
        "ratio": 1
    },
    "gas_price": {
        "cpu_rate": 1000,
        "mem_rate": 1000000,
        "disk_rate": 1,
        "xfee_rate": 1
    },
    "new_account_resource_amount": 1000,
    "genesis_consensus":{
        "name": "xpoa",
        "config": {
            // 声明共识的起始时间戳,建议设置为一个刚过去不久的时间戳,更新前10位
            "timestamp": "1590636296000000000",
            // 每个矿工连续出块的出块间隔
            "period":"3000",
            // 每一轮内每个矿工轮值任期内连续出块的个数
            "block_num":"10",
            // xpoa共识依赖的合约名称,无需修改
            "contract_name":"xpoa_validates",
            // xpoa共识查询候选人的合约方法,无需修改
            "method_name":"get_validates",
            // 指定第一轮初始矿工,所指定的初始矿工需要在网络中存在,不然系统轮到该节点出块时会没有节点出块
            "init_proposer": [
                {
                    "address" : "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN"
                    , "neturl" : "10.26.29.40:47101"
                },
                {
                    "address" : "VSML7NenZnGZgCEwtbQDKDSrPHhT5wsu6"
                    , "neturl" : "10.26.29.40:47102"
                },
                {
                    "address" : "bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3"
                    , "neturl" : "10.26.29.40:47103"
                }
            ],
            // 使用chained-bft
            "bft_config": {}
        }
    }
}

将修改好的1份xuper.json拷贝到另外2个节点的data/config目录下。

注意,拷贝配置内容到xuper.json时需去掉注释。

创建链并启动xchain

检查data/blockchain 目录下内容为空之后,创建链并启动所有节点。命令如下:

# 创建xuper链
./xchain-cli createChain
# 启动服务节点
nohup ./xchain &
# check服务运行状况,修改-H后参数,可以查询每个节点状态
for((i=1;i<=3;i++));do
./xchain-cli status -H 127.0.0.1:3710$i |grep -i height
done

通过变更-H 参数,查看每个节点的状态,若所有节点高度都是一致变化的,则证明环境状态正常。

验证集合合约部署和调用

XPoA共识算法中,候选人的变更依赖"验证集合"合约,所以需要部署"验证集合"合约。通过调用合约中的add_validate方法新增候选人、del_validate方法删除候选人、update_validate方法更新候选人neturl、get_validates方法查询候选人列表。通过设置合约方法的ACL,可以限制哪些用户具有变更候选人的权限,设置方法参考 设置合约方法的ACL

创建合约账号

合约账号用来做合约的管理,创建合约账号,并给合约账号转账。

# 创建合约账号
[work@]$ node1 -> ./xchain-cli account new --account 1111111111111111 --fee 1000 -H 127.0.0.1:37101
# 执行结果
# contract response:
#         {
#             "pm": {
#                 "rule": 1,
#                 "acceptValue": 1.0
#             },
#             "aksWeight": {
#                 "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN": 1.0
#             }
#         }

# The gas you cousume is: 1000
# The fee you pay is: 1000
# Tx id: eb9924c85a16d72f5daf6e6feabb130ef9c8a3ce8f507db08dcb726111aef74f
# account name: XC1111111111111111@xuper

# 给合约账号转账
[work@]$ node1 -> ./xchain-cli transfer --to XC1111111111111111@xuper --amount 100000000 -H 127.0.0.1:37101
# 执行结果
# ec6fa53446a8c6ab0d8d45f2bba80c7e5122341ce9b0c85779f80ce1a55f37b6

编译合约

"验证集合"合约源码位于core/contractsdk/cpp/example/xpoa_validates,执行如下命令编译合约,编译结果为xpoa_validates.wasm。

# prj是xuperchain源码所在目录,设定环境变量
export PATH=$prj/xuperchain/output:$PATH
export XDEV_ROOT=$prj/xuperchain/core/contractsdk/cpp
# 编译合约
cd $prj/xuperchain/core/contractsdk/cpp/example/xpoa_validates
xdev build

部署合约

部署合约,并设置node1、node2为初始候选人。

[work@]$ node1 -> ./xchain-cli wasm deploy --account XC1111111111111111@xuper --cname xpoa_validates --arg '{"addresss":"dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN;VSML7NenZnGZgCEwtbQDKDSrPHhT5wsu6","neturls":"127.0.0.1:47101;127.0.0.1:47102"}' ./xpoa_validates.wasm --fee 222065 -H 127.0.0.1:37101
# 执行结果
# contract response: initialize succeed
# The gas you cousume is: 221920
# The fee you pay is: 222065
# Tx id: 4f9f11afcf080199b93d5f308b6dc0e07ce5b9099c36cbf9b4edb2ee398bcfa3

参数说明:

  • wasm deploy:部署wasm合约

  • --account XC1111111111111111@xuper:此为部署wasm合约的账号

  • --cname xpoa_validates :合约名称,需与xuper.json中配置的contract_name参数一致

  • --arg :此为传入合约的参数,这里设置初始矿工,所指定的初始矿工需要在网络中存在,多个矿工用分号间隔,且address与netrul要 一一对应。

  • ./xpoa_validates.wasm :是编译合约产出的文件

增加候选人

以添加node3为候选人为例,添加后等待1分钟,调查看候选人命令,查看是否添加成功。

[work@]$ node1 -> ./xchain-cli wasm invoke xpoa_validates --method add_validate --args '{"address":"bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3","neturl":"127.0.0.1:47103"}' --fee 300 -H 127.0.0.1:37101
# 执行结果
# contract response: {"address":"bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3","neturl":"127.0.0.1:47103"}
# The gas you cousume is: 252
# The fee you pay is: 300
# Tx id: 5a3993d0e001aa0b140b204c013c6ea0b9741f8e1dfe81db71887579d63ce785

参数说明:

  • wasm invoke:调用合约

  • --method add_validate:调用add_validate方法

  • --args:传入的参数,填写待添加候选人的address和neturl

查看候选人

查询结果中,候选人按字典序排列。

[work@]$ node1 -> ./xchain-cli wasm invoke xpoa_validates --method get_validates -H 127.0.0.1:37101
# 执行结果
# contract response: {"proposers":[{"address":"VSML7NenZnGZgCEwtbQDKDSrPHhT5wsu6","neturl":"127.0.0.1:47102"},{"address":"bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3","neturl":"127.0.0.1:47103"},{"address":"dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN","neturl":"127.0.0.1:47101"}]}
# The gas you cousume is: 439
# You need add fee
  • wasm invoke:调用合约

  • --method get_validates:调用get_validates方法

更新候选人

候选人的netrul发生变化后,需要更新。以更新node3的neturl为例,比如更新为localhost:47103。修改后等待1分钟,调查看候选人命令,查看是否修改成功。

[work@]$ node1 -> ./xchain-cli wasm invoke xpoa_validates --method update_validate -a '{"address":"bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3","neturl":"localhost:47103"}' --fee 300 -H 127.0.0.1:37101
# 执行结果
# contract response: {"address":"bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3","neturl":"localhost:47103"}
# The gas you cousume is: 263
# The fee you pay is: 300
# Tx id: 6e6289c513169cd32c44fa05bb06c0eba0f37f05acd5eb6ae4573ae266363b76

参数说明:

  • wasm invoke:调用合约

  • --method update_validate:调用update_validate方法

  • --args:传入的参数,填写待更新候选人的address和neturl

删除候选人

将node3从候选人集合删除。删除后等待1分钟,调查看候选人命令,查看是否删除成功。

[work@]$ node1 -> ./xchain-cli wasm invoke xpoa_validates --method del_validate -a '{"address":"bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3"}' --fee 300 -H 127.0.0.1:37101
# 执行结果
# contract response: ok
# The gas you cousume is: 128
# The fee you pay is: 300
# Tx id: a033b1c4b548c3515a29b5d643fdad20cc778c71a75a95869ddaae067177d7c4
  • wasm invoke:调用合约

  • --method del_validate:调用del_validate方法

  • --args:传入的参数,填写待删除候选人的address和neturl

查看当前正在出块的候选人

通过日志,可查看当前正在出块的候选人。示例如下,其中proposer是正在出块候选人。并且,多个候选人按字典序轮值出块。

[work@]$ node1 -> tailf logs/xchain.log|grep "bft NewView"
t=2020-06-28T17:04:24+0800 lvl=info msg="bft NewView" module=xchain viewNum=550 dpm.currentView=550 proposer=bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3 preProposer=VSML7NenZnGZgCEwtbQDKDSrPHhT5wsu6 err=nil

t=2020-06-28T17:04:27+0800 lvl=info msg="bft NewView" module=xchain viewNum=551 dpm.currentView=551 proposer=bg3KLC3YCmvLWBCNAVHGHLfk3qeWEdoD3 preProposer=VSML7NenZnGZgCEwtbQDKDSrPHhT5wsu6 err=nil

常见问题

  • 端口冲突:注意如果在一台机器上部署多个节点,各个节点的RPC监听端口以及p2p监听端口都需要设置地不相同,避免冲突;

  • 节点公私钥冲突:注意网络中不同节点./data/keys下的文件内容都应该不一样,这个文件夹是节点在网络中的唯一标识,每个节点需要独自生成,否则网络启动异常;

  • 遇到The gas you cousume is: XXXX, You need add fee 通过加--fee XXXX 参数附加资源;

  • Chained-Bft算法要求3个矿工的集群,不可以有矿工故障,所以如果使用更新候选人接口将节点neturl更新错误,将无法出块,需删除data/blockchain 目录下内容后,从10.1.4节开始重新部署环境。

可信账本使用文档

XuperChain 可信账本包含如下四个核心组件:

  1. mesatee-core-standalone:TEE Enclave App的开发框架,用户可以根据业务需求开发自己的应用

  2. TEESDK:负责与TEE服务的通信,可实现 XuperChain SDK和 XuperChain 对TEE服务的请求

  3. xuperchain: XuperChain 开源代码,支持模块的可插拔机制

  4. XuperChain SDK:负责与 XuperChain 通信,可实现交易的封装和上链、数据的加密和解密等

服务部署

如只想测试TEE的密文计算功能,只需部署TEE和TEESDK;如果想测试链上密文计算功能, 需要全部部署。

部署TEE服务

  1. 下载mesatee-core-standalone最新代码:https://github.com/xuperdata/mesatee-core-standalone

  2. 部署自己实现的app到mesatee_services/fns/sgx_trusted_lib

注解

mesatee-core-standalone并未全部开源,密文计算和秘钥托管功能相关代码暂未公开,用户需根据自身需要开发可信应用。

  1. 按照步骤1的文档进行编译,然后启动TEE服务

编译TEESDK

  1. 拉取TEESDK最新代码:https://github.com/xuperdata/teesdk

  2. 按照如下命令进行编译,编译前要将mesatee/teesdk.go中的 tms_addr.sin_addr.s_addr 修改为TEE服务部署的地址

cd teesdk/mesatee
cp /path/to/mesatee-core-standalone/release/lib/libmesatee_sdk_c.so lib/
cd ../
bash build.sh
  1. 编译之后会在build目录产出libmesateesdk.so.0.0.1, 然后将这个文件和mesatee/xchain-plugin/teeconfig.conf拷贝到xchain的pluginPath配置的目录下面。

部署区块链

  1. 拉取 XuperChain 最新代码:https://github.com/xuperchain/xuperchain

  2. 将makefile文件中的 -mod=vendor 注释掉后再编译

  3. 在编译产出output/conf/xchain.yaml文件中进行如下配置:

# 块广播模式
blockBroadcaseMode: 0
...
#可信环境的入口, optional
wasm:
 driver: "xvm"
 enableUpgrade: false
 teeConfig:
   enable: on
   pluginPath: "/path/to/libmesateesdk.so.0.0.1"
   configPath: "/path/to/xchain_plugin/teeconfig.conf"
 xvm:
   optLevel: 0

#是否开启默认的XEndorser背书服务
enableXEndorser: true

后续部署流程见文档 XuperChain环境部署

编译xuper-sdk-go

  1. 拉取 XuperChain SDK最新代码:https://github.com/xuperdata/xuper-sdk-go

  2. 参考如下配置编辑文件conf/sdk.yaml.tee

tfConfig:
  teeConfig:
        svn: 0
        enable: on
        tmsport: 8082
        uid: "uid1"
        token: "token1"
        auditors:
          -
                publicder: /path/to/mesatee-core-standalone/release/services/auditors/godzilla/godzilla.public.der
                sign: /path/to/mesatee-core-standalone/release/services/auditors/godzilla/godzilla.sign.sha256
                enclaveinfoconfig: /path/to/mesatee-core-standalone/release/services/enclave_info.toml
paillierConfig:
  enable: off
  1. 执行build.sh进行编译,产出main二进制文件

  2. 执行main文件即可完成测试

可信应用开发

用户可根据业务需求开发自己的可信算子和应用

  1. 可信算子开发参考 trust_operatorsdemo_func

  2. 可信应用开发参考合约 data_auth

  3. 可信合约相关测试参考 data_auth_test

  4. mesatee-core-standalone服务相关测试参考 teesdk_test

监控使用文档

XuperChain 系统集成了Prometheus开源监控框架,用户可以通过Prometheus+Grafana构建自己应用的监控系统,并且用户还可以根据自己的需求定义自己的监控采集项。下面会介绍目前 XuperChain 默认采集的监控项,并指导用户如何搭建自己本地的 XuperChain 开源监控系统。

XuperChain 监控项

监控项名称

监控项含义

单位

说明

rpc_flow_out

当前RPC接口上行流量

byte

以链为粒度

rpc_flow_in

当前RPC接口下行流量

byte

以链为粒度

p2p_flow_out

当前p2p网络上行流量

byte

以链为粒度

p2p_flow_in

当前p2p网络下行流量

byte

以链为粒度

grpc接口默认监控项系列

以链为粒度

XuperChain 监控系统搭建

搭建网络

搭建网络,节点xchain.yaml配置中打开prometheus 端口,如下所示:

tcpServer:
  # prometheus监控指标端口, 为空的话就不启动
    metricPort: :37200

使用Prometheus查看监控

  1. prometheus 安装教程:

    1. https://prometheus.io/docs/introduction/first_steps/

  2. 配置prometheus 设置endpoint服务:

    1. 修改 prometheus.yml 文件,添加如下配置,指向prometheus端口:

    _images/monitoring-1.png
  3. 启动prometheus:

    1. nohup ./prometheus --config.file=prometheus.yml &

  4. 查看流量信息(以流量监控信息为例):

    1. http://localhost:9090/graph

    2. p2p_flow_in

    _images/monitoring-2.png
    1. p2p_flow_out

    _images/monitoring-3.png
    1. rpc_flow_in

    _images/monitoring-4.png
    1. rpc_flow_out

    _images/monitoring-5.png

使用Grafana查看监控

因为Prometheus的界面看起来非常简单,我们还可以通过Grafana这个非常强大也是最常用的监控展示框架。

使用文档参看: https://grafana.com/grafana/download

使用接口查看监控

Prometheus提供了一种功能查询语言PromQL(Prometheus查询语言),它允许用户实时选择和聚合时间序列数据。同时为了方便外部系统调用,还提供了HTTP API能力。

详情请参考: https://prometheus.io/docs/prometheus/latest/querying/api/

下载发行版

v3.10

版本说明

  • 智能合约

    • XuperChain VM 支持 EVM

    • XuperChain 支持 solidity 合约的部署和调用

  • 对等网络

    • 支持 TLS 和 IPv6

  • 内核

    • 新的交易哈希算法

  • 命令行

    • 支持发送需要背书的交易

  • 其他

    • 新增 JAVA SDK

    • 修复平行链的 BUG

    • 修复 SMR 模块的 BUG

    • 其他 BUG 修复

  • 文档
    • 新增 XuperChain EVM 合约文档

    • 其他文档优化

电子存证合约

或使用 XuperChain XuperOS,其已发布丰富的合约模板,涵盖溯源、存证、积分、去中心化等多行业模板。 点击了解

问题引入

假设我们面临着这样的一个问题:“几个摄影师朋友找到你,他们的摄影作品上传到自己的blog后总是被其他人盗用,使用水印之类的方法也无法避免像截取部分这种情况,他们需要一个能证明摄影作品最早是由自己上传、而且具有法律效力可供自己进行维权的工具”

显然区块链对于解决此问题有很大的帮助,它的不可篡改等特性很适合存证维权的场景,我们可以通过XuperChain来构建一个存取证据的智能合约(担心不被法院认可? 这里 或许能够解答你的疑问)

下面我们就来教你帮助摄影师朋友开发一个能够存储照片版权、还能在发现被盗用后进行维权的智能合约

数据结构的设计

对于摄影作品,通常是一个图片文件,其大小根据清晰度等原因可以多达几十MB(甚至更多),为避免存储空间浪费、以及保证区块链交易的效率,我们可以使用哈希算法(例如SHA256)只将图片的哈希值上链,而原图可以保存在其他地方

我们可以这样定义“证据文件”的数据结构,包含哈希值和上传的时间戳

1type UserFile struct {
2    Timestamp int64
3    Hashval   []byte
4}

为了能够存储多个“证据文件”,并且能够服务于更多的摄影师朋友,我们可以定义一个上传者到文件的map

1type User struct {
2    Owner     string
3    UserFiles map[string]*UserFile
4}

代码样例可以参看:contractsdk/go/example/eleccert.go

电子存证合约的功能实现

从场景我们可以大致推断,以下两个功能是必要的

  • 存储一个到“证据文件”区块链(save方法)

  • 获取已经存储过的某一个“证据文件”(query方法)

更底层考虑,我们可以使用XuperChain提供的合约SDK功能 PutObjectGetObject 来提供实际的存取功能

对于XuperChain中的智能合约,Initialize是一个必须实现的方法,当且仅当合约被部署的时候会运行一次,我们这里采用“每个摄影师部署自己的合约来存储自己需要的作品”这种方式,将一些和上传者相关的初始化操作放在函数中

Save、Query和Initialize方法的具体实现可以参考代码样例

合约使用方法

合约部署(Deploy)

编译并部署合约的过程可以参考 部署 native 合约 章节,注意资源消耗可以一开始不加 --fee 参数,执行后会给出需要消耗的资源数

合约执行(Save)

执行合约进行“存证操作”的命令如下(运行需要使用 --fee 参数提供资源消耗):

1./xchain-cli native invoke -a '下面json中args字段的内容' --method save -H localhost:37101 eleccert
 1{
 2    "module_name": "wasm",          # native or wasm
 3    "contract_name": "eleccert",    # contract name
 4    "method_name": "save",          # invoke or query
 5    "args": {
 6        "owner": "aaa",             # user name
 7        "filehash": "存证文件的hash值",
 8        "timestamp": "存证的timestamp"
 9    }
10}

合约查询(Query)

执行合约进行“取证操作”的命令如下(查询操作不需要提供资源):

1./xchain-cli native query -a 'args内容' --method query -H localhost:37101 eleccert
 1{
 2    "module_name": "native",        # native or wasm
 3    "contract_name": "eleccert",    # contract name
 4    "method_name": "query",         # invoke or query
 5    "args": {
 6        "owner": "aaa",             # user name
 7        "filehash": "文件hash值"
 8    }
 9}
10# output 如下
11{
12    "filehash": "文件hash值",
13    "timestamp": "文件存入timestamp"
14}

数字资产交易

代码样例参看:contractsdk/go/example/erc721.go

或使用 XuperChain XuperOS,其已发布丰富的合约模板,涵盖溯源、存证、积分、去中心化等多行业模板。 点击了解

ERC721简介

ERC721是数字资产合约,交易的商品是非同质性商品。其中,每一份资产,也就是token_id都是独一无二的类似收藏品交易。

ERC721具备哪些功能

  • 通过initialize方法,向交易池注入自己的token_id
    • 注意token_id必须是全局唯一

  • 通过invoke方法,执行不同的交易功能
    • transfer: userA将自己的某个收藏品token_id转给userB

    • approve: userA将自己的某个收藏品token_id的售卖权限授予userB

    • transferFrom: userB替userA将赋予权限的收藏品token_id卖给userC

    • pproveAll: userA将自己的所有收藏品token_id的售卖权限授予userB

  • 通过query方法,执行不同的查询功能
    • balanceOf: userA的所有收藏品的数量

    • totalSupply: 交易池中所有的收藏品的数量

    • approvalOf: userA授权给userB的收藏品的数量

调用json文件示例

Initialize

./xchain-cli wasm invoke -a '下面json中args字段的内容' --method initialize -H localhost:37101 erc721

1{
2    "module_name": "native",      # native或wasm
3    "contract_name": "erc721",    # contract name
4    "method_name": "initialize",  # initialize or query or invoke
5    "args": {
6        "from": "dudu",           # userName
7        "supply": "1,2"           # token_ids
8    }
9}

Invoke

./xchain-cli native invoke -a 'args内容' --method invoke -H localhost:37101 erc721

 1{
 2    "module_name": "native",      # native或wasm
 3    "contract_name": "erc721",    # contract name
 4    "method_name": "invoke",      # initialize or query or invoke
 5    "args": {
 6        "action": "transfer",     # action name
 7        "from": "dudu",           # usera
 8        "to": "chengcheng",       # userb
 9        "token_id": "1"           # token_ids
10    }
11}
12{
13    "module_name": "native",      # native或wasm
14    "contract_name": "erc721",    # contract name
15    "method_name": "invoke",      # initialize or query or invoke
16    "args": {
17        "action": "transferFrom", # action name
18        "from": "dudu",           # userA
19        "caller": "chengcheng",   # userB
20        "to": "miaomiao",         # userC
21        "token_id": "1"           # token_ids
22    }
23}
24{
25    "module_name": "native",      # native或wasm
26    "contract_name": "erc721",    # contract name
27    "method_name": "invoke",      # initialize or query or invoke
28    "args": {
29        "action": "approve",      # action name
30        "from": "dudu",           # userA
31        "to": "chengcheng",       # userB
32        "token_id": "1"           # token_ids
33    }
34}

Query

./xchain-cli native query -a 'args内容' --method query -H localhost:37101 erc721

 1{
 2    "module_name": "native",     # native或wasm
 3    "contract_name": "erc721",   # contract name
 4    "method_name": "query",      # initialize or query or invoke
 5    "args": {
 6        "action": "balanceOf",   # action name
 7        "from": "dudu"           # userA
 8    }
 9}
10{
11    "module_name": "native",     # native或wasm
12    "contract_name": "erc721",   # contract name
13    "method_name": "query",      # initialize or query or invoke
14    "args": {
15        "action": "totalSupply"  # action name
16    }
17}
18{
19    "module_name": "native",      # native或wasm
20    "contract_name": "erc721",    # contract name
21    "method_name": "query",       # initialize or query or invoke
22    "args": {
23        "action": "approvalOf",   # action name
24        "from": "dudu",           # userA
25        "to": "chengcheng"        # userB
26    }
27}

智能合约SDK使用说明

XuperChain为方便用户开发属于自己的智能合约,提供了一整套SDK套件,即XuperCDT(XuperChain Crontract Development Toolkit),包含C++语言、Go语言和Java语言

C++接口API

get_object

bool ContextImpl::get_object(const std::string& key, std::string* value)

输入

参数

说明

key

查询的key值

value

根据key查到的value值

输出

参数

说明

true

key值查询成功,返回value值

false

key值不存在

put_object

bool ContextImpl::put_object(const std::string& key, const std::string& value)

输入

参数

说明

key

存入的key值

value

存入key值对应的value值

输出

参数

说明

true

存入db成功

false

存入db失败

delete_object

bool ContextImpl::delete_object(const std::string& key)

输入

参数

说明

key

将要删除的key值

输出

参数

说明

true

删除成功

false

删除失败

query_tx

bool ContextImpl::query_tx(const std::string &txid, Transaction* tx)

输入

参数

说明

txid

待查询的txid

tx

得到此txid的transaction

输出

参数

说明

true

查询交易成功

false

查询交易失败

query_block

bool ContextImpl::query_block(const std::string &blockid, Block* block)

输入

参数

说明

blockid

待查询的blockid

block

得到此blockid的block

输出

参数

说明

true

查询block成功

false

查询block失败

table

定义表格
 1// 表格定义以proto形式建立,存放目录为contractsdk/cpp/pb
 2syntax = "proto3";
 3option optimize_for = LITE_RUNTIME;
 4package anchor;
 5message Entity {
 6    int64 id = 1;
 7    string name = 2;
 8    bytes desc = 3;
 9}
10// table名称为Entity,属性分别为id,name,desc
初始化表格
 1// 定义表格的主键,表格的索引
 2struct entity: public anchor::Entity {
 3    DEFINE_ROWKEY(name);
 4    DEFINE_INDEX_BEGIN(2)
 5    DEFINE_INDEX_ADD(0, id, name)
 6    DEFINE_INDEX_ADD(1, name, desc)
 7    DEFINE_INDEX_END();
 8};
 9// 声明表格
10xchain::cdt::Table<entity> _entity;
put
1template <typename T>
2bool Table<T>::put(T t)

输入

参数

说明

t

待插入的数据项

输出

参数

说明

true

插入成功

false

插入失败

样例

 1// 参考样例 contractsdk/cpp/example/anchor.cc
 2DEFINE_METHOD(Anchor, set) {
 3    xchain::Context* ctx = self.context();
 4    const std::string& id= ctx->arg("id");
 5    const std::string& name = ctx->arg("name");
 6    const std::string& desc = ctx->arg("desc");
 7    Anchor::entity ent;
 8    ent.set_id(std::stoll(id));
 9    ent.set_name(name.c_str());
10    ent.set_desc(desc);
11    self.get_entity().put(ent);
12    ctx->ok("done");
13}
find
1template <typename T>
2bool Table<T>::find(std::initializer_list<PairType> input, T* t)

输入

参数

说明

input

查询关键字

t

返回的数据项

输出

参数

说明

true

查询成功

false

查询失败

样例

 1DEFINE_METHOD(Anchor, get) {
 2    xchain::Context* ctx = self.context();
 3    const std::string& name = ctx->arg("key");
 4    Anchor::entity ent;
 5    if (self.get_entity().find({{"name", name}}, &ent)) {
 6        ctx->ok(ent.to_str());
 7        return;
 8    }
 9    ctx->error("can not find " + name);
10}
scan
1template <typename T>
2std::unique_ptr<TableIterator<T>> Table<T>::scan(std::initializer_list<PairType> input)

输入

参数

说明

input

查询关键字

输出

参数

说明

TableIterator

符合条件的迭代器

样例

 1DEFINE_METHOD(Anchor, scan) {
 2    xchain::Context* ctx = self.context();
 3    const std::string& name = ctx->arg("name");
 4    const std::string& id = ctx->arg("id");
 5    // const std::string& desc = ctx->arg("desc");
 6    auto it = self.get_entity().scan({{"id", id},{"name", name}});
 7    Anchor::entity ent;
 8    int i = 0;
 9    std::map<std::string, bool> kv;
10    while(it->next()) {
11        if (it->get(&ent)) {
12            /*
13            std::cout << "id: " << ent.id()<< std::endl;
14            std::cout << "name: " << ent.name()<< std::endl;
15            std::cout << "desc: " << ent.desc()<< std::endl;
16            */
17            if (kv.find(ent.name()) != kv.end()) {
18                ctx->error("find duplicated key");
19                return;
20            }
21            kv[ent.name()] = true;
22            i += 1;
23        } else {
24            std::cout << "get error" << std::endl;
25        }
26    }
27    std::cout << i << std::endl;
28    if (it->error()) {
29        std::cout << it->error(true) << std::endl;
30    }
31    ctx->ok(std::to_string(i));
32}
del
1template <typename T>
2bool Table<T>::del(T t)

输入

参数

说明

t

一个数据项

输出

参数

说明

true

删除成功

false

删除失败

样例

 1DEFINE_METHOD(Anchor, del) {
 2    xchain::Context* ctx = self.context();
 3    const std::string& id= ctx->arg("id");
 4    const std::string& name = ctx->arg("name");
 5    const std::string& desc = ctx->arg("desc");
 6    Anchor::entity ent;
 7    ent.set_id(std::stoll(id));
 8    ent.set_name(name.c_str());
 9    ent.set_desc(desc);
10    self.get_entity().del(ent);
11    ctx->ok("done");
12}

在合约中使用 JSON

XuperChain SDK 包含了 json 相关的库,可以在合约中方便地使用 json 进行序列化和反序列化。 在合约中使用 json 的例子如下

#include "xchain/json/json.h"
#include "xchain/xchain.h"

struct Features : xchain::Contract {
};

DEFINE_METHOD(Features, json_load_dump) {
    xchain::Context *ctx = self.context();
    const std::string v = ctx->arg("value");
    auto j = xchain::json::parse(v);
    ctx->ok(j.dump());
}

DEFINE_METHOD(Features, json_literal) {
    xchain::Context *ctx = self.context();
    xchain::json j = {
            {"int",    3},
            {"float",  3.14},
            {"string", "hello"},
            {"array",  {"hello", "world"}},
            {"object", {{"key", "value"}}},
            {"true",   true},
            {"false",  false},
            {"null",   nullptr},
    };
    ctx->ok(j.dump());
}

关于 json 库更多的内容可以查看 文档

Go接口API

GetObject

func GetObject(key []byte) ([]byte, error)

输入

参数

说明

key

查询的key值

输出

参数

说明

value, nil

key值查询成功,返回value值

_, 非nil

key值不存在

PutObject

func PutObject(key []byte, value []byte) error

输入

参数

说明

key

存入的key值

value

存入key值对应的value值

输出

参数

说明

nil

存入db成功

非nil

存入db失败

DeleteObject

func DeleteObject(key []byte) error

输入

参数

说明

key

将要删除的key值

输出

参数

说明

nil

删除成功

非nil

删除失败

QueryTx

func QueryTx(txid string) (*pb.Transaction, error)

输入

参数

说明

txid

待查询的txid

输出

参数

说明

tx, nil

查询交易成功, 得到此txid的transaction

_, 非nil

查询交易失败

QueryBlock

func QueryBlock(blockid string) (*pb.Block, error)

输入

参数

说明

blockid

待查询的blockid

输出

参数

说明

block, nil

查询block成功, 得到此blockid的block

_, 非nil

查询block失败

NewIterator

func NewIterator(start, limit []byte) Iterator

输入

参数

说明

start

初始关键字

limit

结束关键字

输出

参数

说明

Iterator

Interator的接口

样例

1Key() []byte
2Value() []byte
3Next() bool
4Error() error
5// Iterator 必须在使用完毕后关闭
6Close()

Java接口API

getObject

键值获取

public byte[] getObject(byte[] key)

输入

参数

说明

key

查询的key值

输出

参数

说明

value

key值查询成功,返回value值;为null时,查询失败

putObject

键值存储

public void putObject(byte[] key, byte[] value)

输入

参数

说明

key

存入的key值

value

存入key值对应的value值

输出

参数

说明

void

操作失败时,可捕捉异常;否则,成功

deleteObject

键值删除

public void deleteObject(byte[] key)

输入

参数

说明

key

将要删除的key值

输出

参数

说明

void

操作失败时,可捕捉异常;否则,成功

queryTx

交易查询

public Contract.Transaction queryTx(String txid)

输入

参数

说明

txid

待查询的txid

输出

参数

说明

tx

查询交易成功, 得到此txid的transaction;查询失败,抛出异常

queryBlock

区块查询

public Contract.Block queryBlock(String blockid)

输入

参数

说明

blockid

待查询的blockid

输出

newIterator

迭代器

public Iterator<ContractIteratorItem> newIterator(byte[] start, byte[] limit)

输入

参数

说明

start

初始关键字

limit

结束关键字

输出

参数

说明

Iterator

Interator的接口

样例

 1@ContractMethod
 2public Response getList(Context ctx) {
 3    byte[] start = ctx.args().get("start");
 4    if (start == null) {
 5        return Response.error("missing start");
 6    }
 7
 8    byte[] limit = PrefixRange.generateLimit(start);
 9    Iterator<ContractIteratorItem> iter = ctx.newIterator(start, limit);
10    int i = 0;
11    while (iter.hasNext()) {
12        ContractIteratorItem item = iter.next();
13        String key = bytesToString(item.getKey());
14        String value = bytesToString(item.getValue());
15        ctx.log("item: " + i + ", key: " + key + ", value: " + value);
16        i++;
17    }
18
19    return Response.ok("ok".getBytes());
20}

transfer

从合约向其他地址转账

public void transfer(String to, BigInteger amount)

输入

参数

说明

to

收款地址

amount

数量

输出

transferAmount

调用合约方法向合约转账时,获取转账的数量

public BigInteger transferAmount()

输入

输出

参数

说明

BigInteger

数量

call

跨合约调用

public Response call(String module, String contract, String method, Map<String, byte[]> args)

输入

参数

说明

module

模块名

contract

合约名

method

合约方法

args

合约参数

输出

参数

说明

Response

合约返回值

crossQuery

跨链查询

public Response crossQuery(String uri, Map<String, byte[]> args)

输入

参数

说明

uri

跨链路由地址

args

合约参数

输出

参数

说明

Response

合约返回值

智能合约开发详解

简介

百度 XuperChain 是一个支持多语言合约的区块链框架,有多种语言来供大家选择使用开发智能合约。目前 XuperChain 的智能合约可以使用solidity、c++、go以及 java语言来编写,solidity为EVM合约,c++和go 支持 wasm合约,go和java支持native合约。solidity合约应用最为广泛,完美兼容以太坊开源社区以及相关开发工具,c++合约合约性能会更好些,go合约在易用性上更好,java合约的开发者会更多些。大家可以根据需要选择自己喜欢的语言来编写智能合约,这篇文章会通过一步步的指引来帮助大家使用solidity、c++、go或者java来编写 XuperChain 的智能合约,在阅读完文章后,希望大家对如何编写,部署和测试 XuperChain 的智能合约有初步的认识。

或使用 XuperChain XuperOS,支持合约可视化管理、在线上链。 点击了解

准备工作

环境要求

目前 XuperChain 节点主要运行在linux和mac上,windows不能运行 XuperChain 节点。

  1. go >= 1.12.x && <= 1.13.x

  2. g++ >= 4.8.2 或者 clang++ >= 3.3

  3. Docker

下载编译xuperchain

智能合约只有部署到链上才能运行,因此我们首先要编译并启动xuperchain节点。

如果需要使用特定分支,使用git checkout来切换特定分支,如 **git checkout v3.7**
1$ cd $HOME
2$ git clone https://github.com/xuperchain/xuperchain.git  xuperchain
3$ cd xuperchain && make

设置环境变量

这些环境变量有助于我们更方便的执行一些命令而不用指定命令的全路径。

1export PATH=$HOME/xuperchain/output:$PATH
2export XDEV_ROOT=$HOME/xuperchain/core/contractsdk/cpp

启动xuperchain

–vm ixvm参数是选择ixvm合约虚拟机,开发合约过程中使用ixvm虚拟机能加快合约部署

1$ cd output
2## 首先创建链
3$ ./xchain-cli createChain
4## 后台启动xuperchain节点
5$ nohup ./xchain --vm ixvm &

创建合约账号

合约账号用来进行合约管理,比如合约的权限控制等,要部署合约必须创建合约账号,同时合约账号里面需要有充足的xuper来部署合约。

创建合约账号XC1111111111111111@xuper.

 1$ ./xchain-cli account new --account 1111111111111111 --fee 2000
 2contract response:
 3        {
 4            "pm": {
 5                "rule": 1,
 6                "acceptValue": 1.0
 7            },
 8            "aksWeight": {
 9                "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN": 1.0
10            }
11        }
12The gas you cousume is: 1000
13The fee you pay is: 2000
14Tx id: d62704970705a2682e2bd2c5b4f791065871fd45f64c87815b91d8a00039de35
15account name: XC1111111111111111@xuper

给合约账号转账

1$ ./xchain-cli transfer --to XC1111111111111111@xuper --amount 100000000
2cd26657006f6f75f07bd53ad0a7fe74d76985cd592542d8cc87dc3fcdde115f5

小结

至此我们完成了所有的准备工作,包括编译xuperchain,创建链,启动节点,创建合约账号,后面我们开始体验怎么编译,部署和调用智能合约。

快速体验

在开始编写智能合约之前首先通过一个简单的例子来给大家演示合约是如何从代码到字节码,以及如何部署到链上,如何发起对智能合约的调用。 我们使用一个c++合约为例来展示如何编译、部署、调用合约。

创建合约工程

xdev工具是随xuperchain发布的一个合约编译和测试工具,在编译完xuperchain之后生成在output目录。

xdev提供了一个默认的c++合约工程模板

1$ xdev init hello-cpp

这个命令创建了一个hello-cpp的合约工程

编译合约

第一次编译的时间会长一点,因为xdev需要下载编译器镜像,以及编译 XuperChain 的标准库。
1$ xdev build -o hello.wasm
2CC main.cc
3LD wasm

编译结果为hello.wasm,后面我们使用这个文件来部署合约

部署合约

1$ ./xchain-cli wasm deploy --account XC1111111111111111@xuper --cname hello  --fee 5200000 --runtime c ./hello-cpp/hello.wasm
2contract response: initialize succeed
3The gas you cousume is: 151875
4The fee you pay is: 5200000
5Tx id: 8c33a91c5cf564a28e7b62cad827ba91e19abf961702659dd8b70a3fb872bdf1

此命令看起来很长,但是其中很多参数都有默认值,我们先来看一下参数的含义:

  • wasm deploy :此为部署wasm合约的命令参数,不做过多解释

  • --account XC1111111111111111@xuper :此为部署wasm合约的账号(只有合约账号才能进行合约的部署)

  • --cname hello :这里的hello是指部署后在链上的合约名字,可以自行命名(但有规则,长度在4~16字符)

  • --runtime c 指明我们部署的是一个c++代码编译的合约,如果是go合约这里填 go 即可。

  • --fee 为我们部署这个合约所需要的xuper

  • 最后的hello.wasm是合约编译好的文件

调用合约

1$ ./xchain-cli wasm invoke --method hello --fee 110000 hello
2contract response: hello world
3The gas you cousume is: 35
4The fee you pay is: 110000
5Tx id: d8989ad1bfd2d08bd233b7a09a544cb07976fdf3429144c42f6166d28e9ff695

参数解释如下:

  • wasm invoke 表示我们要调用一个合约

  • --method hello 表示我们要调用合约的 hello 方法

  • --fee 指明我们这次调用合约花费的xuper

  • 最后的参数指明我们调用的合约名字 hello

小结

通过本节的学习,我们快速掌握了如果编译,部署和调用合约,在下面的章节里面我们学些如果使用solidity、c++、go或者java语言来编写智能合约。

合约编写详解

XuperChain目前主要支持以太坊solidity合约,两种编译成wasm格式的合约语言, c++go,以及两种native合约 gojava ,合约框架的整体结构是一致的,在不同语言上的表现形式不太一样,但熟悉一种语言的SDK之后很容易迁移到其他语言。

下面大概说明如何编写这四种类型的合约

Solidity合约

如果本地搭建 XuperChain 环境,在部署、调用solidity合约之前,请先查看`conf/xchain.yaml` 中evm一节,确保evm合约功能开启。

1# evm合约配置
2evm:
3    driver: "evm"
4    enable: true
编译环境准备

安装solc编译器,请参见**https://solidity-cn.readthedocs.io/zh/latest/installing-solidity.html**。

solc --version
// solc, the solidity compiler commandline interface
// Version: 0.5.9+commit.c68bc34e.Darwin.appleclang
// 以上打印说明编译器安装成功

以counter合约为例来看如何编写一个Solidity合约。

合约样例

代码在 contractsdk/evm/example/Counter.sol

 1pragma solidity >=0.0.0;
 2
 3contract Counter {
 4    address owner;
 5    mapping (string => uint256) values;
 6
 7    constructor() public{
 8        owner = msg.sender;
 9    }
10
11    function increase(string memory key) public payable{
12        values[key] = values[key] + 1;
13    }
14
15    function get(string memory key) view public returns (uint) {
16        return values[key];
17    }
18
19    function getOwner() view public returns (address) {
20        return owner;
21    }
22
23}
代码
  • solidity合约相关文档请参见 https://github.com/ethereum/solidity

  • 更多的Solidity语言合约例子在 XuperChain 项目的 core/contractsdk/evm/example 以及 https://github.com/OpenZeppelin/openzeppelin-contracts 里面寻找。

合约编译

Solidity合约使用如下命令来编译合约

1// 通过solc编译合约源码
2solc --bin --abi Counter.sol -o .
3// 合约二进制文件和abi文件分别存放在当前目录下,Counter.bin和Counter.abi
  • --bin :表示需要生成合约二进制文件

  • --abi :表示需要生成合约abi文件,用于合约方法以及参数编解码

  • -o:表示编译结果输出路径

合约部署

Solidity合约部署完整命令如下

1$ ./xchain-cli evm deploy --account XC1111111111111111@xuper --cname counterevm  --fee 5200000 Counter.bin --abi Counter.abi
  • --abi :表示合约abi文件

合约调用
1// 合约increase方法调用
2$ ./xchain-cli evm invoke --method increase -a '{"key":"stones"}' counterevm --fee 22787517 --abi Counter.abi
3// 合约get方法调用
4$ ./xchain-cli evm query --method get -a '{"key":"stones"}' counterevm --abi Counter.abi
  • --abi :表示合约abi文件

C++合约

以counter合约为例来看如何编写一个C++合约。

合约样例

代码在 contractsdk/cpp/example/counter.cc

 1#include "xchain/xchain.h"
 2struct Counter : public xchain::Contract {};
 3DEFINE_METHOD(Counter, initialize) {
 4    xchain::Context* ctx = self.context();
 5    const std::string& creator = ctx->arg("creator");
 6    if (creator.empty()) {
 7        ctx->error("missing creator");
 8        return;
 9    }
10    ctx->put_object("creator", creator);
11    ctx->ok("initialize succeed");
12}
13DEFINE_METHOD(Counter, increase) {
14    xchain::Context* ctx = self.context();
15    const std::string& key = ctx->arg("key");
16    std::string value;
17    ctx->get_object(key, &value);
18    int cnt = 0;
19    cnt = atoi(value.c_str());
20    char buf[32];
21    snprintf(buf, 32, "%d", cnt + 1);
22    ctx->put_object(key, buf);
23    ctx->ok(buf);
24}
25DEFINE_METHOD(Counter, get) {
26    xchain::Context* ctx = self.context();
27    const std::string& key = ctx->arg("key");
28    std::string value;
29    if (ctx->get_object(key, &value)) {
30        ctx->ok(value);
31    } else {
32        ctx->error("key not found");
33    }
34}
代码解析

下面我们逐行解析合约代码:

  • #include <xchain/xchain.h> 为必须的,里面包含了编写合约所需要的库。

  • struct Counter : public xchain::Contract {}: 声明了我们的合约类,所有的合约类都要继承自 xchain::Contract

  • DEFINE_METHOD(Counter, initialize) 我们通过 DEFINE_METHOD 来为合约类定义合约方法,在这个例子里面我们为 Counter 类定义了一个叫 initialize 的合约方法。

  • xchain::Context* ctx = self.context() :用来获取合约的上下文,每个合约都有一个对应的合约执行上下文,通过上下文我们可以获取合约参数,写入合约数据,context对象是我们经常要操作的一个对象。

  • const std::string& creator = ctx->arg("creator"); ,用于从合约上下文里面获取合约方法的参数,这里我们获取了名字叫 creator 的合约参数,合约的参数列表是一个map结构, key为合约参数的名字,value为参数对应的用户传递的值。

  • ctx->put_object("creator", creator); 通过合约上下文的 put_object 方法,我们可以向链上写入数据。

  • ctx->ok("initialize succeed"); 用于返回合约的执行结果,如果合约执行失败则调用 ctx->error

通过上面的代码分析我们得到了如下知识

  • 一个合约有多个方法组成,如counter合约的 initializeincrease , get 方法。

  • initialize 是每个合约必须实现的方法,这个合约方法会在部署合约的时候自动执行。

  • 每个合约方法有一个 Context 对象,通过这个对象我们能获取到很多有用的方法,如获取用户参数等。

  • 通过 Context 对象的 ok 或者 error 方法我们能给调用方反馈合约的执行情况:成功或者失败。

更多的c++语言合约例子在 XuperChain 项目的 core/contractsdk/cpp/example 里面寻找。

Go合约

以counter合约为例来看如何编写一个go合约。

合约样例

代码在 contractsdk/go/example/counter/counter.go

 1package main
 2import (
 3    "strconv"
 4    "github.com/xuperchain/xuperchain/core/contractsdk/go/code"
 5    "github.com/xuperchain/xuperchain/core/contractsdk/go/driver"
 6)
 7type counter struct{}
 8func (c *counter) Initialize(ctx code.Context) code.Response {
 9    creator, ok := ctx.Args()["creator"]
10    if !ok {
11        return code.Errors("missing creator")
12    }
13    err := ctx.PutObject([]byte("creator"), creator)
14    if err != nil {
15        return code.Error(err)
16    }
17    return code.OK(nil)
18}
19func (c *counter) Increase(ctx code.Context) code.Response {
20    key, ok := ctx.Args()["key"]
21    if !ok {
22        return code.Errors("missing key")
23    }
24    value, err := ctx.GetObject(key)
25    cnt := 0
26    if err == nil {
27        cnt, _ = strconv.Atoi(string(value))
28    }
29    cntstr := strconv.Itoa(cnt + 1)
30    err = ctx.PutObject(key, []byte(cntstr))
31    if err != nil {
32        return code.Error(err)
33    }
34    return code.OK([]byte(cntstr))
35}
36func (c *counter) Get(ctx code.Context) code.Response {
37    key, ok := ctx.Args()["key"]
38    if !ok {
39        return code.Errors("missing key")
40    }
41    value, err := ctx.GetObject(key)
42    if err != nil {
43        return code.Error(err)
44    }
45    return code.OK(value)
46}
47func main() {
48    driver.Serve(new(counter))
49}

go合约的整体框架结构跟c++合约一样,在表现形式上稍微有点不一样:

  • c++合约使用 DEFINE_METHOD 来定义合约方法,go通过结构体方法来定义合约方法。

  • c++通过 ctx->ok 来返回合约数据,go通过返回 code.Response 对象来返回合约数据。

  • go合约需要在main函数里面调用 driver.Serve 来启动合约。

更多的go语言合约例子在 XuperChain 项目的 core/contractsdk/go/example 里面寻找。

合约编译

Go合约使用如下命令来编译合约

1go build -o hello
合约部署
1$ ./xchain-cli native deploy --account XC1111111111111111@xuper --cname hello  --fee 5200000 --runtime go ./hello-go/hello

Go合约的调用跟c++合约参数一致。

Java合约

java合约目前只支持native合约。

如果本地搭建 XuperChain 环境,在部署、调用native合约之前,请先查看`conf/xchain.yaml` 中native一节,确保native合约功能开启。

1# 管理native合约的配置
2native:
3    enable: true

以counter合约为例来看如何编写一个java合约。

编译环境准备

编译Java sdk:Java版本不低于Java1.8版本

包管理器:maven,mvn版本3.6+

# 编译java sdk
cd contractsdk/java
mvn install -f pom.xml
# 产出二进制文件target/java-contract-sdk-0.1.0.jar,并自动安装到mvn本地仓库下
合约样例

代码在 contractsdk/java/example/counter/src/main/java/com/baidu/xuper/example/Counter.java

 1package com.baidu.xuper.example;
 2
 3import java.math.BigInteger;
 4
 5import com.baidu.xuper.Context;
 6import com.baidu.xuper.Contract;
 7import com.baidu.xuper.ContractMethod;
 8import com.baidu.xuper.Driver;
 9import com.baidu.xuper.Response;
10
11/**
12* Counter
13*/
14public class Counter implements Contract {
15
16    @Override
17    @ContractMethod
18    public Response initialize(Context ctx) {
19        return Response.ok("ok".getBytes());
20    }
21
22    @ContractMethod
23    public Response increase(Context ctx) {
24        byte[] key = ctx.args().get("key");
25        if (key == null) {
26            return Response.error("missing key");
27        }
28        BigInteger counter;
29        byte[] value = ctx.getObject(key);
30        if (value != null) {
31            counter = new BigInteger(value);
32        } else {
33            ctx.log("key " + new String(key) + " not found, initialize to zero");
34            counter = BigInteger.valueOf(0);
35        }
36        ctx.log("get value " + counter.toString());
37        counter = counter.add(BigInteger.valueOf(1));
38        ctx.putObject(key, counter.toByteArray());
39
40        return Response.ok(counter.toString().getBytes());
41    }
42
43    @ContractMethod
44    public Response get(Context ctx) {
45        byte[] key = ctx.args().get("key");
46        if (key == null) {
47            return Response.error("missing key");
48        }
49        BigInteger counter;
50        byte[] value = ctx.getObject(key);
51        if (value != null) {
52            counter = new BigInteger(value);
53        } else {
54            return Response.error("key " + new String(key) + " not found)");
55        }
56        ctx.log("get value " + counter.toString());
57
58        return Response.ok(counter.toString().getBytes());
59    }
60
61    public static void main(String[] args) {
62        Driver.serve(new Counter());
63    }
64}

java合约的整体框架结构跟c++、go合约一样,在表现形式上稍微有点不一样:

  • c++合约使用 DEFINE_METHOD 来定义合约方法,go通过结构体方法来定义合约方法,java通过定义class类方法来定义合约。

  • c++通过 ctx->ok 来返回合约数据,go通过返回 code.Response 对象来返回合约数据,java通过 Response.ok 来返回合约数据。

  • java合约需要在main函数里面调用 Driver.serve 来启动合约。

更多的java语言合约例子在 XuperChain 项目的 core/contractsdk/java/example 里面寻找。

合约编译

java合约使用如下命令来编译合约

cd contractsdk/java/example/counter
mvn package -f pom.xml
# 产出二进制文件target/counter-0.1.0-jar-with-dependencies.jar,用于合约部署
合约部署

native合约和wasm合约在合约部署和合约执行上通过 nativewasm 字段进行区分。

不同语言的合约通过 --runtime 参数进行指定,完整命令如下。

# 部署golang native合约
./xchain-cli native deploy --account XC1111111111111111@xuper --fee 15587517 --runtime java counter-0.1.0-jar-with-dependencies.jar --cname javacounter
  • --runtime c :表示部署的是c++合约

  • --runtime go :表示部署的是golang合约

  • --runtime java:表示部署的是java合约

java合约的调用跟c++、go合约参数一致。

小结

在这个章节里面我们学习了如何使用solidity、c++、go和java语言来编写合约,更多的合约例子可以在对应语言SDK的example目录里面寻找,在下一章节我们学习如果给合约编写单元测试。

合约单测

如果每次测试合约都需要部署到链上再发起调用会特别麻烦,xdev工具提供了单测能力,可以脱离链上环境运行合约。

test目录下放着合约测试文件,文件以 .test.js结尾,可以有多个测试文件。 以hello-cpp目录下的test/hello.test.js为例,文件内容如下:

 1var assert = require("assert");
 2Test("hello", function (t) {
 3    var contract;
 4    t.Run("deploy", function (tt) {
 5        contract = xchain.Deploy({
 6            name: "hello",
 7            code: "../hello.wasm",
 8            lang: "c",
 9            init_args: {}
10        })
11    });
12    t.Run("invoke", function (tt) {
13        resp = contract.Invoke("hello", {});
14        assert.equal(resp.Body, "hello world");
15    })
16})

使用Test函数来定义测试case,hello为测试名字, 匿名js function作为测试的body。 全局对象xchain是我们跟xchain环境打交道的入口,xchain.Deploy用来部署一个合约到xchain环境,返回的contract对象,调用contract.Invoke方法即可调用合约。 Deploy和Invoke方法都是通过抛出异常的方式来处理错误,测试框架会自动捕获错误来结束测试case。t.Run可以定义子测试case。

使用如下命令来启动测试

1$ cd hello-cpp
2$ xdev test # 测试test目录下的所有case
3=== RUN   hello
4=== RUN   hello/deploy
5=== RUN   hello/invoke
6--- PASS: hello (0.11s)
7    --- PASS: hello/deploy (0.07s)
8    --- PASS: hello/invoke (0.02s)
9PASS

VSCode编辑器集成

配置编译和测试task

为了方便在vscode里面编译和测试合约,在 .vscode/tasks.json 里面添加如下内容

 1{
 2    // See https://go.microsoft.com/fwlink/?LinkId=733558
 3    // for the documentation about the tasks.json format
 4    "version": "2.0.0",
 5    "tasks": [
 6        {
 7            "label": "xdev build",
 8            "type": "shell",
 9            "command": "xdev build -p",
10            "options": {
11                "cwd": "${workspaceFolder}"
12            },
13            "group": {
14                "kind": "build",
15                "isDefault": true
16            }
17        },
18        {
19            "label": "xdev test",
20            "type": "shell",
21            "command": "xdev test",
22            "options": {
23                "cwd": "${workspaceFolder}"
24            }
25        }
26    ]
27}

编译合约

Run Build Task(⇧⌘B)来启动构建

_images/xdev-build1.gif

跑合约单测

调用Run Task命令之后,选择xdev test来触发单元测试

_images/xdev-test.gif

代码补全

为了让vscode帮我们自动补全代码,需要做如下配置,在项目的.vscode/settings.json文件里面加上这一个配置

1{
2    "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json"
3}

之后就能用vscode的自动补全功能了.

开放网络集成环境

XuperChain 开放网络是基于百度自研底层技术搭建的区块链基础服务网络,符合中国标准,超级节点遍布全国,区块链网络完全开放,为用户提供区块链快速部署和运行的环境,最低2元钱就用上的区块链服务,让信任链接更加便利。

XuperChain 开放网络为开发者提供了合约开发、编译、部署、管理的一站式可视化集成环境,下面介绍如何在开放网络上开发部署智能合约。

_images/xuperos-dashboard.png

账户注册

  1. 在 XuperChain 官网 https://xchain.baidu.com/ 使用百度账号登录,如果没有百度账号请先注册。

  2. 进入 XuperChain 开放网络控制台,第一次登录的用户,平台会为用户创建区块链账户,请按照创建账户指引文档完成安全码设置,并记录自己的助记词和私钥。

_images/xuperos-create-account.png

创建合约账户

  1. 在工作台,选择「开放网络 —> 合约管理」,点击「创建合约账户」

  2. 进入创建合约账户页,输入安全码后点击「确认创建」,系统自动生成账户名称后,即创建完毕

_images/xuperos-no-account.png

合约开发和部署

  1. 在工作台,选择「开放网络 —> 合约管理」,点击「创建智能合约」

  2. 进入新页面,按要求填写基本信息、编辑合约代码,编译成功后点击「安装」,即可进入合约安装(部署)流程。 合约代码编译有两种方式:

    • 模板合约;选择模板后,只需在模板代码中填写相关参数即可(参考模板详情完成参数填写)

    • 自定义合约;在编辑器内完成C++语言的合约编辑即可

_images/xuperos-create-contract.png
  1. 进入安装流程,用户需按合约代码完成预执行操作。点击「开始验证」,执行通过会进入安装确认页

    • 模板合约;系统会提供模板的函数,只需填写参数即可(可参考模板详情)

    • 自定义合约;根据页面操作说明,完成函数、参数填写

_images/xuperos-install-contract.png
  1. 进入确认安装页,页面显示安装合约预计消耗的余额。点击「安装合约」将合约上链,上链过程需要等待10S左右。安装完成后,在合约管理列表中可看到合约状态变更为‘安装成功’,即该合约已完成安装。

合约调用

目前开放网络支持通过Go和Javascript两种SDK调用智能合约。

结语

通过上面的学习,相信大家已经掌握了如何编写 XuperChain 智能合约的方法,想要更深入了解 XuperChain ,可以通过访问 XuperChain 开源项目 https://github.com/xuperchain/xuperchain 来获取更多的学习资料。

XuperChain RPC 接口使用说明

XuperChain为方便用户深度使用 XuperChain 的各项功能,提供了多语言版本的SDK(JSGolangC#JavaPython),这里我们以Golang为例来介绍一下XuperChain的RPC接口使用方式。

注解

目前官方提供的SDK中,golang语言版本的功能最为全面,其他语言的功能弱一些,我们非常欢迎社区朋友一起建设SDK,参与贡献会获得开放网络的资源,可用于购买开放网络的服务。

RPC接口介绍

查看XuperChain的 proto文件 ,可以在service定义中获取所有支持的RPC接口

GetBalance

此接口用于查询指定地址中的余额

参数结构

AddressStatus

返回结构

AddressStatus

这里 AddressStatus 的定义如下

1message AddressStatus {
2    Header header = 1;
3    string address = 2;
4    repeated TokenDetail bcs = 3;
5}

其中的 address 字段为需要查询的地址,传入string即可

其中的 bcs 字段为需要查询的链名,因为XuperChain支持平行链的功能,此字段为列表,亦可传入多个链名,

TokenDetail 定义如下:

1message TokenDetail {
2    string bcname = 1;
3    string balance = 2;
4    XChainErrorEnum error = 3;
5}

请求时只需传入 bcname 字段,例如 "xuper",其余字段为返回时携带的,balance即为对应平行链上的余额

其中的 Header 如下

1message Header {
2    string logid = 1;
3    string from_node = 2;
4    XChainErrorEnum error = 3;
5}

Header中的logid是回复中也会携带的id,用来对应请求或追溯日志使用的,一般用 core/global/common.go 中的 Glogid() 生成一个全局唯一id

Header中的from_node一般不需要填写,error字段也是返回中携带的错误内容,发请求时不需填写

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7bc := &pb.TokenDetail{
 8    Bcname: "xuper",
 9}
10in := &pb.AddressStatus{
11    Header: global.Glogid(),
12    Address: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
13    Bcs: []*pb.TokenDetail{bc},
14}
15out, _ := cli.GetBalance(context.Background(), in)

GetBalanceDetail

此接口用于查询指定地址中的余额详细情况

参数结构

AddressBalanceStatus

返回结构

AddressBalanceStatus

AddressBalanceStatus 定义如下

1message AddressBalanceStatus {
2    Header header = 1;
3    string address = 2;
4    repeated TokenFrozenDetails tfds = 3;
5}

address字段与GetBalance一样,tfds字段则多了是否冻结的内容,tfds在请求中只需要填充bcname,返回时会有TokenFrozenDetail数组给出正常余额和冻结余额的信息

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7tfd := &pb.TokenFrozenDetails{
 8    Bcname: "xuper",
 9}
10in := &pb.AddressBalanceStatus{
11    Header: global.Glogid(),
12    Address: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
13    Tfds: []*pb.TokenFrozenDetails{bc},
14}
15out, _ := cli.GetBalanceDetail(context.Background(), in)

GetFrozenBalance

此接口用于查询指定地址中的冻结余额,请求方式与GetBalance完全一致,这里不再赘述

GetBlock

此接口用于查询指定id的区块内容

参数结构

BlockID

返回结构

Block

BlockID 定义如下

1message BlockID {
2    Header header = 4;
3    string bcname = 1;
4    bytes blockid = 2;
5    bool need_content = 3;  //是否需要内容
6}

header和bcname字段如上所述,blocked为要查询的区块id,注意是bytes类型,可能需要hex decode

need_content字段为布尔值,表明是否需要详细的区块内容(还是只查询区块是否在链和前驱后继)

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7id, _ := hex.DecodeString("ee0d6fd34df4a7e1540df309d47441af4fda6fdd9d841046f18e7680fe0cea8c")
 8in := &pb.BlockID{
 9    Header: global.Glogid(),
10    Bcname: "xuper",
11    Blockid: id,
12    NeedContent: true,
13}
14out, _ := cli.GetBlock(context.Background(), in)

GetBlockByHeight

此接口用于查询指定高度的区块内容

参数结构

BlockHeight

返回结构

Block

BlockHeight定义如下

1message BlockHeight {
2    Header header = 3;
3    string bcname = 1;
4    int64 height = 2;
5}

同GetBlock类似,id换成整型的高度即可,返回内容也是类似的

GetBlockChainStatus

此接口用于查询指定链的当前状态

参数结构

BCStatus

返回结构

BCStatus

BCStatus定义如下

1message BCStatus {
2    Header header = 1;
3    string bcname = 2;
4    LedgerMeta meta = 3;
5    InternalBlock block = 4;
6    UtxoMeta utxoMeta = 5;
7    repeated string branchBlockid = 6;
8}

传入参数只需填充header,bcname即可

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7in := &pb.BCStatus{
 8    Header: global.Glogid(),
 9    Bcname: "xuper",
10}
11out, _ := cli.GetBlockChainStatus(context.Background(), in)

GetBlockChains

此接口用于查询当前节点上有哪些链

参数结构

CommonIn

返回结构

BlockChains

CommonIn结构很简单,只有header字段,返回的BlockChains也仅有一个链名的string数组

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7in := &pb.CommonIn{
 8    Header: global.Glogid(),
 9}
10out, _ := cli.GetBlockChains(context.Background(), in)

GetSystemStatus

此接口用于查询当前节点的运行状态

参数结构

CommonIn

返回结构

SystemsStatusReply

此接口相当于先查询了GetBlockChains,在用GetBlockChainStatus查询每个链的状态,不在赘述

GetNetURL

此接口用于查询当前节点的netUrl

参数结构

CommonIn

返回结构

RawUrl

RawUrl除了header字段外仅有一个string字段,表示返回的netURL

QueryACL

此接口用于查询指定合约账号的ACL内容

参数结构

AclStatus

返回结构

AclStatus

AclStatus定义如下

1message AclStatus {
2    Header header = 1;
3    string bcname = 2;
4    string accountName = 3;
5    string contractName = 4;
6    string methodName = 5;
7    bool confirmed = 6;
8    Acl acl = 7;
9}

请求中仅需填充header,bcname,accountName即可,其余为返回内容

以下为Golang示例

1in := &pb.AclStatus{
2    Header: global.Glogid(),
3    Bcname: "xuper",
4    AccountName: "XC1111111111111111@xuper",
5}
6out, _ := cli.QueryACL(context.Background(), in)

QueryTx

此接口用于查询指定id的交易内容

参数结构

TxStatus

返回结构

TxStatus

TxStatus定义如下

1message TxStatus {
2    Header header = 1;
3    string bcname = 2;
4    bytes txid = 3;
5    TransactionStatus status = 4;  //当前状态
6    int64 distance = 5;  //离主干末端的距离(如果在主干上)
7    Transaction tx = 7;
8}

请求中仅需填充header,bcname,txid字段

以下为Golang示例

1id, _ := hex.DecodeString("763ac8212c80b8789cefd049f1529eafe292f4d64eaffbc2d5fe19c79062a484")
2in := &pb.AclStatus{
3    Header: global.Glogid(),
4    Bcname: "xuper",
5    Txid: id,
6}
7out, _ := cli.QueryTx(context.Background(), in)

SelectUTXO

此接口用于获取账号可用的utxo列表

参数结构

UtxoInput

返回结构

UtxoOutput

UtxoInput定义如下

 1message UtxoInput {
 2    Header header = 1;
 3    // which bcname to select
 4    string bcname = 2;
 5    // address to select
 6    string address = 3;
 7    // publickey of the address
 8    string publickey = 4;
 9    // totalNeed refer the total need utxos to select
10    string totalNeed = 5;
11    // userSign of input
12    bytes userSign = 7;
13    // need lock
14    bool needLock = 8;
15}

请求中只需填充header,bcname,address,totalNeed,needLock,其中needLock表示是否需要锁定utxo(适用于并发执行场景)

UtxoOutput中的返回即可在组装交易时使用,具体组装交易的过程可参考文档下方

1in := &pb.UtxoInput{
2    Header: global.Glogid(),
3    Bcname: "xuper",
4    Address: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
5    TotalNeed: "50",
6    NeedLock: true,
7}
8out, _ := cli.SelectUTXO(context.Background(), in)

SelectUTXOBySize

此接口用于获取账号中部分utxo,填满交易后便不在继续获取

参数结构

UtxoInput

返回结构

UtxoOutput

使用过程和SelectUTXO基本相同,仅少了totalNeed字段。适用拥有太多utxo,一次SelectUtxo内容超过交易容纳上限时使用

PreExec

此接口用于在节点上进行合约的预执行操作,返回预执行后的请求和回复

参数结构

InvokeRPCRequest

返回结构

InvokeRPCResponse

InvokeRPCRequest定义如下

1message InvokeRPCRequest {
2    Header header = 1;
3    string bcname = 2;InvokeRequest
4    repeated  requests = 3;
5    string initiator = 4;
6    repeated string auth_require = 5;
7}

其中的InvokeRequest定义如下

1message InvokeRequest {
2    string module_name = 1;
3    string contract_name = 2;
4    string method_name = 3;
5    map<string, bytes> args = 4;
6    repeated ResourceLimit resource_limits = 5;
7    string amount = 6;
8}

其中必填字段有module_name,contract_name,method_name,args,具体示例可参见下一章节

PreExecWithSelectUTXO

此接口用于在节点上进行消耗资源的合约预执行操作,内部是由一个PreExec加上一个SelectUTXO实现的,预执行并选择出需要消耗数额的utxo

参数结构

PreExecWithSelectUTXORequest

返回结构

PreExecWithSelectUTXOResponse

PreExecWithSelectUTXORequest定义如下,实际上就是把预执行的请求结构放在了SelectUTXO结构中

1message PreExecWithSelectUTXORequest {
2    Header header = 1;
3    string bcname = 2;
4    string address = 3;
5    int64 totalAmount = 4;
6    SignatureInfo signInfo = 6;
7    bool needLock = 7;
8    InvokeRPCRequest request = 5;
9}

具体填充方式可参考下一章节

PostTx

此接口用于提交交易,是大部分操作都需要的最终环节

参数结构

TxStatus

返回结构

CommonReply

请求结构TxStatus定义在QueryTx中已经给出,但提交交易时需要填充Transaction字段,定义如下

 1message Transaction {
 2    // txid is the id of this transaction
 3    bytes txid = 1;
 4    // the blockid the transaction belong to
 5    bytes blockid = 2;
 6    // Transaction input list
 7    repeated TxInput tx_inputs = 3;
 8    // Transaction output list
 9    repeated TxOutput tx_outputs = 4;
10    // Transaction description or system contract
11    bytes desc = 6;
12    // Mining rewards
13    bool coinbase = 7;
14    // Random number used to avoid replay attacks
15    string nonce = 8;
16    // Timestamp to launch the transaction
17    int64 timestamp = 9;
18    // tx format version; tx格式版本号
19    int32 version = 10;
20    // auto generated tx
21    bool autogen = 11;
22    repeated TxInputExt tx_inputs_ext = 23;
23    repeated TxOutputExt tx_outputs_ext = 24;
24    repeated InvokeRequest contract_requests = 25;
25    // 权限系统新增字段
26    // 交易发起者, 可以是一个Address或者一个Account
27    string initiator = 26;
28    // 交易发起需要被收集签名的AddressURL集合信息,包括用于utxo转账和用于合约调用
29    repeated string auth_require = 27;
30    // 交易发起者对交易元数据签名,签名的内容包括auth_require字段
31    repeated SignatureInfo initiator_signs = 28;
32    // 收集到的签名
33    repeated SignatureInfo auth_require_signs = 29;
34    // 节点收到tx的时间戳,不参与签名
35    int64 received_timestamp = 30;
36    // 统一签名(支持多重签名/环签名等,与initiator_signs/auth_require_signs不同时使用)
37    XuperSignature xuper_sign = 31;
38    // 可修改区块链标记
39    ModifyBlock modify_block = 32;
40}

Transaction属于XuperChain中比较核心的结构了,下一章我们将介绍各种场景的交易如何构造并提交

RPC接口应用

本章节将以几个简单的场景为例描述RPC接口的使用方法,主要体现逻辑和步骤。代码中仅使用了原始的RPC接口,如果使用SDK则会简便很多。

发起一次转账

这里我们演示如何使用RPC接口实现从账号Aclie向账号Bob的一次数额为10的转账,为了进行此操作,我们事先需要有以下信息(均为string)

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

Bob的地址

addr_bob

发起转账交易的总体逻辑为,首先通过SelectUTXO获取Alice数额为10的资产,然后构造交易,最后通过PostTx提交

 1// 获取Alice的utxo
 2utxoreq := &pb.UtxoInput{
 3    Header: global.Glogid(),
 4    Bcname: "xuper",
 5    Address: addr_alice,
 6    TotalNeed: "10",
 7    NeedLock: true,
 8}
 9utxorsp, _ := cli.SelectUTXO(context.Background(), utxoreq)
10// 声明一个交易,发起者为Alice地址,因为是转账,所以Desc字段什么都不填
11// 如果是提案等操作,将客户端的 --desc 参数写进去即可
12tx := &pb.Transaction{
13    Version: 1,
14    Coinbase: false,
15    Desc: []byte(""),
16    Nonce: global.GenNonce(),
17    Timestamp: time.Now().UnixNano(),
18    Initiator: addr_alice,
19}
20// 填充交易的输入,即Select出来的Alice的utxo
21for _, utxo := range utxorsp.UtxoList {
22    txin := &pb.TxInput{
23        RefTxid: utxo.RefTxid,
24        RefOffset: utxo.RefOffset,
25        FromAddr: utxo.ToAddr,
26        Amount: utxo.Amount,
27    }
28    tx.TxInputs = append(tx.TxInputs, txin)
29}
30// 填充交易的输出,即给Bob的utxo,注意Amount字段的类型
31amount, _ := big.NewInt(0).SetString("10", 10)
32txout := &pb.TxOutput{
33    ToAddr: []byte(addr_bob),
34    Amount: amount.Bytes(),
35}
36tx.TxOutputs = append(tx.TxOutputs, txout)
37// 如果Select出来的Alice的utxo多于10,需要构造一个给Alice的找零
38total, _ := big.NewInt(0).SetString(utxorsp.TotalSelected, 10)
39if total.Cmp(amount) > 0 {
40    delta := total.Sub(total, amount)
41    charge := &pb.TxOutput{
42        ToAddr: []byte(addr_alice),
43        Amount: delta.Bytes(),
44    }
45    tx.TxOutputs = append(tx.TxOutputs, charge)
46}
47// 接下来用Alice的私钥对交易进行签名,在此交易中,我们只需Alice签名确认即可
48tx.AuthRequire = append(tx.AuthRequire, addr_alice)
49// 签名需要的库在 github.com/xuperchain/xuperchain/core/crypto/client
50// 和 github.com/xuperchain/xuperchain/core/crypto/hash
51cryptoCli, _ := client.CreateCryptoClient("default")
52sign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
53signInfo := &pb.SignatureInfo{
54    PublicKey: pub_alice,
55    Sign: sign,
56}
57// 将签名填充进交易
58tx.InitiatorSigns = append(tx.InitiatorSigns, signInfo)
59tx.AuthRequireSigns = append(tx.AuthRequireSigns, signInfo)
60// 生成交易ID
61tx.Txid, _ = txhash.MakeTransactionID(tx)
62// 构造最终要Post的TxStatus
63txs := &pb.TxStatus{
64    Bcname: "xuper",
65    Status: pb.TransactionStatus_UNCONFIRM,
66    Tx: tx,
67    Txid: tx.Txid,
68}
69// 最后一步,执行PostTx
70rsp, err := cli.PostTx(context.Background(), txs)
71// 这里的rsp即CommonReply,包含logid等内容
72// 交易id我们已经生成在tx.Txid中,不过是bytes,输出可能需要hex.EncodeToString一下

新建合约账号

这里我们演示创建一个合约账号 XC1234567812345678@xuper ,ACL如下

 1{
 2    "pm": {
 3        "rule": 1,
 4        "acceptValue": 1.0
 5    },
 6    "aksWeight": {
 7        "XXXaddress-aliceXXX" : 0.6,
 8        "XXXXaddress-bobXXXX" : 0.4
 9    }
10}

为了进行此操作,我们事先需要有以下信息

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

ACL的内容

acct_acl

创建合约账号的总体逻辑为,首先进行创建合约账号的预执行,然后构造相应的交易内容(如果需要支付资源由Alice出),最后提交交易

  1// 构造创建合约账号的请求
  2args := make(map[string][]byte)
  3args["account_name"] = []byte(1234567812345678)
  4args["acl"] = []byte(acct_acl)
  5invokereq := &pb.InvokeRequest{
  6    ModuleName: "xkernel",
  7    MethodName: "NewAccount",
  8    Args: args,
  9}
 10invokereqs := []*pb.InvokeRequest{invokereq}
 11// 构造合约预执行的请求
 12authrequire := []string{addr_alice}
 13rpcreq := &pb.InvokeRPCRequest{
 14    Header: global.Glogid(),
 15    Bcname: "xuper",
 16    Requests: invokereqs,
 17    Initiator: addr_alice,
 18    AuthRequire: authrequire,
 19}
 20// 花手续费需要出资的账号确认,填充一个验证的签名,才能正确的拿出utxo来
 21// 签名需要的库在 github.com/xuperchain/xuperchain/core/crypto/client
 22// 和 github.com/xuperchain/xuperchain/core/crypto/hash
 23content := hash.DoubleSha256([]byte("xuper" + addr_alice + "0" + "true"))
 24cryptoCli, _ := client.CreateCryptoClient("default")
 25prikey, _ := cryptoCli.GetEcdsaPrivateKeyFromJSON([]byte(pri_alice))
 26sign, _ := cryptoCli.SignECDSA(prikey, content)
 27signInfo := &pb.SignatureInfo{
 28    PublicKey: pub_alice,
 29    Sign: sign,
 30}
 31// 组合一个PreExecWithSelectUTXORequest用来预执行同时拿出需要支付的Alice的utxo
 32prereq := &pb.PreExecWithSelectUTXORequest{
 33    Header: global.Glogid(),
 34    Bcname: "xuper",
 35    Address: addr_alice,
 36    TotalAmount: 0,
 37    SignInfo: signInfo,
 38    NeedLock: true,
 39    Request: rpcreq,
 40}
 41prersp := cli.PreExecWithSelectUTXO(context.Background(), prereq)
 42// 构造一个Alice发起的交易
 43tx := &pb.Transaction{
 44    Version: 1,
 45    Coinbase: false,
 46    Desc: []byte(""),
 47    Nonce: global.GenNonce(),
 48    Timestamp: time.Now().UnixNano(),
 49    Initiator: addr_alice,
 50}
 51// 填充支付的手续费,手续费需要“转账”给地址“$”
 52amount := big.NewInt(prersp.Response.GasUsed)
 53fee := &pb.TxOutput{
 54    ToAddr: []byte("$"),
 55    Amount: amount.Bytes(),
 56}
 57tx.TxOutputs = append(tx.TxOutputs, fee)
 58// 填充select出来的Alice的utxo
 59for _, utxo := range prersp.UtxoOutput.UtxoList {
 60    txin := &pb.TxInput{
 61        RefTxid: utxo.RefTxid,
 62        RefOffset: utxo.RefOffset,
 63        FromAddr: utxo.ToAddr,
 64        Amount: utxo.Amount,
 65    }
 66    tx.TxInputs = append(tx.TxInputs, txin)
 67}
 68// 处理找零的逻辑
 69total, _ := big.NewInt(0).SetString(prersp.UtxoOutput.TotalSelected, 10)
 70if total.Cmp(amount) > 0 {
 71    delta := total.Sub(total, amount)
 72    charge := &pb.TxOutput{
 73        ToAddr: []byte(addr_alice),
 74        Amount: delta,
 75    }
 76}
 77// 填充预执行的结果
 78tx.ContractRequests = prersp.GetResponse().GetRequests()
 79tx.TxInputsExt = prersp.GetResponse().GetInputs()
 80tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
 81// 给交易签名
 82tx.AuthRequire = append(tx.AuthRequire, addr_alice)
 83txsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
 84txsignInfo := &pb.SignatureInfo{
 85    PublicKey: pub_alice,
 86    Sign: txsign,
 87}
 88tx.InitiatorSigns = append(tx.InitiatorSigns, txsignInfo)
 89tx.AuthRequireSigns = append(tx.AuthRequireSigns, txsignInfo)
 90// 生成交易ID
 91tx.Txid, _ = txhash.MakeTransactionID(tx)
 92// 构造最终要Post的TxStatus
 93txs := &pb.TxStatus{
 94    Bcname: "xuper",
 95    Status: pb.TransactionStatus_UNCONFIRM,
 96    Tx: tx,
 97    Txid: tx.Txid,
 98}
 99// 最后一步,执行PostTx
100rsp, err := cli.PostTx(context.Background(), txs)

修改合约账号ACL

延续上一小节的例子,假设我们要把ACL修改成以下状态

 1{
 2    "pm": {
 3        "rule": 1,
 4        "acceptValue": 1.0
 5    },
 6    "aksWeight": {
 7        "XXXaddress-aliceXXX" : 1.0,
 8        "XXXXaddress-bobXXXX" : 1.0
 9    }
10}

为了进行此操作,我们事先需要有以下信息

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

Bob的地址

addr_bob

Bob的公钥

pub_bob

Bob的私钥

pri_bob

新ACL的内容

new_acl

修改ACL的总体逻辑为,首先进行修改的预执行,然后构造交易发送,这里需要注意的是,修改ACL操作需要满足现有的ACL要求才有权限,即Alice Bob都需要签名确认。简单起见,当中的手续费依然由Alice支付。

 1// 构造修改ACL的请求
 2args := make(map[string][]byte)
 3args["account_name"] = []byte(1234567812345678)
 4args["acl"] = []byte(new_acl)
 5invokereq := &pb.InvokeRequest{
 6    ModuleName: "xkernel",
 7    MethodName: "SetAccountAcl",
 8    Args: args,
 9}
10invokereqs := []*pb.InvokeRequest{invokereq}
11
12// 构造合约预执行的请求,和上一节一样,此处省略
13///////////////////////////////////////////////
14// 花手续费需要出资的账号确认,填充验证的签名,和上一节一样,此处省略
15/////////////////////////////////////////////////////////////////////
16// 按上一节逻辑一样,填充花费、找零,然后填充预执行的结果
17tx.ContractRequests = prersp.GetResponse().GetRequests()
18tx.TxInputsExt = prersp.GetResponse().GetInputs()
19tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
20// 给交易签名需要原ACL里的多个账号了
21tx.AuthRequire = append(tx.AuthRequire, addr_alice)
22tx.AuthRequire = append(tx.AuthRequire, addr_bob)
23alicesign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
24alicesignInfo := &pb.SignatureInfo{
25    PublicKey: pub_alice,
26    Sign: alicesign,
27}
28bobsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_bob))
29bobsignInfo := &pb.SignatureInfo{
30    PublicKey: pub_bob,
31    Sign: bobsign,
32}
33tx.InitiatorSigns = append(tx.InitiatorSigns, alicesignInfo)
34tx.AuthRequireSigns = append(tx.AuthRequireSigns, alicesignInfo)
35tx.AuthRequireSigns = append(tx.AuthRequireSigns, bobsignInfo)
36// 然后和上一节一致了,生成交易ID
37tx.Txid, _ = txhash.MakeTransactionID(tx)
38// 构造最终要Post的TxStatus
39txs := &pb.TxStatus{
40    Bcname: "xuper",
41    Status: pb.TransactionStatus_UNCONFIRM,
42    Tx: tx,
43    Txid: tx.Txid,
44}
45// 最后一步,执行PostTx
46rsp, err := cli.PostTx(context.Background(), txs)

部署一个合约

这里我们演示使用合约账号 XC1234567812345678@xuper 部署一个C++的counter合约,init参数为{"creator":"xchain"},假设合约账号的ACL是修改过的版本

为了进行此操作,我们事先需要有以下信息

合约文件字节内容

contract_code

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

部署合约的总体逻辑为,首先构造deploy操作预执行,部署需要的手续费由合约账号出,需要的签名由Alice提供(因为一个签名就满足ACL了)

  1// 构造部署合约的请求,关注args的内容,基本上和使用xchain-cli一致
  2args := make(map[string][]byte)
  3args["account_name"] = []byte("XC1234567812345678@xuper")
  4args["contract_name"] = []byte("counter")
  5// github.com/golang/protobuf/proto
  6codedesc := desc := &pb.WasmCodeDesc{
  7    Runtime: "c",
  8}
  9desc, _ := proto.Marshal(codedesc)
 10args["contract_desc"] = desc
 11args["contract_code"] = contract_code
 12initarg := `{"creator":"` + base64.StdEncoding.EncodeToString([]byte("xchain")) + `"}`
 13args["init_args"] = []byte(initarg)
 14invokereq := &pb.InvokeRequest{
 15    ModuleName: "xkernel",
 16    MethodName: "Deploy",
 17    Args: args,
 18}
 19invokereqs := []*pb.InvokeRequest{invokereq}
 20// 这里预执行的authrequire格式为 XC1234567812345678@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN,
 21// 表示是“某个合约账号的股东”,与直接写账号地址含义是不同的,ACL需求多个签名的时候即多个“股东”
 22authrequires := []string{"XC1234567812345678@xuper/XXXaddress-aliceXXX"}
 23rpcreq := &pb.InvokeRPCRequest{
 24    Header: global.Glogid(),
 25    Bcname: "xuper",
 26    Requests: invokereqs,
 27    Initiator: addr_alice,
 28    AuthRequire: authrequires,
 29}
 30// SelectUTXO的目标是合约账号中的余额,出资账号签名中的地址变成了合约账号,与“创建账号”小节有区别
 31content := hash.DoubleSha256([]byte("xuper" + "XC1234567812345678@xuper" + "0" + "true"))
 32prikey, _ := cryptoCli.GetEcdsaPrivateKeyFromJSON([]byte(pri_alice))
 33sign, _ := cryptoCli.SignECDSA(prikey, content)
 34signInfo := &pb.SignatureInfo{
 35    PublicKey: pub_alice,
 36    Sign: sign,
 37}
 38// 组合一个PreExecWithSelectUTXORequest用来预执行同时拿出需要支付的合约账号的utxo
 39prereq := &pb.PreExecWithSelectUTXORequest{
 40    Header: global.Glogid(),
 41    Bcname: "xuper",
 42    Address: "XC1234567812345678@xuper",
 43    TotalAmount: 0,
 44    SignInfo: signInfo,
 45    NeedLock: true,
 46    Request: rpcreq,
 47}
 48prersp, _ := cli.PreExecWithSelectUTXO(context.Background(), prereq)
 49// 构造一个Alice发起的交易
 50tx := &pb.Transaction{
 51    Version: 1,
 52    Coinbase: false,
 53    Desc: []byte(""),
 54    Nonce: global.GenNonce(),
 55    Timestamp: time.Now().UnixNano(),
 56    Initiator: addr_alice,
 57}
 58// 填充支付的手续费,手续费需要“转账”给地址“$”
 59amount := big.NewInt(prersp.Response.GasUsed)
 60fee := &pb.TxOutput{
 61    ToAddr: []byte("$"),
 62    Amount: amount.Bytes(),
 63}
 64tx.TxOutputs = append(tx.TxOutputs, fee)
 65// 填充select出来的Alice的utxo
 66for _, utxo := range prersp.UtxoOutput.UtxoList {
 67    txin := &pb.TxInput{
 68        RefTxid: utxo.RefTxid,
 69        RefOffset: utxo.RefOffset,
 70        FromAddr: utxo.ToAddr,
 71        Amount: utxo.Amount,
 72    }
 73    tx.TxInputs = append(tx.TxInputs, txin)
 74}
 75// 处理找零的逻辑
 76total, _ := big.NewInt(0).SetString(prersp.UtxoOutput.TotalSelected, 10)
 77if total.Cmp(amount) > 0 {
 78    delta := total.Sub(total, amount)
 79    charge := &pb.TxOutput{
 80        ToAddr: []byte("XC1234567812345678@xuper"),
 81        Amount: delta,
 82    }
 83}
 84// 填充预执行的结果
 85tx.ContractRequests = prersp.GetResponse().GetRequests()
 86tx.TxInputsExt = prersp.GetResponse().GetInputs()
 87tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
 88// 给交易签名,此处也是以“股东”身份签名
 89tx.AuthRequire = append(tx.AuthRequire, "XC1234567812345678@xuper/XXXaddress-aliceXXX")
 90txsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
 91txsignInfo := &pb.SignatureInfo{
 92    PublicKey: pub_alice,
 93    Sign: txsign,
 94}
 95// 虽然Alice和“股东Alice”含义不同,但签名的私钥是一样的
 96tx.InitiatorSigns = append(tx.InitiatorSigns, signInfo)
 97tx.AuthRequireSigns = append(tx.AuthRequireSigns, signInfo)
 98tx.Txid, _ = txhash.MakeTransactionID(tx)
 99// 构造最终要Post的TxStatus
100txs := &pb.TxStatus{
101    Bcname: "xuper",
102    Status: pb.TransactionStatus_UNCONFIRM,
103    Tx: tx,
104    Txid: tx.Txid,
105}
106// 最后一步,执行PostTx
107rsp, err := cli.PostTx(context.Background(), txs)

执行一个wasm合约

这里我们演示使用Alice账号调用上一节部署的counter合约,执行 increase 方法,参数为 {"key": "example"}

为了进行此操作,我们事先需要有以下信息

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

执行合约的总体逻辑为,首先构造相应预执行请求并预执行,如果是查询,那么直接读预执行结果即可,如果是要调用上链的操作,使用预执行结果组建交易并发送

 1// 构造执行合约的请求
 2args := make(map[string][]byte)
 3args["key"] = []byte("example")
 4invokereq := &pb.InvokeRequest{
 5    ModuleName: "wasm",
 6    MethodName: "increase",
 7    ContractName: "counter",
 8    Args: args,
 9}
10invokereqs := []*pb.InvokeRequest{invokereq}
11// 其他内容和“创建合约账号”一节完全一致

XuperIDE 使用文档

简介

XuperIDE 由超级链生态伙伴 黑曜石实验室(Obsidian Labs) 贡献。基于XuperChain开发的智能合约集成化开发环境的IDE开源项目,支持开发者一键创建节点、编写、编译、部署、调用合约等。

_images/xuperide_1_preview.jpg

安装

下载

目前 XuperIDE 支持 macOS, Linux 和 Windows 操作系统,对应的安装包可以在下方地址进行下载( macOS , Linux, Windows)。

安装

  • macOS: 双击打开 XuperIDE-x.x.x.dmg 并将 XuperIDE 拖动到应用文件夹内(初次运行时若出现未通过苹果验证的提示,可右键点击应用图标并打开,跳过验证)。

  • Linux: 双击打开 XuperIDE-x.x.x.AppImage, 选择 Properties* => *Permissions* => *Execute, 将 Allow executing file as progrom 选项打勾。关闭属性设置窗口并双击打开应用(不同的 Linux 发行版可能会有不同的安装方式)。

  • Windows: 双击 XuperIDE-x.x.x.exe,安装并打开应用。

功能预览

准备工作

在正确安装 XuperIDE 并初次启动时,你将看到一个欢迎页面,这里有 XuperIDE 正常运行所需要的依赖,包括了 Docker,XuperChain node 及 Xdev

_images/xuperide_2_welcome.jpg
  • XuperIDE 使用 Docker 来启动 Xuper 节点和进行项目编译。如果你之前没有安装过 Docker,可以点击 Install Docker 按钮访问 Docker 官方网站并进行下载安装;
  • XuperChain node 是超级链节点的 Docker 镜像,XuperIDE 使用这个镜像来运行 XuperChain 节点;

  • Xdev 是超级链 C++ 合约的开发及编译工具。

当所有依赖都正确安装并运行后,灰色的 Skip 按钮将会变成绿色的 Get Started 按钮。点击这个按钮进入 XuperIDE 的主界面。

创建密钥对

进入主界面后,我们需要首先创建一些密钥对。在 XuperIDE 的任意界面,点击应用左下⻆的钥匙图标,打开密钥管理器。

_images/xuperide_3_keypair.jpg

你可以在密钥管理器中创建、导入并管理密钥对。在创建和编辑密钥对的时候,你可以为该密钥对设置别名,方便在后续的使用中进行识别。密钥管理器除了对密钥对进行储存管理外,还将为创世区块提供创世地址。创建新的 Xuper 节点实例时,XuperIDE 会使用密钥管理器里的地址作为创世地址。

创建的新密钥对将使用中文助记词来生成私钥。不过,导入私钥的时候也可以导入英文助记词或者 JSON 格式超级链私钥。

在继续之前,请先在密钥管理器中创建一些密钥对,作为接下来创建节点实例的创世地址。

启动节点

点击顶部的 Network 标签,主页面将切换为网络管理器。在网络管理器中,我们可以进行 Xuper 节点版本和节点实例的管理,包括下载、删除 Xuper 节点版本,根据不同版本创建、删除和运行节点实例。

点击主页面中右上角的 New Instance 按钮打开创建新实例的弹窗,填写实例名称和选择合适的版本,点击 Create 按钮完成节点实例的创建。

_images/xuperide_4_newinstance.jpg

节点实例创建完成后,实例列表将显示刚刚创建好的实例,点击实例的绿色 Start 按钮启动 Xuper 节点。启动完成后,你可以在下方的日志查看器中检查节点运行日志。

_images/xuperide_5_node_started.jpg

连接百度超级链开放网络

XuperIDE 除了提供本地节点功能,也提供了连接百度超级链开放网络和连接自定义节点的功能。点击 Network 标签旁的下拉菜单选择 Baidu Xuper 可切换到百度超级链开放网络。

_images/xuperide_6_baidu_xuper.jpg

具体连接超级链方法请前往 百度超级链开放网络

区块浏览器

节点启动后,点击顶部的 Explorer 标签,主页面将切换为区块浏览器。在区块浏览器中,我们可以查询对应地址的信息。

通过标签旁边的下拉箭头,可以选择并打开密钥管理器中的地址。您也可以在地址栏中输入或粘贴一个地址。打开一个有效地址后,我们便可以看到对应地址的余额信息了,在余额信息的右方显示该地址对应的合约账户地址。

_images/xuperide_7_explorer.jpg

创建智能合约账户

XuperChain 的一个账户可以拥有多个智能合约账户,用户可以将智能合约部署在合约账户下。

点击地址栏最右侧的创建合约账号按钮,在弹出的创建合约账号窗口中填入创建的合约地址。合约账户地址格式为 16 位的纯数字,且第一位不能为 0。

_images/xuperide_8_create_account.jpg

点击创建后可以在浏览器中看到刚刚创建的合约账户地址信息,格式为 XC2000000000000000@xuper,点击可以跳转到该地址的合约交互页面。

创建智能合约项目

点击顶部的 Project 标签,主页面将切换至项目管理器。点击页面右上角的 New 按钮打开创建项目弹窗,输入项目名称并选择合适的模版,XuperIDE 目前提供了两个模版,分别为

  • C++ 版本的 Counter

  • Solidity 版本的 Counter

_images/xuperide_9_create_project.jpg

填写项目名称并选择合适的模版后点击创建项目,创建完成后 XuperIDE 将在项目管理页面中打开该项目。

Counter 合约实现了一个简单的区块链计数器,其中定义了两个方法 increase 和 get,分别为自增计数器和获取当前计数器的值。

编译智能合约项目

XuperChain 支持使用 C++ 及 Solidity 开发智能合约,其中 C++ 使用 Xdev 进行合约编译,Solidity 使用 Solc 进行编译。在右下角可以看到目前使用的编译器和编译器版本。可以点击该按钮选择希望使用的编译器版本,或者打开管理器下载更多可用的编译器版本。

点击工具栏的编译按钮(锤子形状),XuperIDE 将进行项目的编译,你可以通过下方的日志查看器来查看编译结果。编译后将在项目目录下中生成 wasm 或者 abi 文件。

_images/xuperide_10_build_contract.jpg

部署智能合约项目

点击工具栏的部署按钮(船形状),部署参数窗口将被打开,在这里可以输入部署合约的名称、构造函数的参数、交易签名者和合约账号。

_images/xuperide_11_deploy_contract.jpg

调用合约

成功部署智能合约后,点击区块浏览器,并在合约地址处选择刚刚部署的地址,主页面将切换至合约浏览器,XuperIDE 将自动打开刚才部署完成的智能合约。

合约浏览器页面主要分为两个部分:

  • 左边为合约的调用方法,可以根据合约代码填写调用的方法及参数,还可以选择不同的签名地址。

  • 右边为合约的数据读取,与调用合约类似,可以根据合约代码填写查询的方法及参数,可以选择不同的签名地址。

XuperIDE 会自动读取合约 ABI 中的 functions,并为每个 function 生成参数表单。用户可以简单的选择要调用的 function,输入参数,选择签名者(需要为 keypair manager 中存在的地址;读操作不需选择),并点击运行按钮进行合约调用。调用结果(成功或失败)将显示在下方的 result 栏中。

_images/xuperide_12_invoke_contract.jpg

接下来我们开始调用部署好的合约。在 Invoke 侧的 Method 中填入 increase,由于 increase 方法需要一个参数 key,将在 Args 下的输入框中留下一个参数,并在左边填入 key 右边填入 a,在 AuthorizationSigner 中选择地址,点击上方的执行按钮。完成交易后,我们可以看到成功执行的结果。合约会根据传入的 key 参数来增加 counter 的值。

在右边的 QueryMethod 中填入 get,将在 Args 下的输入框中留下一个参数,并在左边填入 key 右边填入 a,点击执行按钮,在下方 Result 中可以看到查询的结果,即计数器当前的值。

多次调用 increase 并通过 get 查询可以看到计数器根据调用 increase 的次数发生变化。

_images/xuperide_13_invoke_result.jpg

XuperChain 测试环境说明

区块链是信任的连接器, 通过区块链可以做到很多之前中心化信息系统做不到的事情,使得参与者可以凭借这个“连接器”完成成可信环境的构建和价值的安全流转。然而,目前多数公链的性能和安全不足以支撑各行各业的诸多场景,XuperChain 是百度自主研发的区块链技术,目前已启动测试环境对外公开测试,欢迎各界开发者使用我们的产品并且提出宝贵意见。

XuperChain 公开测试环境(XuperChain-testnet)

XuperChain 公开测试环境是 XuperChain 许可开放网络的测试版本,目前 XuperChain 测试环境已经实现了 XuperChain 的主要功能,为 XuperChain 早期用户和开发者提供一个可供使用的测试环境。

用户可以在 XuperChain 测试环境上测试部署和使用智能合约等功能,用户可以通过开源代码直接开发及平台化操作两种方式获取测试环境资源以及开发智能合约。

测试环境使用场景

XuperChain 测试环境 适用于

  • 创建测试账号,更为方便的按照教程尝试使用。

  • 开发测试智能合约,而无需担心影响真实数据资产。

  • XuperChain 新版本上线前的兼容性升级、功能测试等。

XuperChain 测试环境 不适用于

  • 压力测试:如果有压力测试需求,请在自行搭建的测试环境上实验,数据会更准确。

  • 可用性测试:测试环境并不保证高可用性,可能在某些情况下出现短暂的服务不可用。

  • 长期数据存储:测试环境不保证数据长期有效,可能在系统bug、不兼容升级、遭受攻击等情况下,会重置甚至关闭测试环境。我们会尽量保障用户数据不丢失,但在测试环境重置或关闭时,用户可能并不会得到通知,链上数据也可能无法找回。

测试环境资源

用户在测试环境部署和调用合约需要使用并且消耗测试资源。

目前测试环境获取测试资源会有以下两种方式:

  • 通过 XuperChain 公开网络渠道微信群、邮箱等等获取测试资质并领取定量测试资源;

  • 批量资源获取可邮件联系:xuper.baidu.com

测试资源仅用于测试环境消耗计算,只在测试环境上有效,没有任何经济价值和法律效力,也不支持用于任何形式的交易。如果测试环境因为遭受攻击、不兼容升级等情况下重建时,我们会尽量恢复用户账号中持有的测试资源。

测试环境开发方式

如何接入

测试环境通过RPC方式提供服务,开发者可以在github获取源代码,按照README说明编译得到cli客户端。

测试环境接入地址:14.215.179.74:37101

开发者只需要在使用xchain-cli时,通过-H参数指定测试环境地址,即可将客户端命令发送到测试环境。 例如查询账号测试资源:

1./xchain-cli account balance dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN -H 14.215.179.74:37101

如何开发智能合约

开发者需要首先创建合约账号,合约账号必须消耗一定的测试资源,用于部署合约。

更多关于测试环境使用的方法,请参见 测试环境使用指南

用户使用条款

XuperChain 用户在使用公开测试环境期间,不得访问或使用本网站采取任何可能损害我们或者任何第三方的行为,干扰本网络的运营或者以违反任何法律的方式使用本网络的行为, XuperChain 有权删除相关数据或者追究法律责任,包括但不限于:

  • 分享任何违反这些条款或其他适用条款的内容;

  • 上传病毒或恶意代码或做任何可能导致我们网络无法正常工作,负担过重或损害的事情;

  • 使用自动方式访问或收集我们产品的数据(未经我们事先许可)或尝试访问您无权访问的数据

  • 从事任何限制或禁止任何人使用或享用本网站的行为,或根据我们的判断会使我们或我们的任何用户、关联公司或任何其他第三方承担任何责任,损害或损害任何类型的行为。

  • 违反系统或网络安全可能导致责任。我们可以随时以任何理由暂停或终止您访问本网站,恕不另行通知

  • 使用本网络对外提供服务时,业务须自行前往网信办备案,如由于备案原因造成的法律风险本网站不承担任何责任。

您在使用 XuperChain 测试环境前,请确认已经明确了解上述用户须知,当您使用测试环境时表示您已知悉并接收测试环境用户须知。如果测试环境不能满足您的需求,您也可以按照 ` XuperChain 官方文档 <../quickstart.html>`_ 搭建自己的测试环境。如果因为测试环境使用上带来的问题,我们不承担任何法律责任。

XuperChain 测试环境使用指南

测试环境说明

在使用测试环境之前,请先点开 ` XuperChain 测试环境说明 <description.html>`_ ,认真阅读 XuperChain 测试环境的目的、使用场景、用户使用条款。

如果您对测试环境说明中的内容有任何疑问,可以通过 xchain.baidu.com 联系我们。如果您使用 XuperChain 测试环境,我们认为您已经明确并接受 XuperChain 测试环境说明中的相关内容和用户使用条款。

如何接入

测试环境通过RPC方式提供服务,开发者可以在 github 获取源代码,按照README说明编译得到cli客户端,当前测试环境使用v3.2分支。

  • 测试环境接入地址: 14.215.179.74:37101

  • 黄反服务的address: XDxkpQkfLwG6h56e896f3vBHhuN5g6M9u

开发者只需要在使用xchain-cli时,通过-H参数指定测试环境地址,即可将客户端命令发送到测试环境。 例如查询账号测试资源:

1./xchain-cli account balance dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN -H 14.215.179.74:37101

关于测试资源

测试环境上部署和执行智能合约需要消耗测试资源,测试环境目前尚未开放外部全节点P2P连接,且测试环境上节点出块并不会获得奖励,但出块节点会获得该块中所有Transaction中支付的测试资源。

目前获取测试资源主要有两种方式:

  1. 通过 XuperChain 公开网络渠道微信群、邮箱等等获取测试资质,可以免费获得测试资源。可以通过 这里 的微信二维码加入 XuperChain 用户微信群。

  2. 批量资源获取可邮件联系:xuper.baidu.com

创建账号

创建个人账号(AK)

个人账号(AK)其实是一组公私钥对,个人帐号地址(address)是根据公钥经过一定规则导出的一个散列值。个人账号可以离线生成,不需要上链,只有在个人账号产生测试资源变动时(例如转入了一部分测试资源)才会在UTXO中产生记录。

在data/keys下会有一个默认的个人账号(AK),包括address(你的地址)、private.key(你的私钥)、public.key(你的公钥),建议按照如下命令重新生成一个独有的个人账号。

  • 指定私钥目录:在data/test_demo下生成address、private.key、public.key: ./xchain-cli account newkeys --output data/test_demo

  • 覆盖默认目录: 覆盖data/keys下的文件,需要先删除data/keys目录,然后重新生成新的address、private.key、public.key

1rm -r data/keys
2./xchain-cli account newkeys

个人账号地址默认在data/keys/address文件中,可通过cat data/keys/address 查看自己的个人账号地址。

创建合约账号(Account)

合约账号可以用来部署智能合约,创建合约账号是一个上链操作,因此也需要消耗一定量的测试资源。合约账号可以设置为多个个人账号共同持有,只有一个交易中的背书签名满足一定合约账号的ACL要求,才能代表这个合约账号进行操作。关于合约账号和ACL权限相关的内容,可以参考

注解

创建合约账号需要向黄反服务拿一个签名,对应地,需要将黄反服务的address写到 data/acl/addrs 中,需要注意的是,multisig最终合入签名时需要将签名顺序与 data/acl/addrs 里面的地址顺序保持一致,否则会签名校验失败。

  • Step0: 创建合约账号是一个系统合约,可以通过多重签名的方式发起系统合约调用。系统合约调用需要先创建一个合约调用描述文件,例如下面newAccount.json是一个创建合约账号的描述文件。 newAccount.json文件内容:

1{
2    "module_name": "xkernel",
3    "method_name": "NewAccount",
4    "args" : {
5        "account_name": "1234098776890654",  # 说明:16位数字组成的字符串
6        "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 1},\"aksWeight\": {\"dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN\": 1}}"  # 这里的address改成自己的address
7    }
8}
  • Step1: 多重签名需要收集多个账号的签名,在测试环境中主要是需要交易发起者以及黄反服务的签名,因此修改data/acl/addrs文件,将需要收集签名的address写入该文件中。以创建合约账号为例,需要黄反服务背书,因此在该文件中写入黄反服务address。

1XDxkpQkfLwG6h56e896f3vBHhuN5g6M9u
  • Step2: 生成创建合约账号的原始交易,命令如下:

1./xchain-cli multisig gen --desc newAccount.json -H 14.215.179.74:37101 --fee 1000 --output rawTx.out
  • Step3: 向黄反服务获取签名,命令如下:

1./xchain-cli multisig get --tx ./rawTx.out --host 14.215.179.74:37101 --output complianceCheck.out
  • Step4: 自己对原始交易签名,命令如下:

1./xchain-cli multisig sign --tx ./rawTx.out --output my.sign
  • Step5: 将原始交易以及签名发送出去,命令如下:

1./xchain-cli multisig send my.sign complianceCheck.out --tx ./rawTx.out -H 14.215.179.74:37101

注解

Step5 中放签名的地方:第一个my.sign签名对应的是交易发起者(Initiator),第二个complianceCheck.out签名对应的是需要背书(AuthRequire)的地址,发起者签名和背书签名用空格分开,如果需要多个账号背书,那么多个背书签名用,隔开,且签名顺序需要与data/acl/addrs中的地址顺序一致。

创建成功后,你可以通过这个命令去查看你刚才创建的合约账号:

1./xchain-cli account query --host 14.215.179.74:37101

设置合约账号ACL

注解

前置条件:将合约账号以及合约账号下的有权限的AK以合约账号/address形式以追加方式存放到data/acl/addrs

  • Step1: 生成设置合约账号的原始交易,命令如下:

1./xchain-cli multisig gen --desc accountAclSet.json -H 14.215.179.74:37101 --fee 10 --output rawTx.out
  • Step2: 向黄反服务获取签名,命令如下:

1./xchain-cli multisig get --tx ./rawTx.out --host 14.215.179.74:37101 --output complianceCheck.out
  • Step3: 自己对原始交易签名,命令如下:

1./xchain-cli multisig sign --tx ./rawTx.out --output my.sign
  • Step4: 将原始交易以及签名发送出去,命令如下:

1./xchain-cli multisig send my.sign complianceCheck.out,my.sign --tx ./rawTx.out -H 14.215.179.74:37101

accountAclSet.json模版如下:

1{
2    "module_name": "xkernel",
3    "method_name": "SetAccountAcl",
4    "args" : {
5        "account_name": "XC1234098776890654@xuper",
6        "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 1},\"aksWeight\": {\"ak1\": 1}}"
7    }
8}

合约操作

注解

合约操作包括编译、部署、调用、设置合约接口权限,目前XuperChain支持的合约语言包括C++,Go,我们以C++中的counter.cc为例,以此说明合约相关操作。

合约编译

注解

合约编译是指将合约编译成二进制形式

例子:C++版本的counter.cc,counter.cc存放路径为contractsdk/cpp/example 预置条件:安装docker

1cd contractsdk/cpp
2sh build.sh

到当前目录build里,将编译好的合约二进制counter.wasm,重新命名为counter,放到某个目录下,比如笔者的目录是./output/

合约账号充入测试资源

合约部署需要合约账号才能操作,因此会消耗合约账号的测试资源,需要开发者先将个人账号的测试资源转一部分给合约账号。(注意,目前不支持合约账号的测试资源再转出给个人账号,因此请按需充入测试资源。)

  • Step1: 生成测试资源转给合约账号的原始交易数据,命令如下:

1./xchain-cli multisig gen --to XC1234098776890651@xuper --amount 150000 --output rawTx.out --host 14.215.179.74:37101

其中: --amount是转出的测试资源数量,--to是接收测试资源的账号名。如果转出方不是./data/keys下的默认地址,则可以使用--from指定转账来源账号,并将该来源地址的签名在multisig send时写在Initiator的位置。

  • Step2: 向黄反服务获取签名,命令如下:

1./xchain-cli multisig get --tx ./rawTx.out --output complianceCheck.out --host 14.215.179.74:37101
  • Step3: 自己对原始交易签名,命令如下:

1./xchain-cli multisig sign --tx ./rawTx.out --output my.sign
  • Step4: 将原始交易以及签名发送出去,命令如下:

1./xchain-cli multisig send my.sign complianceCheck.out --tx ./rawTx.out -H 14.215.179.74:37101
  • Step5: 查询合约账号的测试资源数额,确定转账成功:

1./xchain-cli account balance XC1234098776890651@xuper -H 14.215.179.74:37101

合约部署

注解

部署合约的前提条件是先创建一个合约账号,假设按照上述步骤已经创建了一个合约账号 XC1234098776890651@xuper ,并且对应的合约账号有充裕的测试资源 前置条件:将合约账号以及合约账号下的有权限的AK以 合约账号/address 形式以追加方式存放到 data/acl/addrs

  • Step0: 合约部署需要在交易中写入满足合约账号ACL的背书AK签名,为了表示某个AK在代表某个账号背书, XuperChain 中定义了一种AK URI,例如 dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN 代表 XC1234098776890651@xuper 这个合约账号,那么这个背书AK的AK URI可以写成:XC1234098776890651@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN

以此为例,背书AK URI需要同时包含黄反服务和合约账号,因此需要将 data/acl/addrs 文件改成:

1XDxkpQkfLwG6h56e896f3vBHhuN5g6M9u
2XC1234098776890651@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN

Step1: 生成部署合约的原始交易,命令如下:

1./xchain-cli wasm deploy --account XC1234098776890651@xuper --cname counter -H 14.215.179.74:37101 -m ./counter --arg '{"creator":"xchain"}' --output contractRawTx.out --fee 137493

Step2: 向黄反服务获取签名,命令如下:

1./xchain-cli multisig get --tx ./contractRawTx.out --host 14.215.179.74:37101 --output complianceCheck.out

Step3: 自己对原始交易签名,命令如下:

1./xchain-cli multisig sign --tx ./contractRawTx.out --output my.sign

Step4: 将原始交易以及签名发送出去,命令如下:

1./xchain-cli multisig send my.sign complianceCheck.out,my.sign --tx ./contractRawTx.out -H 14.215.179.74:37101

合约调用

编译合约,部署合约的目的都是为了能够在区块链系统上运行智能合约,本小节说明如下调用合约。

  • Step1: 生成合约调用的原始交易,命令有下面两种实现方式:

1./xchain-cli multisig gen --desc counterIncrease.json -H 14.215.179.74:37101 --fee 85 --output rawTx.out
2# 或者这样
3./xchain-cli wasm invoke -a '{"key":"counter"}' --method increase counter -H 14.215.179.74:37101 --fee 85 -m --output rawTx.out
  • Step2: 向黄反服务获取签名,命令如下:

1./xchain-cli multisig get --tx ./rawTx.out --host 14.215.179.74:37101 --output complianceCheck.out
  • Step3: 自己对原始交易签名,命令如下:

1./xchain-cli multisig sign --tx ./rawTx.out --output my.sign
  • Step4: 将原始交易以及签名发送出去,命令如下:

1./xchain-cli multisig send my.sign complianceCheck.out --tx ./rawTx.out -H 14.215.179.74:37101

counterIncrese.json模板,如下:

1{
2    "module_name": "wasm",
3    "contract_name": "counter",
4    "method_name": "increase",
5    "args":{
6        "key":"counter"
7    }
8}

设置合约接口ACL

注解

有这么一种场景:合约账号A部署了counter合约,希望只有拿到特定签名的用户才能调用counter的increase方法,因此XuperChain提供对智能合约某个方法进行权限设置
前置条件:将合约账号以及合约账号下的有权限的AK以合约账号/address形式以追加方式存放到 data/acl/addrs
  • Step1: 生成设置合约方法权限(ACL)的原始交易,命令如下:

1./xchain-cli multisig gen --desc methodAclSet.json -H 14.215.179.74:37101 --fee 10 --output rawTx.out
  • Step2: 向黄反服务获取签名,命令如下:

1./xchain-cli multisig get --tx ./rawTx.out --host 14.215.179.74:37101 --output complianceCheck.out
  • Step3: 自己对原始交易签名,命令如下:

1./xchain-cli multisig sign --tx ./rawTx.out --output my.sign
  • Step4: 将原始交易以及签名发送出去,命令如下:

1./xchain-cli multisig send my.sign complianceCheck.out,my.sign --tx ./rawTx.out -H 14.215.179.74:37101

methodAclSet.json的模版,如下:

1{
2    "module_name": "xkernel",
3    "method_name": "SetMethodAcl",
4    "args" : {
5        "contract_name": "counter",
6        "method_name": "increase",
7        "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 1},\"aksWeight\": {\"TqnHT6QQnD9rjvqRJehEaAUB3ZwzSFZhR\": 1}}"
8    }
9}

FAQ

Q

为什么测试环境现在不开放全节点P2P账本同步?

A

目前 XuperChain 仍然处于高速迭代期,为了保证bug能够得到即时修复更新,我们暂时未开放外部P2P节点加入测试环境的功能,但用户通过GRPC接口已经能体验到测试环境的大部分功能。 当然,我们会在测试环境运行一段时间后,开放P2P节点加入乃至开放外部节点成为超级节点,具体时间目前还没有确定,请大家继续关注。

Q

测试环境中的测试资源可以转给别的个人账号吗?

A

不能,测试资源仅供在测试环境上进行 XuperChain 体验、智能合约开发测试使用,用户可以通过加入测试计划免费获得,用户获得的测试资源无法转给其他任何个人账号。

Q

测试环境为什么所有交易都需要黄反服务签名?

A

XuperChain 测试环境上的数据是所有用户透明可见的,为了保证所有用户的体验,我们会对每个transaction中的数据进行合规性检测,尽量避免涉嫌黄反内容上链。用户违规发起涉嫌黄反内容的transaction引起的任何后果,都需要自行承担。请各位测试用户也从自身做起,保障一个干净和谐的网络环境。

Q

编译cpp合约出现 "Post http:///var/run/docker.sock/v1.19/containers/create: dial unix /var/run/docker.sock: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS?" 是什么原因?

A

这可能是因为用户安装docker后,没有创建docker用户组,或者当前运行的系统账号不在docker用户组中,可以尝试下面的命令:

sudo groupadd docker
sudo usermod -aG docker ${USER}  // 此处${USER}为你编译合约时使用的linux账号
service docker resteart

代码提交指南

XuperChain 欢迎任何形式的贡献(包括但不限于贡献新feature,完善文档,参加线下活动,提Issue)。

对于想给 XuperChain 贡献代码的开发者,在这里我们以给 https://github.com/xuperchain/xuperchain 这个仓库提交代码为例来详细解释代码提交流程

Fork 代码

首先访问 https://github.com/xuperchain/xuperchain ,fork代码到自己的代码仓库

_images/github.png

Clone代码到本地

假设fork完之后的代码仓库路径为 https://github.com/icexin/xuperchain

git clone https://github.com/icexin/xuperchain.git

之后再设置一个upstream的remote地址,方便我们同步原始仓库地址的更新

git remote add upstream https://github.com/xuperchain/xuperchain.git

同步代码并建立新分支

我们每次要提交PR的时候都要新建一个分支,这样可以同时开发多个feature,分支基于upstream的master建立

# 拉取上游的最新代码
git fetch upstream

# 建立新分支
git checkout -b new_feature upstream/master

之后我们就可以在这个分支上开发我们的代码了

提交代码

当我们的代码写完之后就可以提交了,注意我们这里提交的remote是origin,也就是自己的代码仓库 https://github.com/icexin/xuperchain

$ git push origin new_feature

Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 286 bytes | 286.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
remote:
remote: Create a pull request for 'new_feature' on GitHub by visiting:
remote:      https://github.com/icexin/xuperunion/pull/new/new_feature
remote:
To https://github.com/icexin/xuperunion.git
* [new branch]      new_feature -> new_feature

创建PR

提交完之后,一般有个类似 https://github.com/icexin/xuperunion/pull/new/new_feature 这样的地址,在浏览器打开这个地址就跳转到创建PR的页面

_images/pull_request.png

持续提交修改补丁

在review的过程中,会有人提出修改意见,继续在new_feature分支上添加commit,再push,就会在当前的PR上进行更新

git add -u
git commit -m 'some fix'
git push origin new_feature

合入代码

如果代码的CI过了,reviewer也没有意见就会合入代码,代码就进入了master分支,之后就可以删除本地和远端的new_feature分支

git branch -D new_feature

注解

你也可以点击 star 收藏该仓库以便后续快速找到该仓库

_images/star.png

操作指导

如何获取XuperChain

目前XuperChain已经发布了2个版本,最新版本为v3.2,可以在github获取发布包

如何升级软件

当版本升级时,需要更新为新版本的代码,并重新编译,然后将 plugins文件夹, 二进制文件xchain,xchain-cli 全部替换后全部重新启动即可,注意多节点模式下需要先启动bootNodes节点。

配置文件说明

XuperChain的配置文件默认读取有3个优先级:

  • 默认配置:系统中所有配置项都有默认的配置信息,这个是优先级最低的配置;

  • 配置文件:通过读取配置文件的方式,可以覆盖系统中默认的参数配置,默认的配置文件为 ./conf/xchain.yaml;

  • 启动参数:有一些参数支持启动参数的方式设置,该设置方式的优先级最高,会覆盖配置文件中的配置项;

 1log:
 2filepath: logs // 日志输出目录
 3filename: xchain // 日志文件名
 4console: true //是否答应console日志
 5level : trace // 日志等级,debug < trace < info < warn < error < crit
 6tcpServer:
 7port: :57404 // 节点RPC服务监听端口
 8p2pv2:
 9port: 47404 // 节点p2p网络监听的端口
10bootNodes: /ip4/127.0.0.1/tcp/47401/p2p/QmXRyKS1BFmneUEuwxmEmHyeCSb7r7gSNZ28gmDXbTYEXK  // 节点加入网络链接的种子节点的netUrl
11miner:
12keypath: ./data/keys //节点address目录
13datapath: ./data/blockchain //账本存储目录
14utxo:
15cachesize: 5000 //Utxo内存cache大小设置
16tmplockSeconds: 60 //GenerateTx的临时锁定期限,默认是60秒

core目录各文件说明

模块

功能及子文件说明

acl

acl查询 account_acl.go 查询合约账号ACL的接口定义 acl_manager.go 查询合约账号ACL,合约方法ACL的具体实现 contract_acl.go 查询合约方法ACL的接口定义

cmd

XuperChain命令行功能集合,比如多重签名、交易查询、区块查询、合约部署、合约调用、余额查询等

common

公共组件 batch_chan.go 将交易批量写入到channel中 common.go 获取序列化后的交易/区块的大小 lru_cache.go lru cache实现 util.go 去重string切片中的元素

config

系统配置文件 config.go 包括日志配置、Tcp配置、P2p配置、矿工配置、Utxo配置、Fee配置、合约配置、控制台配置、节点配置、raft配置等

consensus

共识模块 base 共识算法接口定义 consensus.go 可插拔共识实现 tdpos dpos共识算法的具体实现 single single共识算法的具体实现

contract

智能合约 contract.go 智能合约接口定义 contract_mgr.go 创建智能合约实例 kernel 系统级串行智能合约 proposal 提案 wasm wasm虚拟机

core

xchaincore.go 区块链的业务逻辑实现 xchainmg.go 负责管理多条区块链 xchainmg_validate.go 对区块、交易、智能合约的合法性验证业务逻辑 sync.go 节点主动向其它节点同步区块业务逻辑 xchaincore_net.go 通过广播形式向周围节点要区块 xchainmg_net.go 注册接收的消息类型 xchainmg_util.go 权限验证

crypto

密码学模块 account 生成用户账号 client 密码学模块的客户端接口 config 定义创建账号时产生的助记词中的标记符的值,及其所对应的椭圆曲线密码学算法的类 hash hash算法 sign 签名相关 utils 常用功能

global

全局方法/变量 common.go 全局方法 global.go 全局变量

kv

存储接口与实现 kvdb 单盘存储 mstorage 多盘存储

ledger

账本模块 genesis.go 创世区块相关实现 ledger.go 账本核心业务逻辑实现 ledger_hash.go 账本涉及的hash实现,如生成Merkle树,生成区块ID

log

日志模块 log.go 创建日志实例

p2pv2

p2p网络模块 pb p2p网络消息的pb定义 config.go p2p网络配置 filter.go p2p网络节点过滤实现 server.go p2p网络对外接口实现 stream.go p2p网络流的定义与实现 subscriber.go p2p网络消息订阅定义与实现 util.go p2p网络的全局方法 handlerMap.go p2p网络消息处理入口 node.go p2p网络节点定义与实现 stream_pool.go p2p网络节点对应的流定义与实现 type.go p2p网络对外接口定义

permission

权限验证模块 permission.go 权限验证的业务逻辑实现 ptree 权限树 rule 权限模型 utils 通用工具

pluginmgr

插件管理模块 pluginmgr.go 插件管理的业务逻辑实现 xchainpm.go 插件初始化工作

replica

多副本模块 replica.go 多副本raft业务逻辑实现

server

util.go 通用工具实现,如获取远程节点ip

xuper3

contract contract/bridge xuperbridge定义与实现 contract/kernel 系统级合约(走预执行) contract/vm.go 虚拟机接口定义

xuper3

xmodel xmodel实现 xmodel/pb 版本数据pb定义 xmodel/dbutils.go xmodel通用方法 xmodel/env.go 预执行环境初始化 xmodel/xmodel_cache.go model cache实现 xmodel/xmodel_iterator.go model迭代器实现 xmodel/xmodel_verify.go 读写集验证 xmodel/interface.go xmodel接口定 xmodel/versioned_data.go 版本数据 xmodel/xmodel_cache_iterator.go model cache迭代器 xmodel/xmodel.go model业务逻辑实现

vendor

依赖的三方库

utxo

utxo模块 acl_valid_verify.go acl验证业务逻辑实现,包括SetAccountAcl, SetMethodAcl, 合约调用时的权限验证 topsort.go 交易集合的拓扑排序实现 txhash 交易相关的hash async.go 异步处理 tx_contract_generator.go 合约交易操作 utxo_cache.go utxo cache实现 utxo_item.go utxo表定义 withdraw.go 赎回实现 tx_contract_verifier.go 合约交易操作

视频教程

如何搭建及使用 XuperChain 网络

如何快速建链

如何玩转TDPOS

如何部署智能合约

带你轻松上手 XuperChain 测试环境

如何创建合约账号

智能合约的使用

指令介绍(API)

节点rpc接口

详细见: core/pb/xchain.proto

API

功能

rpc QueryTx(TxStatus) returns (TxStatus)

查询一个交易

rpc GetBalance(AddressStatus) returns (AddressStatus)

查询可用余额

rpc GetFrozenBalance(AddressStatus) returns (AddressStatus)

查询被冻结的余额

rpc GetBalanceDetail(AddressBalanceStatus) returns (AddressBalanceStatus)

查询余额状态细节

rpc GetBlock(BlockID) returns (Block)

从当前账本获取特定id的区块

rpc GetBlockByHeight(BlockHeight) returns (Block)

从当前账本获取特定高度的区块

rpc GetBlockChains(CommonIn) returns (BlockChains)

获取系统中所有的链名

rpc GetBlockChainStatus(BCStatus) returns (BCStatus)

获取指定账本的最新区块数据

rpc GetSystemStatus(CommonIn) returns (SystemsStatusReply)

获取系统状态

rpc GetNetUrl(CommonIn) returns (RawUrl)

获取区块链网络中某节点的neturl

rpc GetAccountByAK(AK2AccountRequest) returns (AK2AccountResponse)

查询包含指定地址的所有合约账号

rpc SelectUTXO(UtxoInput) returns (UtxoOutput)

选择指定地址/合约账号的utxo

rpc SelectUTXOBySize(UtxoInput) returns (UtxoOutput)

按个数选择地址/合约账号的utxo

rpc PreExec(InvokeRPCRequest) returns (InvokeRPCResponse)

预执行智能合约

rpc PreExecWithSelectUTXO(PreExecWithSelectUTXORequest) returns (PreExecWithSelectUTXOResponse)

预执行智能合约并选择出需要的utxo

rpc PostTx(TxStatus) returns (CommonReply)

对一个交易进行验证并转发给附近网络节点

rpc QueryACL(AclStatus) returns (AclStatus)

查询合约账号/合约方法的Acl

rpc QueryUtxoRecord(UtxoRecordDetail) returns (UtxoRecordDetail)

查询账号的utxo细节

rpc GetAccountContracts(GetAccountContractsRequest) returns (GetAccountContractsResponse)

获取合约账号下部署的智能合约

开发者接口

C++接口详细可参见: core/contractsdk/cpp/xchain/xchain.h

API

功能

map<string, string>& args()

获取传入合约的参数表

string& arg(string& name)

获取传入合约的指定参数值

string& initiator()

获取发起此合约调用的账号

int auth_require_size()

获取授权此合约调用的账号数

string& auth_require(int idx)

获取授权此合约调用的指定账号

bool get_object(string& key, string* value)

进行一次读操作

bool put_object(string& key, string& value)

进行一次写操作

bool delete_object(string& key)

进行一次删除操作

bool query_tx(string &txid, Transaction* tx)

查询指定id的交易内容

bool query_block(string &blockid, Block* block)

查询指定id的区块内容

void ok(string& body)

构造状态码为成功的返回

void error(string& body)

构造状态码为失败的返回

string& transfer_amount()

获取合约调用操作中的转账数额

unique_ptr<Iterator> new_iterator(string& start, string& limit)

获得遍历合约存储的迭代器

call(module, contract, method, args ... )

调用其他合约

Golang接口详细可参见: core/contractsdk/go/code/context.go

API

功能

Args() map[string][]byte

获取传入合约的参数表

Initiator() string

获取发起此合约调用的账号

AuthRequire() []string

获取授权此合约调用的账号

PutObject(key []byte, value []byte) error

进行一次写操作

GetObject(key []byte) ([]byte, error)

进行一次读操作

DeleteObject(key []byte) error

进行一次删除操作

NewIterator(start, limit []byte) Iterator

获得遍历合约存储的迭代器

QueryTx(txid string) (*pb.Transaction, error)

查询指定id的交易内容

QueryBlock(blockid string) (*pb.Block, error)

查询指定id的区块内容

Transfer(to string, amount *big.Int) error

进行转账操作

TransferAmount() (*big.Int, error)

获取合约调用操作中的转账数额

Call(module, contract, method string, args ... ) (*Response, error)

调用其他合约

常见问题解答

系统相关

Q

** XuperChain 按照一般的分法,是属于联盟链还是公链?仅从源码看,XuperChain是否更偏向于采用公链的理念设计,先记账后共识,只有一条主链?**

A

XuperChain在设计中以模块化和可插拔作为基本原则之一,对共识、合约等核心组件都可高度定制开发。因此从这点来讲,XuperChain并不是只为公链或联盟链设计,而是一种通用可定制的区块链架构。在多链方面,未来我们也会开源我们的跨链技术,请持续关注。

Q

超级节点技术中说到,“ XuperChain 实现了计算和存储分离的这样一个架构,即一个节点,它表面上是节点网络中的一个节点,它的背后则是一个强大的、分布式的计算和存储集群。”,这段话怎么理解?

A

超级结点技术现在还不太完善,目前仅支持存储放到nfs上。我们的技术理想是,采用分布式存储+分布式计算调度技术,将超级结点的处理请求分发到这两个内部集群里面去。链内并行技术理论上是能分析出DAG并行处理依赖,然后将无依赖的请求调度计算集群里面分开计算。这样,以后超级结点其实是一个集群技术。

Q

** XuperChain 强调自己是100%自主研发,为什么要重新开发全新的区块链系统?新系统的可靠性和健壮性如何验证?**

A

技术是无国界的,但是工程师是有国籍的。几乎每个流行的区块链项目都有一个稳定的核心开发团队圈子,为了不受制于人,尤其对于新兴技术,唯有自主研发才是正途。 百度 XuperChain 的愿景是让信任的建立更加便捷,未来区块链会广泛应用在各行各业的大量业务,成为信任的连接器,当前我们已经在版权保护、司法存证、数据流通等领域落地了应用,可靠性健壮性得到了验证。此次开源也是为了在更广泛的业务场景中应用,我们也会不断迭代代码、打磨系统,完善文档。

Q

XuperChain能跨链吗?XuperChain上的各个平行链之间数据能相互访问交互吗?如果现有的eth、eos或者fabric要和XuperChain对接该如何对接呢?

A

XuperChain 支持跨链,但此次开源的XuperChain里面没有包含跨链的组件,XuperChain 中的平行链不具备互操作性。跨链的功能后续将会开源在XuperCross这个项目里,目前已经实现对 XuperChain 平行链之间可以跨链,也可以实现对fabric的跨链,目前跨链技术仍在完善中。

Q

百度 XuperChain 在实现上和Fabric相比,具有哪些异同呢?

A

与Fabric相比,唯一的相似点是都有合约预执行阶段,其他很多地方都不一样。首先, Fabric的Ledger是不支持分叉的,XuperChain是支持的。Fabric中数据的版本编码是和区块高度绑定的,因此它不支持在同一个区块内多笔事务修改同一个Key。在智能合约开发方面,Fabric没有合约虚拟机啊,不支持合约资源消耗控制,而且是通过Docker来实现粗粒度的资源限制。在网络方面,Fabric是用的RPC,并不能构建大规模的P2P网络。

Q

超级节点的服务器存储量是有限的,如果达到上限怎么办?是采取什么样的结构存储数据?

A

目前提供多盘存储能力,如果超过单机容量可以挂载nfs,底层是扁平化的key-value存储,而且是可插拔的,目前支持leveldb和badger两种key-value存储引擎。如果有需要也可以开发分布式key-value 的组件。

Q

** XuperChain 在部署方面的亮点和技术优势是什么?**

A

部署亮点:不同于传统的联盟链系统, XuperChain 具备全球化部署能力,节点通信基于加密的P2P网络,支持广域网超大规模节点,且底层账本支持分叉管理,自动收敛一致性,先进的TDPOS算法确保了大规模节点下的快速共识。 主要技术优势是: 高性能:通过原创的XuperModel模型,真正实现了智能合约的并行执行和验证,通过自研的WASM虚拟机,做到了指令集级别的极致优化。 架构灵活: 其可插拔、插件化的设计使得用户可以方便选择适合自己业务场景的解决方案,通过独有的XuperBridge技术,可插拔多语言虚拟机,从而支持丰富的合约开发语言。 安全: 内置了多私钥保护的账户体系,支持权重累计、集合运算等灵活的鉴权策略。保障了智能合约运行的安全和可控。

共识相关

Q

可插拔共识具体指什么?为什么设计成支持热更新的共识机制?

A

当前,每种共识都有各自的优缺点。POW太消耗能源,而DPOS经常被质疑是不够去中心化的。所以,在XuperChain的代码中,我们实现了一种可插拔的共识机制,开发者自己实现相关接口,编译成插件就可以完成替换。并且我们认为在系统运行的不同阶段会对共识机制有不同的要求,例如某些场景下冷启动时可能使用PoW更合适,而当系统进入一个稳定状态时,可能使用TDPoS更合适,因此设计了一种共识热更新机制,通过提案投票,可以在区块链网络不停服的情况下完成共识更新。

Q

** XuperChain 目前支持哪些共识算法?推荐使用哪个共识算法?**

A

XuperChain 是可插拔共识,具有统一的共识接口,目前已经开源了包括TDPoS、PoW、Chained-BFT、授权证明等多种共识机制。我们建议的是TDPoS共识,开发者也可以选择自己实现共识接口,扩展出更多的共识机制。

Q

此次开源的出来的共识中, 最终确认时间是多少?

A

目前开源的共识算法有TDPoS、PoW、Single、Chained-BFT。这几种共识算法的出块时间都是可配置的。一般来说Single是强一致模型,可以认为是即可生效;PoW的交易确认时间根据网络情况而定,一般是6个块以上;如果采用TDPoS+Chained-BFT,则交易确认时间是3个块。

Q

现在 XuperChain 支持一条链共识算法无缝由pow切换为tdpos吗?

A

支持,共识的热更新是基于提案投票机制,可以通过提交一个proposal,并在提案中设定好触发高度,当提案收到足够比例的投票后,系统会在之前设定的高度时统一触发共识升级,调用consensus的updateConsensusMethod方法,来实现共识切换。

Q

在TDPOS机制是怎么优化DPOS机制的分叉问题的?如果此时有一两个块由于网络延时其他节点未收到该广播,那是再发广播,等到其他节点确认收到该广播以后再切换BP节点?

A
主要是有以下2点:
1. 首先在时间调度算法切片上,我们有3个时间间隔配置,分别为出块间隔、轮内BP节点切换的时间间隔和切换轮的时间间隔,这个其实很简单,这样在切换BP节点时会可以让区块有足够的时间广播给下一个BP;
2. 在网络拓扑上进行的优化,我们超级节点的选举是在每轮的第一个区块,并且提前一轮确定,这时我们网络层有足够的时间在BP节点之间建立直接的链接,这样也可以降低我们切换BP节点的分叉率。
Q

如果想扩展自定义共识算法,有相关文档吗?

A

目前没有具体的文档,如果感兴趣的话可以参考一下开源共识代码的实现,主要接口参考文件consensus/base/consensusinterface.go。

性能相关

Q

请问百度 XuperChain 8.7万的TPS是在什么样的硬件环境下测得的?

A

目前百度 XuperChain 通过了国家工业信息安全发展研究中心评测鉴定所的测评,在性能测试中,百度 XuperChain 并发可达每秒87000笔交易;测试环境采用TDPOS共识,5节点异步模式, 万兆网络,每个节点64核 2.4GHZ CPU,NVME SSD存储。

Q

链内并行在开源版本实现了吗?

A

实现了,在func (xc *XChainCore) PostTx中。 节点收到一个tx后,除了校验密码学签名, 还会校验其依赖的Input哈希指针(指向依赖的其他tx output)是否有效(up to date),如果有效则加载数据构造合约执行环境,然后重新执行合约,看输出的数据是否和tx中声明的写集合“TxOutputExts“一致。这整个过程都是无锁的,因此能并行,利用多核能力。 验证通过后,最后调用doTxInternal写入账本, 这个过程是有锁的,会再次check 哈希指针的有效性,因为前面放锁了。 简单而言,是一种乐观锁实现。

Q

XuperChain引入链内并发及多版本事务处理技术, 那么它又是如何保证事务的原子性及时序性? 是否与分布式数据库的事务相似呢?

A

通过将一个事务涉及的数据变更打包在一个底层KV数据库的Batch写,保证其原子性。 事务的处理时序是通过事务的引用关系来定序:DAG的拓扑序。 在经典的UTXO模型中,事务声明了“资金引用”,而XuperChain的事务模型中,有两种引用:资金引用和数据引用,通过数据引用来支持通用的智能合约。 链内并行原理:节点收到一个事务后,除了校验密码学签名,还会校验其依赖的Input哈希指针(指向依赖的其他事务的output)是否有效(up to date),如果有效则加载数据构造合约执行环境,然后重新执行合约,验证输出的数据是否和其声明的Output一致。这整个过程都是无锁的,因此能并行,利用多核能力。验证通过后,事务的Output写入账本, 这个过程是有锁的,写入前会再次检查一次哈希指针的有效性。整体上的原理和分布式数据库的MVCC并发控制有相似之处。

Q

链内并行技术中,如果多个并发交易具有时序性, 是否会产生死锁问题?为什么?

A
不会有死锁。从前面对链内并行的原理分析也可以看到,我们是采用的“乐观锁”的机制,有点类似CPU的硬件同步原语Compare and swap。 事务之间最差情况是冲突导致单次提交失败,不会死锁。

XuperChain 中事务的提交分为两阶段,预执行(PreExec)和提交(PostTx)。预执行阶段合约对账本是只读(Read-Only)的,预执行结果会返回事务的“读写集合”,其中读集合描述了事务依赖数据各个Key的Hash指针,这个Hash指针指向已经成功提交的事务的Output域。客户端将读写集和自己的签名组装起来,开始第二阶段:提交,节点验证成功后,事务输出才写入生效,进入待上链状态。如果提交失败,客户端可以返回第一阶段重新开始。

Q

请问DAG技术有较为详细的文档么?或者在代码中哪个模块能看到相关实现?

A

DAG并发执行目前已经开源了块验证时的DAG识别和并发执行,具体可以参见开源代码中的UTXO模块。对于通过PostTx接收到的交易,目前还没有开源DAG并行架构,会在未来的版本中

合约相关

Q

** XuperChain 合约虚拟机开源了吗,兼容性怎么样?**

A

已经开源了,目前主要支持XVM和WAVM两种虚拟机。XVM开源在XuperChain主框架项目中,可以看下XVM的实现,基本过程是wasm -> code injection -> c -> dylib. 然后go里面调用特定的几个导出函数符号执行。wasm import的符号会在c里面体现为外部导入符号,然后通过cgo,在go里面暴露出来。这个方式算是一种比较巧妙而且简洁的方式吧。 另外wavm开源在XuperChain/Wavm这个项目中。

Q

XuperBridge是合约虚拟机和区块链账本的桥梁, 它的好处当然有很多, 可以统一接口, 更低的耦合度, 但同时也会限制一些灵活性, 关于这点是如何处理的呢?

A

XuperBridge通过统一接口降低了将不同类型的虚拟机接入到XuperChain的难度,给予开发者更多的选择来开发Dapp,而不限于某一类特定的编程语言。目前我们已经接入了WASM和Docker来满足不同场景的业务需求,后续我们会开放更多的接口来满足开发者的多样的开发需求。事实上,我们通过XuperBridge也已经支持了以太坊的solidity虚拟机,只是由于License问题,此次不便开源。

Q

合约预执行,与Fabric的endorser阶段策略类似吗?先生成read/write set?

A

流程上大体类似,实现上是有差别的,例如数据版本定义等。Fabric中数据的版本编码是和区块高度绑定的,因此它不支持在同一个区块内多笔事务修改同一个Key, XuperChain 中的版本类似于UTXO,同一个块中可以对一个key进行多次修改,因此可以大幅提升交易性能。

Q

合约之间是否可访问,例如A账户a1合约里面存储的数据在B账户b1合约里面可以访问么? 那用户B能调用A账户部署的合约吗?

A

目前夸合约调用还不支持,出于用户权限和数据安全考虑,合约数据属于受保护的私有数据,不能被其他合约直接使用,即使两个合约都属于同一账户。未来可以通过系统级跨合约调用的方式实现数据共享,目前这部分技术还没有开源,请持续关注 XuperChain 后续版本。 但用户可以调用其他账户部署的合约,每个合约接口可以设置单独的ACL权限控制,因此合约所有者可以在合约接口ACL中配置哪些账户有权限访问该接口,这里的账户并不限于合约所有者。1. ACL权限模型可以自由扩充定制。 2. 如果合约接口创建后没设置ACL,默认是public,所有用户都可以访问。

Q

是否支持原生合约?原生合约有没有资源消耗机制?

A

XuperChain 支持原生(Native)合约,原生合约可以在docker环境中执行,但原生合约目前不支持资源消耗控制机制。

Q

智能合约是什么时候触发执行的?只能由客户端触发吗, 有没有可能就是在某个条件满足的时候自动触发呢?

A

简单说,智能合约是在用户调用的时候触发执行的。更详细得说,用户在客户端发起一个智能合约调用,服务端会为该智能合约调用创建Context,然后将Context相关信息通过XBridge传给虚拟机,虚拟机通过调用SyscallService服务,来修改/获取智能合约的数据状态。

Q

是不是每种合约都需要有一条单独的链呢?

A

合约由虚拟机来管理,一条链上可以部署很多智能合约,不过每一个智能合约都需要有不同的合约名字。

账户权限相关

Q

普通账户和合约账户的区别是什么?

A

账户是指一种本地或自定义权限的链上标识符。本地标识符称为用户账户,通常分配一个公钥和一个私钥,并对应一个address;自定义权限的链上标识符称为合约账户,通常分配一个或多个密钥或多个账户。

Q

什么是AK? XuperChain 中所说的AK集合是什么?

A

AK是 XuperChain 中对一个公私钥用户账户的称呼,可以理解为Address,即通过一对公私钥转换来的一个唯一用户账户地址。AK集这是权限系统模块中的一种权限模型,是指多个AK组成的具有一定逻辑规则的权限模型,例如多个AK之间满足”或”的关系等。

Q

** XuperChain 的权限设计为什么采用的是ACL设计模式而不是ABAC的设计模式?关于权限验证目前是只有SIGN_THRESHOLD和SIGN_AKSET 的权限规则在使用吗?**

A
  1. ACL更加简单,我们基于ACL实现了一套可扩展的权限模型,能够满足去中心权限系统的要求,同时又可扩展。而ABAC相对比较复杂,不易用;

  2. 目前对外开源的是SIGN_THRESHOLD和SIGN_AKSET这两种权限模型。

Q

在权限系统的设计中 身份账户的验证中会buildPermTree 和 validatePermTree,这个PermTree的设计思想是什么 这个Perm这个词是跟权限框架casbin中的PERM模型有关系吗?

A

在身份验证中,PermTree主要是验证客户提供的账户与签名是否正确以及它们的权重是否满足对应的ACL要求。 XuperChain 的PERM与casbin并不相同, XuperChain 的PERM指的是可扩展规则权限模型(Permission with Extensible Rule Model), XuperChain 而账户/合约权限中可能嵌套其他账户,在验证账户的权重是否满足要求时,由于这种嵌套关系,权限会自然形成树形结构,每个节点都是一个账户或合约方法的权限ACL,而每个节点的ACL可以使用不同的权限规则模型,节点的子节点是代表子节点账户对父节点的授权关系。

Q

在执行背书签名时默认读取data/acl/addrs文件,例如multisig gen 时要读取这个文件的数据,请问这个文件的内容应该是什么?

A

这是一个文本文件,文件内容中每一行表示一个需要签名的账户,是用于多重签名的地址,每个地址用换行分割。如果需要其他个人账户的授权,那么把个人账户的address作为一行写入这个文件中;如果需要合约账户授权,则需要使用”合约账户/个人账户address”的写法,表示需要某个合约账户ACL中的某个个人账户签名。

使用问题

Q

平行链是什么角色有权限创建?创建平行链的权限白名单是写在创世块中的么?

A

目前创建平行连有两种方法:一种直接通过xchain-cli的createChain命令,这种没有权限限制,只在本机创建;另一种是调用创建链的合约,这种情况可以在全网创建平行链,节点可以通过配置白名单的方式指定哪些用户能调用创建链的合约。创建平行链的权限白名单目前不在创世块中,而是在节点配置文件中。这么做的初衷是使每个节点有能力通过配置决定只托管符合自己要求的平行链。具体节点配置可以参考:https://github.com/xuperchain/xuperchain/blob/master/core/conf/xchain.yaml#L52

Q

自己搭链有root链和平行链之分么,还是自己搭的只是xuperchain的平行链?通过xchain-cli的createChain命令能在本地创建多条平行链吗?

A

自己搭建的网络首先需要创建主链(代码里叫xuper链),然后再创建其他平行链,主链具有部分管理其他链的能力,例如创建平行链的系统合约是在主链中执行。通过xchain-cli的createChain命令可以在本地创建任意多条链。

Q

搭建多节点网络时,其他节点需要在配置里面指定根节点的网络地址吗?

A

XuperChain 的P2P网络具有自动路由功能,因此只要在配置中指定任意一个已经在网络中的节点地址即可。

Q

** XuperChain 中的rootBlockid和tipBlockid是什么意思,他们的关系是什么?**

A

rootBlockid和tipBlockid都是指代区块的id,区块的id就是区块的唯一标识,通过两次SHA256生成。而rootBlockid特指创世区块id,即当前链的第一个区块的id;tipBlockid表示当前链主干分支最新的区块的id。也可以从rootBlockid和tipBlockid两个变量名称得知它们的意思,root有”根”的意思,而tip有”尖端”的意思。

Q

客户端发送交易后,怎么查询是否成功上链。通过querytx判断tx状态还是有其他事件机制?支持事件通知么?

A

首先查询交易所在区块,然后查询区块是否在区块链主干上,如果交易所在区块在区块链主干上,表明此交易已经生效。事件通知暂不支持,后续有计划支持部分类型事务执行结果的事件通知。

Q

RPC的文档中没有看到创建账户相关的说明?

A

创建普通账户属于本地操作,该数据不上链,考虑到创建账户涉及到用户私钥传输,处于安全原因不提供RPC接口;创建合约账户属于系统合约调用,没有单独的RPC接口。

Q

Windows系统可以编译运行么?可以用windows安装虚拟机来实现Linux环境么?

A

目前暂不支持Windows的,要求是Linux或者Mac OS。可以使用虚拟机实现。

其他问题

Q

密码学中椭圆曲线选择使用P-256的考虑?

A

首先,P-256曲线目前依然在密码学界被广泛使用。其次,这次开源的是我们的基础版本,而在xchain的代码架构下,密码学相关的模块是插件化使用的,密码学crypto模块是可以独立研发并集成进开源框架中的。同时,在ECDSA之外,也已开源了多重签名、EDDSA、环签名等多种签名算法。在尚未开源的版本中,通过可变签名算法,我们已经支持国密/NIST的多条椭圆曲线。最后,这些算法和曲线被支持混合使用,开发者可以自由选择他们认为安全的曲线和算法来保护自己的数字资产。所以,敬请期待百度xchain后续的密码学相关开源进展。

Q

** XuperChain 有密钥保护机制吗?怎么实现的?**

A

有的,主要实现了两种方式:1) 通过支付密码在私钥加密后保存在本地。2)云端密钥保险箱。密钥保护功能在SDK里有实现,目前尚未开源。

Q

环签名、零知识证明等技术开源了么?他们的使用场景是什么?

A

环签名、零知识证明等技术适用于对隐私保护有较高需求的网络中。目前环签名已经在crypto模块中开源,可以实现对交易发起者信息的混淆,例如在论文评审场景里,实现评审者对论文的匿名打分等;零知识证明目前尚未开源。

Q

** XuperChain 有区块链浏览器吗?**

A

暂时没有,在计划中,敬请关注。

Q

什么是VAT,它的作用是什么?

A

VAT (Verifiable Auto-generated Transaction, 可验证的自动生成事务)是智能合约在运行过程中,根据需要自动生成的系统Tx。这些Tx无法手动发起,结果也会上链,系统可验证。目前主要用在共识,提案等模块中,例如切换到tdpos共识,会自动生成候选人选举检票的VAT等。

Q

商用环境中,需要考虑数据隐私保护的问题,在这一块,xuper怎么考虑,有成熟的方案吗?

A

在xuper+的应用中,有xuperdata,它是基于百度 XuperChain 、多方安全计算、数据隐私保护等技术的多企业数据安全协同计算方案。相关文档介绍:https://xuperchain.baidu.com/case/xuperdata

Q

百度 XuperChain 的多盘存储是什么实现原理?

A

XuperChain 按照goleveldb的storage.go中的接口实现了自己的storage逻辑作为自己的file-system backed storage,代码可参考multi_disk_storage.go。具体实现中,leveldb的sst文件按照编号均匀散列放置在多块盘上,如果盘数扩容,第一次打开某个编号的sst文件的时候可能需要遍历尝试各个盘。另外,由于这个放置策略在compact的时候也生效,所以扩容的场景下,运行一段时间后,sst就会按新的路径均匀分布了。

词汇表

词汇

定义

交易

对区块链进行状态更改的最小操作单元。通常表现为普通转帐以及智能合约调用。

区块(创世区块、普通区块、配置区块、当前区块)

区块链中的最小确认单元,由零个或多个交易组成,一个区块中的交易要么全部生效,要么全部不生效。创世区块:区块链上第一个区块,确定分配规则、共识算法等系统级别的参数。配置区块:需要升级区块链系统级配置时,通过配置区块生效。

账本

存储区块数据、交易数据。

UTXO

一种余额记账方式。用于存储账号的余额数据。

区块链

由若干个区块组成的DAG。从数据结构上来说,区块链就是一个DAG。

系统链

区块链网络中的第一条区块链实例。

平行链

从系统链衍生出来的子链,解决扩展性问题。

跨链

不同的区块链之间的通信操作,目的是实现区块链世界的价值互连,解决扩展性问题。

账号(用户账号、合约账号)

一种本地或自定义权限的链上标识符。本地标识符称为用户账号,通常分配一个公钥和一个私钥;自定义权限的链上标识符称为合约账号,通常分配一个或多个密钥或多个账号。

密钥对(公钥、私钥)

私钥以及由私钥生成的对应的公钥。私钥通常用于签名,公钥通常用于验证。

地址

与用户数据挂钩的最小单元。地址可以是合约账号,也可以是由公钥生成的一个长度为33的字符串。

签名(普通签名、多重签名)

利用密码学哈希函数单向不可逆、抗碰撞特性,进行身份确认的一种机制。

共识

一种确认区块中交易集正确性以及交易上链顺序的机制。

委托权益证明

一种共识算法,通过选举出区块链网络中有限节点作为代表并轮流出块记账。

智能合约(系统级、用户合约)

一个由计算机处理、可执行合约条款的交易协议,其总体目标是满足协议既定的条件,例如支付、抵押、保密协议。

权限(许可)

一种安全机制,通过评估签名权限来验证一个或一组操作是否被正确授权。

虚拟机

智能合约的运行环境。通常包括合约上下文管理。

最长链

区块链中高度最大的分支。

DAG

有向无环图。

双重消费

同一份数据同时消费多次。

最终一致性

存在某个时刻,整个系统达成一致状态。区块链满足最终一致性。

发起人

发起交易的账号,通常为用户账号或合约账号。

见证人

当选为当前周期内出块节点。

节点

区块链网络中的一个节点。

周期(Epoch)

在委托权益证明共识算法中,一轮出块时间为一个周期。

提案

一种区块链系统级配置进行升级的机制。

查询

对区块链中的数据按照key进行查询。

对等网络

网络中的节点直接互联并交互信息,不需要借助第三方。

拜占庭(拜占庭问题、拜占庭容错)

拜占庭问题:在一个需要共识系统中,由于作恶节点导致的问题。拜占庭容错:在一个需要共识系统中,即使出现作恶节点也能保证系统正常运转的能力。

状态转移系统

一个维护状态变化的系统。区块链通常被认为是一种状态转移系统,其中的状态通常包括账本、余额、合约数据。

读写集

用于支持智能合约并发执行的一种技术。

XuperChain 小课堂

第一期

什么是区块链共识算法?

一种通过确认交易上链顺序从而保证网络节点状态一致并且结合经济学中的博弈论让攻击者的攻击成本远远大于收益的算法。常见的区块链共识算法包括:POW,POS,DPOS,VBFT,SPOS等。

TDPOS是什么共识机制?

TDPOS是百度在DPOS共识算法(Delegated Proof of Staking,委托权益证明)基础上自研改进的共识算法。区块链网络中的持币用户通过不定期选举出一小群节点,让这些节点进行区块创建、签名、验证和相互监督。TDPOS在DPOS基础上做了很多优化和细节改造并且TDPOS作为百度 XuperChain 的默认共识算法。

第二期

什么是权限控制系统?

权限控制系统是为了约束资源查询/更新等能力而引入的一种系统要素。常见的权限控制系统包括:基于ACL的权限控制系统,基于RBAC的权限控制系统,基于ABAC的权限控制系统。

XuperChain 的权限控制系统是什么?

百度 XuperChain 基于ACL权限控制模型实现了一套去中心化的权限控制系统,用于限制合约资源数据的访问/更新等能力,从而保障合约资源数据的安全。百度 XuperChain 自研的权限控制系统目前支持签名阈值策略、AK集签名策略等。

第三期

什么是公有链、联盟链以及私有链?

公有链是指所有人都可以参与的区块链,所有人都可以查询、发送、接收、验证交易等特性;联盟链亦称为许可链,参与方限制为联盟成员,参与方通常是各个企业、机构,金融、物流以及司法等是联盟链的主要应用场景;私有链是指参与方受限为企业、机构或者个体,主要用于解决可审计问题。

XuperChain (XuperChain)是什么?

XuperChain 是百度自研的区块链底层技术,具有链内并行技术、可插拔共识机制、一体化智能合约等领先技术,支持隐私保护和安全机制,具有可兼容性、扩展性强、高性能等特点。不仅适用于面向企业以及机构之间的联盟链应用场景,同时适用于公有链应用场景。目前XuperChain已经应用于司法、版权、边缘计算、数据协同、溯源、电子政务、智慧医疗等多个领域。

第四期

什么是区块链账户?

区块链账户是指用于标识区块链网络中不同身份的一种机制。

百度 XuperChain 中的账户是什么?

百度 XuperChain 使用了两种类型的账户,包括普通账户以及合约账户。(1)普通账户通常由账号(address)、公私钥对以及余额组成。账号可以操作转账。(2)合约账户相比普通账户而言,是一个更高级的账户类型,可以为多个账户角色进行权重分配,分配之后多个角色(包括普通账户或者合约账户)可以共同管理合约账户下面的数据资产(包括智能合约、访问权限列表、余额等)。相比传统中心化的权限管理模式而言,不是由某个账户对数据资产进行专权化管理;账户权限一但创建之后,即实现了不同权重账户的多中心化资产管理模式,不同账户角色也可以通过共同签名来更改权限。

第五期

普通账户与合约账户的应用场景各有哪些?

普通账户的资产只归属一个人,而合约账户会分配多个账户共同管理合约账户下面的资产(包括智能合约、访问权限列表、余额等)。因此,在部署合约以及需要多方共同管理资产时,需要使用合约账户;其他应用场景下,普通账户与合约账户没有明显区别。

账户与账号的区别是什么?

账号是账户的组成部分,作为账户的唯一标识符,有固定的生成规则。比如,在百度 XuperChain 中,dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN是一个有效的普通账户的账号;XC1111111111111111@xuper是一个有效的合约账户的账号。

第六期

P2P网络是什么?

P2P网络也称为对等网络,它是一种网络技术和网络拓扑结构。目前P2P技术已经应用于流媒体、点对点通讯等领域,也就是我们常说的BT工具和电驴,比较常见的P2P网络协议有BitTorrent、ED2K等。

区块链P2P网络是什么?

区块链P2P网络主要用于区块链节点之间数据传输和广播、节点发现和维护。因此,区块链P2P网络主要解决数据获取以及节点定位两个问题。节点发现和局域网穿透技术主要解决的是节点定位问题,节点交互协议主要解决的是数据获取问题。节点发现主流协议有Gossip以及KAD,局域网穿透协议主要有NAT。

第七期

混盘技术是什么?

混盘技术也称为多盘技术,将多个磁盘从逻辑上当作一个磁盘来处理,主要用于解决只支持本地单盘场景下数据库空间不够的问题(即扩展性问题),比如被广泛使用的LevelDB。目前对LevelDB的多盘扩展技术,大部分是采用了多个LevelDB实例的方式,也就是每个盘一个单独的LevelDB实例。

XuperChain 自研的混盘技术

多个LevelDB实例的方式好处是简单,不需要修改LevelDB底层代码,缺点是牺牲了多行原子写入的功能。在区块链的应用场景中,需要保证多个写入操作是原子性的特性。 XuperChain 改造LevelDB存储引擎本身,在引擎内部完成了数据文件的多盘放置,能够确保所有写入更新操作的原子性,从而能够满足区块链场景的交易原子性要求。

第八期

平行链是什么?

平行链是相对于单链而言的,在只有一条链情况下,所有交易都由根链验证、执行、存储、打包到区块,在交易量高的情况下,存在吞吐低,延时高的问题。为了解决这类扩展性问题,从根链衍生出多条子链,各个子链打包自己链上的交易,账本、共识算法、网络等模块都可以相互独立。

百度 XuperChain 中的平行链是什么?

百度 XuperChain 通过系统合约方式创建平行链,平行链之间相互独立,拥有各自的账本和共识算法等模块,目前平行链之间共享p2p网络。不同的业务可以跑在不同的平行链上,起到了业务隔离效果,在使用平行链时,需要通过--name指定需要操作的平行链的名字。

第九期

用户可以通过哪些客户端接口访问百度 XuperChain ?

  1. xchain-cli,交互式命令行工具,直接使用xchain-cli即可发起操作,本质是通过rpc接口与服务端进行交互,可以从xuperchain库中获取;xchain-cli具有丰富的命令,包括创建账户、普通转账、合约部署以及调用、提案、投票、链上交易以及区块查询等功能;比如./xchain-cli transfer --to bob --amount 1就可以发起一笔向bob转账1个utxo的交易,更多命令可以通过./xchain-cli -h获取;

  2. SDK:提供一系列的API接口,用户可以基于提供的API接口做定制化的操作,相比xchain-cli更灵活;目前开源的SDK包括Go SDK,Python SDK,C# SDK;

  3. curl:直接通过curl命令来发起查询、构造交易等操作,客户端除了curl,不依赖任何三方库,此时需要服务端启动xchain-httpgw,然后通过curl http://localhost:8098/v1/get_balance -d '{"bcs":[{"bcname":"xuper"}, "address":"bob"], 即可查询xuper链上bob的余额信息

第十期

Gas在区块链中的作用是什么?

Gas是一种资源消耗计量单位,比如执行智能合约时消耗的资源数量。用于奖励矿工并防止恶意攻击,是区块链生态系统可持续发展的重要因素。通常,Gas由各种可衡量资源按照特定比例累加而成。

百度 XuperChain 中,如何计算Gas?

百度 XuperChain 中采用了如下可衡量资源:CPU,Mem,Disk,XFee。其中,CPU是指一个合约执行时消耗的CPU指令,Mem是指一个合约上下文消耗的内存大小,Disk是指一个合约上下文的磁盘大小,而XFee是一种特殊资源,主要针对系统合约消耗的资源,比如创建一个合约账号、设置合约方法的ACL需要消耗的资源。Gas计算公式为:Gas = CPU * cpu_rate + Mem * mem_rate + Disk * disk_rate + XFee * xfee_rate,其中cpu_rate,mem_rate,disk_rate,xfee_rate为资源与Gas的兑换比例。

第十一期

区块链的链上治理是指什么?

区块链的链上治理是指在一个涉及很多利益方的区块链网络中,为了升级系统共识参数并保证区块链网络持续演进的链上解决方案(比特币和以太坊就因为系统共识参数升级分歧发生过多次硬分叉)。

百度 XuperChain 的链上治理是如何做的?

百度 XuperChain 提出一种提案机制,首先,提案发起人会发起一笔修改系统共识参数的提案交易;然后,提案发起人将提案交易通过链外方式(比如邮件列表或者论坛、线下聚会等)告诉社区,对提案作进一步解释,并号召大家投票;之后,区块链网络中的用户可以对该提案交易进行投票;最后,如果投票数量超过提案交易中规定的最低票数,该提案交易就会生效。

第十二期

区块链中,虚拟机的作用是什么?

虚拟机为智能合约提供了一个对底层透明的执行环境,主要工作包括指令解析、链上交互、Gas计算等。目前常见的虚拟机包括EVM,基于WASM的虚拟机等。

XuperChain 虚拟机是如何执行合约的?

XVM(Xuper Virtual Machine, XuperChain 虚拟机)目前支持在预编译模式以及解释模式下执行智能合约。 1. 预编译模式下:在合约部署时,XVM会将wasm指令编译成本地机器可以运行的指令(由wasm2c来做,主要工作包括将wasm转换成c、系统调用、Gas统计等功能);在合约调用时,直接执行相应的指令即可。因此,预编译模式下,合约部署需要消耗时间,通常为数秒;而合约调用因为不需要再次做指令映射,执行效率高; 2. 解释模式下:在合约调用时,XVM对wasm指令挨个解释执行(主要工作包括对wasm指令进行解释执行、Gas统计等功能)。因为在合约部署时不需要做指令映射,合约部署较快;在合约调用时,需要对wasm指令挨个做指令映射,执行效率低。

第十三期

区块链中常见的安全问题有哪些?

区块链中常见的攻击包括DDoS攻击、女巫攻击、整数溢出、可重入攻击、拜占庭攻击等,主要体现在网络层、智能合约层、共识层、数据层等方面。

XuperChain 做了哪些安全工作?

XuperChain 主要在密钥安全、网络安全、数据安全、共识安全以及智能合约安全等方面做了系列工作。

  1. 密钥安全方面,支持密钥加密存储、助记词恢复、密钥备份等能力;

  2. 网络安全方面,通过TLS进行数据加密传输,通过CA实现联盟准入机制,节点身份认证以及分层网络路由保护机制,来源IP数量限制等;

  3. 数据安全方面,除了基本密码学机制外,还实现账号与权限系统细粒度区分数据访问权限;

  4. 共识安全方面,实现了bft组件,能够抵抗拜占庭节点攻击;

  5. 智能合约方面,通过wasm实现指令级资源审计,屏蔽对底层存在较大风险的系统调用接口,保证应用层安全。

第十四期

关于UTXO的命令有哪些?

  1. 查询用户UTXO面额:./xchain-cli account balance;

  2. 查询用户UTXO详细信息:./xchain-cli account list-utxo;可以通过该命令查看哪些utxo当前可用,哪些utxo当前被锁定以及哪些utxo当前被冻结;

  3. 合并UTXO:./xchain-cli account merge;可以通过该命令将用户多个utxo合并,来解决因UTXO太零散导致交易过大问题;

  4. 拆分UTXO:./xchain-cli account split;可以通过该命令将用户的一个UTXO进行拆分,解决用户无法同时发起多笔交易的问题;

第十五期

XuperChain 开放网络是什么?

XuperChain 开放网络是基于百度完全自主研发的开源技术搭建的区块链基础服务网络,由分布在全国的超级联盟节点组成,符合中国标准,为用户提供区块链应用快速部署和运行的环境,以及计算和存储等资源的弹性付费能力,直接降低用户部署和运维成本,让信任链接更加便利。

XuperChain 开放网络有哪些优势?

  1. 自主安全高可靠:基于百度完全自主研发且开源的区块链技术搭建,满足中国区块链标准要求;

  2. 灵活便捷低门槛: 无需建链即可运用区块链技术,丰富的合约模板和强大的功能组件,降低使用门槛;

  3. 弹性付费成本低:具备计算和存储等资源的弹性付费能力,可以实现按需按量灵活计费,一分钱即可用;

  4. 节点开放公信强:由分布全国的超级联盟节点构成,面向社会开放节点接入,具备极强的公信力;

第十六期

XuperChain 有哪些交易类型?

XuperChain 主要包括三种交易类型:

  1. 普通转账交易:基于用户utxo进行转账,此类交易包含utxo的引用关系,即TxInput和TxOutput,能够并行执行;

  2. 二代合约交易:主要用于修改系统共识参数,比如升级共识算法、提案等操作,此类交易执行顺序与区块高度绑定,只能串行执行;

  3. 三代合约交易:采用两阶段提交,首先通过预执行获取合约数据读写集,然后组装交易并转发给记账节点执行,此类交易执行顺序与区块高度无关,能够并行执行。

XuperChain如何统一UTXO和智能合约模型?

UTXO模型主要用于存储用户的utxo数据,一般适用于普通转账交易;而智能合约存储模型主要用于存储用户合约相关数据。本质上,这两种存储模型都是存储用户数据并且包含数据版本依赖关系。因此, XuperChain 自研一套通用的存储模型XuperModel,基于key记录用户数据的依赖关系,实现UTXO和智能合约底层数据存储模型的统一。而比特币和以太坊底层存储模型不同,导致它们无法做到兼容。

第十七期

XuperChain 中,交易执行支持哪些模式?

XuperChain 支持三种交易执行模式,分别为同步模式、纯异步模式以及异步阻塞模式。

  1. 同步模式:客户端发起一笔交易并等待交易执行结果;xchain节点更新交易状态时,加锁,锁内只能同时更新一个交易状态;

  2. 纯异步模式:客户端发起一笔交易并直接返回;xchain节点积攒批量交易,在更新交易状态时,加锁,锁内同时更新批量交易状态;

  3. 异步阻塞模式:客户端发起一笔交易并等待交易执行结果;xchain节点积攒批量交易,在更新交易状态时,加锁,锁内同时更新批量交易状态;

如何使用同步、纯异步以及异步阻塞模式?

三种模式是互斥的,默认采用同步模式。在xchain节点启动时,通过flag来选择。通过nohup ./xchain --asyncBlockMode=true & 启动异步阻塞模式;通过nohup ./xchain --asyncMode=true & 启动纯异步模式。

第十八期

如何参与 XuperChain 的开发?

  1. 可以通过阅读 XuperChain 任意开源项目,包括源代码、文档,以便了解当前的开发方向;

  2. 找到自己感兴趣的功能或者模块;

  3. 实际开发时需要自测功能是否正常、性能是否符合预期,并运行make & make test检查是否通过所有单测;

  4. 发起一个Pull Request,如果你的代码合入到主干后,就有机会运行在线上机器上。

如何提一个PR?

  1. 从GitHub上fork XuperChain 的项目,并通过git拉取到本地;

  2. 在本地用git新起一个分支,贡献的代码全部放在本地分支上;

  3. 本地代码开发完毕,通过git push将本地分支代码提交至远程服务端;

  4. 点击GitHub对应项目栏下面的Pull Request按钮,填写需要合并的分支以及被合并的分支,然后点击create pull request即发起一个PR。

第十九期

XuperChain 支持消息推送机制吗?

消息推送是指客户端主动向xchain节点订阅感兴趣的消息类型,当该类型的消息在链上被触发时,xchain节点会主动将该行为推送给客户端; 目前, XuperChain 支持三种消息类型的推送,分别为区块消息、交易消息以及账户消息。 1. 区块消息:用户可以订阅具有特定策略的区块,当链上触发这类区块时,会将消息主动推送给客户端; 2. 交易消息:用户可以订阅具有特定策略的交易,当链上触发这类交易时,会将消息主动推送给客户端; 3. 账户消息:当用户的余额发生变化时,会将消息推送给客户端。

如何使用 XuperChain 的消息推送机制?

目前, XuperChain 的master分支支持消息推送机制。通过在xchain.yaml中增加pubsubService配置来启动事件推送服务。同时 XuperChain 提供了一个简单的客户端来订阅、接收自己感兴趣的消息。针对每种消息类型可用的策略可以参考event.proto文件。

第二十期

XuperChain 支持群组功能吗?

群组是一种为了实现平行链之间隐私数据隔离,不同平行链只有指定节点才能参与区块打包、区块同步、区块/交易转发等能力的机制。

如何使用 XuperChain 的群组功能?

目前, XuperChain 的master分支支持群组功能。在创世块配置文件中配置群组合约的相关参数,包括合约名、方法名等,并部署好群组合约( XuperChain 有群组合约的默认实现)即可调用群组合约为平行链增加节点白名单,从而让平行链具备群组能力。

Indices and tables