FISCO BCOS 技术文档

_images/FISCO_BCOS_Logo.svg

FISCO BCOS 是一个稳定、高效、安全的区块链底层平台,经过多家机构、多个应用,长时间在生产环境运行的实际检验。

概览

关键特性

重要

  • 本技术文档只适用FISCO BCOS 2.0+,FISCO BCOS 1.3.x版本的技术文档请查看 1.3系列技术文档
  • FISCO BCOS 2.0+版本及兼容性说明 这里

平台介绍

FISCO BCOS是由国内企业主导研发、对外开源、安全可控的企业级金融联盟链底层平台,由金链盟开源工作组协作打造,并于2017年正式对外开源。

社区以开源链接多方,截止2020年5月,汇聚了超1000家企业及机构、逾万名社区成员参与共建共治,发展成为最大最活跃的国产开源联盟链生态圈。底层平台可用性经广泛应用实践检验,数百个应用项目基于FISCO BCOS底层平台研发,超80个已在生产环境中稳定运行,覆盖文化版权、司法服务、政务服务、物联网、金融、智慧社区等领域。

注解

FISCO BCOS以联盟链的实际需求为出发点,兼顾性能、安全、可运维性、易用性、可扩展性,支持多种SDK,并提供了可视化的中间件工具,大幅缩短建链、开发、部署应用的时间。此外,FISCO BCOS通过信通院可信区块链评测功能、性能两项评测,单链TPS可达两万。

关键特性

整体架构
架构模型 一体两翼多引擎
群组架构 支持链内动态扩展多群组
分布式存储 支持海量数据存储
并行计算 支持块内交易并行执行
节点类型 共识节点、观察节点
计算模型 排序-执行-验证
系统性能
峰值TPS 2万+ TPS(PBFT)
交易确认时延 秒级
硬件推荐配置
CPU 2.4GHz * 8核
内存 8GB
存储 4TB
网络带宽 10Mb
账本模型
数据结构 链式结构,区块通过哈希链相连
是否分叉 不分叉
记账类型 账户模型(非UTXO)
共识算法
共识框架 可插拔设计
共识算法 PBFT、Raft、rPBFT
存储引擎
存储设计 支持KV和SQL
引擎类型 支持leveldb、rocksdb、mysql
CRUD接口 提供CRUD接口访问链上数据
网络协议
节点间通信 P2P协议
客户端与节点通信 JsonRPC,Channel协议
消息订阅服务 AMOP协议
智能合约
合约引擎 支持Solidity和预编译合约
引擎特点 图灵完备,沙盒运行
版本控制 基于CNS支持多版本合约
灰度升级 支持多版本合约共存、灰度升级
生命周期管理 支持合约和账户的冻结、解冻
密码算法和协议
国密算法 支持
国密SSL 支持
哈希算法 Keccak256、SM3
对称加密算法 AES、SM4
非对称加密算法 ECDSA、SM2
非对称加密椭圆曲线 secp256k1、sm2p256v1
安全控制
存储安全 支持落盘数据加密存储
通信安全 支持全流程SSL
准入安全 基于PKI身份认证体系
证书管理 支持证书颁发、撤销、更新
权限控制 支持细粒度权限控制
隐私保护
物理隔离 群组间数据隔离
隐私保护协议 支持群签名、环签名、同态加密
场景化隐私保护机制 基于WeDPR支持隐匿支付、匿名投票、匿名竞拍、选择性披露等场景
跨链协议
SPV 提供获取SPV证明的接口
跨链协议 基于WeCross支持同构、异构跨链
开发支持
合约开发工具 WeBASE-IDEChainIDE
开发建链工具 提供一键搭链脚本工具
合约部署与测试工具 交互式控制台Console
SDK语言 Javanodejsgopython
快速开发组件 提供Spring-boot-starter
压测工具 SDK内嵌压测工具,支持Caliper
运维支持
运维建链工具 提供企业级联盟链部署工具
可视化数据展现 区块链浏览器
可视化节点管理 基于WeBASE,提供节点管理器
动态管理节点 支持动态新增、剔除、变更节点
动态更改配置 支持动态变更系统配置
数据备份与恢复 提供数据导出与恢复服务组件
监控统计 输出统计日志,提供监控工具
监管审计 基于WeBASE,提供监管审计入口

架构

FISCO BCOS 在2.0中,创新性提出“一体两翼多引擎”架构,实现系统吞吐能力的横向扩展,大幅提升性能,在安全性、可运维性、易用性、可扩展性上,均具备行业领先优势。

_images/plane.jpg

一体指代群组架构,支持快速组建联盟和建链,让企业建链像建聊天群一样便利。根据业务场景和业务关系,企业可选择不同群组,形成多个不同账本的数据共享和共识,从而快速丰富业务场景、扩大业务规模,且大幅简化链的部署和运维成本。

两翼指的是支持并行计算模型和分布式存储,二者为群组架构带来更好的扩展性。前者改变了区块中按交易顺序串行执行的做法,基于DAG(有向无环图)并行执行交易,大幅提升性能;后者支持企业(节点)将数据存储在远端分布式系统中,克服了本地化数据存储的诸多限制。

多引擎是一系列功能特性的总括,比如预编译合约能够突破EVM的性能瓶颈,实现高性能合约;控制台可以让用户快速掌握区块链使用技巧等。

上述功能特性均聚焦解决技术和体验的痛点,为开发、运维、治理和监管提供更多的工具支持,让系统处理更快、容量更高,使应用运行环境更安全、更稳定。

核心模块

FISCO BCOS采用高通量可扩展的多群组架构,可以动态管理多链、多群组,满足多业务场景的扩展需求和隔离需求,核心模块包括:

  • 共识机制:可插拔的共识机制,支持PBFT、Raft和rPBFT共识算法,交易确认时延低、吞吐量高,并具有最终一致性。其中PBFT和rPBFT可解决拜占庭问题,安全性更高。
  • 存储:世界状态的存储从原来的MPT存储结构转为分布式存储,避免了世界状态急剧膨胀导致性能下降的问题;引入可插拔的存储引擎,支持LevelDB、RocksDB、MySQL等多种后端存储,支持数据简便快速扩容的同时,将计算与数据隔离,降低了节点故障对节点数据的影响。
  • 网络:支持网络压缩功能,并基于负载均衡的思想实现了良好的分布式网络分发机制,最大化降低带宽开销。

性能

为提升系统性能,FISCO BCOS从提升交易执行效率和并发两个方面优化了交易执行,使得交易处理性能达到万级以上。

  • 基于C++的Precompiled合约:区块链底层内置C++语言编写的Precompiled合约,执行效率更高。
  • 交易并行执行:基于DAG算法根据交易间互斥关系构建区块内交易执行流,最大化并行执行区块内的交易。
  • 交易生命周期的异步并行处理:共识、同步、落盘等各个环节的异步化以及并行处理。

安全性

考虑到联盟链的高安全性需求,除了节点之间、节点与客户端之间通信采用TLS安全协议外,FISCO BCOS还实现了一整套安全解决方案:

  • 网络准入机制:限制节点加入、退出联盟链,可将指定群组的作恶节点从群组中删除,保障了系统安全性。
  • 黑白名单机制:每个群组仅可接收相应群组的消息,保证群组间网络通信的隔离性;CA黑名单机制可及时与作恶节点断开网络连接,保障了系统安全。
  • 权限管理机制:基于分布式存储权限控制机制,灵活、细粒度地控制外部账户部署合约和创建、插入、删除和更新用户表的权限。
  • 支持国密算法:支持国密加密、签名算法和国密通信协议。
  • 落盘加密方案:支持加密节点落盘数据,保障链上数据的机密性。
  • 密钥管理方案:在落盘加密方案的基础上,采用KeyManager服务管理节点密钥,安全性更强。
  • 同态加密群环签名:链上提供了同态加密、群环签名接口,用于满足更多的业务需求。

可运维性

联盟链系统中,区块链的运维至关重要,FISCO BCOS提供了一整套运维部署工具,并引入了合约命名服务数据归档和迁移合约生命周期管理等工具来提升运维效率。

  • 运维部署工具: 部署、管理和监控多机构多群组联盟链的便捷工具,支持扩容节点、扩容新群组等多种操作。
  • 合约命名服务: 建立合约地址到合约名和合约版本的映射关系,方便调用者通过记忆简单的合约名来实现对链上合约的调用。
  • 数据归档、迁移和导出功能: 提供数据导出组件,支持链上数据归档、迁移和导出,增加了链上数据的可维护性,降低了运维复杂度。
  • 合约生命周期管理: 链上提供合约生命周期管理功能,便于链管理员对链上合约进行管理。

易用性

FISCO BCOS引入开发部署工具、交互式控制台、区块链浏览器等工具来提升系统的易用性,大幅缩短建链、部署应用的时间。

为了便于不同语言开发者快速开发应用,FISCO BCOS同时支持Java SDKNode.js SDKPython SDKGo SDK

社区开发工具

依托庞大的开源生态,社区内众伙伴秉承“来自开发者,用于开发者”的共建理念,在FISCO BCOS底层平台之上,自主研发多个趁手开发工具并回馈给社区,从不同业务层面需求上降低区块链应用开发难度和成本。以下作部分列举,欢迎更多机构或开发者向社区反馈更多好用的工具。

  • 区块链中间件平台WeBASE:面向多种对象,如开发者、运营者,并根据不同的场景,包括开发、调试、部署、审计等,打造丰富的功能组件和实用工具,提供友好的、可视化的操作环境。
  • 分布式身份解决方案WeIdentity:基于区块链的分布式多中心的技术解决方案,提供分布式实体身份标识及管理、可信数据交换协议等一系列的基础层与应用接口,可实现实体对象(人或物)数据的安全授权与交换。
  • 分布式事件驱动架构WeEvent:实现了可信、可靠、高效的跨机构、跨平台事件通知机制。在不改变已有商业系统的开发语言、接入协议的情况下,实现跨机构、跨平台的事件通知与处理。
  • 跨链协作方案WeCross:支持跨链事务交易,满足跨链交易的原子性,对跨链进行治理,可支持多方协作管理,避免单点风险。
  • 场景式隐私保护解决方案WeDPR:针对隐匿支付、匿名投票、匿名竞拍和选择性披露等应用方案,提供即时可用场景式隐私保护高效解决方案,助力各行业合法合规地探索数据相关业务。
  • ChainIDE:提供智能合约云端开发工具,帮助开发者节约边际成本,加速推送区块链应用落地。

版本及兼容

FISCO BCOS 2.6.0

变更描述、兼容及升级说明

FISCO BCOS 2.5.0

变更描述、兼容及升级说明

FISCO BCOS 2.4.0

变更描述、兼容及升级说明

FISCO BCOS 2.3.0

变更描述、兼容及升级说明

FISCO BCOS 2.2.0

变更描述、兼容及升级说明

FISCO BCOS 2.1.0

变更描述、兼容及升级说明

FISCO BCOS 2.0.0

变更描述、兼容及升级说明

查看节点和数据版本

v2.5.0

v2.4.x升级到v2.5.0

  • 兼容升级 :直接替换v2.4.x节点的二进制为 v2.5.0二进制 ,升级后的版本修复v2.4.x中的bug,并进一步优化了性能。
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.5.0所有新特性
  • v2.5.0 Release Note

变更描述

新增

更改

  • MySQL存储模式下,合约表的字段类型修改为mediumblob
  • 国密模式由编译选项修改为配置项
  • 更改节点只接受同一机构的SDK连接,可配置是否启用
  • 默认开启Paillier同态加密和群签名功能
  • build_chain脚本使用私钥为secp256k1的根证书和机构证书
  • PBFTBackup的存储由LevelDB修改为RocksDB
  • 重构libdevcrypto模块,优化代码结构,使用TASSL实现国密和非国密TLS连接
  • 优化存储模块openTable的锁实现
  • 优化区块数据编码为并行
  • 优化大对象析构耗时为异步
  • 优化日志输出机制,降低日志输出对性能的影响
  • 优化MHD和交易池模块的线程数,减少内存占用
  • 优化MySQL存储适配器实现,优化ZdbStorage的代码实现

修复

  • 修复同一个区块内修改Entry,后续交易查询该Entry结果出错的问题
  • 修复raft共识下,非leader节点交易池中的交易不能被打包的问题
  • 修复CachedStorage的一个死锁问题
  • 修复开启Binlog情况下,极端情况下,从Binlog恢复出错的问题
  • 修复特定情况下节点重启后Viewchange被拒,无法快速恢复视图的问题

兼容性

向前兼容,旧版本可以直接替换程序升级

推荐版本 最低版本 说明
控制台 1.0.10 1.0.4
SDK 2.5.0 2.0.4
generator 1.5.0 1.1.0 搭建新链需要使用该版本
浏览器 2.0.2 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2 0.4.11

兼容模式回滚至v2.4.x方法

当节点采用兼容模式从v2.4.x升级至v2.5.0后,可直接通过将节点二进制替换回v2.4.x完成回滚。

v2.4.0

v2.3.x升级到v2.4.0

  • 兼容升级 :直接替换v2.3.x节点的二进制为 v2.4.0二进制 ,升级后的版本修复v2.3.x中的bug,并新增了2.4.0动态群组生命周期管理功能、网络统计功能,但不会包含2.4.0所有特性,普通场景下可回滚至v2.3.x。回滚方法参考本文最后一节。
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.4.0所有新特性
  • v2.4.0 Release Note

变更描述

新特性

更新

  • CMake最低要求修改为3.7,支持依赖库多源下载

修复

  • 修复国密模式下ecRecover接口不可用问题
  • 修复国密模式、非国密模式下sha256接口返回值不一致的问题

兼容性

向前兼容,旧版本可以直接替换程序升级,替换后的节点修复v2.3.x中的bug,并新增了2.4.0的动态群组生命周期管理功能、网络统计功能,若要启用v2.4.0版本所有新特性,需重新搭链。

推荐版本 最低版本 说明
控制台 1.0.9 1.0.4
SDK 2.4.0 2.0.4
generator 1.4.0 1.1.0 搭建新链需要使用该版本
浏览器 2.0.2 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2 0.4.11
amdb-proxy 2.3.0 2.0.2

兼容模式回滚至v2.2.x方法

当节点采用兼容模式从v2.3.x升级至v2.4.0后,可直接通过将节点二进制替换回v2.3.x完成回滚。

v2.3.0

v2.2.x升级到v2.3.0

  • 兼容升级 :直接替换v2.2.x节点的二进制为 v2.3.0二进制 ,升级后的版本修复v2.2.x中的bug,但不会启用v2.3.0新特性,普通场景下可回滚至v2.2.x。回滚方法参考本文最后一节。
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.3.0新特性
  • v2.3.0 Release Note

变更描述

新特性

更新

修复

  • 修复特定兼容场景下的内存问题

兼容性

向前兼容,旧版本可以直接替换程序升级,但无法启动此版本的新特性。若需要用此版本的新特性,需重新搭链。

推荐版本 最低版本 说明
控制台 1.0.9 1.0.4
SDK 2.3.0 2.0.4
generator 1.3.0 1.1.0 搭建新链需要使用该版本
浏览器 2.0.2 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2 0.4.11
amdb-proxy 2.3.0 2.0.2

兼容模式回滚至v2.2.x方法

当节点采用兼容模式从v2.2.x升级至v2.3.0后,可直接通过将节点二进制替换回v2.2.x完成回滚。

v2.2.0

v2.1.x升级到v2.2.0

  • 兼容升级 :直接替换v2.1.x节点的二进制为 v2.2.0二进制 ,升级后的版本修复v2.1.x中的bug,但不会启用v2.2.0新特性,普通场景下可回滚至v2.1.x。回滚方法参考本文最后一节。
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.2.0新特性
  • v2.2.0 Release Note

变更描述

新特性

更新

从流程、存储、协议三方面进行优化,提升性能。

1. 流程
  • 异步提交RPC交易到交易池
  • 并行化对交易池中交易的处理操作
  • 优化特定数据的缓存策略
  • 优化交易并行执行过程中锁粒度
  • 优化部分对象的访问方式,减少拷贝花销
2. 存储
  • 限制表名最大长度,从64调整为50
  • 以二进制方式对区块数据和nonce数据进行编码存储
  • 移除数据落盘阶段对部分表的排序和hash计算
3. 协议

修复

  • 修复特定兼容场景下的缓存bug

兼容性

向前兼容,旧版本可以直接替换程序升级,但无法启动此版本的新特性。若需要用此版本的新特性,需重新搭链。

推荐版本 最低版本 说明
控制台 1.0.7 1.0.4
SDK 2.2.0 2.0.4
generator 1.2.0 1.1.0 搭建新链需要使用该版本
浏览器 2.0.2 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2 0.4.11
amdb-proxy 2.2.0 2.0.2

兼容模式回滚至v2.1.x方法

当节点采用兼容模式从v2.1.x升级至v2.2.0后,可直接通过将节点二进制替换回v2.1.x完成回滚。

v2.1.0

v2.0.x升级到v2.1.0

  • 兼容升级 :直接替换v2.0.x节点的二进制为 v2.1.0二进制 ,升级后的版本修复v2.0.x中的bug,但不会启用v2.1.0新特性,普通场景下可回滚至v2.0.0。回滚方法参考本文最后一节。
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.1.0新特性
  • v2.1.0 Release Note

变更描述

新特性

更新

  • 支持Channel Message v2协议
  • 节点连接支持域名配置
  • 部署合约的二进制长度放宽至256K
  • 交易出错打印更全面的日志
  • build_chain.sh生成的SDK证书名更名为sdk.crt和sdk.key
  • 为提升性能进行了代码实现细节的调整
  • 降低了节点内存的占用

修复

  • 修复了在某种场景下channel连接抛异常的错误

兼容性

向前兼容,旧版本可以直接替换程序升级,但无法启动此版本的新特性。若需要用此版本的新特性,需重新搭链。

推荐版本 最低版本 说明
控制台 1.0.5 1.0.4
SDK 2.1.0 2.0.4
generator 1.1.0 1.1.0 搭建新链需要使用该版本
浏览器 2.0.0-rc2 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2 0.4.11
amdb-proxy 2.1.0 2.0.2

兼容模式回滚至v2.0.0方法

当节点采用兼容模式从v2.0.x升级至v2.1.0后,可直接通过将节点二进制替换回v2.0.x完成回滚。若在升级到v2.1.0之后部署过较大二进制的合约(在24K-256K之间),回滚至v2.0.x版本则不能重新同步数据,该条部署合约的交易会执行失败,导致同步失败。此时只能先用v2.1.0同步至最新区块,再回滚至v2.0.x。

v2.0.0

v2.0.0-rc3升级到v2.0.0

  • 兼容升级 :直接替换v2.0.0-rc3节点的二进制为 v2.0.0二进制 ,升级后的版本修复v2.0.0-rc3中的bug,但不会启用v2.0.0新特性, 升级到v2.0.0后,无法回滚到v2.0.0-rc3
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.0.0新特性
  • v2.0.0 Release Note

变更描述

新特性

  • AMOP协议支持多播
  • AMOP协议支持二进制传输
  • JSON-RPC getTotalTransactionCount接口新增历史失败交易数统计

更新

  • RocksDB模式支持落盘加密
  • 使用TCMalloc优化内存使用

修复

  • 修复P2P模块偶现不处理消息的问题
  • 修复MySQL或External模式下未赋值字段,查询失败
  • 修复某些极端场景下同步错误的问题

兼容性

向前兼容,旧版本可以直接替换程序升级,但无法启动此版本的新特性。若需要用此版本的新特性,需重新搭链。

最低对应版本 说明
控制台 1.0.4以上
SDK 2.0.4以上
generator 1.0.0 搭建新链需要使用该版本
浏览器 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2
amdb-proxy 2.0.2以上

v2.0.0-rc3

v2.0.0-rc2升级到v2.0.0-rc3

  • 兼容升级 :直接替换v2.0.0-rc2节点的二进制为 rc3二进制 ,升级后的版本修复v2.0.0-rc2中的bug,但不会启用v2.0.0-rc3新特性, 升级到v2.0.0-rc3后,无法回滚到v2.0.0-rc2
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.0.0-rc3新特性
  • v2.0.0-rc3 Release Note

变更描述

新特性

更新

  • 完善ABI解码模块
  • 修改预编译合约和RPC接口错误码,统一为负数
  • 优化存储模块,增加缓存层,支持配置缓存大小
  • 优化存储模块,允许流水线提交区块。可配置[storage].max_capacity控制允许使用的内存空间大小
  • 移动分布式存储配置项[storage],从群组genesis文件移动到到群组ini配置文件中
  • 默认存储升级到RocksDB,仍支持旧版本LevelDB
  • 调整交易互斥变量的拼接逻辑,提高不同合约间交易的并行度

修复

  • 修复CRUD接口合约开启并行时可能出现的异常终止

兼容性说明

RC3向前兼容,旧版本可以直接替换程序升级,但无法启动此版本的新特性。若需要用此版本的新特性,需重新搭链。

最低对应版本 说明
控制台 1.0.3 1.0.3之前版本控制台可用,但无新特性
SDK 2.0.3以上
generator 1.0.0-rc3 搭建新链需要使用该版本
浏览器 2.0.0-rc2
Solidity 最高支持 solidity 0.5.2
amdb-proxy 2.0.0以上

v2.0.0-rc2

v2.0.0-rc1升级到v2.0.0-rc2

  • 兼容升级 :直接替换v2.0.0-rc1节点的二进制为 v2.0.0-rc2二进制 ,升级后的版本修复v2.0.0-rc1中的bug,但不会启用v2.0.0-rc2并行计算、分布式存储等新特性, 升级到v2.0.0-rc2后,无法回滚到v2.0.0-rc1
  • 全面升级 :参考 安装 搭建新链,重新向新节点提交所有历史交易,升级后节点包含v2.0.0-rc2新特性
  • v2.0.0-rc2 Release Note

变更描述

主要特性

版本优化

  • 优化了区块打包交易数的逻辑,根据执行时间动态的调整区块打包交易数
  • 优化了区块同步的流程,让区块同步更快
  • 并行优化了将交易的编解码、交易的验签和落盘的编码
  • 优化了交易执行返回码的逻辑,让返回码更准确
  • 升级了存储模块,支持并发读写

其他特性

  • 加入网络数据包压缩
  • 加入兼容性配置
  • 交易编码中加入chainID和groupID
  • 交易中加入二进制缓存
  • 创世块中加入timestamp信息
  • 增加了一些precompile的demo
  • 支持用Docker搭链
  • 删除不必要的日志
  • 删除不必要的重复操作

Bug修复

  • RPC中处理参数时asInt异常造成程序退出的Bug
  • 交易执行Out of gas时交易一直在交易池中不被处理的Bug
  • 不同组间可以用相同的交易二进制重放的Bug
  • insert操作造成的性能衰减问题
  • 一些稳定性修复

兼容性说明

兼容版本 说明
节点 向下兼容v2.0.0-rc1
不兼容v1.x
可运行于v2.0.0-rc1节点
v2.0.0-rc1节点无法使用v2.0.0-rc2新特性,但可提升稳定性
若需要用此版本的新特性,需重新搭链
控制台 v1.0.2+
Web3SDK v2.0.0-rc2+
运维部署工具 v1.0.0-rc2 仅可使用v1.0.0-rc2
浏览器 v2.0.0-rc2+
Solidity 最高支持 solidity 0.5.2
amdb-proxy v2.0.0+

v2.0.0-rc1

v1.x升级到v2.0.0-rc1

  • v2.0.0-rc2不兼容v1.x,v2.0.0-rc1无法直接解析v1.x产生的历史区块数据 ,但可通过在 v2.0.0-rc1 的新链上执行历史交易的方式恢复旧数据
  • 搭建2.0的新链 :请参考 安装
  • v2.0.0-rc1 Release Note

变更描述

架构

  1. 新增群组架构:各群组独立共识和存储,在较低运维成本基础上实现系统吞吐能力横向扩展。
  2. 新增分布式数据存储:支持节点将数据存储在远端分布式系统中,实现计算与数据隔离、高速扩容、数据安全等级提升等目标。
  3. 新增对预编译合约的支持:底层基于C++实现预编译合约框架,兼容solidity调用方式,提升智能合约执行性能。
  4. 引入evmc扩展框架:支持扩展不同虚拟机引擎。
  5. 升级重塑P2P共识同步交易执行、交易池、区块管理模块。

协议

  1. 实现一套CRUD基本数据访问接口规范合约,基于CRUD接口编写业务合约,实现传统面向SQL方式的业务开发流程。
  2. 支持交易上链异步通知、区块上链异步通知以及自定义的AMOP消息通知等机制。
  3. 升级以太坊虚拟机版本,支持Solidity 0.5.2版本。
  4. 升级RPC模块

安全

  1. 升级落盘加密,提供密钥管理服务。开启落盘加密功能时,依赖KeyManager服务进行密钥管理。
  2. 升级准入机制,通过引入网络准入机制和群组准入机制,在不同维度对链和数据访问进行安全控制。
  3. 升级权限控制体系,基于表进行访问权限的设计。

其他

  1. 提供入门级的搭链工具
  2. 提供模块化的单元测试和端对端集成测试,支持自动化持续集成和持续部署。

兼容性说明

兼容版本 说明
节点 不兼容v1.x节点 v2.0.0-rc1与v1.x完全不兼容
v2.0.0-rc1不能直接运行于v1.x节点
v2.0.0-rc1节点不能与v1.x节点相互通信
控制台 v1.0.0+
Web3SDK v2.0.0-rc1+
运维部署工具 v1.0.0-rc1 仅可使用v1.0.0-rc1
浏览器 v2.0.0-rc1+
Solidity 最高支持 solidity 0.5.2

安装

本章介绍FISCO BCOS所需的必要安装和配置。本章通过在单机上部署一条4节点的FISCO BCOS联盟链,帮助用户掌握FISCO BCOS部署流程。请根据这里使用支持的硬件和平台操作。

注解

单群组FISCO BCOS联盟链的搭建

本节以搭建单群组FISCO BCOS链为例操作。使用开发部署工具 build_chain.sh脚本在本地搭建一条4 节点的FISCO BCOS链,以Ubuntu 16.04 64bit系统为例操作。

注解

  • 若需在已有区块链上进行升级,请转至 版本及兼容 章节。
  • 搭建多群组的链操作类似, 参考这里
  • 本节使用预编译的静态`fisco-bcos`二进制文件,在CentOS 7和Ubuntu 16.04 64bit上经过测试。
  • 使用docker模式搭建 ,供有丰富docker经验和容器化部署需求的用户参考。

准备环境

  • 安装依赖

开发部署工具 build_chain.sh脚本依赖于openssl, curl,使用下面的指令安装。 若为CentOS,将下面命令中的apt替换为yum执行即可。macOS执行brew install openssl curl即可。

sudo apt install -y openssl curl
  • 创建操作目录
cd ~ && mkdir -p fisco && cd fisco
  • 下载build_chain.sh脚本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.6.0/build_chain.sh && chmod u+x build_chain.sh

注解

  • 如果因为网络问题导致长时间无法下载build_chain.sh脚本,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/FISCO-BCOS/raw/master/tools/build_chain.sh && chmod u+x build_chain.sh

搭建单群组4节点联盟链

在fisco目录下执行下面的指令,生成一条单群组4节点的FISCO链。 请确保机器的30300~30303,20200~20203,8545~8548端口没有被占用。

注解

  • 国密版本请执行``bash build_chain.sh -l “127.0.0.1:4” -p 30300,20200,8545 -g -G``
  • 其中-g表示生成国密配置,-G表示使用国密SSL连接
  • web3sdk已经支持国密SSL,如果使用web3sdk建议带上-G选项使用国密SSL
bash build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545

注解

  • 其中-p选项指定起始端口,分别是p2p_port,channel_port,jsonrpc_port
  • 出于安全性和易用性考虑,v2.3.0版本最新配置将listen_ip拆分成jsonrpc_listen_ip和channel_listen_ip,但仍保留对listen_ip的解析功能,详细请参考 这里
  • 为便于开发和体验,channel_listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP

命令执行成功会输出All completed。如果执行出错,请检查nodes/build.log文件中的错误信息。

Checking fisco-bcos binary...
Binary check passed.
==============================================================
Generating CA key...
==============================================================
Generating keys ...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
Generating configurations...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
[INFO] Execute the download_console.sh script in directory named by IP to get FISCO-BCOS console.
e.g.  bash /home/ubuntu/fisco/nodes/127.0.0.1/download_console.sh
==============================================================
[INFO] FISCO-BCOS Path   : bin/fisco-bcos
[INFO] Start Port        : 30300 20200 8545
[INFO] Server IP         : 127.0.0.1:4
[INFO] Output Dir        : /home/ubuntu/fisco/nodes
[INFO] CA Key Path       : /home/ubuntu/fisco/nodes/cert/ca.key
==============================================================
[INFO] All completed. Files in /home/ubuntu/fisco/nodes

启动FISCO BCOS链

  • 启动所有节点
bash nodes/127.0.0.1/start_all.sh

启动成功会输出类似下面内容的响应。否则请使用netstat -an | grep tcp检查机器的30300~30303,20200~20203,8545~8548端口是否被占用。

try to start node0
try to start node1
try to start node2
try to start node3
 node1 start successfully
 node2 start successfully
 node0 start successfully
 node3 start successfully

检查进程

  • 检查进程是否启动
ps -ef | grep -v grep | grep fisco-bcos

正常情况会有类似下面的输出; 如果进程数不为4,则进程没有启动(一般是端口被占用导致的)

fisco       5453     1  1 17:11 pts/0    00:00:02 /home/ubuntu/fisco/nodes/127.0.0.1/node0/../fisco-bcos -c config.ini
fisco       5459     1  1 17:11 pts/0    00:00:02 /home/ubuntu/fisco/nodes/127.0.0.1/node1/../fisco-bcos -c config.ini
fisco       5464     1  1 17:11 pts/0    00:00:02 /home/ubuntu/fisco/nodes/127.0.0.1/node2/../fisco-bcos -c config.ini
fisco       5476     1  1 17:11 pts/0    00:00:02 /home/ubuntu/fisco/nodes/127.0.0.1/node3/../fisco-bcos -c config.ini

检查日志输出

  • 如下,查看节点node0链接的节点数
tail -f nodes/127.0.0.1/node0/log/log*  | grep connected

正常情况会不停地输出链接信息,从输出可以看出node0与另外3个节点有链接。

info|2019-01-21 17:30:58.316769| [P2P][Service] heartBeat,connected count=3
info|2019-01-21 17:31:08.316922| [P2P][Service] heartBeat,connected count=3
info|2019-01-21 17:31:18.317105| [P2P][Service] heartBeat,connected count=3
  • 执行下面指令,检查是否在共识
tail -f nodes/127.0.0.1/node0/log/log*  | grep +++

正常情况会不停输出++++Generating seal,表示共识正常。

info|2019-01-21 17:23:32.576197| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++Generating seal on,blkNum=1,tx=0,myIdx=2,hash=13dcd2da...
info|2019-01-21 17:23:36.592280| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++Generating seal on,blkNum=1,tx=0,myIdx=2,hash=31d21ab7...
info|2019-01-21 17:23:40.612241| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++Generating seal on,blkNum=1,tx=0,myIdx=2,hash=49d0e830...

配置及使用控制台

在控制台通过Web3SDK链接FISCO BCOS节点,实现查询区块链状态、部署调用合约等功能,能够快速获取到所需要的信息。 控制台指令详细介绍参考这里

准备依赖

  • Java环境配置

参考Java环境要求

  • 获取控制台并回到fisco目录
cd ~/fisco && curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v1.1.0/download_console.sh && bash download_console.sh

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 cd ~/fisco && curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh
  • 拷贝控制台配置文件

若节点未采用默认端口,请将文件中的20200替换成节点对应的channle端口。

cp -n console/conf/applicationContext-sample.xml console/conf/applicationContext.xml
  • 配置控制台证书

注解

  • 搭建国密版时,如果使用国密SSL请执行``cp nodes/127.0.0.1/sdk/gm/* console/conf/``
  • 搭建国密版时,请修改 applicationContext.xml 中 encryptType 修改为1
cp nodes/127.0.0.1/sdk/* console/conf/

启动控制台

  • 启动
cd ~/fisco/console && bash start.sh

输出下述信息表明启动成功 否则请检查conf/applicationContext.xml中节点端口配置是否正确

=============================================================================================
Welcome to FISCO BCOS console(1.0.3)!
Type 'help' or 'h' for help. Type 'quit' or 'q' to quit console.
 ________  ______   ______    ______    ______         _______    ______    ______    ______
|        \|      \ /      \  /      \  /      \       |       \  /      \  /      \  /      \
| $$$$$$$$ \$$$$$$|  $$$$$$\|  $$$$$$\|  $$$$$$\      | $$$$$$$\|  $$$$$$\|  $$$$$$\|  $$$$$$\
| $$__      | $$  | $$___\$$| $$   \$$| $$  | $$      | $$__/ $$| $$   \$$| $$  | $$| $$___\$$
| $$  \     | $$   \$$    \ | $$      | $$  | $$      | $$    $$| $$      | $$  | $$ \$$    \
| $$$$$     | $$   _\$$$$$$\| $$   __ | $$  | $$      | $$$$$$$\| $$   __ | $$  | $$ _\$$$$$$\
| $$       _| $$_ |  \__| $$| $$__/  \| $$__/ $$      | $$__/ $$| $$__/  \| $$__/ $$|  \__| $$
| $$      |   $$ \ \$$    $$ \$$    $$ \$$    $$      | $$    $$ \$$    $$ \$$    $$ \$$    $$
 \$$       \$$$$$$  \$$$$$$   \$$$$$$   \$$$$$$        \$$$$$$$   \$$$$$$   \$$$$$$   \$$$$$$

=============================================================================================

控制台启动失败,参考 附录:JavaSDK启动失败场景

使用控制台获取信息

# 获取客户端版本
[group:1]> getNodeVersion
{
    "Build Time":"20200619 06:32:10",
    "Build Type":"Linux/clang/Release",
    "Chain Id":"1",
    "FISCO-BCOS Version":"2.5.0",
    "Git Branch":"HEAD",
    "Git Commit Hash":"72c6d770e5cf0f4197162d0e26005ec03d30fcfe",
    "Supported Version":"2.5.0"
}
# 获取节点链接信息
[group:1]> getPeers
[
    {
        "IPAndPort":"127.0.0.1:49948",
        "NodeID":"b5872eff0569903d71330ab7bc85c5a8be03e80b70746ec33cafe27cc4f6f8a71f8c84fd8af9d7912cb5ba068901fe4131ef69b74cc773cdfb318ab11968e41f",
        "Topic":[]
    },
    {
        "IPAndPort":"127.0.0.1:49940",
        "NodeID":"912126291183b673c537153cf19bf5512d5355d8edea7864496c257630d01103d89ae26d17740daebdd20cbc645c9a96d23c9fd4c31d16bccf1037313f74bb1d",
        "Topic":[]
    },
    {
        "IPAndPort":"127.0.0.1:49932",
        "NodeID":"db75ab16ed7afa966447c403ca2587853237b0d9f942ba6fa551dc67ed6822d88da01a1e4da9b51aedafb8c64e9d208d9d3e271f8421f4813dcbc96a07d6a603",
        "Topic":[]
    }
]

部署及调用HelloWorld合约

HelloWorld合约

HelloWorld合约提供两个接口,分别是get()set(),用于获取/设置合约变量name。合约内容如下:

pragma solidity ^0.4.24;

contract HelloWorld {
    string name;

    function HelloWorld() {
        name = "Hello, World!";
    }

    function get()constant returns(string) {
        return name;
    }

    function set(string n) {
        name = n;
    }
}

部署HelloWorld合约

为了方便用户快速体验,HelloWorld合约已经内置于控制台中,位于控制台目录下contracts/solidity/HelloWorld.sol,参考下面命令部署即可。

# 在控制台输入以下指令 部署成功则返回合约地址
[group:1]> deploy HelloWorld
contract address:0xb3c223fc0bf6646959f254ac4e4a7e355b50a344

调用HelloWorld合约

# 查看当前块高
[group:1]> getBlockNumber
1

# 调用get接口获取name变量 此处的合约地址是deploy指令返回的地址
[group:1]> call HelloWorld 0xb3c223fc0bf6646959f254ac4e4a7e355b50a344 get
Hello, World!

# 查看当前块高,块高不变,因为get接口不更改账本状态
[group:1]> getBlockNumber
1

# 调用set设置name
[group:1]> call HelloWorld 0xb3c223fc0bf6646959f254ac4e4a7e355b50a344 set "Hello, FISCO BCOS"
0x21dca087cb3e44f44f9b882071ec6ecfcb500361cad36a52d39900ea359d0895

# 再次查看当前块高,块高增加表示已出块,账本状态已更改
[group:1]> getBlockNumber
2

# 调用get接口获取name变量,检查设置是否生效
[group:1]> call HelloWorld 0xb3c223fc0bf6646959f254ac4e4a7e355b50a344 get
Hello, FISCO BCOS

# 退出控制台
[group:1]> quit

注:

  1. 部署合约还可以通过deployByCNS命令,可以指定部署的合约版本号,使用方式参考这里
  2. 调用合约通过callByCNS命令,使用方式参考这里

教程

本章将介绍使用FISCO BCOS快速上手开发DApp的基本流程和相关的核心概念。 同时,我们还提供了便于企业用户开发部署的工具包的使用指南。

关键概念

区块链是由多个学科交叉组合形成的一门技术,本章将阐述区块链相关的基本概念,对涉及的基本理论进行科普介绍。如果您已经对这些基本技术很熟悉,可以跳过本章。

区块链是什么

区块链(blockchain)是在比特币之后提出的一个概念,在中本聪关于比特币的论文中没有直接引入blockchain的概念,而是以chain of block来描述一种数据结构。

Chain of block是指由多个区块通过哈希(hash)串联成一条链式结构的数据组织方式。区块链则是采用多项技术交叉组合,维护管理这个chain of block数据结构,形成一个不可篡改的分布式账本的综合技术领域。

区块链技术是一种在对等网络环境下,通过透明和可信规则,构建不可伪造、难以篡改和可追溯的块链式数据结构,实现和管理可信数据的产生、存取和使用的模式。技术架构上,区块链是由分布式架构与分布式存储、块链式数据结构、点对点网络、共识算法、密码学算法、博弈论、智能合约等多种信息技术共同组成的整体解决方案。

区块链技术和生态起源于比特币,随着金融、司法、供应链、文化娱乐、社会管理、物联网等更多行业对此领域技术的关注,希望将其技术价值应用到更广泛的分布式协作中,区块链技术和产品模式也在持续进化,FISCO BCOS区块链底层平台在区块链技术基础上,专注提升安全、性能、可用性、易用性、隐私保护、合规监管等方面的能力,和业界生态共同发展,体现多方参与、智能协同、专业分工、价值分享的效能。

账本

账本顾名思义,用于管理账户、交易流水等数据,支持分类记账、对账、清结算等功能。在多方合作中,多个参与方希望共同维护和共享一份及时、正确、安全的分布式账本,以消除信息不对称,提升运作效率,保证资金和业务安全。而区块链通常被认为是用于构建“分布式共享账本”的一种核心技术,通过链式的区块数据结构、多方共识机制、智能合约、世界状态存储等一系列技术的共同作用,可实现一致、可信、事务安全、难以篡改可追溯的共享账本。

账本里包含的基本内容有区块,交易,账户,世界状态。

区块

区块是按时间次序构建的数据结构,区块链的第一个区块称为“创世块”(genesis block),后续生成的区块用“高度”标识,每个区块高度逐一递增,新区块都会引入前一个区块的hash信息,再用hash算法和本区块的数据生成唯一的数据指纹,从而形成环环相扣的块链状结构,称为“Blockchain”也即区块链。精巧的数据结构设计,使得链上数据按发生时间保存,可追溯可验证,如果修改任何一个区块里的任意一个数据,都会导致整个块链验证不通过,从而篡改的成本会很高。

一个区块的基本数据结构是区块头和区块体,区块头包含区块高度,hash、出块者签名、状态树根等一些基本信息,区块体里包含一批交易数据列表已经相关的回执信息,根据交易列表的大小,整个区块的大小会有所不同,考虑到网络传播等因素,一般不会太大,在1M~几M字节之间。

交易

交易可认为是一段发往区块链系统的请求数据,用于部署合约,调用合约接口,维护合约的生命周期,以及管理资产,进行价值交换等,交易的基本数据结构包括发送者,接受者,交易数据等。用户可以构建一个交易,用自己的私钥给交易签名,发送到链上(通过sendRawTransaction等接口),由多个节点的共识机制处理,执行相关的智能合约代码,生成交易指定的状态数据,然后将交易打包到区块里,和状态数据一起落盘存储,该交易即为被确认,被确认的交易被认为具备了事务性和一致性。

随着交易确认相应还会有交易回执(receipt)产生,和交易一一对应且保存在区块里,用于保存一些交易执行过程生成的信息如结果码、日志、消耗的gas量等。用户可以使用交易hash检查交易回执,判定交易是否完成。

和“写操作”的交易对应,还有一种”只读”调用方式,用于读取链上数据,节点收到请求后会根据请求的参数访问状态信息并返回,并不会将请求加入共识流程,也不会导致修改链上的数据。

账户

在采用账户模型设计的区块链系统里,账户这个术语代表着用户、智能合约的唯一性存在。

在采用公私钥体系的区块链系统里,用户创建一个公私钥对,经过hash等算法换算即得到一个唯一性的地址串,代表这个用户的账户,用户用该私钥管理这个账户里的资产。用户账户在链上不一定有对应的存储空间,而是由智能合约管理用户在链上的数据,因此这种用户账户也会被称为“外部账户”。

对智能合约来说,一个智能合约被部署后,在链上就有了一个唯一的地址,也称为合约账户,指向这个合约的状态位、二进制代码、相关状态数据的索引等。智能合约运行过程中,会通过这个地址加载二进制代码,根据状态数据索引去访问世界状态存储里对应的数据,根据运行结果将数据写入世界状态存储,更新合约账户里的状态数据索引。智能合约被注销时,主要是更新合约账户里的合约状态位,将其置为无效,一般不会直接清除该合约账户的实际数据。

世界状态

FISCO BCOS采用“账户模型”的设计,即除了区块和交易的存储空间外,还会有一块保存智能合约运行结果的存储空间。智能合约执行过程产生的状态数据,经过共识机制确认,分布式的保存在各节点上,数据全局一致,可验证难篡改,所以称为“世界状态”。

状态存储空间的存在,使得区块链上可以保存各种丰富的数据,包括用户账户信息如余额等,智能合约二进制码,智能合约运行结果等相关的各种数据,智能合约执行过程中会从状态存储中获取一些数据参与运算,为实现复杂的合约逻辑提供了基础。

另一方面,维护状态数据需要付出不少存储成本,随着链的持续运行,状态数据会持续膨胀,如采用复杂的数据结构如帕特里夏树(Patricia Tree),状态数据的的容量会进一步扩大,根据不同的场景需要,可对状态数据进行裁剪优化,或采用分布式数据仓库等方案存储,以支持更海量的状态数据容量。

共识机制

共识机制是区块链领域的核心概念,无共识,不区块链。区块链作为一个分布式系统,可以由不同的节点共同参与计算、共同见证交易的执行过程,并确认最终计算结果。协同这些松散耦合、互不信任的参与者达成信任关系,并保障一致性,持续性协作的过程,可以抽象为“共识”过程,所牵涉的算法和策略统称为共识机制。

节点

安装了区块链系统所需软硬件,加入到区块链网络里的计算机,可以称为一个“节点”。节点参与到区块链系统的网络通信、逻辑运算、数据验证,验证和保存区块、交易、状态等数据,并对客户端提供交易处理和数据查询的接口。节点的标识采用公私钥机制,生成一串唯一的NodeID,以保证它在网络上的唯一性。

根据对计算的参与程度和数据的存量,节点可分为共识节点和观察节点。共识节点会参与到整个共识过程,做为记账者打包区块、做为验证者验证区块以完成共识过程。观察节点不参与共识,同步数据,进行验证并保存,可以做为数据服务者提供服务。

共识算法

共识算法需要解决的几个核心问题是:

  1. 选出在整个系统中具有记账权的角色,做为leader发起一次记账。
  2. 参与者采用不可否认和不能篡改的算法,进行多层面验证后,采纳Leader给出的记账。
  3. 通过数据同步和分布式一致性协作,保证所有参与者最终收到的结果都是一致的,无错的。

区块链领域常见的共识算法有公链常用的工作量证明(Proof of Work),权益证明(Proof of Stake),委托权益证明(Delegated Proof of Stake),以及联盟链常用的实用性拜占庭容错共识PBFT(Practical Byzantine Fault Tolerance),Raft等,另外一些前沿性的共识算法通常是将随机数发生器和上述几个共识算法进行有机组合,以改善安全、能耗以及性能和规模问题。

FISCO BCOS共识模块采用插件化的设计,可支持多种共识算法,当前包括PBFT和Raft,后续将会持续实现更大规模,速度更快的共识算法。

智能合约

智能合约概念于1995年由Nick Szabo首次提出,指以数字形式定义的能自动执行条款的合约,数字形式意味着合约必须用计算机代码实现,因为只要参与方达成协定,智能合约建立的权利和义务,就会被自动执行,且结果不能被否认。

FISCO BCOS运用智能合约不仅用于资产管理、规则定义和价值交换,还可以用来进行全局配置、运维治理、权限管理等。

智能合约生命周期

智能合约的生命周期为设计,开发,测试,部署,运行,升级,销毁等几个步骤。

开发人员根据需求,进行智能合约代码的编写,编译,单元测试。合约开发语言可包括solidity,C++,java,go,javascript,rust等,语言的选择根据平台虚拟机选型而定。在合约通过测试后,采用部署指令发布到链上,经过共识算法确认后,合约生效并被后续的交易调用。

当合约需要更新升级时,重复以上开发到部署的步骤,发布新版合约,新版合约会有一个新的地址和独立的存储空间,并不是覆盖掉旧合约。新版合约可通过旧合约数据接口访问旧版本合约里保存的数据,或者通过数据迁移的方式将旧合约的数据迁移到新合约的存储里,最佳实践是设计执行流程的“行为合约”和保存数据的“数据合约”,将数据和合约解耦,当业务流程产生改变,而业务数据本身没有改变时,新行为合约直接访问原有的“数据合约”即可。

销毁一个旧合约并不意味着清除合约的所有数据,只是将其状态置为“无效”,该合约则不可再被调用。

智能合约虚拟机

为了运行数字智能合约,区块链系统必须具备可编译、解析、执行计算机代码的编译器和执行器,统称为虚拟机体系。合约编写完毕后,用编译器编译,发送部署交易将合约部署到区块链系统上,部署交易共识通过后,系统给合约分配一个唯一地址和保存合约的二进制代码,当某个合约被另一个交易调用后,虚拟机执行器从合约存储里加载代码并执行,并输出执行结果。

在强调安全性、事务性和一致性的区块链系统里,虚拟机应具有沙盒特征,屏蔽类似随机数、系统时间、外部文件系统、网络等可能导致不确定性的因素,且可以抵抗恶意代码的侵入,以保证在不同节点上同一个交易和同一个合约的执行生成的结果是一致的,执行过程是安全的。

当前流行的虚拟机机制包括EVM, 受控的Docker,WebAssembly等,FISCO BCOS的虚拟机模块采用模块化设计,已经支持受到社区广泛欢迎的EVM,将会支持更多的虚拟机。

图灵完备

图灵机和图灵完备是计算机领域的经典概念,由数学家艾伦·麦席森·图灵(1912~1954)提出的一种抽象计算模型,引申到区块链领域,主要指合约支持判断、跳转、循环、递归等逻辑运算,支持多种数据类型如整形、字符串、结构体的数据处理能力,甚至有一定的面向对象特性如继承、派生、接口等,这样才能支持复杂的业务逻辑和完备的契约执行,与只支持栈操作的简单脚本进行区分。

2014年后出现的区块链大多支持图灵完备的智能合约,使得区块链系统具备更高的可编程性,在区块链既有的基本特性(如多方共识,难以篡改,可追溯等,安全性等)基础上,还可以实现具有一定业务逻辑的业务契约,如李嘉图合约(The Ricardian Contract),也可以使用智能合约来实现。

合约的执行还需要处理“停机问题”,即判断程序是否会在有限的时间之内解决输入的问题,并结束执行,释放资源。想象一下,一个合约在全网部署,在被调用时在每个节点上都会执行,如果这个合约是个无限循环,就意味着可能会耗尽整个体系的资源。所以停机问题的处理也是区块链领域里图灵完备计算体系的一个重要关注点。

联盟链概念分析

行业里通常将区块链的类型分为公有链,联盟链,私有链。公有链指所有人都可以随时随地参与甚至是匿名参与的链;私有链指一个主体(如一个机构或一个自然人)所有,私有化的管理和使用的链;联盟链通常是指多个主体达成一定的协议,或建立了一个业务联盟后,多方共同组建的链,加入联盟链的成员需要经过验证,一般是身份可知的。正因为有准入机制,所以联盟链也通常被称为“许可链”。

因为联盟链从组建、加入、运营、交易等环节有准入和身份管理,在链上的操作可以用权限进行管控,共识方面一般采用PBFT等基于多方多轮验证投票的共识机制,不采用POW挖矿的高能耗机制,网络规模相对可控,在交易时延性、事务一致性和确定性、并发和容量方面都可以进行大幅的优化。

联盟链在继承区块链技术的优势的同时,更适合性能容量要求高,强调监管、合规的敏感业务场景,如金融、司法、以及大量和实体经济相关的业务。联盟链的路线,兼顾了业务合规稳定和业务创新,也是国家和行业鼓励发展的方向。

性能
性能指标

软件系统的处理性能指标最常见的是TPS(Transaction Per Second),即系统每秒能处理和确认的交易数,TPS越高,性能越高。区块链领域的性能指标除了TPS之外,还有确认时延,网络规模大小等。

确认时延是指交易发送到区块链网络后,经过验证、运算和共识等一系列流程后,到被确认时所用的时间,如比特币网络一个区块是10分钟,交易被大概率确认需要6个区块,即一个小时。采用PBFT算法的话,可以使交易在秒级确认,一旦确认即具有最终确定性,更适合金融等业务需求。

网络规模指在保证一定的TPS和确认时延前提下,能支持多少共识节点的协同工作。业界一般认为采用PBFT共识算法的系统,节点规模在百级左右,再增加就会导致TPS下降,确认时延增加。目前业界有通过随机数算法选择记账组的共识机制,可以改善这个问题。

性能优化

性能的优化有两个方向,向上扩展(Scale up)和平行扩展(Scale out)。向上扩展指在有限的资源基础上优化软硬件配置,极大提升处理能力,如采用更有效率的算法,采用硬件加速等。平行扩展指系统架构具有良好的可扩展性,可以采用分片、分区的方式承载不同的用户、业务流的处理,只要适当增加软硬件资源,就能承载更多的请求。

性能指标和软件架构,硬件配置如CPU、内存、存储规格、网络带宽都密切相关,且随着TPS的增加,对存储容量的压力也会相应增加,需要综合考虑。

安全性

安全性是个很大的话题,尤其是构建在分布式网络上多方参与的区块链系统。在系统层面,需要关注网络攻击、系统渗透、数据破坏和泄漏的问题,在业务层面需要关注越权操作、逻辑错误、系统稳定性造成的资产损失、隐私被侵害等问题。

安全性的保障要关注”木桶的短板“,需要有综合性的防护策略,提供多层面,全面的安全防护,满足高要求的安全标准,并提供安全方面的最佳实践,对齐所有参与者的安全级别,保障全网安全。

准入机制

准入机制指在无论是机构还是个人组建和加入链之前,需要满足身份可知、资质可信,技术可靠的标准,主体信息由多方共同审核后,才会启动联盟链组建工作,然后将经过审核的主体的节点加入到网络,为经过审核的人员分配可发送交易的公私钥。 在准入完成后,机构、节点、人员的信息都会登记到链上或可靠的信息服务里,链上的一切行为都可以追溯到机构和人。

权限控制

联盟链上权限控制即不同人员对各种敏感级别的数据读写的控制,细分可以罗列出如合约部署、合约内数据访问、区块数据同步、系统参数访问和修改、节点启停等不同的权限,根据业务需要,还可以加入更多的权限控制点。

权限是分配给角色的,可沿用典型的基于角色的权限访问控制(Role-Based Access Control)设计,一个参考设计是将角色分为运营管理者,交易操作员,应用开发者,运维管理者,监管方,每个角色还可以根据需要细分层级,完备的模型可能会很庞大复杂,可以根据场景需要进行适当的设计,能达到业务安全可控的程度即可。

隐私保护

基于区块链架构的业务场景要求各参与方都输出和共享相关数据,以共同计算和验证,在复杂的商业环境中,机构希望自己的商业数据受控,在越来越被重视的个人数据隐私保护的形势下,个人对隐私保护的诉求也日益增强。如何对共享的数据牵涉隐私的部分进行保护,以及在避免运作过程泄漏隐私,是一个很重要的问题。

隐私保护首先是个管理问题,要求在构建系统开展业务时,把握“最小授权,明示同意的原则”,对数据的收集、存储、应用、披露、删除、恢复全生命周期进行管理,建立日常管理和应急管理制度,在高敏感业务场景设定监管角色,引入第三方检视和审计,从事先事中事后全环节进行管控。

在技术上,可以采用数据脱敏,业务隔离或者系统物理隔离等方式控制数据分发范围,同时也可以引入密码学方法如零知识证明、安全多方计算、环签名、群签名、盲签名等,对数据进行高强度的加密保护。

物理隔离

这个概念主要用于隐私保护领域,“物理隔离”是避免隐私数据泄露的彻底手段,物理隔离指只有共享数据的参与者在网络通信层互通,不参与共享数据的参与者在网络互相都不能通信,不交换哪怕一个字节的数据。

相对而言的是逻辑隔离,参与者可以接收到和自己无关的数据,但数据本身带上权限控制或加密保护,使得没有授权或密钥的参与者不能访问和修改。但随着技术的发展,所受到的权限受控数据或加密数据在若干年后依旧有可能被破解。

对极高敏感性的数据,可以采用“物理隔离”的策略,从根源上杜绝被破解的可能性。相应的成本是需要仔细甄别数据的敏感级别,对隔离策略进行周密的规划,并分配足够的硬件资源承载不同的数据。

治理与监管
联盟链治理

联盟链治理牵涉到多参与方协调工作,激励机制,安全运营,监管审计等一系列的问题,核心是理清各参与方的责权利,工作流程,构建顺畅的开发和运维体系,以及保障业务的合法合规,对包括安全性在内的问题能事先防范事后应急处理。为达成治理,需要制定相关的规则且保证各参与方达成共识并贯彻执行。

一个典型的联盟链治理参考模型是各参与方共同组建联盟链委员会,共同讨论和决议,根据场景需要设定各种角色和分配任务,如某些机构负责开发,某些机构参与运营管理,所有机构参与交易和运维,采用智能合约实现管理规则和维护系统数据,委员会和监管机构可掌握一定的管理权限,对业务、机构、人员进行审核和设置,并在出现紧急情况时,根据事先约定的流程,通过共识过的智能合约规则,进行应急操作,如账户重置,业务调整等,在需要进行系统升级时,委员会负责协调各方进行系统更新。

在具备完善治理机制的联盟链上,各参与方根据规则进行点对点的对等合作,包括资产交易、数据交换,极大程度提升运作效率,促进业务创新,同时合规性和安全性等方面也得到了保障。

快速部署

构建一个区块链系统的大致步骤包括:获取硬件资源包括服务器、网络、内存、硬盘存储等,进行环境配置包括选择指定操作系统、开通网络端口和相关策略、带宽规划、存储空间分配等,获取区块链二进制可运行软件或者从源码进行编译,然后进行区块链系统的配置,包括创世块配置、运行时参数配置,日志配置等,进行多方互联配置,包括节点准入配置、端口发现、共识参与方列表等,客户端和开发者工具配置,包括控制台、SDK等,这个过程会包括许多细节,如各种证书和公私钥的管理等,很容易出现环境、版本、配置的差错,导致整个过程复杂、繁琐和反复,形成了较高的使用门槛。

如何将以上步骤简化和加速,使构建和组链过程变得简便,快速,不容易出错,且低成本,需要从以下几方面进行考虑: 首先,标准化目标部署平台,事先将操作系统、依赖软件列表、网络带宽和存储容量、网络策略等关键的软硬件准备好,对齐版本和参数,使得平台可用,依赖完备。当下流行的云服务,docker等方式都可以帮助构建这样的标准化平台。

然后,从使用者的视角出发,优化区块链软件的构建、配置和组链流程,提供快速构建,自动组链的工具,使得使用者不需要关注诸多细节,简单的几步操作即可运行起供开发调试、上线运行的链。

FISCO BCOS非常重视使用者的部署体验,提供了一键部署的命令行,帮助开发者快速搭建开发调试环境,提供企业级搭链工具,面向多机构联合组链的场景,灵活的进行主机、网络等参数配置,管理相关的证书,便于多个企业之间协同工作。经过快速部署的优化,将使用者搭起区块链的时间缩短到几分钟到半小时以内。

数据治理

区块链强调数据层层验证,历史记录可追溯,常见的方案是从创世块以来,所有的数据都会保存在所有的参与节点上(轻节点之外),导致的结果是数据膨胀,容量紧张,尤其是在承载海量服务的场景里,在一定时间之后,一般的存储方案已经无法容纳数据,而海量存储成本很高,另一个角度是安全性,全量数据永久保存,可能面临历史数据泄露的风险,所以需要在数据治理方面进行设计。

数据治理主要是几个策略:裁剪迁移,平行扩容,分布式存储。如何选择需要结合场景分析。

对具有较强时间特征的数据,如某业务的清结算周期是一个星期,那么一个星期前的数据不需要参与在线计算和验证,旧的数据则可以从节点迁移到大数据存储里,满足数据可查询可验证的需求以及业务保存年限的要求,线上节点的数据压力大幅降低,历史数据离线保存,在安全策略上也可以进行更严密的保护。

对规模持续扩大的业务,如用户数或合同存证量剧增,可以针对不同的用户和合同,分配到不同的逻辑分区,每个逻辑分区有独立的存储空间,只承载一定容量的数据,当接近容量的上限,则再分配更多资源容纳新的数据。分区的的设计使得在资源调配,成本管理上都更容易把控。

结合数据裁剪迁移和平行扩容,数据的容量成本,安全级别都得到很好的控制,便于开展海量规模的业务。

运维监控

区块链系统从构建和运行逻辑上都具有较高一致性,不同节点的软硬件系统基本一致。其标准化特性给运维人员带来了便利,可使用通用的工具、运维策略和运维流程等对区块链系统进行构建、部署、配置、故障处理,从而降低运维成本以及提升效率。

运维人员对联盟链的操作会被权限系统控制,运维人员有修改系统配置、启停进程、查看运行日志、排查故障等权限,但不参与到业务交易中,也不能直接查看具有较高安全隐私等级的用户数据,交易数据。

系统运行过程中,可通过监控系统对各种运行指标进行监控,对系统的健康程度进行评估,当出现故障时发出告警通知,便于运维快速反应,进行处理。

监控的维度包括基础环境监控,如CPU占比、系统内存占比和增长、磁盘IO情况、网络连接数和流量等。

区块链系统监控包括如区块高度、交易量和虚拟机计算量,共识节点出块投票情况等。

接口监控包括如接口调用计数、接口调用耗时情况、接口调用成功率等。

监控数据可以通过日志或网络接口进行输出,便于和机构的现有的监控系统进行对接,复用机构的监控能力和既有的运维流程。运维人员收到告警后,采用联盟链提供的运维工具,查看系统信息、修改配置、启停进程、处理故障等。

监管审计

随着区块链技术和业务形态探索的发展,需要在区块链技术平台上提供支持监管的功能,避免区块链系统游离于法律法规以及行业规则之外,成为洗钱、非法融资或犯罪交易的载体。

审计功能主要用于满足区块链系统的审计内控、责任鉴定和事件追溯等要求,需要以有效的技术手段,配合业务所属的行业标准进行精确的审计管理。

监管者可以做为节点接入到区块链系统里,或者通过接口和区块链系统进行交互,监管者可同步到所有的数据进行审计分析,跟踪全局的业务流程,如发现异常,可以向区块链发出具备监管权限的指令,对业务、参与人、账户等进行管控,实现“穿透式监管”。

FISCO BCOS在角色和权限设计,功能接口,审计工具等方面都对监管审计进行了支持。

构建第一个区块链应用

本章将会介绍一个基于FISCO BCOS区块链的业务应用场景开发全过程,从业务场景分析,到合约的设计实现,然后介绍合约编译以及如何部署到区块链,最后介绍一个应用模块的实现,通过我们提供的Web3SDK实现对区块链上合约的调用访问。

本教程要求用户熟悉Linux操作环境,具备Java开发的基本技能,能够使用Gradle工具,熟悉Solidity语法

通过学习教程,你将会了解到以下内容:

  1. 如何将一个业务场景的逻辑用合约的形式表达
  2. 如何将Solidity合约转化成Java类
  3. 如何配置Web3SDK
  4. 如何构建一个应用,并集成Web3SDK到应用工程
  5. 如何通过Web3SDK调用合约接口,了解Web3SDK调用合约接口的原理

教程中会提供示例的完整项目源码,用户可以在此基础上快速开发自己的应用。

重要

请参考 安装文档 完成FISCO BCOS区块链的搭建和控制台的下载工作,本教程中的操作假设在该文档搭建的环境下进行。

示例应用需求

区块链天然具有防篡改,可追溯等特性,这些特性决定其更容易受金融领域的青睐,本文将会提供一个简易的资产管理的开发示例,并最终实现以下功能:

  • 能够在区块链上进行资产注册
  • 能够实现不同账户的转账
  • 可以查询账户的资产金额

合约设计与实现

在区块链上进行应用开发时,结合业务需求,首先需要设计对应的智能合约,确定合约需要储存的数据,在此基础上确定智能合约对外提供的接口,最后给出各个接口的具体实现。

存储设计

FISCO BCOS提供合约CRUD接口开发模式,可以通过合约创建表,并对创建的表进行增删改查操作。针对本应用需要设计一个存储资产管理的表t_asset,该表字段如下:

  • account: 主键,资产账户(string类型)
  • asset_value: 资产金额(uint256类型)

其中account是主键,即操作t_asset表时需要传入的字段,区块链根据该主键字段查询表中匹配的记录。t_asset表示例如下:

account asset_value
Alice 10000
Bob 20000
接口设计

按照业务的设计目标,需要实现资产注册,转账,查询功能,对应功能的接口如下:

// 查询资产金额
function select(string account) public constant returns(int256, uint256)
// 资产注册
function register(string account, uint256 amount) public returns(int256)
// 资产转移
function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)
完整源码
pragma solidity ^0.4.24;

import "./Table.sol";

contract Asset {
    // event
    event RegisterEvent(int256 ret, string account, uint256 asset_value);
    event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);

    constructor() public {
        // 构造函数中创建t_asset表
        createTable();
    }

    function createTable() private {
        TableFactory tf = TableFactory(0x1001);
        // 资产管理表, key : account, field : asset_value
        // |  资产账户(主键)      |     资产金额       |
        // |-------------------- |-------------------|
        // |        account      |    asset_value    |
        // |---------------------|-------------------|
        //
        // 创建表
        tf.createTable("t_asset", "account", "asset_value");
    }

    function openTable() private returns(Table) {
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_asset");
        return table;
    }

    /*
    描述 : 根据资产账户查询资产金额
    参数 :
            account : 资产账户

    返回值:
            参数一: 成功返回0, 账户不存在返回-1
            参数二: 第一个参数为0时有效,资产金额
    */
    function select(string account) public constant returns(int256, uint256) {
        // 打开表
        Table table = openTable();
        // 查询
        Entries entries = table.select(account, table.newCondition());
        uint256 asset_value = 0;
        if (0 == uint256(entries.size())) {
            return (-1, asset_value);
        } else {
            Entry entry = entries.get(0);
            return (0, uint256(entry.getInt("asset_value")));
        }
    }

    /*
    描述 : 资产注册
    参数 :
            account : 资产账户
            amount  : 资产金额
    返回值:
            0  资产注册成功
            -1 资产账户已存在
            -2 其他错误
    */
    function register(string account, uint256 asset_value) public returns(int256){
        int256 ret_code = 0;
        int256 ret= 0;
        uint256 temp_asset_value = 0;
        // 查询账户是否存在
        (ret, temp_asset_value) = select(account);
        if(ret != 0) {
            Table table = openTable();

            Entry entry = table.newEntry();
            entry.set("account", account);
            entry.set("asset_value", int256(asset_value));
            // 插入
            int count = table.insert(account, entry);
            if (count == 1) {
                // 成功
                ret_code = 0;
            } else {
                // 失败? 无权限或者其他错误
                ret_code = -2;
            }
        } else {
            // 账户已存在
            ret_code = -1;
        }

        emit RegisterEvent(ret_code, account, asset_value);

        return ret_code;
    }

    /*
    描述 : 资产转移
    参数 :
            from_account : 转移资产账户
            to_account : 接收资产账户
            amount : 转移金额
    返回值:
            0  资产转移成功
            -1 转移资产账户不存在
            -2 接收资产账户不存在
            -3 金额不足
            -4 金额溢出
            -5 其他错误
    */
    function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
        // 查询转移资产账户信息
        int ret_code = 0;
        int256 ret = 0;
        uint256 from_asset_value = 0;
        uint256 to_asset_value = 0;

        // 转移账户是否存在?
        (ret, from_asset_value) = select(from_account);
        if(ret != 0) {
            ret_code = -1;
            // 转移账户不存在
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;

        }

        // 接受账户是否存在?
        (ret, to_asset_value) = select(to_account);
        if(ret != 0) {
            ret_code = -2;
            // 接收资产的账户不存在
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }

        if(from_asset_value < amount) {
            ret_code = -3;
            // 转移资产的账户金额不足
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }

        if (to_asset_value + amount < to_asset_value) {
            ret_code = -4;
            // 接收账户金额溢出
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }

        Table table = openTable();

        Entry entry0 = table.newEntry();
        entry0.set("account", from_account);
        entry0.set("asset_value", int256(from_asset_value - amount));
        // 更新转账账户
        int count = table.update(from_account, entry0, table.newCondition());
        if(count != 1) {
            ret_code = -5;
            // 失败? 无权限或者其他错误?
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }

        Entry entry1 = table.newEntry();
        entry1.set("account", to_account);
        entry1.set("asset_value", int256(to_asset_value + amount));
        // 更新接收账户
        table.update(to_account, entry1, table.newCondition());

        emit TransferEvent(ret_code, from_account, to_account, amount);

        return ret_code;
    }
}

注: Asset.sol合约的实现需要引入FISCO BCOS提供的一个系统合约接口文件 Table.sol ,该系统合约文件中的接口由FISCO BCOS底层实现。当业务合约需要操作CRUD接口时,均需要引入该接口合约文件。Table.sol 合约详细接口参考这里

合约编译

上一小节,我们根据业务需求设计了合约Asset.sol的存储与接口,给出了完整实现,但是Java程序无法直接调用Solidity合约,需要先将Solidity合约文件编译为Java文件。

控制台提供了编译工具,可以将Asset.sol合约文件存放在console/contracts/solidity目录。利用console目录下提供的sol2java.sh脚本进行编译,操作如下:

# 切换到fisco/console/目录
$ cd ~/fisco/console/
# 编译合约,后面指定一个Java的包名参数,可以根据实际项目路径指定包名
$ ./sol2java.sh org.fisco.bcos.asset.contract

运行成功之后,将会在console/contracts/sdk目录生成java、abi和bin目录,如下所示。

|-- abi # 生成的abi目录,存放solidity合约编译生成的abi文件
|   |-- Asset.abi
|   |-- Table.abi
|-- bin # 生成的bin目录,存放solidity合约编译生成的bin文件
|   |-- Asset.bin
|   |-- Table.bin
|-- contracts # 存放solidity合约源码文件,将需要编译的合约拷贝到该目录下
|   |-- Asset.sol # 拷贝进来的Asset.sol合约,依赖Table.sol
|   |-- Table.sol # 实现系统CRUD操作的合约接口文件
|-- java  # 存放编译的包路径及Java合约文件
|   |-- org
|        |--fisco
|             |--bcos
|                  |--asset
|                       |--contract
|                             |--Asset.java  # Asset.sol合约生成的Java文件
|                             |--Table.java  # Table.sol合约生成的Java文件
|-- sol2java.sh

java目录下生成了org/fisco/bcos/asset/contract/包路径目录,该目录下包含Asset.javaTable.java两个文件,其中Asset.java是Java应用调用Asset.sol合约需要的文件。

Asset.java的主要接口:

package org.fisco.bcos.asset.contract;

public class Asset extends Contract {
    // Asset.sol合约 transfer接口生成
    public RemoteCall<TransactionReceipt> transfer(String from_account, String to_account, BigInteger amount);
    // Asset.sol合约 register接口生成
    public RemoteCall<TransactionReceipt> register(String account, BigInteger asset_value);
    // Asset.sol合约 select接口生成
    public RemoteCall<Tuple2<BigInteger, BigInteger>> select(String account);

    // 加载Asset合约地址,生成Asset对象
    public static Asset load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider);

    // 部署Assert.sol合约,生成Asset对象
    public static RemoteCall<Asset> deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider);
}

其中load与deploy函数用于构造Asset对象,其他接口分别用来调用对应的solidity合约的接口,详细使用在下文会有介绍。

SDK配置

我们提供了一个Java工程项目供开发使用,首先获取Java工程项目:

    # 获取Java工程项目压缩包
    $ cd ~
    $ curl -#LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz
    # 解压得到Java工程项目asset-app目录
    $ tar -zxf asset-app.tar.gz

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/tools/asset-app.tar.gz

asset-app项目的目录结构如下:

|-- build.gradle // gradle配置文件
|-- gradle
|   |-- wrapper
|       |-- gradle-wrapper.jar // 用于下载Gradle的相关代码实现
|       |-- gradle-wrapper.properties // wrapper所使用的配置信息,比如gradle的版本等信息
|-- gradlew // Linux或者Unix下用于执行wrapper命令的Shell脚本
|-- gradlew.bat // Windows下用于执行wrapper命令的批处理脚本
|-- src
|   |-- main
|   |   |-- java
|   |         |-- org
|   |             |-- fisco
|   |                   |-- bcos
|   |                         |-- asset
|   |                               |-- client // 放置客户端调用类
|   |                                      |-- AssetClient.java
|   |                               |-- contract // 放置Java合约类
|   |                                      |-- Asset.java
|   |-- test
|       |-- resources // 存放代码资源文件
|           |-- applicationContext.xml // 项目配置文件
|           |-- contract.properties // 存储部署合约地址的文件
|           |-- log4j.properties // 日志配置文件
|           |-- contract //存放solidity约文件
|                   |-- Asset.sol
|                   |-- Table.sol
|
|-- tool
    |-- asset_run.sh // 项目运行脚本
项目引入Web3SDK

项目的build.gradle文件已引入Web3SDK,不需修改。其引入方法介绍如下:

  • Web3SDK引入了以太坊的solidity编译器相关jar包,因此在build.gradle文件需要添加以太坊的远程仓库:
repositories {
    maven {
        url "http://maven.aliyun.com/nexus/content/groups/public/"
    }
    maven { url "https://dl.bintray.com/ethereum/maven/" }
    mavenCentral()
}
  • 引入Web3SDK jar包
compile ('org.fisco-bcos:web3sdk:2.5.0')
证书与配置文件
  • 区块链节点证书配置

拷贝区块链节点对应的SDK证书

# 进入~目录
# 拷贝节点证书到项目的资源目录
$ cd ~
$ cp fisco/nodes/127.0.0.1/sdk/* asset-app/src/test/resources/
  • applicationContext.xml

注意: 如果搭链时设置的jsonrpc_listen_ip为127.0.0.1或者0.0.0.0,channel_port为20200, 则applicationContext.xml配置不用修改。若区块链节点配置有改动,需要同样修改配置applicationContext.xml,具体请参考SDK使用文档

业务开发

我们已经介绍了如何在自己的项目中引入以及配置Web3SDK,本节介绍如何通过Java程序调用合约,同样以示例的资产管理说明。asset-app项目已经包含示例的完整源码,用户可以直接使用,现在介绍核心类AssetClient的设计与实现。

AssetClient.java: 通过调用Asset.java实现对合约的部署与调用,路径/src/main/java/org/fisco/bcos/asset/client,初始化以及调用流程都在该类中进行。

  • 初始化

初始化代码的主要功能为构造Web3j与Credentials对象,这两个对象在创建对应的合约类对象(调用合约类的deploy或者load函数)时需要使用。

// 函数initialize中进行初始化
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Service service = context.getBean(Service.class);
service.run();

ChannelEthereumService channelEthereumService = new ChannelEthereumService();
channelEthereumService.setChannelService(service);
// 初始化Web3j对象
Web3j web3j = Web3j.build(channelEthereumService, 1);
// 初始化Credentials对象
Credentials credentials = Credentials.create(Keys.createEcKeyPair());
  • 构造合约类对象

可以使用deploy或者load函数初始化合约对象,两者使用场景不同,前者适用于初次部署合约,后者在合约已经部署并且已知合约地址时使用。

// 部署合约
Asset asset = Asset.deploy(web3j, credentials, new StaticGasProvider(gasPrice, gasLimit)).send();
// 加载合约地址
Asset asset = Asset.load(contractAddress, web3j, credentials, new StaticGasProvider(gasPrice, gasLimit));
  • 接口调用

使用合约对象调用对应的接口,处理返回结果。

// select接口调用
Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount).send();
// register接口调用
TransactionReceipt receipt = asset.register(assetAccount, amount).send();
// transfer接口
TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount).send();

运行

至此我们已经介绍使用区块链开发资产管理应用的所有流程并实现了功能,接下来可以运行项目,测试功能是否正常。

  • 编译
# 切换到项目目录
$ cd ~/asset-app
# 编译项目
$ ./gradlew build

编译成功之后,将在项目根目录下生成dist目录。dist目录下有一个asset_run.sh脚本,简化项目运行。现在开始一一验证本文开始定下的需求。

  • 部署Asset.sol合约
# 进入dist目录
$ cd dist
$ bash asset_run.sh deploy
Deploy Asset successfully, contract address is 0xd09ad04220e40bb8666e885730c8c460091a4775
  • 注册资产
$ bash asset_run.sh register Alice 100000
Register account successfully => account: Alice, value: 100000
$ bash asset_run.sh register Bob 100000
Register account successfully => account: Bob, value: 100000
  • 查询资产
$ bash asset_run.sh query Alice
account Alice, value 100000
$ bash asset_run.sh query Bob
account Bob, value 100000
  • 资产转移
$ bash asset_run.sh transfer Alice Bob  50000
Transfer successfully => from_account: Alice, to_account: Bob, amount: 50000
$ bash asset_run.sh query Alice
account Alice, value 50000
$ bash asset_run.sh query Bob
account Bob, value 150000

总结: 至此,我们通过合约开发,合约编译,SDK配置与业务开发构建了一个基于FISCO BCOS联盟区块链的应用。

使用手册

本章提供了FISCO BCOS平台的使用手册,使用手册介绍FISCO BCOS平台各种功能使用方式。

获取可执行程序

用户可以自由选择以下任一方式获取FISCO BCOS可执行程序。推荐从GitHub下载预编译二进制。

  • 官方提供的静态链接的预编译文件,可以在Ubuntu 16.04和CentOS 7.2以上版本运行。
  • 官方提供docker镜像,欢迎使用。docker-hub地址
  • 源码编译获取可执行程序,参考源码编译

下载预编译fisco-bcos

我们提供静态链接的预编译程序,在Ubuntu 16.04和CentOS 7经过测试。请从Release页面下载最新发布的预编译程序

docker镜像

从v2.0.0版本开始,我们提供对应版本tag的docker镜像。对应于master分支,我们提供lastest标签的镜像,更多的docker标签请参考这里

build_chain.sh脚本增加了-d选项,提供docker模式建链的选择,方便开发者部署。详情请参考这里

注解

build_chain.sh脚本为了简单易用,启动docker使用了 --network=host 网络模式,实际使用中用户可能需要根据自己的网络场景定制改造。

源码编译

注解

源码编译适合于有丰富开发经验的用户,编译过程中需要下载依赖库,请保持网络畅通。受网络和机器配置影响,编译用时5-20分钟不等。

FSICO-BCOS使用通用CMake构建系统生成特定平台的构建文件,这意味着无论您使用什么操作系统工作流都非常相似:

  1. 安装构建工具和依赖包(依赖于平台)。
  2. FISCO BCOS克隆代码。
  3. 运行cmake生成构建文件并编译。
安装依赖
  • Ubuntu

推荐Ubuntu 16.04以上版本,16.04以下的版本没有经过测试,源码编译时依赖于编译工具和libssl

sudo apt install -y g++ libssl-dev openssl cmake git build-essential autoconf texinfo flex patch bison libgmp-dev zlib1g-dev
  • CentOS

推荐使用CentOS7以上版本。

$ sudo yum install -y epel-release centos-release-scl
$ sudo yum install -y openssl-devel openssl cmake3 gcc-c++ git flex patch bison gmp-static devtoolset-7
  • macOS

推荐xcode10以上版本。macOS依赖包安装依赖于Homebrew

brew install openssl git flex patch bison gmp
克隆代码
git clone https://github.com/FISCO-BCOS/FISCO-BCOS.git
编译

编译完成后二进制文件位于FISCO-BCOS/build/bin/fisco-bcos

$ cd FISCO-BCOS
$ git checkout master
$ mkdir -p build && cd build
$ source /opt/rh/devtoolset-7/enable  # CentOS请执行此命令,其他系统不需要
# CentOS请使用cmake3
$ cmake ..
# 高性能机器可添加-j4使用4核加速编译
$ make

注解

  • 如果因为网络问题导致长时间无法下载依赖库,请尝试从 https://gitee.com/FISCO-BCOS/LargeFiles/tree/master/libs 下载,放在FISCO-BCOS/deps/src/
编译选项介绍
  • TESTS,默认off,单元测试编译开关。通过cmake -DTESTS=on ..打开单元测试开关。

  • DEMO,默认off,测试程序编译开关。通过cmake -DDEMO=on ..打开单元测试开关。

  • TOOL,默认off,工具程序编译开关。通过cmake -DTOOL=on ..打开工具开关,提供FISCO节点的rocksdb读取工具。

  • ARCH_NATIVE,默认off,编译时根据本地CPU指令优化以获得更好的性能,在ARM架构上编译可以使用此选项。GCC9以上版本编译暂未适配完成,可以通过打开此编译选项避过问题,完成编译。

  • BUILD_STATIC,默认off,静态编译开关,只支持Ubuntu。通过cmake -DBUILD_STATIC=on ..打开静态编译开关。

  • CMAKE_BUILD_TYPE,默认RelWithDebInfo,编译类型,如要编译Release版本,通过cmake -DCMAKE_BUILD_TYPE=Release ..设置

  • DEBUG,默认off,调试模式,编译后会打印将要提交的数据,性能大幅降低,仅用于开发查问题。

  • 生成源码文档。

    # 安装Doxygen
    $ sudo apt install -y doxygen graphviz
    # 生成源码文档 生成的源码文档位于build/doc
    $ make doc
    

硬件要求

注解

由于节点多群组共享网络带宽、CPU和内存资源,因此为了保证服务的稳定性,一台机器上不推荐配置过多节点。

下表是单群组单节点推荐的配置,节点耗费资源与群组个数呈线性关系,您可根据实际的业务需求和机器资源,合理地配置节点数目。

配置 最低配置 推荐配置
CPU 1.5GHz 2.4GHz
内存 1GB 8GB
核心 1核 8核
带宽 1Mb 10Mb

支持的平台

  • CentOS 7.2+
  • Ubuntu 16.04
  • macOS 10.14+

开发部署工具

重要

开发部署工具 build_chain脚本目标是让用户最快的使用FISCO BCOS,对于企业级应用部署FISCO BCOS请参考 运维部署工具

FISCO BCOS提供了build_chain.sh脚本帮助用户快速搭建FISCO BCOS联盟链,该脚本默认从GitHub下载master分支最新版本预编译可执行程序进行相关环境的搭建。

脚本功能简介

  • build_chain.sh脚本用于快速生成一条链中节点的配置文件,脚本依赖于openssl请根据自己的操作系统安装openssl 1.0.2以上版本。脚本的源码位于FISCO-BCOS/tools/build_chain.sh
  • 快速体验可以使用-l选项指定节点IP和数目。-f选项通过使用一个指定格式的配置文件,支持创建各种复杂业务场景FISCO BCOS链。-l-f选项必须指定一个且不可共存
  • 建议测试时使用-T-T开启log级别到DEBUG,p2p模块默认监听 0.0.0.0

注解

为便于开发和体验,p2p模块默认监听IP是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如内网IP或特定的外网IP

帮助

Usage:
    -l <IP list>                        [Required] "ip1:nodeNum1,ip2:nodeNum2" e.g:"192.168.0.1:2,192.168.0.2:3"
    -f <IP list file>                   [Optional] split by line, every line should be "ip:nodeNum agencyName groupList p2p_port,channel_port,jsonrpc_port". eg "127.0.0.1:4 agency1 1,2 30300,20200,8545"
    -v <FISCO-BCOS binary version>      Default is the latest v${default_version}
    -e <FISCO-BCOS binary path>         Default download fisco-bcos from GitHub. If set -e, use the binary at the specified location
    -o <Output Dir>                     Default ./nodes/
    -p <Start Port>                     Default 30300,20200,8545 means p2p_port start from 30300, channel_port from 20200, jsonrpc_port from 8545
    -q <List FISCO-BCOS releases>       List FISCO-BCOS released versions
    -i <Host ip>                        Default 127.0.0.1. If set -i, listen 0.0.0.0
    -s <DB type>                        Default RocksDB. Options can be RocksDB / MySQL / Scalable, RocksDB is recommended
    -d <docker mode>                    Default off. If set -d, build with docker
    -c <Consensus Algorithm>            Default PBFT. Options can be pbft / raft /rpbft, pbft is recommended
    -C <Chain id>                       Default 1. Can set uint.
    -g <Generate guomi nodes>           Default no
    -z <Generate tar packet>            Default no
    -t <Cert config file>               Default auto generate
    -6 <Use ipv6>                       Default no. If set -6, treat IP as IPv6
    -k <The path of ca root>            Default auto generate, the ca.crt and ca.key must in the path, if use intermediate the root.crt must in the path
    -K <The path of sm crypto ca root>  Default auto generate, the gmca.crt and gmca.key must in the path, if use intermediate the gmroot.crt must in the path
    -D <Use Deployment mode>            Default false, If set -D, use deploy mode directory struct and make tar
    -G <channel use sm crypto ssl>      Default false, only works for guomi mode
    -X <Certificate expiration time>    Default 36500 days
    -T <Enable debug log>               Default off. If set -T, enable debug log
    -S <Enable statistics>              Default off. If set -S, enable statistics
    -F <Disable log auto flush>         Default on. If set -F, disable log auto flush
    -E <Enable free_storage_evm>        Default off. If set -E, enable free_storage_evm
    -h Help
e.g
    ./build_chain.sh -l "127.0.0.1:4"

选项介绍

l选项:

用于指定要生成的链的IP列表以及每个IP下的节点数,以逗号分隔。脚本根据输入的参数生成对应的节点配置文件,其中每个节点的端口号默认从30300开始递增,所有节点属于同一个机构和群组。

f选项
+ 用于根据配置文件生成节点,相比于`l`选项支持更多的定制。
+ 按行分割,每一行表示一个服务器,格式为`IP:NUM AgencyName GroupList`,每行内的项使用空格分割,**不可有空行**。
+ `IP:NUM`表示机器的IP地址以及该机器上的节点数。`AgencyName`表示机构名,用于指定使用的机构证书。`GroupList`表示该行生成的节点所属的组,以`,`分割。例如`192.168.0.1:2 agency1 1,2`表示`ip`为`192.168.0.1`的机器上有两个节点,这两个节点属于机构`agency1`,属于group1和group2。

下面是一个配置文件的例子,每个配置项以空格分隔。

192.168.0.1:1 agency1 1,2 30300,20200,8545
192.168.0.2:1 agency1 1,2 30300,20200,8545
192.168.0.3:2 agency1 1,3 30300,20200,8545
192.168.0.4:1 agency2 1   30300,20200,8545
192.168.0.5:1 agency3 2,3 30300,20200,8545
192.168.0.6:1 agency2 3   30300,20200,8545

假设上述文件名为ipconf,则使用下列命令建链,表示使用配置文件,设置日志级别为DEBUG

bash build_chain.sh -f ipconf -T
e选项[Optional]

用于指定fisco-bcos二进制所在的完整路径,脚本会将fisco-bcos拷贝以IP为名的目录下。不指定时,默认从GitHub下载最新的二进制程序。

# 从GitHub下载最新release二进制,生成本机4节点
$ bash build_chain.sh -l "127.0.0.1:4"
# 使用 bin/fisco-bcos 二进制,生成本机4节点
$ bash build_chain.sh -l "127.0.0.1:4" -e bin/fisco-bcos
o选项[Optional]

指定生成的配置所在的目录。

p选项[Optional]

指定节点的起始端口,每个节点占用三个端口,分别是p2p,channel,jsonrpc使用,分割端口,必须指定三个端口。同一个IP下的不同节点所使用端口从起始端口递增。

# 两个节点分别占用`30300,20200,8545`和`30301,20201,8546`。
$ bash build_chain.sh -l 127.0.0.1:2 -p 30300,20200,8545
q选项[Optional]

列出FISCO BCOS已经发布的所有版本号。

v选项[Optional]

用于指定搭建FISCO BCOS时使用的二进制版本。build_chain默认下载Release页面最新版本,设置该选项时下载参数指定version版本并设置config.ini配置文件中的[compatibility].supported_version=${version}。如果同时使用-e选项,则配置[compatibility].supported_version=${version}Release页面最新版本号。

d选项[Optional]

使用docker模式搭建FISCO BCOS,使用该选项时不再拉取二进制,但要求用户启动节点机器安装docker且账户有docker权限,即用户加入docker群组。 在节点目录下执行如下命令启动节点

./start.sh

该模式下 start.sh 脚本启动节点的命令如下

docker run -d --rm --name ${nodePath} -v ${nodePath}:/data --network=host -w=/data fiscoorg/fiscobcos:latest -c config.ini
s选项[Optional]

有参数选项,参数为db名,目前支持RocksDB、mysql、Scalable。默认使用rocks。

  • RocksDB模式,使用RocksDB作为后端数据库。
  • MySQL模式,使用MySQL作为后端数据库,节点直连MySQL,需要在群组ini文件中配置数据库相关信息。
  • Scalable模式,区块数据和状态数据存储在不同的数据库中,区块数据根据配置存储在以块高命名的RocksDB实例中。如需使用裁剪数据的功能,必须使用Scalable模式。
c选项[Optional]

有参数选项,参数为共识算法类型,目前支持PBFT、Raft、rPBFT。默认共识算法是PBFT。

  • PBFT:设置节点共识算法为PBFT
  • Raft:设置节点共识算法为Raft
  • rPBFT:设置节点共识算法为rPBFT
C选项[Optional]

用于指定搭建FISCO BCOS时的链标识。设置该选项时将使用参数设置config.ini配置文件中的[chain].id,参数范围为正整数,默认设置为1。

# 该链标识为2。
bash build_chain.sh -l 127.0.0.1:2 -C 2
g选项[Optional]

无参数选项,设置该选项时,搭建国密版本的FISCO BCOS。确认sdk支持的情况下(web3sdk v2.5.0+),可以指定-g -G参数,连接也使用国密SSL-G设置chain.sm_crypto_channel=true

bash build_chain.sh -l 127.0.0.1:2 -g -G
z选项[Optional]

无参数选项,设置该选项时,生成节点的tar包。

t选项[Optional]

该选项用于指定生成证书时的证书配置文件。

6选项[Optional]

该选项表示使用IPv6模式,监听::

T选项[Optional]

无参数选项,设置该选项时,设置节点的log级别为DEBUG。log相关配置参考这里

k选项[Optional]

使用用户指定的链证书和私钥签发机构和节点的证书,参数指定路径,路径下必须包括ca.crt/ca.key,如果所指定的私钥和证书是中间ca,那么此文件夹下还需要包括root.crt,用于存放上级证书链。

K选项[Optional]

国密模式使用用户指定的链证书和私钥签发机构和节点的证书,参数指定路径,路径下必须包括gmca.crt/gmca.key,如果所指定的私钥和证书是中间ca,那么此文件夹下还需要包括gmroot.crt,用于存放上级证书链。

G选项[Optional]

从2.5.0开始,国密模式下,用户可以配置节点与SDK连接是否使用国密SSL,设置此选项则chain.sm_crypto_channel=true。默认节点与SDK的channel连接使用secp256k1的证书。确认sdk支持的情况下(web3sdk v2.5.0+),可以指定-g -G参数,连接也使用国密SSL

D选项[Optional]

无参数选项,设置该选项时,生成节点的目录名为IP_P2P端口,默认为节点从0开始的编号。

E选项[Optional]

无参数选项,设置该选项时,启用Free Storage Gas模式,默认关闭Free Storage Gas模式

节点文件组织结构

  • cert文件夹下存放链的根证书和机构证书。
  • 以IP命名的文件夹下存储该服务器所有节点相关配置、fisco-bcos可执行程序、SDK所需的证书文件。
  • 每个IP文件夹下的node*文件夹下存储节点所需的配置文件。其中config.ini为节点的主配置,conf目录下存储证书文件和群组相关配置。配置文件详情,请参考这里。每个节点中还提供start.shstop.sh脚本,用于启动和停止节点。
  • 每个IP文件夹下的提供start_all.shstop_all.sh两个脚本用于启动和停止所有节点。
nodes/
├── 127.0.0.1
│   ├── fisco-bcos # 二进制程序
│   ├── node0 # 节点0文件夹
│   │   ├── conf # 配置文件夹
│   │   │   ├── ca.crt # 链根证书
│   │   │   ├── group.1.genesis # 群组1初始化配置,该文件不可更改
│   │   │   ├── group.1.ini # 群组1配置文件
│   │   │   ├── node.crt # 节点证书
│   │   │   ├── node.key # 节点私钥
│   │   │   ├── node.nodeid # 节点id,公钥的16进制表示
│   │   ├── config.ini # 节点主配置文件,配置监听IP、端口等
│   │   ├── start.sh # 启动脚本,用于启动节点
│   │   └── stop.sh # 停止脚本,用于停止节点
│   ├── node1 # 节点1文件夹
│   │.....
│   ├── node2 # 节点2文件夹
│   │.....
│   ├── node3 # 节点3文件夹
│   │.....
│   ├── sdk # SDK与节点SSL连接配置,FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错
│   │   ├── ca.crt # SSL连接根证书
│   │   ├── sdk.crt # SSL连接证书
│   │   └── sdk.key # SSL连接证书私钥
|   |   ├── gm # SDK与节点国密SSL连接配置,注意:生成国密区块链环境时才会生成该目录,用于节点与SDK的国密SSL连接
|   |   │   ├── gmca.crt # 国密SSL连接根证书
|   |   │   ├── gmensdk.crt # 国密SSL连接加密证书
|   |   │   ├── gmensdk.key # 国密SSL连接加密证书私钥
|   |   │   ├── gmsdk.crt # 国密SSL连接签名证书
|   |   │   └── gmsdk.key # 国密SSL连接签名证书私钥
├── cert # 证书文件夹
│   ├── agency # 机构证书文件夹
│   │   ├── agency.crt # 机构证书
│   │   ├── agency.key # 机构私钥
│   │   ├── agency.srl
│   │   ├── ca-agency.crt
│   │   ├── ca.crt
│   │   └── cert.cnf
│   ├── ca.crt # 链证书
│   ├── ca.key # 链私钥
│   ├── ca.srl
│   └── cert.cnf

工具脚本

介绍由build_chain.sh生成的脚本。

start_all.sh

启动当前目录下的所有节点。

stop_all.sh

停止当前目录下的所有节点。

download_console.sh

下载console的脚本。

  • v选项支持下载指定版本的console
  • f选项自动配置下载的console的证书和端口
download_bin.sh

用于下载fisco-bcos二进制程序,选项如下。

Usage:
    -v <Version>           Download binary of spectfic version, default latest
    -b <Branch>            Download binary of spectfic branch
    -o <Output Dir>        Default ./bin
    -l                     List FISCO-BCOS released versions
    -m                     Download mini binary, only works with -b option
    -h Help
e.g
    ./download_bin.sh -v 2.6.0

使用举例

无外网条件的单群组

最简单的操作方式是在有外网的Linux机器上使用build_chain建好链,借助-z选项打包,然后拷贝到无外网的机器上运行。

  1. 针对某下场景下无外网条件下建链,请从发布页面下载最新的目标操作系统的二进制,例如对于Linux系统下载fisco-bcos.tar.gz。
  2. 请从发布页面下载最新版本的build_chain脚本。
  3. 上传fisco-bcos.tar.gz和build_chain.sh到目标服务器,需要注意目标服务器要求64位,要求安装有openssl 1.0.2以上版本。
  4. 解压fisco-bcos.tar.gz得到fisco-bcos可执行文件,作为-e选项的参数。
  5. 构建本机上4节点的FISCO BCOS联盟链,使用默认起始端口30300,20200,8545(4个节点会占用30300-30303,20200-20203,8545-8548)。
  6. 执行下面的指令,假设最新版本是2.2.0,则将2.2.0作为-v选项参数。
# 构建FISCO BCOS联盟链
$ bash build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545 -e ./fisco-bcos -v 2.2.0
# 生成成功后,输出`All completed`提示
Generating CA key...
==============================================================
Generating keys ...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
Generating configurations...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
[INFO] FISCO-BCOS Path   : bin/fisco-bcos
[INFO] Start Port        : 30300 20200 8545
[INFO] Server IP         : 127.0.0.1:4
[INFO] State Type        : storage
[INFO] RPC listen IP     : 127.0.0.1
[INFO] Output Dir        : /Users/fisco/WorkSpace/FISCO-BCOS/tools/nodes
[INFO] CA Key Path       : /Users/fisco/WorkSpace/FISCO-BCOS/tools/nodes/cert/ca.key
==============================================================
[INFO] All completed. Files in /Users/fisco/WorkSpace/FISCO-BCOS/tools/nodes
群组新增节点

本节以为上一小节生成的群组1新增一个共识节点为例操作。

为新节点生成私钥证书

接下来的操作,都在上一节生成的nodes/127.0.0.1目录下进行

  1. 获取证书生成脚本
curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/gen_node_cert.sh

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/FISCO-BCOS/raw/master/tools/gen_node_cert.sh
  1. 生成新节点私钥证书
# -c指定机构证书及私钥所在路径
# -o输出到指定文件夹,其中newNode/conf中会存在机构agency新签发的证书和私钥
bash gen_node_cert.sh -c ../cert/agency -o newNode

国密版本请执行下面的指令生成证书。

bash gen_node_cert.sh -c ../cert/agency -o newNodeGm -g ../gmcert/agency/
准备配置文件
  1. 拷贝群组1中节点node0配置文件与工具脚本

    cp node0/config.ini newNode/config.ini
    cp node0/conf/group.1.genesis newNode/conf/group.1.genesis
    cp node0/conf/group.1.ini newNode/conf/group.1.ini
    cp node0/*.sh newNode/
    cp -r node0/scripts newNode/
    
  2. 更新newNode/config.ini中监听的IP和端口,对于[rpc]模块,修改listen_ipchannel_listen_portjsonrpc_listen_port;对于[p2p]模块,修改listen_port

  3. 将新节点的P2P配置中的IP和Port加入原有节点的config.ini中的[p2p]字段。假设新节点IP:Port为127.0.0.1:30304则,修改后的[P2P]配置为

注解

为便于开发和体验,p2p模块默认监听IP是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP

```bash
[p2p]
    listen_ip=0.0.0.0
    listen_port=30300
    ;enable_compress=true
    ; nodes to connect
    node.0=127.0.0.1:30300
    node.1=127.0.0.1:30301
    node.2=127.0.0.1:30302
    node.3=127.0.0.1:30303
    node.4=127.0.0.1:30304
```
  1. 启动新节点,执行newNode/start.sh
  2. 通过console将新节点加入群组1,请参考这里这里nodeID可以通过命令cat newNode/conf/node.nodeid来获取
  3. 检查连接和共识
为机构生成新的SDK证书

接下来的操作,都在上一节生成的nodes/127.0.0.1目录下进行

  1. 获取证书生成脚本
curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/gen_node_cert.sh

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/FISCO-BCOS/raw/master/tools/gen_node_cert.sh
  1. 生成新节点私钥证书
# -c指定机构证书及私钥所在路径
# -o输出到指定文件夹,其中newSDK中会存在机构agency新签发的证书和私钥
bash gen_node_cert.sh -c ../cert/agency -o newSDK -s

国密版本请执行下面的指令生成证书。

bash gen_node_cert.sh -c ../cert/agency -o newSDK -g ../gmcert/agency/ -s
生成新机构证书
  1. 获取机构证书生成脚本
curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/gen_agency_cert.sh
  1. 生成新机构私钥和证书
# -c 指定链证书及私钥所在路径,目录下必须有ca.crt 和 ca.key, 如果ca.crt是二级CA,则还需要root.crt(根证书)
# -g 指定国密链证书及私钥所在路径,目录下必须有gmca.crt 和 gmca.key,如果gmca.crt是二级CA,则还需要gmroot.crt(根证书)
# -a 新机构的机构名
bash gen_agency_cert.sh -c nodes/cert/ -a newAgencyName

国密版本请执行下面的指令。

bash gen_agency_cert.sh -c nodes/cert/ -a newAgencyName -g nodes/gmcert/
多服务器多群组

使用build_chain脚本构建多服务器多群组的FISCO BCOS联盟链需要借助脚本配置文件,详细使用方式可以参考这里

证书说明

FISCO BCOS网络采用面向CA的准入机制,支持任意多级的证书结构,保障信息保密性、认证性、完整性、不可抵赖性。

FISCO BCOS使用x509协议的证书格式,根据现有业务场景,默认采用三级的证书结构,自上而下分别为链证书、机构证书、节点证书。

在多群组架构中,一条链拥有一个链证书及对应的链私钥,链私钥由联盟链委员会共同管理。联盟链委员会可以使用机构的证书请求文件agency.csr,签发机构证书agency.crt

机构私钥由机构管理员持有,可以对机构下属节点签发节点证书。

节点证书是节点身份的凭证,用于与其他持有合法证书的节点间建立SSL连接,并进行加密通讯。

sdk证书是sdk与节点通信的凭证,机构生成sdk证书,允许sdk与节点进行通信。

FISCO BCOS节点运行时的文件后缀介绍如下:

后缀 说明
.key 私钥文件
.crt 证书文件
.csr 证书请求文件

使用第三方证书部署节点的说明,可以参考使用CFCA证书部署节点

角色定义

FISCO BCOS的证书结构中,共有四种角色,分别是联盟链委员会管理员、机构、节点和SDK。

联盟链委员会
  • 联盟链委员会管理链的私钥,并根据机构的证书请求文件agency.csr为机构颁发机构证书。
ca.crt 链证书
ca.key 链私钥

FISCO BCOS进行SSL加密通信时,拥有相同链证书ca.crt的节点才可建立连接。

机构
  • 机构管理员管理机构私钥,可以颁发节点证书和sdk证书。
ca.crt 链证书
agency.crt 机构证书
agency.csr 机构证书请求文件
agency.key 机构私钥
节点/SDK
  • FISCO BCOS节点包括节点证书和私钥,用于建立节点间SSL加密连接;
  • SDK包括SDK证书和私钥,用于与区块链节点建立SSL加密连接。
ca.crt #链证书
node.crt #节点证书
node.key #节点私钥
sdk.crt #SDK证书
sdk.key #SDK私钥

节点证书node.crt包括节点证书和机构证书信息,节点与其他节点/SDK通信验证时会用自己的私钥node.key对消息进行签名,并发送自己的node.crt至对方进行验证

证书生成流程

FISCO BCOS的证书生成流程如下,用户也可以使用企业部署工具生成相应证书

生成链证书
  • 联盟链委员会使用openssl命令请求链私钥ca.key,根据ca.key生成链证书ca.crt
生成机构证书
  • 机构使用openssl命令生成机构私钥agency.key
  • 机构使用机构私钥agency.key得到机构证书请求文件agency.csr,发送agency.csr给联盟链委员会
  • 联盟链委员会使用链私钥ca.key,根据得到机构证书请求文件agency.csr生成机构证书agency.crt,并将机构证书agency.crt发送给对应机构
生成节点/SDK证书
  • 节点生成私钥node.key和证书请求文件node.csr,机构管理员使用私钥agency.key和证书请求文件node.csr为节点颁发证书。同理,可用相同的方式为SDK生成证书

节点证书续期操作

完成证书续期前推荐使用证书检测脚本对证书进行检测。

当证书过期时,需要用户使用对当前节点私钥重新签发证书,操作如下:

假设用户证书过期的节点目录为~/mynode,节点目录如下:

mynode
├── conf
│   ├── ca.crt
│   ├── group.1.genesis
│   ├── group.1.ini
│   ├── node.crt #节点证书过期,需要替换
│   ├── node.key #节点私钥,证书续期需要使用
│   └── node.nodeid
├── config.ini
├── scripts
│   ├── load_new_groups.sh
│   └── reload_whitelist.sh
├── start.sh
└── stop.sh

设用户机构证书目录为~/myagency,目录如下:

agency
├── agency.crt #机构证书,证书续期需要使用
├── agency.key #机构私钥,证书续期需要使用
├── agency.srl
├── ca.crt
└── cert.cnf

续期操作如下:

  • 使用节点私钥生成证书请求文件,请将~/mynode/node/conf/node.key修改为你自己的节点私钥,将~/myagency/cert.cnf替换为自己的证书配置文件
openssl req -new -sha256 -subj "/CN=RenewalNode/O=fisco-bcos/OU=node" -key ~/mynode/node/conf/node.key -config ~/myagency/cert.cnf -out node.csr

操作完成后会在当前目录下生成证书请求文件node.csr

  • 查看证书请求文件
cat node.csr

操作完成后显示如下:

-----BEGIN CERTIFICATE REQUEST-----
MIIBGzCBwgIBADA6MRQwEgYDVQQDDAtSZW5ld2FsTm9kZTETMBEGA1UECgwKZmlz
Y28tYmNvczENMAsGA1UECwwEbm9kZTBWMBAGByqGSM49AgEGBSuBBAAKA0IABICU
KLP9GFRF6bBz+pfHCl1ifqzqrPiVoSPtwubXx+NRAI502EENMpnLqaXWm+OyadKz
PqUneVDQ6U+CvgY2IPygKTAnBgkqhkiG9w0BCQ4xGjAYMAkGA1UdEwQCMAAwCwYD
VR0PBAQDAgXgMAoGCCqGSM49BAMCA0gAMEUCIQDa8PzS1sCdk+rWgEsaOdvBnY+z
NDw6LU44WHCtrW6iNQIgY7Ne4EpAvPGmMOXalJsvYm2Xy6Bm9MlL7NEIP9Y0ai0=
-----END CERTIFICATE REQUEST-----
  • 使用机构私钥和机构证书对证书请求文件node.csr签发新证书,请将~/myagency/agency.key修改为你自己的机构私钥,请将~/myagency/agency.crt修改为你自己的机构证书
openssl x509 -req -days 3650 -sha256 -in node.csr -CAkey ~/myagency/agency.key -CA ~/myagency/agency.crt -out node.crt -CAcreateserial -extensions v3_req -extfile ~/myagency/cert.cnf

成功会有如下显示

Signature ok
subject=/CN=RenewalNode/O=fisco-bcos/OU=node
Getting CA Private Key

操作完成后会在当前目录下生成续期后的证书node.crt

  • 查看节点新证书
cat ./node.crt

操作完成后显示如下:

-----BEGIN CERTIFICATE-----
MIICQDCCASigAwIBAgIJALm++fKF6UmXMA0GCSqGSIb3DQEBCwUAMDcxDzANBgNV
BAMMBmFnZW5jeTETMBEGA1UECgwKZmlzY28tYmNvczEPMA0GA1UECwwGYWdlbmN5
MB4XDTE5MDkyNjEwMjEyNVoXDTI5MDkyMzEwMjEyNVowOjEUMBIGA1UEAwwLUmVu
ZXdhbE5vZGUxEzARBgNVBAoMCmZpc2NvLWJjb3MxDTALBgNVBAsMBG5vZGUwVjAQ
BgcqhkjOPQIBBgUrgQQACgNCAASAlCiz/RhURemwc/qXxwpdYn6s6qz4laEj7cLm
18fjUQCOdNhBDTKZy6ml1pvjsmnSsz6lJ3lQ0OlPgr4GNiD8oxowGDAJBgNVHRME
AjAAMAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQsFAAOCAQEAVvLUYeOJBfr1bbwp
E2H2QTb4phgcFGvrW5tqfvDvKaVGrSjJowZPKX+ruWFRQAZJBCc3/4M0Q1PYlWpB
R5a9Tpc7ebmUVltY7/GqASlDExdt2nqSvLxOKWgE++FveCdJzOEGuuttTZxjWFhQ
Yr9rPlKhzhEo2jM0lFIxdoCrG/WkcKmzJEyHdVwxLr2FOF9q9e9O9xyUkt2QRBGD
T4dIOeLRK6V1pnNkbBNRYG+tGMq2nBUPCAKJbV1LnhaNNRRbE5z7I4JkRnLHea6P
1VIiwnmbv9a3aM7lsnisPAz8PY5Ddmflo87UiL02J2UnQmq+gtAB9C9DUROGbSH5
Q6CXDA==
-----END CERTIFICATE-----
  • 将机构证书添加到节点证书末尾

由于fisco-bcos使用三级证书结构,需要将机构证书和节点证书合并

cat ~/myagency/agency.crt >> ./node.crt
  • 查看合并后的节点新证书
cat ./node.crt

操作完成后显示如下:

-----BEGIN CERTIFICATE-----
MIICQDCCASigAwIBAgIJALm++fKF6UmXMA0GCSqGSIb3DQEBCwUAMDcxDzANBgNV
BAMMBmFnZW5jeTETMBEGA1UECgwKZmlzY28tYmNvczEPMA0GA1UECwwGYWdlbmN5
MB4XDTE5MDkyNjEwMjEyNVoXDTI5MDkyMzEwMjEyNVowOjEUMBIGA1UEAwwLUmVu
ZXdhbE5vZGUxEzARBgNVBAoMCmZpc2NvLWJjb3MxDTALBgNVBAsMBG5vZGUwVjAQ
BgcqhkjOPQIBBgUrgQQACgNCAASAlCiz/RhURemwc/qXxwpdYn6s6qz4laEj7cLm
18fjUQCOdNhBDTKZy6ml1pvjsmnSsz6lJ3lQ0OlPgr4GNiD8oxowGDAJBgNVHRME
AjAAMAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQsFAAOCAQEAVvLUYeOJBfr1bbwp
E2H2QTb4phgcFGvrW5tqfvDvKaVGrSjJowZPKX+ruWFRQAZJBCc3/4M0Q1PYlWpB
R5a9Tpc7ebmUVltY7/GqASlDExdt2nqSvLxOKWgE++FveCdJzOEGuuttTZxjWFhQ
Yr9rPlKhzhEo2jM0lFIxdoCrG/WkcKmzJEyHdVwxLr2FOF9q9e9O9xyUkt2QRBGD
T4dIOeLRK6V1pnNkbBNRYG+tGMq2nBUPCAKJbV1LnhaNNRRbE5z7I4JkRnLHea6P
1VIiwnmbv9a3aM7lsnisPAz8PY5Ddmflo87UiL02J2UnQmq+gtAB9C9DUROGbSH5
Q6CXDA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIJAKK0/dNnUmlqMA0GCSqGSIb3DQEBCwUAMDUxDjAMBgNV
BAMMBWNoYWluMRMwEQYDVQQKDApmaXNjby1iY29zMQ4wDAYDVQQLDAVjaGFpbjAe
Fw0xOTA5MjYwOTU4NDFaFw0yOTA5MjMwOTU4NDFaMDcxDzANBgNVBAMMBmFnZW5j
eTETMBEGA1UECgwKZmlzY28tYmNvczEPMA0GA1UECwwGYWdlbmN5MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPqz154aXw4t+dcRl+aOz3X7yy0PUymm
DqMq3O7OeWXWYa8MWss5GBGWa2SL6puX/uryZJUUYcmSDwAo7Rsrf8zmbiHqouEC
liy01IqM+9jE7/IywRpRZO7W/QNrv9vRXxDJsr120vs760aMRKWD6UCd7bOQ/m/H
N8VC66r3cvcqey1q49idwOnhh5g80921MFlvu30Rire8kzckzUDr/SV3yt036tZs
D+9l/jHRc/tWo38nkiPy3DIm2oOlrNeJ4+IHnXOfxQxOwsiAeFluxtCq/ZFh4pTL
5lJZTo7bzRcORLOdz40svwDxJKyrMflhue0kGDC0WMExzzvx2oT14wIDAQABoxAw
DjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBlXrFIPQPlKosm2q/O
KktQA04Qh/y6w94Z4bHve0AqzTZn3/tf5q0e9C4f8F/Da+D+nV0GETLtEqRSHT+r
CCAAm78qN9oXmfkt3LvK/YXLNCVB6SSXw8fQx+bfDbIVRB5ivkG1+pmmnh3po1zU
zbrnfdSQi0ZV9MjIPsArjWwkE1i0GkXeiXov305iEX6J5pgu3AMe2RRMwyJiJ6ud
PRPCsF5BN6QrtMubwEnyvyrrX0/drBMtHLMCgecLd/nYMyJ4P15L6UnxC8taQSjM
rAtP3RZrBvBTwXKED0ge/hGIzrO9I1vjfCEuxV3DLlKfGVewuuboW2tYFWGfmrEX
MB7w
-----END CERTIFICATE-----
  • 将生成的节点证书node.crt替换至节点的conf文件夹下
cp -f ./node.crt ~/mynode/node/conf
  • 启动节点
bash ~/mynode/node/start.sh
  • 查看节点共识
tail -f ~/mynode/log/log*  | grep +++

正常情况会不停输出++++Generating seal,表示共识正常。

通过上述操作,完成了证书续期的操作。

四类证书续期简易流程

当整条链的证书均已过期时,需要重新对整条链的证书进行续期操作,续期证书的OpenSSL命令与节点续期操作基本相同,或查阅build_chain.sh脚本签发证书的操作,简要步骤如下:

  • 使用链私钥ca.key重新签发链证书ca.crt
  • 使用机构私钥agency.key生成证书请求文件agency.csr
  • 使用链私钥ca.key和链证书ca.crt对证书请求文件agency.csr签发得到机构证书agency.crt
  • 使用节点私钥node.key生成证书请求文件node.csr
  • 使用机构私钥agency.key和机构证书agency.crt对证书请求文件node.csr签发得到节点证书node.crt
  • 将节点证书和机构证书拼接得到node.crt,拼接操作可以参考节点证书续期操作
  • SDK证书sdk.crt签发步骤同节点证书签发
  • 使用新生成的链证书ca.crt,节点证书node.crt替换所有节点conf目录下的证书

使用CFCA证书部署节点

使用前建议阅读证书说明

购买前注意事项

  • 普通版FISCO BCOS节点使用的节点证书算法为EC secp256k1曲线
  • 国密版FISCO BCOS节点使用的节点证书算法为SM2
  • 用户向CFCA购买前请确认签发算法是否正确
  • 购买前请确认证书用途、节点信息已经填写正确
  • 使用前请确认已经安装openssl 1.0.2k以上版本
  • cfca申请的ca.crt--node.crt二级证书结构与fisco bcos自带工具生成的三级证书结构互相兼容,用户可直接使用cfca签发的二级证书作为节点或sdk的证书使用

建议用户结合白名单机制一起使用

本地生成证书请求文件

普通证书操作流程

根据CFCA的要求,生成节点私钥node.key和节点证书node.crt

生成的私钥会存储在node.key中,生成后私钥示例如下:

-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgZQE8JAJfs97BAj3mJbme
jSyNG+5kClhuAmXpZ1aI5VyhRANCAASLs7td5X1aDPLynH9HjruPLlovJYx1nIWu
E9mB8iTehZ++qd4b6YWXZoAizCgjvXIRIPEXOSkNaVSzJG7whmgb
-----END PRIVATE KEY-----

注意,节点私钥需要私密存储,部署区块链网络时需要用到私钥,每个节点都需要独立的私钥

将此步生成的证书请求文件node.csr发送给CFCA,得到PEM格式的证书node.crt,同时,CFCA会给你返回一个验证的根证书ca.crt

最终得到的节点证书node.crt示例如下:

-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIJALk+dh2WPTueMA0GCSqGSIb3DQEBCwUAMDUxDjAMBgNV
BAMMBWNoYWluMRMwEQYDVQQKDApmaXNjby1iY29zMQ4wDAYDVQQLDAVjaGFpbjAe
Fw0xOTExMDEwODM0MDZaFw0yOTEwMjkwODM0MDZaMDcxDzANBgNVBAMMBmFnZW5j
eTETMBEGA1UECgwKZmlzY28tYmNvczEPMA0GA1UECwwGYWdlbmN5MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4AkR1wfCodSiUraqb0zR/QLMGjpzuz5
r+s78Ao9G9oLq4q8NiGNlPOC+vrQZPtFlTzf8Ax+0DaC6L33JHIDTH6GCE/qDS0W
b1sBCZtB02opqHcKjzgRDlL7ITwS5o7wtEMm3bp1ade7rZbYavlBQzSQ4aBgwDt7
8YX8XkqDun4KYDL6EGPq7xDTKpkE81hCU1L4huXdo4HXO16JeabP8J7EEsIfdtUZ
prl/e+QmvgKs7HThjA61OT2spSAFFNh+q48ZbGuEUF2iRQA47wnk/H9zI5phMSt5
DqQNP4D1w5DM7poDsOhc8GpM7gDOmnhC5gmiomBSYiZRmB3jSUQWHwIDAQABoxAw
DjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA9GSvkYPHljWtKSpzV
RqszxyqfpxpH+X1z2LXHrNhE1mVSmcfU74rmu1dOlCOEJcOFTe/XTVUMgB14PWuh
Bpigh/pDMEP3eMLNlKuJhH2SxPmTJTKtAkAg2uu8Q6nr7UJA58ja9PA8clT4YNDO
XnI4r/cEF8qoNBw2Gna8+ENuqlD8IbLMt4JbX0zlmjIL6sJnfRZ8SuOqPANSvJiz
/6j8yys1QX7b66MkGHoUt6mnMzncmO0BCjXOu0DUC4kx57EKLX5jaB3Wm6jJogDE
hOGRKcNXMkct51ISRTJ7Yrn+mP0ELiACDOpM/dbNsqfyxwxQMcmZe2gNHgZcpI1L
C7/4
-----END CERTIFICATE-----

开始部署FISCO BCOS节点

部署前需要先获取所有节点的证书,并使用FISCO BCOS企业级部署工具,完成下载安装的操作,操作过程与文档类似。

本节中,我们将部署如下图所示的节点网络:

机器环境

每个节点的IP,端口号为如下:

机构 节点 所属群组 P2P地址 RPC/channel监听地址
机构A 节点0 群组1、2 127.0.0.1:30300 127.0.0.1:8545/:20200
节点1 群组1、2 127.0.0.1:30301 127.0.0.1:8546/:20201
节点2 群组1 127.0.0.1:30302 127.0.0.1:8547/:20202
节点3 群组1 127.0.0.1:30303 127.0.0.1:8548/:20203

重要

针对云服务器中的vps服务器,RPC监听地址需要写网卡中的真实地址(如内网地址或127.0.0.1),可能与用户登录的ssh服务器不一致。

  • 获取generator
cd ~/ && git clone https://github.com/FISCO-BCOS/generator.git
  • 安装依赖
cd ~/generator && bash ./scripts/install.sh
普通版部署教程

我们假设用户上述过程中生成了4个节点证书,分别放置为~/cert/node_127.0.0.1_30300,~/cert/node_127.0.0.1_30301,~/cert/node_127.0.0.1_30302,~/cert/node_127.0.0.1_30303路径下,每个目录包含以下文件

node_127.0.0.1_30300 # 节点证书存放文件夹,名称必须为node_ip_port
├── cert_127.0.0.1_30300.crt # 节点证书,名称必须为cert_ip_port.crt
├── node.key # 节点私钥,名称必须为node.key
  • 拷贝节点证书路径
cp -r ~/cert/node_127.0.0.1_30300 ./meta
cp -r ~/cert/node_127.0.0.1_30301 ./meta
cp -r ~/cert/node_127.0.0.1_30302 ./meta
cp -r ~/cert/node_127.0.0.1_30303 ./meta
  • 生成群组创世区块

将上一步所有节点的节点证书node.crt拷贝至meta文件夹下

cp ./meta/node_127.0.0.1_30300/cert_127.0.0.1_30300.crt ./meta
cp ./meta/node_127.0.0.1_30301/cert_127.0.0.1_30301.crt ./meta
cp ./meta/node_127.0.0.1_30302/cert_127.0.0.1_30302.crt ./meta
cp ./meta/node_127.0.0.1_30303/cert_127.0.0.1_30303.crt ./meta
  • 填写group_genesis.ini,教程中采用默认ip
  • 生成群组创世区块
./generator --create_group_genesis ./group

生成的group.1.genesis即为群组创世区块

  • 修改节点配置文件
```bash
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=2

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
p2p_listen_port=30301
channel_listen_port=20201
jsonrpc_listen_port=8546

[node2]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
p2p_listen_port=30302
channel_listen_port=20202
jsonrpc_listen_port=8547

[node3]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
p2p_listen_port=30303
channel_listen_port=20203
jsonrpc_listen_port=8548
EOF```
  • 生成节点
echo "" >> ./meta/peers.txt
./generator --build_install_package ./meta/peers.txt ./nodeA

查看生成节点配置文件夹:

ls ./nodeA
# 命令解释 此处采用tree风格显示
# 生成的文件夹nodeA信息如下所示,
├── monitor # monitor脚本
├── node_127.0.0.1_30300 # 127.0.0.1服务器 端口号30300的节点配置文件夹
├── node_127.0.0.1_30301
├── node_127.0.0.1_30302 # 127.0.0.1服务器 端口号30300的节点配置文件夹
├── node_127.0.0.1_30303
├── scripts # 节点的相关工具脚本
├── start_all.sh # 节点批量启动脚本
└── stop_all.sh # 节点批量停止脚本

机构A启动节点:

bash ./nodeA/start_all.sh

查看节点进程:

ps -ef | grep fisco

后续操作与使用企业级部署工具相同

配置文件与配置项

FISCO BCOS支持多账本,每条链包括多个独立账本,账本间数据相互隔离,群组间交易处理相互隔离,每个节点包括一个主配置config.ini和多个账本配置group.group_id.genesisgroup.group_id.ini

  • config.ini:主配置文件,主要配置RPC、P2P、SSL证书、账本配置文件路径、兼容性等信息。
  • group.group_id.genesis:群组配置文件,群组内所有节点一致,节点启动后,不可手动更改该配置。主要包括群组共识算法、存储类型、最大gas限制等配置项。
  • group.group_id.ini:群组可变配置文件,包括交易池大小等,配置后重启节点生效。

主配置文件config.ini

config.ini采用ini格式,主要包括 rpc、p2p、group、network_security和log 配置项。

重要

  • 云主机的公网IP均为虚拟IP,若listen_ip/jsonrpc_listen_ip/channel_listen_ip填写外网IP,会绑定失败,须填写0.0.0.0
  • RPC/P2P/Channel监听端口必须位于1024-65535范围内,且不能与机器上其他应用监听端口冲突
  • 为便于开发和体验,listen_ip/channel_listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP
配置RPC
  • channel_listen_ip: Channel监听IP,为方便节点和SDK跨机器部署,默认设置为0.0.0.0
  • jsonrpc_listen_ip:RPC监听IP,安全考虑,默认设置为127.0.0.1,若有外网访问需求,请监听节点外网IP0.0.0.0
  • channel_listen_port: Channel端口,对应到Web3SDK配置中的channel_listen_port
  • jsonrpc_listen_port: JSON-RPC端口。

注解

出于安全性和易用性考虑,v2.3.0版本最新配置将listen_ip拆分成jsonrpc_listen_ip和channel_listen_ip,但仍保留对listen_ip的解析功能:

  • 配置中仅包含listen_ip:RPC和Channel的监听IP均为配置的listen_ip
  • 配置中同时包含listen_ip、channel_listen_ip或jsonrpc_listen_ip:优先解析channel_listen_ip和jsonrpc_listen_ip,没有配置的配置项用listen_ip的值替代
  • v2.6.0 版本开始,RPC 支持 ipv4 和 ipv6

RPC配置示例如下:

# ipv4
[rpc]
    channel_listen_ip=0.0.0.0
    jsonrpc_listen_ip=127.0.0.1
    channel_listen_port=30301
    jsonrpc_listen_port=30302

# ipv6
[rpc]
    channel_listen_ip=::1
    jsonrpc_listen_ip=::1
    channel_listen_port=30301
    jsonrpc_listen_port=30302
配置P2P

当前版本FISCO BCOS必须在config.ini配置中配置连接节点的IPPort,P2P相关配置包括:

注解

  • 为便于开发和体验,listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP
  • v2.6.0 版本开始,P2P 支持 ipv4 和 ipv6
  • listen_ip:P2P监听IP,默认设置为0.0.0.0
  • listen_port:节点P2P监听端口。
  • node.*: 节点需连接的所有节点IP:PortDomainName:Port。该选项支持域名,但建议需要使用的用户手动编译源码
  • enable_compress:开启网络压缩的配置选项,配置为true,表明开启网络压缩功能,配置为false,表明关闭网络压缩功能,网络压缩详细介绍请参考这里
  • v2.6.0 版本开始,P2P 支持 ipv4 和 ipv6

P2P配置示例如下:

# ipv4
[p2p]
    listen_ip=0.0.0.0
    listen_port=30300
    node.0=127.0.0.1:30300
    node.1=127.0.0.1:30304
    node.2=127.0.0.1:30308
    node.3=127.0.0.1:30312

# ipv6
[p2p]
    listen_ip=::1
    listen_port=30300
    node.0=[::1]:30300
    node.1=[::1]:30304
    node.2=[::1]:30308
    node.3=[::1]:30312
配置账本文件路径

[group]配置本节点所属的所有群组配置路径:

  • group_data_path: 群组数据存储路径。
  • group_config_path: 群组配置文件路径。
节点根据group_config_path路径下的所有.genesis后缀文件启动群组。
[group]
    ; 所有群组数据放置于节点的data子目录
    group_data_path=data/
    ; 程序自动加载该路径下的所有.genesis文件
    group_config_path=conf/
配置证书信息

基于安全考虑,FISCO BCOS节点间采用SSL加密通信,[network_security]配置SSL连接的证书信息:

  • data_path:证书和私钥文件所在目录。
  • key: 节点私钥相对于data_path的路径。
  • cert: 证书node.crt相对于data_path的路径。
  • ca_cert: ca证书文件路径。
  • ca_path: ca证书文件夹,多ca时需要。
  • check_cert_issuer:设置SDK是否只能连本机构节点,默认为开启(check_cert_issuer=true)。
[network_security]
    data_path=conf/
    key=node.key
    cert=node.crt
    ca_cert=ca.crt
    ;ca_path=
配置黑名单列表

基于防作恶考虑,FISCO BCOS允许节点将不受信任的节点加入到黑名单列表,并拒绝与这些黑名单节点建立连接,通过[certificate_blacklist]配置:

crl.idx: 黑名单节点的Node ID, 节点Node ID可通过node.nodeid文件获取; idx是黑名单节点的索引。

黑名单的详细信息还可参考CA黑名单

黑名单列表配置示例如下:

; 证书黑名单
[certificate_blacklist]
    crl.0=4d9752efbb1de1253d1d463a934d34230398e787b3112805728525ed5b9d2ba29e4ad92c6fcde5156ede8baa5aca372a209f94dc8f283c8a4fa63e
3787c338a4
配置日志信息

FISCO BCOS支持功能强大的boostlog,日志配置主要位于config.ini[log]配置项中。

日志通用配置

FISCO BCOS通用日志配置项如下:

  • enable: 启用/禁用日志,设置为true表示启用日志;设置为false表示禁用日志,默认设置为true,性能测试可将该选项设置为false,降低打印日志对测试结果的影响
  • log_path:日志文件路径。
  • level: 日志级别,当前主要包括tracedebuginfowarningerror五种日志级别,设置某种日志级别后,日志文件中会输大于等于该级别的日志,日志级别从大到小排序error > warning > info > debug > trace
  • max_log_file_size:每个日志文件最大容量,计量单位为MB,默认为200MB
  • flush:boostlog默认开启日志自动刷新,若需提升系统性能,建议将该值设置为false。

boostlog示例配置如下:

[log]
    ; 是否启用日志,默认为true
    enable=true
    log_path=./log
    level=info
    ; 每个日志文件最大容量,默认为200MB
    max_log_file_size=200
    flush=true
统计日志配置

考虑到实时监控系统资源使用情况在实际生产系统中非常重要,FISCO BCOS v2.4.0引入了统计日志,统计日志配置项位于config.ini中。

配置统计日志开关

考虑到并非所有场景都需要网络流量和Gas统计功能,FISCO BCOS在config.ini中提供了enable_statistic选项来开启和关闭该功能,默认关闭该功能。

  • log.enable_statistic配置成true,开启网络流量和Gas统计功能
  • log.enable_statistic配置成false,关闭网络流量和Gas统计功能

配置示例如下:

[log]
    ; enable/disable the statistics function
    enable_statistic=false
配置网络统计日志输出间隔

由于网络统计日志周期性输出,引入了log.stat_flush_interval来控制统计间隔和日志输出频率,单位是秒,默认为60s,配置示例如下:

[log]
    ; network statistics interval, unit is second, default is 60s
    stat_flush_interval=60
配置链属性

可通过config.ini中的[chain]配置节点的链属性。此配置项建链时工具会自动生成,用户不需修改。

  • id,链ID,默认为1;
  • sm_crypto,2.5.0版本以后,节点支持以国密模式或非国密模式启动,true表示节点使用国密模式,false表示节点使用非国密模式,默认为false
  • sm_crypto_channel,2.5.0版本以后,节点支持与SDK连接使用国密SSL,此选项用于配置是否使用国密SSL与SDK连接,默认为false。
配置节点兼容性

FISCO BCOS 2.0+所有版本向前兼容,可通过config.ini中的[compatibility]配置节点的兼容性,此配置项建链时工具会自动生成,用户不需修改。

  • supported_version:当前节点运行的版本

重要

  • 可通过 `./fisco-bcos –version | grep “Version” ` 命令查看FISCO BCOS的当前支持的最高版本
  • build_chain.sh生成的区块链节点配置中,supported_version配置为FISCO BCOS当前的最高版本
  • 旧节点升级为新节点时,直接将旧的FISCO BCOS二进制替换为最新FISCO BCOS二进制即可,千万不可修改supported_version

FISCO BCOS 2.2.0节点的[compatibility]配置如下:

[compatibility]
    supported_version=2.2.0
可选配置:落盘加密

为了保障节点数据机密性,FISCO BCOS引入落盘加密保障节点数据的机密性,落盘加密操作手册请参考这里

config.ini中的storage_security用于配置落盘加密,主要包括:

  • enable: 是否开启落盘加密,默认不开启;
  • key_manager_ipKey Manager服务的部署IP;
  • key_manager_portKey Manager服务的监听端口;
  • cipher_data_key: 节点数据加密密钥的密文,cipher_data_key的产生参考落盘加密操作手册

落盘加密节点配置示例如下:

[storage_security]
enable=true
key_manager_ip=127.0.0.1
key_manager_port=31443
cipher_data_key=ed157f4588b86d61a2e1745efe71e6ea
可选配置:流量控制

为实现区块链系统柔性服务,防止多群组间资源相互影响,FISCO BCOS v2.5.0引入了流量控制功能,主要包括SDK到节点请求速率的限制以及节点间流量限制,配置项位于config.ini[flow_control],默认关闭,流控的详细设计请参考这里

SDK请求速率限制配置

SDK请求速率限制位于配置项[flow_control].limit_req中,用于限制SDK每秒到节点的最大请求数目,当每秒到节点的请求超过配置项的值时,请求会被拒绝,SDK请求速率限制默认关闭,若要开启该功能,需要将limit_req配置项前面的;去掉,打开SDK请求速率限制并设计节点每秒可接受2000个SDK请求的示例如下:

[flow_control]
    ; restrict QPS of the node
    limit_req=2000
节点间流量限制配置

为了防止区块同步、AMOP消息传输占用过多的网络流量,并影响共识模块的消息包传输,FISCO BCOS v2.5.0引入了节点间流量限制的功能,该配置项用于配置节点平均出带宽的上限,但不限制区块共识、交易同步的流量,当节点平均出带宽超过配置值时,会暂缓区块发送、AMOP消息传输。

  • [flow_control].outgoing_bandwidth_limit:节点出带宽限制,单位为Mbit/s,当节点出带宽超过该值时,会暂缓区块发送,也会拒绝客户端发送的AMOP请求,但不会限制区块共识和交易广播的流量,该配置项默认关闭,若要打开流量限制功能,请将outgoing_bandwidth_limit配置项前面的;去掉

打开节点出带宽流量限制,并将其设置为5MBit/s的配置示例如下:

[flow_control]
    ; Mb, can be a decimal
    ; when the outgoing bandwidth exceeds the limit, the block synchronization operation will not proceed
    outgoing_bandwidth_limit=5

群组系统配置说明

每个群组都有单独的配置文件,按照启动后是否可更改,可分为群组系统配置群组可变配置。 群组系统配置一般位于节点的conf目录下.genesis后缀配置文件中。

如:group1的系统配置一般命名为group.1.genesis,群组系统配置主要包括群组ID、共识、存储和gas相关的配置。

重要

配置系统配置时,需注意:

  • 配置群组内一致 :群组系统配置用于产生创世块(第0块),因此必须保证群组内所有节点的该配置一致
  • 节点启动后不可更改 :系统配置已经作为创世块写入了系统表,链初始化后不可更改
  • 链初始化后,即使更改了genesis配置,新的配置不会生效,系统仍然使用初始化链时的genesis配置
  • 由于genesis配置要求群组内所有节点一致,建议使用 开发部署工具 build_chain 生成该配置
群组配置

[group]配置群组ID,节点根据该ID初始化群组。

群组2的群组配置示例如下:

[group]
id=2
共识配置

[consensus]涉及共识相关配置,包括:

  • consensus_type:共识算法类型,目前支持PBFTRaftrPBFT,默认使用PBFT共识算法;
  • max_trans_num:一个区块可打包的最大交易数,默认是1000,链初始化后,可通过控制台动态调整该参数;
  • consensus_timeout:PBFT共识过程中,每个区块执行的超时时间,默认为3s,单位为秒,可通过控制台动态调整该参数;
  • node.idx:共识节点列表,配置了参与共识节点的Node ID,节点的Node ID可通过${data_path}/node.nodeid文件获取(其中${data_path}可通过主配置config.ini[network_security].data_path配置项获取)

FISCO BCOS v2.3.0引入了rPBFT共识算法,具体可参考这里,rPBFT相关配置如下:

  • epoch_sealer_num:一个共识周期内选择参与共识的节点数目,默认是所有共识节点总数,链初始化后可通过控制台动态调整该参数;
  • epoch_block_num:一个共识周期出块数目,默认为1000,可通过控制台动态调整该参数;

注解

rPBFT配置对其他共识算法不生效。

配置节点使用PBFT共识算法如下:

; 共识协议配置
[consensus]
    ; 共识算法,目前支持PBFT(consensus_type=pbft), Raft(consensus_type=raft)和rPBFT(consensus_type=rpbft)
    consensus_type=pbft
    ; 单个块最大交易数
    max_trans_num=1000
    ;共识过程中区块最长执行时间,默认为3秒
    consensus_timeout=3
    ; 一个共识周期内选取参与共识的节点数,rPBFT配置项,对其他共识算法不生效
    epoch_sealer_num=4
    ; 一个共识周期出块数,rPBFT配置项,对其他共识算法不生效
    epoch_block_num=1000
    ; leader节点的ID列表
    node.0=123d24a998b54b31f7602972b83d899b5176add03369395e53a5f60c303acb719ec0718ef1ed51feb7e9cf4836f266553df44a1cae5651bc6ddf50e01789233a
    node.1=70ee8e4bf85eccda9529a8daf5689410ff771ec72fc4322c431d67689efbd6fbd474cb7dc7435f63fa592b98f22b13b2ad3fb416d136878369eb413494db8776
    node.2=7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50
    node.3=fd6e0bfe509078e273c0b3e23639374f0552b512c2bea1b2d3743012b7fed8a9dec7b47c57090fa6dcc5341922c32b89611eb9d967dba5f5d07be74a5aed2b4a

配置节点开启rPBFT共识算法如下:

; 共识协议配置
[consensus]
    ; 共识算法,目前支持PBFT(consensus_type=pbft), Raft(consensus_type=raft)和rPBFT(consensus_type=rpbft)
    consensus_type=rpbft
    ; 单个块最大交易数
    max_trans_num=1000
    ; 一个共识周期内选取参与共识的节点数,rPBFT配置项,对其他共识算法不生效
    epoch_sealer_num=4
    ; 一个共识周期出块数,rPBFT配置项,对其他共识算法不生效
    epoch_block_num=1000
    ; leader节点的ID列表
    node.0=123d24a998b54b31f7602972b83d899b5176add03369395e53a5f60c303acb719ec0718ef1ed51feb7e9cf4836f266553df44a1cae5651bc6ddf50e01789233a
    node.1=70ee8e4bf85eccda9529a8daf5689410ff771ec72fc4322c431d67689efbd6fbd474cb7dc7435f63fa592b98f22b13b2ad3fb416d136878369eb413494db8776
    node.2=7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50
    node.3=fd6e0bfe509078e273c0b3e23639374f0552b512c2bea1b2d3743012b7fed8a9dec7b47c57090fa6dcc5341922c32b89611eb9d967dba5f5d07be74a5aed2b4a
状态模式配置

state用于存储区块链状态信息,位于genesis文件中[state]

  • type:state类型,目前支持storage stateMPT state默认为storage state,storage state将交易执行结果存储在系统表中,效率较高,MPT state将交易执行结果存储在MPT树中,效率较低,但包含完整的历史信息。

重要

推荐使用 storage state

[state]
    type=storage
gas配置

FISCO BCOS兼容以太坊虚拟机(EVM),为了防止针对EVM的DOS攻击,EVM在执行交易时,引入了gas概念,用来度量智能合约执行过程中消耗的计算和存储资源,包括交易最大gas限制和区块最大gas限制,若交易或区块执行消耗的gas超过限制(gas limit),则丢弃交易或区块。FISCO BCOS是联盟链,简化了gas设计,仅保留交易最大gas限制,区块最大gas通过共识配置的max_trans_num和交易最大gas限制一起约束。FISCO BCOS通过genesis的[tx].gas_limit来配置交易最大gas限制,默认是300000000,链初始化完毕后,可通过控制台指令动态调整gas限制。

[tx]
    gas_limit=300000000
EVM配置

FISCO BCOS v2.4.0引入Free Storage Gas衡量模式,提升CPU和内存在Gas消耗中的占比,详细可参考这里Free Storage Gas模式的开启和关闭通过genesis文件的evm.enable_free_storage配置项控制。

注解

  • evm.enable_free_storage v2.4.0开始支持,当 supported_version 小于v2.4.0,或者旧链直接替换二进制升级时,不支持该特性
  • 链初始化时,evm.enable_free_storage 写入创世块中;链初始化后,节点从创世块中读取 evm.enable_free_storage 配置项,手动修改 genesis 配置项不会生效
  • evm.enable_free_storage 默认设置为false
  • evm.enable_free_storage设置为true:开启Free Storage Gas模式
  • evm.enable_free_storage设置为false:关闭Free Storage Gas模式

配置示例如下:

[evm]
    enable_free_storage=false

账本可变配置说明

账本可变配置位于节点conf目录下.ini后缀的文件中。

如:group1可变配置一般命名为group.1.ini,可变配置主要包括交易池大小、PBFT共识消息转发的TTL、PBFT共识打包时间设置、PBFT交易打包动态调整设置、并行交易设置等。

配置storage

存储目前支持RocksDB、MySQL、External三种模式,用户可以根据需要选择使用的DB,其中RocksDB性能最高;MySQL支持用户使用MySQL数据库,方便数据的查看;External通过数据代理访问MySQL,用户需要在启动并配置数据代理。设计文档参考AMDB存储设计。RC3版本起我们使用RocksDB替代LevelDB以获得更好的性能表现,仍支持旧版本LevelDB。

注解

  • v2.3.0版本开始,为便于链的维护,推荐使用 MySQL 存储模式替代 External 存储模式
公共配置项

重要

推荐使用Mysql直连模式,配置type为MySQL。

  • type:存储的DB类型,支持RocksDBMySQLScalable,不区分大小写。DB类型为RocksDB时,区块链系统所有数据存储于RocksDB本地数据库中;type为MySQL时,节点根据配置访问MySQL数据库;type为Scalable时,需要设置binary_log=true,此时状态数据和区块数据分别存储在不同的RocksDB实例中,存储区块数据的RocksDB实例根据配置项scroll_threshold_multiple*1000切换实例,实例以存储的起始区块高度命名。
  • max_capacity:配置允许节点用于内存缓存的空间大小。
  • max_forward_block:配置允许节点用于内存区块的大小,当节点出的区块超出该数值时,节点停止共识等待区块写入数据库。
  • binary_log:当设置为true时打开binary_log,此时关闭RocksDB的WAL。
  • cached_storage:控制是否使用缓存,默认true
数据库相关配置项
  • topic:当type为External时,需要配置该字段,表示区块链系统关注的AMDB代理topic,详细请参考这里
  • max_retry:当type为External时,需要配置该字段,表示写入失败时的重试次数,详细请参考这里
  • scroll_threshold_multiple:当type为Scalable时,此配置项用于配置区块数据库的切换阈值,按scroll_threshold_multiple*1000。默认为2,区块数据按每2000块存储在不同的RocksDB实例中。
  • db_ip:当type为MySQL时,需要配置该字段,表示MySQL的IP地址。
  • db_port:当type为MySQL时,需要配置该字段,表示MySQL的端口号。
  • db_username:当type为MySQL时,需要配置该字段,表示MySQL的用户名。
  • db_passwd:当type为MySQL时,需要配置该字段,表示MySQL用户对应的密码。
  • db_name:当type为MySQL时,需要配置该字段,表示MySQL中使用的数据库名。
  • init_connections:当type为MySQL时,可选配置该字段,表示与MySQL建立的初始连接数,默认15。使用默认值即可。
  • max_connections:当type为MySQL时,可选配置该字段,表示与MySQL建立的最大连接数,默认20。使用默认值即可。
下面是[storage]的配置示例:
[storage]
    ; storage db type, RocksDB / MySQL / Scalable, RocksDB is recommended
    type=RocksDB
    max_capacity=256
    max_forward_block=10
    ; only for external
    max_retry=100
    topic=DB
    ; only for MySQL
    db_ip=127.0.0.1
    db_port=3306
    db_username=
    db_passwd=
    db_name=
交易池配置

FISCO BCOS将交易池容量配置开放给用户,用户可根据自己的业务规模需求、稳定性需求以及节点的硬件配置动态调整交易池配置。

交易池容量限制

为防止过多交易堆积在交易池内占用太多内存,FISCO BCOS提供了[tx_pool].limit[tx_pool].memory_limit两个配置项来限制交易池容量:

  • [tx_pool].limit: 限制交易池内可以容纳的最大交易数目,默认为150000,超过该限制后,客户端发到节点的交易会被拒绝。
  • [tx_pool].memory_limit: 交易池内交易占用的内存大小限制,默认为512MB,超过该限制后,客户端发到节点的交易会被拒绝。

交易池容量配置如下:

[tx_pool]
    limit=150000
    ; transaction pool memory size limit, MB
    memory_limit=512
交易池推送线程数配置

为提升区块链系统性能,FISCO BCOS采用了交易回执异步推送逻辑,当交易上链后,交易池内的推送线程会把交易上链的回执异步推送给客户端,为防止推送线程过多占用较多的系统资源,也为了防止推送线程过少影响交易推送的时效性,FISCO BCOS提供了[tx_pool].notify_worker_num配置项来配置异步推送线程数目:

  • [tx_pool].notify_worker_num:异步推送线程数目,默认为2,建议该值不超过8

交易池推送线程数配置如下:

[tx_pool]
    ; number of threads responsible for transaction notification,
    ; default is 2, not recommended for more than 8
    notify_worker_num=2
PBFT共识配置

为提升PBFT算法的性能、可用性、网络效率,FISCO BCOS针对区块打包算法和网络做了一系列优化,包括PBFT区块打包动态调整策略、PBFT消息转发优化、PBFT Prepare包结构优化等。

注解

因协议和算法一致性要求,建议保证所有节点PBFT共识配置一致。

PBFT共识消息转发配置

PBFT共识算法为了保证共识过程最大网络容错性,每个共识节点收到有效的共识消息后,会向其他节点广播该消息,在网络较好的环境下,共识消息转发机制会造成额外的网络带宽浪费,因此在群组可变配置项中引入了ttl来控制消息最大转发次数,消息最大转发次数为ttl-1该配置项仅对PBFT有效

设置共识消息最多转发一次,配置示例如下:

; the ttl for broadcasting pbft message
[consensus]
ttl=2
PBFT共识打包时间配置

考虑到PBFT模块打包太快会导致某些区块中仅打包1到2个很少的交易,浪费存储空间,FISCO BCOS v2.0.0-rc2在群组可变配置group.group_id.ini[consensus]下引入min_block_generation_time配置项来控制PBFT共识打包的最短时间,即:共识节点打包时间超过min_block_generation_time且打包的交易数大于0才会开始共识流程,处理打包生成的新区块。

重要

  • min_block_generation_time 默认为500ms
  • 共识节点最长打包时间为1000ms,若超过1000ms新区块中打包到的交易数仍为0,共识模块会进入出空块逻辑,空块并不落盘;
  • min_block_generation_time 不可超过出空块时间1000ms,若设置值超过1000ms,系统默认min_block_generation_time为500ms
[consensus]
;min block generation time(ms), the max block generation time is 1000 ms
min_block_generation_time=500
PBFT交易打包动态调整

考虑到CPU负载和网络延迟对系统处理能力的影响,PBFT提供了动态调整一个区块内可打包最大交易数的算法,该算法会根据历史交易处理情况动态调整区块内可打包的最大交易数,默认开启,也可通过将可变配置group.group_id.ini[consensus].enable_dynamic_block_size配置项修改为false来关闭该算法,此时区块内可打包的最大交易数为group.group_id.genesis[consensus].max_trans_num

关闭区块打包交易数动态调整算法的配置如下:

[consensus]
    enable_dynamic_block_size=false
PBFT消息转发配置

FISCO BCOS v2.2.0优化了PBFT消息转发机制,保证网络断连场景下PBFT消息包能尽量到达每个共识节点的同时,降低网络中冗余的PBFT消息包,PBFT消息转发优化策略请参考这里。可通过group.group_id.ini[consensus].enable_ttl_optimization配置项开启或关闭PBFT消息转发优化策略。

  • [consensus].enable_ttl_optimization配置为true:打开PBFT消息转发优化策略
  • [consensus].enable_ttl_optimization配置为false:关闭PBFT消息转发优化策略
  • supported_version不小于v2.2.0时,默认打开PBFT消息转发策略;supported_version小于v2.2.0时,默认关闭PBFT消息转发优化策略

关闭PBFT消息转发优化策略配置如下:

[consensus]
    enable_ttl_optimization=false
PBFT Prepare包结构优化

考虑到PBFT算法中,Leader广播的Prepare包内区块的交易有极大概率在其他共识节点的交易池中命中,为了节省网络带宽,FISCO BCOS v2.2.0优化了Prepare包结构:Prepare包内的区块仅包含交易哈希列表,其他共识节点收到Prepare包后,优先从本地交易池获取命中的交易,并向Leader请求缺失的交易,详细设计请参考这里。可通过group.group_id.ini[consensus].enable_prepare_with_txsHash配置项开启或关闭该策略。

  • [consensus].enable_prepare_with_txsHash配置为true:打开Prepare包结构优化,Prepare消息包内区块仅包含交易哈希列表
  • [consensus].enable_prepare_with_txsHash配置为false:关闭Prepare包结构优化,Prepare消息包内区块包含全量的交易
  • supported_version不小于v2.2.0时,[consensus].enable_prepare_with_txsHash默认为truesupported_version小于v2.2.0时,[consensus].enable_prepare_with_txsHash默认为false

注解

因协议一致性要求,须保证所有节点 enable_prepare_with_txsHash 配置一致

关闭PBFT Prepare包结构优化配置如下:

[consensus]
    enable_prepare_with_txsHash=false
rPBFT共识配置

FISCO BCOS v2.3.0引入rPBFT共识算法,具体可参考这里,为保证rPBFT算法网络流量负载均衡,引入了Prepare包树状广播策略以及该策略相对应的容错方案。

  • [consensus].broadcast_prepare_by_tree:Prepare包树状广播策略开启/关闭开关,设置为true,开启Prepare包树状广播策略;设置为false,关闭Prepare包树状广播策略,默认为true

下面为开启Prepare包树状广播策略后的容错配置:

  • [consensus].prepare_status_broadcast_percent:Prepare状态包随机广播的节点占共识节点总数的百分比,取值在25到100之间,默认为33
  • [consensus].max_request_prepare_waitTime:节点Prepare缓存缺失时,等待父节点发送Prepare包的最长时延,默认为100ms,超过这个时延后,节点会向其他拥有该Prepare包的节点请求

下面为rPBFT模式下开启Prepare包结构优化后,负载均衡相关配置:

  • [consensus].max_request_missedTxs_waitTime:节点Prepare包内交易缺失后,等待父节点或其他非leader节点同步Prepare包状态的最长时延,默认为100ms,若在等待时延窗口内同步到父节点或非leader节点Prepare包状态,则会随机选取一个节点请求缺失交易;若等待超时,直接向leader请求缺失交易。

rPBFT默认配置如下:

; 默认开启Prepare包树状广播策略
broadcast_prepare_by_tree=true
; 仅在开启prepare包树状广播时生效
; 每个节点随机选取33%共识节点同步prepare包状态
prepare_status_broadcast_percent=33
; prepare包树状广播策略下,缺失prepare包的节点超过100ms没等到父节点转发的prepare包,会向其他节点请求缺失的prepare包
max_request_prepare_waitTime=100
; 节点等待父节点或其他非leader节点同步prepare包最长时延为100ms
max_request_missedTxs_waitTime=100
同步配置

同步模块是”网络消耗大户”,包括区块同步和交易同步,FISCO BCOS秉着负载均衡的原则优化了共识模块网络使用效率。

注解

因协议一致性要求,建议保证所有节点PBFT共识配置一致。

区块同步优化配置

为了增强区块链系统在网络带宽受限情况下的可扩展性,FISCO BCOS v2.2.0对区块同步进行了优化,详细的优化策略请参考这里。可通过group.group_id.ini[sync].sync_block_by_tree开启或关闭区块同步优化策略。

  • [sync].sync_block_by_tree配置为true:打开区块同步优化策略
  • [sync].sync_block_by_tree配置为false:关闭区块同步优化策略
  • supported_version不小于v2.2.0时,[sync].sync_block_by_tree默认为truesupported_version小于v2.2.0时,[sync].sync_block_by_tree默认为false

此外,为了保障树状拓扑区块同步的健壮性,FISCO BCOS v2.2.0还引入了gossip协议定期同步区块状态,gossip协议相关配置项均位于group.group_id.ini[sync]中,具体如下:

  • gossip_interval_ms:gossip协议同步区块状态周期,默认为1000ms
  • gossip_peers_number:节点每次同步区块状态时,随机选取的邻居节点数目,默认为3

注解

  1. gossip协议配置项,仅在开启区块树状广播优化时生效
  2. 必须保证所有节点 sync_block_by_tree 配置一致

开启区块树状广播优化配置如下:

[sync]
    ; 默认开启区块树状同步策略
    sync_block_by_tree=true
    ; 每个节点每隔1000ms同步一次最新区块状态
    gossip_interval_ms=1000
    ; 每个节点每次随机选择3个邻居节点同步最新区块状态
    gossip_peers_number=3
交易树状广播优化配置

为了降低SDK直连节点的峰值出带宽,提升区块链系统可扩展性,FISCO BCOS v2.2.0引入了交易树状广播优化策略,详细设计请参考这里。可通过group.group_id.ini[sync].send_txs_by_tree开启或关闭交易树状广播策略,详细配置如下:

  • [sync].sync_block_by_tree:设置为true,打开交易树状广播策略;设置为false,关闭交易树状广播优化策略

关闭交易树状广播策略的配置如下:

[sync]
    ; 默认开启交易树状广播策略
    send_txs_by_tree=false

注解

  • 由于协议一致性需求,须保证所有节点交易树状广播开关`send_txs_by_tree`配置一致
  • supported_version 不小于v2.2.0时,默认打开交易树状广播优化策略; supported_version 小于v2.2.0时,默认关闭交易树状广播策略
交易转发优化配置

为降低交易转发导致的流量开销,FISCO BCOS v2.2.0引入基于状态包的交易转发策略,具体设计可参考这里。可通过group.group_id.ini[sync].txs_max_gossip_peers_num配置交易状态最多转发节点数目,默认为5。

注解

为保障交易到达每个节点的同时,尽量降低交易状态转发引入的流量开销,不建议将 txs_max_gossip_peers_num 设置太小或太大,直接使用默认配置即可

交易状态转发最大节点数配置如下:

[sync]
    ; 每个节点每轮最多随机选择5个邻居节点同步最新交易状态
    txs_max_gossip_peers_num=5
并行交易配置

FISCO BCOS支持交易的并行执行。开启交易并行执行开关,能够让区块内的交易被并行的执行,提高吞吐量,交易并行执行仅在storage state模式下生效

注解

为简化系统配置,v2.3.0去除了 enable_parallel 配置项,该配置项仅在 supported_version < v2.3.0 时生效,v2.3.0版本中:
  • storageState模式:开启并行交易
  • mptState模式: 关闭并行交易
[tx_execute]
    enable_parallel=true
可选配置:群组流量控制

为了防止多群组间资源相互影响,FISCO BCOS v2.5.0引入了流量控制功能,支持群组级别的SDK请求速率限制以及流量限制,配置位于group.{group_id}.ini[flow_control],默认关闭,流控的详细设计请参考这里

SDK到群组的请求速率限制配置

群组内的SDK请求速率限制位于配置项[flow_control].limit_req中,用于限制SDK每秒到群组的最大请求数目,当每秒到节点的请求超过配置项的值时,请求会被拒绝,SDK到群组请求速率限制默认关闭,若要开启该功能,需要将limit_req配置项前面的;去掉,打开SDK请求速率限制并配置群组每秒可接受1000个SDK请求的示例如下:

[flow_control]
    ; restrict QPS of the group
    limit_req=1000
群组间流量限制配置

为了防止区块同步占用过多的网络流量影响到共识模块的消息包传输,FISCO BCOS v2.5.0引入了群组级流量限制的功能,其配置了群组平均出带宽的上限,但不限制区块共识、交易同步的流量,当群组平均出带宽超过配置值时,会暂缓区块发送。

  • [flow_control].outgoing_bandwidth_limit:群组出带宽限制,单位为Mbit/s,当群组出带宽超过该值时,会暂缓发送区块,但不会限制区块共识和交易广播的流量,该配置项默认关闭,若要打开流量限制功能,请将outgoing_bandwidth_limit配置项前面的;去掉

打开群组出带宽流量限制,并将其设置为2MBit/s的配置示例如下:

[flow_control]
    ; Mb, can be a decimal
    ; when the outgoing bandwidth exceeds the limit, the block synchronization operation will not proceed
    outgoing_bandwidth_limit=2
可选配置:SDK白名单配置

为了实现sdk到群组的访问控制,FISCO BCOS v2.6.0引入了群组级的SDK白名单访问控制机制,配置位于group.{group_id}.ini[sdk_allowlist],默认关闭,群组级别SDK白名单机制请参考这里

重要

FISCO BCOS v2.6.0默认关闭SDK到群组的白名单访问控制功能,即默认情况下sdk与所有群组均可通信,若要开启sdk与群组间基于白名单的访问控制功能,需要将 ;public_key.0 等配置项前面的分号去掉

  • public_key.0public_key.1、…、public_key.i:配置允许与该群组进行通信的SDK公钥公钥列表。

SDK白名单配置示例如下:

[sdk_allowlist]
; When sdk_allowlist is empty, all SDKs can connect to this node
; when sdk_allowlist is not empty, only the SDK in the allowlist can connect to this node
; public_key.0 should be nodeid, nodeid's length is 128
public_key.0=b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f3

动态配置系统参数

FISCO BCOS系统目前主要包括如下系统参数(未来会扩展其他系统参数):

系统参数 默认值 含义
tx_count_limit 1000 一个区块中可打包的最大交易数目
tx_gas_limit 300000000 一个交易最大gas限制
rpbft_epoch_sealer_num 链共识节点总数 rPBFT系统配置,一个共识周期内选取参与共识的节点数目,rPBFT每个共识周期都会动态切换参与共识的节点数目
rpbft_epoch_block_num 1000 rPBFT系统配置,一个共识周期内出块数目
consensus_timeout 3 PBFT共识过程中,区块执行的超时时间,最少为3s, supported_version>=v2.6.0时,配置项生效

控制台提供 setSystemConfigByKey 命令来修改这些系统参数,getSystemConfigByKey 命令可查看系统参数的当前值:

重要

不建议随意修改tx_count_limit和tx_gas_limit,如下情况可修改这些参数:

  • 机器网络或CPU等硬件性能有限:调小tx_count_limit,或降低业务压力;
  • 业务逻辑太复杂,执行交易时gas不足:调大tx_gas_limit。

rpbft_epoch_sealer_numrpbft_epoch_block_num 仅对rPBFT共识算法生效,为了保障共识性能,不建议频繁动态切换共识列表,即不建议 rpbft_epoch_block_num 配置值太小

# 设置一个区块可打包最大交易数为500
[group:1]> setSystemConfigByKey tx_count_limit 500
# 查询tx_count_limit
[group:1]> getSystemConfigByKey tx_count_limit
[500]

# 设置交易gas限制为400000000
[group:1]> setSystemConfigByKey tx_gas_limit 400000000
[group:1]> getSystemConfigByKey tx_gas_limit
[400000000]

# rPBFT共识算法下,设置一个共识周期选取参与共识的节点数目为4
[group:1]> setSystemConfigByKey rpbft_epoch_sealer_num 4
Note: rpbft_epoch_sealer_num only takes effect when rPBFT is used
{
    "code":0,
    "msg":"success"
}
# 查询rpbft_epoch_sealer_num
[group:1]> getSystemConfigByKey rpbft_epoch_sealer_num
Note: rpbft_epoch_sealer_num only takes effect when rPBFT is used
4

# rPBFT共识算法下,设置一个共识周期出块数目为10000
[group:1]> setSystemConfigByKey rpbft_epoch_block_num 10000
Note: rpbft_epoch_block_num only takes effect when rPBFT is used
{
    "code":0,
    "msg":"success"
}
# 查询rpbft_epoch_block_num
[group:1]> getSystemConfigByKey rpbft_epoch_block_num
Note: rpbft_epoch_block_num only takes effect when rPBFT is used
10000
# 获取区块执行超时时间
[group:1]> getSystemConfigByKey consensus_timeout
3

# 设置区块执行超时时间为5s
[group:1]> setSystemConfigByKey consensus_timeout 5
{
    "code":0,
    "msg":"success"
}

多群组部署

本章主要以星形组网和并行多组组网拓扑为例,指导您了解如下内容:

  • 了解如何使用build_chain.sh创建多群组区块链安装包;
  • 了解build_chain.sh创建的多群组区块链安装包目录组织形式;
  • 学习如何启动该区块链节点,并通过日志查看各群组共识状态;
  • 学习如何向各群组发送交易,并通过日志查看群组出块状态;
  • 了解群组内节点管理,包括节点入网、退网等;
  • 了解如何新建群组。

重要

  • build_chain.sh适用于开发者和体验者快速搭链使用
  • 搭建企业级业务链,推荐使用 企业搭链工具

星形拓扑和并行多组

如下图,星形组网拓扑和并行多组组网拓扑是区块链应用中使用较广泛的两种组网方式。

  • 星形拓扑:中心机构节点同时属于多个群组,运行多家机构应用,其他每家机构属于不同群组,运行各自应用;
  • 并行多组:区块链中每个节点均属于多个群组,可用于多方不同业务的横向扩展,或者同一业务的纵向扩展。

_images/group.png

下面以构建八节点星形拓扑和四节点并行多组区块链为例,详细介绍多群组操作方法。

安装依赖

部署FISCO BCOS区块链节点前,需安装openssl, curl等依赖软件,具体命令如下:

# CentOS
$ sudo yum install -y openssl curl

# Ubuntu
$ sudo apt install -y openssl curl

# Mac OS
$ brew install openssl curl

星形拓扑

本章以构建上图所示的单机、四机构、三群组、八节点的星形组网拓扑为例,介绍多群组使用方法。

星形区块链组网如下:

  • agencyA:在127.0.0.1上有2个节点,同时属于group1、group2、group3
  • agencyB:在127.0.0.1上有2个节点,属于group1
  • agencyC:在127.0.0.1上有2个节点,属于group2
  • agencyD:在127.0.0.1上有2个节点,属于group3

重要

  • 实际应用场景中,不建议将多个节点部署在同一台机器,建议根据 机器负载 选择部署节点数目,请参考 硬件配置
  • 星形网络拓扑 中,核心节点(本例中agencyA节点)属于所有群组,负载较高,建议单独部署于性能较好的机器
  • 在不同机器操作时,请将生成的对应IP的文件夹拷贝到对应机器启动,建链操作只需要执行一次!
构建星形区块链节点配置文件夹

build_chain.sh支持任意拓扑多群组区块链构建,可使用该脚本构建星形拓扑区块链节点配置文件夹:

准备依赖

  • 创建操作目录
mkdir -p ~/fisco && cd ~/fisco
  • 获取build_chain.sh脚本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.6.0/build_chain.sh && chmod u+x build_chain.sh

生成星形区块链系统配置文件

# 生成区块链配置文件ip_list
$ cat > ipconf << EOF
127.0.0.1:2 agencyA 1,2,3
127.0.0.1:2 agencyB 1
127.0.0.1:2 agencyC 2
127.0.0.1:2 agencyD 3
EOF

# 查看配置文件ip_list内容
$ cat ipconf
# 空格分隔的参数分别表示如下含义:
# ip:num: 物理机IP以及物理机上的节点数目
# agency_name: 机构名称
# group_list: 节点所属的群组列表,不同群组以逗号分隔
127.0.0.1:2 agencyA 1,2,3
127.0.0.1:2 agencyB 1
127.0.0.1:2 agencyC 2
127.0.0.1:2 agencyD 3

使用build_chain脚本构建星形区块链节点配置文件夹

build_chain更多参数说明请参考这里

# 根据配置生成星形区块链 需要保证机器的30300~30301,20200~20201,8545~8546端口没有被占用
$ bash build_chain.sh -f ipconf -p 30300,20200,8545
Generating CA key...
==============================================================
Generating keys ...
Processing IP:127.0.0.1 Total:2 Agency:agencyA Groups:1,2,3
Processing IP:127.0.0.1 Total:2 Agency:agencyB Groups:1
Processing IP:127.0.0.1 Total:2 Agency:agencyC Groups:2
Processing IP:127.0.0.1 Total:2 Agency:agencyD Groups:3
==============================================================
......此处省略其他输出......
==============================================================
[INFO] FISCO-BCOS Path   : ./bin/fisco-bcos
[INFO] IP List File      : ipconf
[INFO] Start Port        : 30300 20200 8545
[INFO] Server IP         : 127.0.0.1:2 127.0.0.1:2 127.0.0.1:2 127.0.0.1:2
[INFO] State Type        : storage
[INFO] RPC listen IP     : 127.0.0.1
[INFO] Output Dir        : /home/ubuntu16/fisco/nodes
[INFO] CA Key Path       : /home/ubuntu16/fisco/nodes/cert/ca.key
==============================================================
[INFO] All completed. Files in /home/ubuntu16/fisco/nodes

# 生成的节点文件如下
nodes
|-- 127.0.0.1
|   |-- fisco-bcos
|   |-- node0
|   |   |-- conf  #节点配置目录
|   |   |   |-- ca.crt
|   |   |   |-- group.1.genesis
|   |   |   |-- group.1.ini
|   |   |   |-- group.2.genesis
|   |   |   |-- group.2.ini
|   |   |   |-- group.3.genesis
|   |   |   |-- group.3.ini
|   |   |   |-- node.crt
|   |   |   |-- node.key
|   |   |   `-- node.nodeid # 记录节点Node ID信息
|   |   |-- config.ini #节点配置文件
|   |   |-- start.sh  #节点启动脚本
|   |   `-- stop.sh   #节点停止脚本
|   |-- node1
|   |   |-- conf
......此处省略其他输出......

注解

若生成的区块链节点属于不同物理机,需要将区块链节点拷贝到相应的物理机

启动节点

节点提供start_all.shstop_all.sh脚本启动和停止节点。

# 进入节点目录
$ cd ~/fisco/nodes/127.0.0.1

# 启动节点
$ bash start_all.sh

# 查看节点进程
$ ps aux | grep fisco-bcos
ubuntu16         301  0.8  0.0 986644  7452 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node5/../fisco-bcos -c config.ini
ubuntu16         306  0.9  0.0 986644  6928 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node6/../fisco-bcos -c config.ini
ubuntu16         311  0.9  0.0 986644  7184 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node7/../fisco-bcos -c config.ini
ubuntu16      131048  2.1  0.0 1429036 7452 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node0/../fisco-bcos -c config.ini
ubuntu16      131053  2.1  0.0 1429032 7180 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node1/../fisco-bcos -c config.ini
ubuntu16      131058  0.8  0.0 986644  7928 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node2/../fisco-bcos -c config.ini
ubuntu16      131063  0.8  0.0 986644  7452 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node3/../fisco-bcos -c config.ini
ubuntu16      131068  0.8  0.0 986644  7672 pts/0    Sl   15:21   0:00 /home/ubuntu16/fisco/nodes/127.0.0.1/node4/../fisco-bcos -c config.ini

查看群组共识状态

不发交易时,共识正常的节点会输出+++日志,本例中,node0node1同时属于group1group2group3node2node3属于group1node4node5属于group2node6node7属于group3,可通过tail -f node*/log/* | grep "++"查看各节点是否正常。

重要

节点正常共识打印 +++ 日志, +++ 日志字段含义:
  • g::群组ID
  • blkNum:Leader节点产生的新区块高度;
  • tx: 新区块中包含的交易数目;
  • nodeIdx: 本节点索引;
  • hash: 共识节点产生的最新区块哈希。
# 查看node0 group1是否正常共识(Ctrl+c退回命令行)
$ tail -f node0/log/* | grep "g:1.*++"
info|2019-02-11 15:33:09.914042| [g:1][p:264][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=72254a42....

# 查看node0 group2是否正常共识
$ tail -f node0/log/* | grep "g:2.*++"
info|2019-02-11 15:33:31.021697| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=3,hash=ef59cf17...

# ... 查看node1, node2节点每个群组是否正常可参考以上操作方法...

# 查看node3 group1是否正常共识
$ tail -f node3/log/*| grep "g:1.*++"
info|2019-02-11 15:39:43.927167| [g:1][p:264][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=3,hash=5e94bf63...

# 查看node5 group2是否正常共识
$ tail -f node5/log/* | grep "g:2.*++"
info|2019-02-11 15:39:42.922510| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=b80a724d...
配置控制台

控制台通过Web3SDK链接FISCO BCOS节点,实现查询区块链状态、部署调用合约等功能,能够快速获取到所需要的信息。控制台指令详细介绍参考这里

重要

控制台依赖于Java 8以上版本,Ubuntu 16.04系统安装openjdk 8即可。CentOS请安装Oracle Java 8以上版本。 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh && bash download_console.sh

#回到fisco目录
$ cd ~/fisco

# 获取控制台
$ curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v1.1.0/download_console.sh && bash download_console.sh

# 进入控制台操作目录
$ cd console

# 拷贝group2节点证书到控制台配置目录
$ cp ~/fisco/nodes/127.0.0.1/sdk/* conf/

# 获取node0的channel_listen_port
$ grep "channel_listen_port" ~/fisco/nodes/127.0.0.1/node*/config.ini
/home/ubuntu16/fisco/nodes/127.0.0.1/node0/config.ini:    channel_listen_port=20200
/home/ubuntu16/fisco/nodes/127.0.0.1/node1/config.ini:    channel_listen_port=20201
/home/ubuntu16/fisco/nodes/127.0.0.1/node2/config.ini:    channel_listen_port=20202
/home/ubuntu16/fisco/nodes/127.0.0.1/node3/config.ini:    channel_listen_port=20203
/home/ubuntu16/fisco/nodes/127.0.0.1/node4/config.ini:    channel_listen_port=20204
/home/ubuntu16/fisco/nodes/127.0.0.1/node5/config.ini:    channel_listen_port=20205
/home/ubuntu16/fisco/nodes/127.0.0.1/node6/config.ini:    channel_listen_port=20206
/home/ubuntu16/fisco/nodes/127.0.0.1/node7/config.ini:    channel_listen_port=20207

重要

使用控制台连接节点时,控制台连接的节点必须在控制台配置的组中

创建控制台配置文件conf/applicationContext.xml的配置如下,控制台从node0(127.0.0.1:20200)分别接入三个group中,控制台配置方法请参考这里

cat > ./conf/applicationContext.xml << EOF
<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
           xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
         http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


        <bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
                <constructor-arg value="0"/> <!-- 0:standard 1:guomi -->
        </bean>

      <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
        <property name="allChannelConnections">
        <list>
            <bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                <property name="groupId" value="1" />
                    <property name="connectionsStr">
                    <list>
                    <value>127.0.0.1:20200</value>
                    </list>
                    </property>
            </bean>
            <bean id="group2"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                <property name="groupId" value="2" />
                    <property name="connectionsStr">
                    <list>
                    <value>127.0.0.1:20200</value>
                    </list>
                    </property>
            </bean>
            <bean id="group3"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                <property name="groupId" value="3" />
                    <property name="connectionsStr">
                    <list>
                    <value>127.0.0.1:20200</value>
                    </list>
                    </property>
            </bean>
        </list>
        </property>
        </bean>

        <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
                <property name="groupId" value="1" />
                <property name="orgID" value="fisco" />
                <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
        </bean>
</beans>
EOF

启动控制台

$ bash start.sh
# 输出下述信息表明启动成功 否则请检查conf/applicationContext.xml中节点端口配置是否正确
=====================================================================================
Welcome to FISCO BCOS console(1.0.3)!
Type 'help' or 'h' for help. Type 'quit' or 'q' to quit console.
 ________ ______  ______   ______   ______       _______   ______   ______   ______
|        |      \/      \ /      \ /      \     |       \ /      \ /      \ /      \
| $$$$$$$$\$$$$$|  $$$$$$|  $$$$$$|  $$$$$$\    | $$$$$$$|  $$$$$$|  $$$$$$|  $$$$$$\
| $$__     | $$ | $$___\$| $$   \$| $$  | $$    | $$__/ $| $$   \$| $$  | $| $$___\$$
| $$  \    | $$  \$$    \| $$     | $$  | $$    | $$    $| $$     | $$  | $$\$$    \
| $$$$$    | $$  _\$$$$$$| $$   __| $$  | $$    | $$$$$$$| $$   __| $$  | $$_\$$$$$$\
| $$      _| $$_|  \__| $| $$__/  | $$__/ $$    | $$__/ $| $$__/  | $$__/ $|  \__| $$
| $$     |   $$ \\$$    $$\$$    $$\$$    $$    | $$    $$\$$    $$\$$    $$\$$    $$
 \$$      \$$$$$$ \$$$$$$  \$$$$$$  \$$$$$$      \$$$$$$$  \$$$$$$  \$$$$$$  \$$$$$$

=====================================================================================
[group:1]>
向群组发交易

上节配置了控制台,本节通过控制台向各群组发交易。

重要

多群组架构中,群组间账本相互独立,向某个群组发交易仅会导致本群组区块高度增加,不会增加其他群组区块高度

控制台发送交易

# ... 向group1发交易...
$ [group:1]> deploy HelloWorld
contract address:0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744
# 查看group1当前块高,块高增加为1表明出块正常,否则请检查group1是否共识正常
$ [group:1]> getBlockNumber
1

# ... 向group2发交易...
# 切换到group2
$ [group:1]> switch 2
Switched to group 2.
# 向group2发交易,返回交易哈希表明交易部署成功,否则请检查group2是否共识正常
$ [group:2]> deploy HelloWorld
contract address:0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744
# 查看group2当前块高,块高增加为1表明出块正常,否则请检查group2是否共识正常
$ [group:2]> getBlockNumber
1

# ... 向group3发交易...
# 切换到group3
$ [group:2]> switch 3
Switched to group 3.
# 向group3发交易,返回交易哈希表明交易部署成功
$ [group:3]> deploy HelloWorld
contract address:0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744
# 查看group3当前块高,块高为1表明出块正常,否则请检查group3是否共识正常
$ [group:3]> getBlockNumber
1

# ... 切换到不存在的组4,控制台提示group4不存在,并输出当前的group列表 ...
$ [group:3]> switch 4
Group 4 does not exist. The group list is [1, 2, 3].

# 退出控制台
$ [group:3]> exit

查看日志

节点出块后,会输出Report日志,日志各个字段含义如下:

重要

节点每出一个新块,会打印一条Report日志,Report日志中各字段含义如下:
  • g::群组ID
  • num:出块高度;
  • sealerIdx:共识节点索引;
  • hash:区块哈希;
  • next:下一个区块高度;
  • tx:区块包含的交易数;
  • nodeIdx:当前节点索引。
# 进入节点目录
$ cd ~/fisco/nodes/127.0.0.1

# 查看group1出块情况:有新区块产生
$ cat node0/log/* |grep "g:1.*Report"
info|2019-02-11 16:08:45.077484| [g:1][p:264][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=1,hash=9b5487a6...,next=2,tx=1,nodeIdx=2

# 查看group2出块情况:有新区块产生
$ cat node0/log/* |grep "g:2.*Report"
info|2019-02-11 16:11:55.354881| [g:2][p:520][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=0,hash=434b6e07...,next=2,tx=1,nodeIdx=0

# 查看group3出块情况:有新区块产生
$ cat node0/log/* |grep "g:3.*Report"
info|2019-02-11 16:14:33.930978| [g:3][p:776][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=1,hash=3a42fcd1...,next=2,tx=1,nodeIdx=2
节点加入群组

通过控制台,FISCO BCOS可将指定节点加入到指定群组,也可将节点从指定群组删除,详细介绍请参考节点准入管理手册,控制台配置参考控制台操作手册

本章以将node2加入group2为例,介绍如何在已有的群组中,加入新节点。

重要

新节点加入群组前,请确保:

  • 新加入NodeID存在
  • 群组内节点正常共识:正常共识的节点会输出+++日志

拷贝group2群组配置到node2

# 进入节点目录
$ cd ~/fisco/nodes/127.0.0.1

# ... 从node0拷贝group2的配置到node2...
$ cp node0/conf/group.2.* node2/conf

# ...重启node2(重启后请确定节点正常共识)...
$ cd node2 && bash stop.sh && bash start.sh

获取node2的节点ID

# 请记住node2的node ID,将node2加入到group2需用到该node ID
$ cat conf/node.nodeid
6dc585319e4cf7d73ede73819c6966ea4bed74aadbbcba1bbb777132f63d355965c3502bed7a04425d99cdcfb7694a1c133079e6d9b0ab080e3b874882b95ff4

通过控制台向group2发送命令,将node2加入到group2

# ...回到控制台目录,并启动控制台(直接启动到group2)...
$ cd ~/fisco/console && bash start.sh 2

# ...通过控制台将node2加入为共识节点...
# 1. 查看当前共识节点列表
$ [group:2]> getSealerList
[
    9217e87c6b76184cf70a5a77930ad5886ea68aefbcce1909bdb799e45b520baa53d5bb9a5edddeab94751df179d54d41e6e5b83c338af0a19c0611200b830442,
    227c600c2e52d8ec37aa9f8de8db016ddc1c8a30bb77ec7608b99ee2233480d4c06337d2461e24c26617b6fd53acfa6124ca23a8aa98cb090a675f9b40a9b106,
    7a50b646fcd9ac7dd0b87299f79ccaa2a4b3af875bd0947221ba6dec1c1ba4add7f7f690c95cf3e796296cf4adc989f4c7ae7c8a37f4505229922fb6df13bb9e,
    8b2c4204982d2a2937261e648c20fe80d256dfb47bda27b420e76697897b0b0ebb42c140b4e8bf0f27dfee64c946039739467b073cf60d923a12c4f96d1c7da6
]
# 2. 将node2加入到共识节点
# addSealer后面的参数是上步获取的node ID
$ [group:2]> addSealer 6dc585319e4cf7d73ede73819c6966ea4bed74aadbbcba1bbb777132f63d355965c3502bed7a04425d99cdcfb7694a1c133079e6d9b0ab080e3b874882b95ff4
{
    "code":0,
    "msg":"success"
}
# 3. 查看共识节点列表
$ [group:2]> getSealerList
[
    9217e87c6b76184cf70a5a77930ad5886ea68aefbcce1909bdb799e45b520baa53d5bb9a5edddeab94751df179d54d41e6e5b83c338af0a19c0611200b830442,
    227c600c2e52d8ec37aa9f8de8db016ddc1c8a30bb77ec7608b99ee2233480d4c06337d2461e24c26617b6fd53acfa6124ca23a8aa98cb090a675f9b40a9b106,
    7a50b646fcd9ac7dd0b87299f79ccaa2a4b3af875bd0947221ba6dec1c1ba4add7f7f690c95cf3e796296cf4adc989f4c7ae7c8a37f4505229922fb6df13bb9e,
    8b2c4204982d2a2937261e648c20fe80d256dfb47bda27b420e76697897b0b0ebb42c140b4e8bf0f27dfee64c946039739467b073cf60d923a12c4f96d1c7da6,
    6dc585319e4cf7d73ede73819c6966ea4bed74aadbbcba1bbb777132f63d355965c3502bed7a04425d99cdcfb7694a1c133079e6d9b0ab080e3b874882b95ff4 # 新加入节点
]
# 获取group2当前块高
$ [group:2]> getBlockNumber
2

#... 向group2发交易
# 部署HelloWorld合约,输出合约地址,若合约部署失败,请检查group2共识情况
$ [group:2] deploy HelloWorld
contract address:0xdfdd3ada340d7346c40254600ae4bb7a6cd8e660

# 获取group2当前块高,块高增加为3,若块高不变,请检查group2共识情况
$ [group:2]> getBlockNumber
3

# 退出控制台
$ [group:2]> exit

通过日志查看新加入节点出块情况

# 进入节点所在目录
cd ~/fisco/nodes/127.0.0.1
# 查看节点共识情况(Ctrl+c退回命令行)
$ tail -f node2/log/* | grep "g:2.*++"
info|2019-02-11 18:41:31.625599| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=4,tx=0,nodeIdx=1,hash=c8a1ed9c...
......此处省略其他输出......

# 查看node2 group2出块情况:有新区块产生
$ cat node2/log/* | grep "g:2.*Report"
info|2019-02-11 18:53:20.708366| [g:2][p:520][CONSENSUS][PBFT]^^^^^Report:,num=3,idx=3,hash=80c98d31...,next=10,tx=1,nodeIdx=1
# node2也Report了块高为3的区块,说明node2已经加入group2
停止节点
# 回到节点目录 && 停止节点
$ cd ~/fisco/nodes/127.0.0.1 && bash stop_all.sh

并行多组

并行多组区块链搭建方法与星形拓扑区块链搭建方法类似,以搭建四节点两群组并行多链系统为例:

  • 群组1:包括四个节点,节点IP均为127.0.0.1
  • 群组2:包括四个节点,节点IP均为127.0.0.1

重要

  • 真实应用场景中,不建议将多个节点部署在同一台机器 ,建议根据 机器负载 选择部署节点数目
  • 为演示并行多组扩容流程,这里仅先创建group1
  • 并行多组场景中,节点加入和退出群组操作与星形组网拓扑类似
构建单群组四节点区块链
用build_chain.sh脚本生成单群组四节点区块链节点配置文件夹
$ mkdir -p ~/fisco && cd ~/fisco
# 获取build_chain.sh脚本
$ curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.6.0/build_chain.sh && chmod u+x build_chain.sh
# 构建本机单群组四节点区块链(生产环境中,建议每个节点部署在不同物理机上)
$ bash build_chain.sh -l "127.0.0.1:4" -o multi_nodes -p 20000,20100,7545
Generating CA key...
==============================================================
Generating keys ...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
Generating configurations...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
[INFO] FISCO-BCOS Path   : bin/fisco-bcos
[INFO] Start Port        : 20000 20100 7545
[INFO] Server IP         : 127.0.0.1:4
[INFO] State Type        : storage
[INFO] RPC listen IP     : 127.0.0.1
[INFO] Output Dir        : /home/ubuntu16/fisco/multi_nodes
[INFO] CA Key Path       : /home/ubuntu16/fisco/multi_nodes/cert/ca.key
==============================================================
[INFO] All completed. Files in /home/ubuntu16/fisco/multi_nodes
启动所有节点
# 进入节点目录
$ cd ~/fisco/multi_nodes/127.0.0.1
$ bash start_all.sh

# 查看进程情况
$ ps aux | grep fisco-bcos
ubuntu16       55028  0.9  0.0 986384  6624 pts/2    Sl   20:59   0:00 /home/ubuntu16/fisco/multi_nodes/127.0.0.1/node0/../fisco-bcos -c config.ini
ubuntu16       55034  0.8  0.0 986104  6872 pts/2    Sl   20:59   0:00 /home/ubuntu16/fisco/multi_nodes/127.0.0.1/node1/../fisco-bcos -c config.ini
ubuntu16       55041  0.8  0.0 986384  6584 pts/2    Sl   20:59   0:00 /home/ubuntu16/fisco/multi_nodes/127.0.0.1/node2/../fisco-bcos -c config.ini
ubuntu16       55047  0.8  0.0 986396  6656 pts/2    Sl   20:59   0:00 /home/ubuntu16/fisco/multi_nodes/127.0.0.1/node3/../fisco-bcos -c config.ini
查看节点共识情况
# 查看node0共识情况(Ctrl+c退回命令行)
$ tail -f node0/log/* | grep "g:1.*++"
info|2019-02-11 20:59:52.065958| [g:1][p:264][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=da72649e...

# 查看node1共识情况
$ tail -f node1/log/* | grep "g:1.*++"
info|2019-02-11 20:59:54.070297| [g:1][p:264][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=0,hash=11c9354d...

# 查看node2共识情况
$ tail -f node2/log/* | grep "g:1.*++"
info|2019-02-11 20:59:55.073124| [g:1][p:264][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=1,hash=b65cbac8...

# 查看node3共识情况
$ tail -f node3/log/* | grep "g:1.*++"
info|2019-02-11 20:59:53.067702| [g:1][p:264][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=3,hash=0467e5c4...
将group2加入区块链

并行多组区块链每个群组的genesis配置文件几乎相同,但[group].id不同,为群组号。

# 进入节点目录
$ cd ~/fisco/multi_nodes/127.0.0.1

# 拷贝group1的配置
$ cp node0/conf/group.1.genesis node0/conf/group.2.genesis
$ cp node0/conf/group.1.ini node0/conf/group.2.ini

# 修改群组ID
$ sed -i "s/id=1/id=2/g"  node0/conf/group.2.genesis
$ cat node0/conf/group.2.genesis | grep "id"
# 已修改到    id=2

# 更新group.2.genesis文件中的共识节点列表,剔除已废弃的共识节点。

# 将配置拷贝到各个节点
$ cp node0/conf/group.2.genesis node1/conf/group.2.genesis
$ cp node0/conf/group.2.genesis node2/conf/group.2.genesis
$ cp node0/conf/group.2.genesis node3/conf/group.2.genesis
$ cp node0/conf/group.2.ini node1/conf/group.2.ini
$ cp node0/conf/group.2.ini node2/conf/group.2.ini
$ cp node0/conf/group.2.ini node3/conf/group.2.ini

# 重启各个节点
$ bash stop_all.sh
$ bash start_all.sh
查看群组共识情况
# 查看node0 group2共识情况(Ctrl+c退回命令行)
$ tail -f node0/log/* | grep "g:2.*++"
info|2019-02-11 21:13:28.541596| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=f3562664...

# 查看node1 group2共识情况
$ tail -f node1/log/* | grep "g:2.*++"
info|2019-02-11 21:13:30.546011| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=0,hash=4b17e74f...

# 查看node2 group2共识情况
$ tail -f node2/log/* | grep "g:2.*++"
info|2019-02-11 21:13:59.653615| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=1,hash=90cbd225...

# 查看node3 group2共识情况
$ tail -f node3/log/* | grep "g:2.*++"
info|2019-02-11 21:14:01.657428| [g:2][p:520][CONSENSUS][SEALER]++++++++Generating seal on,blkNum=1,tx=0,nodeIdx=3,hash=d7dcb462...
向群组发交易

获取控制台

# 若从未下载控制台,请进行下面操作下载控制台,否则将控制台拷贝到~/fisco目录:
$ cd ~/fisco
# 获取控制台
$ curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v1.1.0/download_console.sh && bash download_console.sh

配置控制台

# 获取channel_port
$ grep "channel_listen_port" multi_nodes/127.0.0.1/node0/config.ini
multi_nodes/127.0.0.1/node0/config.ini:    channel_listen_port=20100

# 进入控制台目录
$ cd console
# 拷贝节点证书
$ cp ~/fisco/multi_nodes/127.0.0.1/sdk/* conf

创建控制台配置文件conf/applicationContext.xml的配置如下,在node0(127.0.0.1:20100)上配置了两个group(group1和group2):

cat > ./conf/applicationContext.xml << EOF
<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
           xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
         http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


        <bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
                <constructor-arg value="0"/> <!-- 0:standard 1:guomi -->
        </bean>

      <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
        <property name="allChannelConnections">
        <list>
            <bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                <property name="groupId" value="1" />
                    <property name="connectionsStr">
                    <list>
                    <value>127.0.0.1:20100</value>
                    </list>
                    </property>
            </bean>
            <bean id="group2"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                <property name="groupId" value="2" />
                    <property name="connectionsStr">
                    <list>
                    <value>127.0.0.1:20100</value>
                    </list>
                    </property>
            </bean>
        </list>
        </property>
        </bean>

        <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
                <property name="groupId" value="1" />
                <property name="orgID" value="fisco" />
                <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
        </bean>
</beans>
EOF

通过控制台向群组发交易

# ... 启动控制台 ...
$ bash start.sh
# 输出如下信息表明控制台启动成功,若启动失败,请检查是否配置证书、channel listen port配置是否正确
=====================================================================================
Welcome to FISCO BCOS console(1.0.3)!
Type 'help' or 'h' for help. Type 'quit' or 'q' to quit console.
 ________ ______  ______   ______   ______       _______   ______   ______   ______
|        |      \/      \ /      \ /      \     |       \ /      \ /      \ /      \
| $$$$$$$$\$$$$$|  $$$$$$|  $$$$$$|  $$$$$$\    | $$$$$$$|  $$$$$$|  $$$$$$|  $$$$$$\
| $$__     | $$ | $$___\$| $$   \$| $$  | $$    | $$__/ $| $$   \$| $$  | $| $$___\$$
| $$  \    | $$  \$$    \| $$     | $$  | $$    | $$    $| $$     | $$  | $$\$$    \
| $$$$$    | $$  _\$$$$$$| $$   __| $$  | $$    | $$$$$$$| $$   __| $$  | $$_\$$$$$$\
| $$      _| $$_|  \__| $| $$__/  | $$__/ $$    | $$__/ $| $$__/  | $$__/ $|  \__| $$
| $$     |   $$ \\$$    $$\$$    $$\$$    $$    | $$    $$\$$    $$\$$    $$\$$    $$
 \$$      \$$$$$$ \$$$$$$  \$$$$$$  \$$$$$$      \$$$$$$$  \$$$$$$  \$$$$$$  \$$$$$$

=====================================================================================
# ... 向group1发交易...
# 获取当前块高
$ [group:1]> getBlockNumber
0
# 向group1部署HelloWorld合约,若部署失败,请检查group1共识是否正常
$ [group:1]> deploy HelloWorld
contract address:0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744
# 获取当前块高,若块高没有增加,请检查group1共识是否正常
$ [group:1]> getBlockNumber
1

# ... 向group2发交易...
# 切换到group2
$ [group:1]> switch 2
Switched to group 2.
# 获取当前块高
$ [group:2]> getBlockNumber
0
# 向group2部署HelloWorld合约
$ [group:2]> deploy HelloWorld
contract address:0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744
# 获取当前块高,若块高没有增加,请检查group2共识是否正常
$ [group:2]> getBlockNumber
1
# 退出控制台
$[group:2]> exit

通过日志查看节点出块状态

# 切换到节点目录
$ cd ~/fisco/multi_nodes/127.0.0.1/

# 查看group1出块情况,看到Report了属于group1的块高为1的块
$ cat node0/log/* | grep "g:1.*Report"
info|2019-02-11 21:14:57.216548| [g:1][p:264][CONSENSUS][PBFT]^^^^^Report:,num=1,sealerIdx=3,hash=be961c98...,next=2,tx=1,nodeIdx=2

# 查看group2出块情况,看到Report了属于group2的块高为1的块
$ cat node0/log/* | grep "g:2.*Report"
info|2019-02-11 21:15:25.310565| [g:2][p:520][CONSENSUS][PBFT]^^^^^Report:,num=1,sealerIdx=3,hash=5d006230...,next=2,tx=1,nodeIdx=2
停止节点
# 回到节点目录 && 停止节点
$ cd ~/fisco/multi_nodes/127.0.0.1 && bash stop_all.sh

分布式存储

安装MySQL

当前支持的分布式数据库是MySQL,在使用分布式存储之前,需要先搭建MySQL服务,在Ubuntu和CentOS服务器上的配置方式如下:

Ubuntu:执行下面三条命令,安装过程中,配置 root 账户密码。

sudo apt install -y mysql-server mysql-client libmysqlclient-dev

启动 MySQL 服务并登陆: root 账户密码。

service msyql start
mysql -uroot -p

CentOS: 执行下面两条命令进行安装。

yum install mysql*
#某些版本的linux,需要安装mariadb,mariadb是mysql的一个分支
yum install mariadb*

启动 MySQL 服务,登陆并为 root 用户设置密码。

service mysqld start
#若安装了mariadb,则使用下面的命令启动
service mariadb start
mysql -uroot -p
mysql> set password for root@localhost = password('123456');

配置MySQL参数

查看配置文件my.cnf
mysql --help | grep 'Default options' -A 1

执行之后可以看到如下如下数据

Default options are read from the following files in the given order:
/etc/mysql/my.cnf /etc/my.cnf ~/.my.cnf
配置my.cnf

mysql依次从/etc/mysql/my.cnf,/etc/my.cnf,~/.my.cnf中加载配置。依次查找这几个文件,找到第一个存在的文件,在[mysqld]段中新增如下内容(如果存在则修改值)。

max_allowed_packet = 1024M
sql_mode =STRICT_TRANS_TABLES
重启mysql-sever,验证参数。

Ubuntu:执行如下命令重启

service mysql restart

CentOS:执行如下命令重启

service mysqld start
#若安装了mariadb,则使用下面的命令启动
service mariadb start

验证参数过程

mysql -uroot -p
#执行下面命令,查看max_allowed_packet的值
MariaDB [(none)]>  show variables like 'max_allowed_packet%';
+--------------------+------------+
| Variable_name      | Value      |
+--------------------+------------+
| max_allowed_packet | 1073741824 |
+--------------------+------------+
1 row in set (0.00 sec)

#执行下面命令,查看sql_mode的值
MariaDB [(none)]>  show variables like 'sql_mode%';
+---------------+---------------------+
| Variable_name | Value               |
+---------------+---------------------+
| sql_mode      | STRICT_TRANS_TABLES |
+---------------+---------------------+
1 row in set (0.00 sec)

节点直连MySQL

FISCO BCOS在2.0.0-rc3之后,支持节点通过连接池直连MySQL,相对于代理访问MySQL方式,配置简单,不需要手动创建数据库。配置方法请参考:

逻辑架构图

多群组架构是指区块链节点支持启动多个群组,群组间交易处理、数据存储、区块共识相互隔离的。因此群组下的每一个节点对应一个数据库实例,例如,区块链网络中,有三个节点A,B,C,其中A,B属于Group1,B,C属于Group2。节点A和C分别对应1个数据库实例,B节点对应了2个数据库实例,逻辑架构图如下 _images/storage1.png 如上图所示,节点B属于多个群组,不同群组下的同一个节点,对应的数据库实例是分开的,为了区分不同群组下的同一个节点,将A,B,C三个节点,分别用Group1_A(Group1下的A节点,下同),Group1_B,Group2_B,Group2_C表示。

下面以上图为例,描述搭建配置过程。

节点搭建

使用分布式存储之前,需要完成联盟链的搭建和多群组的配置,具体参考如下步骤。

准备依赖
mkdir -p ~/fisco && cd ~/fisco
# 获取build_chain.sh脚本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.6.0/build_chain.sh && chmod u+x build_chain.sh
生成配置文件
# 生成区块链配置文件ipconf
cat > ipconf << EOF
127.0.0.1:1 agencyA 1
127.0.0.1:1 agencyB 1,2
127.0.0.1:1 agencyC 2
EOF

# 查看配置文件
cat ipconf
127.0.0.1:1 agencyA 1
127.0.0.1:1 agencyB 1,2
127.0.0.1:1 agencyC 2
使用build_chain搭建区块链
### 搭建区块链(请先确认30300~30302,20200~20202,8545~8547端口没有被占用)
### 这里区别是在命令后面追加了参数"-s MySQL" 以及换了端口。
bash build_chain.sh -f ipconf -p 30300,20200,8545 -s MySQL
==============================================================
Generating CA key...
==============================================================
Generating keys ...
Processing IP:127.0.0.1 Total:1 Agency:agencyA Groups:1
Processing IP:127.0.0.1 Total:1 Agency:agencyB Groups:1,2
Processing IP:127.0.0.1 Total:1 Agency:agencyC Groups:2
==============================================================
Generating configurations...
Processing IP:127.0.0.1 Total:1 Agency:agencyA Groups:1
Processing IP:127.0.0.1 Total:1 Agency:agencyB Groups:1,2
Processing IP:127.0.0.1 Total:1 Agency:agencyC Groups:2
==============================================================
Group:1 has 2 nodes
Group:2 has 2 nodes
修改节点ini文件

group.[群组].ini配置文件中,和本特性相关的是MySQL的配置信息。假设MySQL的配置信息如下:

|节点|db_ip|db_port|db_username|db_passwd|db_name|
|Group1_A|127.0.0.1|3306|root|123456|db_Group1_A|
|Group1_B|127.0.0.1|3306|root|123456|db_Group1_B|
|Group2_B|127.0.0.1|3306|root|123456|db_Group2_B|
|Group2_C|127.0.0.1|3306|root|123456|db_Group2_C|
修改node0下的group.1.ini配置

修改~/fisco/nodes/127.0.0.1/node0/conf/group.1.ini[storage]段的内容,配置如下内容。db_passwd为对应的密码。

    	db_ip=127.0.0.1
    	db_port=3306
    	db_username=root
    	db_name=db_Group1_A
    	db_passwd=
修改node1下的group.1.ini配置

修改~/fisco/nodes/127.0.0.1/node0/conf/group.1.ini[storage]段的内容,新增如下内容。db_passwd为对应的密码。

    	db_ip=127.0.0.1
    	db_port=3306
    	db_username=root
    	db_name=db_Group1_B
    	db_passwd=
修改node1下的group.2.ini配置

修改~/fisco/nodes/127.0.0.1/node1/conf/group.2.ini[storage]段的内容,新增如下内容。db_passwd为对应的密码。

    	db_ip=127.0.0.1
    	db_port=3306
    	db_username=root
    	db_name=db_Group2_B
    	db_passwd=
修改node2下的group.2.ini配置

修改~/fisco/nodes/127.0.0.1/node2/conf/group.2.ini[storage]段的内容,新增如下内容。db_passwd为对应的密码。

    	db_ip=127.0.0.1
    	db_port=3306
    	db_username=root
    	db_name=db_Group2_C
    	db_passwd=
启动节点
cd ~/fisco/nodes/127.0.0.1;sh start_all.sh
检查进程
ps -ef|grep fisco-bcos|grep -v grep
fisco   111061      1  0 16:22 pts/0    00:00:04 /data/home/fisco/nodes/127.0.0.1/node2/../fisco-bcos -c config.ini
fisco   111065      1  0 16:22 pts/0    00:00:04 /data/home/fisco/nodes/127.0.0.1/node0/../fisco-bcos -c config.ini
fisco   122910      1  1 16:22 pts/0    00:00:02 /data/home/fisco/nodes/127.0.0.1/node1/../fisco-bcos -c config.ini

启动成功,3个fisco-bcos进程。不成功的话请参考日志确认配置是否正确。

检查日志输出

执行下面指令,查看节点node0链接的节点数(其他节点类似)

tail -f nodes/127.0.0.1/node0/log/log*  | grep connected

正常情况会看到类似下面的输出,从输出可以看出node0与另外2个节点有连接。

info|2019-05-28 16:28:57.267770|[P2P][Service] heartBeat,connected count=2
info|2019-05-28 16:29:07.267935|[P2P][Service] heartBeat,connected count=2
info|2019-05-28 16:29:17.268163|[P2P][Service] heartBeat,connected count=2
info|2019-05-28 16:29:27.268284|[P2P][Service] heartBeat,connected count=2
info|2019-05-28 16:29:37.268467|[P2P][Service] heartBeat,connected count=2

执行下面指令,检查是否在共识

tail -f nodes/127.0.0.1/node0/log/log*  | grep +++

正常情况会不停输出++++Generating seal表示共识正常。

info|2019-05-28 16:26:32.454059|[g:1][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=28,tx=0,nodeIdx=3,hash=c9c859d5...
info|2019-05-28 16:26:36.473543|[g:1][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=28,tx=0,nodeIdx=3,hash=6b319fa7...
info|2019-05-28 16:26:40.498838|[g:1][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=28,tx=0,nodeIdx=3,hash=2164360f...
使用控制台发送交易
准备依赖
cd ~/fisco;
curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v1.1.0/download_console.sh && bash download_console.sh
cp -n console/conf/applicationContext-sample.xml console/conf/applicationContext.xml
cp nodes/127.0.0.1/sdk/* console/conf/
修改配置文件

将~/fisco/console/conf/applicationContext.xml修改为如下配置(部分信息)

<bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
	<property name="allChannelConnections">
		<list>
			<bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
				<property name="groupId" value="1" />
					<property name="connectionsStr">
					<list>
						<value>127.0.0.1:20200</value>
					</list>
				</property>
			</bean>
		</list>
	</property>
</bean>
启用控制台
cd ~/fisco/console
sh start.sh 1
#部署TableTest合约
[group:1]> deploy TableTest
contract address:0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744

查看数据库中的表情况

MySQL -uroot -p123456 -A db_Group1_A
use db_Group1_A;
show tables;
----------------------------------------------------------+
| Tables_in_db_Group1_A                                  |
+----------------------------------------------------------+
| c_8c17cf316c1063ab6c89df875e96c9f0f5b2f744 |
| c_f69a2fa2eca49820218062164837c6eecc909abd |
| _sys_block_2_nonces_                                     |
| _sys_cns_                                                |
| _sys_config_                                             |
| _sys_consensus_                                          |
| _sys_current_state_                                      |
| _sys_hash_2_block_                                       |
| _sys_number_2_hash_                                      |
| _sys_table_access_                                       |
| _sys_tables_                                             |
| _sys_tx_hash_2_block_                                    |
+----------------------------------------------------------+
12 rows in set (0.02 sec)

查看数据库中的表情况

show tables;
+----------------------------------------------------------+
| Tables_in_db_Group1_A                                  |
+----------------------------------------------------------+
| c_8c17cf316c1063ab6c89df875e96c9f0f5b2f744 |
| c_f69a2fa2eca49820218062164837c6eecc909abd |
| _sys_block_2_nonces_                                     |
| _sys_cns_                                                |
| _sys_config_                                             |
| _sys_consensus_                                          |
| _sys_current_state_                                      |
| _sys_hash_2_block_                                       |
| _sys_number_2_hash_                                      |
| _sys_table_access_                                       |
| _sys_tables_                                             |
| _sys_tx_hash_2_block_                                    |
| u_t_test                                             |
+----------------------------------------------------------+

往表里面插入一条数据

#往表里插入数据
call TableTest 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 insert "fruit" 100 "apple"
0x082ca6a5a292f1f7b20abeb3fb03f45e0c6f48b5a79cc65d1246bfe57be358d1

打开MySQL客户端,查询u_t_test表数据

#查看用户表中的数据
select * from u_t_test\G;
*************************** 1. row ***************************
     _id_: 31
   _hash_: 0a0ed3b2b0a227a6276114863ef3e8aa34f44e31567a5909d1da0aece31e575e
    _num_: 3
 _status_: 0
     name: fruit
  item_id: 100
item_name: apple
1 row in set (0.00 sec)

控制台

控制台是FISCO BCOS 2.0重要的交互式客户端工具,它通过Web3SDK与区块链节点建立连接,实现对区块链节点数据的读写访问请求。控制台拥有丰富的命令,包括查询区块链状态、管理区块链节点、部署并调用合约等。此外,控制台提供一个合约编译工具,用户可以方便快捷的将Solidity合约文件编译为Java合约文件。

控制台命令

控制台命令由两部分组成,即指令和指令相关的参数:

  • 指令: 指令是执行的操作命令,包括查询区块链相关信息,部署合约和调用合约的指令等,其中部分指令调用JSON-RPC接口,因此与JSON-RPC接口同名。 使用提示: 指令可以使用tab键补全,并且支持按上下键显示历史输入指令。
  • 指令相关的参数: 指令调用接口需要的参数,指令与参数以及参数与参数之间均用空格分隔,与JSON-RPC接口同名命令的输入参数和获取信息字段的详细解释参考JSON-RPC API

常用命令链接

合约相关命令
其他命令

快捷键

  • Ctrl+A:光标移动到行首
  • Ctrl+D:退出控制台
  • Ctrl+E:光标移动到行尾
  • Ctrl+R:搜索输入的历史命令
  • ↑:向前浏览历史命令
  • ↓:向后浏览历史命令

控制台响应

当发起一个控制台命令时,控制台会获取命令执行的结果,并且在终端展示执行结果,执行结果分为2类:

  • 正确结果: 命令返回正确的执行结果,以字符串或是json的形式返回。
  • 错误结果: 命令返回错误的执行结果,以字符串或是json的形式返回。
    • 控制台的命令调用JSON-RPC接口时,错误码参考这里
    • 控制台的命令调用Precompiled Service接口时,错误码参考这里

控制台配置与运行

重要

前置条件:搭建FISCO BCOS区块链请参考 开发部署工具企业工具

获取控制台
cd ~ && mkdir -p fisco && cd fisco
# 获取控制台
curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v1.1.0/download_console.sh && bash download_console.sh

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh && bash download_console.sh

目录结构如下:

|-- apps # 控制台jar包目录
|   -- console.jar
|-- lib # 相关依赖的jar包目录
|-- conf
|   |-- applicationContext-sample.xml # 配置文件
|   |-- log4j.properties  # 日志配置文件
|-- contracts # 合约所在目录
|   -- solidity  # solidity合约存放目录
|       -- HelloWorld.sol # 普通合约:HelloWorld合约,可部署和调用
|       -- TableTest.sol # 使用CRUD接口的合约:TableTest合约,可部署和调用
|       -- Table.sol # 提供CRUD操作的接口合约
|   -- console  # 控制台部署合约时编译的合约abi, bin,java文件目录
|   -- sdk      # sol2java.sh脚本编译的合约abi, bin,java文件目录
|-- start.sh # 控制台启动脚本
|-- get_account.sh # 账户生成脚本
|-- sol2java.sh # solidity合约文件编译为java合约文件的开发工具脚本
|-- replace_solc_jar.sh # 编译jar包替换脚本
配置控制台
  • 区块链节点和证书的配置:
    • 将节点sdk目录下的ca.crtsdk.crtsdk.key文件拷贝到conf目录下。
    • conf目录下的applicationContext-sample.xml文件重命名为applicationContext.xml文件。配置applicationContext.xml文件,其中添加注释的内容根据区块链节点配置做相应修改。提示:如果搭链时设置的channel_listen_ip(若节点版本小于v2.3.0,查看配置项listen_ip)为127.0.0.1或者0.0.0.0,channel_port为20200, 则applicationContext.xml配置不用修改。
    • FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错。
<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
           xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
         http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


        <bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
                <constructor-arg value="0"/> <!-- 0:standard 1:guomi -->
        </bean>

        <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
                <property name="allChannelConnections">
                        <list>  <!-- 每个群组需要配置一个bean -->
                                <bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                                        <property name="groupId" value="1" /> <!-- 群组的groupID -->
                                        <property name="connectionsStr">
                                                <list>
                                                        <value>127.0.0.1:20200</value>  <!-- IP:channel_port -->
                                                </list>
                                        </property>
                                </bean>
                        </list>
                </property>
        </bean>

        <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
                <property name="groupId" value="1" /> <!-- 连接ID为1的群组 -->
                <property name="agencyName" value="fisco" />
                <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
        </bean>

</beans>

配置项详细说明参考这里

重要

控制台说明

  • 控制台启动失败

    参考,附录:JavaSDK启动失败场景

  • 当控制台配置文件在一个群组内配置多个节点连接时,由于群组内的某些节点在操作过程中可能退出群组,因此控制台轮询节点查询时,其返回信息可能不一致,属于正常现象。建议使用控制台时,配置一个节点或者保证配置的节点始终在群组中,这样在同步时间内查询的群组内信息保持一致。

配置国密版控制台

国密版的控制台配置与非国密版控制台的配置流程有一些区别,流程如下:

  • 区块链节点和证书的配置:
    • 将节点sdk目录下的ca.crtsdk.crtsdk.key文件拷贝到conf目录下。
    • conf目录下的applicationContext-sample.xml文件重命名为applicationContext.xml文件。配置applicationContext.xml文件,其中添加注释的内容根据区块链节点配置做相应修改。提示:如果搭链时设置的channel_listen_ip(若节点版本小于v2.3.0,查看配置项listen_ip)为127.0.0.1或者0.0.0.0,channel_port为20200, 则applicationContext.xml配置不用修改。
    • FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错。
  • 打开国密开关
<bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
    <!-- encryptType值设置为1,打开国密开关 -->
    <constructor-arg value="1"/> <!-- 0:standard 1:guomi -->
</bean>

重要

控制台编译工具重要说明

  • 控制台自1.1.0版本起,移除对solcJ-all-0.x.x.jar、solcJ-all-0.x.x-gm.jar的依赖
  • 新编译工具支持0.4.25、0.5.2、0.6.10三个版本,与同版本的solidity编译器对应
  • 新的编译上传至maven仓库进行管理,不再需要替换文件的操作
  • 控制台默认配置0.4.25版本编译工具,用户可以修改build.gradle配置的版本号重新编译,也可以通过download_console.sh脚本指定-v参数,下载配置对应编译器版本的控制台
  • 新的编译工具同时支持国密、非国密编译功能,控制台国密或者非国密环境运行时,不再需要solcJ国密与非国密版本的替换
合约编译工具

控制台提供一个专门的编译合约工具,方便开发者将solidity合约文件编译为java合约文件。 使用该工具,分为两步:

  • 将solidity合约文件放在contracts/solidity目录下。

  • 通过运行sol2java.sh脚本(需要指定一个java的包名)完成编译合约任务。例如,contracts/solidity目录下已有HelloWorld.solTableTest.solTable.sol合约,指定包名为org.com.fisco,命令如下:

    $ cd ~/fisco/console
    $ ./sol2java.sh org.com.fisco
    

    运行成功之后,将会在console/contracts/sdk目录生成java、abi和bin目录,如下所示。

    |-- abi # 编译生成的abi目录,存放solidity合约编译的abi文件
    |   |-- HelloWorld.abi
    |   |-- Table.abi
    |   |-- TableTest.abi
    |-- bin # 编译生成的bin目录,存放solidity合约编译的bin文件
    |   |-- HelloWorld.bin
    |   |-- Table.bin
    |   |-- TableTest.bin
    |-- java  # 存放编译的包路径及Java合约文件
    |   |-- org
    |       |-- com
    |           |-- fisco
    |               |-- HelloWorld.java # 编译的HelloWorld Java文件
    |               |-- Table.java  # 编译的CRUD接口合约 Java文件
    |               |-- TableTest.java  # 编译的TableTest Java文件
    

    java目录下生成了org/com/fisco/包路径目录。包路径目录下将会生成java合约文件HelloWorld.javaTableTest.javaTable.java。其中HelloWorld.javaTableTest.java是java应用所需要的java合约文件。

重要

Java合约文件说明

  • 控制台自1.1.0版本起,生成的Java合约文件国密、非国密环境均可以运行,国密与非国密环境生成一份合约代码即可
启动控制台

在节点正在运行的情况下,启动控制台:

$ ./start.sh
# 输出下述信息表明启动成功
=====================================================================================
Welcome to FISCO BCOS console(1.0.4)!
Type 'help' or 'h' for help. Type 'quit' or 'q' to quit console.
 ________ ______  ______   ______   ______       _______   ______   ______   ______
|        |      \/      \ /      \ /      \     |       \ /      \ /      \ /      \
| $$$$$$$$\$$$$$|  $$$$$$|  $$$$$$|  $$$$$$\    | $$$$$$$|  $$$$$$|  $$$$$$|  $$$$$$\
| $$__     | $$ | $$___\$| $$   \$| $$  | $$    | $$__/ $| $$   \$| $$  | $| $$___\$$
| $$  \    | $$  \$$    \| $$     | $$  | $$    | $$    $| $$     | $$  | $$\$$    \
| $$$$$    | $$  _\$$$$$$| $$   __| $$  | $$    | $$$$$$$| $$   __| $$  | $$_\$$$$$$\
| $$      _| $$_|  \__| $| $$__/  | $$__/ $$    | $$__/ $| $$__/  | $$__/ $|  \__| $$
| $$     |   $$ \\$$    $$\$$    $$\$$    $$    | $$    $$\$$    $$\$$    $$\$$    $$
 \$$      \$$$$$$ \$$$$$$  \$$$$$$  \$$$$$$      \$$$$$$$  \$$$$$$  \$$$$$$  \$$$$$$

=====================================================================================
启动脚本说明
查看当前控制台版本:
./start.sh --version
console version: 1.0.4
账户使用方式
控制台加载私钥

控制台提供账户生成脚本get_account.sh(脚本用法请参考账户管理文档,生成的的账户文件在accounts目录下,控制台加载的账户文件必须放置在该目录下。 控制台启动方式有如下几种:

./start.sh
./start.sh groupID
./start.sh groupID -pem pemName
./start.sh groupID -p12 p12Name
默认启动

控制台随机生成一个账户,使用控制台配置文件指定的群组号启动。

./start.sh
指定群组号启动

控制台随机生成一个账户,使用命令行指定的群组号启动。

./start.sh 2
  • 注意:指定的群组在控制台配置文件中需要配置bean。
使用PEM格式私钥文件启动
  • 使用指定的pem文件的账户启动,输入参数:群组号、-pem、pem文件路径
./start.sh 1 -pem accounts/0xebb824a1122e587b17701ed2e512d8638dfb9c88.pem
使用PKCS12格式私钥文件启动
  • 使用指定的p12文件的账户,需要输入密码,输入参数:群组号、-p12、p12文件路径
./start.sh 1 -p12 accounts/0x5ef4df1b156bc9f077ee992a283c2dbb0bf045c0.p12
Enter Export Password:

注意: 控制台启动时加载p12文件出现下面报错:

exception unwrapping private key - java.security.InvalidKeyException: Illegal key size

可能是Java版本的原因,参考解决方案:https://stackoverflow.com/questions/3862800/invalidkeyexception-illegal-key-size

控制台命令

help

输入help或者h,查看控制台所有的命令。

[group:1]> help
-------------------------------------------------------------------------------------
addObserver                              Add an observer node.
addSealer                                Add a sealer node.
call                                     Call a contract by a function and paramters.
callByCNS                                Call a contract by a function and paramters by CNS.
deploy                                   Deploy a contract on blockchain.
deployByCNS                              Deploy a contract on blockchain by CNS.
desc                                     Description table information.
exit                                     Quit console.
getBlockHeaderByHash                     Query information about a block header by hash.
getBlockHeaderByNumber                   Query information about a block header by block number.
getBlockByHash                           Query information about a block by hash.
getBlockByNumber                         Query information about a block by block number.
getBlockHashByNumber                     Query block hash by block number.
getBlockNumber                           Query the number of most recent block.
getCode                                  Query code at a given address.
getConsensusStatus                       Query consensus status.
getDeployLog                             Query the log of deployed contracts.
getGroupList                             Query group list.
getGroupPeers                            Query nodeId list for sealer and observer nodes.
getNodeIDList                            Query nodeId list for all connected nodes.
getNodeVersion                           Query the current node version.
getObserverList                          Query nodeId list for observer nodes.
getPbftView                              Query the pbft view of node.
getPeers                                 Query peers currently connected to the client.
getPendingTransactions                   Query pending transactions.
getPendingTxSize                         Query pending transactions size.
getSealerList                            Query nodeId list for sealer nodes.
getSyncStatus                            Query sync status.
getSystemConfigByKey                     Query a system config value by key.
setSystemConfigByKey                     Set a system config value by key.
getTotalTransactionCount                 Query total transaction count.
getTransactionByBlockHashAndIndex        Query information about a transaction by block hash and transaction index position.
getTransactionByBlockNumberAndIndex      Query information about a transaction by block number and transaction index position.
getTransactionByHash                     Query information about a transaction requested by transaction hash.
getTransactionReceipt                    Query the receipt of a transaction by transaction hash.
getTransactionByHashWithProof            Query the transaction and transaction proof by transaction hash.
getTransactionReceiptByHashWithProof     Query the receipt and transaction receipt proof by transaction hash.
grantCNSManager                          Grant permission for CNS by address.
grantDeployAndCreateManager              Grant permission for deploy contract and create user table by address.
grantNodeManager                         Grant permission for node configuration by address.
grantSysConfigManager                    Grant permission for system configuration by address.
grantUserTableManager                    Grant permission for user table by table name and address.
help(h)                                  Provide help information.
listCNSManager                           Query permission information for CNS.
listDeployAndCreateManager               Query permission information for deploy contract and create user table.
listNodeManager                          Query permission information for node configuration.
listSysConfigManager                     Query permission information for system configuration.
listUserTableManager                     Query permission for user table information.
queryCNS                                 Query CNS information by contract name and contract version.
quit(q)                                  Quit console.
removeNode                               Remove a node.
revokeCNSManager                         Revoke permission for CNS by address.
revokeDeployAndCreateManager             Revoke permission for deploy contract and create user table by address.
revokeNodeManager                        Revoke permission for node configuration by address.
revokeSysConfigManager                   Revoke permission for system configuration by address.
revokeUserTableManager                   Revoke permission for user table by table name and address.
listContractWritePermission              Query the account list which have write permission of the contract.
grantContractWritePermission             Grant the account the contract write permission.
revokeContractWritePermission            Revoke the account the contract write permission.
grantContractStatusManager               Grant contract authorization to the user.
getContractStatus                        Get the status of the contract.
listContractStatusManager                List the authorization of the contract.
grantCommitteeMember                     Grant the account committee member
revokeCommitteeMember                    Revoke the account from committee member
listCommitteeMembers                     List all committee members
grantOperator                            Grant the account operator
revokeOperator                           Revoke the operator
listOperators                            List all operators
updateThreshold                          Update the threshold
queryThreshold                           Query the threshold
updateCommitteeMemberWeight              Update the committee member weight
queryCommitteeMemberWeight               Query the committee member weight
freezeAccount                            Freeze the account.
unfreezeAccount                          Unfreeze the account.
getAccountStatus                         GetAccountStatus of the account.
freezeContract                           Freeze the contract.
unfreezeContract                         Unfreeze the contract.
switch(s)                                Switch to a specific group by group ID.
[create sql]                             Create table by sql.
[delete sql]                             Remove records by sql.
[insert sql]                             Insert records by sql.
[select sql]                             Select records by sql.
[update sql]                             Update records by sql.
-------------------------------------------------------------------------------------

注:

  • help显示每条命令的含义是:命令 命令功能描述
  • 查看具体命令的使用介绍说明,输入命令 -h或--help查看。例如:
[group:1]> getBlockByNumber -h
Query information about a block by block number.
Usage: getBlockByNumber blockNumber [boolean]
blockNumber -- Integer of a block number, from 0 to 2147483647.
boolean -- (optional) If true it returns the full transaction objects, if false only the hashes of the transactions.
switch

运行switch或者s,切换到指定群组。群组号显示在命令提示符前面。

[group:1]> switch 2
Switched to group 2.

[group:2]>

注: 需要切换的群组,请确保在console/conf目录下的applicationContext.xml(该配置文件初始状态只提供群组1的配置)文件中配置了该群组的信息,并且该群组中配置的节点ip和端口正确,该节点正常运行。

getBlockNumber

运行getBlockNumber,查看区块高度。

[group:1]> getBlockNumber
90
getSealerList

运行getSealerList,查看共识节点列表。

[group:1]> getSealerList
[
    0c0bbd25152d40969d3d3cee3431fa28287e07cff2330df3258782d3008b876d146ddab97eab42796495bfbb281591febc2a0069dcc7dfe88c8831801c5b5801,
    10b3a2d4b775ec7f3c2c9e8dc97fa52beb8caab9c34d026db9b95a72ac1d1c1ad551c67c2b7fdc34177857eada75836e69016d1f356c676a6e8b15c45fc9bc34,
    622af37b2bd29c60ae8f15d467b67c0a7fe5eb3e5c63fdc27a0ee8066707a25afa3aa0eb5a3b802d3a8e5e26de9d5af33806664554241a3de9385d3b448bcd73
]
getObserverList

运行getObserverList,查看观察节点列表。

[group:1]> getObserverList
[
    037c255c06161711b6234b8c0960a6979ef039374ccc8b723afea2107cba3432dbbc837a714b7da20111f74d5a24e91925c773a72158fa066f586055379a1772
]
getNodeIDList

运行getNodeIDList,查看节点及连接p2p节点的nodeId列表。

[group:1]> getNodeIDList
[
    41285429582cbfe6eed501806391d2825894b3696f801e945176c7eb2379a1ecf03b36b027d72f480e89d15bacd43462d87efd09fb0549e0897f850f9eca82ba,
    87774114e4a496c68f2482b30d221fa2f7b5278876da72f3d0a75695b81e2591c1939fc0d3fadb15cc359c997bafc9ea6fc37345346acaf40b6042b5831c97e1,
    29c34347a190c1ec0c4507c6eed6a5bcd4d7a8f9f54ef26da616e81185c0af11a8cea4eacb74cf6f61820292b24bc5d9e426af24beda06fbd71c217960c0dff0,
    d5b3a9782c6aca271c9642aea391415d8b258e3a6d92082e59cc5b813ca123745440792ae0b29f4962df568f8ad58b75fc7cea495684988e26803c9c5198f3f8
]
getPbftView

运行getPbftView,查看pbft视图。

[group:1]> getPbftView
2730
getConsensusStatus

运行getConsensusStatus,查看共识状态。

[group:1]> getConsensusStatus
[
    {
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "accountType": 1,
      "allowFutureBlocks": true,
      "cfgErr": false,
      "connectedNodes": 3,
      "consensusedBlockNumber": 38207,
      "currentView": 54477,
      "groupId": 1,
      "highestblockHash": "0x19a16e8833e671aa11431de589c866a6442ca6c8548ba40a44f50889cd785069",
      "highestblockNumber": 38206,
      "leaderFailed": false,
      "max_faulty_leader": 1,
      "nodeId": "f72648fe165da17a889bece08ca0e57862cb979c4e3661d6a77bcc2de85cb766af5d299fec8a4337eedd142dca026abc2def632f6e456f80230902f93e2bea13",
      "nodeNum": 4,
      "node_index": 3,
      "omitEmptyBlock": true,
      "protocolId": 65544,
      "sealer.0": "6a99f357ecf8a001e03b68aba66f68398ee08f3ce0f0147e777ec77995369aac470b8c9f0f85f91ebb58a98475764b7ca1be8e37637dd6cb80b3355749636a3d",
      "sealer.1": "8a453f1328c80b908b2d02ba25adca6341b16b16846d84f903c4f4912728c6aae1050ce4f24cd9c13e010ce922d3393b846f6f5c42f6af59c65a814de733afe4",
      "sealer.2": "ed483837e73ee1b56073b178f5ac0896fa328fc0ed418ae3e268d9e9109721421ec48d68f28d6525642868b40dd26555c9148dbb8f4334ca071115925132889c",
      "sealer.3": "f72648fe165da17a889bece08ca0e57862cb979c4e3661d6a77bcc2de85cb766af5d299fec8a4337eedd142dca026abc2def632f6e456f80230902f93e2bea13",
      "toView": 54477
    },
    [
      {
        "nodeId": "6a99f357ecf8a001e03b68aba66f68398ee08f3ce0f0147e777ec77995369aac470b8c9f0f85f91ebb58a98475764b7ca1be8e37637dd6cb80b3355749636a3d",
        "view": 54474
      },
      {
        "nodeId": "8a453f1328c80b908b2d02ba25adca6341b16b16846d84f903c4f4912728c6aae1050ce4f24cd9c13e010ce922d3393b846f6f5c42f6af59c65a814de733afe4",
        "view": 54475
      },
      {
        "nodeId": "ed483837e73ee1b56073b178f5ac0896fa328fc0ed418ae3e268d9e9109721421ec48d68f28d6525642868b40dd26555c9148dbb8f4334ca071115925132889c",
        "view": 54476
      },
      {
        "nodeId": "f72648fe165da17a889bece08ca0e57862cb979c4e3661d6a77bcc2de85cb766af5d299fec8a4337eedd142dca026abc2def632f6e456f80230902f93e2bea13",
        "view": 54477
      }
    ]
  ]
}
]
getSyncStatus

运行getSyncStatus,查看同步状态。

[group:1]> getSyncStatus
{
    "blockNumber":5,
    "genesisHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
    "isSyncing":false,
    "latestHash":"0xb99703130e24702d3b580111b0cf4e39ff60ac530561dd9eb0678d03d7acce1d",
    "nodeId":"cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
    "peers":[
        {
            "blockNumber":5,
            "genesisHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
            "latestHash":"0xb99703130e24702d3b580111b0cf4e39ff60ac530561dd9eb0678d03d7acce1d",
            "nodeId":"0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278"
        },
        {
            "blockNumber":5,
            "genesisHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
            "latestHash":"0xb99703130e24702d3b580111b0cf4e39ff60ac530561dd9eb0678d03d7acce1d",
            "nodeId":"2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108"
        },
        {
            "blockNumber":5,
            "genesisHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
            "latestHash":"0xb99703130e24702d3b580111b0cf4e39ff60ac530561dd9eb0678d03d7acce1d",
            "nodeId":"ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec"
        }
    ],
    "protocolId":265,
    "txPoolSize":"0"
}
getNodeVersion

运行getNodeVersion,查看节点的版本。

[group:1]> getNodeVersion
{
	"Build Time":"20190107 10:15:23",
	"Build Type":"Linux/g++/RelWithDebInfo",
	"FISCO-BCOS Version":"2.0.0",
	"Git Branch":"master",
	"Git Commit Hash":"be95a6e3e85b621860b101c3baeee8be68f5f450"
}
getPeers

运行getPeers,查看节点的peers。

[group:1]> getPeers
[
	{
		"IPAndPort":"127.0.0.1:50723",
		"nodeId":"8718579e9a6fee647b3d7404d59d66749862aeddef22e6b5abaafe1af6fc128fc33ed5a9a105abddab51e12004c6bfe9083727a1c3a22b067ddbaac3fa349f7f",
		"Topic":[

		]
	},
	{
		"IPAndPort":"127.0.0.1:50719",
		"nodeId":"697e81e512cffc55fc9c506104fb888a9ecf4e29eabfef6bb334b0ebb6fc4ef8fab60eb614a0f2be178d0b5993464c7387e2b284235402887cdf640f15cb2b4a",
		"Topic":[

		]
	},
	{
		"IPAndPort":"127.0.0.1:30304",
		"nodeId":"8fc9661baa057034f10efacfd8be3b7984e2f2e902f83c5c4e0e8a60804341426ace51492ffae087d96c0b968bd5e92fa53ea094ace8d1ba72de6e4515249011",
		"Topic":[

		]
	}
]
getGroupPeers

运行getGroupPeers,查看节点所在group的共识节点和观察节点列表。

[group:1]> getGroupPeers
[
    cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd,
    ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec,
    0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278,
    2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108
]
getGroupList

运行getGroupList,查看群组列表:

[group:1]> getGroupList
[1]
getBlockHeaderByHash

运行getBlockHeaderByHash,根据区块哈希查询区块头信息。 参数:

  • 区块哈希:0x开头的区块哈希值
  • 签名列表标志:默认为false,即:区块头信息中不显示区块签名列表信息,设置为true,则显示区块签名列表。
[group:1]> getBlockHeaderByHash 0x99576e7567d258bd6426ddaf953ec0c953778b2f09a078423103c6555aa4362d
{
    "dbHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData":[

    ],
    "gasLimit":"0x0",
    "gasUsed":"0x0",
    "hash":"0x99576e7567d258bd6426ddaf953ec0c953778b2f09a078423103c6555aa4362d",
    "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number":1,
    "parentHash":"0x4f6394763c33c1709e5a72b202ad4d7a3b8152de3dc698cef6f675ecdaf20a3b",
    "receiptsRoot":"0x69a04fa6073e4fc0947bac7ee6990e788d1e2c5ec0fe6c2436d0892e7f3c09d2",
    "sealer":"0x2",
    "sealerList":[
        "11e1be251ca08bb44f36fdeedfaeca40894ff80dfd80084607a75509edeaf2a9c6fee914f1e9efda571611cf4575a1577957edfd2baa9386bd63eb034868625f",
        "78a313b426c3de3267d72b53c044fa9fe70c2a27a00af7fea4a549a7d65210ed90512fc92b6194c14766366d434235c794289d66deff0796f15228e0e14a9191",
        "95b7ff064f91de76598f90bc059bec1834f0d9eeb0d05e1086d49af1f9c2f321062d011ee8b0df7644bd54c4f9ca3d8515a3129bbb9d0df8287c9fa69552887e",
        "b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36"
    ],
    "stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp":"0x173ad8703d6",
    "transactionsRoot":"0xb563f70188512a085b5607cac0c35480336a566de736c83410a062c9acc785ad"
}
getBlockHeaderByNumber

运行getBlockHeaderByNumber,根据区块高度查询区块头信息。 参数:

  • 区块高度
  • 签名列表标志:默认为false,即:区块头信息中不显示区块签名列表信息,设置为true,则显示区块签名列表。
[group:1]> getBlockHeaderByNumber 1 true
{
    "dbHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData":[

    ],
    "gasLimit":"0x0",
    "gasUsed":"0x0",
    "hash":"0x99576e7567d258bd6426ddaf953ec0c953778b2f09a078423103c6555aa4362d",
    "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number":1,
    "parentHash":"0x4f6394763c33c1709e5a72b202ad4d7a3b8152de3dc698cef6f675ecdaf20a3b",
    "receiptsRoot":"0x69a04fa6073e4fc0947bac7ee6990e788d1e2c5ec0fe6c2436d0892e7f3c09d2",
    "sealer":"0x2",
    "sealerList":[
        "11e1be251ca08bb44f36fdeedfaeca40894ff80dfd80084607a75509edeaf2a9c6fee914f1e9efda571611cf4575a1577957edfd2baa9386bd63eb034868625f",
        "78a313b426c3de3267d72b53c044fa9fe70c2a27a00af7fea4a549a7d65210ed90512fc92b6194c14766366d434235c794289d66deff0796f15228e0e14a9191",
        "95b7ff064f91de76598f90bc059bec1834f0d9eeb0d05e1086d49af1f9c2f321062d011ee8b0df7644bd54c4f9ca3d8515a3129bbb9d0df8287c9fa69552887e",
        "b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36"
    ],
    "signatureList":[
        {
            "index":"0x3",
            "signature":"0xb5b41e49c0b2bf758322ecb5c86dc3a3a0f9b98891b5bbf50c8613a241f05f595ce40d0bb212b6faa32e98546754835b057b9be0b29b9d0c8ae8b38f7487b8d001"
        },
        {
            "index":"0x0",
            "signature":"0x411cb93f816549eba82c3bf8c03fa637036dcdee65667b541d0da06a6eaea80d16e6ca52bf1b08f77b59a834bffbc124c492ea7a1601d0c4fb257d97dc97cea600"
        },
        {
            "index":"0x1",
            "signature":"0xea3c27c2a1486c7942c41c4dc8f15fbf9a668aff2ca40f00701d73fa659a14317d45d74372d69d43ced8e81f789e48140e7fa0c61997fa7cde514c654ef9f26d00"
        }
    ],
    "stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp":"0x173ad8703d6",
    "transactionsRoot":"0xb563f70188512a085b5607cac0c35480336a566de736c83410a062c9acc785ad"
}
getBlockByHash

运行getBlockByHash,根据区块哈希查询区块信息。 参数:

  • 区块哈希:0x开头的区块哈希值。
  • 交易标志:默认false,区块中的交易只显示交易哈希,设置为true,显示交易具体信息。
[group:1]> getBlockByHash 0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855
{
    "extraData":[

    ],
    "gasLimit":"0x0",
    "gasUsed":"0x0",
    "hash":"0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855",
    "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number":"0x1",
    "parentHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
    "sealer":"0x0",
    "sealerList":[
        "0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278",
        "2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108",
        "cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
        "ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec"
    ],
    "stateRoot":"0x9711819153f7397ec66a78b02624f70a343b49c60bc2f21a77b977b0ed91cef9",
    "timestamp":"0x1692f119c84",
    "transactions":[
        "0xa14638d47cc679cf6eeb7f36a6d2a30ea56cb8dcf0938719ff45023a7a8edb5d"
    ],
    "transactionsRoot":"0x516787f85980a86fd04b0e9ce82a1a75950db866e8cdf543c2cae3e4a51d91b7"
}
[group:1]> getBlockByHash 0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855 true
{
    "extraData":[

    ],
    "gasLimit":"0x0",
    "gasUsed":"0x0",
    "hash":"0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855",
    "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number":"0x1",
    "parentHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
    "sealer":"0x0",
    "sealerList":[
        "0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278",
        "2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108",
        "cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
        "ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec"
    ],
    "stateRoot":"0x9711819153f7397ec66a78b02624f70a343b49c60bc2f21a77b977b0ed91cef9",
    "timestamp":"0x1692f119c84",
    "transactions":[
        {
            "blockHash":"0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855",
            "blockNumber":"0x1",
            "from":"0x7234c32327795e4e612164e3442cfae0d445b9ad",
            "gas":"0x1c9c380",
            "gasPrice":"0x1",
            "hash":"0xa14638d47cc679cf6eeb7f36a6d2a30ea56cb8dcf0938719ff45023a7a8edb5d",
            "input":"0x608060405234801561001057600080fd5b506040805190810160405280600d81526020017f48656c6c6f2c20576f726c6421000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b6102d7806101166000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634ed3885e146100515780636d4ce63c146100ba575b600080fd5b34801561005d57600080fd5b506100b8600480360381019080803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061014a565b005b3480156100c657600080fd5b506100cf610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010f5780820151818401526020810190506100f4565b50505050905090810190601f16801561013c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b8060009080519060200190610160929190610206565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101fc5780601f106101d1576101008083540402835291602001916101fc565b820191906000526020600020905b8154815290600101906020018083116101df57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820fd74886bedbe51a7f3d834162de4d9af7f276c70133e04fd6776b5fbdd3653000029",
            "nonce":"0x3443a1391c9c29f751e8350304efb310850b8afbaa7738f5e89ddfce79b1d6",
            "to":null,
            "transactionIndex":"0x0",
            "value":"0x0"
        }
    ],
    "transactionsRoot":"0x516787f85980a86fd04b0e9ce82a1a75950db866e8cdf543c2cae3e4a51d91b7"
}
getBlockByNumber

运行getBlockByNumber,根据区块高度查询区块信息。 参数:

  • 区块高度:十进制整数。
  • 交易标志:默认false,区块中的交易只显示交易哈希,设置为true,显示交易具体信息。
[group:1]> getBlockByNumber 1
{
    "extraData":[

    ],
    "gasLimit":"0x0",
    "gasUsed":"0x0",
    "hash":"0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855",
    "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number":"0x1",
    "parentHash":"0xeccad5274949b9d25996f7a96b89c0ac5c099eb9b72cc00d65bc6ef09f7bd10b",
    "sealer":"0x0",
    "sealerList":[
        "0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278",
        "2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108",
        "cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
        "ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec"
    ],
    "stateRoot":"0x9711819153f7397ec66a78b02624f70a343b49c60bc2f21a77b977b0ed91cef9",
    "timestamp":"0x1692f119c84",
    "transactions":[
        "0xa14638d47cc679cf6eeb7f36a6d2a30ea56cb8dcf0938719ff45023a7a8edb5d"
    ],
    "transactionsRoot":"0x516787f85980a86fd04b0e9ce82a1a75950db866e8cdf543c2cae3e4a51d91b7"
}
getBlockHashByNumber

运行getBlockHashByNumber,通过区块高度获得区块哈希。 参数:

  • 区块高度:十进制整数。
[group:1]> getBlockHashByNumber 1
0xf6afbcc3ec9eb4ac2c2829c2607e95ea0fa1be914ca1157436b2d3c5f1842855
getTransactionByHash

运行getTransactionByHash,通过交易哈希查询交易信息。 参数:

  • 交易哈希:0x开头的交易哈希值。
  • 合约名:可选,发送交易产生该交易的合约名称,使用该参数可以将交易中的input解析并输出。如果是部署合约交易则不解析。
[group:1]> getTransactionByHash 0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gas":"0x11e1a300",
    "gasPrice":"0x11e1a300",
    "hash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "nonce":"0x1aec6e447da49b9a140bf39a91a4d75fd19ea77f7dc38ccf940d8d510d78bd0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionIndex":"0x0",
    "value":"0x0"
}
# input字段是合约接口的编码,解析后的内容包括接口签名,输入参数值。
[group:1]> getTransactionByHash 0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5 TableTest
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gas":"0x11e1a300",
    "gasPrice":"0x11e1a300",
    "hash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "nonce":"0x1aec6e447da49b9a140bf39a91a4d75fd19ea77f7dc38ccf940d8d510d78bd0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionIndex":"0x0",
    "value":"0x0"
}
---------------------------------------------------------------------------------------------
Input
function: insert(string,int256,string)
input value: (fruit, 1, apple)
---------------------------------------------------------------------------------------------
getTransactionByBlockHashAndIndex

运行getTransactionByBlockHashAndIndex,通过区块哈希和交易索引查询交易信息。 参数:

  • 区块哈希:0x开头的区块哈希值。
  • 交易索引:十进制整数。
  • 合约名:可选,发送交易产生该交易的合约名称,使用该参数可以将交易中的input解析并输出。如果是部署合约交易则不解析。
[group:1]> getTransactionByBlockHashAndIndex 0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b 0
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gas":"0x11e1a300",
    "gasPrice":"0x11e1a300",
    "hash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "nonce":"0x1aec6e447da49b9a140bf39a91a4d75fd19ea77f7dc38ccf940d8d510d78bd0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionIndex":"0x0",
    "value":"0x0"
}

[group:1]> getTransactionByBlockHashAndIndex 0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b 0 TableTest
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gas":"0x11e1a300",
    "gasPrice":"0x11e1a300",
    "hash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "nonce":"0x1aec6e447da49b9a140bf39a91a4d75fd19ea77f7dc38ccf940d8d510d78bd0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionIndex":"0x0",
    "value":"0x0"
}
---------------------------------------------------------------------------------------------
Input
function: insert(string,int256,string)
input value: (fruit, 1, apple)
---------------------------------------------------------------------------------------------
getTransactionByBlockNumberAndIndex

运行getTransactionByBlockNumberAndIndex,通过区块高度和交易索引查询交易信息。 参数:

  • 区块高度:十进制整数。
  • 交易索引:十进制整数。
  • 合约名:可选,发送交易产生该交易的合约名称,使用该参数可以将交易中的input解析并输出。如果是部署合约交易则不解析。
[group:1]> getTransactionByBlockNumberAndIndex 8 0
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gas":"0x11e1a300",
    "gasPrice":"0x11e1a300",
    "hash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "nonce":"0x1aec6e447da49b9a140bf39a91a4d75fd19ea77f7dc38ccf940d8d510d78bd0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionIndex":"0x0",
    "value":"0x0"
}

[group:1]> getTransactionByBlockNumberAndIndex 8 0 TableTest
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gas":"0x11e1a300",
    "gasPrice":"0x11e1a300",
    "hash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "nonce":"0x1aec6e447da49b9a140bf39a91a4d75fd19ea77f7dc38ccf940d8d510d78bd0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionIndex":"0x0",
    "value":"0x0"
}
---------------------------------------------------------------------------------------------
Input
function: insert(string,int256,string)
input value: (fruit, 1, apple)
---------------------------------------------------------------------------------------------
getTransactionReceipt

运行getTransactionReceipt,通过交易哈希查询交易回执。 参数:

  • 交易哈希:0x开头的交易哈希值。
  • 合约名:可选,发送交易产生该交易回执的合约名称,使用该参数可以将交易回执中的input、output和event log解析并输出。(注:input字段在web3sdk 2.0.4版本中新增加的字段,之前版本无该字段则只解析output和event log。)
[group:1]> getTransactionReceipt 0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "contractAddress":"0x0000000000000000000000000000000000000000",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gasUsed":"0x94f5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "logs":[
        {
            "address":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
            "data":"0x0000000000000000000000000000000000000000000000000000000000000001",
            "topics":[
                "0xc57b01fa77f41df77eaab79a0e2623fab2e7ae3e9530d9b1cab225ad65f2b7ce"
            ]
        }
    ],
    "logsBloom":"0x00000000000000800000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000800000000000000000000000000000000002000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "output":"0x0000000000000000000000000000000000000000000000000000000000000001",
    "status":"0x0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionHash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "transactionIndex":"0x0"
}

[group:1]> getTransactionReceipt 0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5 TableTest
{
    "blockHash":"0xe4e1293837013f547ad7f443a8ff20a4e32a060b9cac56c41462255603548b7b",
    "blockNumber":"0x8",
    "contractAddress":"0x0000000000000000000000000000000000000000",
    "from":"0xf0d2115e52b0533e367447f700bfbf2ed35ff6fc",
    "gasUsed":"0x94f5",
    "input":"0xebf3b24f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005667275697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056170706c65000000000000000000000000000000000000000000000000000000",
    "logs":[
        {
            "address":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
            "data":"0x0000000000000000000000000000000000000000000000000000000000000001",
            "topics":[
                "0xc57b01fa77f41df77eaab79a0e2623fab2e7ae3e9530d9b1cab225ad65f2b7ce"
            ]
        }
    ],
    "logsBloom":"0x00000000000000800000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000800000000000000000000000000000000002000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "output":"0x0000000000000000000000000000000000000000000000000000000000000001",
    "status":"0x0",
    "to":"0x42fc572759fd568bd590f46011784be2a2d53f0c",
    "transactionHash":"0x1dfc67c51f5cc93b033fc80e5e9feb049c575a58b863483aa4d04f530a2c87d5",
    "transactionIndex":"0x0"
}
---------------------------------------------------------------------------------------------
Input
function: insert(string,int256,string)
input value: (fruit, 1, apple)
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
Output
function: insert(string,int256,string)
return type: (int256)
return value: (1)
---------------------------------------------------------------------------------------------
Event logs
event signature: InsertResult(int256) index: 0
event value: (1)
---------------------------------------------------------------------------------------------
getPendingTransactions

运行getPendingTransactions,查询等待处理的交易。

[group:1]> getPendingTransactions
[]
getPendingTxSize

运行getPendingTxSize,查询等待处理的交易数量(交易池中的交易数量)。

[group:1]> getPendingTxSize
0
getCode

运行getCode,根据合约地址查询合约二进制代码。 参数:

  • 合约地址:0x的合约地址(部署合约可以获得合约地址)。
[group:1]> getCode 0x97b8c19598fd781aaeb53a1957ef9c8acc59f705
0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806366c99139146100465780636d4ce63c14610066575bfe5b341561004e57fe5b610064600480803590602001909190505061008c565b005b341561006e57fe5b61007661028c565b6040518082815260200191505060405180910390f35b8060006001015410806100aa57506002600101548160026001015401105b156100b457610289565b806000600101540360006001018190555080600260010160008282540192505081905550600480548060010182816100ec919061029a565b916000526020600020906004020160005b608060405190810160405280604060405190810160405280600881526020017f32303137303431330000000000000000000000000000000000000000000000008152508152602001600060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200185815250909190915060008201518160000190805190602001906101ec9291906102cc565b5060208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550606082015181600301555050505b50565b600060026001015490505b90565b8154818355818115116102c7576004028160040283600052602060002091820191016102c6919061034c565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061030d57805160ff191683800117855561033b565b8280016001018555821561033b579182015b8281111561033a57825182559160200191906001019061031f565b5b50905061034891906103d2565b5090565b6103cf91905b808211156103cb57600060008201600061036c91906103f7565b6001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600382016000905550600401610352565b5090565b90565b6103f491905b808211156103f05760008160009055506001016103d8565b5090565b90565b50805460018160011615610100020316600290046000825580601f1061041d575061043c565b601f01602090049060005260206000209081019061043b91906103d2565b5b505600a165627a7a723058203c1f95b4a803493db0120df571d9f5155734152548a532412f2f9fa2dbe1ac730029
getTotalTransactionCount

运行getTotalTransactionCount,查询当前块高和累计交易数(从块高为0开始)。

[group:1]> getTotalTransactionCount
{
	"blockNumber":1,
	"txSum":1,
	"failedTxSum":0
}
deploy

运行deploy,部署合约。(默认提供HelloWorld合约和TableTest.sol进行示例使用) 参数:

  • 合约名称:部署的合约名称(可以带.sol后缀),即HelloWorld或者HelloWorld.sol均可。
# 部署HelloWorld合约
[group:1]> deploy HelloWorld.sol
contract address:0xc0ce097a5757e2b6e189aa70c7d55770ace47767

# 部署TableTest合约
[group:1]> deploy TableTest.sol
contract address:0xd653139b9abffc3fe07573e7bacdfd35210b5576

注:

  • 部署用户编写的合约,只需要将solidity合约文件放到控制台根目录的contracts/solidity/目录下,然后进行部署即可。按tab键可以搜索contracts/solidity/目录下的合约名称。
  • 若需要部署的合约引用了其他合约或library库,引用格式为import "./XXX.sol";。其相关引入的合约和library库均放在contracts/solidity/目录。
  • 如果合约引用了library库,library库文件的名称必须以Lib字符串开始,以便于区分是普通合约与library库文件。library库文件不能单独部署和调用。
  • 由于FISCO BCOS已去除以太币的转账支付逻辑,因此solidity合约的方法不支持使用payable关键字,该关键字会导致solidity合约转换成的java合约文件在编译时失败。
getDeployLog

运行getDeployLog,查询群组内由当前控制台部署合约的日志信息。日志信息包括部署合约的时间,群组ID,合约名称和合约地址。参数:

  • 日志行数,可选,根据输入的期望值返回最新的日志信息,当实际条数少于期望值时,按照实际数量返回。当期望值未给出时,默认按照20条返回最新的日志信息。
[group:1]> getDeployLog 2

2019-05-26 08:37:03  [group:1]  HelloWorld            0xc0ce097a5757e2b6e189aa70c7d55770ace47767
2019-05-26 08:37:45  [group:1]  TableTest             0xd653139b9abffc3fe07573e7bacdfd35210b5576

[group:1]> getDeployLog 1

2019-05-26 08:37:45  [group:1]  TableTest             0xd653139b9abffc3fe07573e7bacdfd35210b5576

注: 如果要查看所有的部署合约日志信息,请查看console目录下的deploylog.txt文件。该文件只存储最近10000条部署合约的日志记录。

call

运行call,调用合约。 参数:

  • 合约名称:部署的合约名称(可以带.sol后缀)。
  • 合约地址: 部署合约获取的地址,合约地址可以省略前缀0,例如,0x000ac78可以简写成0xac78。
  • 合约接口名:调用的合约接口名。
  • 参数:由合约接口参数决定。参数由空格分隔,其中字符串、字节类型参数需要加上双引号;数组参数需要加上中括号,比如[1,2,3],数组中是字符串或字节类型,加双引号,例如[“alice”,”bob”],注意数组参数中不要有空格;布尔类型为true或者false。
# 调用HelloWorld的get接口获取name字符串
[group:1]> call HelloWorld.sol 0xc0ce097a5757e2b6e189aa70c7d55770ace47767 get
Hello, World!

# 调用HelloWorld的set接口设置name字符串
[group:1]> call HelloWorld.sol 0xc0ce097a5757e2b6e189aa70c7d55770ace47767 set "Hello, FISCO BCOS"
transaction hash:0xa7c7d5ef8d9205ce1b228be1fe90f8ad70eeb6a5d93d3f526f30d8f431cb1e70

# 调用HelloWorld的get接口获取name字符串,检查设置是否生效
[group:1]> call HelloWorld.sol 0xc0ce097a5757e2b6e189aa70c7d55770ace47767 get
Hello, FISCO BCOS

# 调用TableTest的insert接口插入记录,字段为name, item_id, item_name
[group:1]> call TableTest.sol 0xd653139b9abffc3fe07573e7bacdfd35210b5576 insert "fruit" 1 "apple"
transaction hash:0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1
---------------------------------------------------------------------------------------------
Output
function: insert(string,int256,string)
return type: (int256)
return value: (1)
---------------------------------------------------------------------------------------------
Event logs
event signature: InsertResult(int256) index: 0
event value: (1)
---------------------------------------------------------------------------------------------

# 调用TableTest的select接口查询记录
[group:1]> call TableTest.sol 0xd653139b9abffc3fe07573e7bacdfd35210b5576 select "fruit"
[[fruit], [1], [apple]]

注: TableTest.sol合约代码参考这里

deployByCNS

运行deployByCNS,采用CNS部署合约。用CNS部署的合约,可用合约名直接调用。 参数:

  • 合约名称:部署的合约名称。
  • 合约版本号:部署的合约版本号(长度不能超过40)。
# 部署HelloWorld合约1.0版
[group:1]> deployByCNS HelloWorld.sol 1.0
contract address:0x3554a56ea2905f366c345bd44fa374757fb4696a

# 部署HelloWorld合约2.0版
[group:1]> deployByCNS HelloWorld.sol 2.0
contract address:0x07625453fb4a6459cbf14f5aa4d574cae0f17d92

# 部署TableTest合约
[group:1]> deployByCNS TableTest.sol 1.0
contract address:0x0b33d383e8e93c7c8083963a4ac4a58b214684a8

注:

  • 部署用户编写的合约,只需要将solidity合约文件放到控制台根目录的contracts/solidity/目录下,然后进行部署即可。按tab键可以搜索contracts/solidity/目录下的合约名称。
  • 若需要部署的合约引用了其他合约或library库,引用格式为import "./XXX.sol";。其相关引入的合约和library库均放在contracts/solidity/目录。
  • 如果合约引用了library库,library库文件的名称必须以Lib字符串开始,以便于区分是普通合约与library库文件。library库文件不能单独部署和调用。
  • 由于FISCO BCOS已去除以太币的转账支付逻辑,因此solidity合约的方法不支持使用payable关键字,该关键字会导致solidity合约转换成的java合约文件在编译时失败。
queryCNS

运行queryCNS,根据合约名称和合约版本号(可选参数)查询CNS表记录信息(合约名和合约地址的映射)。 参数:

  • 合约名称:部署的合约名称。
  • 合约版本号:(可选)部署的合约版本号。
[group:1]> queryCNS HelloWorld.sol
---------------------------------------------------------------------------------------------
|                   version                   |                   address                   |
|                     1.0                     | 0x3554a56ea2905f366c345bd44fa374757fb4696a  |
---------------------------------------------------------------------------------------------

[group:1]> queryCNS HelloWorld 1.0
---------------------------------------------------------------------------------------------
|                   version                   |                   address                   |
|                     1.0                     | 0x3554a56ea2905f366c345bd44fa374757fb4696a  |
---------------------------------------------------------------------------------------------
callByCNS

运行callByCNS,采用CNS调用合约,即用合约名直接调用合约。 参数:

  • 合约名称与合约版本号:合约名称与版本号用英文冒号分隔,例如HelloWorld:1.0HelloWorld.sol:1.0。当省略合约版本号时,例如HelloWorldHelloWorld.sol,则调用最新版本的合约。
  • 合约接口名:调用的合约接口名。
  • 参数:由合约接口参数决定。参数由空格分隔,其中字符串、字节类型参数需要加上双引号;数组参数需要加上中括号,比如[1,2,3],数组中是字符串或字节类型,加双引号,例如[“alice”,”bob”];布尔类型为true或者false。
# 调用HelloWorld合约1.0版,通过set接口设置name字符串
[group:1]> callByCNS HelloWorld:1.0 set "Hello,CNS"
transaction hash:0x80bb37cc8de2e25f6a1cdcb6b4a01ab5b5628082f8da4c48ef1bbc1fb1d28b2d

# 调用HelloWorld合约2.0版,通过set接口设置name字符串
[group:1]> callByCNS HelloWorld:2.0 set "Hello,CNS2"
transaction hash:0x43000d14040f0c67ac080d0179b9499b6885d4a1495d3cfd1a79ffb5f2945f64

# 调用HelloWorld合约1.0版,通过get接口获取name字符串
[group:1]> callByCNS HelloWorld:1.0 get
Hello,CNS

# 调用HelloWorld合约最新版(即2.0版),通过get接口获取name字符串
[group:1]> callByCNS HelloWorld get
Hello,CNS2
addSealer

运行addSealer,将节点添加为共识节点。 参数:

  • 节点nodeId
[group:1]> addSealer ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}
addObserver

运行addObserver,将节点添加为观察节点。 参数:

  • 节点nodeId
[group:1]> addObserver ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}
removeNode

运行removeNode,节点退出。通过addSealer命令可以将退出的节点添加为共识节点,通过addObserver命令将节点添加为观察节点。 参数:

  • 节点nodeId
[group:1]> removeNode ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}
setSystemConfigByKey

运行setSystemConfigByKey,以键值对方式设置系统参数。目前设置的系统参数支持tx_count_limit,tx_gas_limit, rpbft_epoch_sealer_numrpbft_epoch_block_num。这些系统参数的键名可以通过tab键补全:

  • tx_count_limit:区块最大打包交易数
  • tx_gas_limit:交易执行允许消耗的最大gas数
  • rpbft_epoch_sealer_num: rPBFT系统配置,一个共识周期内选取的共识节点数目
  • rpbft_epoch_block_num: rPBFT系统配置,一个共识周期出块数目
  • consensus_timeout:PBFT共识过程中,每个区块执行的超时时间,默认为3s,单位为秒

参数:

  • key
  • value
[group:1]> setSystemConfigByKey tx_count_limit 100
{
	"code":0,
	"msg":"success"
}
getSystemConfigByKey

运行getSystemConfigByKey,根据键查询系统参数的值。 参数:

  • key
[group:1]> getSystemConfigByKey tx_count_limit
100
grantPermissionManager

运行grantPermissionManager,授权账户的链管理员权限。参数:

  • 账户地址
[group:1]> grantPermissionManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}

注:权限控制相关命令的示例使用可以参考权限控制使用文档

listPermissionManager

运行listPermissionManager,查询拥有链管理员权限的账户列表。

[group:1]> listPermissionManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                      2                      |
---------------------------------------------------------------------------------------------
revokePermissionManager

运行revokePermissionManager,撤销账户的链管理员权限。 参数:

  • 账户地址
[group:1]> revokePermissionManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
grantUserTableManager

运行grantUserTableManager,授权账户对用户表的写权限。 参数:

  • 表名
  • 账户地址
[group:1]> grantUserTableManager t_test 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
listUserTableManager

运行listUserTableManager,查询拥有对用户表写权限的账号列表。 参数:

  • 表名
[group:1]> listUserTableManager t_test
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                      2                      |
---------------------------------------------------------------------------------------------
revokeUserTableManager

运行revokeUserTableManager,撤销账户对用户表的写权限。 参数:

  • 表名
  • 账户地址
[group:1]> revokeUserTableManager t_test 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
grantDeployAndCreateManager

运行grantDeployAndCreateManager,授权账户的部署合约和创建用户表权限。

参数:

  • 账户地址
[group:1]> grantDeployAndCreateManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
listDeployAndCreateManager

运行listDeployAndCreateManager,查询拥有部署合约和创建用户表权限的账户列表。

[group:1]> listDeployAndCreateManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                      2                      |
---------------------------------------------------------------------------------------------
revokeDeployAndCreateManager

运行revokeDeployAndCreateManager,撤销账户的部署合约和创建用户表权限。 参数:

  • 账户地址
[group:1]> revokeDeployAndCreateManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
grantNodeManager

运行grantNodeManager,授权账户的节点管理权限。参数:

  • 账户地址
[group:1]> grantNodeManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
listNodeManager

运行listNodeManager,查询拥有节点管理的账户列表。

[group:1]> listNodeManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                      2                      |
---------------------------------------------------------------------------------------------
revokeNodeManager

运行revokeNodeManager,撤销账户的节点管理权限。 参数:

  • 账户地址
[group:1]> revokeNodeManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
grantCNSManager

运行grantCNSManager,授权账户的使用CNS权限。参数:

  • 账户地址
[group:1]> grantCNSManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
listCNSManager

运行listCNSManager,查询拥有使用CNS的账户列表。

[group:1]> listCNSManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                      2                      |
---------------------------------------------------------------------------------------------
revokeCNSManager

运行revokeCNSManager,撤销账户的使用CNS权限。参数:

  • 账户地址
[group:1]> revokeCNSManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
grantSysConfigManager

运行grantSysConfigManager,授权账户的修改系统参数权限。参数:

  • 账户地址
[group:1]> grantSysConfigManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
listSysConfigManager

运行listSysConfigManager,查询拥有修改系统参数的账户列表。

[group:1]> listSysConfigManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                      2                      |
---------------------------------------------------------------------------------------------
revokeSysConfigManager

运行revokeSysConfigManager,撤销账户的修改系统参数权限。参数:

  • 账户地址
[group:1]> revokeSysConfigManager 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
grantContractWritePermission

运行grantContractWritePermission,添加账户对合约写接口的调用权限。参数:

  • 合约地址
  • 账户地址
[group:1]> grantContractWritePermission 0xc0ce097a5757e2b6e189aa70c7d55770ace47767 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
listContractWritePermission

运行listContractWritePermission,显示对某个合约的写接口有调用权限的账户。参数:

  • 合约地址
[group:1]> listContractWritePermission 0xc0ce097a5757e2b6e189aa70c7d55770ace47767
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d  |                     11                      |
---------------------------------------------------------------------------------------------
revokeContractWritePermission

运行revokeContractWritePermission,撤销账户对合约的写接口调用权限。参数:

  • 合约地址
  • 账户地址
[group:1]> revokeContractWritePermission 0xc0ce097a5757e2b6e189aa70c7d55770ace47767 0xc0d0e6ccc0b44c12196266548bec4a3616160e7d
{
	"code":0,
	"msg":"success"
}
quit

运行quit、q或exit,退出控制台。

quit
[create sql]

运行create sql语句创建用户表,使用mysql语句形式。

# 创建用户表t_demo,其主键为name,其他字段为item_id和item_name
[group:1]> create table t_demo(name varchar, item_id varchar, item_name varchar, primary key(name))
Create 't_demo' Ok.

注意:

  • 创建表的字段类型均为字符串类型,即使提供数据库其他字段类型,也按照字符串类型设置。
  • 必须指定主键字段。例如创建t_demo表,主键字段为name。
  • 表的主键与关系型数据库中的主键不是相同概念,这里主键取值不唯一,区块链底层处理记录时需要传入主键值。
  • 可以指定字段为主键,但设置的字段自增,非空,索引等修饰关键字不起作用。
desc

运行desc语句查询表的字段信息,使用mysql语句形式。

# 查询t_demo表的字段信息,可以查看表的主键名和其他字段名
[group:1]> desc t_demo
{
    "key":"name",
    "valueFields":"item_id,item_name"
}
[insert sql]

运行insert sql语句插入记录,使用mysql语句形式。

[group:1]> insert into t_demo (name, item_id, item_name) values (fruit, 1, apple1)
Insert OK, 1 row affected.

注意:

  • 插入记录sql语句必须插入表的主键字段值。
  • 输入的值带标点符号、空格或者以数字开头的包含字母的字符串,需要加上双引号,双引号中不允许再用双引号。
[select sql]

运行select sql语句查询记录,使用mysql语句形式。

# 查询包含所有字段的记录
select * from t_demo where name = fruit
{item_id=1, item_name=apple1, name=fruit}
1 row in set.

# 查询包含指定字段的记录
[group:1]> select name, item_id, item_name from t_demo where name = fruit
{name=fruit, item_id=1, item_name=apple1}
1 row in set.

# 插入一条新记录
[group:1]> insert into t_demo values (fruit, 2, apple2)
Insert OK, 1 row affected.

# 使用and关键字连接多个查询条件
[group:1]> select * from t_demo where name = fruit and item_name = apple2
{item_id=2, item_name=apple2, name=fruit}
1 row in set.

# 使用limit字段,查询第1行记录,没有提供偏移量默认为0
[group:1]> select * from t_demo where name = fruit limit 1
{item_id=1, item_name=apple1, name=fruit}
1 row in set.

# 使用limit字段,查询第2行记录,偏移量为1
[group:1]> select * from t_demo where name = fruit limit 1,1
{item_id=2, item_name=apple2, name=fruit}
1 rows in set.

注意:

  • 查询记录sql语句必须在where子句中提供表的主键字段值。
  • 关系型数据库中的limit字段可以使用,提供两个参数,分别offset(偏移量)和记录数(count)。
  • where条件子句只支持and关键字,其他or、in、like、inner、join,union以及子查询、多表联合查询等均不支持。
  • 输入的值带标点符号、空格或者以数字开头的包含字母的字符串,需要加上双引号,双引号中不允许再用双引号。
[update sql]

运行update sql语句更新记录,使用mysql语句形式。

[group:1]> update t_demo set item_name = orange where name = fruit and item_id = 1
Update OK, 1 row affected.

注意:

  • 更新记录sql语句的where子句必须提供表的主键字段值。
  • 输入的值带标点符号、空格或者以数字开头的包含字母的字符串,需要加上双引号,双引号中不允许再用双引号。
[delete sql]

运行delete sql语句删除记录,使用mysql语句形式。

[group:1]> delete from t_demo where name = fruit and item_id = 1
Remove OK, 1 row affected.

注意:

  • 删除记录sql语句的where子句必须提供表的主键字段值。
  • 输入的值带标点符号、空格或者以数字开头的包含字母的字符串,需要加上双引号,双引号中不允许再用双引号。

重要

执行`freezeContract`、`unfreezeContract`和`grantContractStatusManager`三个合约管理的控制台命令,需指定私钥启动控制台,用于进行操作权限判断。该私钥为部署指定合约时所用的账号私钥,即部署合约时也许指定私钥启动控制台。

freezeContract

运行freezeContract,对指定合约进行冻结操作。参数:

  • 合约地址:部署合约可以获得合约地址,其中0x前缀非必须。
[group:1]> freezeContract 0xcc5fc5abe347b7f81d9833f4d84a356e34488845
{
    "code":0,
    "msg":"success"
}
unfreezeContract

运行unfreezeContract,对指定合约进行解冻操作。参数:

  • 合约地址:部署合约可以获得合约地址,其中0x前缀非必须。
[group:1]> unfreezeContract 0xcc5fc5abe347b7f81d9833f4d84a356e34488845
{
    "code":0,
    "msg":"success"
}
grantContractStatusManager

运行grantContractStatusManager,用于已有权限账号给其他账号授予指定合约的合约管理权限。参数:

  • 合约地址:部署合约可以获得合约地址,其中0x前缀非必须。
  • 账号地址:tx.origin,其中0x前缀非必须。
[group:1]> grantContractStatusManager 0x30d2a17b6819f0d77f26dd3a9711ae75c291f7f1 0x965ebffc38b309fa706b809017f360d4f6de909a
{
    "code":0,
    "msg":"success"
}
getContractStatus

运行getContractStatus,查询指定合约的状态。参数:

  • 合约地址:部署合约可以获得合约地址,其中0x前缀非必须。
[group:1]> getContractStatus 0xcc5fc5abe347b7f81d9833f4d84a356e34488845
The contract is available.
listContractStatusManager

运行listContractStatusManager,查询能管理指定合约的权限账号列表。参数:

  • 合约地址:部署合约可以获得合约地址,其中0x前缀非必须。
[group:1]> listContractStatusManager 0x30d2a17b6819f0d77f26dd3a9711ae75c291f7f1
[
    "0x0cc9b73b960323816ac5f52806257184c08b5ac0",
    "0x965ebffc38b309fa706b809017f360d4f6de909a"
]
grantCommitteeMember

添加治理委员会委员,如果当前没有委员,则直接添加成功,否则判断投票账号是否有权限投票,如有则记录投票并检查投票是否生效。委员账号可以添加运维、管理链系统配置、节点增删等,详情参考这里。参数:

  • 账号地址:投票添加该账号为委员
[group:1]> grantCommitteeMember 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a
{
    "code":0,
    "msg":"success"
}
revokeCommitteeMember

撤销治理委员会委员判断投票账号是否有权限投票,如有则记录投票并检查投票是否生效。参数:

  • 账号地址:投票撤销该账号的委员权限
[group:1]> revokeCommitteeMember 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a
{
    "code":0,
    "msg":"success"
}
listCommitteeMembers

查询所有治理委员会委员。

[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
| 0x85961172229aec21694d742a5bd577bedffcfec3  |                      2                      |
---------------------------------------------------------------------------------------------
updateThreshold

投票更新生效阈值,判断投票账号是否是委员,是则计票并判断是否生效。参数:

  • 生效阈值:取值范围[0,99]
[group:1]> updateThreshold 75
{
    "code":0,
    "msg":"success"
}
queryThreshold

查询生效阈值。

[group:1]> queryThreshold
Effective threshold : 50%
queryCommitteeMemberWeight

查询治理委员会委员的票数。

[group:1]> queryCommitteeMemberWeight 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a
Account: 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a Weight: 1
updateCommitteeMemberWeight

投票更新委员账号的票数,检查投票账号是否有权限,有则计票并检查是否生效,生效后该委员账号投票操作相当于设置的票数。参数:

  • 委员账号:被投票修改票数的委员账号
  • 投票权重:希望修改的权重
[group:1]> updateCommitteeMemberWeight 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a 2
{
    "code":0,
    "msg":"success"
}
grantOperator

添加运维账号,运维角色拥有部署合约、创建用户表和管理CNS的权限,治理委员会委员可以添加运维,如果当前没有委员,则不限制。参数:

  • 账号地址:添加该账号为运维
[group:1]> grantOperator 0x283f5b859e34f7fd2cf136c07579dcc72423b1b2
{
    "code":0,
    "msg":"success"
}
revokeOperator

撤销账号的运维权限,委员可以操作,如果当前没有委员,则不限制。参数:

  • 账号地址:撤销该账号的运维权限
[group:1]> revokeOperator 0x283f5b859e34f7fd2cf136c07579dcc72423b1b2
{
    "code":0,
    "msg":"success"
}
listOperators

查询有运维权限的账号。

[group:1]> listOperators
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x283f5b859e34f7fd2cf136c07579dcc72423b1b2  |                      1                      |
---------------------------------------------------------------------------------------------
freezeAccount

运行freezeAccount,对指定账号进行冻结操作。对没有发送过交易的账号,冻结操作将提示该账号地址不存在。参数:

  • 账号地址:tx.origin,其中0x前缀必须。
[group:1]> freezeAccount 0xcc5fc5abe347b7f81d9833f4d84a356e34488845
{
    "code":0,
    "msg":"success"
}
unfreezeAccount

运行unfreezeAccount,对指定账号进行解冻操作。对没有发送过交易的账号,解冻操作将提示该账号地址不存在。参数:

  • 账号地址:tx.origin,其中0x前缀必须。
[group:1]> unfreezeAccount 0xcc5fc5abe347b7f81d9833f4d84a356e34488845
{
    "code":0,
    "msg":"success"
}
getAccountStatus

运行getAccountStatus,查询指定账号的状态。对没有发送过交易的账号,查询操作将提示该账号地址不存在。参数:

  • 账号地址:tx.origin,其中0x前缀必须。
[group:1]> getAccountStatus 0xcc5fc5abe347b7f81d9833f4d84a356e34488845
The account is available.

附录:Java环境配置

Ubuntu环境安装Java
# 安装默认Java版本(Java 8或以上)
sudo apt install -y default-jdk
# 查询Java版本
java -version
CentOS环境安装Java

注意:CentOS下OpenJDK无法正常工作,需要安装OracleJDK下载链接

# 创建新的文件夹,安装Java 8或以上的版本,将下载的jdk放在software目录
# 从Oracle官网(https://www.oracle.com/technetwork/java/javase/downloads/index.html)选择Java 8或以上的版本下载,例如下载jdk-8u201-linux-x64.tar.gz
$ mkdir /software
# 解压jdk
$ tar -zxvf jdk-8u201-linux-x64.tar.gz
# 配置Java环境,编辑/etc/profile文件
$ vim /etc/profile
# 打开以后将下面三句输入到文件里面并退出
export JAVA_HOME=/software/jdk-8u201  #这是一个文件目录,非文件
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
# 生效profile
$ source /etc/profile
# 查询Java版本,出现的版本是自己下载的版本,则安装成功。
java -version

账户管理

FISCO BCOS使用账户来标识和区分每一个独立的用户。在采用公私钥体系的区块链系统里,每一个账户对应着一对公钥和私钥。其中,由公钥经哈希等安全的单向性算法计算后得到地址字符串被用作该账户的账户名,即账户地址,为了与智能合约的地址相区别和一些其他的历史原因,账户地址也常被称之外部账户地址。而仅有用户知晓的私钥则对应着传统认证模型中的密码。用户需要通过安全的密码学协议证明其知道对应账户的私钥,来声明其对于该账户的所有权,以及进行敏感的账户操作。

重要

在之前的其他教程中,为了简化操作,使用了工具提供的默认的账户进行操作。但在实际应用部署中,用户需要创建自己的账户,并妥善保存账户私钥,避免账户私钥泄露等严重的安全问题。

本文将具体介绍账户的创建、存储和使用方式,要求阅读者有一定的Linux操作基础。

FISCO BCOS提供了脚本和Web3SDK用以创建账户,同时也提供了Web3SDK和控制台来存储账户私钥。用户可以根据需求选择将账户私钥存储为PEM或者PKCS12格式的文件。其中,PEM格式使用明文存储私钥,而PKCS12使用用户提供的口令加密存储私钥。

账户的创建

使用脚本创建账户

国密生成账户脚本get_gm_account.sh与非国密get_account.sh选项和使用方式一致,请参考操作即可,不再赘述。

1. 获取脚本
curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/get_account.sh && chmod u+x get_account.sh && bash get_account.sh -h

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/get_account.sh && chmod u+x get_account.sh && bash get_account.sh -h

国密版本请使用下面的指令获取脚本

curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/get_gm_account.sh && chmod u+x get_gm_account.sh && bash get_gm_account.sh -h

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/get_gm_account.sh && chmod u+x get_gm_account.sh && bash get_gm_account.sh -h
  • get_gm_account需要下载tassl,如果无法下载,请尝试 curl -#LO https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/tools/tassl-1.0.2/tassl.tar.gz ,解压放在~/.fisco/tassl,1.0.9及以下版本放在~/.tassl

执行上面的指令,看到如下输出则下载到了正确的脚本,否则请重试。

Usage: ./get_account.sh
    default       generate account and store private key in PEM format file
    -p            generate account and store private key in PKCS12 format file
    -k [FILE]     calculate address of PEM format [FILE]
    -P [FILE]     calculate address of PKCS12 format [FILE]
    -h Help
2. 使用脚本生成PEM格式私钥
  • 生成私钥与地址
bash get_account.sh

执行上面的命令,可以得到类似下面的输出,包括账户地址和以账户地址为文件名的私钥PEM文件。

[INFO] Account Address   : 0xee5fffba2da55a763198e361c7dd627795906ead
[INFO] Private Key (pem) : accounts/0xee5fffba2da55a763198e361c7dd627795906ead.pem
  • 指定PEM私钥文件计算账户地址
bash get_account.sh -k accounts/0xee5fffba2da55a763198e361c7dd627795906ead.pem

执行上面的命令,结果如下

[INFO] Account Address   : 0xee5fffba2da55a763198e361c7dd627795906ead
3. 使用脚本生成PKCS12格式私钥
  • 生成私钥与地址
bash get_account.sh -p

执行上面的命令,可以得到类似下面的输出,按照提示输入密码,生成对应的p12文件。

Enter Export Password:
Verifying - Enter Export Password:
[INFO] Account Address   : 0x02f1b23310ac8e28cb6084763d16b25a2cc7f5e1
[INFO] Private Key (p12) : accounts/0x02f1b23310ac8e28cb6084763d16b25a2cc7f5e1.p12
  • 指定p12私钥文件计算账户地址,按提示输入p12文件密码
bash get_account.sh -P accounts/0x02f1b23310ac8e28cb6084763d16b25a2cc7f5e1.p12

执行上面的命令,结果如下

Enter Import Password:
MAC verified OK
[INFO] Account Address   : 0x02f1b23310ac8e28cb6084763d16b25a2cc7f5e1
调用Web3SDK创建账户
//创建普通账户
EncryptType.encryptType = 0;
//创建国密账户,向国密区块链节点发送交易需要使用国密账户
// EncryptType.encryptType = 1;
Credentials credentials = GenCredential.create();
//账户地址
String address = credentials.getAddress();
//账户私钥
String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
//账户公钥
String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);

更多操作详情,请参见创建并使用指定外部账号

账户的存储

  • web3SDK支持通过私钥字符串或者文件加载,所以账户的私钥可以存储在数据库中或者本地文件。
  • 本地文件支持两种存储格式,其中PKCS12加密存储,而PEM格式明文存储。
  • 开发业务时可以根据实际业务场景选择私钥的存储管理方式。

账户的使用

控制台加载私钥文件

控制台提供账户生成脚本get_account.sh,生成的的账户文件在accounts目录下,控制台加载的账户文件必须放置在该目录下。 控制台启动方式有如下几种:

./start.sh
./start.sh groupID
./start.sh groupID -pem pemName
./start.sh groupID -p12 p12Name
默认启动

控制台随机生成一个账户,使用控制台配置文件指定的群组号启动。

./start.sh
指定群组号启动

控制台随机生成一个账户,使用命令行指定的群组号启动。

./start.sh 2
  • 注意:指定的群组在控制台配置文件中需要配置bean。
使用PEM格式私钥文件启动
  • 使用指定的pem文件的账户启动,输入参数:群组号、-pem、pem文件路径
./start.sh 1 -pem accounts/0xebb824a1122e587b17701ed2e512d8638dfb9c88.pem
使用PKCS12格式私钥文件启动
  • 使用指定的p12文件的账户,需要输入密码,输入参数:群组号、-p12、p12文件路径
./start.sh 1 -p12 accounts/0x5ef4df1b156bc9f077ee992a283c2dbb0bf045c0.p12
Enter Export Password:
Web3SDK加载私钥文件

如果通过账户生成脚本get_accounts.sh生成了PEM或PKCS12格式的账户私钥文件,则可以通过加载PEM或PKCS12账户私钥文件使用账户。加载私钥有两个类:P12Manager和PEMManager,其中,P12Manager用于加载PKCS12格式的私钥文件,PEMManager用于加载PEM格式的私钥文件。

  • P12Manager用法举例: 在applicationContext.xml中配置PKCS12账户的私钥文件路径和密码
<bean id="p12" class="org.fisco.bcos.channel.client.P12Manager" init-method="load" >
	<property name="password" value="123456" />
	<property name="p12File" value="classpath:0x0fc3c4bb89bd90299db4c62be0174c4966286c00.p12" />
</bean>

开发代码

//加载Bean
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
P12Manager p12 = context.getBean(P12Manager.class);
//提供密码获取ECKeyPair,密码在生产p12账户文件时指定
ECKeyPair p12KeyPair = p12.getECKeyPair(p12.getPassword());

//以十六进制串输出私钥和公钥
System.out.println("p12 privateKey: " + p12KeyPair.getPrivateKey().toString(16));
System.out.println("p12 publicKey: " + p12KeyPair.getPublicKey().toString(16));

//生成web3sdk使用的Credentials
Credentials credentials =  GenCredential.create(p12KeyPair.getPrivateKey().toString(16));
System.out.println("p12 Address: " + credentials.getAddress());
  • PEMManager使用举例

在applicationContext.xml中配置PEM账户的私钥文件路径

<bean id="pem" class="org.fisco.bcos.channel.client.PEMManager" init-method="load" >
	<property name="pemFile" value="classpath:0x0fc3c4bb89bd90299db4c62be0174c4966286c00.pem" />
</bean>

使用代码加载

//加载Bean
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-keystore-sample.xml");
PEMManager pem = context.getBean(PEMManager.class);
ECKeyPair pemKeyPair = pem.getECKeyPair();

//以十六进制串输出私钥和公钥
System.out.println("PEM privateKey: " + pemKeyPair.getPrivateKey().toString(16));
System.out.println("PEM publicKey: " + pemKeyPair.getPublicKey().toString(16));

//生成web3sdk使用的Credentials
Credentials credentialsPEM = GenCredential.create(pemKeyPair.getPrivateKey().toString(16));
System.out.println("PEM Address: " + credentialsPEM.getAddress());

账户地址的计算

FISCO BCOS的账户地址由ECDSA公钥计算得来,对ECDSA公钥的16进制表示计算keccak-256sum哈希,取计算结果的后20字节的16进制表示作为账户地址,每个字节需要两个16进制数表示,所以账户地址长度为40。FISCO BCOS的账户地址与以太坊兼容。 注意keccak-256sum与SHA3不相同,详情参考这里

以太坊地址生成

1. 生成ECDSA私钥

首先,我们使用OpenSSL生成椭圆曲线私钥,椭圆曲线的参数使用secp256k1。执行下面的命令,生成PEM格式的私钥并保存在ecprivkey.pem文件中。

openssl ecparam -name secp256k1 -genkey -noout -out ecprivkey.pem

执行下面的指令,查看文件内容。

cat ecprivkey.pem

可以看到类似下面的输出。

-----BEGIN EC PRIVATE KEY-----
MHQCAQEEINHaCmLhw9S9+vD0IOSUd9IhHO9bBVJXTbbBeTyFNvesoAcGBSuBBAAK
oUQDQgAEjSUbQAZn4tzHnsbeahQ2J0AeMu0iNOxpdpyPo3j9Diq3qdljrv07wvjx
zOzLpUNRcJCC5hnU500MD+4+Zxc8zQ==
-----END EC PRIVATE KEY-----

接下来根据私钥计算公钥,执行下面的指令

openssl ec -in ecprivkey.pem -text -noout 2>/dev/null| sed -n '7,11p' | tr -d ": \n" | awk '{print substr($0,3);}'

可以得到类似下面的输出

8d251b400667e2dcc79ec6de6a143627401e32ed2234ec69769c8fa378fd0e2ab7a9d963aefd3bc2f8f1cceccba54351709082e619d4e74d0c0fee3e67173ccd
2. 根据公钥计算地址

本节我们根据公钥计算对应的账户地址。我们需要获取keccak-256sum工具,可以从这里下载

openssl ec -in ecprivkey.pem -text -noout 2>/dev/null| sed -n '7,11p' | tr -d ": \n" | awk '{print substr($0,3);}' | ./keccak-256sum -x -l | tr -d ' -' | tail -c 41

得到类似下面的输出,就是计算得出的账户地址。

dcc703c0e500b653ca82273b7bfad8045d85a470

链上信使协议

介绍

链上信使协议AMOP(Advanced Messages Onchain Protocol)系统旨在为联盟链提供一个安全高效的消息信道,联盟链中的各个机构,只要部署了区块链节点,无论是共识节点还是观察节点,均可使用AMOP进行通讯,AMOP有如下优势:

  • 实时:AMOP消息不依赖区块链交易和共识,消息在节点间实时传输,延时在毫秒级。
  • 可靠:AMOP消息传输时,自动寻找区块链网络中所有可行的链路进行通讯,只要收发双方至少有一个链路可用,消息就保证可达。
  • 高效:AMOP消息结构简洁、处理逻辑高效,仅需少量cpu占用,能充分利用网络带宽。
  • 安全:AMOP的所有通讯链路使用SSL加密,加密算法可配置,支持身份认证机制。
  • 易用:使用AMOP时,无需在SDK做任何额外配置。

逻辑架构

_images/AMOP.jpg 以银行典型IDC架构为例,各区域概述:

  • 链外区域:机构内部的业务服务区,此区域内的业务子系统使用区块链SDK,连接到区块链节点。
  • 区块链P2P网络:此区域部署各机构的区块链节点,此区域为逻辑区域,区块链节点也可部署在机构内部。

配置

AMOP无需任何额外配置,以下为Web3SDK的配置案例。 SDK配置(Spring Bean):

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans   
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
          http://www.springframework.org/schema/tx   
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
          http://www.springframework.org/schema/aop   
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    
<!-- AMOP消息处理线程池配置,根据实际需要配置 -->
<bean id="pool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  <property name="corePoolSize" value="50" />
  <property name="maxPoolSize" value="100" />
  <property name="queueCapacity" value="500" />
  <property name="keepAliveSeconds" value="60" />
  <property name="rejectedExecutionHandler">
    <bean class="java.util.concurrent.ThreadPoolExecutor.AbortPolicy" />
  </property>
</bean>

<!-- 群组信息配置 -->
  <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
      <property name="allChannelConnections">
        <list>
          <bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
            <property name="groupId" value="1" />
            <property name="connectionsStr">
              <list>
                <value>127.0.0.1:20200</value> <!-- 格式:IP:端口 -->
                <value>127.0.0.1:20201</value>
              </list>
            </property>
          </bean>
        </list>
      </property>
    </bean>

  <!-- 区块链节点信息配置 -->
    <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
      <property name="groupId" value="1" />
      <property name="orgID" value="fisco" />
      <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
      <!-- 如果需要使用topic认证功能,请将下面的注释去除 -->
      <!-- <property name="topic2KeyInfo" ref="amopVerifyTopicToKeyInfo"></property>--> 
    </bean>

  <!--  这里配置的是topic到公私钥配置信息的映射关系,这里只配置了一个topic,可以通过新增entry的方式来新增映射关系。-->
  <!--
    <bean class="org.fisco.bcos.channel.handler.AMOPVerifyTopicToKeyInfo" id="amopVerifyTopicToKeyInfo">
		<property name="topicToKeyInfo">
			<map>
				<entry key="${topicname}" value-ref="AMOPVerifyKeyInfo_${topicname}" />
			</map>
		</property>
	</bean>
  -->

  <!--  在topic的生产者端,请将如下的注释打开,并配置公钥文件,
        每个需要身份验证的消费者都拥有不同的公私钥对,请列出所有需要身份验证的消费者的公钥文件。
  -->
  <!--
	<bean class="org.fisco.bcos.channel.handler.AMOPVerifyKeyInfo" id="AMOPVerifyKeyInfo_${topicname}">
		<property name="publicKey">
			<list>
				<value>classpath:$consumer_public_key_1.pem$</value>
				<value>classpath:$consumer_public_key_2.pem$</value>
			</list>
		</property>
	</bean>
	--> 

  <!--  在topic的消费者端,请将如下的注释打开,并配置私钥文件,程序使用私钥向相应的主题生产者验证您的身份。-->
  <!--
	<bean class="org.fisco.bcos.channel.handler.AMOPVerifyKeyInfo" id="AMOPVerifyKeyInfo_${topicname}">
		<property name="privateKey" value="classpath:$consumer_private_key.pem$"></property>
	</bean>
	--> 

SDK使用

AMOP的消息收发基于topic(主题)机制,服务端首先设置一个topic,客户端往该topic发送消息,服务端即可收到。

AMOP支持在同一个区块链网络中有多个topic收发消息,topic支持任意数量的服务端和客户端,当有多个服务端关注同一个topic时,该topic的消息将随机下发到其中一个可用的服务端。

服务端代码案例:

package org.fisco.bcos.channel.test.amop;

import org.fisco.bcos.channel.client.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.HashSet;
import java.util.Set;

public class Channel2Server {
    static Logger logger = LoggerFactory.getLogger(Channel2Server.class);

    public static void main(String[] args) throws Exception {
        if (args.length < 1) {
          System.out.println("Param: topic");
          return;
        }

        String topic = args[0];

        logger.debug("init Server");

        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Service service = context.getBean(Service.class);

        // 设置topic,支持多个topic
        Set<String> topics = new HashSet<String>();
        topics.add(topic);
        service.setTopics(topics);

        // 处理消息的PushCallback类,参见Callback代码
        PushCallback cb = new PushCallback();
        service.setPushCallback(cb);

        System.out.println("3s...");
        Thread.sleep(1000);
        System.out.println("2s...");
        Thread.sleep(1000);
        System.out.println("1s...");
        Thread.sleep(1000);

        System.out.println("start test");
        System.out.println("===================================================================");
        
        // 启动服务
        service.run();
    }
}

服务端的PushCallback类案例:

package org.fisco.bcos.channel.test.amop;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.fisco.bcos.channel.client.ChannelPushCallback;
import org.fisco.bcos.channel.dto.ChannelPush;
import org.fisco.bcos.channel.dto.ChannelResponse;

class PushCallback extends ChannelPushCallback {
    static Logger logger = LoggerFactory.getLogger(PushCallback.class);

    // onPush方法,在收到AMOP消息时被调用
    @Override
    public void onPush(ChannelPush push) {
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        logger.debug("push:" + push.getContent());

        System.out.println(df.format(LocalDateTime.now()) + "server:push:" + push.getContent());

        // 回包消息
        ChannelResponse response = new ChannelResponse();
        response.setContent("receive request seq:" + String.valueOf(push.getMessageID()));
        response.setErrorCode(0);

        push.sendResponse(response);
    }
}

客户端案例:

package org.fisco.bcos.channel.test.amop;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.fisco.bcos.channel.client.Service;
import org.fisco.bcos.channel.dto.ChannelRequest;
import org.fisco.bcos.channel.dto.ChannelResponse;

public class Channel2Client {
    static Logger logger = LoggerFactory.getLogger(Channel2Client.class);

    public static void main(String[] args) throws Exception {
        if(args.length < 2) {
            System.out.println("param: target topic total number of request");
            return;
        }

        String topic = args[0];
        Integer count = Integer.parseInt(args[1]);
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

        Service service = context.getBean(Service.class);
        service.run();

        System.out.println("3s ...");
        Thread.sleep(1000);
        System.out.println("2s ...");
        Thread.sleep(1000);
        System.out.println("1s ...");
        Thread.sleep(1000);

        System.out.println("start test");
        System.out.println("===================================================================");
        for (Integer i = 0; i < count; ++i) {
          Thread.sleep(2000);  // 建立连接需要一点时间,如果立即发送消息会失败

          ChannelRequest request = new ChannelRequest();
          request.setToTopic(topic);  // 设置消息topic
          request.setMessageID(service.newSeq());  // 消息序列号,唯一标识某条消息,可用newSeq()随机生成
          request.setTimeout(5000);  // 消息的超时时间

          request.setContent("request seq:" + request.getMessageID());  // 发送的消息内容
          System.out.println(df.format(LocalDateTime.now()) + " request seq:" + String.valueOf(request.getMessageID())
          + ", Content:" + request.getContent());

          ChannelResponse response = service.sendChannelMessage2(request);  // 发送消息

          System.out.println(df.format(LocalDateTime.now()) + "response seq:" + String.valueOf(response.getMessageID())
                + ", ErrorCode:" + response.getErrorCode() + ", Content:" + response.getContent());
        }
    }
}

注解

  • 如果使用 2.5.0 版本的 FISCO BCOS,那么服务端和客户端需要连接不同的节点

Topic认证功能

在普通的配置下,任何一个监听了某topic的接收者都能接受到发送者推送的消息。但在某些场景下,发送者只希望特定的接收者能接收到消息,不希望无关的接收者能任意的监听此topic。在此场景下,需要使用Topic认证功能。 认证功能是指对于特定的topic消息,允许通过认证的接收者接收消息。 2.1.0及之后的sdk和节点版本新增了topic认证功能,默认的配置没有开启认证功能,需要用到认证功能的话请参考配置文件配置配置好公私钥,公私钥的生成方式请参考生成公私钥脚本。使用过程如下:

  • 1:接收者使用生成公私钥脚本生成公私钥文件,私钥保留,公钥给生产者。
  • 2:参考配置案例将配置文件配好。启动接收端和发送端进行收发消息。

假定链外系统1是消息发送者,链外系统2是消息接收者。链外系统2宣称监听topic T1的消息,topic认证流程图如下:

_images/AMOP_AUTHOR.jpg

  • 1:链外系统2连接Node2,宣称监听T1,Node2将T1加入到topic列表,并将seq加1。同时每5秒同步seq到其他节点。
  • 2:Node1收到seq之后,对比本地seq和同步过来的seq,不一致从Node2获取topic列表,并将topic列表更新到p2p的topic列表,对于需要认证且还没认证的topic,状态置为待认证。Node1遍历列表。针对每一个待认证的topic,进行如下操作:
    • 2.1:Node1往Node1推送消息(消息类型0x37),请求链外系统1发起topic认证流程。
    • 2.2:链外系统1接收到消息之后,生成随机数,并使用amop消息(消息类型0x30)将消息发送出去,并监听回包。
    • 2.3:消息经过 链外系统1–>Node1–>Node2–>链外系统2的路由,链外系统2接收到消息后解析出随机数并使用私钥签名随机数。
    • 2.4:签名包(消息类型0x31)经过 链外系统2–>Node2–>Node1->链外系统1的路由,链外系统1接收到签名包之后,解析出签名并使用公钥验证签名。
    • 2.5:链外系统1验证签名后,发送消息(消息类型0x38),请求节点更新topic状态(认证成功或者认证失败)。
  • 3:如果认证成功,链外系统的一条消息到达Node1之后,Node1会将这条消息转发给Node2,Node2会将消息推送给链外系统2。

topic认证功能配置

默认提供的配置文件不包括认证功能,需要使用认证功能,请参考如下配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans   
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
          http://www.springframework.org/schema/tx   
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
          http://www.springframework.org/schema/aop   
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    
<!-- AMOP消息处理线程池配置,根据实际需要配置 -->
<bean id="pool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  <property name="corePoolSize" value="50" />
  <property name="maxPoolSize" value="100" />
  <property name="queueCapacity" value="500" />
  <property name="keepAliveSeconds" value="60" />
  <property name="rejectedExecutionHandler">
    <bean class="java.util.concurrent.ThreadPoolExecutor.AbortPolicy" />
  </property>
</bean>

<!-- 群组信息配置 -->
  <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
      <property name="allChannelConnections">
        <list>
          <bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
            <property name="groupId" value="1" />
            <property name="connectionsStr">
              <list>
                <value>127.0.0.1:20200</value> <!-- 格式:IP:端口 -->
                <value>127.0.0.1:20201</value>
              </list>
            </property>
          </bean>
        </list>
      </property>
    </bean>

  <!-- 区块链节点信息配置 -->
    <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
      <property name="groupId" value="1" />
      <property name="orgID" value="fisco" />
      <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
        <!--  topic认证功能的配置项 -->
      <property name="topic2KeyInfo" ref="amopVerifyTopicToKeyInfo"></property>
    </bean>

  <!--  这里配置的是topic到公私钥配置信息的映射关系,这里只配置了一个topic,可以通过新增entry的方式来新增映射关系。-->
    <bean class="org.fisco.bcos.channel.handler.AMOPVerifyTopicToKeyInfo" id="amopVerifyTopicToKeyInfo">
		<property name="topicToKeyInfo">
			<map>
				<entry key="${topicname}" value-ref="AMOPVerifyKeyInfo_${topicname}" />
			</map>
		</property>
	</bean>

  <!--  在topic的生产者端,请在这里配置公钥文件,每个需要身份验证的消费者 都拥有不同的公私钥对,
        请列出所有需要身份验证的消费者的公钥文件。 程序启动前请确保所有的公钥文件都存在于web3sdk的conf目录下,
        文件名分别为$consumer_public_key_1.pem$,$consumer_public_key_2.pem$(请将这2个变量替换为实际文件名),如果不需要两个公钥文件,请将其中一行删除并替换变量名,可以通过新增行的方式来增加公钥文件配置。-->
	<bean class="org.fisco.bcos.channel.handler.AMOPVerifyKeyInfo" id="AMOPVerifyKeyInfo_${topicname}">
		<property name="publicKey">
			<list>
				<value>classpath:$consumer_public_key_1.pem$</value>
				<value>classpath:$consumer_public_key_2.pem$</value>
			</list>
		</property>
	</bean>

  <!--  在topic的消费者端,请在这里配置私钥文件,程序使用私钥向相应的主题生产者验证您的身份。
        程序启动前请确保私钥文件存在于web3sdk的conf目录下,文件名为$consumer_private_key.pem$(请将变量替换为实际文件名)。-->
	<bean class="org.fisco.bcos.channel.handler.AMOPVerifyKeyInfo" id="AMOPVerifyKeyInfo_${topicname}">
		<property name="privateKey" value="classpath:$consumer_private_key.pem$"></property>
	</bean>

配置需要重启才可以生效,配置修改完成后,请重启基于web3sdk的应用程序。

测试

按上述说明配置好后,用户指定一个主题:topic,执行以下两个命令可以进行测试。

单播文本

启动AMOP服务端:

java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2Server [topic]

启动AMOP客户端:

java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2Client [topic] [消息条数]

AMOP除了支持单播文本,还支持发送二进制,多播以及身份认证机制。相应的测试命令如下:

单播二进制,多播文本,多播二进制

启动AMOP服务端:

java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2Server [topic]

启动AMOP客户端:

#单播二进制
java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2ClientBin [topic] [filename]
#多播文本
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.channel.test.amop.Channel2ClientMulti [topic]  [消息条数]
#多播二进制
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.channel.test.amop.Channel2ClientMultiBin [topic]  [filename]
带认证机制的单播文本,单播二进制,多播文本,多播二进制

在使用认证机制前,请确保参考配置文件配置好了用于认证的公私钥对。 启动AMOP服务端:

java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2ServerNeedVerify [topic]

启动AMOP客户端:

#带认证机制的单播文本
java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2ClientNeedVerify [topic] [消息条数]
#带认证机制的单播二进制
java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2ClientBinNeedVerify [topic] [filename]
#带认证机制的多播文本
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.channel.test.amop.Channel2ClientMultiNeedVerify [topic]  [消息条数]
#带认证机制的多播二进制
java -cp 'conf/:lib/*:apps/*' org.fisco.bcos.channel.test.amop.Channel2ClientMultiBinNeedVerify [topic]  [filename]

错误码

  • 99:发送消息失败,AMOP经由所有链路的尝试后,消息未能发到服务端,建议使用发送时生成的seq,检查链路上各个节点的处理情况。
  • 100:区块链节点之间经由所有链路的尝试后,消息未能发送到可以接收该消息的节点,和错误码‘99’一样,建议使用发送时生成的‘seq’,检查链路上各个节点的处理情况。
  • 101:区块链节点往Sdk推送消息,经由所有链路的尝试后,未能到达Sdk端,和错误码‘99’一样,建议使用发送时生成的‘seq’,检查链路上各个节点以及Sdk的处理情况。
  • 102:消息超时,建议检查服务端是否正确处理了消息,带宽是否足够。
  • 103:因节点出带宽限制,SDK发到节点的AMOP请求被拒绝。

智能合约开发

FISCO BCOS平台目前支持Solidity及Precompiled两类合约形式。

  • Solidity合约与以太坊相同,用Solidity语法实现,最高支持0.5.2版本。
  • KVTable合约的读写接口与Table合约的CRUD接口通过在Solidity合约中支持分布式存储预编译合约,可以实现将Solidity合约中数据存储在FISCO BCOS平台AMDB的表结构中,实现合约逻辑与数据的分离。
  • 预编译(Precompiled)合约使用C++开发,内置于FISCO BCOS平台,相比于Solidity合约具有更好的性能,其合约接口需要在编译时预先确定,适用于逻辑固定但需要共识的场景,例如群组配置。关于预编译合约的开发将在下一节进行介绍。

使用KVTable合约读写接口

注解

  • 为实现AMDB创建的表可被多个合约共享访问,其表名是群组内全局可见且唯一的,所以无法在同一条链上的同一个群组中,创建多个表名相同的表
  • KVTable功能在2.3.0版本添加,2.3.0以上版本的链可以使用此功能。

KVTable合约实现键值型读写数据的方式,KVTable合约接口声明如下:

pragma solidity ^0.4.24;

contract KVTableFactory {
    function openTable(string) public view returns (KVTable);
    // 创建KVTable,参数分别是表名、主键列名、以逗号分割的字段名,字段可以有多个
    function createTable(string, string, string) public returns (int256);
}

//一条记录
contract Entry {
    function getInt(string) public constant returns (int256);
    function getUInt(string) public constant returns (uint256);
    function getAddress(string) public constant returns (address);
    function getBytes64(string) public constant returns (bytes1[64]);
    function getBytes32(string) public constant returns (bytes32);
    function getString(string) public constant returns (string);

    function set(string, int256) public;
    function set(string, uint256) public;
    function set(string, string) public;
    function set(string, address) public;
}

//KVTable 每个键对应一条entry
contract KVTable {
    function get(string) public view returns (bool, Entry);
    function set(string, Entry) public returns (int256);
    function newEntry() public view returns (Entry);
}

提供一个合约案例KVTableTest.sol,代码如下:

pragma solidity ^0.4.24;
import "./Table.sol";

contract KVTableTest {
    event SetResult(int256 count);

    KVTableFactory tableFactory;
    string constant TABLE_NAME = "t_kvtest";

    constructor() public {
        //The fixed address is 0x1010 for KVTableFactory
        tableFactory = KVTableFactory(0x1010);
        tableFactory.createTable(TABLE_NAME, "id", "item_price,item_name");
    }

    //get record
    function get(string id) public view returns (bool, int256, string) {
        KVTable table = tableFactory.openTable(TABLE_NAME);
        bool ok = false;
        Entry entry;
        (ok, entry) = table.get(id);
        int256 item_price;
        string memory item_name;
        if (ok) {
            item_price = entry.getInt("item_price");
            item_name = entry.getString("item_name");
        }
        return (ok, item_price, item_name);
    }

    //set record
    function set(string id, int256 item_price, string item_name)
        public
        returns (int256)
    {
        KVTable table = tableFactory.openTable(TABLE_NAME);
        Entry entry = table.newEntry();
        // the length of entry's field value should < 16MB
        entry.set("id", id);
        entry.set("item_price", item_price);
        entry.set("item_name", item_name);
        // the first parameter length of set should <= 255B
        int256 count = table.set(id, entry);
        emit SetResult(count);
        return count;
    }
}

KVTableTest.sol调用了KVTable合约,实现的是创建用户表t_kvtest,并对t_kvtest表进行读写的功能。t_kvtest表结构如下,该表记录某公司仓库中物资,以唯一的物资编号作为主key,保存物资的名称和价格。

id* item_name item_price
100010001001 Laptop 6000

重要

客户端需要调用转换为Java文件的合约代码,需要将KVTableTest.sol和Table.sol放入控制台的contracts/solidity目录下,通过控制台的编译脚本sol2java.sh生成SImpleTableTest.java。

使用Table合约CRUD接口

访问 AMDB 需要使用Table合约CRUD接口,Table合约声明于Table.sol,该接口是数据库合约,可以创建表,并对表进行增删改查操作。

注解

为实现AMDB创建的表可被多个合约共享访问,其表名是群组内全局可见且唯一的,所以无法在同一条链上的同一个群组中,创建多个表名相同的表。 Table的CRUD接口一个key下可以有多条记录,使用时会进行数据批量操作,包括批量写入和范围查询。对应此特性,推荐使用关系型数据库MySQL作为后端数据库。 使用KVTtable的get/set接口时,推荐使用RocksDB作为后端数据库,因RocksDB是Key-Value存储的非关系型数据库,使用KVTable接口时单key操作效率更高。

Table.sol文件代码如下:

pragma solidity ^0.4.24;

contract TableFactory {
    function openTable(string) public constant returns (Table);  // 打开表
    function createTable(string,string,string) public returns(int);  // 创建表
}

// 查询条件
contract Condition {
    //等于
    function EQ(string, int) public;
    function EQ(string, string) public;

    //不等于
    function NE(string, int) public;
    function NE(string, string)  public;

    //大于
    function GT(string, int) public;
    //大于或等于
    function GE(string, int) public;

    //小于
    function LT(string, int) public;
    //小于或等于
    function LE(string, int) public;

    //限制返回记录条数
    function limit(int) public;
    function limit(int, int) public;
}

// 单条数据记录
contract Entry {
    function getInt(string) public constant returns(int);
    function getAddress(string) public constant returns(address);
    function getBytes64(string) public constant returns(byte[64]);
    function getBytes32(string) public constant returns(bytes32);
    function getString(string) public constant returns(string);

    function set(string, int) public;
    function set(string, string) public;
    function set(string, address) public;
}

// 数据记录集
contract Entries {
    function get(int) public constant returns(Entry);
    function size() public constant returns(int);
}

// Table主类
contract Table {
    // 查询接口
    function select(string, Condition) public constant returns(Entries);
    // 插入接口
    function insert(string, Entry) public returns(int);
    // 更新接口
    function update(string, Entry, Condition) public returns(int);
    // 删除接口
    function remove(string, Condition) public returns(int);

    function newEntry() public constant returns(Entry);
    function newCondition() public constant returns(Condition);
}

注解

  • Table合约的insert、remove、update和select函数中key的类型为string,其长度最大支持255字符。
  • Entry的get/set接口的key的类型为string,其长度最大支持255字符,value支持的类型有int256(int)、address和string,其中string的不能超过16MB。

提供一个合约案例TableTest.sol,代码如下:

pragma solidity ^0.4.24;

import "./Table.sol";

contract TableTest {
    event CreateResult(int count);
    event InsertResult(int count);
    event UpdateResult(int count);
    event RemoveResult(int count);

    // 创建表
    function create() public returns(int){
        TableFactory tf = TableFactory(0x1001);  // TableFactory的地址固定为0x1001
        // 创建t_test表,表的key_field为name,value_field为item_id,item_name
        // key_field表示AMDB主key value_field表示表中的列,可以有多列,以逗号分隔
        int count = tf.createTable("t_test", "name", "item_id,item_name");
        emit CreateResult(count);

        return count;
    }

    // 查询数据
    function select(string name) public constant returns(bytes32[], int[], bytes32[]){
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_test");

        // 条件为空表示不筛选 也可以根据需要使用条件筛选
        Condition condition = table.newCondition();

        Entries entries = table.select(name, condition);
        bytes32[] memory user_name_bytes_list = new bytes32[](uint256(entries.size()));
        int[] memory item_id_list = new int[](uint256(entries.size()));
        bytes32[] memory item_name_bytes_list = new bytes32[](uint256(entries.size()));

        for(int i=0; i<entries.size(); ++i) {
            Entry entry = entries.get(i);

            user_name_bytes_list[uint256(i)] = entry.getBytes32("name");
            item_id_list[uint256(i)] = entry.getInt("item_id");
            item_name_bytes_list[uint256(i)] = entry.getBytes32("item_name");
        }

        return (user_name_bytes_list, item_id_list, item_name_bytes_list);
    }
    // 插入数据
    function insert(string name, int item_id, string item_name) public returns(int) {
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_test");

        Entry entry = table.newEntry();
        entry.set("name", name);
        entry.set("item_id", item_id);
        entry.set("item_name", item_name);

        int count = table.insert(name, entry);
        emit InsertResult(count);

        return count;
    }
    // 更新数据
    function update(string name, int item_id, string item_name) public returns(int) {
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_test");

        Entry entry = table.newEntry();
        entry.set("item_name", item_name);

        Condition condition = table.newCondition();
        condition.EQ("name", name);
        condition.EQ("item_id", item_id);

        int count = table.update(name, entry, condition);
        emit UpdateResult(count);

        return count;
    }
    // 删除数据
    function remove(string name, int item_id) public returns(int){
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_test");

        Condition condition = table.newCondition();
        condition.EQ("name", name);
        condition.EQ("item_id", item_id);

        int count = table.remove(name, condition);
        emit RemoveResult(count);

        return count;
    }
}

TableTest.sol调用了 AMDB 专用的智能合约Table.sol,实现的是创建用户表t_test,并对t_test表进行增删改查的功能。t_test表结构如下,该表记录某公司员工领用物资和编号。

name* item_name item_id
Bob Laptop 100010001001

重要

客户端需要调用转换为Java文件的合约代码,需要将TableTest.sol和Table.sol放入控制台的contracts/solidity目录下,通过控制台的编译脚本sol2java.sh生成TableTest.java。

预编译合约开发

一. 简介

预编译(precompiled)合约是一项以太坊原生支持的功能:在底层使用c++代码实现特定功能的合约,提供给EVM模块调用。FISCO BCOS继承并且拓展了这种特性,在此基础上发展了一套功能强大并易于拓展的框架precompiled设计原理。 本文作为一篇入门指导,旨在指引用户如何实现自己的precompiled合约,并实现precompiled合约的调用。

二. 实现预编译合约
2.1 流程

实现预编译合约的流程:

  • 分配合约地址

调用solidity合约或者预编译合约需要根据合约地址来区分,地址空间划分:

地址用途 地址范围
以太坊precompiled 0x0001-0x0008
保留 0x0008-0x0fff
FISCO BCOS precompied 0x1000-0x1006
FISCO BCOS预留 0x1007-0x5000
用户分配区间 0x5001 - 0xffff
CRUD预留 0x10000+
solidity 其他

用户分配地址空间为0x5001-0xffff,用户需要为新添加的预编译合约分配一个未使用的地址,预编译合约地址必须唯一, 不可冲突

FISCO BCOS中实现的precompild合约列表以及地址分配:

地址 功能 源码(libprecompiled目录)
0x1000 系统参数管理 SystemConfigPrecompiled.cpp
0x1001 表工厂合约 TableFactoryPrecompiled.cpp
0x1002 CRUD操作实现 CRUDPrecompiled.cpp
0x1003 共识节点管理 ConsensusPrecompiled.cpp
0x1004 CNS功能 CNSPrecompiled.cpp
0x1005 存储表权限管理 AuthorityPrecompiled.cpp
0x1006 并行合约配置 ParallelConfigPrecompiled.cpp
  • 定义合约接口

同solidity合约,设计合约时需要首先确定合约的ABI接口, precomipiled合约的ABI接口规则与solidity完全相同,solidity ABI链接

定义预编译合约接口时,通常需要定义一个有相同接口的solidity合约,并且将所有的接口的函数体置空,这个合约我们称为预编译合约的接口合约,接口合约在调用预编译合约时需要使用。
    pragma solidity ^0.4.24;
    contract Contract_Name {
        function interface0(parameters ... ) {}
        ....
        function interfaceN(parameters ... ) {}
    }
  • 设计存储结构

预编译合约涉及存储操作时,需要确定存储的表信息(表名与表结构,存储数据在FISCO BCOS中会统一抽象为表结构), 存储结构

注解

不涉及存储操作可以省略该流程。

  • 实现调用逻辑

实现新增合约的调用逻辑,需要新实现一个c++类,该类需要继承Precompiled, 重载call函数, 在call函数中实现各个接口的调用行为。

    // libprecompiled/Precompiled.h
    class Precompiled
    {
        virtual bytes call(std::shared_ptr<ExecutiveContext> _context, bytesConstRef _param,
            Address const& _origin = Address()) = 0;
    };

call函数有三个参数:

std::shared_ptr<ExecutiveContext> _context : 保存交易执行的上下文

bytesConstRef _param : 调用合约的参数信息,本次调用对应合约接口以及接口的参数可以从_param解析获取

Address const& _origin : 交易发送者,用来进行权限控制

如何实现一个Precompiled类在下面的sample中会详细说明。

  • 注册合约

最后需要将合约的地址与对应的类注册到合约的执行上下文,这样通过地址调用precompiled合约时合约的执行逻辑才能被正确识别执行, 查看注册的预编译合约列表。 注册路径:

    file        libblockverifier/ExecutiveContextFactory.cpp
    function    initExecutiveContext
2.2 示例合约开发
// HelloWorld.sol
pragma solidity ^0.4.24;

contract HelloWorld{
    string name;

    function HelloWorld(){
       name = "Hello, World!";
    }

    function get()constant returns(string){
        return name;
    }

    function set(string n){
    	name = n;
    }
}

上述源码为solidity编写的HelloWorld合约, 本章节会实现一个相同功能的预编译合约,通过step by step使用户对预编译合约编写有直观的认识。 示例的c++源码路径

    libprecompiled/extension/HelloWorldPrecompiled.h
    libprecompiled/extension/HelloWorldPrecompiled.cpp
2.2.1 分配合约地址

参照地址分配空间,HelloWorld预编译合约的地址分配为:

0x5001
2.2.2 定义合约接口

需要实现HelloWorld合约的功能,接口与HelloWorld接口相同, HelloWorldPrecompiled的接口合约:

pragma solidity ^0.4.24;

contract HelloWorldPrecompiled {
    function get() public constant returns(string) {}
    function set(string _m) {}
}
2.2.3 设计存储结构

HelloWorldPrecompiled需要存储set的字符串值,所以涉及到存储操作,需要设计存储的表结构。

表名: _ext_hello_world_

表结构:

key value
hello_key hello_value

该表只存储一对键值对,key字段为hello_key,value字段为hello_value 存储对应的字符串值,可以通过set(string)接口修改,通过get()接口获取。

2.2.4 实现调用逻辑

添加HelloWorldPrecompiled类,重载call函数,实现所有接口的调用行为,call函数源码

用户自定义的Precompiled合约需要新增一个类,在类中定义合约的调用行为,在示例中添加HelloWorldPrecompiled类,然后主要需要完成以下工作:

  • 接口注册
// 定义类中所有的接口
const char* const HELLO_WORLD_METHOD_GET = "get()";
const char* const HELLO_WORLD_METHOD_SET = "set(string)";

// 在构造函数进行接口注册
HelloWorldPrecompiled::HelloWorldPrecompiled()
{
    // name2Selector是基类Precompiled类中成员,保存接口调用的映射关系
    name2Selector[HELLO_WORLD_METHOD_GET] = getFuncSelector(HELLO_WORLD_METHOD_GET);
    name2Selector[HELLO_WORLD_METHOD_SET] = getFuncSelector(HELLO_WORLD_METHOD_SET);
}
  • 创建表

定义表名,表的字段结构

// 定义表名
const std::string HELLO_WORLD_TABLE_NAME = "_ext_hello_world_";
// 主键字段
const std::string HELLOWORLD_KEY_FIELD = "key";
// 其他字段字段,多个字段使用逗号分割,比如 "field0,field1,field2"
const std::string HELLOWORLD_VALUE_FIELD = "value";
// call函数中,表存在时打开,否则首先创建表
Table::Ptr table = openTable(_context, HELLO_WORLD_TABLE_NAME);
if (!table)
{
    // 表不存在,首先创建
    table = createTable(_context, HELLO_WORLD_TABLE_NAME, HELLOWORLD_KEY_FIELD,
        HELLOWORLD_VALUE_FIELD, _origin);
    if (!table)
    {
        // 创建表失败,返回错误码
    }
}

获取表的操作句柄之后,用户可以实现对表操作的具体逻辑。

  • 区分调用接口

通过getParamFunc解析_param可以区分调用的接口。 注意:合约接口一定要先在构造函数中注册

uint32_t func = getParamFunc(_param);
if (func == name2Selector[HELLO_WORLD_METHOD_GET])
{
    // get() 接口调用逻辑
}
else if (func == name2Selector[HELLO_WORLD_METHOD_SET])
{
    // set(string) 接口调用逻辑
}
else
{
    // 未知接口,调用错误,返回错误码
}
  • 参数解析与结果返回

调用合约时的参数包含在call函数的_param参数中,是按照Solidity ABI格式进行编码,使用dev::eth::ContractABI工具类可以进行参数的解析,同样接口返回时返回值也需要按照该编码格编码。Solidity ABI

dev::eth::ContractABI类中我们需要使用abiIn abiOut两个接口,前者用户参数的序列化,后者可以从序列化的数据中解析参数

// 序列化ABI数据, c++类型数据序列化为evm使用的格式
// _id : 函数接口声明对应的字符串, 一般默认为""即可。
template <class... T> bytes abiIn(std::string _id, T const&... _t)
// 将序列化数据解析为c++类型数据
template <class... T> void  abiOut(bytesConstRef _data, T&... _t)

下面的示例代码说明接口如何使用:

// 对于transfer接口 : transfer(string,string,uint256)

// 参数1
std::string str1 = "fromAccount";
// 参数2
std::string str2 = "toAccount";
// 参数3
uint256 transferAmoumt = 11111;

dev::eth::ContractABI abi;
// 序列化, abiIn第一个string参数默认""
bytes out = abi.abiIn("", str1, str2, transferAmoumt);

std::string strOut1;
std::string strOut2;
uint256 amoumt;

// 解析参数
abi.abiOut(out, strOut1, strOut2, amount);
// 解析之后
// strOut1 = "fromAccount";
// strOut2 = "toAccount"
// amoumt = 11111

最后,给出HelloWorldPrecompiled call函数的完整实现源码链接

bytes HelloWorldPrecompiled::call(dev::blockverifier::ExecutiveContext::Ptr _context,
    bytesConstRef _param, Address const& _origin)
{
    // 解析函数接口
    uint32_t func = getParamFunc(_param);
    //
    bytesConstRef data = getParamData(_param);
    bytes out;
    dev::eth::ContractABI abi;

    // 打开表
    Table::Ptr table = openTable(_context, HELLO_WORLD_TABLE_NAME);
    if (!table)
    {
        // 表不存在,首先创建
        table = createTable(_context, HELLO_WORLD_TABLE_NAME, HELLOWORLD_KEY_FIELD,
            HELLOWORLD_VALUE_FIELD, _origin);
        if (!table)
        {
            // 创建表失败,无权限?
            out = abi.abiIn("", CODE_NO_AUTHORIZED);
            return out;
        }
    }

    // 区分调用接口,各个接口的具体调用逻辑
    if (func == name2Selector[HELLO_WORLD_METHOD_GET])
    {  // get() 接口调用
        // 默认返回值
        std::string retValue = "Hello World!";
        auto entries = table->select(HELLOWORLD_KEY_FIELD_NAME, table->newCondition());
        if (0u != entries->size())
        {
            auto entry = entries->get(0);
            retValue = entry->getField(HELLOWORLD_VALUE_FIELD);
        }
        out = abi.abiIn("", retValue);
    }
    else if (func == name2Selector[HELLO_WORLD_METHOD_SET])
    {  // set(string) 接口调用

        std::string strValue;
        abi.abiOut(data, strValue);
        auto entries = table->select(HELLOWORLD_KEY_FIELD_NAME, table->newCondition());
        auto entry = table->newEntry();
        entry->setField(HELLOWORLD_KEY_FIELD, HELLOWORLD_KEY_FIELD_NAME);
        entry->setField(HELLOWORLD_VALUE_FIELD, strValue);

        int count = 0;
        if (0u != entries->size())
        {  // 值存在,更新
            count = table->update(HELLOWORLD_KEY_FIELD_NAME, entry, table->newCondition(),
                std::make_shared<AccessOptions>(_origin));
        }
        else
        {  // 值不存在,插入
            count = table->insert(
                HELLOWORLD_KEY_FIELD_NAME, entry, std::make_shared<AccessOptions>(_origin));
        }

        if (count == CODE_NO_AUTHORIZED)
        {  // 没有表操作权限
            PRECOMPILED_LOG(ERROR) << LOG_BADGE("HelloWorldPrecompiled") << LOG_DESC("set")
                                   << LOG_DESC("non-authorized");
        }
        out = abi.abiIn("", u256(count));
    }
    else
    {  // 参数错误,未知的接口调用
        PRECOMPILED_LOG(ERROR) << LOG_BADGE("HelloWorldPrecompiled") << LOG_DESC(" unkown func ")
                               << LOG_KV("func", func);
        out = abi.abiIn("", u256(CODE_UNKNOW_FUNCTION_CALL));
    }

    return out;
}
2.2.5 注册合约并编译源码
  • 注册开发的预编译合约。修改FISCO-BCOS/cmake/templates/UserPrecompiled.h.in,在下面的函数中注册HelloWorldPrecompiled合约的地址。默认已有,取消注释即可。
void dev::blockverifier::ExecutiveContextFactory::registerUserPrecompiled(dev::blockverifier::ExecutiveContext::Ptr context)
{
    // Address should in [0x5001,0xffff]
    context->setAddress2Precompiled(Address(0x5001), std::make_shared<dev::precompiled::HelloWorldPrecompiled>());
}
  • 编译源码。请参考这里,安装依赖并编译源码。

注意:实现的HelloWorldPrecompiled.cpp和头文件需要放置于FISCO-BCOS/libprecompiled/extension目录下。

  • 搭建FISCO BCOS联盟链。 假设当前位于FISCO-BCOS/build目录下,则使用下面的指令搭建本机4节点的链指令如下。更多选项参考这里
bash ../tools/build_chain.sh -l "127.0.0.1:4" -e bin/fisco-bcos
三 调用

从用户角度,预编译合约与solidity合约的调用方式基本相同,唯一的区别是solidity合约在部署之后才能获取到调用的合约地址,预编译合约的地址为预分配,不用部署,可以直接使用。

3.1 使用控制台调用HelloWorld预编译合约

在控制台contracts/solidity创建HelloWorldPrecompiled.sol文件,文件内容是HelloWorld预编译合约的接口声明,如下

pragma solidity ^0.4.24;
contract HelloWorldPrecompiled{
    function get() public constant returns(string);
    function set(string n);
}

使用编译出的二进制搭建节点后,部署控制台v1.0.2以上版本,然后执行下面语句即可调用 _images/call_helloworld.png

3.2 solidity调用

我们尝试在Solidity合约中创建预编译合约对象并调用其接口。在控制台contracts/solidity创建HelloWorldHelper.sol文件,文件内容如下

pragma solidity ^0.4.24;
import "./HelloWorldPrecompiled.sol";

contract HelloWorldHelper {
    HelloWorldPrecompiled hello;
    function HelloWorldHelper() {
        // 调用HelloWorld预编译合约
        hello = HelloWorldPrecompiled(0x5001);
    }
    function get() public constant returns(string) {
        return hello.get();
    }
    function set(string m) {
        hello.set(m);
    }
}

部署HelloWorldHelper合约,然后调用HelloWorldHelper合约的接口,结果如下 _images/call_helloworldHelper.png

并行合约

FISCO BCOS提供了可并行合约开发框架,开发者按照框架规范编写的合约,能够被FISCO BCOS节点并行地执行。并行合约的优势有:

  • 高吞吐:多笔独立交易同时被执行,能最大限度利用机器的CPU资源,从而拥有较高的TPS
  • 可拓展:可以通过提高机器的配置来提升交易执行的性能,以支持不断扩大业务规模

接下来,我将介绍如何编写FISCO BCOS并行合约,以及如何部署和执行并行合约。

预备知识

并行互斥

两笔交易是否能被并行执行,依赖于这两笔交易是否存在互斥。互斥,是指两笔交易各自操作合约存储变量的集合存在交集

例如,在转账场景中,交易是用户间的转账操作。用transfer(X, Y) 表示从X用户转到Y用户的转账接口,则互斥情况如下。

交易 互斥对象 交集 是否互斥
transfer(A, B) 和 transfer(A, C) [A, B] 和 [A, C] [A] 互斥,不可并行执行
transfer(A, B) 和 transfer(B, C) [A, B] 和 [B, C] [B] 互斥,不可并行执行
transfer(A, C) 和 transfer(B, C) [A, C] 和 [B, C] [C] 互斥,不可并行执行
transfer(A, B) 和 transfer(A, B) [A, B] 和 [A, B] [A, B] 互斥,不可并行执行
transfer(A, B) 和 transfer(C, D) [A, B] 和 [C, D] 无互斥,可并行执行

此处给出更具体的定义:

  • 互斥参数:合约接口中,与合约存储变量的“读/写”操作相关的参数。例如转账的接口transfer(X, Y),X和Y都是互斥参数。
  • 互斥对象:一笔交易中,根据互斥参数提取出来的、具体的互斥内容。例如转账的接口transfer(X, Y), 一笔调用此接口的交易中,具体的参数是transfer(A, B),则这笔操作的互斥对象是[A, B];另外一笔交易,调用的参数是transfer(A, C),则这笔操作的互斥对象是[A, C]。

判断同一时刻两笔交易是否能并行执行,就是判断两笔交易的互斥对象是否有交集。相互之间交集为空的交易可并行执行。

编写并行合约

FISCO BCOS提供了可并行合约开发框架,开发者只需按照框架的规范开发合约,定义好每个合约接口的互斥参数,即可实现能被并行执行的合约。当合约被部署后,FISCO BCOS会在执行交易前,自动解析互斥对象,在同一时刻尽可能让无依赖关系的交易并行执行。

目前,FISCO BCOS提供了solidity预编译合约两种可并行合约开发框架。

solidity合约并行框架

编写并行的solidity合约,开发流程与开发普通的solidity合约的流程相同。在基础上,只需要将ParallelContract 作为需要并行的合约的基类,并调用registerParallelFunction(),注册可以并行的接口即可。(ParallelContract.sol合约代码参考这里

先给出完整的举例,例子中的ParallelOk合约实现了并行转账的功能

pragma solidity ^0.4.25;

import "./ParallelContract.sol";  // 引入ParallelContract.sol

contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
    // 合约实现
    mapping (string => uint256) _balance;
    
    function transfer(string from, string to, uint256 num) public
    {
        // 此处为简单举例,实际生产中请用SafeMath代替直接加减
        _balance[from] -= num;
        _balance[to] += num;
    }

    function set(string name, uint256 num) public
    {
        _balance[name] = num;
    }

    function balanceOf(string name) public view returns (uint256)
    {
        return _balance[name];
    }
    
    // 注册可以并行的合约接口
    function enableParallel() public
    {
        // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数(设计函数时互斥参数必须放在前面
        registerParallelFunction("transfer(string,string,uint256)", 2); // critical: string string
        registerParallelFunction("set(string,uint256)", 1); // critical: string
    } 

    // 注销并行合约接口
    function disableParallel() public
    {
        unregisterParallelFunction("transfer(string,string,uint256)");
        unregisterParallelFunction("set(string,uint256)"); 
    } 
}

具体步骤如下:

(1)将ParallelContract作为合约的基类

pragma solidity ^0.4.25;

import "./ParallelContract.sol"; // 引入ParallelContract.sol

contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
   // 合约实现
   
   // 注册可以并行的合约接口
   function enableParallel() public;
   
   // 注销并行合约接口
   function disableParallel() public;
}

(2)编写可并行的合约接口

合约中的public函数,是合约的接口。编写可并行的合约接口,是根据一定的规则,实现一个合约中的public函数。

确定接口是否可并行

可并行的合约接口,必须满足:

  • 无调用外部合约
  • 无调用其它函数接口

确定互斥参数

在编写接口前,先确定接口的互斥参数,接口的互斥即是对全局变量的互斥,互斥参数的确定规则为:

  • 接口访问了全局mapping,mapping的key是互斥参数
  • 接口访问了全局数组,数组的下标是互斥参数
  • 接口访问了简单类型的全局变量,所有简单类型的全局变量共用一个互斥参数,用不同的变量名作为互斥对象
例如:合约里有多个简单类型的全局变量,不同接口访问了不同的全局变量。如要将不同接口并行,则需要在修改了全局变量的接口参数中定义一个互斥参数,用来调用时指明使用了哪个全局变量。在调用时,主动给互斥参数传递相应修改的全局变量的“变量名”,用以标明此笔交易的互斥对象。如:setA(int x)函数中修改了全局参数globalA,则需要将setA函数定义为set(string aflag, int x), 在调用时,传入setA("globalA", 10),用变量名“globalA”来指明此交易的互斥对象是globalA

确定参数类型和顺序

确定互斥参数后,根据规则确定参数类型和顺序,规则为:

  • 接口参数仅限:string、address、uint256、int256(未来会支持更多类型)
  • 互斥参数必须全部出现在接口参数中
  • 所有互斥参数排列在接口参数的最前
mapping (string => uint256) _balance; // 全局mapping

// 互斥变量from、to排在最前,作为transfer()开头的两个参数
function transfer(string from, string to, uint256 num) public
{
    _balance[from] -= num;  // from 是全局mapping的key,是互斥参数
    _balance[to] += num; // to 是全局mapping的key,是互斥参数 
}

// 互斥变量name排在最前,作为set()开头的参数
function set(string name, uint256 num) public
{
    _balance[name] = num;
}

(3)在框架中注册可并行的合约接口

在合约中实现 enableParallel() 函数,调用registerParallelFunction()注册可并行的合约接口。同时也需要实现disableParallel()函数,使合约具备取消并行执行的能力。

// 注册可以并行的合约接口
function enableParallel() public
{
    // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数
    registerParallelFunction("transfer(string,string,uint256)", 2); // transfer接口,前2个是互斥参数
    registerParallelFunction("set(string,uint256)", 1); // set接口,前1个是互斥参数
}  

// 注销并行合约接口
function disableParallel() public
{
    unregisterParallelFunction("transfer(string,string,uint256)");
    unregisterParallelFunction("set(string,uint256)"); 
} 

(4)部署/执行并行合约

控制台Web3SDK编译和部署合约,此处以控制台为例。

部署合约

[group:1]> deploy ParallelOk.sol

调用 enableParallel()接口,让ParallelOk能并行执行

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 enableParallel

发送并行交易 set()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 set "jimmyshi" 100000

发送并行交易 transfer()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 transfer "jimmyshi" "jinny" 80000

查看交易执行结果 balanceOf()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 balanceOf "jinny"
80000

用SDK发送大量交易的例子,将在下文的举例中给出。

预编译并行合约框架

编写并行的预编译合约,开发流程与开发普通预编译合约的流程相同。普通的预编译合约以Precompile为基类,在这之上实现合约逻辑。基于此,Precompile的基类还为并行提供了两个虚函数,继续实现这两个函数,即可实现并行的预编译合约。

(1)将合约定义成支持并行

bool isParallelPrecompiled() override { return true; }

(2)定义并行接口和互斥参数

注意,一旦定义成支持并行,所有的接口都需要进行定义。若返回空,表示此接口无任何互斥对象。互斥参数与预编译合约的实现相关,此处涉及对FISCO BCOS存储的理解,具体的实现可直接阅读代码或询问相关有经验的程序员。

// 根据并行接口,从参数中取出互斥对象,返回互斥对象
std::vector<std::string> getParallelTag(bytesConstRef param) override
{
    // 获取被调用的函数名(func)和参数(data)
    uint32_t func = getParamFunc(param);
    bytesConstRef data = getParamData(param);

    std::vector<std::string> results;
    if (func == name2Selector[DAG_TRANSFER_METHOD_TRS_STR2_UINT]) // 函数是并行接口
    {  
        // 接口为:userTransfer(string,string,uint256)
        // 从data中取出互斥对象
        std::string fromUser, toUser;
        dev::u256 amount;
        abi.abiOut(data, fromUser, toUser, amount);
        
        if (!invalidUserName(fromUser) && !invalidUserName(toUser) && (amount > 0))
        {
            // 将互斥对象写到results中
            results.push_back(fromUser);
            results.push_back(toUser);
        }
    }
    else if ... // 所有的接口都需要给出互斥对象,返回空表示无任何互斥对象
        
 	return results;  //返回互斥
}

(3)编译,重启节点

手动编译节点的方法,参考:这里

编译之后,关闭节点,替换掉原来的节点二进制文件,再重启节点即可。

举例:并行转账

此处分别给出solidity合约和预编译合约的并行举例。

配置环境

该举例需要以下执行环境:

  • Web3SDK客户端
  • 一条FISCO BCOS链

Web3SDK用来发送并行交易,FISCO BCOS链用来执行并行交易。相关配置,可参考:

若需要压测最大的性能,至少需要:

  • 3个Web3SDK,才能产生足够多的交易
  • 4个节点,且所有Web3SDK都配置了链上所有的节点信息,让交易均匀的发送到每个节点上,才能让链能接收足够多的交易
并行Solidity合约:ParallelOk

基于账户模型的转账,是一种典型的业务操作。ParallelOk合约,是账户模型的一个举例,能实现并行的转账功能。ParallelOk合约已在上文中给出。

FISCO BCOS在Web3SDK中内置了ParallelOk合约,此处给出用Web3SDK来发送大量并行交易的操作方法。

(1)用SDK部署合约、新建用户、开启合约的并行能力

# 参数:<groupID> add <创建的用户数量> <此创建操作请求的TPS> <生成的用户信息文件名>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 add 10000 2500 user
# 在group1上创建了 10000个用户,创建操作以2500TPS发送的,生成的用户信息保存在user中

执行成功后,ParallelOk被部署到区块链上,创建的用户信息保存在user文件中,同时开启了ParallelOk的并行能力。

(2)批量发送并行转账交易

注意:在批量发送前,请将SDK的日志等级请调整为ERROR,才能有足够的发送能力。

# 参数:<groupID> transfer <总交易数量> <此转账操作请求的TPS上限> <需要的用户信息文件> <交易互斥百分比:0~10>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 transfer 100000 4000 user 2

# 向group1发送了 100000比交易,发送的TPS上限是4000,用的之前创建的user文件里的用户,发送的交易间有20%的互斥。

(3)验证并行正确性

并行交易执行完成后,Web3SDK会打印出执行结果。TPS 是此SDK发送的交易在节点上执行的TPS。validation 是转账交易执行结果的检查。

Total transactions:  100000
Total time: 34412ms
TPS: 2905.9630361501804
Avg time cost: 4027ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 44  : 0.044000000000000004%
100  < time <  200ms  : 2617  : 2.617%
200  < time <  400ms  : 6214  : 6.214%
400  < time <  1000ms : 14190  : 14.19%
1000 < time <  2000ms : 9224  : 9.224%
2000 < time           : 67711  : 67.711%
validation:
 	user count is 10000
 	verify_success count is 10000
 	verify_failed count is 0

可以看出,本次交易执行的TPS是2905。执行结果校验后,无任何错误(verify_failed count is 0)。

(4)计算总TPS

单个Web3SDK无法发送足够多的交易以达到节点并行执行能力的上限。需要多个Web3SDK同时发送交易。在多个Web3SDK同时发送交易后,单纯的将结果中的TPS加和得到的TPS不够准确,需要直接从节点处获取TPS。

用脚本从日志文件中计算TPS

cd tools
sh get_tps.sh log/log_2019031821.00.log 21:26:24 21:26:59 # 参数:<日志文件> <计算开始时间> <计算结束时间>

得到TPS(2 SDK、4节点,8核,16G内存)

statistic_end = 21:26:58.631195
statistic_start = 21:26:24.051715
total transactions = 193332, execute_time = 34580ms, tps = 5590 (tx/s)
并行预编译合约:DagTransferPrecompiled

与ParallelOk合约的功能一样,FISCO BCOS内置了一个并行预编译合约的例子(DagTransferPrecompiled),实现了简单的基于账户模型的转账功能。该合约能够管理多个用户的存款,并提供一个支持并行的transfer接口,实现对用户间转账操作的并行处理。

注意:DagTransferPrecompiled为并行交易的举例,功能较为简单,请勿用于线上业务。

(1)生成用户

用Web3SDK发送创建用户的操作,创建的用户信息保存在user文件中。命令参数与parallelOk相同,不同的仅仅是命令所调用的对象是precompile。

# 参数:<groupID> add <创建的用户数量> <此创建操作请求的TPS> <生成的用户信息文件名>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 add 10000 2500 user
# 在group1上创建了 10000个用户,创建操作以2500TPS发送的,生成的用户信息保存在user中

(2)批量发送并行转账交易

用Web3SDK发送并行转账交易

注意:在批量发送前,请将SDK的日志等级请调整为ERROR,才能有足够的发送能力。

# 参数:<groupID> transfer <总交易数量> <此转账操作请求的TPS上限> <需要的用户信息文件> <交易互斥百分比:0~10>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 transfer 100000 4000 user 2
# 向group1发送了 100000比交易,发送的TPS上限是4000,用的之前创建的user文件里的用户,发送的交易间有20%的互斥。

(3)验证并行正确性

并行交易执行完成后,Web3SDK会打印出执行结果。TPS 是此SDK发送的交易在节点上执行的TPS。validation 是转账交易执行结果的检查。

Total transactions:  80000
Total time: 25451ms
TPS: 3143.2949589407094
Avg time cost: 5203ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 0  : 0.0%
100  < time <  200ms  : 0  : 0.0%
200  < time <  400ms  : 0  : 0.0%
400  < time <  1000ms : 403  : 0.50375%
1000 < time <  2000ms : 5274  : 6.592499999999999%
2000 < time           : 74323  : 92.90375%
validation:
 	user count is 10000
 	verify_success count is 10000
 	verify_failed count is 0

从图中可看出,本次交易执行的TPS是3143。执行结果校验后,无任何错误(verify_failed count is 0)。

(4)计算总TPS

单个Web3SDK无法发送足够多的交易以达到节点并行执行能力的上限。需要多个Web3SDK同时发送交易。在多个Web3SDK同时发送交易后,单纯的将结果中的TPS加和得到的TPS不够准确,需要直接从节点处获取TPS。

用脚本从日志文件中计算TPS

cd tools
sh get_tps.sh log/log_2019031311.17.log 11:25 11:30 # 参数:<日志文件> <计算开始时间> <计算结束时间>

得到TPS(3 SDK、4节点,8核,16G内存)

statistic_end = 11:29:59.587145
statistic_start = 11:25:00.642866
total transactions = 3340000, execute_time = 298945ms, tps = 11172 (tx/s)
结果说明

本文举例中的性能结果,是在3SDK、4节点、8核、16G内存、1G网络下测得。每个SDK和节点都部署在不同的VPS中,硬盘为云硬盘。实际TPS会根据你的硬件配置、操作系统和网络带宽有所变化。

组员管理

FISCO BCOS引入了游离节点、观察者节点和共识节点,这三种节点类型可通过控制台相互转换。

  • 组员
    • 共识节点:参与共识的节点,拥有群组的所有数据(搭链时默认都生成共识节点)。
    • 观察者节点:不参与共识,但能实时同步链上数据的节点。
  • 非组员
    • 游离节点:已启动,待等待加入群组的节点。处在一种暂时的节点状态,不能获取链上的数据。

操作命令

控制台提供了 addSealeraddObserverremoveNode 三类命令将指定节点转换为共识节点、观察者节点和游离节点,并可使用 getSealerListgetObserverListgetNodeIDList 查看当前组的共识节点列表、观察者节点列表和组内所有节点列表。

  • addSealer:根据节点NodeID设置对应节点为共识节点;
  • addObserver:根据节点NodeID设置对应节点为观察节点;
  • removeNode:根据节点NodeID设置对应节点为游离节点;
  • getSealerList:查看群组中共识节点列表;
  • getObserverList:查看群组中观察节点列表;
  • getNodeIDList:查看节点已连接的所有其他节点的NodeID。

例: 将指定节点分别转换成共识节点、观察者节点、游离节点,主要操作命令如下:

重要

节点准入操作前,请确保:

  • 操作节点Node ID存在,节点Node ID可在节点目录下执行 cat conf/node.nodeid获取
  • 节点加入的区块链所有节点共识正常:正常共识的节点会输出+++日志
# 获取节点Node ID(设节点目录为~/nodes/192.168.0.1/node0/)
$ cat ~/fisco/nodes/192.168.0.1/node0/conf/node.nodeid
7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50

# 连接控制台(设控制台位于~/fisco/console目录)
$ cd ~/fisco/console

$ bash start.sh

# 将指定节点转换为共识节点
[group:1]> addSealer 7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50
# 查询共识节点列表
[group:1]> getSealerList
[
	7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50
]

# 将指定节点转换为观察者节点
[group:1]> addObserver 7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50

# 查询观察者节点列表
[group:1]> getObserverList
[
	7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50
]

# 将指定节点转换为游离节点
[group:1]> removeNode 7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50

# 查询节点列表
[group:1]> getNodeIDList
[
	7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2922aa0ef50
]
[group:1]> getSealerList
[]
[group:1]> getObserverList
[]

操作案例

下面结合具体操作案例详细阐述群组扩容操作及节点退网操作。扩容操作分两个阶段, 分别为将节点加入网络将节点加入群组。退网操作也分为两个阶段,为将节点退出群组将节点退出网络

操作方式
  • 修改节点配置:节点修改自身配置后重启生效,涉及的操作项目包括网络的加入/退出、CA黑名单的列入/移除
  • 交易共识上链:节点发送上链交易修改需群组共识的配置项,涉及的操作项目包括节点类型的修改。目前提供的发送交易途径为控制台、SDK提供的precompiled service接口。
  • RPC查询:使用curl命令查询链上信息,涉及的操作项目包括群组节点的查询
操作步骤

本节将以下图为例对上述扩容操作及退网操作进行描述。虚线表示节点间能进行网络通信,实线表示节点间在可通信的基础上具备群组关系,不同颜色区分不同的群组关系。下图有一个网络,包含三个群组,其中群组Group3有三个节点。Group3是否与其他群组存在交集节点,不影响以下操作过程的通用性。

_images/multi_ledger_example.png

群组例子
Group3的相关节点信息举例为:

节点1的目录名为node0,IP端口为127.0.0.1:30400,nodeID前四个字节为b231b309…

节点2的目录名为node1,IP端口为127.0.0.1:30401,nodeID前四个字节为aab37e73…

节点3的目录名为node2,IP端口为127.0.0.1:30402,nodeID前四个字节为d6b01a96…

A节点加入网络

场景描述:

节点3原先不在网络中,现在加入网络。

操作顺序:

1 . 进入nodes同级目录,在该目录下拉取并执行gen_node_cert.sh生成节点目录,目录名以node2为例,node2内有conf/目录;

# 获取脚本
$ curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/gen_node_cert.sh && chmod u+x gen_node_cert.sh
# 执行,-c为生成节点所提供的ca路径,agency为机构名,-o为将生成的节点目录名(如果是国密节点,使用 -g 参数)
$ ./gen_node_cert.sh -c nodes/cert/agency -o node2

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/FISCO-BCOS/raw/master/tools/gen_node_cert.sh

2 . 拷贝node2到nodes/127.0.0.1/下,与其他节点目录(node0node1)同级;

$ cp -r ./node2/ nodes/127.0.0.1/

3 . 进入nodes/127.0.0.1/,拷贝node0/config.ininode0/start.shnode0/stop.sh到node2目录;

$ cd nodes/127.0.0.1/
$ cp node0/config.ini node0/start.sh node0/stop.sh node2/

4 . 修改node2/config.ini。对于[rpc]模块,修改channel_listen_portjsonrpc_listen_port;对于[p2p]模块,修改listen_port并在node.中增加自身节点信息;

$ vim node2/config.ini
[rpc]
    ;rpc listen ip
    listen_ip=127.0.0.1
    ;channelserver listen port
    channel_listen_port=20302
    ;jsonrpc listen port
    jsonrpc_listen_port=8647
[p2p]
    ;p2p listen ip
    listen_ip=0.0.0.0
    ;p2p listen port
    listen_port=30402
    ;nodes to connect
    node.0=127.0.0.1:30400
    node.1=127.0.0.1:30401
    node.2=127.0.0.1:30402

5 . 节点3拷贝节点1的node1/conf/group.3.genesis(内含群组节点初始列表)和node1/conf/group.3.ininode2/conf目录下,不需改动;

$ cp node1/conf/group.3.genesis node2/conf/
$ cp node1/conf/group.3.ini node2/conf/

6 . 执行node2/start.sh启动节点3;

$ ./node2/start.sh

7 . 确认节点3与节点1和节点2的连接已经建立,加入网络操作完成。

# 在打开DEBUG级别日志前提下,查看自身节点(node2)连接的节点数及所连接的节点信息(nodeID)
# 以下日志表明节点node2与两个节点(节点的nodeID前4个字节为b231b309、aab37e73)建立了连接
$ tail -f node2/log/log*  | grep P2P
debug|2019-02-21 10:30:18.694258| [P2P][Service] heartBeat ignore connected,endpoint=127.0.0.1:30400,nodeID=b231b309...
debug|2019-02-21 10:30:18.694277| [P2P][Service] heartBeat ignore connected,endpoint=127.0.0.1:30401,nodeID=aab37e73...
info|2019-02-21 10:30:18.694294| [P2P][Service] heartBeat connected count,size=2

注解

  • 若启用了白名单,需确保所有节点的config.ini中的白名单都已配置了所有的节点,并正确的将白名单配置刷新入节点中。参考《CA黑白名单》;
  • 从节点1拷贝过来的config.ini的其余配置可保持不变;
  • 理论上,节点1和2不需修改自身的P2P节点连接列表,即可完成扩容节点3的操作;
  • 步骤5中所选择的群组建议为节点3后续需加入的群组;
  • 建议用户在节点1和2的config.ini的P2P节点连接列表中加入节点3的信息并重启节点1和2,保持全网节点的全互联状态。
A节点退出网络

场景描述:

节点3已在网络中,与节点1和节点2通信,现在退出网络。

操作顺序:

1 . 对于节点3,将自身的P2P节点连接列表内容清空,重启节点3;

# 在node2目录下执行
$ ./stop.sh
$ ./start.sh
nohup: appending output to ‘nohup.out’

2 . 对于节点1和2,将节点3从自身的P2P节点连接列表中移除(如有),重启节点1和2;

3 . 确认节点3与节点1(和2)的原有连接已经断开,退出网络操作完成。

注解

  • 节点3需先退出群组再退出网络,退出顺序由用户保证,系统不再作校验
  • 网络连接由节点主动发起,如缺少第2步,节点3仍可感知节点1和节点2发起的P2P连接请求,并建立连接,可使用CA黑名单避免这种情况。
  • 若启用了白名单,需将退出节点的从所有节点的config.ini的白名单配置中删除,并正确的将新的白名单配置刷入节点中。参考《CA黑白名单》。
A节点加入群组

场景描述:

群组Group3原有节点1和节点2,两节点轮流出块,现在将节点3加入群组。

操作顺序:

  1. 节点3加入网络;
  2. 使用控制台addSealer根据节点3的nodeID设置节点3为共识节点
  3. 使用控制台getSealerList查询group3的共识节点中是否包含节点3的nodeID,如存在,加入群组操作完成。

注解

  • 节点3的NodeID可以使用`cat nodes/127.0.0.1/node2/conf/node.nodeid`获取;
  • 节点3首次启动会将配置的群组节点初始列表内容写入群组节点系统表,区块同步结束后,群组各节点的群组节点系统表均一致
  • 节点3需先完成网络准入后,再执行加入群组的操作,系统将校验操作顺序
  • 节点3的群组固定配置文件需与节点1和2的一致
A节点退出群组

场景描述:

群组Group3原有节点1、节点2和节点3,三节点轮流出块,现在将节点3退出群组。

操作顺序:

  1. 使用控制台removeNode根据节点3的NodeID设置节点3为游离节点
  2. 使用控制台getSealerList查询group3的共识节点中是否包含节点3的nodeID,如已消失,退出群组操作完成。

补充说明:

注解

  • 节点3可以共识节点或观察节点的身份执行退出操作。

权限控制

基于角色的权限控制

本节描述角色权限控制的操作,使用前请先阅读设计文档角色权限控制设计文档。2.5.0版本开始,提供一种基于角色的权限控制模型,原来的链管理员相当于当前的治理委员会委员角色,拥有链治理相关的操作权限。用户不需要去具体关注底层系统表对应的权限,只需要关注角色的权限即可。

权限与角色
  1. 链治理委员会委员简称为委员
  2. 权限使用白名单机制,默认不检查,当存在至少一个角色的账号时,角色对应的权限检查生效
  3. 委员可以冻结解冻任意合约,同时合约的部署账号也可以冻结解冻合约
  4. 委员可以冻结解冻账号,被冻结的账号无法发送交易
权限操作 修改方式
增删委员 委员投票
修改委员权重 委员投票
修改生效投票阈值(投票委员权重和大于该值) 委员投票
增删节点(观察/共识) 委员账号
修改链配置项 委员账号
冻结解冻合约 委员账号
冻结解冻账号 委员账号
添加撤销运维 委员账号
用户表的写权限 委员账号
部署合约 运维账号
创建表 运维账号
合约版本管理 运维账号
冻结解冻本账号部署的合约 运维账号
调用合约写接口 有管理合约生命周期权限的账号
操作内容
  • 委员新增、撤销与查询
  • 委员权重修改
  • 委员投票生效阈值修改
  • 委员权限
  • 运维新增、撤销与查询
环境配置

配置并启动FISCO BCOS 2.0区块链节点和控制台,请参考安装文档

权限控制示例账户

控制台提供账户生成脚本get_account.sh,生成的账户文件在accounts目录下。控制台可以指定账户启动,具体用法参考控制台手册。因此,通过控制台可以指定账户,体验权限控制功能。在控制台根目录下通过get_account.sh脚本生成三个PEM格式的账户文件如下:

# 账户1
0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a.pem
# 账户2
0x85961172229aec21694d742a5bd577bedffcfec3.pem
# 账户3
0x0b6f526d797425540ea70becd7adac7d50f4a7c0.pem

现在可以打开三个连接Linux的终端,分别以三个账户登录控制台。

指定账户1登录控制台:

./start.sh 1 -pem accounts/0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a.pem

指定账户2登录控制台:

./start.sh 1 -pem accounts/0x85961172229aec21694d742a5bd577bedffcfec3.pem

指定账户3登录控制台:

./start.sh 1 -pem accounts/0x0b6f526d797425540ea70becd7adac7d50f4a7c0.pem
委员新增、撤销与查询

我们添加账户1、账户2为委员,账户3为普通用户。链初始状态,没有任何权限账户记录。测试账户3不能操作委员权限,而账户1、2可以。

委员的权限包括治理投票、增删节点、冻结解冻合约、冻结解冻账号、修改链配置和增删运维账号。

添加账户1为委员

添加账户1为委员成功。

[group:1]> grantCommitteeMember 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a
{
    "code":0,
    "msg":"success"
}
[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
---------------------------------------------------------------------------------------------
使用账户1添加账户2为委员

增加委员需要链治理委员会投票,有效票大于阈值才可以生效。此处由于只有账号1是委员,所以账号1投票即可生效。

[group:1]> grantCommitteeMember 0x85961172229aec21694d742a5bd577bedffcfec3
{
    "code":0,
    "msg":"success"
}

[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
| 0x85961172229aec21694d742a5bd577bedffcfec3  |                      2                      |
---------------------------------------------------------------------------------------------

注意这里使用账户1对应的控制台操作。

验证账号3无权限执行委员操作

在账号3的控制台中操作,此处以链配置操作为例。

[group:1]> setSystemConfigByKey tx_count_limit 100
{
    "code":-50000,
    "msg":"permission denied"
}
撤销账号2的委员权限

此时系统中有两个委员,默认投票生效阈值50%,所以需要两个委员都投票撤销账号2的委员权限,有效票/总票数=2/2=1>0.5才满足条件。

  • 账号1投票撤销账号2的委员权限
[group:1]> revokeCommitteeMember 0x85961172229aec21694d742a5bd577bedffcfec3
{
    "code":0,
    "msg":"success"
}

[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
| 0x85961172229aec21694d742a5bd577bedffcfec3  |                      2                      |
---------------------------------------------------------------------------------------------
  • 账号2投票撤销账号2的委员权限
[group:1]> revokeCommitteeMember 0x85961172229aec21694d742a5bd577bedffcfec3
{
    "code":0,
    "msg":"success"
}

[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
---------------------------------------------------------------------------------------------
委员权重修改

先添加账户1、账户3为委员。然后更新委员1的票数为2。

  • 使用账号1的控制台添加账号3为委员
[group:1]> grantCommitteeMember 0x0b6f526d797425540ea70becd7adac7d50f4a7c0
{
    "code":0,
    "msg":"success"
}

[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
| 0x0b6f526d797425540ea70becd7adac7d50f4a7c0  |                      9                      |
---------------------------------------------------------------------------------------------
  • 使用账号1的控制台投票更新账号1的票数为2
[group:1]> updateCommitteeMemberWeight 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a 2
{
    "code":0,
    "msg":"success"
}

[group:1]> queryCommitteeMemberWeight 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a
Account: 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a Weight: 1
  • 使用账号3的控制台投票更新账号1的票数为2
[group:1]> updateCommitteeMemberWeight 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a 2
{
    "code":0,
    "msg":"success"
}

[group:1]> queryCommitteeMemberWeight 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a
Account: 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a Weight: 2
委员投票生效阈值修改

账户1和账户3为委员,账号1有2票,账号3有1票,使用账号1添加账号2为委员,由于2/3>0.5所以直接生效。使用账号1和账号2,更新生效阈值为75%。

  • 账户1添加账户2为委员
[group:1]> grantCommitteeMember 0x85961172229aec21694d742a5bd577bedffcfec3
{
    "code":0,
    "msg":"success"
}

[group:1]> listCommitteeMembers
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x61d88abf7ce4a7f8479cff9cc1422bef2dac9b9a  |                      1                      |
| 0x0b6f526d797425540ea70becd7adac7d50f4a7c0  |                      9                      |
| 0x85961172229aec21694d742a5bd577bedffcfec3  |                     12                      |
---------------------------------------------------------------------------------------------
  • 使用账户1控制台投票更新生效阈值为75%
[group:1]> updateThreshold 75
{
    "code":0,
    "msg":"success"
}

[group:1]> queryThreshold
Effective threshold : 50%
  • 使用账户2控制台投票更新生效阈值为75%
[group:1]> updateThreshold 75
{
    "code":0,
    "msg":"success"
}

[group:1]> queryThreshold
Effective threshold : 75%
运维新增、撤销与查询

委员可以添加运维,运维角色的权限包括部署合约、创建表、冻结解冻所部署的合约、使用CNS服务。

基于职责权限分离的设计,委员角色不能兼有运维的权限,生成一个新的账号40x283f5b859e34f7fd2cf136c07579dcc72423b1b2.pem

  • 添加账号4为运维角色
[group:1]> grantOperator 0x283f5b859e34f7fd2cf136c07579dcc72423b1b2
{
    "code":0,
    "msg":"success"
}

[group:1]> listOperators
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x283f5b859e34f7fd2cf136c07579dcc72423b1b2  |                     15                      |
---------------------------------------------------------------------------------------------
  • 使用运维账号部署HelloWorld
[group:1]> deploy HelloWorld
contract address: 0xac1e28ad93e0b7f9108fa1167a8a06585f663726
  • 使用账号1部署HelloWorld失败
[group:1]> deploy HelloWorld
permission denied
  • 使用账号1控制台撤销账号4的运维权限
[group:1]> revokeOperator 0x283f5b859e34f7fd2cf136c07579dcc72423b1b2
{
    "code":0,
    "msg":"success"
}

[group:1]> listOperators
Empty set.

基于表的权限控制

本文档描述权限控制的实践操作,有关权限控制的详细设计请参考权限控制设计文档

重要

推荐管理员机制:由于系统默认无权限设置记录,因此任何账户均可以使用权限设置功能。例如当账户1设置账户1有权限部署合约,但是账户2也可以设置账户2有权限部署合约。那么账户1的设置将失去控制的意义,因为其他账户可以自由添加权限。因此,搭建联盟链之前,推荐确定权限使用规则。可以使用grantPermissionManager指令设置链管理员账户,即指定特定账户可以使用权限分配功能,非链管理员账户无权限分配功能。

环境配置

配置并启动FISCO BCOS 2.0区块链节点和控制台,请参考安装文档

权限控制工具

FISCO BCOS提供控制台命令使用权限功能(针对开发者,可以调用SDK API的PermissionService接口使用权限功能),其中涉及的权限控制命令如下:

命令名称 命令参数 功能
grantPermissionManager address 授权账户的链管理员权限
revokePermissionManager address 撤销账户的链管理员权限
listPermissionManager 查询拥有链管理员权限的账户列表
grantDeployAndCreateManager address 授权账户的部署合约和创建用户表权限
revokeDeployAndCreateManager address 撤销账户的部署合约和创建用户表权限
listDeployAndCreateManager 查询拥有部署合约和创建用户表权限的账户列表
grantNodeManager address 授权账户的节点管理权限
revokeNodeManager address 撤销账户的节点管理权限
listNodeManager 查询拥有节点管理的账户列表
grantCNSManager address 授权账户的使用CNS权限
revokeCNSManager address 撤销账户的使用CNS权限
listCNSManager 查询拥有使用CNS的账户列表
grantSysConfigManager address 授权账户的修改系统参数权限
revokeSysConfigManager address 撤销账户的修改系统参数权限
listSysConfigManager 查询拥有修改系统参数的账户列表
grantUserTableManager table_name address 授权账户对用户表的写权限
revokeUserTableManager table_name address 撤销账户对用户表的写权限
listUserTableManager table_name 查询拥有对用户表写权限的账号列表
权限控制示例账户

控制台提供账户生成脚本get_account.sh,生成的账户文件在accounts目录下。控制台可以指定账户启动,具体用法参考控制台手册。因此,通过控制台可以指定账户,体验权限控制功能。为了账户安全起见,我们可以在控制台根目录下通过get_account.sh脚本生成三个PKCS12格式的账户文件,生成过程中输入的密码需要牢记。生成的三个PKCS12格式的账户文件如下:

# 账户1
0x2c7f31d22974d5b1b2d6d5c359e81e91ee656252.p12
# 账户2
0x7fc8335fec9da5f84e60236029bb4a64a469a021.p12
# 账户3
0xd86572ad4c92d4598852e2f34720a865dd4fc3dd.p12

现在可以打开三个连接Linux的终端,分别以三个账户登录控制台。

指定账户1登录控制台:

$ ./start.sh 1 -p12 accounts/0x2c7f31d22974d5b1b2d6d5c359e81e91ee656252.p12

指定账户2登录控制台:

$ ./start.sh 1 -p12 accounts/0x7fc8335fec9da5f84e60236029bb4a64a469a021.p12

指定账户3登录控制台:

$ ./start.sh 1 -p12 accounts/0xd86572ad4c92d4598852e2f34720a865dd4fc3dd.p12
授权账户为链管理员

提供的三个账户设为三种角色,设定账户1为链管理员账户,账户2为系统管理员账户,账户3为普通账户。链管理员账户拥有权限管理的权限,即能分配权限。系统管理员账户可以管理系统相关功能的权限,每一种系统功能权限都需要单独分配,具体包括部署合约和创建用户表的权限、管理节点的权限、利用CNS部署合约的权限以及修改系统参数的权限。链管理员账户可以授权其他账户为链管理员账户或系统管理员账户,也可以授权指定账号可以写指定的用户表,即普通账户。

链初始状态,没有任何权限账户记录。现在,可以进入账户1的控制台,设置账户1成为链管理员账户,则其他账户为非链管理员账户。

[group:1]> grantPermissionManager 0x2c7f31d22974d5b1b2d6d5c359e81e91ee656252
{
    "code":0,
    "msg":"success"
}

[group:1]> listPermissionManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x2c7f31d22974d5b1b2d6d5c359e81e91ee656252  |                      1                      |
---------------------------------------------------------------------------------------------

设置账户1为链管理员成功。

授权账户为系统管理员
授权部署合约和创建用户表

通过账户1授权账户2为系统管理员账户,首先授权账户2可以部署合约和创建用户表。

[group:1]> grantDeployAndCreateManager 0x7fc8335fec9da5f84e60236029bb4a64a469a021
{
    "code":0,
    "msg":"success"
}

[group:1]> listDeployAndCreateManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x7fc8335fec9da5f84e60236029bb4a64a469a021  |                      2                      |
---------------------------------------------------------------------------------------------

登录账户2的控制台,部署控制台提供的TableTest合约。TableTest.sol合约代码参考这里。其提供创建用户表t_test和相关增删改查的方法。

[group:1]> deploy TableTest.sol
contract address:0xfe649f510e0ca41f716e7935caee74db993e9de8

调用TableTest的create接口创建用户表t_test。

[group:1]> call TableTest.sol 0xfe649f510e0ca41f716e7935caee74db993e9de8 create
transaction hash:0x67ef80cf04d24c488d5f25cc3dc7681035defc82d07ad983fbac820d7db31b5b
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
createResult index: 0
count = 0
---------------------------------------------------------------------------------------------

用户表t_test创建成功。

登录账户3的控制台,部署TableTest合约。

[group:1]> deploy TableTest.sol
{
    "code":-50000,
    "msg":"permission denied"
}

账户3没有部署合约的权限,部署合约失败。

  • 注意: 其中部署合约和创建用户表是“二合一”的控制项,在使用Table合约(CRUD接口合约)时,我们建议部署合约的时候一起把合约里用到的表创建了(在合约的构造函数中创建表),否则接下来读写表的交易可能会遇到“缺表”错误。如果业务流程需要动态创建表,动态建表的权限也应该只分配给少数账户,否则链上可能会出现各种废表。
授权利用CNS部署合约

控制台提供3个涉及CNS的命令,如下所示:

命令名称 命令参数 功能
deployByCNS contractName contractVersion 利用CNS部署合约
callByCNS contractName contractVersion funcName params 利用CNS调用合约
queryCNS contractName [contractVersion] 查询CNS信息

注意: 其中deployByCNS命令受权限可以控制,且同时需要部署合约和使用CNS的权限,callByCNS和queryCNS命令不受权限控制。

登录账户1的控制台,授权账户2拥有利用CNS部署合约的权限。

[group:1]> grantCNSManager 0x7fc8335fec9da5f84e60236029bb4a64a469a021
{
    "code":0,
    "msg":"success"
}

[group:1]> listCNSManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x7fc8335fec9da5f84e60236029bb4a64a469a021  |                     13                      |
---------------------------------------------------------------------------------------------

登录账户2的控制台,利用CNS部署合约。

[group:1]> deployByCNS TableTest.sol 1.0
contract address:0x24f902ff362a01335db94b693edc769ba6226ff7

[group:1]> queryCNS TableTest.sol
---------------------------------------------------------------------------------------------
|                   version                   |                   address                   |
|                     1.0                     | 0x24f902ff362a01335db94b693edc769ba6226ff7  |
---------------------------------------------------------------------------------------------

登录账户3的控制台,利用CNS部署合约。

[group:1]> deployByCNS TableTest.sol 2.0
{
    "code":-50000,
    "msg":"permission denied"
}

[group:1]> queryCNS TableTest.sol
---------------------------------------------------------------------------------------------
|                   version                   |                   address                   |
|                     1.0                     | 0x24f902ff362a01335db94b693edc769ba6226ff7  |
---------------------------------------------------------------------------------------------

部署失败,账户3无权限利用CNS部署合约。

授权管理节点

控制台提供5个有关节点类型操作的命令,如下表所示:

命令名称 命令参数 功能
addSealer nodeID 设置节点为共识节点
addObserver nodeID 设置节点为观察节点
removeNode nodeID 设置节点为游离节点
getSealerList 查询共识节点列表
getObserverList 查询观察节点列表
  • 注意: 其中addSealer、addObserver和removeNode命令受权限控制,getSealerList和getObserverList命令不受权限控制。

登录账户1的控制台,授权账户2拥有管理节点的权限。

[group:1]> grantNodeManager 0x7fc8335fec9da5f84e60236029bb4a64a469a021
{
    "code":0,
    "msg":"success"
}

[group:1]> listNodeManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x7fc8335fec9da5f84e60236029bb4a64a469a021  |                     20                      |
---------------------------------------------------------------------------------------------

登录账户2的控制台,查看共识节点列表。

[group:1]> getSealerList
[
    01cd46feef2bb385bf03d1743c1d1a52753129cf092392acb9e941d1a4e0f499fdf6559dfcd4dbf2b3ca418caa09d953620c2aa3c5bbe93ad5f6b378c678489e,
    279c4adfd1e51e15e7fbd3fca37407db84bd60a6dd36813708479f31646b7480d776b84df5fea2f3157da6df9cad078c28810db88e8044741152eb037a19bc17,
    320b8f3c485c42d2bfd88bb6bb62504a9433c13d377d69e9901242f76abe2eae3c1ca053d35026160d86db1a563ab2add127f1bbe1ae96e7d15977538d6c0fb4,
    c26dc878c4ff109f81915accaa056ba206893145a7125d17dc534c0ec41c6a10f33790ff38855df008aeca3a27ae7d96cdcb2f61eb8748fefe88de6412bae1b5
]

查看观察节点列表:

[group:1]> getObserverList
[]

将第一个nodeID对应的节点设置为观察节点:

[group:1]> addObserver 01cd46feef2bb385bf03d1743c1d1a52753129cf092392acb9e941d1a4e0f499fdf6559dfcd4dbf2b3ca418caa09d953620c2aa3c5bbe93ad5f6b378c678489e
{
    "code":0,
    "msg":"success"
}

[group:1]> getObserverList
[
    01cd46feef2bb385bf03d1743c1d1a52753129cf092392acb9e941d1a4e0f499fdf6559dfcd4dbf2b3ca418caa09d953620c2aa3c5bbe93ad5f6b378c678489e
]

[group:1]> getSealerList
[
    279c4adfd1e51e15e7fbd3fca37407db84bd60a6dd36813708479f31646b7480d776b84df5fea2f3157da6df9cad078c28810db88e8044741152eb037a19bc17,
    320b8f3c485c42d2bfd88bb6bb62504a9433c13d377d69e9901242f76abe2eae3c1ca053d35026160d86db1a563ab2add127f1bbe1ae96e7d15977538d6c0fb4,
    c26dc878c4ff109f81915accaa056ba206893145a7125d17dc534c0ec41c6a10f33790ff38855df008aeca3a27ae7d96cdcb2f61eb8748fefe88de6412bae1b5
]

登录账户3的控制台,将观察节点加入共识节点列表。

[group:1]> addSealer 01cd46feef2bb385bf03d1743c1d1a52753129cf092392acb9e941d1a4e0f499fdf6559dfcd4dbf2b3ca418caa09d953620c2aa3c5bbe93ad5f6b378c678489e
{
    "code":-50000,
    "msg":"permission denied"
}

[group:1]> getSealerList
[
    279c4adfd1e51e15e7fbd3fca37407db84bd60a6dd36813708479f31646b7480d776b84df5fea2f3157da6df9cad078c28810db88e8044741152eb037a19bc17,
    320b8f3c485c42d2bfd88bb6bb62504a9433c13d377d69e9901242f76abe2eae3c1ca053d35026160d86db1a563ab2add127f1bbe1ae96e7d15977538d6c0fb4,
    c26dc878c4ff109f81915accaa056ba206893145a7125d17dc534c0ec41c6a10f33790ff38855df008aeca3a27ae7d96cdcb2f61eb8748fefe88de6412bae1b5
]

[group:1]> getObserverList
[
    01cd46feef2bb385bf03d1743c1d1a52753129cf092392acb9e941d1a4e0f499fdf6559dfcd4dbf2b3ca418caa09d953620c2aa3c5bbe93ad5f6b378c678489e
]

添加共识节点失败,账户3没有权限管理节点。现在只有账户2有权限将观察节点加入共识节点列表。

授权修改系统参数

控制台提供2个关于修改系统参数的命令,如下表所示:

命令名称 命令参数 功能
setSystemConfigByKey key value 设置键为key,值为value的系统参数
getSystemConfigByKey key 根据key查询value
  • 注意: 目前支持键为tx_count_limit和tx_gas_limit的系统参数设置。其中setSystemConfigByKey命令受权限控制,getSystemConfigByKey命令不受权限控制。

登录账户1的控制台,授权账户2拥有修改系统参数的权限。

[group:1]> grantSysConfigManager 0x7fc8335fec9da5f84e60236029bb4a64a469a021
{
    "code":0,
    "msg":"success"
}

[group:1]> listSysConfigManager
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0x7fc8335fec9da5f84e60236029bb4a64a469a021  |                     23                      |
---------------------------------------------------------------------------------------------

登录账户2的控制台,修改系统参数tx_count_limit的值为2000。

[group:1]> getSystemConfigByKey tx_count_limit
1000

[group:1]> setSystemConfigByKey tx_count_limit 2000
{
    "code":0,
    "msg":"success"
}

[group:1]> getSystemConfigByKey tx_count_limit
2000

登录账户3的控制台,修改系统参数tx_count_limit的值为3000。

[group:1]> setSystemConfigByKey tx_count_limit 3000
{
    "code":-50000,
    "msg":"permission denied"
}

[group:1]> getSystemConfigByKey tx_count_limit
2000

设置失败,账户3没有修改系统参数的权限。

授权账户写用户表

通过账户1授权账户3可以写用户表t_test的权限。

[group:1]> grantUserTableManager t_test 0xd86572ad4c92d4598852e2f34720a865dd4fc3dd
{
    "code":0,
    "msg":"success"
}
[group:1]> listUserTableManager t_test
---------------------------------------------------------------------------------------------
|                   address                   |                 enable_num                  |
| 0xd86572ad4c92d4598852e2f34720a865dd4fc3dd  |                      6                      |
---------------------------------------------------------------------------------------------

登录账户3的控制台,在用户表t_test插入一条记录,然后查询该表的记录。

[group:1]> call TableTest.sol 0xfe649f510e0ca41f716e7935caee74db993e9de8 insert "fruit" 1 "apple"

transaction hash:0xc4d261026851c3338f1a64ecd4712e5fc2a028c108363181725f07448b986f7e
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
InsertResult index: 0
count = 1
---------------------------------------------------------------------------------------------

[group:1]> call TableTest.sol 0xfe649f510e0ca41f716e7935caee74db993e9de8 select "fruit"
[[fruit], [1], [apple]]

登录账户2的控制台,更新账户3插入的记录,并查询该表的记录。

[group:1]> call TableTest.sol 0xfe649f510e0ca41f716e7935caee74db993e9de8 update "fruit" 1 "orange"
{
    "code":-50000,
    "msg":"permission denied"
}
[group:1]> call TableTest.sol 0xfe649f510e0ca41f716e7935caee74db993e9de8 select "fruit"
[[fruit], [1], [apple]]

更新失败,账户2没有权限更新用户表t_test。

  • 通过账户1撤销账户3写用户表t_test的权限。
[group:1]> revokeUserTableManager t_test 0xd86572ad4c92d4598852e2f34720a865dd4fc3dd
{
    "code":0,
    "msg":"success"
}

[group:1]> listUserTableManager t_test
Empty set.

撤销成功。

  • 注意: 此时没有账户拥有对用户表t_test的写权限,因此对该表的写权限恢复了初始状态,即所有账户均拥有对该表的写权限。如果让账户1没有对该表的写权限,则可以通过账号1授权另外一个账号,比如账号2拥有该表的写权限实现。

CA黑白名单

本文档描述CA黑、白名单的实践操作,建议阅读本操作文档前请先行了解《CA黑白名单介绍》

黑名单

通过配置黑名单,能够拒绝与指定的节点连接。

配置方法

编辑config.ini

[certificate_blacklist]
    ; crl.0 should be nodeid, nodeid's length is 128
    ;crl.0=

重启节点生效

$ bash stop.sh && bash start.sh

查看节点连接

$ curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

白名单

通过配置白名单,能够只与指定的节点连接,拒绝与白名单之外的节点连接。

配置方法

编辑config.ini不配置表示白名单关闭,可与任意节点建立连接。

[certificate_whitelist]
    ; cal.0 should be nodeid, nodeid's length is 128
    cal.0=7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb
    cal.1=f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0

若节点未启动,则直接启动节点,若节点已启动,可直接用脚本reload_whitelist.sh刷新白名单配置即可(暂不支持动态刷新黑名单)。

# 若节点未启动
$ bash start.sh
# 若节点已启动
$ cd scripts
$ bash reload_whitelist.sh
node_127.0.0.1_30300 is not running, use start.sh to start and enable whitelist directlly.

查看节点连接

$ curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

使用场景:公共CA

所有用CFCA颁发证书搭的链,链的CA都是CFCA。此CA是共用的。必须启用白名单功能。使用公共CA搭的链,会存在两条链共用同一个CA的情况,造成无关的两条链的节点能彼此建立连接。此时需要配置白名单,拒绝与无关的链的节点建立连接。

搭链操作步骤

  1. 用工具搭链
  2. 查询所有节点的NodeID
  3. 将所有NodeID配置入每个节点的白名单中
  4. 启动节点或用脚本reload_whitelist.sh刷新节点白名单配置

扩容操作步骤

  1. 用工具扩容一个节点
  2. 查询此扩容节点的NodeID
  3. 将此NodeID追加到入所有节点的白名单配置中
  4. 将其他节点的白名单配置拷贝到新扩容的节点上
  5. 用脚本reload_whitelist.sh刷新已启动的所有节点的白名单配置
  6. 启动扩容节点
  7. 将扩容节点加成组员(addSealer 或 addObserver)

黑白名单操作举例

准备

搭一个四个节点的链

bash build_chain.sh -l "127.0.0.1:4"

查看四个节点的NodeID

$ cat node*/conf/node.nodeid
219b319ba7b2b3a1ecfa7130ea314410a52c537e6e7dda9da46dec492102aa5a43bad81679b6af0cd5b9feb7cfdc0b395cfb50016f56806a2afc7ee81bbb09bf
7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb
f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0
38158ef34eb2d58ce1d31c8f3ef9f1fa829d0eb8ed1657f4b2a3ebd3265d44b243c69ffee0519c143dd67e91572ea8cb4e409144a1865f3e980c22d33d443296

可得四个节点的NodeID:

  • node0: 219b319b….
  • node1: 7718df20….
  • node2: f306eb10….
  • node3: 38158ef3….

启动所有节点

$ cd node/127.0.0.1/
$ bash start_all.sh

查看连接,以node0为例。(8545是node0的rpc端口)

$ curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

可看到连接信息,node0连接了除自身之外的其它三个节点。

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:62774",
      "Node": "node3",
      "NodeID": "38158ef34eb2d58ce1d31c8f3ef9f1fa829d0eb8ed1657f4b2a3ebd3265d44b243c69ffee0519c143dd67e91572ea8cb4e409144a1865f3e980c22d33d443296",
      "Topic": []
    },
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:62766",
      "Node": "node1",
      "NodeID": "7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb",
      "Topic": []
    },
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:30302",
      "Node": "node2",
      "NodeID": "f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0",
      "Topic": []
    }
  ]
}
配置黑名单:node0拒绝node1的连接

将node1的NodeID写入node0的配置中

vim node0/config.ini

需要进行的配置如下,白名单为空(默认关闭)

[certificate_blacklist]
    ; crl.0 should be nodeid, nodeid's length is 128
    crl.0=7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb

[certificate_whitelist]
    ; cal.0 should be nodeid, nodeid's length is 128
    ; cal.0=

重启节点生效

$ cd node0
$ bash stop.sh && bash start.sh

查看节点连接

$ curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

可看到只与两个节点建立的连接,未与node1建立连接

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:30303",
      "Node": "node3",
      "NodeID": "38158ef34eb2d58ce1d31c8f3ef9f1fa829d0eb8ed1657f4b2a3ebd3265d44b243c69ffee0519c143dd67e91572ea8cb4e409144a1865f3e980c22d33d443296",
      "Topic": []
    },
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:30302",
      "Node": "node2",
      "NodeID": "f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0",
      "Topic": []
    }
  ]
}
配置白名单:node0拒绝与node1,node2之外的节点连接

将node1和node2的NodeID写入node0的配置中

$ vim node0/config.ini

需要进行的配置如下,黑名单置空,白名单配置上node1,node2

[certificate_blacklist]
    ; crl.0 should be nodeid, nodeid's length is 128
    ;crl.0=

[certificate_whitelist]
    ; cal.0 should be nodeid, nodeid's length is 128
    cal.0=7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb
    cal.1=f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0

重启节点生效

$ bash stop.sh && bash start.sh

查看节点连接

$ curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

可看到只与两个节点建立的连接,未与node1建立连接

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:30302",
      "Node": "node2",
      "NodeID": "f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0",
      "Topic": []
    },
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:30301",
      "Node": "node1",
      "NodeID": "7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb",
      "Topic": []
    }
  ]
}
黑名单与白名单混合配置:黑名单优先级高于白名单,白名单配置的基础上拒绝与node1建立连接

编辑node0的配置

$ vim node0/config.ini

需要进行的配置如下,黑名单配置上node1,白名单配置上node1,node2

[certificate_blacklist]
    ; crl.0 should be nodeid, nodeid's length is 128
    crl.0=7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb

[certificate_whitelist]
    ; cal.0 should be nodeid, nodeid's length is 128
    cal.0=7718df20f0f7e27fdab97b3d69deebb6e289b07eb7799c7ba92fe2f43d2efb4c1250dd1f11fa5b5ce687c8283d65030aae8680093275640861bc274b1b2874cb
    cal.1=f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0

重启节点生效

$ bash stop.sh && bash start.sh

查看节点连接

$ curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

可看到虽然白名单上配置了node1,但由于node1在黑名单中也有配置,node0也不能与node1建立连接

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "Agency": "agency",
      "IPAndPort": "127.0.0.1:30302",
      "Node": "node2",
      "NodeID": "f306eb1066ceb9d46e3b77d2833a1bde2a9899cfc4d0433d64b01d03e79927aa60a40507c5739591b8122ee609cf5636e71b02ce5009f3b8361930ecc3a9abb0",
      "Topic": []
    }
  ]
}

存储安全

联盟链的数据,只对联盟内部成员可见。落盘加密,保证了运行联盟链的数据,在硬盘上的安全性。一旦硬盘被带出联盟链自己的内网环境,数据将无法被解密。

落盘加密是对节点存储在硬盘上的内容进行加密,加密的内容包括:合约的数据、节点的私钥。

具体的落盘加密介绍,可参考:落盘加密的介绍

部署Key Manager

每个机构一个Key Manager,具体的部署步骤,可参考Key Manager README

重要

若节点为国密版,Key Manager也需是国密版。

生成节点

build_chain.sh脚本,用普通的操作方法,先生成节点。

curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\.[0-9]\.[0-9]\"" | sort -u | tail -n 1 | cut -d \" -f 4`/build_chain.sh && chmod u+x build_chain.sh

bash build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545

重要

节点生成后,不能启动,待dataKey配置后,再启动。节点在第一次运行前,必须配置好是否采用落盘加密。一旦节点开始运行,无法切换状态。

启动Key Manager

直接启动key-manager。若未部署key-manager,可参考Key Manager README

# 参数:端口,superkey
./key-manager 31443 123xyz

启动成功,打印日志

[1546501342949][TRACE][Load]key-manager started,port=31443

配置dataKey

重要

配置dataKey的节点,必须是新生成,未启动过的节点。

执行脚本,定义dataKey,获取cipherDataKey

cd key-manager/scripts
bash gen_data_secure_key.sh 127.0.0.1 31443 123456

CiherDataKey generated: ed157f4588b86d61a2e1745efe71e6ea
Append these into config.ini to enable disk encryption:
[storage_security]
enable=true
key_manager_ip=127.0.0.1
key_manager_port=31443
cipher_data_key=ed157f4588b86d61a2e1745efe71e6ea

得到cipherDataKey,脚本自动打印出落盘加密需要的ini配置(如下)。此时得到节点的cipherDataKey:cipher_data_key=ed157f4588b86d61a2e1745efe71e6ea 将得到的落盘加密的ini配置,写入节点配置文件(config.ini)中。

vim nodes/127.0.0.1/node0/config.ini

修改[storage_security]中的字段如下。

[storage_security]
enable=true
key_manager_ip=127.0.0.1
key_manager_port=31443
cipher_data_key=ed157f4588b86d61a2e1745efe71e6ea

加密节点私钥

执行脚本,加密节点私钥

cd key-manager/scripts
# 参数:ip port 节点私钥文件 cipherDataKey
bash encrypt_node_key.sh 127.0.0.1 31443 ../../nodes/127.0.0.1/node0/conf/node.key ed157f4588b86d61a2e1745efe71e6ea

执行后,节点私钥自动被加密,加密前的文件备份到了文件node.key.bak.xxxxxx中,请将备份私钥妥善保管,并删除节点上生成的备份私钥

[INFO] File backup to "nodes/127.0.0.1/node0/conf/node.key.bak.1546502474"
[INFO] "nodes/127.0.0.1/node0/conf/node.key" encrypted!

若查看node.key,可看到,已经被加密为密文

8b2eba71821a5eb15b0cbe710e96f23191419784f644389c58e823477cf33bd73a51b6f14af368d4d3ed647d9de6818938ded7b821394446279490b537d04e7a7e87308b66fc82ab3987fb9f3c7079c2477ed4edaf7060ae151f237b466e4f3f8a19be268af65d7b4ae8be37d81810c30a0f00ec7146a1125812989c2205e1e37375bc5e4654e569c21f0f59b3895b137f3ede01714e2312b74918e2501ac6568ffa3c10ae06f7ce1cbb38595b74783af5fea7a8a735309db3e30c383e4ed1abd8ca37e2aa375c913e3d049cb439f01962dd2f24b9e787008c811abd9a92cfb7b6c336ed78d604a3abe3ad859932d84f11506565f733d244f75c9687ef9334b8fabe139a82e9640db9e956b540f8b61675d04c3fb070620c7c135f3f4f6319aae8b6df2b091949a2c9938e5c1e5bb13c0f530764b7c2a884704637be953ce887

重要

所有需要加密的文件列举如下,若未加密,节点无法启动。

  • 非国密版:conf/node.key
  • 国密版:conf/gmnode.key和conf/origin_cert/node.key

节点运行

直接启动节点即可

cd nodes/127.0.0.1/node0/
./start.sh

正确性判断

(1)节点正常运行,正常共识,不断输出共识打包信息。

tail -f nodes/127.0.0.1/node0/log/* | grep +++

(2)key-manager在节点每次启动时,都会打印一条日志。例如,节点在一次启动时,Key Manager直接输出的日志如下。

[1546504272699][TRACE][Dec]Respond
{
   "dataKey" : "313233343536",
   "error" : 0,
   "info" : "success"
}

国密支持

为了充分支持国产密码学算法,金链盟基于国产密码学标准,在FISCO BCOS平台中集成了国密加解密、签名、验签、哈希算法、国密SSL通信协议,实现了对国家密码局认定的商用密码的完全支持。设计文档见国密版FISCO BCOS设计手册

初次部署国密版FISCO BCOS

本节使用build_chain脚本在本地搭建一条4节点的FISCO BCOS链,以Ubuntu 16.04系统为例操作。本节使用预编译的静态fisco-bcos二进制文件,在CentOS 7和Ubuntu 16.04上经过测试。

# Ubuntu16安装依赖
sudo apt install -y openssl curl
# 准备环境
cd ~ && mkdir -p fisco && cd fisco
# 下载build_chain.sh脚本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.6.0/build_chain.sh && chmod u+x build_chain.sh
  • 搭建4节点FISCO BCOS链
# 生成一条4节点的FISCO链 4个节点都属于group1 下面指令在fisco目录下执行
# -p指定起始端口,分别是p2p_port,channel_port,jsonrpc_port
# 根据下面的指令,需要保证机器的30300~30303,20200~20203,8545~8548端口没有被占用
# -g 搭建国密版本的链
# -G 设置`chain.sm_crypto_channel=true`。确认sdk支持的情况下(web3sdk v2.5.0+),可以指定-G参数,连接也使用国密SSL
$ ./build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545 -g -G

关于build_chain.sh脚本选项,请参考这里。命令正常执行会输出All completed。(如果没有输出,则参考nodes/build.log检查)。

[INFO] Downloading tassl binary ...
Generating CA key...
Generating Guomi CA key...
==============================================================
Generating keys ...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
Generating configurations...
Processing IP:127.0.0.1 Total:4 Agency:agency Groups:1
==============================================================
[INFO] FISCO-BCOS Path   : bin/fisco-bcos
[INFO] Start Port        : 30300 20200 8545
[INFO] Server IP         : 127.0.0.1:4
[INFO] State Type        : storage
[INFO] RPC listen IP     : 127.0.0.1
[INFO] Output Dir        : /mnt/c/Users/asherli/Desktop/key-manager/build/nodes
[INFO] CA Key Path       : /mnt/c/Users/asherli/Desktop/key-manager/build/nodes/gmcert/ca.key
[INFO] Guomi mode        : yes
==============================================================
[INFO] All completed. Files in /mnt/c/Users/asherli/Desktop/key-manager/build/nodes

当国密联盟链部署完成之后,其余操作与安装的操作相同。

国密配置信息

国密版本FISCO BCOS节点之间采用SSL安全通道发送和接收消息,证书主要配置如下:

[network_security]

data_path:证书文件所在路径
key:节点私钥相对于data_path的路径
cert: 证书gmnode.crt相对于data_path的路径
ca_cert: gmca证书路径

;certificate configuration
[network_security]
    ;directory the certificates located in
    data_path=conf/
    ;the node private key file
    key=gmnode.key
    ;the node certificate file
    cert=gmnode.crt
    ;the ca certificate file
    ca_cert=gmca.crt

FISCO-BCOS 2.5.0版本以后,节点与SDK之间既支持SSL连接进行通信,也支持国密SSL连接进行通信,相关配置如下:

[chain]
    ; use SM crypto or not, should nerver be changed
    sm_crypto=true
    ; use SM SSL connection with SDK
    sm_crypto_channel=true

国密版SDK使用

详细操作参考SDK文档

国密版控制台配置

详情操作参考配置国密版控制台

国密控制台使用

国密版控制台功能与标准版控制台使用方式相同,见控制台操作手册

国密落盘加密配置

国密版Key Manager

国密版的Key Manager需重新编译Key Manager,不同点在于cmake时带上-DBUILD_GM=ON选项。

# centos下
cmake3 .. -DBUILD_GM=ON
# ubuntu下
cmake .. -DBUILD_GM=ON

其它步骤与标准版Key Manager相同,请参考:key-manager repository

国密版节点配置

FISCO BCOS国密版采用双证书模式,因此落盘加密需要加密的两套证书,分别为:conf/gmnode.key 和 conf/origin_cert/node.key。其它与标准版落盘加密操作相同。

cd key-manager/scripts
#加密 conf/gmnode.key 参数:ip port 节点私钥文件 cipherDataKey
bash encrypt_node_key.sh 127.0.0.1 31443 nodes/127.0.0.1/node0/conf/gmnode.key ed157f4588b86d61a2e1745efe71e6ea
#加密 conf/origin_cert/node.key 参数:ip port 节点私钥文件 cipherDataKey
bash encrypt_node_key.sh 127.0.0.1 31443 nodes/127.0.0.1/node0/conf/origin_cert/node.key ed157f4588b86d61a2e1745efe71e6ea

日志说明

FISCO BCOS的所有群组日志都输出到log目录下log_%YYYY%mm%dd%HH.%MM的文件中,且定制了日志格式,方便用户通过日志查看各群组状态。日志配置说明请参考日志配置说明

日志格式

每一条日志记录格式如下:

# 日志格式:
log_level|time|[g:group_id][module_name] content

# 日志示例:
info|2019-06-26 16:37:08.253147|[g:3][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=a4e10062...,next=1,tx=0,nodeIdx=2

各字段含义如下:

  • log_level: 日志级别,目前主要包括trace, debug, info, warning, errorfatal,其中在发生极其严重错误时会输出fatal
  • time: 日志输出时间,精确到纳秒
  • group_id: 输出日志记录的群组ID
  • module_name:模块关键字,如同步模块关键字为SYNC,共识模块关键字为CONSENSUS
  • content:日志记录内容

常见日志说明

共识打包日志

注解

  • 仅共识节点会周期性输出共识打包日志(节点目录下可通过命令 tail -f log/* | grep "${group_id}.*++" 查看指定群组共识打包日志)
  • 打包日志可检查指定群组的共识节点是否异常,异常的共识节点不会输出打包日志

下面是共识打包日志的示例:

info|2019-06-26 18:00:02.551399|[g:2][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,nodeIdx=3,hash=1f9c2b14...

日志中各字段的含义如下:

  • blkNum: 打包区块的高度
  • tx: 打包区块中包含的交易数
  • nodeIdx: 当前共识节点的索引
  • hash: 打包区块的哈希

共识异常日志

网络抖动、网络断连或配置出错(如同一个群组的创世块文件不一致)均有可能导致节点共识异常,PBFT共识节点会输出ViewChangeWarning日志,示例如下:

warning|2019-06-26 18:00:06.154102|[g:1][CONSENSUS][PBFT]ViewChangeWarning: not caused by omit empty block ,v=5,toV=6,curNum=715,hash=ed6e856d...,nodeIdx=3,myNode=e39000ea...

该日志各字段含义如下:

  • v: 当前节点PBFT共识视图
  • toV: 当前节点试图切换到的视图
  • curNum: 节点最高块高
  • hash: 节点最高块哈希
  • nodeIdx: 当前共识节点索引
  • myNode: 当前节点Node ID

区块落盘日志

区块共识成功或节点正在从其他节点同步区块,均会输出落盘日志。

注解

向节点发交易,若交易被处理,非游离节点均会输出落盘日志(节点目录下可通过命令 tail -f log/* | grep "${group_id}.*Report" 查看节点出块情况),若没有输出该日志,说明节点已处于异常状态,请优先检查网络连接是否正常、节点证书是否有效

下面是区块落盘日志:

info|2019-06-26 18:00:07.802027|[g:1][CONSENSUS][PBFT]^^^^^^^^Report,num=716,sealerIdx=2,hash=dfd75e06...,next=717,tx=8,nodeIdx=3

日志中各字段说明如下:

  • num: 落盘区块块高
  • sealerIdx: 打包该区块的共识节点索引
  • hash: 落盘区块哈希
  • next: 下一个区块块高
  • tx: 落盘区块中包含的交易数
  • nodeIdx: 当前共识节点索引

网络连接日志

注解

节点目录下可通过命令 tail -f log/* | grep "connected count" 检查网络状态,若日志输出的网络连接数目不符合预期,请通过 netstat -anp | grep fisco-bcos 命令检查节点连接

日志示例如下:

info|2019-06-26 18:00:01.343480|[P2P][Service] heartBeat,connected count=3

日志中各字段含义如下:

  • connected count: 与当前节点建立P2P网络连接的节点数

日志模块关键字

FISCO BCOS日志中核心模块关键字如下:

模块 模块关键字
区块链初始化模块 INITIALIZER
网络基础模块 NETWORK
P2P网络模块 P2P
ChannelRPC模块 CHANNEL
RPC模块 RPC
账本模块 LEDGER
共识区块打包模块 CONSENSUS, SEALER
PBFT共识处理模块 CONSENSUS, PBFT
RAFT共识处理模块 CONSENSUS, RAFTENGINE
区块/交易同步模块 SYNC
交易池 TXPOOL
区块链模块 BLOCKCHAIN
区块验证器模块 BLOCKVERIFIER
DAG模块 DAG
区块执行模块 EXECUTIVECONTEXT
Precompile合约 PRECOMPILED
存储中间件模块 STORAGE
External存储引擎 SQLConnectionPool
MySQL存储引擎 ZdbStorage

Caliper压力测试指南

一、环境要求

1.1 硬件
  • 需要外网权限
1.2 操作系统
  • 版本要求:Ubuntu >= 16.04, CentOS >= 7, MacOS >= 10.14
1.3 基础软件
  • python 2.7,make,g++,gcc,git
1.4 NodeJS
  • 版本要求:

    NodeJS 8 (LTS), 9, 或 10 (LTS),Caliper尚未在更高的NodeJS版本中进行过验证。

  • 安装指南:

    建议使用nvm(Node Version Manager)安装,nvm的安装方式如下:

    # 安装nvm
    curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
    # 加载nvm配置
    source ~/.$(basename $SHELL)rc
    # 安装Node.js 8
    nvm install 8
    # 使用Node.js 8
    nvm use 8
    
1.5 Docker
  • 版本要求:>= 18.06.01

  • 安装指南:

    CentOS:

    # 添加源
    sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    # 更新缓存
    sudo yum makecache fast
    # 安装社区版Docker
    sudo yum -y install docker-ce
    # 将当前用户加入docker用户组(重要)
    sudo groupadd docker
    sudo gpasswd -a ${USER} docker
    # 重启Docker服务
    sudo service docker restart
    newgrp - docker
    # 验证Docker是否已经启动
    sudo systemctl status docker
    

    Ubuntu

    # 更新包索引
    sudo apt-get update
    # 安装基础依赖库
    sudo apt-get install \
        apt-transport-https \
        ca-certificates \
        curl \
        gnupg-agent \
        software-properties-common
    # 添加Docker官方GPG key
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    # 添加docker仓库
    sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
    # 更新包索引
    sudo apt-get update
    # 安装Docker
    sudo apt-get install docker-ce docker-ce-cli containerd.io
    

    MacOs下

    请安装Docker Desktop

  • 加入Docker用户组

    CentOS

    sudo groupadd docker
    sudo gpasswd -a ${USER} docker
    
    # 重启Docker服务
    sudo service docker restart
    # 验证Docker是否已经启动
    sudo systemctl status docker
    

    Ubuntu

    sudo groupadd docker
    sudo usermod -aG docker $USER
    
1.6 Docker Compose
  • 版本要求:>= 1.22.0

  • 安装指南:

    sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose
    

二、Caliper部署

2.1 部署

Caliper提供了方便易用的命令行界面工具caliper-cli,推荐在本地进行局部安装:

  1. 建立一个工作目录
mkdir benchmarks && cd benchmarks
  1. 对NPM项目进行初始化
npm init

这一步主要是为在工作目录下创建package.json文件以方便后续依赖项的安装,如果不需要填写项目信息的话可以直接执行npm init -y

  1. 安装caliper-cli
npm install --only=prod @hyperledger/caliper-cli@0.2.0

由于Caliper所有依赖项的安装较为耗时,因此使用--only=prod选项用于指定NPM只安装Caliper的核心组件,而不安装其他的依赖项(如各个区块链平台针对Caliper的适配器)。在部署完成后,可以通过caliper-cli显式绑定需要测试的区块链平台及相应的适配器。

  1. 验证caliper-cli安装成功
npx caliper --version

若安装成功,则会打印相应的版本信息,如:

user@ubuntu:~/benchmarks$ npx caliper --version
v0.2.0
2.2 绑定

由于Caliper采用了轻量级的部署方式,因此需要显式的绑定步骤指定要测试的平台及适配器版本,caliper-cli会自动进行相应依赖项的安装。使用npx caliper bind命令进行绑定,命令所需的各项参数可以通过如下命令查看:

user@ubuntu:~/benchmarks$ npx caliper bind --help
Usage:
  caliper bind --caliper-bind-sut fabric --caliper-bind-sdk 1.4.1 --caliper-bind-cwd ./ --caliper-bind-args="-g"

Options:
  --help               Show help  [boolean]
  -v, --version        Show version number  [boolean]
  --caliper-bind-sut   The name of the platform to bind to  [string]
  --caliper-bind-sdk   Version of the platform SDK to bind to  [string]
  --caliper-bind-cwd   The working directory for performing the SDK install  [string]
  --caliper-bind-args  Additional arguments to pass to "npm install". Use the "=" notation when setting this parameter  [string]

其中,

–caliper-bind-sut :用于指定需要测试的区块链平台,即受测系统(***S***ystem ***u***nder ***T***est); –caliper-bind-sdk:用于指定适配器版本; –caliper-bind-cwd:用于绑定caliper-cli的工作目录,caliper-cli在加载配置文件等场合时均是使用相对于工作目录的相对路径; caliper-bind-args:用于指定caliper-cli在安装依赖项时传递给npm的参数,如用于全局安装的-g

对于FISCO BCOS,可以采用如下方式进行绑定:

npx caliper bind --caliper-bind-sut fisco-bcos --caliper-bind-sdk latest

由于FISCO BCOS对于caliper 0.2.0版本的适配存在部分不兼容情况,需要手动按照https://github.com/FISCO-BCOS/FISCO-BCOS/issues/1248中的步骤修改代码后方可正常运行。

2.3 快速体验FISCO BCOS基准测试

为方便测试人员快速上手,FISCO BCOS已经为Caliper提供了一组预定义的测试样例,测试对象涵盖HelloWorld合约、Solidity版转账合约及预编译版转账合约。同时在测试样例中,Caliper测试脚本会使用docker在本地自动部署及运行4个互连的节点组成的链,因此测试人员无需手工搭链及编写测试用例便可直接运行这些测试样例。

  1. 在工作目录下下载预定义测试用例:
git clone https://github.com/vita-dounai/caliper-benchmarks.git
  1. 执行HelloWorld合约测试
npx caliper benchmark run --caliper-workspace caliper-benchmarks --caliper-benchconfig benchmarks/samples/fisco-bcos/helloworld/config.yaml  --caliper-networkconfig networks/fisco-bcos/4nodes1group/fisco-bcos.json
  1. 执行Solidity版转账合约测试
npx caliper benchmark run --caliper-workspace caliper-benchmarks --caliper-benchconfig benchmarks/samples/fisco-bcos/transfer/solidity/config.yaml  --caliper-networkconfig networks/fisco-bcos/4nodes1group/fisco-bcos.json
  1. 执行预编译版转账合约测试
npx caliper benchmark run --caliper-workspace caliper-benchmarks --caliper-benchconfig benchmarks/samples/fisco-bcos/transfer/precompiled/config.yaml  --caliper-networkconfig networks/fisco-bcos/4nodes1group/fisco-bcos.json

测试完成后,会在命令行界面中展示测试结果(TPS、延迟等)及资源消耗情况,同时会在caliper-benchmarks目录下生成一份包含上述内容的可视化HTML报告。

caliper benchmark run所需的各项参数可以通过如下命令查看:

user@ubuntu:~/benchmarks$ npx caliper benchmark run --help
caliper benchmark run --caliper-workspace ~/myCaliperProject --caliper-benchconfig my-app-test-config.yaml --caliper-networkconfig my-sut-config.yaml

Options:
  --help                   Show help  [boolean]
  -v, --version            Show version number  [boolean]
  --caliper-benchconfig    Path to the benchmark workload file that describes the test client(s), test rounds and monitor.  [string]
  --caliper-networkconfig  Path to the blockchain configuration file that contains information required to interact with the SUT  [string]
  --caliper-workspace      Workspace directory that contains all configuration information  [string]

其中,

–caliper-workspace:用于指定caliper-cli的工作目录,如果没有绑定工作目录,可以通过该选项动态指定工作目录; –caliper-benchconfig:用于指定测试配置文件,测试配置文件中包含测试的具体参数,如交易的发送方式、发送速率控制器类型、性能监视器类型等; –caliper-networkconfig:用于指定网络配置文件,网络配置文件中会指定Caliper与受测系统的连接方式及要部署测试的合约等。

三、自定义测试用例

本节将会以测试HelloWorld合约为例,介绍如何使用Caliper测试自定义的测试用例。

Caliper前后端分离的设计原则使得只要后端的区块链系统开放了相关网络端口,Caliper便可以对该系统进行测试。结合Docker提供的性能数据统计服务或本地的ps命令工具,Caliper能够在测试的同时收集节点所在机器上的各种性能数据,包括CPU、内存、网络及磁盘的使用等。尽管Caliper能工作在不使用Docker模式而是使用原生二进制ficos-bcos可执行程序搭建出的链上,但是那样Caliper将无法获知节点所在机器上的资源消耗。因此,在目前的Caliper版本下(v0.2.0),我们推荐使用Docker模式搭链。

3.1、配置Docker Daemon及部署FISCO BCOS网络

如果只想基于已经搭建好的链进行测试,可以跳过本小节。

3.1.1 配置Docker Daemon

为方便Caliper统一管理节点容器及监控性能数据,在运行节点的服务器上首先需要开启Docker Daemon服务。

开始之前,先停止docker进程:

sudo service docker stop

创建/etc/docker/daemon.json文件(如果已经存在则修改),加入以下内容:

{
  "hosts" : ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
}

“unix:///var/run/docker.sock”:UNIX套接字,本地客户端将通过这个来连接Docker Daemon; tcp://0.0.0.0:2375,TCP套接字,表示允许任何远程客户端通过2375端口连接Docker Daemon.

使用sudo systemctl edit docker新建或修改/etc/systemd/system/docker.service.d/override.conf,其内容如下:

##Add this to the file for the docker daemon to use different ExecStart parameters (more things can be added here)
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd

默认情况下使用systemd时,docker.service的设置为:ExecStart=/usr/bin/dockerd -H fd://,这将覆写daemon.json中的任何hosts。通过override.conf文件将ExecStart定义为:ExecStart=/usr/bin/dockerd,就能使daemon.json中设置的hosts生效。override.conf中的第一行ExecStart=必须要有,这一行将用于清除默认的ExecStart参数。

重新加载daemon并重启docker服务:

sudo systemctl daemon-reload
sudo systemctl restart docker.service

检查端口监听:

sudo netstat -anp | grep 2375

如果出现以下字样则表明配置成功:

tcp6       0      0 :::2375                 :::*                    LISTEN      79018/dockerd

此时能够在另一台机器上通过远程连接访问本机的Docker Daemon服务,例如:

# 假设开启Docker Daemon服务的机器IP地址为192.168.1.1
docker -H 192.168.1.1:2375 images
3.1.2 建链

使用开发部署工具 build_chain.sh脚本快速建链。本节以4个节点、全连接的形式搭链,但本节所述的测试方法能够推广任意数量节点及任意网络拓扑形式的链。

创建生成节点的配置文件(如一个名为ipconf的文件),文件内容如下:

192.168.1.1:1 agency1 1
192.168.1.2:1 agency1 1
192.168.1.3:1 agency1 1
192.168.1.4:1 agency1 1

生成链中节点的配置文件:

bash build_chain.sh -f ipconf -i -p 30914,20914,8914

将产生的节点配置文件夹分别拷贝至对应的服务器上:

scp -r 192.168.1.1/node0/ app@192.168.1.1:/data/test
scp -r 192.168.1.2/node0/ app@192.168.1.2:/data/test
scp -r 192.168.1.3/node0/ app@192.168.1.3:/data/test
scp -r 192.168.1.4/node0/ app@192.168.1.3:/data/test
3.2 配置FISCO BCOS适配器

在另外一台机器上部署Caliper,部署教程见第二节。

3.2.1 网络配置

新建一个名为4nodes1group的目录,本阶示例中的FISCO BCOS适配器的网络配置文件均会放置于此。新建一个名为fisco-bcos.json的配置文件,文件内容如下:

{
    "caliper": {
        "blockchain": "fisco-bcos",
        "command": {
            "start": "sh network/fisco-bcos/4nodes1group/start.sh",
            "end": "sh network/fisco-bcos/4nodes1group/end.sh"
        }
    },
    "fisco-bcos": {
        "config": {
            "privateKey": "bcec428d5205abe0f0cc8a734083908d9eb8563e31f943d760786edf42ad67dd",
            "account": "0x64fa644d2a694681bd6addd6c5e36cccd8dcdde3"
        },
        "network": {
            "nodes": [
                {
                    "ip": "192.168.1.1",
                    "rpcPort": "8914",
                    "channelPort": "20914"
                },
                {
                    "ip": "192.168.1.2",
                    "rpcPort": "8914",
                    "channelPort": "20914"
                },
                {
                    "ip": "192.168.1.3",
                    "rpcPort": "8914",
                    "channelPort": "20914"
                },
           ],
            "authentication": {
                "key": "packages/caliper-samples/network/fisco-bcos/4nodes1group/sdk/node.key",
                "cert": "packages/caliper-samples/network/fisco-bcos/4nodes1group/sdk/node.crt",
                "ca": "packages/caliper-samples/network/fisco-bcos/4nodes1group/sdk/ca.crt"
            },
            "groupID": 1,
            "timeout": 600000
        },
        "smartContracts": [
            {
                "id": "helloworld",
                "path": "src/contract/fisco-bcos/helloworld/HelloWorld.sol",
                "language": "solidity",
                "version": "v0"
            }
        ]
    },
    "info": {
        "Version": "2.0.0",
        "Size": "4 Nodes",
        "Distribution": "Remote Host"
    }
}

配置文件中每一项的具体含义如下:

  • caliper.command.start

启动Caliper时会首先执行start配置中指定的命令,主要用于初始化SUT。本文示例中使用Docker模式启动,启动Caliper时首先执行当前目录下的start.sh文件,其具体内容是:

docker -H 192.168.1.1:2375 run -d --rm --name node0 -v /data/test/node0/:/data -p 8914:8914 -p 20914:20914 -p 30914:30914 -w=/data fiscoorg/fiscobcos:latest -c config.ini 1> /dev/null
docker -H 192.168.1.2:2375 run -d --rm --name node1 -v /data/test/node0/:/data -p 8914:8914 -p 20914:20914 -p 30914:30914 -w=/data fiscoorg/fiscobcos:latest -c config.ini 1> /dev/null
docker -H 192.168.1.3:2375 run -d --rm --name node2 -v /data/test/node0/:/data -p 8914:8914 -p 20914:20914 -p 30914:30914 -w=/data fiscoorg/fiscobcos:latest -c config.ini 1> /dev/null
docker -H 192.168.1.4:2375 run -d --rm --name node3 -v /data/test/node0/:/data -p 8914:8914 -p 20914:20914 -p 30914:30914 -w=/data fiscoorg/fiscobcos:latest -c config.ini 1> /dev/null

即启动远程的Docker容器。如果不需要在Caliper启动时执行命令,需要将该配置项置空。

  • caliper.command.end

Caliper在退出流程的最后会执行end配置指定的命令,主要用于清理环境。本例中在测试结束时会执行当前目录下的end.sh文件,其具体内容是:

docker -H 192.168.1.1:2375 stop $(docker -H 192.168.1.1:2375 ps -a | grep node0 | cut -d " " -f 1) 1> /dev/null && echo -e "\033[32mremote container node0 stopped\033[0m"
docker -H 192.168.1.2:2375 stop $(docker -H 192.168.1.2:2375 ps -a | grep node1 | cut -d " " -f 1) 1> /dev/null && echo -e "\033[32mremote container node1 stopped\033[0m"
docker -H 192.168.1.3:2375 stop $(docker -H 192.168.1.3:2375 ps -a | grep node2 | cut -d " " -f 1) 1> /dev/null && echo -e "\033[32mremote container node2 stopped\033[0m"
docker -H 192.168.1.4:2375 stop $(docker -H 192.168.1.3:2375 ps -a | grep node3 | cut -d " " -f 1) 1> /dev/null && echo -e "\033[32mremote container node3 stopped\033[0m"

即停止并删除有所的远程容器。如果不需要在Caliper退出时执行命令,需要将该配置项置空。

  • network.nodes

一个包含了所有要连接节点的列表,列表中每一项需要指明被连接节点的IP地址、RPC端口及Channel端口号,所有端口号需要和节点的配置文件保持一致。

  • network.authentication

适配器向节点的Channel端口发起请求时需要使用CA根证书等文件,这些文件已在3.1.2节中调用build_chain.sh脚本时已经生成好,使用任一节点配置下的sdk文件夹中的相应文件即可,需要在该配置中写上所有文件的路径(使用相对路径时需要以caliper-cli工作目录为起始目录)。

  • network.smartContracts

指定要测试的合约,Caliper会在启动时想后端区块链系统部署合约。目前FISCO BCOS适配器支持通过language字段指定两种类型的合约——Solidity合约和预编译合约,当测试合约为Solidity合约时,language字段需要指定为solidity,当测试合约为预编译合约时,language字段需要指定为precompiled。当测试合约为预编译合约时,需要在address字段中指定预编译合约的地址,否则需要在path字段中指定Solidity合约的路径。

3.2.2 测试配置

测试配置用于指定测试的具体运行方式。测试配置是一个YAML文件,HelloWorld合约的测试配置文件内容如下所示:

---
test:
  name: Hello World
  description: This is a helloworld benchmark of FISCO BCOS for caliper
  clients:
    type: local
    number: 1
  rounds:
  - label: get
    description: Test performance of getting name
    txNumber:
    - 10000
    rateControl:
    - type: fixed-rate
      opts:
        tps: 1000
    callback: benchmarks/samples/fisco-bcos/helloworld/get.js
  - label: set
    description: Test performance of setting name
    txNumber:
    - 10000
    rateControl:
    - type: fixed-rate
      opts:
        tps: 1000
    callback: benchmarks/samples/fisco-bcos/helloworld/set.js
monitor:
  type:
  - docker
  docker:
    name:
    - http://192.168.1.1:2375/node0
    - http://192.168.1.2:2375/node1
    - http://192.168.1.3:2375/node2
    - http://192.168.1.4:2375/node3
  interval: 0.1

测试文件中主要包括两部分:

  • 测试内容配置

test项负责对测试内容进行配置。配置主要集中在round字段中指定如何对区块链系统进行测试。每一个测试可以包含多轮,每一轮可以向区块链发起不同的测试请求。具体要HelloWorld合约测试,测试中包含两轮,分别对合约的get接口和set接口进行测试。在每一轮测试中,可以通过txNumbertxDuration字段指定测试的交易发送数量或执行时间,并通过rateControl字段指定交易发送时的速率控制器,在本节的示例中,使用了QPS为1000的匀速控制器,更多速率控制器的介绍可以参考官方文档

  • 性能监视器配置

monitor项负责对测试所使用的性能监视器的进行配置。每项配置项的解释如下:

  1. monitor.type,需要指定为docker,指对docker容器进行监控;
  2. monitor.docker.name,一个包含所有要监视的节点的docker容器名称列表,名字必须以http://开头,其后跟随”{节点的IP}:{节点docker daemon端口}/{docker容器的名称}”;
  3. monitor.interval,监视器的采样间隔,单位为秒。

如果是在本地搭好的链,则可以添加本地性能监视器,相应地监视器的配置更改如下:

monitor:
  type:
  - process
  process:
    - command: node0
      multiOutput: avg
    - command: node1
      multiOutput: avg
    - command: node2
      multiOutput: avg
    - command: node3
      multiOutput: avg
  interval: 0.1

其中每项配置项的解释如下:

  1. monitor.type,需要指定为process,只对进程进行监控;
  2. monitor.process,一个包含所有要监视的进称列表,其中每个进程的command属性为一个正则表达式,表示进程名称;每个进程还可以有一个arguments属性(未在上述示例中使用到),表示进程的参数。Caliper会先使用ps命令搜索commad + arguments,然后匹配以得到目标的进程的进程ID及系统资源的使用情况。每个进程的multiOutput属性用于指定结果的输出方式,目前支持平均值(avg)及总和(sum)两种方式;
  3. monitor.interval,监视器的采样间隔,单位为秒。

需要注意的是,进程监控目前暂不支持监控进程对网络和磁盘的使用情况。

隐私保护

隐私保护是联盟链的一大技术挑战。为了保护链上数据、保障联盟成员隐私,并且保证监管的有效性,FISCO BCOS以预编译合约的形式集成了同态加密、群/环签名验证功能,提供了多种隐私保护手段。

文档一、二节分别对同态加密和群/环签名算法以及相关应用场景进行了简单介绍,第三、四节则详细介绍了FISCO BCOS隐私保护模块启用方法以及调用方式。

同态加密

算法简介

同态加密(Homomorphic Encryption)是公钥密码系统领域的明珠之一,已有四十余年的研究历史。其绝妙的密码特性,吸引密码学家前赴后继,在业界也受到了广泛的关注。

  • 同态加密本质是一种公钥加密算法,即加密使用公钥pk,解密使用私钥sk;
  • 同态加密支持密文计算,即由相同公钥加密生成的密文可以计算​f( )操作,生成的新密文解密后恰好等于两个原始明文计算f( )的结果;
  • 同态加密公式描述如下:

_images/formula.jpg

FISCO BCOS采用的是paillier加密算法,支持加法同态。paillier的公私钥兼容主流的RSA加密算法,接入门槛低。同时paillier作为一种轻量级的同态加密算法,计算开销小易被业务系统接受。因此经过功能性和可用性的权衡,最终选定了paillier算法。

功能组件

FISCO BCOS同态加密模块提供的功能组件包括:

  • paillier同态库,包括java库和c++同态接口。
  • paillier预编译合约,供智能合约调用,提供密文同态运算接口。
使用方式

对于有隐私保护需求的业务,如果涉及简单密文计算,可借助本模块实现相关功能。凡是上链的数据可通过调用paillier库完成加密,链上的密文数据可通过调用paillier预编译合约实现密文的同态加运算,密文返还回业务层后,可通过调用paillier库完成解密,得到执行结果。具体流程如下图所示:

_images/paillier.jpg

应用场景

在联盟链中,不同的业务场景需要配套不同的隐私保护策略。对于强隐私的业务,比如金融机构之间的对账,对资产数据进行加密是很有必要的。在FISCO BCOS中,用户可以调用同态加密库对数据进行加密,共识节点执行交易的时候调用同态加密预编译合约,得到密文计算的结果。

群/环签名

算法简介

群签名

群签名(Group Signature)是一种能保护签名者身份的具有相对匿名性的数字签名方案,用户可以代替自己所在的群对消息进行签名,而验证者可以验证该签名是否有效,但是并不知道签名属于哪一个群成员。同时,用户无法滥用这种匿名行为,因为群管理员可以通过群主私钥打开签名,暴露签名的归属信息。群签名的特性包括:

  • 匿名性:群成员用群参数产生签名,其他人仅可验证签名的有效性,并通过签名知道签名者所属群组,却无法获取签名者身份信息;
  • 不可伪造性:只有群成员才能生成有效可被验证的群签名;
  • 不可链接性:给定两个签名,无法判断它们是否来自同一个签名者;
  • 可追踪性:在监管介入的场景中,群主可通过签名获取签名者身份。

环签名

环签名(Ring Signature)是一种特殊的群签名方案,但具备完全匿名性,即不存在管理员这个角色,所有成员可主动加入环,且签名无法被打开。环签名的特性包括:

  • 不可伪造性:环中其他成员不能伪造真实签名者签名;
  • 完全匿名性:没有群主,只有环成员,其他人仅可验证环签名的有效性,但没有人可以获取签名者身份信息。
功能组件

FISCO BCOS群/环签名模块提供的功能组件包括:

  • 群/环签名库,提供完整的群/环签名算法c++接口
  • 群/环签名预编译合约,供智能合约调用,提供群/环签名验证接口。
使用方式

有签名者身份隐匿需求的业务可借助本模块实现相关功能。签名者通过调用群/环签名库完成对数据的签名,然后将签名上链,业务合约通过调用群/环签名预编译合约完成签名的验证,并将验证结果返还回业务层。如果是群签名,那么监管方还能打开指定签名数据,获得签名者身份。具体流程如下图所示:

_images/group_sig.jpg

应用场景

群/环签名由于其天然的匿名性,在需要对参与者身份进行隐匿的场景中有广泛的应用前景,例如匿名投票、匿名竞拍、匿名拍卖等等,甚至在区块链UTXO模型中可用于实现匿名转账。同时,由于群签名具备可追踪性,可以用于需要监管介入的场景,监管方作为群主或者委托群主揭露签名者身份。

开发示例

FISCO BCOS专门为用户提供了群/环签名开发示例,包括:

  • 群/环签名服务端: 提供完整的群/环签名RPC服务。
  • 群/环签名客户端: 调用RPC服务对数据进行签名,并提供签名上链以及链上验证等功能。

示例框架如下图所示,具体操作方法请参阅客户端指南

_images/demo.jpg

启用方法

The FISCO BCOS privacy protection module is implemented through a precompiled contract and is enabled by default. For source code compilation FISCO BCOS, please refer to here.

搭建联盟链

假设当前位于FISCO-BCOS/build目录下,则使用下面的指令搭建本机4节点的链指令如下,更多选项参考这里

bash ../tools/build_chain.sh -l "127.0.0.1:4" -e bin/fisco-bcos 

预编译合约接口

隐私模块的代码和用户开发的预编译合约都位于FISCO-BCOS/libprecompiled/extension目录,因此隐私模块的调用方式和用户开发的预编译合约调用流程相同,不过有两点需要注意:

1.已为隐私模块的预编译合约分配了地址,无需另行注册。隐私模块实现的预编译合约列表以及地址分配如下:

地址 功能 源码
0x5003 同态加密 PaillierPrecompiled.cpp
0x5004 群签名 GroupSigPrecompiled.cpp
0x5005 环签名 RingSigPrecompiled.cpp

2.需要通过solidity合约方式声明隐私模块预编译合约的接口,合约文件需保存在控制台合约目录console/contracts/solidity中,各个隐私功能的合约接口如下,可直接复制使用:

  • 同态加密

    // PaillierPrecompiled.sol
    pragma solidity ^0.4.24;
    contract PaillierPrecompiled{
        function paillierAdd(string cipher1, string cipher2) public constant returns(string);
    }
    
  • 群签名

    // GroupSigPrecompiled.sol
    pragma solidity ^0.4.24;
    contract GroupSigPrecompiled{
    		function groupSigVerify(string signature, string message, string gpkInfo, string paramInfo) public constant returns(bool);
    }
    
  • 环签名

    // RingSigPrecompiled.sol
    pragma solidity ^0.4.24;
    contract RingSigPrecompiled{
         function ringSigVerify(string signature, string message, string paramInfo) public constant returns(bool);
    }
    

控制台调用

使用新编译出的二进制搭建节点后,部署控制台v1.0.2以上版本,将预编译合约接口声明文件拷贝到控制台合约目录。以调用同态加密为例,命令如下:

# 在console目录下启动控制台
bash start.sh

# 调用合约
call PaillierPrecompiled.sol 0x5003 paillierAdd "0100E97E06A781DAAE6DBC9C094FC963D73B340D99FD934782A5D629E094D3B051FBBEA26F46BB681EB5314AE98A6A63805834E26BD0154243581DD77709C5DB15357DBEC34F8D8B9D5892FDF5F5FC56B1474CF58D6AC23AA867E34653B469058DCDBDC283D493711022131FBCBCFAC639919A7C0FE45EECDBD43FA543492D3058737F567626318282C2C35027B05E901F45CB3B71479FC49FD08B3F81C82326DEF28739D6A7D090F93D1B5058CDA89134AB103DB49EA51FF14310179FF9B3284BC6C3B6BA3BB0FCB35FEA6AF1453A4AAEB3EB82E9B99875BEA89CD970905B40E6E5FC906F0959769865FF29A83CD23ACC0C832A0DE30D0C691335A7ABE2EA0782778E14DAACACD60767862D5E285E8FB7B6D62C9AABE1BE751DD68001F492D6FD4C46583B25FF1806A954E8DB5ED935C5A2CA6816C4EDB48D4A41D146E09EF1CA5AFECC38F38E4147D9C6D07429F058266CC879AF8AA88510E85608756585C8D78400DFFA32244B99DC1A533780CAEA07578AF340C3C4ABED09355A006FCE99F22BE113F5317256198ACB6CA9D8501EDF42A11CFCCF677755D081E48249B9345FA8C2F73E6CB37CB17C2A5A1EA4DC44949A63E8DA45F3DCE827654688312F399873E897CDD413C84DC0891884BEF8ECBC99F48CBB3DA2D04B40CDCB03A6CD8FDC11277A5AA5F73DB6B828AB24978480604D0143A0A5020EE985A88EEC73FD9DF205E5CD5C54C06ADD092E839B91F28887C9BF7775275552E1B673997A792683B784827078CC7BF7121318B0565739588268372EDD320B1BB2FEE8D873222C00AF93D07C0572BF3E089806EA7E3C8D334973B4BE61E31E389CB919FCEE834E1D9EBA624DE324D85425BCCDF8C3F8F3A94E33A307CAAE5922E46FFE96A521ECB6E7D681E7CF6A1900EEF0DDF23ADEC6EFA8842110FF1F266AEDA7B501DBC94D20817DD43D9EB056E3DA4DA977E85A53207F2C229F9302EB5029B5C31EE40FC7E25591CDC6B4AD206BDFB50C5F7D2DA2D6B8AB7A6B575C20FDD12A37EBECF27D60B651842DED09776218613F72628C1A3540252895A192DF51A1B7479EFC45A4B489FC" "0100E97E06A781DAAE6DBC9C094FC963D73B340D99FD934782A5D629E094D3B051FBBEA26F46BB681EB5314AE98A6A63805834E26BD0154243581DD77709C5DB15357DBEC34F8D8B9D5892FDF5F5FC56B1474CF58D6AC23AA867E34653B469058DCDBDC283D493711022131FBCBCFAC639919A7C0FE45EECDBD43FA543492D3058737F567626318282C2C35027B05E901F45CB3B71479FC49FD08B3F81C82326DEF28739D6A7D090F93D1B5058CDA89134AB103DB49EA51FF14310179FF9B3284BC6C3B6BA3BB0FCB35FEA6AF1453A4AAEB3EB82E9B99875BEA89CD970905B40E6E5FC906F0959769865FF29A83CD23ACC0C832A0DE30D0C691335A7ABE2EA07827736DDD4C285AB8C5F53DA58502BD2E312C939139E591C83D009C763836B05676CEDE46C9BFEBA7AF6242739F1E27A0AABB13F37CFF2DFEE5D14D52A660FDD012748025F9915585123FD263E211C594A0EDB7AFDA1240A2C340C44C3568FA47AB2CC00459DF31828C56CAC0284B5D0D3BC3D6F37969FACED77B03107B88F60A73E1F9FEBE6152FB00BDAECA9954AC28D4D9599FE234AF2E52748DBEB65570F2B99A638C275235494189B887FAEA39FE12CB1BAE9AE085E353D4DC01863052FE141D87D98E78C46FFD0F634D498E4E4B2F87B60D505F8401DCCC1BC0D9E32B8C5AF6A6257DB61DDD166CC17E712626218C16D00C24042D928028972816919C1CD9E1AB2F3135D798BE795D79013C3BDE507811E05D88050E7DF1BD3ED0EB7405BA21E854551A7EBD6351E0B9300428C77B1FA532DB9C5D9A0D4BB9F7E96BAFA259D419D75398141801B148C3EF9AE437A424E4E781238964C10EE39260DD0058392CD83C1DFEDAE2D557E5E7D643608B1BB0327AB92550A66F6D636F9F638A5077D721E6BD9344851E3FE288984F120C05A62DD9E283498B5AD680E91E0CBAD3093598B54E8A6964EB406068BB765945B182CD5EBC8910F2DE80C902751EEB77FCB583784DD852E4B6FF2CC1EBA44A5F750B2AD11240F7B95D87055F3CC5A837FA682117ACA1787CF107C9D4B111C8B9FBB78553452E351A8F0E3C50F536CA3304C"

# 返回结果
0100E97E06A781DAAE6DBC9C094FC963D73B340D99FD934782A5D629E094D3B051FBBEA26F46BB681EB5314AE98A6A63805834E26BD0154243581DD77709C5DB15357DBEC34F8D8B9D5892FDF5F5FC56B1474CF58D6AC23AA867E34653B469058DCDBDC283D493711022131FBCBCFAC639919A7C0FE45EECDBD43FA543492D3058737F567626318282C2C35027B05E901F45CB3B71479FC49FD08B3F81C82326DEF28739D6A7D090F93D1B5058CDA89134AB103DB49EA51FF14310179FF9B3284BC6C3B6BA3BB0FCB35FEA6AF1453A4AAEB3EB82E9B99875BEA89CD970905B40E6E5FC906F0959769865FF29A83CD23ACC0C832A0DE30D0C691335A7ABE2EA0782774D011D9A668B26E65506EF2E8B3EBA70B882DE36FEC5951B64B9D967EE5E05B1AF62EE569358360C43220A756E7FB610FCBD5A366D077C48342EE3025735D6590531A7E609ADE2608BB4173167F972AB860674DB89769E2D97EE3E308D3CA04802EF8F85BC6BFCD270F1FC6AEA5C09F51E8914273D8BD7833D55D9AF3D0102315880A57D531E30EDD8E777CDE8708AE7DDF83A8C5B48854FD4CD4E7372CED017C0BACD49E409C45F7071113B12494D3955BA1D7618E196A14012B11ADB63B857C9033604575FC7FF1D5833A53ACDE8877311FFE78F0CAEBAA27B0E5ADCDD809AEDCD5C7D2CA7F15E53AB7D62ADF04686E05B1F79CA91AFD2CE120AAD7D0F15C8E7B59968CE13BA10C99B50BE696C22A59109C3E6E5EDBE364FF5717443C175DEE5680908AEF67AF6261644AEEFAD42538A8686481CF0109296D1A2FF030143A0DED95F54CC158BF3A3FCD0B2278BDB6942D6E536CA45E3015B764FF5A94B57D232F86CFC66A29C55B9A78336026FFB6D8882E6F4CE2F8D007C225B6B3DE814FC60EB278B25FB0A1F6B4A34E920CA952BC3F14D121D5438E12634AD951EBD5911B281E3ADEC43410B91DC28B68F9D79D7F203245E87EE1DB3883B0C925C5A9BA157AB2F07ACD9A09F89EF211EED16358E78EDAF091FBA914225BF8A7DCDD2DD12EC0ABBC10E9E5F7DD48239B0A68CBD81637B1C0D7ED1DF89D714DFC6C1B7B6B3

注: 控制台输入的密文可通过paillier库中的java库生成。

solidity合约调用

以调用同态加密为例,通过在solidity合约中创建预编译合约对象并调用其接口,在控制台console/contracts/solidity创建CallPaillier.sol文件,文件内容如下:

// CallPaillier.sol
pragma solidity ^0.4.24;
import "./PaillierPrecompiled.sol";

contract CallPaillier {
    PaillierPrecompiled paillier;
    function CallPaillier() {
        // 调用PaillierPrecompiled预编译合约
        paillier = PaillierPrecompiled(0x5003); 
    }
    function add(string cipher1, string cipher2) public constant returns(string) {
        return paillier.paillierAdd(cipher1, cipher2);
    }
}

部署CallPaillier合约,然后调用CallPaillier合约的add接口,使用上面的密文作为输入,可以得到相同的结果。

国内镜像和CDN加速攻略

本节为访问GitHub较慢的用户提供国内镜像下载地址,以及CDN加速访问介绍。

FISCO BCOS源码与二进制程序

源码同步

FISCO BCOS当前所有仓库源码位于https://github.com/FISCO-BCOS/FISCO-BCOS,每个新的版本发布会将代码合入master分支。

为了方便国内用户,我们同样在gitee上提供了镜像仓库https://gitee.com/FISCO-BCOS/FISCO-BCOS,每次新版本发布后,镜像仓库会同步GitHub上官方仓库的更新,如果从GitHub下载失败,请尝试使用gitee镜像仓库。

二进制程序

FISCO BCOS每个新版本发布会在GitHub的tag中提供对应的二进制程序和部署工具,同时在官网https://www.fisco.com.cn也会提供同样的二进制程序,官网通过CDN加速下载。当前所提供的二进制程序包括:

  1. fisco-bcos.tar.gz :静态二进制程序,支持CentOS 7 和Ubuntu 16.04以上版本
  2. fisco-bcos-gm.tar.gz :国密版本的静态二进制程序,支持CentOS 7 和Ubuntu 16.04以上版本
  3. build_chain.sh :对应版本的开发部署工具,依赖openssl和curl,支持CentOS 7/Ubuntu 16.04以上/macOS 10.15以上版本
  4. fisco-bcos-macOS.tar.gz :对应macOS系统的二进制程序

用户使用开发部署工具(build_chain),工具先尝试从GitHub下载所需要的二进制程序,如果下载失败则尝试从官网下载。

用户运维部署工具(generator)的时候,工具默认从GitHub下载所需要的二进制程序,可以通过–cdn参数指定从官网下载。例如./generator --download_fisco ./meta --cdn

FISCO BCOS文档

FISCO BCOS文档使用readthedocs管理,全部开源于https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/,同样提供国内镜像http://www.fisco-bcos.org/developer/

每个版本发布会为上个版本的文档打Tag,新版本的文档会合入主干分支,文档由于会持续改进,所以是下个版本发布才打上个版本的tag。readthedocs文档支持下载PDF格式,方便用户使用。

FISCO BCOS配套工具

控制台

FISCO BCOS控制台是一个交互式命令行工具,使用Java开发,代码位于https://github.com/FISCO-BCOS/console,国内镜像https://gitee.com/FISCO-BCOS/console

控制台每个版本发布会提供编译好的包,用户下载后配置后即可使用,为了下载控制台用户需要获取download_console.sh脚本。此脚本会从GitHub下载最新版本console.tar.gz,如果下载失败则尝试从官网CDN下载。下面的指令从国内镜像获取download_console.sh脚本并执行。

curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh && bash download_console.sh
TASSL

FISCO BCOS国密版本需要使用TASSL生成国密版本的证书,部署工具会自动从GitHub下载,解压后放置于~/.fisco/tassl,如果碰到下载失败,请尝试从https://gitee.com/FISCO-BCOS/LargeFiles/blob/master/tools/tassl.tar.gz下载并解压后,放置于~/.fisco/tassl

账户生成脚本

FISCO BCOS在国密模式下使用sm2曲线和对应签名算法,在非国密场景使用secp256k1曲线和ecdsa签名算法。为方便用户提供了生成脚本,脚本生成私钥并以账户地址命名,支持PEM和PKCS12两种格式。详情请参考这里https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/account.html

get_account.sh脚本依赖于openssl指令,用于生成secp256k1私钥,如果从GitHub下载失败,可以尝试镜像地址 https://gitee.com/FISCO-BCOS/console/raw/master/tools/get_account.sh

get_gm_account.sh脚本用于生成sm2私钥,依赖于TASSL。如果从GitHub下载失败,可以尝试镜像地址 https://gitee.com/FISCO-BCOS/console/raw/master/tools/get_gm_account.sh

举例:使用国内镜像建链

本节以搭建2.4.0国密版本为例,使用国内镜像建链,非国密版本的操作类似,参考https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/installation.html

下载开发部署工具
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.4.0/build_chain.sh

如果下载失败请尝试curl -#LO https://gitee.com/FISCO-BCOS/FISCO-BCOS/raw/master/tools/build_chain.sh

下载二进制程序

开发部署工具(build_chain)会自动下载二进制程序,下载失败自动切换官网CDN,不需要用户关注。用户也可以手动下载二进制程序或编译源码,通过开发部署工具的-e选项指定,此时工具不会再去下载。-e选项参考https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/build_chain.html#e-optional

搭建2.4.0国密FISCO BCOS链

搭建国密版本时,开发部署工具还依赖tassl,工具会自动下载,如果失败请用户参考TASSL手动下载方法,下载解压后放置于~/.fisco/tassl。执行下面的指令,输出All completed即表示执行成功。

bash build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545 -g -v 2.4.0

举例:使用国内源码镜像编译

本节以CentOS 7 为例,从gitee镜像下载源码并编译,其他操作系统编译流程类似,请参考https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/get_executable.html#id2

安装依赖
sudo yum install -y epel-release
sudo yum install -y openssl-devel openssl cmake3 gcc-c++ git
下载源码
git clone https://gitee.com/FISCO-BCOS/FISCO-BCOS.git
下载依赖包

FISCO BCOS在编译时会自动下载依赖包,每个依赖包有多个源。如果在编译阶段下载依赖包失败,请根据提示从下面的国内镜像手动下载,放置于FISCO-BCOS/deps/src目录下,再次make

https://gitee.com/FISCO-BCOS/LargeFiles/tree/master/libs

编译源码
cd FISCO-BCOS && mkdir build && cd build
cmake3 ..
make -j2

SDK白名单机制

FISCO BCOS 2.0开始支持多群组,但没有控制SDK对多群组的访问权限,SDK只要可以与节点建立连接,就可以访问节点的所有群组,会带来安全风险。

FISCO BCOS v2.6.0引入了群组级别的SDK白名单机制,控制SDK对群组的访问权限,并提供脚本支持SDK白名单列表的动态加载,进一步提升区块链系统的安全性。

注解

  • 当配置项中的SDK白名单列表数目为0时,节点没有开启SDK白名单控制功能,任意SDK均可访问该群组;
  • SDK白名单是节点级别的访问控制机制,开启该功能的节点基于本地配置的白名单列表控制SDK对本节点群组的访问权限
  • SDK白名单机制控制SDK对节点所有群组级接口的访问权限

配置方法

群组级别的SDK白名单配置选项位于group.{group_id}.ini ,具体可参考 这里

注解

每个群组的SDK白名单配置位于 group.{group_id}.ini 配置文件的 [sdk_allowlist] 配置项中,详细可参考 这里

获取SDK公钥

将sdk加入到白名单列表前,首先需要获取SDK的公钥,用于设置group.*.inipublic_key,各个版本获取SDK公钥的方法如下:

新搭建的链

新搭建的链生成的SDK证书自带SDK私钥信息,非国密版为sdk.publickey,国密版为gmsdk.publickey

# 设证书已拷贝到SDK,则进入SDK目录,执行如下命令(设sdk位于~/fisco目录)
$ cd ~/fisco/web3sdk

# 获取国密版SDK公钥
$ cat dist/conf/sdk.publickey

# 获取非国密版SDK公钥
$ cat dist/conf/gmsdk.publickey
旧链

旧链需要使用openssl或tassl命令生成sdk.publickey(国密版是gmsdk.publickey),并从生成的文件中加载公钥,具体如下:

非国密

# 进入SDK目录,执行如下命令:
$ openssl ec -in dist/conf/sdk.key -text -noout 2> /dev/null | sed -n '7,11p' | tr -d ": \n" | awk '{print substr($0,3);}' | cat > dist/conf/sdk.publickey
# 获取SDK公钥
$ cat dist/conf/sdk.publickey

国密链

# 注:必须保证~/.fisco/tassl存在
$ ~/.fisco/tassl ec -in dist/conf/gmsdk.key -text -noout 2> /dev/null | sed -n '7,11p' | sed 's/://g' | tr "\n" " " | sed 's/ //g' | awk '{print substr($0,3);}'  | cat > dist/conf/gmsdk.publickey
# 获取SDK公钥
$ cat dist/conf/gmsdk.publickey

动态修改SDK白名单列表

为方便用户修改SDK白名单列表,每个节点的scripts目录下提供了reload_sdk_allowlist.sh脚本用于重新加载SDK白名单列表。

注解

旧链节点无 reload_sdk_allowlist.sh 脚本,可通过命令 curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/reload_sdk_allowlist.sh 下载该脚本。

示例

本节以将控制台加入和删除白名单列表为例,详细展示SDK白名单机制的使用方法。

搭建区块链并拷贝证书到控制台

请参考安装

获取控制台公钥信息

# 进入到控制台目录
$ cd ~/fisco/console/

# 通过sdk.publickey获取控制台公钥信息
$ cat conf/sdk.publickey
ebf5535c92f7116310ed9e0f9fc9bfc66a607415d4fa444d91f528485eff61b15e40a70bc5d73f0441d3959efbc7718c20bd452ac4beed5f6c4feb9fabc1f9f6

开启SDK白名单机制

将某控制台的公钥添加到node0的group.[group_id].ini配置文件白名单列表中:

[sdk_allowlist]
public_key.0=b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36

重新加载SDK白名单列表

$ bash node0/scripts/reload_sdk_allowlist.sh
 [INFO] node0 is trying to reload sdk allowlist. Check log for more information.

# 热加载SDK白名单列表成功后,节点输出了如下日志:
parseSDKAllowList,sdkAllowList=[b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36],enableSDKAllowListControl=true

控制台访问节点

注解

由于SDK白名单是节点级别的访问控制机制,为了展示 node0 对SDK的访问控制功能,控制台仅连接 node0

由于node0没有配置控制台对群组的访问权限,部署合约的返回结果如下:

# 控制台部署HelloWorld合约
[group:1]> deploy HelloWorld
sendRawTransaction The SDK is not allowed to access this group

添加控制台到SDK白名单列表

将控制台配置到node0的白名单列表中:

[sdk_allowlist]
public_key.0=b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36
public_key.1=ebf5535c92f7116310ed9e0f9fc9bfc66a607415d4fa444d91f528485eff61b15e40a70bc5d73f0441d3959efbc7718c20bd452ac4beed5f6c4feb9fabc1f9f6

重新加载SDK白名单列表:

$ bash node0/scripts/reload_sdk_allowlist.sh
 [INFO] node0 is trying to reload sdk allowlist. Check log for more information.

# 热加载SDK白名单列表成功后,节点输出了如下日志:
parseSDKAllowList,sdkAllowList=[b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36,ebf5535c92f7116310ed9e0f9fc9bfc66a607415d4fa444d91f528485eff61b15e40a70bc5d73f0441d3959efbc7718c20bd452ac4beed5f6c4feb9fabc1f9f6],enableSDKAllowListControl=true

控制台访问节点

node0加入控制台到白名单列表后,控制台可正常部署合约,如下:

[group:1]> deploy HelloWorld
contract address: 0xcd4ccd96c86fe8e4f27b056c0fdb7eb4ca201f0f

极端异常下的共识恢复应急方案

重要

  • 使用此方案之前,请确保您对区块链共识、存储、以及区块链无法篡改特性有深刻理解。该方案所涉及的操作都是高危操作,仅用于发生极端异常情况导致区块链网络无法正常运行时的紧急修复。切勿在其它场合使用,使用不当可能导致数据异常甚至系统异常,请明确知悉,谨慎使用。确定使用该恢复方案之前,建议先对已有数据进行备份。
  • 此方案并不会篡改区块链数据,详细请阅读文章 《如何解释“我篡改了区块链”这个问题》

背景:FISCO BCOS 支持的 PBFT 共识算法 需要区块链网络中至少存在 2*f+1 个共识节点正常工作(假定总节点数为 3*f+1 )才能维持网络的良性运转。但是实际生产环境中往往会出现各种特殊情况,如网络脑裂、节点网络中断、节点硬件崩溃,从而导致网络中节点数量少于 2*f+1,这时网络将无法对交易和区块达成共识,网络陷入瘫痪。

目标:处理 FISCO BCOS 区块链网络中由于节点数量不足 2*f+1 而引起的 PBFT 网络共识异常问题。

解决方案:手动关闭正常共识节点,修改正常共识节点数据库 _sys_consensus_ 表中的异常共识节点信息,将异常共识节点的 type 字段值从 sealer 修改为 observer,也就是手动将异常共识节点修改为观察者节点,然后启动正常共识节点。

FISCO BCOS 支持不同的数据库存储模式,不同的数据库模式在操作和存储方面存在差异。接下来分别讲解 Mysql 和 RocksDB 数据库存储模式在面对 PBFT 共识节点不足的情况下可采取的处理方案。

Mysql存储模式

处理步骤:假设网络中存在A、B、C、D四个共识节点,C、D节点由于某些原因发生崩溃,导致网络无法正常共识打包出块。

  1. 关闭节点A和节点B:为了防止操作数据库给正在运行的网络造成影响,建议先关闭节点A和节点B;
  2. 手动修改节点A和节点B的数据库:确保A、B、C、D节点块高一致的前提下修改节点A和节点B数据库中 _sys_consensus_ 表,将节点C和节点D的 type 字段值从 sealer 修改为 observer;
  3. 启动节点A和节点B:重启节点之后,网络中共识节点只有节点A和节点B,满足 PBFT 共识条件,网络可正常共识打包出块;
  4. 手动修改节点C和节点D的数据库:修改节点C和节点D中数据库 _sys_consensus_ 表,将节点C和节点D的 type 字段值从 sealer 修改为 observer;
  5. 启动节点C和节点D:节点C和节点D作为观察者节点,可以顺利同步区块,但无法参与网络共识;
  6. 添加共识节点:通过控制台调用 addSealer 命令,将节点C和节点D转换为共识节点。

注解

该解决方案会引入一个新的问题:当对现有网络进行共识节点扩容时,新加入的共识节点在同步区块的过程中会在某一个区块高度出现同步失败的情况,此时需要手动将新节点数据库 _sys_consensus_ 表中节点C和节点D的 type 字段值从 sealer 修改为 observer,然后重启新加入的共识节点。重启后共识节点将继续同步区块,当同步的区块高度追上网络中最新的区块高度时,新加入的共识节点方可参与网络共识。

关闭节点A和节点B

网络在运行的过程中直接修改数据库表中字段值是非常危险的,所以先关闭节点A和节点B。

# 关闭节点A
bash ./node0/stop.sh

# 关闭节点B
bash ./node1/stop.sh
手动修改节点A和节点B的数据库

在A、B、C、D节点拥有相同的最新区块高度前提下,为了可以让由节点A和节点B组成的网络可以继续共识,需要手动修改节点A和节点B数据库中 _sys_consensus_ 表的 type 字段,将该字段值从 sealer 修改为 observer。

  1. 查看节点C和节点D的 node_id
# 节点C编号
> cat ./node2/conf/node.nodeid
da8688893fcf5b4194e4da1b79e77e81be9cb740fb373e0f59bf3d28a349e7fba9b79ec36e05715fcbe7dbb68cae661d451893128e5ccf81bc1eef7b32163496

# 节点D编号
> cat ./node3/conf/node.nodeid
17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e
  1. 修改节点A和节点B数据库表 _sys_consensus_
# 登录 mysql
> mysql -u username -p password  # 根据实际情况填写 username 和 password

# 选择数据库
> use database-name # 根据实际情况填写 database-name

# 查看 _sys_consensus_ 表信息
> select * from _sys_consensus_;
+--------+-------+----------+------+----------+----------------------------------------------------------------------------------------------------------------------------------+------------+
| _id_   | _num_ | _status_ | name | type     | node_id                                                                                                                          | enable_num |
+--------+-------+----------+------+----------+----------------------------------------------------------------------------------------------------------------------------------+------------+
| 100004 |     0 |        0 | node | sealer   | 060a3dc076c57e42dabb9a54d5d0460dae97f1d7f63a5f4d24f4c126f7ee5d5c2f0ced34f7a661b59fe10962f26dd2f3433f6b127c860eb0a029ad3afeb05e30 | 0          |
| 100005 |     0 |        0 | node | sealer | 17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e | 0          |
| 100006 |     0 |        0 | node | sealer   | 205d9fdeb3e3598bb6e50b4f54dab64841187cb3f8fa7f1eac71dd45fda07b81d3615857891fcd8c7cdcbb2374d8a5df0c2009d80f40e7d83db350532bc2659d | 0          |
| 100007 |     6 |        0 | node | sealer   | da8688893fcf5b4194e4da1b79e77e81be9cb740fb373e0f59bf3d28a349e7fba9b79ec36e05715fcbe7dbb68cae661d451893128e5ccf81bc1eef7b32163496 | 6          |
+--------+-------+----------+------+----------+----------------------------------------------------------------------------------------------------------------------------------+------------+
4 rows in set (0.00 sec)

# 将节点C和节点D的 type 字段修改为 observer
> update _sys_consensus_ set type="observer" where _id_="100005";
> update _sys_consensus_ set type="observer" where _id_="100007";

# 再次查看 _sys_consensus_  表信息
> select * from _sys_consensus_;
+--------+-------+----------+------+----------+----------------------------------------------------------------------------------------------------------------------------------+------------+
| _id_   | _num_ | _status_ | name | type     | node_id                                                                                                                          | enable_num |
+--------+-------+----------+------+----------+----------------------------------------------------------------------------------------------------------------------------------+------------+
| 100004 |     0 |        0 | node | sealer   | 060a3dc076c57e42dabb9a54d5d0460dae97f1d7f63a5f4d24f4c126f7ee5d5c2f0ced34f7a661b59fe10962f26dd2f3433f6b127c860eb0a029ad3afeb05e30 | 0          |
| 100005 |     0 |        0 | node | observer | 17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e | 0          |
| 100006 |     0 |        0 | node | sealer   | 205d9fdeb3e3598bb6e50b4f54dab64841187cb3f8fa7f1eac71dd45fda07b81d3615857891fcd8c7cdcbb2374d8a5df0c2009d80f40e7d83db350532bc2659d | 0          |
| 100007 |     6 |        0 | node | observer   | da8688893fcf5b4194e4da1b79e77e81be9cb740fb373e0f59bf3d28a349e7fba9b79ec36e05715fcbe7dbb68cae661d451893128e5ccf81bc1eef7b32163496 | 6          |
+--------+-------+----------+------+----------+----------------------------------------------------------------------------------------------------------------------------------+------------+
4 rows in set (0.00 sec)

# 继续修改另外一个节点的数据库表
启动节点A和节点B

启动节点A和节点B,从数据库中读取新的节点配置信息。通过查看节点A的日志发现网络共识已恢复正常。

# 启动节点A
bash ./node0/start.sh

# 启动节点B
bash ./node1/start.sh
手动修改节点C和节点D的数据库

如果此时将节点C和节点D启动,因为节点C和节点D数据库表中记录的共识节点包括A、B、C、D,所以节点C和节点D无法同步网络中共识产生的新区块,也无法参与到新区块的共识中。为此,用户需手动修改节点C和节点D数据库中 _sys_consensus_ 表的字段,将节点C和节点D的 type 字段值从 sealer 修改为 observer。

修改步骤同 手动修改节点A和节点B的数据库 小节,不重复占用篇幅。

启动节点C和节点D

再次启动节点C和节点D之后,节点C和节点D作为观察者节点可顺利同步区块,但是无法参与区块共识。

# 启动节点C
bash ./node2/start.sh

# 启动节点D
bash ./node3/start.sh

注解

特殊情况处理:节点C和节点D启动之后,此时网络已经可以正常共识出块,共识节点为A和B。假如节点A开启了 binary_log=true 配置项用于备份数据,某一个时刻节点A的主数据发生异常损坏,节点A需要从备份数据中进行数据恢复,在数据恢复完成之后,节点A需要再次手动修改数据库 _sys_consensus_ 表中节点C和节点D的 type 字段值,从 sealer 修改为 observer,然后重启节点A。同理,如果节点B、C、D遇到这种情况,也需要进行相同的操作。

添加共识节点

现在节点C和节点D是观察者节点,只能同步区块却无法参与区块共识。为此,可通过在控制台执行 addSealer 命令将节点C和节点D转换为共识节点。关于节点角色转换可参考:组员管理

# 转换节点C为共识节点
[group:1]> addSealer da8688893fcf5b4194e4da1b79e77e81be9cb740fb373e0f59bf3d28a349e7fba9b79ec36e05715fcbe7dbb68cae661d451893128e5ccf81bc1eef7b32163496
{
    "code":0,
    "msg":"success"
}

# 转换节点D为共识节点
[group:1]> addSealer 17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e
{
    "code":0,
    "msg":"success"
}

RocksDB存储模式

处理步骤:假设网络中存在A、B、C、D四个共识节点,C、D节点由于某些原因发生崩溃,导致网络无法正常共识打包出块。

  1. 获取rocksdb-storage工具:编译 FISCO BCOS源码 获取 rocksdb-storage 工具,使用该工具查询、修改 rocksdb 数据库中信息;
  2. 关闭节点A和节点B:为了防止操作数据库给正在运行的网络造成影响,建议先关闭节点A和节点B;
  3. 手动修改节点A和节点B的数据库:确保A、B、C、D节点块高一致的前提下修改节点A和节点B数据库中 _sys_consensus_ 表,将节点C和节点D的 type 字段值从 sealer 修改为 observer;
  4. 启动节点A和节点B:启动节点之后,网络中共识节点只有节点A和节点B,满足 PBFT 共识条件,网络可正常共识打包出块;
  5. 手动修改节点C和节点D的数据库:修改节点C和节点D中数据库 _sys_consensus_ 表,将节点C和节点D的 type 字段值从 sealer 修改为 observer;
  6. 启动节点C和节点D:节点C和节点D作为观察者节点,可以顺利同步区块,但无法参与网络共识;
  7. 添加共识节点:通过控制台调用 addSealer 命令,将节点C和节点D转换为共识节点。

注解

该解决方案会引入一个新的问题:当对现有网络进行共识节点扩容时,新加入的共识节点在同步区块的过程中会在某一个区块高度出现同步失败的情况,此时需要手动将新节点数据库 _sys_consensus_ 表中节点C和节点D的 type 字段值从 sealer 修改为 observer,然后重启新加入的共识节点。重启后共识节点将继续同步区块,当同步的区块高度追上网络中最新的区块高度时,新加入的共识节点方可参与网络共识。

获取rocksdb-storage工具

FISCO BCOS 提供查询、修改 rocksdb 数据库信息的 rocksdb-storage 工具。该工具需要手动编译 FISCO BCOS源码 获取,编译时通过 cmake -DTOOL=on .. 打开工具开关,编译成功后 rocksdb-storage 工具位于 FISCO-BCOS/build/bin/。详细编译步骤可参考:编译

#  编译成功后将 rocksdb-storage 工具移动到节点主目录,如 ~/fisco/nodes/127.0.0.1/
cp ./bin/rocksdb-storage ~/fisco/nodes/127.0.0.1/

有关 rocksdb-storage 工具的使用说明可以参考本页第三章:rocksdb-storage 工具

关闭节点A和节点B

网络在运行的过程中直接修改数据库表中字段值是非常危险的,所以先关闭节点A和节点B。

# 关闭节点A
bash ./node0/stop.sh

# 关闭节点B
bash ./node1/stop.sh
手动修改节点A和节点B的数据库

在A、B、C、D节点拥有相同的最新区块高度前提下,为了可以让由节点A和节点B组成的网络可以继续共识,需要使用 rocksdb-storage 工具手动修改节点A和节点B数据库中 _sys_consensus_ 表的 type 字段,将该字段值从 sealer 修改为 observer。

  1. 查看节点C和节点D的 node_id
# 节点C编号
> cat ./node2/conf/node.nodeid
700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7

# 节点D编号
> cat ./node3/conf/node.nodeid
b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa
  1. 修改节点A和节点B数据库表 _sys_consensus_
# 使用 rocksdb-storage 工具查询节点A数据库 _sys_consensus_ 表数据
> ./rocksdb-storage -p ./node0/data/group1/block/RocksDB -s _sys_consensus_ node
DB path : ./node0/data/group1/block/RocksDB
select [_sys_consensus_,node] || params num : 2
================ open Table [_sys_consensus_] success! key node
***0 [ enable_num=0 ][ name=node ][ node_id=700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7 ][ type=sealer ]
***1 [ enable_num=0 ][ name=node ][ node_id=b64c6d23f479e4740c09662bcbf721c265abf34d511644d92344a20bb7c661de33d72cf56d3ba0d3eb4cb553cd4913dc30519351270e02af2c6bb45706ad4896 ][ type=sealer ]
***2 [ enable_num=0 ][ name=node ][ node_id=b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa ][ type=sealer ]
***3 [ enable_num=0 ][ name=node ][ node_id=e794f3666fb0c9d36e8f5e9a99d49bc15304a8332f3945c260bfdafd66dc04b5a6c1397e8900a835cce54b58b94d7993097b1706bc1398d5c3ddf1e002902b03 ][ type=sealer ]

# 修改节点A数据库 _sys_consensus_ 表中节点C的 type 字段值
> ./rocksdb-storage -p ./node0/data/group1/block/RocksDB -u _sys_consensus_ node node_id 700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7 type observer
DB path : ./node0/data/group1/block/RocksDB
update [_sys_consensus_,node,node_id,700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7,type,observer] || params num : 6
open Table [_sys_consensus_] success!
condition is [node_id=700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7]
update [type:observer]

# 修改节点A数据库 _sys_consensus_ 表中节点D的 type 字段值
> ./rocksdb-storage -p ./node0/data/group1/block/RocksDB -u _sys_consensus_ node node_id b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa type observer
DB path : ./node0/data/group1/block/RocksDB
update [_sys_consensus_,node,node_id,b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa,type,observer] || params num : 6
open Table [_sys_consensus_] success!
condition is [node_id=b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa]
update [type:observer]

# 再次查询节点A数据库 _sys_consensus_ 表数据
> ./rocksdb-storage -p ./node0/data/group1/block/RocksDB -s _sys_consensus_ node
DB path : ./node0/data/group1/block/RocksDB
select [_sys_consensus_,node] || params num : 2
================ open Table [_sys_consensus_] success! key node
***0 [ enable_num=0 ][ name=node ][ node_id=700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7 ][ type=observer ]
***1 [ enable_num=0 ][ name=node ][ node_id=b64c6d23f479e4740c09662bcbf721c265abf34d511644d92344a20bb7c661de33d72cf56d3ba0d3eb4cb553cd4913dc30519351270e02af2c6bb45706ad4896 ][ type=sealer ]
***2 [ enable_num=0 ][ name=node ][ node_id=b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa ][ type=observer ]
***3 [ enable_num=0 ][ name=node ][ node_id=e794f3666fb0c9d36e8f5e9a99d49bc15304a8332f3945c260bfdafd66dc04b5a6c1397e8900a835cce54b58b94d7993097b1706bc1398d5c3ddf1e002902b03 ][ type=sealer ]

# 继续修改节点B的数据库表
启动节点A和节点B

启动节点A和节点B,从数据库中读取新的节点配置信息。启动成功后,通过查看节点A的日志发现网络共识已恢复正常。

# 启动节点A
bash ./node0/start.sh

# 启动节点B
bash ./node1/start.sh
手动修改节点C和节点D的数据库

如果此时将节点C和节点D启动,因为节点C和节点D数据库表中记录的共识节点包括A、B、C、D,所以节点C和节点D无法同步网络中共识产生的新区块,也无法参与到新区块的共识中。为此,用户需手动修改节点C和节点D数据库中 _sys_consensus_ 表的字段,将节点C和节点D的 type 字段值从 sealer 修改为 observer。

修改步骤同 手动修改节点A和节点B的数据库 小节,不重复占用篇幅。

启动节点C和节点D

启动节点C和节点D之后,节点C和节点D作为观察者节点可顺利同步区块,但是无法参与区块共识。

# 启动节点C
bash ./node2/start.sh

# 启动节点D
bash ./node3/start.sh

注解

特殊情况处理:节点C和节点D启动之后,此时网络已经可以正常共识出块,共识节点为A和B。假如节点A开启了 binary_log=true 配置项用于备份数据,某一个时刻节点A的主数据发生异常损坏,节点A需要从备份数据中进行数据恢复,在数据恢复完成之后,节点A需要再次手动修改数据库 _sys_consensus_ 表中节点C和节点D的 type 字段值,从 sealer 修改为 observer,然后重启节点A。同理,如果节点B、C、D遇到这种情况,也需要进行相同的操作。

添加共识节点

现在节点C和节点D是观察者节点,只能同步区块却无法参与区块共识。为此,可通过在控制台执行 addSealer 命令将节点C和节点D转换为共识节点。关于节点角色转换可参考:组员管理

# 转换节点C为共识节点
[group:1]> addSealer 700076fb7e70c073dd04858f1030cfe51b482e6ec80e20cdcfa33335091f930b67620dd5b1627607195b1512816bb11cc4bc569d71708a302181a6e645b0a1c7
{
    "code":0,
    "msg":"success"
}

# 转换节点D为共识节点
[group:1]> addSealer b88921fc39d90190fa908b24cbbd8282d823984e24f56a01f622c87c2d0f255e02eec03dbf427e29a56180b8dbc743ed054c43faa36df4ba1e81ae78ed0ad3aa
{
    "code":0,
    "msg":"success"
}

rocksdb-storage 工具

FISCO BCOS 提供 rocksdb-storage 工具帮助用户对 rocksdb 数据库进行常规 CRUD 操作,此外 rocksdb-storage 工具也支持手动创建数据表。

获取途径:手动编译 FISCO BCOS源码。编译时通过 cmake -DTOOL=on .. 打开工具开关,编译成功后 rocksdb-storage 工具位于 FISCO-BCOS/build/bin/。详细编译步骤可参考:编译

帮助
Usage:
      -h [ --help ]              help of rocksdb reader
      -c [ --createTable ] arg   [TableName] [KeyField] [ValueField]
      -p [ --path ] arg (=data/) [RocksDB path]
      -s [ --select ] arg        [TableName] [priKey]
      -u [ --update ] arg        [TableName] [priKey] [keyEQ] [valueEQ] [Key] 
                                 [NewValue]
      -i [ --insert ] arg        [TableName] [priKey] [Key]:[Value],...,[Key]:[Valu
                                 e]
      -r [ --remove ] arg        [TableName] [priKey]
      -e [ --encrypt ] arg       [encryptKey] [SMCrypto]
e.g
	./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ node
选项介绍
-c选项[Optional]

--createTable 选项,用于创建数据库表。参数包括:

  • TableName:表名;
  • KeyField:主键字段名;
  • ValueField:其余字段名。如果有多个字段名则以符号 , 进行分隔。
-p选项

--path 选项,用于指定节点 rocksdb 数据文件所在存储目录。rocksdb 默认文件路径为 ./node*/data/group*/block/RocksDB/

-s选项[Optional]

--select 选项,用于查询数据库表中数据项记录。参数包括:

  • TableName:表名;
  • priKey:主键字段值;

注解

priKey 是 KeyFiled 的具体值。比如 _sys_consensus_ 系统表的主键是 name,主键值为 node。详细请参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/design/storage/storage.html

-u选项[Optional]

--update 选项,用于更新数据库表中数据项记录。参数包括:

  • TableName:表名;
  • priKey:主键字段值;
  • keyEQ:key 值,用于指定非主键字段名;
  • valueEQ:value 值,用于指定非主键字段值,和 keyEQ 组合使用;
  • Key:key 值,待修改的字段名;
  • NewValue:value 值,待修改值。

注解

keyEQ 和 valueEQ 组合在一起的作用可以类比为 mysql 中的 where 条件。

-i选项[Optional]

--insert 选项,用于新增数据库表中数据项记录。参数包括:

  • TableName:表名;
  • priKey:主键字段值,同时也是待插入的数据;
  • Key:Value:以符号 , 连接的多个待插入数据。
-r选项[Optional]

--remove 选项,用于删除数据库表中数据项记录。参数包括:

  • TableName:表名;
  • priKey:主键字段值,此处用于指定待删除的数据项。
-e选项[Optional]

--encrypt 选项,用于标识已开启落盘加密的节点,有关落盘加密可 参考。参数包括:

  • encryptKey:dataKey;
  • SMCrypto:是否开启国密。
使用举例

注解

使用 rocksdb-storage 工具操作网络中开启落盘加密节点的数据库时,需要额外指定 -e 选项,同时建议将 -e 选项放置在命令行最后。

  1. 搭建单群组四节点区块链网络,可参考:安装。搭建完之后需要将所有节点关闭,rocksdb 数据库无法同时被打开两次。

  2. 编译 FISCO BCOS 源码,获取 rocksdb-storage 工具,移动工具至 ~/fisco/ 文件夹目录

  3. 查询数据库表中数据项记录:使用 -s 参数查看 _sys_consensus_ 系统表中的节点数据。关于该系统表的说明可参考:AMDB

    # -p 指定 rocksdb 文件夹,-s 指定 TableName 和 priKey
    # 非国密落盘加密 ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ node -e 123456
    # 国密落盘加密 ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ node -e 123456 SMCrypto
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ node
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    select [_sys_consensus_,node] || params num : 2
    ================ open Table [_sys_consensus_] success! key node
    ***0 [ enable_num=0 ][ name=node ][ node_id=5ea630a8cad6681c9e54a30009caf2afd3fe75a01a46375a5c9ad947d42b3bae8830cd9dbf67ea5d3cccb3eb02cca9a0d91a65e56c97e3005cb85455b52fa89b ][ type=sealer ]
    ***1 [ enable_num=0 ][ name=node ][ node_id=6a6bfde992a8bc7b5fa77a5dfc5512df588b7ea8229ae48925929f77762f40e9bdecef420f5af7b752d9335f74cea270f3dd3a306db647e205dc8255acaefe32 ][ type=sealer ]
    ***2 [ enable_num=0 ][ name=node ][ node_id=6b5612f7206ef9559f1e33efc7f427bc4ab2e3a4674be4c7939f2ea49cb23dab341ab8f6f4b1586b38822e516b6a8391226bea2036df42fe295bf973b2a15ceb ][ type=sealer ]
    ***3 [ enable_num=0 ][ name=node ][ node_id=8e30de9cf192ee644b735d8a4765c7160a3227fa398e040eafa52cd834fd04260b5b960a6dc72c1a3ed8cbb713e29a875bdf8fb0b69f1e46c389068b551e90c7 ][ type=sealer ]
    
  4. 更新数据库表中数据项记录:使用 -u 参数修改 _sys_consensus_ 系统表中的节点数据

    # -p 指定 rocksdb 文件夹,-u 指定 TableName、priKey、keyEQ、valueEQ、Key 和 NewValue
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -u _sys_consensus_ node node_id 5ea630a8cad6681c9e54a30009caf2afd3fe75a01a46375a5c9ad947d42b3bae8830cd9dbf67ea5d3cccb3eb02cca9a0d91a65e56c97e3005cb85455b52fa89b type observer
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    update [_sys_consensus_,node,node_id,5ea630a8cad6681c9e54a30009caf2afd3fe75a01a46375a5c9ad947d42b3bae8830cd9dbf67ea5d3cccb3eb02cca9a0d91a65e56c97e3005cb85455b52fa89b,type,observer] || params num : 6
    open Table [_sys_consensus_] success!
    condition is [node_id=5ea630a8cad6681c9e54a30009caf2afd3fe75a01a46375a5c9ad947d42b3bae8830cd9dbf67ea5d3cccb3eb02cca9a0d91a65e56c97e3005cb85455b52fa89b]
    update [type:observer]
    
    # 查看修改后的 _sys_consensus_ 系统表
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ node
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    select [_sys_consensus_,node] || params num : 2
    ================ open Table [_sys_consensus_] success! key node
    ***0 [ enable_num=0 ][ name=node ][ node_id=5ea630a8cad6681c9e54a30009caf2afd3fe75a01a46375a5c9ad947d42b3bae8830cd9dbf67ea5d3cccb3eb02cca9a0d91a65e56c97e3005cb85455b52fa89b ][ type=observer ]
    ***1 [ enable_num=0 ][ name=node ][ node_id=6a6bfde992a8bc7b5fa77a5dfc5512df588b7ea8229ae48925929f77762f40e9bdecef420f5af7b752d9335f74cea270f3dd3a306db647e205dc8255acaefe32 ][ type=sealer ]
    ***2 [ enable_num=0 ][ name=node ][ node_id=6b5612f7206ef9559f1e33efc7f427bc4ab2e3a4674be4c7939f2ea49cb23dab341ab8f6f4b1586b38822e516b6a8391226bea2036df42fe295bf973b2a15ceb ][ type=sealer ]
    ***3 [ enable_num=0 ][ name=node ][ node_id=8e30de9cf192ee644b735d8a4765c7160a3227fa398e040eafa52cd834fd04260b5b960a6dc72c1a3ed8cbb713e29a875bdf8fb0b69f1e46c389068b551e90c7 ][ type=sealer ]
    
  5. 新增数据库表中数据项记录:使用 -i 参数新增 _sys_consensus_ 系统表中的节点数据

    # -p 指定 rocksdb 文件夹,-i 指定 TableName、priKey 和 [Key]:[Value],...,[Key]:[Value],注意 priKey 为 peer
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -i _sys_consensus_ peer node_id:17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e,type:sealer,enable_num:0
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    insert [_sys_consensus_,peer,node_id:17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e,type:sealer,enable_num:0] || params num : 3
    open Table [_sys_consensus_] success!
    
    # 查看新增后的 _sys_consensus_ 系统表,注意 priKey 为 peer
    >  ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ peer
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    select [_sys_consensus_,peer] || params num : 2
    ================ open Table [_sys_consensus_] success! key peer
    ***0 [ enable_num=0 ][ name=peer ][ node_id=17f4ad3e71418be3e074c897c73fdd07a3a69a20524476dfbd4dc18fc6284714acf58d753bbc651549f7b5e30bfd82b84fdc5ac44e67aab8c1299ef92fd4e30e ][ type=sealer ]
    
  6. 删除数据库表中数据项记录:使用 -r 参数删除 _sys_consensus_ 系统表中的节点数据

    # -p 指定 rocksdb 文件夹,-r 指定 TableName 和 priKey,注意 priKey 为 peer
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -r _sys_consensus_ peer
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    remove [_sys_consensus_,peer] || params num : 2
    open Table [_sys_consensus_] success!
    
    # 查看删除后的 _sys_consensus_ 系统表,注意 priKey 为 peer
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s _sys_consensus_ peer
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    select [_sys_consensus_,peer] || params num : 2
    ================ open Table [_sys_consensus_] success! key peer is empty!
    
  7. 创建数据库表:使用 -c 参数为数据库创建新的数据表 user,包括 username、age 和 sex 三个字段,其中 username 是主键

    # -p 指定 rocksdb 文件夹,-c 指定 TableName、KeyField 和 ValueField
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -c user username age,sex
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    createTable [user,username,age,sex] || params num : 3
    KeyField:[username]
    ValueField:[age,sex]
    createTable [user] success!
    
    # 插入数据
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -i user Bob age:20,sex:male
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    insert [user,Bob,age:20,sex:male] || params num : 3
    open Table [user] success!
    
    # 查看新插入的数据
    > ./rocksdb-storage -p ./nodes/127.0.0.1/node0/data/group1/block/RocksDB -s user Bob
    
    DB path : ./nodes/127.0.0.1/node0/data/group1/block/RocksDB
    select [user,Bob] || params num : 2
    ================ open Table [user] success! key Bob
    ***0 [ age=20 ][ sex=male ][ username=Bob ]
    

区块链部署

外部调用

合约开发

管理与安全

其它

运维部署工具

基本介绍

FISCO BCOS generator为企业用户提供了部署、管理和监控多机构多群组联盟链的便捷工具。

  • 本工具降低了机构间生成与维护区块链的复杂度,提供了多种常用的部署方式。
  • 本工具考虑了机构间节点安全性需求,所有机构间仅需要共享节点的证书,同时对应节点的私钥由各机构自己维护,不需要向机构外节点透露。
  • 本工具考虑了机构间节点的对等性需求,多机构间可以通过交换数字证书对等安全地部署自己的节点。

_images/toolshow.png

设计背景

在联盟链中,多个对等机构是不完全信任的。联盟链的节点之间需要使用数字证书互相进行身份认证。

证书是机构对外身份的凭证,生成证书的过程中需要使用机构本身的公钥和私钥对。私钥即为机构在互联网上的身份信息,是私密的,不可对外告诉其他人的。节点在启动、运行过程中,需要使用私钥对数据包进行签名,从而完成身份认证过程。假设私钥泄露,则任何人都可以伪装成对应的机构,在不经过该机构授权行使该机构的权利。

重要

即在联盟链部署、运行过程中,机构节点的私钥是不应该告诉任何人,应当只能由本机构生成和保管。

在FISCO BCOS的群组初始化过程中,需要多个节点协商生成群组的创世区块。创世区块在同一个群组中是唯一的,其中包含了初始节点身份信息的区块。这些身份信息需要通过交换数字证书的方式来构建。

现有的联盟链运维管理工具在初始化时都没有考虑联盟链间多个企业地位对等安全的诉求。联盟链在初始化时,需要协商创世节点中包含的节点信息。因此谁来生成这些信息就显得十分重要。现有做法为某一机构生成自己的节点信息,启动区块链,再加入其它机构的节点;或是由权威第三方机构直接生成所有机构内的节点信息,并将节点配置文件夹发送给各机构。

另一方面,FISCO BCOS 2.0引入了隐私性和可扩展性更强的多群组架构。在群组架构下,群组间数据、交易相互隔离,每个群组运行独立的共识算法,可满足区块链场景中的隐私保护需求。

在上述模式中,总有一个机构会优先加入到联盟链之中;并且在这种模式中,总有一个机构会获得所有节点的私钥。

如何保证企业间对等、安全、隐私地新建群组。新建群组之后如何保证节点可靠,有效的运行;群组账本的隐私性和安全性,以及企业建立群组、使用群组操作的隐私性都需要一个有效的方式来保证。

设计思路

FISCO BCOS generator从上述背景出发,根据灵活、安全、易用、对等的原则,从不同机构对等部署、新建群组的角度考虑,设计了解决上述问题的解决方案。

灵活:

  • 无需安装即可使用
  • 支持多种部署上报方式
  • 支持多种架构改动

安全:

  • 支持多种架构改动
  • 节点私钥不出内网
  • 机构间只需协商证书

易用:

  • 支持多种组网模式
  • 多种命令满足不同需求
  • 监控审计脚本

对等:

  • 机构地位对等
  • 所有机构共同产生创世区块
  • 机构对等管理所属群组

针对同一根证书的联盟链,本工具可以快速配置链内的多个群组,满足不同企业的不同业务需求。

不同机构间通过协商节点证书、IP、端口号等数据的模式,填写配置项,每个机构都可以在本地生成不含节点私钥的节点配置文件夹,节点的私钥可以不出内网,即使节点配置文件丢失,防止恶意攻击者伪装节点的同时,不会泄露链上任何信息。使用这种方式,在保证节点可用的同时,保护节点的安全性。

用户通过协商生成创世区块,生成节点配置文件夹后,启动节点,节点会根据用户配置信息进行多群组组网。

一键部署

one_click_generator.sh脚本为根据用户填写的节点配置,一键部署联盟链的脚本。脚本会根据用户指定文件夹下配置的node_deployment.ini,在文件夹下生成相应的节点。

本章主要以部署3机构2群组6节点的组网模式,为用户讲解单机构一键部署运维部署工具的使用方法。

本教程适用于单机构搭建所有节点的部署方式,运维部署工具多机构部署教程可以参考使用运维部署工具

重要

一键部署脚本使用时需要确保当前meta文件夹下不含节点证书信息,请清空meta文件夹。

下载安装

下载

cd ~/ && git clone https://github.com/FISCO-BCOS/generator.git

安装

此操作要求用户具有sudo权限。

cd ~/generator && bash ./scripts/install.sh

检查是否安装成功,若成功,输出 usage: generator xxx

./generator -h

获取节点二进制

拉取最新fisco-bcos二进制文件到meta中

./generator --download_fisco ./meta

检查二进制版本

若成功,输出 FISCO-BCOS Version : x.x.x-x

./meta/fisco-bcos -v

PS源码编译节点二进制的用户,只需要用编译出来的二进制替换掉meta文件夹下的二进制即可。

背景介绍

本节以部署6节点3机构2群组的组网模式,演示如何使用运维部署工具一键部署功能,搭建区块链。

节点组网拓扑结构

一个如图所示的6节点3机构2群组的组网模式。机构B和机构C分别位于群组1和群组2中。机构A同属于群组1和群组2中。

_images/one_click_step_3.png

机器环境

每个节点的IP,端口号为如下:

机构 节点 所属群组 P2P地址 RPC Channel监听地址
机构A 节点0 群组1、2 127.0.0.1:30300 127.0.0.1:8545 0.0.0.0:20200
节点1 群组1、2 127.0.0.1:30301 127.0.0.1:8546 0.0.0.0:20201
节点4 群组1、2 127.0.0.1:30304 127.0.0.1:8549 0.0.0.0:20202
机构B 节点2 群组1 127.0.0.1:30302 127.0.0.1:8547 0.0.0.0:20203
节点3 群组1 127.0.0.1:30303 127.0.0.1:8548 0.0.0.0:20204
机构C 节点5 群组1、2 127.0.0.1:30305 127.0.0.1:8550 0.0.0.0:20205

注解

  • 云主机的公网IP均为虚拟IP,若rpc_ip/p2p_ip/channel_ip填写外网IP,会绑定失败,须填写0.0.0.0
  • RPC/P2P/Channel监听端口必须位于1024-65535范围内,且不能与机器上其他应用监听端口冲突
  • 出于安全性和易用性考虑,FISCO BCOS v2.3.0版本最新节点config.ini配置将listen_ip拆分成jsonrpc_listen_ip和channel_listen_ip,但仍保留对listen_ip的解析功能,详细请参考 这里
  • 为便于开发和体验,channel_listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP

部署网络

首先完成如图所示机构A、B搭建群组1的操作:

_images/one_click_step_1.png

使用前用户需准备如图如tmp_one_click的文件夹,在文件夹下分别拥有不同机构的目录,每个机构目录下需要有对应的配置文件node_deployment.ini。使用前需要保证generator的meta文件夹没有进行过任何操作。

查看一键部署模板文件夹:

cd ~/generator
ls ./tmp_one_click
# 参数解释
# 如需多个机构,需要手动创建该文件夹
tmp_one_click # 用户指定进行一键部署操作的文件夹
├── agencyA # 机构A目录,命令执行后会在该目录下生成机构A的节点及相关文件
│   └── node_deployment.ini # 机构A节点配置文件,一键部署命令会根据该文件生成相应节点
└── agencyB # 机构B目录,命令执行后会在该目录下生成机构B的节点及相关文件
    └── node_deployment.ini # 机构B节点配置文件,一键部署命令会根据该文件生成相应节点
机构填写节点信息

教程中将配置文件放置与tmp_one_click文件夹下的agencyA, agencyB下

cat > ./tmp_one_click/agencyA/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; Host IP for the communication among peers.
; Please use your ssh login IP.
p2p_ip=127.0.0.1
; listening IP for the communication between SDK clients.
; This IP is the same as p2p_ip for the physical host.
; But for virtual host e.g., VPS servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30301
channel_listen_port=20201
jsonrpc_listen_port=8546
EOF
cat > ./tmp_one_click/agencyB/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; Host IP for the communication among peers.
; Please use your ssh login IP.
p2p_ip=127.0.0.1
; listening IP for the communication between SDK clients.
; This IP is the same as p2p_ip for the physical host.
; But for virtual host e.g., VPS servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30302
channel_listen_port=20202
jsonrpc_listen_port=8547

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30303
channel_listen_port=20203
jsonrpc_listen_port=8548
EOF
生成节点
bash ./one_click_generator.sh -b ./tmp_one_click

执行完毕后,./tmp_one_click文件夹结构如下:

查看执行后的一键部署模板文件夹:

ls ./tmp_one_click
├── agencyA # A机构文件夹
│   ├── agency_cert # A机构证书及私钥
│   ├── generator-agency # 自动代替A机构进行操作的generator文件夹
│   ├── node # A机构生成的节点,多机部署时推送至对应服务器即可
│   ├── node_deployment.ini # A机构的节点配置信息
│   └── sdk # A机构的sdk或控制台配置文件
├── agencyB
|   ├── agency_cert
|   ├── generator-agency
|   ├── node
|   ├── node_deployment.ini
|   └── sdk
|── ca.crt # 链证书
|── ca.key # 链私钥
|── group.1.genesis # 群组1创世区块
|── peers.txt # 节点的peers.txt信息
启动节点

调用脚本启动节点:

bash ./tmp_one_click/agencyA/node/start_all.sh
bash ./tmp_one_click/agencyB/node/start_all.sh

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyA/node/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyA/node/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15442     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyB/node/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15456     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyB/node/node_127.0.0.1_30303/fisco-bcos -c config.ini
查看节点运行状态

查看节点log:

tail -f ~/generator/tmp_one_click/agency*/node/node*/log/log*  | grep +++
# 命令解释
# +++即为节点正常共识
info|2019-02-25 17:25:56.028692| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=833bd983...
info|2019-02-25 17:25:59.058625| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=343b1141...
info|2019-02-25 17:25:57.038284| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=1,hash=ea85c27b...

新增节点 (扩容新节点)

重要

一键部署脚本使用时需要确保当前meta文件夹下不含节点证书信息,请清空meta文件夹。

接下来,我们为机构A和机构C增加新节点,完成下图所示的组网:

_images/one_click_step_2.png

初始化扩容配置

创建扩容文件夹,示例中tmp_one_click_expand可以为任意名称,请每次扩容使用新的文件夹

mkdir ~/generator/tmp_one_click_expand/

拷贝链证书及私钥至扩容文件夹

cp  ~/generator/tmp_one_click/ca.* ~/generator/tmp_one_click_expand/

拷贝群组1创世区块group.1.genesis至扩容文件夹

cp  ~/generator/tmp_one_click/group.1.genesis ~/generator/tmp_one_click_expand/

拷贝群组1节点P2P连接文件peers.txt至扩容文件夹

cp  ~/generator/tmp_one_click/peers.txt ~/generator/tmp_one_click_expand/
机构A配置节点信息

创建机构A扩容节点所在目录

mkdir ~/generator/tmp_one_click_expand/agencyA

此时机构A已经存在联盟链中,因此需拷贝机构A证书、私钥至对应文件夹

cp -r ~/generator/tmp_one_click/agencyA/agency_cert ~/generator/tmp_one_click_expand/agencyA

机构A填写节点配置信息

cat > ./tmp_one_click_expand/agencyA/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; Host IP for the communication among peers.
; Please use your ssh login IP.
p2p_ip=127.0.0.1
; listening IP for the communication between SDK clients.
; This IP is the same as p2p_ip for the physical host.
; But for virtual host e.g., VPS servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30304
channel_listen_port=20204
jsonrpc_listen_port=8549
EOF
机构C配置节点信息

创建机构C扩容节点所在目录

mkdir ~/generator/tmp_one_click_expand/agencyC

机构C填写节点配置信息

cat > ./tmp_one_click_expand/agencyC/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; Host IP for the communication among peers.
; Please use your ssh login IP.
p2p_ip=127.0.0.1
; listening IP for the communication between SDK clients.
; This IP is the same as p2p_ip for the physical host.
; But for virtual host e.g., VPS servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30305
channel_listen_port=20205
jsonrpc_listen_port=8550
EOF
生成扩容节点
bash ./one_click_generator.sh -e ./tmp_one_click_expand
启动新节点

调用脚本启动节点:

bash ./tmp_one_click_expand/agencyA/node/start_all.sh
bash ./tmp_one_click_expand/agencyC/node/start_all.sh

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyA/node/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyA/node/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15403     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click_expand/agencyA/node/node_127.0.0.1_30304/fisco-bcos -c config.ini
fisco  15442     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyB/node/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15456     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click/agencyB/node/node_127.0.0.1_30303/fisco-bcos -c config.ini
fisco  15466     1  0 17:22 pts/2    00:00:00 ~/generator/tmp_one_click_expand/agencyC/node/node_127.0.0.1_30305/fisco-bcos -c config.ini

重要

为群组1扩容的新节点需要使用sdk或控制台加入到群组中。

使用控制台注册节点

由于控制台体积较大,一键部署中没有直接集成,用户可以使用以下命令获取控制台

注解

企业部署工具会根据generator/meta文件夹下的机构证书及私钥生成sdk相应证书,如需手动生成可以参考操作手册中的generate_sdk_certificate命令

获取控制台,可能需要较长时间,国内用户可以使用--cdn命令:

以机构A使用控制台为例,此步需要切换到机构A对应的generator-agency文件夹

cd ~/generator/tmp_one_click/agencyA/generator-agency
./generator --download_console ./ --cdn
查看机构A节点4

机构A使用控制台加入机构A节点4为共识节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的node.nodeid文件

查看机构A节点nodeid:

cat ~/generator/tmp_one_click_expand/agencyA/node/node_127.0.0.1_30304/conf/node.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
使用控制台注册共识节点

启动控制台:

cd ~/generator/tmp_one_click/agencyA/generator-agency/console && bash ./start.sh 1

使用控制台addSealer命令将节点注册为共识节点,此步需要用到cat命令查看得到机构A节点的node.nodeid

addSealer ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
# 命令解释
# 执行成功会提示success
$ [group:1]> addSealer ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit
查看机构C节点5

机构A使用控制台加入机构C的节点5为观察节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的node.nodeid文件

查看机构C节点nodeid:

cat ~/generator/tmp_one_click_expand/agencyC/node/node_127.0.0.1_30305/conf/node.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
使用控制台注册观察节点

启动控制台:

cd ~/generator/tmp_one_click/agencyA/generator-agency/console && bash ./start.sh 1

使用控制台addObserver命令将节点注册为观察节点,此步需要用到cat命令查看得到机构C节点的node.nodeid

addObserver 5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
# 命令解释
# 执行成功会提示success
$ [group:1]> addObserver 5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit

至此,我们完成了新增节点至现有群组的操作。

新增群组 (扩容新群组)

新建群组的操作用户可以在执行one_click_generator.sh脚本的目录下,通过修改./conf/group_genesis.ini文件,并执行--create_group_genesis命令。

为如图4个节点生成群组2

_images/one_click_step_3.png

配置群组2创世区块

重要

此操作需要在和上述操作generator下执行。

cd ~/generator

配置群组创世区块文件,指定group_id为2。并在[node]下指定新群组中各个节点的IP和P2P端口,分别为机构A-节点0,机构A-节点1,机构A-节点4和机构C-节点5。

cat > ./conf/group_genesis.ini << EOF
[group]
group_id=2

[nodes]
node0=127.0.0.1:30300
node1=127.0.0.1:30301
node2=127.0.0.1:30304
node3=127.0.0.1:30305
EOF
获取对应节点证书

机构A-节点0(node0=127.0.0.1:30300

cp ~/generator/tmp_one_click/agencyA/generator-agency/meta/cert_127.0.0.1_30300.crt ~/generator/meta

机构A-节点1(node1=127.0.0.1:30301

cp ~/generator/tmp_one_click/agencyA/generator-agency/meta/cert_127.0.0.1_30301.crt ~/generator/meta

机构A-节点4(node2=127.0.0.1:30304

cp ~/generator/tmp_one_click_expand/agencyA/generator-agency/meta/cert_127.0.0.1_30304.crt ~/generator/meta

机构C-节点5(node3=127.0.0.1:30305

cp ~/generator/tmp_one_click_expand/agencyC/generator-agency/meta/cert_127.0.0.1_30305.crt ~/generator/meta
生成群组创世区块
./generator --create_group_genesis ./group2

将群组创世区块加入现有节点:

机构A-节点0(node0=127.0.0.1:30300

./generator --add_group ./group2/group.2.genesis ./tmp_one_click/agencyA/node/node_127.0.0.1_30300

机构A-节点1(node1=127.0.0.1:30301

./generator --add_group ./group2/group.2.genesis ./tmp_one_click/agencyA/node/node_127.0.0.1_30301

机构A-节点4(node2=127.0.0.1:30304

 ./generator --add_group ./group2/group.2.genesis ./tmp_one_click_expand/agencyA/node/node_127.0.0.1_30304

机构C-节点5(node3=127.0.0.1:30305

./generator --add_group ./group2/group.2.genesis ./tmp_one_click_expand/agencyC/node/node_127.0.0.1_30305
加载、启动新群组

节点在运行时,可直接用脚本load_new_groups.sh加载新群组配置,并调用startGroupRPC接口启动新群组:

机构A-节点0(node0=127.0.0.1:30300

bash ./tmp_one_click/agencyA/node/node_127.0.0.1_30300/scripts/load_new_groups.sh
curl -X POST --data '{"jsonrpc":"2.0","method":"startGroup","params":[2],"id":1}' http://127.0.0.1:8545

机构A-节点1(node1=127.0.0.1:30301

bash ./tmp_one_click/agencyA/node/node_127.0.0.1_30301/scripts/load_new_groups.sh
curl -X POST --data '{"jsonrpc":"2.0","method":"startGroup","params":[2],"id":1}' http://127.0.0.1:8546

机构A-节点4(node2=127.0.0.1:30304

bash ./tmp_one_click_expand/agencyA/node/node_127.0.0.1_30304/scripts/load_new_groups.sh
curl -X POST --data '{"jsonrpc":"2.0","method":"startGroup","params":[2],"id":1}' http://127.0.0.1:8549

机构C-节点5(node3=127.0.0.1:30305

bash ./tmp_one_click_expand/agencyC/node/node_127.0.0.1_30305/scripts/load_new_groups.sh
curl -X POST --data '{"jsonrpc":"2.0","method":"startGroup","params":[2],"id":1}' http://127.0.0.1:8550
查看节点

查看节点log内group1信息:

tail -f ~/generator/tmp_one_click/agency*/node/node*/log/log* | grep g:2 | grep +++
# 命令解释
# +++即为节点正常共识
info|2019-02-25 17:25:56.028692| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=833bd983...
info|2019-02-25 17:25:59.058625| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=343b1141...
info|2019-02-25 17:25:57.038284| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=1,hash=ea85c27b...

至此 我们完成了所示构建教程中的所有操作。

注解

使用完成后建议用以下命令对meta文件夹进行清理:

  • rm ./meta/cert_*
  • rm ./meta/group*

更多操作

更多操作,可以参考操作手册,或企业工具对等部署教程

如果使用该教程遇到问题,请查看FAQ

使用运维部署工具

FISCO BCOS运维部署工具面向于真实的多机构生产环境。为了保证机构的密钥安全,运维部署工具提供了一种机构间相互合作部署联盟链方式。

本章以部署6节点3机构2群组的组网模式,演示运维部署工具的使用方法。更多参数选项说明请参考这里

国密部署教程说明请参考使用运维部署工具部署国密区块链

本章节为多机构对等部署的过程,适用于多机构部署,机构私钥不出内网的情况,由单机构一键生成所有机构节点配置文件的教程可以参考FISCO BCOS运维部署工具一键部署

下载安装

下载

cd ~/ && git clone https://github.com/FISCO-BCOS/generator.git

安装

此操作要求用户具有sudo权限。

cd ~/generator && bash ./scripts/install.sh

检查是否安装成功,若成功,输出 usage: generator xxx

./generator -h

获取节点二进制

拉取最新fisco-bcos二进制文件到meta中

./generator --download_fisco ./meta

检查二进制版本

若成功,输出 FISCO-BCOS Version : x.x.x-x

./meta/fisco-bcos -v

PS源码编译节点二进制的用户,只需要用编译出来的二进制替换掉meta文件夹下的二进制即可。

典型示例

为了保证机构的密钥安全,运维部署工具提供了一种机构间相互合作的的搭链方式。本节以部署6节点3机构2群组的组网模式,演示企业间如何相互配合,搭建区块链。

节点组网拓扑结构

一个如图所示的6节点3机构2群组的组网模式。机构B和机构C分别位于群组1和群组2中。机构A同属于群组1和群组2中。

_images/tutorial_step_2.png

机器环境

每个节点的IP,端口号为如下:

机构 节点 所属群组 P2P地址 RPC监听地址 Channel监听地址
机构A 节点0 群组1、2 127.0.0.1:30300 127.0.0.1:8545 0.0.0.0:20200
节点1 群组1、2 127.0.0.1:30301 127.0.0.1:8546 0.0.0.0:20201
机构B 节点2 群组1 127.0.0.1:30302 127.0.0.1:8547 0.0.0.0:20202
节点3 群组1 127.0.0.1:30303 127.0.0.1:8548 0.0.0.0:20203
机构C 节点4 群组2 127.0.0.1:30304 127.0.0.1:8549 0.0.0.0:20204
节点5 群组2 127.0.0.1:30305 127.0.0.1:8550 0.0.0.0:20205

注解

  • 云主机的公网IP均为虚拟IP,若rpc_ip/p2p_ip/channel_ip填写外网IP,会绑定失败,须填写0.0.0.0
  • RPC/P2P/Channel监听端口必须位于1024-65535范围内,且不能与机器上其他应用监听端口冲突
  • 出于安全性和易用性考虑,FISCO BCOS v2.3.0版本最新节点config.ini配置将listen_ip拆分成jsonrpc_listen_ip和channel_listen_ip,但仍保留对listen_ip的解析功能,详细请参考 这里
  • 为便于开发和体验,channel_listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP
涉及机构

搭链操作涉及多个机构的合作,包括:

  • 证书颁发机构
  • 搭建节点的机构(简称“机构”)
关键流程

本流程简要的给出证书颁发机构节点机构间如何相互配合搭建区块链。

一、初始化链证书
  1. 证书颁发机构操作:
    • 生成链证书
二、生成群组1
  1. 证书颁发机构操作:颁发机构证书
    • 生成机构证书
    • 发送证书
  2. 机构间独立操作
    • 修改配置文件node_deployment.ini
    • 生成节点证书及节点P2P端口地址文件
  3. 选取其中一个机构为群组生成创世块
    • 收集群组内所有节点证书
    • 修改配置文件group_genesis.ini
    • 为群组生成创世块文件
    • 分发创世块文件
  4. 机构间独立操作:生成节点
    • 收集群组其他节点的P2P端口地址文件
    • 生成节点
    • 启动节点
三、初始化新机构
  1. 证书颁发机构操作:颁发新机构证书
    • 生成机构证书
    • 发送证书
四、生成群组2
  1. 新机构独立操作
    • 修改配置文件node_deployment.ini
    • 生成节点证书及节点P2P端口地址文件
  2. 选取其中一个机构为群组生成创世块
    • 收集群组内所有节点证书
    • 修改配置文件group_genesis.ini
    • 为群组生成创世块文件
    • 分发创世块文件
  3. 新机构独立操作:生成节点
    • 收集群组其他节点的P2P端口地址文件
    • 生成节点
    • 启动节点
  4. 已有机构操作:配置新群组
    • 收集群组其他节点的P2P端口地址文件
    • 配置新群组与新增节点的P2P端口地址
    • 重启节点
五、现有节点加入群组1
  1. 群组1原有机构操作:
    • 发送群组1创世区块至现有节点
    • 配置控制台
    • 获取加入节点nodeid
    • 使用控制台将节点加入群组1

联盟链初始化

为了操作简洁,本示例所有操作在同一台机器上进行,用不同的目录模拟不同的机构环境。用文件复制操作来模拟网络的发送。进行了教程中的下载安装后,请将generator复制到对应机构的generator目录中。

机构初始化

我们以教程中下载的generator作为证书颁发机构

初始化机构A

cp -r ~/generator ~/generator-A

初始化机构B

cp -r ~/generator ~/generator-B
初始化链证书

在证书颁发机构上进行操作,一条联盟链拥有唯一的链证书ca.crt

--generate_chain_certificate 命令生成链证书

在证书生成机构目录下操作:

cd ~/generator
./generator --generate_chain_certificate ./dir_chain_ca

查看链证书及私钥:

ls ./dir_chain_ca
# 上述命令解释
# 从左至右分别为链证书、链私钥
ca.crt  ca.key

机构A、B构建群组1

初始化机构A

教程中为了简化操作直接生成了机构证书和私钥,实际应用时应该由机构本地生成私钥agency.key,再生成证书请求文件,向证书签发机构获取机构证书agency.crt

在证书生成机构目录下操作:

cd ~/generator

生成机构A证书:

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyA

查看机构证书及私钥:

ls dir_agency_ca/agencyA/
# 上述命令解释
# 从左至右分别为机构证书、机构私钥、链证书
agency.crt  agency.key  ca.crt

发送链证书、机构证书、机构私钥至机构A,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下

cp ./dir_agency_ca/agencyA/* ~/generator-A/meta/
初始化机构B

在证书生成机构目录下操作:

cd ~/generator

生成机构B证书:

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyB

发送链证书、机构证书、机构私钥至机构B,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下

cp ./dir_agency_ca/agencyB/* ~/generator-B/meta/

重要

一条联盟链中只能用到一个根证书ca.crt,多服务器部署时不要生成多个根证书和私钥。一个群组只能有一个群组创世区块group.x.genesis

机构A修改配置文件

node_deployment.ini为节点配置文件,运维部署工具会根据node_deployment.ini下的配置生成相关节点证书,及生成节点配置文件夹等。

机构A修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-A目录下执行下述命令

cd ~/generator-A
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30301
channel_listen_port=20201
jsonrpc_listen_port=8546
EOF
机构B修改配置文件

机构B修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-B目录下执行下述命令

cd ~/generator-B
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30302
channel_listen_port=20202
jsonrpc_listen_port=8547

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30303
channel_listen_port=20203
jsonrpc_listen_port=8548
EOF
机构A生成并发送节点信息

在~/generator-A目录下执行下述命令

cd ~/generator-A

机构A生成节点证书及P2P连接信息文件,此步需要用到上述配置的node_deployment.ini,及机构meta文件夹下的机构证书与私钥,机构A生成节点证书及P2P连接信息文件

./generator --generate_all_certificates ./agencyA_node_info

查看生成文件:

ls ./agencyA_node_info
# 上述命令解释
# 从左至右分别为需要交互给机构A的节点证书,节点P2P连接地址文件(根据node_deployment.ini生成的本机构节点信息)
cert_127.0.0.1_30300.crt cert_127.0.0.1_30301.crt peers.txt

机构生成节点时需要指定其他节点的节点P2P连接地址,因此,A机构需将节点P2P连接地址文件发送至机构B

cp ./agencyA_node_info/peers.txt ~/generator-B/meta/peersA.txt
机构B生成并发送节点信息

在~/generator-B目录下执行下述命令

cd ~/generator-B

机构B生成节点证书及P2P连接信息文件:

./generator --generate_all_certificates ./agencyB_node_info

生成创世区块的机构需要节点证书,示例中由A机构生成创世区块,因此B机构除了发送节点P2P连接地址文件外,还需发送节点证书至机构A

发送证书

cp ./agencyB_node_info/cert*.crt ~/generator-A/meta/

发送节点P2P连接地址文件

cp ./agencyB_node_info/peers.txt ~/generator-A/meta/peersB.txt
机构A生成群组1创世区块

在~/generator-A目录下执行下述命令

cd ~/generator-A

机构A修改conf文件夹下的group_genesis.ini,配置项可参考手册。:

cat > ./conf/group_genesis.ini << EOF
[group]
group_id=1

[nodes]
node0=127.0.0.1:30300
node1=127.0.0.1:30301
node2=127.0.0.1:30302
node3=127.0.0.1:30303
EOF

命令执行之后会修改./conf/group_genesis.ini文件:

;命令解释
[group]
;群组id
group_id=1

[nodes]
;机构A节点p2p地址
node0=127.0.0.1:30300
;机构A节点p2p地址
node1=127.0.0.1:30301
;机构B节点p2p地址
node2=127.0.0.1:30302
;机构B节点p2p地址
node3=127.0.0.1:30303

教程中选择机构A生成群组创世区块,实际生产中可以通过联盟链委员会协商选择。

此步会根据机构A的meta文件夹下配置的节点证书,生成group_genesis.ini配置的群组创世区块,教程中需要机构A的meta下有名为cert_127.0.0.1_30300.crtcert_127.0.0.1_30301.crtcert_127.0.0.1_30302.crtcert_127.0.0.1_30303.crt的节点证书,此步需要用到机构B的节点证书。

./generator --create_group_genesis ./group

分发群组1创世区块至机构B:

cp ./group/group.1.genesis ~/generator-B/meta
机构A生成所属节点

在~/generator-A目录下执行下述命令

cd ~/generator-A

生成机构A所属节点,此命令会根据用户配置的node_deployment.ini文件生成相应的节点配置文件夹:

注意,此步指定的节点P2P连接信息peers.txt为群组内其他节点的链接信息,多个机构组网的情况下需要将其合并。

./generator --build_install_package ./meta/peersB.txt ./nodeA

查看生成节点配置文件夹:

ls ./nodeA
# 命令解释 此处采用tree风格显示
# 生成的文件夹nodeA信息如下所示,
├── monitor # monitor脚本
├── node_127.0.0.1_30300 # 127.0.0.1服务器 端口号30300的节点配置文件夹
├── node_127.0.0.1_30301
├── scripts # 节点的相关工具脚本
├── start_all.sh # 节点批量启动脚本
└── stop_all.sh # 节点批量停止脚本

机构A启动节点:

bash ./nodeA/start_all.sh

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
机构B生成所属节点

在~/generator-B目录下执行下述命令

cd ~/generator-B

生成机构B所属节点,此命令会根据用户配置的node_deployment.ini文件生成相应的节点配置文件夹:

./generator --build_install_package ./meta/peersA.txt ./nodeB

机构B启动节点:

bash ./nodeB/start_all.sh

注解

节点启动只需要推送对应ip的node文件夹即可,如127.0.0.1的服务器,只需node_127.0.0.1_port对应的节点配置文件夹。多机部署时,只需要将生成的节点文件夹推送至对应服务器即可。

查看群组1节点运行状态

查看进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下所示的进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini

查看节点log:

tail -f ./node*/node*/log/log*  | grep +++
# 命令解释
# log中打印的+++即为节点正常共识
info|2019-02-25 17:25:56.028692| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=833bd983...
info|2019-02-25 17:25:59.058625| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=343b1141...
info|2019-02-25 17:25:57.038284| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=1,hash=ea85c27b...

至此,我们完成了如图所示机构A、B搭建群组1的操作:

_images/tutorial_step_1.png

证书授权机构初始化机构C

在证书生成机构目录下操作:

cd ~/generator

初始化机构C,请注意,此时generator目录下有链证书及私钥,实际环境中机构C无法获取链证书及私钥。

cp -r ~/generator ~/generator-C

生成机构C证书:

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyC

查看机构证书及私钥:

ls dir_agency_ca/agencyC/
# 上述命令解释
# 从左至右分别为机构证书、机构私钥、链证书
agency.crt  agency.key  ca.crt

发送链证书、机构证书、机构私钥至机构C,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下

cp ./dir_agency_ca/agencyC/* ~/generator-C/meta/

机构A、C构建群组2

接下来,机构C需要与A进行新群组建立操作,示例中以C生成创世区块为例。

机构A发送节点信息

由于机构A已经生成过节点证书及peers文件,只需将之前生成的节点P2P连接信息以及节点证书发送至机构C,操作如下:

在~/generator-A目录下执行下述命令

cd ~/generator-A

示例中由机构C生成群组创世区块,因此需要机构A的节点证书和节点P2P连接地址文件,将上述文件发送至机构C

发送证书

cp ./agencyA_node_info/cert*.crt ~/generator-C/meta/

发送节点P2P连接地址文件

cp ./agencyA_node_info/peers.txt ~/generator-C/meta/peersA.txt
机构C修改配置文件

机构C修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-C目录下执行下述命令

cd ~/generator-C
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=2

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30304
channel_listen_port=20204
jsonrpc_listen_port=8549

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30305
channel_listen_port=20205
jsonrpc_listen_port=8550
EOF
机构C生成并发送节点信息

在~/generator-C目录下执行下述命令

cd ~/generator-C

机构C生成节点证书及P2P连接信息文件:

./generator --generate_all_certificates ./agencyC_node_info

查看生成文件:

ls ./agencyC_node_info
# 上述命令解释
# 从左至右分别为需要交互给机构A的节点证书,节点P2P连接地址文件(根据node_deployment.ini生成的本机构节点信息)
cert_127.0.0.1_30304.crt cert_127.0.0.1_30305.crt peers.txt

机构生成节点时需要指定其他节点的节点P2P连接地址,因此,C机构需将节点P2P连接地址文件发送至机构A

cp ./agencyC_node_info/peers.txt ~/generator-A/meta/peersC.txt
机构C生成群组2创世区块

在~/generator-C目录下执行下述命令

cd ~/generator-C

机构C修改conf文件夹下的group_genesis.ini如下图所示:

cat > ./conf/group_genesis.ini << EOF
[group]
group_id=2

[nodes]
node0=127.0.0.1:30300
node1=127.0.0.1:30301
node2=127.0.0.1:30304
node3=127.0.0.1:30305
EOF

命令执行之后会修改./conf/group_genesis.ini文件:

;命令解释
[group]
group_id=2

[nodes]
node0=127.0.0.1:30300
;机构A节点p2p地址
node1=127.0.0.1:30301
;机构A节点p2p地址
node2=127.0.0.1:30304
;机构C节点p2p地址
node3=127.0.0.1:30305
;机构C节点p2p地址

教程中选择机构C生成群组创世区块,实际生产中可以通过联盟链委员会协商选择。

此步会根据机构C的meta文件夹下配置的节点证书,生成group_genesis.ini配置的群组创世区块。

./generator --create_group_genesis ./group

分发群组2创世区块至机构A:

cp ./group/group.2.genesis ~/generator-A/meta/
机构C生成所属节点

在~/generator-C目录下执行下述命令

cd ~/generator-C
./generator --build_install_package ./meta/peersA.txt ./nodeC

机构C启动节点:

bash ./nodeC/start_all.sh
ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini
fisco  15550     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30304/fisco-bcos -c config.ini
fisco  15589     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30305/fisco-bcos -c config.ini
机构A为现有节点初始化群组2

在~/generator-A目录下执行下述命令

cd ~/generator-A

添加群组2配置文件至已有节点,此步将群组2创世区块group.2.genesis添加至./nodeA下的所有节点内:

./generator --add_group ./meta/group.2.genesis ./nodeA

添加机构C节点连接文件peers至已有节点,此步将peersC.txt的节点P2P连接地址添加至./nodeA下的所有节点内:

./generator --add_peers ./meta/peersC.txt ./nodeA

重启机构A节点:

bash ./nodeA/stop_all.sh
bash ./nodeA/start_all.sh
查看群组2节点运行状态

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini
fisco  15550     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30304/fisco-bcos -c config.ini
fisco  15589     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30305/fisco-bcos -c config.ini

查看节点log:

在~/generator-C目录下执行下述命令

cd ~/generator-C
tail -f ./node*/node*/log/log*  | grep +++
# 命令解释
# log中打印的+++即为节点正常共识
info|2019-02-25 17:25:56.028692| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=833bd983...
info|2019-02-25 17:25:59.058625| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=343b1141...
info|2019-02-25 17:25:57.038284| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=1,hash=ea85c27b...

至此,我们完成了如图所示的机构A、C搭建群组2构建:

_images/tutorial_step_2.png

扩展教程–机构C节点加入群组1

将节点加入已有群组需要用户使用控制台发送指令,将节点加入群组,示例如下:

注解

企业部署工具会根据generator/meta文件夹下的机构证书及私钥生成sdk相应证书,如需手动生成可以参考操作手册中的generate_sdk_certificate命令

此时群组1内有机构A、B的节点,机构C节点加入群组1需要经过群组内节点的准入,示例以机构A节点为例:

在~/generator-A目录下执行下述命令

cd ~/generator-A
发送群组1创世区块至机构C

发送群组1配置文件至机构C节点:

./generator --add_group ./group/group.1.genesis  ~/generator-C/nodeC

当前FISCO BCOS暂不支持文件热更新,为机构C节点添加群组1创世区块后需重启节点。

重启机构C节点:

bash ~/generator-C/nodeC/stop_all.sh
bash ~/generator-C/nodeC/start_all.sh
配置控制台

机构A配置控制台或sdk,教程中以控制台为例:

注意:此命令会根据用户配置的node_deployment.ini中节点及群组完成了控制台的配置,用户可以直接启动控制台,启动前请确保已经安装java

国内用户推荐使用cdn下载,如果访问github较快,可以去掉--cdn选项:

./generator --download_console ./ --cdn
查看机构C节点4信息

机构A使用控制台加入机构C节点4为观察节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的node.nodeid文件

查看机构C节点nodeid:

cat ~/generator-C/nodeC/node_127.0.0.1_30304/conf/node.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
使用控制台注册观察节点

启动控制台:

cd ~/generator-A/console && bash ./start.sh 1

使用控制台addObserver命令将节点注册为观察节点,此步需要用到cat命令查看得到机构C节点的node.nodeid

addObserver ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
# 命令解释
# 执行成功会提示success
$ [group:1]> addObserver ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit
查看机构C节点5信息

机构A使用控制台加入机构C的节点5为共识节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的node.nodeid文件

查看机构C节点nodeid:

cat ~/generator-C/nodeC/node_127.0.0.1_30305/conf/node.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
使用控制台注册共识节点

启动控制台:

cd ~/generator-A/console && bash ./start.sh 1

使用控制台addSealer命令将节点注册为共识节点,此步需要用到cat命令查看得到机构C节点的node.nodeid

addSealer 5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
# 命令解释
# 执行成功会提示success
$ [group:1]> addSealer 5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit
查看节点

在~/generator-C目录下执行下述命令

cd ~/generator-C

查看节点log内group1信息:

cat node*/node_127.0.0.1_3030*/log/log* | grep g:1  | grep Report
# 命令解释
# 观察节点只会同步交易数据,不会同步非交易状态的共识信息
# log中的^^^即为节点的交易信息,g:1为群组1打印的信息
info|2019-02-26 16:01:39.914367| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=9b76de5d...,next=1,tx=0,nodeIdx=65535
info|2019-02-26 16:01:40.121075| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=3,hash=46b7f17c...,next=2,tx=1,nodeIdx=65535
info|2019-02-26 16:03:44.282927| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=2,sealerIdx=2,hash=fb982013...,next=3,tx=1,nodeIdx=65535
info|2019-02-26 16:01:39.914367| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=9b76de5d...,next=1,tx=0,nodeIdx=4
info|2019-02-26 16:01:40.121075| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=3,hash=46b7f17c...,next=2,tx=1,nodeIdx=4
info|2019-02-26 16:03:44.282927| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=2,sealerIdx=2,hash=fb982013...,next=3,tx=1,nodeIdx=4

_images/tutorial_step_3.png

通过本节教程,我们在本机生成一个网络拓扑结构为3机构2群组6节点的多群组架构联盟链。

如果使用该教程遇到问题,请查看FAQ

扩展教程2–机构A扩容节点加入群组1

本节中,我们将会为机构A新增节点6,IP、端口号如下:

机构 节点 所属群组 P2P地址 RPC监听地址 Channel监听地址
机构A 节点6 群组1 127.0.0.1:30306 127.0.0.1:8551 0.0.0.0:20206

在上述过程中,机构A已经生成了自己所属的机构证书及私钥,并且拥有了群组1的创世区块,扩容节点需要进行的操作如下。

将节点加入已有群组需要用户使用控制台发送指令,将节点加入群组,示例如下:

机构A修改配置文件

node_deployment.ini为节点配置文件,运维部署工具会根据node_deployment.ini下的配置生成相关节点证书,及生成节点配置文件夹等。

机构A修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-A目录下执行下述命令

cd ~/generator-A
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30306
channel_listen_port=20206
jsonrpc_listen_port=8551
EOF
机构A生成节点证书

在~/generator-A目录下执行下述命令

cd ~/generator-A

机构A生成节点证书及P2P连接信息文件,此步需要用到上述配置的node_deployment.ini,及机构meta文件夹下的机构证书与私钥,机构A生成节点证书及P2P连接信息文件

./generator --generate_all_certificates ./agencyA_node_info_new

查看生成文件:

ls ./agencyA_node_info_new
# 上述命令解释
# 从左至右分别为需要交互给机构A的节点证书,节点P2P连接地址文件(根据node_deployment.ini生成的本机构节点信息)
cert_127.0.0.1_30306.crt peers.txt
机构A生成新增节点

在~/generator-A目录下执行下述命令

cd ~/generator-A

生成机构A所属节点,此命令会根据用户配置的node_deployment.ini文件生成相应的节点配置文件夹:

注意,此步指定的节点P2P连接信息peers.txt为群组内其他节点的链接信息,多个机构组网的情况下需要将其合并。

合并当前节点的peers.txt

cat ./agencyA_node_info/peers.txt >> ./meta/peersB.txt

生成新增节点:

./generator --build_install_package ./meta/peersB.txt ./nodeA_new

查看生成节点配置文件夹:

ls ./nodeA_new
# 命令解释 此处采用tree风格显示
# 生成的文件夹nodeA信息如下所示,
├── monitor # monitor脚本
├── node_127.0.0.1_30306 # 127.0.0.1服务器 端口号30306的节点配置文件夹
├── scripts # 节点的相关工具脚本
├── start_all.sh # 节点批量启动脚本
└── stop_all.sh # 节点批量停止脚本

机构A启动节点:

bash ./nodeA_new/start_all.sh

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini
fisco  15550     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30304/fisco-bcos -c config.ini
fisco  15589     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30305/fisco-bcos -c config.ini
fisco  15673     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA_new/node_127.0.0.1_30306/fisco-bcos -c config.ini

注解

企业部署工具会根据generator/meta文件夹下的机构证书及私钥生成sdk相应证书,如需手动生成可以参考操作手册中的generate_sdk_certificate命令

此时群组1内有机构A、B的节点,机构C节点加入群组1需要经过群组内节点的准入,示例以机构A节点为例:

在~/generator-A目录下执行下述命令

cd ~/generator-A
配置控制台

机构A配置控制台或sdk,教程中以控制台为例:

注意:此命令会根据用户配置的node_deployment.ini中节点及群组完成了控制台的配置,用户可以直接启动控制台,启动前请确保已经安装java

国内用户推荐使用cdn下载,如果访问github较快,可以去掉--cdn选项:

如果已经配置过控制台,请忽略此步

./generator --download_console ./ --cdn
查看机构A节点6信息

机构A使用控制台加入节点6为共识节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的node.nodeid文件

查看机构A节点nodeid:

cat ~/generator-A/nodeA_new/node_127.0.0.1_30306/conf/node.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
degca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
使用控制台注册观察节点

启动控制台:

cd ~/generator-A/console && bash ./start.sh 1

使用控制台addSealer命令将节点注册为共识节点,此步需要用到cat命令查看得到机构A节点的node.nodeid

添加共识节点的过程类似,参考addObserver命令

addSealer degca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
# 命令解释
# 执行成功会提示success
$ [group:1]> addSealer degca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit
查看机构A新增节点

在~/generator-A目录下执行下述命令

cd ~/generator-A

查看节点log内group1信息:

cat nodeA_new/node_127.0.0.1_30306/log/log* | grep g:1  | grep Report
# 命令解释
# 观察节点只会同步交易数据,不会同步非交易状态的共识信息
# log中的^^^即为节点的交易信息,g:1为群组1打印的信息
info|2019-02-26 16:01:39.914367| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=9b76de5d...,next=1,tx=0,nodeIdx=65535
info|2019-02-26 16:01:40.121075| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=3,hash=46b7f17c...,next=2,tx=1,nodeIdx=65535
info|2019-02-26 16:03:44.282927| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=2,sealerIdx=2,hash=fb982013...,next=3,tx=1,nodeIdx=65535
info|2019-02-26 16:01:39.914367| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=9b76de5d...,next=1,tx=0,nodeIdx=4
info|2019-02-26 16:01:40.121075| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=3,hash=46b7f17c...,next=2,tx=1,nodeIdx=4
info|2019-02-26 16:03:44.282927| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=2,sealerIdx=2,hash=fb982013...,next=3,tx=1,nodeIdx=4

至此 我们完成了所示构建教程中的所有操作。

如果使用该教程遇到问题,请查看FAQ

使用运维部署工具部署国密区块链

此文档为使用运维部署工具部署的国密部署版本

本章以部署6节点3机构2群组的组网模式,演示运维部署工具的使用方法。更多参数选项说明请参考这里

本章节为多机构对等部署的过程,适用于多机构部署,机构私钥不出内网的情况,由单机构一键生成所有机构节点配置文件的教程可以参考FISCO BCOS运维部署工具一键部署

下载安装

下载

cd ~/ && git clone https://github.com/FISCO-BCOS/generator.git

安装

此操作要求用户具有sudo权限。

cd ~/generator && bash ./scripts/install.sh

检查是否安装成功,若成功,输出 usage: generator xxx

./generator -h

获取节点二进制

拉取最新fisco-bcos二进制文件到meta中

./generator --download_fisco ./meta

检查二进制版本

若成功,输出 FISCO-BCOS gm Version : x.x.x-x

./meta/fisco-bcos -v

PS源码编译节点二进制的用户,只需要用编译出来的二进制替换掉meta文件夹下的二进制即可。

典型示例

为了保证机构的密钥安全,运维部署工具提供了一种机构间相互合作的的搭链方式。本节以部署6节点3机构2群组的组网模式,演示企业间如何相互配合,搭建区块链。

节点组网拓扑结构

一个如图所示的6节点3机构2群组的组网模式。机构B和机构C分别位于群组1和群组2中。机构A同属于群组1和群组2中。

_images/tutorial_step_2.png

机器环境

每个节点的IP,端口号为如下:

机构 节点 所属群组 P2P地址 RPC监听地址 Channel监听地址
机构A 节点0 群组1、2 127.0.0.1:30300 127.0.0.1:8545 0.0.0.0:20200
节点1 群组1、2 127.0.0.1:30301 127.0.0.1:8546 0.0.0.0:20201
机构B 节点2 群组1 127.0.0.1:30302 127.0.0.1:8547 0.0.0.0:20202
节点3 群组1 127.0.0.1:30303 127.0.0.1:8548 0.0.0.0:20203
机构C 节点4 群组2 127.0.0.1:30304 127.0.0.1:8549 0.0.0.0:20204
节点5 群组2 127.0.0.1:30305 127.0.0.1:8550 0.0.0.0:20205

注解

  • 云主机的公网IP均为虚拟IP,若rpc_ip/p2p_ip/channel_ip填写外网IP,会绑定失败,须填写0.0.0.0
  • RPC/P2P/Channel监听端口必须位于1024-65535范围内,且不能与机器上其他应用监听端口冲突
  • 出于安全性和易用性考虑,FISCO BCOS v2.3.0版本最新节点config.ini配置将listen_ip拆分成jsonrpc_listen_ip和channel_listen_ip,但仍保留对listen_ip的解析功能,详细请参考 这里
  • 为便于开发和体验,channel_listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP
涉及机构

搭链操作涉及多个机构的合作,包括:

  • 证书颁发机构
  • 搭建节点的机构(简称“机构”)
关键流程

本流程简要的给出证书颁发机构节点机构间如何相互配合搭建区块链。

一、初始化链证书
  1. 证书颁发机构操作:
    • 生成链证书
二、生成群组1
  1. 证书颁发机构操作:颁发机构证书
    • 生成机构证书
    • 发送证书
  2. 机构间独立操作
    • 修改配置文件node_deployment.ini
    • 生成节点证书及节点P2P端口地址文件
  3. 选取其中一个机构为群组生成创世块
    • 收集群组内所有节点证书
    • 修改配置文件group_genesis.ini
    • 为群组生成创世块文件
    • 分发创世块文件
  4. 机构间独立操作:生成节点
    • 收集群组其他节点的P2P端口地址文件
    • 生成节点
    • 启动节点
三、初始化新机构
  1. 证书颁发机构操作:颁发新机构证书
    • 生成机构证书
    • 发送证书
四、生成群组2
  1. 新机构独立操作
    • 修改配置文件node_deployment.ini
    • 生成节点证书及节点P2P端口地址文件
  2. 选取其中一个机构为群组生成创世块
    • 收集群组内所有节点证书
    • 修改配置文件group_genesis.ini
    • 为群组生成创世块文件
    • 分发创世块文件
  3. 新机构独立操作:生成节点
    • 收集群组其他节点的P2P端口地址文件
    • 生成节点
    • 启动节点
  4. 已有机构操作:配置新群组
    • 收集群组其他节点的P2P端口地址文件
    • 配置新群组与新增节点的P2P端口地址
    • 重启节点
五、现有节点加入群组1
  1. 群组1原有机构操作:
    • 发送群组1创世区块至现有节点
    • 配置控制台
    • 获取加入节点nodeid
    • 使用控制台将节点加入群组1

联盟链初始化

为了操作简洁,本示例所有操作在同一台机器上进行,用不同的目录模拟不同的机构环境。用文件复制操作来模拟网络的发送。进行了教程中的下载安装后,请将generator复制到对应机构的generator目录中。

机构初始化

我们以教程中下载的generator作为证书颁发机构

初始化机构A

cp -r ~/generator ~/generator-A

初始化机构B

cp -r ~/generator ~/generator-B
初始化链证书

由于FISCO BCOS节点与SDK通信时采用非国密方式,因此节点需要生成非国密版本的证书

在证书颁发机构上进行操作,一条联盟链拥有唯一的链证书ca.crtgmca.crt

--generate_chain_certificate 命令生成链证书

在证书生成机构目录下操作:

cd ~/generator

生成国密证书

./generator --generate_chain_certificate ./dir_chain_ca -g

生成普通证书

./generator --generate_chain_certificate ./dir_chain_ca_normal

查看链证书及私钥:

ls ./dir_chain_ca
# 上述命令解释
# 从左至右分别为链证书、链私钥
gmca.crt  gmca.key
ls ./dir_chain_ca_normal
# 上述命令解释
# 从左至右分别为链证书、链私钥
ca.crt  ca.key

机构A、B构建群组1

初始化机构A

教程中为了简化操作直接生成了机构证书和私钥,实际应用时应该由机构本地生成私钥agency.key和gmagency.key,再生成证书请求文件,向证书签发机构获取机构证书agency.crt和gmagency.crt

在证书生成机构目录下操作:

cd ~/generator

生成机构A证书:

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyA -g
./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca_normal agencyA_normal

查看机构证书及私钥:

ls dir_agency_ca/agencyA/
# 上述命令解释
# 从左至右分别为机构证书、机构私钥、链证书
gmagency.crt  gmagency.key  gmca.crt
ls dir_agency_ca/agencyA_normal/
# 上述命令解释
# 从左至右分别为机构证书、机构私钥、链证书
agency.crt  agency.key  ca.crt

发送链证书、机构证书、机构私钥至机构A,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下

cp ./dir_agency_ca/agencyA/* ~/generator-A/meta/
cp ./dir_agency_ca/agencyA_normal/* ~/generator-A/meta/
初始化机构B

在证书生成机构目录下操作:

cd ~/generator

生成机构B证书:

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyB -g
./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca_normal agencyB_normal

发送链证书、机构证书、机构私钥至机构B,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下

cp ./dir_agency_ca/agencyB/* ~/generator-B/meta/
cp ./dir_agency_ca/agencyB_normal/* ~/generator-B/meta/

重要

一条联盟链中只能用到一个根证书ca.crt,多服务器部署时不要生成多个根证书和私钥。一个群组只能有一个群组创世区块group.x.genesis

机构A修改配置文件

node_deployment.ini为节点配置文件,运维部署工具会根据node_deployment.ini下的配置生成相关节点证书,及生成节点配置文件夹等。

机构A修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-A目录下执行下述命令

cd ~/generator-A
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30301
channel_listen_port=20201
jsonrpc_listen_port=8546
EOF
机构B修改配置文件

机构B修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-B目录下执行下述命令

cd ~/generator-B
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=1

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30302
channel_listen_port=20202
jsonrpc_listen_port=8547

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30303
channel_listen_port=20203
jsonrpc_listen_port=8548
EOF
机构A生成并发送节点信息

在~/generator-A目录下执行下述命令

cd ~/generator-A

机构A生成节点证书及P2P连接信息文件,此步需要用到上述配置的node_deployment.ini,及机构meta文件夹下的机构证书与私钥,机构A生成节点证书及P2P连接信息文件

./generator --generate_all_certificates ./agencyA_node_info -g

查看生成文件:

ls ./agencyA_node_info
# 上述命令解释
# 从左至右分别为需要交互给机构A的节点证书,节点P2P连接地址文件(根据node_deployment.ini生成的本机构节点信息)
gmcert_127.0.0.1_30300.crt gmcert_127.0.0.1_30301.crt peers.txt

机构生成节点时需要指定其他节点的节点P2P连接地址,因此,A机构需将节点P2P连接地址文件发送至机构B

cp ./agencyA_node_info/peers.txt ~/generator-B/meta/peersA.txt
机构B生成并发送节点信息

在~/generator-B目录下执行下述命令

cd ~/generator-B

机构B生成节点证书及P2P连接信息文件:

./generator --generate_all_certificates ./agencyB_node_info -g

生成创世区块的机构需要节点证书,示例中由A机构生成创世区块,因此B机构除了发送节点P2P连接地址文件外,还需发送节点证书至机构A

发送证书

cp ./agencyB_node_info/gmcert*.crt ~/generator-A/meta/

发送节点P2P连接地址文件

cp ./agencyB_node_info/peers.txt ~/generator-A/meta/peersB.txt
机构A生成群组1创世区块

在~/generator-A目录下执行下述命令

cd ~/generator-A

机构A修改conf文件夹下的group_genesis.ini,配置项可参考手册。:

cat > ./conf/group_genesis.ini << EOF
[group]
group_id=1

[nodes]
node0=127.0.0.1:30300
node1=127.0.0.1:30301
node2=127.0.0.1:30302
node3=127.0.0.1:30303
EOF

命令执行之后会修改./conf/group_genesis.ini文件:

;命令解释
[group]
;群组id
group_id=1

[nodes]
;机构A节点p2p地址
node0=127.0.0.1:30300
;机构A节点p2p地址
node1=127.0.0.1:30301
;机构B节点p2p地址
node2=127.0.0.1:30302
;机构B节点p2p地址
node3=127.0.0.1:30303

教程中选择机构A生成群组创世区块,实际生产中可以通过联盟链委员会协商选择。

此步会根据机构A的meta文件夹下配置的节点证书,生成group_genesis.ini配置的群组创世区块,教程中需要机构A的meta下有名为gmcert_127.0.0.1_30300.crtgmcert_127.0.0.1_30301.crtgmcert_127.0.0.1_30302.crtgmcert_127.0.0.1_30303.crt的节点证书,此步需要用到机构B的节点证书。

./generator --create_group_genesis ./group -g

分发群组1创世区块至机构B:

cp ./group/group.1.genesis ~/generator-B/meta
机构A生成所属节点

在~/generator-A目录下执行下述命令

cd ~/generator-A

生成机构A所属节点,此命令会根据用户配置的node_deployment.ini文件生成相应的节点配置文件夹:

注意,此步指定的节点P2P连接信息peers.txt为群组内其他节点的链接信息,多个机构组网的情况下需要将其合并。

./generator --build_install_package ./meta/peersB.txt ./nodeA -g

查看生成节点配置文件夹:

ls ./nodeA
# 命令解释 此处采用tree风格显示
# 生成的文件夹nodeA信息如下所示,
├── monitor # monitor脚本
├── node_127.0.0.1_30300 # 127.0.0.1服务器 端口号30300的节点配置文件夹
├── node_127.0.0.1_30301
├── scripts # 节点的相关工具脚本
├── start_all.sh # 节点批量启动脚本
└── stop_all.sh # 节点批量停止脚本

机构A启动节点:

bash ./nodeA/start_all.sh

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
机构B生成所属节点

在~/generator-B目录下执行下述命令

cd ~/generator-B

生成机构B所属节点,此命令会根据用户配置的node_deployment.ini文件生成相应的节点配置文件夹:

./generator --build_install_package ./meta/peersA.txt ./nodeB -g

机构B启动节点:

bash ./nodeB/start_all.sh

注解

节点启动只需要推送对应ip的node文件夹即可,如127.0.0.1的服务器,只需node_127.0.0.1_port对应的节点配置文件夹。多机部署时,只需要将生成的节点文件夹推送至对应服务器即可。

查看群组1节点运行状态

查看进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下所示的进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini

查看节点log:

tail -f ./node*/node*/log/log*  | grep +++
# 命令解释
# log中打印的+++即为节点正常共识
info|2019-02-25 17:25:56.028692| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=833bd983...
info|2019-02-25 17:25:59.058625| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=343b1141...
info|2019-02-25 17:25:57.038284| [g:1][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=1,hash=ea85c27b...

至此,我们完成了如图所示机构A、B搭建群组1的操作:

_images/tutorial_step_1.png

证书授权机构初始化机构C

在证书生成机构目录下操作:

cd ~/generator

初始化机构C,请注意,此时generator目录下有链证书及私钥,实际环境中机构C无法获取链证书及私钥。

cp -r ~/generator ~/generator-C

生成机构C证书:

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyC -g
./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca_normal agencyC_normal

发送链证书、机构证书、机构私钥至机构C,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下

cp ./dir_agency_ca/agencyC/* ~/generator-C/meta/
cp ./dir_agency_ca/agencyC_normal/* ~/generator-C/meta/

机构A、C构建群组2

接下来,机构C需要与A进行新群组建立操作,示例中以C生成创世区块为例。

机构A发送节点信息

由于机构A已经生成过节点证书及peers文件,只需将之前生成的节点P2P连接信息以及节点证书发送至机构C,操作如下:

在~/generator-A目录下执行下述命令

cd ~/generator-A

示例中由机构C生成群组创世区块,因此需要机构A的节点证书和节点P2P连接地址文件,将上述文件发送至机构C

发送证书

cp ./agencyA_node_info/gmcert*.crt ~/generator-C/meta/

发送节点P2P连接地址文件

cp ./agencyA_node_info/peers.txt ~/generator-C/meta/peersA.txt
机构C修改配置文件

机构C修改conf文件夹下的node_deployment.ini如下图所示:

在~/generator-C目录下执行下述命令

cd ~/generator-C
cat > ./conf/node_deployment.ini << EOF
[group]
group_id=2

[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=127.0.0.1
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please see https://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30304
channel_listen_port=20204
jsonrpc_listen_port=8549

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30305
channel_listen_port=20205
jsonrpc_listen_port=8550
EOF
机构C生成并发送节点信息

在~/generator-C目录下执行下述命令

cd ~/generator-C

机构C生成节点证书及P2P连接信息文件:

./generator --generate_all_certificates ./agencyC_node_info -g

查看生成文件:

ls ./agencyC_node_info
# 上述命令解释
# 从左至右分别为需要交互给机构A的节点证书,节点P2P连接地址文件(根据node_deployment.ini生成的本机构节点信息)
gmcert_127.0.0.1_30304.crt gmcert_127.0.0.1_30305.crt peers.txt

机构生成节点时需要指定其他节点的节点P2P连接地址,因此,C机构需将节点P2P连接地址文件发送至机构A

cp ./agencyC_node_info/peers.txt ~/generator-A/meta/peersC.txt
机构C生成群组2创世区块

在~/generator-C目录下执行下述命令

cd ~/generator-C

机构C修改conf文件夹下的group_genesis.ini如下图所示:

cat > ./conf/group_genesis.ini << EOF
[group]
group_id=2

[nodes]
node0=127.0.0.1:30300
node1=127.0.0.1:30301
node2=127.0.0.1:30304
node3=127.0.0.1:30305
EOF

命令执行之后会修改./conf/group_genesis.ini文件:

;命令解释
[group]
group_id=2

[nodes]
node0=127.0.0.1:30300
;机构A节点p2p地址
node1=127.0.0.1:30301
;机构A节点p2p地址
node2=127.0.0.1:30304
;机构C节点p2p地址
node3=127.0.0.1:30305
;机构C节点p2p地址

教程中选择机构C生成群组创世区块,实际生产中可以通过联盟链委员会协商选择。

此步会根据机构C的meta文件夹下配置的节点证书,生成group_genesis.ini配置的群组创世区块。

./generator --create_group_genesis ./group -g

分发群组2创世区块至机构A:

cp ./group/group.2.genesis ~/generator-A/meta/
机构C生成所属节点

在~/generator-C目录下执行下述命令

cd ~/generator-C
./generator --build_install_package ./meta/peersA.txt ./nodeC -g

机构C启动节点:

bash ./nodeC/start_all.sh
ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini
fisco  15550     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30304/fisco-bcos -c config.ini
fisco  15589     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30305/fisco-bcos -c config.ini
机构A为现有节点初始化群组2

在~/generator-A目录下执行下述命令

cd ~/generator-A

添加群组2配置文件至已有节点,此步将群组2创世区块group.2.genesis添加至./nodeA下的所有节点内:

./generator --add_group ./meta/group.2.genesis ./nodeA

添加机构C节点连接文件peers至已有节点,此步将peersC.txt的节点P2P连接地址添加至./nodeA下的所有节点内:

./generator --add_peers ./meta/peersC.txt ./nodeA

重启机构A节点:

bash ./nodeA/stop_all.sh
bash ./nodeA/start_all.sh
查看群组2节点运行状态

查看节点进程:

ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco  15347     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30300/fisco-bcos -c config.ini
fisco  15402     1  0 17:22 pts/2    00:00:00 ~/generator-A/nodeA/node_127.0.0.1_30301/fisco-bcos -c config.ini
fisco  15457     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco  15498     1  0 17:22 pts/2    00:00:00 ~/generator-B/nodeB/node_127.0.0.1_30303/fisco-bcos -c config.ini
fisco  15550     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30304/fisco-bcos -c config.ini
fisco  15589     1  0 17:22 pts/2    00:00:00 ~/generator-C/nodeC/node_127.0.0.1_30305/fisco-bcos -c config.ini

查看节点log:

在~/generator-C目录下执行下述命令

cd ~/generator-C
tail -f ./node*/node*/log/log*  | grep +++
# 命令解释
# log中打印的+++即为节点正常共识
info|2019-02-25 17:25:56.028692| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=833bd983...
info|2019-02-25 17:25:59.058625| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=0,hash=343b1141...
info|2019-02-25 17:25:57.038284| [g:2][p:264][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,myIdx=1,hash=ea85c27b...

至此,我们完成了如图所示的机构A、C搭建群组2构建:

_images/tutorial_step_2.png

扩展教程–机构C节点加入群组1

将节点加入已有群组需要用户使用控制台发送指令,将节点加入群组,示例如下:

此时群组1内有机构A、B的节点,机构C节点加入群组1需要经过群组内节点的准入,示例以机构A节点为例:

在~/generator-A目录下执行下述命令

cd ~/generator-A
发送群组1创世区块至机构C

发送群组1配置文件至机构C节点:

./generator --add_group ./group/group.1.genesis  ~/generator-C/nodeC

当前FISCO BCOS暂不支持文件热更新,为机构C节点添加群组1创世区块后需重启节点。

重启机构C节点:

bash ~/generator-C/nodeC/stop_all.sh
bash ~/generator-C/nodeC/start_all.sh
配置控制台

机构A配置控制台或sdk,教程中以控制台为例:

注解

企业部署工具会根据generator/meta文件夹下的机构证书及私钥生成sdk相应证书,如需手动生成可以参考操作手册中的generate_sdk_certificate命令

国内用户推荐使用cdn下载,如果访问github较快,可以去掉--cdn选项:

./generator --download_console ./ --cdn

修改国密配置

vi ./console/conf/applicationContext.xml

进行如下修改

<bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
    <!-- encryptType值设置为1,打开国密开关 -->
    <constructor-arg value="1"/> <!-- 0:standard 1:guomi -->
</bean>
查看机构C节点4信息

机构A使用控制台加入机构C节点4为观察节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的gmnode.nodeid文件

查看机构C节点nodeid:

cat ~/generator-C/nodeC/node_127.0.0.1_30304/conf/gmnode.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
使用控制台注册观察节点

启动控制台:

cd ~/generator-A/console && bash ./start.sh 1

使用控制台addObserver命令将节点注册为观察节点,此步需要用到cat命令查看得到机构C节点的gmnode.nodeid

addObserver ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
# 命令解释
# 执行成功会提示success
$ [group:1]> addObserver ea2ca519148cafc3e92c8d9a8572b41ea2f62d0d19e99273ee18cccd34ab50079b4ec82fe5f4ae51bd95dd788811c97153ece8c05eac7a5ae34c96454c4d3123
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit
查看机构C节点5信息

机构A使用控制台加入机构C的节点5为共识节点,其中参数第二项需要替换为加入节点的nodeid,nodeid在节点文件夹的conf的node.nodeid文件

查看机构C节点nodeid:

cat ~/generator-C/nodeC/node_127.0.0.1_30305/conf/gmnode.nodeid
# 命令解释
# 可以看到类似于如下nodeid,控制台使用时需要传入该参数
5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
使用控制台注册共识节点

启动控制台:

cd ~/generator-A/console && bash ./start.sh 1

使用控制台addSealer命令将节点注册为共识节点,此步需要用到cat命令查看得到机构C节点的gmnode.nodeid

addSealer 5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
# 命令解释
# 执行成功会提示success
$ [group:1]> addSealer 5d70e046047e15a68aff8e32f2d68d1f8d4471953496fd97b26f1fbdc18a76720613a34e3743194bd78aa7acb59b9fa9aec9ec668fa78c54c15031c9e16c9e8d
{
	"code":0,
	"msg":"success"
}

退出控制台:

exit
查看节点

在~/generator-C目录下执行下述命令

cd ~/generator-C

查看节点log内group1信息:

cat node*/node_127.0.0.1_3030*/log/log* | grep g:1  | grep Report
# 命令解释
# 观察节点只会同步交易数据,不会同步非交易状态的共识信息
# log中的^^^即为节点的交易信息,g:1为群组1打印的信息
info|2019-02-26 16:01:39.914367| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=9b76de5d...,next=1,tx=0,nodeIdx=65535
info|2019-02-26 16:01:40.121075| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=3,hash=46b7f17c...,next=2,tx=1,nodeIdx=65535
info|2019-02-26 16:03:44.282927| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=2,sealerIdx=2,hash=fb982013...,next=3,tx=1,nodeIdx=65535
info|2019-02-26 16:01:39.914367| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=0,sealerIdx=0,hash=9b76de5d...,next=1,tx=0,nodeIdx=4
info|2019-02-26 16:01:40.121075| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=1,sealerIdx=3,hash=46b7f17c...,next=2,tx=1,nodeIdx=4
info|2019-02-26 16:03:44.282927| [g:1][p:65544][CONSENSUS][PBFT]^^^^^^^^Report,num=2,sealerIdx=2,hash=fb982013...,next=3,tx=1,nodeIdx=4

至此 我们完成了所示构建教程中的所有操作。

_images/tutorial_step_3.png

通过本节教程,我们在本机生成一个网络拓扑结构为3机构2群组6节点的多群组架构联盟链。

如果使用该教程遇到问题,请查看FAQ

下载安装

环境依赖

FISCO BCOS generator依赖如下:

依赖软件 支持版本
python 2.7+ 或 3.6+
openssl 1.0.2k+
curl 默认版本
nc 默认版本

下载安装

下载

$ git clone https://github.com/FISCO-BCOS/generator.git

安装

$ cd generator
$ bash ./scripts/install.sh

检查是否安装成功

$ ./generator -h
# 若成功,输出 usage: generator xxx

拉取节点二进制

拉取最新fisco-bcos二进制文件到meta中

$ ./generator --download_fisco ./meta

检查二进制版本

$ ./meta/fisco-bcos -v
# 若成功,输出 FISCO-BCOS Version : x.x.x-x

PS源码编译节点二进制的用户,只需要把编译出来的二进制放到meta文件夹下即可。

配置文件

FISCO BCOS generator的配置文件在./conf文件夹下,配置文件为:群组创世区块配置文件group_genesis.ini和生成节点配置文件node_deployment.ini

用户通过对conf文件夹下文件的操作,配置生成节点配置文件夹的具体信息。

元数据文件夹meta

FISCO BCOS generator的meta文件夹为元数据文件夹,需要存放fisco bcos二进制文件、链证书ca.crt、本机构机构证书agency.crt、机构私钥节点证书、群组创世区块文件等。

证书的存放格式需要为cert_p2pip_port.crt的格式,如cert_127.0.0.1_30300.crt。

FISCO BCOS generator会根据用户在元数据文件夹下放置的相关证书、conf下的配置文件,生成用户下配置的节点配置文件夹。

group_genesis.ini

通过修改group_genesis.ini的配置,用户在指定目录及meta文件夹下生成新群组创世区块的相关配置,如group.1.genesis

[group]
group_id=1

[nodes]
;群组创世区块的节点p2p地址
node0=127.0.0.1:30300
node1=127.0.0.1:30301
node2=127.0.0.1:30302
node3=127.0.0.1:30303

重要

生成群组创世区块时需要节点的证书,如上述配置文件中需要4个节点的证书。分别为:cert_127.0.0.1_30301.crt,cert_127.0.0.1_30302.crt,cert_127.0.0.1_30303.crt和cert_127.0.0.1_30304.crt。

node_deployment.ini

通过修改node_deployment.ini的配置,用户可以使用–build_install_package命令在指定文件夹下生成节点不含私钥的节点配置文件夹。用户配置的每个section[node]即为用户需要生成的节点配置文件夹.section[peers]为需要连接的其他节点p2p信息。

配置文件示例如下:

[group]
group_id=1

# Owned nodes
[node0]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545

[node1]
p2p_ip=127.0.0.1
rpc_ip=127.0.0.1
channel_ip=0.0.0.0
p2p_listen_port=30301
channel_listen_port=20201
jsonrpc_listen_port=8546

读取节点配置的命令,如生成节点证书和节点配置文件夹等会读取该配置文件。

模板文件夹tpl

generator的模板文件夹如下图所示:

├── applicationContext.xml # sdk配置文件模板
├── config.ini # 节点配置文件模板
├── config.ini.gm # 国密节点配置文件模板
├── group.i.genesis # 群组创世区块模板
├── group.i.ini # 群组区块配置模板
├── start.sh  # 节点启动脚本模板
├── start_all.sh # 节点批量启动脚本模板
├── stop.sh # 节点停止脚本模板
└── stop_all.sh # 节点批量停止脚本模板

generator在进行如生成节点或群组配置的相关操作时,会根据模板文件夹下的配置文件生成相应的节点配置文件夹/群组配置,用户可以修改模板文件夹下的相关文件,再运行部署相关命令,即可生成自定义节点。

FISCO BCOS配置的相关解释可以参考FISCO BCOS配置文件

节点p2p连接文件peers.txt

节点p2p连接文件peers.txt为生成节点配置文件夹时指定的其他机构的节点连接信息,在使用build_install_package命令时,需要指定与本机构节点进行连接的节点p2p连接文件peers.txt,生成的本机构节点配置文件夹会根据该文件与其他节点进行通信。

采用generate_all_certificates命令的用户会根据在conf目录下填写的node_deployment.ini生成相应的peers.txt,采用其他方式生成证书的用户需要手动生成本机构节点的p2p连接文件并发送给对方,节点p2p连接文件的格式如下所示:

127.0.0.1:30300
127.0.0.1:30301

格式为 对应节点ip:p2p_listen_port

  • 当需要与多机构节点通信时,需要将该文件合并

操作手册

FISCO BCOS generator 提供多种节点生成、扩容、群组划分、证书相关操作,简略介绍如下:

命令名称 基本功能
create_group_genesis 指定文件夹
build_install_package 在指定文件夹下生成node_deployment.ini中配置的
节点配置文件夹(需要在meta下存放生成节点的证书)
generate_all_certificates 根据node_deployment.ini生成相关节点证书和私钥
generate_*_certificate 生成相应链、机构、节点、sdk证书及私钥
merge_config 将两个节点配置文件中的P2P部分合并
deploy_private_key 将私钥批量导入生成的节点配置文件夹中
add_peers 将节点连接文件批量导入节点配置文件夹中
add_group 将群组创世区块批量导入节点配置文件夹中
version 打印当前版本号
h/help 帮助命令

create_group_genesis (-c)

命令解释 生成群组创世区块
使用前提 用户需配置group_genesis.ini,并在meta下配置相关节点的证书
参数设置 指定生成群组节点配置文件夹
实现功能 根据用户在meta下配置的证书,在指定目录生成新群组的配置
适用场景 联盟链中已有节点从新划分群组时

操作示例

$ cp node0/node.crt ./meta/cert_127.0.0.1_3030n.crt
...
$ vim ./conf/group_genesis.ini
$ ./generator --create_group_genesis ~/mydata

程序执行完成后,会在~/mydata文件夹下生成mgroup.ini中配置的group.i.genesis

用户生成的group.i.genesis即为群组的创世区块,即可完成新群组划分操作。

注解

FISCO BCOS 2.0中每个群组都会有一个群组创世区块。

build_install_package (-b)

命令解释 部署新节点及新群组
使用前提 用户需配置node_deployment.ini,并指定节点p2p链接文件
参数设置 指定文件夹作为配置文件夹存放路径
实现功能 通过给定群组创世区块、节点证书和节点信息,生成节点配置文件夹
适用场景 生成节点配置文件夹

操作示例

$ vim ./conf/node_deployment.ini
$ ./generator --build_install_package ./peers.txt ~/mydata

程序执行完成后,会在~/mydata文件夹下生成多个名为node_hostip_port的文件夹,推送到对应服务器后即可启动节点

generate_chain_certificate

命令解释 生成根证书
使用前提
参数设置 指定根证书存放文件夹
实现功能 在指定目录生成根证书和私钥
适用场景 用户需要生成自签相关根证书
$ ./generator --generate_chain_certificate ./dir_chain_ca

执行完成后用户可以在./dir_chain_ca文件夹下看到根证书ca.crt 和私钥ca.key

generate_agency_certificate

命令解释 生成机构证书
使用前提 存在根证书和私钥
参数设置 指定机构证书目录,链证书及私钥存放目录和机构名称
实现功能 在指定目录生成机构证书和私钥
适用场景 用户需要生成自签相关机构证书
$ ./generator --generate_agency_certificate ./dir_agency_ca ./chain_ca_dir The_Agency_Name

执行完成后可以在./dir_agency_ca路径下生成名为The_Agency_Name的文件夹,包含相应的机构证书agency.crt 和私钥agency.key

generate_node_certificate

命令解释 生成节点证书
使用前提 存在机构证书和私钥
参数设置 指定节点证书目录,机构证书及私钥存放目录和节点名称
实现功能 在指定目录生成节点证书和私钥
适用场景 用户需要生成自签相关节点证书
$ ./generator --generate_node_certificate node_dir(SET) ./agency_dir  node_p2pip_port

执行完成后可以在node_dir 路径下生成节点证书node.crt 和私钥node.key

generate_sdk_certificate

命令解释 生成SDK证书
使用前提 存在机构证书和私钥
参数设置 指定节点证书目录,机构证书及私钥存放目录和节点名称
实现功能 在指定目录生成SDK证书和私钥
适用场景 用户需要生成自签相关SDK证书
$ ./generator --generate_sdk_certificate ./dir_sdk_ca ./dir_agency_ca

执行完成后可以在./dir_sdk_ca路径下生成名为SDK的文件夹,包含相应的SDK证书node.crt 和私钥node.key

FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错。

generate_all_certificates

命令解释 根据node_deployment.ini生成相关证书及私钥
使用前提
参数设置 指定节点证书目录
实现功能 根据node_deployment.ini在meta下生成节点私钥、证书,在指定目录生成中需要交换的节点证书、节点p2p连接文件
适用场景 用户交换节点数据时
$ ./generator --generate_all_certificates ./cert

注解

上述命令会根据meta目录下存放的ca.crt、机构证书agency.crt和机构私钥agency.key生成相应的节点证书。

  • 如果用户缺少上述三个文件,则无法生成节点证书,程序会抛出异常。

执行完成后会在./cert文件夹下生成节点的相关证书与私钥,并将节点证书放置于./meta下

merge_config (-m)

使用–merge_config命令可以合并两个config.ini中的p2p section

如 A目录下的config.ini文件的p2p section为

[p2p]
listen_ip = 127.0.0.1
listen_port = 30300
node.0 = 127.0.0.1:30300
node.1 = 127.0.0.1:30301
node.2 = 127.0.0.1:30302
node.3 = 127.0.0.1:30303

B目录下的config.ini文件的p2p section为

[p2p]
listen_ip = 127.0.0.1
listen_port = 30303
node.0 = 127.0.0.1:30300
node.1 = 127.0.0.1:30303
node.2 = 127.0.0.1:30300
node.3 = 127.0.0.1:30301

使用此命令后会成为:

[p2p]
listen_ip = 127.0.0.1
listen_port = 30304
node.0 = 127.0.0.1:30300
node.1 = 127.0.0.1:30301
node.2 = 127.0.0.1:30302
node.3 = 127.0.0.1:30303
node.4 = 127.0.0.1:30300
node.5 = 127.0.0.1:30301

使用示例

$ ./generator --merge_config ~/mydata/node_A/config.ini  ~/mydata/node_B/config.ini

使用成功后会将node_A和node_B的config.ini中p2p section合并与 ~/mydata/node_B/config.ini的文件中

deploy_private_key (-d)

使用–deploy_private_key可以将路径下名称相同的节点私钥导入到生成好的配置文件夹中。

使用示例:

$./generator --deploy_private_key ./cert ./data

如./cert下有名为node_127.0.0.1_30300,node_127.0.0.1_30301的文件夹,文件夹中有节点私钥文件node.key

./data下有名为node_127.0.0.1_30300,node_127.0.0.1_30301的配置文件夹

执行完成后可以将./cert下的对应的节点私钥导入./data的配置文件夹中

add_peers (-p)

使用–add_peers可以指定的peers文件导入到生成好的节点配置文件夹中。

使用示例:

$./generator --add_peers ./meta/peers.txt ./data

./data下有名为node_127.0.0.1_30300,node_127.0.0.1_30301的配置文件夹

执行完成后可以将peers文件中的连接信息导入./data下所有节点的配置文件config.ini

add_group (-a)

使用–add_group可以指定的peers文件导入到生成好的节点配置文件夹中。

使用示例:

$./generator --add_group ./meta/group.2.genesis ./data

./data下有名为node_127.0.0.1_30300,node_127.0.0.1_30301的配置文件夹

执行完成后可以将群组2的连接信息导入./data下所有节点的conf文件夹中

download_fisco

使用–download_fisco可以指定的目录下下载fisco-bcos二进制文件,国内用户可以使用--cdn命令从cdn下载。

使用示例:

$./generator --download_fisco ./meta

$./generator --download_fisco ./meta --cdn

执行完成后会在./meta文件夹下下载fisco-bcos可执行二进制文件

download_console

使用–download_console可以指定的目录下下载并配置控制台,国内用户可以使用--cdn命令从cdn下载。。

使用示例:

$./generator --download_console ./meta

$./generator --download_console ./meta --cdn

执行完成后会在./meta文件夹下根据node_deployment.ini完成对控制台的配置

get_sdk_file

使用–get_sdk_file可以指定的目录下下获取控制台和sdk配置所需要的node.crtnode.keyca.crtapplicationContext.xml

使用示例:

$./generator --get_sdk_file ./sdk

执行完成后会在./sdk文件夹下根据node_deployment.ini生成上述配置文件

version (-v)

使用–version命令查看当前部署工具的版本号。

$ ./generator --version

help (-h)

用户可以使用-h或–help命令查看帮助菜单

使用示例:

$ ./generator -h
usage: generator [-h] [-v] [-b peer_path data_dir] [-c data_dir]
                 [--generate_chain_certificate chain_dir]
                 [--generate_agency_certificate agency_dir chain_dir agency_name]
                 [--generate_node_certificate node_dir agency_dir node_name]
                 [--generate_sdk_certificate sdk_dir agency_dir] [-g]
                 [--generate_all_certificates cert_dir] [-d cert_dir pkg_dir]
                 [-m config.ini config.ini] [-p peers config.ini]
                 [-a group genesis config.ini]

国密操作相关

FISCO BCOS generator的所有命令同时支持国密版fisco-bcos,使用时,国密证书、私钥均加以前缀gm。基本使用解释如下

国密开关 (-g)

国密开关-g打开时,生成证书、节点、群组创世区块的操作会相应生成国密版的上述文件。

生成证书操作

如generate_*_certificate操作时,配合-g命令会生成相应的国密证书。

操作示例:

$ ./generator --generate_all_certificates ./cert -g

注解

上述命令会根据meta目录下存放的gmca.crt、机构证书gmagency.crt和机构私钥gmagency.key生成相应的节点证书。

  • 如果用户缺少上述三个文件,则无法生成节点证书,程序会抛出异常。
生成国密群组创世区块

操作示例

$ cp node0/gmnode.crt ./meta/gmcert_127.0.0.1_3030n.crt
...
$ vim ./conf/group_genesis.ini
$ ./generator --create_group_genesis ~/mydata -g

程序执行完成后,会在~/mydata文件夹下生成mgroup.ini中配置的group.i.genesis

用户生成的group.i.genesis即为群组的创世区块,即可完成新群组划分操作。

生成国密节点配置文件夹

操作示例

$ vim ./conf/node_deployment.ini
$ ./generator --build_install_package ./peers.txt ~/mydata -g

程序执行完成后,会在~/mydata文件夹下生成多个名为node_hostip_port的文件夹,推送到对应服务器后即可启动节点

监控设计

FISCO BCOS generator 生成的节点配置文件夹中提供了内置的监控脚本,用户可以通过对其进行配置,将节点的告警信息发送至指定地址。FISCO BCOS generator会将monitor脚本放置于生成节点配置文件的指定目录下,假设用户指定生成的文件夹名为data,则monitor脚本会在data目录下的monitor文件夹下

使用方式如下:

$ cd ./data/monitor

用途如下:

  1. 监控节点是否存活, 并且可以重新启动挂掉的节点.
  2. 获取节点的块高和view信息, 判断节点共识是否正常.
  3. 分析最近一分钟的节点日志打印, 收集日志关键错误打印信息, 准实时判断节点的状态.
  4. 指定日志文件或者指定时间段, 分析节点的共识消息处理, 出块, 交易数量等信息, 判断节点的健康度.
配置告警服务

用户使用前,首先需要配置告警信息服务,这里以server酱的微信推送为例,可以参考配置server酱

绑定自己的github账号,以及微信后,可以使用本脚本向微信发送告警信息,使用本脚本的-s命令 可以向指定微信发送告警信息

如果用户希望使用其他服务,可以修改monitor.sh中的alarm() { # change http server }函数,个性化配置为自己需要的服务

help命令

使用help命令查看脚本使用方式

$ ./monitor.sh -h
Usage : bash monitor.sh
   -s : send alert to your address
   -m : monitor, statistics.  default : monitor .
   -f : log file to be analyzed.
   -o : dirpath
   -p : name of the monitored program , default is fisco-bcos
   -g : specified the group list to be analized
   -d : log analyze time range. default : 10(min), it should not bigger than max value : 60(min).
   -r : setting alert receiver
   -h : help.
 example :
   bash  monitor.sh -s YourHttpAddr -o nodes -r your_name
   bash  monitor.sh -s YourHttpAddr -m statistics -o nodes -r your_name
   bash  monitor.sh -s YourHttpAddr -m statistics -f node0/log/log_2019021314.log -g 1 2 -r your_name

命令解释如下:

  • -s 指定告警配置地址,可以配置为告警上报服务的ip
  • -m 设定监控模式,可以配置为statistics和monitor两种模式,默认为monitor模式。
  • -f 分析节点log
  • -o 指定节点路径
  • -p 设定监控上报名称,默认为fisco-bcos
  • -g 指定监控群组,默认分析所有群组
  • -d log分析时间范围,默认10分钟内的log,最大不超过60分钟
  • -r 指定上报接收者名称
  • -h 帮助命令
使用示例
  • 使用脚本监控指定路径下节点,发送给接收者Alice:
$ bash monitor.sh -s https://sc.ftqq.com/[SCKEY(登入后可见)].send -o alice/nodes -r Alice
  • 使用脚本统计指定路径下节点信息,发送给接收者Alice
$ bash monitor.sh -s https://sc.ftqq.com/[SCKEY(登入后可见)].send -m statistics -o alice/nodes -r Alice
  • 使用脚本统计指定路径下节点指定log指定群组1和群组2的信息,发送给接收者Alice
$ bash monitor.sh -s https://sc.ftqq.com/[SCKEY(登入后可见)].send -m statistics -f node0/log/log_2019021314.log -g 1 2 -o alice/nodes -r Alice

handshake failed检测

FISCO BCOS generator 的scripts文件夹的check_certificates.sh脚本包含了节点log中提示handshake failed的异常检测。

获取脚本

如果用户需要检测由开发部署工具buildchain.sh生成的节点时,可以采用以下命令获取检测脚本:

curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/generator/master/scripts/check_certificates.sh && chmod u+x check_certificates.sh

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/generator/raw/master/scripts/check_certificates.sh && chmod u+x check_certificates.sh

使用generator部署节点的用户可以从generator的根目录下,从scripts/check_certificates.sh获取脚本。

检测证书有效期

check_certificates.sh的-t命令会根据用户证书签发的有效期,以及当前的系统时间对证书进行检测。

使用示例:

$ ./check_certificates.sh -t ~/certificates.crt

参数第二项为任意符合x509格式的证书,验证成功时会提示check certificates time successful, 验证失败会提示异常。

验证证书

check_certificates.sh的-v命令会根据用户指定的根证书从而验证节点证书。

$ ./check_certificates.sh -v ~/ca.crt ~/node.crt

验证成功时会提示use ~/ca.crt verify ~/node.crt successful, 验证失败会提示异常。

节点配置错误检查

获取脚本
curl -#LO https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/check_node_config.sh && chmod u+x check_node_config.sh

注解

  • 如果因为网络问题导致长时间无法下载,请尝试 curl -#LO https://gitee.com/FISCO-BCOS/FISCO-BCOS/raw/master/tools/check_node_config.sh
使用

使用下面的命令,脚本-p选项指定节点路径,脚本会根据路径下的config.ini分析配置是否有错误。

bash check_node_config.sh -p node_127.0.0.1_30300

SDK

FISCO BCOS区块链向外部暴露了接口,外部业务程序能够通过FISCO BCOS提供的SDK来调用这些接口。开发者只需要根据自身业务程序的要求,选择相应语言的SDK,用SDK提供的API进行编程,即可实现对区块链的操作。

对接应用

目前,SDK接口可实现的功能包括(但不限于):

  • 合约操作
    • 合约编译、部署、查询
    • 交易发送、上链通知、参数解析、回执解析
  • 链管理
    • 链状态查询、链参数设置
    • 组员管理
    • 权限设置
  • 其它
    • SDK间的相互消息推送(AMOP)

内置控制台

为了方便开发者,部分SDK内置了控制台的功能。开发者可直接通过用命令行进行上述功能的操作。如编译合约、部署合约、发送交易、查询交易、链管理等等。

多种语言SDK

目前,FISCO BCOS提供的SDK包括:

  • Java SDK (稳定、功能强大、无内置控制台)
  • Python SDK (简单轻便、有内置控制台)
  • Node-js SDK(简单轻便、有内置控制台)
  • Go SDK(简单轻便、有内置控制台)

Java SDK

Web3SDK可以支持访问节点、查询节点状态、修改系统设置和发送交易等功能。该版本(2.0)的技术文档只适用Web3SDK 2.0及以上版本(与FISCO BCOS 2.0及以上版本适配),1.2.x版本的技术文档请查看Web3SDK 1.2.x版本技术文档

2.0+版本主要特性包括:

  • 提供调用FISCO BCOS 2.0 JSON-RPC的Java API
  • 支持预编译(Precompiled)合约管理区块链
  • 支持链上信使协议为联盟链提供安全高效的消息信道
  • 支持使用国密算法发送交易
  • 支持通过国密SSL与节点通信

环境要求

重要

  • Java版本

JDK1.8 或者以上版本,推荐使用OracleJDK。

注意:CentOS的yum仓库的OpenJDK缺少JCE(Java Cryptography Extension),会导致JavaSDK无法正常连接区块链节点。

  • Java安装
  • FISCO BCOS区块链环境搭建
  • 网络连通性
检查Web3SDK连接的FISCO BCOS节点`channel_listen_port`是否能telnet通,若telnet不通,需要检查网络连通性和安全策略。

Java应用引入SDK

通过gradle或maven引入SDK到java应用

gradle:

compile ('org.fisco-bcos:web3sdk:2.6.1')

maven:

<dependency>
    <groupId>org.fisco-bcos</groupId>
    <artifactId>web3sdk</artifactId>
    <version>2.6.1</version>
</dependency>

由于引入了以太坊的solidity编译器相关jar包,需要在Java应用的gradle配置文件build.gradle中添加以太坊的远程仓库。

repositories {
        mavenCentral()
        maven { url "https://dl.bintray.com/ethereum/maven/" }
    }

注: 如果下载Web3SDK的依赖solcJ-all-0.4.25.jar速度过慢,可以参考这里进行下载。

配置SDK

证书配置

FISCO BCOS作为联盟链,SDK连接区块链节点时通过SSL进行双向认证。JavaSDK支持SSL与国密SSL两种认证方式。

SSL连接配置

国密区块链和非国密区块链环境下,节点与SDK之间均可以建立SSL的连接,将节点所在目录nodes/${ip}/sdk/目录下的ca.crtsdk.crtsdk.key文件拷贝到项目的资源目录。(低于2.1版本的FISCO BCOS节点目录下只有node.crtnode.key,需将其重命名为sdk.crtsdk.key以兼容最新的SDK)

国密SSL连接配置

FISCO-BCOS 2.5及之后的版本,在国密区块链环境下支持节点与SDK建立国密SSL连接,将节点所在目录nodes/${ip}/sdk/gm/目录下的gmca.crtgmensdk.crtgmensdk.keygmsdk.crtgmsdk.key文件拷贝到项目的资源目录。

重要

  • 国密SSL连接只有在国密区块链环境下才可以使用。
  • 是否选择国密SSL连接,SDK与区块链节点的配置要保持一致,节点配置参考 配置链属性
  • FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错。
配置文件设置

Java应用的配置文件需要做相关配置。值得关注的是,FISCO BCOS 2.0+版本支持多群组功能,SDK需要配置群组的节点信息。将以Spring项目和Spring Boot项目为例,提供配置指引。

Spring项目配置

提供Spring项目中关于applicationContext.xml的配置下所示。

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
           xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
         http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


        <bean id="encryptType" class="org.fisco.bcos.web3j.crypto.EncryptType">
                <constructor-arg value="0"/> <!-- 0:standard 1:guomi -->
        </bean>

        <bean id="groupChannelConnectionsConfig" class="org.fisco.bcos.channel.handler.GroupChannelConnectionsConfig">
                <!-- SSL certificate configuration -->
                <property name="caCert" value="ca.crt" />
                <property name="sslCert" value="sdk.crt" />
                <property name="sslKey" value="sdk.key" />
                <!-- GM SSL certificate configuration -->
                <property name="gmCaCert" value="gmca.crt" />
                <property name="gmEnSslCert" value="gmensdk.crt" />
                <property name="gmEnSslKey" value="gmensdk.key" />
                <property name="gmSslCert" value="gmsdk.crt" />
                <property name="gmSslKey" value="gmsdk.key" />
                <property name="allChannelConnections">
                        <list>  <!-- 每个群组需要配置一个bean,每个群组可以配置多个节点 -->
                                <bean id="group1"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                                        <property name="groupId" value="1" /> <!-- 群组的groupID -->
                                        <property name="connectionsStr">
                                                <list>
                                                        <value>127.0.0.1:20200</value>  <!-- IP:channel_port -->
                                                        <value>127.0.0.1:20201</value>
                                                </list>
                                        </property>
                                </bean>
                                <bean id="group2"  class="org.fisco.bcos.channel.handler.ChannelConnections">
                                        <property name="groupId" value="2" /> <!-- 群组的groupID -->
                                        <property name="connectionsStr">
                                                <list>
                                                        <value>127.0.0.1:20202</value> 
                                                        <value>127.0.0.1:20203</value> 
                                                </list>
                                        </property>
                                </bean>
                        </list>
                </property>
        </bean>

        <bean id="channelService" class="org.fisco.bcos.channel.client.Service" depends-on="groupChannelConnectionsConfig">
                <property name="groupId" value="1" /> <!-- 配置连接群组1 -->
                <property name="agencyName" value="fisco" /> <!-- 配置机构名 -->
                <property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
        </bean>

</beans>

applicationContext.xml配置项详细说明:

  • encryptType: 国密开关(默认为0,关闭)
    • 0: 不使用国密
    • 1: 使用国密
      • 开启国密功能,需要连接的区块链节点是国密节点,搭建国密版FISCO BCOS区块链参考这里)
      • 使用国密SSL,在国密区块链环境基础上,SDK需要打开encryptType开关,然后配置国密SSL证书
  • groupChannelConnectionsConfig:
    • 配置待连接的群组,可以配置一个或多个群组,每个群组需要配置群组ID
    • 每个群组可以配置一个或多个节点,设置群组节点的配置文件config.ini[rpc]部分的channel_listen_ip(若节点小于v2.3.0版本,查看配置项listen_ip)和channel_listen_port
    • SSL配置项: SDK与节点SSL连接时使用
      • caCertSL连接根证书路径
      • sslCertSDK证书路径
      • sslKeySDK证书私钥路径
    • 国密SSL配置项: SDK与节点国密SSL连接时使用
      • gmCaCert国密SSL连接根证书路径
      • gmEnSslCert国密SSL连接加密证书路径
      • gmEnSslKey国密SSL连接加密证书私钥路径
      • gmSslCertSSL连接签名证书路径
      • gmSslKeySSL连接签名证书私钥路径
  • channelService: 通过指定群组ID配置SDK实际连接的群组,指定的群组ID是groupChannelConnectionsConfig配置中的群组ID。SDK会与群组中配置的节点均建立连接,然后随机选择一个节点发送请求。

备注:刚下载项目时,有些插件可能没有安装,代码会报错。当你第一次在IDEA上使用lombok这个工具包时,请按以下步骤操作:

  • 进入setting->Plugins->Marketplace->选择安装Lombok plugin
  • 进入设置Setting-> Compiler -> Annotation Processors -> 勾选Enable annotation processing。
Spring Boot项目配置

提供Spring Boot项目中关于application.yml的配置如下所示。

encrypt-type: # 0:普通, 1:国密
  encrypt-type: 0

group-channel-connections-config:
  caCert: ca.crt
  sslCert: sdk.crt
  sslKey: sdk.key
  all-channel-connections:
    - group-id: 1 #group ID
      connections-str:
        # 若节点小于v2.3.0版本,查看配置项listen_ip:channel_listen_port
        - 127.0.0.1:20200 # node channel_listen_ip:channel_listen_port
        - 127.0.0.1:20201
    - group-id: 2
      connections-str:
        # 若节点小于v2.3.0版本,查看配置项listen_ip:channel_listen_port
        - 127.0.0.1:20202 # node channel_listen_ip:channel_listen_port
        - 127.0.0.1:20203
channel-service:
  group-id: 1 # sdk实际连接的群组
  agency-name: fisco # 机构名称

application.yml配置项与applicationContext.xml配置项相对应,详细介绍参考applicationContext.xml配置说明。

使用SDK

Spring项目开发指引
调用SDK的API(参考Web3SDK API列表设置或查询相关的区块链数据。
调用SDK Web3j的API

加载配置文件,SDK与区块链节点建立连接,获取web3j对象,根据Web3j对象调用相关API。示例代码如下:

    //读取配置文件,SDK与区块链节点建立连接
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Service service = context.getBean(Service.class);
    service.run(); 
    ChannelEthereumService channelEthereumService = new ChannelEthereumService();
    channelEthereumService.setChannelService(service);

    //获取Web3j对象
    Web3j web3j = Web3j.build(channelEthereumService, service.getGroupId());
    //通过Web3j对象调用API接口getBlockNumber
    BigInteger blockNumber = web3j.getBlockNumber().send().getBlockNumber();
    System.out.println(blockNumber);

注: SDK处理交易超时时间默认为60秒,即60秒内没有收到交易响应,判断为超时。该值可以通过ChannelEthereumService进行设置,示例如下:

// 设置交易超时时间为100000毫秒,即100秒
channelEthereumService.setTimeout(100000);
调用SDK Precompiled的API

加载配置文件,SDK与区块链节点建立连接。获取SDK Precompiled Service对象,调用相关的API。示例代码如下:

    //读取配置文件,SDK与区块链节点建立连接,获取Web3j对象
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Service service = context.getBean(Service.class);
    service.run(); 
    ChannelEthereumService channelEthereumService = new ChannelEthereumService();
    channelEthereumService.setChannelService(service);
    Web3j web3j = Web3j.build(channelEthereumService, service.getGroupId());
    String privateKey = "b83261efa42895c38c6c2364ca878f43e77f3cddbc922bf57d0d48070f79feb6"; 
    //指定外部账户私钥,用于交易签名
    Credentials credentials = GenCredential.create(privateKey); 
    //获取SystemConfigService对象
    SystemConfigService systemConfigService = new SystemConfigService(web3j, credentials);
    //通过SystemConfigService对象调用API接口setValueByKey
    String result = systemConfigService.setValueByKey("tx_count_limit", "2000");
    //通过Web3j对象调用API接口getSystemConfigByKey
    String value = web3j.getSystemConfigByKey("tx_count_limit").send().getSystemConfigByKey();
    System.out.println(value);
创建并使用指定外部账户

SDK发送交易需要一个外部账户,下面是随机创建一个外部账户的方法。

//创建普通外部账户
EncryptType.encryptType = 0;
//创建国密外部账户,向国密区块链节点发送交易需要使用国密外部账户
// EncryptType.encryptType = 1; 
Credentials credentials = GenCredential.create();
//账户地址
String address = credentials.getAddress();
//账户私钥 
String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
//账户公钥 
String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);

使用指定的外部账户

//通过指定外部账户私钥使用指定的外部账户
Credentials credentials = GenCredential.create(privateKey);
加载账户私钥文件

如果通过账户生成脚本get_accounts.sh生成了PEM或PKCS12格式的账户私钥文件(账户生成脚本的用法参考账户管理文档),则可以通过加载PEM或PKCS12账户私钥文件使用账户。加载私钥有两个类:P12Manager和PEMManager,其中,P12Manager用于加载PKCS12格式的私钥文件,PEMManager用于加载PEM格式的私钥文件。

  • P12Manager用法举例: 在applicationContext.xml中配置PKCS12账户的私钥文件路径和密码
<bean id="p12" class="org.fisco.bcos.channel.client.P12Manager" init-method="load" >
	<property name="password" value="123456" />
	<property name="p12File" value="classpath:0x0fc3c4bb89bd90299db4c62be0174c4966286c00.p12" />
</bean>

开发代码

//加载Bean
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
P12Manager p12 = context.getBean(P12Manager.class);
//提供密码获取ECKeyPair,密码在生产p12账户文件时指定
ECKeyPair p12KeyPair = p12.getECKeyPair(p12.getPassword());
			
//以十六进制串输出私钥和公钥
System.out.println("p12 privateKey: " + p12KeyPair.getPrivateKey().toString(16));
System.out.println("p12 publicKey: " + p12KeyPair.getPublicKey().toString(16));

//生成Web3SDK使用的Credentials
Credentials credentials = GenCredential.create(p12KeyPair.getPrivateKey().toString(16));
System.out.println("p12 Address: " + credentials.getAddress());
  • PEMManager使用举例

在applicationContext.xml中配置PEM账户的私钥文件路径

<bean id="pem" class="org.fisco.bcos.channel.client.PEMManager" init-method="load" >
	<property name="pemFile" value="classpath:0x0fc3c4bb89bd90299db4c62be0174c4966286c00.pem" />
</bean>

使用代码加载

//加载Bean
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-keystore-sample.xml");
PEMManager pem = context.getBean(PEMManager.class);
ECKeyPair pemKeyPair = pem.getECKeyPair();

//以十六进制串输出私钥和公钥
System.out.println("PEM privateKey: " + pemKeyPair.getPrivateKey().toString(16));
System.out.println("PEM publicKey: " + pemKeyPair.getPublicKey().toString(16));

//生成Web3SDK使用的Credentials
Credentials credentialsPEM = GenCredential.create(pemKeyPair.getPrivateKey().toString(16));
System.out.println("PEM Address: " + credentialsPEM.getAddress());
通过SDK部署并调用合约
准备Java合约文件

控制台提供一个专门的编译合约工具,方便开发者将Solidity合约文件编译为Java合约文件,具体使用方式参考这里

部署并调用合约

SDK的核心功能是部署/加载合约,然后调用合约相关接口,实现相关业务功能。部署合约调用Java合约类的deploy方法,获取合约对象。通过合约对象可以调用getContractAddress方法获取部署合约的地址以及调用该合约的其他方法实现业务功能。如果合约已部署,则通过部署的合约地址可以调用load方法加载合约对象,然后调用该合约的相关方法。

    //读取配置文件,SDK与区块链节点建立连接,获取web3j对象
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Service service = context.getBean(Service.class);
    service.run(); 
    ChannelEthereumService channelEthereumService = new ChannelEthereumService();
    channelEthereumService.setChannelService(service);
    channelEthereumService.setTimeout(10000);
    Web3j web3j = Web3j.build(channelEthereumService, service.getGroupId());
    //准备部署和调用合约的参数
    BigInteger gasPrice = new BigInteger("300000000");
    BigInteger gasLimit = new BigInteger("300000000");
    String privateKey = "b83261efa42895c38c6c2364ca878f43e77f3cddbc922bf57d0d48070f79feb6"; 
    //指定外部账户私钥,用于交易签名
    Credentials credentials = GenCredential.create(privateKey); 
    //部署合约 
    YourSmartContract contract = YourSmartContract.deploy(web3j, credentials, new StaticGasProvider(gasPrice, gasLimit)).send();
    //根据合约地址加载合约
    //YourSmartContract contract = YourSmartContract.load(address, web3j, credentials, new StaticGasProvider(gasPrice, gasLimit)); 
    //调用合约方法发送交易
    TransactionReceipt transactionReceipt = contract.someMethod(<param1>, ...).send(); 
    //查询合约方法查询该合约的数据状态
    Type result = contract.someMethod(<param1>, ...).send(); 
Spring Boot项目开发指引

提供spring-boot-starter示例项目供参考。Spring Boot项目开发与Spring项目开发类似,其主要区别在于配置文件方式的差异。该示例项目提供相关的测试案例,具体描述参考示例项目的README文档。

SDK国密功能使用
  • 前置条件:FISCO BCOS区块链采用国密算法,搭建国密版的FISCO BCOS区块链请参考国密使用手册
  • 启用国密功能:applicationContext.xml/application.yml配置文件中将encryptType属性设置为1。
  • 加载私钥使用GenCredential类(适用于国密和非国密),Credential类只适用于加载非国密私钥。

国密版SDK调用API的方式与普通版SDK调用API的方式相同,其差异在于国密版SDK需要生成国密版的Java合约文件。编译国密版的Java合约文件参考这里

Web3SDK API

Web3SDK API主要分为Web3j API和Precompiled Service API。其中Web3j API可以查询区块链相关的状态,发送和查询交易信息;Precompiled Service API可以管理区块链相关配置以及实现特定功能。

Web3j API

Web3j API是由web3j对象调用的FISCO BCOS的RPC API,其API名称与RPC API相同,参考RPC API文档

Precompiled Service API

预编译合约是FISCO BCOS底层通过C++实现的一种高效智能合约。SDK已提供预编译合约对应的Java接口,控制台通过调用这些Java接口实现了相关的操作命令,体验控制台,参考控制台手册。SDK提供Precompiled对应的Service类,分别是分布式控制权限相关的PermissionService,CNS相关的CnsService,系统属性配置相关的SystemConfigService和节点类型配置相关ConsensusService。相关错误码请参考:Precompiled Service API 错误码

PermissionService

SDK提供对分布式控制权限的支持,PermissionService可以配置权限信息,其API如下:

  • public String grantUserTableManager(String tableName, String address): 根据用户表名和外部账户地址设置权限信息。
  • public String revokeUserTableManager(String tableName, String address): 根据用户表名和外部账户地址去除权限信息。
  • public List<PermissionInfo> listUserTableManager(String tableName): 根据用户表名查询设置的权限记录列表(每条记录包含外部账户地址和生效块高)。
  • public String grantDeployAndCreateManager(String address): 增加外部账户地址的部署合约和创建用户表权限。
  • public String revokeDeployAndCreateManager(String address): 移除外部账户地址的部署合约和创建用户表权限。
  • public List<PermissionInfo> listDeployAndCreateManager(): 查询拥有部署合约和创建用户表权限的权限记录列表。
  • public String grantPermissionManager(String address): 增加外部账户地址的管理权限的权限。
  • public String revokePermissionManager(String address): 移除外部账户地址的管理权限的权限。
  • public List<PermissionInfo> listPermissionManager(): 查询拥有管理权限的权限记录列表。
  • public String grantNodeManager(String address): 增加外部账户地址的节点管理权限。
  • public String revokeNodeManager(String address): 移除外部账户地址的节点管理权限。
  • public List<PermissionInfo> listNodeManager(): 查询拥有节点管理的权限记录列表。
  • public String grantCNSManager(String address): 增加外部账户地址的使用CNS权限。
  • public String revokeCNSManager(String address): 移除外部账户地址的使用CNS权限。
  • public List<PermissionInfo> listCNSManager(): 查询拥有使用CNS的权限记录列表。
  • public String grantSysConfigManager(String address): 增加外部账户地址的系统参数管理权限。
  • public String revokeSysConfigManager(String address): 移除外部账户地址的系统参数管理权限。
  • public List<PermissionInfo> listSysConfigManager(): 查询拥有系统参数管理的权限记录列表。
CnsService

SDK提供对CNS的支持。CnsService可以配置CNS信息,其API如下:

  • String registerCns(String name, String version, String address, String abi): 根据合约名、合约版本号、合约地址和合约abi注册CNS信息。
  • String getAddressByContractNameAndVersion(String contractNameAndVersion): 根据合约名和合约版本号(合约名和合约版本号用英文冒号连接)查询合约地址。若缺失合约版本号,默认使用合约最新版本。
  • List<CnsInfo> queryCnsByName(String name): 根据合约名查询CNS信息。
  • List<CnsInfo> queryCnsByNameAndVersion(String name, String version): 根据合约名和合约版本号查询CNS信息。
SystemConfigService

SDK提供对系统配置的支持。SystemConfigService可以配置系统属性值(目前支持tx_count_limit和tx_gas_limit属性的设置),其API如下:

  • String setValueByKey(String key, String value): 根据键设置对应的值(查询键对应的值,参考Web3j API中的getSystemConfigByKey接口)。
ConsensusService

SDK提供对节点类型配置的支持。ConsensusService可以设置节点类型,其API如下:

  • String addSealer(String nodeId): 根据节点NodeID设置对应节点为共识节点。
  • String addObserver(String nodeId): 根据节点NodeID设置对应节点为观察节点。
  • String removeNode(String nodeId): 根据节点NodeID设置对应节点为游离节点。
CRUDService

SDK提供对CRUD(增删改查)操作的支持。CRUDService可以创建表,对表进行增删改查操作,其API如下:

  • int createTable(Table table): 创建表,提供表对象。表对象需要设置其表名,主键字段名和其他字段名。其中,其他字段名是以英文逗号分隔拼接的字符串。返回创建表的状态值,返回为0则代表创建成功。
  • int insert(Table table, Entry entry): 插入记录,提供表对象和Entry对象。表对象需要设置表名和主键值;Entry是map对象,提供插入的字段名和字段值。返回插入的记录数。
  • int update(Table table, Entry entry, Condition condition): 更新记录,提供表对象,Entry对象和Condtion对象。表对象需要设置表名和主键值;Entry是map对象,提供更新的字段名和字段值;Condition对象是条件对象,可以设置更新的匹配条件。返回更新的记录数。
  • List<Map<String, String>> select(Table table, Condition condition): 查询记录,提供表对象和Condtion对象。表对象需要设置表名和主键值;Condition对象是条件对象,可以设置查询的匹配条件。返回查询的记录。
  • int remove(Table table, Condition condition): 移除记录,提供表对象和Condtion对象。表对象需要设置表名和主键值;Condition对象是条件对象,可以设置移除的匹配条件。返回移除的记录数。
  • Table desc(String tableName): 根据表名查询表的信息,主要包含表的主键和其他属性字段。返回表类型,主要包含表的主键字段名和其他属性字段名。

交易解析

FISCO BCOS的交易是一段发往区块链系统的请求数据,用于部署合约,调用合约接口,维护合约的生命周期以及管理资产,进行价值交换等。当交易确认后会产生交易回执,交易回执交易均保存在区块里,用于记录交易执行过程生成的信息,如结果码、日志、消耗的gas量等。用户可以使用交易哈希查询交易回执,判定交易是否完成。

交易回执包含三个关键字段,分别是input, output , logs:

字段 类型 描述
input String 交易输入的ABI编码十六进制字符串
output String 交易返回的ABI编码十六进制字符串
logs List event log列表,保存交易的event信息

交易解析功能帮助用户解析这三个字段为json数据和java对象。

接口说明

代码包路径org.fisco.bcos.web3j.tx.txdecode,使用TransactionDecoderFactory工厂类建立交易解析对象TransactionDecoder,有两种方式:

  1. TransactionDecoder buildTransactionDecoder(String abi, String bin);

    abi:合约的ABI

    bin:合约bin,暂无使用,可以直接传入空字符串””

  2. TransactionDecoder buildTransactionDecoder(String contractName);

    contractName:合约名称,在应用的根目录下创建solidity目录,将交易相关的合约放在solidity目录,通过指定合约名获取交易解析对象

交易解析对象TransactionDecoder接口列表:

  1. String decodeInputReturnJson(String input)

    解析input,将结果封装为json字符串,json格式

    {"data":[{"name":"","type":"","data":} ... ],"function":"","methodID":""}
    

    function : 函数签名字符串

    methodID : 函数选择器

  2. InputAndOutputResult decodeInputReturnObject(String input)

    解析input,返回Object对象,InputAndOutputResult结构:

    public class InputAndOutputResult {
       private String function; // 函数签名
       private String methodID; // methodID
       private List<ResultEntity> result; // 返回列表
     }
    
    public class ResultEntity {
       private String name;  // 字段名称, 解析output返回时,值为空字符串
       private String type;  // 字段类型
       private Object data;  // 字段值
     }
    
  3. String decodeOutputReturnJson(String input, String output)

    解析output,将结果封装为json字符串,格式同decodeInputReturnJson

  4. InputAndOutputResult decodeOutputReturnObject(String input, String output)

    解析output,返回java Object对象

  5. String decodeEventReturnJson(List<Log> logList)

    解析event列表,将结果封装为json字符串,json格式

    {"event1签名":[[{"name":"","type":"","data":}...]...],"event2签名":[[{"name":"","type":"","data":}...]...]...}
    
  6. Map<String, List<List<ResultEntity>>> decodeEventReturnObject(List<Log> logList)

    解析event列表,返回java Map对象,key为event签名字符串,List<ResultEntity>为交易中单个event参数列表,List<List<ResultEntity>>表示单个交易可以包含多个event

TransactionDecoder对input,output和event logs均分别提供返回json字符串和java对象的方法。json字符串方便客户端处理数据,java对象方便服务端处理数据。

示例

TxDecodeSample合约为例说明接口的使用:

pragma solidity ^0.4.24;
contract TxDecodeSample
{
    event Event1(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs);
    event Event2(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes  _bs);
    
    function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) public constant returns (uint256,int256,bool,address,bytes32,string,bytes)
    {
      Event1(_u, _i, _b, _addr, _bs32, _s, _bs);
      return (_u, _i, _b, _addr, _bs32, _s, _bs);
    }
    
    function do_event(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) public 
    {
      Event1(_u, _i, _b, _addr, _bs32, _s, _bs);
      Event2(_u, _i, _b, _addr, _bs32, _s, _bs);
    }
}

使用buildTransactionDecoder 创建TxDecodeSample合约的解析对象:

// TxDecodeSample合约ABI
String abi = "[{\"constant\":false,\"inputs\":[{\"name\":\"_u\",\"type\":\"uint256\"},{\"name\":\"_i\",\"type\":\"int256\"},{\"name\":\"_b\",\"type\":\"bool\"},{\"name\":\"_addr\",\"type\":\"address\"},{\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"name\":\"_s\",\"type\":\"string\"},{\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"do_event\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_u\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_i\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"_b\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_addr\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"_s\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"Event1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_u\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_i\",\"type\":\"int256\"},{\"indexed\":false,\"name\":\"_b\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"_addr\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"_s\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"Event2\",\"type\":\"event\"},{\"constant\":true,\"inputs\":[{\"name\":\"_u\",\"type\":\"uint256\"},{\"name\":\"_i\",\"type\":\"int256\"},{\"name\":\"_b\",\"type\":\"bool\"},{\"name\":\"_addr\",\"type\":\"address\"},{\"name\":\"_bs32\",\"type\":\"bytes32\"},{\"name\":\"_s\",\"type\":\"string\"},{\"name\":\"_bs\",\"type\":\"bytes\"}],\"name\":\"echo\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"int256\"},{\"name\":\"\",\"type\":\"bool\"},{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"bytes\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]";
String bin = "";
TransactionDecoder txDecodeSampleDecoder = TransactionDecoderFactory.buildTransactionDecoder(abi, bin);
解析input

调用function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji FISCO-BCOS nice ]

// function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 
String input = "0x406d373b000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000";
String jsonResult = txDecodeSampleDecoder.decodeInputReturnJson(input);
InputAndOutputResult objectResult = txDecodeSampleDecoder.decodeInputReturnObject(input);
System.out.println("json => \n" + jsonResult);
System.out.println("object => \n" + objectResult);

输出:

json => 
{
  "function": "echo(uint256,int256,bool,address,bytes32,string,bytes)",
  "methodID": "0x406d373b",
  "result": [
    {
      "name": "_u",
      "type": "uint256",
      "data": 111111
    },
    {
      "name": "_i",
      "type": "int256",
      "data": -1111111
    },
    {
      "name": "_b",
      "type": "bool",
      "data": false
    },
    {
      "name": "_addr",
      "type": "address",
      "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a"
    },
    {
      "name": "_bs32",
      "type": "bytes32",
      "data": "abcdefghiabcdefghiabcdefghiabhji"
    },
    {
      "name": "_s",
      "type": "string",
      "data": "FISCO-BCOS"
    },
    {
      "name": "_bs",
      "type": "bytes",
      "data": "nice"
    }
  ]
}

object => 
InputAndOutputResult[
  function=echo(uint256,
  int256,
  bool,
  address,
  bytes32,
  string,
  bytes),
  methodID=0x406d373b,
  result=[
    ResultEntity[
      name=_u,
      type=uint256,
      data=111111
    ],
    ResultEntity[
      name=_i,
      type=int256,
      data=-1111111
    ],
    ResultEntity[
      name=_b,
      type=bool,
      data=false
    ],
    ResultEntity[
      name=_addr,
      type=address,
      data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
    ],
    ResultEntity[
      name=_bs32,
      type=bytes32,
      data=abcdefghiabcdefghiabcdefghiabhji
    ],
    ResultEntity[
      name=_s,
      type=string,
      data=FISCO-BCOS
    ],
    ResultEntity[
      name=_bs,
      type=bytes,
      data=nice
    ]
  ]
]
解析output

调用function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji FISCO-BCOS nice ],echo接口直接将输入返回,因此返回与输入相同

//  function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs)  public constant returns (uint256,int256,bool,address,bytes32,string,bytes)
String input = “0x406d373b000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000”;

String output = "“0x000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000";

String jsonResult = txDecodeSampleDecoder.decodeOutputReturnJson(input, output);
InputAndOutputResult objectResult = txDecodeSampleDecoder.decodeOutputReturnObject(input, output);
System.out.println("json => \n" + jsonResult);
System.out.println("object => \n" + objectResult);

结果:

json => 
{
  "function": "echo(uint256,int256,bool,address,bytes32,string,bytes)",
  "methodID": "0x406d373b",
  "result": [
    {
      "name": "",
      "type": "uint256",
      "data": 111111
    },
    {
      "name": "",
      "type": "int256",
      "data": -1111111
    },
    {
      "name": "",
      "type": "bool",
      "data": false
    },
    {
      "name": "",
      "type": "address",
      "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a"
    },
    {
      "name": "",
      "type": "bytes32",
      "data": "abcdefghiabcdefghiabcdefghiabhji"
    },
    {
      "name": "",
      "type": "string",
      "data": "FISCO-BCOS"
    },
    {
      "name": "",
      "type": "bytes",
      "data": "nice"
    }
  ]
}

object => 
InputAndOutputResult[
  function=echo(uint256,
  int256,
  bool,
  address,
  bytes32,
  string,
  bytes),
  methodID=0x406d373b,
  result=[
    ResultEntity[
      name=,
      type=uint256,
      data=111111
    ],
    ResultEntity[
      name=,
      type=int256,
      data=-1111111
    ],
    ResultEntity[
      name=,
      type=bool,
      data=false
    ],
    ResultEntity[
      name=,
      type=address,
      data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
    ],
    ResultEntity[
      name=,
      type=bytes32,
      data=abcdefghiabcdefghiabcdefghiabhji
    ],
    ResultEntity[
      name=,
      type=string,
      data=FISCO-BCOS
    ],
    ResultEntity[
      name=,
      type=bytes,
      data=nice
    ]
  ]
]
解析event logs

调用function do_event(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs) 接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji FISCO-BCOS nice ],解析交易中的logs

// transactionReceipt为调用do_event接口的交易回执
String jsonResult = txDecodeSampleDecoder.decodeEventReturnJson(transactionReceipt.getLogs());
String mapResult = txDecodeSampleDecoder.decodeEventReturnJson(transactionReceipt.getLogs());

System.out.println("json => \n" + jsonResult);
System.out.println("map => \n" + mapResult);

结果:

json => 
{
  "Event1(uint256,int256,bool,address,bytes32,string,bytes)": [
    [
      {
        "name": "_u",
        "type": "uint256",
        "data": 111111,
        "indexed": false
      },
      {
        "name": "_i",
        "type": "int256",
        "data": -1111111,
        "indexed": false
      },
      {
        "name": "_b",
        "type": "bool",
        "data": false,
        "indexed": false
      },
      {
        "name": "_addr",
        "type": "address",
        "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
        "indexed": false
      },
      {
        "name": "_bs32",
        "type": "bytes32",
        "data": "abcdefghiabcdefghiabcdefghiabhji",
        "indexed": false
      },
      {
        "name": "_s",
        "type": "string",
        "data": "Fisco Bcos",
        "indexed": false
      },
      {
        "name": "_bs",
        "type": "bytes",
        "data": "sadfljkjkljkl",
        "indexed": false
      }
    ]
  ],
  "Event2(uint256,int256,bool,address,bytes32,string,bytes)": [
    [
      {
        "name": "_u",
        "type": "uint256",
        "data": 111111,
        "indexed": false
      },
      {
        "name": "_i",
        "type": "int256",
        "data": -1111111,
        "indexed": false
      },
      {
        "name": "_b",
        "type": "bool",
        "data": false,
        "indexed": false
      },
      {
        "name": "_addr",
        "type": "address",
        "data": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
        "indexed": false
      },
      {
        "name": "_bs32",
        "type": "bytes32",
        "data": "abcdefghiabcdefghiabcdefghiabhji",
        "indexed": false
      },
      {
        "name": "_s",
        "type": "string",
        "data": "FISCO-BCOS",
        "indexed": false
      },
      {
        "name": "_bs",
        "type": "bytes",
        "data": "nice",
        "indexed": false
      }
    ]
  ]
}

map => 
{
  Event1(uint256,
  int256,
  bool,
  address,
  bytes32,
  string,
  bytes)=[
    [
      ResultEntity[
        name=_u,
        type=uint256,
        data=111111
      ],
      ResultEntity[
        name=_i,
        type=int256,
        data=-1111111
      ],
      ResultEntity[
        name=_b,
        type=bool,
        data=false
      ],
      ResultEntity[
        name=_addr,
        type=address,
        data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
      ],
      ResultEntity[
        name=_bs32,
        type=bytes32,
        data=abcdefghiabcdefghiabcdefghiabhji
      ],
      ResultEntity[
        name=_s,
        type=string,
        data=FISCO-BCOS
      ],
      ResultEntity[
        name=_bs,
        type=bytes,
        data=nice
      ]
    ]
  ],
  Event2(uint256,
  int256,
  bool,
  address,
  bytes32,
  string,
  bytes)=[
    [
      ResultEntity[
        name=_u,
        type=uint256,
        data=111111
      ],
      ResultEntity[
        name=_i,
        type=int256,
        data=-1111111
      ],
      ResultEntity[
        name=_b,
        type=bool,
        data=false
      ],
      ResultEntity[
        name=_addr,
        type=address,
        data=0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
      ],
      ResultEntity[
        name=_bs32,
        type=bytes32,
        data=abcdefghiabcdefghiabcdefghiabhji
      ],
      ResultEntity[
        name=_s,
        type=string,
        data=FISCO-BCOS
      ],
      ResultEntity[
        name=_bs,
        type=bytes,
        data=nices
      ]
    ]
  ]
}

合约事件推送

功能简介

合约事件推送功能提供了合约事件的异步推送机制,客户端向节点发送注册请求,在请求中携带客户端关注的合约事件的参数,节点根据请求参数对请求区块范围的Event Log进行过滤,将结果分次推送给客户端。

交互协议

客户端与节点的交互基于Channel协议。交互分为三个阶段:注册请求,节点回复,Event Log数据推送。

注册请求

客户端向节点发送Event推送的注册请求:

// request sample:
{
  "fromBlock": "latest",
  "toBlock": "latest",
  "addresses": [
    "0xca5ed56862869c25da0bdf186e634aac6c6361ee"
  ],
  "topics": [
    "0x91c95f04198617c60eaf2180fbca88fc192db379657df0e412a9f7dd4ebbe95d"
  ],
  "groupID": "1",
  "filterID": "bb31e4ec086c48e18f21cb994e2e5967"
}
  • filerID:字符串类型,每次请求唯一,标记一次注册任务
  • groupID:字符串类型,群组ID
  • fromBlock:整形字符串,初始区块。“latest” 当前块高
  • toBlock:整形字符串,最终区块。“latest” 处理至当前块高时,继续等待新区块
  • addresses:字符串或者字符串数组:字符串表示单个合约地址,数组为多个合约地址,数组可以为空
  • topics:字符串类型或者数组类型:字符串表示单个topic,数组为多个topic,数组可以为空
节点回复

节点接受客户端注册请求时,会对请求参数进行校验,将是否成功接受该注册请求结果回复给客户端。

// response sample:
{
  "filterID": "bb31e4ec086c48e18f21cb994e2e5967",
  "result": 0
}
  • filterID:字符串类型,每次请求唯一,标记一次注册任务
  • result:整形,返回结果。0成功,其余为失败状态码
Event Log数据推送

节点验证客户端注册请求成功之后,根据客户端请求参数条件,向客户端推送EventLog数据。

// event log push sample:
{
  "filterID": "bb31e4ec086c48e18f21cb994e2e5967",
  "result": 0,
  "logs": [
    
  ]
}
  • filterID:字符串类型,每次请求唯一,标记一次注册任务
  • result:整形 0:Event Log数据推送 1:推送完成。客户端一次注册请求对应节点的数据推送会有多次(请求区块范围比较大或者等待新的区块),result字段为1时说明节点推送已经结束
  • logs:Log对象数组,result为0时有效
Java SDK教程
注册接口

Java SDK中org.fisco.bcos.channel.client.Service类提供合约事件的注册接口,用户可以调用registerEventLogFilter向节点发送注册请求,并设置回调函数。

  public void registerEventLogFilter(EventLogUserParams params, EventLogPushCallback callback);
params注册参数

事件回调请求注册的参数:

public class EventLogUserParams {
    private String fromBlock;   
    private String toBlock;
    private List<String> addresses;
    private List<Object> topics;
}
callback回调对象
public abstract class EventLogPushCallback {
    public void onPushEventLog(int status, List<LogResult> logs);
}
  • status 回调返回状态:
    0       : 正常推送,此时logs为节点推送的Event日志
    1       : 推送完成,执行区间的区块都已经处理
    -41000  : 参数无效,客户端验证参数错误返回
    -41001  : 参数错误,节点验证参数错误返回
    -41002  : 群组不存在
    -41003  : 请求错误的区块区间
    -41004  : 节点推送数据格式错误
    -41005  : 请求发送超时
    -41006  : 其他错误
  • logs表示回调的Event Log对象列表,status为0有效
  public class LogResult {
    private List<EventResultEntity> logParams;
    private Log log;
  }

  // Log对象
  public class Log {
    private String logIndex;
    private String transactionIndex;
    private String transactionHash;
    private String blockHash;
    private String blockNumber;
    private String address;
    private String data;
    private String type;
    private List<String> topics;
  }

Log log:Log对象

List<EventResultEntity> logParams:默认值null,可以在子类中解析Log的data字段,将结果保存入logParams [参考交易解析]

  • 实现回调对象

Java SDK默认实现的回调类ServiceEventLogPushCallback,将statuslogs在日志中打印,用户可以通过继承ServiceEventLogPushCallback类,重写onPushEventLog接口,实现自己的回调逻辑处理。

class MyEventLogPushCallBack extends ServiceEventLogPushCallback {
    @Override
    public void onPushEventLog(int status, List<LogResult> logs) {
        // ADD CODE
    }
}

注意:onPushEventLog接口多次回调的logs有重复的可能性,可以根据Log对象中的blockNumber,transactionIndex,logIndex进行去重

topic工具

org.fisco.bcos.channel.event.filter.TopicTools提供将各种类型参数转换为对应topic的工具,用户设置EventLogUserParamstopics参数可以使用。

 class TopicTools {
    // int1/uint1~uint1/uint256 
    public static String integerToTopic(BigInteger i)
    // bool
    public static String boolToTopic(boolean b)
    // address
    public static String addressToTopic(String s)
    // string
    public static String stringToTopic(String s)
    // bytes
    public static String bytesToTopic(byte[] b)
    // byte1~byte32
    public static String byteNToTopic(byte[] b)
}
Solidity To Java

为了简化使用,solidity合约生成对应的Java合约代码时,为每个Event生成两个重载的同名接口,接口命名规则: register + Event名称 + EventLogFilter

这里以Asset合约的TransferEvent为例说明

contract Asset {
    event TransferEvent(int256 ret, string indexed from_account, string indexed to_account, uint256 indexed amount)

    function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
        // 结果
        int result = 0;

        // 其他逻辑,省略

        // TransferEvent 保存结果以及接口参数
        TransferEvent(result, from_account, to_account, amount);
    }
}

Asset.sol生成对应Java合约文件[将solidity合约生成对应的Java调用文件]

class Asset {
    // 其他生成代码 省略

    public void registerTransferEventEventLogFilter(EventLogPushWithDecodeCallback callback);
    public void registerTransferEventEventLogFilter(String fromBlock, String toBlock, List<String> otherTopics, EventLogPushWithDecodeCallback callback);
}
registerTransferEventEventLogFilter

这两个接口对org.fisco.bcos.channel.client.Service.registerEventLogFilter进行了封装,调用等价于将registerEventLogFilterparams参数设置为:

    EventLogUserParams params = new EventLogUserParams();
    // fromBlock, 无参数设置为“latest”
    params.setFromBlock(fromBlock); // params.setFromBlock("latest");
    // toBlock, 无参数设置为“latest”
    params.setToBlock(toBlock); // params.setToBlock("latest");

    // addresses,设置为Java合约对象的地址
    // 当前java合约对象为:Asset asset 
    ArrayList<String> addresses = new ArrayList<String>();
    addresses.add(asset.getContractedAddress());
    params.setAddresses(addresses);

    // topics, topic0设置为Event接口对应的topic
    ArrayList<Object> topics = new ArrayList<>();
    topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
    // 其他topic设置, 没有则忽略
    topics.addAll(otherTopics);

可以看出,在关注指定地址特定合约的某个Event,使用生成的Java合约对象中的接口,更加简单方便。

EventLogPushWithDecodeCallback

EventLogPushWithDecodeCallbackServiceEventLogPushCallback相同,是EventLogPushCallback的子类,区别在于:

  • ServiceEventLogPushCallback回调接口onPushEventLog(int status, List<LogResult> logs) LogResult成员logParams为空,用户需要使用Log数据时需要解析数据
  • EventLogPushWithDecodeCallback作为Asset对象的成员,可以根据其保存的ABI成员构造对应Event的解析工具,解析返回的Log数据,解析结果保存在logParams中。
示例

这里以Asset合约为例,给出合约事件推送的一些场景供用户参考。

  • 场景1:将链上所有/最新的Event回调至客户端
        // 其他初始化逻辑,省略
        
        // 参数设置
        EventLogUserParams params = new EventLogUserParams();

        // 全部Event fromBlock设置为"1" 
        params.setFromBlock("1");

        // 最新Event fromBlock设置为"latest"
        // params.setFromBlock("latest");

        // toBlock设置为"latest",处理至最新区块继续等待新的区块
        params.setToBlock("latest");

        // addresses设置为空数组,匹配所有的合约地址
        params.setAddresses(new ArrayList<String>());

        // topics设置为空数组,匹配所有的Event
        params.setTopics(new ArrayList<Object>());

        // 回调,用户可以替换为自己实现的类的回调对象
        ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
        service.registerEventLogFilter(params, callback);
  • 场景2: 将Asset合约最新的TransferEvent事件回调至客户端
        // 其他初始化逻辑,省略
        
        // 设置参数
        EventLogUserParams params = new EventLogUserParams();

        // 从最新区块开始,fromBlock设置为"latest"
        params.setFromBlock("latest");
        // toBlock设置为"latest",处理至最新区块继续等待新的区块
        params.setToBlock("latest");

        // addresses设置为空数组,匹配所有的合约地址
        params.setAddresses(new ArrayList<String>());
        
        // topic0,TransferEvent(int256,string,string,uint256)
        ArrayList<Object> topics = new ArrayList<>();
        topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
        params.setTopics(topics);

        // 回调,用户可以替换为自己实现的类的回调对象
        ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
        service.registerEventLogFilter(params, callback);
  • 场景3: 将指定地址的Asset合约最新的TransferEvent事件回调至客户端

合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324";

方案1.

        // 其他初始化逻辑,省略

        String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
        
        // 设置参数
        EventLogUserParams params = new EventLogUserParams();

        // 从最新区块开始,fromBlock设置为"latest"
        params.setFromBlock("latest");
        // toBlock设置为"latest",处理至最新块并继续等待共识出块
        params.setToBlock("latest");

        // 合约地址
        ArrayList<String> addresses = new ArrayList<String>();
        addresses.add(addr);
        params.setAddresses(addresses);
        
        // topic0,匹配 TransferEvent(int256,string,string,uint256) 事件
        ArrayList<Object> topics = new ArrayList<>();
        topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,uint256)"));
        params.setTopics(topics);

        ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
        service.registerEventLogFilter(params, callback);

方案2.

        // 其他初始化逻辑,省略

        String addr = "0x06922a844c542df030a2a2be8f835892db99f324";

        // 构造Asset合约对象
        Asset asset = Asset.load(addr, ... );

        EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
        asset.registerTransferEventEventLogFilter(callback);
  • 场景4: 将指定地址的Asset合约所有TransferEvent事件回调至客户端

合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324";

方案1:

        // 其他初始化逻辑,省略
        
        // 设置参数
        EventLogUserParams params = new EventLogUserParams();

        // 从最初区块开始,fromBlock设置为"1"
        params.setFromBlock("1");
        // toBlock设置为"latest",处理至最新块并继续等待共识出块
        params.setToBlock("latest");

        // 设置合约地址
        ArrayList<String> addresses = new ArrayList<String>();
        addresses.add(addr);
        params.setAddresses(addresses);
        
        // TransferEvent(int256,string,string,uint256) 转换为topic
        ArrayList<Object> topics = new ArrayList<>();
        topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
        params.setTopics(topics);

        ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
        service.registerEventLogFilter(params, callback);

方案2.

        // 其他初始化逻辑,省略
        
        Asset asset = Asset.load(addr, ... );
        
        // 设置区块范围
        String fromBlock = "1";
        String toBlock = "latest";

        // 参数topic为空
        ArrayList<Object> otherTopics = new ArrayList<>();

        EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();

        asset.registerTransferEventEventLogFilter(fromBlock,toBlock,otherTopics,callback);
  • 场景5: 将Asset指定合约指定账户转账的所有事件回调至客户端

合约地址: String addr = "0x06922a844c542df030a2a2be8f835892db99f324"

转账账户: String fromAccount = "account"

方案1:

        // 其他初始化逻辑,省略

        String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
        String fromAccount = "account";
        
        // 参数
        EventLogUserParams params = new EventLogUserParams();

        // 从最初区块开始,fromBlock设置为"1"
        params.setFromBlock("1");
        // toBlock设置为"latest"
        params.setToBlock("latest");

        // 设置合约地址
        ArrayList<String> addresses = new ArrayList<String>();
        addresses.add(addr);
        params.setAddresses(addresses);
        
        // 设置topic
        ArrayList<Object> topics = new ArrayList<>();
        // TransferEvent(int256,string,string,uint256) 转换为topic
        topics.add(TopicTools.stringToTopic("TransferEvent(int256,string,string,uint256)"));
        // 转账账户 fromAccount转换为topic
        topics.add(TopicTools.stringToTopic(fromAccount));
        params.setTopics(topics);

        ServiceEventLogPushCallback callback = new ServiceEventLogPushCallback();
        service.registerEventLogFilter(params, callback);

方案2.

        // 其他初始化逻辑,省略

        String addr = "0x06922a844c542df030a2a2be8f835892db99f324";
        String fromAccount = "account";
        
        // 加载合约地址,生成Java合约对象
        Asset asset = Asset.load(addr, ... );

        // 回调函数
        EventLogPushWithDecodeCallback callback = new EventLogPushWithDecodeCallback();
        
        // 设置区块范围
        String fromBlock = "1";
        String toBlock = "latest";
        // 参数topic
        ArrayList<Object> otherTopics = new ArrayList<>();
        // 转账账户 fromAccount转换为topic
        otherTopics.add(TopicTools.stringToTopic(fromAccount));

        asset.registerRegisterEventEventLogFilter(fromBlock,toBlock,otherTopics,callback);

附录:JavaSDK启动异常场景

  • Failed to connect to the node. Please check the node status and the console configuration.
    比较旧的SDK版本的提示,建议将JavaSDK版本升级至2.2.2或者以上(修改gradle.build或者maven配置文件中web3sdk的版本号),可以获取更准确友好的提示,然后参考下面的错误提示解决问题。

  • Failed to initialize the SSLContext: class path resource [ca.crt] cannot be opened because it does not exist.
    无法加载到证书文件,证书文件没有正确拷贝至conf目录,可以参考控制台安装流程,拷贝证书文件至conf目录下。

  • Failed to initialize the SSLContext: Input stream not contain valid certificates.
    加载证书文件失败,CentOS系统使用OpenJDK的错误,参考CentOS环境安装JDK章节重新安装OracleJDK。

  • Failed to connect to nodes: [connection timed out: /127.0.0.1:20200]
    连接超时,节点的网络不可达,请检查提示的IP是否配置错误,或者,当前JavaSDK运行环境与节点的环境网络确实不通,可以咨询运维人员解决网络不通的问题。

  • Failed to connect to nodes: [Connection refused: /127.0.0.1:20200]
    拒绝连接,无法连接对端的端口,可以使用telnet命令检查端口是否连通,可能原因:
    1. 节点未启动,端口处于未监听状态,启动节点即可。
    2. 节点监听127.0.0.1的网段,监听127.0.0.1网络只能本机的客户端才可以连接,控制台位于不同服务器时无法连接节点,将节点配置文件config.ini中的channel_listen_ip修改为控制台连接节点使用的网段IP,或者将其修改为0.0.0.0
    3. 错误的端口配置,配置的端口并不是节点监听的channel端口,修改连接端口为节点config.ini配置的channel_listen_port的值。
      注意:控制台(或者JavaSDK)连接节点时使用Channel端口,并不是RPC端口,Channel端口在节点配置文件中通过channel_listen_ip字段配置,RPC端口通过jsonrpc_listen_port字段配置,注意区分,RPC默认从8545开始分配, Channel端口默认从20200开始分配。
  • Failed to connect to nodes: [ ssl handshake failed:/127.0.0.1:20233] 与节点ssl握手失败,可能原因:
    1. 拷贝了错误的证书,检查拷贝的证书是否正确。
    2. 端口配置错误,连接其他服务正在监听的端口,检查连接端口是否为节点channel_listen_port端口。
    3. JDK版本问题,推荐使用1.8以及以上的OracleJDK,参考CentOS环境安装JDK章节安装OracleJDK。

  • Failed to connect to [127.0.0.1:20233, 127.0.0.1:20234, 127.0.0.1:20235] ,groupId: 1 ,caCert: classpath:ca.crt ,sslKey: classpath:sdk.key ,sslCrt: classpath:sdk.crt ,java version: 1.8.0_231.
    其他未知的错误,需要查看日志文件分析具体错误。

Node.js SDK

Node.js SDK 提供了访问 FISCO BCOS 节点的Node.js API,支持节点状态查询、部署和调用合约等功能,基于Node.js SDK可快速开发区块链应用,目前支持 FISCO BCOS 2.0+

注意

Node.js SDK目前仅处于个人开发者体验阶段,开发企业级应用请使用 Web3SDK

主要特性

  • 提供调用FISCO BCOS JSON-RPC 的Node.js API
  • 提供部署及调用Solidity合约(支持Solidity 0.4.x 及Solidity 0.5.x)的Node.js API
  • 提供调用预编译(Precompiled)合约的Node.js API
  • 使用 Channel协议 与FISCO BCOS节点通信,双向认证更安全
  • 提供CLI(Command-Line Interface)工具供用户在命令行中方便快捷地与区块链交互

快速安装

环境要求
  • Node.js开发环境

    • Node.js >= 8.10.0
    • npm >= 5.6.0

    如果您没有部署过Node.js环境,可以参考下列部署方式:

    • 如果您使用Linux或MacOS:

      推荐使用nvm快速部署,使用nvm同时也能够避免潜在的导致Node.js SDK部署失败的权限问题。以部署Node.js 8为例,部署步骤如下:

      # 安装nvm
      curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
      # 加载nvm配置
      source ~/.$(basename $SHELL)rc
      # 安装Node.js 8
      nvm install 8
      # 使用Node.js 8
      nvm use 8
      
    • 如果您使用Windows:

      请前往Node.js官网下载Windows下的安装包进行安装。

  • 基本开发组件

    • Python 2(Windows、Linux及MacOS需要)
    • g++(Linux及MacOS需要)
    • make(Linux及MacOS需要)
    • Git(Windows、Linux及MacOS需要)
    • Git bash(仅Windows需要)
    • MSBuild构建环境(仅Windows需要)
  • FISCO BCOS节点:请参考FISCO BCOS安装搭建

部署Node.js SDK
拉取源代码
git clone https://github.com/FISCO-BCOS/nodejs-sdk.git
使用npm安装依赖项

如果您的网络中使用了代理,请先为npm配置代理:

npm config set proxy <your proxy>
npm config set https-proxy <your proxy>

如果您所在的网络不便访问npm官方镜像,请使用其他镜像代替,如淘宝:

npm config set registry https://registry.npm.taobao.org
# 部署过程中请确保能够访问外网以能够安装第三方依赖包
cd nodejs-sdk
npm install
npm run repoclean
npm run bootstrap
Node.js CLI

Node.js SDK内嵌CLI工具,供用户在命令行中方便地与区块链进行交互。CLI工具在Node.js SDK提供的API的基础上开发而成,使用方式与结果输出对脚本友好,同时也是一个展示如何调用Node.js API进行二次开发的范例。

快速建链(可选)

若您的系统中已经搭建了FISCO BCOS链,请跳过本节。

# 获取开发部署工具
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\.[0-9]\.[0-9]\"" | sort -u | tail -n 1 | cut -d \" -f 4`/build_chain.sh && chmod u+x build_chain.sh
# 在本地建一个4节点的FISCO BCOS链
bash build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545 -i
# 启动FISCO BCOS链
bash nodes/127.0.0.1/start_all.sh

配置证书及Channel端口

  • 配置证书

    修改配置文件,证书配置位于packages/cli/conf/config.json文件的authentication配置项中。你需要根据您实际使用的证书文件的路径修改该配置项的keycertca配置,其中key为SDK私钥文件的路径,cert为SDK证书文件的路径,ca为链根证书文件的路径,这些文件可以由开发部署工具运维部署工具自动生成,具体的生成方式及文件位置请参阅上述工具的说明文档。

  • 配置Channel端口

    修改配置文件,节点IP及端口配置位于packages/cli/conf/config.json文件的nodes配置项中。您需要根据您要连接FISCO BCOS节点的实际配置修改该配置项的ipport配置,其中ip为所连节点的IP地址,port为节点目录下的 config.ini 文件中的channel_listen_port配置项的值。如果您使用了快速搭链,可以跳过此步。

配置完成后,即可开始使用CLI工具,CLI工具位于packages/cli/cli.js,所有操作均需要在packages/cli/目录下执行,您需要先切换至该目录:

cd packages/cli

开启自动补全(仅针对bash及zsh用户,可选)

为方便用户使用CLI工具,CLI工具支持在bash或zsh中进行自动补全,此功能需要手动启用,执行命令:

rcfile=~/.$(basename $SHELL)rc && ./cli.js completion >> $rcfile && source $rcfile

便可启用自动补全。使用CLI工具时,按下Tab键(依据系统配置的不同,可能需要按两下)便可弹出候选命令或参数的列表并自动补全。

示例

以下给出几个使用示例:

查看CLI工具的帮助

./cli.js --help

查看CLI工具能够调用的命令及对应的功能

./cli.js list

以下示例中的输入、输出及参数仅供举例

查看所连的FISCO BCOS节点版本

./cli.js getClientVersion

输出如下:

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "Build Time": "20190705 21:19:13",
    "Build Type": "Linux/g++/RelWithDebInfo",
    "Chain Id": "1",
    "FISCO-BCOS Version": "2.0.0",
    "Git Branch": "master",
    "Git Commit Hash": "d8605a73e30148cfb9b63807fb85fa211d365014",
    "Supported Version": "2.0.0"
  }
}

获取当前的块高

./cli.js getBlockNumber

输出如下:

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0xfa"
}

部署SDK自带的HelloWorld合约

./cli.js deploy HelloWorld

输出如下:

{
  "contractAddress": "0x11b6d7495f2f04bdca45e9685ceadea4d4bd1832"
}

调用HelloWorld合约的set接口,请将合约地址改为实际地址

./cli.js call HelloWorld 0x11b6d7495f2f04bdca45e9685ceadea4d4bd1832 set vita

输出如下:

{
  "transactionHash": "0xa71f136107389348d5a092a345aa6bc72770d98805a7dbab0dbf8fe569ff3f37",
  "status": "0x0"
}

调用HelloWorld合约的get接口,请将合约地址改为实际地址

./cli.js call HelloWorld 0xab09b29dd07e003776d22566ae5c078f2cb2279e get

输出如下:

{
  "status": "0x0",
  "output": {
    "0": "vita"
  }
}

CLI帮助

如果您想知道某一个命令该如何使用,可以使用如下的命令:

./cli.js <command> ?

其中command为一个命令名,使用?作为参数便可获取该命令的使用提示,如:

./cli.js call ?

会得到如下的输出:

cli.js call <contractName> <contractAddress> <function> [parameters...]

Call a contract by a function and parameters

位置:
  contractName     The name of a contract                        [字符串] [必需]
  contractAddress  20 Bytes - The address of a contract          [字符串] [必需]
  function         The function of a contract                    [字符串] [必需]
  parameters       The parameters(splited by a space) of a function
                                                             [数组] [默认值: []]

选项:
  --help     显示帮助信息                                                 [布尔]
  --version  显示版本号                                                   [布尔]

配置说明

Node.js SDK的配置文件为一个JSON文件,主要包括通用配置群组配置通信配置证书配置

通用配置
  • privateKey: object,必需。外部账户的私钥,可以为一个256 bits的随机整数,也可以是一个pem或p12格式的私钥文件,后两者需要结合get_account.sh生成的私钥文件使用。privateKey包含两个必需字段,一个可选字段:
    • type: string,必需。用于指示私钥类型。type的值必需为下列三个值之一:
      • ecrandom:随机整数
      • pem:pem格式的文件
      • p12:p12格式的文件
    • valuestring,必需。用于指示私钥具体的值:
      • 如果typeecrandom,则value为一个长度为256 bits 的随机整数,其值介于1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141之间。
      • 如果typepem,则value为pem文件的路径,如果是相对路径,需要以配置文件所在的目录为相对路径起始位置。
      • 如果typep12,则value为p12文件的路径,如果是相对路径,需要以配置文件所在的目录为相对路径起始位置。
    • passwordstring,可选。如果typep12,则需要此字段以解密私钥,否则会忽略该字段。
  • timeout: number。Node.js SDK所连节点可能会陷入停止响应的状态。为避免陷入无限等待,Node.js SDK的每一次API调用在timeout之后若仍没有得到结果,则强制返回一个错误对象。timeout的单位为毫秒。
  • solc: string,可选。Node.js SDK已经自带0.4.26及0.5.10版本的Solidity编译器,如果您有特殊的编译器需求,可以设置本配置项为您的编译器的执行路径或全局命令
群组配置
  • groupID: number。Node.js SDK访问的链的群组ID
通信配置
  • nodes: list,必需。FISCO BCOS节点列表,Node.js SDK在访问节点时时会从该列表中随机挑选一个节点进行通信,要求节点数目必须 >= 1。在FISCO BCOS中,一笔交易上链并不代表网络中的所有节点都已同步到了最新的状态,如果Node.js SDK连接了多个节点,则可能会出现读取不到最新状态的情况,因此在对状态同步有较高要求的场合,请谨慎连接多个节点。每个节点包含两个字段:
    • ip: string,必需。FISCO BCOS节点的IP地址
    • port: string,必需,FISCO BCOS节点的Channel端口
证书配置
  • authenticationobject。必需,包含建立Channel通信时所需的认证信息,一般在建链过程中自动生成。authentication包含三个必需字段:
    • key: string,必需。私钥文件路径,如果是相对路径,需要以配置文件所在的目录为相对路径起始位置。
    • cert: string,必需。证书文件路径,如果是相对路径,需要以配置文件所在的目录为相对路径起始位置。
    • ca: string,必需。CA根证书文件路径,如果是相对路径,需要以配置文件所在的目录为相对路径起始位置。

Node.js API

Node.js SDK为区块链应用开发者提供了Node.js API接口,以服务的形式供外部调用。按照功能,Node.js API可以分为如下几类:

  • Web3jService:提供访问FISCO BCOS 2.0+节点JSON-RPC接口支持;提供部署及调用合约的支持。

  • PrecompiledService

    Precompiled合约(预编译合约)是一种FISCO BCOS底层内嵌的、通过C++实现的高效智能合约,提供包括分布式权限控制CNS、系统属性配置、节点类型配置等功能。PrecompiledService是调用这类功能的API的统称,分为:

    • PermissionService:提供对分布式权限控制的支持
    • CNSService:提供对CNS的支持
    • SystemConfigService:提供对系统配置的支持
    • ConsensusService:提供对节点类型配置的支持
    • CRUDService:提供对CRUD(增删改查)操作的支持,可以创建表或对表进行增删改查操作。
API调用约定
  • 使用服务之前,首先需要初始化全局的Configuration对象,用以为各个服务提供必要的配置信息。Configuration对象位于nodejs-sdk/packages/api/common/configuration.js,其初始化参数为一个配置文件的路径或包含配置项的对象。配置文件的配置项说明见配置说明
  • 如无特殊说明,Node.js SDK提供的API均为异步API。异步API的实际返回值是一个包裹了API返回值的Promise对象,开发者可以使用async/await语法then…catch…finally方法操作该Promise对象以实现自己的程序逻辑
  • 当API内部出现错误导致逻辑无法继续执行时(如合约地址不存在),均会直接抛出异常,所有异常均继承自Error类
Web3jService

位置nodejs-sdk/packages/api/web3j

功能:访问FISCO BCOS 2.0+节点JSON-RPC;部署合约;调用合约

接口名 描述 参数 返回值
getBlockNumber 获取最新块高 Object,结果位于result字段**
getPbftView 获取PBFT视图 同上
getObserverList 获取观察者节点列表 同上
getSealerList 获取共识节点列表 同上
getConsensusStatus 获取区块链节点共识状态 同上
getSyncStatus 获取区块链节点同步状态 同上
getClientVersion 获取区块链节点版本信息 同上
getPeers 获取区块链节点的连接信息 同上
getNodeIDList 获取节点及其连接节点的列表 同上
getGroupPeers 获取指定群组的共识节点
和观察节点列表
同上
getGroupList 获取节点所属群组的群组ID列表 同上
getBlockByHash 根据区块哈希获取区块信息 区块哈希 同上
getBlockByNumber 根据区块高度获取区块信息 区块高度 同上
getBlockHashByNumber 根据区块高度获取区块哈希 区块高度 同上
getTransactionByHash 根据交易哈希获取交易信息 交易哈希 同上
getTransactionByBlockHashAndIndex 根据交易所属的区块哈希、
交易索引获取交易信息
交易所属的区块哈希
交易索引
同上
getTransactionByBlockNumberAndIndex 根据交易所属的区块高度、
交易索引获取交易信息
交易所属的区块高度
交易索引
同上
getPendingTransactions 获取交易池内所有未上链的交易 同上
getPendingTxSize 获取交易池内未上链的交易数目 同上
getTotalTransactionCount 获取指定群组的上链交易数目 同上
getTransactionReceipt 根据交易哈希获取交易回执 交易哈希 同上
getCode 根据合约地址查询的合约数据 合约地址 同上
getSystemConfigByKey 获取系统配置 系统配置关键字,目前支持

- tx_count_limit
- tx_gas_limit
同上
sendRawTransaction 发送一个经过签名的交易,该交易随后会被链上节点执行并共识 接受数量可变的参数:当参数数量为1时,参数应为交易的RLP编码;当参数数量为3时,参数应为合约地址、方法签名及方法参数 同上
deploy 部署合约 合约路径
输出路径
同上
call 调用只读合约 合约地址
调用接口*
参数列表
同上

*调用接口:函数名(参数类型,…),例如:func(uint256,uint256),参数类型之间不能有空格

PrecompiledService
PermissionService

位置nodejs-sdk/packages/api/precompiled/permission

功能:提供对分布式权限控制的支持

接口名 描述 参数 返回值
grantUserTableManager 根据用户表名和外部账户地址设置权限信息 表名
外部账户地址
Number,表示成功改写权限表的行数
revokeUserTableManager 根据用户表名和外部账户地址去除权限信息 表名
外部账户地址
Number,表示成功改写权限表的行数
listUserTableManager 根据用户表名查询设置的权限记录列表(每条记录包含外部账户地址和生效块高) 表名 Array,查询到的记录
grantDeployAndCreateManager 增加外部账户地址的部署合约和创建用户表权限 外部账户地址 Number,表示成功改写权限表的行数
revokeDeployAndCreateManager 移除外部账户地址的部署合约和创建用户表权限 外部账户地址 Number,表示成功改写权限表的行数
listDeployAndCreateManager 查询拥有部署合约和创建用户表权限的权限记录列表 Array,查询到的记录
grantPermissionManager 增加外部账户地址的管理权限的权限 外部账户地址 Number,表示成功改写权限表的行数
revokePermissionManager 移除外部账户地址的管理权限的权限 外部账户地址 Number,表示成功改写权限表的行数
listPermissionManager 查询拥有管理权限的权限记录列表 Array,查询到的记录
grantNodeManager 增加外部账户地址的节点管理权限 外部账户地址 Number,表示成功改写权限表的行数
revokeNodeManager 移除外部账户地址的节点管理权限 外部账户地址 Number,表示成功改写权限表的行数
listNodeManager 查询拥有节点管理的权限记录列表 Array,查询到的记录
grantCNSManager 增加外部账户地址的使用CNS权限 外部账户地址 Number,表示成功改写权限表的行数
revokeCNSManager 移除外部账户地址的使用CNS权限 外部账户地址 Number,表示成功改写权限表的行数
listCNSManager 查询拥有使用CNS的权限记录列表 Array,查询到的记录
grantSysConfigManager 增加外部账户地址的系统参数管理权限 外部账户地址 Number,表示成功改写权限表的行数
revokeSysConfigManager 移除外部账户地址的系统参数管理权限 外部账户地址 Number,表示成功改写权限表的行数
listSysConfigManager 查询拥有系统参数管理的权限记录列表 Array,查询到的记录
CNSService

位置nodejs-sdk/packages/api/precompiled/cns

功能:提供对节点类型配置的支持

接口名 描述 参数 返回值
registerCns 根据合约名、合约版本号、合约地址和合约abi注册CNS信息 合约名
合约版本号
合约地址
合约abi
Number,表示成功增加的CNS条目记录数
getAddressByContractNameAndVersion 根据合约名和合约版本号(合约名和合约版本号用英文冒号连接)查询合约地址。若缺失合约版本号,默认使用合约最新版本 合约名 + ':' + 版本号 Object,查询到的CNS信息
queryCnsByName 根据合约名查询CNS信息 合约名 Array,查询到的CNS信息
queryCnsByNameAndVersion 根据合约名和合约版本号查询CNS信息 合约名
版本号
同上
SystemConfigService

位置nodejs-sdk/packages/api/precompiled/systemConfig

功能:提供对系统配置的支持

接口名 描述 参数 返回值
setValueByKey 根据键设置对应的值(查询键对应的值,参考Web3jService中的getSystemConfigByKey接口) 键名
Number,表示成功修改的系统配置的数目
ConsensusService

位置nodejs-sdk/packages/api/precompiled/consensus

功能:提供对节点类型配置的支持

接口名 描述 参数 返回值
addSealer 根据节点NodeID设置对应节点为共识节点 节点ID Number,表示成功增加的共识节点数目
addObserver 根据节点NodeID设置对应节点为观察者节点 节点ID Number,表示成功增加的观察数目
removeNode 根据节点NodeID设置对应节点为游离节点 节点ID Number,表示成功增加的游离数目
CRUDService

位置nodejs-sdk/packages/api/precompiled/crud

功能:提供对CRUD(增删改查)操作的支持

接口名 描述 参数 返回值
createTable 创建表 表对象
表对象需要设置其表名,主键字段名和其他字段名。其中,其他字段名是以英文逗号分隔拼接的字符串
Number,状态码,0代表创建成功
insert 插入记录 表对象 Entry对象
表对象需要设置表名和主键字段名;Entry是map对象,提供插入的字段名和字段值,注意必须设置主键字段
Number,表示插入的记录数
update 更新记录 表对象 Entry对象 Condtion对象
表对象需要设置表名和主键字段名;Condition对象是条件对象,可以设置查询的匹配条件
Array,查询到的记录
remove 移除记录 表对象 条件对象
表对象需要设置表名和主键字段名;Condition对象是条件对象,可以设置移除的匹配条件
Select 查询记录 表对象:表对象需要设置表名和主键字段值
Condtion对象:Condition对象是条件对象,可以设置查询的匹配条件
Number,成功查询的记录数
desc 根据表名查询表的信息 表名 Object,主要包含表的主键字段名和其他属性字段

Python SDK

Python SDK 提供了访问 FISCO BCOS 节点的Python API,支持节点状态查询、部署和调用合约等功能,基于Python SDK可快速开发区块链应用,目前支持 FISCO BCOS 2.0+

注意

  • Python SDK当前为候选版本,可供开发测试使用,企业级应用可用 Web3SDK
  • Python SDK目前支持FISCO BCOS 2.0.0及其以上版本

主要特性

  • 提供调用FISCO BCOS JSON-RPC 的Python API
  • 支持使用 Channel协议 与FISCO BCOS节点通信,保证节点与SDK安全加密通信的同时,可接收节点推送的消息。
  • 支持交易解析功能:包括交易输入、交易输出、Event Log等ABI数据的拼装和解析
  • 支持合约编译,将 sol 合约编译成 abibin 文件
  • 支持基于keystore的账户管理
  • 支持合约历史查询

快速安装

环境要求

依赖软件

  • Ubuntu: sudo apt install -y zlib1g-dev libffi6 libffi-dev wget git
  • CentOSsudo yum install -y zlib-devel libffi-devel wget git
  • MacOs: brew install wget git

Python环境要求

  • 支持版本
    • python 3.6.3
    • 3.7.x
部署Python SDK
环境要求
  • Python环境:python 3.6.3, 3.7.x
  • FISCO BCOS节点:请参考FISCO BCOS安装搭建
初始化环境(若python环境符合要求,可跳过)
Linux环境初始化

拉取源代码

git clone https://github.com/FISCO-BCOS/python-sdk

配置环境

注解

  • bash init_env.sh -p 主要功能是安装pyenv,并使用pyenv安装名称为 python-sdk 的python-3.7.3虚拟环境
  • 若python环境符合要求,可以跳过此步
  • 若脚本执行出错,请检查是否参考[依赖软件]说明安装了依赖
  • 安装python-3.7.3可能耗时比较久
  • 此步骤仅需初始化一遍,再次登录直接使用命令 pyenv activate python-sdk 激活 python-sdk 虚拟环境即可
# 判断python版本,并为不符合条件的python环境安装python 3.7.3的虚拟环境,命名为python-sdk
# 若python环境符合要求,可以跳过此步
# 若脚本执行出错,请检查是否参考[依赖软件]说明安装了依赖
# 提示:安装python-3.7.3可能耗时比较久
cd python-sdk && bash init_env.sh -p

# 激活python-sdk虚拟环境
source ~/.bashrc && pyenv activate python-sdk && pip install --upgrade pip
Windows环境初始化

在Windows运行Python SDK,需要按照以下步骤安装依赖软件并配置合约编译器:

安装依赖软件

注解

  • Microsoft Visual C++ 14.0 is required. Get it with “Microsoft Visual C++ Build Tools”解决方法: https://visualstudio.microsoft.com/downloads (注意选择vs 2005即14.0版)或 https://pan.baidu.com/s/1ZmDUGZjZNgFJ8D14zBu9og 提取码: zrby
  • solc编译器下载成功后,解压,将其中的 solc.exe 文件复制 ${python-sdk}\bin 目录下,若python-sdk路径为 D:\\open-source\\python-sdk , 则 solc.exe 文件复制路径为 D:\\open-source\\python-sdk\\bin\\solc.exe

拉取源代码

打开 git,在任意目录执行如下命令

git clone https://github.com/FISCO-BCOS/python-sdk

配置solc编译器

修改client_config.py.template,配置solc编译器路径,solc二进制下载请参考bcos_solc.py中的描述,并将client_config.py.template拷贝为client_config.py

# 修改client_config.py.template: 
# 配置solc编译器路径,若solc存放路径为D:\\open-source\\python-sdk\\bin\\solc.exe,则solc_path配置如下:
solc_path = "D:\\open-source\\python-sdk\\bin\\solc.exe"

# 将client_config.py.template拷贝到client_config.py
安装Python SDK依赖
pip install -r requirements.txt

若因网络原因,安装依赖失败,可使用清华的pip源下载,安装命令如下:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
初始化配置(Windows环境可跳过)
# 该脚本执行操作如下:
# 1. 拷贝client_config.py.template->client_config.py
# 2. 下载solc编译器
bash init_env.sh -i

若没有执行以上初始化步骤,需要将contracts/目录下的sol代码手动编译成binabi文件并放置于contracts目录,才可以部署和调用相应合约。合约编译可以使用remix

配置Channel通信协议

Python SDK支持使用Channel协议与FISCO BCOS节点通信,通过SSL加密通信保障SDK与节点通信的机密性。

设SDK连接的节点部署在目录~/fisco/nodes/127.0.0.1目录下,则通过如下步骤使用Channel协议:

配置Channel信息

注解

为便于开发和体验,channel_listen_ip参考配置是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP

在节点目录下的 config.ini 文件中获取 channel_listen_port, 这里为20200

[rpc]
    channel_listen_ip=0.0.0.0
    jsonrpc_listen_ip=127.0.0.1
    channel_listen_port=20200
    jsonrpc_listen_port=8545

切换到python-sdk目录,修改 client_config.py 文件中channel_host为实际的IP,channel_port为上步获取的channel_listen_port

channel_host = "127.0.0.1"
channel_port = 20200

配置证书

# 若节点与python-sdk位于不同机器,请将节点sdk目录下所有相关文件拷贝到bin目录
# 若节点与sdk位于相同机器,直接拷贝节点证书到SDK配置目录
cp ~/fisco/nodes/127.0.0.1/sdk/* bin/

配置证书路径

注解

  • client_config.pychannel_node_certchannel_node_key 选项分别用于配置SDK证书和私钥
  • release-2.1.0 版本开始,SDK证书和私钥更新为 sdk.crtsdk.key ,配置证书路径前,请先检查上步拷贝的证书名和私钥名,并将 channel_node_cert 配置为SDK证书路径,将 channel_node_key 配置为SDK私钥路径
  • FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错

检查从节点拷贝的sdk证书路径,若sdk证书和私钥路径分别为bin/sdk.crtbin/sdk.key,则client_config.py中相关配置项如下:

channel_node_cert = "bin/sdk.crt"  # 采用channel协议时,需要设置sdk证书,如采用rpc协议通信,这里可以留空
channel_node_key = "bin/sdk.key"   # 采用channel协议时,需要设置sdk私钥,如采用rpc协议通信,这里可以留空

若sdk证书和私钥路径分别为bin/node.crtbin/node.key,则client_config.py中相关配置项如下:

channel_node_cert = "bin/node.crt"  # 采用channel协议时,需要设置sdk证书,如采用rpc协议通信,这里可以留空
channel_node_key = "bin/node.key"   # 采用channel协议时,需要设置sdk私钥,如采用rpc协议通信,这里可以留空

使用Channel协议访问节点

注解

windows环境下执行console.py请使用 .\console.py 或者 python console.py

# 获取FISCO BCOS节点版本号
./console.py getNodeVersion
开启命令行自动补全

Python SDK引入argcomplete支持命令行补全,运行如下命令开启此功能(bashrc仅需设置一次,设置之后每次登陆自动生效)

注解

  • 此步骤仅需设置一次,设置之后以后每次登陆自动生效
  • 请在 bash环境 下执行此步骤
  • 目前仅支持bash,不支持zsh
echo "eval \"\$(register-python-argcomplete ./console.py)\"" >> ~/.bashrc
source ~/.bashrc

配置说明

client_config.py是Python SDK的配置文件,主要包括SDK算法类型配置通用配置账户配置群组配置通信配置证书配置

注解

  • 确保连接端口开放:推荐使用 telnet ip port 确认客户端与节点网络是否连通
  • 使用RPC通信协议,不需设置证书
  • 日志配置参见 client/clientlogger.py ,默认在 bin/logs 目录下生成日志,默认级别为DEBUG
SDK算法类型配置
  • crypto_type: SDK接口类型,目前支持国密接口(GM)和非国密接口(ECDSA)
通用配置
  • contract_info_file: 保存已部署合约信息的文件,默认为bin/contract.ini
  • account_keyfile_path: 存放keystore文件的目录,默认为bin/accounts
  • logdir:默认日志输出目录,默认为bin/logs
账户配置

非国密账户的配置如下:

  • account_keyfile: 存储非国密账号信息的keystore文件路径, 默认为pyaccount.keystore
  • account_password: 非国密keystore文件的存储口令,默认为123456

国密账户的配置如下:

  • gm_account_keyfile: 存储国密账号信息的加密文件路径, 默认为gm_account.json
  • gm_account_password: 国密账户文件的存储口令,默认为123456
群组配置

群组配置主要包括链ID和群组ID:

  • fiscoChainId:链ID,必须与通信节点的一致,默认为1
  • groupid:群组ID,必须与通信的节点一致,获取节点群组ID请参考这里,默认为1
通信配置
  • client_protocol:Python SDK与节点通信协议,包括rpcchannel选项,前者使用JSON-RPC接口访问节点,后者使用Channel访问节点,需要配置证书,默认为channel
  • remote_rpcurl:采用rpc通信协议时,节点的rpc IP和端口,参考这里获取节点RPC信息,默认为http://127.0.0.1:8545如采用channel协议,可以留空
  • channel_host:采用channel协议时,节点的channel IP地址,参考这里获取节点Channel信息,默认为127.0.0.1如采用rpc协议通信,可以留空
  • channel_port:节点的channel 端口,默认为20200如采用rpc协议通信,可以留空
证书配置
  • channel_ca:链CA证书,使用channel协议时设置,默认为bin/ca.crt
  • channel_node_cert:节点证书,使用channel协议时设置,默认为bin/sdk.crt如采用rpc协议通信,可以留空
  • channel_node_key:Python SDK与节点通信私钥,采用channel协议时须设置,默认为bin/sdk.key如采用rpc协议通信,这里可以留空
solc编译器配置

Python SDK支持使用配置的solc和solcjs编译器自动编译合约,同时配置solc和solcjs时,选择性能较高的solc编译器,编译选项如下:

  • solc_path:非国密solc编译器路径
  • gm_solc_path: 国密编译器路径
  • solcjs_path:solcjs编译脚本路径,为./solc.js
配置项示例

配置项示例如下

    """
    类成员常量和变量,便于用.调用和区分命名空间
    """
    # keyword used to represent the RPC Protocol
    PROTOCOL_RPC = "rpc"
    # keyword used to represent the Channel Protocol
    PROTOCOL_CHANNEL = "channel"

    # ---------crypto_type config--------------
    # crypto_type : 大小写不敏感:"GM" for 国密, "ECDSA" 或其他是椭圆曲线默认实现。
    crypto_type = "ECDSA"
    crypto_type = crypto_type.upper()
    set_crypto_type(crypto_type)  # 使密码算法模式全局生效,切勿删除此行

    # --------------------------------------
    # configure below
    # ---------client communication config--------------
    client_protocol = "channel"  # or PROTOCOL_CHANNEL to use channel prototol
    # client_protocol = PROTOCOL_CHANNEL
    remote_rpcurl = "http://127.0.0.1:8545"  # 采用rpc通信时,节点的rpc端口,和要通信的节点*必须*一致,如采用channel协议通信,这里可以留空
    channel_host = "127.0.0.1"  # 采用channel通信时,节点的channel ip地址,如采用rpc协议通信,这里可以留空
    channel_port = 20200  # 节点的channel 端口,如采用rpc协议通信,这里可以留空
    channel_ca = "bin/ca.crt"  # 采用channel协议时,需要设置链证书,如采用rpc协议通信,这里可以留空
    channel_node_cert = "bin/sdk.crt"  # 采用channel协议时,需要设置sdk证书,如采用rpc协议通信,这里可以留空
    channel_node_key = "bin/sdk.key"  # 采用channel协议时,需要设置sdk私钥,如采用rpc协议通信,这里可以留空
    fiscoChainId = 1  # 链ID,和要通信的节点*必须*一致
    groupid = 1  # 群组ID,和要通信的节点*必须*一致,如和其他群组通信,修改这一项,或者设置bcosclient.py里对应的成员变量

    # ---------account &keyfile config--------------
    # 注意账号部分,国密和ECDSA采用不同的配置
    contract_info_file = "bin/contract.ini"  # 保存已部署合约信息的文件
    account_keyfile_path = "bin/accounts"  # 保存keystore文件的路径,在此路径下,keystore文件以 [name].keystore命名
    account_keyfile = "pyaccount.keystore"
    account_password = "123456"  # 实际使用时建议改为复杂密码
    gm_account_keyfile = "gm_account.json"  # 国密账号的存储文件,可以加密存储,如果留空则不加载gm_account_password = "123456"
    gm_account_password = "123456"
    # ---------console mode, support user input--------------
    background = True

    # ---------runtime related--------------
    # 非国密编译器路径
    solc_path = "./bin/solc/v0.4.25/solc"
    # 国密编译器路径
    gm_solc_path = "./bin/solc/v0.4.25/solc-gm" 
    solcjs_path = "./solcjs"

    logdir = "bin/logs"  # 默认日志输出目录,该目录必须先建立

Python API

Python SDK为区块链应用开发者提供了Python API接口,主要包括:

  • Python API:封装了访问FISCO BCOS 2.0+节点JSON-RPC的Python API
  • 交易结构定义:定义了FISCO BCOS 2.0+的交易数据结构
  • 交易输入输出解析:提供ABI、Event Log、交易输入和输出解析功能
  • ChannelHandler:FISCO BCOS channel协议实现类,支持节点之间SSL加密通信
Python API:BcosClient

实现于client/bcosclient.py,封装了访问FISCO BCOS 2.0+节点JSON-RPC的Python API,主要接口包括:

接口名 描述 参数
getNodeVersion 获取区块链节点版本信息
getBlockNumber 获取最新块高
getPbftView 获取PBFT视图
getSealerList 获取共识节点列表
getObserverList 获取观察者节点列表
getConsensusStatus 获取区块链节点共识状态
getSyncStatus 获取区块链节点同步状态
getPeers 获取区块链节点的连接信息
getGroupPeers 获取指定群组的共识节点
和观察节点列表
getNodeIDList 获取节点及其连接节点的列表
getGroupList 获取节点所属群组的群组ID列表
getBlockByHash 根据区块哈希获取区块信息 区块哈希
getBlockByNumber 根据区块高度获取区块信息 区块高度
getBlockHashByNumber 根据区块高度获取区块哈希 区块高度
getTransactionByHash 根据交易哈希获取交易信息 交易哈希
getTransactionByBlockHashAndIndex 根据交易所属的区块哈希、
交易索引获取交易信息
交易所属的区块哈希
交易索引
getTransactionByBlockNumberAndIndex 根据交易所属的区块高度、
交易索引获取交易信息
交易所属的区块高度
交易索引
getTransactionReceipt 根据交易哈希获取交易回执 交易哈希
getPendingTransactions 获取交易池内所有未上链的交易
getPendingTxSize 获取交易池内未上链的交易数目
getCode 根据合约地址查询的合约数据 合约地址
getTotalTransactionCount 获取指定群组的上链交易数目
getSystemConfigByKey 获取系统配置 系统配置关键字
如:
- tx_count_limit
- tx_gas_limit
deploy 部署合约 合约binary code
call 调用合约 合约地址
合约abi
调用接口名称
参数列表
sendRawTransaction 发送交易 合约地址
合约abi
接口名
参数列表
合约binary code
sendRawTransactionGetReceipt 发送交易
并获取交易执行结果
合约地址
合约abi接口名
参数列表
合约binary code
Precompile Service
CNS

类名

client.precompile.cns.cns_service.CnsService

功能接口

  • register_cns:注册合约名到(合约地址,合约版本)的映射到CNS系统表中
  • query_cns_by_name:根据合约名查询CNS信息
  • query_cns_by_nameAndVersion:根据合约名和合约名查询CNS信息
共识

类名

client.precompile.consensus.consensus_precompile.ConsensusPrecompile

功能接口

  • addSealer:添加共识节点
  • addObserver:添加观察者节点
  • removeNode:将节点从群组中删除
权限控制

类名

client.precompile.permission.permission_service.PermissionService

功能接口

  • grant: 将指定表的权限授权给用户
  • revoke:收回指定用户对指定表的写权限
  • list_permission: 显示对指定表有写权限的账户信息
CRUD

类名

client.precompile.crud.crud_service.Entry

功能接口

  • create_table:创建用户表
  • insert:向用户表插入记录
  • update:更新用户表记录
  • remove:删除用户表指定记录
  • select:查询用户表指定记录
  • desc: 查询用户表信息
系统配置

类名

client.precompile.config.config_precompile.ConfigPrecompile

功能接口

  • setValueByKey: 设置系统配置项的值
交易结构定义:BcosTransaction

实现于client/bcostransaction.py,定义了FISCO BCOS 2.0+的交易数据结构:

字段 描述
randomid 随机数,用于交易防重
gasPrice 默认为30000000
gasLimit 交易消耗gas上限,默认为30000000
blockLimit 交易防重上限,默认为500
to 一般为合约地址
value 默认为0
data 交易数据
fiscoChainId 链ID,通过配置client_config.py加载
groupId 群组ID,通过配置client_config.py加载
extraData 附加数据,默认为空字符串
交易输入输出解析:DatatypeParser

提供ABI、Event Log、交易输入和输出解析功能,实现于client/datatype_parser.py

接口 参数 描述
load_abi_file abi文件路径 从指定路径加载并解析ABI文件
建立函数名、selector到函数abi映射列表
parse_event_logs event log 解析event log
parse_transaction_input 交易输入 解析交易输入
返回交易调用的接口名、交易参数等
parse_receipt_output 交易调用的接口名
交易输出
解析交易输出
ChannelHandler

FISCO BCOS channel协议实现类,支持节点之间SSL加密通信,并可接收节点推送的消息,主要实现于client/channelhandler.py,channel协议编解码参考这里

合约历史查询
  • client/contratnote.py: 采用ini配置文件格式保存合约的最新地址和历史地址,以便加载(如console命令里可以用(合约名 last)指代某个合约最新部署的地址)
日志模块
  • client/clientlogger.py: logger定义,目前包括客户端日志和统计日志两种
  • client/stattool.py 一个简单的统计数据收集和打印日志的工具类

控制台

Python SDK通过console.py实现了一个简单的控制台,支持合约操作、账户管理操作等。

注解

  • Python SDK当前为候选版本,可供开发测试使用,企业级应用可用 Web3SDK
  • 安装Java版本控制台可参考 这里
  • windows环境下执行console.py请使用 .\console.py 或者 python console.py
常用命令
deploy

部署合约:

./console.py deploy [contract_name] [save]

参数包括:

  • contract_name: 合约名,需要先放到contracts目录
  • save:若设置了save参数,表明会将合约地址写入历史记录文件
$ ./console.py deploy HelloWorld save

INFO >> user input : ['deploy', 'HelloWorld', 'save']

backup [contracts/HelloWorld.abi] to [contracts/HelloWorld.abi.20190807102912]
backup [contracts/HelloWorld.bin] to [contracts/HelloWorld.bin.20190807102912]
INFO >> compile with solc compiler
deploy result  for [HelloWorld] is:
 {
    "blockHash": "0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d",
    "blockNumber": "0x1",
    "contractAddress": "0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce",
    "from": "0x95198b93705e394a916579e048c8a32ddfb900f7",
    "gasUsed": "0x44ab3",
    "input": "0x6080604052...省略若干行...c6f2c20576f726c642100000000000000000000000000",
    "logs": [],
    "logsBloom": "0x000...省略若干行...0000",
    "output": "0x",
    "status": "0x0",
    "to": "0x0000000000000000000000000000000000000000",
    "transactionHash": "0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91",
    "transactionIndex": "0x0"
}
on block : 1,address: 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce
address save to file:  bin/contract.ini
call

调用合约接口,并解析返回结果:

./console.py  call [contract_name] [contract_address] [function] [args]

参数包括:

  • contract_name:合约名
  • contract_address:调用的合约地址
  • function:调用的合约接口
  • args:调用参数
# 合约地址:0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce
# 调用接口:get
$./console.py  call HelloWorld 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce get

INFO >> user input : ['call', 'HelloWorld', '0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce', 'get']
INFO >> call HelloWorld , address: 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce, func: get, args:[]
INFO >> call result: ('Hello, World!',)
sendtx

发送交易调用指定合约的接口,交易结果会写入区块和状态:

./console.py sendtx [contract_name] [contract_address] [function] [args]

参数包括:

  • contract_name:合约名
  • contract_address:合约地址
  • function:函数接口
  • args:参数列表
# 合约名:HelloWorld
# 合约地址:0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce
# 调用接口:set
# 参数:"Hello, FISCO"
$ ./console.py sendtx HelloWorld 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce set "Hello, FISCO"

INFO >> user input : ['sendtx', 'HelloWorld', '0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce', 'set', 'Hello, FISCO']

INFO >> sendtx HelloWorld , address: 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce, func: set, args:['Hello, FISCO']

INFO >>  receipt logs :
INFO >> transaction hash :  0xc20cbc6b0f28ad8fe1c560c8ce28c0e7eb7719a4a618a81604ac87ac46cc60f0
tx input data detail:
 {'name': 'set', 'args': ('Hello, FISCO',), 'signature': 'set(string)'}
receipt output : ()
newaccount

创建新账户,并将结果以加密的形式把保存与bin/accounts/${accoutname}.keystore文件中,如同目录下已经有同名帐户文件,旧文件会复制一个备份

./console.py newaccount [account_name] [account_password]

参数包括:

  • account_name:账户名
  • account_password:加密keystore文件的口令

注解

  • 采用创建帐号的命令创建帐号后,若需作为默认帐号使用,注意修改client_config.py的 account_keyfileaccount_password 配置项
  • 账户名不可超过240个字符
  • account_password 中包含特殊字符,请在 account_password 周围加上单引号,否则无法解析
$ ./console.py newaccount test_account "123456"

>> user input : ['newaccount', 'test_account', '123456']

starting : test_account 123456
new address :    0x247e7AE892a94c9e089D61A7DB08af23CEDBec16
new privkey :    0xe2cf070a7c1da05577841b54b4f8ca7d9f7eb52e688bb7e61a2c6ada8a4c5c77
new pubkey :     0x71317d52a7f8b5bb3fa882b9936d7d31a04e6a122e6fdf790d39aeee8ed2883d3c0b90f644cab0b30153d700d93da4c4ea4aef07a7eca2a5e62c8d0f058b3533
encrypt use time : 1.453 s
save to file : [bin/accounts/test_account.keystore]
>>-------------------------------------------------------
>> read [bin/accounts/test_account.keystore] again after new account,address & keys in file:
decrypt use time : 1.447 s
address:         0x247e7AE892a94c9e089D61A7DB08af23CEDBec16
privkey:         0xe2cf070a7c1da05577841b54b4f8ca7d9f7eb52e688bb7e61a2c6ada8a4c5c77
pubkey :         0x71317d52a7f8b5bb3fa882b9936d7d31a04e6a122e6fdf790d39aeee8ed2883d3c0b90f644cab0b30153d700d93da4c4ea4aef07a7eca2a5e62c8d0f058b3533

account store in file: [bin/accounts/test_account.keystore]

**** please remember your password !!! *****
showaccount

根据账户名和账户keystore文件口令,输出账户公私钥信息:

./console.py showaccount [account_name] [account_password]

参数包括:

  • name:账户名称
  • password: 账户keystore文件口令
$ ./console.py showaccount test_account "123456"

>> user input : ['showaccount', 'test_account', '123456']

show account : test_account, keyfile:bin/accounts/test_account.keystore ,password 123456
decrypt use time : 1.467 s
address:         0x247e7AE892a94c9e089D61A7DB08af23CEDBec16
privkey:         0xe2cf070a7c1da05577841b54b4f8ca7d9f7eb52e688bb7e61a2c6ada8a4c5c77
pubkey :         0x71317d52a7f8b5bb3fa882b9936d7d31a04e6a122e6fdf790d39aeee8ed2883d3c0b90f644cab0b30153d700d93da4c4ea4aef07a7eca2a5e62c8d0f058b3533

account store in file: [bin/accounts/test_account.keystore]

**** please remember your password !!! *****
usage

输出控制台使用方法:

 $ ./console.py usage

INFO >> user input : ['usage']

FISCO BCOS 2.0 @python-SDK Usage:
newaccount [name] [password] [save]
        创建一个新帐户,参数为帐户名(如alice,bob)和密码
        结果加密保存在配置文件指定的帐户目录 *如同目录下已经有同名帐户文件,旧文件会复制一个备份
        如输入了"save"参数在最后,则不做询问直接备份和写入
        create a new account ,save to :[bin/accounts] (default) ,
        the path in client_config.py:[account_keyfile_path]
        if account file has exist ,then old file will save to a backup
        if "save" arg follows,then backup file and write new without ask
        the account len should be limitted to 240

    ... 省略若干行...
    [getTransactionByBlockHashAndIndex] [blockHash] [transactionIndex]
    [getTransactionByBlockNumberAndIndex] [blockNumber] [transactionIndex]
    [getSystemConfigByKey] [tx_count_limit/tx_gas_limit]
list

输出Python SDK支持的所有接口:

$ ./console.py list

INFO >> user input : ['list']

 >> RPC commands
    [getNodeVersion]
    [getBlockNumber]
    ... 省略若干行...
    [getTransactionByBlockHashAndIndex] [blockHash] [transactionIndex]
    [getTransactionByBlockNumberAndIndex] [blockNumber] [transactionIndex]
    [getSystemConfigByKey] [tx_count_limit/tx_gas_limit]
CNS

Python SDK控制台提供了CNS命令,主要包括注册CNS、查询CNS信息,CNS设计使用方法请参考这里

registerCNS

将(合约地址, 合约版本)到合约名的映射注册到CNS系统表中:

./console.py registerCNS [contract_name] [contract_address] [contract_version]

参数包括:

  • contract_name: 合约名
  • contract_address: 合约地址
  • contract_version: 合约版本
# 将合约地址0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce和合约版本v_1.0映射到合约名HelloWorld
$ ./console.py registerCNS HelloWorld 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce v_1.0

INFO >> user input : ['registerCNS', 'HelloWorld', '0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce', 'v_1.0']

INFO >> CNS version (strip space): v_1.0
INFO >> registerCNS
     >> status: 0x0
     >> transactionHash: 0x14720764a67c669811c02e9d9b4c7faa5ea94328e1e33fb7e35e885a27843a4e
     >> gasUsed: 0x6a98
     >> registerCNS succ, output: 1
queryCNSByName

根据合约名查询CNS信息:

./console.py queryCNSByName [contract_name]

参数包括:

  • contract_name:合约名
查询HelloWorld合约名对应的CNS信息
$ ./console.py queryCNSByName HelloWorld

INFO >> user input : ['queryCNSByName', 'HelloWorld']

     >> ('[{"abi":"\\"\\"","address":"0x2d1c577e41809453C50e7E5C3F57D06f3CDD90Ce","name":"HelloWorld","version":"v_1.0"}]\n',)
CNS ITEM 0 >>
        ContractName: HelloWorld
        ContractVersion: v_1.0
        ContractAddress: 0x2d1c577e41809453C50e7E5C3F57D06f3CDD90Ce
queryCNSByNameAndVersion

根据合约名和合约版本查询CNS信息:

./console.py queryCNSByNameAndVersion [contract_name] [contract_version]

参数包括:

  • contract_name: 合约名
  • contract_version: 合约版本
# 查询合约名为HelloWorld,版本为v_1.0的CNS信息
$ ./console.py queryCNSByNameAndVersion HelloWorld v_1.0

INFO >> user input : ['queryCNSByNameAndVersion', 'HelloWorld', 'v_1.0']

INFO >> CNS version (strip space): v_1.0
     >> ('[{"abi":"\\"\\"","address":"0x2d1c577e41809453C50e7E5C3F57D06f3CDD90Ce","name":"HelloWorld","version":"v_1.0"}]\n',)
CNS ITEM 0 >>
        ContractName: HelloWorld
        ContractVersion: v_1.0
        ContractAddress: 0x2d1c577e41809453C50e7E5C3F57D06f3CDD90Ce
节点管理

Python SDK提供了节点管理命令,包括添加共识节点、添加观察者节点、将节点从群组中删除,节点管理的详细资料可参考这里

removeNode

将指定该节点从群组中删除:

./console.py removeNode [nodeId]

参数包括:

  • nodeId:被删除节点的nodeID
# 设节点位于~/fisco/nodes目录,查询node1的nodeID
$ cat ~/fisco/nodes/127.0.0.1/node1/conf/node.nodeid
12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4
# 将节点1从群组中删除node1
./console.py removeNode 12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4

INFO >> user input : ['removeNode', '12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4']

INFO >> removeNode
     >> status: 0x0
     >> transactionHash: 0x68cde78d76f490b35431905d2336d9811966a370da8b4041db092feb09981f28
     >> gasUsed: 0x7698
     >> removeNode succ, output: 1
addSealer

将指定节点加入共识节点列表:

./console.py addSealer [nodeId]

参数包括:

  • nodeId: 加入的共识节点nodeID,获取节点nodeID可参考这里
# 设节点位于~/fisco/nodes目录,查询node1的nodeID
$ cat ~/fisco/nodes/127.0.0.1/node1/conf/node.nodeid
12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4

# 将节点node1加入为共识节点
$./console.py addSealer 12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4

INFO >> user input : ['addSealer', '12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4']

INFO >> addSealer
     >> status: 0x0
     >> transactionHash: 0xfddfa618419880e37f82c8cd385994fcb1ee1d4c5b4b506ae0d67f223c8b723d
     >> gasUsed: 0x7698
     >> addSealer succ, output: 1
addObserver

将指定节点加入为观察者节点:

./console.py addObserver [nodeId]

参数包括:

  • nodeId: 加入的观察者节点nodeID,获取节点nodeID可参考这里
# 设节点位于~/fisco/nodes目录,查询node1的nodeID
$ cat ~/fisco/nodes/127.0.0.1/node1/conf/node.nodeid
12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4

# 将节点node1加入为观察节点
$ ./console.py addObserver 12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4

INFO >> user input : ['addObserver', '12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4']

INFO >> addObserver
     >> status: 0x0
     >> transactionHash: 0xb126900787205a5f913e6643058359a07ace1cc550190a5a9478ae4f49cfc1eb
     >> gasUsed: 0x7658
     >> addObserver succ, output: 1
系统配置

Python SDK提供了系统配置修改命令,FISCO BCOS目前支持的系统配置参考这里

./console.py setSystemConfigByKey [key(tx_count_limit/tx_gas_limit)] [value]

参数包括:

  • key:配置关键字,目前主要包括tx_count_limittx_gas_limit
  • value: 配置关键字的值
# 将区块内最大交易数目调整为500
$ ./console.py setSystemConfigByKey tx_count_limit 500

INFO >> user input : ['setSystemConfigByKey', 'tx_count_limit', '500']

INFO >> setSystemConfigByKey
     >> status: 0x0
     >> transactionHash: 0xded8abc0858f8a7be5961ae38958928c98f75ee78dbe8197a47c382cb2549de1
     >> gasUsed: 0x5b58
     >> setSystemConfigByKey succ, output: 1

# 将交易gas限制调整为400000000
$ ./console.py setSystemConfigByKey tx_gas_limit 400000000

INFO >> user input : ['setSystemConfigByKey', 'tx_gas_limit', '400000000']

INFO >> setSystemConfigByKey
     >> status: 0x0
     >> transactionHash: 0x4b78868ec183c432e07971f578f5ab8222a9effda39dfa8e87643410cb2cea05
     >> gasUsed: 0x5c58
     >> setSystemConfigByKey succ, output: 1
权限管理

Python SDK提供了权限管理功能,包括授权、撤销权限和列出权限列表等,权限控制的详细内容可参考这里

grantPermissionManager

将控制权限的功能授权给指定账户:

./console.py grantPermissionManager [account_adddress]

参数包括:

  • account_adddress:被授予权限的账户地址,账户可通过newaccount命令生成
# 获取默认账户地址
./console.py showaccount pyaccount "123456"
INFO >> user input : ['showaccount', 'pyaccount', '123456']
show account : pyaccount, keyfile:bin/accounts/pyaccount.keystore ,password 123456
decrypt use time : 1.450 s
address:         0x95198B93705e394a916579e048c8A32DdFB900f7
privkey:         0x48140af2cf0879631d558833aa48b7bb4b37091dbfe902a573886538041b69c0
pubkey :         0x142d340c0f4df64bf56bbc0a3931e5228c7836add09cf8ff3cefeb3d7e610deb458ec871a9da86bae1ffc029f5aba41e725786ecb7f93ad2670303bf2db27b8a
account store in file: [bin/accounts/pyaccount.keystore]
**** please remember your password !!! *****

# 为账户0x95198B93705e394a916579e048c8A32DdFB900f7添加权限管理权限
$ ./console.py grantPermissionManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['grantPermissionManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> grantPermissionManager
     >> status: 0x0
     >> transactionHash: 0xdac11796dcfb663842a13333976626d844527605edb5bf9daadcfa28236bb5c8
     >> gasUsed: 0x6698
     >> grantPermissionManager succ, output: 1
listPermissionManager

列出有权限管理功能的账户信息:

# 列出所有权限管理账户信息
$ ./console.py listPermissionManager
INFO >> user input : ['listPermissionManager']
----->> ITEM 0
     = address: 0x95198B93705e394a916579e048c8A32DdFB900f7
     = enable_num: 9
grantUserTableManager

将给定用户表权限授予指定用户:

./console.py grantUserTableManager [tableName] [account_adddress]

注解

给用户授权用户表权限前,请确保用户表存在,可用 createTable 命令创建用户表

参数包括:

  • tableName: 用户表名
  • account_adddress:被授权用户账户地址
# 创建用户表t_test
$./console.py createTable t_test "key" "value1, value2, value3"
INFO >> user input : ['createTable', 't_test', 'key', 'value1, value2, value3']
INFO >> createTable
     >> status: 0x0
     >> transactionHash: 0xfbc10c0d9e4652f59655903e5ba772bb7f127e8e9de12be250d487f0ff9c5268
     >> gasUsed: 0x6098
     >> createTable succ, output: 0

# 为账户0x95198B93705e394a916579e048c8A32DdFB900f7对用户表t_test的管理功能
$ ./console.py grantUserTableManager t_test 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['grantUserTableManager', 't_test', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> table t_test
     >> key_field: key
     >> value_field: value1,value2,value3
INFO >> grantUserTableManager
     >> status: 0x0
     >> transactionHash: 0x2b9640f02db7afa839b5bdf158cca33a96a9718dc2e80f2c7b8af6100f6f8e92
     >> gasUsed: 0x6398
     >> grantUserTableManager succ, output: 1
listUserTableManager

列出对指定用户表有管理权限的账户信息:

./console.py listUserTableManager [tableName]

参数包括:

  • tableName: 用户表
# 查看用户表t_test的管理信息
$./console.py listUserTableManager t_test
INFO >> user input : ['listUserTableManager', 't_test']
----->> ITEM 0
     = address: 0x95198B93705e394a916579e048c8A32DdFB900f7
     = enable_num: 11
grantNodeManager

将节点管理权限授予指定账户:

./console.py grantNodeManager [account_adddress]

参数包括:

  • account_adddress:被授权用户账户地址
# 为账户0x95198B93705e394a916579e048c8A32DdFB900f7添加节点管理功能
$ ./console.py grantNodeManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['grantNodeManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> grantNodeManager
     >> status: 0x0
     >> transactionHash: 0x3a8839bfdfefcd3fff2678f91f231d44d8d442e40fc7f3af726daec624ba80c8
     >> gasUsed: 0x65d8
     >> grantNodeManager succ, output: 1
listNodeManager

列出有节点管理功能的账户信息:

$ ./console.py listNodeManager
INFO >> user input : ['listNodeManager']
----->> ITEM 0
     = address: 0x95198B93705e394a916579e048c8A32DdFB900f7
     = enable_num: 12
grantCNSManager

将CNS管理权限授予指定账户:

./console.py grantCNSManager [account_adddress]

参数包括:

  • account_adddress:被授权用户账户地址
# 为账户0x95198B93705e394a916579e048c8A32DdFB900f7添加CNS管理权限
$ ./console.py grantCNSManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['grantCNSManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> grantCNSManager
     >> status: 0x0
     >> transactionHash: 0x4a112be9f582fb1ae98ae9d6a84706930f4ab3523b45722cc4bf08341397dd1e
     >> gasUsed: 0x6458
     >> grantCNSManager succ, output: 1
listCNSManager

列出有CNS管理权限的账户信息

$ ./console.py listCNSManager

INFO >> user input : ['listCNSManager']

----->> ITEM 0
     = address: 0x95198B93705e394a916579e048c8A32DdFB900f7
     = enable_num: 13
grantSysConfigManager

将系统配置修改权限授予指定账户:

./console.py grantSysConfigManager [account_adddress]

参数包括:

  • account_adddress:被授权用户账户地址
# 为账户0x95198B93705e394a916579e048c8A32DdFB900f7添加系统配置权限
$ ./console.py grantSysConfigManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['grantSysConfigManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> grantSysConfigManager
     >> status: 0x0
     >> transactionHash: 0xf6ec040686496256a8c01233d1339ee147551f6a2dfcbd7bd6d7647f240f1411
     >> gasUsed: 0x6518
     >> grantSysConfigManager succ, output: 1
listSysConfigManager

列出有系统配置修改权限的账户信息:

$ ./console.py listSysConfigManager
INFO >> user input : ['listSysConfigManager']
----->> ITEM 0
     = address: 0x95198B93705e394a916579e048c8A32DdFB900f7
     = enable_num: 14
grantDeployAndCreateManager

将部署和创建表的权限授予指定账户:

./console.py grantDeployAndCreateManager [account_adddress]

参数包括:

  • account_adddress:被授权用户账户地址
# 为账户0x95198B93705e394a916579e048c8A32DdFB900f7添加创建表和部署合约权限
$./console.py grantDeployAndCreateManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['grantDeployAndCreateManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> grantDeployAndCreateManager
     >> status: 0x0
     >> transactionHash: 0xf60452a12d5346fa641bca6bee662c261fa0c67ef90aca3944cdb29a5803c625
     >> gasUsed: 0x6518
     >> grantDeployAndCreateManager succ, output: 1
listDeployAndCreateManager

列出有创建合约和用户表的账户信息:

$ ./console.py listDeployAndCreateManager
INFO >> user input : ['listDeployAndCreateManager']
----->> ITEM 0
     = address: 0x95198B93705e394a916579e048c8A32DdFB900f7
     = enable_num: 15
revokeUserTableManager

撤销指定用户对指定用户表的写入权限:

./console.py revokeUserTableManager [tableName] [account_adddress]

参数包括:

  • tableName:禁止指定用户写入的表名
  • account_adddress:被撤销权限的账户地址
# 撤销账户0x95198B93705e394a916579e048c8A32DdFB900f7对用户表t_test的控制权限
$ ./console.py revokeUserTableManager t_test 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['revokeUserTableManager', 't_test', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> revokeUserTableManager
     >> status: 0x0
     >> transactionHash: 0xc7ffbd0f79bfe06f43c603afde5997f9127a9fe499338362e64c653a593ded36
     >> gasUsed: 0x6398
     >> revokeUserTableManager succ, output: 1
revokeDeployAndCreateManager

撤销指定账户创建表、部署合约的权限:

./console.py revokeDeployAndCreateManager [account_adddress]

参数包括:

  • account_adddress:被撤销权限的账户地址
# 撤销账户0x95198B93705e394a916579e048c8A32DdFB900f7部署和创建表权限
$ ./console.py revokeDeployAndCreateManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['revokeDeployAndCreateManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> revokeDeployAndCreateManager
     >> status: 0x0
     >> transactionHash: 0xeac82f3464093f0659eb6412c39599d51b64082401ac43df9d7670cf17882f78
     >> gasUsed: 0x6518
     >> revokeDeployAndCreateManager succ, output: 1
revokeNodeManager

撤销指定账户的节点管理权限:

./console.py revokeNodeManager [account_adddress]

参数包括:

  • account_adddress:被撤销权限的账户地址
# 撤销账户0x95198B93705e394a916579e048c8A32DdFB900f7节点管理权限
$ ./console.py revokeNodeManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['revokeNodeManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> revokeNodeManager
     >> status: 0x0
     >> transactionHash: 0xc9f3799dc81a146f562fe10b493d14920676a8e49a6de94e7b4b998844198342
     >> gasUsed: 0x65d8
     >> revokeNodeManager succ, output: 1
revokeCNSManager

撤销指定账户CNS管理权限:

./console.py revokeCNSManager [account_adddress]

参数包括:

  • account_adddress:被撤销权限的账户地址
# 撤销账户0x95198B93705e394a916579e048c8A32DdFB900f7 CNS管理权限
$ ./console.py revokeCNSManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['revokeCNSManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> revokeCNSManager
     >> status: 0x0
     >> transactionHash: 0xa5aa6d115875156512af8c9974e353336e00bc3b9c2f2c2e21749d728e45abb4
     >> gasUsed: 0x6458
     >> revokeCNSManager succ, output: 1
revokeSysConfigManager

撤销指定账户修改系统配置权限:

./console.py revokeSysConfigManager [account_adddress]

参数包括:

  • account_adddress:被撤销权限的账户地址
# 撤销账户0x95198B93705e394a916579e048c8A32DdFB900f7系统表管理权限
$ ./console.py revokeSysConfigManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['revokeSysConfigManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> revokeSysConfigManager
     >> status: 0x0
     >> transactionHash: 0xfaffc25a4b111cfdaddca323d8b125c553c5e8f83b85fae1de21a6bc3bef792a
     >> gasUsed: 0x6518
     >> revokeSysConfigManager succ, output: 1
revokePermissionManager

撤销指定账户管理权限的权限:

./console.py revokePermissionManager [account_adddress]

参数包括:

  • account_adddress:被撤销权限的账户地址
# 撤销账户0x95198B93705e394a916579e048c8A32DdFB900f7权限管理权限
$ ./console.py revokePermissionManager 0x95198B93705e394a916579e048c8A32DdFB900f7
INFO >> user input : ['revokePermissionManager', '0x95198B93705e394a916579e048c8A32DdFB900f7']
INFO >> revokePermissionManager
     >> status: 0x0
     >> transactionHash: 0xa9398d4de7a3e86238a48bdbf5e053c61bc57ccd1aa57ebaa3c070bc47ea0f98
     >> gasUsed: 0x6698
     >> revokePermissionManager succ, output: 1
RPC

可以通过Python SDK查询节点信息,目前Python SDK支持的查询命令如下:

getNodeVersion

获取节点版本信息:

$ ./console.py getNodeVersion

INFO >> user input : ['getNodeVersion']

INFO >> getNodeVersion
     >> {
    "Build Time":"20200619 06:32:10",
    "Build Type":"Linux/clang/Release",
    "Chain Id":"1",
    "FISCO-BCOS Version":"2.5.0",
    "Git Branch":"HEAD",
    "Git Commit Hash":"72c6d770e5cf0f4197162d0e26005ec03d30fcfe",
    "Supported Version":"2.5.0"
}
getBlockNumber

获取节点最新块高:

$ ./console.py getBlockNumber
INFO >> user input : ['getBlockNumber']
INFO >> getBlockNumber
     >> 21
getPbftView

获取节点共识视图:

$ ./console.py getPbftView

INFO >> user input : ['getPbftView']

INFO >> getPbftView
     >> 0x34e
getSealerList

获取当前共识节点列表:

$ ./console.py getSealerList

INFO >> user input : ['getSealerList']

INFO >> getSealerList
     >> 3ad90ae5a10b8d88c9936492a37f564884e82b176e91f5e2e2f75a347be87212aac148ee7fa2060be8a790eaa3d44a299f94ba3d97adfa45526346902d64e0af
     >> 6bd07f2f8180ac9d56b40ff9977ba528a4f65e83d4ca95a0537e12f6638f78848e0765cbee0cb2b5f581d7eb5027d189f8691bfa92186bbf51deefd467339b6f
     >> b8783cfe3c073a532e9cbc47978d45a187da179d2fef4a85990c3b286d69d1afcd061de1b8cba07a59819d94f021db1e7707304908024f80e5830298e3829b82
getObserverList

获取观察者节点列表:

$ ./console.py getObserverList
INFO >> user input : ['getObserverList']
INFO >> getObserverList
     >> 12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4
getConsensusStatus

获取节点共识状态信息:

$ ./console.py getConsensusStatus

INFO >> user input : ['getConsensusStatus']

INFO >> getConsensusStatus
     >> {
    "accountType": 1,
    "allowFutureBlocks": true,
    "cfgErr": false,
    "connectedNodes": 3,
    "consensusedBlockNumber": 22,
    "currentView": 904,
    "groupId": 1,
    "highestblockHash": "0x2aa73c33c054eb168dd1cb5d62cd211c780731c3fe40333be0f32069568d0083",
    "highestblockNumber": 21,
    "leaderFailed": false,
    "max_faulty_leader": 0,
    "nodeId": "b8783cfe3c073a532e9cbc47978d45a187da179d2fef4a85990c3b286d69d1afcd061de1b8cba07a59819d94f021db1e7707304908024f80e5830298e3829b82",
    "nodeNum": 3,
    "node_index": 2,
    "omitEmptyBlock": true,
    "protocolId": 65544,
    ... 省略若干行 ...
}
getSyncStatus

获取节点同步状态信息:

$ ./console.py getSyncStatus

INFO >> user input : ['getSyncStatus']

INFO >> getSyncStatus
     >> {
    "blockNumber": 21,
    "genesisHash": "0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04",
    "isSyncing": false,
    "knownHighestNumber": 21,
    "knownLatestHash": "2aa73c33c054eb168dd1cb5d62cd211c780731c3fe40333be0f32069568d0083",
    "latestHash": "0x2aa73c33c054eb168dd1cb5d62cd211c780731c3fe40333be0f32069568d0083",
    "nodeId": "b8783cfe3c073a532e9cbc47978d45a187da179d2fef4a85990c3b286d69d1afcd061de1b8cba07a59819d94f021db1e7707304908024f80e5830298e3829b82",
    "peers": [
        {
            "blockNumber": 21,
            "genesisHash": "0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04",
            "latestHash": "0x2aa73c33c054eb168dd1cb5d62cd211c780731c3fe40333be0f32069568d0083",
            "nodeId": "12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4"
        },
        {
            "blockNumber": 21,
            "genesisHash": "0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04",
            "latestHash": "0x2aa73c33c054eb168dd1cb5d62cd211c780731c3fe40333be0f32069568d0083",
            "nodeId": "3ad90ae5a10b8d88c9936492a37f564884e82b176e91f5e2e2f75a347be87212aac148ee7fa2060be8a790eaa3d44a299f94ba3d97adfa45526346902d64e0af"
        },
        {
            "blockNumber": 21,
            "genesisHash": "0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04",
            "latestHash": "0x2aa73c33c054eb168dd1cb5d62cd211c780731c3fe40333be0f32069568d0083",
            "nodeId": "6bd07f2f8180ac9d56b40ff9977ba528a4f65e83d4ca95a0537e12f6638f78848e0765cbee0cb2b5f581d7eb5027d189f8691bfa92186bbf51deefd467339b6f"
        }
    ],
    "protocolId": 65545,
    "txPoolSize": "0"
}
getPeers

获取节点连接列表:

$ ./console.py getPeers
INFO >> user input : ['getPeers']
INFO >> getPeers
     >> {
    "Agency": "agency",
    "IPAndPort": "127.0.0.1:30301",
    "Node": "node1",
    "NodeID": "12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4",
    "Topic": []
}
     >> {
    "Agency": "agency",
    "IPAndPort": "127.0.0.1:30302",
    "Node": "node2",
    "NodeID": "6bd07f2f8180ac9d56b40ff9977ba528a4f65e83d4ca95a0537e12f6638f78848e0765cbee0cb2b5f581d7eb5027d189f8691bfa92186bbf51deefd467339b6f",
    "Topic": []
}
     >> {
    "Agency": "agency",
    "IPAndPort": "127.0.0.1:30303",
    "Node": "node3",
    "NodeID": "3ad90ae5a10b8d88c9936492a37f564884e82b176e91f5e2e2f75a347be87212aac148ee7fa2060be8a790eaa3d44a299f94ba3d97adfa45526346902d64e0af",
    "Topic": []
}
getGroupPeers

获取群组内节点连接信息:

$ ./console.py getGroupPeers
INFO >> user input : ['getGroupPeers']
INFO >> getGroupPeers
     >> 3ad90ae5a10b8d88c9936492a37f564884e82b176e91f5e2e2f75a347be87212aac148ee7fa2060be8a790eaa3d44a299f94ba3d97adfa45526346902d64e0af
     >> 6bd07f2f8180ac9d56b40ff9977ba528a4f65e83d4ca95a0537e12f6638f78848e0765cbee0cb2b5f581d7eb5027d189f8691bfa92186bbf51deefd467339b6f
     >> b8783cfe3c073a532e9cbc47978d45a187da179d2fef4a85990c3b286d69d1afcd061de1b8cba07a59819d94f021db1e7707304908024f80e5830298e3829b82
     >> 12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4
getNodeIDList

获取区块链所有组网节点列表:

$ ./console.py getNodeIDList
INFO >> user input : ['getNodeIDList']
INFO >> getNodeIDList
     >> b8783cfe3c073a532e9cbc47978d45a187da179d2fef4a85990c3b286d69d1afcd061de1b8cba07a59819d94f021db1e7707304908024f80e5830298e3829b82
     >> 12ce3fc76bc3253ba9be25dc3adb8b75df392583b8f2813f4c623cff258980c8c2c73f384ce6f37dca7261ea0a9fb24ff59fa3c58ee8f278be009827114500e4
     >> 6bd07f2f8180ac9d56b40ff9977ba528a4f65e83d4ca95a0537e12f6638f78848e0765cbee0cb2b5f581d7eb5027d189f8691bfa92186bbf51deefd467339b6f
     >> 3ad90ae5a10b8d88c9936492a37f564884e82b176e91f5e2e2f75a347be87212aac148ee7fa2060be8a790eaa3d44a299f94ba3d97adfa45526346902d64e0af
getGroupList

获取群组列表:

$ ./console.py getGroupList
INFO >> user input : ['getGroupList']
INFO >> getGroupList
     >> 1
getPendingTransactions

获取交易池内还未上链的交易信息:

$ ./console.py getPendingTransactions
INFO >> user input : ['getPendingTransactions']
INFO >> getPendingTransactions
     >> Empty Set
getPendingTxSize

获取交易池内还未上链的交易数目:

$ ./console.py getPendingTxSize
INFO >> user input : ['getPendingTxSize']
INFO >> getPendingTxSize
     >> 0x0
getTotalTransactionCount

获取已经上链的交易数目:

$ ./console.py getTotalTransactionCount
INFO >> user input : ['getTotalTransactionCount']
INFO >> getTotalTransactionCount
     >> {
    "blockNumber": "0x16",
    "failedTxSum": "0x0",
    "txSum": "0x16"
     }
getBlockByNumber

根据块高查询区块:

$ ./console.py getBlockByNumber [block_number] [True/False]

参数包括:

  • block_number:区块高度
  • True/False: 可选,True表明返回的区块信息内包含具体的交易信息;False表明返回的区块内仅包含交易哈希
$ ./console.py getBlockByNumber 0

INFO >> user input : ['getBlockByNumber', '0']

INFO >> getBlockByNumber
     >> {
    "dbHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData": [
        "0x312d62383738336366653363303733613533326539636263343739373864
        ... 省略若干行...
        7652d313030302d333030303030303030"
    ],
    "gasLimit": "0x0",
    "gasUsed": "0x0",
    "hash": "0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04",
    "logsBloom": "0x00000000... 省略若干行...0000000000000000000000",
    "number": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "sealer": "0x0",
    "sealerList": [],
    "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp": "0x16c61113388",
    "transactions": [],
    "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
getBlockHashByNumber

根据块高查询区块哈希:

$ ./console.py getBlockHashByNumber 0
INFO >> user input : ['getBlockHashByNumber', '0']
INFO >> getBlockHashByNumber
     >> 0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04
getBlockByHash

根据区块哈希获取区块信息:

$ ./console.py getBlockByHash [block_hash] [True/False]

参数包括:

  • block_hash:区块哈希
  • True/False: 可选,True表明返回的区块内包含交易具体信息;False表明返回的区块仅包含交易哈希
$ ./console.py getBlockByHash 0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04
INFO >> user input : ['getBlockByHash', '0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04']
INFO >> getBlockByHash
     >> {
    "extraData": [
        "0x312d623...省略若干...3030303030"
    ],
    "gasLimit": "0x0",
    "gasUsed": "0x0",
    "hash": "0xff1404962c6c063a98cc9e6a20b408e6a612052dc4267836bb1dc378acc6ce04",
    "logsBloom": "0x0000...省略若干...000000",
    "number": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "sealer": "0x0",
    "sealerList": [],
    "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp": "0x16c61113388",
    "transactions": [],
    "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
getCode

获取指定合约的二进制编码:

$ ./console.py getCode 0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce
INFO >> user input : ['getCode', '0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce']
INFO >> getCode
     >> 0x60806040526...省略若干...a40029
getTransactionByHash

根据交易哈希获取交易信息:

./console.py getTransactionByHash [hash] [contract_name]

参数包括:

  • hash: 交易哈希
  • contract_name:可选,该交易相关的合约名,若输入该参数,会解析返回交易的具体内容
$ ./console.py getTransactionByHash 0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91
INFO >> user input : ['getTransactionByHash', '0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91']
INFO >> getTransactionByHash
     >> {
    "blockHash": "0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d",
    "blockNumber": "0x1",
    "from": "0x95198b93705e394a916579e048c8a32ddfb900f7",
    "gas": "0x1c9c380",
    "gasPrice": "0x1c9c380",
    "hash": "0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91",
    "input": "0x60806...省略若干...ddd81c4a40029",
    "nonce": "0x2b2350c8",
    "to": "0x0000000000000000000000000000000000000000",
    "transactionIndex": "0x0",
    "value": "0x0"
}
getTransactionReceipt

根据交易哈希获取交易回执信息:

./console.py getTransactionReceipt [hash] [contract_name]

参数包括:

  • hash:交易哈希
  • contract_name:可选,该交易相关的合约名,若输入该参数,会解析交易和回执的具体内容
$ ./console.py getTransactionReceipt 0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91
INFO >> user input : ['getTransactionReceipt', '0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91']
INFO >> getTransactionReceipt
     >> {
    "blockHash": "0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d",
    "blockNumber": "0x1",
    "contractAddress": "0x2d1c577e41809453c50e7e5c3f57d06f3cdd90ce",
    "from": "0x95198b93705e394a916579e048c8a32ddfb900f7",
    "gasUsed": "0x44ab3",
    "input": "0x608060405234...省略若干...d9acf16e2fc2d570d491ddd81c4a40029",
    "logs": [],
    "logsBloom": "0x00000...省略若干...00000000000",
    "output": "0x",
    "status": "0x0",
    "to": "0x0000000000000000000000000000000000000000",
    "transactionHash": "0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91",
    "transactionIndex": "0x0"
}
getTransactionByBlockHashAndIndex

根据区块哈希和交易索引查询交易信息:

./console.py getTransactionByBlockHashAndIndex [blockHash] [transactionIndex] [contract_name]

参数包括:

  • blockHash: 交易所在的区块哈希
  • transactionIndex:交易索引
  • contract_name:可选,该交易相关的合约名,若输入该参数,会解析返回交易的具体内容
$  ./console.py getTransactionByBlockHashAndIndex 0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d 0

INFO >> user input : ['getTransactionByBlockHashAndIndex', '0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d', '0']

INFO >> getTransactionByBlockHashAndIndex
     >> {
    "blockHash": "0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d",
    "blockNumber": "0x1",
    "from": "0x95198b93705e394a916579e048c8a32ddfb900f7",
    "gas": "0x1c9c380",
    "gasPrice": "0x1c9c380",
    "hash": "0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91",
    "input": "0x6080...省略若干...4a40029",
    "nonce": "0x2b2350c8",
    "to": "0x0000000000000000000000000000000000000000",
    "transactionIndex": "0x0",
    "value": "0x0"
}
getTransactionByBlockNumberAndIndex

根据块高和交易索引查询交易信息:

$ ./console.py getTransactionByBlockNumberAndIndex [blockNumber] [transactionIndex] [contract_name]

参数包括:

  • blockNumber:交易所在的区块块高
  • transactionIndex:交易索引
  • contract_name:可选,该交易相关的合约名,若输入该参数,会解析返回交易的具体内容
$ ./console.py getTransactionByBlockNumberAndIndex 1 0
INFO >> user input : ['getTransactionByBlockNumberAndIndex', '1', '0']
INFO >> getTransactionByBlockNumberAndIndex
    >> {
   "blockHash": "0x3912605dde5f7358fee40a85a8b97ba6493848eae7766a8c317beecafb2e279d",
   "blockNumber": "0x1",
   "from": "0x95198b93705e394a916579e048c8a32ddfb900f7",
   "gas": "0x1c9c380",
   "gasPrice": "0x1c9c380",
   "hash": "0xb291e9ca38b53c897340256b851764fa68a86f2a53cb14b2ecdcc332e850bb91",
   "input": "0x608060...省略若干...a40029",
   "nonce": "0x2b2350c8",
   "to": "0x0000000000000000000000000000000000000000",
   "transactionIndex": "0x0",
   "value": "0x0"
}
getSystemConfigByKey

获取系统配置信息:

# 获取区块可打包最大交易数目
$ ./console.py getSystemConfigByKey tx_count_limit
INFO >> user input : ['getSystemConfigByKey', 'tx_count_limit']
INFO >> getSystemConfigByKey tx_count_limit
     >> 500
# 获取系统gas限制
$ ./console.py getSystemConfigByKey  tx_gas_limit
INFO >> user input : ['getSystemConfigByKey', 'tx_gas_limit']
INFO >> getSystemConfigByKey tx_gas_limit
     >> 400000000

开发样例

Python SDK的源码中提供了完整的Demo供开发者学习

调用节点API

正确的配置了SDK连接的节点信息后。在代码中实例化client结构,并调用client的接口即可。返回json,可以根据对fisco bcos rpc接口json格式的理解,进行字段获取和转码。

完整Demo: demo_get.py

# 实例化client
client = BcosClient()

# 调用查节点版本接口
res = client.getNodeVersion()
print("getClientVersion",res)

# 调用查节点块高接口
try:
    res = client.getBlockNumber()
    print("getBlockNumber",res)
except BcosError as e:
    print("bcos client error,",e.info())
操作合约

正确的配置了SDK连接的节点信息后。可进行部署合约、发送交易、处理回执、查询合约数据的操作。按照举例,调用deploysendRawTransactionGetReceiptcallparse_event_logs等函数。

完整Demo: demo_transaction.py

# 实例化client
client = BcosClient()

# 从文件加载abi定义
abi_file  ="contracts/SimpleInfo.abi"
data_parser = DatatypeParser()
data_parser.load_abi_file(abi_file)
contract_abi = data_parser.contract_abi

# 部署合约
print("\n>>Deploy:---------------------------------------------------------------------")
with open("contracts/SimpleInfo.bin", 'r') as load_f:
    contract_bin = load_f.read()
    load_f.close()
result = client.deploy(contract_bin)
print("deploy",result)
print("new address : ",result["contractAddress"])
contract_name =  os.path.splitext(os.path.basename(abi_file))[0]
memo = "tx:"+result["transactionHash"]
#把部署结果存入文件备查
from client.contractnote import ContractNote
ContractNote.save_address(contract_name, result["contractAddress"], int(result["blockNumber"], 16), memo)


#发送交易,调用一个改写数据的接口
print("\n>>sendRawTransaction:----------------------------------------------------------")
to_address = result['contractAddress'] #use new deploy address
args = ['simplename', 2024, to_checksum_address('0x7029c502b4F824d19Bd7921E9cb74Ef92392FB1c')]

receipt = client.sendRawTransactionGetReceipt(to_address,contract_abi,"set",args)
print("receipt:",receipt)

#解析receipt里的log
print("\n>>parse receipt and transaction:----------------------------------------------------------")
txhash = receipt['transactionHash']
print("transaction hash: " ,txhash)
logresult = data_parser.parse_event_logs(receipt["logs"])
i = 0
for log in logresult:
    if 'eventname' in log:
        i = i + 1
        print("{}): log name: {} , data: {}".format(i,log['eventname'],log['eventdata']))
#获取对应的交易数据,解析出调用方法名和参数

txresponse = client.getTransactionByHash(txhash)
inputresult = data_parser.parse_transaction_input(txresponse['input'])
print("transaction input parse:",txhash)
print(inputresult)

#解析该交易在receipt里输出的output,即交易调用的方法的return值
outputresult  = data_parser.parse_receipt_output(inputresult['name'], receipt['output'])
print("receipt output :",outputresult)


#调用一下call,获取数据
print("\n>>Call:------------------------------------------------------------------------")
res = client.call(to_address,contract_abi,"getbalance")
print("call getbalance result:",res)
res = client.call(to_address,contract_abi,"getbalance1",[100])
print("call getbalance1 result:",res)
res = client.call(to_address,contract_abi,"getname")
print("call getname:",res)
res = client.call(to_address,contract_abi,"getall")
print("call getall result:",res)
print("demo_tx,total req {}".format(client.request_counter))
client.finish()

Go SDK

Go SDK 提供了访问 FISCO BCOS 节点的Go API,支持节点状态查询、部署和调用合约等功能,基于Go SDK可快速开发区块链应用,目前支持 FISCO BCOS 2.2.0+

注意

  • Go SDK目前处于个人开发者体验阶段,开发企业级应用请参考 Web3SDK

主要特性

  • 提供调用FISCO BCOS JSON-RPC 的Go API
  • 提供合约编译,将Solidity合约文件编译成abi和bin文件,然后再转换成Go合约文件
  • 提供部署及调用go合约文件的GO API
  • 提供调用预编译(Precompiled)合约的Go API
  • 提供与FISCO BCOS节点通信的 Channel协议,双向认证更安全
  • 提供CLI(Command-Line Interface)工具,供用户在命令行中方便快捷地与区块链交互

环境和配置文件

环境
  • Go开发环境
    • Golang >= 1.13.6
    • 项目采用 go module 进行包管理,可参考 Using Go Modules
    • 如果您没有部署过Go环境,可参考 官方文档
  • 基本开发组件
    • Git(Windows、Linux及MacOS需要)
    • Git bash(仅Windows需要)
配置文件

Go SDK 的配置文件为一个 TOML 文件,主要包括网络配置账户配置以及链配置。配置文件 config.toml 示例如下:

[Network]
Type="channel"
CAFile="ca.crt"
Cert="sdk.crt"
Key="sdk.key"
[[Network.Connection]] 
NodeURL="127.0.0.1:20200"
GroupID=1
# [[Network.Connection]]
# NodeURL="127.0.0.1:20200"
# GroupID=2

[Account]
KeyFile=".ci/0x83309d045a19c44dc3722d15a6abd472f95866ac.pem"

[Chain]
ChainID=1
SMCrypto=false
网络配置

网络配置主要用于设置 网络连接模式证书文件 和待连接的 节点信息,支持设置多个节点。

  • Type:是Go SDK与区块链节点建立连接的模式,支持channel和rpc两种方式;
    • channel:使用ssl协议建立连接,需要提供ca.crt、sdk.crt、sdk.key证书;
    • rpc:使用http协议建立连接,不需要提供证书;
  • CAfile:CA根证书文件路径,用于验证待连接节点的合法性;
  • Cert:SDK证书文件路径,用于待连接节点验证SDK的合法性;
  • Key:SDK私钥文件路径,Cert证书对应的私钥,用于加解密和签名;
  • NodeURL:待连接节点的URL地址,由IP和port两部分组成;
  • GroupID:待连接节点所属的群组ID。
账户配置
  • KeyFile:外部账户的私钥文件路径,目前只支持pem格式的私钥文件。国密和非国密账户脚本可从get_account.shget_gm_account.sh下载,使用方式可参考账户管理。此外,Go SDK代码也支持生成账号,参考这里了解更多。
链配置
  • ChainID:待连接节点所属的链ID,可通过查看节点config.ini配置文件中chain.id配置项获得;
  • SMCrypto:待连接节点所属链使用的签名算法,ture表示使用国密SM2,false表示使用普通ECDSA。

Go API

Go SDK为区块链应用开发者提供了Go API接口,以服务的形式供外部调用。按照功能,Go API可以分为如下几类:

  • ApiHandler:提供访问FISCO BCOS 2.0+节点JSON-RPC接口支持、提供部署及调用合约的支持;
  • PrecompiledService:Precompiled合约(预编译合约)是一种FISCO BCOS底层内嵌的、通过C++实现的高效智能合约,提供包括 基于表的权限控制CNS、系统属性配置、节点类型配置、用户表 CRUD、基于角色的权限控制、合约生命周期权限控制等功能。PrecompiledService是调用这类功能的API的统称,分为:
    • PermissionService:提供对分布式权限控制的支持;
    • CNSService:提供对CNS的支持;
    • SystemConfigService:提供对系统配置的支持;
    • ConsensusService:提供对节点类型配置的支持;
    • CRUDService:提供对CRUD(增删改查)操作的支持,可以创建表或对表进行增删改查操作;
    • ChainGovernanceService:提供基于角色的权限控制支持;
    • ContractLifeCycleService:提供合约生命周期权限控制支持。
Client

位置:go-sdk/client/goclient.go

功能:访问FISCO BCOS 2.0+节点JSON-RPC

接口名 描述 参数
GetClientVersion 获取区块链节点版本信息
GetBlockNumber 获取最新块高
GetPbftView 获取PBFT视图
GetBlockLimit 获取最新区块高度限制
GetSealerList 获取共识节点列表
GetObserverList 获取观察者节点列表
GetConsensusStatus 获取区块链节点共识状态
GetSyncStatus 获取区块链节点同步状态
GetPeers 获取区块链节点的连接信息
GetGroupPeers 获取指定群组的共识节点和观察节点列表
GetNodeIDList 获取节点及其连接节点的列表
GetGroupList 获取节点所属群组的群组ID列表
GetBlockByHash 根据区块哈希获取区块信息 区块哈希 & bool
GetBlockByNumber 根据区块高度获取区块信息 区块高度 & bool
GetBlockHashByNumber 根据区块高度获取区块哈希 区块高度
GetTransactionByHash 根据交易哈希获取交易信息 交易哈希
GetTransactionByBlockHashAndIndex 根据交易所属的区块哈希、 交易索引获取交易信息 交易所属的区块哈希 & 交易索引
GetTransactionByBlockNumberAndIndex 根据交易所属的区块高度、 交易索引获取交易信息 交易所属的区块高度 & 交易索引
GetTransactionReceipt 根据交易哈希获取交易回执 交易哈希
GetContractAddress 根据部署合约时产生的交易地址获取合约地址 交易哈希
GetPendingTransactions 获取交易池内所有未上链的交易
GetPendingTxSize 获取交易池内未上链的交易数目
GetCode 根据合约地址查询合约数据 合约地址
GetTotalTransactionCount 获取指定群组的上链交易数目
GetSystemConfigByKey 根据关键字获取区块链系统配置 系统配置关键字,目前支持:
- tx_count_limit
- tx_gas_limit
- rpbft_epoch_sealer_num
- rpbft_epoch_block_num
Call 调用只读合约 合约地址
调用接口*
参数列表
SendRawTransaction 发送一个经过签名的交易,该交易随后会被链上节点执行并共识 群组ID & 已签名交易

注解

  • 如果用户试图尝试使用一个 sdk 连接多个群组,可以利用 APIHandler 中暴露的接口,详细内容可阅读源码 [go-sdk](https://github.com/FISCO-BCOS/go-sdk)
PermissionService

位置:go-sdk/precompiled/permission

功能:提供对基于表的权限控制支持

接口名 描述 参数
GrantUserTableManager 根据用户表名和外部账户地址设置权限信息 表名 & 外部账户地址
RevokeUserTableManager 根据用户表名和外部账户地址去除权限信息 表名 & 外部账户地址
ListUserTableManager 根据用户表名查询设置的权限记录列表(每条记录包含外部账户地址和生效块高) 表名
GrantDeployAndCreateManager 增加外部账户地址部署合约和创建用户表的权限 外部账户地址
RevokeDeployAndCreateManager 移除外部账户地址部署合约和创建用户表的权限 外部账户地址
ListDeployAndCreateManager 查询拥有部署合约和创建用户表权限的记录列表
GrantPermissionManager 授予外部账户地址链管理员权限,链管理员可以使用权限分配功能 外部账户地址
RevokePermissionManager 撤销链外部账户地址链管理员权限 外部账户地址
ListPermissionManager 查询拥有链管理权限的记录列表
GrantNodeManager 增加外部账户地址的节点管理权限 外部账户地址
RevokeNodeManager 移除外部账户地址的节点管理权限 外部账户地址
ListNodeManager 查询拥有节点管理的权限记录列表
GrantCNSManager 增加外部账户地址使用CNS的权限 外部账户地址
RevokeCNSManager 移除外部账户地址使用CNS的权限 外部账户地址
ListCNSManager 查询拥有使用CNS权限的记录列表
GrantSysConfigManager 增加外部账户地址的系统参数管理权限 外部账户地址
RevokeSysConfigManager 移除外部账户地址的系统参数管理权限 外部账户地址
ListSysConfigManager 查询拥有系统参数管理的权限记录列表
CNSService

位置:go-sdk/precompiled/cns

功能:提供对CNS的支持

接口名 描述 参数
RegisterCns 根据合约名、合约版本号、合约地址和合约abi注册CNS信息 合约名 & 合约版本号 & 合约地址 & 合约abi
GetAddressByContractNameAndVersion 根据合约名和合约版本号(合约名和合约版本号用英文冒号连接)查询合约地址。若缺失合约版本号,默认使用合约最新版本 合约名 + ':' + 版本号
QueryCnsByName 根据合约名查询CNS信息 合约名
QueryCnsByNameAndVersion 根据合约名和版本号查询CNS信息 合约名 & 版本号
SystemConfigService

位置:go-sdk/precompiled/config

功能:提供修改系统配置项的功能,目前支持的配置项有 tx_count_limit、tx_gas_limit、rpbft_epoch_sealer_num 和 rpbft_epoch_block_num

接口名 描述 参数
SetValueByKey 根据键设置对应的值(查询键对应的值,参考ApiHandler中的 getSystemConfigByKey 接口) 键名 & 值
ConsensusService

位置:go-sdk/precompiled/consensus

功能:提供对节点类型配置的支持

接口名 描述 参数
AddSealer 根据节点NodeID设置对应节点为共识节点 节点ID
AddObserver 根据节点NodeID设置对应节点为观察者节点 节点ID
RemoveNode 根据节点NodeID设置对应节点为游离节点 节点ID
CRUDService

位置:go-sdk/precompiled/crud

功能:提供对CRUD(增删改查)操作的支持

接口名 描述 参数
CreateTable 创建表 表对象:表对象需要设置其表名,主键字段名和其他字段名。其中,其他字段名是以英文逗号分隔拼接的字符串
Insert 插入记录 表对象:表对象需要设置表名和主键字段值
Entry对象:Entry是map对象,提供插入的字段名和字段值
Select 查询记录 表对象:表对象需要设置表名和主键字段值
Condtion对象:Condition对象是条件对象,可以设置查询的匹配条件
Update 更新记录 表对象:表对象需要设置表名和主键字段值
Entry对象:Entry是map对象,提供待更新的字段名和字段值
Condtion对象:Condition对象是条件对象,可以设置查询的匹配条件
Remove 移除记录 表对象 & 条件对象
Desc 根据表名查询表的信息 表名
ChainGovernanceService

位置:go-sdk/precompiled/chaingovernance

功能:提供对基于角色的权限控制支持

接口名 描述 参数
GrantCommitteeMember 根据外部账户地址新增委员会成员 外部账户地址
RevokeCommitteeMember 根据外部账户地址撤销委员会成员 外部账户地址
ListCommitteeMembers 查询委员会成员列表
QueryCommitteeMemberWeight 根据外部账户地址查询委员会成员投票权值 外部账户地址
UpdateCommitteeMemberWeight 根据外部账户地址更新委员会成员投票权值 外部账户地址 & 权值(大于 0 的整数)
UpdateThreshold 更新委员会全体委员投票时票数占比生效的阈值 阈值 [0, 99)
QueryThreshold 查询委员会全体委员投票时票数占比生效的阈值
GrantOperator 根据外部账户地址新增运维权限 外部账户地址
RevokeOperator 根据外部账户地址撤销运维权限 外部账户地址
ListOperators 查询全体运维成员列表
FreezeAccount 根据外部账户地址冻结账户,该外部账号需要是部署过合约的账号 外部账户地址
UnfreezeAccount 根据外部账户地址解冻合账户 外部账户地址
GetAccountStatus 根据外部账户地址查询账户状态 外部账户地址
ContractLifeCycleService

位置:go-sdk/precompiled/contractlifecycle

功能:提供对合约生命周期操作的支持

接口名 描述 参数
Freeze 根据合约地址冻结合约 合约地址
Unfreeze 根据合约地址解冻合约 合约地址
GrantManager 根据合约地址和外部账户地址授予账户合约管理权限 合约地址 & 外部账户地址
GetStatus 根据合约地址查询合约状态 合约地址
ListManager 查询拥有合约地址管理权限的外部账号 合约地址

控制台

Go SDK 通过 console.go 实现了一个简单的控制台,支持区块链和节点信息查询。

使用
  • 拉取代码并编译
# 拉取代码
git clone https://github.com/FISCO-BCOS/go-sdk.git
# 切换目录
cd go-sdk
# 编译 cmd/console.go
go build cmd/console.go
  • 搭建FISCO BCOS 2.2以上版本节点,请 参考
  • config.toml 配置文件默认使用 channel 连接模式,请拷贝对应的 ca.crt、sdk.crt 和 sdk.key 证书至 go-sdk 目录
  • FISCO-BCOS 2.5及之后的版本,添加了SDK只能连本机构节点的限制,操作时需确认拷贝证书的路径,否则建联报错
  • 开启命令行支持:

注解

  • 此步骤只需设置一次,设置之后重启终端即可使用
# bash 命令行自动补全,请在 bash 环境下执行此步骤
./console completion bash > go_sdk_completion && sudo mv go_sdk_completion /etc/bash_completion.d/
# zsh 命令行自动补全,请在 zsh 环境下执行此步骤
./console completion zsh > ~/.go-sdk-completion.sh && echo 'source ~/.go-sdk-completion.sh' >> ~/.zshrc
getBlockByHash

根据区块哈希获取区块信息:

./console getBlockByHash [blockHash] [true/false]

参数包括:

  • blockHash:区块 hash 值;
  • true/false:true 会返回区块中所有交易的详细内容,false 只会返回区块中所有交易的 hash 值,默认为 true。
> ./console getBlockByHash 0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848 true

Block:
{
  "extraData": [],
  "gasLimit": "0x0",
  "gasUsed": "0x0",
  "hash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "number": "0x3",
  "parentHash": "0x57016509418eb81c0353b1252a364383fcfc5c71035c8a01d24e785ac6e2ce4a",
  "receiptsRoot": "0x4e430ca6474d7013a819a7c602497b7bfdfa14a1197a1edc35444b756cf7e6fc",
  "sealer": "0x1",
  "sealerList": [
    "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
    "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
    "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
    "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0"
  ],
  "stateRoot": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
  "timestamp": "0x172fe19b855",
  "transactions": [
    {
      "blockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
      "blockNumber": "0x3",
      "from": "0x4ca29e9e8cb79c863c04f83827ab540315f25e67",
      "gas": "0x11e1a300",
      "gasPrice": "0x11e1a300",
      "hash": "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434",
      "input": "0x2800efc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000803432616532616533393530613239333362366635373664633934366239306232343262376639613261386434356161653533633161313636346364353832626437353966613639653462353266376134353364636537303238373862396566313166643334666365316264313563396264636162636262656534336531333032",
      "nonce": "0x359fbc4677e4f4ca87a96a31372b1194f03ba200db94a18ad0b30f2e858ac32",
      "to": "0x0000000000000000000000000000000000001003",
      "transactionIndex": "0x0",
      "value": "0x0"
    }
  ],
  "transactionsRoot": "0xcf057dc481d7a97700e93a1ea65f331c3cfee2fee80e3bb80c30748e4988fe9d"
}

> ./console getBlockByHash 0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848 false

Block:
{
  "extraData": [],
  "gasLimit": "0x0",
  "gasUsed": "0x0",
  "hash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "number": "0x3",
  "parentHash": "0x57016509418eb81c0353b1252a364383fcfc5c71035c8a01d24e785ac6e2ce4a",
  "receiptsRoot": "0x4e430ca6474d7013a819a7c602497b7bfdfa14a1197a1edc35444b756cf7e6fc",
  "sealer": "0x1",
  "sealerList": [
    "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
    "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
    "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
    "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0"
  ],
  "stateRoot": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
  "timestamp": "0x172fe19b855",
  "transactions": [
    "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434"
  ],
  "transactionsRoot": "0xcf057dc481d7a97700e93a1ea65f331c3cfee2fee80e3bb80c30748e4988fe9d"
}
getBlockByNumber

根据区块高度获取区块信息:

./console getBlockByNumber [blockNumber] [true/false]

参数包括:

  • blockNumber:区块高度;
  • true/false:true 会返回区块中所有交易的详细内容,false 只会返回区块中所有交易的 hash 值,默认为 true。
> ./console getBlockByNumber 3 true

Block:
{
  "dbHash": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
  "extraData": [],
  "gasLimit": "0x0",
  "gasUsed": "0x0",
  "hash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "number": "0x3",
  "parentHash": "0x57016509418eb81c0353b1252a364383fcfc5c71035c8a01d24e785ac6e2ce4a",
  "receiptsRoot": "0x4e430ca6474d7013a819a7c602497b7bfdfa14a1197a1edc35444b756cf7e6fc",
  "sealer": "0x1",
  "sealerList": [
    "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
    "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
    "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
    "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0"
  ],
  "stateRoot": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
  "timestamp": "0x172fe19b855",
  "transactions": [
    {
      "blockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
      "blockNumber": "0x3",
      "from": "0x4ca29e9e8cb79c863c04f83827ab540315f25e67",
      "gas": "0x11e1a300",
      "gasPrice": "0x11e1a300",
      "hash": "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434",
      "input": "0x2800efc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000803432616532616533393530613239333362366635373664633934366239306232343262376639613261386434356161653533633161313636346364353832626437353966613639653462353266376134353364636537303238373862396566313166643334666365316264313563396264636162636262656534336531333032",
      "nonce": "0x359fbc4677e4f4ca87a96a31372b1194f03ba200db94a18ad0b30f2e858ac32",
      "to": "0x0000000000000000000000000000000000001003",
      "transactionIndex": "0x0",
      "value": "0x0"
    }
  ],
  "transactionsRoot": "0xcf057dc481d7a97700e93a1ea65f331c3cfee2fee80e3bb80c30748e4988fe9d"
}

> ./console getBlockByNumber 3 false

Block:
{
  "dbHash": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
  "extraData": [],
  "gasLimit": "0x0",
  "gasUsed": "0x0",
  "hash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "number": "0x3",
  "parentHash": "0x57016509418eb81c0353b1252a364383fcfc5c71035c8a01d24e785ac6e2ce4a",
  "receiptsRoot": "0x4e430ca6474d7013a819a7c602497b7bfdfa14a1197a1edc35444b756cf7e6fc",
  "sealer": "0x1",
  "sealerList": [
    "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
    "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
    "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
    "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0"
  ],
  "stateRoot": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
  "timestamp": "0x172fe19b855",
  "transactions": [
    "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434"
  ],
  "transactionsRoot": "0xcf057dc481d7a97700e93a1ea65f331c3cfee2fee80e3bb80c30748e4988fe9d"
}
getBlockHashByNumber

根据区块高度获取区块哈希:

./console getBlockHashByNumber [blockNumber]

参数包括:

  • blockNumber:区块高度。
> ./console getBlockHashByNumber 3

Block Hash:
"0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848"
getBlockNumber

获取最新块高:

> ./console getBlockNumber

blocknumber:
    hex: "0x3"
decimal:  3
getClientVersion

获取区块链节点版本信息:

> ./console getClientVersion

Client Version:
{
  "Build Time": "20200610 15:42:05",
  "Build Type": "Linux/g++/RelWithDebInfo",
  "Chain Id": "1",
  "FISCO-BCOS Version": "2.5.0",
  "Git Branch": "master",
  "Git Commit Hash": "b0978f773ca1dbb499a4343b9fb3a12c40b8fc97",
  "Supported Version": "2.4.0"
}
getCode

根据合约地址查询合约数据:

./console getCode [contract address]

参数包括:

  • contract address:合约地址。
> ./console getCode 0x65474dbd4f08170bc2dc30f9ae32f8e2206b15a6

Contract Code:
"0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634ed3885e146100515780636d4ce63c146100ba575b600080fd5b34801561005d57600080fd5b506100b8600480360381019080803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061014a565b005b3480156100c657600080fd5b506100cf610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010f5780820151818401526020810190506100f4565b50505050905090810190601f16801561013c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b8060009080519060200190610160929190610206565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101fc5780601f106101d1576101008083540402835291602001916101fc565b820191906000526020600020905b8154815290600101906020018083116101df57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820d0c58adfbd1215902f16e710a4e52b14e5c9ad7f0f3363c86d2b3156894bd0610029"
getConsensusStatus

获取区块链节点共识状态

> ./console getConsensusStatus

Consensus Status:
[
  {
    "accountType": 1,
    "allowFutureBlocks": true,
    "cfgErr": false,
    "connectedNodes": 3,
    "consensusedBlockNumber": 4,
    "currentView": 80,
    "groupId": 1,
    "highestblockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
    "highestblockNumber": 3,
    "leaderFailed": false,
    "max_faulty_leader": 0,
    "nodeId": "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0",
    "nodeNum": 3,
    "node_index": 2,
    "omitEmptyBlock": true,
    "protocolId": 65544,
    "sealer.0": "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
    "sealer.1": "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
    "sealer.2": "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0",
    "toView": 80
  },
  [
    {
      "nodeId": "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
      "view": 78
    },
    {
      "nodeId": "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
      "view": 79
    },
    {
      "nodeId": "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0",
      "view": 80
    }
  ]
]
getGroupID

获取配置文件中指定的连接节点群组ID:

> ./console getGroupID

Group ID:
1
getGroupList

获取节点所属群组的群组ID列表:

> ./console getGroupList

Group ID List:
[
  1
]
getGroupPeers

获取指定群组的共识节点和观察节点列表:

> ./console getGroupPeers

Peers:
[
  "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
  "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
  "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
  "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0"
]
getNodeIDList

获取节点及其连接节点的列表:

> ./console getNodeIDList

Node ID list:
[
  "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0",
  "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
  "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
  "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7"
]
getObserverList

获取观察者节点列表:

> ./console getObserverList

Observer List:
[
  "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302"
]
getPbftView

获取PBFT视图:

> ./console getPbftView

PBFT view:
"0x30"
getPeers

获取区块链节点的连接信息:

> ./console getPeers

Peers:
[
  {
    "Agency": "agency",
    "IPAndPort": "127.0.0.1:51808",
    "Node": "node3",
    "NodeID": "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
    "Topic": []
  },
  {
    "Agency": "agency",
    "IPAndPort": "127.0.0.1:30302",
    "Node": "node2",
    "NodeID": "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302",
    "Topic": []
  },
  {
    "Agency": "agency",
    "IPAndPort": "127.0.0.1:30301",
    "Node": "node1",
    "NodeID": "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
    "Topic": []
  }
]
getPendingTransactions

获取交易池内所有未上链的交易:

> ./console getPendingTransactions

Pending Transactions:
[]
getPendingTxSize

获取交易池内未上链的交易数目:

> ./console getPendingTxSize

Pending Transactions Count:
    hex: "0x0"
decimal:  0
getSealerList

获取共识节点列表:

>  ./console getSealerList

Sealer List:
[
  "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14",
  "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7",
  "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0"
]
getSyncStatus

获取区块链节点同步状态:

> ./console getSyncStatus

Synchronization Status:
{
  "blockNumber": 3,
  "genesisHash": "65e9c13da61b1f47564ccc6498260d739ce3dfd3366accc539a64412c1ef8e88",
  "isSyncing": false,
  "knownHighestNumber": 3,
  "knownLatestHash": "ce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "latestHash": "ce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "nodeId": "955ab783c6adc7a5f817773a5fbe32ecd9310f9392406eb1f9fa3d2b21539577a70c933158c1bd3a0bf183d5498bf57202b88401e1cb20e8972aab43ba3354f0",
  "peers": [
    {
      "blockNumber": 3,
      "genesisHash": "65e9c13da61b1f47564ccc6498260d739ce3dfd3366accc539a64412c1ef8e88",
      "latestHash": "ce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
      "nodeId": "42ae2ae3950a2933b6f576dc946b90b242b7f9a2a8d45aae53c1a1664cd582bd759fa69e4b52f7a453dce702878b9ef11fd34fce1bd15c9bdcabcbbee43e1302"
    },
    {
      "blockNumber": 3,
      "genesisHash": "65e9c13da61b1f47564ccc6498260d739ce3dfd3366accc539a64412c1ef8e88",
      "latestHash": "ce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
      "nodeId": "8b5e90815966004e807803aba5f003bc271d0b0aa82805c85764b21187bd504f79ec46eaf1e60752956af174a927d7b16c072c0bca1601968b29342521639c14"
    },
    {
      "blockNumber": 3,
      "genesisHash": "65e9c13da61b1f47564ccc6498260d739ce3dfd3366accc539a64412c1ef8e88",
      "latestHash": "ce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
      "nodeId": "95381c1d22d10ad73171f0d34ec1f2e5809f47ee76264aeed4bb0daaf594bfb5da89f6a65ff4a056952f66a6a99fc927320d002191cecc7a48905edd61ad84b7"
    }
  ],
  "protocolId": 65545,
  "txPoolSize": "0"
}
getSystemConfigByKey

根据关键字获取区块链系统配置:

./console getSystemConfigByKey [tx_count_limit/tx_gas_limit]

参数包括:

  • tx_count_limit/tx_gas_limit:单个区块中交易数量限制/单笔交易中可消耗的 gas 限制
> ./console getSystemConfigByKey tx_count_limit
Result:
"1000"

> ./console getSystemConfigByKey tx_gas_limit
Result:
"300000000"
getTotalTransactionCount

获取指定群组的上链交易数目:

> ./console getTotalTransactionCount

Latest Statistics on Transaction and Block Height:
{
  "blockNumber": "0x5",
  "failedTxSum": "0x0",
  "txSum": "0x5"
}
getTransactionByBlockHashAndIndex

根据交易所属的区块哈希、 交易索引获取交易信息:

./console getTransactionByBlockHashAndIndex [blockHash] [transactionIndex]

参数包括:

  • blockHash:区块 hash 值;
  • transactionIndex:交易索引值,注意需要转换为16进制。
> ./console getTransactionByBlockHashAndIndex 0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848 0x0

Transaction:
{
  "blockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "blockNumber": "0x3",
  "from": "0x4ca29e9e8cb79c863c04f83827ab540315f25e67",
  "gas": "0x11e1a300",
  "gasPrice": "0x11e1a300",
  "hash": "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434",
  "input": "0x2800efc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000803432616532616533393530613239333362366635373664633934366239306232343262376639613261386434356161653533633161313636346364353832626437353966613639653462353266376134353364636537303238373862396566313166643334666365316264313563396264636162636262656534336531333032",
  "nonce": "0x359fbc4677e4f4ca87a96a31372b1194f03ba200db94a18ad0b30f2e858ac32",
  "to": "0x0000000000000000000000000000000000001003",
  "transactionIndex": "0x0",
  "value": "0x0"
}
getTransactionByBlockNumberAndIndex

根据交易所属的区块高度、 交易索引获取交易信息:

./console getTransactionByBlockNumberAndIndex [blockNumber] [transactionIndex]

参数包括:

  • blockNumber:区块高度;
  • transactionIndex:交易索引值,注意需要转换为16进制。
> ./console getTransactionByBlockNumberAndIndex 3 0x0

Transaction:
{
  "blockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "blockNumber": "0x3",
  "from": "0x4ca29e9e8cb79c863c04f83827ab540315f25e67",
  "gas": "0x11e1a300",
  "gasPrice": "0x11e1a300",
  "hash": "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434",
  "input": "0x2800efc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000803432616532616533393530613239333362366635373664633934366239306232343262376639613261386434356161653533633161313636346364353832626437353966613639653462353266376134353364636537303238373862396566313166643334666365316264313563396264636162636262656534336531333032",
  "nonce": "0x359fbc4677e4f4ca87a96a31372b1194f03ba200db94a18ad0b30f2e858ac32",
  "to": "0x0000000000000000000000000000000000001003",
  "transactionIndex": "0x0",
  "value": "0x0"
}
getTransactionByHash

根据交易哈希获取交易信息:

./console getTransactionByHash [transactionHash]

参数包括:

  • transactionHash:交易 hash 值。
> ./console getTransactionByHash 0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434

Transaction:
{
  "blockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
  "blockNumber": "0x3",
  "from": "0x4ca29e9e8cb79c863c04f83827ab540315f25e67",
  "gas": "0x11e1a300",
  "gasPrice": "0x11e1a300",
  "hash": "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434",
  "input": "0x2800efc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000803432616532616533393530613239333362366635373664633934366239306232343262376639613261386434356161653533633161313636346364353832626437353966613639653462353266376134353364636537303238373862396566313166643334666365316264313563396264636162636262656534336531333032",
  "nonce": "0x359fbc4677e4f4ca87a96a31372b1194f03ba200db94a18ad0b30f2e858ac32",
  "to": "0x0000000000000000000000000000000000001003",
  "transactionIndex": "0x0",
  "value": "0x0"
}
getTransactionReceipt

根据交易哈希获取交易回执:

./console getTransactionReceipt [transactionHash]

参数包括:

  • transactionHash:交易 hash 值。
> ./console getTransactionReceipt 0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434

Transaction Receipt:
{
        "transactionHash": "0x5518df7c2063efeb6481c35c4c58f378fac5f476c023c2019b9b01d221478434",
        "transactionIndex": "0x0",
        "blockHash": "0xce28a18b54ee72450c403968f705253a59c87a22801a88cc642ae800bb8b4848",
        "blockNumber": "0x3",
        "gasUsed": "0x765b",
        "contractAddress": "0x0000000000000000000000000000000000000000",
        "root": "0x2d23b1248fe53a1769db06af5c0e99261678643f405f058cfa89193592d13fa7",
        "status": "0x0",
        "from": "0x4ca29e9e8cb79c863c04f83827ab540315f25e67",
        "to": "0x0000000000000000000000000000000000001003",
        "input": "0x2800efc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000803432616532616533393530613239333362366635373664633934366239306232343262376639613261386434356161653533633161313636346364353832626437353966613639653462353266376134353364636537303238373862396566313166643334666365316264313563396264636162636262656534336531333032",
        "output": "0x0000000000000000000000000000000000000000000000000000000000000001",
        "logs": [],
        "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
setSystemConfigByKey

根据关键字设置区块链系统配置:

./console setSystemConfigByKey [tx_count_limit/tx_gas_limit/rpbft_epoch_sealer_num/rpbft_epoch_block_num]

参数包括:

  • tx_count_limit/tx_gas_limit/rpbft_epoch_sealer_num/rpbft_epoch_block_num:单个区块中交易数量限制/单笔交易中可消耗的 gas 限制
> ./console setSystemConfigByKey tx_count_limit 999
success

> ./console setSystemConfigByKey tx_gas_limit 30000000
success

> ./console setSystemConfigByKey rpbft_epoch_sealer_num 20
success

> ./console setSystemConfigByKey rpbft_epoch_block_num 30
success
grantUserTableManager

根据用户表名和外部账户地址设置权限信息

> ./console grantUserTableManager [tableName] [accountAddress]

参数包括:

  • tableName:表名
  • accountAddress:外部账户地址
> ./console grantUserTableManager t_test 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064d
success
revokeUserTableManager

根据用户表名和外部账户地址撤销权限信息

> ./console revokeUserTableManager [tableName] [accountAddress]

参数包括:

  • tableName:表名
  • accountAddress:外部账户地址
> ./console revokeUserTableManager t_test 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064d
success
listUserTableManager

根据用户表名查询设置的权限记录列表

./console listUserTableManager [tableName]

参数包括:

  • tableName:表名
> ./console listUserTableManager t_test
{"user table managers":[{"address":"0xfbb18d54e9ee57529cda8c7c52242efe879f064d","enable_num":"11","table_name":"u_t_test"}]}
grantDeployAndCreateManager

增加外部账户地址部署合约和创建用户表的权限

./console grantDeployAndCreateManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console grantDeployAndCreateManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
revokeDeployAndCreateManager

移除外部账户地址部署合约和创建用户表的权限

./console revokeDeployAndCreateManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console revokeDeployAndCreateManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
listDeployAndCreateManager

查询拥有部署合约和创建用户表权限的记录列表

> ./console listDeployAndCreateManager
{"managers":[{"address":"0xfbb18d54e9ee57529cda8c7c52242efe879f064f","enable_num":"15","table_name":"_sys_tables_"}]}
grantPermissionManager

授予外部账户地址链管理员权限,链管理员可以使用权限分配功能。该命令只支持 FISCO BCOS 2.4.0 和以下版本。

./console grantPermissionManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console grantPermissionManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
revokePermissionManager

撤销链外部账户地址链管理员权限

./console revokePermissionManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console revokePermissionManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
listPermissionManager

查询拥有链管理权限的记录列表

> ./console listPermissionManager
{"managers":[{"address":"0x83309d045a19c44dc3722d15a6abd472f95866ac","enable_num":"24","table_name":"_sys_table_access_"}]}
grantNodeManager

增加外部账户地址的节点管理权限

./console grantNodeManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console grantNodeManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
revokeNodeManager

移除外部账户地址的节点管理权限

./console revokeNodeManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console revokeNodeManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
listNodeManager

查询拥有节点管理的权限记录列表

> ./console listNodeManager
{"managers":[{"address":"0xfbb18d54e9ee57529cda8c7c52242efe879f064f","enable_num":"18","table_name":"_sys_consensus_"}]}
grantCNSManager

增加外部账户地址使用CNS的权限

./console grantCNSManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console grantCNSManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
revokeCNSManager

移除外部账户地址使用CNS的权限

./console revokeCNSManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console revokeCNSManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
listCNSManager

查询拥有使用CNS权限的记录列表

> ./console listCNSManager
{"managers":[{"address":"0xfbb18d54e9ee57529cda8c7c52242efe879f064f","enable_num":"19","table_name":"_sys_cns_"}]}
grantSysConfigManager

增加外部账户地址的系统参数管理权限

./console grantSysConfigManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console grantSysConfigManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
revokeSysConfigManager

移除外部账户地址的系统参数管理权限

./console revokeSysConfigManager [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console revokeSysConfigManager 0xFbb18d54e9Ee57529cda8c7c52242EFE879f064F
success
listSysConfigManager

查询拥有系统参数管理的权限记录列表

> ./console listSysConfigManager
{"managers":[{"address":"0xfbb18d54e9ee57529cda8c7c52242efe879f064f","enable_num":"21","table_name":"_sys_config_"}]}
queryCNS

根据合约名和版本号查询CNS信息

./console queryCNS [name] [version]

参数包括:

  • name:合约名(必须)
  • version:版本号(可选)
> ./console queryCNS store 5.0
name: store, version: 5.0, address: 0x0626918C51A1F36c7ad4354BB1197460A533a2B9

> ./console queryCNS store
name: store, version: 5.0, address: 0x0626918C51A1F36c7ad4354BB1197460A533a2B9
getAddressByContractNameAndVersion

根据合约名和合约版本号查询合约地址

./console getAddressByContractNameAndVersion [name] [version]

参数包括:

  • name:合约名
  • version:版本号
> ./console getAddressByContractNameAndVersion store 5.0
0626918c51a1f36c7ad4354bb1197460a533a2b9
addObserver

根据节点NodeID设置对应节点为观察者节点

./console addObserver [Node ID]

参数包括:

  • Node ID:节点 ID。
> ./console addObserver 58108297d9b545dc6e9a7ee4fea539c7886ced0c4cfeb33acd16ad23158247901d7d45dfbacc2fe97e38afaf163a4608f2fc2338d3ca37245d44e983adbde202
success
addSealer

根据节点NodeID设置对应节点为共识节点

./console addSealer [Node ID]

参数包括:

  • Node ID:节点 ID。
> ./console addSealer 58108297d9b545dc6e9a7ee4fea539c7886ced0c4cfeb33acd16ad23158247901d7d45dfbacc2fe97e38afaf163a4608f2fc2338d3ca37245d44e983adbde202
success
removeNode

根据节点NodeID设置对应节点为游离节点

./console removeNode [Node ID]

参数包括:

  • Node ID:节点 ID。
> ./console removeNode 58108297d9b545dc6e9a7ee4fea539c7886ced0c4cfeb33acd16ad23158247901d7d45dfbacc2fe97e38afaf163a4608f2fc2338d3ca37245d44e983adbde202
success
grantCommitteeMember

根据外部账户地址新增委员会成员

./console grantCommitteeMember [accountAddress]

参数包括:

  • accountAddress:外部账户地址。
> ./console grantCommitteeMember 0x83309d045a19c44dc3722d15a6abd472f95866ac
success
revokeCommitteeMember

根据外部账户地址撤销委员会成员

./console revokeCommitteeMember [accountAddress]

参数包括:

  • accountAddress:外部账户地址。
> ./console revokeCommitteeMember 0x83309d045a19c44dc3722d15a6abd472f95866ac
success
listCommitteeMembers

查询委员会成员列表

> ./console listCommitteeMembers
{"committee_members":[{"address":"0x83309d045a19c44dc3722d15a6abd472f95866ac","enable_num":"24"}]}
queryCommitteeMemberWeight

根据外部账户地址查询委员会成员投票权值

./console queryCommitteeMemberWeight [accountAddress]

参数包括:

  • accountAddress:外部账户地址。
> ./console queryCommitteeMemberWeight 0x83309d045a19c44dc3722d15a6abd472f95866ac
success, the weight 0x83309d045a19c44dc3722d15a6abd472f95866ac is 1
updateCommitteeMemberWeight

根据外部账户地址更新委员会成员投票权值

./console updateCommitteeMemberWeight [accountAddress] [weight]

参数包括:

  • accountAddress:外部账户地址
  • weight:权值,大于 0 的整数
> ./console updateCommitteeMemberWeight 0x83309d045a19c44dc3722d15a6abd472f95866ac 2
success
queryThreshold

查询委员会全体委员投票时总票数生效的阈值

> ./console queryThreshold
success, the effective threshold of voting is 50
updateThreshold

更新委员会全体委员投票时票数占比生效的阈值

./console updateThreshold [threshold]

参数包括:

  • threshold:阈值,支持范围 [0, 99)。
> ./console updateThreshold 60
success
grantOperator

根据外部账户地址新增运维权限

./console grantOperator [accountAddress]

参数包括:

  • accountAddress:外部账户地址。
> ./console grantOperator 0x112fb844934c794a9e425dd6b4e57eff1b519f17
success
revokeOperator

根据外部账户地址撤销运维权限

./console revokeOperator [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console revokeOperator 0x112fb844934c794a9e425dd6b4e57eff1b519f17
success
listOperators

查询全体运维成员列表

> ./console listOperators
{"operators":[{"address":"0x112fb844934c794a9e425dd6b4e57eff1b519f17","enable_num":"32"}]}
freezeAccount

根据外部账户地址冻结账户,该外部账号需要是部署过合约的账号

./console freezeAccount [accountAddress]

参数包括:

  • accountAddress:外部账户地址。
> ./console freezeAccount 0x112fb844934c794a9e425dd6b4e57eff1b519f17
success
unfreezeAccount

根据外部账户地址解冻合账户

./console unfreezeAccount [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console unfreezeAccount 0x112fb844934c794a9e425dd6b4e57eff1b519f17
success
getAccountStatus

根据外部账户地址查询账户状态

./console getAccountStatus [accountAddress]

参数包括:

  • accountAddress:外部账户地址
> ./console getAccountStatus 0x112fb844934c794a9e425dd6b4e57eff1b519f17
The account has been frozen. You can use this account after unfreezing it.
freezeContract

根据合约地址冻结合约

./console freezeContract [contract address]

参数包括:

  • contract address:合约地址
> ./console freezeContract 0x54Fb7aAAF3D2d6663E3472d641b7fB54cB246Ff0
success
unfreezeContract

根据合约地址解冻合约

./console unfreezeContract [contract address]

参数包括:

  • contract address:合约地址
> ./console unfreezeContract 0x54Fb7aAAF3D2d6663E3472d641b7fB54cB246Ff0
success
grantContractStatusManager

根据合约地址和外部账户地址授予账户合约管理权限

./console grantContractStatusManager [contract address] [accountAddress]

参数包括:

  • contract address:合约地址
  • accountAddress:外部账户地址
> ./console grantContractStatusManager 0x54Fb7aAAF3D2d6663E3472d641b7fB54cB246Ff0 0xae66fbe9ee2b5007e245d98bf7cf9904cc61e394
success
getContractStatus

根据合约地址查询合约状态

./console getContractStatus [contract address]

参数包括:

  • contract address:合约地址
> ./console getContractStatus 0x54Fb7aAAF3D2d6663E3472d641b7fB54cB246Ff0
The contract is available.
ListManager

查询拥有合约地址管理权限的外部账号

./console ListManager [contract address]

参数包括:

  • contract address:合约地址
> ./console ListManager 0x54Fb7aAAF3D2d6663E3472d641b7fB54cB246Ff0
{"managers":["0x112fb844934c794a9e425dd6b4e57eff1b519f17","0x83309d045a19c44dc3722d15a6abd472f95866ac","0xae66fbe9ee2b5007e245d98bf7cf9904cc61e394"]}

合约开发样例

非国密样例

本开发样例使用标准单群组四节点区块链网络结构,搭建请参考:安装

在利用SDK进行项目开发时,对智能合约进行操作需要利用go-sdk的abigen工具将Solidity智能合约转换为Go文件代码。整体上主要包含六个流程:

  • 准备需要编译的智能合约
  • 配置好相应版本的solc编译器
  • 构建go-sdk的合约编译工具abigen
  • 编译生成go文件
  • 准备建立ssl连接需要的证书
  • 使用生成的go文件进行合约部署、调用
HelloWorld样例
准备HelloWorld.sol合约文件
# 该指令在go-sdk目录中执行
mkdir helloworld && cd helloworld

在 go-sdk 主目录中新建 helloworld 文件夹,在该文件夹中创建 HelloWorld.sol 合约。该合约提供两个接口,分别是get()和set(),用于获取/设置合约变量name。合约内容如下

pragma solidity>=0.4.24 <0.6.11;

contract HelloWorld {
    string value;
    event setValue(string);
    string public version = "1";

    constructor() public {
        value = "Hello, World!";
    }

    function get() public view returns (string memory) {
        return value;
    }

    function set(string v) public {
        value = v;
        emit setValue(v);
    }
}
安装solc编译器

该编译器用于将 sol 合约文件编译成 abi 和 bin 文件,目前FISCO BCOS支持的solc编译器版本0.4.25/0.5.2,即将支持0.6.10

# 该指令在helloworld文件夹中执行
bash ../tools/download_solc.sh -v 0.4.25
构建go-sdk的代码生成工具abigen

该工具用于将 abi 和 bin 文件转换为 go 文件

# 该指令在helloworld文件夹中执行,编译生成abigen工具
go build ../cmd/abigen
编译生成go文件

先利用solc编译合约文件HelloWorld.sol,生成abi和bin文件

# 该指令在helloworld文件夹中执行
./solc-0.4.25 --bin --abi -o ./ ./HelloWorld.sol

helloworld目录下会生成HelloWorld.bin和HelloWorld.abi。此时利用abigen工具将HelloWorld.bin和HelloWorld.abi转换成HelloWorld.go:

# 该指令在helloworld文件夹中执行
./abigen --bin ./HelloWorld.bin --abi ./HelloWorld.abi --pkg helloworld --type HelloWorld --out ./HelloWorld.go

最后helloworld文件夹下面存在以下6个文件:

HelloWorld.abi、HelloWorld.bin、HelloWorld.go、HelloWorld.sol、solc-0.4.25、abigen
准备建立ssl连接需要的证书

使用build_chain.sh脚本搭建区块链时会在./nodes/127.0.0.1/sdk文件夹中生成sdk证书、私钥以及ca证书,需要将这三个文件拷贝至go-sdk主目录

部署合约

在helloworld文件夹中创建contract文件夹,在contract文件夹中创建helloworld_main.go文件,在该文件中调用HelloWorld.go部署智能合约

package main

import (
	"fmt"
	"log"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	"github.com/FISCO-BCOS/go-sdk/helloworld" // import helloworld
)

func main(){
	config := &conf.ParseConfig("config.toml")[0]

	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}
	address, tx, instance, err := helloworld.DeployHelloWorld(client.GetTransactOpts(), client) // deploy contract
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("contract address: ", address.Hex())  // the address should be saved
	fmt.Println("transaction hash: ", tx.Hash().Hex())
	_ = instance
}

注解

  • 合约地址需要手动保存,调用合约接口时使用
调用合约get/set接口

在contract文件夹中创建helloworld_get.go文件,调用合约get接口,获取智能合约中name变量存储的值

package main

import (
	"fmt"
	"log"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	"github.com/FISCO-BCOS/go-sdk/helloworld"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x481D3A1dcD72cD618Ea768b3FbF69D78B46995b0
	instance, err := helloworld.NewHelloWorld(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	helloworldSession := &helloworld.HelloWorldSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	value, err := helloworldSession.Get()    // call Get API
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("value :", value)

	value = "Hello, FISCO BCOS"
	tx, err = helloworldSession.Set(value)  // call set API
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("tx sent: %s\n", tx.Hash().Hex())

	// wait for the mining
	receipt, err := client.WaitMined(tx)
	if err != nil {
		log.Fatalf("tx mining error:%v\n", err)
	}
	fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash())
}
KVTableTest样例
准备Table.sol合约文件

在 go-sdk 主目录中新建 kvtabletest 文件夹,拷贝 Table.sol 合约。

# 创建 kvtabletest 文件夹
mkdir kvtabletest && cd kvtabletest

# 拷贝 Table.sol 合约
cp ../.ci/Table/Table.sol ./
准备KVTableTest.sol合约文件

该合约调用 Table 合约,实现创建用户表 t_kvtest,并对 t_kvtest 表进行读写。

pragma solidity>=0.4.24 <0.6.11;

import "./Table.sol";

contract KVTableTest {
    event SetResult(int256 count);

    KVTableFactory tableFactory;
    string constant TABLE_NAME = "t_kvtest";

    constructor() public {
        //The fixed address is 0x1010 for KVTableFactory
        tableFactory = KVTableFactory(0x1010);
        // the parameters of createTable are tableName,keyField,"vlaueFiled1,vlaueFiled2,vlaueFiled3,..."
        tableFactory.createTable(TABLE_NAME, "id", "item_price,item_name");
    }

    //get record
    function get(string memory id) public view returns (bool, int256, string memory) {
        KVTable table = tableFactory.openTable(TABLE_NAME);
        bool ok = false;
        Entry entry;
        (ok, entry) = table.get(id);
        int256 item_price;
        string memory item_name;
        if (ok) {
            item_price = entry.getInt("item_price");
            item_name = entry.getString("item_name");
        }
        return (ok, item_price, item_name);
    }

    //set record
    function set(string memory id, int256 item_price, string memory item_name)
    public
    returns (int256)
    {
        KVTable table = tableFactory.openTable(TABLE_NAME);
        Entry entry = table.newEntry();
        // the length of entry's field value should < 16MB
        entry.set("id", id);
        entry.set("item_price", item_price);
        entry.set("item_name", item_name);
        // the first parameter length of set should <= 255B
        int256 count = table.set(id, entry);
        emit SetResult(count);
        return count;
    }
}
安装solc编译器

该编译器用于将 sol 合约文件编译成 abi 和 bin 文件,目前FISCO BCOS支持的solc编译器版本0.4.25/0.5.2,即将支持0.6.10

# 该指令在 kvtabletest 文件夹中执行
bash ../tools/download_solc.sh -v 0.4.25
构建 go-sdk 的代码生成工具 abigen

该工具用于将 abi 和 bin 文件转换为 go 文件

# 该指令在 kvtabletest 文件夹中执行,编译生成 abigen 工具
go build ../cmd/abigen
编译生成 go 文件

先利用 solc 编译合约文件 KVTableTest.sol,生成 abi 和 bin 文件

# 该指令在 kvtabletest 文件夹中执行(控制台可能会打印 warning)
./solc-0.4.25 --bin --abi -o ./ ./KVTableTest.sol

kvtabletest 目录下会生成 KVTableTest.bin、KVTableTest.abi和其它一些文件。此时利用 abigen 工具将 KVTableTest.bin 和 KVTableTest.abi 转换成 KVTableTest.go:

# 该指令在 kvtabletest 文件夹中执行
./abigen --bin ./KVTableTest.bin --abi ./KVTableTest.abi --pkg kvtabletest --type KVTableTest --out ./KVTableTest.go

最后 kvtabletest 文件夹下面存在以下6个文件和其它若干文件:

KVTableTest.abi、KVTableTest.bin、KVTableTest.go、KVTableTest.sol、solc-0.4.25、abigen
准备建立ssl连接需要的证书

使用build_chain.sh脚本搭建区块链时会在./nodes/127.0.0.1/sdk文件夹中生成sdk证书、私钥以及ca证书,需要将这三个文件拷贝至go-sdk主目录

部署合约

在 kvtabletest 文件夹中创建 contract 文件夹,在 contract 文件夹中创建 kvtabletest_main.go 文件,调用 KVTableTest.go 部署智能合约。合约将创建 t_kvtest 表,该表用于记录某公司仓库中物资,以唯一的物资编号作为主key,保存物资的名称和价格。

package main

import (
	"fmt"
	"log"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	kvtable "github.com/FISCO-BCOS/go-sdk/kvtabletest" // import kvtabletest
)

func main(){
	config := &conf.ParseConfig("config.toml")[0]

	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}
	address, tx, instance, err := kvtable.DeployKVTableTest(client.GetTransactOpts(), client)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("contract address: ", address.Hex())  // the address should be saved
	fmt.Println("transaction hash: ", tx.Hash().Hex())
	_ = instance
}

注解

  • 合约地址需要手动保存,调用合约接口时使用
调用合约set接口

在 contract 文件夹中新建 kvtabletest_set.go 文件,该文件调用合约 set 接口,向 t_kvtest 表中插入一条数据:id=”100010001001”、item_name=”Laptop”、item_price=6000。

package main

import (
	"fmt"
	"log"
	"math/big"
	"strconv"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	kvtable "github.com/FISCO-BCOS/go-sdk/kvtabletest"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x9526BDd51d7F346ec2B48192f25a800825A8dBF3
	instance, err := kvtable.NewKVTableTest(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	kvtabletestSession := &kvtable.KVTableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	id := "100010001001"
	item_name := "Laptop"
	item_price := big.NewInt(6000)
	tx, err := kvtabletestSession.Set(id,item_price,item_name)    // call set API
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("tx sent: %s\n", tx.Hash().Hex())

	// wait for the mining
	receipt, err := client.WaitMined(tx)
	if err != nil {
		log.Fatalf("tx mining error:%v\n", err)
	}
	fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash())
	setedLines, err := strconv.Atoi(receipt.Output[2:])
	if err != nil {
		log.Fatalf("error when transfer string to int: %v\n", err)
	}
	fmt.Printf("seted lines: %v\n", setedLines)
}
调用合约get接口

在 contract 文件夹中新建 kvtabletest_get.go 文件,该文件调用合约 get 接口,查看id=”100010001001” 在表 t_kvtest 中的数据。

package main

import (
	"fmt"
	"log"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	kvtable "github.com/FISCO-BCOS/go-sdk/kvtabletest"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x481D3A1dcD72cD618Ea768b3FbF69D78B46995b0
	instance, err := kvtable.NewKVTableTest(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	kvtabletestSession := &kvtable.KVTableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	id := "100010001001"

	bool, item_price, item_name, err := kvtabletestSession.Get(id)  // call get API
	if err != nil {
		log.Fatal(err)
	}
	if !bool {
		log.Fatalf("id:%v is not found \n", id)
	}
	fmt.Printf("id: %v, item_price: %v, item_name: %v \n", id, item_price, item_name)
}
TableTest样例
准备Table.sol合约文件

在 go-sdk 主目录中新建 tabletest 文件夹,拷贝 Table.sol 合约。

# 创建 tabletest 文件夹
mkdir tabletest && cd tabletest

# 拷贝 Table.sol 合约
cp ../.ci/Table/Table.sol ./
准备TableTest.sol合约文件

该合约调用 AMDB 专用的智能合约 Table.sol,实现创建用户表 t_test,并对t_test 表进行增删改查。

pragma solidity>=0.4.24 <0.6.11;
pragma experimental ABIEncoderV2;

import "./Table.sol";

contract TableTest {
    event CreateResult(int256 count);
    event InsertResult(int256 count);
    event UpdateResult(int256 count);
    event RemoveResult(int256 count);

    TableFactory tableFactory;
    string constant TABLE_NAME = "t_test";
    constructor() public {
        tableFactory = TableFactory(0x1001); //The fixed address is 0x1001 for TableFactory
        // the parameters of createTable are tableName,keyField,"vlaueFiled1,vlaueFiled2,vlaueFiled3,..."
        tableFactory.createTable(TABLE_NAME, "name", "item_id,item_name");
    }

    //select records
    function select(string memory name)
    public
    view
    returns (string[] memory, int256[] memory, string[] memory)
    {
        Table table = tableFactory.openTable(TABLE_NAME);

        Condition condition = table.newCondition();

        Entries entries = table.select(name, condition);
        string[] memory user_name_bytes_list = new string[](
            uint256(entries.size())
        );
        int256[] memory item_id_list = new int256[](uint256(entries.size()));
        string[] memory item_name_bytes_list = new string[](
            uint256(entries.size())
        );

        for (int256 i = 0; i < entries.size(); ++i) {
            Entry entry = entries.get(i);

            user_name_bytes_list[uint256(i)] = entry.getString("name");
            item_id_list[uint256(i)] = entry.getInt("item_id");
            item_name_bytes_list[uint256(i)] = entry.getString("item_name");
        }

        return (user_name_bytes_list, item_id_list, item_name_bytes_list);
    }
    //insert records
    function insert(string memory name, int256 item_id, string memory item_name)
    public
    returns (int256)
    {
        Table table = tableFactory.openTable(TABLE_NAME);

        Entry entry = table.newEntry();
        entry.set("name", name);
        entry.set("item_id", item_id);
        entry.set("item_name", item_name);

        int256 count = table.insert(name, entry);
        emit InsertResult(count);

        return count;
    }
    //update records
    function update(string memory name, int256 item_id, string memory item_name)
    public
    returns (int256)
    {
        Table table = tableFactory.openTable(TABLE_NAME);

        Entry entry = table.newEntry();
        entry.set("item_name", item_name);

        Condition condition = table.newCondition();
        condition.EQ("name", name);
        condition.EQ("item_id", item_id);

        int256 count = table.update(name, entry, condition);
        emit UpdateResult(count);

        return count;
    }
    //remove records
    function remove(string memory name, int256 item_id) public returns (int256) {
        Table table = tableFactory.openTable(TABLE_NAME);

        Condition condition = table.newCondition();
        condition.EQ("name", name);
        condition.EQ("item_id", item_id);

        int256 count = table.remove(name, condition);
        emit RemoveResult(count);

        return count;
    }
}
安装solc编译器

该编译器用于将 sol 合约文件编译成 abi 和 bin 文件,目前FISCO BCOS支持的solc编译器版本0.4.25/0.5.2,即将支持0.6.10

# 该指令在 tabletest 文件夹中执行
bash ../tools/download_solc.sh -v 0.4.25
构建 go-sdk 的代码生成工具 abigen

该工具用于将 abi 和 bin 文件转换为 go 文件

# 该指令在 tabletest 文件夹中执行,编译生成 abigen 工具
go build ../cmd/abigen
编译生成 go 文件

先利用 solc 编译合约文件 TableTest.sol,生成 abi 和 bin 文件

# 该指令在 tabletest 文件夹中执行(控制台可能会打印 warning)
./solc-0.4.25 --bin --abi -o ./ ./TableTest.sol

tabletest 目录下会生成 TableTest.bin、TableTest.abi和其它一些文件。此时利用 abigen 工具将 TableTest.bin 和 TableTest.abi 转换成 TableTest.go:

# 该指令在 tabletest 文件夹中执行
./abigen --bin ./TableTest.bin --abi ./TableTest.abi --pkg tabletest --type TableTest --out ./TableTest.go

最后 tabletest 文件夹下面存在以下6个文件和其它若干文件:

TableTest.abi、TableTest.bin、TableTest.go、TableTest.sol、solc-0.4.25、abigen
准备建立ssl连接需要的证书

使用build_chain.sh脚本搭建区块链时会在./nodes/127.0.0.1/sdk文件夹中生成sdk证书、私钥以及ca证书,需要将这三个文件拷贝至go-sdk主目录

部署合约

在 tabletest 文件夹中创建 contract 文件夹,在 contract 文件夹中创建 tabletest_main.go 文件,调用 TableTest.go 部署智能合约。合约将创建用户表 t_test,该表有三个字段 name、item_id 和 item_name,用于记录某公司员工领用的物资和编号信息。

package main

import (
	"fmt"
	"log"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	table "github.com/FISCO-BCOS/go-sdk/tabletest" // import tabletest
)

func main(){
	config := &conf.ParseConfig("config.toml")[0]

	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}
	address, tx, instance, err := table.DeployTableTest(client.GetTransactOpts(), client)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("contract address: ", address.Hex())  // the address should be saved
	fmt.Println("transaction hash: ", tx.Hash().Hex())
	_ = instance
}

注解

  • 合约地址需要手动保存,调用合约接口时使用
调用合约Insert接口

在 contract 文件夹中新建 tabletest_insert.go 文件,该文件调用合约 Insert 接口,向用户表 t_test 插入一条数据:name=”Bob”、item_id=100010001001、item_name=”Laptop”。

package main

import (
	"fmt"
	"log"
	"math/big"
	"strconv"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	table "github.com/FISCO-BCOS/go-sdk/tabletest"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x9526BDd51d7F346ec2B48192f25a800825A8dBF3
	instance, err := table.NewTableTest(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	tabletestSession := &table.TableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	name := "Bob"
	item_id := big.NewInt(100010001001)
	item_name := "Laptop"
	tx, err := tabletestSession.Insert(name,item_id,item_name)    // call Insert API
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("tx sent: %s\n", tx.Hash().Hex())

	// wait for the mining
	receipt, err := client.WaitMined(tx)
	if err != nil {
		log.Fatalf("tx mining error:%v\n", err)
	}
	fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash())
	insertedLines, err := strconv.Atoi(receipt.Output[2:])
	if err != nil {
		log.Fatalf("error when transfer string to int: %v\n", err)
	}
	fmt.Printf("inserted lines: %v\n", insertedLines)
}
调用合约select接口

在 contract 文件夹中新建 tabletest_select.go 文件,该文件调用合约 select 接口,查看用户 “Bob” 在用户表 t_test 中的数据。

package main

import (
	"fmt"
	"log"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	table "github.com/FISCO-BCOS/go-sdk/tabletest"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x481D3A1dcD72cD618Ea768b3FbF69D78B46995b0
	instance, err := table.NewTableTest(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	tabletestSession := &table.TableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	name := "Bob"

	names, item_ids, item_names, err := tabletestSession.Select(name)  // call select API
	if err != nil {
		log.Fatal(err)
	}
	for i:=0; i<len(names); i++ {
		fmt.Printf("name: %v, item_id: %v, item_name: %v \n", names[i], item_ids[i], item_names[i])
	}

}
调用合约update接口

在 contract 文件夹中新建 tabletest_update.go 文件,该文件调用合约 update 接口,更新用户表 t_test 中用户 “Bob” 的数据,将 item_name 从 Laptop 修改为 Macbook Pro。

package main

import (
	"fmt"
	"log"
	"math/big"
	"strconv"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	table "github.com/FISCO-BCOS/go-sdk/tabletest"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x9526BDd51d7F346ec2B48192f25a800825A8dBF3
	instance, err := table.NewTableTest(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	tabletestSession := &table.TableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	name := "Bob"
	item_id := big.NewInt(100010001001)
	item_name := "Macbook Pro"
	tx, err := tabletestSession.Update(name,item_id,item_name)    // call Update API
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("tx sent: %s\n", tx.Hash().Hex())

	// wait for the mining
	receipt, err := client.WaitMined(tx)
	if err != nil {
		log.Fatalf("tx mining error:%v\n", err)
	}
	fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash())
	updatedLines, err := strconv.Atoi(receipt.Output[2:])
	if err != nil {
		log.Fatalf("error when transfer string to int: %v\n", err)
	}
	fmt.Printf("updated lines: %v\n", updatedLines)
}
调用合约remove接口

在 contract 文件夹中新建 tabletest_remove.go 文件,该文件调用合约 remove 接口,删除用户表 t_test 中用户 “Bob” 的数据。

package main

import (
	"fmt"
	"log"
	"math/big"
	"strconv"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	table "github.com/FISCO-BCOS/go-sdk/tabletest"
	"github.com/ethereum/go-ethereum/common"
)

func main() {
	config := &conf.ParseConfig("config.toml")[0]
	client, err := client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	// load the contract
	contractAddress := common.HexToAddress("contract addree in hex") // 0x9526BDd51d7F346ec2B48192f25a800825A8dBF3
	instance, err := table.NewTableTest(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}

	tabletestSession := &table.TableTestSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}

	name := "Bob"
	item_id := big.NewInt(100010001001)
	tx, err := tabletestSession.Remove(name,item_id)    // call Remove API
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("tx sent: %s\n", tx.Hash().Hex())

	// wait for the mining
	receipt, err := client.WaitMined(tx)
	if err != nil {
		log.Fatalf("tx mining error:%v\n", err)
	}
	fmt.Printf("transaction hash of receipt: %s\n", receipt.GetTransactionHash())
	removedLines, err := strconv.Atoi(receipt.Output[2:])
	if err != nil {
		log.Fatalf("error when transfer string to int: %v\n", err)
	}
	fmt.Printf("removed lines: %v\n", removedLines)
}
国密样例

使用国密特性的开发流程和非国密大致相同,不同点在于以下几部分:

  • 搭建的 FISCO BCOS 区块链网络需要开启国密特性,可参考:国密支持
  • go-sdk 的 config.toml 配置文件中 KeyFile 配置项,需要将非国密私钥替换为国密私钥
  • go-sdk 的 config.toml 配置文件中 SMCrypto 配置项,需要修改为 true
  • 安装 solc 编译器时需要添加 -g 选项,替换为国密版本
  • 使用 abigen 工具将 bin 和 abi 转换为 go 文件时,需要添加参数 –smcrypto=true
HelloWorld样例
准备HelloWorld.sol合约文件

在 go-sdk 主目录中新建 helloworld 文件夹,在该文件夹中创建 HelloWorld.sol 合约。该合约提供两个接口,分别是get()和set(),用于获取/设置合约变量name。合约内容如下

pragma solidity>=0.4.24 <0.6.11;

contract HelloWorld {
    string name;

    constructor() public {
        name = "Hello, World!";
    }

    function get() public view returns (string memory) {
        return name;
    }

    function set(string memory n) public {
        name = n;
    }
}
安装国密solc编译器

该编译器用于将 sol 合约文件编译成 abi 和 bin 文件

# 该指令在helloworld文件夹中执行
bash ../tools/download_solc.sh -v 0.4.25 -g
构建go-sdk的代码生成工具abigen

该工具用于将 abi 和 bin 文件转换为 go 文件

# 该指令在helloworld文件夹中执行,编译生成abigen工具
go build ../cmd/abigen
编译生成go文件

先利用solc编译合约文件HelloWorld.sol,生成abi和bin文件

# 该指令在helloworld文件夹中执行
./solc-0.4.25-gm --bin --abi -o ./ ./HelloWorld.sol

helloworld目录下会生成HelloWorld.bin和HelloWorld.abi。此时利用abigen工具将HelloWorld.bin和HelloWorld.abi转换成HelloWorld.go:

# 该指令在helloworld文件夹中执行
./abigen --bin ./HelloWorld.bin --abi ./HelloWorld.abi --pkg helloworld --type HelloWorld --out ./HelloWorld.go --smcrypto=true
  • 接下来的步骤同非国密,不占用多余篇幅

AMOP 使用案例

AMOP(Advanced Messages Onchain Protocol)即链上信使协议,旨在为联盟链提供一个安全高效的消息信道,联盟链中的各个机构,只要部署了区块链节点,无论是共识节点还是观察节点,均可使用AMOP进行通讯,AMOP有如下优势:

  • 实时:AMOP消息不依赖区块链交易和共识,消息在节点间实时传输,延时在毫秒级。
  • 可靠:AMOP消息传输时,自动寻找区块链网络中所有可行的链路进行通讯,只要收发双方至少有一个链路可用,消息就保证可达。
  • 高效:AMOP消息结构简洁、处理逻辑高效,仅需少量cpu占用,能充分利用网络带宽。
  • 安全:AMOP的所有通讯链路使用SSL加密,加密算法可配置,支持身份认证机制。
  • 易用:使用AMOP时,无需在SDK做任何额外配置。

进一步了解 AMOP,请参考:链上信使协议。案例源码,请参考:go-sdk

初始化

  • 搭建单群组四节点区块链网络,可参考:安装
单播案例

单播 指的是节点从监听相同 Topic 的多个订阅者中随机抽取一个订阅者转发消息,流程详细可参考 单播时序图

  • 启动 AMOP 消息订阅者:

    # go run examples/amop/sub/subscriber.go [endpoint] [topic]
    > go run examples/amop/sub/subscriber.go 127.0.0.1:20201 hello
    
      Subscriber success
      2020/08/11 21:21:50 received: hello, FISCO BCOS, I am unique broadcast publisher! 0
      2020/08/11 21:21:52 received: hello, FISCO BCOS, I am unique broadcast publisher! 1
      2020/08/11 21:21:54 received: hello, FISCO BCOS, I am unique broadcast publisher! 2
      2020/08/11 21:21:56 received: hello, FISCO BCOS, I am unique broadcast publisher! 3
    
  • 运行 AMOP 消息发布者:

    # go run examples/amop/unicast_pub/publisher.go [endpoint] [topic]
    > go run examples/amop/unicast_pub/publisher.go 127.0.0.1:20200 hello
    
      2020/08/11 21:21:50 publish message: hello, FISCO BCOS, I am unique broadcast publisher! 0 
      2020/08/11 21:21:52 publish message: hello, FISCO BCOS, I am unique broadcast publisher! 1 
      2020/08/11 21:21:54 publish message: hello, FISCO BCOS, I am unique broadcast publisher! 2 
      2020/08/11 21:21:56 publish message: hello, FISCO BCOS, I am unique broadcast publisher! 3
    
多播案例

多播 指的是节点向监听相同 Topic 的所有订阅者转发消息。只要网络正常,即使没有监听 Topic 的订阅者,消息发布者也会收到节点消息推送成功的响应包,流程详细可参考 多播时序图

  • 启动 AMOP 消息订阅者:

    # go run examples/amop/sub/subscriber.go [endpoint] [topic]
    > go run examples/amop/sub/subscriber.go 127.0.0.1:20201 hello
    
      Subscriber success
      2020/08/11 21:23:54 received: hello, FISCO BCOS, I am multi broadcast publisher! 0
      2020/08/11 21:23:56 received: hello, FISCO BCOS, I am multi broadcast publisher! 1
      2020/08/11 21:23:58 received: hello, FISCO BCOS, I am multi broadcast publisher! 2
      2020/08/11 21:24:00 received: hello, FISCO BCOS, I am multi broadcast publisher! 3
    
  • 运行 AMOP 消息发布者:

    # go run examples/amop/multicast_pub/publisher.go [endpoint] [topic]
    > go run examples/amop/multicast_pub/publisher.go 127.0.0.1:20200 hello 
    
      2020/08/11 21:23:54 publish message: hello, FISCO BCOS, I am multi broadcast publisher! 0 
      2020/08/11 21:23:56 publish message: hello, FISCO BCOS, I am multi broadcast publisher! 1 
      2020/08/11 21:23:58 publish message: hello, FISCO BCOS, I am multi broadcast publisher! 2 
      2020/08/11 21:24:00 publish message: hello, FISCO BCOS, I am multi broadcast publisher! 3
    

区块链浏览器

一、描述

1.1、基本描述

全新适配FISCO BCOS 2.0+版本,如果使用FISCO BCOS 1.2或1.3版本请用v1.2.1版本

区块链浏览器将区块链中的数据可视化,并进行实时展示。方便用户以Web页面的方式,获取当前区块链中的信息。本浏览器版本适配FISCO BCOS 2.0+,关于2.0+版本的特性可以参考此链接。在使用本浏览器之前需要先理解2.0+版本的群组特性,详情可以参考此链接

_images/overview.png

1.2、主要功能模块

本小节概要介绍浏览器的各个模块,方便大家对浏览器有一个整体的认识。区块链浏览器主要的功能模块有:群组切换模块,配置模块,区块链信息展示模块。

1.2.1、群组切换模块

群组切换主要用于在多群组场景中切换到不同群组,进行区块链信息浏览。

_images/switch_group.jpg

1.2.2、配置模块

主要包括群组配置,节点配置,合约配置。

_images/group_config.png

1.2.3、区块链信息展示模块

区块链浏览器主要展示了链上群组的具体信息,这些信息包括:概览信息,区块信息,交易信息。

_images/show.jpg

二、使用前提

2.1、群组搭建

区块链浏览器展示的数据是从区块链上同步下来的。为了同步数据需要初始化配置(添加群组信息和节点信息),故在同步数据展示前需要用户先搭建好区块链群组。FISCO BCOS 2.0+提供了多种便捷的群组搭建方式。

  1. 如果是开发者进行开发调试,建议使用开发部署工具 build_chain
  2. 如果是开发企业级应用,建议使用企业部署工具运维部署工具 FISCO BCOS generator

两者的主要区别在于build_chain为了使体验更好,搭建速度更快,辅助生成了群组内各个节点的私钥;但企业部署工具出于安全的考虑不辅助生成私钥,需要用户自己生成并设置。

三、区块链浏览器搭建

区块链浏览器分为两个部分:后台服务fisco-bcos-browser、前端web页面fisco-bcos-browser-front。

当前版本我们提供了两种搭建方式:一键搭建和手动搭建。

3.1.1、一键搭建

适合前后端同机部署,快速体验的情况使用。具体搭建流程参见安装文档

3.1.2、手动搭建

后台服务搭建

区块链浏览器后台服务使用Spring Boot的JAVA后台服务,具体搭建流程参见安装文档

前端web页面服务搭建

区块链浏览器前端web页面使用框架vue-cli,具体搭建流程参见安装文档

四、初始化环境

4.1、添加群组

_images/create_group.png

服务搭建成功后,可使用网页浏览器访问nginx配置的前端IP和前端端口,进入到浏览器页面。未初始化群组的浏览器页面会引导大家到新建群组配置页面,新建群组需要配置群组ID,群组名称,描述。

群组ID需要和区块链群组ID保持一致。 群组ID有多种查看方式,1、rpc接口获取。2、控制台命令

群组名称是为群组ID取的一个有意义,便于理解的名字。

描述字段是对名称的进一步说明。

4.2、添加节点

_images/add_node.png

添加群组所在的节点信息,用于区块链浏览器连接拉取相关展示信息。节点的rpc端口信息和p2p端口信息可以从节点的 config.ini配置文件中获取。

为了使用方便,新添加的群组会自动同步添加其他群组已经配置的共用节点信息。

4.3、添加合约

本浏览器版本提供合约解析的功能。此功能需要用户把本群组使用的所有合约进行导入。本版本支持用zip包上传一级目录,用于解决同名合约的问题。

导入步骤:

4.3.1 上传合约
  1. 合约上传支持sol文件上传和将sol文件打包成zip包上传。
  2. zip包最多支持一级目录,如果没有目录默认上传到根目录。zip包中只能有sol文件。
4.3.2 编译合约

_images/contract.png

五、功能介绍

5.1、概览

5.1.1 概览信息

主要包括当前群组的块高,交易总量,正在处理的交易数,PBFT视图。

5.1.2 最近15天的交易量

用折线图的形式展示了当前群组15内的交易情况。

5.1.3 节点概览

节点概览展示了当前群组内各个节点的ID,当前快高,pbftView,和节点状态。

5.1.4 区块概览

区块概览展示了最近4个区块的信息,包括每个区块的块高,出块者,块产生的时间及块上的交易总量。

5.1.5 交易概览

交易概览展示了最近四个交易,包括交易hash,交易时间,交易的发送者、交易的接收者,如果是正确导入了交易相关的合约还能展出交易调用的接口信息。

_images/overview.png

5.2、区块信息浏览

区块信息浏览主要包括区块列表页面和区块详情页面。

5.3、交易浏览

交易信息浏览主要包括交易列表页面和交易详情页面。

5.3.1、交易解析

合约成功上传并编译后,区块链浏览器能够解析出此合约相关交易的方法名和参数。浏览器的解析建立在合约的准确导入的基础上,故提醒用户在使用java和js等语言调用合约时,请注意保存合约的正确版本。

_images/transaction.png

5.3.2、事件解析

合约成功上传并编译后,区块链浏览器能够解析出此合约相关交易回执中的事件方法名和参数。

_images/receipt.png

系统设计

本章介绍FISCO BCOS平台的设计思路,包括每个模块的结构以及实现,面向FISCO BCOS平台开发者。

整体架构

整体架构上,FISCO BCOS划分成基础层、核心层、管理层和接口层:

  • 基础层:提供区块链的基础数据结构和算法库
  • 核心层: 实现了区块链的核心逻辑,核心层分为两大部分:
    1. 链核心层: 实现区块链的链式数据结构、交易执行引擎和存储驱动
    2. 互联核心层: 实现区块链的基础P2P网络通信、共识机制和区块同步机制
  • 管理层: 实现区块链的管理功能,包括参数配置、账本管理和AMOP
  • 接口层: 面向区块链用户,提供多种协议的RPC接口、SDK和交互式控制台

FISCO BCOS基于多群组架构实现了强扩展性的群组多账本,基于清晰的模块设计,构建了稳定、健壮的区块系统。

本章重点介绍FISCO BCOS的群组架构和系统运行时的交易流(包括交易提交、打包、执行和上链)。

_images/architecture.png

群组架构

考虑到真实的业务场景需求,FISCO BCOS引入多群组架构,支持区块链节点启动多个群组,群组间交易处理、数据存储、区块共识相互隔离,保障区块链系统隐私性的同时,降低了系统的运维复杂度。

注解

举个例子:

机构A、B、C所有节点构成一个区块链网络,运行业务1;一段时间后,机构A、B启动业务2,且不希望该业务相关数据、交易处理被机构C感知,有何解?

  • 1.3系列FISCO BCOS系统 :机构A和机构B重新搭一条链运行业务2;运维管理员需要运维两条链,维护两套端口
  • FISCO BCOS 2.0+ :机构A和机构B新建一个群组运行业务2;运维管理员仅需维护一条链

显然在达到相同隐私保护需求基础上,FISCO BCOS 2.0+具有更好的扩展性、可运维性和灵活性。

多群组架构中,群组间共享网络,通过网络准入和账本白名单实现各账本间网络消息隔离。

_images/ledger.png

群组间数据隔离,每个群组独立运行各自的共识算法,不同群组可使用不同的共识算法。每个账本模块自底向上主要包括核心层、接口层和调度层三层,这三层相互协作,FISCO BCOS可保证单个群组独立健壮地运行。

核心层

核心层负责将群组的区块数据、区块信息、系统表以及区块执行结果写入底层数据库。

存储分为世界状态(State)和分布式存储(AMDB)两部分,世界状态包括MPTState和StorageState,负责存储交易执行的状态信息,StorageState性能高于MPTState,但不存储区块历史信息;AMDB则向外暴露简单的查询(select)、提交(commit)和更新(update)接口,负责操作合约表、系统表和用户表,具有可插拔特性,后端可支持多种数据库类型,目前支持RocksDB数据库和MySQLstorage

_images/storage.png

接口层

接口层包括交易池(TxPool)、区块链(BlockChain)和区块执行器(BlockVerifier)三个模块。

  • 交易池(TxPool): 与网络层以及调度层交互,负责缓存客户端或者其他节点广播的交易,调度层(主要是同步和共识模块)从交易池中取出交易进行广播或者区块打包;
  • 区块链(BlockChain): 与核心层和调度层交互,是调度层访问底层存储的唯一入口,调度层(同步、共识模块)可通过区块链接口查询块高、获取指定区块、提交区块;
  • 区块执行器(BlockVerifier): 与调度层交互,负责执行从调度层传入的区块,并将区块执行结果返回给调度层。
调度层

调度层包括共识模块(Consensus)和同步模块(Sync)。

  • 共识模块:包括Sealer线程和Engine线程,分别负责打包交易、执行共识流程。Sealer线程从交易池(TxPool)取交易,并打包成新区块;Engine线程执行共识流程,共识过程会执行区块,共识成功后,将区块以及区块执行结果提交到区块链(BlockChain),区块链统一将这些信息写入底层存储,并触发交易池删除上链区块中包含的所有交易、将交易执行结果以回调的形式通知客户端,目前FISCO BCOS主要支持PBFTRaft共识算法;
  • 同步模块:负责广播交易和获取最新区块, 考虑到共识过程中,leader负责打包区块,而leader随时有可能切换,因此必须保证客户端的交易尽可能发送到每个区块链节点,节点收到新交易后,同步模块将这些新交易广播给所有其他节点;考虑到区块链网络中机器性能不一致或者新节点加入都会导致部分节点区块高度落后于其他节点,同步模块提供了区块同步功能,该模块向其他节点发送自己节点的最新块高,其他节点发现块高落后于其他节点后,会主动下载最新区块。

交易流

1 总体方案

用户通过SDK或curl命令向节点发起RPC请求以发起交易,节点收到交易后将交易附加到交易池中,打包器不断从交易池中取出交易并通过一定条件触发将取出交易打包为区块。生成区块后,由共识引擎进行验证及共识,验证区块无误且节点间达成共识后,将区块上链。当节点通过同步模块从其他节点处下载缺失的区块时,会同样对区块进行执行及验证。

2 整体架构

整体架构如下图所示:

_images/transaction_stream.jpg

Node:区块节点

TxPool:交易池,节点自身维护的、用于暂存收到的交易的内存区域

Sealer:打包器

Consensus Engine:共识引擎

BlockVerifier:区块验证器,用于验证一个区块的正确性

Executor:执行引擎,执行单个交易

BlockChain:区块链管理模块,是唯一有写权限的模块,提交区块接口需要同时传入区块数据和执行上下文数据,区块链管理将两种数据整合成一个事务提交到底层存储

Storage:底层存储

主要关系如下:

  1. 用户通过操作SDK或直接编写curl命令向所连接的节点发起交易。
  2. 节点收到交易后,若当前交易池未满则将交易附加至TxPool中并向自己所连的节点广播该交易;否则丢弃交易并输出告警。
  3. Sealer会不断从交易池中取出交易,并立即将收集到的交易打包为区块并发送至共识引擎。
  4. 共识引擎调用BlockVerifier对区块进行验证并在网络中进行共识,BlockVerifier调用Executor执行区块中的每笔交易。当区块验证无误且网络中节点达成一致后,共识引擎将区块发送至BlockChain。
  5. BlockChain收到区块,对区块信息(如块高等)进行检查,并将区块数据与表数据写入底层存储中,完成区块上链。
3 方案流程
3.1 合约执行流程

执行引擎基于执行上下文(Executive Context)执行单个交易,其中执行上下文由区块验证器创建用于缓存暂存区块执行过程中执行引擎产生的所有数据,执行引擎同时支持EVM合约与预编译合约,其中EVM合约可以通过交易创建合约、合约创建合约两种方式来创建,其执行流程如下:

_images/EVM_contract_execution.png

EVM合约创建后,保存到执行上下文的_sys_contracts_表中,EVM合约的地址在区块链全局状态内自增,从0x1000001开始(可定制),EVM合约执行过程中,Storage变量保存到执行上下文的c_(合约地址)表中。

预编译合约分永久和临时两种:(1) 永久预编译合约,整合在底层或插件中,合约地址固定;(2) 临时预编译合约,EVM合约或预编译合约执行时动态创建,合约地址在执行上下文内自增,从0x1000开始,至0x1000000截止,临时预编译合约仅在执行上下文内有效预编译合约没有Storage变量,只能操作表,其执行流程如下:

_images/precompiled_contract_execution.png

同步

本节介绍节点间的同步机制以及同步优化策略。

同步基础流程

同步,是区块链节点非常重要的功能。它是共识的辅助,给共识提供必需的运行条件。同步分为交易的同步和状态的同步。交易的同步,确保了每笔交易能正确的到达每个节点上。状态的同步,能确保区块落后的节点能正确的回到最新的状态。只有持有最新区块状态的节点,才能参与到共识中去。

交易同步

交易同步,是让区块链的上的交易尽可能的到达所有的节点。为共识中将交易打包成区块提供基础。

_images/tx.png

一笔交易(tx1),从客户端上发往某个节点,节点在接收到交易后,会将交易放入自身的交易池(Tx Pool)中供共识去打包。与此同时,节点会将交易广播给其它的节点,其它节点收到交易后,也会将交易放到自身的交易池中。交易在发送的过程中,会有丢失的情况,为了能让交易尽可能的到达所有的节点,收到广播过来交易的节点,会根据一定的策略,选择其它的节点,再进行一次广播。

交易广播策略

如果每个节点都没有限制的转发/广播收到的交易,带宽将被占满,出现交易广播雪崩的问题。为了避免交易广播的雪崩,FISCO BCOS根据经验,选择了较为精巧的交易广播策略。在尽可能保证交易可达性的前提下,尽量的减少重复的交易广播。

  • 对于SDK来的交易,广播给所有的节点
  • 对于其它节点广播来的交易,随机选择25%的节点再次广播
  • 一条交易在一个节点上,只广播一次,当收到了重复的交易,不会进行二次广播

通过上述的策略,能够尽量的让交易到达所有的节点,但也会在极小的概率下出现某交易无法到达某节点的情况。此情况是允许的。交易尽可能到达更多的节点,是为了让此交易尽快的被打包、共识、确认,尽量的让交易能够更快的得到执行的结果。当交易未到达某个节点时,只会使得交易的执行时间变长,不会影响交易的正确性。

状态同步

状态同步,是让区块链节点的状态保持在最新。区块链的状态的新旧,是指区块链节点当前持有数据的新旧,即节点持有的当前区块块高的高低。若一个节点的块高是区块链的最高块高,则此节点就拥有区块链的最新状态。只有拥有最新状态的节点,才能参与到共识中去,进行下一个新区块的共识。

_images/block.png

在一个全新的节点加入到区块链上,或一个已经断网的节点恢复了网络时,此节点的区块落后于其它节点,状态不是最新的。此时就需要进行状态同步。如图,需要状态同步的节点(Node 1),会主动向其它节点请求下载区块。整个下载的过程会将下载的负载分散到多个节点上。

状态同步与下载队列

区块链节点在运行时,会定时向其它节点广播自身的最高块高。节点收到其它节点广播过来的块高后,会和自身的块高进行比较,若自身的块高落后于此块高,就会启动区块下载流程。

区块的下载通过请求的方式完成。进入下载流程的节点,会随机的挑选满足要求的节点,发送需要下载的区块区间。收到下载请求的节点,会根据请求的内容,回复相应的区块。

_images/Download.png

收到回复区块的节点,在本地维护一个下载队列,用来对下载下来的区块进行缓冲和排序。下载队列是一个以块高为顺序的优先队列。下载下来的区块,会不断的插入到下载队列中,当队列中的区块能连接上节点当前本地的区块链,则将区块从下载队列中取出,真正的连接到当前本地的区块链上。

同步场景举例
交易同步

一笔交易被广播到所有节点的过程:

  1. 一笔交易通过channel或RPC发送到某节点上
  2. 收到交易的节点全量广播此交易给其它节点
  3. 其它节点收到交易后,为了保险起见,选择25%的节点再广播一次
  4. 节点收到广播过的交易,不会再次广播
状态同步

节点出块时的广播逻辑

  1. 某个节点出块
  2. 此节点将自己最新的状态(最新块高,最高块哈希,创世块哈希)广播给所有的节点
  3. 其它的节点收到peer的状态后,更新在本地管理的peer数据

组内成员的同步

组内成员在某时刻意外关闭,但其它成员在出块,当此组员再次启动时,发现区块落后于其它组员:

  1. 组员再次启动
  2. 收到其它组员发来的状态包
  3. 比较发现自己的最高块高落后于其它组员,启动下载流程
  4. 将相差的区块按区间划分成多个下载请求包,发送给多个组员,负载均衡
  5. 等待其它节点回复区块包
  6. 其它节点接受响应,从自己的区块链上查询出区块,回复给启动的节点
  7. 节点收到区块,放入下载队列
  8. 节点从下载队列中将区块拿出,写到区块链上
  9. 若下载未结束,则继续请求,若下载结束,则切换自身状态,开启交易同步,开启共识

新组员的同步

非组员作为一个新组员加入到某个组中,且此节点第一次启动,从原来的组员中同步区块:

  1. 非组员未被注册到组中,但非组员先启动
  2. 此时发现自己不在组中,不进行状态广播,也不进行交易广播,只等待其它组员发来状态消息
  3. 此时组员中并没有此新组员,不会向新组员广播状态
  4. 管理员将新组员加入到组中
  5. 组员向新组员广播自身状态
  6. 新组员收到组员状态,比较自身块高(为0),启动下载流程
  7. 之后的下载流程,与组内成员区块同步流程相同

区块同步优化

为了增强区块链系统在网络带宽受限情况下的可扩展性,FISCO BCOS v2.2.0对区块同步进行了优化:

  • 为了降低单个节点的出带宽,消除网络带宽对网络规模的限制,支持更大网络规模,采用树状拓扑进行区块同步
  • 采用gossip协议来保障树状拓扑区块同步的健壮性,定期同步区块状态,使得在部分节点网络断连的情况下,所有节点均能同步到最新区块状态
背景

考虑到目前使用BFT类共识算法的区块链网络复杂度较高、不具有无限可扩展性,因此大部分业务架构仅有部分节点作为共识节点,其他节点均作为观察节点(不参与共识,但拥有区块链全量数据),如下图所示。

_images/common_blockchain_system.png

在这种架构中,大部分观察节点均随机从拥有最新区块的共识节点同步区块,在包含n个共识节点、m个观察节点的区块链系统中,设每个区块大小为block_size,理想情况下(即负载均衡),每共识一个区块,每个共识节点需要向m/n个观察节点发送区块,共识节点出带宽大约是(m/n)*block_size,设网络带宽是bandwidth,则每个共识节点最多可向(bandwidth/block_size)个节点同步区块,即区块链网络规模最大是(n*bandwidth/block_size),在公网带宽bandwidth较小,区块较大的情况下,能容纳的节点数有限,因此随机的区块同步策略不具有可扩展性。

区块状态树状广播

为降低多个观察节点向单个共识节点同步区块时,共识节点的网络出带宽对网络规模的影响,FISCO BCOS v2.2.0实现了区块状态树状广播策略。下图是由3个共识节点、18个观察节点构成的区块链系统沿三叉树进行区块同步的示意图: _images/tree_topology_blockchain_system.png

该策略将观察节点分摊给每个共识节点,并以共识节点为顶点构造一颗三叉树,共识节点出块后,优先向其子观察节点发送最新区块状态,子观察节点同步最新区块后,优先向自己的子节点发送最新区块状态,以此类推。采用了区块状态树状广播策略后,每个节点仅将最新区块状态发送给子节点,设区块大小为block_size,树的宽度为w,则用于区块同步的网络带宽均为(block_size * w),与区块链系统的节点总数无关,具有可扩展性。上图所示的共识节点采用区块状态树状广播后,出带宽降低了2倍。

区块状态树状广播工作流程如下:

  • 共识节点共识提交新区块block i后,若其与子节点连通,则向其子节点同步最新区块状态,包括高度和区块哈希,记为{i, block_hash(i)};否则递归判断是否与孙子节点连通,若连通,则向孙子节点同步最新区块状态;
  • 子节点收到共识节点的区块状态后,判断接收到的区块状态{i, block_hash(i)}比自身区块状态新,则向共识节点发送区块请求,共识节点收到请求后,向该节点发送对应的区块;
  • 子节点收到共识节点的区块后,验证成功后将其落盘,继续向自己的子节点发送自身的区块状态,同样,若该节点与自己的子节点断连,会递归判断是否与孙子节点连通,并向连通的孙子节点发送最新区块状态;
  • 收到新区块状态的子节点,重复步骤(2),进行区块同步。

当然,使用区块状态树状广播策略时,由于区块并非由拥有最新区块的根节点直接下发到所有观察者节点,作为叶子节点的观察者节点同步区块的时延会相对长一些。

下图展示了各层节点提交n个区块的时延,设树的深度为d,每个区块提交时延为t,则根节点(共识节点)提交n个区块的时延为n*t,第一层节点(观察者节点)同步并提交区块的时延为n*t + t,第二层节点(观察者节点)同步并提交区块的时延为n*t + 2*t,叶子节点同步并提交区块的时延为n*t + d * t,与共识节点的时延差为d*tn远大于d时,这个时延几乎可以忽略,因此该策略对观察者节点TPS的影响非常小。

_images/tree_topology_delay.png

定期同步区块状态

考虑到若树状拓扑中部分节点断连,可能会导致区块无法到达部分节点,区块状态树状广播优化策略还采用了gossip协议定期同步区块状态。

即:随机挑选若干个节点,同步最新区块状态信息。由于最终区块状态信息会收敛所有区块链节点,树状拓扑中断连节点也能从其邻居节点同步最新区块,保证了树状区块状态广播的健壮性。

_images/gossip.png

上图展示了各个节点如何使用gossip协议定期同步区块状态:

  • 各个区块链节点每2s随机选择三个邻居节点广播当前区块状态,包括{区块高度,区块哈希}
  • 节点收到这些区块状态包后,更新本地缓存的各个节点区块状态到最新
  • 若某节点区块高度高于本节点区块高度,该节点会向拥有更高区块的节点同步区块

由于区块链节点之间定期同步区块状态,即使树状拓扑中部分节点断连,也可以保证每个节点同步到尽可能多的节点区块状态,并从拥有最高区块的节点下载最新区块,保障了树状区块状态广播可扩展性的同时,增强了整个系统的健壮性。

带宽对比

下图是采用了区块状态树状广播、定期同步区块状态策略后,区块同步优化效果:

_images/bandwidth.png

整个区块链网络中包含10个共识节点,90个观察者节点,树的度设置为2。优化前,观察者节点主要从10个共识节点下载区块,共识节点的出流量可达到5000MB;优化后,部分下载流量分摊到了观察者节点,节点由区块下载带来的流量开销降低到了1400MB,降低了3倍多,基本接近最优(最优的情况是优化前峰值出带宽是优化后峰值出带宽的4.5倍,由于gossip协议导致的区块随机拉取情况的存在,无法达到最优,只能接近最优)。

交易同步优化

区块链系统中,为了保障客户端发送的交易能到达所有节点,SDK直连的区块链节点需要将收到的交易广播给其他节点,如下图所示:

_images/txs_sync.png

显然,SDK直连节点的出带宽与区块链节点总数成正比,随着区块链系统节点数的增加,该节点必然成为整个系统的瓶颈。

此外,为了保障节点网络断连的情况下,交易也能尽量到达所有节点,还引入了交易转发逻辑,节点收到其他节点广播过来的交易后,会随机选取25%的邻居节点转发收到的交易,在网络全连的情况下,这种交易转发策略无疑会带来巨大的带宽浪费,且节点数目越多,因交易转发带来的数据包冗余越多。

为降低SDK直连节点的出带宽、降低交易转发引起的大量冗余消息包,提升区块链系统的可扩展性,FISCO BCOS v2.2.0提出了交易广播优化策略交易转发优化策略

交易广播优化策略

为了降低SDK直连节点交易广播带来的网络压力,FISCO BCOS v2.2.0中,SDK直连节点收到交易后,沿着树状拓扑广播交易(树的宽度默认为3)。下图展示了优化前后7节点区块链系统交易广播拓扑:

_images/txs_broadcast_optimize.png

  • 优化前:节点收到SDK的交易后,全量广播给其他节点;
  • 优化后:节点收到SDK的交易后,将其发送给子节点,子节点收到交易后,继续将其发送给自身的子节点。

采用交易树状广播后,上图所示的7节点区块链系统,SDK直连节点的带宽降低为原先的一半,且由于SDK直连节点以及其他节点广播交易的出带宽仅与树状拓扑的宽度有关,因此优化后的交易同步具有可扩展性。

交易转发优化策略

交易转发对于交易同步尤为重要,可以包含部分节点网络断连情况下,SDK发出的交易能尽量到达所有节点。但正如前面提到的,已有的交易转发策略会带来大量的带宽冗余,因此在交易树状广播的基础上,FISCO BCOS v2.2.0提出了交易转发优化策略,如下图所示,优化后的交易转发策略不直接转发交易,仅转发交易状态,节点根据其他节点的交易状态,获取缺失的交易,然后直接向对应节点请求交易。

_images/txs_status.png

上图中,SDK直连node0,但是node0node1断连,此时node0仅能将交易广播给node2node3node2node3收到交易后,将最新交易的列表打包成状态包发送给其他节点,node1node4收到状态包后,与本地交易池内的交易列表做对比,获取缺失的交易列表,并批量向拥有这些交易的node2node3请求这些交易。

交易转发具体流程如下:

  • 节点收到新交易txs后,获取所有新交易的哈希,记为txs_hash_list,并将其打包成状态包,随机发送给25%的节点;
  • 节点node_x收到某节点node_i交易状态包后,从中解出交易哈希列表txs_hash_list,并将其与本地交易池中的交易列表做对比,获取缺失的交易列表,记为missed_txs_hash_list,将其打包成交易请求,向node_i发出交易请求;
  • node_i接收到交易请求后,从交易池中取出missed_txs_hash_list对应的所有交易,回复给node_x

由于在全连的网络拓扑中,所有节点交易状态基本一致,因此节点间交易请求较少,相较于直接转发交易,大大降低了转发冗余交易引起的带宽浪费。

共识算法

区块链系统通过共识算法保障系统一致性。 理论上,共识是对某个提案(proposal)达成一致意见的过程,分布式系统中提案的含义十分宽泛,包括事件发生顺序、谁是leader等。区块链系统中,共识是各个共识节点对交易执行结果达成一致的过程。

共识算法分类

根据是否容忍 拜占庭错误 ,共识算法可分为容错(Crash Fault Tolerance, CFT)类算法和拜占庭容错(Byzantine Fault Tolerance, BFT)类算法:

  • CFT类算法 :普通容错类算法,当系统出现网络、磁盘故障,服务器宕机等普通故障时,仍能针对某个提议达成共识,经典的算法包括Paxos、Raft等,这类算法性能较好、处理速度较快、可以容忍不超过一半的故障节点;
  • BFT类算法 :拜占庭容错类算法,除了容忍系统共识过程中出现的普通故障外,还可容忍部分节点故意欺骗(如伪造交易执行结果)等拜占庭错误,经典算法包括PBFT等,这类算法性能较差,能容忍不超过三分之一的故障节点。

FISCO BCOS共识算法

FISCO BCOS基于多群组架构实现了插件化的共识算法,不同群组可运行不同的共识算法,组与组之间的共识过程互不影响,FISCO BCOS目前支持PBFT(Practical Byzantine Fault Tolerance)和Raft(Replication and Fault Tolerant)两种共识算法:

  • PBFT共识算法: BFT类算法,可容忍不超过三分之一的故障节点和作恶节点,可达到最终一致性;
  • Raft共识算法: CFT类算法, 可容忍一半故障节点,不能防止节点作恶,可达到一致性。

框架

FISCO BCOS实现了一套可扩展的共识框架,可插件化扩展不同的共识算法,目前支持 PBFT(Practical Byzantine Fault Tolerance)Raft(Replication and Fault Tolerant) 共识算法,共识模块框架如下图:

_images/architecture1.png

Sealer线程

交易打包线程,负责从交易池取交易,并基于节点最高块打包交易,产生新区块,产生的新区块交给Engine线程处理,PBFT和Raft的交易打包线程分别为PBFTSealer和RaftSealer。

Engine线程

共识线程,负责从本地或通过网络接收新区块,并根据接收的共识消息包完成共识流程,最终将达成共识的新区块写入区块链(BlockChain),区块上链后,从交易池中删除已经上链的交易,PBFT和Raft的共识线程分别为PBFTEngine和RaftEngine。

PBFT基础流程

PBFT(Practical Byzantine Fault Tolerance)共识算法可以在少数节点作恶(如伪造消息)场景中达成共识,它采用签名、签名验证、哈希等密码学算法确保消息传递过程中的防篡改性、防伪造性、不可抵赖性,并优化了前人工作,将拜占庭容错算法复杂度从指数级降低到多项式级别,在一个由(3*f+1)个节点构成的系统中,只要有不少于(2*f+1)个非恶意节点正常工作,该系统就能达成一致性,如:7个节点的系统中允许2个节点出现拜占庭错误。

FISCO BCOS区块链系统实现了PBFT共识算法。

1. 重要概念

节点类型、节点ID、节点索引和视图是PBFT共识算法的关键概念。区块链系统基本概念请参考关键概念

1.1 节点类型
  • Leader/Primary: 共识节点,负责将交易打包成区块和区块共识,每轮共识过程中有且仅有一个leader,为了防止leader伪造区块,每轮PBFT共识后,均会切换leader;
  • Replica: 副本节点,负责区块共识,每轮共识过程中有多个Replica节点,每个Replica节点的处理过程类似;
  • Observer: 观察者节点,负责从共识节点或副本节点获取最新区块,执行并验证区块执行结果后,将产生的区块上链。

其中Leader和Replica统称为共识节点。

1.2 节点ID && 节点索引

为了防止节点作恶,PBFT共识过程中每个共识节点均对其发送的消息进行签名,对收到的消息包进行验签名,因此每个节点均维护一份公私钥对,私钥用于对发送的消息进行签名,公钥作为节点ID,用于标识和验签。

节点ID : 共识节点签名公钥和共识节点唯一标识, 一般是64字节二进制串,其他节点使用消息包发送者的节点ID对消息包进行验签

考虑到节点ID很长,在共识消息中包含该字段会耗费部分网络带宽,FISCO BCOS引入了节点索引,每个共识节点维护一份公共的共识节点列表,节点索引记录了每个共识节点ID在这个列表中的位置,发送网络消息包时,只需要带上节点索引,其他节点即可以从公共的共识节点列表中索引出节点的ID,进而对消息进行验签:

节点索引 : 每个共识节点ID在这个公共节点ID列表中的位置
1.3 视图(view)

PBFT共识算法使用视图view记录每个节点的共识状态,相同视图节点维护相同的Leader和Replicas节点列表。当Leader出现故障,会发生视图切换,若视图切换成功(至少2*f+1个节点达到相同视图),则根据新的视图选出新leader,新leader开始出块,否则继续进行视图切换,直至全网大部分节点(大于等于2*f+1)达到一致视图。

FISCO BCOS系统中,leader索引的计算公式如下:

leader_idx = (view + block_number) % node_num

下图简单展示了4(3*f+1, f=1)节点FISCO BCOS系统中,第三个节点(node3)为拜占庭节点情况下,视图切换过程:

_images/pbft_view.png

  • 前三轮共识: node0、node1、node2为leader,且非恶意节点数目等于2*f+1,节点正常出块共识;
  • 第四轮共识:node3为leader,但node3为拜占庭节点,node0-node2在给定时间内未收到node3打包的区块,触发视图切换,试图切换到view_new=view+1的新视图,并相互间广播viewchange包,节点收集满在视图view_new上的(2*f+1)个viewchange包后,将自己的view切换为view_new,并计算出新leader;
  • 为第五轮共识:node0为leader,继续打包出块。
1.4 共识消息

PBFT模块主要包括PrepareReq、SignReq、CommitReq和ViewChangeReq四种共识消息:

  • PrepareReqPacket: 包含区块的请求包,由leader产生并向所有Replica节点广播,Replica节点收到Prepare包后,验证PrepareReq签名、执行区块并缓存区块执行结果,达到防止拜占庭节点作恶、保证区块执行结果的最终确定性的目的;
  • SignReqPacket: 带有区块执行结果的签名请求,由收到Prepare包并执行完区块的共识节点产生,SignReq请求带有执行后区块的hash以及该hash的签名,分别记为SignReq.block_hash和SignReq.sig,节点将SignReq广播到所有其他共识节点后,其他节点对SignReq(即区块执行结果)进行共识;
  • CommitReqPacket: 用于确认区块执行结果的提交请求,由收集满(2*f+1)个block_hash相同且来自不同节点SignReq请求的节点产生,CommitReq被广播给所有其他共识节点,其他节点收集满(2*f+1)个block_hash相同、来自不同节点的CommitReq后,将本地节点缓存的最新区块上链;
  • ViewChangeReqPacket: 视图切换请求,当leader无法提供正常服务(如网络连接不正常、服务器宕机等)时, 其他共识节点会主动触发视图切换,ViewChangeReq中带有该节点即将切换到的视图(记为toView,为当前视图加一),某节点收集满(2*f+1)个视图等于toView、来自不同节点的ViewChangeReq后,会将当前视图切换为toView。

这四类消息包包含的字段大致相同,所有消息包共有的字段如下:

字段名 字段含义
字段名 字段含义
idx 当前节点索引
packetType 请求包类型(包括PrepareReqPacket/SignReqPacket/CommitReqPacket/ViewChangeReqPacket)
height 当前正在处理的区块高度(一般是本地区块高度加一)
blockHash 当前正在处理的区块哈希
view 当前节点所处的视图
sig 当前节点对blockHash的签名

PrepareReqPacket类型消息包包含了正在处理的区块信息:

消息包类型 字段名 字段含义
PrepareReqPacket block 所有共识节点正在共识的区块数据
2. 系统框架

系统框架如下图:

_images/pbft_architecture.png

PBFT共识主要包括两个线程:

  • PBFTSealer: PBFT打包线程,负责从交易池取交易,并将打包好的区块封装成PBFT Prepare包,交给PBFTEngine处理;
  • PBFTEngine: PBFT共识线程,从PBFTSealer或者P2P网络接收PBFT共识消息包,区块验证器(Blockverifier)负责开始执行区块,完成共识流程,将达成共识的区块写入区块链,区块上链后,从交易池中删除已经上链的交易。
3. 核心流程

PBFT共识主要包括Pre-prepare、Prepare和Commit三个阶段:

  • Pre-prepare:负责执行区块,产生签名包,并将签名包广播给所有共识节点;
  • Prepare:负责收集签名包,某节点收集满2*f+1的签名包后,表明自身达到可以提交区块的状态,开始广播Commit包;
  • Commit:负责收集Commit包,某节点收集满2*f+1的Commit包后,直接将本地缓存的最新区块提交到数据库。

_images/pbft_process.png

下图详细介绍了PBFT各个阶段的具体流程:

graph TB classDef blue fill:#4C84FF,stroke:#4C84FF,stroke-width:4px, font:#1D263F, text-align:center; classDef yellow fill:#FFEEB8,stroke:#FFEEB8,stroke-width:4px, font:#1D263F, text-align:center; classDef light fill:#EBF5FF,stroke:#1D263F,stroke-width:2px, font:#1D263F, text-align:center; subgraph 共识处理流程 A((开始))-->B B(获取PBFT请求类型)-->|Prepare请求|C B-->|Sign请求|D B-->|Commit请求|F C(Prepare是否有效?)-->|是|G C-->|否|B G(addRawPrepare<br/>缓存Prepare请求)-->H H(Prepare内区块是空块?)-->|否|I H-->|是|T T(视图切换) I(execBlock<br/>执行Prepare内区块)-->J J(generateSignPacket<br/>产生签名请求)-->K K(addPrepareCache<br/>缓存执行后区块)-->L L(broadcacstSignReq<br/>广播签名请求) D(isSignReqValid<br/>签名请求是否有效?)-->|是|M D-->|否|B M(addSignReq<br/>缓存收到的签名请求)-->N N(checkSignEnough<br/>签名请求是否达到2*f+1?)-->|是|O N-->|否|B O(updateLocalPrepare<br/>备份Prepare请求)-->P P(broadcastCommitReq<br/>广播Commit请求, 表明节点已达到可提交区块状态) F(isCommitReqValid <br/> Commit请求是否有效?)-->|是|Q Q(addCommitReq <br/> 缓存Commit请求)-->R R(checkCommitEnough <br/> Commit请求是否达到2*f+1?)-->|是|S R-->|否|B S(CommitBlock<br> 将缓存的执行后区块提交到DB) class A,B light class C,G,H,I,J,K,L,T light class D,M,N,O,P light class Q,F,R,S light end
3.1 leader打包区块

PBFT共识算法中,共识节点轮流出块,每一轮共识仅有一个leader打包区块,leader索引通过公式(block_number + current_view) % consensus_node_num计算得出。

节点计算当前leader索引与自己索引相同后,就开始打包区块。区块打包主要由PBFTSealer线程完成,Sealer线程的主要工作如下图所示:

_images/sealer.png

  • 产生新的空块: 通过区块链(BlockChain)获取当前最高块,并基于最高块产生新空块(将新区块父哈希置为最高块哈希,时间戳置为当前时间,交易清空);
  • 从交易池打包交易: 产生新空块后,从交易池中获取交易,并将获取的交易插入到产生的新区块中;
  • 组装新区块: Sealer线程打包到交易后,将新区块的打包者(Sealer字段)置为自己索引,并根据打包的交易计算出所有交易的transactionRoot;
  • 产生Prepare包: 将组装的新区块编码到Prepare包内,通过PBFTEngine线程广播给组内所有共识节点,其他共识节点收到Prepare包后,开始进行三阶段共识。
3.2 pre-prepare阶段

共识节点收到Prepare包后,进入pre-prepare阶段,此阶段的主要工作流程包括:

  • Prepare包合法性判断:主要判断是否是重复的Prepare包、Prepare请求中包含的区块父哈希是否是当前节点最高块哈希(防止分叉)、Prepare请求中包含区块的块高是否等于最高块高加一;
  • 缓存合法的Prepare包: 若Prepare请求合法,则将其缓存到本地,用于过滤重复的Prepare请求;
  • 空块判断:若Prepare请求包含的区块中交易数目是0,则触发空块视图切换,将当前视图加一,并向所有其他节点广播视图切换请求;
  • 执行区块并缓存区块执行结果: 若Prepare请求包含的区块中交易数目大于0,则调用BlockVerifier区块执行器执行区块,并缓存执行后的区块;
  • 产生并广播签名包:基于执行后的区块哈希,产生并广播签名包,表明本节点已经完成区块执行和验证。
3.3 Prepare阶段

共识节点收到签名包后,进入Prepare阶段,此阶段的主要工作流程包括:

  • 签名包合法性判断:主要判断签名包的哈希与pre-prepare阶段缓存的执行后的区块哈希相同,若不相同,则继续判断该请求是否属于未来块签名请求(产生未来块的原因是本节点处理性能低于其他节点,还在进行上一轮共识,判断未来块的条件是:签名包的height字段大于本地最高块高加一),若请求也非未来块,则是非法的签名请求,节点直接拒绝该签名请求;
  • 缓存合法的签名包:节点会缓存合法的签名包;
  • 判断pre-prepare阶段缓存的区块对应的签名包缓存是否达到2*f+1,若收集满签名包,广播Commit包:若pre-prepare阶段缓存的区块哈希对应的签名包数目超过2*f+1,则说明大多数节点均执行了该区块,并且执行结果一致,说明本节点已经达到可以提交区块的状态,开始广播Commit包;
  • 若收集满签名包,备份pre-prepare阶段缓存的Prepare包落盘:为了防止Commit阶段区块未提交到数据库之前超过2*f+1个节点宕机,这些节点启动后重新出块,导致区块链分叉(剩余的节点最新区块与这些节点最高区块不同),还需要备份pre-prepare阶段缓存的Prepare包到数据库,节点重启后,优先处理备份的Prepare包。
3.4 Commit阶段

共识节点收到Commit包后,进入Commit阶段,此阶段工作流程包括:

  • Commit包合法性判断:主要判断Commit包的哈希与pre-prepare阶段缓存的执行后的区块哈希相同,若不相同,则继续判断该请求是否属于未来块Commit请求(产生未来块的原因是本节点处理性能低于其他节点,还在进行上一轮共识,判断未来块的条件是:Commit的height字段大于本地最高块高加一),若请求也非未来块,则是非法的Commit请求,节点直接拒绝该请求;
  • 缓存合法的Commit包:节点缓存合法的Commit包;
  • 判断pre-prepare阶段缓存的区块对应的Commit包缓存是否达到2*f+1,若收集满Commit包,则将新区块落盘:若pre-prepare阶段缓存的区块哈希对应的Commit请求数目超过2*f+1,则说明大多数节点达到了可提交该区块状态,且执行结果一致,则调用BlockChain模块将pre-prepare阶段缓存的区块写入数据库;
3.5 视图切换处理流程

当PBFT三阶段共识超时或节点收到空块时,PBFTEngine会试图切换到更高的视图(将要切换到的视图toView加一),并触发ViewChange处理流程;节点收到ViewChange包时,也会触发ViewChange处理流程:

  • 判断ViewChange包是否有效: 有效的ViewChange请求中带有的块高值必须不小于当前节点最高块高,视图必须大于当前节点视图;
  • 缓存有效ViewChange包: 防止相同的ViewChange请求被处理多次,也作为判断节点是否可以切换视图的统计依据;
  • 收集ViewChange包:若收到的ViewChange包中附带的view等于本节点的即将切换到的视图toView且本节点收集满2*f+1来自不同节点view等于toView的ViewChange包,则说明超过三分之二的节点要切换到toView视图,切换当前视图到toView。

PBFT网络优化

FISCO BCOS v2.2.0优化了PBFT消息转发机制和Prepare包的结构,尽量减少网络中冗余的数据包,提升网络效率。

PBFT消息转发优化

为了保证节点断连情况下共识消息包能到达所有节点,FISCO BCOS PBFT共识模块采用了消息转发机制,优化前的消息转发机制如下:

sequenceDiagram participant consensusNodeA participant consensusNodeB consensusNodeA->>consensusNodeB: 发送PBFT消息msg{id, ttl} consensusNodeB->>consensusNodeB: 缓存{id, consensusNodeB nodeID}到广播过滤列表filter consensusNodeB->>consensusNodeB: 转发pbft msg{id, ttl-1} Note right of ConsensusNodeB: ttl > 1, 向未缓存于filter<br/>节点转发PBFT消息<br/>msg{id, ttl-1}

对于全连四节点区块链系统,系统TTL设置为2时,每个共识消息包均会被转发三次,且节点规模越大、TTL值越大冗余的共识消息包越多。且Leader广播的Prepare包内含有整个区块,多次转发同样的Prepare包会带来巨大的网络开销。

_images/pbft_forward_demo.png

为了在网络全连的情况下,避免冗余的共识消息包;在网络断连情况下,共识消息包能尽量到达每个共识节点,FISCO BCOS v2.2.0对PBFT消息转发机制进行了优化,优化后的PBFT消息转发流程如下:

sequenceDiagram participant ConsensusNodeA participant ConsensusNodeB participant Neighbors Of ConsensusNodeB ConsensusNodeA->>ConsensusNodeA: 获取断连共识节点列表forwardNodes ConsensusNodeA->>ConsensusNodeB: 发送PBFT消息msg{id, ttl, forwardNodes} ConsensusNodeB->>ConsensusNodeB: 更新forwardNodes Note right of ConsensusNodeB: forwardNodes不为空,<br/>将forwardNodes中<br/>非邻居节点过滤出来,<br/>记为forwardNodes2<br/>若forwardNodes为空,<br/>不转发PBFT消息 ConsensusNodeB->>Neighbors Of ConsensusNodeB: 转发msg{id, ttl, forwardNodes2}

下图展示了四节点区块链系统在节点断连情况下,PBFT消息包转发流程:

_images/pbft_optimized_forward.png

  • node0{node1, node2, node3}发送PBFT消息,发现{node1, node3}不在连接列表内,则将PBFT消息msg的forwardNodes字段设置为{node1, node3},并将其转发给node2
  • node2收到node1的PBFT消息后,判断forwardNodes字段不为空,则遍历邻居节点列表{node1, node3},并将邻居节点从forwardNodes中移除;
  • node2node1node3转发更新后的PBFT消息msg;
  • node1node3收到msg后,判断forwardNodes字段为空,认为该消息已经到达了所有节点,不继续转发PBFT消息。

优化后的PBFT消息转发策略,源节点在PBFT消息包中加入了forwardNodes字段记录断连节点信息,其他节点收到PBFT消息包后,将消息转发给forwardNodes记录的可达节点,保障PBFT消息包尽量能到达所有节点的同时,减少了网络中冗余的PBFT消息,提升网络效率。

Prepare包结构优化

PBFT共识算法中,Leader向所有节点广播Prepare包,Prepare包内包含Leader节点从交易池打包的整个区块,由于同步模块会将交易同步到所有共识节点,因此Prepare包内区块的交易有很大概率在其他共识节点的交易池命中。基于这点,FISCO BCOS 2.2.0优化了Prepare包结构,Prepare消息包内的区块仅包含交易哈希,其他节点收到Prepare包后,优先从本地交易池内获取命中交易,缺失的交易向Leader请求。

优化后的Prepare消息包内的区块结构如下:

_images/partial_block.png

Prepare包处理流程如下:

sequenceDiagram participant Leader participant ConsensusNodes Leader->>ConsensusNodes: 广播Prepare包 ConsensusNodes->>ConsensusNodes: 从交易池获取命中交易 Note right of ConsensusNodes: 解码Prepare包,<br/>获取交易哈希列表 <br/> 从本地交易池<br/>获取命中交易 ConsensusNodes->>Leader: 请求缺失交易 Note right of ConsensusNodes: 若有交易缺失 <br/> 向Leader节点发送交<br/>易请求txs_request <br/> 包括{block_hash, txs_hash_list} Leader->>ConsensusNodes: 回复交易 Note left of Leader: 缓存的Prepare包哈希<br/>与txs_request的<br/>block_hash一致<br/>返回请求的交易列表,<br/>记为txs_response ConsensusNodes->>ConsensusNodes: 填充Prepare包内区块 Note right of ConsensusNodes: 解码txs_response<br/>将其填充到Prepare包<br/>内的区块中

优化Prepare结构后,充分利用了交易池缓存的交易,进一步降低了Prepare消息包的大小,节省了网络流量。

Raft

1 名词解释
1.1 Raft

Raft(Replication and Fault Tolerant)是一个允许网络分区(Partition Tolerant)的一致性协议,它保证了在一个由N个节点构成的系统中有(N+1)/2(向上取整)个节点正常工作的情况下的系统的一致性,比如在一个5个节点的系统中允许2个节点出现非拜占庭错误,如节点宕机、网络分区、消息延时。Raft相比于Paxos更容易理解,且被证明可以提供与Paxos相同的容错性以及性能,其详细介绍可见官网动态演示

1.2 节点类型

在Raft算法中,每个网络节点只能如下三种身份之一:LeaderFollower以及Candidate,其中:

  • Leader:主要负责与外界交互,由Follower节点选举而来,在每一次共识过程中有且仅有一个Leader节点,由Leader全权负责从交易池中取出交易、打包交易组成区块并将区块上链;
  • Follower:以Leader节点为准进行同步,并在Leader节点失效时举行选举以选出新的Leader节点;
  • Candidate:Follower节点在竞选Leader时拥有的临时身份。
1.3 节点ID & 节点索引

在Raft算法中,每个网络节点都会有一个固定且全局的唯一的用于表明节点身份的ID(一般是一个64字节表示数字),这称为节点ID;同时每个共识节点还会维护一份公共的共识节点列表,这个列表记录了每个共识节点的ID,而自己在这个列表中的位置被称为节点索引。

1.4 任期

Raft算法将时间划分为不定长度的任期Terms,Terms为连续的数字。每个Term以选举开始,如果选举成功,则由当前leader负责出块,如果选举失败,并没有选举出新的单一Leader,则会开启新的Term,重新开始选举。 _images/raft_terms.png.

1.5 消息

在Raft算法中,每个网络节点间通过发送消息进行通讯,当前Raft模块包括四种消息:VoteReqVoteRespHeartbeatHeartbeatResp,其中:

  • VoteReq:投票请求,由Candidate节点主动发出,用于向网络中其他节点请求投票以竞选Leader;
  • VoteResp:投票响应,在节点收到投票请求后,用于对投票请求进行响应,响应内容为同意或拒绝该投票请求;
  • Heartbeat:心跳,由Leader节点主动周期发出,其作用有两个:(1) 用于维护Leader节点身份,只要Leader能够一直正常发送心跳且被其他节点响应,Leader身份就不会发生变化;(2) 区块数据复制,当Leader节点成功打包一个区块后,会将区块数据编码至心跳中以将区块进行广播,其他节点在收到该心跳后会解码出区块数据并将区块放入自己的缓冲区中;
  • HeartbeatResp:心跳响应,在节点收到心跳后,用于对心跳进行响应,特别的,当收到一个包含区块数据的心跳时,该心跳的响应中会带上该区块的哈希;

所有消息共有的字段如下表所示:

字段名 字段含义
idx 自身节点索引
term 前节点所处在的任期
height 当前节点所持有的最高块的块高
blockHash 前节点所持有的最高块的哈希

每种消息类型特有的字段如下表所示:

消息类型 字段名 字段含义
VoteReq candidate Candidate自身的节点索引
lastLeaderTerm Candidate见到过的最后一个Leader的Term,其详细作用见3.1.2节
lastBlockNumber Candidate见到过的最新块的块高,其详细作用见3.1.2节
VoteResp voteFlag 对投票请求的响应标志位,用以标记对是否同意投票请求,若是拒绝还会具体标记拒绝原因,其详细作用见3.1.2节
lastLeaderTerm 收到VoteReq的节点见到过的最新块的块高,其详细作用见3.1.2节
Heartbeat leader 发出心跳的Leader节点的节点索引
uncommitedBlock 当Leader节点预备提交一个新块时,会先将区块数据编码进此字段以并通过心跳进行广播,其详细作用见3.2节
uncommitedBlockNumber uncommitedBlock对应的块高,其详细作用见3.2节
HeartbeatResp uncommitedBlockHash 当收到Leader发送过来的uncommitedBlock数据时,节点在心跳响应中写入uncommitedBlock对应的哈希(指纹),并发送回Leader,表明节点已经收到Leader预备提交的区块数据且已写入本地缓存,其详细作用见3.2节
2 系统框架

系统框架如下图所示:

_images/raft_architecture.png

  • Raft Sealer:负责从交易池取出交易并打包成区块,并发送至Raft Engine进行共识。区块上链后,Raft Sealer负责从交易池中删除已上链交易;
  • Raft Engine:负责在共识节点进行共识,将达成共识的区块上链。
3 核心流程
3.1 节点状态转换

节点类型之间转换关系如下图所示,每种状态转换形式将在接下来的各个小节进行阐述:

_images/raft_nodes_transfer.jpg

3.1.1 选举

Raft共识模块中使用心跳机制来触发Leader选举。当节点启动时,节点自动成为Follower且将Term置0。只要Follower从Leader或者Candidate收到有效的Heartbeat或RequestVote消息,其就会保持在Follower状态,如果Follower在一段时间内(这段时间称为 Election Timeout)没收到上述消息,则它会假设系统当前的Leader已经失活,然后增加自己的Term并转换为Candidiate,开启新一轮的Leader选举流程,流程如下:

  1. Follower增加当前的Term,转换为Candidate;
  2. Candidate将票投给自己,并广播RequestVote到其他节点请求投票;
  3. Candidate节点保持在Candidate状态,直到下面三种情况中的一种发生:(1)该节点赢得选举;(2) 在等待选举期间,Candidate收到了其他节点的Heartbeat;(3) 经过Election Timeout后,没有Leader被选出。Raft算法采用随机定时器的方法来避免节点选票出现平均瓜分的情况以保证大多数时候只会有一个节点超时进入Candidate状态并获得大部分节点的投票成为Leader。
3.1.2 投票

节点在收到VoteReq消息后,会根据消息的内容选择不同的响应策略:

  1. VoteReq的Term小于或等于自己的Term

    • 如果节点是Leader,则拒绝该投票请求,Candidate收到此响应后会放弃选举转变为Follower,并增加投票超时;
    • 如果节点不是Leader:
      • 如果VoteReq的Term小于自己的Term,则拒绝该投票请求,如果Candidate收到超过半数的该种响应则表明其已经过时,此时Candidate会放弃选举转变为Follower,并增加投票超时;
      • 如果VoteReq的Term等于自己的Term,则拒绝该投票请求,对于该投票请求不作任何处理。对于每个节点而言,只能按照先到先得的原则投票给一个Candidate,从而保证每轮选举中至多只有一个Candidate被选为Leader。
  2. VoteReq的lastLeaderTerm小于自己的lastLeaderTerm

    每个节点中会有一个lastLeaderTerm字段表示该节点见过的最后一个Leader的Term,lastLeaderTerm仅能由Heartbeat进行更新。如果VoteReq中的lastLeaderTerm小于自己的lastLeaderTerm,表明Leader访问这个Candidate存在问题,如果此时Candidate处于网络孤岛的环境中,会不断向外提起投票请求,因此需要打断它的投票请求,所以此时节点会拒绝该投票请求。

  3. VoteReq的lastBlockNumber小于自己的lastBlockNumber

    每个节点中会有一个lastBlockNumber字段表示节点见到过的最新块的块高。在出块过程中,节点间会进行区块复制(详见3.2节),在区块复制的过程中,可能有部分节点收到了较新的区块数据而部分没有,从而导致不同节点的lastBlockNumber不一致。为了使系统能够达成一致,需要要求节点必须把票投给拥有较新数据的节点,因此在这种情况下节点会拒绝该投票请求。

  4. 节点是第一次投票

    为了避免出现Follower因为网络抖动导致重新发起选举,规定如果节点是第一次投票,直接拒绝该投票请求,同时会将自己的firstVote字段置为该Candidate的节点索引。

  5. 1~4步骤中都没有拒绝投票请求

    同意该投票请求。

3.1.3 心跳超时

在Leader成为网络孤岛时,Leader可以发出心跳、Follower可以收到心跳但是Leader收不到心跳回应,这种情况下Leader此时已经出现网络异常,但是由于一直可以向外发送心跳包会导致Follower无法切换状态进行选取,系统陷入停滞。为了避免第二种情况发生,模块中设置了心跳超时机制,Leader每次收到心跳回应时会进行相应记录,一旦一段时间后记录没有更新则Leader放弃Leader身份并转换为Follower节点。

3.2 区块复制

Raft协议强依赖Leader节点的可用性来确保集群数据的一致性,因为数据只能从Leader节点向Follower节点转移。当Raft Sealer向集群Leader提交区块数据后,Leader将该数据置为未提交(uncommitted)状态,接着Leader 节点会通过在Heartbeat中附加数据的形式并发向所有Follower节点复制数据并等待接收响应,在确保网络中超过半数节点已接收到数据后,再将区块数据写入底层存储中,此时区块数据状态已经进入已提交(committed)状态。此后Leader节点再通过Sync模块向其他Follower节点广播该区块数据,区块复制及提交的流程图如下图所示:

sequenceDiagram participant Sealer participant Leader participant Follower Sealer->>Leader: 将交易打包为区块,阻塞自身 Leader->>Follower: 将区块编码为RLP编码随心跳包发送 Note right of Follower: 对心跳包进行解码,<br/>并将解码出来的区块<br/>写入缓存中 Follower->>Leader: 发送ACK loop 收集ACK Leader->>Leader: 检查大多数节点是否已经收到区块拷贝 end Leader->>Sealer: 解除阻塞 Leader->>Leader: 执行区块 Leader->>Leader: 丢弃已经上链的交易

其中RaftSealer验证是否当前是否能打包交易的验证条件包括:(1) 是否为Leader;(2) 是否存在尚未完成同步的peer; (3) uncommitBlock字段是否为空,只有三个条件均符合才允许打包。

rPBFT

区块链共识困境
POW类算法

POW算法因如下特点,不适用于交易吞吐量大、交易时延要求低的联盟链场景:

  • 性能低:10分钟出一个区块,交易确认时延一个小时,耗电多
  • 无最终一致性保证
  • 吞吐量低
基于分布式一致性原理的共识算法

基于分布式一致性原理的共识算法,如BFT类和CFT类共识算法具有秒级交易确认时延、最终一致性、吞吐量高、不耗电等优势,尤其是BFT类共识算法还可应对节点作恶的场景,在性能、安全性等方面均可达到联盟链需求。

但这类算法复杂度均与节点规模有关,可支撑的网络规模有限,极大限制了联盟链节点规模。

综上所述,FISCO BCOS v2.3.0提出了rPBFT共识算法,旨在保留BFT类共识算法高性能、高吞吐量、高一致性、安全性的同时,尽量减少节点规模对共识算法的影响。

rPBFT共识算法
节点类型
  • 共识委员:执行PBFT共识流程的节点,有轮流出块权限
  • 验证节点:不执行共识流程,验证共识节点是否合法、区块验证,经过若干轮共识后,会切换为共识节点
核心思想

_images/rpbft.png

rPBFT算法每轮共识流程仅选取若干个共识节点出块,并根据区块高度周期性地替换共识节点,保障系统安全,主要包括2个系统参数:

  • epoch_sealer_num:每轮共识过程中参与共识的节点数目,可通过控制台发交易方式动态配置该参数
  • epoch_block_num: 共识节点替换周期,为防止选取的共识节点联合作恶,rPBFT每出epoch_block_num个区块,会替换一个共识节点,可通过控制台发交易的方式动态配置该参数

这两个配置项记录在系统配置表中,配置表主要包括配置关键字、配置对应的值、生效块高三个字段,其中生效块高记录了配置最新值最新生效块高,例:在100块发交易将epoch_sealer_numepoch_block_num分别设置为4和10000,此时系统配置表如下:

key value enable_num
epoch_sealer_num 4 101
epoch_block_num 10000 101
算法流程
确定各共识节点编号IDX

对所有共识节点的NodeID进行排序,如下图,节点排序后的NodeID索引即为该共识节点编号:

_images/sealer_order.png

链初始化

链初始化时,rPBFT需要选取epoch_sealer_num个共识节点到共识委员中参与共识,目前初步实现是选取索引为0到epoch_sealer_num-1的节点参与前epoch_block_num个区块共识。

共识委员节点运行PBFT共识算法

选取的epoch_sealer_num个共识委员节点运行PBFT共识算法,验证节点同步并验证这些共识委员节点共识产生的区块,验证节点的验证步骤包括:

  • 校验区块签名列表:每个区块必须至少包含三分之二共识委员的签名
  • 校验区块执行结果:本地区块执行结果须与共识委员在区块头记录的执行结果一致
动态替换共识委员列表

为保障系统安全性,rPBFT算法每出epoch_block_num个区块后,会从共识委员列表中剔除一个节点作为验证节点,并加入一个验证节点到共识委员列表中,如下图所示:

_images/epoch_rotating.png

rPBFT算法目前实现中,轮流将共识委员列表节点替换为验证节点,设当前有序的共识委员会节点列表为CommitteeSealersList,共识节点总数为N,则共识epoch_block_num个区块后,会将CommitteeSealersList[0]剔除共识委员列表,并加入索引为(CommitteeSealersList[0].IDX + epoch_sealer_num) % N的验证节点到共识委员列表中。第i轮替换周期,将CommitteeSealersList[i % epoch_sealer_num]剔除共识委员列表,加入索引为(CommitteeSealersList[i%epoch_sealer_num].IDX + epoch_sealer_num) % N的验证节点到共识委员列表中。

节点重启

节点重启后,rPBFT算法需要快速确定共识委员列表,由于epoch_block_num可通过控制台动态更新,需要结合epoch_block_num最新配置生效块高获取共识委员列表,主要步骤如下:

计算共识周期rotatingRound

设当前块高为blockNumepoch_block_num生效块高为enableNum,则共识周期为: rotatingRound = (blockNumber - enableNum) / epoch_block_num

确定共识委员起始节点索引: N为共识节点总数,索引从(rotatingRound * epoch_block_num) % N(rotatingRound * epoch_block_num + epoch_sealer_num) % N之间的节点均属于共识委员节点

rPBFT算法分析
  • 网络复杂度:O(epoch_sealer_num * epoch_sealer_num),与节点规模无关,可扩展性强于PBFT共识算法
  • 性能:可秒级确认,且由于算法复杂度与节点数无关,性能衰减远小于PBFT
  • 一致性、可用性要求:需要至少三分之二的共识委员节点正常工作,系统才可正常共识
  • 安全性:未来将引入VRF算法,随机、私密地替换共识委员,增强共识算法安全性
rPBFT网络优化
Prepare包广播优化

为进一步提升Prepare包在带宽有限场景下广播效率,FISCO BCOS v2.3.0在rPBFT的基础上实现了Prepare包树状广播,如下图所示:

_images/broadcast_prepare_by_tree.png

  • 根据共识节点索引,构成完全n叉树(默认是3)
  • Leader产生Prepare包后,沿着树状拓扑将Prepare包转发给其所有下属子节点

优势

  • 传播速度比gossip快,无冗余消息包
  • 分而治之,每个节点出带宽为O(1),可扩展性强

劣势: 中间节点是单点,需要额外的容错策略

基于状态包的容错方案

注解

基于状态包的容错策略仅在开启Prepare包树状广播时生效

为保证节点断连情况下,开启树状广播时,Prepare包能到达每个节点,rPBFT引入了基于状态包的容错机制,如下图所示:

_images/prepare_tree_broadcast_fault_tolerant.png

主要流程包括:

(1) 节点A收到Prepare后,随机选取33%节点广播Prepare包状态,记为prepareStatus,包括{blockNumber, blockHash, view, idx}

(2) 节点B收到节点A随机广播过来的prepareStatus后,判断节点A的Prepare包状态是否比节点B当前Prepare包localPrepare状态新,主要判断包括:

  • prepareStatus.blockNumber是否大于当前块高
  • prepareStatus.blockNumber是否大于localPrepare.blockNumber
  • prepareStatus.blockNumber等于localPrepare.blockNumber情况下,prepareStatus.view是否大于localPrepare.view

以上任意一个条件成立,都说明节点A的Prepare包状态比节点B的状态新

(3) 若节点B的状态落后于节点A,且节点B与其父节点断连,则节点B向节点A发出prepareRequest请求,请求相应的Prepare包

(4) 若节点B的状态落后于节点A,但节点B与其父节点相连,若节点B最多等待100ms(可配)后,状态仍然落后于节点A,则节点B向节点A发出prepareRequest请求,请求相应的Prepare包

(5) 节点B收到节点A的prepareRequest请求后,向其回复相应的Prepare消息包

(6) 节点A收到节点B的Prepare消息包后,执行handlePrepare流程处理收到的Prepare包。

流量负载均衡策略

注解

流量负载均衡策略仅在开启Prepare包树状广播时生效

rPBFT开启Prepare包结构优化后,其他共识节点交易缺失后,向leader请求交易,导致leader出带宽成为瓶颈,FISCO BCOS v2.3.0结合Prepare包状态,设计并实现了负载均衡策略,该策略时序图如下:

sequenceDiagram participant leader participant sealerA(父节点) participant sealerB(子节点) leader->>sealerA(父节点): 发送Prepare leader->>sealerA(父节点): 发送PrepareStatus sealerA(父节点)->>sealerA(父节点): 更新Prepare状态缓存{leader, PrepareStatus} sealerA(父节点)->>sealerB(子节点): 转发Prepare sealerA(父节点)->>sealerA(父节点): 向leader请求并获取缺失交易,Prepare包加入缓存 sealerA(父节点)->>sealerB(子节点): 发送PrepareStatus sealerB(子节点)->>sealerB(子节点): 更新Prepare状态缓存{sealerA, PrepareStatus} sealerB(子节点)->>sealerB(子节点): 向sealerA请求缺失并获取 sealerB(子节点)->>leader: 发送PrepareStatus

Leader的子节点sealerA的主要处理流程如下:

(1) leader产生新区块后,将仅包含交易哈希列表的Prepare包发送给三个子节点

(2) 子节点sealerA收到Prepare包后,将其沿树状拓扑转发给三个子节点

(3) 子节点sealerA开始处理Prepare包:

  • 从交易池中获取命中的交易,填充到Prepare包内的区块中
  • 向父节点Leader请求缺失的交易

(4) sealerA收到Leader的回包后,将回包内的交易填充到Prepare包内,并随机选取33%的节点广播Prepare包的状态,主要包括{blockNumber, blockHash, view, idx},其他节点收到该状态包后,将sealerA最新状态包更新到缓存中

sealerA的子节点sealerB的主要处理流程如下

(1) sealerB收到SealerA转发过来的Prepare包后,同样继续将该Prepare包转发给sealerB的子节点

(2) sealerB开始处理Prepare包,首先从交易池中获取命中的交易,填充到Prepare包的区块中,并选取节点获取缺失的交易:

  • 若sealerB缓存来自节点sealerA的prepareStatus.blockHash等于Prepare.blockHash,则直接向父节点sealerA请求缺失交易
  • 若sealerB缓存的sealerA状态包哈希不等于Prepare.blockHash,但存在来自其他节点C的prepareStatus.blockHash等于prepare.blockHash,则向C请求缺失交易
  • 若sealerB缓存的任何节点prepareStatus的哈希均不但等于prepare.blockHash,最多等待100ms(可配)后,向Leader请求缺失的交易

(3) sealerB收到被请求节点回复的交易后,填充Prepare包内区块,并随机选取33%(可配)节点广播Prepare包状态

(4) 其他节点收到sealerB的状态包后,将其sealerB的最新状态包更新到缓存中

虚拟机与合约

交易的执行是区块链节点上的一个重要的功能。交易的执行,是把交易中的智能合约二进制代码取出来,用执行器(Executor)执行。共识模块(Consensus)把交易从交易池(TxPool)中取出,打包成区块,并调用执行器去执行区块中的交易。在交易的执行过程中,会对区块链的状态(State)进行修改,形成新区块的状态储存下来(Storage)。执行器在这个过程中,类似于一个黑盒,输入是智能合约代码,输出是状态的改变。

随着技术的发展,人们开始关注执行器的性能和易用性。一方面,人们希望智能合约在区块链上能有更快的执行速度,满足大规模交易的需求。另一方面,人们希望能用更熟悉更好用的语言进行开发。进而出现了一些替代传统的执行器(EVM)的方案,如:JIT、 WASM_甚至JVM。然而,传统的EVM是耦合在节点代码中的。首先要做的,是将执行器的接口抽象出来,兼容各种虚拟机的实现。因此,EVMC被设计出来。

EVMC (Ethereum Client-VM Connector API),是以太坊抽象出来的执行器的接口,旨在能够对接各种类型的执行器。FISCO BCOS目前采用了以太坊的智能合约语言Solidity,因此也沿用了以太坊对执行器接口的抽象。

_images/evmc_frame.png

在节点上,共识模块会调用EVMC,将打包好的交易交由执行器执行。执行器执行时,对状态进行的读写,会通过EVMC的回调反过来操作节点上的状态数据。

经过EVMC一层的抽象,FISCO BCOS能够对接今后出现的更高效、易用性更强的执行器。目前,FISCO BCOS采用的是传统的EVM根据EVMC抽象出来的执行器—Interpreter。因此能够支持基于Solidity语言的智能合约。目前其他类型的执行器发展尚未成熟,后续将持续跟进。

EVM 以太坊虚拟机

在区块链上,用户通过运行部署在区块链上的合约,完成需要共识的操作。以太坊虚拟机,是智能合约代码的执行器。

当智能合约被编译成二进制文件后,被部署到区块链上。用户通过调用智能合约的接口,来触发智能合约的执行操作。EVM执行智能合约的代码,修改当前区块链上的数据(状态)。被修改的数据,会被共识,确保一致性。

EVMC – Ethereum Client-VM Connector API

新版本的以太坊将EVM从节点代码中剥离出来,形成一个独立的模块。EVM与节点的交互,抽象出EVMC接口标准。通过EVMC,节点可以对接多种虚拟机,而不仅限于传统的基于solidity的虚拟机。

传统的solidity虚拟机,在以太坊中称为interpreter,下文主要解释interpreter的实现。

EVMC 接口

EVMC主要定义了两种调用的接口:

  • Instance接口:节点调用EVM的接口
  • Callback接口:EVM回调节点的接口

EVM本身不保存状态数据,节点通过instance接口操作EVM,EVM反过来,调Callback接口,对节点的状态进行操作。

_images/evmc.png

Instance 接口

定义了节点对虚拟机的操作,包括创建,销毁,设置等。

接口定义在evmc_instance(evmc.h)中

  • abi_version
  • name
  • version
  • destroy
  • execute
  • set_tracer
  • set_option

Callback接口

定义了EVM对节点的操作,主要是对state读写、区块信息的读写等。

接口定义在evmc_context_fn_table(evmc.h)中。

  • evmc_account_exists_fn account_exists
  • evmc_get_storage_fn get_storage
  • evmc_set_storage_fn set_storage
  • evmc_get_balance_fn get_balance
  • evmc_get_code_size_fn get_code_size
  • evmc_get_code_hash_fn get_code_hash
  • evmc_copy_code_fn copy_code
  • evmc_selfdestruct_fn selfdestruct
  • evmc_call_fn call
  • evmc_get_tx_context_fn get_tx_context
  • evmc_get_block_hash_fn get_block_hash
  • evmc_emit_log_fn emit_log
EVM 执行
EVM 指令

solidity是合约的执行语言,solidity被solc编译后,变成类似于汇编的EVM指令。Interpreter定义了一套完整的指令集。solidity被编译后,生成二进制文件,二进制文件就是EVM指令的集合,交易以二进制的形式发往节点,节点收到后,通过EVMC调用EVM执行这些指令。在EVM中,用代码模拟实现了这些指令的逻辑。

Solidity是基于堆栈的语言,EVM在执行二进制时,也是以堆栈的方式进行调用。

算术指令举例

一条ADD指令,在EVM中的代码实现如下。SP是堆栈的指针,从栈顶第一和第二个位置(SP[0]SP[1])拿出数据,进行加和后,写入结果堆栈SPP的顶端SPP[0]

CASE(ADD)
{
    ON_OP();
    updateIOGas();

    // pops two items and pushes their sum mod 2^256.
    m_SPP[0] = m_SP[0] + m_SP[1];
}

跳转指令举例

JUMP指令,实现了二进制代码间的跳转。首先从堆栈顶端SP[0]取出待跳转的地址,验证一下是否越界,放到程序计数器PC中,下一个指令,将从PC指向的位置开始执行。

CASE(JUMP)
{
    ON_OP();
    updateIOGas();
    m_PC = verifyJumpDest(m_SP[0]);
}

状态读指令举例

SLOAD可以查询状态数据。大致过程是,从堆栈顶端SP[0]取出要访问的key,把key作为参数,然后调evmc的callback函数get_storage() ,查询相应的key对应的value。之后将读到的value写到结果堆栈SPP的顶端SPP[0]

CASE(SLOAD)
{
    m_runGas = m_rev >= EVMC_TANGERINE_WHISTLE ? 200 : 50;
    ON_OP();
    updateIOGas();

    evmc_uint256be key = toEvmC(m_SP[0]);
    evmc_uint256be value;
    m_context->fn_table->get_storage(&value, m_context, &m_message->destination, &key);
    m_SPP[0] = fromEvmC(value);
}

状态写指令举例

SSTORE指令可以将数据写到节点的状态中,大致过程是,从栈顶第一和第二个位置(SP[0]SP[1])拿出key和value,把key和value作为参数,调用evmc的callback函数set_storage() ,写入节点的状态。

CASE(SSTORE)
{
    ON_OP();
    if (m_message->flags & EVMC_STATIC)
        throwDisallowedStateChange();

    static_assert(
        VMSchedule::sstoreResetGas <= VMSchedule::sstoreSetGas, "Wrong SSTORE gas costs");
    m_runGas = VMSchedule::sstoreResetGas;  // Charge the modification cost up front.
    updateIOGas();

    evmc_uint256be key = toEvmC(m_SP[0]);
    evmc_uint256be value = toEvmC(m_SP[1]);
    auto status =
        m_context->fn_table->set_storage(m_context, &m_message->destination, &key, &value);

    if (status == EVMC_STORAGE_ADDED)
    {
        // Charge additional amount for added storage item.
        m_runGas = VMSchedule::sstoreSetGas - VMSchedule::sstoreResetGas;
        updateIOGas();
    }
}

合约调用指令举例

CALL指令能够根据地址调用另外一个合约。首先,EVM判断是CALL指令,调用caseCall(),在caseCall()中,用caseCallSetup()从堆栈中拿出数据,封装成msg,作为参数,调用evmc的callback函数call。Eth在被回调call()后,启动一个新的EVM,处理调用,之后将新的EVM的执行结果,通过call()```的参数返回给当前的EVM,当前的EVM将结果写入结果堆栈SSP中,调用结束。合约创建的逻辑与此逻辑类似。

CASE(CALL)
CASE(CALLCODE)
{
    ON_OP();
    if (m_OP == Instruction::DELEGATECALL && m_rev < EVMC_HOMESTEAD)
        throwBadInstruction();
    if (m_OP == Instruction::STATICCALL && m_rev < EVMC_BYZANTIUM)
        throwBadInstruction();
    if (m_OP == Instruction::CALL && m_message->flags & EVMC_STATIC && m_SP[2] != 0)
        throwDisallowedStateChange();
    m_bounce = &VM::caseCall;
}
BREAK

void VM::caseCall()
{
    m_bounce = &VM::interpretCases;

    evmc_message msg = {};

    // Clear the return data buffer. This will not free the memory.
    m_returnData.clear();

    bytesRef output;
    if (caseCallSetup(msg, output))
    {
        evmc_result result;
        m_context->fn_table->call(&result, m_context, &msg);

        m_returnData.assign(result.output_data, result.output_data + result.output_size);
        bytesConstRef{&m_returnData}.copyTo(output);

        m_SPP[0] = result.status_code == EVMC_SUCCESS ? 1 : 0;
        m_io_gas += result.gas_left;

        if (result.release)
            result.release(&result);
    }
    else
    {
        m_SPP[0] = 0;
        m_io_gas += msg.gas;
    }
    ++m_PC;
}
总结

EVM是一个状态执行的机器,输入是solidity编译后的二进制指令和节点的状态数据,输出是节点状态的改变。以太坊通过EVMC实现了多种虚拟机的兼容。但截至目前,并未出现除开interpreter之外的,真正生产可用的虚拟机。也许要做到同一份代码在不同的虚拟机上跑出相同的结果,是一件很难的事情。BCOS将持续跟进此部分的发展。

Precompiled

预编译合约提供一种使用C++编写合约的方法,合约逻辑与数据分离,相比于solidity合约具有更好的性能,可以通过修改底层代码实现合约升级。

预编译合约与Solidity合约对比
表名 预编译合约 Solidity合约
地址 固定地址,代码中定义 部署时确定
合约代码 数据存储在表中,与合约分离,可升级合约逻辑 合约变量和数据存储在MPT树中
执行 C++底层执行,性能更高,可实现并行 EVM虚拟机,串行执行
模块架构

Precompiled的架构如下图所示:

  • 区块验证器在执行交易的时候会根据被调用合约的地址来判断类型。地址1-4表示以太坊预编译合约,地址0x1000-0x10000是C++预编译合约,其他地址是EVM合约。

_images/architecture5.png

关键流程
  • 执行预编译合约时首先需要根据合约地址获取到预编译合约的对象。
  • 每个预编译合约对象都会实现call接口,预编译合约的具体逻辑在该接口中实现。
  • call根据交易的abi编码,获取到Function Selector和参数,然后执行对应的逻辑。
graph TB Start(开始) --> branch1{预编译合约} branch1 --> |是|op1[根据地址获取合约对象] branch1 --> |否|op2[EVM] op1 --> op3[解析调用函数及参数] op3 --> End(返回执行结果) op2 --> End(返回执行结果)
接口定义

每个预编译合约都必须实现自己的call接口,接口接受三个参数,分别是ExecutiveContext执行上下文、bytesConstRef参数的abi编码和外部账户地址,其中外部账户地址用于判断是否具有写权限。Precompiled源码

接口名 参数说明 接口说明
virtual bytes call(std::shared_ptr<ExecutiveContext> context, bytesConstRef param, Address const& origin = Address()) context为区块执行上下文,param为abi编码的参数,origin为调用的外部账户地址 具体合约接口的实现
virtual uint32_t getParamFunc(bytesConstRef param) param为abi编码的参数 获取调用的函数的Function Select(函数名的sha3的前四个大端字节)
virtual uint32_t getFuncSelector(std::string const& _functionName) _functionName为函数名 根据函数名计算Function Select
virtual bytesConstRef getParamData(bytesConstRef param) param为abi编码的参数 获取调用函数的具体参数的abi编码

Gas

EVM虚拟机有一整套Gas机制来衡量每笔交易上链消耗的CPU、内存和存储资源。FISCO BCOS 2.0引入了Precompiled合约,支持内置的C++合约,为了提升Precompiled合约的安全性,FISCO BCOS v2.4.0在Precompiled合约中引入了Gas机制。

此外,EVM原始的Gas机制中,交易的主要Gas消耗来源于存储,考虑到联盟链场景更关注CPU和内存消耗,FISCO BCOS v2.4.0调整了存储Gas,引入Free Storage Gas衡量模式,提升CPU和内存在交易Gas消耗中的占比。

Precompiled合约支持Gas计算

注解

Precompiled合约支持Gas计算的特性从v2.4.0开始支持,当 supported_version 小于v2.4.0,或者旧链直接替换二进制升级时,不支持该特性

模块架构

FISCO BCOS v2.4.0新增了PrecompiledGas模块进行Gas计算,Gas开销主要包括CPU、内存和存储三个维度,模块图如下:

_images/precompiled_gas.png

PrecompiledGas主要记录了每个交易执行Precompiled合约过程中调用的基础操作、占用内存消耗的Gas,交易调用Precompiled合约时Gas计算机制如下:

  • 虚拟机执行交易调用Precompiled合约的call接口时,每调用一个基础操作,会将其对应的OPCode添加到PrecompiledGas运行时指令集合
  • 虚拟机执行交易调用Precompiled合约的call接口时,基础操作占用的内存变化时,会更新PrecompiledGas的运行时消耗的内存
  • Precompiled合约执行完毕后,可调用接口,根据运行Precompiled合约过程中执行的指令集合、消耗的内存,计算出该Precompiled合约Gas消耗。
Precompiled合约Gas衡量标准

FISCO BCOS Precompiled合约Gas衡量标准参考了EVM,主要包括CPU、内存和存储三个维度。下面详细介绍Precompiled合约具体的Gas计算方法。

Precompiled合约内存Gas计算

Precompiled合约内存消耗主要来自于输入、输出以及运行时产生的额外内存消耗。某笔交易消耗的总内存为txMemUsed时,其对应的内存Gas计算公式如下。即:每32字节增加memoryGasUnit个Gas,memoryGasUnit的值为3.

    MemoryGas(txMemUsed) = memoryGasUnit * txMemUsed / 32 + (txMemUsed * txMemUsed)/512
Precompiled合约CPU、存储Gas计算

为了计算Precompiled合约基础操作消耗的Gas,FISCO BCOS v2.4.0将Precompiled合约映射到具体的操作码,并定义了每个基础操作对应的Gas。

Precompiled合约基础操作对应的操作码

PrecompiledGas模块将Precompiled合约基础操作映射到了操作码如下:

操作 说明 操作码
EQ ConditionPrecompiled的EQ调用,判断两个操作数是否相等 0x00
GE ConditionPrecompiled的GE调用,判读左值是否大于等于右值 0x01
GT ConditionPrecompiled的GT调用,判断左值是否大于右值 0x02
LE ConditionPrecompiled的LE调用,判断左值是否小于等于右值 0x03
LT ConditionPrecompiled的LT调用,判断左值是否小于右值 0x04
NE ConditionPrecompiled的NE调用,判断左值是否不等于右值 0x05
Limit ConditionPrecompiled的Limit调用,限制从CRUD接口中查询出来的数据条数 0x06
GetInt EntryPrecompiled的getInt调用,将字符串转换成int256/uint256返回 0x07
GetAddr EntryPrecompiled的getAddress调用,将字符串转换成Address 0x08
Set EntryPrecompiled的set调用,设置指定Key的值为指定的Value 0x09
GetByte32 EntryPrecompiled的getByte32,将字符串转换为byte 32 0x0a
GetByte64 EntryPrecompiled的getByte64,将字符串转换为byte 64 0x0b
GetString EntryPrecompiled的getString,获取输入的Key对应的值Value 0x0c
CreateTable TableFactoryPrecompiled的createTable调用,创建表 0x0d
OpenTable TableFactoryPrecompiled的openTable调用,打开表 0x0e
Select TablePrecompiled的select调用,查询表 0x0f
Insert TablePrecompiled的insert调用,向表中插入指定记录 0x10
Update TablePrecompiled的update调用,更新指定表中的指定记录 0x11
Remove TablePrecompiled的remove调用,删除指定表中的指定记录 0x12
PaillierAdd 同态加接口 0x13
GroupSigVerify 群签名验证接口 0x14
RingSigVerify 环签名验证接口 0x15
Precompiled合约基础操作衡量标准

PrecompiledGas定义了Precompiled合约每个基础操作对应的Gas消耗,具体如下:

操作 Gas消耗
EQ 3
GE 3
GT 3
LE 3
LT 3
NE 3
Limit 3
GetInt 3
GetAddr 3
Set 3
GetByte32 3
GetByte64 3
GetString 3
CreateTable 16000
OpenTable 200
Select 200
Insert 10000
Update 10000
Remove 2500
PaillierAdd 20000
GroupSigVerify 20000
RingSigVerify 20000
EVM Gas衡量标准插件化

如前面所述,针对部分场景衡量交易资源耗用时,更加关注CPU和Gas,FISCO BCOS v2.4.0引入了Free Storage的Gas衡量模式,提升CPU和内存在交易Gas消耗中的占比。

注解

EVM Gas衡量标准支持插件化配置的特性从v2.4.0开始支持,当 supported_version 小于v2.4.0,或者旧链直接替换二进制升级时,不支持该特性

模块架构

为了支持Gas衡量标准插件化配置和FreeStorage的Gas衡量模式,FISCO BCOS v2.4.0在以太坊EVMSchedule引入FreeStorageEVMSchedule,在PrecopmiledGas的GasMetrics基础上引入了FreeStorageGasMetrics,并根据genesis文件的enable_free_storage配置项决定启用哪种Gas衡量模式,如下图所示:

_images/free_storage.png

为了提升CPU和内存在交易Gas消耗中的占比,FreeStorageEVMSchedule调整了创建合约、SSTORESLOAD等操作的Gas消耗;FreeStorageGasMetrics主要调整了CreateTableInsertRemoveUpdate等操作的Gas消耗。

Gas衡量标准

下面分别介绍非FreeStorage模式和FreeStorage模式下,EVM虚拟机和Precompiled合约Gas衡量标准:

EVM虚拟机Gas衡量标准

Gas 说明 EVMSchedule模式下Gas消耗 FreeStorageEVMSchedule模式下Gas消耗
CreateGas 创建合约的Gas消耗 32000 16000
sloadGas 从存储读取32字节数据消耗的Gas 200 1200
sstoreSetGas 添加32字节数据到存储的Gas消耗 20000 1200
sstoreResetGas 更新32字节存储数据的Gas消耗 5000 1200

Precompiled合约Gas衡量标准

Gas 说明 GasMetrics模式下Gas消耗 FreeStorageGasMetrics模式下Gas消耗
CreateTableGas 创建表的Gas消耗 16000 500
StoreGas 向表中插入数据或更新表中数据的Gas消耗 10000 200
RemoveGas 删除表中数据的Gas消耗 2500 200
配置项

注解

EVM Gas衡量标准支持插件化配置项位于 genesis 文件中,详细可参考 这里

存储模块

FISCO BCOS继承以太坊存储的同时,引入了高扩展性、高吞吐量、高可用、高性能的分布式存储。存储模块主要包括两部分:

世界状态: 可进一步划分成 MPTStateStorageState

  • MPTState: 使用MPT树存储账户的状态,与以太坊一致
  • StorageState: 使用分布式存储的表结构存储账户状态,不存历史信息,去掉了对MPT树的依赖,性能更高

分布式存储(Advanced Mass Database,AMDB): 通过抽象表结构,实现了SQL和NOSQL的统一,通过实现对应的存储驱动,可以支持各类数据库,目前已经支持LevelDB和MySQL。

_images/architecture4.png

AMDB

分布式存储(Advanced Mass Database,AMDB)通过对表结构的设计,既可以对应到关系型数据库的表,又可以拆分使用KV数据库存储。通过实现对应于不同数据库的存储驱动,AMDB理论上可以支持所有关系型和KV的数据库。

  • CRUD数据、区块数据默认情况下都保存在AMDB,无需配置,合约局部变量存储可根据需要配置为MPTState或StorageState,无论配置哪种State,合约代码都不需要变动。
  • 当使用MPTState时,合约局部变量保存在MPT树中。当使用StorageState时,合约局部变量保存在AMDB表中。
  • 尽管MPTState和AMDB最终数据都会写向RocksDB,但二者使用不同的RocksDB实例,没有事务性,因此当配置成使用MPTState时,提交数据时异常可能导致两个RocksDB数据不一致。
名词解释
Table

存储表中的所有数据。Table中存储AMDB主key到对应Entries的映射,可以基于AMDB主key进行增删改查,支持条件筛选。

Entries

Entries中存放主Key相同的Entry,数组。AMDB的主Key与Mysql中的主key不同,AMDB主key用于标示Entry属于哪个key,相同key的Entry会存放在同一个Entries中。

Entry

对应于表中的一行,每行以列名作为key,对应的值作为value,构成KV结构。每个Entry拥有自己的AMDB主key,不同Entry允许拥有相同的AMDB主key。

Condition

Table中的删改查接口支持传入条件,这三种接口会返回根据条件筛选后的结果。如果条件为空,则不做任何筛选。

数据更新或者插入过程中,需要根据主Key获取数据并将对数据更新或者append操作,然后再写回存储系统。因此,在一个主Key对应的Entries中Entry个数很多的时候,执行效率会受到影响;同时,会加大内存的使用。所以,实际生产过程中主Key对应的Entries中Entry个数不宜过多。

举例

以某公司员工领用物资登记表为例,解释上述名词。

Name* item_id item_name
Alice 1001001 laptop
Alice 1001002 screen
Bob 1002001 macbook
Chris 1003001 PC

解释如下:

  • 表中Name是AMDB主key。
  • 表中的每一行为一个Entry。一共有4个Entry,每个Entry以Map存储数据。4个Entry如下:
    • Entry1:{Name:Alice,item_id:1001001,item_name:laptop}
    • Entry2:{Name:Alice,item_id:1001002,item_name:screen}
    • Entry3:{Name:Bob,item_id:1002001,item_name:macbook}
    • Entry4:{Name:Chris,item_id:1003001,item_name:PC}
  • Table中以Name为主key,存有3个Entries对象。第1个Entries中存有Alice的2条记录,第2个Entries中存有Bob的1条记录,第3个Entries中存有Chris的一条记录。
  • 调用Table类的查询接口时,查接口需要指定AMDB主key和条件,设置查询的AMDB主key为Alice,条件为item_id = 1001001,会查询出Entry1。
AMDB表分类

表中的所有entry,都会有_status_,_num_,_hash_内置字段。

系统表

系统表默认存在,由存储驱动保证系统表的创建。

表名 keyField valueField 存储数据说明 AMDB主key
_sys_tables_ table_name key_field,value_field 存储所有表的结构,以表名为主键 所有表的表名
_sys_consensus_ name type,node_id,enable_num 存储共识节点和观察节点的列表 node
_sys_table_access_ table_name address,enable_num 存储每个表的具有写权限的外部账户地址 表的表名
_sys_cns_ name version,address,abi 存储CNS映射关系 合约名
_sys_config_ key value,enable_num 存储需要共识的群组配置项 配置项
_sys_current_state_ key value 存储最新的状态 current_number/total_transaction_count
_sys_tx_hash_2_block_ hash value,index 存储交易hash到区块号的映射 交易hash的16进制
_sys_number_2_hash_ hash value 存储区块号到区块头hash的16进制表示的映射 区块高
_sys_hash_2_block_ key value 存储hash到序列化的区块数据 区块头hash的16进制
_sys_block_2_nonces_ number value 存储区块中交易的nonces 区块高
用户表

用户调用CRUD接口所创建的表,从2.2版本开始以u_<TableName>为表名,底层自动添加u_前缀。

StorageState账户表

从2.2版本开始以c_+Address作为表名。表中存储外部账户相关信息。表结构如下

key* value
balance
nonce
code
codeHash
alive

StorageState

StorageState是一种使用AMDB实现的存储账户状态的方式。相比于MPTState主要有以下区别:

StorageState MPTState
账户数据组织方式 AMDB表 MPT树
历史状态 不支持,不维护历史状态 支持

MPTState每个账户使用MPT树存储其数据,当历史数据逐渐增多时,会因为存储方式和磁盘IO导致性能问题。StorageState每个账户对应一个Table存储其相关数据,包括账户的nonce,code,balance等内容,而AMDB可以通过实现对应的存储驱动支持不同的数据库以提高性能,我们使用RocksDB测试发现,StorageState性能大约是MPTState的两倍。

MPT State

MPT State是以太坊上经典的数据存储方式。通过MPT树的方式,将所有合约的数据组织起来,实现了对数据的查找和追溯。

重要

推荐使用 storage state

MPT树

MPT(Merkle Patricia Trie),是一种用hash索引数据的前缀树。

从宏观上来说,MPT树是一棵前缀树,用key查询value。通过key去查询value,就是用key去在MPT树上进行索引,在经过多个中间节点后,最终到达存储数据的叶子节点。

从细节上来说,MPT树,是一棵Merkle树,每个树上节点的索引,都是这个节点的hash值。在用key查找value的时候,是根据key在某节点内部,获取下一个需要跳转的节点的hash值,拿到下一个节点的hash值,才能从底层的数据库中取出下一个节点的数据,之后,再用key,去下一个节点中查询下下个节点的hash值,直至到达value所在的叶子节点。

当MPT树上某个叶子节点的数据更新后,此叶子节点的hash也会更新,随之而来的,是这个叶子节点回溯到根节点的所有中间节点的hash都会更新。最终,MPT根节点的hash也会更新。当要索引这个新的数据时,用MPT新的根节点hash,从底层数据库查出新的根节点,再往后一层层遍历,最终找到新的数据。而如果要查询历史数据,则可用老的树根hash,从底层数据库取出老的根节点,再往下遍历,就可查询到历史的数据。

MPT树的实现图(图片来自以太坊黄皮书)

_images/mpt.png

状态 State

在以太坊上,数据是以account为单位存储的,每个account内,保存着这个合约(用户)的代码、参数、nonce等数据。account的数据,通过account的地址(address)进行索引。以太坊上用MPT将这些address作为查询的key,实现了对account的查询。

随着account数据的改变,account的hash也进行改变。于此同时,MPT的根的hash也会改变。不同的时候,account的数据不同,对应的MPT的根就不同。此处,以太坊把这层含义进行了具体化,提出了“状态”的概念。把MPT根的hash,叫state root。不同的state root,对应着不同的“状态”,对应查询到不同的MPT根节点,再用account的address从不同的MPT根节点查询到此状态下的account数据。不同的state,拿到的MPT根节点不同,查询的account也许会有不同。

state root是区块中的一个字段,每个区块对应着不同的“状态”。区块中的交易会对account进行操作,进而改变account中的数据。不同的区块下,account的数据有所不同,即此区块的状态有所不同,具体的,是state root不同。从某个区块中取出这个区块的state root,查询到MPT的根节点,就能索引到这个区块当时account的数据历史。

(图片来自以太坊白皮书)

_images/mpt_state.png

Trade Off

MPT State的引入,是为了实现对数据的追溯。根据不同区块下的state root,就能查询到当时区块下account的历史信息。而MPT State的引入,带来了大量hash的计算,同时也打散了底层数据的存储的连续性。在性能方面,MPT State存在着天然的劣势。可以说,MPT State是极致的追求可追溯性,而大大的忽略了性能。

在FISCO BCOS的业务场景中,性能与可追溯性相比,性能更为重要。FISCO BCOS对底层的存储进行了重新的设计,实现了Storage State。Storage State牺牲了部分的可追溯性,但带来了性能上的提升。

安全控制

为了保障节点间通信安全性,以及对节点数据访问的安全性,FISCO BCOS引入了节点准入机制、CA黑名单和权限控制三种机制,在网络和存储层面上做了严格的安全控制。

网络层面安全控制

  • 节点使用 SSL连接 ,保障了通信数据的机密性
  • 引入 网络准入机制 ,可将指定群组的作恶节点从共识节点列表或群组中删除,保障了系统安全性
  • 通过 群组白名单机制 ,保证每个群组仅可接收相应群组的消息,保证群组间通信数据的隔离性
  • 引入 CA黑名单机制 ,可及时与作恶节点断开网络连接
  • 提出 分布式存储权限控制 机制,灵活、细粒度地控制外部账户部署合约和创建、插入、删除和更新用户表的权限。

存储层面安全控制

基于分布式存储,提出分布式存储权限控制的机制,以灵活、细粒度的方式进行有效的权限控制,设计并实现了权限控制机制限制外部账户(tx.origin)对存储的访问,权限控制范围包括合约部署、表的创建、表的写操作。

节点准入管理介绍

本文档对节点准入管理进行介绍性说明,实践方法参见《节点准入管理操作文档》

概述
单链多账本

区块链技术是一种去中心化、公开透明的分布式数据存储技术,能够降低信任成本,实现安全可靠的数据交互。然而区块链的交易数据面临着隐私泄露威胁:

  • 对于公有链,一节点可任意加入网络,从全局账本中获得所有数据;
  • 对于联盟链,虽有网络准入机制,但节点加入区块链后即可获取全局账本的数据。

作为联盟链的FISCO BCOS,对链上隐私这一问题,提出了单链多账本的解决方案。FISCO BCOS通过引入群组概念,使联盟链从原有一链一账本的存储/执行机制扩展为一链多账本的存储/执行机制,基于群组维度实现同一条链上的数据隔离和保密。

_images/multi_ledger.png

多账本

如上图所示,节点ABC加入蓝色群组,并共同维护蓝色账本; 节点B和C加入粉色群组并维护粉红色账本; 节点A和B加入黄色群组并维护黄色账本。三个群组间共享公共的网络服务,但各群组有各自独立的账本存储及交易执行环境。客户端将交易发到节点所属的某个群组上,该群组内部对交易及数据进行共识并存储,其他群组对该交易无感知不可见。

节点准入机制

基于群组概念的引入,节点准入管理可分为网络准入机制群组准入机制。准入机制的规则记录在配置中,节点启动后将读取配置信息实现网络及群组的准入判断。

名词解释
节点类型

本文档所讨论的节点为已完成网络准入可进行P2P通信的节点。网络准入过程涉及P2P节点连接列表添加和证书验证。

  • 群组节点:完成网络准入并加入群组的节点。群组节点只能是共识节点和观察节点两者之一。其中共识节点参与共识出块和交易/区块同步,观察节点只参与区块同步。群组节点准入过程涉及动态增删节点的交易发送。
  • 游离节点:完成网络准入但没有加入群组的节点。游离节点尚未通过群组准入,不参与共识和同步。

节点关系如下:

_images/node_relationship.png

节点关系
配置类型
划分维度 配置类型
说明
影响范围 网络配置全局性质的配置,节点的配置影响该节点所在的整个网络,节点对整个网络使用同一份配置,
文件名为config.*
群组配置节点的配置影响该节点所在的单个群组,每个群组各有一份配置,
文件名为group.X.*,其中X为群组号
是否可改固定配置只使用首次配置内容,后续对配置的修改无效,
文件后缀为.genesis
可改配置配置后续可改动,节点重启生效,
文件后缀为.ini
存放位置本地存储配置存放在本地文件,用户可直接修改,
用户修改自身文件能重启生效的配置项
链上存储配置存放在区块链上,对其修改需群组共识,目前没有需全网共识的内容,
需新链重置或通过交易修改生效的配置项
节点准入配置项

涉及节点转入管理相关的配置项有:P2P节点连接列表节点证书CA黑名单群组节点初始列表群组节点系统表

配置项
作用
影响范围
是否可改
存放位置
P2P节点连接列表记录本节点期望与哪些节点建立网络通信网络配置可改配置本地存储
节点证书证明自己是由可信第三方许可的节点网络配置可改配置本地存储
CA黑名单记录本节点禁止与哪些节点建立网络通信网络配置可改配置本地存储
群组节点初始列表记录创世块阶段参与共识/同步的节点列表群组配置固定配置本地存储
群组节点系统表记录当前参与一群组共识/同步的节点列表群组配置可改配置链上存储
模块架构

_images/architecture3.png

模块架构

配置项及系统模块关系图如上,箭头方向A->B表示B模块依赖A模块的数据,同时B模块晚于A模块初始化。

核心流程
一般初始化流程

_images/initialization.png

一般初始化流程
首次初始化流程

节点在首次启动时,对其所属的各个群组,以群组为单位将固定配置文件的内容写入第0块并直接提交上链。初始化的具体逻辑为:

_images/first_initialization.png

首次初始化流程

这一阶段需写入的与节点准入管理相关的配置内容有:群组节点初始列表->群组节点系统表

说明:

  • 同一账本的所有节点的第0块需一致,即固定配置文件均一致;
  • 节点后续的每次启动均检查第0块信息是否与固定配置文件一致。如果固定配置文件被修改,节点再次启动将输出告警信息,但不会影响群组正常运作。
基于CA黑名单的节点建连流程

SSL认证用于确定节点之间是否许可加入某条链。一条链上的节点均信任可信的第三方(节点证书的颁发者)。

FISCO BCOS要求实现SSL双向认证。节点在handshake过程中,从对方节点提供的证书中获取对方节点的nodeID,检查该nodeID是否在自身的CA黑名单。如存在,关闭该connection,如不在,建立session。

CA黑名单机制也支持SSL单向认证的场景,作用时机是:节点在session建立后,可从session中获取对方节点的nodeID进行判断,如果nodeID在自身的CA黑名单中,将已建立的session断连。

节点相关类型及其转换操作

三种节点类型(共识节点+观察节点+游离节点)可通过相关接口进行如下转换:

_images/type_and_conversion_of_nodes.png

共识节点相关类型及其转换操作
接口及配置描述
节点配置文件层级

_images/config_file_organization.png

配置文件的层级关系

配置文件的组织规则为:各群组的配置独立固定配置和可改配置相独立。目前使用的文件有网络可改配置文件config.ini群组固定配置文件group.N.genesis群组可改配置文件group.N.ini,其中N为节点所在的群组号。对于网络/群组可改配置文件,如果文件中没有显式定义某配置项的值,程序将使用该配置项的默认值。

配置文件示例

对于网络可改配置文件config.ini,节点准入管理涉及P2P节点连接列表[p2p]节点证书[network_security]CA黑名单[certificate_blacklist][certificate_blacklist]可缺少。配置项举例如下:

注解

为便于开发和体验,p2p模块默认监听IP是 0.0.0.0 ,出于安全考虑,请根据实际业务网络情况,修改为安全的监听地址,如:内网IP或特定的外网IP

[p2p]
    ;p2p listen ip
    listen_ip=0.0.0.0
    ;p2p listen port
    listen_port=30300
    ;nodes to connect
    node.0=127.0.0.1:30300
    node.1=127.0.0.1:30301
    node.2=127.0.0.1:30302
    node.3=127.0.0.1:30303
    
;certificate blacklist
[certificate_blacklist]
    ;crl.0 should be nodeid, nodeid's length is 128 
    ;crl.0=

;certificate configuration
[network_security]
    ;directory the certificates located in
    data_path=conf/
    ;the node private key file
    key=node.key
    ;the node certificate file
    cert=node.crt
    ;the ca certificate file
    ca_cert=ca.crt

对于群组固定配置文件group.N.genesis,节点准入管理涉及群组节点初始列表[consensus]。配置项举例如下:

;consensus configuration
[consensus]
    ;consensus algorithm type, now support PBFT(consensus_type=pbft) and Raft(consensus_type=raft)
    consensus_type=pbft
    ;the max number of transactions of a block
    max_trans_num=1000
    ;the node id of leaders
    node.0=79d3d4d78a747b1b9e59a3eb248281ee286d49614e3ca5b2ce3697be2da72cfa82dcd314c0f04e1f590da8db0b97de466bd08e27eaa13f85df9b60e54d6a1ec8
    node.1=da527a4b2aeae1d354102c6c3ffdfb54922a092cc9acbdd555858ef89032d7be1be499b6cf9a703e546462529ed9ea26f5dd847110ff3887137541bc651f1c32
    node.2=160ba08898e1e25b31e24c2c4e3c75eed996ec56bda96043aa8f27723889ab774b60e969d9bd25d70ea8bb8779b7070521d9bd775dc7636f4b2b800d2fc8c7dd
    node.3=a968f1e148e4b51926c5354e424acf932d61f67419cf7c5c00c7cb926057c323bee839d27fe9ad6c75386df52ae2b30b2e7ba152b0023979d25dee25b20c627f
群组节点系统表定义
Field
Type
Null
Key
Expain
namestringNoPRI各行同一值,分布式存储基于此key实现全表查询
typestringNo节点类型(sealer/observer)
node_idstringNo节点NodeID
enable_numstringNo该节点类型生效的区块高度
_status_stringNo分布式存储通用字段,“0”可用“1”删除
群组系统表接口定义

群组系统表实现群组层的白名单机制(对比CA黑名单实现网络的黑名单机制)。群组系统表提供的接口有:

contract ConsensusSystemTable
{
    // 修改一节点为共识节点
    function addSealer(string nodeID) public returns(int256);
    // 修改一节点为观察节点
    function addObserver(string nodeID) public returns(int256);
    // 把该节点从群组系统表中移除
    function remove(string nodeID) public returns(int256);
}
功能展望
  • 可改配置目前为修改后重启生效,后续可实现动态加载,修改实时生效;
  • CA黑名单目前实现了基于节点的黑名单,后续可考虑基于机构的黑名单。

CA黑白名单介绍

本文档对黑、白名单进行介绍性说明,实践方法参见《CA黑白名单操作手册》

名词解释

CA黑名单

  • 别称证书拒绝列表(certificate blacklist,简称CBL)。CA黑名单基于config.ini文件中[certificate_blacklist]配置的NodeID进行判断,拒绝此NodeID节点发起的连接。

CA白名单

  • 别称证书接受列表(certificate whitelist,简称CAL)。CA白名单基于config.ini文件中[certificate_whitelist]配置的NodeID进行判断,拒绝除白名单外所有节点发起的连接。

CA黑、白名单所属的配置类型

  • 基于作用范围(网络配置/账本配置)维度可划分为网络配置,影响整个网络的节点连接建立过程;
  • 基于是否可改(可改配置/固定配置)维度可划分为可改配置,内容可改,重启后生效;
  • 基于存放位置(本地存储/链上存储)维度可划分为本地存储,内容记录在本地,不存于链上。
模块架构

下图表示CA黑名单所涉及的模块及其关系。图例A->B表示B模块依赖A模块的数据,同时B模块晚于A模块初始化。白名单的架构与黑名单相同。

_images/architecture3.png

模块架构
## 核心流程

底层实现SSL双向验证。节点在handshake过程中,通过对方提供的证书获取对方节点的nodeID,检查该nodeID与节点配置的黑、白名单是否有关系。如果根据黑、白名单的配置,拒绝该关闭的connection,继续后续流程。

拒绝逻辑

  • 黑名单:拒绝写在黑名单中的节点连接
  • 白名单:拒绝所有未配置在白名单中的节点连接。白名单为空表示不开启,接受任何连接。

优先级

黑名单的优先级高于白名单。例如,白名单里配置了A,B,C,会拒绝掉D的连接,若黑名单里同时配了A,则A也会被拒绝连接。

影响范围
  • CA黑、白名单对网络层的P2P节点连接及AMOP功能有显著影响,使之失效
  • 对账本层的共识和同步功能有潜在影响,影响共识及同步消息/数据的转发
配置格式

黑名单

节点config.ini配置中增加[certificate_blacklist]路径([certificate_blacklist]在配置中可选)。CA黑名单内容为节点NodeID列表,node.X为本节点拒绝连接的对方节点NodeID。CA黑名单的配置格式示例如下。

[certificate_blacklist]
    crl.0=4d9752efbb1de1253d1d463a934d34230398e787b3112805728525ed5b9d2ba29e4ad92c6fcde5156ede8baa5aca372a209f94dc8f283c8a4fa63e3787c338a4
    crl.1=af57c506be9ae60df8a4a16823fa948a68550a9b6a5624df44afcd3f75ce3afc6bb1416bcb7018e1a22c5ecbd016a80ffa57b4a73adc1aeaff4508666c9b633a
   

白名单

节点config.ini配置中增加[certificate_whitelist]路径([certificate_whitelist]在配置中可选)。CA白名单内容为节点NodeID列表,node.X为本节点可接受连接的对方节点NodeID。CA白名单的配置格式示例如下。

[certificate_whitelist]
    cal.0=4d9752efbb1de1253d1d463a934d34230398e787b3112805728525ed5b9d2ba29e4ad92c6fcde5156ede8baa5aca372a209f94dc8f283c8a4fa63e3787c338a4
    cal.1=af57c506be9ae60df8a4a16823fa948a68550a9b6a5624df44afcd3f75ce3afc6bb1416bcb7018e1a22c5ecbd016a80ffa57b4a73adc1aeaff4508666c9b633a

基于角色的权限控制

当前基于表的权限控制,由于涉及到许多系统表,要求用户对底层的逻辑有一定理解,使用门槛较高。从2.5.0版本开始,基于已有的表权限控制模型,新增了ChainGovernance预编译合约,用于实现基于角色的权限控制。

角色定义

分为治理方、运维方、监管方和业务方。考虑到权责分离,治理方、运维方和开发方权责分离,角色互斥。

  • 治理方:拥有投票权,可以参与治理投票(AUTH_ASSIGN_AUTH),可以增删节点、修改链配置、添加撤销运维、冻结解冻合约、对用户表的写权限控制。链级别的可变配置的权限。
  • 运维方:由治理方添加运维账号,运维账号可以部署合约、创建表、管理合约版本、冻结解冻本账号部署的合约。
  • 业务方:业务方账号由运维添加到某个合约,可以调用该合约的写接口。
  • 监管方:监管方监管链的运行,能够获取链运行中权限变更的记录、能够获取需要审计的数据
权限

以下简称治理账号为委员

  • 权限项命名符合动宾结构。
  • 增删委员、修改委员权重、修改生效阈值三个操作,需要有效投票权重/总权重>生效阈值生效。其中总权重=SUM(委员权重)
  • 治理账号可以添加运维账号,但治理账号不拥有运维的权限
  • 运维账号可以为某个合约添加业务账号,但运维账号没有业务账号权限

_images/roles.png

权限项
权限操作 控制方式 命名 默认阈值 修改方式
增删委员 控制写权限表 AUTH_ASSIGN_AUTH 0.5 委员投票
修改委员权重 控制写权限表 AUTH_ASSIGN_AUTH 0.5 委员投票
修改生效投票阈值(投票委员权重和大于该值) 控制写权限表 AUTH_ASSIGN_AUTH 0.5 委员投票
增删节点(观察/共识) 控制写共识表 AUTH_CTRL_NODE 委员可操作
修改链配置项 控制写配置表 AUTH_MODIFY_CONFIG 委员可操作
冻结解冻合约 合约生命周期 AUTH_CTRL_CONTRACT_LIFE 委员可操作
添加撤销运维 AUTH_CTRL_OPERATOR 委员可操作
用户表写权限 AUTH_CTRL_USER_TABLE 委员可操作
部署合约 _sys_tables_的写权限 AUTH_CREATE_TABLE 运维操作
创建表 _sys_tables_的写权限 AUTH_CREATE_TABLE 运维操作
合约版本管理 CNS AUTH_CTRL_CNS 运维操作
冻结解冻本账号部署的合约 修改合约状态 运维操作
调用合约 合约表写权限 业务操作
计票与生效
  • 所有治理操作需要有效投票数/委员数>生效阈值才能生效
  • 每次投票操作,如果是委员投票,则记录操作内容和投票委员,不重复计票
  • 每次投票操作,计票结束后,计算有效投票数/委员数,如果大于此操作的生效阈值,则对应操作生效,写入
  • 投票设置过期时间,根据块高,blockLimit的10倍,固定不可改
功能列表
  1. 增删委员计票与生效
  2. 修改委员权重计票与生效
  3. 修改生效阈值计票与生效
  4. 委员增删运维
  5. 委员解冻冻结合约
  6. 委员增删节点
  7. 委员修改系统配置
  8. 权限项默认阈值存储
  9. 运维部署合约的权限
  10. 运维管理合约版本的权限
委员相关
function grantCommitteeMember(address user) public returns (int256);
function updateCommitteeMemberWeight(address user, int256 weight) public returns (int256);
function revokeCommitteeMember(address user) public returns (int256);
function listCommitteeMembers() public returns (string);
// threshold取值范围[0,100]
function updateThreshold(int256 threshold) public returns (int256);
运维相关
function grantOperator(address user) public returns (int256);
function revokeOperator(address user) public returns (int256);
function listOperator() public returns (string);
数据结构
表:_sys_committee_votes_
_id_ _status_ _num_ key value origin enable_num
  • key是账户,value记录grant/revoke,origin记录投票人
  • key是账户_update_weight,value记录目标权重,origin记录投票人
  • key是账户_weight,value记录权重
  • key是auth_threshold,value记录当前阈值
  • key是update_auth_threshold,value记录目标阈值,origin记录投票人

权限控制

权限控制介绍

与可自由加入退出、自由交易、自由检索的公有链相比,联盟链有准入许可、交易多样化、基于商业上隐私及安全考虑、高稳定性等要求。因此,联盟链在实践过程中需强调“权限”及“控制”的理念。

为体现“权限”及“控制”理念,FISCO BCOS平台基于分布式存储,提出分布式存储权限控制的机制,可以灵活,细粒度的方式进行有效的权限控制,为联盟链的治理提供重要的技术手段。分布式权限控制基于外部账户(tx.origin)的访问机制,对包括合约部署,表的创建,表的写操作(插入、更新和删除)进行权限控制,表的读操作不受权限控制。 在实际操作中,每个账户使用独立且唯一的公私钥对,发起交易时使用其私钥进行签名,接收方可通过公钥验签知道交易具体是由哪个账户发出,实现交易的可控及后续监管的追溯。

权限控制规则

权限控制规则如下:

  1. 权限控制的最小粒度为表,基于外部账户进行控制。
  2. 使用白名单机制,未配置权限的表,默认完全放开,即所有外部账户均有读写权限。
  3. 权限设置利用权限表(_sys_table_access_)。权限表中设置表名和外部账户地址,则表明该账户对该表有读写权限,设置之外的账户对该表仅有读权限。
权限控制分类

分布式存储权限控制分为对用户表和系统表的权限控制。用户表指用户合约所创建的表,用户表均可以设置权限。系统表指FISCO BCOS区块链网络内置的表,系统表的设计详见存储文档。系统表的权限控制如下所示:

表名 表存储数据说明 权限控制意义
_sys_tables_ 存储所有表的结构 控制部署合约和创建表
_sys_table_access_ 存储权限控制信息 控制权限功能设置
_sys_consensus_ 存储共识节点和观察节点的列表 控制节点类型设置
_sys_cns_ 存储cns列表 控制使用CNS
_sys_config_ 存储系统配置的列表 控制系统配置设置

针对用户表和每个系统表,SDK分别实现三个接口进行权限相关操作:

  • 用户表:
    • public String grantUserTableManager(String tableName, String address): 根据用户表名和外部账户地址设置权限信息。
    • public String revokeUserTableManager(String tableName, String address): 根据用户表名和外部账户地址去除权限信息。
    • public List<PermissionInfo> listUserTableManager(String tableName): 根据用户表名查询设置的权限记录列表(每条记录包含外部账户地址和生效块高)。
  • _sys_tables_表:
    • public String grantDeployAndCreateManager(String address): 增加外部账户地址的部署合约和创建用户表权限。
    • public String revokeDeployAndCreateManager(String address): 移除外部账户地址的部署合约和创建用户表权限。
    • public List<PermissionInfo> listDeployAndCreateManager(): 查询拥有部署合约和创建用户表权限的权限记录列表。
  • _sys_table_access_表:
    • public String grantPermissionManager(String address): 增加外部账户地址的管理权限的权限。
    • public String revokePermissionManager(String address): 移除外部账户地址的管理权限的权限。
    • public List<PermissionInfo> listPermissionManager(): 查询拥有管理权限的权限记录列表。
  • _sys_consensus_表:
    • public String grantNodeManager(String address): 增加外部账户地址的节点管理权限。
    • public String revokeNodeManager(String address): 移除外部账户地址的节点管理权限。
    • public List<PermissionInfo> listNodeManager(): 查询拥有节点管理的权限记录列表。
  • _sys_cns_表:
    • public String grantCNSManager(String address): 增加外部账户地址的使用CNS权限。
    • public String revokeCNSManager(String address): 移除外部账户地址的使用CNS权限。
    • public List<PermissionInfo> listCNSManager(): 查询拥有使用CNS的权限记录列表。
  • _sys_config_表:
    • public String grantSysConfigManager(String address): 增加外部账户地址的系统参数管理权限。
    • public String revokeSysConfigManager(String address): 移除外部账户地址的系统参数管理权限。
    • public List<PermissionInfo> listSysConfigManager(): 查询拥有系统参数管理的权限记录列表。

设置和移除权限接口返回json字符串,包含code和msg字段,当无权限操作时,其code定义-50000,msg定义为“permission denied”。当成功设置权限时,其code为0,msg为“success”。

数据定义

权限信息以系统表的方式进行存储,权限表表名为_sys_table_access_,其字段信息定义如下:

字段 类型 是否为空 主键 描述
table_name string No PRI 表名称
address string No   外部账户地址
enable_num string No   权限设置生效区块高度
_status_ string No   分布式存储通用字段,“0”表示可用,“1”表示移除

其中,对权限表的插入或更新,当前区块不生效,在当前区块的下一区块生效。状态字段为“0”时,表示权限记录处于正常生效状态,为“1”时表示已删除,即表示权限记录处于失效状态。

权限控制设计
权限控制功能设计

根据交易信息确定外部账户,待操作的表以及操作方式。待操作的表为用户表或系统表。系统表用于控制区块链的系统功能,用户表用于控制区块链的业务功能,如下图所示。外部账户通过查询权限表获取权限相关信息,确定权限后再操作相关的用户表和权限表,从而可以控制相关的系统功能和业务功能。

sequenceDiagram participant 外部账户 participant 权限表 participant 系统表 participant 用户表 外部账户->>权限表: 查询 权限表->>系统表: 控制 权限表->>用户表: 控制 系统表->>区块链的系统功能: 控制 用户表->>区块链的业务功能: 控制
权限控制流程设计

权限控制的流程如下:首先由客户端发起交易请求,节点获取交易数据,从而确定外部账户和待操作的表以及操作表的方式。如果判断操作方式为写操作,则检查该外部账户针对操作的表的权限信息(权限信息从权限表中查询获取)。若检查有权限,则执行写操作,交易正常执行;若检查无权限,则拒绝写操作,返回无权限信息。如果判断操作方式为读操作,则不检查权限信息,正常执行读操作,返回查询数据。流程图如下。

graph TB classDef blue fill:#4C84FF,stroke:#4C84FF,stroke-width:4px, font:#1D263F, text-align:center; classDef yellow fill:#FFEEB8,stroke:#FFEEB8,stroke-width:4px, font:#1D263F, text-align:center; classDef light fill:#EBF5FF,stroke:#1D263F,stroke-width:2px, font:#1D263F, text-align:center; subgraph 权限控制流程 A((开始))-->B B(客户端发起交易请求)-->C C(确定待操作的表和操作方式)-->D D(操作方式是否为写操作)-->|否|E E(获取查询结果) D-->|是|F F(是否有权限记录缓存)-->|否|G F-->H G(查询权限表)-->H H(是否有权限)-->|否|I H(是否有权限)-->|是|J I(拒绝写操作) J(执行写操作) class A,B,C,D,E,F,G,H,I,J light end
权限控制工具

FISCO BCOS的分布式存储权限控制有如下使用方式:

  • 针对普通用户,通过控制台命令使用权限功能,具体参考权限控制使用手册
  • 针对开发者,SDK根据权限控制的用户表和每个系统表均实现了三个接口,分别是授权,撤销和查询权限接口。可以调用SDK API的PermissionService接口使用权限功能。

P2P网络

设计目标

FISCO BCOS P2P模块提供高效、通用和安全的网络通信基础功能,支持区块链消息的单播、组播和广播,支持区块链节点状态同步,支持多种协议。

P2P主要功能

  • 区块链节点标识

通过区块链节点标识唯一标识一个区块链节点,在区块链网络上通过区块链节点标识对区块链节点进行寻址

  • 管理网络连接

维持区块链网络上区块链节点间的TCP长连接,自动断开异常连接,自动发起重连

  • 消息收发

在区块链网络的区块链节点间,进行消息的单播、组播或广播

  • 状态同步

在区块链节点间同步状态

区块链节点标识

区块链节点标识由ECC算法的公钥生成,每个区块链节点必须有唯一的ECC密钥对,区块链节点标识在区块链网络中唯一标识一个区块链节点

通常情况下,一个节点要加入区块链网络,至少要准备三个文件:

  • node.key 节点密钥,ECC格式
  • node.crt 节点证书,由CA颁发
  • ca.crt CA证书,CA机构提供

区块链节点除了有唯一区块链节点标识,还能关注Topic,供寻址使用

区块链节点寻址:

  • 区块链节点标识寻址

通过区块链节点标识,在区块链网络中定位唯一的区块链节点

  • Topic寻址

通过Topic,在区块链网络中定位一组关注该Topic的节点

管理网络连接

区块链节点间,会自动发起和维持TCP长连接,在系统故障、网络异常时,主动发起重连

区块链节点间建立连接时,会使用CA证书进行认证

连接建立流程
sequenceDiagram participant 区块链节点A participant 区块链节点B 区块链节点A->>区块链节点A: 加载密钥和证书 区块链节点B->>区块链节点B: 加载密钥和证书 区块链节点A->>区块链节点B: 发起连接 区块链节点B->>区块链节点A: 连接成功 区块链节点B->区块链节点A: 发起SSL握手 区块链节点A->>区块链节点A: 从证书获取公钥,作为节点ID 区块链节点B->>区块链节点B: 从证书获取公钥,作为节点ID 区块链节点B->区块链节点A: 握手成功,建立SSL连接

消息收发

区块链节点间消息支持单播、组播和广播

  • 单播,单个区块链节点向单个区块链节点发送消息,通过区块链节点标识寻址
  • 组播,单个区块链节点向一组区块链节点发送消息,通过Topic寻址
  • 广播,单个区块链节点向所有区块链节点发送消息
单播流程
sequenceDiagram participant 区块链节点A participant 区块链节点B 区块链节点A->>区块链节点A: 根据节点ID,筛选在线节点 区块链节点A->>区块链节点B: 发送消息 区块链节点B->>区块链节点A: 消息回包
组播流程
sequenceDiagram participant 区块链节点A participant 区块链节点B participant 区块链节点C participant 区块链节点D 区块链节点A->>区块链节点A: 根据Topic 1,选择节点B、C 区块链节点A->>区块链节点B: 发送消息 区块链节点A->>区块链节点C: 发送消息 区块链节点B->>区块链节点B: 根据Topic 2,选择节点C、D 区块链节点B->>区块链节点C: 发送消息 区块链节点B->>区块链节点D: 发送消息 区块链节点C->>区块链节点C: 根据Topic 3,选择节点D 区块链节点C->>区块链节点D: 发送消息
广播流程
sequenceDiagram participant 区块链节点A participant 区块链节点B participant 区块链节点C participant 区块链节点D 区块链节点A->>区块链节点A: 遍历所有节点ID 区块链节点A->>区块链节点B: 发送消息 区块链节点A->>区块链节点C: 发送消息 区块链节点A->>区块链节点D: 发送消息 区块链节点B->>区块链节点B: 遍历所有节点ID 区块链节点B->>区块链节点C: 发送消息 区块链节点B->>区块链节点D: 发送消息 区块链节点C->>区块链节点C: 遍历所有节点ID 区块链节点C->>区块链节点D: 发送消息

状态同步

每个节点会维护自身的状态,并将状态的Seq在全网定时广播,与其它节点同步

sequenceDiagram participant 区块链节点A participant 区块链节点B 区块链节点A->区块链节点B: 广播seq 区块链节点A->>区块链节点A: 判断节点B的seq是否变化 区块链节点A->>区块链节点B: seq变化,发起状态查询请求 区块链节点B->>区块链节点A: 返回节点状态 区块链节点A->>区块链节点A: 更新节点B的状态和seq

AMOP 消息转发流程

单播时序图
sequenceDiagram participant sdk [Subscriber] participant 节点0 participant 节点1 participant sdk [Publisher] sdk [Subscriber]->>节点0: 订阅 topic1、类型:0x32 节点0->>节点0: 更新 topic 列表 节点1->>节点0: 请求 topic 列表 节点0-->>节点1: 响应 topic 列表 sdk [Publisher]->>节点1: 往 topic1 单播消息、类型:0x30 节点1->>节点0: 节点转发消息 节点0->>sdk [Subscriber]: 节点转发消息 sdk [Subscriber]-->>节点0: 回包、类型:0x31 节点0-->>节点1: 节点转发消息 节点1-->>sdk [Publisher]:节点转发消息

注解

  • 单播是指如果有多个 Subscriber 订阅同一个 topic,节点则随机选择一个 Subscriber 推送消息
  • 消息发布者和消息订阅者需要选择同一个 topic
  • Subscriber 接收到消息之后的回包是由 sdk 自动发送,不需要用户自己处理,该回包仅仅表示 Subscriber 成功收到消息
  • 如果 Publisher 在推送消息之前,没有对应的订阅者,那么 Publisher 将会收到错误码 100,表示网络中没有可用节点
多播时序图
sequenceDiagram participant sdk [Subscriber0] participant sdk [Subscriber1] participant 节点0 participant 节点1 participant 节点2 participant sdk [Publisher] sdk [Subscriber0]->>节点0: 订阅 topic1、类型:0x32 节点0->>节点0: 更新 topic 列表 节点1->>节点0: 请求 topic 列表 节点0-->>节点1: 响应 topic 列表 节点2->>节点0: 请求 topic 列表 节点0-->>节点2: 响应 topic 列表 sdk [Subscriber1]->>节点1: 订阅 topic1、类型:0x32 节点1->>节点1: 更新 topic 列表 节点0->>节点1: 请求 topic 列表 节点1-->>节点0: 响应 topic 列表 节点2->>节点1: 请求 topic 列表 节点1-->>节点2: 响应 topic 列表 sdk [Publisher]->>节点2: 往 topic1 多播消息、类型:0x35 节点2->>节点0: 节点转发消息 节点2->>节点1: 节点转发消息 节点2-->>sdk [Publisher]: 回包、类型:0x31 节点0->>sdk [Subscriber0]: 节点转发消息 节点1->>sdk [Subscriber1]: 节点转发消息

注解

  • 多播是指如果有多个 Subscriber 订阅同一个 topic,节点则向所有的 Subscriber 推送消息
  • 只要网络正常,即使没有 Subsciber 接收消息, Publisher 也可以收到节点消息推送成功的响应包
带身份验证的单播时序图
sequenceDiagram participant sdk [Publisher] participant 节点0 participant 节点1 participant 节点2 participant sdk [Subscriber0] participant sdk [Subscriber1] sdk [Publisher]->>节点0: 订阅 hello、类型:0x32 节点1->>节点0: 请求 topic 列表 节点0-->>节点1: 响应 topic 列表 节点2->>节点0: 请求 topic 列表 节点0-->>节点2: 响应 topic 列表 sdk [Subscriber0]->>节点1: 订阅 world(非身份验证)、类型:0x32 节点2 ->> 节点1: 请求 topic 列表 节点1-->>节点2: 响应 topic 列表 节点0->>节点1: 请求 topic 列表 节点1-->>节点0: 响应 topic 列表 sdk [Subscriber1]->>节点2: 订阅 hello、类型:0x32 节点1 ->> 节点2: 请求 topic 列表 节点2-->>节点1: 响应 topic 列表 节点0->>节点2: 请求 topic 列表 节点2-->>节点0: 响应 topic 列表 节点0->>节点0: !$VerifyChannel_!$TopicNeedVerify_hello_{uuid} topic 待验证 节点0->>sdk [Publisher]: 发送 topic 为 !$TopicNeedVerify_hello 的消息,类型:0x37 sdk [Publisher]-->>节点0: 回包,类型:0x31 sdk [Publisher]->>sdk [Publisher]: 生成随机数 sdk [Publisher]->>节点0: 发送 topic 为<br> !$VerifyChannel_!$TopicNeedVerify_hello_{uuid} <br>的消息,类型:0x30 节点0->>节点2: 节点转发消息 节点2->>sdk [Subscriber1]: 节点转发消息 sdk [Subscriber1]->>sdk [Subscriber1]: 使用私钥 S 对随机数进行签名 sdk [Subscriber1]-->>节点2: 回包,包中 topic 为 !$VerifyChannel_!$TopicNeedVerify_hello、类型:0x31 节点2-->>节点0: 节点转发消息 节点0-->>sdk [Publisher]: 节点转发消息 sdk [Publisher]->>sdk [Publisher]: 使用公钥 P 验证签名 sdk [Publisher]->>节点0: 更新 topic !$TopicNeedVerify_hello,类型:0x38 节点0->>节点0: 更新状态 sdk [Publisher]->>节点0: 单播、类型:0x30 节点0->>节点2: 节点转发消息 节点2->>sdk [Subscriber1]: 节点转发消息 sdk [Subscriber1]-->>节点2: 回包、类型:0x31 节点2-->>节点0: 节点转发消息 节点0-->>sdk [Publisher]: 节点转发消息

注解

  • 身份验证: Publisher 在推送消息的时候,只给满足身份条件的订阅者推送消息
  • Publisher 拥有公钥 P,同时监听 #!$TopicNeedVerify_hello 和 #!​$PushChannel_#!​$TopicNeedVerify_hello 两个 topic
  • Subscriber1 拥有私钥 S,同时监听 #!$TopicNeedVerify_hello 和#!$VerifyChannel_#!$TopicNeedVerify_hello_{uuid} 两个 topic
  • 时序图中所有的 !$ 前都缺少符号 #, mermaid 不支持该符号转义
  • 节点0 给 Publisher 发送的 0x37 消息包中除了 topic:#!$TopicNeedVerify_hello,还有 topicForCert:#!$VerifyChannel_#!$TopicNeedVerify_hello_{uuid} 以及 NodeID
  • Publisher 收到 0x37 消息后回包,包中使用的 topic 为:#!$VerifyChannel_#!$VerifyChannel_#!$TopicNeedVerify_hello_{uuid}
  • 身份验证通过之后,推送消息使用的 topic 为 !$TopicNeedVerify_hello
  • 带身份验证的多播在验证身份的流程上同带身份验证的单播一致。不同点在于身份验证通过之后,单播推送消息是一对一,而多播推送消息是一对多

通信协议使用的数据结构:

  • 0x37 消息类型中 content 数据结构:

    {"nodeId":"2bb4562f4f4b69e2c5156510da4beddfca548403eb7cea49bd56daed46de31e4d78a44fdfa051170c64f0233dbc0fd75b5b4e8bc2df50a3c9ade833794128623","topic":"#!$TopicNeedVerify_hello","topicForCert":"#!$VerifyChannel_#!$TopicNeedVerify_hello_92be6ce4dbd311eaae5a983b8fda4e0e"}
    
  • 随机数单播中的 content 数据结构:

    {"randValue":"14131f50ef730219d48e1f9c441db871c","topic":"hello"}
    
  • 随机数签名回包中 content 数据结构:

    {"signature":"vU/Vzqn4MiP0nO1xN+M5TOk/YcyFtY/TLrgU38jFdooN66r3TzNVKEBpkNId8gCuAeNpNPCo8vmTV3dcs/Xj1AE="}
    
  • 0x38 消息类型中 content 数据结构:

    {"checkResult":0,"nodeId":"2bb4562f4f4b69e2c5156510da4beddfca548403eb7cea49bd56daed46de31e4d78a44fdfa051170c64f0233dbc0fd75b5b4e8bc2df50a3c9ade833794128623","topic":"#!$TopicNeedVerify_hello"}
    

RPC

RPC(Remote Procedure Call,远程过程调用)是客户端与区块链系统交互的一套协议和接口。用户通过RPC接口可查询区块链相关信息(如块高、区块、节点连接等)和发送交易。

1 名词解释

  • JSON(JavaScript Object Notation):一种轻量级的数据交换格式。它可以表示数字、字符串、有序序列和键值对。
  • JSON-RPC:一种无状态、轻量级的远程过程调用协议。 该规范主要定义了几个数据结构及其处理规则。它允许运行在基于socket,http等诸多不同消息传输环境的同一进程中。它使用JSON (RFC 4627)作为数据格式。FISCO BCOS采用JSON-RPC 2.0协议。

2 模块架构

_images/rpc.png

RPC模块负责提供FISCO BCOS的外部接口,客户端通过RPC发送请求,RPC通过调用账本管理模块p2p模块获取相关响应,并将响应返回给客户端。其中账本管理模块通过多账本机制管理区块链底层的相关模块,具体包括共识模块同步模块,区块管理模块,交易池模块以及区块验证模块。

3 数据定义

3.1 客户端请求

客户端请求发送至区块链节点会触发RPC调用,客户端请求包括下列数据成员:

  • jsonrpc: 指定JSON-RPC协议版本的字符串,必须准确写为“2.0”。
  • method: 调用方法的名称。
  • params: 调用方法所需要的参数,方法参数可选。由于FISCO BCOS 2.0启用了多账本机制,因此本规范要求传入的第一个参数必须为群组ID。
  • id: 已建立客户端的唯一标识ID,ID必须是一个字符串、数值或NULL空值。如果不包含该成员则被认定为是一个通知。

RPC请求包格式示例:

{"jsonrpc": "2.0", "method": "getBlockNumber", "params": [1], "id": 1}

注:

  • 在请求对象中不建议使用NULL作为id值,因为该规范将使用空值认定为未知id的请求。
  • 在请求对象中不建议使用小数作为id值,因为具有不确定性。
3.2 服务端响应

当发起一个RPC调用时,除通知之外,区块链节点都必须回复响应。响应表示为一个JSON对象,使用以下成员:

  • jsonrpc: 指定JSON-RPC协议版本的字符串。必须准确写为“2.0”。
  • result: 正确结果字段。该成员在响应处理成功时必须包含,当调用方法引起错误时必须不包含该成员。
  • error: 错误结果字段。该成员在失败是必须包含,当没有引起错误的时必须不包含该成员。该成员参数值必须为3.3节中定义的对象。
  • id: 响应id。该成员必须包含,该成员值必须与对应客户端请求中的id值一致。若检查请求对象的id错误(例如参数错误或无效请求),则该值必须为空值。

RPC响应包格式示例:

{"jsonrpc": "2.0", "result": "0x1", "id": 1}

注: 服务端响应必须包含result或error成员,但两个成员不能同时包含。

3.3 错误对象

当一个RPC调用遇到错误时,返回的响应对象必须包含error错误结果字段,相关的描述和错误码,请参考:RPC 错误码

4 RPC接口的设计

FISCO BCOS提供丰富的RPC接口供客户端调用。其中分为3类:

  • 以get开头命名的查询接口:例如[getBlockNumber]接口,查询最新的区块高度。
  • [sendRawTransaction]接口: 执行一笔签名的交易,将等待区块链共识后才返回响应。
  • [call]接口: 执行一个请求将不会创建一笔交易,不需要区块链共识,而是获取响应立刻返回。

5 RPC接口列表

参考RPC API文档

数据结构与编码协议

交易结构及其RLP编码描述

FISCO BCOS的交易结构在原以太坊的交易结构的基础上,有所增减字段。FISCO BCOS 2.0+的交易结构字段如下:

name type description RLP index RC1 RLP index RC2
type enum 交易类型,表明该交易是创建合约还是调用合约交易,初始为空合约 - -
nonce u256 消息发送方提供的随机数,用于唯一标识交易 0 0
value u256 转账数额,目前去币化的FISCO BCOS不使用该字段 5 5
receiveAddress h160 交易接收方地址,type为创建合约时该地址为0x0 4 4
gasPrice u256 本次交易的gas的单价,FISCO BCOS中为固定值300000000 1 1
gas u256 本次交易允许最多消耗的gas数量,FISCO BCOS可配置该值 2 2
data vector< byte > 与交易相关的数据,或者是创建合约时的初始化参数 6 6
chainId u256 记录本次交易所属的链信息/业务信息 - 7
groupId u256 记录本次交易所属的群组 - 8
extraData vector< byte > 预留字段,记录交易信息,内部使用“#”分割信息 - 9
vrs SignatureStruct 交易发送方对交易7字段RLP编码后的哈希值签名生成的数据 7,8,9 10,11,12
hashWith h256 交易结构所有字段(含签名信息)RLP编码后的哈希值 - -
sender h160 交易发送方地址,基于vrs生成 - -
blockLimit u256 交易生命周期,该交易最晚被处理的块高,FISCO BCOS新增字段 3 3
importTime u256 交易进入交易池的unix时间戳,FISCO BCOS新增字段 - -
rpcCallback function 交易出块后RPC回调,FISCO BCOS新增字段 - -

RC1的hashWith字段(也称交易hash/交易唯一标识)的生成流程如下:

_images/generate_hash_process.png

RC2的生成流程也类似,只是在第一步rlp+hash的transaction结构体中增加chainId、groupId和extraData三个字段。

区块结构及其RLP编码描述

FISCO BCOS的区块由以下五部分组成

rc1版本

name description RLP index
blockHeader 区块头RLP编码 0
transactions 交易列表RLP编码 1
transactionReceipts 交易回执列表RLP编码 2
hash 区块头RLP编码后的哈希值 3
sigList PBFT共识落盘阶段收集到的节点签名信息,Raft不使用 4

rc2、rc3、2.0及以上版本

name description RLP index
blockHeader 区块头RLP编码 0
transactions 交易列表RLP编码 1
hash 区块头RLP编码后的哈希值 2
sigList PBFT共识落盘阶段收集到的节点签名信息,Raft不使用 3
transactionReceipts 交易回执列表RLP编码 4

FISCO BCOS的区块头中每个字段意义如下:

name type description RLP index
parentHash h256 父区块的哈希值 0
stateRoot h256 状态树的根哈希值 1
transactionsRoot h256 交易树的根哈希值 2
receiptsRoot h256 收据树的根哈希值 3
dbHash h256 分布式存储通过计算哈希值来记录一区块中写入的数据,FISCO BCOS新增字段 4
logBloom LogBloom 交易收据日志组成的Bloom过滤器,FISCO BCOS目前尚未使用 5
number int64_t 本区块的块号,块号从0号开始计算 6
gasLimit u256 本区块中所有交易消耗的Gas上限 7
gasUsed u256 本区块中所有交易使用的Gas之和 8
timestamp int64_t 打包区块的unix时间戳 9
extraData vector 区块的附加数据,FISCO BCOS目前只用于在第0块中记录群组genesis文件信息 10
sealer u256 打包区块的节点在共识节点列表中的索引,FISCO BCOS新增字段 11
sealerList vector 区块的共识节点列表(不含观察节点),FISCO BCOS新增字段 12
hash h256 区块头前13个字段RLP编码后的哈希值,FISCO BCOS新增字段 -

交易收据

name type description RLP index
stateRoot h256 区块状态根 0
gasUsed u256 交易消耗的gas 1
contractAddress Address 部署合约的地址 2
bloom h2048 布隆滤波器 3
status h256 交易执行结果的状态码 4
output LogBloom 交易返回值 5
logs LogEntry[] event logs 6

网络传输协议

FISCO BCOS 目前有两类数据包格式,节点与节点间通信的数据包为P2PMessage格式,节点与SDK间通信的数据包为ChannelMessage格式。

_images/message_type.png

P2PMessage

v2.0.0-rc2扩展了群组ID和模块ID范围,最多支持32767个群组,且新增了Version字段来支持其他特性(如网络压缩),包头大小为16字节,v2.0.0-rc2的网络数据包结构如下:

_images/network_packet1.png

name type description
Length uint32_t 数据包长度,含包头和数据
Version uint16_t 记录数据包版本和特性信息,目前最高位0x8000用于记录数据包是否压缩
groupID (GID) int16_t 群组ID,范围1-32767
ModuleID (MID) uint16_t 模块ID,范围1-65535
PacketType uint16_t 数据包类型,同一模块ID下的子协议标识
Seq uint32_t 数据包序列号,每个数据包自增
Data vector 数据本身,长度为lenght-12

v2.0.0-rc2以前的P2PMessage定义请参考这里

补充

  1. P2PMessage不限制包大小,由上层调用模块(共识/同步/AMOP等)进行包大小管理;
  2. 群组ID和模块ID可唯一标识协议ID(protocolID),三者关系为protocolID = (groupID << sizeof(groupID)*8) | ModuleID
  3. 数据包通过protocolID所在的16位二进制数值来区分请求包和响应包,大于0为请求包,小于0为相应包。
  4. 目前AMOP使用的packetType有SendTopicSeq = 1,RequestTopics = 2,SendTopics = 3
ChannelMessage v2

ChannelMessage v1 请参考这里

字段 类型 长度(Byte) 描述
length uint32_t 4 数据包长度,含包头和数据,大端
type uint16_t 2 数据包类型,大端
seq string 32 数据包序列号,32字节uuid
result int32_t 4 错误码,大端
data bytes length-42 数据包体,字节流
AMOP消息包

AMOP消息包继承ChannelMessage包结构,在data字段添加了自定义内容。包括0x30,0x31,0x35,0x1001

长度Byte 说明
length 1 Topic的长度
topic length topic名
消息包类型

数据包类型枚举值及其对应的含义如下:

类型 包体 描述 解释
0x12 JSONRPC 2.0格式 RPC接口消息包 SDK->节点
0x13 json格式心跳包{"heartbeat":"0"} 心跳包 0:SDK->节点,1:节点->SDK
0x14 SDK->节点的包体{"minimumSupport":version,"maximumSupport":version,"clientType":"client type"},节点->SDK的包体{"protocol":version,"nodeVersion":"fisco-bcos version" 握手包,json格式的协议版本协商 SDK<->节点,双向
0x30 AMOP消息包包体 AMOP请求包 SDK<->节点,双向
0x31 失败的AMOP消息的包体 AMOP失败响应包 节点->SDK或节点->节点
0x32 json数组,存储SDK监听的Topics 上报Topic信息 SDK->节点
0x35 AMOP消息包包体 AMOP多播消息 节点->节点
0x1000 json格式的交易上链通知 交易上链回调 节点->SDK
0x1001 json格式的区块上链通知{"groupID":"groupID","blockNumber":"blockNumber"} 区块高度通知 节点->SDK
错误码
code message
0 成功
100 节点不可达
101 SDK不可达
102 超时

交易并行

1 名词解释

1.1 DAG

一个无环的有向图称做有向无环图(Directed Acyclic Graph),简称DAG图。在一批交易中,可以通过一定方法识别出每笔交易需要占用的互斥资源,再根据交易在Block中的顺序及互斥资源的占用关系构造出一个交易依赖DAG图,如下图所示,凡是入度为0(无被依赖的前序任务)的交易均可以并行执行。如下图所示,基于左图的原始交易列表的顺序进行拓扑排序后,可以得到右图的交易DAG。

_images/DAG.png

2 模块架构

_images/architecture2.png 其中主要流程包括:

  • 用户直接或间接通过SDK发起交易。交易可以是能够并行执行的交易和不能并行执行的交易;
  • 交易进入节点的交易池中,等待打包;
  • 交易被Sealer打包为区块,经过共识后,发送至BlockVerifier进行验证;
  • BlockVerifier根据区块中的交易列表生成交易DAG;
  • BlockVerifier构造执行上下文,并行执行交易DAG;
  • 区块验证通过后,区块上链。

3 重要流程

3.1 交易DAG构建
3.1.1 DAG数据结构

方案中所用到的DAG数据结构如下: _images/TxDAG.png 其中:

  • 顶点(Vertex)
    • inDegree用于存储顶点当前的入度;
    • outEdge用于保存该顶点的出边信息,具体为所有出边所连顶点的ID列表。
  • DAG:
    • vtxs是用于存储DAG中所有节点的列表;
    • topLevel是一个并发队列,用于存储当前入度为0的节点ID,执行时供多个线程并发访问;
    • totalVtxs:顶点总数
    • totalConsume:已经执行过的顶点总数;
    • void init(uint32_t _maxSize):初始化一个最大顶点数为maxSize的DAG;
    • void addEdge(ID from, ID to):在顶点from和to之间建立一条有向边;
    • void generate():根据已有的边和顶点构造出一个DAG结构;
    • ID waitPop(bool needWait):等待从topLevel中取出一个入度为0的节点;
    • void clear():清除DAG中所有的节点与边信息。
  • TxDAG:
    • dag:DAG实例
    • exeCnt:已经执行过的交易计数;
    • totalParaTxs:并行交易总数;
    • txs:并行交易列表
    • bool hasFinished():若整个DAG已经执行完毕,返回true,否则返回false;
    • void executeUnit():取出一个没有上层依赖的交易并执行;
3.1.2 交易DAG构造流程

流程如下:

_images/dag_construction.png

  1. 从打包好的区块从取出区块中的所有交易;
  2. 将交易数量作为最大顶点数量初始化一个DAG实例;
  3. 按序读出所有交易,如果一笔交易是可并行交易,则解析其冲突域,并检查是否有之前的交易与该交易冲突,如果有,则在相应交易间构造依赖边;若该交易不可并行,则认为其必须在前序的所有交易都执行完后才能执行,因此在该交易与其所有前序交易间建立一条依赖边。
3.2 DAG执行流程

流程如下:

_images/execution.png

  1. 主线程会首先根据硬件核数初始化一个相应大小的线程组,若获取硬件核数失败,则不创建其他线程;
  2. 当DAG尚未执行完毕时,线程循环等待从DAG中pop出入度为0的交易。若成功取出待执行的交易,则执行该交易,执行完后将后续的依赖任务的入度减1,若有交易入度被减至0,则将该交易加入topLevel中;若失败,则表示DAG已经执行完毕,线程退出。

其他特性

为了提供更好的智能合约调用体验、支持更高的安全性,FISCO BCOS引入了合约命名服务(Contract Name Service, CNS)、国密算法和落盘加密特性。

  • 合约命名服务(Contract Name Service, CNS)

以太坊基于智能合约地址调用合约,存在如下问题:

  • 合约abi为较长的JSON字符串,调用方无法直接感知
  • 合约地址为20字节的魔数,不方便记忆,若丢失后将导致合约不可访问
  • 约重新部署后,一个或多个调用方都需更新合约地址
  • 不便于进行版本管理以及合约灰度升级

FISCO BCOS引入的合约命名服务CNS通过提供链上合约名称与合约地址映射关系的记录及相应的查询功能,方便调用者通过记忆简单的合约名来实现对链上合约的调用。

  • 国密算法

为了充分支持国产密码学算法,FISCO BCOS基于 国产密码学标准 ,实现了国密加解密、签名、验签、哈希算法、国密SSL通信协议,并将其集成到FISCO BCOS平台中,实现对 国家密码局认定的商用密码 的完全支持。

  • 落盘加密特性

考虑到联盟链的架构中,数据在联盟链的各个机构内是可见的,FISCO BCOS引入了落盘加密特性,对存储到节点数据库中的数据进行加密,并引入Key Manager保存加密密钥,保障了节点数据的机密性。

CNS方案

概述

调用以太坊智能合约的流程包括:

  1. 编写合约;
  2. 编译合约得到合约接口abi描述;
  3. 部署合约得到合约地址address;
  4. 封装合约的abi和地址,通过SDK等工具实现对合约的调用。

从合约调用流程可知,调用之前必须准备合约abi以及合约地址address。这种使用方式存在以下的问题:

  1. 合约abi为较长的JSON字符串,调用方不需直接感知;
  2. 合约地址为20字节的魔数,不方便记忆,若丢失后将导致合约不可访问;
  3. 合约重新部署后,一个或多个调用方都需更新合约地址;
  4. 不便于进行版本管理以及合约灰度升级。

为解决以上问题,给调用者提供良好的智能合约调用体验,FISCO BCOS提出CNS合约命名服务

名词解释
  • CNS(Contract Name Service)通过提供链上合约名称与合约地址映射关系的记录及相应的查询功能,方便调用者通过记忆简单的合约名来实现对链上合约的调用。
  • CNS信息为合约名称、合约版本、合约地址和合约abi
  • CNS表用于存储CNS信息
CNS对比以太坊原有调用方式的优势
  • 简化调用合约方式;
  • 合约升级对调用者透明,支持合约灰度升级。
对标ENS

ENS (Ethereum Name Service) ,以太坊名称服务。

ENS的功能类似我们较熟悉的DNS(Domain Name Service)域名系统,但提供的不是Internet网址,而是将以太坊(Ethereum)合约地址和钱包地址以xxxxxx.eth网址的方式表示,用于存取合约或转账。两者相比:

  • ENS映射的地址类型包括合约地址及钱包地址,CNS可支持,当地址类型为钱包地址时合约abi为空。
  • ENS有竞拍功能,CNS不需支持。
  • ENS支持多级域名,CNS不需支持。
模块架构

_images/cns_architecture.png

CNS架构
核心流程

用户调用SDK部署合约及调用合约流程如下:

_images/deploy_and_call.png

SDK部署合约及调用合约流程
  • 部署合约时,SDK生成合约对应的Java类,调用类的deploy接口发布合约获得合约地址,然后调用CNS合约insert接口上链CNS信息。
  • 调用合约时,SDK引入合约的Java类,并加载实例化。load加载接口可传入合约地址(原有以太坊方式)或合约名称和合约版本的组合(CNS方式),SDK处理CNS方式时通过调用CNS模块查询链上信息来获取合约地址。
  • 对于缺少版本号的合约调用,由SDK实现默认调用合约的最新版本。
  • 上链的合约abi信息属于可选字段。
数据结构
CNS表结构

CNS信息以系统表的方式进行存储,各账本独立。CNS表定义如下:

Field
Type
Null
Key
Expain
namestringNoPRI合约名称,name和version为联合主键
versionstringNo合约版本,name和version为联合主键
addressstringNo合约地址
abistringYES合约abi
_status_stringNo分布式存储通用字段,“0”可用“1”删除
合约接口
pragma solidity ^0.4.2;
contract CNS
{
    function insert(string name, string version, string addr, string abi) public returns(uint256);
    function selectByName(string name) public constant returns(string);
    function selectByNameAndVersion(string name, string version) public constant returns(string);
}
  • CNS合约不暴露给用户,为SDK与底层CNS表的交互接口。
  • insert接口提供CNS信息上链的功能,接口四个参数分别为合约名称name、合约版本version、合约地址addr和合约ABI信息abi。SDK调用接口需判断name和version的组合与数据库原有记录是否重复,在不重复的前提下才能发起上链交易。节点在执行交易时,precompiled逻辑会Double Check,发现数据重复就直接抛弃该交易。insert接口对CNS表的内容只增不改。
  • selectByName接口参数为合约名称name,返回表中所有基于该合约的不同version记录。
  • selectByNameAndVersion接口参数为合约名称name和合约版本version,返回表中该合约该版本的唯一地址。
更新CNS表方式

预编译合约是FISCO BCOS底层通过C++实现的一种高效智能合约,用于FISCO BCOS底层的系统信息配置与管理。引入precompiled逻辑后,FISCO BCOS节点执行交易的流程如下:

CNS合约属于预编译合约类型,节点将通过内置C++代码逻辑实现对CNS表的插入和查询操作,不经EVM执行,因此CNS合约只提供了函数接口描述而没有函数实现。预置CNS合约的precompiled地址为0x1004。

合约接口返回示例

selectByName和selectByNameAndVersion接口返回的string为Json格式,示例如下:

[
    {
        "name" : "Ok",
        "version" : "1.0",
        "address" : "0x420f853b49838bd3e9466c85a4cc3428c960dde2",
        "abi" : "[{\"constant\":false,\"inputs\":[{\"name\":\"num\",\"type\":\"uint256\"}],\"name\":\"trans\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"co
nstant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\
"type\":\"constructor\"}]"
    },
    {
        "name" : "Ok",
        "version" : "2.0",
        "address" : "0x420f853b49838bd3e9466c85a4cc3428c960dde2",
        "abi" : "[{\"constant\":false,\"inputs\":[{\"name\":\"num\",\"type\":\"uint256\"}],\"name\":\"trans\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"co
nstant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\
"type\":\"constructor\"}]"
    }
]
SDK_API

SDK开发者可使用org.fisco.bcos.web3j.precompile.cns中以下两接口实现CNS的注册及查询功能。

registerCns
  • 描述:public TransactionReceipt registerCns(String name, String version, String addr, String abi)
  • 功能:上链合约信息
  • 参数:name——合约名,version——合约版本,addr——合约地址,abi——合约abi
  • 返回:上链交易回执,回执中含上链结果信息及错误信息(如有)
resolve
  • 描述:public String resolve(String contractNameAndVersion)
  • 功能:基于合约名和合约版本查询合约地址
  • 参数:contractNameAndVersion——合约名+合约版本信息
  • 返回:合约地址,如无参数指定版本的合约信息,接口抛出异常
  • 说明:contractNameAndVersion通过:来分割合约名和合约版本,当缺少合约版本时,SDK默认调用使用合约的最新版本进行查询

注意:

  1. 在调用接口前,需将sol合约转换Java类,并将生成的Java类以及abi、bin文件置于正确的目录,详细使用方法请参考Web3SDK
  2. 两个接口的使用例子可参考ConsoleImpl.java中的deployByCNS和callByCNS接口实现。
操作工具

控制台可提供部署合约、调用合约、基于合约名查询链上已有合约的功能。控制台的详细使用方法请参考《控制台》

控制台提供的命令包括:

  • deployByCNS:通过CNS方式部署合约
  • callByCNS:通过CNS方式调用合约
  • queryCNS:根据合约名称和合约版本号(可选参数)查询CNS表信息

国密支持方案

设计目标

为了充分支持国产密码学算法,金链盟基于国产密码学标准,实现了国密加解密、签名、验签、哈希算法、国密SSL通信协议,并将其集成到FISCO BCOS平台中,实现了对国家密码局认定的商用密码的完全支持。

国密版FISCO BCOS将交易签名验签、p2p网络连接、节点连接、数据落盘加密等底层模块的密码学算法均替换为国密算法,国密版FISCO BCOS与标准版主要特性对比如下:

标准版FISCO BCOS 国密版FISCO BCOS
SSL链接 Openssl TLSv1.2协议 国密TLSv1.1协议
签名验证 ECDSA签名算法 SM2签名算法
消息摘要算法 SHA-256 SHA-3 SM3消息摘要算法
落盘加密算法 AES-256加密算法 SM4加密算法
证书模式 OpenSSL证书模式 国密双证书模式
合约编译器 以太坊solidity编译器 国密solidity编译器

(注:国密算法SM2, SM3, SM4均基于国产密码学标准开发)

系统框架

系统整体框架如下图所示:

_images/guomishakehand.png

国密SSL 1.1 握手建立流程

国密版FISCO BCOS节点之间的认证选用国密SSL 1.1的ECDHE_SM4_SM3密码套件进行SSL链接的建立,差异如下表所示:

OpenSSL 国密SSL
加密套件 采用ECDH、RSA、SHA-256、AES256等密码算法 采用国密算法
PRF算法 SHA-256 SM3
密钥交换方式 传输椭圆曲线参数以及当前报文的签名 当前报文的签名和加密证书
证书模式 OpenSSL证书模式 国密双证书模式,分别为加密证书和签名证书
数据结构差异

国密版与标准版FISCO BCOS在数据结构上的差异如下:

算法类型 标准版FISCO BCOS 国密版FISCO BCOS
签名 ECDSA (公钥长度: 512 bits, 私钥长度: 256 bits) SM2 (公钥长度:512 bits, 私钥长度:256 bits)
哈希 SHA3 (哈希串长度: 256 bits) SM3 (哈希串长度: 256 bits)
对称加解密 AES (加密秘钥长度: 256 bits) SM4 (对称密钥长度: 128 bits)
交易长度 520bits(其中标识符8bits,签名长度512bits) 1024bits(128字节,其中公钥512bits,签名长度512bits)

落盘加密

背景介绍

在联盟链的架构中,机构和机构之间搭建一条区块链,数据在联盟链的各个机构内是可见的。

在某些数据安全性要求较高的场景下,联盟内部的成员并不希望联盟之外的机构能够获取联盟链上的数据。此时,就需要对联盟链上的数据进行访问控制。

联盟链数据的访问控制,主要分为两个方面

  • 链上通信数据的访问控制
  • 节点存储数据的访问控制

对于链上通信数据的访问控制,FISCO BCOS是通过节点证书和SSL来完成。此处主要介绍的是节点存储数据的访问控制,即落盘加密。

_images/data_secure_background.png

主要思想

落盘加密是在机构内部进行的。在机构的内网环境中,每个机构独立地对节点的硬盘数据进行加密。当节点所在机器的硬盘被带离机构,并让节点在机构内网之外的网络启动,硬盘数据将无法解密,节点无法启动。进而无法盗取联盟链上的数据。

方案架构

_images/diskencryption_framework.png

落盘加密是在机构内部进行的,每个机构独立管理自己硬盘数据的安全。内网中,每个节点的硬盘数据是被加密的。所有加密数据的访问权限,通过Key Manager来管理。Key Manager是部署在机构内网内,专门管理节点硬盘数据访问秘钥的服务,外网无法访问。当内网的节点启动时,从Key Manager处获取加密数据的访问秘钥,来对自身的加密数据进行访问。

加密保护的对象包括:

  • 节点本地存储的数据库:leveldb
  • 节点私钥:node.key,gmnode.key(国密)
具体实现

具体的实现过程,是通过节点自身持有的秘钥(dataKey)和Key Manager管理的全局秘钥(superKey)来完成的。

节点

  • 节点用自己的dataKey,对自身加密的数据(Encrypted Space)进行加解密。
  • 节点本身不会在本地磁盘中存储dataKey,而是存储dataKey被加密后的cipherDataKey。
  • 节点启动时,拿cipherDataKey向Key Manager请求,获取dataKey。
  • dataKey只在节点的内存中,当节点关闭后,dataKey自动丢弃。

Key Manager

持有全局的superKey,负责对所有节点启动时的授权请求进行响应,授权。

  • Key Manager必须实时在线,响应节点的启动请求。
  • 当节点启动时,发来cipherDataKey,Key Manager用superKey对cipherDataKey进行解密,若解密成功,就将节点的dataK返回给节点。
  • Key Manager只能在内网访问,机构内的外网无法访问Key Manager.

_images/diskencryption.png

方案流程

方案流程分为节点初始配置和节点安全运行。

节点初始配置

节点启动前,需要为节点配置dataKey

重要

节点在生成后,启动前,必须决定好是否采用落盘加密,一旦节点配置成功,并正常启动,将无法切换状态。

(1)管理员定义好节点的的dataKey,并将dataKey发送给Key Manager,从Key Manager处获取cipherDataKey。

(2)将cipherDataKey配置到节点的配置文件中

(3)启动节点

节点安全运行

节点启动时,会通过Key Manager,获取本地数据访问的秘钥dataKey。

(1)节点启动,从配置文件中读取cipherDataKey,并发送给Key Manager。

(2)Key Manager收到cipherDataKey,用superKey解密cipherDataKey,若解密成功,则将解密后的dataKey返回给节点。

(3)节点拿到dataKey,用dataKey对本地的数据(Encrypted Space)进行交互。从Encrypted Space读取的数据,用dataKey解密获取真实数据。要写入Encrypted Space的数据,先用dataKey加密,再写入。

为什么可以保护数据?

当某节点的硬盘被意外的带到内网环境之外,数据是不会泄露的。

(1)当节点在内网之外启动时,无法连接Key Manager,虽然有cipherDataKey,也无法获取dataKey。

(2)不启动节点,直接对节点本地的数据进行操作,由于拿不到dataKey,无法解密Encrypted Space,拿不到敏感数据。

具体落盘加密的使用,可参考:落盘加密操作

网络压缩

外网环境下,区块链系统性能受限于网络带宽,为了尽量减少网络带宽对系统性能的影响,FISCO BCOS从relase-2.0.0-rc2开始支持网络压缩功能,该功能主要在发送端进行网络数据包压缩,在接收端将解包数据,并将解包后的数据传递给上层模块。

系统框架

网络压缩主要在P2P网络层实现,系统框架如下:

_images/network_compress.png

网络压缩主要包括两个过程:

  • 发送端压缩数据包:群组层通过P2P层发送数据时,若数据包大小超过1KB,则压缩数据包后,将其发送到目标节点;
  • 接收端解压数据包:节点收到数据包后,首判断收到的数据包是否被压缩,若数据包是压缩后的数据包,则将其解压后传递给指定群组,否则直接将数据传递给对应群组。
核心实现

综合考虑性能、压缩效率等,我们选取了Snappy来实现数据包压缩和解压功能。本节主要介绍网络压缩的实现。

数据压缩标记位

FISCO BCOS的网络数据包结构如下图:

_images/network_packet.png

网络数据包主要包括包头和数据两部分,包头占了16个字节,各个字段含义如下:

  • Length: 数据包长度
  • Version: 扩展位,用于扩展网络模块功能
  • ProtocolID: 存储了数据包目的群组ID和模块ID,用于多群组数据包路由,目前最多支持32767个群组
  • PaketType: 标记了数据包类型
  • Seq: 数据包序列号

网络压缩模块仅压缩网络数据,不压缩数据包头。

考虑到压缩、解压小数据包无法节省数据空间,而且浪费性能,在数据压缩过程中,不压缩过小的数据包,仅压缩数据包大于c_compressThreshold的数据包.c_compressThreshold默认是1024(1KB)。我们扩展了Version的最高位,作为数据包压缩标志:

_images/network_version.png

  • Version最高位为0,表明数据包对应的数据Data是未压缩的数据;
  • Version最高位为1,表明数据包对应的数据Data是压缩后的数据。
处理流程

下面以群组1的一个节点向群组内其他节点发送网络消息包packetA为例(比如发送交易、区块、共识消息包等),详细说明网络压缩模块的关键处理流程。

发送端处理流程

  • 群组1的群组模块将packetA传入到P2P层;
  • P2P判断packetA的数据包大于c_compressThreshold,则调用压缩接口,对packetA进行压缩,否则直接将packetA传递给编码模块;
  • 编码模块给packetA加上包头,附带上数据压缩信息,即:若packetA是压缩后的数据包,将包头Version的最高位置为1,否则置为0;
  • P2P将编码后的数据包传送到目的节点。

接收端处理流程:

  • 目标机器收到数据包后,解码模块分离出包头,通过包头Version字段的最高位是否为1,判断网络数据是否被压缩;
  • 若网络数据包被压缩过,则调用解压接口,对Data部分数据进行解压,并根据数据包头附带的GID和PID,将解压后的数据传递给指定群组的指定模块;否则直接将数据包传递给上层模块。
兼容性说明
  • 数据兼容:不涉及存储数据的变更;
  • 网络兼容rc1:向前兼容,仅有relase-2.0.0-rc2及以上节点具有网络压缩功能。

合约管理

本文档描述合约生命周期管理中冻结/解冻操作(以下简称合约生命周期管理操作)及其操作权限的设计方案。

重要

合约生命周期管理操作支持storagestate的存储模式,不支持mptstate的存储模式。这里提及的合约目前只限于Solidity合约,不包括预编译合约。

名词解释

合约管理的相关操作包括冻结、解冻、查询状态、授权、查询授权。

  • 冻结合约:可逆操作,一合约冻结后读写接口都不能被调用
  • 解冻合约:撤销冻结的操作,一合约解冻后读写接口都可调用
  • 查询合约状态:查询合约状态,返回该合约可用/已冻结的状态
  • 授权:已有权限的账号可以给其他账号授予合约管理权限
  • 查询授权:查询合约的管理权限列表

重要

冻结合约的操作不会对原有合约内容(代码逻辑+数据)进行修改,只会通过标志位进行记录。

合约状态(可用、已冻结)转换矩阵如下:

available(可用) frozen(已冻结)
freeze(冻结) 冻结成功 失败,提示已冻结
unfreeze(解冻) 失败,提示合约可用无需解冻 解冻成功
具体实现
合约状态存储
  • 新增一字段frozen,用于记录该合约是否已冻结,该字段默认为false,表示可用,冻结时该值为true;
  • 新增一字段authority,用于记录合约管理员账号,每个账号对应一行authority记录。

注意:

  1. 对不存在字段frozen的合约表,查询该字段时将返回false;
  2. 部署合约时,将部署账号tx.origin写入authority;
  3. 调用合约A接口过程中创建新合约B时,对于新合约B,默认将tx.origin及合约A的权限信息写入合约B的authority。
合约状态判断

Executive中根据合约地址获取frozen字段值,进行判断后交易顺利执行,或者抛出异常,提示该合约已冻结。

管理权限判断
  • 更新合约状态的操作需进行权限判断,只有authority列表中的账号才能设置该合约的状态;
  • 授予权限的操作也需进行权限判断,只有authority列表中的账号才能授予其他账号管理该合约的权限;
  • 查询合约状态及权限列表不需进行权限判断。
合约生命周期管理接口

新增一个合约生命周期管理的预编译合约ContractLifeCyclePrecompiled,地址为0x1007,用于给指定合约设置指定状态,并提供查询功能。

contract ContractLifeCyclePrecompiled {
    function freeze(address addr) public returns(int);
    function unfreeze(address addr) public returns(int);
    function grantManager(address contractAddr, address userAddr) public returns(int);
    function getStatus(address addr) public constant returns(uint,string);
    function listManager(address addr) public constant returns(uint,address[]);
}
返回码描述
code message
0 success
-51900 the contract has been frozen
-51901 the contract is available
-51902 the contract has been granted authorization with same user
-51903 the contract address is invalid
-51904 the address is not exist
-51905 this operation has no permissions

重要

兼容性说明:合约管理相关操作只能在2.3及以上版本上进行。

账号管理

本文档描述账号的冻结/解冻操作及其操作权限的设计方案。

重要

账号冻结/解冻操作支持storagestate的存储模式,不支持mptstate的存储模式。

名词解释

账号管理的相关操作包括冻结、解冻、查询状态、授权、查询授权。

  • 冻结账号:可逆操作,一账号冻结后不能部署及调用合约
  • 解冻账号:撤销冻结的操作,一账号解冻后可部署及调用合约
  • 查询账号状态:查询账号状态,返回该账号可用/已冻结的状态

账号的冻结/解冻操作只能由链的委员进行。权限相关操作请参考基于角色的权限控制

重要

冻结账号的操作不会对原有账号内容进行修改,只会通过标志位进行记录。

账号状态(可用、已冻结)转换矩阵如下:

available(可用) frozen(已冻结)
freeze(冻结) 冻结成功 失败,提示已冻结
unfreeze(解冻) 失败,提示账号可用无需解冻 解冻成功
具体实现
账号状态存储
  • 复用字段frozen,用于记录该账号是否已冻结,该字段默认为false,表示可用,冻结时该值为true;

注意:

  1. 对不存在字段frozen的账号表,查询该字段时将返回false;
账号状态判断

Executive中根据账号地址获取frozen字段值,进行判断后部署或执行交易,或者抛出异常,提示该账号已冻结。

重要

兼容性说明:账号管理相关操作只能在2.5及以上版本上进行。

网络流量和Gas统计

FISCO BCOS 2.0引入了多群组架构,允许一个节点启动若干个群组,这种架构可快速平行扩展、简化了运维复杂度、降低了管理成本,但由于一个节点进程运行了多个群组,增加了群组监控复杂度。考虑到实时监控系统资源使用情况在实际生产系统中非常重要,FISCO BCOS v2.4.0引入了统计日志,并实现了群组级别的网络流量统计以及交易级别的Gas消耗统计功能。

模块架构

下图网络流量和Gas统计的模块图,FISCO BCOS节点实时统计群组内网络、Gas消耗信息,并将其输出到统计日志中:

_images/network_gas_stat.png

网络流量统计信息定期输出到统计日志中,目前统计的网络流量主要包括:

  • SDK到节点的网络流量:统计每个群组RPC流量、交易推送流量以及事件推送流量
  • P2P网络流量:统计群组间因同步、共识等模块进行P2P网络交互产生的网络流量
  • 网络总流量:每个群组网络消耗总流量,包括SDK到节点的网络流量和P2P网络流量

Gas统计输出通过区块落盘触发,每落盘一个区块会将对应的Gas消耗信息输出到统计日志中,目前统计的信息包括:

  • 每个区块Gas消耗信息
  • 每笔交易Gas消耗信息
统计日志详细说明

注解

  • 统计日志的级别是info
  • 统计日志每一小时分割一次
网络流量统计日志

为了方便开发者根据网络流量统计日志对系统进行诊断,FISCO BCOS v2.4.0统计网络流量时,根据流量的相关模块做了细分。这里详细介绍网络流量统计日志的格式和关键字含义。

注解

  • 输出到日志里的网络流量单位是字节Byte
  • 网络统计功能仅统计某个时间段的网络消耗总流量,统计信息输出到日志后,计数器会清零,重新开始下一轮统计
SDK到节点的网络流量

SDK到节点的网络流量统计日志记录的模块关键字是SDK,不仅统计了每个群组与SDK之间的总的出流量和入流量,还细分了RPC请求产生的流量、事件推送流量以及交易推送流量。日志示例如下:

info|2020-04-24 12:58:41.173045|SDK|,g=1,SDK_RPCIn=10023,SDK_EventLogIn=500,SDK_totalIn=10523,SDK_RPC_Out=0,SDK_Txs_Out=0,SDK_EventLog_Out=0,SDK_total_Out=0

关键字说明

日志关键字 说明
g 群组ID
SDK_RPCIn RPC请求入流量
SDK_EventLogIn 事件推送相关的入流量
SDK_totalIn 群组的总的入流量
SDK_RPC_Out RPC请求出流量
SDK_Txs_Out 交易推送产生的出流量
SDK_EventLog_Out 事件推送产生的出流量
SDK_total_Out 群组的总出流量
P2P网络流量

群组间P2P流量统计日志记录的模块关键字是P2P,不仅统计了每个群组总的P2P出流量和入流量,还细分了共识流量、同步流量,日志示例如下:

info|2020-04-24 12:58:41.173077|P2P|,g=2,P2P_CONSIn=80505,P2P_SYNCIn=19008,P2P_totalIn=99513,P2P_CONS_Out=211377,P2P_SYNC_Out=19008,P2P_total_Out=230385  

关键字说明

日志关键字 说明
g 群组ID
P2P_CONSIn 因共识模块调度产生的入流量
P2P_SYNCIn 因同步模块调度产生的入流量
P2P_totalIn 群组总的P2P入流量
P2P_CONS_Out 因共识模块调度产生的出流量
P2P_SYNC_Out 因同步模块调度产生的出流量
P2P_total_Out 群组总的P2P出流量
群组总流量

为方便业务方对区块链系统的整体带宽消耗有所了解,网络流量统计模块还统计了每个群组的总出流量和入流量,模块关键字是Total,日志示例如下:

info|2020-04-24 12:58:41.173052|Total|,g=1,Total_In=74524,Total_Out=115434 

关键字说明

日志关键字 说明
g 群组ID
Total_In 群组总的入流量
Total_Out 群组总的出流量
Gas消耗统计日志

交易的Gas消耗衡量了交易消耗的物理资源,为了方便业务方监测交易资源消耗情况,FISCO BCOS v2.4.0统计了每个区块、每笔交易的Gas消耗情况。

每个区块Gas消耗信息

区块Gas消耗的统计日志记录模块关键字是BlockGasUsed,统计信息包括:区块高度、区块所属群组、区块内交易数目、区块gas消耗等,日志示例如下:

info|2020-04-24 12:46:31.974147|BlockGasUsed|,g=2,txNum=193,gasUsed=3860579,blockNumber=1419,sealerIdx=2,blockHash=b10bdcc5da9c9cd5399ca5821bed9ae6f3fecbe1ddf8ec723b44e6fa30c4bd05,nodeID=0e23d6e237cfc5041d1754fa6682d71bef842b29ddfe3412b284aeac4b8b4794a51df409b667829750c2b4e91bdf95f51742e001e44dc9f97123a5002e49b8ca

关键字说明

日志关键字 说明
g 群组ID
txNum 区块内交易数目
gasUsed 区块内所有交易Gas消耗的总和
blockNumber 区块高度
sealerIdx 产生该区块的共识节点索引
blockHash 区块哈希
nodeID 节点Node ID
每笔交易Gas消耗信息

每笔交易Gas消耗的统计日志记录模块关键字是TxsGasUsed,主要统计了每笔交易的Gas消耗,日志示例如下:

info|2020-04-24 12:46:31.976080|TxsGasUsed|,g=2,txHash=a81ae1f60289cf7e8f6987b20c68ba9580a1c34d9252c5b4b9c097113309b9d7,gasUsed=20003

关键字说明

日志关键字 说明
g 群组ID
txHash 交易哈希
gasUsed 交易消耗的Gas
配置选项

注解

统计日志配置选项位于 config.ini 中,详细可参考 这里

流量控制

为实现区块链系统柔性服务,并防止多群组架构下,多个群组运行在相同进程中,某些群组占用资源过多干扰到其他群组,FISCO BCOS v2.5.0引入了流量控制功能。

模块架构

下图是流量控制的模块图,主要包括SDK请求速率限制以及网络流量限制,前者限制SDK到节点的请求速率,后者通过限制区块同步和AMOP请求流量,限制节点出带宽流量,防止区块同步、AMOP请求消息包过多影响共识模块性能。

_images/flow_control.png

SDK请求速率限制

SDK请求速率限制包括节点级别的请求速率限制和群组级别的请求速率限制:

  • 节点级别请求速率限制:限制SDK客户端到节点的总的请求速率,当SDK到节点的请求速率超过指定阈值后,节点会拒绝SDK的请求,达到QoS目标的同时,防止过多的SDK请求导致节点异常;
  • 群组级别请求速率限制:限制SDK客户端到群组的请求速率,当SDK发向指定群组的请求速率超过阈值后,群组会拒绝SDK请求。

注解

节点和群组都开启请求速率限制时:
  • 区块链节点收到SDK发送的请求包后,首先调用节点级别的请求速率限制模块判断是否应该接收该请求;
  • 被接收的请求继续进入到群组级别的请求速率限制模块,通过群组级别请求速率限制模块检查的请求最终才会被转发到相应群组并被处理。
节点网络流量控制

类似于SDK请求速率限制,网络流量限制也包括节点级别的流量控制和群组级别的流量控制:

  • 节点级别的流量控制:限制节点的平均出带宽,当节点平均出带宽超过阈值后,节点收到区块同步请求后会暂缓发送区块,也会拒绝收到的AMOP请求,避免区块同步、AMOP消息包发送对节点共识的影响;
  • 群组级别的流量控制:限制每个群组的平均出带宽,当群组平均出带宽流量超过阈值后,该群组会暂停区块发送和AMOP请求包转发逻辑,优先将网络流量提供给共识模块使用。

注解

节点和群组都开启流量控制功能时:
  • 当节点收到客户端的AMOP请求时,调用节点级流量控制模块判断是否可以接收该AMOP请求;
  • 当某个群组收到其他节点对应群组的区块请求后,群组向其回复区块前,(1) 调用节点级流量控制模块,判断节点平均出带宽是否超过设置的阈值;(2) 调用群组级流量控制模块,判断群组的出带宽是否超过设置的阈值,当且仅当节点级和群组级平均出带宽均没有超过设置阈值时,该群组才会回复区块
配置选项

注解

  • 节点级别的流量控制配置选项位于 config.ini ,具体可参考 这里
  • 群组级别的流量控制选项位于 group.{group_id}.ini ,具体可参考 这里

JSON-RPC API

下列接口的示例中采用curl命令,curl是一个利用url语法在命令行下运行的数据传输工具,通过curl命令发送http post请求,可以访问FISCO BCOS的JSON RPC接口。curl命令的url地址设置为节点配置文件[rpc]部分的[jsonrpc_listen_ip](若节点小于v2.3.0版本,查看配置项listen_ip)和[jsonrpc listen port]端口。为了格式化json,使用jq工具进行格式化显示。错误码参考RPC设计文档。交易回执状态列表参考这里

getClientVersion

返回节点的版本信息

参数

返回值

  • object - 版本信息,字段如下:
    • Build Time: string - 编译时间
    • Build Type: string - 编译机器环境
    • Chain Id: string - 链ID
    • FISCO-BCOS Version: string - 节点版本
    • Git Branch: string - 版本分支
    • Git Commit Hash: string - 版本最新commit哈希
    • Supported Version: string - 节点支持的版本
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getClientVersion","params":[],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 83,
  "jsonrpc": "2.0",
  "result": {
    "Build Time": "20190106 20:49:10",
    "Build Type": "Linux/g++/RelWithDebInfo",
    "FISCO-BCOS Version": "2.0.0",
    "Git Branch": "master",
    "Git Commit Hash": "693a709ddab39965d9c39da0104836cfb4a72054"
  }
}

getBlockNumber

返回节点指定群组内的最新区块高度

参数

  • groupID: unsigned int - 群组ID

返回值

  • string - 最新区块高度(0x开头的十六进制字符串)
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockNumber","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x1"
}

getPbftView

返回节点所在指定群组内的最新PBFT视图

参数

  • groupID: unsigned int - 群组ID

返回值

  • string - 最新的PBFT视图
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getPbftView","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x1a0"
}

注: FISCO BCOS支持PBFT共识Raft共识,当访问的区块链采用Raft共识时,该接口返回FISCO BCOS自定义错误响应如下:

{
  "error": {
    "code": 7,
    "data": null,
    "message": "Only pbft consensus supports the view property"
  },
  "id": 1,
  "jsonrpc": "2.0"
}

getSealerList

返回指定群组内的共识节点列表

参数

  • groupID: unsigned int - 群组ID

返回值

  • array - 共识节点ID列表
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getSealerList","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": [
        "037c255c06161711b6234b8c0960a6979ef039374ccc8b723afea2107cba3432dbbc837a714b7da20111f74d5a24e91925c773a72158fa066f586055379a1772",
        "0c0bbd25152d40969d3d3cee3431fa28287e07cff2330df3258782d3008b876d146ddab97eab42796495bfbb281591febc2a0069dcc7dfe88c8831801c5b5801",
        "622af37b2bd29c60ae8f15d467b67c0a7fe5eb3e5c63fdc27a0ee8066707a25afa3aa0eb5a3b802d3a8e5e26de9d5af33806664554241a3de9385d3b448bcd73"
    ]
}

getObserverList

返回指定群组内的观察节点列表

参数

  • groupID: unsigned int - 群组ID

返回值

  • array - 观察节点ID列表
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getObserverList","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": [
        "10b3a2d4b775ec7f3c2c9e8dc97fa52beb8caab9c34d026db9b95a72ac1d1c1ad551c67c2b7fdc34177857eada75836e69016d1f356c676a6e8b15c45fc9bc34"
    ]
}

getConsensusStatus

返回指定群组内的共识状态信息

参数

  • groupID: unsigned int - 群组ID

返回值

  • object - 共识状态信息。
  • 当共识机制为PBFT时(PBFT详细设计参考PBFT设计文档),字段如下:
    • accountType: unsigned int - 节点类型,0表示观察节点,1表示共识节点
    • allowFutureBlocks: bool - 允许未来块标志,当前为true
    • cfgErr: bool - 表明节点是否出错,true表示节点已经异常
    • connectedNodes: unsigned int - 连接的节点数
    • consensusedBlockNumber: unsigned int - 当前正在共识的区块高度
    • currentView: unsigned int - 当前视图
    • groupId: unsigned int - 群组ID
    • highestblockHash: string - 最新块哈希
    • highestblockNumber: unsigned int - 最新区块高度
    • leaderFailed: bool - leader失败标志,若为false,节点可能正在处理超时
    • max_faulty_leader: unsigned int - 最大容错节点数
    • nodeNum: unsigned int - 节点的数
    • node_index: unsigned int - 共识节点索引
    • nodeId: string - 节点的ID
    • omitEmptyBlock: bool - 忽略空块标志位,为true
    • protocolId: unsigned int - 协议ID号
    • sealer.index: string - 指定索引index对应的共识节点nodeID
    • toView: unsigned int - 目前到达的view值
    • 与本节点相连的所有共识节点nodeID和视图view信息
  • 当共识机制为Raft时(Raft详细设计参考Raft设计文档),字段如下:
    • accountType: unsigned int - 账户类型
    • allowFutureBlocks: bool - 允许未来块标志
    • cfgErr: bool - 配置错误标志
    • consensusedBlockNumber: unsigned int - 下一个共识的最新块高
    • groupId: unsigned int - 群组ID
    • highestblockHash: string - 最新块哈希
    • highestblockNumber: unsigned int - 最新区块高度
    • leaderId: string - leader的nodeId
    • leaderIdx: unsigned int - leader的序号
    • max_faulty_leader: unsigned int - 最大容错节点数
    • sealer.index: string - 节点序号为index的nodeId
    • node index: unsigned int - 节点的index
    • nodeId: string - 节点的ID
    • nodeNum: unsigned int - 节点的数
    • omitEmptyBlock: bool - 忽略空块标志位
    • protocolId: unsigned int - 协议ID号
  • 示例
// Request PBFT
curl -X POST --data '{"jsonrpc":"2.0","method":"getConsensusStatus","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "accountType": 1,
      "allowFutureBlocks": true,
      "cfgErr": false,
      "connectedNodes": 3,
      "consensusedBlockNumber": 38207,
      "currentView": 54477,
      "groupId": 1,
      "highestblockHash": "0x19a16e8833e671aa11431de589c866a6442ca6c8548ba40a44f50889cd785069",
      "highestblockNumber": 38206,
      "leaderFailed": false,
      "max_faulty_leader": 1,
      "nodeId": "f72648fe165da17a889bece08ca0e57862cb979c4e3661d6a77bcc2de85cb766af5d299fec8a4337eedd142dca026abc2def632f6e456f80230902f93e2bea13",
      "nodeNum": 4,
      "node_index": 3,
      "omitEmptyBlock": true,
      "protocolId": 65544,
      "sealer.0": "6a99f357ecf8a001e03b68aba66f68398ee08f3ce0f0147e777ec77995369aac470b8c9f0f85f91ebb58a98475764b7ca1be8e37637dd6cb80b3355749636a3d",
      "sealer.1": "8a453f1328c80b908b2d02ba25adca6341b16b16846d84f903c4f4912728c6aae1050ce4f24cd9c13e010ce922d3393b846f6f5c42f6af59c65a814de733afe4",
      "sealer.2": "ed483837e73ee1b56073b178f5ac0896fa328fc0ed418ae3e268d9e9109721421ec48d68f28d6525642868b40dd26555c9148dbb8f4334ca071115925132889c",
      "sealer.3": "f72648fe165da17a889bece08ca0e57862cb979c4e3661d6a77bcc2de85cb766af5d299fec8a4337eedd142dca026abc2def632f6e456f80230902f93e2bea13",
      "toView": 54477
    },
    [
      {
        "nodeId": "6a99f357ecf8a001e03b68aba66f68398ee08f3ce0f0147e777ec77995369aac470b8c9f0f85f91ebb58a98475764b7ca1be8e37637dd6cb80b3355749636a3d",
        "view": 54474
      },
      {
        "nodeId": "8a453f1328c80b908b2d02ba25adca6341b16b16846d84f903c4f4912728c6aae1050ce4f24cd9c13e010ce922d3393b846f6f5c42f6af59c65a814de733afe4",
        "view": 54475
      },
      {
        "nodeId": "ed483837e73ee1b56073b178f5ac0896fa328fc0ed418ae3e268d9e9109721421ec48d68f28d6525642868b40dd26555c9148dbb8f4334ca071115925132889c",
        "view": 54476
      },
      {
        "nodeId": "f72648fe165da17a889bece08ca0e57862cb979c4e3661d6a77bcc2de85cb766af5d299fec8a4337eedd142dca026abc2def632f6e456f80230902f93e2bea13",
        "view": 54477
      }
    ]
  ]
}


// Request Raft
curl -X POST --data '{"jsonrpc":"2.0","method":"getConsensusStatus","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": [
    {
      "accountType": 1,
      "allowFutureBlocks": true,
      "cfgErr": false,
      "consensusedBlockNumber": 1,
      "groupId": 1,
      "highestblockHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
      "highestblockNumber": 0,
      "leaderId": "d5b3a9782c6aca271c9642aea391415d8b258e3a6d92082e59cc5b813ca123745440792ae0b29f4962df568f8ad58b75fc7cea495684988e26803c9c5198f3f8",
      "leaderIdx": 3,
      "max_faulty_leader": 1,
      "sealer.0": "29c34347a190c1ec0c4507c6eed6a5bcd4d7a8f9f54ef26da616e81185c0af11a8cea4eacb74cf6f61820292b24bc5d9e426af24beda06fbd71c217960c0dff0",
      "sealer.1": "41285429582cbfe6eed501806391d2825894b3696f801e945176c7eb2379a1ecf03b36b027d72f480e89d15bacd43462d87efd09fb0549e0897f850f9eca82ba",
      "sealer.2": "87774114e4a496c68f2482b30d221fa2f7b5278876da72f3d0a75695b81e2591c1939fc0d3fadb15cc359c997bafc9ea6fc37345346acaf40b6042b5831c97e1",
      "sealer.3": "d5b3a9782c6aca271c9642aea391415d8b258e3a6d92082e59cc5b813ca123745440792ae0b29f4962df568f8ad58b75fc7cea495684988e26803c9c5198f3f8",
      "node index": 1,
      "nodeId": "41285429582cbfe6eed501806391d2825894b3696f801e945176c7eb2379a1ecf03b36b027d72f480e89d15bacd43462d87efd09fb0549e0897f850f9eca82ba",
      "nodeNum": 4,
      "omitEmptyBlock": true,
      "protocolId": 267
    }
  ]
}

getSyncStatus

返回指定群组内的同步状态信息

参数

  • groupID: unsigned int - 群组ID

返回值

  • object - 同步状态信息,字段如下:
    • blockNumber: unsigned int - 最新区块高度
    • genesisHash: string - 创世块哈希
    • isSyncing: bool - 正在同步标志
    • knownHighestNumber: unsigned int - 此节点已知的当前区块链最高块高
    • knownLatestHash: string - 此节点已知的当前区块链最高块哈希
    • latestHash: string - 最新区块哈希
    • nodeId: string - 节点的ID
    • protocolId: unsigned int - 协议ID号
    • txPoolSize: string - 交易池中交易的数量
    • peers: array - 已连接的指定群组内p2p节点,节点信息字段如下:
      • blockNumber: unsigned int - 最新区块高度
      • genesisHash: string - 创始区块哈希
      • latestHash: string - 最新块哈希
      • nodeId: string - 节点的ID
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getSyncStatus","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "blockNumber": 0,
    "genesisHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
    "isSyncing": false,
    "knownHighestNumber":0,
    "knownLatestHash":"0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
    "latestHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
    "nodeId": "41285429582cbfe6eed501806391d2825894b3696f801e945176c7eb2379a1ecf03b36b027d72f480e89d15bacd43462d87efd09fb0549e0897f850f9eca82ba",
    "peers": [
      {
        "blockNumber": 0,
        "genesisHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
        "latestHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
        "nodeId": "29c34347a190c1ec0c4507c6eed6a5bcd4d7a8f9f54ef26da616e81185c0af11a8cea4eacb74cf6f61820292b24bc5d9e426af24beda06fbd71c217960c0dff0"
      },
      {
        "blockNumber": 0,
        "genesisHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
        "latestHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
        "nodeId": "87774114e4a496c68f2482b30d221fa2f7b5278876da72f3d0a75695b81e2591c1939fc0d3fadb15cc359c997bafc9ea6fc37345346acaf40b6042b5831c97e1"
      },
      {
        "blockNumber": 0,
        "genesisHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
        "latestHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
        "nodeId": "d5b3a9782c6aca271c9642aea391415d8b258e3a6d92082e59cc5b813ca123745440792ae0b29f4962df568f8ad58b75fc7cea495684988e26803c9c5198f3f8"
      }
    ],
    "protocolId": 265,
    "txPoolSize": "0"
  }
}

getPeers

返回已连接的p2p节点信息

参数

  • groupID: unsigned int - 群组ID

返回值

  • array - 已连接的p2p节点信息,字段如下:
    • IPAndPort: string - 节点连接的ip和端口
    • nodeId: string - 节点的ID
    • Topic: array - 节点关注的topic信息
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": [
        {
            "IPAndPort": "127.0.0.1:30308",
            "nodeId": "0701cc9f05716690437b78db5b7c9c97c4f8f6dd05794ba4648b42b9267ae07cfcd589447ac36c491e7604242149601d67c415504a838524939ef2230d36ffb8",
            "Topic": [ ]
        },
        {
            "IPAndPort": "127.0.0.1:58348",
            "nodeId": "353ab5990997956f21b75ff5d2f11ab2c6971391c73585963e96fe2769891c4bc5d8b7c3d0d04f50ad6e04c4445c09e09c38139b1c0a5937a5778998732e34da",
            "Topic": [ ]
        },
        {
            "IPAndPort": "127.0.0.1:30300",
            "nodeId": "73aebaea2baa9640df416d0e879d6e0a6859a221dad7c2d34d345d5dc1fe9c4cda0ab79a7a3f921dfc9bdea4a49bb37bdb0910c338dadab2d8b8e001186d33bd",
            "Topic": [ ]
        }
    ]
}

getGroupPeers

返回指定群组内的共识节点和观察节点列表

参数

  • groupID: unsigned int - 群组ID

返回值

  • array - 共识节点和观察节点的ID列表
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getGroupPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": [
        "0c0bbd25152d40969d3d3cee3431fa28287e07cff2330df3258782d3008b876d146ddab97eab42796495bfbb281591febc2a0069dcc7dfe88c8831801c5b5801",
        "037c255c06161711b6234b8c0960a6979ef039374ccc8b723afea2107cba3432dbbc837a714b7da20111f74d5a24e91925c773a72158fa066f586055379a1772",
        "622af37b2bd29c60ae8f15d467b67c0a7fe5eb3e5c63fdc27a0ee8066707a25afa3aa0eb5a3b802d3a8e5e26de9d5af33806664554241a3de9385d3b448bcd73",
        "10b3a2d4b775ec7f3c2c9e8dc97fa52beb8caab9c34d026db9b95a72ac1d1c1ad551c67c2b7fdc34177857eada75836e69016d1f356c676a6e8b15c45fc9bc34"
    ]
}

getNodeIDList

返回节点本身和已连接的p2p节点列表

参数

  • groupID: unsigned int - 群组ID

返回值

  • array - 节点本身和已连接p2p节点的ID列表
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getNodeIDList","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": [
        "0c0bbd25152d40969d3d3cee3431fa28287e07cff2330df3258782d3008b876d146ddab97eab42796495bfbb281591febc2a0069dcc7dfe88c8831801c5b5801",
        "037c255c06161711b6234b8c0960a6979ef039374ccc8b723afea2107cba3432dbbc837a714b7da20111f74d5a24e91925c773a72158fa066f586055379a1772",
        "622af37b2bd29c60ae8f15d467b67c0a7fe5eb3e5c63fdc27a0ee8066707a25afa3aa0eb5a3b802d3a8e5e26de9d5af33806664554241a3de9385d3b448bcd73",
        "10b3a2d4b775ec7f3c2c9e8dc97fa52beb8caab9c34d026db9b95a72ac1d1c1ad551c67c2b7fdc34177857eada75836e69016d1f356c676a6e8b15c45fc9bc34"
    ]
}

getGroupList

返回节点所属群组的群组ID列表

参数

返回值

  • array - 节点所属群组的群组ID列表
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getGroupList","params":[],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": [1]
}

getBlockByHash

返回根据区块哈希查询的区块信息

参数

  • groupID: unsigned int - 群组ID
  • blockHash: string - 区块哈希
  • includeTransactions: bool - 包含交易标志(true显示交易详细信息,false仅显示交易的hash)

返回值

  • object - 区块信息,字段如下:
    • extraData: array - 附加数据
    • gasLimit: string - 区块中允许的gas最大值
    • gasUsed: string - 区块中所有交易消耗的gas
    • hash: string - 区块哈希
    • logsBloom: string - log的布隆过滤器值
    • number: string - 区块高度
    • parentHash: string - 父区块哈希
    • sealer: string - 共识节点序号
    • sealerList: array - 共识节点列表
    • stateRoot: string - 状态根哈希
    • timestamp: string - 时间戳
    • transactions: array - 交易列表,当includeTransactionsfalse时,显示交易的哈希。当includeTransactionstrue时,显示交易详细信息(详细字段见getTransactionByHash
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockByHash","params":[1,"0x910ea44e2a83618c7cc98456678c9984d94977625e224939b24b3c904794b5ec",true],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "extraData": [],
    "gasLimit": "0x0",
    "gasUsed": "0x0",
    "hash": "0x910ea44e2a83618c7cc98456678c9984d94977625e224939b24b3c904794b5ec",
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number": "0x1",
    "parentHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
    "sealer": "0x3",
    "sealerList":[
    "0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278",
    "2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108",
    "cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
    "ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec"
    ],
    "stateRoot": "0xfb7ca5a7a271c8ffb51bc689b78d0aeded23497c9c22e67dff8b1c7b4ec88a2a",
    "timestamp": "0x1687e801d99",
    "transactions": [
      {
        "blockHash": "0x910ea44e2a83618c7cc98456678c9984d94977625e224939b24b3c904794b5ec",
        "blockNumber": "0x1",
        "from": "0xadf06b974703a1c25c621ce53676826198d4b046",
        "gas": "0x1c9c380",
        "gasPrice": "0x1",
        "hash": "0x022dcb1ad2d940ce7b2131750f7458eb8ace879d129ee5b650b84467cb2184d7",
        "input": "0x608060405234801561001057600080fd5b5060016000800160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506402540be40060006001018190555060028060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060006002600101819055506103bf806100c26000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806366c99139146100515780636d4ce63c1461007e575b600080fd5b34801561005d57600080fd5b5061007c600480360381019080803590602001909291905050506100a9565b005b34801561008a57600080fd5b506100936102e1565b6040518082815260200191505060405180910390f35b8060006001015410806100c757506002600101548160026001015401105b156100d1576102de565b8060006001015403600060010181905550806002600101600082825401925050819055507fc77b710b83d1dc3f3fafeccd08a6c469beb873b2f0975b50d1698e46b3ee5b4c816040518082815260200191505060405180910390a160046080604051908101604052806040805190810160405280600881526020017f323031373034313300000000000000000000000000000000000000000000000081525081526020016000800160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001838152509080600181540180825580915050906001820390600052602060002090600402016000909192909190915060008201518160000190805190602001906102419291906102ee565b5060208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550606082015181600301555050505b50565b6000600260010154905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061032f57805160ff191683800117855561035d565b8280016001018555821561035d579182015b8281111561035c578251825591602001919060010190610341565b5b50905061036a919061036e565b5090565b61039091905b8082111561038c576000816000905550600101610374565b5090565b905600a165627a7a72305820fb983c66bee66788f407721b23b10a8aae3dc9ef8f1b09e08ec6a6c0b0ec70100029",
        "nonce": "0x1a9d06264238ea69c1bca2a74cfced979d6b6a66ce8ad6f5a30e8017b5a98d8",
        "to": null,
        "transactionIndex": "0x0",
        "value": "0x0"
      }
    ],
    "transactionsRoot": "0x07506c27626365c4f0db788619a96df1e6f8f62c583f158192700e08c10fec6a"
  }
}

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockByHash","params":[1,"0x910ea44e2a83618c7cc98456678c9984d94977625e224939b24b3c904794b5ec",false],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "extraData": [],
    "gasLimit": "0x0",
    "gasUsed": "0x0",
    "hash": "0x910ea44e2a83618c7cc98456678c9984d94977625e224939b24b3c904794b5ec",
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number": "0x1",
    "parentHash": "0x4765a126a9de8d876b87f01119208be507ec28495bef09c1e30a8ab240cf00f2",
    "sealer": "0x3",
    "sealerList":[
    "0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278",
    "2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108",
    "cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
    "ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec"
    ],
    "stateRoot": "0xfb7ca5a7a271c8ffb51bc689b78d0aeded23497c9c22e67dff8b1c7b4ec88a2a",
    "timestamp": "0x1687e801d99",
    "transactions": [
      "0x022dcb1ad2d940ce7b2131750f7458eb8ace879d129ee5b650b84467cb2184d7"
    ],
    "transactionsRoot": "0x07506c27626365c4f0db788619a96df1e6f8f62c583f158192700e08c10fec6a"
  }
}

getBlockByNumber

返回根据区块高度查询的区块信息

参数

  • groupID: unsigned int - 群组ID
  • blockNumber: string - 区块高度(十进制字符串或0x开头的十六进制字符串)
  • includeTransactions: bool - 包含交易标志(true显示交易详细信息,false仅显示交易的hash)

返回值

getBlockByHash

  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockByNumber","params":[1,"0x0",true],"id":1}' http://127.0.0.1:8545 |jq

Result见getBlockByHash

getBlockHeaderByHash

根据区块哈希获取区块头信息

参数

  • groupID: unsigned int - 群组ID
  • blockHash: string - 区块哈希
  • includeSignatures: bool - 包含签名列表标志(true显示签名列表)

返回值

//Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockHeaderByHash","params":[1,"0x99576e7567d258bd6426ddaf953ec0c953778b2f09a078423103c6555aa4362d",true],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "dbHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData": [],
    "gasLimit": "0x0",
    "gasUsed": "0x0",
    "hash": "0x99576e7567d258bd6426ddaf953ec0c953778b2f09a078423103c6555aa4362d",
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "number": 1,
    "parentHash": "0x4f6394763c33c1709e5a72b202ad4d7a3b8152de3dc698cef6f675ecdaf20a3b",
    "receiptsRoot": "0x69a04fa6073e4fc0947bac7ee6990e788d1e2c5ec0fe6c2436d0892e7f3c09d2",
    "sealer": "0x2",
    "sealerList": [
      "11e1be251ca08bb44f36fdeedfaeca40894ff80dfd80084607a75509edeaf2a9c6fee914f1e9efda571611cf4575a1577957edfd2baa9386bd63eb034868625f",
      "78a313b426c3de3267d72b53c044fa9fe70c2a27a00af7fea4a549a7d65210ed90512fc92b6194c14766366d434235c794289d66deff0796f15228e0e14a9191",
      "95b7ff064f91de76598f90bc059bec1834f0d9eeb0d05e1086d49af1f9c2f321062d011ee8b0df7644bd54c4f9ca3d8515a3129bbb9d0df8287c9fa69552887e",
      "b8acb51b9fe84f88d670646be36f31c52e67544ce56faf3dc8ea4cf1b0ebff0864c6b218fdcd9cf9891ebd414a995847911bd26a770f429300085f37e1131f36"
    ],
    "signatureList": [
      {
        "index": "0x2",
        "signature": "0xae098aabc63a53b8dcb57da9a87f13aebf231bfe1704da88f125cee6b4b30ee0609d0720a97bed1900b96bc3e7a63584158340b5b7f802945241f61731f9358900"
      },
      {
        "index": "0x0",
        "signature": "0x411cb93f816549eba82c3bf8c03fa637036dcdee65667b541d0da06a6eaea80d16e6ca52bf1b08f77b59a834bffbc124c492ea7a1601d0c4fb257d97dc97cea600"
      },
      {
        "index": "0x3",
        "signature": "0xb5b41e49c0b2bf758322ecb5c86dc3a3a0f9b98891b5bbf50c8613a241f05f595ce40d0bb212b6faa32e98546754835b057b9be0b29b9d0c8ae8b38f7487b8d001"
      }
    ],
    "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp": "0x173ad8703d6",
    "transactionsRoot": "0xb563f70188512a085b5607cac0c35480336a566de736c83410a062c9acc785ad"
  }
}

getBlockHeaderByNumber

返回根据区块高度查询的区块头

参数

  • groupID: unsigned int - 群组ID
  • blockNumber: string - 区块高度(十进制字符串或0x开头的十六进制字符串)
  • includeSignatures: bool - 包含签名列表标志(true显示签名列表)

返回值

  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockHeaderByNumber","params":[1,"0x0",true],"id":1}' http://127.0.0.1:8545 |jq

Result见getBlockHeaderByHash

getBlockHashByNumber

返回根据区块高度查询的区块哈希

参数

  • groupID: unsigned int - 群组ID
  • blockNumber: string - 区块高度(十进制字符串或0x开头的十六进制字符串)

返回值

  • blockHash: string - 区块哈希
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockHashByNumber","params":[1,"0x1"],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x10bfdc1e97901ed22cc18a126d3ebb8125717c2438f61d84602f997959c631fa"
}

getTransactionByHash

返回根据交易哈希查询的交易信息

参数

  • groupID: unsigned int - 群组ID
  • transactionHash: string - 交易哈希

返回值

  • object: - 交易信息,其字段如下:
    • blockHash: string - 包含该交易的区块哈希
    • blockNumber: string - 包含该交易的区块高度
    • from: string - 发送者的地址
    • gas: string - 发送者提供的gas
    • gasPrice: string - 发送者提供的gas的价格
    • hash: string - 交易哈希
    • input: string - 交易的输入
    • nonce: string - 交易的nonce值
    • to: string - 接收者的地址,创建合约交易的该值为0x0000000000000000000000000000000000000000
    • transactionIndex: string - 交易的序号
    • value: string - 转移的值
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionByHash","params":[1,"0x7536cf1286b5ce6c110cd4fea5c891467884240c9af366d678eb4191e1c31c6f"],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "blockHash": "0x10bfdc1e97901ed22cc18a126d3ebb8125717c2438f61d84602f997959c631fa",
        "blockNumber": "0x1",
        "from": "0x6bc952a2e4db9c0c86a368d83e9df0c6ab481102",
        "gas": "0x9184e729fff",
        "gasPrice": "0x174876e7ff",
        "hash": "0x7536cf1286b5ce6c110cd4fea5c891467884240c9af366d678eb4191e1c31c6f",
        "input": "0x48f85bce000000000000000000000000000000000000000000000000000000000000001bf5bd8a9e7ba8b936ea704292ff4aaa5797bf671fdc8526dcd159f23c1f5a05f44e9fa862834dc7cb4541558f2b4961dc39eaaf0af7f7395028658d0e01b86a37",
        "nonce": "0x65f0d06e39dc3c08e32ac10a5070858962bc6c0f5760baca823f2d5582d03f",
        "to": "0xd6f1a71052366dbae2f7ab2d5d5845e77965cf0d",
        "transactionIndex": "0x0",
        "value": "0x0"
    }
}

getTransactionByBlockHashAndIndex

返回根据区块哈希和交易序号查询的交易信息

参数

  • groupID: unsigned int - 群组ID
  • blockHash: string - 区块哈希
  • transactionIndex: string - 交易序号

返回值

getTransactionByHash

  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionByBlockHashAndIndex","params":[1,"0x10bfdc1e97901ed22cc18a126d3ebb8125717c2438f61d84602f997959c631fa","0x0"],"id":1}' http://127.0.0.1:8545 |jq

Result见getTransactionByHash

getTransactionByBlockNumberAndIndex

返回根据区块高度和交易序号查询的交易信息

参数

  • groupID: unsigned int - 群组ID
  • blockNumber: string - 区块高度(十进制字符串或0x开头的十六进制字符串)
  • transactionIndex: string - 交易序号

返回值

getTransactionByHash

  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionByBlockNumberAndIndex","params":[1,"0x1","0x0"],"id":1}' http://127.0.0.1:8545 |jq

Result见getTransactionByHash

getTransactionReceipt

返回根据交易哈希查询的交易回执信息

参数

  • groupID: unsigned int - 群组ID
  • transactionHash: string - 交易哈希

返回值

  • object: - 交易信息,其字段如下:
    • blockHash: string - 包含该交易的区块哈希
    • blockNumber: string - 包含该交易的区块高度
    • contractAddress: string - 合约地址,如果创建合约交易,则为合约部署地址,如果是调用合约,则为”0x0000000000000000000000000000000000000000”
    • from: string - 发送者的地址
    • gasUsed: string - 交易消耗的gas
    • input: string - 交易的输入
    • logs: array - 交易产生的log
    • logsBloom: string - log的布隆过滤器值
    • output: string - 交易的输出
    • root: string - 状态根(state root)
    • status: string - 交易的状态值,参考:交易回执状态
    • to: string - 接收者的地址,创建合约交易的该值为null
    • transactionHash: string - 交易哈希
    • transactionIndex: string - 交易序号
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionReceipt","params":[1,"0x708b5781b62166bd86e543217be6cd954fd815fd192b9a124ee9327580df8f3f"],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "blockHash": "0x977efec48c248ea4be87016446b40d7785d7b71b7d4e3aa0b103b9cf0f5fe19e",
        "blockNumber": "0xa",
        "contractAddress": "0x0000000000000000000000000000000000000000",
        "from": "0xcdcce60801c0a2e6bb534322c32ae528b9dec8d2",
        "gasUsed": "0x1fb8d",
        "input": "0xb602109a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000203078313030303030303030303030303030303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000832303139303733300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002616100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026262000000000000000000000000000000000000000000000000000000000000",
        "logs": [ ],
        "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "output": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "root":"0x38723a2e5e8a17aa7950dc008209944e898f69a7bd10a23c839d341e935fd5ca",
        "status": "0x0",
        "to": "0x15538acd403ac1b2ff09083c70d04856b8c0bdfd",
        "transactionHash": "0x708b5781b62166bd86e543217be6cd954fd815fd192b9a124ee9327580df8f3f",
        "transactionIndex": "0x0"
    }
}

getPendingTransactions

返回待打包的交易信息

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: - 带打包的交易信息,其字段如下:
    • from: string - 发送者的地址
    • gas: string - 发送者提供的gas
    • gasPrice: string - 发送者提供的gas的价格
    • hash: string - 交易哈希
    • input: string - 交易的输入
    • nonce: string - 交易的nonce值
    • to: string - 接收者的地址,创建合约交易的该值为null
    • value: string - 转移的值
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getPendingTransactions","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        [
            {
                "from": "0x6bc952a2e4db9c0c86a368d83e9df0c6ab481102",
                "gas": "0x9184e729fff",
                "gasPrice": "0x174876e7ff",
                "hash": "0x7536cf1286b5ce6c110cd4fea5c891467884240c9af366d678eb4191e1c31c6f",
                "input": "0x48f85bce000000000000000000000000000000000000000000000000000000000000001bf5bd8a9e7ba8b936ea704292ff4aaa5797bf671fdc8526dcd159f23c1f5a05f44e9fa862834dc7cb4541558f2b4961dc39eaaf0af7f7395028658d0e01b86a37",
                "nonce": "0x65f0d06e39dc3c08e32ac10a5070858962bc6c0f5760baca823f2d5582d03f",
                "to": "0xd6f1a71052366dbae2f7ab2d5d5845e77965cf0d",
                "value": "0x0"
            }
        ]
    }
}

getPendingTxSize

返回待打包的交易数量

参数

  • groupID: unsigned int - 群组ID

返回值

  • string: - 待打包的交易数量
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getPendingTxSize","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0x1"
}

getCode

返回根据合约地址查询的合约数据

参数

  • groupID: unsigned int - 群组ID
  • address: string - 合约地址

返回值

  • string: - 合约数据
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getCode","params":[1,"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x60606040523415600b57fe5b5b60928061001a6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636d4ce63c14603a575bfe5b3415604157fe5b6047605d565b6040518082815260200191505060405180910390f35b60004290505b905600a165627a7a723058203d9c292921247163d180a161baa8db840c9da6764cab1d23f1e11a5cff13c7910029"
}

getTotalTransactionCount

返回当前交易总数和区块高度

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: - 当前交易总数和区块高度信息,其字段如下:
    • blockNumber: string - 区块高度
    • failedTxSum: string - 失败的交易总数
    • txSum: string - 交易总数
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getTotalTransactionCount","params":[1],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
      "blockNumber": "0x1",
      "failedTxSum": "0x0",
      "txSum": "0x1"
    }
}

getSystemConfigByKey

返回根据key值查询的value值

参数

  • groupID: unsigned int - 群组ID
  • key: string - 支持tx_count_limit和tx_gas_limit

返回值

  • string - value值
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getSystemConfigByKey","params":[1,"tx_count_limit"],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "1000"
}

call

执行一个可以立即获得结果的请求,无需区块链共识

参数

  • groupID: unsigned int - 群组ID
  • object: - 请求信息,其字段如下:
    • from: string - 发送者的地址
    • to: string - 接收者的地址
    • value: string - (可选)转移的值
    • data: string - (可选)编码的参数,编码规范参考Ethereum Contract ABI

返回值

  • object: - 执行的结果
    • currentBlockNumber: string - 当前区块高度
    • output: string - 请求结果
    • status: string - 请求状态(与交易状态码一致)
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"call","params":[1,{"from":"0x6bc952a2e4db9c0c86a368d83e9df0c6ab481102","to":"0xd6f1a71052366dbae2f7ab2d5d5845e77965cf0d","value":"0x1","data":"0x3"}],"id":1}' http://127.0.0.1:8545 |jq

// Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "currentBlockNumber": "0xb",
        "output": "0x",
        "status": "0x0"
    }
}

sendRawTransaction

执行一个签名的交易,需要区块链共识

参数

  • groupID: unsigned int - 群组ID
  • rlp: string - 签名的交易数据

返回值

  • string - 交易哈希
  • 示例
// RC1 Request
curl -X POST --data '{"jsonrpc":"2.0","method":"sendRawTransaction","params":[1,"f8ef9f65f0d06e39dc3c08e32ac10a5070858962bc6c0f5760baca823f2d5582d03f85174876e7ff8609184e729fff82020394d6f1a71052366dbae2f7ab2d5d5845e77965cf0d80b86448f85bce000000000000000000000000000000000000000000000000000000000000001bf5bd8a9e7ba8b936ea704292ff4aaa5797bf671fdc8526dcd159f23c1f5a05f44e9fa862834dc7cb4541558f2b4961dc39eaaf0af7f7395028658d0e01b86a371ca00b2b3fabd8598fefdda4efdb54f626367fc68e1735a8047f0f1c4f840255ca1ea0512500bc29f4cfe18ee1c88683006d73e56c934100b8abf4d2334560e1d2f75e"],"id":1}' http://127.0.0.1:8545 |jq

// RC1 Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x7536cf1286b5ce6c110cd4fea5c891467884240c9af366d678eb4191e1c31c6f"
}

// RC2 Request
curl -X POST --data '{"jsonrpc":"2.0","method":"sendRawTransaction","params":[1,"f8d3a003922ee720bb7445e3a914d8ab8f507d1a647296d563100e49548d83fd98865c8411e1a3008411e1a3008201f894d6c8a04b8826b0a37c6d4aa0eaa8644d8e35b79f80a466c9913900000000000000000000000000000000000000000000000000000000000000040101a466c9913900000000000000000000000000000000000000000000000000000000000000041ba08e0d3fae10412c584c977721aeda88df932b2a019f084feda1e0a42d199ea979a016c387f79eb85078be5db40abe1670b8b480a12c7eab719bedee212b7972f775"],"id":1}' http://127.0.0.1:8545 |jq

// RC2 Result
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x0accad4228274b0d78939f48149767883a6e99c95941baa950156e926f1c96ba"
}

// FISCO BCOS支持国密算法,采用国密算法的区块链请求示例
// RC1 Request
curl -X POST --data '{"jsonrpc":"2.0","method":"sendRawTransaction","params":[1,"f8ef9f65f0d06e39dc3c08e32ac10a5070858962bc6c0f5760baca823f2d5582d03f85174876e7ff8609184e729fff82020394d6f1a71052366dbae2f7ab2d5d5845e77965cf0d80b86448f85bce000000000000000000000000000000000000000000000000000000000000001bf5bd8a9e7ba8b936ea704292ff4aaa5797bf671fdc8526dcd159f23c1f5a05f44e9fa862834dc7cb4541558f2b4961dc39eaaf0af7f7395028658d0e01b86a371ca00b2b3fabd8598fefdda4efdb54f626367fc68e1735a8047f0f1c4f840255ca1ea0512500bc29f4cfe18ee1c88683006d73e56c934100b8abf4d2334560e1d2f75e"],"id":1}' http://127.0.0.1:8545 |jq
// RC2 Request
curl -X POST --data '{"jsonrpc":"2.0","method":"sendRawTransaction","params":[1,"f90114a003eebc46c9c0e3b84799097c5a6ccd6657a9295c11270407707366d0750fcd598411e1a30084b2d05e008201f594bab78cea98af2320ad4ee81bba8a7473e0c8c48d80a48fff0fc400000000000000000000000000000000000000000000000000000000000000040101a48fff0fc40000000000000000000000000000000000000000000000000000000000000004b8408234c544a9f3ce3b401a92cc7175602ce2a1e29b1ec135381c7d2a9e8f78f3edc9c06ee55252857c9a4560cb39e9d70d40f4331cace4d2b3121b967fa7a829f0a00f16d87c5065ad5c3b110ef0b97fe9a67b62443cb8ddde60d4e001a64429dc6ea03d2569e0449e9a900c236541afb9d8a8d5e1a36844439c7076f6e75ed624256f"],"id":1}' http://127.0.0.1:8545 |jq

sendRawTransactionAndGetProof

执行一个签名的交易,交易上链后,推送交易回执、交易Merkle证明、交易回执Merkle证明,Merkle证明可参考这里

注解

  • supported_version < 2.2.0: 调用 sendRawTransactionAndGetProof 接口,交易上链后仅推送交易回执
  • supported_version >= 2.2.0: 调用 sendRawTransactionAndGetProof 接口,交易上链后推送交易回执、交易Merkle证明、交易回执Merkle证明

参数

  • groupID: unsigned int - 群组ID
  • rlp: string - 签名的交易数据

返回值

  • string - 交易哈希
  • 示例:同sendRawTransaction,参考这里

getTransactionByHashWithProof

返回根据交易哈希查询的带证明的交易信息,本接口仅在兼容性版本为2.2.0及以后的版本有效,证明信息是为了验证交易的存在性,交易存在性证明请参考文档交易证明

参数

  • groupID: unsigned int - 群组ID
  • transactionHash: string - 交易哈希

返回值

  • object: - 交易信息,其字段如下:
    • blockHash: string - 包含该交易的区块哈希
    • blockNumber: string - 包含该交易的区块高度
    • from: string - 发送者的地址
    • gas: string - 发送者提供的gas
    • gasPrice: string - 发送者提供的gas的价格
    • hash: string - 交易哈希
    • input: string - 交易的输入
    • nonce: string - 交易的nonce值
    • to: string - 接收者的地址,创建合约交易的该值为0x0000000000000000000000000000000000000000
    • transactionIndex: string - 交易的序号
    • value: string - 转移的值
  • array - 交易证明,字段如下:
    • left: array - 左边的哈希列表
    • right: array - 右边的哈希列表
  • 示例
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionByHashWithProof","params":[1,"0xd2c12e211315ef09dbad53407bc820d062780232841534954f9c23ab11d8ab4c"],"id":1}' http://127.0.0.1:8585 |jq
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "transaction": {
      "blockHash": "0xcd31b05e466bce99460b1ed70d6069fdfbb15e6eef84e9b9e4534358edb3899a",
      "blockNumber": "0x5",
      "from": "0x148947262ec5e21739fe3a931c29e8b84ee34a0f",
      "gas": "0x1c9c380",
      "gasPrice": "0x1c9c380",
      "hash": "0xd2c12e211315ef09dbad53407bc820d062780232841534954f9c23ab11d8ab4c",
      "input": "0x8a42ebe90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000000a3564646636663863653800000000000000000000000000000000000000000000",
      "nonce": "0x208f6fd78d48aad370df51c6fdf866f8ab022de765c2959841ff2e81bfd9af9",
      "to": "0xd6c8a04b8826b0a37c6d4aa0eaa8644d8e35b79f",
      "transactionIndex": "0x32",
      "value": "0x0"
    },
    "txProof": [
      {
        "left": [
          "30f0abfcf4ca152815548620e33d21fd0feaa7c78867791c751e57cb5aa38248c2",
          "31a864156ca9841da8176738bb981d5da9102d9703746039b3e5407fa987e5183e"
        ],
        "right": [
          "33d8078d7e71df3544f8845a9db35aa35b2638e8468a321423152e64b9004367b4",
          "34343a4bce325ec8f6cf48517588830cd15f69b60a05598b78b03c3656d1fbf2f5",
          "35ac231554047ce77c0b31cd1c469f1f39ebe23404fa8ff6cc7819ad83e2c029e7",
          "361f6c588e650323e03afe6460dd89a9c061583e0d62c117ba64729d2c9d79317c",
          "377606f79f3e08b1ba3759eceada7fde3584f01822467855aa6356652f2499c738",
          "386722fe270659232c5572ba54ce23b474c85d8b709e7c08e85230afb1c155fe18",
          "39a9441d668e5e09a5619c365577c8c31365f44a984bde04300d4dbba190330c0b",
          "3a78a8c288120cbe612c24a33cce2731dd3a8fe6927d9ee25cb2350dba08a541f5",
          "3bd9b67256e201b5736f6081f39f83bcb917261144384570bdbb8766586c3bb417",
          "3c3158e5a82a1ac1ed41c4fd78d5be06bf79327f60b094895b886e7aae57cff375",
          "3de9a4d98c5ae658ffe764fbfa81edfdd4774e01b35ccb42beacb67064a5457863",
          "3e525e60c0f7eb935125f1156a692eb455ab4038c6b16390ce30937b0d1b314298",
          "3f1600afe67dec2d21582b8c7b76a15e569371d736d7bfc7a96c0327d280b91dfc"
        ]
      },
      {
        "left": [
          "3577673b86ad4d594d86941d731f17d1515f4669483aed091d49f279d677cb19",
          "75603bfea5b44df4c41fbb99268364641896334f006af3a3f67edaa4b26477ca",
          "1339d43c526f0f34d8a0f4fb3bb47b716fdfde8d35697be5992e0888e4d794c9"
        ],
        "right": [
          "63c8e574fb2ef52e995427a8acaa72c27073dd8e37736add8dbf36be4f609ecb",
          "e65353d911d6cc8ead3fad53ab24cab69a1e31df8397517b124f578ba908558d"
        ]
      },
      {
        "left": [],
        "right": []
      }
    ]
  }
}

getTransactionReceiptByHashWithProof

返回根据交易哈希查询的带证明的交易回执信息,本接口仅在兼容性版本为2.2.0及以后的版本有效,证明信息是为了验证回执的存在性,回执存在性证明请参考文档交易证明

  • groupID: unsigned int - 群组ID
  • transactionHash: string - 交易哈希

返回值

  • array - 回执证明,字段如下:
    • left: array - 左边的哈希列表
    • right: array - 右边的哈希列表
  • object: - 交易信息,其字段如下:
    • blockHash: string - 包含该交易的区块哈希
    • blockNumber: string - 包含该交易的区块高度
    • contractAddress: string - 合约地址,如果创建合约交易,则为合约部署地址,如果是调用合约,则为”0x0000000000000000000000000000000000000000”
    • from: string - 发送者的地址
    • gasUsed: string - 交易消耗的gas
    • input: string - 交易的输入
    • logs: array - 交易产生的log
    • logsBloom: string - log的布隆过滤器值
    • output: string - 交易的输出
    • status: string - 交易的状态值,参考:交易回执状态
    • to: string - 接收者的地址,创建合约交易的该值为null
    • transactionHash: string - 交易哈希
    • transactionIndex: string - 交易序号
  • 示例
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionReceiptByHashWithProof","params":[1,"0xd2c12e211315ef09dbad53407bc820d062780232841534954f9c23ab11d8ab4c"],"id":1}' http://127.0.0.1:8585 |jq

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "receiptProof": [
      {
        "left": [
          "3088b5c8f9d92a3411a911f35ff0119a02e8f8f04852cf2fdfaa659843eac6a3ad",
          "31170ac8fd555dc50e59050841da0d96e4c4bc7e6266e1c6865c08c3b2391801dd"
        ],
        "right": [
          "33c572c8f961e0c56689d641fcf274916857819769a74e6424c58659bf530e90e3",
          "341233933ea3d357b4fdd6b3d1ed732dcff15cfd54e527c93c15a4e0238585ed11",
          "351e7ba09965cce1cfb820aced1d37204b06d96a21c5c2cf36850ffc62cf1fc84c",
          "361f65633d9ae843d4d3679b255fd448546a7b531c0056e8161ea0adbf1af12c0f",
          "37744f6e0d320314536b230d28b2fd6ac90b0111fb1e3bf4a750689abc282d8589",
          "386e60d9daa0be9825019fcf3d08cdaf51a90dc62a22a6e11371f94a8e516679cc",
          "391ef2f2cee81f3561a9900d5333af18f59aa3cd14e70241b5e86305ba697bf5f2",
          "3ac9999d4f36d76c95c61761879eb9ec60b964a489527f5af844398ffaa8617f0d",
          "3b0039ce903e275170640f3a464ce2e1adc2a7caee41267c195469365074032401",
          "3ca53017502028a0cb5bbf6c47c4779f365138da6910ffcfebf9591b45b89abd48",
          "3de04fc8766a344bb73d3fe6360c61d036e2eeedfd9ecdb86a0498d7849ed591f0",
          "3e2fc73ee22c4986111423dd20e8db317a313c9df29fa5aa3090f27097ecc4e1a9",
          "3fa7d31ad5c6e7bba3f99f9efc03ed8dd97cb1504003c34ad6bde5a662481f00a0"
        ]
      },
      {
        "left": [
          "cd46118c0e99be585ffcf50423630348dbc486e54e9d9293a6a8754020a68a92",
          "3be78209b3e3c83af3668ec3192b5bf232531323ef66b66de80a11f386270132",
          "bd3a11d74a3fd79b1e1ea17e45b76eda4d25f6a5ec7fc5f067ea0d086b1ce70f"
        ],
        "right": [
          "6a6cefef8b48e455287a8c8694b06f4f7cb7950017ab048d6e6bdd8029f9f8c9",
          "0a27c5ee02e618d919d228e6a754dc201d299c91c9e4420a48783bb6fcd09be5"
        ]
      },
      {
        "left": [],
        "right": []
      }
    ],
    "transactionReceipt": {
      "blockHash": "0xcd31b05e466bce99460b1ed70d6069fdfbb15e6eef84e9b9e4534358edb3899a",
      "blockNumber": "0x5",
      "contractAddress": "0x0000000000000000000000000000000000000000",
      "from": "0x148947262ec5e21739fe3a931c29e8b84ee34a0f",
      "gasUsed": "0x21dc1b",
      "input": "0x8a42ebe90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000000a3564646636663863653800000000000000000000000000000000000000000000",
      "logs": [],
      "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "output": "0x",
      "root": "0xc3b4185963c78a4ca8eb90240e5cd95371d7217a9ce2bfa1149d53f79c73afbb",
      "status": "0x0",
      "to": "0xd6c8a04b8826b0a37c6d4aa0eaa8644d8e35b79f",
      "transactionHash": "0xd2c12e211315ef09dbad53407bc820d062780232841534954f9c23ab11d8ab4c",
      "transactionIndex": "0x32"
    }
  }
}

generateGroup

根据群组ID及创世块参数创建新的群组,本接口仅在兼容性版本为2.2.0及以后的版本有效

参数

  • groupID: unsigned int - 群组ID
  • params: object - 创世块参数,其字段如下:
    • timestamp: unsigned int - 创世块时间戳
    • sealers: array - 共识节点列表,要求所有所列共识节点间存在有效的P2P连接
    • enable_free_storage: bool - 可选,是否启用”free storage”模式,启用后节点将减少STORAGE相关指令的gas耗费

返回值

  • object: - 接口调用结果,其字段如下:
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"generateGroup","params":[2, {"timestamp":"1585214879000","sealers":["70f18c055d366615e86df99f91b6d3f16f07d66293b203b73498442c0366d2c8ff7a21bb56923d9d81b1c2916251888e47adf66c350738c898defac50aead5ab","dde37f534885f08db914566efeb03183d59363a4be972bbcdde25c37f0b350e1980a7de4fdc4aaf956b931aab00b739a8af475ed2461b8591d8c734b27285f57","d41672b29b3b1bfe6cad563d0f0b2a2735865b27918307b85085f892043a63f681ac8799243e920f7bb144b111d854d0592ba5f28aa7a4e0f9f533f9fdf76ead","7ba2717f81f38e7371ccdbe173751f051b86819f709e940957664dbde028698fd31ba3042f7dd9accd73741ba42afc35a8ef67fe7abbdeb76344169773aa0eca"],"enable_free_storage":true}],"id":1}' http://127.0.0.1:8545 | jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "code": "0x0",
    "message": "Group 2 generated successfully"
  }
}

startGroup

根据群组ID启动相应的群组,本接口仅在兼容性版本为2.2.0及以后的版本有效

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: 接口调用结果,其字段如下:
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"startGroup","params":[2],"id":1}' http://127.0.0.1:8545 | jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "code": "0x0",
    "message": "Group 2 started successfully"
  }
}

stopGroup

根据群组ID停止相应的群组,本接口仅在兼容性版本为2.2.0及以后的版本有效

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: 接口调用结果,其字段如下:
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"stopGroup","params":[2],"id":1}' http://127.0.0.1:8545 | jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "code": "0x0",
    "message": "Group 2 stopped successfully"
  }
}

removeGroup

根据群组ID删除相应群组,群组数据会被保留以供将来恢复群组,本接口仅在兼容性版本为2.2.0及以后的版本有效

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: 接口调用结果,其字段如下:
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"removeGroup","params":[2],"id":1}' http://127.0.0.1:8545 | jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "code": "0x0",
    "message": "Group 2 deleted successfully"
  }
}

recoverGroup

根据群组ID恢复相应群组,本接口仅在兼容性版本为2.2.0及以后的版本有效

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: 接口调用结果,其字段如下:
  • 示例
// Request
curl -Ss -X POST --data '{"jsonrpc":"2.0","method":"recoverGroup","params":[2],"id":1}' http://127.0.0.1:8545 | jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "code": "0x0",
    "message": "Group 2 recovered successfully"
  }
}

queryGroupStatus

根据群组ID查询相应群组的状态

参数

  • groupID: unsigned int - 群组ID

返回值

  • object: 接口调用结果,其字段如下:
    • code: - 接口调用状态码,状态码的释义请参见动态群组管理 API 状态码
    • message: - 接口消息
    • status: - 群组状态标识,为下列值之一:
      • INEXISTENT: 群组不存在
      • STOPPING: 群组正在停止
      • RUNNING: 群组正在运行
      • STOPPED: 群组已停止
      • DELETED: 群组已删除
  • 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"queryGroupStatus","params":[2],"id":1}' http://127.0.0.1:8545 | jq

// Result
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "code": "0x0",
    "message": "",
    "status": "STOPPED"
  }
}

错误码描述

RPC 错误码

当一个RPC调用遇到错误时,返回的响应对象必须包含error错误结果字段,该字段有下列成员参数:

  • code: 使用数值表示该异常的错误类型,必须为整数。
  • message: 对该错误的简单描述字符串。
  • data: 包含关于错误附加信息的基本类型或结构化类型,该成员可选。

错误对象包含两类错误码,分别是JSON-RPC标准错误码和FISCO BCOS RPC错误码。

JSON-RPC标准错误码

标准错误码及其对应的含义如下:

code message 含义
-32600 INVALID_JSON_REQUEST 发送无效的请求对象
-32601 METHOD_NOT_FOUND 该方法不存在或无效
-32602 INVALID_PARAMS 无效的方法参数
-32603 INTERNAL_ERROR 内部调用错误
-32604 PROCEDURE_IS_METHOD 内部错误,请求未提供id字段
-32700 JSON_PARSE_ERROR 服务端接收到的json无法解析
FISCO BCOS RPC错误码

FISCO BCOS RPC接口错误码及其对应的含义如下:

code message 含义
-40001 GroupID does not exist GroupID不存在
-40002 Response json parse error JSON RPC获取的json数据解析错误
-40003 BlockHash does not exist 区块哈希不存在
-40004 BlockNumber does not exist 区块高度不存在
-40005 TransactionIndex is out of range 交易索引越界
-40006 Call needs a 'from' field call接口需要提供from字段
-40007 Only pbft consensus supports the view property getPbftView接口,只有pbft共识机制有view属性
-40008 Invalid System Config getSystemConfigByKey接口,查询无效的key
-40009 Don't send requests to this group,
the node doesn't belong to the group
非群组内节点发起无效的请求
-40010 RPC module initialization is incomplete RPC模块初始化尚未完成
-40011 Over QPS limit SDK到节点的请求速率超过节点的请求速率限制
-40012 The SDK is not allowed to access this group SDK无访问群组的权限

交易回执状态

status(十进制/十六进制) message 含义
0(0x0) None 正常
1(0x1) Unknown 未知异常
2(0x2) BadRLP 无效RLP异常
3(0x3) InvalidFormat 无效格式异常
4(0x4) OutOfGasIntrinsic 部署的合约长度超过gas限制/调用合约接口参数超过gas限制
5(0x5) InvalidSignature 无效的签名异常
6(0x6) InvalidNonce 无效nonce异常
7(0x7) NotEnoughCash cash不足异常
8(0x8) OutOfGasBase 调用合约的参数过长 (RC版本)
9(0x9) BlockGasLimitReached GasLimit异常
10(0xa) BadInstruction 错误指令异常
11(0xb) BadJumpDestination 错误目的跳转异常
12(0xc) OutOfGas 合约执行时gas不足 / 部署的合约长度超过最长上限
13(0xd) OutOfStack 栈溢出异常
14(0xe) StackUnderflow 栈下限溢位异常
15(0xf) NonceCheckFail nonce检测失败异常
16(0x10) BlockLimitCheckFail blocklimit检测失败异常
17(0x11) FilterCheckFail filter检测失败异常
18(0x12) NoDeployPermission 非法部署合约异常
19(0x13) NoCallPermission 非法call合约异常
20(0x14) NoTxPermission 非法交易异常
21(0x15) PrecompiledError precompiled错误异常
22(0x16) RevertInstruction revert指令异常
23(0x17) InvalidZeroSignatureFormat 无效签名格式异常
24(0x18) AddressAlreadyUsed 地址占用异常
25(0x19) PermissionDenied 无权限异常
26(0x1a) CallAddressError 被调用的合约地址不存在
27(0x1b) GasOverflow Gas溢出错误
28(0x1c) TxPoolIsFull 交易池已满异常
29(0x1d) TransactionRefused 交易被拒绝异常
30(0x1e) ContractFrozen 合约被冻结异常
31(0x1f) AccountFrozen 账户被冻结异常
10000(0x2710) AlreadyKnown 交易已经在交易池中
10001(0x2711) AlreadyInChain 交易已经上链异常
10002(0x2712) InvalidChainId 无效的链ID异常
10003(0x2713) InvalidGroupId 无效的群组ID异常
10004(0x2714) RequestNotBelongToTheGroup 请求不属于群组异常
10005(0x2715) MalformedTx 交易格式错误
10006(0x2716) OverGroupMemoryLimit 超出群组内存限制异常

Precompiled Service API 错误码

错误码 消息内容 备注
0 success
-50000 permission denied
-50001 table name already exist
-50002 table name length is overflowed
-50003 table name field length is overflowed
-50004 table name field total length is overflowed
-50005 table key value length is overflowed
-50006 table field value length is overflowed
-50007 table field is duplicated
-50008 table field is invalidate
-50100 table does not exist
-50101 unknown function call
-50102 address invalid
-51000 table name and address already exist
-51001 table name and address does not exist
-51002 table name overflow
-51003 contract not exist
-51004 committee member permission managed by ChainGovernance
-51100 invalid node ID SDK错误码
-51101 the last sealer cannot be removed
-51102 the node is not reachable SDK错误码
-51103 the node is not a group peer SDK错误码
-51104 the node is already in the sealer list SDK错误码
-51105 the node is already in the observer list SDK错误码
-51200 contract name and version already exist SDK错误码
-51201 version length exceeds the maximum limit SDK错误码
-51300 invalid configuration entry
-51500 entry parse error
-51501 condition parse error
-51502 condition operation undefined
-51600 invalid ciphers
-51700 group sig failed
-51800 ring sig failed
-51900 contract frozen
-51901 contract available
-51902 CONTRACT_REPEAT_AUTHORIZATION
-51903 INVALID_CONTRACT_ADDRESS
-51904 TABLE_NOT_EXIST
-51905 NO_AUTHORIZED
-52000 COMMITTEE_MEMBER_EXIST
-52001 COMMITTEE_MEMBER_NOT_EXIST
-52002 INVALID_REQUEST_PERMISSION_DENIED
-52003 INVALID_THRESHOLD
-52004 OPERATOR_CANNOT_BE_COMMITTEE_MEMBER
-52005 COMMITTEE_MEMBER_CANNOT_BE_OPERATOR
-52006 OPERATOR_EXIST
-52007 OPERATOR_NOT_EXIST
-52008 ACCOUNT_NOT_EXIST
-52009 INVALID_ACCOUNT_ADDRESS
-52010 ACCOUNT_ALREADY_AVAILABLE
-52011 ACCOUNT_FROZEN
-52012 CURRENT_VALUE_IS_EXPECTED_VALUE

动态群组管理 API 状态码

状态码 消息内容 释义
0x0 SUCCESS 接口调用成功
0x1 INTERNAL_ERROR 节点内部错误
0x2 GROUP_ALREADY_EXISTS 调用创建群组接口时,群组已存在
0x3 GROUP_ALREADY_RUNNING 调用启动群组接口时,群组已处于运行状态
0x4 GROUP_ALREADY_STOPPED 调用停止群组接口时,群组已处于停止状态
0x5 GROUP_ALREADY_DELETED 调用删除群组接口时,群组已处于删除状态
0x6 GROUP_NOT_FOUND 调用接口时,对应的群组不存在
0x7 INVALID_PARAMS 调用接口时,参数不合法
0x8 PEERS_NOT_CONNECTED 调用创建群组接口时,与sealer间不存在有效的P2P连接
0x9 GENESIS_CONF_ALREADY_EXISTS 调用创建群组接口时,创世块配置文件已存在
0xa GROUP_CONF_ALREADY_EXIST 调用创建群组接口时,群组配置文件已存在
0xb GENESIS_CONF_NOT_FOUND 调用启动群组接口时,未找到创世块配置文件
0xc GROUP_CONF_NOT_FOUND 调用启动群组接口时,未找到群组配置文件
0xd GROUP_IS_STOPPING 调用接口时,群组正在释放资源
0xf GROUP_NOT_DELETED 调用恢复接口时,群组并未被删除

常见问题解答

版本相关

问: FISCO BCOS 2.0版本与之前版本有哪些变化?
答: 请 参考这里

问: 开发者如何与FISCO BCOS平台交互?
答: FISCO BCOS提供多种开发者与平台交互的方式,参考如下:

  • FISCO BCOS 2.0版本提供JSON-RPC接口,具体请 参考这里
  • FISCO BCOS 2.0版本提供Web3SDK帮助开发者快速实现应用,具体请 参考这里
  • FISCO BCOS 2.0版本提供控制台帮助用户快速了解使用FISCO BCOS,具体请 参考这里

问: FISCO BCOS 2.0版本如何搭建?
答: FISCO BCOS支持多种搭建方式,常用方式有:

  • 开发部署工具 build_chain.sh:适合开发者体验、测试FISCO BCOS联盟链,具体请 参考这里
  • 运维部署工具 generator:适用于企业用户部署、维护FISCO BCOS联盟链,具体请 参考这里

问: FISCO BCOS 2.0版本的智能合约与之前版本合约有什么不同,兼容性如何?
答: FISCO BCOS 2.0版本支持最新的Solidity合约,同时增加了precompile合约,具体请 参考这里

问: 国密和普通版本的区别有哪些?
答: 国密版FISCO BCOS将交易签名验签、p2p网络连接、节点连接、数据落盘加密等底层模块的密码学算法均替换为国密算法。同时在编译版本,证书,落盘加密,solidity编译java,Web3SDK使用国密版本和普通版本都有区别,具体请 参考这里

问: 是否支持从1.3或1.5升级到2.0版本?
答: 不支持。

控制台

问: 控制台指令区分大小写吗?
答: 区分大小写,命令是完全匹配,但是可以采用tab补全命令。

问: 加入共识列表或观察者列表报错,nodeID is not in network,为什么?
答: 节点加入共识列表和观察者列表的节点必须是连接peer的nodeID列表里面的成员。

问: 删除节点操作报错,nodeID is not in group peers,为什么?
答: 节点删除操作中的节点必须是getGroupPeers里面展示的group的peers。

问: 游离节点(非群组节点)是否可以同步group数据?
答: 游离节点不参与group内的共识、同步和出块,游离节点可以通过控制台addSealer/addObserver命令可以将退出的节点添加为共识/观察节点。

问: 某节点属于不同的group,是否可以支持查询多group的信息。
答: 可以,在进入控制台时,输入要查看的groupID: ./start [groupID]

FISCO BCOS使用

问: 2.0版本证书在哪里使用?
答: 请参考证书说明文档

问: 2.0版本交易结构包括哪些字段?
答: 请参考这里

问: 系统配置、群组配置、节点配置分别指什么?
答: 系统配置是指节点配置中一些影响账本功能,并需账本节点共识的配置项。群组配置指节点所属的群组的相关配置,节点的每个群组都有独立的配置。节点配置指所有可配置项。

问: 群组配置都是可改的吗?
答: 从配置项是否可改的维度,分为

  • 节点首次启动生成创世块后不能再修改。这类配置放置于group.x.genesis文件,其中x表示组编号,全链唯一。
  • 通过发交易修改配置项实现账本内一致。
  • 修改自身配置文件后,节点重启生效。这类配置放置于group.x.ini文件。群组配置改后重启可改项就是本地配置,nodeX/conf下的group.*.ini文件,更改重启生效。涉及配置项为[tx_pool].limit(交易池容量),[consensus].ttl(节点转发数)。

问: 群组配置用户可以改的涉及哪些配置?
答: 群组可修改配置分为共识可改配置和手工可改配置

  • 共识可改配置:全组所有节点相同,共识后生效。[consensus].max_trans_num,[consensus].node.X,[tx].gas_limit。
  • 手工可改配置:group.x.ini文件中,修改后重启生效,只影响节点。配置项有[tx_pool].limit。

问: 群组共识可改配置如何更改、查询?
答: 共识可改配置可以通过控制台修改。共识可改配置项查询除了控制台外,还可以通过RPC接口查询,具体请 参考这里

  • [consensus].max_trans_num,[tx].gas_limit使用接口setSystemConfigByKey更改,对于的配置项为tx_count_limit,tx_gas_limit。具体参见setSystemConfigByKey -h 。
  • [consensus].node.X的更改涉及到节点管理,控制台接口涉及到addSealer,addObserver,removeNode,具体参考《节点管理》。

问: 群组观察节点和共识节点有什么区别?
答: 观察节点能同步群组数据,但不能参与共识。共识节点除了具有观察者权限,还参与共识。

问: 如何将合约纳入CNS管理?
答: 在部署合约时,调用CNS合约接口,将合约name、version、address信息写入CNS表中。

问: 如何查询合约CNS表?
答: 通过Web3SDK控制台指令查询,查询指令根据合约name查询。

问: 为什么本地SDK无法连接云服务器上的FISCO BCOS节点?
答:

  1. 检查云服务器上的节点配置,channel是否监听外网IP,而不是127.0.0.1。端口介绍参考这里
  2. 检查通过云服务器提厂商提供的控制台,检查是否配置了安全组,需要在安全组中开放FISCO BCOS节点所使用的channel端口。
  3. 检查生成的证书是否正确,参考这里

问: 节点启动后,为什么无法连接其他节点且节点日志中出现『错误的文件描述符』等网络异常信息?
答:

  1. 请检查节点证书配置是否正确
  2. 请检查节点类型(国密、非国密)是否与链中其他节点一致

问: 日志中为何出现形如”invalid group status”的错误提示?

答:可能由于文件系统错误等原因导致节点在本地记录的群组状态不合法,可以检查群组数据目录下的.group_status文件,将其内容改为下列值之一:

  • STOPPED
  • DELETED
  • RUNNING

Web3SDK

问: Web3SDK对Java版本有要求吗?
答: 参考Java环境要求

问: Web3SDK配置完成,启动失败的原因是什么?
答: 参考JavaSDK异常场景

运维部署工具

问: 运维部署工具使用时出现找不到pip

答: 运维部署工具依赖python pip,使用以下命令安装:

$ bash ./scripts/install.sh

问: 运维部署工具使用时出现

Traceback (most recent call last):
   File "./generator", line 19, in <module>
    from pys.build import config
   File "/data/asherli/generator/pys/build/config.py", line 25, in <module>
     import configparse

答: 系统缺少python configparser模块,请按照以下命令安装:

  $ pip install configparser

问: 节点或SDK使用的OpenSSL证书过期了,如何续期?

答: 证书续期操作可以参考证书续期操作

问: 使用下载命令提示certificate verify failed 答: 在 ./pys/tool/utils.py 这个文件的开头中加入如下两行

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

《深入浅出FISCO BCOS》

介绍

FISCO BCOS开源社区已沉淀系列技术文章,为了方便大家了解熟悉并更快上手FISCO BCOS,社区依据平台关键特性对这些文章进行整理编排,以便索引。

欢迎大家就FISCO BCOS平台的关键技术点提交PR分享使用心得。

区块链的概念与原理

亲朋好友都能看懂的区块链

作者:张开翔|FISCO BCOS 首席架构师

据新华社10月25日晚消息,中共中央政治局10月24日下午就区块链技术发展现状和趋势进行第十八次集体学习。中共中央总书记习近平在主持学习时强调,区块链技术的集成应用在新的技术革新和产业变革中起着重要作用。

随后“什么是区块链”、“区块链有什么用”、“怎么掌握区块链”这几个问题响不绝耳。这里尽量用通俗易懂的语句回答这些问题,期望即使是从没接触过区块链的人也能搞懂这项已经成为国家级现象的神奇技术。

什么是区块链?

专业解释里,一般都会带上“分布式网络”、“密码学”、“共识算法”、“智能合约”这些有点晦涩的术语,这里打算举一个例子。

要理解区块链,首先要接受一个设定:有个账本,要记录的是一群人之间的公共账目(比如班费、物业费、公益捐款的收支),这个账本由大家一起来记账。选一个人,在账本的某一页,一行一行的记录明细,当一页记满后,大家都去核对账目,正确的话,大家签字认可这一页的所有账目。记满一页后,再选另一个人开始记接下来的一页。现在有意思的事情来了,新的一页首先要把上一页的一些摘要特征(比如页码、余额、人数、条数什么的…)抄写下来放在页首以供对照,免得前一页被改了或丢了还无据可查。然后,再一条条记账,记满一页后,核对、签名确认…依次反复。这样,账本的一页和一页之间就形成了“证据链”。

更重要的是,账本上已经签名确认的每一页,所有人都会一字不漏地复制一份,放到自己家里,以免少数人篡改、污损、丢失账目。这样,账本的每一页是一个“区块(Block)”,一页一页之间形成前后连贯的证据链,每个人之间构成了多点的网络,这就是“区块链”的概要原型了。这里面的关键点在于,这个账本一定是一群人的账,并不是一个人的账。如果是一个人的账,自己拿个小本本记就好了,为什么要整这么麻烦。就是因为这一群人互相之间并不完全信任,记账过程可能出纰漏,所以必须用这么繁琐的步骤,让大家平等参与,一起保证账目的准确和公允性,产生的结果大家保存,永远不会丢也不会错。

上面的举例看起来步骤繁琐,还要求每个人都一字不差地复刻和保存,那么就需要技术手段来帮助大家记账。这又回到技术环节,随着计算机科技的发展,无论是网络、密码学、数据结构、分布式算法等技术都趋于成熟,区块链领域把这些技术组合在一起,一揽子完成了从记账到分发到验证到保存的整个过程。运用技术服务整个群体,这就是神奇的区块链技术。

区块链是比特币吗?

首先,可以肯定地回答:不是。比特币确实是早期的一种区块链技术的应用,它提供了一种价格波动不定的虚拟代币,之后还有许多“山寨币”效仿比特币运作,这些虚拟代币风险都很高,不被法律法规所容。现在国家提倡的“区块链”,已经和虚拟代币明确划清界限,更强调研究核心技术,加固系统安全,控制业务风险,也更强调区块链技术和具体实体经济融合,如合规合法的金融业务、政务、智慧城市、农业工业等,用技术去解决实际的问题,发掘创新场景。区块链并不是发币割韭菜的工具,不是资金盘的幌子,而是越来越成熟和规范的创新技术,即将像互联网、APP等常见的技术服务一样,潜移默化地影响大家的日常生活。

区块链只是一种技术吗?

如果只是谈技术,可能还没参透区块链的魅力。上面说到,区块链的记录一定是多人参与的,那么大家为什么要花精力参与到记账这个麻烦的事情里,为什么要出成本买本子和笔去记账?这就牵涉到“多方协作”了。现代社会很多事情是很难一个人完成的,必须大家一起合作才能达到1+1>2的效应,但是人多了,账就复杂,可能出现类似“信息不对称”之类的问题,那么就需要把大家组织成联盟,共同记账,共享数据,使一切都发生在阳光下,这就消除了“信息不对称”。这样的协作环境,是可信任和高效的,每个人的利益都得到了保护,营商环境得以良性发展。这就是更重要的“区块链思维”,让更多人参与进来按规则运作:诚实做事将获得应有的收益,如果作弊自然就会路人皆知,千夫所指。技术就是帮助实现这种模式的基础。

区块链能做什么?

综合区块链技术和区块链思维,只要是牵涉到多方参与的,有着复杂的账目来往和数据共享要求的场景,都可以考虑使用区块链。区块链不但能记账,也能记录人和物的信息,并通过共同对信息进行验证,固化下来,而变得可信。

传统金融场景中,金融机构每天都需要进行大量的对账清算工作,如果交易发生在区块链上,那么就可以起到“交易即对账”的作用,极大提升运营效率。更进一步,在供应链、跨境支付、票据等典型的金融业务中,都可以用区块链在合作伙伴之间构建公众账本,海量的资金和资产在链上记录、核验、交易,账本可信、准确、高效,可以极大扩大金融业务规模、提升运作效率、降低成本和风险,能更好地解决小微企业融资难、银行风控难、监管接入难等一系列问题。另外,合规且标准化数字资产也可以在区块链链上定义、流转、承兑,可以构建创新的商业模式。

司法存证领域,如合同的签署过程被全部记录在区块链上,并且由包括司法机构在内的参与方共同见证,那么发生纠纷时,司法机构就可以一键从链上提取证据进行核验,证明合同从诞生到取证时都没有修改过。由于链上有司法机构参与,这样的证据已经具备一定的司法效力,大大减少了司法成本。

政务服务方面,区块链用于身份认证,可以让人们的身份证明在一处验证,处处可用。用区块链连接多个部门,就能做到“办事时数据多跑腿,用户少跑腿”,并且“证明我是我,我妈是我妈”的事情就不复存在,还能保护用户数据隐私。

此外,区块链技术还可以被运用到版权、物业、物联网、智慧城市、新能源、文娱、人才交流等海量领域。正确地将区块链技术和区块链思维,与国计民生相关的事务深入融合,能够大幅提升智能化、精准化水平,使得产业之间互联互通,保障生产要素在区域内有序高效流动。

点击查看区块链应用案例精编(附高清PDF全本下载)

为什么国家这么重视区块链?

区块链有巨大的发展潜力,本身蕴含着极密集的技术含量,对技术有足够研究和把控,才能使自身发展不受外界影响和制约。区块链技术将会被用到与国计民生有关的广泛场景里,牵涉到许多人的财务和个人信息,甚至是金融行业、政务民生等关键领域的重要信息,重视技术和运作的安全可控,才能保护财务安全、信息安全,乃至社会安全。所以,当下国家给我们提出了明确的要求:“注意区块链技术发展现状和趋势,提高运用和管理区块链技术能力”,这样才能更好地建设网络强国、发展数字经济、助力经济社会发展。

我国的区块链发展现状如何?

我国产业界在区块链领域投入很早、力度很大,诸多大型金融企业、互联网公司、科技公司都在进行区块链研究,在过去几年中,已经逐步解决或接近解决了区块链领域的一系列核心问题,如性能、安全、易用性、合规等,在这个过程中也积累了大量的知识产权和专利。

我们重点发展的区块链领域是“联盟链”,联盟链和匿名的、有虚拟代币的“公有链”(诸如比特币、以太坊等)有极大不同,联盟链不发币不挖矿,摈弃了野蛮运作的弊端,能更规范、合法的运作,可有效服务于实体经济。

由微众银行、深圳市金融科技协会等国内机构共同发起成立的区块链联盟——金链盟,掌握多项核心技术,于2017年开源发布了以FISCO BCOS为代表的区块链底层平台和系列解决方案。这一系列开源项目都安全可控、性能优异、免费且易用,致力于服务实体经济,在大量关系国计民生的产业中已广泛落地,技术和模式得到了大量实际案例的验证。

另外值得一提的是,FISCO BCOS采用技术开源的路径加速行业发展,孵化拓展了国内最大和最活跃的行业生态社区,在推进中重视人才培养,在高校、社会、产业机构中培养了大量的区块链专业人才,帮助区块链技术和产业发展加速人才方面的突破。

我该如何切入区块链领域?

如果您是一位对区块链感兴趣的非工科人士,那么可以多关注这个领域里正规和权威的公众号、主流媒体,多了解区块链相关的新闻动态、产业趋势,建立正确的区块链概念,消除来自虚拟币和资金盘的噪声,并逐步将诸如“多方对等协作”、“公开透明”等“区块链思维”对照到当前的工作生活中来。

如果您是一位工科、信息学方面的学生,那么建议您在学校打好学业基础,比如数学、算法和数据结构、概率论、博弈论、密码学等,掌握一两门主要的计算机语言,向经验丰富的教授和老师请教,积极参与学校的区块链社团活动,也可以参加到FISCO BCOS区块链开源技术社区和学校联合开展的课程中来,三天上手区块链应用。

如果您是商科、文科背景,可以重点关注分布式商业的趋势,善用人文、经管、经济理论和博弈论方面的经验和知识,应对多方协作的种种挑战,用创新思维开拓可服务实体经济、人民生活的新场景。

如果您是在IT界工作的专业人士,需要区块链技术的加持,欢迎您加入FISCO BCOS区块链开源技术社区。这里有活跃的微信群讨论技术和产业问题,有公众号定期推送技术解析文章、活动通知等。更重要的是,您可以获得全面开源且免费的区块链解决方案,从区块链底层平台到身份、物联网、图形化工具、云服务应有尽有,有丰富的文档和专业的线上线下课程帮助您快速学习,从入门到精通。区块链技术可以连接多项技术,包括人工智能、物联网、大数据、金融科技等,技术界人士和区块链的结合,将大有可为。

如果您已经是硬核区块链专业人士,且愿意参与到开源社区里来,欢迎您关注FISCO BCOS区块链开源技术社区,互通有无,共同深入研究核心技术,为开源社区添砖加瓦,进行代码优化、文档编著等,一起实现更多的功能,共同打造最好用的区块链技术平台。

如果您已经面对各种实际场景,希望用区块链解决您的具体问题,FISCO BCOS区块链开源技术社区有诸多的金融机构、科技公司和产业专家,可以与您一起整合资源,进行业务合作,完成系统建设和业务运营。

区块链已经触达当下,更将影响未来,和一个具备无限潜力的趋势共同成长,现在,就是最好的时节。

反常理,反直觉,区块链是怎样的一种“分布式系统”

作者:张开翔|FISCO BCOS 首席架构师

我们经常看到“区块链是分布式系统”的说法,并推论出区块链先天具备分布式系统的优势,仿佛作为分布式系统,规模就该足够大,数据就该足够分散。 事实上,典型区块链有很多特征和常见的分布式系统不同,甚至是相悖的,为此,区块链曾被戏称为“最慢的分布式数据库”。 其实区块链之所以难以理解,其中一个原因是其设计哲学的“反常理、反直觉”。笔者本人曾多年在互联网海量服务领域里踩坑,然后转向区块链领域深入研究,也经历过一阵子的观念切换期。

本文不打算全面讲述分布式系统原理和历史,那能写几本书。这里打算从常见的、被人广泛认知的互联网分布式系统出发,聊聊“分布式系统”和区块链有什么异同,对技术和设计的要求有哪些路径分支。

经典的分布式著作《分布式系统概念与设计》中,对“分布式系统”给出的定义其实很笼统:分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。

简而言之,只要不是运行在一台机器上或一个进程里的系统,都可以是分布式系统。 比如,大型网站、APP、全民IM社交软件的支撑系统,基于X86体系的新型金融基础设施,都可以是分布式系统。 至于这个分布式系统是属于一个机构的,还是属于多个不同机构维护的,并不是这个定义的重点。

互联网海量服务系统之道

提示:已经比较熟悉互联网海量设计的读者,可以直接跳过本节

如果要求一个互联网服务的架构师,在面对山呼海啸的请求量、保存无限增长的数据的同时,提供良好的用户体验,保证延时很短的响应表现,这位架构师脑子里蹦出来的通常是“分层设计”、“冷热分离”、“平行扩展”、“并行处理”、“分库分表”等等关键字。 互联网海量服务的架构通常是分层的,比如接入层、服务层、数据层等。在接入层根据各种策略进行负载均衡和灵活的路由分发,服务可以按功能分组。比如: 用户要改个人资料,请求发给资料服务; 用户浏览网页,请求查询服务吐数据; 用户要发帖了,转给内容服务; 用户要下单,该交易服务器接手了,每个服务只处理一部分业务逻辑,互相之间通过远程接口互相调用,且本身通常是“无状态”的,不受自身和其他服务处理状态的牵制,这种高内聚和松耦合的设计,利于针对不同服务的特性和负载,进行特定优化和灵活部署; 最后,数据存储是“分片”的,俗称“分库分表”,根据用户标识、业务类型等规则,将数据分摊在不同的数据库实例中,每个数据库实例上只有“部分”数据,仅仅是数据的“子集”。

_images/IMG_4981.PNG

以UGC、社交、富文本、视频内容为主的互联网服务,对“交易事务性”要求并不那么强,其“分布式”更多的是追求将计算和数据均匀分散在多台服务器上,利用更多的CPU、更大的带宽、更大的内存、更大的磁盘空间来处理请求。 一组服务器不够了,立刻增加一组,平行扩容和多活特性表现得淋漓尽致。整个体系会有一些主备和冗余,更多是为了满足服务质量、可用性、备份方面的要求。 电商以及一些和金融、支付相关的流程会强调ACID事务性,采用的分布式一致性算法,如Raft、Paxos等,主要是追求多模块之间的最终一致性和系统稳定性。

毕竟在同一个机构里的系统,对抗欺诈的要求并不会太苛刻,而且可以根据特定的业务流程将交易分类,将单个事务控制在有限的范围内(如只有用户向商户单向转账),事务和事务之间进行清晰地隔离,这样也比较容易进行平行扩展。

如上图所示,一个典型的多层结构的互联网服务,模块众多,计算和存储分布都比较均匀,哪个模块成为瓶颈,就去增加哪个模块的硬件资源,进行业务路由配置和必要的数据迁移就是了。

像分布式数据库、大数据集群、或者BT网络、CDN这些“分布式系统”,大多也是遵循“将用户和数据分散到不同的物理设备上”这个原则,达到简单的“堆硬件”即可平行扩展的效果。 硬件越多,技术上的“规模效应”越显著,即存储量更大、计算能力更强、总带宽越多,能服务的用户就越多。 最重要的是,整个服务大部分是在同一个机构里,由同一个公司的团队维护,服务和服务之间并不需要解决“信任”的问题,默认信任其他服务的接口给出的结果,简单校验后立刻继续处理,整个系统就是要“快”,要“稳”。 如果有一些合作类的接口暴露给外部公司,只需要确保接口层的性能、安全、稳定,以及满足合作规则即可。

“带着锁链跳舞”的区块链

再看区块链,首先整个网络并不是只有一个机构参与,节点都可能归属不同的机构,大家一起共同维护网络,共同对交易达成共识。 区块链的使命是突破机构边界,解决机构之间的信任和博弈问题,如果只在一个机构内“家养”一个区块链系统,固然可以运作,但收效未必最佳。 区块链体系里,为了保证数据可追溯和可验证,避免少数节点篡改数据影响全网,每个节点保存全部数据,而不是像互联网服务这样“分库分表”。

_images/IMG_4982.PNG

让我们算一下,如果有一千万个用户,在互联网系统里分成10个数据库实例保存,则每个数据库实例只保存100万用户的数据。 即使每个数据库实例都是“一主两备”,那么总的数据开销也只是原始数据的3倍。 而经典的区块链设计是,每个节点都如数保存一千万用户的所有数据,且全网节点越多,保存的数据副本就越多。 如全网有100个节点,则全网总的数据存储开销是原始数据的100倍;如有200个节点,全网总的数据存储则达到原始数据的200倍……以此类推,挺触目惊心。 极端情况下,如果把整个地球的数据都上链,链上有1万个节点,那么相当于我们有了1万个地球……然而我们并没有这么多资源吧。

区块链的数据结构本身也是很有意思的一个环节。区块之间是链式关系,新的区块必须基于旧的区块生成。 智能合约生成状态数据是全局性的,常常用类似帕特里夏树、merkle树这种复杂的数据结构进行维护,以便全网追溯、验证和计算。 数据环环相扣,仿佛“铁板一块”,使得按冷热特征、时间和用户等维度切分数据有相当的挑战性,既要使数据可以分布到不同的存储里,又要保证可以快速访问,保证盘根错节的追溯验证。工程难度很大。

交易层面,在一个互相没有强信任关系的网络里,且处理的是大概率和“价值”有关系的交易,区块链尤其强调交易的全局事务性,保证全网一致性,且为了避免有作恶节点伪造假数据,所有的交易都会在共识机制和网络同步驱动下,在所有的节点上进行排序、运算和校验。 在支持智能合约的区块链上,每个智能合约被交易调用时,都会在所有节点上把合约代码跑一遍,以确保在每个节点上生成数据的过程是公认的、运行结果是一致的。

可见,区块链并不能把计算量分摊到不同的服务器上,所有的节点都是“复读机”,这就意味着增加节点,也不会增加全网并行计算能力,就算是全网有一万台计算机,速度也跟只有一台是一样的。 如果把这种区块链称为“世界计算机”,可算是全世界步骤最统一、冗余度最高、速度最慢的世界计算机了。

区块链系统的核心是共识算法。

POW挖矿是矿工用算力解一个难题,以争到记账权,再进行记账,并期盼自己的记账结果成为最长链。 POW算法是出了名的又慢又耗电; POS权益类共识,由一批“富翁”抵押资产以获得记账权,轮流记账,不需要算力竞争,性能表现好一些,但依旧是有轮流打包出块然后全网最终确认的过程; PBFT(实用性拜占庭)共识,需要记账者多次往返交互,大多数人参与到多阶段的确认,才能达到最终状态。 总的来说,和互联网服务追求的“无状态”不同,共识服务是“有状态”的,每种共识算法都需要参照网络、数据、记账者参与度、链当前状态(如“区块高度”和“共识阶段”)等等多维度的信息进行决策。

记账节点越多,协作成本越高,尤其是典型的PBFT算法,在节点增多时,共识会越来越慢,交易延迟会越来越高。 如果让节点内部也“集群化”,采用“多层分布式架构”,是否可以实现类似互联网海量服务系统这样的可平行扩展呢?比如,将虚拟机改为多实例,并行计算交易,这样就可以解决计算瓶颈问题。 但计算越快,单位时间可以产生的数据越多,这样也要求硬盘存储更多的数据,而硬盘的容量和IO速度是有限的。 同时,计算快了,节点也会向网络广播更多的数据,节点公网互联的带宽通常不会太大,延时一般也比较明显(几十毫秒起)。 于是,节点和节点之间交互又会命中网络速度的瓶颈,共识时延和数据同步时间变长,节点们就像一串绑在绳子上的蚂蚱,谁也蹦不高。由于存储和网络这些硬条件的天花板存在,集群化的计算再快也没有用了。 最后,节点集群化确实可以使以节点为单位的服务处理能力有一定上升,同时也会带来的架构上的额外复杂性和部署运维成本。

还是引用一个数钱的例子: 有一大堆钱让十个人数,有两种方法。 方法一,可以把钱分成十份,每个人数一份,这样很快就数完了,人越多,数得越快,但是如果里面有人数错了,或者甚至偷钱,那就有问题了。 于是,为了资金安全,改成方法二,让一个人整理出一叠钱,先数一遍,然后让其他九个人也数一遍,每个人都验算记账后,再换个人整理出下一叠钱,重复上述节奏去数,这样结果肯定是不会错的,且能得到大家的公认。但参与数钱的人增加,并不会加速计数,反而有可能因为人多手杂导致更慢。 方法一就是常见的互联网分布式系统的做法,方法二是区块链。 可见,目的不同,导致设计哲学、系统结构、最终效果都不同。

如何把“好钢用到刀刃上”

综上所述,区块链这种“分布式系统”,存储成本和节点数同比线性增加,而计算效率不升反降,使整个系统显得“贵”和“重”,这和互联网服务的“轻快灵”相背而驰。最要命的是,难以通过增加硬件、带宽、节点数来显著提升并行处理能力和存储量。 但区块链的“网络规模效应”并不体现在硬件和计算上,而是体现在因为“共识”和“信任”上。 区块链通过复杂的算法和博弈,构建了一种可信网络,使得更多人愿意参与到网络里,共同贡献数据和维护网络,体现“协作共赢”这个价值效应。 这样也给我们一个重要启示:既然区块链的计算和存储成本是很高的,其目的是为了达到共识,那么,我们应该让区块链干最该干的事情。

1、哪些数据可以“上链”?

只有多方要在交易过程中用到的、必须共享的关键数据要上链,比如公共账本的账目。

视频、文件、图片、大规模的业务数据,可以生成摘要与交易数据关联,其本体通过其他渠道(如FTP、分布式文件系统等)进行交换。 比如下棋,只需要把棋局结果放到链上,或者把每一步的数据算个摘要放到链上,并不需要每一步都记录到链上(除非认为这也是非常关键的信息)。 毕竟每一笔放到“链上”的数据,都会占用所有节点的硬盘。

2、合约里写什么逻辑?

应该是写多方共同参与、协作记账、必须全局共识的关键逻辑,而不是牵涉密集计算的逻辑。 比如,进行复杂查询或建模分析,可以把链上的数据导出来放在链下去做,而不是写在合约里。要清楚,你写的每一行代码,再不是只在自己的服务上跑一次了,而是会在链上所有参与者的节点上跑起来,多写一行代码就会多消耗大家的一点CPU。 所以,区块链上会有类似“Gas上限”这样的机制,来控制合约的代码规模。新一代的合约引擎,更是考虑只提供有限的、可以定制化的商业规则实现,而不是完全的开放式编程。 作为开发者,是面向“自己的电脑”编程,还是面向“大家的电脑”编程,这就是互联网海量服务系统和区块链最大的不同。 开发者必须切换思维模式,切忌滥用区块链上宝贵的计算、存储、网络资源,避免有意或无意的“公地悲剧”,而是精打细算,从全局权衡,找出协作模式和数据共享里的“最大公约数”,把好钢用到刀刃上。

性能也不是什么大问题

区块链的规模化、并发能力,依旧是业界非常关注的研究方向。互联网海量分布式系统的一些思想,对区块链的优化也有很重要的参考意义,包括平行扩展、分库分表、冷热分离、服务集群、负载均衡等等。 现在我们在研究区块链优化时,常常有一种感觉,就是把之前在互联网业里解决经典“C1000K问题(应对百万级的并发量)”的过程再来一遍。 技术优化方案百花齐放,诸如FISCO BCOS的多群组和并行多链架构,基于DAG的交易并发模型,以及行业热议的交易分片、Layer1/2多层网络、链外通道如闪电网络等。 因为要满足区块链苛刻的信任和安全要求,实施这些方案成为“带着锁链跳舞”的艰辛工作。在不远的未来,在力求保证“信任”、“一致性”、“事务性”、“安全”等大前提下,区块链系统也可以具备可观的可扩展性,突破或逼近“不可能三角”的极限并不是梦。

目前,在保证金融级业务的正确性、稳定性的前提下,FISCO BCOS已经做到在PBFT共识、16个节点规模、采用智能合约实现业务逻辑的条件下,达到单链2万多的TPS(硬件条件比较理想的压测环境)。 如果开启多群组、多链跨链、点对点通道等模式,更有显著的平行扩展效果,满足当前的业务需求已经没什么问题了,只要有足够的硬件资源投入,进行合理的调度,百万千万TPS也不是梦。

最后总结一下,我们认为区块链是一种特殊的“分布式系统”,要透彻地理解区块链,应该清晰地、就事论事地与其他类型分布式系统进行区分,理清各自的本质和设计哲学,避免混淆和迷惑。 说到底,“分布式系统”本身就是一门博大精深的学科,包罗万象,并不能从一而论,而是有太多的精彩和技术路径需要去学习和发掘。

说信任区块链时究竟在信任什么?

作者:张开翔|FISCO BCOS 首席架构师

当前,“区块链,信任的机器(Blockchain:The trust machine)”已经成为了一句口号,紧接着就是“去中心化、群体共识、不可篡改、高一致性、安全和保护隐私”等一系列听起来很厉害的术语。究竟区块链具有多大的魔力能让人如此信任,或者说,我们在说“”的时候究竟信的是什么。

信息,指身份、资产、价格、地理位置等自然属性和行为信息,它并不是先天可信任的,因为信息散乱、不完整,可能虚假,甚至可能会有人利用信息的不对称性牟利。

把信息整理成结构化数据,通过数据校验的方式,保证其在传播中可保持完整性、全网一致性、可追溯性,不会被恶意篡改;通过冗余存储的方式,保证其公开、共享、可访问,保证数据一直有效。那么,这信息本身就可以被“信任”,从而成为大家的“公共知识”,成为全网参与者都认可的“最大公约数”。

如果信息体现着价值,且这些价值被大家认知、认可,能被量化,具有可交易的等价物属性,或可能随着时间增值,甚至得到司法背书承认,这些信息才具有商业意义上的“信用”。

好比我们认识一个人,但不代表我们信任他。然而这个人一贯表现不错,在社群里言行一致,渐渐地获得了大家的信任。这时的信任依旧不等于信用,除非这个人拥有可观的资产,或者其个人历史上有盈利和偿还的能力,未来也大概率能持续持有资产和承兑债务,那么这个人才具备了“信用”。

区块链体系基于算法而不是人治,有望通过其独特的分布式架构、加密算法、数据结构、共识机制等,把信息固化成大家的信任锚点;有望通过技术手段把各种现实世界的资源转换成可兑付的数字资产,并展开一系列多方商业协作的活动,这就是所谓的 “信息到信任到信用”,甚至于因为区块链这个黑科技的、行之有效且难以理解的玄妙,这个“信”字仿佛升华成了“信仰”。

那么我们说信区块链时,信的是什么呢?

信密码学算法

区块链是用算法达成信任的,其中最重要的算法之一,就是密码学。区块链中最基本的密码学应用是HASH摘要、对称加密和非对称加密算法,以及相关的签名验签算法。

HASH算法的旧版本已经被证明可破解而被抛弃了,目前在用的SHA256等算法依旧坚不可破。HASH算法的特性是把一堆数据单向生成一段定长的数据,基本不会发生碰撞,可起到原始数据的“指纹”作用,其单向性不可逆,推不出原始数据,具有一定的抗量子性,是能隐藏原始数据又能在必要时提供校验凭据的最佳方式。

数字签名一般基于公私钥体系,用私钥签名,公钥验签或者反之。数字签名源自密码学的牢靠性,使得不可能有人能伪造别人的私钥签名,所以一个拥有私钥的人可以通过数字签名,对他的资产签名确权,或者在双方交易时,采用对手方的公钥发起交易,将资产转移给对方,对方用自己的私钥才能验签解开,以获得所有权。

https://mmbiz.qpic.cn/mmbiz_jpg/XPgc9ZEIOSQyI8HRE9icDfuX1R4Mb4GvFK8VvPXpkCaE0W2YPKUjYbZ5LV0zuH0UQpnpkVgG9qgHeItRCCmNeicg/640?wx_fmt=jpeg

AES、RSA、ECC椭圆曲线等几种对称和非对称算法广泛地用于数据加解密、安全通信等场景,其安全级别取决于算法本身和密钥长度,当AES使用128~512位密钥,RSA/ECC采用1024甚至2048位密钥时,其保护的数据理论上需要普通计算机上亿年的计算时间才能暴力破解。这些算法在商业、科学、军事领域都经受了考验。

加密学领域里还有同态加密、零知识证明、环签名群签名、格密码等新的方向,目前都处于从理论发展到工程的阶段,都在功能、安全强度、效率方面快速优化中,已经可以看到落地使用的可能性了。同时我们也意识到,密码学通常需要经过长期的发展、验证,稳定后才能获得广泛认可,要么实践中经历了大量考验,要么经过权威机构的审核和认证,才能在生产领域大放异彩。密码学里的理论到工程,常常有很长的时间周期。

加密算法的一个基本哲学是计算成本,当一个算法保护的资产价值,远低于攻破该算法所需的成本时,就是安全的。但如果用一个算法保护一个无价之宝,自然就会有人不计成本地去攻击获利,所以,密码学的安全,也是辩证的、需要量化的。

随着量子计算机等学说的兴起,经典密码学可能会经受一些挑战,但量子计算机的理论完善和工程实现还有待时日,目前来看,基本上我们可以近乎“无条件”相信区块链里已经采用的密码学算法,同时,区块链领域的实践者也在陆续引入各种抗量子的密码学算法,这是一场持续的博弈。

信数据

区块链的数据结构,无非是区块+链。新区块将自己的区块高度、交易列表,和上一个区块的HASH,共同再生成一个HASH做为新区块的标识,如此循环,形成了一个环环相扣的数据链。这个链条里的任何一个字节甚至一个Bit被修改,都会因为HASH算法的特性被校验发现。

同时,区块数据被广播给全网所有参与者,参与者越多,规模效应越强。少数人即使强行修改、删剪自己的区块数据,也很容易被其他人校验出异常并不予采纳,只有多数人认可的数据得以留存和流传。也就是说,数据是大家人盯人的形态盯着的,且存在多份副本,一旦落地,只要链还在,数据就可以永远留存。

基于容易验证的链式数据结构、群体冗余保存、共同鉴证,区块链数据是“难以篡改”的,所有人拿到的数据也都是一致的,信息公开透明,公共知识得以彰显和固化。

从另一个角度看,数据达到信任,但能否达到“信用”还要看数据的价值,也就是数据本身携带的信息,是否能代表有价的资产、有用的信息,诸如身份、交易关系、交易行为、大数据等等,都能代表一定的商业价值。这些数据如果分享出来,足以构建完整的商业基础。

但如果是在过于强调隐私的场景里,大家愿意分享的信息本来就很少,那样就很难达到信用的“最大公约数”。然而,在当前的商业环境里,信息隔离和隐私保护是硬诉求,信息共享和隐私保护成了严峻的矛和盾的关系,除非整个商业关系和商业逻辑出现了革新。

所以,隐私保护相关的研究被大量关注,诸如“多方安全计算”、“零知识证明”的理论大行其道。理论上讲,确实可以做到公布很少的信息就能做到可验证,但其复杂性和计算开销,又是工程层面要去解决的事情了。

信博弈论

区块链中最玄妙的部分是“共识算法”。共识算法的定义是在一个群体中,用一种机制协调大家共同或轮流记账,得出无争议的、唯一性的结果,且保证这个机制可以持续下去。

换句话说,大家一起维护一个账本,选择谁做为记账者?凭什么相信记账者的动作是正确的?怎么防止记账者作恶?如果记账者正确记账如何得到激励?共识机制完整地回答了这些问题。

共识的逻辑是发生在线上的,但实际上,背后是现实世界的竞争博弈。

POW(工作量证明)采用算力去竞争记账者的席位和获得记账者的奖励。现实生活中,为了构建具有竞争力的算力工厂,矿工通常需要研发或购买大量的新型号矿机,运输到有稳定和便宜电力供应地区,消耗大量的电费、网费以及其他运营费用,在被监管时又得举家搬迁,浪迹全球,实际上投入了大量(现实世界的)资金、精力以及背着巨大的风险。如果要在POW竞争中获得稳定可观的收益,投入的资金动辄以亿计,并不亚于办一家企业。

POS和DPOS用权益证明代替了算力消耗,看起来是环保多了。而代表权益的token,除了创始团队自己发行的之外,“矿工们”一般需要通过币币兑换,或者直接法币购买数字币的方式获得,即使是币币兑换,掏出来的币也常常是采用法币购买的,或者至少这些权益都能以法币进行标价,这其实也是现实世界里的财富注入和背书。

然而,和现实的商业关系对比,POW和POS等共识并没有法律和监管机制兜底,也容易受不断变化的博弈形势所影响,比如社群的规模、矿工的更迭、核心技术运营团队的变化。慢慢地,本来有钱和有能力的人,或许会更加有钱和有权力,去中心化的网络可能逐渐变成了卡特尔组织,矿工和技术社区的瓜葛也会不停地掀起波澜,造成分叉、回滚、价格倾轧、对韭当割等现象。

总的来说,人们还是信任区块链上的“自治”,在这种分布式自治里,单个事件(如一笔交易)具有“概率性”,同时全网又追求“最终一致性”(公共账本的一致)。这种短期的概率性和长期的确定性,一定程度上可以达成动态的“纳什均衡”,支撑起链的生态,给人演化出一种玄妙的“信仰”感。

另一方面,联盟链的记账者一般是机构级的角色。联盟链要求记账者身份可知,参与者们经过许可才能接入网络,他们之间是一种合作博弈。联盟链引入了现实世界里的身份信息作为信用背书,如工商注册信息、商业声誉、承兑信用、周转资金,或者行业地位、执业牌照、法律身份等,参与者在链上的一切行为均可审计、追查,也让相关的监管部门在必要时可以有的放矢,精准惩戒,强制执行,具有很高的威慑力。

在这种环境里,联盟链的参与者一起协作维护网络,共享必要的信息,在平等透明、安全可信的网络里开展交易,只需要防止少量记账者的恶意操作风险,避免系统上的可用性风险。因引入了现实世界里必要的信任背书,即使联盟链业务逻辑非常复杂,而信任模型却更直观。

所以,所谓的共识机制,背后依旧是现实世界里财力物力的竞争和信用背书,以及相应行之有效的激励和惩戒机制。

天下并没有免费的午餐,也没有平白无故的爱恨。“信”一个记账者,是信他在现实世界里所投入的成本、付出的代价,以及考虑到整个机制有震慑他的惩罚,相信记账者为了持续的收益和增值,不会无故破坏这个网络。

信智能合约

智能合约是由多产的跨领域法律学者尼克·萨博(Nick Szabo)提出来的。他在发表于自己的网站的几篇文章中提到了智能合约的理念,定义如下:

“一个智能合约是一套以数字形式定义的承诺(promises),包括合约参与方可以在上面执行这些承诺的协议”。

简单地说,可以理解为纸质合约的电子版,用代码实现,无差别地运行在区块链网络的每一个节点上,在共识的作用下执行既定的合约规则。

智能合约一般基于一个特制的虚拟机,使用沙盒模式运行,屏蔽掉可能导致不一致性的一些功能。比如获取系统时间这个操作,在不同的机器上,时钟都可能不同,这就可能导致依赖时间的业务逻辑出现问题。再比如随机数,以及外部文件系统、外部网站输入等,这些都可能导致虚拟机执行结果不同,都会被虚拟机沙盒环境隔离。

如果要采用java语言写合约,要么裁减掉jdk里的相关函数(系统时间、随机数、网络、文件等),要么放到一个有严密权限管控和隔离设定的docker里运行。或者干脆设计一门新的语言,如以太坊的Solidity,只实现特定的指令。又或者放弃掉一些“智能”特性,用简单的堆栈指令序列完成关键的验证判断逻辑。

所以,在区块链上执行智能合约,基于沙盒机制控制,凭借区块链的共识算法,达到全网一致、难以篡改、不可否认等特性,运行结果输出就是全网认可的一份合同,江湖人称“Code is Law”。

然而,只要是代码,就一定有出现bug或漏洞的概率,可能来自底层虚拟机和网络漏洞,更多的可能来自逻辑实现。随便搜一下“智能合约 安全 漏洞”,就有一堆搜索结果,包括溢出、重入、权限错误等,甚至就是低级错误。近年来,这些漏洞已经造成各种资产损失,最著名的是DAO项目代码漏洞、Parity的多签钱包漏洞、某互联网公司的代币交易过程溢出归零……

技术文章可以参考:https://paper.seebug.org/601/

目前,行业里对智能合约的安全也是各出奇招,包括安全公司和白帽子审查、形式化证明、众测等,对安全问题会有一定地改善。如果再出问题,要么是黑客太厉害,或者只能抓程序员祭天了:)

所以,信智能合约,是有条件的,是要信经过严格测试、长时间稳定运行、万一出错还有办法补救(而不是绝望的只能等分叉大招)的合约。联盟链里的智能合约一般是经过严格测试的,上线时会执行灰度验证流程,运营中监控运行过程,且根据治理规则设计事后追责、补救(冲正,调账,冻结…)等措施,还是比较可信的。

信中间人(?)

注意本小节标题打了问号,区块链推崇“去中心或多中心,去中介或弱中介”的运作模式,但是由于目前发展尚未完善,很多场景实际上还是引入了中介,如币币兑换通常需要经过交易所,尤其是中心化的交易所。其交易原理是要求用户把资产存入交易所的帐户里,交易时其实是在交易所的数据库里进行记账,只有在存币或提币时,才会和区块链网络发生交互。

交易所的信任模型和区块链某种程度上是脱钩了,这时,交易所本身的资质,运营方的技术能力、安全防护能力、资产信用和承兑能力,才是用户最需要关心的。一旦交易所出了问题,比如跑路、破产、暗盘操作、监守自盗,基本上散户就只能做韭菜了。

多的不说,参见著名的“门头沟事件”:https://baike.baidu.com/item/Mt.Gox/3611884

所以,相信一个托管者,是一个见仁见智的事情,只是现行的模式里,类似交易所这样的角色还在某些区域运作着。2018年,全球虚拟数字资产交易所有1万多家,其中多少能做到高规格的安全,运营规范,干净……那就看情况了。

最后提一点:联盟链默认是没有公链那种虚拟数字资产交易所的。


区块链领域的细节还有很多,以上先罗列主要的几个点,信任技术,信任共识机制,信任规模化的社群博弈,超过了信任“人”。“人”是一种不确定因素,你可以信任一个你很熟悉很老铁的人,也可以信任一大群有共同理念且有完善机制协作的人,但你不能信任某一小撮居心叵測的人,要不分分钟变成韭菜:)

总结一下,在区块链世界中,人们可以建立以下基本的信心:

  • 我持有的资产和信息,只有我能动用或披露
  • 我可以按公允的规则参与交易,分享信息,转入转出资产
  • 别人给我转过来的资产一定是有效的,不会被重复花费而失效
  • 一旦交易完成,就是板上钉钉的事情
  • 一切已经发生的事情都可验证,可追溯
  • 违反规则的人会损失更大
  • 维护网络的人付出了劳动会有恰当的回报,整个模式可持续

基于这些信心和信任,在合法合规的前提下,人们给网络注入各种资产,开展互补互利、规则透明、公开公平公正的商业行为,将会是一种理想的状态。

区块链世界里不能信什么?

作者:张开翔|FISCO BCOS 首席架构师

上一篇分享了“信任区块链时究竟在信任什么?”(还没看的童鞋,点击标题可直达),这次换个角度,漫步月之暗面,谈谈在区块链系统和业务设计时,不信任什么。

先讲结论: 几乎什么都不能信!建立Don’t Trust,Just Verify的理念,才是通往区块链世界的正确态度。——By我随口说的

不信任其他节点

区块链节点和其他节点会建立P2P通信,共同组成网络,传递区块、交易、共识信令等各种信息。其他节点可能是由不同的机构、不同的人持有,持有节点的人可能是善意,也可能是恶意。即使在善意假设时,节点运行存活的健康度也会受运维水平和资源影响,比如处于一个不稳定的网络里,会偶尔挂掉,会抽风乱发消息,或者硬盘满等原因导致数据存储失败,以及出现其他可能的故障。在恶意假设时,要预设其他节点可能会骗自己或伤害自己,比如传递过来错误的协议包,或者用诡异的指令寻找漏洞进行攻击,或者发起高频垃圾请求,频繁连接然后断开,又或者海量连接占用资源等。

所以节点应该是把自己看成在黑暗丛林里孤身求生存的猎人,必须有“独立自主”、“自给自足”的态度,摆出“不相信其他任何节点”的姿势保护自己。在节点准入时,需要采用证书技术来认证节点身份;在连接控制上,拒绝有异常的连接;采用频率控制对连接次数、请求量等做限制;在协议包格式和指令正确性等方面做验证。自己发出去的信息,不应暴露自己的私有信息,也不期望其他节点一定会给出立刻和正确的响应,必须采用异步处理和校验容错的设计。

节点和客户端互相不信任

客户端,指在区块链网络外,向区块链发起请求的模块,如业务使用的java sdk、钱包客户端等。客户端和节点通过网络端口通信。如果客户端掌握在不受控的人手里,有可能会向节点发起大量的请求,或发送一堆垃圾信息,使节点疲于应对,甚至巧妙地构建漏洞攻击信息,试图越权访问,窃取信息或使节点出错。

同时,从客户端的角度看,节点有可能不响应或响应缓慢,或者返回错误的数据,包括格式错误、状态错误、表示收妥但其实不处理等,甚至别有用心的人会设置一个“假”节点和客户端通信,欺骗客户端。节点做出这些与期望不符的反应,可能使客户端运行出错,功能受损。

为提升节点和客户端的互信,可以为双方分配数字证书,必须通过证书进行双向握手,客户端经过私钥签名才能对节点发起交易类请求,节点应对客户端进行权限控制,拒绝高危的接口调用,不要轻易开放节点管理接口、系统配置接口等。双方对每次通信的数据格式、数据有效性都进行严密校验。双方在交互时也应该进行频率控制,异步处理,对每一个交互进行结果校验,不能预设对方正确处理,必须获取交易回执和处理结果进行确认。

当认为只和一个节点通信并不能保证安全时,客户端可以采用“f+1查询”的思路,尽可能多地和几个节点通信。如果当前链的共识安全模型是“3f+1”,那么,如果从f+1个节点读到的信息是一致的,结果是可以确认的。

不信任区块高度

区块高度是一个非常关键的信息,代表整个链当前的状态。向区块链发送交易、节点间进行共识、对区块和状态的校验等操作都会依赖区块高度。

某个节点在断网或处理速度缓慢时,其区块高度有可能落后于整个链,又或者某个节点恶意伪造数据时,其高度又可能超过整个链。在链出现分叉时,如某一个分叉上的区块高度被另一个分叉超越,落后的分叉就会变得毫无意义。即使在正常的情况下,节点依旧有可能间歇性地落后于整个链一到几个区块,然后在一定时间内才可能追上最新高度。

如在PBFT共识模型里,总数2/3以上节点在同一个高度时,全链就有机会达成共识继续出块。余下的1/3的节点有可能和参与共识的节点高度不同,这时意味着从这个节点读取到的数据,并不是全网最新的数据,只能代表链在该高度时的一个快照。

业务逻辑可以把区块高度做为一个参考值,基于高度做一些判定逻辑,在确定性共识(如PBFT)的链上,采用f+1查询等方法确认链的最新高度,在可能分叉的链上,需要参考“6个区块确认”的逻辑,审慎选取可信的区块高度。

不信任交易数据

交易(Transaction)代表一方向另一方发起了一个事务请求,交易可能导致资产的转移、改变帐户状态或系统配置,区块链系统通过共识后确认交易,使相关的事务生效。交易必须带上发送者的数字签名,交易里所有数据字段都必须包含在签名里,未经签名的字段存在被伪造的可能,不予采信。交易数据在网络上广播时,可以被其他人读取,如交易数据里包含隐私数据,发送者则必须对数据进行脱敏或加密保护。交易可能因为网络原因被重发,或者被其他人保存下来刻意再次发送,造成交易的“重放”,所以区块链系统必须对交易进行防重,避免出现“双花”。

不信任状态数据

区块链的状态(State)数据是由智能合约运行后生成的,理想情况下,每个节点的合约引擎一致、输入一致、规则一致,那么输出的状态就应该一致。但不同的节点可能安装了不同的软件版本,或者合约引擎的沙盒机制不够严密引入了不确定性因素,甚至被侵入、篡改,或者存在其他莫名其妙的bug,都可能导致合约运行输出结果不一致,那么一致性和事务性就无法得到保障。

状态的校验是成本很高的事情,典型的校验方法是使用MPT(Merkle Patricia Tree)树,把所有状态都塞到树里管理起来。MPT树可以把所有的状态归结为一个Merkleroot Hash,节点之间在共识过程中确认交易运行后生成的状态树Merkleroot,确保状态一致。

这棵树结构复杂,数据量大,消耗不少的计算和存储资源,很容易就成为了性能瓶颈。所以对状态的校验需要有更快、更简单,且又稳妥的方案,如结合版本验证、增量Hash验证等算法,辅以数据缓存,可减少重复计算和优化IO次数,能在保证一致性、正确性的同时,有效地提升验证效率。

不信任私钥持有者

采用私钥对交易以及其他关键操作进行签名,再使用公钥验签,是区块链上最基础的验证逻辑。只要私钥被正确使用,这个逻辑是安全的。

但私钥仅仅是一段数据,只依赖私钥则用户是匿名的。在联盟链面对的场景里,需要使用许可型的身份,首先通过KYC、尽调、权威认证等现实世界的验证方式确认身份,然后将身份和公钥绑定并公示,或者结合PKI体系的数字证书发放公私钥,这样私钥对应的身份是可知、可信、可控的。

私钥可能会因丢失、泄漏而被他人盗用,或者因被遗忘导致资产损失。所以在私钥的保存上,需要考虑采用周全的保护方案,如加密存储、TEE环境、密码卡、USBkey、软硬加密机等方案。在私钥的管理上,则需要考虑密钥丢失后如何安全的重置、找回。

加强版的私钥使用思路有几个,比如使用多签、门限签名等方式,每次交易时必须用多个私钥进行签名,私钥可以保管在不同的地方,安全性高,但技术方案和使用体验复杂。

还有一种是交易私钥和管理私钥分离。交易私钥用于管理资产,管理私钥用于管理个人资料,交易私钥可以被管理私钥重置,管理私钥本身则通过门限、分片等算法,分开存储保管,以备重置或找回。

不信任其他链

在跨链的场景里,每条链有自己的资产、共识,链之间的安全模型变得非常复杂,比如一条链上的记账者串通造假,或者链出现了分叉、区块高度回滚,这时如果链外的其他模块和链有不够严谨的交互,都会造成数据不一致或资产损失。如果不同的链采用的还是不一样的平台架构,那么在工程上会更加复杂。

跨链、侧链目前依旧是业界在研究和逐步实现的课题,主要目的是解决链和链之间的通信,进行资产锁定和资产交换,保证整个过程的全局一致性、交易事务性,以及抗欺诈。从A链往B链转移一个资产,必须要确保A链上的资产被锁定或销毁,且B链上一定能增加对应的一笔资产,在双方可能分别出现分叉、回滚的时间窗里,要有机制确保双向的资产安全。

在现有跨链的方案里,存在中继、链间HUB等方式,这些系统的设计本身也要达到高度可信可靠的标准,安全等级应不低于甚至高于所对接的链,同样也应采用多中心、群体共识的体系设计,整体复杂度可算是链的N次方了。

不信任网络层

区块链节点需要和其他节点发生通信,所以必须在网络上暴露自己的通信端口,如果通过公网通信,那么相当于在公网上暴露了自己,很容易遭到类似渗透、DDOS这样的网络攻击。节点必须在网络层保护自己,包括在网关上设置IP黑白名单、设置端口策略、进行DDOS流量防护,且对网络流量、网络状态进行监测,如果突发网络流量或连接数暴增,说不定,就是被人当肉鸡或者正在脱库进行时了。非必要端口,切忌对公网开放,如用于做管理监控的RPC端口,只能对机构内部开放,在进行网络策略设定之前,一定要慎之又慎。

不信任代码

“Code is law”确实是一句响亮的口号,但是在程序员头发掉光之前,他写的代码都可能有bug,只是看写bug快还是修bug快而已。

无论是底层的代码还是智能合约代码,都可能存在技术性或逻辑性的坑,但凡代码产生的数据和指令行为,都需要另一段代码对其进行严格地校验,代码本身也需要进行静态和动态扫描,包括采用形式化证明等技术进行全面地审核验证,以检测可能的逻辑错误、安全漏洞或是否有信息泄露。前段时间有一份公布到github上的某酒店系统的代码,居然包括了mysql的连接用户名密码,且数据库端口居然是向公网开放的,这种坑简直不可想象。

开放出去的开源代码,固然可以被人审查、反馈以提升安全性,也可能被人翻找漏洞、随意修改,甚至恶意埋雷。但总的来说,开源还是利大于弊。在开源社区中,开发者会向项目提交PR(Pull Request)。审核PR是很关键也很繁重的工作,值得安排专家并分配大量时间去做审核。有开源项目的老司机透露,其项目核心模块的PR的审核时间长达经年,否则“加了个功能引入两个bug”那真是得不偿失,更别说如果被植入漏洞埋雷了。

不信任记账者

共识的流程大致可以抽象为,选出记账者,记账者发布区块,其他节点校验和确认。公链里记账可以用“挖矿”的方式进行(如比特币),矿工用大量的算力代价为它自己的诚信背书,又或者是用大量的资产权益抵押获得记账权(Pos和DPos等共识)。在联盟链常用的PBFT/Raft等算法里,记账者列表可以是随机或轮换产生,记账者给出提案,其他投票人多步提交,收集投票。按少数服从多数的原则,一般是2/3以上共识节点同意,共识才能达成。

从系统可用性角度看,记账者有可能出错、崩溃,或者运行缓慢,影响整个链的出块。又或者记账者可以只收录手续费高的交易,抛弃一些交易,导致有些交易总是不能达成。有的记账者还可以凭借算力或暗箱运作,进行“预挖”或者“扣块攻击”,破坏博弈关系……

记账者故障或作恶,超越了共识的安全阈值的话,将直接伤害整条链的价值基础。根据不同的记账模式,记账者需要设计不同的容错、校验、抗欺诈算法,执行激励和惩罚机制,在运行过程中定期检查记账者的健康度,对于无力记账或者作恶的记账节点,全网不接受他们的记账结果,并对其进行惩戒,甚至是踢出网络。


罗列起来还有很多,包括合约、证书、同步等等,每一个模块都有自己的功用和风险点,简直罄竹难书。总之,区块链做为分布式的多方协作的体系,接入了形形色色参与者,整个体系绝不是单个开发者或运营者所能单点把控,“善意推测”在这个领域已经不尽适用,整个世界步步惊心,处处冷箭,只能通过周密的算法和繁杂的流程维系共识和安全,简而言之,没有经过验证的信息,一个字节都不能相信。

比起单一环境里的软件设计,区块链领域的设计思路确实存在颠覆性,开发者要从“做功能,只容错,不防骗”的思维模式里跳出来,带着“怀疑一切”的态度进行设计。

开发者在面向区块链领域时,不能只是思考怎么实现一个功能,而更要去思考整个流程会不会有出错,会不会被人篡改数据、发掘漏洞、攻击系统、欺诈其他参与者。要换位思考自己所实现的功能,会被别人用什么方式使用,在不同的环境会有什么表现,可能造成什么后果。任何收到的信息,任何流程输入、输出,都必须经过严格地校验才能采信,开发者能做到这一点,才算是打开了区块链新世界的大门,才能在连续剧里至少活到第二集。

分布式算法、对称非对称加密、HASH、证书、安全和隐私等技术在区块链领域大行其道,都是为了在保护信息的同时,给信息加上一层又一层的证明和可验证因子,这使得整个系统变得复杂、繁琐,但这是值得的,因为这样才能共同验证,构建“安全”和“信任”。

以上,写给准备跳坑,或已经在坑里的程序员。共勉。

区块链的速度困境:“贵”在信任,“慢”得其所

作者:张开翔|FISCO BCOS 首席架构师

举个例子

数钱,比如数一个亿(是不是好刺激~)

1、如果一个人数,慢,但好在专注,全力以赴,在可见的时间内可以数完。这叫单线程密集计算。

2、如果N个人一起数,每人平分,分头同时数,最后汇总总数,所用时间基本上是第一种情况的1/N,参与的人越多,所需时间就越少,TPS就越高。这叫并行计算和MapReduce。

3、如果N个人一起数,但由于这N个人互相不信任,得彼此盯着,首先抽签选一个人,这个人捡出一叠钱(比如一万块一叠)数一遍,打上封条,签名盖章,然后给另外几个人一起同时重新数一遍,数好的人都签名盖章,这叠钱才算点好了。然后再抽签换个人检出下一叠来数,如此循环。因为一个人数钱时别人只是盯着,而且一个人数完且打上封条和签名的一叠钱,其他人要重复数一遍再签名确认,那么可想而知,这种方式肯定是最慢的。这就叫区块链。

但换个角度,方式1,一个人数有可能会数错,这个人有可能生病或休假,导致没有人干活,更坏的结果是,这个人可能调换假币或者私藏一部分钱,报一个错的总数。

方式2,N个人中会有一定比例数错,也可能其中一个人休假或者怠工,导致最终结果出不来,更可能因为人多手杂,出现部分人偷钱、换假钱、报假数……

方式3,很慢,但是很安全,因为所有人都会盯着全过程进行验算,所以肯定不会数错。如果其中有人掉线,可以换人捡出新的一叠钱继续数,工作不会中断。所有数过的钱上面都有封条和签名,不会被做手脚,万一出错了也可以找到责任人进行追责。这种情况下,资金安全是完全得到保障的,除非所有的参与者都串通一气了。该模式下,参与的人越多,资金安全性就越高。

所以,区块链方案致力追求的是,在缺乏互相信任的分布式网络环境下,实现交易的安全性、公允性,达成数据的高度一致性,防篡改、防作恶、可追溯,付出的代价之一就是性能。

最著名的比特币网络,平均每秒只能处理5~7笔交易,10分钟出1个块,达到交易的最终确定性需要6个块也就是1个小时,且出块过程相当损耗算力(POW挖矿)。号称“全球计算机”的以太坊,每秒能处理的交易数也仅是2位数的量级,十几秒出1个块。以太坊目前也是采用损耗算力的共识机制POW挖矿,会逐步迁移到POS共识机制。这两个网络在粉丝们爆炸性地进行交易时,可能会陷入拥堵状态,大量的交易发出后,一两天甚至更长的时间才会被打包确认。

但在资金安全就是命的场景下,有些事情是“必须”的,所以,即使慢,还是会考虑选择区块链。

区块链为什么慢

分布式系统里有一个著名的理论叫CAP理论:2000年,Eric Brewer教授提出一个猜想:一致性、可用性和分区容错性三者,无法在分布式系统中被同时满足,并且最多只能满足其中两个。

CAP的大致解释

Consistency(一致性) :数据一致更新,所有数据变动都是同步的

Availability(可用性):好的响应性能

Partition tolerance(分区容错性): 可靠性

这个理论虽然有一些争议,但从工程实践中看,和光速理论一样,可以无限逼近极致但是难以突破。区块链系统能把一致性和可靠性做到极致,但是“好的响应性能”方面一直有点被人诟病。

我们面向的“联盟链”领域,因为在准入标准,系统架构、参与节点数、共识机制等方面都和公链不同,其性能表现远高于公有链,但是目前几个主流的区块链平台,在常规PC级服务器硬件上实测,TPS一般是在千级的样子,交易延迟一般在1秒到10秒这个级别。(听说TPS十几万级和百万级千万级区块链已经做出来了?好吧,期待)

笔者曾在大型互联网公司工作多年,在海量服务领域,面对C10K问题(concurrent 10000 connection,万级并发)已经有轻车熟路的解决方案,对一般的电商业务或内容浏览服务,普通pc级服务器单机达到几万TPS,且平均延时在500毫秒以内,飞一般的体验已经是常态,毕竟互联网产品卡一下说不定就会导致用户流失。对于快速增长的互联网项目,通过平行扩容、弹性扩容、立体扩容的方式,几乎能无底线、无上限地面对山呼海啸的海量流量。

相比而言,区块链的性能比互联网服务慢,而且难以扩容,根因还是在其“用计算换信任”的设计思路上。

具体哪里慢呢?

从“古典”区块链的系统内部来看👇

1、为了安全防篡改防泄密可追溯,引入了加密算法来处理交易数据,增加了CPU计算开销,包括HASH、对称加密、椭圆曲线或RSA等算法的非对称加密、数据签名和验签、CA证书校验,甚至是目前还慢到令人发指的同态加密、零知识证明等。在数据格式上,区块链的数据结构本身包含了各种签名、HASH等交易外的校验性数据,数据打包解包、传输、校验等处理起来较为繁琐。

对比互联网服务,也会有数据加密和协议打包解包的步骤,但是越精简越好,优化到了极致,如无必要,绝不增加累赘的计算负担。

2、为了保证交易事务性,交易是串行进行的,而且是彻底的串行,先对交易排序,然后用单线程执行智能合约,以避免乱序执行导致的事务混乱、数据冲突等。即使在一个服务器有多核的CPU,操作系统支持多线程多进程,以及网络中有多个节点、多台服务器的前提下,所有交易也是有条不紊地、严格地按单线程在每台计算机上单核地进行运算,这个时候多核CPU其他的核可能完全是空闲的。

而互联网服务则是能用多少服务器的多少个核,采用全异步处理、多进程、多线程、协程、缓存、优化IOWAIT等等,一定会把硬件计算能力跑满。

3、为了保证网络的整体可用性,区块链采用了P2P网络架构以及类似Gossip的传输模式,所有的区块和交易数据,都会无差别地向网络广播,接收到的节点继续接力传播,这种模式可以使数据尽可能地传达给网络中的所有人,即使这些人在不同的区域或子网里。代价是传输冗余度高,会占用较多的带宽,且传播的到达时间不确定,可能很快,也可能很慢(中转次数很多)。

对比互联网服务,除非出错重传,否则网络传输一定是最精简的,用有限的带宽来承载海量的数据,且传输路径会争取最优,点对点传输。

4、为了支持智能合约特性,类似以太坊等区块链解决方案,为了实现沙盒特性,保证运行环境的安全和屏蔽不一致性因素,其智能合约引擎要么是解释型的EVM,或者是采用docker封装的计算单元,智能合约核心引擎的启动速度,指令执行速度,都没有达到最高水平,消耗的内存资源也没有达到最优。

而用常规计算机语言如C++、JAVA、go、rust语言直接实现海量互联网服务,在这方面常常没有限制。

5、为了达到可容易校验防篡改的效果,除了第一条提到的,区块数据结构里携带数据较多之外,针对交易输入和输出,会采用类似merkle树、帕特里夏(Patricia )树等复杂的树状结构,通过层层计算得到数据证明,供后续流程快速校验。树的细节这里不展开,可以通过网络上的资料来学习其机制。

基本上,生成和维护这种树的过程是非常非常非常非常繁琐的,既占用CPU的计算量,又占用存储量,使用了树后,整体有效数据承载量(即客户端发起的交易数据和实际存储下来的最终数据对比)急剧下降到百分之几,极端情况下,可能接受了10m的交易数据后,在区块链磁盘上可能实际需要几百兆的数据维护开销),因为存储量的几何级数增加,对IO性能要求也会更高。

互联网服务因为基本不考虑分布式互验互信的问题,很少有使用这种树的证明结构,了不起算下MD5和HASH做为协议校验位。

6、为了达到全网一致性和公信力,在区块链中所有的区块和交易数据,都会通过共识机制框架驱动,在网络上广播出去,由所有的节点运行多步复杂的验算和表决,大多数节点认可的数据,才会落地确认。

在网络上增加新的节点,并不会增加系统容量和提升处理速度,这一点彻底颠覆了“性能不足硬件补”的常规互联网系统思维,其根因是区块链中所有节点都在做重复的验算以及生成自己的数据存储,并不复用其他节点数据,且节点计算能力参差不齐,甚至会使最终确认的速度变慢。

在区块链系统中增加节点,只会增加可容错性和网络的公信力,而不会增强性能表现,使得在同一个链中,平行扩展的可能性基本缺失了。

而互联网服务大多是无状态的,数据可缓存可复用,请求和返回之间的步骤相对简单,容易进行平行扩展,可以快速调度更多的资源参与服务,拥有无限的弹性。

7、因为区块数据结构和共识机制特性,导致交易到了区块链之后,会先排序,然后加入到区块里,以区块为单位,一小批一小批数据的进行共识确认,而不是收到一个交易立刻进行共识确认,比如:每个区块包含1000个交易,每3秒共识确认一次,这个时候交易有可能需要1~3秒的时间才能被确认。

更坏的情况是,交易一直在排队,而没有被打包进区块(因为队列拥堵),导致确认时延更长。这种交易时延一般远大于互联网服务500ms响应的标准。所以区块链其实并不适合直接用于追求快速响应的实时交易场景,行业通常说的“提高交易效率”是把最终清结算的时间都算在内的,比如把T+1长达一两天的对账或清计算时延,缩短到几十秒或几分钟,成为一个“准实时”的体验。

综上所述,区块链系统天生就背着几座大山,包括单机内部计算开销和存储较大,背着串行计算的原罪,网络结构复杂冗余度高,区块打包共识的节奏导致时延较长,而在可扩展性上又难以直接增加硬件来平行扩容,导致scale up和scale out两方面,都存在明显瓶颈。

Scale Out(等同scale horizontally):横向扩展,向外扩展,如:向原有系统添加一组独立的新机器,用更多的机器来增加服务容量

Scale Up(等同Scale vertically):纵向扩展,向上扩展,如向原有的机器添加CPU、内存,在机器内部增加处理能力

直面区块链的速度困境,FISCO BCOS的开发者发挥“愚公移山”的精神,努力优化。经过一段时间的努力,已经移山倒海,修出了一条又一条高速通道,使区块链找到了迈向极速时代的路子(详见下篇),这就是我们系列文章要深入解析的内容。

区块链上的交易真的是“点对点”吗?

作者:张开翔|FISCO BCOS 首席架构师

区块链上的交易是点对点的,不需要中心实体来维护交易。这是很多为区块链摇旗呐喊的文章常用的开篇语,斩钉截铁,毋庸置疑,让人有种“一入区块链,宛如面对面”的错觉。

_images/IMG_5005.JPG

字面上的点对点和面对面

其实这句话有一定的迷惑性,如果精确地将“中心实体”定义为类似当下的一些强中介(这里不讨论这种模式的好坏),貌似说得通,毕竟区块链上没有物理上隔断各参与方的中介存在。

_images/IMG_5006.PNG

星型结构和公共账本互连模式对比

但如果到此为止,很容易给人“区块链上的交易就是‘点对点’了”的误导,而模糊了下述事实:实际上任何区块链系统都需要**共识节点,或者称为“记账者”或“矿工”**来打包和确认交易。即使是plasma、闪电/雷电网络等方案,也只是让参与者在特定通道上,直接或间接地交换账务信息流,最终结算或者纠纷处理,还是得回到主链上进行共识完成流程。

“矿工”这种存在,从理念上可以说是“去中心化”的,因为矿工不止一个,所以“没有中心实体”这句话看起来对,但矿工的“去中心化”事实上是一种不确定性,公链上的矿工已经出现了明显的集中趋势,或者类似“21个长老”这种被去中心化信徒诟病的卡特尔组织已经萌芽,其去中心化程度和安全性,某种程度上取决于“社区”博弈或某个大神的运作。

_images/IMG_5007.JPG

围绕着“矿工”的网络生态

而在效率和体验上,“古典”去中心化记账的服务质量很难说是可靠的,一旦出现热点交易状况,那么长达几天的网络堵塞、交易延迟、交易手续费飞涨都是常见的事情,一笔几美金的价值转换,手续费可能要上百美金。同时,做为普通小散韭菜用户,基本上没有机会去投诉或推动你期望的服务质量提升,连客服渠道都是找不到的,毕竟是“去中心化”嘛。

如果用户是使用区块链钱包或客户端交易,那么更别提“点对点”了,非轻客户端类型的钱包客户端,实际上是把交易发给了服务器,服务器来代理,将交易提交到区块链网络上。这还算是好的,只要钱包服务商不闹妖就问题不大。

如果是交易所的客户端,那很多情况就是在交易所的业务数据库里记一笔,连区块链网络都不碰。在缺乏监管、运营水平又参差不齐的“交易所”行业里,这种模式既“中心化”,又面临严峻的道德和安全风险,而“去中心化交易所”则是把中介角色交给了链上智能合约和矿工,理论上更加透明,但在效率、流程方面,依旧说不上是“点对点”。

如果用户干脆自己安装节点,直接加入到网络里,又是什么情况呢?

物理上,因为连接模型的约束(常见的Gossip模型只连接少数“邻居”节点),仅仅从网络层面看,你和交易对手大概率也是不会直连的,而是经过多次转发后,双方才能“碰头”,这是P2P网络的基本特性,并不是“字面上”的“点对点”。如果转发链路上有什么幺蛾子,那也是不确定性的。

一般来说,反复广播转发的网络模型里,在严谨地选择路由、验证连接邻居的前提下,信息到达率还是可以保障的。这里的“点对点”,事实上是广义的桥接型多跳连接(雷电/闪电网络也类似),这种模型不可避免的有一定的传输冗余和延迟率。事实上,被我们熟知的“P2P下载网络”,如果要达到飞一般的速度体验,是依赖普通节点之外的中心化服务器来加速的。

话说回来,就算物理网络建立了点对点连接,交易事务层面上还是要等待记账者(矿工)收拢交易打包,共识确认和同步区块,一笔交易才算完成了,这个过程中“物理直连”并不涉及经济模型方面的意义。

_images/IMG_5008.PNG

综上所述,所谓的“点对点”更像一句口号,其真实含义需要穿透表象来理解。对普通用户来说,他们理解的最朴素的“点对点”,是面对面地把钱塞到对方手里,一次握手即完成交易。而用户把交易发出去,无论下一步是到银行、支付公司、还是“分布式账本”,对普通用户来说,从本质上讲都是一种委托交易

_images/IMG_5009.JPG

一手交钱一手交货

_images/IMG_5010.JPG

字面上的“点对点”交易

_images/IMG_5011.JPG

事实上的分布式账本

在这种格局下,“面对面”是不存在的,私密一定程度上也是不存在的。信息不给到记账者,怎么记账?零知识、环签名等重武器并不是每条链都配备的,追求全面隐私保护的链,计算和架构都会更加复杂,我们会另外分析。

最后,想想当下移动支付各种顺畅体验,银行大额转账秒级短信通知,区块链字面上所谓的“点对点”,真是说得美好,而现实骨感呢。

所以,如果读文章时,看到以吹嘘区块链是“点对点”这个slogan为立论基准,建议读者先理清楚他这里的“点对点”究竟说的是什么语境下的“点对点”。我心目中的分布式账本,真正的意义并不在于“点对点”,而是“可靠可信可控”,通过博弈和协作,多方共同实现的价值网络,让每个参与角色在其中都有其存在感。

_images/IMG_5012.PNG

当区块链系统能达到或接近现有中心化交易系统的速度和交互体验,包括7\24高可用的多活多方记账、秒级延迟确认、快速达成最终一致性、并发处理能力又足够高、交易成本足够低、对公对私都有明确的QoS约定和兜底容灾容错方案,而且区块链先天的分布式架构体系上有群体规模效应,可防作恶、防篡改,可监管,那么才算是**“多中心”信任背书、透明协作**的靠谱分布式交易系统。不喊口号、实干为主的联盟链目标,更贴近这个方向。

怎样让你的区块链更加安全可控

作者:张开翔|FISCO BCOS 首席架构师

随着技术和产业的发展,区块链会被用到实体行业的广泛场景,以及金融业、政务、工业等关键领域。我们更需要重视技术和运作的安全可控,以保护信息安全、财务安全,乃至社会安全。

安全稳定的技术路径

对比任意驰骋的“越野车”,联盟链更像安全可靠的“商旅车”。 正所谓“道路千万条,交规第一条”,出行首先要选择对的路线,也要遵守交规;其次,安全的重要性毋庸置疑,车子硬件达到高安全标准,驾驶员审慎专业驾驶,才能保证乘客的人身和财务安全;最后,车子本身也得可靠和耐用,不能因故障频发而给旅途制造各种麻烦。 在这些基础上,再去追求为乘客创造舒适的环境、友好的体验,并以强劲的性能,高效率地将乘客送到目的地。 同理,联盟链的技术路径兼顾稳定和创新,优先保障“合规、安全、稳定”这三个指标,而后再追求易用友好、高性能,这两者也是很重要的加分项。

_images/IMG_5013.JPG

具体而言,联盟链合规的第一要务是去代币,尤其是金融业,绝不应参与虚拟代币的运作。 在安全方面,对网络、存储、业务等进行安全加固,保证链上多方共同执行,避免出现木桶的短板。 在稳定性方面,严格检测系统质量,遵循“两地多中心”等容灾容错要求,建立可快速反应的监控体系,都是必要的基础工作。

可信可控的几个要点

从系统功能看,区块链是多方参与、重视协作、用算法传递和加固“信任”的系统,针对可信和可控的要求有以下一些要点:

_images/IMG_5014.JPG

身份可知

拒绝匿名,任何加入这个系统的人或者节点,都需要经过KYC(Know Your Client)环节,依托PKI体系(公开密钥基础建设,Public Key Infrastructure)等技术,向网络认证自己。

权限可配

针对不同的角色,分配不同的系统权限,避免越权访问,或者出现“既当裁判又当运动员”的情况。

交易可控

交易调用的是智能合约,智能合约是用代码写成的,目前“Code is law”依旧是个理想值,交易可控的要点是,事先检查合约逻辑,执行时避免异常和错误,对不符合预期的指令进行干预,在出错后要能冲正、补账、纠正。

隐私可保

个人信息、财务情况、交易明细等都属于个人隐私。机构也有自己的商业隐私,如风控规则、重大交易细节等。在多方共享数据的同时,还需要保护隐私不会泄露。

历史可验

区块链先天是一种“只增不删”的数据库。所有历史交易以及产生的数据都可以记录在区块链上,需要妥善管理链上海量数据,对导出到链外的数据,也要有方法和链上对照,确保数据是完整的、可验证的。

监管可达

许多行业都有对应的监管部门,金融更是强监管行业。区块链系统要做到的是,为监管者提供相关功能接口、访问权限,并提供完整的、可校验的数据以供审计分析。

联盟链角色和治理

联盟链治理模式目前也是正在探讨的热点,总的来说,联盟链是由联盟一起建设、使用和管理的,下面将按这三个维度角色进行梳理归纳。

_images/IMG_5015.JPG

运营委员会可以由联盟里的部分或全部机构一起组成,其本身是“多中心化”的,并不是独家管理。 运营委员会共同对联盟链的各种事务进行讨论和决策,包括联盟链的准入审批、规则制定和执行、日常运营等。比如,目前有的联盟链会把“部署合约”的权限收敛到委员会,这样可以保障链上交易规则的有效性和合理性。

监管方则独立于运营委员会之外,可以由主管部门、权威机构、行业协会等角色承担,运营委员会制定的规则,本身应该符合监管要求。

在链上进行交易、参与共识记账、负责清结算等事务的角色,都归纳为链的“使用者”。他们遵循运营委员会制定的规则加入联盟链,用自己的私钥对区块和交易签名,基于链上智能合约和共识机制驱动,高效的完成协作。

开发者负责研究和开发联盟链的底层平台,根据业务需求构建应用系统和编写智能合约,这些内容经过运营委员会审核之后,再由运维团队发布上线。

运维团队还负责配置软硬件环境的参数,关注监控告警,进行版本升级,长期维护系统的稳定性和可持续性。

可见,“建设”和“管理”的角色,都不会直接参与到业务交易流程中,而“使用”角色基于健壮高效的平台进行协作,并接受运营委员会和监管方的管理监督。 同时,无论对哪一个角色,都会进行权限控制和操作审计,如果账号丢失、私钥泄露,也能通过链上治理的方式进行找回和重置。 这样的治理体系是职责清晰、各司其职、安全可控的,既能保证链上治理的合理性、有效性、安全性,也能保护参与者的权益,使得大家可以高效协作。

贯通全程的安全、可控、合规

_images/IMG_5016.JPG

从业务流程上看,区块链上的业务参与方众多、流程复杂、资产的生命周期漫长,应该确保端到端的业务可控,否则到了承兑结算时,才发现资产是无价值的,那就难以挽回了。

首先,来自实体世界的人、物、资产,要经过严格鉴定才能上链。 这个环节还是要依靠有公信力的权威机构把关,如中国公民的身份来自公安认证,房产的产权则需要国土局认证,以此类推。还可以依托物联网、基因技术、人工智能分析等技术,对数据进行交叉比对、深度分析,提升鉴别效率和准确度。 资产发行上链后,要检查这笔资产的权责是否明确,定价是否公允,资产的交易规则是否公开透明,这个步骤需要由多方共同完成并达成共识。 对刚上链的资产设置一个“预发布”的状态,链上多方检验后才能使资产“生效”,同时结合链下措施,锁定资产防“双花”,不失为一种好的实践。

然后,资产可以进入“点对点交易”的高效流转环节,交易过程可以采用多方签名、拜占庭容错等机制,避免少数人作恶。

同时,设置关键检测点,对交易进行实时监测,如发现可能的异常,如高频交易、异常出价、违反交易规则的行为等,则由权限的管理者调用管理接口及时制止和纠正。

当交易正常完成,即进入了清结算和承兑环节,在没有原生代币的联盟链上,需要稳妥的对接链下支付系统,以及物理世界的执行体系等,以保证承兑和执行。 即使前面的环节都顺利完成,因为各种复杂的因素,业务还是有小概率会出现错误或纠纷,那么监管审计和司法仲裁就可以发挥作用了,目前基于区块链实现业务的存证、司法仲裁也是很常见的方式。 可见,端到端地进行设计,并重视链上链下的结合,有助于完整、闭环地管理资产生命周期。

可监管,可干预

关于“区块链业务难以监管”的说法,其实说的是匿名和跨监管主体运作的公链。 部分公链参与者是匿名的,其网络遍布全世界,链上发行虚拟代币且没有稳妥的治理方式,常常只能靠硬分叉治理,使得难以定位链上行为人、难以套用对应的法律法规、也无法针对某一个节点实施监管。

_images/IMG_5017.JPG

而具备一系列可信可控特性的联盟链,因为身份可知,使得参与者在链上的行为具有确定性,诚实合规的行为可以在业务合作中获得回报,而恶意不轨的行为则无法否认抵赖。区块链特有的链式数据结构具有很强的自校验性,数据难以篡改,可以全程追溯。监管机构可以安装节点接入业务链,或用数据同步的方式获得区块链上的数据,得到完整无错漏的数据集合。

然后,将这些数据纳入大数据挖掘分析,可以实现端到端的、跨机构和交易全流程的监管审计。同时,进行风控建模,预判业务风险,以便识别和裁决恶意行为。

联盟链通过权限配置和业务智能合约的设计,可以实现细粒度的控制功能,如将合约的发布权限收拢到委员会或监管方。合约在运行时置入监管规则检查,发现违规交易立刻终止或回滚,针对用户和节点制定黑名单,采取冻结违规账号、踢掉恶意节点、进行冲正补账等措施。区块链具有全网事务一致性的特点,所有节点基于共识机制协同工作,监管措施可以一点接入,全网生效,这为全网协同的稳健运营、反洗钱等提供了行之有效的可操作性,可以达成“穿透式监管”的效果。

所以,为区块链加入可信可控特性的联盟链,和公链相比,可以呈现截然不同的可监管效果。

技术中立,风控优先

作为区块链和金融从业者,我们在研究技术之余,也应该关注政策的走向。2017年七部委发文禁止“虚拟货币以及代币融资”以来,国际国内都相继出台一系列的规定,对“冒区块链之名行违规之实”的行为明确说不。

_images/IMG_5018.JPG

“技术中立”和“风控优先”可说是非常经典的总结,这两个词来自工信部给人大代表的答复函,摘录如下:“银保监会督促指导银行机构坚持‘技术中立’和‘风控优先’原则,在厘清金融业务本质特征的基础上,遵循同等的业务规则和风险管理要求,积极推动区块链等金融科技在银行业的应用,采取有效措施防控区块链技术风险。”不同行业都有各自的本质规律和基本守则,如互联网积分,虽然看起来不是“钱”,但其实在业务上积分是有清晰的会计核算规则的,监管部门也对积分的运营提出了“三条底线”要求(如上图),并不会因为用了区块链,运作上就可以突破这三条底线。

再如,有关部门已经发布的“隐私保护标准”,以及欧洲的GDPR等条例,定义了清晰的隐私保护要求,技术运用和管理规范,让隐私保护有法可依。无论采用什么技术,最终都要回到业务本质上。技术运用的初衷,应该是使业务更加健康和可持续发展,并严控和降低其中的风险,而不是带来新的风险,尤其是金融业,有句话说的很好:“金融不只是经营资金,更多是在经营风险。”

总结一下,在区块链技术和应用探索的过程中,我们一直遵循几个基本原则:

技术过硬:深耕关键核心技术,持续创新的同时追求安全可控。

广泛开源:开源社区本身有茂盛的生命力,而且还能通过开放源代码,获得社区协助,提升安全性。

善良规范:当我们把技术用于实体领域时,就有可能牵涉国计民生,影响人们身家性命,所以,有着善良的意图和规范合规运作,能使世界因为创新而变得更好一点。


一文说清“链上”和“链下”

作者:张开翔|FISCO BCOS 首席架构师

什么是“上链”?什么数据和逻辑应该“上链”?文件能不能上链?链上能不能批量查数据?“链下”又是什么? “链上”、“链下”诸多问题,一文说清。

什么是“链上”和“链下”

_images/IMG_5019.PNG

区块“链”的链,包含“数据链”和“节点链”。数据链指用链式结构组织区块数据,构成数据校验和追溯的链条;“节点链”指多个节点通过网络连接在一起,互相共享信息,其中的共识节点则联合执行共识算法,产生并确认区块。

交易“上链”的简要过程如下:

  1. 记账者们收录交易,按链式数据结构打包成“区块”。
  2. 共识算法驱动大家验证新区块里的交易,确保计算出一致的结果。
  3. 数据被广播到所有节点,稳妥存储下来,每个节点都会存储一个完整的数据副本。

交易一旦“上链”,则意味着得到完整执行,达成了“分布式事务性”。简单地说,就像一段话经过集体核准后在公告板上公示于众,一字不错不少,永久可见且无法涂改。 “上链”意味着“共识”和“存储”,两者缺一不可。交易不经过共识,则不能保证一致性和正确性,无法被链上所有参与者接受;共识后的数据不被多方存储,意味着数据有可能丢失或被单方篡改,更谈不上冗余可用。 除此之外,如果仅仅是调用接口查询一下,没有改变任何链上数据,也不需要进行共识确认,则不算“上链”。

或者,某个业务服务本身和区块链并不直接相关,或其业务流程无需参与共识,所生成的数据也不写入节点存储,那么这个业务服务称为“链下服务”,无论它是否和区块链节点共同部署在一台服务器,甚至和节点进程编译在一起。 当这个业务服务调用区块链的接口发送交易,且交易完成“共识”和“存储”后,才称为“上链”;如果这个交易没有按预期被打包处理,那么可以叫“上链失败”。

事实上,几乎所有的区块链系统,尤其是和实体经济、现实世界结合的区块链应用,都需要链上链下协同,用“混合架构“来实现,系统本身就包含丰富的技术生态。

*注1:交易(transaction)是区块链里的通用术语,泛指发往区块链,会改动链上数据和状态的一段指令和数据

*注2:本节描述的是简要的模型,在多层链、分片模型里,流程会更加复杂,事务划分更细,但“共识”和“存储”才叫上链的基本原则不变

交易之轻和“上链”之重

目前区块链底层平台逐步趋于成熟,性能和成本已经不是什么大问题,只是以下几个开销是因“分布式多方协作”而先天存在的:

  • 共识开销:主流共识算法里,PoW(工作量证明,也就是挖矿)消耗电力;PoS(权益证明)要抵押资产获得记账权;PBFT(联盟链常用的拜占庭容错算法)记账者要完成多次往返投票,流程步骤繁杂。
  • 计算开销:除了加解密、协议解析等计算之外,在支持智能合约的区块链上,为了验证合约的执行结果,所有节点都会无差别地执行合约代码,牵一发而动全身。
  • 网络开销:与节点数呈指数级比例,节点越多,网络传播次数越多,带宽和流量开销越大,如果数据包过大,就更雪上加霜。
  • 存储开销:和节点数成正比,所有的链上数据,都会写入所有节点的硬盘,在一个有100个节点的链上,就变成了100份副本,如果有1000个节点,那就是1000份。

也许有人会说:“这就是‘信任’的成本,值得的!”我同意。只是理想无法脱离现实,毕竟硬件资源总是有限的。 想象一下,如果每个交易都是一个复杂科学计算任务,那么每个节点CPU和内存会跑满;如果每个交易都包含一个大大的图片或视频,那么全网的带宽,以及各节点存储很快被塞爆;如果大家都敞开来滥用“链上”资源,“公地悲剧”就不可避免。 调用API发个交易是很容易的,而链上的开销就像房间里的大象,难以视而不见。作为开发者,需要正视“交易之轻和链上之重”,积极“上链”的同时减少不必要的开销,找到平衡之道。

*注1:常规联盟链节点参考配置:8核/16G内存/10m外网带宽/4T硬盘,不考虑“矿机”和其他特种配置。土豪随意,俗话说“钱能解决的问题都不是问题,问题是…”

*注2:本节暂未讨论“局部/分片共识”,也不探讨“平行扩容”的情况,默认假定全网参与共识和存储

让“链上”归链上,“链下”归链下

开销只是成本问题,而本质上,应该让区块链干自己最该干的事情。链上聚焦多方协作,尽快达成共识,营造或传递信任,将好钢用到刀刃上;那些非全局性的、无需多方共识的、数据量大的、计算繁杂的…通通放到链下实现,一个好汉三个帮。

如何进行切割?在业务层面,识别多方协作事务和数据共享中“最大公约数”,抓住要点痛点,四两拨千斤;在技术上,合理设计多层架构,扬长避短、因地制宜地运用多种技术,避免拿着锤子看什么都是钉子、一招打天下的思维。

为避免过于抽象,下面给出几个例子。

*注:每个例子其实都有大量的细节,考虑篇幅,这里做概要介绍,聚焦链上链下的区别和有机结合

文件能不能上链?

_images/IMG_5020.PNG

这是个非常高频的问题,经常被问到。这里的文件一般指图像、视频、PDF等,也可以泛指大体量的数据集,上链可信分享的目的,是使接受者可以验证文件的完整性、正确性。 常见的场景里,文件共享一般是局部的、点对点的,而不是广播给所有人,让区块链无差别地保存海量数据,会不堪重负。所以,合理的做法是计算文件的数字指纹(MD5或HASH),并与其他一些可选信息一起上链,如作者、持有人签名、访问地址等,单个上链信息并不多。 文件本身则保存在私有的文件服务器、云文件存储、或者IPFS系统里,这些专业方案更适合维护海量文件和大尺寸文件,容量更高、成本更低。注意,如果文件的安全级别到了“一个字节都不能泄露给无关人等”的程度,那么应慎用IPFS这种分布式存储的方案,优选私有存储方式。 需要分享文件给指定的朋友时,可以走专用传输通道点对点的发送文件,或者授权朋友到指定的URL下载,可以和区块链的P2P网络隔离,不占用区块链带宽。朋友获得文件后,计算文件的MD5、HASH,和链上对应的信息进行比对,验证数字签名,确保收到了正确且完整的文件。 这种方案,文件在链上“确权”、“锚定”和“寻址”,明文在链下传输并与链上互验,无论是成本、效率、还是隐私安全都取得了平衡。

怎么批量查询和分析数据?

_images/IMG_5021.PNG

对区块链上的数据进行分析是自然的需求,比如“某个账户参与哪些业务流程、完成了多少笔交易、成功率如何”,“某个记账节点在一段时间内参与了多少次区块记账、是否及时、有否作弊”,这些逻辑会牵涉到时间范围、区块高度、交易收发双方、合约地址、事件日志、状态数据等维度。 目前区块链底层平台一般是采用“Key-Value”的存储结构,其优势是读写效率极高,但难以支持复杂查询。 其次,复杂查询逻辑一般是在区块生成后进行,时效性略低,且并不需要进行多方共识,有一定的“离线”性。 最后,数据一旦“上链”,就不会改变,且只增不减,数据本身有明显特征(如区块高度、互相关联的HASH值、数字签名等)可以检验数据的完整性和正确性,在链上还是链下处理并无区别,任何拥有完整数据的节点都能支持独立的复杂查询。

于是,我们可以将数据完整地从链上导出,包括从创世块开始到最新的所有区块、所有交易流水和回执、所有交易产生的事件、状态数据等,通通写入链外的关系型数据库(如MySQL)或大数据平台,构建链上数据的“镜像”,然后可以采用这些引擎强大的索引模型、关联分析、建模训练、并行任务能力,灵活全面地对数据进行查询分析。 区块链浏览器、运营管理平台、监控平台、监管审计等系统,都会采用这种策略,链上出块,链下及时ETL入库,进行本地化地分析处理后,如需要和链上进行交互,再通过接口发送交易上链即可。

复杂逻辑和计算

_images/IMG_5022.PNG

和复杂查询略有不同,复杂逻辑指交易流程中关系复杂、流程繁杂的部分。 如上所述,链上的智能合约会在所有节点上运行,如果智能合约写得过于复杂,或者包含其实不需要全网共识的多余逻辑,全网就会承担不必要的开销。极端的例子是,合约里写了个超级大的数据遍历逻辑(甚至是死循环),那么全网所有节点都会陷入这个遍历中,吭哧吭哧跑半天,甚至被拖死。

除了用类似GAS机制来控制逻辑的长度外,在允许的GAS范围内,我们推荐智能合约的设计尽量精简,单个合约接口里包含的代码在百行以上就算是比较复杂的了,可以考虑是否将一部分拆解出去。 拆解的边界因不同业务而异,颇为考验对业务的熟悉程度。开发者要对业务进行庖丁解牛式地分层分模块解耦,仅将业务流程中牵涉多方协作、需要共识、共享和公示的部分放到链上,使得合约只包含“必须”“铁定”要在链上运行的逻辑,合约逻辑“小而美”。

一般来说,多方见证的线上协同、公共账本管理、一定要分享给全体的关键数据(或数据的HASH)都是可以放到链上的,但相关的一些前置或后续的检验、核算、对账等逻辑可以适当拆解到链下。 一些和密集计算有关的逻辑,宜尽量将其在链下实现,如复杂的加解密算法,可以设计成链下生成证明链上快速验证的逻辑;如果业务流程中牵涉对各种数据的遍历、排序和统计,则在链下建立索引,链上仅进行Key-Value的精准读写。 其实,现在但凡看到合约里有用到mapping或array,我都会强迫症地想想能不能把这部分放链下服务去,个人比较欣赏“胖链下”和“瘦链上”的设计取向。 强调一下,精简链上合约逻辑,并不全是因为合约引擎的效率问题,合约引擎已经越来越快了。核心原因还是在发挥区块链最大功效的同时,避免“公地悲剧”。开发者拿出计算和存储成本最小的合约,有着“如无必要勿增实体”的奥卡姆剃刀式美感,更是对链上所有参与者表达尊重和负责任的态度。

即时消息:快速协商和响应

_images/IMG_5023.PNG

受队列调度、共识算法、网络广播等因素约束,“上链”的过程多少都会有一点延时。采用工作量证明共识的链,时延在十几秒到10分钟,采用DPOS、PBFT的共识,时延可缩短到秒级,此外,如果遇到网络波动、交易拥挤等特殊情况,时延表现会有抖动。 总的来说,对照毫秒或百毫秒级响应的瞬时交互,“上链”会显得些许“迟钝”。比如去超市买瓶水,支付后肯定不能站在那里等十几秒到十分钟,链出块确认后才走吧(略尴尬)。

对类似场景,宜结合链上预存和链外支付,在链下的点对点通道实现高频、快速、低延时的交易,链下确保收妥和响应,最后将双方的账户余额、交易凭据汇总到链上,在链上完成妥善记账。著名的“闪电网络”就类似这种模式。

另外,有些商业场景会先进行多轮的订单撮合、竞价拍卖或讨价还价。一般来说,这些操作是发生在局部的交易对手方之间,未必需要全网共识,所以也可以通过链下通道完成,最后将双方的订单(包含双方磋商结果、数字签名等信息)发送到链上,完成交易事务即可。

举个下快棋的例子,棋手的每一步棋并不需要实时上链,双方只管啪啪地下,裁判和观众只管围观,在棋局结束时,比如总共下了一百手,那么将这一百手的记录汇总起来,连同输赢结果上链,以便记录战绩分配奖金。如果要复盘棋局详情(如视频),可以参考上文提及的链下文件存储模式,用专用的服务器或分布式存储实现。

针对类似需求,在FISCO BCOS底层平台中,提供了AMOP(链上信使协议),利用已经搭建起来的区块链网络,在全网范围实现点对点、实时、安全的通信。基于AMOP,可以支持即时消息、快速协商、事件通知、交换秘密、构建私有交易等,推荐。

*注:【AMOP】详情可参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/amop_protocol.html

链下信息如何可信上链?

_images/IMG_5024.PNG

先看一个典型问题:“智能合约运行中要使用链外信息,怎么办?” 比如,链上有个世界杯决赛竞猜游戏,但世界杯不可能在链上踢吧;或者需要参考今天的天气,天气显然不是链上原生信息,应该从气象局获取;在跨境业务中,可能用到法定汇率,而汇率一定是来自权威机构的,不能在链上凭空生成。 这时候就要用到“预言机(Oracle)”,由一个或多个链下可信机构将球赛、天气、汇率等信息写到链上的公共合约,其他合约统一使用这份经过共识确认的可信信息,不会出现歧义。考虑到安全和效率,预言机(Oracle)会有多种具体做法,实现起来相当有趣。

_images/IMG_5025.PNG

更进一步的灵魂拷问是:**“如何保证上链的数据是真实的?”**坦率地说,区块链并不能从根本上保证链下数据的可信性,只能保证信息一旦上链,就是全网一致且难以篡改的。而区块链跟实体经济结合时,势必要面对“如何可信上链”这个问题。 如资产相关应用,除了进行人员管理之外,还要“四流合一”,即“信息流、商流、物流、资金流”互相匹配和交叉印证,会使业务流程更加可信。这些“流”常常发生在链下现实世界,要把控它们,可能会用到物联网(传感器、摄像头等)、人工智能(模式识别、联邦学习等)、大数据分析、可信机构背书等多种技术和方式,这已经远远超出了区块链的范围。

所以,本节的命题其实是:区块链如何和数字世界里的技术广泛结合,更好地发挥自身多方协作、营造信任的作用。 随着数字世界的发展、尤其“新基建”的强力推动,我们相信广泛的数字化能在保护隐私的前提下,降低信息采集和校验的成本,采集的数据会越来越丰富。 如在使用、转移、回收实体物资时,及时采集监测,甚至是多方、多路、多维度立体化的采集监控,并上链进行共识、公示、锚定,链上链下交叉验证,这样就可以逐渐逼近“物理世界可信上链”的效果,逻辑会更严密,更具有公信力,数据和价值流通会更可靠,协作的摩擦更低。

“链上”还是“链下”治理?

_images/IMG_5026.PNG

“治理”即制定行业联盟和业务运作规则,确保规则的执行,处理异常事件,奖励和惩戒参与者等。 以理想化的标准,似乎应该实现链上治理,通过代码决策、制定和执行规则,出错时系统具有“自修复”的“超能力”。实际上,完备的链上治理过于复杂,实现起来很有挑战性,尤其在需要达成现实世界法律法规的执行力时,纯链上的治理往往力不从心。 再多想一步:如完全依赖代码,万一代码本身有BUG、或者要“改需求”呢?链下的决策者、开发者如何发现和介入? 所以,“Code is Law”还是个理想化的目标,链下治理不可或缺。

联盟链参与者们组成管理委员会,在现实世界里进行民主集中制的讨论和决策,共同制定规则,采用多签、工作流的方式一起发起治理动作,调用区块链接口上链。 在链上,包括区块链底层平台和智能合约在内,都会内置一系列的决策和控制点,如支持多方投票决策,具备从业务层穿透到底层的准入和权限控制能力,可修改业务和节点的参数,能应对异常情况的重置账户,对错账进行冲正调账等等。 治理动作和结果经过共识确认,在链上全网生效,公开透明,接受广泛监督,彰显其合理性和公正性。必要时还可以引入监管方和司法仲裁。 反过来,联盟链上的数据,具备身份可知、难以篡改、无法否认且可全程追溯等特点,可为链下治理决策提供完备的数据基础,也便于为链下实际执行提供可信的凭据。所以,链上和链下有机结合,有助于设计完备、可控、可持续的治理机制。

如何做到“上” “下”自如

或许有人会说:“这链上链下什么的太复杂了,我就想用区块链!” 我认为这个说法很对。说到底,用户就想要一条趁手的“链”。作为开发者,我们要打造灵活的、插件化的系统架构,实现各种能力,什么数据导出、文件存储和传输、密集计算、数据采集和异步上链、治理监管、一键部署……按需取舍后,打包起来开箱即用,实际上提供了“基于区块链的一系列能力”。 最终呈现的“链”,除了节点之外,还有区块链浏览器、管理台、监控和审计系统、业务模板、APP/小程序等一系列交互入口,用户只需动动鼠标,点点页面,调调接口,一站式体验到一个完整的区块链应用。用户会觉得:“这就是区块链”,无需再分“链上”和“链下”,浑然一体。

说到这里,推荐一个我认为非常棒的设计:分布式身份标识(DID)。 DID是一套涵盖了分布式身份管理、可信数据交换的规范。权威机构为用户完成KYC,颁发凭据。用户将身份标识的摘要公布到链上,而将自己隐私数据存在链下(这一点非常重要)。 使用时,用户采用“明确授权”和“选择性披露”的策略,仅需出示少量的信息或加密证明,与链上数据进行对照校验,即可证明用户凭据和数据可信性,达成了“数据多跑路,用户少跑腿”、保护了用户隐私的可喜效果。 这种设计很好地将链上链下结合起来,逻辑闭环自洽,并不因为数据存在链下,就削弱了链上的功效,反而使得链的授信模型更为重要。 DID规范定义了语义清晰、层次分明的数据结构,以及通用的交互协议。开源项目WeIdentity完整地实现了DID协议,并提供丰富的周边支撑工具和服务,值得参考。

*注:【WeIdentity】详情可见:https://fintech.webank.com/weidentity

结语

链漫漫其修远兮,吾将“上下”而求索。在未来,“可信的”区块链将越来越多地和人们日常生活、实体经济联动,步入寻常百姓家。作为从业者,保持开放的心态,积极而创新地将区块链与更多技术结合,无论运作于链上还是链下,只要能解决问题、创造价值,就是一条好链。

如何解释“我篡改了区块链”这个问题

作者:张开翔|FISCO BCOS 首席架构师

区块链数据“全局一致”、“难以篡改”这两个特性已经广为人知,是区块链营造“信任”的基石。为了达到这两个效果,区块链的共识、同步、校验等技术细节足可大书特书,而本文要从“我篡改了区块链数据”讲起。

“我篡改了区块链数据”

FISCO BCOS开源联盟链社区现在相当活跃,每天都会产生大量的讨论,大家也会饶有兴趣地研究和挑战区块链如何做到“难以篡改”。我们注意到,尤其在FISCO BCOS支持MySQL数据库作为数据存储引擎后,隔一阵子就有同学在群里问:“我手动修改了我节点连接的数据库里某个状态数据,这是不是就是篡改了区块链数据呢?”

直观地举个例,如链上有个智能合约,管理特定资产余额,在数据库合约表里,经过共识的Alice的余额本来是100,这时有人打开MySQL客户端,找到那个合约对应的Table,把Alice的余额update成10000

这时他表示:“你看,我调用合约的查询接口,查出来Alice的余额确实是10000,这就不对了嘛,而且,链还在出块,根本不防篡改嘛!”。

初步分析和解答

为何这类问题最近多起来了?我们分析了下,猜想主要是由于MySQL数据库用户基础良好,体系比较成熟,给用户提供友好的命令行或图形化交互工具,FISCO BCOS提供了一种Table风格的合约开发模式,表结构设计得清晰直观,对于用户来说,一方面理解和管理起来更容易了,另一方面顺手update甚至delete一下都是小意思。

下图仅为示例数据,采用KVTable合约方式,创建了名为t_kv_node的合约表,系统自动加了“u_”前缀,可见,这个表结构和数据一目了然。

_images/IMG_5027.PNG

而之前只采用LevelDB或RocksDB作为存储引擎,这两个文件型数据库交互工具比较少,在用户面前的存在感不强,操作相对晦涩,主要通过API编程访问,数据用肉眼难以辨别的Hash key寻址,动手修改数据库的case就少了一些(但也不是不可能)。

所以,热点问题浮出水面,前提是用户可以更方便地修改底层数据了,而不是这个问题之前不存在。这时我们会建议用户试一下,针对Alice的余额,发起一个交易,比如给Alice充值,或者让Alice转账操作,这时,修改过数据的节点将无法参与共识。因为该节点上算出来的Alice的余额和其他节点结果不同,其他节点依旧按100的余额进行计算,而不是10000,显然结果是对不齐的。

_images/IMG_5028.PNG

复习下PBFT的容错模型:定义“f”为可容错节点数,网络中共识节点总数应等于或多于3f+1。即链上有4个共识节点时,可容错的f=1,共识节点总数为7个时,f=2,以此类推。

如果未修改过数据的节点数满足PBFT要求的2f+1的数量,链依旧可以出块。但被修改过的节点,一旦有交易涉及脏数据,就像踩到了雷一样,从此再无法与链共识、同步,相当于被抛弃了。这种节点可以称为“拜占庭节点”,即作恶或出错的节点,具备节点准入控制能力的联盟链甚至会将拜占庭节点隔离出去。

还有一种可能性是,手动修改了数据库里的数据,但节点内存里还刚好缓存了一份副本,并没有被修改,所以通过节点对这个数据的查询、交易还是正常的,甚至会用正确的结果把数据库里被篡改过的数据覆盖掉,不过这是概率性事件,取决于缓存的大小和当时包含的数据项。

*注:对于采用PoW或其他共识机制的链来说,容错模型有所不同,但在容错范围内的少数节点被篡改,也不会影响链的共识。

“能否篡改整个联盟链”

有的同学可能会继续刨根问底:“那我多修改几个节点的数据是不是就篡改了?”,一般提出这个问题的同学是面向他自己部署的开发测试环境,所有节点都在他手上,所以可以随便改。

在真实的联盟链环境上,节点分别掌握在不同机构手里,要修改,首先得侵入他人的网络、获得服务器和数据库权限、发起修改再全身而退。事实上,在注重安全防护的商业化环境里,这是非常艰难、几乎不可能做到的事情。

_images/IMG_5029.PNG

从机构粒度来看,单个机构掌握的节点数,应该低于共识算法可容错的数量。比如,链上总共有7个共识节点,那么单个机构掌握的共识节点不应多于2个,这样可以避免机构内部强行修改自己掌握的节点数据,或一个机构的所有节点都意外出错、掉线(比如机房光纤都被挖断了),导致链无法出块。

_images/IMG_5030.PNG

真的没有办法防“本地篡改”吗?

考虑区块链数据本地验证的机制,比如区块之间的Hash关系、状态的Merkle树结构、共识节点的签名等,按数据的互验关系顺藤摸瓜进行检测,似乎有一定概率可以本地检测出数据异常。

但进一步想,对某个数据的查询,区块链的本地校验范围是有限的,一般不会超出单个区块或者一棵merkle树,所以如果篡改者比较熟悉区块链数据的结构和本地校验逻辑,也可以顺着数据校验关系,从状态值开始,把merkle树、区块Hash等关键数据全部改掉。

甚至更彻底地,从创世块开始,把所有的区块、系统配置(对于PoW,可以修改挖矿难度以加速出块)、PBFT的共识者列表等等,都按他的逻辑改一遍,这样这条本地数据链依旧是校验自洽的,只是无法和其他节点共识了。

这种改法,听起来需要不少力气活,但对于一个有决心、有能力的篡改者来说,改改本地数据这个事情其实并不难,难的只是去改别的机构数据而已。

_images/IMG_5031.PNG

到了这个份上,就相当于一个人铁了心要“骗自己”,那神仙都没有办法了。一旦把本地数据修改的权限交给了不适当的人,最坏情况下,整条链没有一个字节是对的。

但是,本地数据再错,也只会影响自己,影响不了别人,一旦和其他节点进行共识,就会被发现,甚至被惩戒,整个效果会有一点掩耳盗铃的意思。

“为什么区块链不拦住我篡改数据?”

再进一步,那位同学又会问:“为什么区块链不能立刻发现、并且阻止我篡改数据?也许我只是无意手误呢”。坦率说,这有点对区块链期望过高了。区块链系统并非无所不能,也不会包办一切,区块链并不会阻止用户对自己的服务器、软件、数据库等施加操作,就像法律不能也不应去阻止你打碎家里的杯子一样。

本质上,区块链的一致性、难以篡改性是面向“全局”的,是由多方博弈和协作达成的,当链上交易牵涉错误数据时,共识机制可以检测并拒绝已被篡改的数据,保证链上剩余的大多数健康节点继续共识出块。而节点本地不参与共识的数据,共识机制鞭长莫及。

那么,区块链为什么不能主动检测和纠正错误,保证每个节点上的数据一致性?首先,链上的数据非常庞杂,用户直接登入数据库手动修改少量数据,区块链节点并不知道哪一条数据被修改了,无法触发检查。

如果区块链系统定期巡检所有数据,并将所有数据和其他节点进行比对,可想而知,这样做的话,网络、磁盘和计算开销会非常大。关键是,这并不解决问题,因为从数据被篡改后到检测出来的时间窗里,哪怕脏数据只存在了几十毫秒,但这时如果不幸有应用来查询数据,依旧会得到篡改后的结果。对要求苛刻的业务来说,事后检测未必是最佳选项,因为有可能已经造成了业务损失,届时能做的最多就是告警和冲正了。

当然,也可以结合数据库的操作监控、binlog等辅助机制,加速响应速度和检测效率。方法还是有的,如上所述,只是性价比较低,也不彻底解决问题,只有对数据修改极其敏感,且业务上接受延时发现和修订的特定场景,才会考虑将其作为补救措施。我们把这部分归类到运营管理工具里,根据场景需求来实现。

还有一种方法,可以部分解决查询问题:f+1查询。即查询数据时,无论是查区块数据,还是合约的状态数据,不妨多查几个节点,查询节点数多于 f 即可。如链上有个7个节点时,f=2,用户查询自己节点之外,继续发出网络请求去查询其他机构的2个节点,共查询3个节点,如果得到的数据都是一致的,则表示数据一定是正确的,反之,一定是这3个节点里出了问题。

_images/IMG_5032.PNG

但是,要执行f+1查询,前提是其他机构开了查询接口权限,让你连接上去查询。在很多安全防护严密的联盟链上,一般只打开节点之间P2P互联的网络端口,不会轻易给其他机构提供数据查询权限。再则,在网络上发起多次查询,其异步性、时效性、成功率和性能表现都会带来更多变数。

综上所述,对节点本地的数据,就像打地鼠,冒头的(发出交易参与共识,或进行f+1查询),区块链全局共识和容错机制能发现,没有冒头仅蹲在用户硬盘里的,只能用户自己负责了。

结语

区块链通过网络博弈、多方校验实现了全网的容错防作恶,而区块链同步给到各节点的本地数据,需要用户自行妥善管理保存。

从信任传递来看,首先用户得“信自己”,如果连自己都无法相信,说明系统和数据管理有漏洞,莫说是修改数据了,在本地系统的整个链路上,包括区块链软件、SDK、业务服务都有可能出错和篡改作假,这样的环境有何信任可言?

节点持有者必须非常审慎,首先不要手痒或手误去改数据,然后关键是要建立周全的制度,包括管理策略和技术防护,比如,主机访问控制、数据库登录和操作权限控制、操作审计、日志审计等,以避免本机构内有人越权访问监守自盗,或者被外部渗透。

万一数据出错,区块链比中心化系统好一点的就是,还有可能通过与其他节点互相校验检测出来,这时则应该进行告警、查证、补正和追责,以及在有条件和有必要的前提下,善用f+1查询方法,给查询操作加一点点保险。

另外,建议定期备份节点数据到安全的离线设备上,这样无论是出现意外还是人为的数据问题,依旧可以从冷数据里快速恢复,保证一定的RTO(复原时间目标)和RPO(恢复点目标)。

而区块链的健壮性在于,无论单个角色怎么折腾自己的节点和数据,对全局是没有影响的,只有修改者自己受损。任凭窝里翻天覆地,链上依旧云淡风轻,其“全局一致”、“难以篡改”的定律依旧成立,链仍然是信任的锚点,这就是区块链的魅力。

区块链学习必读

新人必读:区块链实用型技能树

作者:张开翔|FISCO BCOS 首席架构师

随着新一波的区块链热潮,许多同学怀着巨大的热情进入了这个领域,同时也会遇到不少疑惑,区块链开发需要哪些知识?怎么学习?从哪里学习?遇到问题怎么办?本文将试图给区块链领域新人一个快速实用的指引。

一、基本IT技能

区块链堪称“黑科技”,本身具有大量的技术元素,有志于从技术角度切入区块链的人,应该具备或掌握基本的IT技能,达到至少是常规级别“程序员”或“系统管理员”的技能水平。

_images/IMG_4890.PNG

首先需要熟练的Linux操作系统知识。

大多数区块链系统是可以跑在Linux上的,包括CentOS和Ubuntu等,你至少要会一些基本的Linux操作指令,比如ls查看目录、ps或top查看进程、find查找文件、netstat查看网络、ulimit检查系统参数限制、df/du查看磁盘空间、用apt/yum安装软件等等,如果这些基本命令都不掌握,在Linux上操作肯定是举步维艰的。

这方面的书和资料都很多,相信一星期就能上手。另外,善于Linux的man指令,可以获得每个命令的详细帮助。如果学会写shell脚本,那更如虎添翼,可以把大量的繁琐操作给自动化了。

要有清晰的网络概念。

区块链本来是分布式系统,节点之间一定是通过网络相连的,只是跑起来的话,不需要多高深的网络知识,只需要了解什么是TCP/IP;公网、内网、本地地址的区别;端口如何配置;节点和节点、SDK和节点之间的互联是否会被防火墙和网络策略挡住;采用ifconfig、telnet、ping、netstat等命令检查网络信息和进行探测、定位网络问题。一般来说,Linux书籍也都会介绍这部分内容。

区块链周边的支持,如浏览器、中间件、业务应用,会依赖一些第三方基础软件,如MySQL/MariaDB数据库、Nginx服务、Tomcat服务等,至少懂得怎么去安装指定版本的软件,掌握修改这些软件的配置文件并使之生效的基本操作,了解各款软件的密码、权限配置和网络安全策略,以保护自身安全。

如果是基于云、docker或者k8s等容器环境构建,需要了解使用的服务商或容器的功能、性能、配置方式,包括对资源的分配:CPU、内存、带宽、存储等,以及安全和权限的配置、网络策略配置、运维方式,达到轻松分发构建的同时,还能保持其稳定性和可用性。

各种云服务商和容器解决方案都有周全的文档和客服服务渠道,可以帮助用户顺畅地使用。

到编程语言阶段,可以根据自己的学习路径,选择不同的语言。

如果是使用Java语言,那就应该熟练掌握Eclipse、IntelliJ IDEA等集成IDE,熟悉Gradle为主的工程管理软件,熟悉Spring、Springboot等java的基础开发组件,熟悉在IDE或命令行下对资源路径如ApplicationContext等路径的定义,或许还有myBatis等流行的组件,这些都可以在java相关的社区和网站找到资料和书籍。

在熟练使用Java语言的情况下,采用Java SDK接入到区块链,跑起一个Demo Sample,将是非常轻松写意的事情。

如果是采用其他语言,我们也提供了Python、Node.js、Golang等语言的区块链SDK。

不同的语言,其安装包有不同的稳定版本,会采用不同的环境和依赖安装配置方法,会有不同的IDE和调试方法,就不在本文一一罗列,相信学习和使用语言这件事本身,于程序员已经是最基本的技能了。

最后,作为在开源世界里冲浪的玩家,“全球最大同性交友网站”——github一定是要上的了。

注册github账号,掌握git版本管理工具的基本操作,clone和pull开源软件代码,提交issue,commit自己的修改,给开源项目提交pull request,再顺手点个star,激情而有范儿,在开源世界里留下你的姓名。

二、区块链领域的基础知识栈

以下部分的知识和区块链或区块链某一个平台更加相关,从底到上依次如下:

_images/IMG_4891.JPG

HASH(哈希算法)、签名、证书

严格来说,这并不是区块链领域的专有知识,只是必须具备的基础知识,包括SHA3/SHA256/RIPEMD160等摘要算法,以及这些算法和“区块链地址”的关系,基于公私钥的数字签名和验证方法,数字证书的概念和格式,比如X.509证书,以及保存证书/公私钥的文件格式,如PEM文件、keystore文件等。

基础应用密码学

基础应用密码学其实范围很广,作为入门者,至少要了解对称和非对称加密的常见算法,如AES对称加密,RSA、ECDSA椭圆曲线等非对称加密算法,以及这些算法在签名验签、数据加密、通信协商和保护方面的作用。如果要使用国密,那么需要了解SM2~SM9一系列算法的概念和使用。

分布式网络结构

区块链是先天的“分布式网络系统”,节点和节点通过网络的P2P端口互连,客户端、SDK通过RPC/Channel端口互连,首先要保证网络之间是互通的,监听的地址和端口是对的,端口是开放的,防火墙和网络策略是正确的,用于安全连接的证书已经到位,才能保证区块链的“通则不痛”。

这也要求使用者具备基本的网络知识、网络工具,同时了解区块链特有的节点类型(共识节点、观察节点、轻节点等)、互连方式(点对点双向连接、JSON RPC的HTTP短连接、Channel长连接等)。详情点击参考《FISCO BCOS网络端口讲解》

智能合约

智能合约可说是应用开发者直面区块链的一道大门,入得此门,精彩无穷。流行的智能合约语言是Solidity语言,这门源自以太坊,从诞生开始就是为区块链而来的。

Solidity语言更新活跃、文档完备,具有良好的一致性和事务性,功能足够实现中型的商业应用。

当然,它在实时调试、第三库支持、运行速度等方面还比不上成熟的语言,如果开发者想要用C++等语言编写智能合约,那就要对区块链上的计算范式进行深入了解,避免写出无法共识的智能合约来,一般是建议有深入的了解后再采用Solidity之外的其他语言编写合约。

要掌握Solidity合约,当然是通读文档,并动手尝试。具体参考以下文:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/smart_contract.html

ABI接口原理

在采用EVM作为虚拟机的区块链上,EVM执行的是Solidity语言的合约。合约编译会生成后缀名为ABI的文件,其实里面就是该合约接口定义的JSON文本,可以用文本查看器查阅,了解你写的合约如何翻译成ABI里的接口,接口返回类型,参数列表,参数类型等,只要有合约的ABI文件,就可以调用区块链SDK的接口,解析这个合约相关的交易、返回值、回执等。

区块数据结构

区块(Block)有区块头和区块体。区块体有交易列表,交易列表里的每个交易(Transaction或Tx)有发起方、目标地址、调用方法和参数,以及发送者签名。交易的结果会生成一个“回执(Receipt)”,回执里包含被调用方法的返回值、运行过程生成的EventLog等……

了解这些,基本上就掌握了区块链数据的脉络,还可以继续深究数据结构里的merkle root以及对应的merkle tree是如何生成的,有什么作用(如用于SPV:Simplified PaymentVerification)。具体参考以下文档:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/design/protocol_description.html

RPC接口

这里把区块链节点暴露的功能接口统称为“RPC接口”。查看链上数据,包括区块、交易、回执、系统信息、配置信息,向链上发起交易,以调用智能合约、修改系统配置等,或者通过AMOP协议发送消息、监听事件,都是通过RPC接口。

几十个RPC接口建议一一走读,或善用搜索,以发现自己想要的接口。

接口通信采用的协议可能是JSON RPC,或者是FISCO BCOS独创的Channel协议,SDK基本上已经对接口和协议进行了良好的包装,也可以在深入理解ABI和RLP等编码模式前提下自行开发接口客户端。具体参考以下文档:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/api.html

准入和权限模型

联盟链强调安全可控,节点准入是第一步,在链初始化后,其他节点或者SDK配置了相应的证书,才能接入到既有的联盟链上。

链上的角色用权限模型控制,包括管理员权限、发布合约的权限、创建表的权限、参数配置权限等,以避免角色之间操作混淆,某些角色既当运动员又当裁判员。

初学者需要仔细阅读区块链平台提供的技术文档了解原理,遵循操作手册的步骤进行操作。具体参考以下文档:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/permission_control.html

数据存储模型

区块链节点会采用文件数据库(LevelDB或RocksDB),或者关系型数据库如MySQL保存数据,所以,链上是真的有“数据库”的。

写入数据库的数据包括区块、交易、回执、合约产生的状态数据等,是否写入“调用合约产生的历史数据”根据不同的平台而定,FISCO BCOS默认只保存最新的状态值,可以选择性地将修改记录写入“回执”或“历史表”里进行追踪。

FISCO BCOS还提供方案,将历史数据导出到链下数据库进行关联分析。具体参考以下文档:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/design/storage/index.html

共识机制原理

联盟链通常采用插件化共识机制实现,FISCO BCOS提供PBFT和RAFT两种高效共识算法,而不会采用“挖矿”这些高耗能低效率的共识。

共识机制是区块链的灵魂,对共识机制进行深入学习,才可以深入理解区块链通过多方协作、达成高度一致性、支持交易事务性、防篡改防作恶的功效。具体参考以下文档:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/design/consensus/index.html

区块链的知识包罗万象,更深层次的知识还有分布式系统理论、博弈论、前沿密码学、经济学、社会学等,掌握以上的基础知识,再深入学习,举一反三,用场景去验证和探索创新式应用,方可发挥技术的潜力,感受分布式商业的魅力。

三、做一个怎样的学习者

在这个过程中,希望学习者做到:

读文档的耐心

我们的开源项目文档足有20万字以上的篇幅,公众号里还有大量的技术解析和科普文章,这都是程序员们在coding之外,用尽自己仅有的语文储备,码出的海量文字,是一笔巨大的技术财富,涵盖了相关开源项目的方方面面。如果能通读,或者记住文档结构和标题,需要时快速打开,足以解惑且深入。

搜资料的能力

文档、公众号都有搜索功能,当想起和开源社区有关的问题时,可以随手用关键字搜索,一般都能找到答案。如果有语言不详之处,可以向开源项目团队提出,或者根据自己的理解进行补充。通用的知识点,如操作系统、网络等,通过公网搜索引擎,一般都能找到答案。

排查环境和依赖问题的能力

开源软件牵涉的系统环境、第三方软件、软件的版本等常常有错综复杂的依赖关系,太高或太低的版本都可能会有一些问题,请注意阅读项目文档对软硬件环境和依赖的描述,保证自己的环境符合要求,并善用配置管理工具、软件安装工具获取和设置合适的版本。

调试能力

如上所述,Solidity语言的调试工具完善程度尚未达到完美,但可以善用合约的返回值、EventLog等方式,通过WeBASE、控制台等趁手的工具进行调试,并查阅Solidity文档,了解问题可能出在哪里。

区块链节点的日志开启debug级别后,也会打印详细的信息,可以查阅运行日志,获取运行信息和可能的错误信息,将这些信息与自己所做的操作比如发交易的流程结合起来进行分析,提高调试效率。

同时,目前的开源软件通常会在屏幕上打印错误原因和解决问题的提示,仔细查看操作反馈,大概率能了解错误原因和解决方案。

代码阅读能力

开源软件的最大效能是把代码毫无遗漏的摊到了开发者和学习者面前,了解代码结构,查阅代码里的关键流程,用关键字去搜索代码里的对应实现,都可以深入系统细节,挖掘设计思想,定位问题,寻找优化方法。一个好学且硬核的程序员,足可通过代码,和世界对话。

问问题的方式方法

“一个好问题,比答案还重要”。我们的社区非常活跃,大家都很热情地答复和解决问题。我们鼓励在社区里公开提出问题,一方面使大家都可以分享问题,找到解决方案,另一方面提问者也可以得到更多人的帮助。同时,希望提问者提出问题时,一次性描述详尽,把相关的操作步骤、系统环境、软件版本、出错提示以及希望得到的解决方案都提出来。

如果是通用性的问题,可以先搜索再提问,有利于培养独立解决问题的能力。希望提问者能向社区反馈更深层次的问题,以帮助社区快速优化。对很多典型问题,社区也积累了一些行之有效的解决方案,我们会整理和公布出来,以便查阅。

从新人到老鸟的路也许漫漫,如果能参考这篇小文的一些方法,可以少踩许多坑,多写一些应用。Enjoy blockchain,社区与你共同进步。


排除万难,从入门到精通区块链

作者:张开翔|FISCO BCOS 首席架构师

目前越来越多的人已经进入或准备进入区块链领域,过程中不免抱着各样的疑虑和问题。想起自己之前用几年时间,从“略懂”区块链到all in,同样也经历着类似的心路历程,这个领域确实是有一些门槛的,但万事开头难,摸索路上还远不止八十一难,这里梳理几个概要性的困难和感悟,谨作分享。

方向之难

我是谁”、“我在哪”、“我要去哪儿”,一切都源于这么一个哲学三问。区块链是什么?区块链究竟能做什么?为什么区块链这么火?不用区块链行不行?这些问题都充满了终极拷问的意味。

其实很难彻底回答这些问题,因为,并没有标准答案。所有创新的前沿的事物大抵如此,在质疑和动荡中发展,在黑暗和荒芜中摸索,精华和糟粕齐飞,绿洲与韭菜一色,直到引爆点迸发。如果因为充满疑虑而左右摇摆,或者裹足不前,那么在从事相关工作研究的过程,体验会很糟糕,结果也不会好。

分享一点个人体会:区块链领域从一开始就吸引了全世界无数的聪明人,不乏极客、学者、大师, 他们进行了大量的技术和社会实践。这个领域蕴含着数学、计算机、密码学、博弈论、经济学、社会学等学科的精华,这是一个智力飞扬、思想激荡的世界。目前,整个行业更是获得前所未有的关注,包括政府、各行业巨头都在关注,大量注意力和资源持续涌入,区块链迎来了“最好的时节”。

在持续进化、结构多元的现代社会里,分布式商业的理念已经成为现实。人和人、机构和机构之间会产生更多的联系和协作,信息和价值在新型网络模式中快速流转,作为分布式技术代表之一的区块链,很有机会成为新一代基础设施和创新的据点。

所以,方向并不是问题。即使你不把区块链当做“信仰”,只看这个充满魅力的技术本身,以及区块链与实体经济深度融合的机遇,还是可以给我们带来信心。

概念之难

在哲学三问中,“区块链是什么”是最晦涩的问题,区块、交易、账户、共识、智能合约、双花…这都是什么?!我自己在刚接触区块链时,也有一种被颠覆了认知的感觉。有一些介绍区块链的文章,往往着眼在区块链的社会和经济效能,从价值理念讲起,这些固然是必要的,但俗话说“科学要定性更要定量”,作为工程技术人员,我们更应该关注的是,区块链里的知识点、基本原理,进而澄清术语、把握架构、处理逻辑和程序流程。

前面提到,区块链蕴含着大量学科的精华,同时,行业也有一句俗语:“区块链并没有发明什么新技术,都是成熟技术的组合”。

  • 这些成熟的技术包括数据结构,诸如链表、树、图、过滤器等,是大学里数据结构的基础知识;
  • 基础密码学,包括HASH和对称非对称加密、数字签名等等,已经是几十年的经典技术,而事关隐私保护等领域的新一代密码学,更给密码学专业人士开拓了一片广阔的发挥空间;
  • 分布式网络和系统这门学科本身包罗万象,覆盖了海量服务的知识范畴,许多从事过海量互联网服务技术的同学,都会对区块链的P2P网络和共识算法、并行计算模型、事务性一致性原理如数家珍;
  • 博弈论和激励相容是协作方面的知识点,是“区块链思维”的重要组成部分,工科学生可能需要翻翻书,社科经管背景的同学估计会似曾相识吧;
  • 至于智能合约,比如solidity语言、WebAssembly等,很少听说吗?其实这些语言以及编程模式学习曲线未必有javascript高,有几年程序基础的同学基本上一周就可以上手,写出顺溜的智能合约来。

区块链让人觉得认知困难,是因为它就像个“筐”,什么都可以往里装,牵涉的技术繁杂,组合方式却和常规技术套路不同。所以学习者一定程度上先要放空自己,避免让自己在原有领域的思维定势干扰了学习,在丰富自己知识面的同时,接受区块链的“群体共识”、“防止篡改”、“不可否认”、“高度一致性”等神奇逻辑,然后潜心进去看每一个独立的概念时,并不会觉得高不可攀。

突破概念之难的要点,是排除来自各种渠道的噪声,有些信息似是而非,或者是各说各话,把同一件事用N种话术讲出来,混淆了定义,模糊了本质,于事无补的同时还带来更多疑问。靠谱的方法是着重阅读权威媒体的正规内容,关注一些主流区块链项目的文档库,认真地、全面地通读技术文档,然后找一个自己感兴趣的领域(如共识算法),结合自身经验知识进行对照研究。

同时,也可以加入活跃的开源社区和技术圈子,和有经验的人多讨论,勇于把问题抛出来,挨个术语,挨个流程讨论透彻。我们在研究区块链的初期,团队经常就一个概念的定义咬文嚼字地争论很久,最后愉快地达成共识时,大家都觉得神清气爽。

在概念阶段,切忌求全责备,不要变成资料收集机,一口吃不成胖子,基于靠谱的学习资料,澄清基本概念,在实践中去验证和发掘新的概念,建立发现问题、解决问题的方法论,慢慢就能举一反三,说不定某一天就能醍醐灌顶了。

上手之难

好吧,哲学和概念问题终于不会阻碍我们学习的脚步了,那么怎么继续“二十一天入门到精通”之路。作为技术人员,遇到新的技术平台、软件体系、编程语言…那当然是:“不要怂,就是干!”

几年前,我们刚开始研究区块链时,通读过几个国外流行的开源区块链项目代码,并搭建测试网进行试验剖析,分析如何让这些平台能在复杂的金融业务上用起来。当时有个困惑,如果直接基于底层平台开发应用,当需要实现更多功能时,是不是就得直接修改底层平台代码。

而当看到“智能合约”这个东西时,思路一下就打开了:采用智能合约作为中间层,在合约里编写业务逻辑,并为调用者定义清晰的功能接口,这样,业务可以很好和底层解耦,而底层平台则定位成强大的引擎,通过架构的解耦,使得整个开发过程变得清晰合理、轻松愉快,感觉就像是从“C/S”架构到“B/S”架构的演变,多年互联网海量服务开发的手感又回来了。

另外,我们觉得当时的开源项目主要是公链形态,在安全可控以及合规方面考虑没有那么周全,并不适合金融场景使用。

那么,没有趁手的平台,就想办法造一个。从此,深耕底层技术、迭代应用验证的漫漫之路开启了。这个过程也和开源社区一众伙伴建立了紧密的合作,正是“来自开源并回馈开源”,经过开源工作组几年的打磨,FISCO BCOS已经是一个全面开源、安全可控、高速稳定、易用友好的金融级底层技术平台,面向金融和广大的产业领域,提供丰富的功能和各种操作工具。丰富全面的文档和便捷易用的体验,可以帮助开发者从快速入门到精通,整体的技术门槛和开发成本变得前所未有的低。

有了基础的底层平台之后,下载、安装、配置、运行、阅读使用手册、写hello world和业务应用、Debug和分析日志…都是step by step的工作了。

我们目标是,用户在几分钟内,用一键安装、docker、云服务等方式顺畅无错地搭建出自己的区块链网络,在一周内通过学习就可以写出完整的智能合约,基于支持多款语言的SDK(Java、Node.js、Python、Go…还在增补中)实现业务逻辑,将业务发布上线,保持稳定运行。

为了这个目标,我们一直在持续优化使用文档、开发手册,以及部署和运维工具。众所周知,“码农”们喜欢写代码,而写注释和文档就比较痛苦了,为了向社区交出一份漂亮的作业,大家倾尽了有生以来的语文水平,一次又一次修订,硬是写出了数十万字的文档库。

同时,开源社区推出了一系列的线下线上沙龙、培训,用社区的方式,进行广泛交流和技术支持。在多次的现场学习和黑客松大赛中,我们欣喜地看到,开发者用两三天时间,就能基于FISCO BCOS实现他们精巧的项目设计,而且有开发者将其中和开源项目相关的优化贡献到Github上。

到这个程度,即使是对没有区块链研发经验的开发人员,快速上手已经没有什么问题,即便区块链底层还像一个黑盒子有待探索,但就像在电脑上安装App、使用mysql、tomcat之类的软件一样,足可以用起来,感受区块链的魅力了。

深入之难

对技术人员来说,探索技术的内涵永无止境:参与到区块链底层开发,实现大型的区块链应用,为区块链生态增加更多有用的特性、工具,对现有功能性能进行极致优化,这些都是进入“深水区”的路线。

之前提到,区块链系统知识点和框架涉猎广泛,无论是知识面还是深度都有相当的规模。量化一下的话,可以套用一万小时理论:如果每天学习工作8-10小时,一个月足以上手,一年可得小成,两年轻车熟路,三年可成老司机…但老司机要行的前路依旧漫漫。我们希望通过持续的科普、交流、实践,去缩短这个过程,但毕竟学习就是一种最基本的“工作量证明”,并没有什么其他捷径。

学习方法,首先是大量的泛读,每天早上一睁眼到晚上,都可以看到持续更新的行业新闻、公众号文章、技术大咖博客、邮件组讨论组、开源项目……阅读的过程或许会遇到不同观点的碰撞,需要去伪存真,在心态开放的同时,也保持自己的立场和方向。

然后是深入的精读,先选定一两个感兴趣的方向,研读诸如密码学、分布式理论等方面的一些经典论文。FISCO BCOS的核心共识算法使用的PBFT和RAFT算法,是基于对原版论文的研究解读,有了深入的理解再去做的实现和优化。区块链里广泛用到密码学原理,场景和逻辑多变,其原理有可能来自某一篇“顶会”论文。精读深度原理剖析文章和学术论文,基于扎实的理论,才能根据自己的需要进行发挥,创造性地解决工程问题。

其实我们的在线文档已经有数十万字的规模,各种信息应有尽有,技术社区会定期对热点知识进行解读,只要读者认真阅读在线技术文档,接受公众号的体贴推送,并动手进行更多的实践,随着时间推移,必能深度理解区块链的技术原理,参透架构设计的来龙去脉,建立起巩固的知识体系。

最后,精读的对象,自然也包括源代码,毕竟“Talk is cheap, show me the code”,区块链开源项目代码大多是几万到几十万行的级别,阅读代码是达成庖丁解牛水准最直接的方法。在研究区块链的历程里,我们有许多长夜漫漫review代码的日子,当读到一眯眼,眼前都是代码在飞,各种接口和对象翩翩起舞,既优雅又有规律,脉络清晰,那种愉悦简直难以名状。这种体验,之前有,之后也会继续有。

如果已经深入到了这个程度,领域门槛已经基本越过,考验的就是开发者的脑力和体力了吧。

持续之难

在过去几年因为鱼目混珠、政策法规、技术障碍等等一系列情况,区块链会遇到市场波动、应用落地延缓等挑战。未来如何,虽然没有预言家告诉我们,但现在大家已经看到了趋势。这又回到了第一个问题:“方向”,明确清晰的方向,不但能回答“要不要进入这个领域”,同样也能回答“要不要坚持下去”。我们一直在分布式商业模式中开拓场景,为公众提供优质的服务,为行业提供完备和好用的开源技术,这是从开始到现在,乃至未来坚持的方向,从未改变。

再具体一点,如果我们已经用区块链部署上线了业务系统,那么影响系统生命周期和持续性的问题还有:可运维性、可升级性、兼容性、数据容量、业务性能容量等等。

多次和社区朋友交流的过程,他们会提起一些问题,例如,新版本是否能兼容旧版本?随着业务发展,越来越多的数据是否可以迁移和重用?这都是用户的真实声音。我们所构建的平台,一定要走可持续发展的路线,重视软件体系的兼容性,有合理的版本发布节奏,以及周全的数据迁移维护策略,可以更好地保护社区用户利益,也使得用户愿意长期与社区共同发展。

另外,区块链领域还在高速发展中,各种新技术、新思潮、新模式、新政策还在层出不穷,这个领域集结了世界上大量的聪明人,他们不但聪明而且努力,从来都不闲着。于是在这个领域工作,每天都会有新的知识、新的刺激,这一方面是一种幸运,另一方面,也会让人极其焦虑。

如何去消化这么海量的信息,如何去探索和掌握前沿的知识,如何更好满足高速发展带来的用户需求和新挑战,如何做出卓有成效的突破性创新,这真的是一个创新和焦虑并存的世界,让人欲罢不能。

作为从业者,必须持续进行大量阅读,在各种信息流里过滤和吸收,不断地归纳/总结/思考/开拓;每一个需求和用户ISSUE反馈都是一个小目标,每发布一个新版本都会是下一个版本的新起点。区块链的世界和其他技术领域没有什么不同,都必须保持敏锐和奔跑,保持好奇和谦逊,不断学习和实践,修订短板寻求突破,将成果分享给社区。正如系统需要极大的弹性,人也需要极大的韧性。共勉。

带你读源码:四大视角多维走读区块链源码

作者:李辉忠|FISCO BCOS 高级架构师

引子

区块链作为「新基建」的重要组成部分,越来越受技术爱好者关注。区块链极客信奉“code is law”,相信通过代码可以构筑一个可信的世界。

而作为一门综合学科技术,区块链建立在数学、密码学、计算机原理、分布式网络和博弈论等众多基础学科之上,底层代码动辄数十万行,如果没有摸清门道,要完全掌握这些代码是极具挑战的。

本文希望给读者一个走读区块链源码的方法,让读者面对区块链底层项目时可以从容地说出“show me the code”。

基础知识储备

区块链是一门综合学科,涉及多个专业领域,涵括多方面的基础知识,在深度研究区块链之前需要做一定广度的知识储备。注意,这里说的是广度,并非深度,也就是说你只需要大概知道这些基础知识的基本原理与作用即可。

  • 密码学相关:理解哈希、对称加密、非对称加密以及数字签名的基本原理和作用;
  • 计算机操作系统相关:理解多进程、多线程、互斥、并行等相关概念和作用;
  • 数据结构相关:理解队列、堆栈、树等基本数据结构和使用场景;
  • 计算机网络相关:理解TCP/IP、心跳包、消息流等基本概念;
  • 数据库相关:理解数据库基本概念,了解KV数据库的基本原理;
  • 计算机原理相关:理解程序编译、解析、执行和字节码、虚拟机等概念;
  • 分布式系统相关:理解点对点网络、分布式一致性、CAP等相关概念和基本原理;
  • 程序开发相关:掌握相关的编程语言、构建工具等,理解项目构建基本流程。
多维走读

在储备了相关的基础知识之后,你就可以打开一份真正的区块链底层代码了,一般通过git clone可以快速下载到项目代码。

但是,面对数十万行的代码,该从何看起呢?

一个优秀的区块链底层项目,必然有一份优秀的工程代码,这份代码有其合理的组织结构与纹理逻辑。走读代码应效仿庖丁解牛,先摸清区块链的基本结构和逻辑,再开始走读,可以达到事半功倍的效果。

本文推荐要从四个不同视角进行走读,站在自己的需求角度出发去看代码,而不要被巨量的代码所左右。这四个角度为功能视角、系统视角、用户视角和开发视角,分别从逻辑层面、运行层面、使用层面和开发层面厘清代码架构和关键算法。

功能视角

在深入一份区块链底层代码之前,首先要通过其官网、技术文档、github wiki等渠道获取项目设计文档,了解其基本功能设计。

一般每个项目都会提供核心功能列表、总体架构图、功能模块图等介绍文档,通过这些介绍可以掌握项目基本功能。即使你真的找不到也不打紧,大部分区块链底层项目在功能设计层面的差异较小,核心功能模块也大致相同。

_images/IMG_5076.PNG

以FISCO BCOS为例,基础层代码如下:

_images/IMG_5077.PNG

核心层核心代码如下:

_images/IMG_5078.PNG

接口层核心代码如下:

_images/IMG_5079.PNG

从功能视角出发,先定位核心功能模块的代码位置,再仔细深入各个功能代码,从单个功能模块内,也可继续递归采用功能视角拆分法,广度遍历直至了解全貌。

系统视角

系统视角从整个区块链网络运行角度,关注区块链节点全生命周期所参与的系统行为。

关注点包括从敲下启动节点的命令开始,节点经历了哪些初始化环节,之后又是如何与其他节点建立点对点网络,以及完成分布式协作的。

由于不同区块链在部署架构上略有差异,系统运行方式也有所不同,但万变不离其宗,系统视角来看,每个区块链系统都要经历节点初始化、建立点对点网络、完成分布式交互的过程。

从系统视角看区块链,首先要关注初始化工作。以FISCO BCOS为例,区块链节点启动从main函数入口进入,通过libinitializer模块初始化并启动各模块,启动顺序如下:

_images/IMG_5080.PNG

通过启动顺序可以知道FISCO BCOS的一个重要特性——支持多群组账本,每个群组是一个独立的Ledger模块,每个Ledger具有独立的存储、同步、共识处理功能。

完成初始化工作同时,系统将会启动若干线程(或者进程、协程,原理类似),这些线程包括网络监听、共识、消息同步等,可以结合代码分析与系统命令查看运行节点配合确定有哪些关键线程,搞清楚关键线程的工作机制就可以基本掌握区块链系统运行机制。

以FISCO BCOS为例,节点启动之后的关键线程以及他们之间的关系如下:

_images/IMG_5081.PNG

初始化完成之后,网络模块的Host线程将根据配置列表,主动与其他节点建立连接,并且持续监听来自其他节点的连接;Sync线程开始相互发送区块高度,发现高度低于其他节点则开启下载逻辑;RPC与Channel线程等待客户端发送请求,将收到的交易塞入txpool;Sealer线程从txpool获取交易,Consensus线程则开始处理共识消息包。

如此,整个区块链系统有条不紊地运转,完成客户端请求与分布式协作。

用户视角

用户视角关注操作接口和交易生命周期,关注访问区块链的接口和协议设计、编解码方式、核心数据结构、错误码规范等,还会关注如何发送一笔交易到链上,交易在链上又经历了哪些处理流程,直到达成全网共识。

一般区块链底层项目都会给出交互协议的说明文档,通常实现包括JsonRPC、gRPC、Restful等不同类型的交互协议。

不同项目的交互接口会有所不同,但大都会包含发送交易、部署合约、调用合约、查看区块、查看交易以及回执、查看区块链状态等接口。不同项目的数据编码也会有所不同,有些采用Json,有些采用protobuf等。

当从技术文档中了解清楚交互协议、接口、编解码和错误码等设计细节之后,接下来最重要的是通过发送交易、部署合约、调用合约这些关键接口,对代码进行抽丝剥茧,贯穿交易整个生命周期,从而搞清楚区块链底层最核心的逻辑。

以FISCO BCOS为例,通过多个模块相互协作,完成交易整个生命周期的处理:

_images/IMG_5082.PNG

开发视角

开发视角关注的是整个代码工程,包括第三方依赖,源码模块之间的相互关系,单元测试框架和测试用例,编译和构建方式,持续集成和benchmark,以及如何参与社区源码贡献等等。

不同语言都有相应推荐的编译构建方式以及单测框架,通常在区块链项目源码目录可以快速定位到第三方依赖库,比如以cmake构建的C++项目有CmakeLists.txt文件,go项目有go.mod文件,rust项目有cargo.toml文件等。

以FISCO BCOS为例,从CmakeLists.txt可以看到依赖库包括:

_images/IMG_5083.PNG

项目核心源码包括fisco-bcos程序入口代码,以及libxxx的各模块代码,根据模块的名字可以快速识别其对应功能,这里也体现了一个项目源码质量的高低,高质量的代码应该是“代码即注释”。

单元测试代码在test目录,采用boost的单元测试框架,子目录unittests中单测代码与源码目录一一对应,非常容易找到源码对应的单元测试代码。

构建和持续集成工具代码在tools目录,子目录ci中维护了多个不同场景的持续集成用例,在github提交的每一个pr(pull request)都会触发这些持续集成用例,当且仅当每个用例成功通过方可允许合入pr。

关于FISCO BCOS的代码规范和贡献方式,在CODING_STYLE.md和CONTRIBUTING.md文件中有详细描述,鼓励社区用户积极参与贡献。

总结

区块链涉及领域和知识较多,需要深入源码细节,才能真正完全掌握区块链核心技术。所谓“重剑无锋,大巧不工”,掌握源码走读的基本方法论,才能在巨量代码前,面不改色心不跳。

本文提出从功能、系统、用户和开发四个不同视角进行区块链底层代码走读的方法,一般来说,依次选择不同视角进行走读是比较推荐的方式,也可以根据个人喜好和能力模型选择视角顺序。

最后,本文所举示例皆为FISCO BCOS,但这套走读方法可以适用于任何其他区块链底层项目,希望本文对你有所帮助。

FISCO BCOS的原理和特性

整体架构

架构模型:一体两翼多引擎

群组架构:支持链内动态扩展多群组

FISCO BCOS 2.0原理解析篇1: 群组架构的设计

作者:陈宇杰|FISCO BCOS 核心开发者

为了方便企业、开发者更深入理解FISCO BCOS 2.0诸多新特性,更快速地运用FISCO BCOS搭建联盟链应用,我们启动了FISCO BCOS 2.0系列剖析的计划。在后续的推送中,我们将陆续推出《FISCO BCOS 2.0原理解析》、《FISCO BCOS 2.0使用教程》、《FISCO BCOS 2.0源码分析》等文章系列,抽丝剥茧地将FISCO BCOS 2.0进行全面拆解。

本文是原理解析系列第一篇,介绍FISCO BCOS 2.0众多新特性中的主线——群组架构。主要包括群组架构的整体架构设计,群组架构包括哪些组件,各组件的主要功能以及组件间的交互。

设计目标

理解群组架构的设计目标,可以从人人都熟悉的群聊模式说起。

灵活扩展:保证业务接入和扩展像拉群聊天一样方便

群的建立非常灵活,几个人就可以快速拉个主题群进行交流。同一个人可以参与到自己感兴趣的多个群里,并行地收发信息。现有的群也可以继续增加成员。

看回群组架构,采用群组架构的网络中,根据业务场景的不同,可存在多个不同的账本,区块链节点可以根据业务关系选择群组加入,参与到对应账本的数据共享和共识过程中。群组架构具有良好的扩展性,一个机构一旦参与到这样的联盟链里,有机会灵活快速地丰富业务场景和扩大业务规模,而系统的运维复杂度和管理成本也线性下降。

隐私保护:各群组之间解除耦合独立运作

回想一下群聊场景:群聊用户都在你的通信录中,都是经过验证才添加的,且不在群里的用户看不到群聊信息。这与联盟链准入机制不谋而合,所有参与者的机构身份可知。

另一方面,群组架构中各群组独立执行共识流程,各组独立维护自己的交易事务和数据,不受其他群组影响。这样的好处是,可以使得各群组之间解除耦合独立运作,从而达成更好的隐私隔离。在跨群组之间的消息互通,则会带上验证信息,是可信和可追溯的。

架构设计
架构设计全景

_images/IMG_4892.PNG

▲ 图为群组架构设计全景

如上图所示,群组架构自底向下主要划分为网络层、群组层,网络层主要负责区块链节点间通信,群组层主要负责处理群组内交易,每个群组均运行着一个独立的账本。

网络层

群组架构中,所有群组共享P2P网络,群组层传递给网络层的数据包中含有群组ID信息,接收节点根据数据包中的群组ID,将收到的数据包传递给目标节点的相应群组。为了做到群组间通信数据隔离,群组架构引入了账本白名单机制,下图展示了群组架构下群组间收发消息的流程:

_images/IMG_4893.PNG

账本白名单

每个群组均持有一个账本白名单,用于维护该群组的节点列表。为了保证账本白名单群组内一致性,仅可通过发交易共识的方式修改账本白名单。

发包流程

以node0的第一组向node1的第一组发送消息packetA为例:

(1) group1将消息packetA传递到网络层;

(2) 网络层模块对packetA进行编码,在packetA包头加上本群组ID,记为{groupID(1) + packetA};

(3) 网络层访问账本白名单,判断node0是否是group1的节点,若非group1节点,则拒绝数据包;若是group1节点,则将编码后的包发送给目标节点node1。

收包流程

node1接收到node0 group1的数据包{groupID(1) + packetA}后:

(1) 网络层访问账本白名单,判断源节点node0是否是group1节点,若非group1节点,则拒绝数据包,否则将数据包传递给解码模块;

(2) 解码模块从数据包中解码出group ID=1和数据包packetA,将数据包packetA发送到group1。

通过账本白名单,可以有效地防止群组节点获取其他群组通信消息,保障了群组网络通信的隐私性。

群组层

群组层是群组架构的核心。为了实现群组间账本数据的隔离,每个群组持有单独的账本模块。群组层自下向上一次分为核心层、接口层和调度层:核心层提供底层存储和交易执行接口;接口层是访问核心层的接口;调度层包括同步和共识模块,负责处理交易、同步交易和区块。

核心层

主要包括存储(AMDB/storage/state)和执行(EVM)两大模块。存储负责从底层数据库中存储或读取群组账本的区块数据、区块执行结果、区块信息以及系统表等。执行(EVM)模块主要负责执行交易。

接口层

接口层包括交易池(TxPool)、区块链(BlockChain)和区块执行器(BlockVerifier)三个模块。

模块1:交易池(TxPool)

交易池是客户端与调度层交互的接口,负责从客户端或者其他节点收到的新交易,共识模块会从中取出交易打包处理,同步模块从中取出新交易进行广播。

模块2:区块链(BlockChain)

区块链模块是核心层与调度层交互的接口,是调度层访问底层存储和执行模块的唯一入口,调度层可通过该模块提交新区块和区块执行结果,查询历史区块等信息。区块链模块也是RPC模块与核心层的接口,RPC模块通过区块链模块可获取区块、块高以及交易执行结果等信息。

模块3:区块执行器(BlockVerifier)

与调度层交互,负责执行从调度层传入的区块,并将区块执行结果返回给调度层。

调度层

调度层包括共识模块(Consensus)和同步模块(Sync)。

模块1:共识(Consensus)模块

共识模块主要负责执行客户端提交的交易,并对交易执行结果达成共识。

如下图,共识模块包括打包(Sealer)线程和共识(Engine)线程,Sealer线程负责从交易池获取未执行交易,并将交易打包成区块;Engine线程负责对区块执行结果进行共识,目前主要支持PBFT和Raft共识算法。

_images/IMG_4893.PNG

共识模块主要流程包括:

(1) 客户端提交的交易缓存到TxPool后,会唤醒共识节点的Sealer线程,Sealer线程从交易池中获取最新交易,并基于当前最高区块,打包产生一个新区块blockI;

(2) Sealer线程将打包产生的新区块blockI传递到Engine线程,用于共识;

(3) Engine线程收到新区块blockI后,启动共识流程,共识过程中,会调用区块执行器BlockVerifier执行区块blockI内的每笔交易,并对执行结果达成一致共识;

(4) 若共识成功,则调用BlockChain将新区块blockI以及区块执行结果等提交到底层数据库;

(5) 新区块blockI上链成功后,触发交易池删除以上链的所有交易,并将交易执行结果以回调的形式推送到客户端。

模块2:同步(Sync)模块

考虑到共识过程中,需要尽可能保证每个群组节点拥有全量的交易,FISCO BCOS 2.0引入了同步模块来保证客户端的交易尽可能发送到每个共识节点。

同步模块主要包括交易同步和区块同步:

交易同步

客户端通过RPC向指定群组交易池提交新交易时,会唤醒相应群组同步模块的交易同步线程,该线程将所有新收到的交易广播到其他群组节点,其他群组节点将最新交易插入到交易池,保证每个群组节点拥有全量的交易。

如下图,客户端将交易tx_j发送到group1, tx_i发送到group2后,交易同步线程会将tx_i广播到所有群组节点的group1,将tx_j广播到所有群组节点的group2。

_images/IMG_4894.PNG

区块同步

考虑到区块链网络中机器性能不一致或者新节点加入都会导致部分节点区块高度落后于其他节点,同步模块提供了区块同步功能,该模块向其他节点发送自己节点的最新块高,其他节点发现块高落后于其他节点后,会主动下载最新区块。

以三节点区块链系统为例,区块同步流程如下图:

_images/IMG_4895.PNG

(1) Node0, Node1和Node2的区块同步线程定期广播最新区块高度信息;

(2) Node1收到Node0和Node2的最新区块高度后,发现自身块高3低于Node0和Node2最新快高6;

(3)本着负载均衡的原则, Node1向Node2请求第4个区块,向Node0请求区块5和区块6;

(4) Node0和Node2接收到Node1的区块请求后,分别将第{5,6}和第{4}个区块返回给Node1;

(5) Node1按照区块高度执行第4、5、6个区块,并将最新区块按次序提交到底层存储。


下篇预告:群组架构的使用教程

下一篇文章,我会以搭建群组区块链为例,向大家提供群组架构的实操课程,敬请持续锁定FISCO BCOS开源社区。

FISCO BCOS 2.0使用教程篇1: 群组架构实操演练

作者:陈宇杰|FISCO BCOS 核心开发者

如果说,上篇是带你潜入团队的大脑,看清群组架构诞生的由来和架构设计(还没看的伙伴可以点标题直接进入:群组架构的设计

那么下篇,即本文,视线则聚焦到团队的双手,看看群组架构如何在跳动的十指中轻快飞舞。

本文是高能实操攻略,全程硬核干货,我将以搭建仲裁链为例,并演示如何向该链发送交易。

课程知识点

  • 使用build_chain创建多群组区块链安装包
  • 如何启动区块链节点、查看节点共识状态和出块状态
  • 搭建控制台,向多个群组部署合约
仲裁链组织结构

下图是一个仲裁链示例:

_images/IMG_5084.PNG

企业A、企业B和企业C分别和仲裁机构合作,采用区块链搭建仲裁服务。在群组架构下,搭链方式为:仲裁机构配置两个节点,分别加入三个群组;企业A配置两个节点,加入群组1;企业B配置两个节点,加入群组2;企业C配置两个节点,加入群组3。

仲裁链组网详情

上节介绍了仲裁链组织结构,这里在一台机器的环境下模拟仲裁链组网环境。仿真的组网环境如下:

  • 仲裁机构:包括两个节点,节点IP均为127.0.0.1,同时属于群组1,群组2和群组3
  • 企业A:包括两个节点,节点IP均为127.0.0.1,仅属于群组1
  • 企业B:包括两个节点,节点IP均为127.0.0.1,仅属于群组2
  • 企业C:包括两个节点,节点IP均为127.0.0.1,仅属于群组3

温馨提示

实际应用场景中,不建议将多个节点部署在同一台机器,建议根据机器负载选择部署节点数目。本例中仲裁机构节点归属于所有群组,负载较高,建议单独部署于性能较好的机器。

仲裁链搭建关键流程

如下图所示,使用FISCO BCOS 2.0快速建链脚本搭建仲裁链(以及所有其他区块链系统)主要包括五个步骤:

_images/IMG_5085.PNG

  • step1:安装依赖软件,主要是openssl和build_chain.sh脚本
  • step2: 使用build_chain.sh生成区块链节点配置
  • step3: 启动所有机构区块链节点
  • step4: 启动控制台
  • step5: 使用控制台发送交易

下面我将就这五个步骤详细叙述构建仲裁链的关键流程。

安装依赖软件

搭建FISCO BCOS 2.0区块链节点需要准备如下依赖软件:

  • openssl:FISCO BCOS 2.0的网络协议依赖openssl
  • build_chain.sh脚本:主要用于构建区块链节点配置,可从https://raw.githubusercontent.com/FISCO-BCOS/FISCO-BCOS/master/tools/build_chain.sh下载
生成区块链节点配置

FISCO BCOS 2.0提供的build_chain.sh可快速生成区块链节点配置,按照【仲裁链组网详情】介绍的节点组织结构,先生成区块链配置文件ip_list:

#ip_list文件内容格式:[ip]:[节点数] [机构名] [所属群组列表]
$ cat > ipconf << EOF
127.0.0.1:2 arbitrator 1,2,3
127.0.0.1:2 agencyA 1
127.0.0.1:2 agencyB 2
127.0.0.1:2 agencyC 3
EOF

调用build_chain.sh脚本构建仿真的本机仲裁链:

$ bash build_chain.sh -f ipconf -p 30300,20200,8545

区块链节点配置成功后,会看到[INFO] All completed.的输出。

启动节点

生成区块链节点后,需要启动所有节点,节点提供start_all.sh和stop_all.sh脚本启动和停止节点。

# 启动节点
$ bash start_all.sh

# 查看节点进程
$ ps aux | grep fisco-bcos

不发交易时,共识正常的节点会输出+++日志,使用tail -f node*/log/* | grep “++”查看各节点是否共识正常。

启动控制台

控制台是用户与FISCO BCOS 2.0区块链节点交互的重要工具,实现查询区块链状态、部署调用合约等功能,能够快速获取用户到所需要信息。

启动控制台前需获取并配置控制台:

  • 获取控制台:从https://github.com/FISCO-BCOS/console/releases/download/v1.0.0/console.tar.gz下载控制台
  • **配置控制台:**主要拷贝证书、配置conf/applicationContext.xml所连接节点的IP和端口信息,控制台关键配置如下:

_images/IMG_5086.PNG

当然,控制台也支持连接多个群组,并提供了switch命令来切换群组,连接多个群组时,需要在groupChannelConnectionsConfig bean id中配置多个连接,分别连接到对应群组的区块链节点。

**注:**控制台依赖于Java 8以上版本,Ubuntu 16.04系统安装openjdk 8即可。CentOS请安装Oracle Java 8以上版本。

使用start.sh脚本启动控制台,控制台启动成功会输出如下界面:

_images/IMG_5087.PNG

向群组发交易

控制台提供了deploy HelloWorld指令向节点发交易,发完交易后,区块链节点块高会增加

# ... 向group1发交易...
$ [group:1]> deploy HelloWorld
0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744
# 查看group1当前块高,块高增加为1表明出块正常,否则请检查group1是否共识正常
$ [group:1]> getBlockNumber 
1
# ... 向group2发交易...
# 切换到group2
$ [group:1]> switch 2
Switched to group 2
[group:2]deploy Helloworld
...
总结

本文介绍了搭建仲裁链的关键过程,FISCO BCOS 2.0的操作文档step by step介绍了如何部署多群组区块链,详细流程可以参考https://fisco-bcos-documentation.readthedocs.io/zh_CN/release-2.0/docs/tutorial/group_use_cases.html

分布式存储:支持海量数据存储

FISCO BCOS 2.0原理解析篇2: 分布式存储架构设计

作者:莫楠|FISCO BCOS 高级架构师

FISCO BCOS 2.0新增对分布式数据存储的支持,克服了本地化数据存储的诸多限制。

在FISCO BCOS 1.0中,节点采用MPT数据结构,通过LevelDB将数据存储于本地,这种模式受限于本地磁盘大小,当业务量增大时数据会急剧膨胀,要进行数据迁移也非常复杂,给数据存储带来较大的成本和维护难度。

为了突破性能的瓶颈,我们在FISCO BCOS 2.0中,对底层的存储进行了重新设计,实现了分布式存储,使用不同于MPT的方式来实现追溯,带来了性能上的提升。

先夸夸分布式存储方案的优点:

  • 支持多种存储引擎,选用高可用的分布式存储系统,可以支持数据简便快速地扩容;
  • 将计算和数据隔离,节点故障不会导致数据异常;
  • 数据在远端存储,数据可以在更安全的隔离区存储,这在很多场景中非常有意义;
  • 分布式存储不仅支持Key-Value形式,还支持SQL方式,使得业务开发更为简便;
  • 世界状态的存储从原来的MPT存储结构转为分布式存储,避免了世界状态急剧膨胀导致性能下降的问题;
  • 优化了数据存储的结构,更节约存储空间。
从MPT存储到分布式存储
MPT存储

MPT(Merkle Paricia Trie),来自以太坊,对外接口为Key-Value,使用前缀树来存储数据,是FISCO BCOS 1.0的存储模式。

MPT是前缀树结构,树中的每个叶子节点允许有最多16个子叶子节点,叶子节点有一个HASH字段,由该叶子的所有子叶子节点HASH运算得出。树根有唯一的HASH值,标识整棵树的HASH。

_images/IMG_5088.JPG

图片来自以太坊白皮书

以太坊的全局状态数据,保存在MPT树中,状态数据由账号组成。账号在MPT中是一个叶子节点,账号数据包括Nonce、Balance、CodeHash和StorageRoot。任意一个账号字段发生变化时,会导致该账号所在的叶子的HASH发生变化,从该叶子直到顶部的所有叶子的HASH都会变化,最后导致顶部的StateRoot变化。

由此可见,任何账户的任意字段变化,都会导致StateRoot的变化,StateRoot能唯一标识以太坊的全局状态。

_images/IMG_5089.JPG

图片来自以太坊白皮书

MPT可以实现轻客户端和数据追溯,通过StateRoot可以查询到区块的状态。MPT带来了大量HASH的计算,打散了底层数据存储的连续性。在性能方面,MPT State存在着天然的劣势。可以说,MPT State追求极致的可证明性和可追溯性,对性能和可扩展性做了一定的妥协。

分布式存储

FISCO BCOS 2.0在保持存储接口兼容性的同时,引入了高扩展性、高吞吐量、高可用、高性能的分布式存储。分布式存储(Advanced Mass Database,AMDB):重新抽象了区块链的底层存储模型,实现了类SQL的抽象存储接口,支持多种后端数据库,包括KV数据库和关系型数据库。 引入了分布式存储后,数据读写请求不经过MPT,直接访问存储,结合缓存机制,存储性能相比基于MPT的存储有大幅提升。MPT数据结构仍然保留,仅做为可选方案。

_images/IMG_5090.JPG

分布式存储支持MySQL等关系型数据库,支持MySQL集群、分库分表等平行扩展方式,理论上存储容量无限。

分布式存储架构

_images/IMG_5091.JPG

State层(State)

抽象了智能合约的存储访问接口,由EVM调用,分为StorageState和MPTState。StorageState为分布式存储的适配层,MPTState为老的MPT适配层,FISCO BCOS默认使用StorageState。

分布式存储层(Table)

抽象了分布式存储的类SQL接口,由State层和Precompiled调用。分布式存储层抽象了存储的增删改查接口,把区块链的核心数据分类存储到不同的表中。

驱动层(Storage)

实现具体的数据库访问逻辑,包括LevelDB和MySQL。

分布式存储名词解释
Table

存储表中的所有数据。Table中存储分布式存储主key到对应Entries的映射,可以基于分布式存储主key进行增删改查,支持条件筛选。

Entries

Entries中存放主Key相同的Entry,数组。分布式存储的主Key与Mysql中的主key不同,分布式存储主key用于标示Entry属于哪个key,相同key的Entry会存放在同一个Entries中。

Entry

对应于表中的一行,每行以列名作为key,对应的值作为value,构成KV结构。每个Entry拥有自己的分布式存储主key,不同Entry允许拥有相同的分布式存储主key。

Condition

Table中的“删改查”接口可传入条件,支持“等于”“大于”“小于”等过滤逻辑,接口根据条件对数据进行筛选后进行相应操作,返回结果数据。如果条件为空,则不做任何筛选。

举例

以某公司员工领用物资登记表为例,解释上述名词

_images/IMG_5092.PNG

  • 表中Name是分布式存储主key。
  • 表中的每一行为一个Entry。一共有4个Entry,每个Entry有三个字段。
  • Table中以Name为主key,存有3个Entries对象。第1个Entries中存有Alice的2条记录,第2个Entries中存有Bob的1条记录,第3个Entries中存有Chris的一条记录。
  • 调用Table类的查询接口时,查接口需要指定分布式存储主key和条件,设置查询的分布式存储主key为Alice,条件为price > 40,会查询出Entry1。
分布式存储表分类

表中的所有entry,都会有_status_,num,_hash_内置字段。

系统表

系统表默认存在,由存储驱动保证系统表的创建。

_images/IMG_5093.JPG

用户表

用户CRUD合约所创建的表,以_user_为表名,底层自动添加_user_前缀。

StorageState账户表

contract_data+Address+_作为表名。表中存储外部账户相关信息。表结构如下:

_images/IMG_5094.PNG

总结

FISCO BCOS发布至今,历经大量真实业务实践。分布式存储在持续改进的过程中,总结出适合于金融业务、高性能、高可用性和高可扩展的存储模型,架构愈发稳定成熟,未来分布式存储将继续作为区块链系统的基石,支持区块链系统的发展。

下篇文章,我会提供分布式存储的体验流程,敬请持续锁定FISCO BCOS开源社区。

系列精选

FISCO BCOS 2.0发布:(附新特性解读)

原理解析

群组架构的设计:使企业间建立多方协作的商业关系像拉群聊天一样简便。

使用教程

群组架构实操演练:以搭建仲裁链为例,并演示如何向该链发送交易。

FISCO BCOS 2.0使用教程: 分布式存储体验

作者:莫楠|FISCO BCOS 高级架构师

《分布式存储架构设计》一文发布后,社区成员对技术内核及使用非常关注。团队和社区热心小伙伴、业内专家针对分布式存储,进行了一箩筐地讨论。在此,跟大家分享交流心得,或有助你更好地理解和使用分布式存储:

  • FISCO BCOS 2.0的分布式存储采用库表风格,CRUD操作符合业务习惯。
  • 不用合约存储变量模式,解构了合约和数据的内嵌式耦合,合约升级更容易。
  • 存储访问引擎逻辑和数据结构更直观,容易适配各种存储引擎,扩展空间大。
  • 数据本身行列式存储,没有MPT树那般盘根错节的关系,更容易打快照和切割迁移。
  • 表加主键的结构索引数据,存取效率高,并发访问更容易。
  • 存储开销更少,容量模型和交易数、状态数线性相关,更容易预测业务容量,对海量服务非常有意义。
  • 细节方面,弱化状态MPT,但保留了交易和回执MPT,依旧可支持轻客户端,采用过程证明和存在证明,而不依赖易变的状态,不影响实现跨链。
  • 状态由增量HASH检验,每块交易产生的状态集会全网严格检验以保证一致性。
  • 一开始是面向SQL类型构建的,可支持MySQL和Oracle等引擎,然后适配到NoSQL类型,如LevelDB等。后续还会适配更多高速和海量存储引擎,在【单次io延迟/并发效率/容量扩展】这个三角关系中,探索出最优解。

分布式存储虽说是个大工程(团队几个快枪手撸了小一年才敢拿出来见人),但使用非常简单,本文就讲讲分布式存储的体验流程。初步接触用户,建议先从上篇入手(点标题可直接跳转)→ 分布式存储架构设计

配置分布式存储

分布式存储支持多种存储引擎,根据业务需求和部署环境灵活选择,可以配置为不同的存储引擎。

区块链的区块、交易等基础数据采用库表结构保存,状态数据的存储方式可配为库表结构或MPT,满足不同场景的需求。

分布式存储的配置项位于群组的配置文件中,各个群组可以使用单独的存储策略,群组配置文件位于区块链节点中名为conf/group.[群组号].genesis的路径下,如group.1.genesis,一旦群组启动,该群组的分布式存储的相关配置不能再改变。

分布式存储配置项示例如下:

[storage]

type=LevelDB:分布式存储的DB引擎类型,支持”LevelDB”和“External”(rc2版本)

[state]

type=storage:state类型,目前支持storage state和MPT state,默认为storage state

推荐使用 storage state除非必须使用MPT来追溯全局历史状态不建议使用MPT State

使用CRUD智能合约开发

分布式存储提供了专用的CRUD接口,支持合约直接访问底层的存储表。

访问CRUD需要引用分布式存储专用的智能合约Table.sol接口,该接口是数据库合约,可以创建表,并对表进行增删改查操作。

引用Table.sol

import "./Table.sol";

Table.sol的接口包括

  • createTable //创建表
  • select(string, Condition) //查询数据
  • insert(string, Entry) //插入数据
  • update(string, Entry, Condition) //更新数据
  • remove(string, Condition) //删除数据

每个接口的用法如下

创建表

// TableFactory的地址固定为0x1001
TableFactory tf = TableFactory(0x1001); 

// 创建t_test表,表的key_field为name,value_field为item_id,item_name 
// key_field表示分布式存储主key value_field表示表中的列,可以有多列,以逗号分隔
int count = tf.createTable("t_test", "name", "item_id,item_name");

查询数据

TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_test");

// 条件为空表示不筛选 也可以根据需要使用条件筛选
Condition condition = table.newCondition();

Entries entries = table.select(name, condition);

插入数据

TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_test"); 

Entry entry = table.newEntry();
entry.set("name", name);
entry.set("item_id", item_id);
entry.set("item_name", item_name);

int count = table.insert(name, entry);

更新数据

TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_test");

Entry entry = table.newEntry();
entry.set("item_name", item_name);

Condition condition = table.newCondition();
condition.EQ("name", name);
condition.EQ("item_id", item_id);

int count = table.update(name, entry, condition);

删除数据

TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_test");

Condition condition = table.newCondition();
condition.EQ("name", name);
condition.EQ("item_id", item_id);

int count = table.remove(name, condition);
PS

存储架构的优化是个基础工程,也是个大工程。实现的转变,其实是架构世界观的一种进化,影响会比看到的功能点更深远。此二文所讲述的,仅是分布式存储的冰山一角。更多原理和使用案例,请参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/release-2.0/docs/manual/smart_contract.html

系列精选

FISCO BCOS 2.0发布:(附新特性解读)

原理解析

群组架构的设计:使企业间建立多方协作的商业关系像拉群聊天一样简便。

使用教程

群组架构实操演练:以搭建仲裁链为例,并演示如何向该链发送交易。

并行计算:支持块内交易并行执行

区块链性能腾飞:基于DAG的并行交易执行引擎

作者:李陈希|FISCO BCOS 核心开发者

在区块链世界中,交易是组成事务的基本单元。交易吞吐量很大程度上能限制或拓宽区块链业务的适用场景,愈高的吞吐量,意味着区块链能够支持愈广的适用范围和愈大的用户规模。当前,反映交易吞吐量的TPS(Transaction per Second,每秒交易数量)是评估性能的热点指标。为了提高TPS,业界提出了层出不穷的优化方案,殊途同归,各种优化手段的最终聚焦点,均是尽可能提高交易的并行处理能力,降低交易全流程的处理时间。

在多核处理器架构已经成为主流的今天,利用并行化技术充分挖掘CPU潜力是行之有效的方案。FISCO BCOS 2.0 中设计了一种基于DAG模型的并行交易执行器(PTE,Parallel Transaction Executor)。

PTE能充分发挥多核处理器优势,使区块中的交易能够尽可能并行执行;同时对用户提供简单友好的编程接口,使用户不必关心繁琐的并行实现细节。基准测试程序的实验结果表明:相较于传统的串行交易执行方案,理想状况下4核处理器上运行的PTE能够实现约200%~300%的性能提升,且计算方面的提升跟核数成正比,核数越多性能越高。

PTE为助力FISCO BCOS性能腾飞奠定了坚实基础,本文将全面介绍PTE的设计思路及实现方案,主要包括以下内容:

  • 背景:传统方案的性能瓶颈与DAG并行模型的介绍
  • 设计思路:PTE应用到FISCO BCOS中时遇到的问题以及解决方案
  • 架构设计:应用PTE后FISCO BCOS的架构及核心流程
  • 核心算法:介绍主要用到的数据结构与主要算法
  • 性能测评:分别给出PTE的性能与可扩展性测试结果
背景

FISCO BCOS交易处理模块可以被抽象为一个基于交易的状态机。在FISCO BCOS中,『状态』即是指区块链中所有账户的状态,而『基于交易』即是指FISCO BCOS将交易作为状态迁移函数,并根据交易内容从旧的状态更新为新的状态。FISCO BCOS从创世块状态开始,不断收集网络上发生的交易并打包为区块,并在所有参与共识的节点间执行区块中的交易。当一个区块内的交易在多个共识节点上执行完成且状态一致,则我们称在该块上达成了共识,并将该区块永久记录在区块链中。

从上述区块链的打包→共识→存储过程中可以看到,执行区块中的所有交易是区块上链的必经之路。传统交易执行方案是:执行单元从待共识的区块逐条读出交易,执行完每一笔交易后,状态机都会迁移至下一个状态,直到所有交易都被串行执行完成,如下图所示:

_images/IMG_5175.PNG

显而易见,这种交易执行方式对性能并不友好。即使两笔交易没有交集,也只能按照先后顺序依次执行。就交易间的关系而言,既然一维的『线』结构有这般痛点,那何不把目光投向二维的『图』结构呢?

在实际应用中,根据每笔交易执行时需要使用的互斥资源(互斥意味着对资源的排他性使用,比如在上述转账问题互斥资源中,指的就是各个账户的余额状态), 我们可以组织出一张交易依赖关系图,为防止交易依赖关系在图中成环,我们可以规定交易列表中牵涉到相同的互斥资源,且排序靠后的交易,必须等待靠前的交易完成后才被执行,由此得到的输出便是一张反映交易依赖关系的有向无环图,即交易DAG。

如下图所示,左侧的6笔转账交易可以组织为右侧的DAG形式:

_images/IMG_5176.PNG

在交易DAG中,入度为0的交易是没有任何依赖项、可以被立即投入运行的就绪交易。当就绪交易的数量大于1时,就绪交易可以被分散至多个CPU核心上并行执行。当一笔交易执行完,依赖于该交易的所有交易的入度减1,随着交易不断被执行,就绪交易也源源不断被产生。在极限情况下,假如构造出的交易DAG层数为1 (即所有交易均是没有依赖项的独立交易),则交易整体执行速度的提升倍数将直接取决于处理器的核心数量n,此时若n大于区块内的交易数,则区块内所有交易的执行时间与单笔交易执行的时间相同。

理论上拥有如此让人无法拒绝的优美特性的交易DAG模型,该如何应用至FISCO BCOS中?

设计思路

要应用交易DAG模型,我们面临的首要问题便是:对于同一个区块,如何确保所有节点执行完后能够达到同一状态,这是一个关乎到区块链能否正常出块的关键问题。

FISCO BCOS采用验证(state root, transaction root, receipt root)三元组是否相等的方式,来判断状态是否达成一致。transaction root是根据区块内的所有交易算出的一个哈希值,只要所有共识节点处理的区块数据相同,则transaction root必定相同,这点比较容易保证,因此重点在于如何保证交易执行后生成的state和receipt root也相同。

众所周知,对于在不同CPU核心上并行执行的指令,指令间的执行顺序无法提前预测,并行执行的交易也存在同样情况。在传统的交易执行方案中,每执行一笔交易,state root便发生一次变迁,同时将变迁后的state root写入交易回执中,所有交易执行完后,最终的state root就代表了当前区块链的状态,同时再根据所有交易回执计算出一个receipt root。

可以看出,在传统的执行方案中,state root扮演着一个类似全局共享变量的角色。当交易被并行且乱序执行后,传统计算state root的方式显然不再适用,这是因为在不同的机器上,交易的执行顺序一般不同,此时无法保证最后的state root能够一致,同理,receipt root也无法保证一致。

在FISCO BCOS中,我们采用的解决方案是先执行交易,将每笔交易对状态的改变历史记录下来,待所有交易执行完后,再根据这些历史记录再算出一个state root,同时,交易回执中的state root,也全部变为所有交易执行完后最终的state root,由此就可以保证即使并行执行交易,最后共识节点仍然能够达成一致。

搞定状态问题后,下一个问题便是:如何判断两笔交易之间是否存在依赖关系?

若两笔交易本来无依赖关系但被判定为有,则会导致不必要的性能损失;反之,如果这两笔交易会改写同一个账户的状态却被并行执行了,则该账户最后的状态可能是不确定的。因此,依赖关系的判定是影响性能甚至能决定区块链能否正常工作的重要问题。

在简单的转账交易中,我们可以根据转账的发送者和接受者的地址,来判断两笔交易是否有依赖关系,比如如下3笔转账交易:A→B,C→D,D→E,可以很容易看出,交易D→E依赖于交易C→D的结果,但是交易A→B和其他两笔交易没有什么关系,因此可以并行执行。

这种分析在只支持简单转账的区块链中是正确的,但是一旦放到图灵完备、运行智能合约的区块链中,则可能不那么准确,因为我们无法准确知道用户编写的转账合约中到底有什么操作,可能出现的情况是:A->B的交易看似与C、D的账户状态无关,但是在用户的底层实现中,A是特殊账户,通过A账户每转出每一笔钱必须要先从C账户中扣除一定手续费。在这种场景下,3笔交易均有关联,则它们之间无法使用并行的方式执行,若还按照先前的依赖分析方法对交易进行划分,则必定会掉坑。

我们能否做到根据用户的合约内容自动推导出交易中实际存在哪些依赖项?答案是不太靠谱。我们很难去追踪用户合约中到底操作了什么数据,即使做到也需要花费不小的成本,这和我们优化性能的目标相去甚远。

综上,我们决定在FISCO BCOS中,将交易依赖关系的指定工作交给更熟悉合约内容的开发者。具体地说,交易依赖的互斥资源可以由一组字符串表示,FISCO BCOS暴露接口给到开发者,开发者以字符串形式定义交易依赖的资源,告知链上执行器,执行器则会根据开发者指定的交易依赖项,自动将区块中的所有交易排列为交易DAG。比如在简单转账合约中,开发者仅需指定每笔转账交易的依赖项是{发送者地址+接收者地址}。进一步地,如开发者在转账逻辑中引入了另一个第三方地址,那么依赖项就需要定义为{发送者地址+接受者地址+第三方地址}了。

这种方式实现起来较为直观简单,也比较通用,适用于所有智能合约,但也相应增加了开发者肩上的责任,开发者在指定交易依赖项时必须十分小心,如果依赖项没有写正确,后果无法预料。指定依赖项的相关接口会在后续文章中给出使用教程,本文暂且假定所有谈论到的交易依赖项都是明确无误的。

解决完上面两个比较重要的问题后,还剩下一些较为细节的工程问题:比如并行交易能否和非并行交易混合到一起执行?怎么保证资源字符串的全局唯一性?

答案也不复杂,前者可通过将非并行交易作为屏障(barrier)插入到交易DAG中——即我们认为,它即依赖于它的所有前序交易,同时又被它的所有后序交易依赖——来实现;后者可以通过在开发者指定的交易依赖项中,加入标识合约的特殊标志位解决。由于这些问题并不影响PTE的根本设计,本文暂不展开。

万事俱备,带着全新交易执行引擎PTE的FISCO BCOS已经呼之欲出。

架构设计

搭载PTE的FISCO BCOS架构图:

_images/IMG_5177.PNG

整个架构的核心流程如下:

用户通过SDK等客户端将交易发送至节点,此处的交易既可以是可并行执行的交易,也可以是不能并行执行的交易。随后交易在节点间同步,同时拥有打包权的节点调用打包器(Sealer),从交易池(Tx Pool)中取出一定量交易并将其打包成一个区块。此后,区块被发送至共识单元(Consensus)准备进行节点间共识。

共识前需要执行区块中的交易,此处便是PTE施展威力之处。从架构图中可以看到,PTE首先按序读取区块中的交易,并输入到DAG构造器(DAG Constructor)中,DAG构造器会根据每笔交易的依赖项,构造出一个包含所有交易的交易DAG,PTE随后唤醒工作线程池,使用多个线程并行执行交易DAG。汇合器(Joiner)负责挂起主线程,直到工作线程池中所有线程将DAG执行完毕,此时Joiner负责根据各个交易对状态的修改记录计算state root及receipt root,并将执行结果返回至上层调用者。

在交易执行完成后,若各个节点状态一致,则达成共识,区块随即写入底层存储(Storage),被永久记录于区块链上。

核心算法
1.交易DAG的数据结构

交易DAG的数据结构如下图所示:

_images/IMG_5178.PNG

Vertex类为最基础里的类型,在交易DAG中,每一个Vertex实例都表征一笔交易。Vertex类包含:

  • inDegree:表示该顶点的入度
  • outEdges:用于存储该节点的出边信息,即所有出边所连顶点的ID列表

DAG类用于对DAG的顶点与边关系进行封装,并提供操作DAG的接口,其包含:

  • vtxs:Vertex数组
  • topLevel:包含所有入度为0的顶点的队列,由于在执行过程中topLevel会动态变化且会被多个线程访问,因此其需要一个能够支持线程安全访问的容器
  • void init(int32_t size)接口:根据传入的size初始化一个包含相应数量顶点的DAG结构
  • addEdge(ID from, ID to)接口:用于在顶点from和顶点to之间建立边关系,具体地说,将顶点to的ID加入顶点from的outEdges中
  • void generate()接口:当所有的边关系录入完毕后,调用该方法以初始化topLevel成员
  • ID waitPop()接口:从topLevel中获取一个入度为0的顶点ID

TxDAG类是DAG类更上一层的封装,是DAG与交易之间建立联系的桥梁,其包含:

  • dag:持有的DAG类实例
  • exeCnt:已执行过的交易总数
  • totalTxs:交易总数
  • txs:区块中的交易列表
2. 交易DAG的构造流程

DAG构造器在构造交易DAG时,会首先将totalTxs成员的值设置为区块中的交易总数,并依据交易总数对dag对象进行初始化,dag会在vtxs中为每笔交易生成一个位置关系一一对应的顶点实例。随后,初始化一个空的资源映射表criticalFields,并按序逐个扫描每笔交易。

对于某笔交易tx,DAG构造器会在其解析出该交易的所有依赖项,对于每个依赖项均会去criticalFields中查询,如果对于某个依赖项d,有前序交易也依赖于该依赖项,则在这两笔交易间建边,并更新criticalFields中d的映射项为tx的ID。

交易DAG构造流程的伪代码如下所示:

criticalFields ← map<string, ID>();
totalTxs ← txs.size();
dag.init(txs.size());
for id ← 0 to txs.size() by 1 do
  tx ← txs[id];
  dependencies ← 解析出tx的依赖项;
  for d in dependencies do
    if d in criticalFields then
        dag.addEdge(id, criticalFields[d]);
      end
    criticalFields[d] = id;
    end
  end
end
dag.generate();
3.交易DAG的执行流程

PTE在被创建时,会根据配置生成一个用于执行交易DAG的工作线程池,线程池的大小默认等于CPU的逻辑核心数,此线程池的生命周期与PTE的生命周期相同。工作线程会不断调用dag对象的waitPop方法以取出入度为0的就绪交易并执行,执行后该交易的所有后序依赖任务的入度减1,若有交易的入度被减至0,则将该交易加入到topLevel中。循环上述过程,直到交易DAG执行完毕。

交易DAG执行流程的伪代码如下所示:

while exeCnt < totalTxs do
  id ← dag.waitPop();
  tx ← txs[id];
  执行tx;
  exeCnt ← exeCnt + 1;
  for txID in dag.vtxs[id].outEdges do
    dag.vtxs[txID].inDegree ← dag.vtxs[txID].inDegree - 1;
    if dag.vtxs[txID].inDegree == 0 then
      dag.topLevel.push(txID)
    end
  end
end    
性能测评

我们选用了2个基准测试程序,用以测试PTE给FISCO BCOS的性能带来了怎样的变化,它们分别是基于预编译框架实现的转账合约和基于Solidity语言编写的转账合约,两份合约代码的路径分别为:

FISCO-BCOS/libprecompiled/extension/DagTransferPrecompiled.cpp

web3sdk/src/test/resources/contract/ParallelOk.sol

我们使用一条单节点链进行测试,因为我们主要关注PTE的交易处理性能,因此并不考虑网络、存储的延迟带来的影响。

测试环境的基本硬件信息如下表所示

_images/IMG_5179.PNG

1.性能测试

_images/IMG_5180.JPG

性能测试部分,我们主要测试PTE和串行交易执行方式(Serial)在各个测试程序下的交易处理能力。可以看到,相对于串行执行方式,PTE从左至右分别实现了2.91和2.69倍的加速比。无论是对于预编译合约还是Solidity合约,PTE均有着不俗的性能表现。

2.可扩展性测试

_images/IMG_5181.JPG

可扩展性测试部分,我们主要测试PTE在不同CPU核心数下的交易处理能力,使用的基准测试程序是基于预编译框架实现的转账合约。可以看到,随着核数增加,PTE的交易吞吐量呈近似线性递增。但是同时也能看到,随着核数在增加,性能增长的幅度在放缓,这是因为随着核数增加线程间调度及同步的开销也会增大。

写在最后

从列表到DAG,PTE赋予了FISCO BCOS新的进化。更高的TPS会将FISCO BCOS带至更加广阔的舞台。予以长袖,FISCO BCOS必能善舞!

让木桶没有短板,FISCO BCOS全面推进并行化改造

作者:李陈希|FISCO BCOS 核心开发者

背景

PTE(Parallel Transaction Executor,一种基于DAG模型的并行交易执行器)的引入,使FISCO BCOS具备了并行执行交易的能力,显著提升了节点交易处理的效率。对这个阶段性结果,我们并不满足,继续深入挖掘发现,FISCO BCOS的整体TPS仍有较大提升空间。 用木桶打个比方:如果参与节点的交易处理所有模块构成木桶,交易执行只是组成整个木桶的一块木板,根据短板理论,一只木桶能盛多少水取决于桶壁上最矮的那块,同理,FISCO BCOS的性能也由速度最慢的组件决定。 尽管PTE取得了理论上极高的性能容量,但是FISCO BCOS的整体性能仍然会被其他模块较慢的交易处理速度所掣肘。为了能够最大化利用计算资源以进一步提高交易处理能力,在FISCO BCOS中全面推进并行化改造势在必行。

数据分析

根据并行程序设计的『分析→分解→设计→验证』四步走原则,首先需定位出系统中仍存在的性能瓶颈的精确位置,才能更深入地对任务进行分解,并设计相应的并行化策略。使用自顶向下分析法,我们将交易处理流程分为四个模块进行性能分析,这四个模块分别是:

区块解码(decode):区块在节点间共识或同步时需要从一个节点发送至另一个节点,这个过程中,区块以RLP编码的形式在网络间传输。节点收到区块编码后,需要先进行解码,将区块还原为内存中的二进制对象,然后才能做进一步处理。

交易验签(verify):交易在发送之前由发送者进行签名,签名得到的数据可以分为(v, r, s)三部分,验签的主要工作便是在收到交易或交易执行前,从(v, r, s)数据中还原出交易发送者的公钥,以验证交易发送者的身份。

交易执行(execute):执行区块中的所有交易,更新区块链状态。

数据落盘(commit):区块执行完成后,需要将区块及相关数据写入磁盘中,进行持久化保存。

以包含2500笔预编译转账合约交易的区块为测试对象,在我们的测试环境中,各阶段的平均耗时分布如下图所示:

_images/IMG_5182.JPG

从图中可以看出,2500笔交易的执行时间已经被缩短到了50毫秒以内,可以证明PTE对FISCO BCOS交易执行阶段的优化是行之有效的。但图中也暴露出了非常明显的问题:其他阶段的用时远远高于交易执行的用时,导致交易执行带来的性能优势被严重抵消,PTE无法发挥出其应有的价值。

早在1967年,计算机体系结构领域的元老Amdahl提出的以他名字命名的定律,便已经向我们阐明了衡量处理器并行计算后效率提升能力的经验法则:

_images/IMG_5183.PNG

其中,SpeedUp为加速比,Ws是程序的串行分量,Wp是程序中的并行分量,N为CPU数量。可以看出,在工作总量恒定的情况下,可并行部分代码占比越多,系统的整体性能越高。我们需要把思维从线性模型中抽离出来,继续细分整个处理流程,找出执行时间最长的程序热点,对这些代码段进行并行化从而将所有瓶颈逐个击破,这才是使通过并行化获得最大性能提升的最好办法。

根因拆解
1.串行的区块解码

区块解码主要性能问题出在RLP编码方法本身。RLP全称是递归的长度前缀编码,是一种用长度作为前缀标明编码对象中元素个数的编码方法。如下图所示,RLP编码的开头即是此编码中的对象个数(Object num)。在个数后,是相应个数的对象(Object)。递归地,每个对象,也是RLP编码,其格式也与下图相同。

需要特别注意的是,在RLP编码中。每个Object的字节大小是不固定的,Object num只表示Object的个数,不表示Object的字节长度。

_images/IMG_5184.JPG

RLP通过一种长度前缀与递归结合的方式,理论上可编码任意个数的对象。下图是一个区块的RLP编码,在对区块进行编码时,先递归至最底层,对多个sealer进行编码,多个sealer被编码并加上长度前缀后,编码成为一串RLP编码(sealerList),此编码又作为一个对象,被编入上层的一串RLP编码(blockHeader)中。此后层层递归,最后编码成为区块的RLP编码。由于RLP编码是递归的,在编码前,无法获知编码后的长度。

_images/IMG_5185.JPG

解码时,由于RLP编码中每个对象的长度不确定,且RLP编码只记录了对象的个数,没记录对象的字节长度,若要获取其中的一个编码对象,必须递归解码其前序的所有对象,在解码前序的对象后,才能访问到需要访问的编码对象的字节位置。例如在上图中,若需要访问区块中的第0笔交易,即tx0,必须先将blockHeader解码,而blockHeader的解码,需要再次递归,把parentHash,stateRoot直至sealerList都解码出来。

解码区块最重要的目的是解码出包含在区块中的交易,而交易的编码都是互相独立的,但在RLP特殊的编码方式下,解码一笔交易的必要条件是解码出上一笔交易,交易的解码任务之间环环相扣,形成了一种链式的依赖关系。需要指出的是,这种解码方式并不是RLP的缺陷,RLP的设计目标之一本就是尽量减少空间占用,充分利用好每一个字节,虽然编解码变得低效了些,但编码的紧凑度却是有目共睹,因此这种编码方式本质上还是一种时间换空间的权衡结果。

由于历史原因,FISCO BCOS中使用了RLP编码作为多处信息交换协议,贸然换用其他并行化友好的序列化方案可能会带来较大的开发负担。基于这一考虑,我们决定在原有的RLP编解码方案稍作修改,通过为每个被编码的元素添加额外的位置偏移信息,便可以做到并行解码RLP的同时不会改动大量原有代码。

2.交易验签&数据落盘开销大

通过对交易验签和数据落盘部分的代码进行拆解,我们发现两者的主要功能都集中在一个耗时巨大的for循环。交易验签负责按序取出交易,然后从交易的签名数据中取出(v, r, s)数据,并从中还原出交易发送者的公钥,其中,还原公钥这一步,由于涉及密码学算法,因此耗时不少;数据落盘负责从缓存中逐个取出交易相关数据,将其编码为JSON字符串后写入磁盘,由于JSON编码过程本身效率比较低,因此也是性能损失的重灾区。

两者代码分别如下所示:

// 交易验签
for(int i = 0; i < transactions.size(); ++i)
{
  tx = transactions[i];
  v, r, s = tx.getSignature();
  publicKey = recover(v, r, s);  // (v, r, s)中复原出发送者公钥
  ...
}
// 数据落盘
for(int i = 0; i < datas.size(); ++i)
{
  data = datas[i];
  jsonStr = jsonEncode(data);  // 将数据编码为JSON字符串进行存储
  db.commit(jsonStr);
  ...
}

两个过程共有的特点是,它们均是将同样的操作应用到数据结构中不同的部分,对于这种类型的问题,可以直接使用数据级并行进行改造。所谓数据级并行,即是将数据作为划分对象,通过将数据划分为大小近似相等的片段,通过在多个线程上对不同的数据片段上进行操作,达到并行处理数据集的目的。

数据级并行唯一的附加要求是任务之间彼此独立,毫无疑问,在FISCO BCOS的实现中,交易验签和数据落盘均满足这一要求。

优化实践
1.区块解码并行化

改造过程中,我们在系统中使用的普通RLP编码的基础上,加入了offset字段,用以索引每个Object的位置。如下图所示,改造后编码格式的开头,仍然是对象的个数(Object num),但是在个数字段后,是一个记录对象偏移量的数组(Offsets)。

_images/IMG_5186.JPG

数组中的每个元素有着固定的长度。因此要读取某个Offset的值,只需向访问数组一样,根据Offset的序号直接索引便可以进行随机访问。在Offsets后,是与RLP编码相同的对象列表。相应序号的Offset,指向相应序号的对象的RLP编码字节位置。因此,任意解码一个对象,只需要根据对象的序号,找到其偏移量,再根据偏移量,就可定位到相应对象的RLP编码字节位置。

编码流程也进行了重新设计。流程本身仍然基于递归的思路,对于输入的对象数组,首先将对象数组的大小编码在输出编码的开头处,若数组大小超过1,则按序逐个取出待编码对象并缓存其递归编码,并在Offsets数组中记录该对象的偏移位置,待数组遍历完后,将缓存的对象编码第一次性取出并附加至输出编码末尾;若数组大小为1,则递归对其编码并写入输出编码的末尾,结束递归。

编码流程的伪代码如下:

Rlps = RLP();  // Output,初始时为空
void encode(objs)  //Input: objs = 待编码对象的数组
{
  offset = 0;
  codes = [];
  
  objNum = objs.size()
  Rlps.push(objNum)
  if objNum > 1
  {
    for obj in objs
    {
      rlp = encode(obj);  // 递归调用编码方法
      Rlps.push(offset);
      offset += rlp.size();
      codes.add(rlp);  // 缓存递归编码的结果
    }
    for x in codes
    {
      Rlps.push(x);
    }
  }
  else
  {
    rlp = encode(objs[0]);
    Rlps.push(rlp);
  }
}

偏移量的引入使解码模块能够对元素编码进行随机访问。Offsets的数组范围可以在多个线程间均摊,从而每个线程可以并行访问对象数组的不同部分,分别进行解码。由于是只读访问,这种并行方式是线程安全的,仅需最后再对输出进行汇总即可。

解码流程的伪代码如下:

Objs decode(RLP Rlps)
{
  objNum = Rlps.objNum;  // 获取对象个数
  outputs = []  // 输出的对象数组
  if objNum > 1
  {
    parallel for i = 0 to objNum
    {
      offset = Rlps.offsets[i];
      code = Rlps.objs[offset];
      x = decode(code);
      outputs.add(x);   // 有序插入outputs
    }
  }
  else
  {
    outputs.add(decode(Rlps.objs[0]));
  }
  return outputs;
}
2.交易验签 & 数据落盘并行化

对于数据级并行,业内已有多种成熟的多线程编程模型。虽然Pthread这类显式的多线程编程模型能够提供对线程进行更精细的控制,但是需要我们对线程通信、同步拥有娴熟的驾驭技巧。实现的复杂度越高,犯错的几率越大,日后代码维护的难度也相应增加。我们的主要目标仅仅对密集型循环进行并行化,因此在满足需求的前提下,Keep It Simple & Stupid才是我们的编码原则,因此我们使用隐式的编程模型来达成我们的目的。

经过再三权衡,我们在市面上众多隐式多线程编程模型中,选择了来自Intel的线程构建块(Thread Building Blocks,TBB)开源库。在数据级并行方面,TBB算是老手,TBB运行时系统不仅屏蔽了底层工作线程的实现细节,还能够根据任务量自动在处理器间平衡工作负载,从而充分利用底层CPU资源。

使用TBB后,交易验签和数据落盘的代码如下所示:

// 并行交易验签
tbb::parallel_for(tbb::blocked_range<size_t>(0, transactions.size()),
[&](const tbb::blocked_range<size_t>& _range)
{
  for(int i = _range.begin(); i != _range.end(); ++i)
  {
    tx = transactions[i];
    v, r, s = tx.getSignature();
    publicKey = recover(v, r, s);  // (v, r, s)中复原出发送者公钥
    ...
  }
});
// 并行数据落盘
tbb::parallel_for(tbb::blocked_range<size_t>(0, transactions.size()),
[&](const tbb::blocked_range<size_t>& _range)
{
  for(int i = _range.begin(); i != _range.end(); ++i)
  {
    data = datas[i];
    jsonStr = jsonEncode(data);  // 将数据编码为JSON字符串进行存储
    db.commit(jsonStr);
    ...
  }
});

可以看到,除了使用TBB提供的tbb::parallel_for进行并行循环和tbb::blocked_range引用数据分片外,循环体内的代码几乎没有任何变化,接近C++原生语法正是TBB的特点。TBB提供了抽象层级较高的并行接口,如parallel_for、parallel_for_each这类泛型并行算法,从而使得改造能够较为容易地进行。同时,TBB不依赖任何语言或编译器,只要有能支持ISO C++标准的编译器,便有TBB的用武之地。

当然,使用TBB并不是完全没有额外负担,比如线程间安全还是需要开发人员的仔细分析来保证,但TBB考虑周到,提供了一套方便的工具来辅助我们解决线程间互斥的问题,如原子变量、线程局部存储和并行容器等,这些并行工具同样被广泛地应用在FISCO BCOS中,为FISCO BCOS的稳定运行保驾护航。

写在最后

经过一套并行优化的组合拳,FISCO BCOS的性能表现更上层楼。压力测试的结果表明,FISCO BCOS的交易处理能力,相较于并行化改造之前,成功提升了1.74倍,基本达到了这个环节的预期效果。

但是我们也深深明白,性能优化之路漫漫,木桶最短的一板总是交替出现,并行之道在于,通过反复的分析、拆解、量化和优化,使得各模块互相配合齐头并进,整个系统达到优雅的平衡,而最优解总是在“跳一跳”才能够得着的地方。

FISCO BCOS可并行合约开发框架(附实操教程)

作者:石翔|FISCO BCOS 核心开发者

本专题系列文章追到现在,也许你会想问,FISCO BCOS的并行到底怎么用?作为专题的完结篇,本文就来揭晓“庐山真面目”,并教你上手使用FISCO BCOS的并行特性!FISCO BCOS提供了可并行合约开发框架,开发者按照框架规范编写的合约,能够被FISCO BCOS节点并行地执行。并行合约的优势有:

  • 高吞吐:多笔独立交易同时被执行,能最大限度利用机器的CPU资源,从而拥有较高的TPS
  • 可拓展:可以通过提高机器的配置来提升交易执行的性能,以支持不断扩大业务规模

接下来,我将介绍如何编写FISCO BCOS并行合约,以及如何部署和执行并行合约。

预备知识
并行互斥

两笔交易是否能被并行执行,依赖于这两笔交易是否存在互斥。互斥,是指两笔交易各自操作合约存储变量的集合存在交集

例如,在转账场景中,交易是用户间的转账操作。用transfer(X, Y) 表示从X用户转到Y用户的转账接口。互斥情况如下:

_images/IMG_5187.PNG

此处给出更具体的定义:

  • 互斥参数:合约接口中,与合约存储变量的“读/写”操作相关的参数。例如转账的接口transfer(X, Y),X和Y都是互斥参数。
  • 互斥对象:一笔交易中,根据互斥参数提取出来的、具体的互斥内容。例如转账的接口transfer(X, Y), 一笔调用此接口的交易中,具体的参数是transfer(A, B),则这笔操作的互斥对象是[A, B];另外一笔交易,调用的参数是transfer(A, C),则这笔操作的互斥对象是[A, C]。

判断同一时刻两笔交易是否能并行执行,就是判断两笔交易的互斥对象是否有交集。相互之间交集为空的交易可并行执行。

编写并行合约

FISCO BCOS提供了可并行合约开发框架,开发者只需按照框架的规范开发合约,定义好每个合约接口的互斥参数,即可实现能被并行执行的合约。当合约被部署后,FISCO BCOS会在执行交易前,自动解析互斥对象,在同一时刻尽可能让无依赖关系的交易并行执行。

目前,FISCO BCOS提供了solidity与预编译合约两种可并行合约开发框架。

solidity合约的并行框架

编写并行的solidity合约,开发流程与开发普通solidity合约流程相同。在此基础上,只需将ParallelContract 作为需要并行的合约基类,并调用registerParallelFunction(),注册可以并行的接口即可。

先给出完整的举例。例子中的ParallelOk合约实现了并行转账的功能:

pragma solidity ^0.4.25;
import "./ParallelContract.sol";  // 引入ParallelContract.sol
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
    // 合约实现
    mapping (string => uint256) _balance;
    
    function transfer(string from, string to, uint256 num) public
{
        // 此处为简单举例,实际生产中请用SafeMath代替直接加减
        _balance[from] -= num; 
        _balance[to] += num;
    }
​
    function set(string name, uint256 num) public
{
        _balance[name] = num;
    }
​
    function balanceOf(string name) public view returns (uint256)
{
        return _balance[name];
    }
    
    // 注册可以并行的合约接口
    function enableParallel() public
{
        // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数(设计函数时互斥参数必须放在前面
        registerParallelFunction("transfer(string,string,uint256)", 2); // 冲突参数: string string
        registerParallelFunction("set(string,uint256)", 1); // 冲突参数: string
    } 
​
    // 注销并行合约接口
    function disableParallel() public
{
        unregisterParallelFunction("transfer(string,string,uint256)");
        unregisterParallelFunction("set(string,uint256)"); 
    } 
}

具体步骤如下:

step1 将ParallelContract作为合约的基类
pragma solidity ^0.4.25;
​
import "./ParallelContract.sol"; // 引入ParallelContract.sol
​
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
   // 合约实现
   
   // 注册可以并行的合约接口
   function enableParallel() public;
   
   // 注销并行合约接口
   function disableParallel() public;
}
step2 编写可并行的合约接口

合约中的public函数,是合约的接口。编写可并行的合约接口,是根据一定的规则,实现一个合约中的public函数。

确定接口是否可并行

可并行的合约接口,必须满足:

  • 无调用外部合约
  • 无调用其它函数接口
确定互斥参数

在编写接口前,先确定接口的互斥参数,接口的互斥即是对全局变量的互斥,互斥参数的确定规则为:

  • 接口访问了全局mapping,mapping的key是互斥参数
  • 接口访问了全局数组,数组的下标是互斥参数
  • 接口访问了简单类型的全局变量,所有简单类型的全局变量共用一个互斥参数,用不同的变量名作为互斥对象
确定参数类型和顺序

确定互斥参数后,根据规则确定参数类型和顺序,规则为:

  • 接口参数仅限:string、address、uint256、int256(未来会支持更多类型)
  • 互斥参数必须全部出现在接口参数中
  • 所有互斥参数排列在接口参数的最前
mapping (string => uint256) _balance; // 全局mapping
​
// 互斥变量from、to排在最前,作为transfer()开头的两个参数
function transfer(string from, string to, uint256 num) public
{
    _balance[from] -= num;  // from 是全局mapping的key,是互斥参数
    _balance[to] += num; // to 是全局mapping的key,是互斥参数 
}
​
// 互斥变量name排在最前,作为set()开头的参数
function set(string name, uint256 num) public
{
    _balance[name] = num;
}
step3 在框架中注册可并行的合约接口

在合约中实现 enableParallel() 函数,调用registerParallelFunction()注册可并行的合约接口。同时也需要实现disableParallel()函数,使合约具备取消并行执行的能力。

// 注册可以并行的合约接口
function enableParallel() public
{
    // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数
    registerParallelFunction("transfer(string,string,uint256)", 2); // transfer接口,前2个是互斥参数
    registerParallelFunction("set(string,uint256)", 1); // transfer接口,前1个四互斥参数
}  
​
// 注销并行合约接口
function disableParallel() public
{
    unregisterParallelFunction("transfer(string,string,uint256)");
    unregisterParallelFunction("set(string,uint256)"); 
}
step4 部署/执行并行合约

用控制台或Web3SDK编译和部署合约,此处以控制台为例:

部署合约

[group:1]> deploy ParallelOk.sol

调用 enableParallel()接口,让ParallelOk能并行执行

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 enableParallel

发送并行交易 set()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 set "jimmyshi" 100000

发送并行交易 transfer()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 transfer "jimmyshi" "jinny" 80000

查看交易执行结果 balanceOf()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 balanceOf "jinny"80000

用SDK发送大量交易的例子,将在下文的例子中给出。

预编译合约的并行框架

编写并行的预编译合约,开发流程与开发普通预编译合约流程相同。普通的预编译合约以Precompile为基类,在这之上实现合约逻辑。基于此,Precompile的基类还为并行提供了两个虚函数,继续实现这两个函数,即可实现并行的预编译合约。

step1 将合约定义成支持并行
bool isParallelPrecompiled() override { return true; }
step2 定义并行接口和互斥参数

注意,一旦定义成支持并行,所有的接口都需要进行定义。若返回空,表示此接口无任何互斥对象。互斥参数与预编译合约的实现相关,此处涉及对FISCO BCOS存储的理解,具体的实现可直接阅读代码或询问相关有经验的程序员。

// 根据并行接口,从参数中取出互斥对象,返回互斥对象
std::vector<std::string> getParallelTag(bytesConstRef param) override
{
    // 获取被调用的函数名(func)和参数(data)
    uint32_t func = getParamFunc(param);
    bytesConstRef data = getParamData(param);
​
    std::vector<std::string> results;
    if (func == name2Selector[DAG_TRANSFER_METHOD_TRS_STR2_UINT]) // 函数是并行接口
    {  
        // 接口为:userTransfer(string,string,uint256)
        // 从data中取出互斥对象
        std::string fromUser, toUser;
        dev::u256 amount;
        abi.abiOut(data, fromUser, toUser, amount);
        
        if (!invalidUserName(fromUser) && !invalidUserName(toUser))
        {
            // 将互斥对象写到results中
            results.push_back(fromUser);
            results.push_back(toUser);
        }
    }
    else if ... // 所有的接口都需要给出互斥对象,返回空表示无任何互斥对象
        
   return results;  //返回互斥
}
step3 编译,重启节点

手动编译节点的方法,参考FISCO BCOS技术文档。编译之后,关闭节点,替换掉原来的节点二进制文件,再重启节点即可。

举例:并行转账

此处分别给出solidity合约和预编译合约的并行举例。

配置环境

该举例需要以下执行环境:

  • Web3SDK客户端
  • 一条FISCO BCOS链

若需要压测最大的性能,至少需要:

  • 3个Web3SDK,才能产生足够多的交易
  • 4个节点,且所有Web3SDK都配置了链上所有的节点信息,让交易均匀发送到每个节点上,才能让链接收足够多的交易
并行Solidity合约:ParallelOk

基于账户模型的转账,是一种典型的业务操作。ParallelOk合约,是账户模型的一个举例,能实现并行的转账功能。ParallelOk合约已在上文中给出。

FISCO BCOS在Web3SDK中内置了ParallelOk合约,此处给出用Web3SDK来发送大量并行交易的操作方法。

step1 用SDK部署合约、新建用户、开启合约的并行能力
# 参数:<groupID> add <创建的用户数量> <此创建操作请求的TPS> <生成的用户信息文件名>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 add 10000 2500 user
# 在group1上创建了 10000个用户,创建操作以2500TPS发送的,生成的用户信息保存在user中

执行成功后,ParallelOk被部署到区块链上,创建的用户信息保存在user文件中,同时开启了ParallelOk的并行能力。

step2 批量发送并行转账交易

注意:在批量发送前,请将SDK的日志等级调整为ERROR,才能有足够的发送能力。

# 参数:<groupID> transfer <总交易数量> <此转账操作请求的TPS上限> <需要的用户信息文件> <交易互斥百分比:0~10>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 transfer 100000 4000 user 2
​
# 向group1发送了 100000比交易,发送的TPS上限是4000,用的之前创建的user文件里的用户,发送的交易间有20%的互斥。
step3 验证并行正确性

并行交易执行完成后,Web3SDK会打印出执行结果。TPS 是此SDK发送的交易在节点上执行的TPS。validation 是转账交易执行结果的检查。

Total transactions:  100000
Total time: 34412ms
TPS: 2905.9630361501804
Avg time cost: 4027ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 44  : 0.044000000000000004%
100  < time <  200ms  : 2617  : 2.617%
200  < time <  400ms  : 6214  : 6.214%
400  < time <  1000ms : 14190  : 14.19%
1000 < time <  2000ms : 9224  : 9.224%
2000 < time           : 67711  : 67.711%
validation:
   user count is 10000
   verify_success count is 10000
   verify_failed count is 0

可以看出,本次交易执行的TPS是2905。执行结果校验后,无任何错误(verify_failed count is 0)。

step4 计算总TPS

单个Web3SDK无法发送足够多的交易以达到节点并行执行能力的上限。需要多个Web3SDK同时发送交易。在多个Web3SDK同时发送交易后,单纯将结果中的TPS加和得到的TPS不够准确,需要直接从节点处获取TPS。

用脚本从日志文件中计算TPS

cd tools
sh get_tps.sh log/log_2019031821.00.log 21:26:24 21:26:59 # 参数:<日志文件> <计算开始时间> <计算结束时间>

得到TPS(3 SDK、4节点,8核,16G内存)

statistic_end = 21:26:58.631195
statistic_start = 21:26:24.051715
total transactions = 193332, execute_time = 34580ms, tps = 5590 (tx/s)
并行预编译合约:DagTransferPrecompiled

与ParallelOk合约的功能一样,FISCO BCOS内置了一个并行预编译合约的例子(DagTransferPrecompiled),实现了简单的基于账户模型的转账功能。该合约能够管理多个用户的存款,并提供一个支持并行的transfer接口,实现对用户间转账操作的并行处理。

注意:DagTransferPrecompiled仅做示例使用,请勿直接运用于生产环境。

step1 生成用户

用Web3SDK发送创建用户的操作,创建的用户信息保存在user文件中。命令参数与parallelOk相同,不同的仅仅是命令所调用的对象是precompile。

java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 add 10000 2500 user
step2 批量发送并行转账交易

用Web3SDK发送并行转账交易。

注意:在批量发送前,请将SDK的日志等级请调整为ERROR,才能有足够的发送能力。

java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 transfer 100000 4000 user 2
step3 验证并行正确性

并行交易执行完成后,Web3SDK会打印出执行结果。TPS 是此SDK发送的交易在节点上执行的TPS。validation 是转账交易执行结果的检查。

Total transactions:  80000
Total time: 25451ms
TPS: 3143.2949589407094
Avg time cost: 5203ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 0  : 0.0%
100  < time <  200ms  : 0  : 0.0%
200  < time <  400ms  : 0  : 0.0%
400  < time <  1000ms : 403  : 0.50375%
1000 < time <  2000ms : 5274  : 6.592499999999999%
2000 < time           : 74323  : 92.90375%
validation:
    user count is 10000
    verify_success count is 10000
    verify_failed count is 0

可以看出,本次交易执行的TPS是3143。执行结果校验后,无任何错误(verify_failed count is 0)。

step4 计算总TPS

单个Web3SDK无法发送足够多的交易以达到节点并行执行能力的上限。需要多个Web3SDK同时发送交易。在多个Web3SDK同时发送交易后,单纯将结果中的TPS加和得到的TPS不够准确,需要直接从节点处获取TPS。

用脚本从日志文件中计算TPS

cd tools
sh get_tps.sh log/log_2019031311.17.log 11:25 11:30 # 参数:<日志文件> <计算开始时间> <计算结束时间>

得到TPS(3 SDK、4节点,8核,16G内存)

statistic_end = 11:29:59.587145
statistic_start = 11:25:00.642866
total transactions = 3340000, execute_time = 298945ms, tps = 11172 (tx/s)
结果说明

本文举例中的性能结果,是在3SDK、4节点、8核、16G内存、1G网络下测得。每个SDK和节点都部署在不同的VPS中,硬盘为云硬盘。实际TPS会根据你的硬件配置、操作系统和网络带宽有所变化。

如您在部署过程中遇到阻碍或有问题需要咨询,可以进入FISCO BCOS官方技术交流群寻求解答。(进群请长按下方二维码识别添加小助手)

FISCO BCOS中交易的一生

作者:李陈希|FISCO BCOS 核心开发者

交易——区块链系统的核心,负责记录区块链上发生的一切。区块链引入智能合约后,交易便超脱『价值转移』的原始定义,其更加精准的定义应该是区块链中一次事务的数字记录。无论大小事务,都需要交易的参与。

交易的一生,贯穿下图所示的各个阶段。本文将梳理交易的整个流转过程,一窥FISCO BCOS交易完整生命周期。

_images/IMG_5188.PNG

交易生成

用户的请求给到客户端后,客户端会构建出一笔有效交易,交易中包括以下关键信息:

  1. 发送地址:即用户自己的账户,用于表明交易来自何处。
  2. 接收地址:FISCO BCOS中的交易分为两类,一类是部署合约的交易,一类是调用合约的交易。前者,由于交易并没有特定的接收对象,因此规定这类交易的接收地址固定为0x0;后者,则需要将交易的接收地址置为链上合约的地址。
  3. 交易相关的数据:一笔交易往往需要一些用户提供的输入来执行用户期望的操作,这些输入会以二进制的形式被编码到交易中。
  4. 交易签名:为了表明交易确实是由自己发送,用户会向SDK提供私钥来让客户端对交易进行签名,其中私钥和用户账户是一一对应的关系。

之后,区块链客户端会再向交易填充一些必要的字段,如用于防交易重放的交易ID及blockLimit。交易的具体结构和字段含义可以参考编码协议文档,交易构造完成后,客户端随后便通过Channel或RPC信道将交易发送给节点。

_images/IMG_5189.PNG

交易池

区块链交易被发送到节点后,节点会通过验证交易签名的方式来验证一笔交易是否合法。若一笔交易合法,则节点会进一步检查该交易是否重复出现过,若从未出现过,则将交易加入交易池缓存起来。若交易不合法或交易重复出现,则将直接丢弃交易。

_images/IMG_5190.PNG

交易广播

节点在收到交易后,除了将交易缓存在交易池外,节点还会将交易广播至该节点已知的其他节点。

为了能让交易尽可能到达所有节点,其他收到广播过来的交易节点,也会根据一些精巧的策略选择一些节点,将交易再一次进行广播,比如:对于从其他节点转发过来的交易,节点只会随机选择25%的节点再次广播,因为这种情况一般意味着交易已经开始在网络中被节点接力传递,缩减广播的规模有助于避免因网络中冗余的交易太多而出现的广播风暴问题。

交易打包

为了提高交易处理效率,同时也为了确定交易之后的执行顺序保证事务性,当交易池中有交易时,Sealer线程负责从交易池中按照先进先出的顺序取出一定数量的交易,组装成待共识区块,随后待共识区块会被发往各个节点进行处理。

_images/IMG_5191.JPG

交易执行

节点在收到区块后,会调用区块验证器把交易从区块中逐一拿出来执行。如果是预编译合约代码,验证器中的执行引擎会直接调用相应的C++功能,否则执行引擎就会把交易交给EVM(以太坊虚拟机)执行。

交易可能会执行成功,也可能因为逻辑错误或Gas不足等原因执行失败。交易执行的结果和状态会封装在交易回执中返回。

_images/IMG_5192.JPG

交易共识

区块链要求节点间就区块的执行结果达成一致才能出块。FISCO BCOS中一般采用PBFT算法保证整个系统的一致性,其大概流程是:各个节点先独立执行相同的区块,随后节点间交换各自的执行结果,如果发现超过2/3的节点都得出了相同的执行结果,那说明这个区块在大多数节点上取得了一致,节点便会开始出块。

交易落盘

在共识出块后,节点需要将区块中的交易及执行结果写入硬盘永久保存,并更新区块高度与区块哈希的映射表等内容,然后节点会从交易池中剔除已落盘的交易,以开始新一轮的出块流程。用户可以通过交易哈希等信息,在链上的历史数据中查询自己感兴趣的交易数据及回执信息。

FISCO BCOS中交易池及其优化策略

作者:陈宇杰|FISCO BCOS 核心开发者

作者语

FISCO BCOS区块链系统中,交易上链之前,均存储在交易池中。交易池是区块链小能手,一方面担任质检员的职务,将所有非法交易拒之门外;一方面担任供应商的职责,向共识模块输送合法交易;还负责向客户端推送上链通知。可以说,FISCO BCOS区块链系统的交易池异常忙碌,其性能会直接影响区块链系统性能。本文就带领大家揭开交易池面纱,了解交易池多重身份,并一起领会FISCO BCOS区块链系统中的交易池如何在多重角色间游刃有余。


初识交易池

_images/IMG_5193.PNG

如上图所示,FISCO BCOS区块链系统中,接收存储客户端发送的交易是交易池基本职责,这些交易是共识模块打包交易和同步模块广播交易的“原材料”。交易池需要保证这些交易“原材料”的质量,对交易合法性进行验证。当然,为了防止DOS攻击,FISCO BCOS对交易池容量进行了限制,当交易池内交易数目超过容量限制后,会拒绝客户端发送的新交易。

交易池的重要性

FISCO BCOS区块链系统中,交易池作为关键系统模块,同时肩负着和SDK以及后端多个模块的交互,本节就以交易池的多重职责为切入点,和大家一起看看交易池有多忙。

交易池的四重职责

_images/IMG_5194.PNG

上图展示了交易从客户端发出到上链整个生命周期里,交易池所扮演的多重角色:

  • 交易质量检测员:交易放入交易池前,检测交易的有效性,有效的交易必须满足:①签名有效;② 非重复交易;③ 非已上链交易。
  • 交易供应商:存储合法交易,为后端模块提供交易“原材料”。
  • 验签防重小助手:为共识模块提供区块验签接口,仅验证在交易池内未命中交易,提升共识区块验证效率。
  • 交易上链通知员:交易上链成功后,将交易执行结果通知到客户端。

交易池作为区块链核心模块,身兼四职,每笔交易处理流程中,均需与后端三个模块、内部四个模块以及客户端进行多达八次交互,确实是异常忙碌。

交易池的作用

下面就以区块链节点交易处理生命周期为例,详细了解交易池各重角色在其中所起的作用。

_images/IMG_5195.PNG

如上图,客户端发送的交易到节点会被流水线式处理,每条流水均需有五个处理流程:

  • 交易检测:客户端发送的交易发送到节点后,首先要经过交易池的质量检测员的检验,交易池仅会把签名有效、不重复、未上链的交易放入交易池内。
  • 交易存储:交易过了“质量检测”后,被存储交易池内,此时交易池身负“供应商”的角色,共识模块从交易池内获取新交易用于打包成区块;同步模块从交易池内获取新增交易,广播给所有其他共识节点。
  • 交易打包&&交易共识:共识模块从交易池内获取合法交易,打包成新区块,并广播到所有其他共识节点,其他共识节点收到打包的新区块后,为了保证区块的合法性,会验证区块内每笔交易签名。考虑到交易验签是非常耗时的操作,且新区块内交易有极大概率在其他节点交易池内命中,为了提升共识验证效率,交易验签防重小助手此时派上了用场,它会仅验证新区块中未在本地交易池命中的交易签名。
  • 交易提交:交易共识达成后,会调用存储模块,将交易及其执行结果提交到区块链数据库。
  • 交易通知:交易上链成功后,交易池的上链通知员将交易执行结果通知给客户端。

在交易从发出到上链的整个生命周期里,每个过程都有交易池的参与,因此交易池对于整个区块链系统非常重要,交易池每个处理过程都直接影响了区块链系统性能。

交易池优化

通过前面介绍,我们了解到FISCO BCOS区块链系统的交易池异常忙碌,并且直接影响了区块链系统性能,本节就来详细说说交易池的优化历程和优化方法。

优化交易处理流水线效率

通过上面交易处理流水线示意图可看出,交易池参与了交易处理的每个流程,因此交易池每个处理流程都对系统性能影响很大。FISCO BCOS区块链系统采用拆分且并行执行交易验证任务、交易异步通知策略来优化交易流水线处理效率。

优化交易验证效率

FISCO BCOS rc2引入并行交易后,FISCO BCOS开发者们发现压测过程中共识模块每个区块经常无法打满交易,偶尔还会出现共识模块出空块、等待交易池提供新交易的现象。排查发现,交易池作为交易检测员任务太重,既要验证交易签名,又要检查交易是否重复、是否已上链,导致向交易供应商提供交易效率非常低,经常出现交易供不应求的情况,严重影响了区块链系统TPS。

下图描述了这种供不应求的现象:

_images/IMG_5196.PNG

为了打破交易池供不应求的困局,优化交易流水线处理效率,FISCO BCOS区块链系统引入专门的交易验签模块,并将“交易检测员”的“验证签名”职责分担给了这个新模块,且为了进一步提升交易存储效率,交易验签模块并行对交易进行验证。优化交易处理流水线后,“交易检测员”的工作负担轻了很多,交易供应商完全能满足共识模块的交易需求,而且还留有部分存货。

_images/IMG_5197.PNG

优化处理流水线后,”交易检测员”的重活被”人力充足”的”验签模块”分担了,系统性能显著提升:采用并行交易压测,FISCO BCOS区块链系统性能突破了1W。

交易异步通知

通过前面的介绍,大家了解到交易池还承担着交易通知的职责,”交易通知员”也是个忙碌的角色,它需要在收到区块落盘信号后,将所有上链交易通知给客户端,当且仅当共识模块确认上一轮共识的上链交易都会被通知到后,共识模块才会开始下一轮共识,交易同步推送无疑会拖慢共识流程。为了进一步优化流水线处理效率,FISCO BCOS区块链系统采用了交易异步通知策略:存储模块将交易通知结果放置于交易通知队列后直接返回,共识模块直接开始下一轮共识流程,与此同时,交易通知模块将交易执行结果依次返回给客户端。

如下图所示:

_images/IMG_5198.PNG

采用交易异步通知策略后,交易通知不会阻塞共识流程,大约能提升10%的系统性能。

双缓存队列

FISCO BCOS 2.1之后,FISCO BCOS团队认真统计了每个区块的处理时间,觉得系统性能还有继续上升的空间,于是决定继续优化性能,进一步提升FISCO BCOS区块链系统的处理能力。

当存储模块和执行模块性能优化到极致后,可是最终的压测结果总是不符合预期。经过排查发现交易池又出现了供不应求的情况,只是这种供不应求是客户端引起的,客户端发送交易后,大量线程阻塞在等待交易验证通过,返回交易哈希,无法空出更多线程发送新交易。

为了提升节点对客户端的响应速度,从而提升客户端交易发送速率,FISCO BCOS区块链系统在“交易供应商”持有的交易储存队列基础上,引入了交易预缓冲区,存放客户端发送到节点的交易,并直接对客户端进行响应。

交易预缓冲区会持续将缓存的交易送到”验签模块”和”交易检测员”处进行验证,验证通过的交易最终会被放入到真正的交易队列供交易供应商调度,如下图所示:

_images/IMG_5199.PNG

这种双缓存队列机制,大大提升了交易池对客户端的响应速度,客户端也可以持续腾出线程继续发送新交易,优化后,客户端发送性能更高,交易池供大于求。

小结

交易池很忙,FISCO BCOS区块链系统中,交易池更忙,它被用来验证交易、存储交易、防止交易重复验签、向客户端推送交易执行结果等。交易池很重要,FISCO BCOS区块链系统中,交易池更重要,任务繁重的”交易检测员”会大大降低交易插入速率,导致交易供不应求;没有”交易预缓冲区”的交易池会阻塞客户端交易发送线程,降低客户端交易发送速率;交易同步推送,会损耗10%左右的系统性能… …

在性能优化的道路上,交易池性能优化一直会被排在重要的位置。

FISCO BCOS性能

峰值TPS:2万+ TPS(PBFT)

交易确认时延:秒级

FISCO BCOS的速度与激情:性能优化方案最全解密

作者:石翔|FISCO BCOS 核心开发者

上篇文章说到,区块链的速度困境是“贵”在信任,“慢”得其所,说到底,根因还是在其“用计算换信任”的设计思路上。业内普遍赞誉区块链是信任的机器,为了实现信任,区块链不得不做很多复杂而繁琐的操作,同步、验签、执行、共识等,都是区块链中必不可少的环节。

这就像行车时的“交规”,时刻在告诉我们开发者,为了安全,请按规定速度行驶!然而,社区里大家还是有着共同的心声:真的太慢了!

那么,能不能对这台信任的机器来一次装备升级,让它既安全又快速呢?经过团队深入的探索和实践,我们打通了多条迈向极速时代的路子。回看整个过程,恰如打造了一台性能卓越的汽车。

  • 高功率发动机☞基于DAG的交易并行执行引擎
  • 燃料输送装置☞分布式存储
  • 前排与后座☞共识与同步的流程优化
  • 传动装置☞全方位并行处理
  • 氢燃料☞预编译合约
  • 监控仪表☞全面的性能分析工具
  • 专属的方向盘☞可并行合约开发框架
高功率发动机:基于DAG的交易并行执行引擎——极尽所能让交易并行执行

传统的交易执行引擎,采用串行的方式执行交易,交易只能被一条条依次执行。一个区块中无论有多少交易,都需要一条条依次执行完。这就好比一个低功率的发动机,纵使给它装上巨无霸油箱,仍然无法输出强大的动力。1个气缸不够,改成4个气缸,8个气缸,行不行?

FISCO BCOS实现了一种交易并行执行引擎(PTE),能够让一个区块内的多个交易同时被执行。若机器有4个核,最大限度能支持4笔交易同时执行,如果有8个核,则能支持8笔交易同时执行。当然,在“交规”管控下,并行执行的正确性需要得到保证,也就是说,并行执行的结果和串行执行的结果需要一致。为了保证并行执行的一致性,FISCO BCOS的交易并行执行引擎(PTE)引入了DAG(有向无环图)这个数据结构。

执行引擎在执行区块中的交易之前,会根据交易相互间的互斥关系,自动构建交易间的依赖关系。这种依赖关系是一种DAG,在引擎执行时,会根据DAG让可并行的交易并行执行。这样一来,交易执行的一致性得以保证,交易执行的吞吐量也得到数量级的提升。

燃料输送装置:分布式存储——为引擎提供足够的燃料

传统的区块链存储模式,是一棵参天的MPT树。区块链上所有的数据,都汇聚到这棵树上来。对数据的每一次写或读,都是一次从树枝到树根(或者从树根到树枝)的漫长旅行。随着链上的数据越来越多,树也越来越高,树枝到树根的路程会变得越来越长。更麻烦的是,虽然树枝有很多个,但是树根只有一个。对海量链上数据的写或读,就像千军万马抢过独木桥一样悲壮,惨烈程度可想而知。所以传统的区块链,选择了一个个来,一个数据一个数据地读,一条交易一条交易地执行。形象地说,就是用一根输油管为引擎输送燃料。

这样肯定不行!我们需要多个输油管为引擎输送燃料!这一次,FISCO BCOS不是粗暴地为引擎接上多个输油管(MPT树),因为用输油管输油(用MPT存数据)实在是太慢了。我们干脆抛弃输油管,直接把引擎泡进油箱里!这样的比喻也许欠妥当,但理解了FISCO BCOS的执行引擎和存储设计,相信你会和我有一样的感慨。

我们抛弃MPT树,采用“表”的方式组织数据。执行引擎读写数据,无需再对MPT树进行树根到树枝的遍历,直接在“表”上读写。这样一来,每一条数据的读写,都不依赖于一个全局的操作,可以分开独立进行。这就为交易并行执行引擎(PTE)提供了并发数据读写的基础,类似于泡在油箱里的发动机,汽油直接流入气缸,谁也不共用谁的输油管。

分布式存储详细解析请点击:分布式存储架构设计

前排与后座:共识与同步的流程优化——不搞平均主义,先富带动后富

在区块链节点中,同步模块和共识模块,是一对形影不离的双胞胎,有时相互帮助,有时也为争夺资源大打出手。在以往的设计中,同步模块和共识模块并没有优先级的区分。好比坐车,谁坐前排,谁坐后排,没个规定,导致这对双胞胎经常在争夺先后顺序上浪费大量的时间。

一切从实际出发,先富带动后富!

共识模块负责主导整个区块链出块的节奏,应让共识模块先行。而同步模块,理应扮演好配合的角色,辅佐共识模块更快出块。基于此思想,FISCO BCOS对共识与同步的流程进行了优化:

  • 第一,将同步模块中交易验签的操作,从P2P的回调线程中剥离出来,让共识模块能够更加顺畅地收到共识消息,以便更快进行共识。
  • 第二,对交易验签进行去重,并对交易的二进制进行缓存。一笔交易只进行一次验签和解码,为共识模块中区块的执行腾出更多的CPU资源。
  • 第三,优化同步流程,在交易同步之前,尽可能地让同步模块跑在共识模块之前,从而使得同步模块优先把交易写入交易池中,优先进行解码和验签,让共识模块拿到交易时,免去解码和验签的过程,更快进入区块打包阶段。

总而言之,言而总之。一切的目的,都是为共识的流程服务,让其更快更顺畅地打包、执行、共识、出块。

传动装置:全方位并行处理——让功率有效地输出

若不搭配合适的传动装置,再高功率的引擎也无法将功率有效输出。签名验证、编解码、数据落盘,是区块链中除开交易以外,其他耗时占用较高的部分。在以往的设计中,签名验证、编解码、数据落盘,都是串行执行的。就算交易被并行执行,这台信任机器的性能,也受制于这三个环节的性能。

这三个环节的性能问题一日不绝,性能永无抬头之日!那就给高功率发动机配上一个高性能传动装置,释放出它的威力来。

FISCO BCOS引入了并行容器,让数据的读写天然支持并发访问。在此基础上,对于交易的验签,直接让交易的验签并行执行,交易与交易间的验签流程互不影响;对于编解码,改造了RLP的编码格式,使原来只能按顺序读写的RLP格式支持并行的编解码;对于区块落盘,对状态的改变进行并行编码。

不仅如此,FISCO BCOS在可并行之处都进行了并行处理,让系统CPU资源得到最大化利用。交易不仅在进入合约引擎时能并行执行,在诸如签名验证、编解码、数据落盘等环节中也都是被并行处理的。强大的发动机,配合上高性能的传动装置,果然效果显著啊!

氢燃料:预编译合约 ——高效率的轻量级合约框架

众所周知,区块链上跑的是智能合约,智能合约用solidity语言编写。solidity合约部署到链上,烧掉Gas,得到结果。但是,有没有想过换一种燃料,一种成本更低却又让车跑得更快的燃料?

且看FISCO BCOS自研的“氢燃料”——预编译合约!

FISCO BCOS为机构提供了一种高性能、定制化、轻量级合约框架。机构可按照自身业务需求,将自己实现的预编译合约内置于FISCO BCOS节点中。预编译合约用C++编写,其性能高于solidity引擎,而且启动速度更快、指令更精简、内存使用更少。正如“氢燃料”一般,成本更低,热值更高,让汽车跑得更快!当然,提取“氢燃料”需要下一点小功夫,预编译合约的实现相对复杂,门槛比较高。了解预编译合约请点击:预编译合约架构设计

监控仪表:多维度性能分析工具——给人全局在握的踏实感

FISCO BCOS在开发过程中使用了大量的性能分析工具,就像汽车上安装了诸多指数清晰的监控仪表。我们采用了主流的性能分析工具,如perf,systemtap,对程序的热点、锁、内存等进行了分析,还根据区块链的程序流程特点,开发了定制化的性能分析工具,以便在共识,区块验证、存储模块和流程中更好地评估数据。工具能够对程序中各个阶段的时间占比、时间变化进行分析。有了可靠的量化工具,开发者在做每一处优化时,都能做到心中有数。

专属的方向盘:可并行合约开发框架——给开发者流畅的操作体验

一切准备就绪,上车!坐上驾驶位,你手中掌控的,将是FISCO BCOS为你提供的专属方向盘——可并行合约开发框架!如何合理操作这台机器,全靠这个方向盘。“手握”并行合约开发框架,在开发并行合约时,合约开发者无需关心具体的底层逻辑,而是将更多注意力集中在自己的合约逻辑上。当合约部署成功后,并行合约会被底层代码自动识别,自动并行地执行!

现在,终于开上了车。没过瘾?没关系,接下来的几篇,请接好,是真正的硬核干货!我们将在下一篇文章中,系统介绍FISCO BCOS中基于DAG模型的并行交易执行器(PTE),敬请期待~

区块链性能腾飞:基于DAG的并行交易执行引擎

作者:李陈希|FISCO BCOS 核心开发者

在区块链世界中,交易是组成事务的基本单元。交易吞吐量很大程度上能限制或拓宽区块链业务的适用场景,愈高的吞吐量,意味着区块链能够支持愈广的适用范围和愈大的用户规模。当前,反映交易吞吐量的TPS(Transaction per Second,每秒交易数量)是评估性能的热点指标。为了提高TPS,业界提出了层出不穷的优化方案,殊途同归,各种优化手段的最终聚焦点,均是尽可能提高交易的并行处理能力,降低交易全流程的处理时间。

在多核处理器架构已经成为主流的今天,利用并行化技术充分挖掘CPU潜力是行之有效的方案。FISCO BCOS 2.0 中设计了一种基于DAG模型的并行交易执行器(PTE,Parallel Transaction Executor)。

PTE能充分发挥多核处理器优势,使区块中的交易能够尽可能并行执行;同时对用户提供简单友好的编程接口,使用户不必关心繁琐的并行实现细节。基准测试程序的实验结果表明:相较于传统的串行交易执行方案,理想状况下4核处理器上运行的PTE能够实现约200%~300%的性能提升,且计算方面的提升跟核数成正比,核数越多性能越高。

PTE为助力FISCO BCOS性能腾飞奠定了坚实基础,本文将全面介绍PTE的设计思路及实现方案,主要包括以下内容:

  • 背景:传统方案的性能瓶颈与DAG并行模型的介绍
  • 设计思路:PTE应用到FISCO BCOS中时遇到的问题以及解决方案
  • 架构设计:应用PTE后FISCO BCOS的架构及核心流程
  • 核心算法:介绍主要用到的数据结构与主要算法
  • 性能测评:分别给出PTE的性能与可扩展性测试结果
背景

FISCO BCOS交易处理模块可以被抽象为一个基于交易的状态机。在FISCO BCOS中,『状态』即是指区块链中所有账户的状态,而『基于交易』即是指FISCO BCOS将交易作为状态迁移函数,并根据交易内容从旧的状态更新为新的状态。FISCO BCOS从创世块状态开始,不断收集网络上发生的交易并打包为区块,并在所有参与共识的节点间执行区块中的交易。当一个区块内的交易在多个共识节点上执行完成且状态一致,则我们称在该块上达成了共识,并将该区块永久记录在区块链中。

从上述区块链的打包→共识→存储过程中可以看到,执行区块中的所有交易是区块上链的必经之路。传统交易执行方案是:执行单元从待共识的区块逐条读出交易,执行完每一笔交易后,状态机都会迁移至下一个状态,直到所有交易都被串行执行完成,如下图所示:

_images/IMG_51751.PNG

显而易见,这种交易执行方式对性能并不友好。即使两笔交易没有交集,也只能按照先后顺序依次执行。就交易间的关系而言,既然一维的『线』结构有这般痛点,那何不把目光投向二维的『图』结构呢?

在实际应用中,根据每笔交易执行时需要使用的互斥资源(互斥意味着对资源的排他性使用,比如在上述转账问题互斥资源中,指的就是各个账户的余额状态), 我们可以组织出一张交易依赖关系图,为防止交易依赖关系在图中成环,我们可以规定交易列表中牵涉到相同的互斥资源,且排序靠后的交易,必须等待靠前的交易完成后才被执行,由此得到的输出便是一张反映交易依赖关系的有向无环图,即交易DAG。

如下图所示,左侧的6笔转账交易可以组织为右侧的DAG形式:

_images/IMG_51761.PNG

在交易DAG中,入度为0的交易是没有任何依赖项、可以被立即投入运行的就绪交易。当就绪交易的数量大于1时,就绪交易可以被分散至多个CPU核心上并行执行。当一笔交易执行完,依赖于该交易的所有交易的入度减1,随着交易不断被执行,就绪交易也源源不断被产生。在极限情况下,假如构造出的交易DAG层数为1 (即所有交易均是没有依赖项的独立交易),则交易整体执行速度的提升倍数将直接取决于处理器的核心数量n,此时若n大于区块内的交易数,则区块内所有交易的执行时间与单笔交易执行的时间相同。

理论上拥有如此让人无法拒绝的优美特性的交易DAG模型,该如何应用至FISCO BCOS中?

设计思路

要应用交易DAG模型,我们面临的首要问题便是:对于同一个区块,如何确保所有节点执行完后能够达到同一状态,这是一个关乎到区块链能否正常出块的关键问题。

FISCO BCOS采用验证(state root, transaction root, receipt root)三元组是否相等的方式,来判断状态是否达成一致。transaction root是根据区块内的所有交易算出的一个哈希值,只要所有共识节点处理的区块数据相同,则transaction root必定相同,这点比较容易保证,因此重点在于如何保证交易执行后生成的state和receipt root也相同。

众所周知,对于在不同CPU核心上并行执行的指令,指令间的执行顺序无法提前预测,并行执行的交易也存在同样情况。在传统的交易执行方案中,每执行一笔交易,state root便发生一次变迁,同时将变迁后的state root写入交易回执中,所有交易执行完后,最终的state root就代表了当前区块链的状态,同时再根据所有交易回执计算出一个receipt root。

可以看出,在传统的执行方案中,state root扮演着一个类似全局共享变量的角色。当交易被并行且乱序执行后,传统计算state root的方式显然不再适用,这是因为在不同的机器上,交易的执行顺序一般不同,此时无法保证最后的state root能够一致,同理,receipt root也无法保证一致。

在FISCO BCOS中,我们采用的解决方案是先执行交易,将每笔交易对状态的改变历史记录下来,待所有交易执行完后,再根据这些历史记录再算出一个state root,同时,交易回执中的state root,也全部变为所有交易执行完后最终的state root,由此就可以保证即使并行执行交易,最后共识节点仍然能够达成一致。

搞定状态问题后,下一个问题便是:如何判断两笔交易之间是否存在依赖关系?

若两笔交易本来无依赖关系但被判定为有,则会导致不必要的性能损失;反之,如果这两笔交易会改写同一个账户的状态却被并行执行了,则该账户最后的状态可能是不确定的。因此,依赖关系的判定是影响性能甚至能决定区块链能否正常工作的重要问题。

在简单的转账交易中,我们可以根据转账的发送者和接受者的地址,来判断两笔交易是否有依赖关系,比如如下3笔转账交易:A→B,C→D,D→E,可以很容易看出,交易D→E依赖于交易C→D的结果,但是交易A→B和其他两笔交易没有什么关系,因此可以并行执行。

这种分析在只支持简单转账的区块链中是正确的,但是一旦放到图灵完备、运行智能合约的区块链中,则可能不那么准确,因为我们无法准确知道用户编写的转账合约中到底有什么操作,可能出现的情况是:A->B的交易看似与C、D的账户状态无关,但是在用户的底层实现中,A是特殊账户,通过A账户每转出每一笔钱必须要先从C账户中扣除一定手续费。在这种场景下,3笔交易均有关联,则它们之间无法使用并行的方式执行,若还按照先前的依赖分析方法对交易进行划分,则必定会掉坑。

我们能否做到根据用户的合约内容自动推导出交易中实际存在哪些依赖项?答案是不太靠谱。我们很难去追踪用户合约中到底操作了什么数据,即使做到也需要花费不小的成本,这和我们优化性能的目标相去甚远。

综上,我们决定在FISCO BCOS中,将交易依赖关系的指定工作交给更熟悉合约内容的开发者。具体地说,交易依赖的互斥资源可以由一组字符串表示,FISCO BCOS暴露接口给到开发者,开发者以字符串形式定义交易依赖的资源,告知链上执行器,执行器则会根据开发者指定的交易依赖项,自动将区块中的所有交易排列为交易DAG。比如在简单转账合约中,开发者仅需指定每笔转账交易的依赖项是{发送者地址+接收者地址}。进一步地,如开发者在转账逻辑中引入了另一个第三方地址,那么依赖项就需要定义为{发送者地址+接受者地址+第三方地址}了。

这种方式实现起来较为直观简单,也比较通用,适用于所有智能合约,但也相应增加了开发者肩上的责任,开发者在指定交易依赖项时必须十分小心,如果依赖项没有写正确,后果无法预料。指定依赖项的相关接口会在后续文章中给出使用教程,本文暂且假定所有谈论到的交易依赖项都是明确无误的。

解决完上面两个比较重要的问题后,还剩下一些较为细节的工程问题:比如并行交易能否和非并行交易混合到一起执行?怎么保证资源字符串的全局唯一性?

答案也不复杂,前者可通过将非并行交易作为屏障(barrier)插入到交易DAG中——即我们认为,它即依赖于它的所有前序交易,同时又被它的所有后序交易依赖——来实现;后者可以通过在开发者指定的交易依赖项中,加入标识合约的特殊标志位解决。由于这些问题并不影响PTE的根本设计,本文暂不展开。

万事俱备,带着全新交易执行引擎PTE的FISCO BCOS已经呼之欲出。

架构设计

搭载PTE的FISCO BCOS架构图:

_images/IMG_51771.PNG

整个架构的核心流程如下:

用户通过SDK等客户端将交易发送至节点,此处的交易既可以是可并行执行的交易,也可以是不能并行执行的交易。随后交易在节点间同步,同时拥有打包权的节点调用打包器(Sealer),从交易池(Tx Pool)中取出一定量交易并将其打包成一个区块。此后,区块被发送至共识单元(Consensus)准备进行节点间共识。

共识前需要执行区块中的交易,此处便是PTE施展威力之处。从架构图中可以看到,PTE首先按序读取区块中的交易,并输入到DAG构造器(DAG Constructor)中,DAG构造器会根据每笔交易的依赖项,构造出一个包含所有交易的交易DAG,PTE随后唤醒工作线程池,使用多个线程并行执行交易DAG。汇合器(Joiner)负责挂起主线程,直到工作线程池中所有线程将DAG执行完毕,此时Joiner负责根据各个交易对状态的修改记录计算state root及receipt root,并将执行结果返回至上层调用者。

在交易执行完成后,若各个节点状态一致,则达成共识,区块随即写入底层存储(Storage),被永久记录于区块链上。

核心算法
1.交易DAG的数据结构

交易DAG的数据结构如下图所示:

_images/IMG_51781.PNG

Vertex类为最基础里的类型,在交易DAG中,每一个Vertex实例都表征一笔交易。Vertex类包含:

  • inDegree:表示该顶点的入度
  • outEdges:用于存储该节点的出边信息,即所有出边所连顶点的ID列表

DAG类用于对DAG的顶点与边关系进行封装,并提供操作DAG的接口,其包含:

  • vtxs:Vertex数组
  • topLevel:包含所有入度为0的顶点的队列,由于在执行过程中topLevel会动态变化且会被多个线程访问,因此其需要一个能够支持线程安全访问的容器
  • void init(int32_t size)接口:根据传入的size初始化一个包含相应数量顶点的DAG结构
  • addEdge(ID from, ID to)接口:用于在顶点from和顶点to之间建立边关系,具体地说,将顶点to的ID加入顶点from的outEdges中
  • void generate()接口:当所有的边关系录入完毕后,调用该方法以初始化topLevel成员
  • ID waitPop()接口:从topLevel中获取一个入度为0的顶点ID

TxDAG类是DAG类更上一层的封装,是DAG与交易之间建立联系的桥梁,其包含:

  • dag:持有的DAG类实例
  • exeCnt:已执行过的交易总数
  • totalTxs:交易总数
  • txs:区块中的交易列表
2. 交易DAG的构造流程

DAG构造器在构造交易DAG时,会首先将totalTxs成员的值设置为区块中的交易总数,并依据交易总数对dag对象进行初始化,dag会在vtxs中为每笔交易生成一个位置关系一一对应的顶点实例。随后,初始化一个空的资源映射表criticalFields,并按序逐个扫描每笔交易。

对于某笔交易tx,DAG构造器会在其解析出该交易的所有依赖项,对于每个依赖项均会去criticalFields中查询,如果对于某个依赖项d,有前序交易也依赖于该依赖项,则在这两笔交易间建边,并更新criticalFields中d的映射项为tx的ID。

交易DAG构造流程的伪代码如下所示:

criticalFields ← map<string, ID>();
totalTxs ← txs.size();
dag.init(txs.size());
for id ← 0 to txs.size() by 1 do
  tx ← txs[id];
  dependencies ← 解析出tx的依赖项;
  for d in dependencies do
    if d in criticalFields then
        dag.addEdge(id, criticalFields[d]);
      end
    criticalFields[d] = id;
    end
  end
end
dag.generate();
3.交易DAG的执行流程

PTE在被创建时,会根据配置生成一个用于执行交易DAG的工作线程池,线程池的大小默认等于CPU的逻辑核心数,此线程池的生命周期与PTE的生命周期相同。工作线程会不断调用dag对象的waitPop方法以取出入度为0的就绪交易并执行,执行后该交易的所有后序依赖任务的入度减1,若有交易的入度被减至0,则将该交易加入到topLevel中。循环上述过程,直到交易DAG执行完毕。

交易DAG执行流程的伪代码如下所示:

while exeCnt < totalTxs do
  id ← dag.waitPop();
  tx ← txs[id];
  执行tx;
  exeCnt ← exeCnt + 1;
  for txID in dag.vtxs[id].outEdges do
    dag.vtxs[txID].inDegree ← dag.vtxs[txID].inDegree - 1;
    if dag.vtxs[txID].inDegree == 0 then
      dag.topLevel.push(txID)
    end
  end
end    
性能测评

我们选用了2个基准测试程序,用以测试PTE给FISCO BCOS的性能带来了怎样的变化,它们分别是基于预编译框架实现的转账合约和基于Solidity语言编写的转账合约,两份合约代码的路径分别为:

FISCO-BCOS/libprecompiled/extension/DagTransferPrecompiled.cpp

web3sdk/src/test/resources/contract/ParallelOk.sol

我们使用一条单节点链进行测试,因为我们主要关注PTE的交易处理性能,因此并不考虑网络、存储的延迟带来的影响。

测试环境的基本硬件信息如下表所示

_images/IMG_51791.PNG

1.性能测试

_images/IMG_51801.JPG

性能测试部分,我们主要测试PTE和串行交易执行方式(Serial)在各个测试程序下的交易处理能力。可以看到,相对于串行执行方式,PTE从左至右分别实现了2.91和2.69倍的加速比。无论是对于预编译合约还是Solidity合约,PTE均有着不俗的性能表现。

2.可扩展性测试

_images/IMG_51811.JPG

可扩展性测试部分,我们主要测试PTE在不同CPU核心数下的交易处理能力,使用的基准测试程序是基于预编译框架实现的转账合约。可以看到,随着核数增加,PTE的交易吞吐量呈近似线性递增。但是同时也能看到,随着核数在增加,性能增长的幅度在放缓,这是因为随着核数增加线程间调度及同步的开销也会增大。

写在最后

从列表到DAG,PTE赋予了FISCO BCOS新的进化。更高的TPS会将FISCO BCOS带至更加广阔的舞台。予以长袖,FISCO BCOS必能善舞!

《新摩登时代》:卓别林演绎共识与同步流程优化

作者:石翔|FISCO BCOS 核心开发者

共识与同步的流程优化,是FISCO BCOS性能优化迈开的第一步。仅依靠这一流程优化,就给系统TPS带来可观的1.75倍提升。但这不是目的,其目的在于确定了共识的主导地位,排除了同步给共识带来的性能影响,让之后的性能分析更好地聚焦在共识流程中。

基础讲解

在卓别林的电影《摩登时代》里,卓别林扮演一个工人,日复一日地重复着拧螺丝的动作:提起扳手,找到零件,对准螺丝,拧紧,再提起扳手,再找到下一个零件,再对准螺丝,再拧紧…… 在FISCO BCOS的设计里,共识和同步也在干着这样重复的事情。

共识与同步是个啥?

共识与同步,是FISCO BCOS节点中的两个核心流程。它们相互配合,实现了区块链的核心功能:生产出一条在每个节点上都一致的区块链。在FISCO BCOS节点的实现里,共识和同步的实体,我们称为共识模块和同步模块。

  • 共识模块:负责生产区块,让节点产生的区块都是一模一样的
  • 同步模块:负责广播交易,让用户发出的交易尽可能地到达每个节点
共识与同步在干啥?

我们来看看共识模块和同步模块的工作环境:

  • 交易池:节点中缓存未被处理交易的容器
  • 网络模块:接收其它节点发来的消息包,也可向其它节点发送消息包 那么,共识模块与同步模块在干啥?
共识模块

不断进行共识消息的处理和发送,让所有节点上的区块达到一致,此处以PBFT的共识为例。

  1. 打包区块:从交易池拿取出交易,打包成区块广播出去,或处理从网络模块拿到的其它节点的区块
  2. 执行区块:解码区块,验证区块,执行区块,将区块的执行结果签个名广播出去
  3. 收集签名:收集其它节点执行结果的签名,如果收集到的签名达到一定数量,就向其它节点广播“commit消息”
  4. 收集commit:收集其它节点的commit消息,当收集到的commit消息达到一定数量,说明区块已经一致,可以落盘了
  5. 落盘:把区块连接到现有区块链的末端,形成区块链,存储到DB中

_images/IMG_5231.JPG

同步模块

不断进行交易的收发,让每一笔交易尽可能地到达每个节点。

  1. 取交易:从交易池中取出未发送的交易
  2. 发交易:把未发送的交易广播给其它节点
  3. 收交易:从网络模块收取其它节点的交易
  4. 验交易:对交易进行解码和验签
  5. 存交易:把验签通过的交易存到交易池中

_images/IMG_5232.JPG

问题与优化

卓别林和他的伙伴各司其职,井然有序,看似非常和谐。可当工厂落后的生产力跟不上旺盛的市场需求,即便是卓别林这种熟练工,加班加点也干不完。这个时候,卓别林不得不开始思考自己和伙伴在生产关系上的问题。

在以往的设计中,共识模块和同步模块并没有优先级的区分,导致它们在争夺资源时浪费了大量的时间。同时,共识模块和同步模块中还有很多重复的操作,也浪费了时间。因此,应该将共识模块和同步模块的执行流程一并考虑,优化流程,提高效率。在经过详细分析和缜密验证后,FISCO BCOS将共识模块和同步模块流程进行了优化。优化基于以下思想:

共识模块负责主导整个区块链出块的节奏,应让共识模块先行。而同步模块,理应扮演好配合的角色,辅佐共识模块更快出块。

基于上述思想,我们来看看其中几个问题的优化方法。

问题1:工作阻塞

共识模块与同步模块都是从网络模块中获取消息包,再根据相应消息包进行下一步操作。但是,由于网络回调线程数量的限制,同步模块在处理消息包时,占用了网络的回调线程,导致共识模块无法及时处理其它节点发来的共识消息,共识流程被阻塞。

_images/IMG_5233.JPG

如何解决?——将同步消息的处理操作从网络回调线程中剥离

基于共识模块先行的思想,应让共识模块更及时地收到共识消息,不能让同步模块占用网络回调线程太久。因此,同步模块在拿到消息时,不是直接在回调线程中对交易进行解码和验签,而是将同步消息包缓存起来,用另外一个线程“私下”慢慢处理。这样一来,同步消息的处理不会长时间占用网络回调线程,能让共识消息响应得更快。

_images/IMG_5234.JPG

问题2:编解码冗余

同步模块收到同步消息中的交易,是经过编码的,同步模块需将其解码成节点代码中的数据结构,再存入交易池中。共识模块打包区块时,把交易从交易池中取出,将交易进行编码,打包成区块,再将区块发送出去。在这个过程中,交易先被解码,又被编码,操作存在冗余。

_images/IMG_5235.JPG

如何解决?——交易编码缓存

共识优先级高于同步,应尽量减少共识模块中不必要的操作。因此,在同步模块存交易时,一并将交易的编码存入交易池。共识模块取交易时,直接从交易池中拿出编码好的交易,免去了编码操作。

_images/IMG_5236.JPG

问题3:重复验签

同步模块在收到交易后,需对交易的签名进行验证(简称“验签”),共识模块在收到区块后,也需要对区块中的交易进行验签。同步模块和共识模块所验签的交易,有很大概率是重复的。验签是一个非常耗时的操作,每进行一次额外的验签,都会消耗大量的时间。

_images/IMG_5237.JPG

如何解决?——验签去重

无论是同步模块还是共识模块,在验签前,都去交易池里查询该笔交易是否存在。如果存在,就省略验签操作。如此一来,一笔交易只验签一次,减少了不必要的验签开销。

_images/IMG_5238.JPG

解决方案可否更优?——尽量让同步验签,减少共识模块验签的次数

仍然是共识模块优先的思想,尽量减少共识模块验签的操作。因此,同步模块必须比共识模块跑得快,在共识模块处理一笔交易前,同步模块先拿到交易,优先对交易验签。

_images/IMG_5239.JPG

FISCO BCOS在此处对同步模块采取的策略是:对交易进行全量的广播

在一个打包节点拿到交易时,其它节点的同步模块也收到了相应的交易。在其它节点收到打包节点发过去的区块时,区块中所包含的交易早已被同步模块验签后写入交易池中。同时,为了让同步模块在相同操作的处理速度上不低于共识模块,同步模块的交易编解码,也采用了和共识模块一样的“并行编解码”和“交易编码缓存”。

结果如何?

共识与同步的流程优化,一定程度上也提高了交易处理的TPS。经测试,交易处理的TPS提高至原来的1.75倍!更重要的是,通过流程优化,确定了共识的主导地位,排除了同步给共识带来的性能影响,让之后的性能分析更好地聚焦在共识流程中!

消除了阻塞,消除了编码冗余,消除了重复验签,卓别林和他的伙伴工作得更轻松,更顺畅了!

下篇文章,我们将集中阐述并行优化,让可并行的操作都并行起来!敬请期待《全方位的并行处理》。

让木桶没有短板,FISCO BCOS全面推进并行化改造

作者:李陈希|FISCO BCOS 核心开发者

背景

PTE(Parallel Transaction Executor,一种基于DAG模型的并行交易执行器)的引入,使FISCO BCOS具备了并行执行交易的能力,显著提升了节点交易处理的效率。对这个阶段性结果,我们并不满足,继续深入挖掘发现,FISCO BCOS的整体TPS仍有较大提升空间。 用木桶打个比方:如果参与节点的交易处理所有模块构成木桶,交易执行只是组成整个木桶的一块木板,根据短板理论,一只木桶能盛多少水取决于桶壁上最矮的那块,同理,FISCO BCOS的性能也由速度最慢的组件决定。 尽管PTE取得了理论上极高的性能容量,但是FISCO BCOS的整体性能仍然会被其他模块较慢的交易处理速度所掣肘。为了能够最大化利用计算资源以进一步提高交易处理能力,在FISCO BCOS中全面推进并行化改造势在必行。

数据分析

根据并行程序设计的『分析→分解→设计→验证』四步走原则,首先需定位出系统中仍存在的性能瓶颈的精确位置,才能更深入地对任务进行分解,并设计相应的并行化策略。使用自顶向下分析法,我们将交易处理流程分为四个模块进行性能分析,这四个模块分别是:

区块解码(decode):区块在节点间共识或同步时需要从一个节点发送至另一个节点,这个过程中,区块以RLP编码的形式在网络间传输。节点收到区块编码后,需要先进行解码,将区块还原为内存中的二进制对象,然后才能做进一步处理。

交易验签(verify):交易在发送之前由发送者进行签名,签名得到的数据可以分为(v, r, s)三部分,验签的主要工作便是在收到交易或交易执行前,从(v, r, s)数据中还原出交易发送者的公钥,以验证交易发送者的身份。

交易执行(execute):执行区块中的所有交易,更新区块链状态。

数据落盘(commit):区块执行完成后,需要将区块及相关数据写入磁盘中,进行持久化保存。

以包含2500笔预编译转账合约交易的区块为测试对象,在我们的测试环境中,各阶段的平均耗时分布如下图所示:

_images/IMG_51821.JPG

从图中可以看出,2500笔交易的执行时间已经被缩短到了50毫秒以内,可以证明PTE对FISCO BCOS交易执行阶段的优化是行之有效的。但图中也暴露出了非常明显的问题:其他阶段的用时远远高于交易执行的用时,导致交易执行带来的性能优势被严重抵消,PTE无法发挥出其应有的价值。

早在1967年,计算机体系结构领域的元老Amdahl提出的以他名字命名的定律,便已经向我们阐明了衡量处理器并行计算后效率提升能力的经验法则:

_images/IMG_51831.PNG

其中,SpeedUp为加速比,Ws是程序的串行分量,Wp是程序中的并行分量,N为CPU数量。可以看出,在工作总量恒定的情况下,可并行部分代码占比越多,系统的整体性能越高。我们需要把思维从线性模型中抽离出来,继续细分整个处理流程,找出执行时间最长的程序热点,对这些代码段进行并行化从而将所有瓶颈逐个击破,这才是使通过并行化获得最大性能提升的最好办法。

根因拆解
1.串行的区块解码

区块解码主要性能问题出在RLP编码方法本身。RLP全称是递归的长度前缀编码,是一种用长度作为前缀标明编码对象中元素个数的编码方法。如下图所示,RLP编码的开头即是此编码中的对象个数(Object num)。在个数后,是相应个数的对象(Object)。递归地,每个对象,也是RLP编码,其格式也与下图相同。

需要特别注意的是,在RLP编码中。每个Object的字节大小是不固定的,Object num只表示Object的个数,不表示Object的字节长度。

_images/IMG_51841.JPG

RLP通过一种长度前缀与递归结合的方式,理论上可编码任意个数的对象。下图是一个区块的RLP编码,在对区块进行编码时,先递归至最底层,对多个sealer进行编码,多个sealer被编码并加上长度前缀后,编码成为一串RLP编码(sealerList),此编码又作为一个对象,被编入上层的一串RLP编码(blockHeader)中。此后层层递归,最后编码成为区块的RLP编码。由于RLP编码是递归的,在编码前,无法获知编码后的长度。

_images/IMG_51851.JPG

解码时,由于RLP编码中每个对象的长度不确定,且RLP编码只记录了对象的个数,没记录对象的字节长度,若要获取其中的一个编码对象,必须递归解码其前序的所有对象,在解码前序的对象后,才能访问到需要访问的编码对象的字节位置。例如在上图中,若需要访问区块中的第0笔交易,即tx0,必须先将blockHeader解码,而blockHeader的解码,需要再次递归,把parentHash,stateRoot直至sealerList都解码出来。

解码区块最重要的目的是解码出包含在区块中的交易,而交易的编码都是互相独立的,但在RLP特殊的编码方式下,解码一笔交易的必要条件是解码出上一笔交易,交易的解码任务之间环环相扣,形成了一种链式的依赖关系。需要指出的是,这种解码方式并不是RLP的缺陷,RLP的设计目标之一本就是尽量减少空间占用,充分利用好每一个字节,虽然编解码变得低效了些,但编码的紧凑度却是有目共睹,因此这种编码方式本质上还是一种时间换空间的权衡结果。

由于历史原因,FISCO BCOS中使用了RLP编码作为多处信息交换协议,贸然换用其他并行化友好的序列化方案可能会带来较大的开发负担。基于这一考虑,我们决定在原有的RLP编解码方案稍作修改,通过为每个被编码的元素添加额外的位置偏移信息,便可以做到并行解码RLP的同时不会改动大量原有代码。

2.交易验签&数据落盘开销大

通过对交易验签和数据落盘部分的代码进行拆解,我们发现两者的主要功能都集中在一个耗时巨大的for循环。交易验签负责按序取出交易,然后从交易的签名数据中取出(v, r, s)数据,并从中还原出交易发送者的公钥,其中,还原公钥这一步,由于涉及密码学算法,因此耗时不少;数据落盘负责从缓存中逐个取出交易相关数据,将其编码为JSON字符串后写入磁盘,由于JSON编码过程本身效率比较低,因此也是性能损失的重灾区。

两者代码分别如下所示:

// 交易验签
for(int i = 0; i < transactions.size(); ++i)
{
  tx = transactions[i];
  v, r, s = tx.getSignature();
  publicKey = recover(v, r, s);  // (v, r, s)中复原出发送者公钥
  ...
}
// 数据落盘
for(int i = 0; i < datas.size(); ++i)
{
  data = datas[i];
  jsonStr = jsonEncode(data);  // 将数据编码为JSON字符串进行存储
  db.commit(jsonStr);
  ...
}

两个过程共有的特点是,它们均是将同样的操作应用到数据结构中不同的部分,对于这种类型的问题,可以直接使用数据级并行进行改造。所谓数据级并行,即是将数据作为划分对象,通过将数据划分为大小近似相等的片段,通过在多个线程上对不同的数据片段上进行操作,达到并行处理数据集的目的。

数据级并行唯一的附加要求是任务之间彼此独立,毫无疑问,在FISCO BCOS的实现中,交易验签和数据落盘均满足这一要求。

优化实践
1.区块解码并行化

改造过程中,我们在系统中使用的普通RLP编码的基础上,加入了offset字段,用以索引每个Object的位置。如下图所示,改造后编码格式的开头,仍然是对象的个数(Object num),但是在个数字段后,是一个记录对象偏移量的数组(Offsets)。

_images/IMG_51861.JPG

数组中的每个元素有着固定的长度。因此要读取某个Offset的值,只需向访问数组一样,根据Offset的序号直接索引便可以进行随机访问。在Offsets后,是与RLP编码相同的对象列表。相应序号的Offset,指向相应序号的对象的RLP编码字节位置。因此,任意解码一个对象,只需要根据对象的序号,找到其偏移量,再根据偏移量,就可定位到相应对象的RLP编码字节位置。

编码流程也进行了重新设计。流程本身仍然基于递归的思路,对于输入的对象数组,首先将对象数组的大小编码在输出编码的开头处,若数组大小超过1,则按序逐个取出待编码对象并缓存其递归编码,并在Offsets数组中记录该对象的偏移位置,待数组遍历完后,将缓存的对象编码第一次性取出并附加至输出编码末尾;若数组大小为1,则递归对其编码并写入输出编码的末尾,结束递归。

编码流程的伪代码如下:

Rlps = RLP();  // Output,初始时为空
void encode(objs)  //Input: objs = 待编码对象的数组
{
  offset = 0;
  codes = [];
  
  objNum = objs.size()
  Rlps.push(objNum)
  if objNum > 1
  {
    for obj in objs
    {
      rlp = encode(obj);  // 递归调用编码方法
      Rlps.push(offset);
      offset += rlp.size();
      codes.add(rlp);  // 缓存递归编码的结果
    }
    for x in codes
    {
      Rlps.push(x);
    }
  }
  else
  {
    rlp = encode(objs[0]);
    Rlps.push(rlp);
  }
}

偏移量的引入使解码模块能够对元素编码进行随机访问。Offsets的数组范围可以在多个线程间均摊,从而每个线程可以并行访问对象数组的不同部分,分别进行解码。由于是只读访问,这种并行方式是线程安全的,仅需最后再对输出进行汇总即可。

解码流程的伪代码如下:

Objs decode(RLP Rlps)
{
  objNum = Rlps.objNum;  // 获取对象个数
  outputs = []  // 输出的对象数组
  if objNum > 1
  {
    parallel for i = 0 to objNum
    {
      offset = Rlps.offsets[i];
      code = Rlps.objs[offset];
      x = decode(code);
      outputs.add(x);   // 有序插入outputs
    }
  }
  else
  {
    outputs.add(decode(Rlps.objs[0]));
  }
  return outputs;
}
2.交易验签 & 数据落盘并行化

对于数据级并行,业内已有多种成熟的多线程编程模型。虽然Pthread这类显式的多线程编程模型能够提供对线程进行更精细的控制,但是需要我们对线程通信、同步拥有娴熟的驾驭技巧。实现的复杂度越高,犯错的几率越大,日后代码维护的难度也相应增加。我们的主要目标仅仅对密集型循环进行并行化,因此在满足需求的前提下,Keep It Simple & Stupid才是我们的编码原则,因此我们使用隐式的编程模型来达成我们的目的。

经过再三权衡,我们在市面上众多隐式多线程编程模型中,选择了来自Intel的线程构建块(Thread Building Blocks,TBB)开源库。在数据级并行方面,TBB算是老手,TBB运行时系统不仅屏蔽了底层工作线程的实现细节,还能够根据任务量自动在处理器间平衡工作负载,从而充分利用底层CPU资源。

使用TBB后,交易验签和数据落盘的代码如下所示:

// 并行交易验签
tbb::parallel_for(tbb::blocked_range<size_t>(0, transactions.size()),
[&](const tbb::blocked_range<size_t>& _range)
{
  for(int i = _range.begin(); i != _range.end(); ++i)
  {
    tx = transactions[i];
    v, r, s = tx.getSignature();
    publicKey = recover(v, r, s);  // (v, r, s)中复原出发送者公钥
    ...
  }
});
// 并行数据落盘
tbb::parallel_for(tbb::blocked_range<size_t>(0, transactions.size()),
[&](const tbb::blocked_range<size_t>& _range)
{
  for(int i = _range.begin(); i != _range.end(); ++i)
  {
    data = datas[i];
    jsonStr = jsonEncode(data);  // 将数据编码为JSON字符串进行存储
    db.commit(jsonStr);
    ...
  }
});

可以看到,除了使用TBB提供的tbb::parallel_for进行并行循环和tbb::blocked_range引用数据分片外,循环体内的代码几乎没有任何变化,接近C++原生语法正是TBB的特点。TBB提供了抽象层级较高的并行接口,如parallel_for、parallel_for_each这类泛型并行算法,从而使得改造能够较为容易地进行。同时,TBB不依赖任何语言或编译器,只要有能支持ISO C++标准的编译器,便有TBB的用武之地。

当然,使用TBB并不是完全没有额外负担,比如线程间安全还是需要开发人员的仔细分析来保证,但TBB考虑周到,提供了一套方便的工具来辅助我们解决线程间互斥的问题,如原子变量、线程局部存储和并行容器等,这些并行工具同样被广泛地应用在FISCO BCOS中,为FISCO BCOS的稳定运行保驾护航。

写在最后

经过一套并行优化的组合拳,FISCO BCOS的性能表现更上层楼。压力测试的结果表明,FISCO BCOS的交易处理能力,相较于并行化改造之前,成功提升了1.74倍,基本达到了这个环节的预期效果。

但是我们也深深明白,性能优化之路漫漫,木桶最短的一板总是交替出现,并行之道在于,通过反复的分析、拆解、量化和优化,使得各模块互相配合齐头并进,整个系统达到优雅的平衡,而最优解总是在“跳一跳”才能够得着的地方。

FISCO BCOS性能优化——工具篇

作者:李陈希|FISCO BCOS 核心开发者

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. ——Donald Knuth

『过早的优化是万恶之源』

说出这句话的计算机科学先驱Donald Knuth并不是反对优化,而是强调要对系统中的关键位置进行优化。假设一个for循环耗时0.01秒,即使使用循环展开等各种奇技淫巧将其性能提升100倍,把耗时降到0.00001秒,对于用户而言,也基本无法感知到。对性能问题进行量化测试之前,在代码层面进行各种炫技式优化,可能不仅提升不了性能,反而会增加代码维护难度或引入更多错误。

『没有任何证据支撑的优化是万恶之源』

在对系统施展优化措施前,一定要对系统进行详尽的性能测试,从而找出真正的性能瓶颈。奋战在FISCO BCOS性能优化的前线上,我们对如何使用性能测试工具来精确定位性能热点这件事积累了些许经验心得。本文将我们在优化过程中使用到的工具进行了整理汇总,以飨读者。


1.Poor Man’s Profiler

穷人的分析器,简称PMP。尽管名字有些让人摸不着头脑,但人家真的是一种正经的性能分析手段,甚至有自己的官方网站https://poormansprofiler.org/。PMP的原理是Stack Sampling,通过调用第三方调试器(比如gdb),反复获取进程中每个线程的堆栈信息,PMP便可以得到目标进程的热点分布。

第一步,获取一定数量的线程堆栈快照:

pid=$(pidof fisco-bcos)
num=10
for x in $(seq 1 $(num))
  do
    gdb -ex "set pagination 0" -ex "thread apply all bt" -batch -p $pid
    sleep 0.5
done 

第二步,从快照中取出函数调用栈信息,按照调用频率排序:

awk '
  BEGIN { s = ""; } 
  /^Thread/ { print s; s = ""; } 
  /^\#/ { if (s != "" ) { s = s "," $4} else { s = $4 } } 
  END { print s }' | \
sort | uniq -c | sort -r -n -k

最后得到输出,如下图所示:

_images/IMG_5240.PNG

从输出中可以观察到哪些线程的哪些函数被频繁采样,进而可按图索骥找出可能存在的瓶颈。上述寥寥数行shell脚本便是PMP全部精华之所在。极度简单易用是PMP的最大卖点,除了依赖一个随处可见的调试器外,PMP不需要安装任何组件,正如PMP作者在介绍中所言:『尽管存在更高级的分析技术,但毫无例外它们安装起来都太麻烦了……Poor man doesn’t have time. Poor man needs food.』😝。

PMP的缺点也比较明显:gdb的启动非常耗时,限制了PMP的采样频率不能太高,因此一些重要的函数调用事件可能会被遗漏,从而导致最后的profile结果不够精确。但是在某些特殊场合,PMP还是能发挥作用的,比如在一些中文技术博客中,就有开发人员提到使用PMP成功定位出了线上生产环境中的死锁问题,PMP作者也称这项技术在Facebook、Intel等大厂中有所应用。不管怎样,这种闪烁着程序员小智慧又带点小幽默的技术,值得一瞥。

2.perf

perf的全称是Performance Event,在2.6.31版本后的Linux内核中均有集成,是Linux自带的强力性能分析工具,使用现代处理器中的特殊硬件PMU(Performance Monitor Unit,性能监视单元)和内核性能计数器统计性能数据。perf的工作方式是对运行中的进程按一定频率进行中断采样,获取当前执行的函数名及调用栈。如果大部分的采样点都落在同一个函数上,则表明该函数执行的时间较长或该函数被频繁调用,可能存在性能问题。

使用perf需要首先对目标进程进行采样:

$ sudo perf record -F 1000 -p `pidof fisco-bcos` -g -- sleep 60

在上述命令中, 我们使用perf record指定记录性能的统计数据;使用-F指定采样的频率为1000Hz,即一秒钟采样1000次;使用-p指定要采样的进程ID(既fisco-bcos的进程ID),我们可以直接通过pidof命令得到;使用-g表示记录调用栈信息;使用sleep指定采样持续时间为60秒。待采样完成后,perf会将采集到的性能数据写入当前目录下的perf.data文件中。

$ perf report -n

上述命令会读取perf.data并统计每个调用栈的百分比,且按照从高到低的顺序排列,如下图所示:

_images/IMG_5241.JPG

信息已足够丰富,但可读性仍然不太友好。尽管示例中perf的用法较为简单,但实际上perf能做的远不止于此。配合其他工具,perf采样出的数据能够以更加直观清晰的方式展现在我们面前,这便是我们接下来要介绍的性能分析神器——火焰图。

3.火焰图

火焰图,即Flame Graph,藉由系统性能大牛 Brendan Gregg提出的动态追踪技术而发扬光大,主要用于将性能分析工具生成的数据进行可视化处理,方便开发人员一眼就能定位到性能问题所在。火焰图的使用较为简单,我们仅需将一系列工具从github上下载下来,置于本地任一目录即可:

wget https://github.com/brendangregg/FlameGraph/archive/master.zipunzip master.zip
3.1CPU火焰图

当我们发现FISCO BCOS性能较低时,直觉上会想弄清楚到底是哪一部分的代码拖慢了整体速度,CPU是我们的首要考察对象。

首先使用perf对FISCO BCOS进程进行性能采样:

sudo perf record  -F 10000 -p `pidof fisco-bcos` -g -- sleep 60
# 对采样数据文件进行解析生成堆栈信息
sudo perf script > cpu.unfold

生成了采样数据文件后,接下来调用火焰图工具生成火焰图:

# 对perf.unfold进行符号折叠
sudo ./stackcollapse-perf.pl cpu.unfold > cpu.folded
# 生成SVG格式的火焰图
sudo ./flamegraph.pl  cpu.folded > cpu.svg

最后输出一个SVG格式图片,用来展示CPU的调用栈,如下图所示:

_images/IMG_5242.JPG

纵轴表示调用栈。每一层都是一个函数,也是其上一层的父函数,最顶部就是采样时正在执行的函数,调用栈越深,火焰就越高。横轴表示抽样数。注意,并不是表示执行时间。若一个函数的宽度越宽,则表示它被抽到的次数越多,所有调用栈会在汇总后,按字母序列排列在横轴上。

火焰图使用了SVG格式,可交互性大大提高。在浏览器中打开时,火焰的每一层都会标注函数名,当鼠标悬浮其上,会显示完整的函数名、被抽样次数和占总抽样字数的百分比,如下:

_images/IMG_5243.JPG

点击某一层时,火焰图会水平放大,该层会占据所有宽度,并显示详细信息,点击左上角的『Reset Zoom』即可还原。下图展示了PBFT模块在执行区块时,各个函数的抽样数占比:

_images/IMG_5244.JPG

从图中可以看出,在执行区块时,主要开销在交易的解码中,这是由于传统的RLP编码在解码时,RLP编码中每个对象的长度不确定,且RLP编码只记录了对象的个数,没记录对象的字节长度,若要获取其中的一个编码对象,必须递归解码其前序的所有对象。

因此,RLP编码的解码过程是一个串行的过程,当区块中交易数量较大时,这一部分的开销将变得十分巨大。对此,我们提出了一种并行解码RLP编码的优化方案,具体实现细节可以参考上一篇文章《FISCO BCOS中的并行化实践 》

有了火焰图,能够非常方便地查看CPU的大部分时间开销都消耗在何处,进而也能针对性进行优化了。

3.2 Off-CPU火焰图

在实现FISCO BCOS的并行执行交易功能时,我们发现有一个令人困惑的现象:有时即使交易量非常大,区块的负载已经打满,但是通过top命令观察到CPU的利用率仍然比较低,通常4核CPU的利用率不足200%。在排除了交易间存在依赖关系的可能后,推测CPU可能陷入了I/O或锁等待中,因此需要确定CPU到底在什么地方等待。

使用perf,我们可以轻松地了解系统中任何进程的睡眠过程,其原理是利用perf static tracer抓取进程的调度事件,并通过perf inject对这些事件进行合并,最终得到诱发进程睡眠的调用流程以及睡眠时间。

我们要通过perf分别记录sched:sched_stat_sleep、sched:sched_switch、sched:sched_process_exit三种事件,这三种事件分别表示进程主动放弃 CPU 而进入睡眠的等待事件、进程由于I/O和锁等待等原因被调度器切换而进入睡眠的等待事件、进程的退出事件。

perf record -e sched:sched_stat_sleep -e sched:sched_switch \
-e sched:sched_process_exit -p `pidof fisco-bcos` -g \
-o perf.data.raw sleep 60
perf inject -v -s -i perf.data.raw -o perf.data
# 生成Off-CPU火焰图
perf script -f comm,pid,tid,cpu,time,period,event,ip,sym,dso,trace | awk '
    NF > 4 { exec = $1; period_ms = int($5 / 1000000) }
    NF > 1 && NF <= 4 && period_ms > 0 { print $2 }
    NF < 2 && period_ms > 0 { printf "%s\n%d\n\n", exec, period_ms }' | \
./stackcollapse.pl | \
./flamegraph.pl --countname=ms --title="Off-CPU Time Flame Graph" --colors=io > offcpu.svg

在较新的Ubuntu或CentOS系统中,上述命令可能会失效,出于性能考虑,这些系统并不支持记录调度事件。好在我们可以选择另一种profile工具——OpenResty的SystemTap,来替代perf帮助我们收集进程调度器的性能数据。我们在CentOS下使用SystemTap时,只需要安装一些依赖kenerl debuginfo即可使用。

wget https://raw.githubusercontent.com/openresty/openresty-systemtap-toolkit/master/sample-bt-off-cpu
chmod +x sample-bt-off-cpu
​
./sample-bt-off-cpu -t 60 -p `pidof fisco-bcos` -u > out.stap
./stackcollapse-stap.pl out.stap > out.folded
./flamegraph.pl --colors=io out.folded > offcpu.svg

得到的Off-CPU火焰图如下图所示:

_images/IMG_5245.JPG

展开执行交易的核心函数后,位于火焰图中右侧的一堆lock_wait很快引起了我们的注意。分析过它们的调用栈后,我们发现这些lock_wait的根源,来自于我们在程序中有大量打印debug日志的行为。

在早期开发阶段,我们为了方便调试,添加了很多日志代码,后续也没有删除。虽然我们在测试过程中将日志等级设置得较高,但这些日志相关的代码仍会产生运行时开销,如访问日志等级状态来决定是否打印日志等。由于这些状态需要线程间互斥访问,因此导致线程由于竞争资源而陷入饥饿。

我们将这些日志代码删除后,执行交易时4核CPU的利用率瞬间升至300%+,考虑到线程间调度和同步的开销,这个利用率已属于正常范围。这次调试的经历也提醒了我们,在追求高性能的并行代码中输出日志一定要谨慎,避免由于不必要的日志而引入无谓的性能损失。

3.3 内存火焰图

在FISCO BCOS早期测试阶段,我们采取的测试方式是反复执行同一区块,再计算执行一个区块平均耗时,我们发现,第一次执行区块的耗时会远远高于后续执行区块的耗时。从表象上看,这似乎是在第一次执行区块时,程序在某处分配了缓存,然而我们并不知道具体是在何处分配的缓存,因此我们着手研究了内存火焰图。

内存火焰图是一种非侵入式的旁路分析方法,相较于模拟运行进行内存分析的Valgrid和统计heap使用情况的TC Malloc,内存火焰图可以在获取目标进程的内存分配情况的同时不干扰程序的运行。

制作内存火焰图,首先需要向perf动态添加探针以监控标准库的malloc行为,并采样捕捉正在进行内存申请/释放的函数的调用堆栈:

perf record -e probe_libc:malloc -F 1000 -p `pidof fisco-bcos` -g -- sleep 60

然后绘制内存火焰图:

perf script > memory.perf
./stackcollapse-perf.pl memory.perf > memory.folded
./flamegraph.pl  --colors=mem memory.folded > memory.svg

得到的火焰图如下图所示:

_images/IMG_5246.JPG

我们起初猜想,这块未知的缓存可能位于LevelDB的数据库连接模块或JSON解码模块中,但通过比对第一次执行区块和后续执行区块的内存火焰图,我们发现各个模块中malloc采样数目的比例大致相同,因此很快便将这些猜想否定掉了。直到结合Off-CPU火焰图观察,我们才注意到第一次执行区块时调用sysmalloc的次数异常之高。联想到malloc会在首次被调用时进行内存预分配的特性,我们猜想第一次执行区块耗时较多可能就是由此造成的。

为验证猜想,我们将malloc的预分配空间上限调低:

export MALLOC_ARENA_MAX=1

然后再次进行测试并绘制Off-CPU火焰图,发现虽然性能有所降低,但是第一次执行区块的耗时和sysmalloc调用次数,基本无异于之后执行的区块。据此,我们基本可以断定这种有趣的现象是由于malloc的内存预分配行为导致。

当然,这种行为是操作系统为了提高程序整体性能而引入的,我们无需对其进行干涉,况且第一个区块的执行速度较慢,对用户体验几乎也不会造成负面影响,但是再小的性能问题也是问题,作为开发人员我们应当刨根问底,做到知其然且知其所以然。

虽然这次Memory火焰图并没有帮我们直接定位到问题的本质原因,但通过直观的数据比对,我们能够方便地排除错误的原因猜想,减少了大量的试错成本。面对复杂的内存问题,不仅需要有敏锐的嗅觉,更需要Memory火焰图这类好帮手。

4.DIY工具

尽管已经有如此多优秀的分析工具,帮助我们在性能优化前进的道路上披荆斩棘,但强大的功能有时也会赶不上性能问题的多变性,这种时候就需要我们结合自身的需求,自给自足地开发分析工具。

在进行FISCO BCOS的稳定性测试时,我们发现随着测试时间的增长,FISCO BCOS节点的性能呈现衰减趋势,我们需要得到所有模块的性能趋势变化图,以排查出导致性能衰减的元凶,但现有的性能分析工具基本无法快速、便捷地实现这一需求,因此我们选择另寻他路。

首先,我们在代码中插入大量的桩点,这些桩点用于测量我们感兴趣的代码段的执行耗时,并将其附加上特殊的标识符记录于日志中:

auto startTime = utcTime();
/*
...code to be measured...
*/
auto endTime = utcTime();
auto elapsedTime = endTime - startTime;
LOG(DEBUG) << MESSAGE("<identifier>timeCost: ") \
  << MESSAGE(to_string(elspasedTime));

当节点性能已经开始明显下降后,我们将其日志导出,使用自己编写的Python脚本将日志以区块为单位进行分割,随后读取每个区块在执行时产生的桩点日志,并解析出各个阶段的耗时,然后由脚本汇总到一张大的Excel表格中,最后再直接利用Excel自带的图表功能,绘制出所有模块的性能趋势变化图,如下图所示:

_images/IMG_5247.JPG

其中,横坐标为区块高度,纵坐标为执行耗时(ms),不同颜色曲线代表了不同模块的性能变化。

从图中可以看出,只有由红色曲线代表的区块落盘模块的执行耗时明显地随着数据库中数据量的增大而迅速增加,由此可以判断节点性能衰减问题的根源就出在区块落盘模块中。使用同样的方式,对区块落盘模块的各个函数进一步剖析,我们发现节点在向数据库提交新的区块数据时,调用的是LevelDB的update方法,并非insert方法。

两者的区别是,由于LevelDB以K-V的形式存储数据,update方法在写入数据前会进行select操作,因为待update的数据可能在数据库中已存在,需要先按Key查询出Value的数据结构才能进行修改,而查询的耗时与数据量成正比,insert方法则完全不需要这一步。由于我们写入的是全新的数据,因此查询这一步是不必要的,只需改变数据写入的方式,节点性能衰减的问题便迎刃而解。

相同的工具稍微变换一下用法,就能衍生出其他的用途,比如:将两批桩点性能数据放入同一张Excel表格中,便能够通过柱状图工具清晰地展现两次测试结果的性能变化。

下图展示的是我们在优化交易解码及验签流程时,优化前后性能柱状对比图:

_images/IMG_5248.JPG

从图中可以看出,交易解码和验签流程优化后的耗时的确比优化前有所降低。借由柱状对比图,我们能够轻松地检查优化手段是否行之有效,这一点在性能优化的过程中起到了重要的指导作用。

综上可见,DIY工具并不一定需要有多复杂,但一定可以最快地满足我们的定制化需求。

FISCO BCOS可并行合约开发框架(附实操教程)

作者:石翔|FISCO BCOS 核心开发者

本专题系列文章追到现在,也许你会想问,FISCO BCOS的并行到底怎么用?作为专题的完结篇,本文就来揭晓“庐山真面目”,并教你上手使用FISCO BCOS的并行特性!FISCO BCOS提供了可并行合约开发框架,开发者按照框架规范编写的合约,能够被FISCO BCOS节点并行地执行。并行合约的优势有:

  • 高吞吐:多笔独立交易同时被执行,能最大限度利用机器的CPU资源,从而拥有较高的TPS
  • 可拓展:可以通过提高机器的配置来提升交易执行的性能,以支持不断扩大业务规模

接下来,我将介绍如何编写FISCO BCOS并行合约,以及如何部署和执行并行合约。

预备知识
并行互斥

两笔交易是否能被并行执行,依赖于这两笔交易是否存在互斥。互斥,是指两笔交易各自操作合约存储变量的集合存在交集

例如,在转账场景中,交易是用户间的转账操作。用transfer(X, Y) 表示从X用户转到Y用户的转账接口。互斥情况如下:

_images/IMG_51871.PNG

此处给出更具体的定义:

  • 互斥参数:合约接口中,与合约存储变量的“读/写”操作相关的参数。例如转账的接口transfer(X, Y),X和Y都是互斥参数。
  • 互斥对象:一笔交易中,根据互斥参数提取出来的、具体的互斥内容。例如转账的接口transfer(X, Y), 一笔调用此接口的交易中,具体的参数是transfer(A, B),则这笔操作的互斥对象是[A, B];另外一笔交易,调用的参数是transfer(A, C),则这笔操作的互斥对象是[A, C]。

判断同一时刻两笔交易是否能并行执行,就是判断两笔交易的互斥对象是否有交集。相互之间交集为空的交易可并行执行。

编写并行合约

FISCO BCOS提供了可并行合约开发框架,开发者只需按照框架的规范开发合约,定义好每个合约接口的互斥参数,即可实现能被并行执行的合约。当合约被部署后,FISCO BCOS会在执行交易前,自动解析互斥对象,在同一时刻尽可能让无依赖关系的交易并行执行。

目前,FISCO BCOS提供了solidity与预编译合约两种可并行合约开发框架。

solidity合约的并行框架

编写并行的solidity合约,开发流程与开发普通solidity合约流程相同。在此基础上,只需将ParallelContract 作为需要并行的合约基类,并调用registerParallelFunction(),注册可以并行的接口即可。

先给出完整的举例。例子中的ParallelOk合约实现了并行转账的功能:

pragma solidity ^0.4.25;
import "./ParallelContract.sol";  // 引入ParallelContract.sol
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
    // 合约实现
    mapping (string => uint256) _balance;
    
    function transfer(string from, string to, uint256 num) public
{
        // 此处为简单举例,实际生产中请用SafeMath代替直接加减
        _balance[from] -= num; 
        _balance[to] += num;
    }
​
    function set(string name, uint256 num) public
{
        _balance[name] = num;
    }
​
    function balanceOf(string name) public view returns (uint256)
{
        return _balance[name];
    }
    
    // 注册可以并行的合约接口
    function enableParallel() public
{
        // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数(设计函数时互斥参数必须放在前面
        registerParallelFunction("transfer(string,string,uint256)", 2); // 冲突参数: string string
        registerParallelFunction("set(string,uint256)", 1); // 冲突参数: string
    } 
​
    // 注销并行合约接口
    function disableParallel() public
{
        unregisterParallelFunction("transfer(string,string,uint256)");
        unregisterParallelFunction("set(string,uint256)"); 
    } 
}

具体步骤如下:

step1 将ParallelContract作为合约的基类
pragma solidity ^0.4.25;
​
import "./ParallelContract.sol"; // 引入ParallelContract.sol
​
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
   // 合约实现
   
   // 注册可以并行的合约接口
   function enableParallel() public;
   
   // 注销并行合约接口
   function disableParallel() public;
}
step2 编写可并行的合约接口

合约中的public函数,是合约的接口。编写可并行的合约接口,是根据一定的规则,实现一个合约中的public函数。

确定接口是否可并行

可并行的合约接口,必须满足:

  • 无调用外部合约
  • 无调用其它函数接口
确定互斥参数

在编写接口前,先确定接口的互斥参数,接口的互斥即是对全局变量的互斥,互斥参数的确定规则为:

  • 接口访问了全局mapping,mapping的key是互斥参数
  • 接口访问了全局数组,数组的下标是互斥参数
  • 接口访问了简单类型的全局变量,所有简单类型的全局变量共用一个互斥参数,用不同的变量名作为互斥对象
确定参数类型和顺序

确定互斥参数后,根据规则确定参数类型和顺序,规则为:

  • 接口参数仅限:string、address、uint256、int256(未来会支持更多类型)
  • 互斥参数必须全部出现在接口参数中
  • 所有互斥参数排列在接口参数的最前
mapping (string => uint256) _balance; // 全局mapping
​
// 互斥变量from、to排在最前,作为transfer()开头的两个参数
function transfer(string from, string to, uint256 num) public
{
    _balance[from] -= num;  // from 是全局mapping的key,是互斥参数
    _balance[to] += num; // to 是全局mapping的key,是互斥参数 
}
​
// 互斥变量name排在最前,作为set()开头的参数
function set(string name, uint256 num) public
{
    _balance[name] = num;
}
step3 在框架中注册可并行的合约接口

在合约中实现 enableParallel() 函数,调用registerParallelFunction()注册可并行的合约接口。同时也需要实现disableParallel()函数,使合约具备取消并行执行的能力。

// 注册可以并行的合约接口
function enableParallel() public
{
    // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数
    registerParallelFunction("transfer(string,string,uint256)", 2); // transfer接口,前2个是互斥参数
    registerParallelFunction("set(string,uint256)", 1); // transfer接口,前1个四互斥参数
}  
​
// 注销并行合约接口
function disableParallel() public
{
    unregisterParallelFunction("transfer(string,string,uint256)");
    unregisterParallelFunction("set(string,uint256)"); 
}
step4 部署/执行并行合约

用控制台或Web3SDK编译和部署合约,此处以控制台为例:

部署合约

[group:1]> deploy ParallelOk.sol

调用 enableParallel()接口,让ParallelOk能并行执行

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 enableParallel

发送并行交易 set()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 set "jimmyshi" 100000

发送并行交易 transfer()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 transfer "jimmyshi" "jinny" 80000

查看交易执行结果 balanceOf()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 balanceOf "jinny"80000

用SDK发送大量交易的例子,将在下文的例子中给出。

预编译合约的并行框架

编写并行的预编译合约,开发流程与开发普通预编译合约流程相同。普通的预编译合约以Precompile为基类,在这之上实现合约逻辑。基于此,Precompile的基类还为并行提供了两个虚函数,继续实现这两个函数,即可实现并行的预编译合约。

step1 将合约定义成支持并行
bool isParallelPrecompiled() override { return true; }
step2 定义并行接口和互斥参数

注意,一旦定义成支持并行,所有的接口都需要进行定义。若返回空,表示此接口无任何互斥对象。互斥参数与预编译合约的实现相关,此处涉及对FISCO BCOS存储的理解,具体的实现可直接阅读代码或询问相关有经验的程序员。

// 根据并行接口,从参数中取出互斥对象,返回互斥对象
std::vector<std::string> getParallelTag(bytesConstRef param) override
{
    // 获取被调用的函数名(func)和参数(data)
    uint32_t func = getParamFunc(param);
    bytesConstRef data = getParamData(param);
​
    std::vector<std::string> results;
    if (func == name2Selector[DAG_TRANSFER_METHOD_TRS_STR2_UINT]) // 函数是并行接口
    {  
        // 接口为:userTransfer(string,string,uint256)
        // 从data中取出互斥对象
        std::string fromUser, toUser;
        dev::u256 amount;
        abi.abiOut(data, fromUser, toUser, amount);
        
        if (!invalidUserName(fromUser) && !invalidUserName(toUser))
        {
            // 将互斥对象写到results中
            results.push_back(fromUser);
            results.push_back(toUser);
        }
    }
    else if ... // 所有的接口都需要给出互斥对象,返回空表示无任何互斥对象
        
   return results;  //返回互斥
}
step3 编译,重启节点

手动编译节点的方法,参考FISCO BCOS技术文档。编译之后,关闭节点,替换掉原来的节点二进制文件,再重启节点即可。

举例:并行转账

此处分别给出solidity合约和预编译合约的并行举例。

配置环境

该举例需要以下执行环境:

  • Web3SDK客户端
  • 一条FISCO BCOS链

若需要压测最大的性能,至少需要:

  • 3个Web3SDK,才能产生足够多的交易
  • 4个节点,且所有Web3SDK都配置了链上所有的节点信息,让交易均匀发送到每个节点上,才能让链接收足够多的交易
并行Solidity合约:ParallelOk

基于账户模型的转账,是一种典型的业务操作。ParallelOk合约,是账户模型的一个举例,能实现并行的转账功能。ParallelOk合约已在上文中给出。

FISCO BCOS在Web3SDK中内置了ParallelOk合约,此处给出用Web3SDK来发送大量并行交易的操作方法。

step1 用SDK部署合约、新建用户、开启合约的并行能力
# 参数:<groupID> add <创建的用户数量> <此创建操作请求的TPS> <生成的用户信息文件名>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 add 10000 2500 user
# 在group1上创建了 10000个用户,创建操作以2500TPS发送的,生成的用户信息保存在user中

执行成功后,ParallelOk被部署到区块链上,创建的用户信息保存在user文件中,同时开启了ParallelOk的并行能力。

step2 批量发送并行转账交易

注意:在批量发送前,请将SDK的日志等级调整为ERROR,才能有足够的发送能力。

# 参数:<groupID> transfer <总交易数量> <此转账操作请求的TPS上限> <需要的用户信息文件> <交易互斥百分比:0~10>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 transfer 100000 4000 user 2
​
# 向group1发送了 100000比交易,发送的TPS上限是4000,用的之前创建的user文件里的用户,发送的交易间有20%的互斥。
step3 验证并行正确性

并行交易执行完成后,Web3SDK会打印出执行结果。TPS 是此SDK发送的交易在节点上执行的TPS。validation 是转账交易执行结果的检查。

Total transactions:  100000
Total time: 34412ms
TPS: 2905.9630361501804
Avg time cost: 4027ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 44  : 0.044000000000000004%
100  < time <  200ms  : 2617  : 2.617%
200  < time <  400ms  : 6214  : 6.214%
400  < time <  1000ms : 14190  : 14.19%
1000 < time <  2000ms : 9224  : 9.224%
2000 < time           : 67711  : 67.711%
validation:
   user count is 10000
   verify_success count is 10000
   verify_failed count is 0

可以看出,本次交易执行的TPS是2905。执行结果校验后,无任何错误(verify_failed count is 0)。

step4 计算总TPS

单个Web3SDK无法发送足够多的交易以达到节点并行执行能力的上限。需要多个Web3SDK同时发送交易。在多个Web3SDK同时发送交易后,单纯将结果中的TPS加和得到的TPS不够准确,需要直接从节点处获取TPS。

用脚本从日志文件中计算TPS

cd tools
sh get_tps.sh log/log_2019031821.00.log 21:26:24 21:26:59 # 参数:<日志文件> <计算开始时间> <计算结束时间>

得到TPS(3 SDK、4节点,8核,16G内存)

statistic_end = 21:26:58.631195
statistic_start = 21:26:24.051715
total transactions = 193332, execute_time = 34580ms, tps = 5590 (tx/s)
并行预编译合约:DagTransferPrecompiled

与ParallelOk合约的功能一样,FISCO BCOS内置了一个并行预编译合约的例子(DagTransferPrecompiled),实现了简单的基于账户模型的转账功能。该合约能够管理多个用户的存款,并提供一个支持并行的transfer接口,实现对用户间转账操作的并行处理。

注意:DagTransferPrecompiled仅做示例使用,请勿直接运用于生产环境。

step1 生成用户

用Web3SDK发送创建用户的操作,创建的用户信息保存在user文件中。命令参数与parallelOk相同,不同的仅仅是命令所调用的对象是precompile。

java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 add 10000 2500 user
step2 批量发送并行转账交易

用Web3SDK发送并行转账交易。

注意:在批量发送前,请将SDK的日志等级请调整为ERROR,才能有足够的发送能力。

java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 transfer 100000 4000 user 2
step3 验证并行正确性

并行交易执行完成后,Web3SDK会打印出执行结果。TPS 是此SDK发送的交易在节点上执行的TPS。validation 是转账交易执行结果的检查。

Total transactions:  80000
Total time: 25451ms
TPS: 3143.2949589407094
Avg time cost: 5203ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 0  : 0.0%
100  < time <  200ms  : 0  : 0.0%
200  < time <  400ms  : 0  : 0.0%
400  < time <  1000ms : 403  : 0.50375%
1000 < time <  2000ms : 5274  : 6.592499999999999%
2000 < time           : 74323  : 92.90375%
validation:
    user count is 10000
    verify_success count is 10000
    verify_failed count is 0

可以看出,本次交易执行的TPS是3143。执行结果校验后,无任何错误(verify_failed count is 0)。

step4 计算总TPS

单个Web3SDK无法发送足够多的交易以达到节点并行执行能力的上限。需要多个Web3SDK同时发送交易。在多个Web3SDK同时发送交易后,单纯将结果中的TPS加和得到的TPS不够准确,需要直接从节点处获取TPS。

用脚本从日志文件中计算TPS

cd tools
sh get_tps.sh log/log_2019031311.17.log 11:25 11:30 # 参数:<日志文件> <计算开始时间> <计算结束时间>

得到TPS(3 SDK、4节点,8核,16G内存)

statistic_end = 11:29:59.587145
statistic_start = 11:25:00.642866
total transactions = 3340000, execute_time = 298945ms, tps = 11172 (tx/s)
结果说明

本文举例中的性能结果,是在3SDK、4节点、8核、16G内存、1G网络下测得。每个SDK和节点都部署在不同的VPS中,硬盘为云硬盘。实际TPS会根据你的硬件配置、操作系统和网络带宽有所变化。

如您在部署过程中遇到阻碍或有问题需要咨询,可以进入FISCO BCOS官方技术交流群寻求解答。(进群请长按下方二维码识别添加小助手)

区块链的同步及其性能优化方法

作者:石翔|FISCO BCOS 核心开发者

同步,是区块链中非常重要的流程,从功能上分为“交易同步”和“状态同步”。交易同步在交易提交时执行,优先保证交易能发往所有的节点,被打包处理。状态同步发生在某个节点发现自己区块高度落后于全网时,通过状态同步快速追回到全网最高的高度,这样作为共识节点可以参与到最新的共识过程中,而非共识节点能获取到最新的区块数据,以进行存储和验证。

交易同步

交易同步,是让区块链上的交易尽可能到达所有节点,为共识中将交易打包成区块提供基础。

_images/IMG_5249.PNG

一笔交易(tx1),从客户端上发往某个节点,节点在接收到交易后,会将交易放入自身的交易池(Tx Pool)中供共识去打包。与此同时,节点会将交易广播给其它的节点,其它节点收到交易后,也会将交易放到自身的交易池中。

交易在发送的过程中,会有丢失的情况,为了能让交易尽可能到达所有节点,收到广播交易的节点,会根据自身网络拓扑和网络流量策略,选择一到多个相邻节点,进行接力广播。

交易广播策略

如果每个节点都没有限制的转发/广播收到的交易,带宽将被占满,出现交易广播雪崩的问题。为了避免交易广播的雪崩,FISCO BCOS设计了较为精巧的交易广播策略,在尽可能保证交易可达性的前提下,尽量减少重复的交易广播。

  • 对于SDK来的交易,广播给所有的节点
  • 对于其它节点广播来的交易,随机选择25%的节点再次广播
  • 一条交易在一个节点上,只广播一次,当收到了重复的交易,不会进行二次广播

通过上述策略,能够尽量让交易到达所有的节点,交易会尽快被打包、共识、确认,让交易能够更快得到执行的结果。

广播策略已经在复杂网络里尽量追求了网络最终到达率,但也会有极小的概率,出现某交易在一定时间窗内无法到达某节点的情况。当交易未到达某个节点,只会使得交易被确认的时间变长,不会影响交易的正确性,也不会漏处理交易,因为有广播机制,网络里还有更多的节点有机会继续处理这个交易。

区块同步

区块同步,能让区块链节点的数据状态保持在最新。

区块链状态的新旧,最重要的标识之一就是区块高度,而区块里包含了链上的历史交易,若一个节点的块高和全网最高块高对齐,则此节点有机会回溯历史交易,以获得区块链的最新状态,只有拥有最新状态的节点,才能参与到共识中去,进行下一个新区块的共识。

_images/IMG_5250.PNG

当一个全新的节点加入到区块链上,或一个已经断网的节点恢复了网络,此节点的区块高度落后于其它节点,其状态不是最新的。此时就需要进行区块同步。如上图,需要区块同步的节点(Node 1),会主动向其它节点请求下载区块。整个下载过程会将网络请求负载分散到多个节点上。

区块同步与下载队列

区块链节点在运行时,会定时向其它节点广播自身的最高块高。节点收到其它节点广播过来的块高后,会和自身的块高进行比较,若自身的块高落后于此块高,就会启动区块下载流程。区块的下载通过“请求/响应”的方式完成,进入下载流程的节点,会随机挑选满足要求的节点,发送需要下载的区块高度区间。收到下载请求的节点,会根据请求的内容,回应相应的区块。

_images/IMG_5251.PNG

收到回应区块的节点,在本地维护一个下载队列,用来对下载下来的区块进行缓冲和排序。下载队列是一个以块高为顺序的优先队列。下载下来的新区块,会不断插入到下载队列中,按高度排序。排序好的区块,会依次被节点执行和验证。验证通过后,更新本地的数据状态,让块高增加,直至更新到最新块,块高达到最高。

性能优化

对同步的性能优化,能有效提升系统效率。FISCO BCOS在这方面做了很多,下面选取了一些关键的优化点。

编码缓存

在交易广播中,需要将交易编码成二进制数据发送给其它节点,其它节点在收到交易二进制数据后,需要解码成程序可识别的数据结构。当交易量很大时,编解码成为交易广播的性能瓶颈。FISCO BCOS将交易的二进制编码进行缓存,当要发送交易时,直接从缓存中取出二进制发送,减少编解码的频率,增加交易广播的速率。

负载均衡

区块落后的节点会通过请求的方式从其它节点下载区块。其它节点在收到请求后,会将相应区间的区块发送给落后的节点。在区块落后很多的情况下,FISCO BCOS节点会将下载区间均匀切分,向不同的节点发起请求,把下载负载分散到不同的节点上,避免单一被请求节点因承载大量的数据访问请求而影响其运行性能。

回调剥离

在FISCO BCOS节点中,有多个回调线程处理网络上收到的包。当网络流量很大时,处理网络包的线程处理不过来,会将网络包放到缓冲队列中。网络上的包主要为同步包和共识包,共识包优先级更高,直接影响出块速度。为了不影响共识包的处理,FISCO BCOS将同步包的处理逻辑从网络回调线程中剥离出来,交给另外的独立线程,和共识包的处理解耦和并行了。

验签去重

同步模块在收到交易时,需要对交易进行验签。共识模块收到区块后,从区块中取出交易,也需要对交易进行验签。虽然是同一笔交易,却在同步和共识中都进行了验签。然而验签非常耗时,大大影响了交易执行的TPS。FISCO BCOS在交易执行时做了去重逻辑,无论是同步还是共识,在验签前先查询验签记录,若此交易已验签,则直接从记录中获取验签结果,保证同一笔交易只验签一次。同时,FISCO BCOS让同步尽可能地在共识前验签,让共识尽可能直接获取验签结果,减少验签在共识中的耗时。共识得到了加速,链的TPS性能表现相应得到提升。

总结

共识与同步是区块链中必不可少的环节。共识打头阵,同步打辅助。同步流程使得整个区块链网络所有节点都达到数据一致性,保证数据全网可验证。同时,在不影响共识的前提下,为共识提前准备好所需的数据,让共识跑得更快更稳定。

「群聊互动」

Q 大雪无痕:负载均衡同步时如果同步到一个支链上的块,怎么办?另外,出现分叉时,支链保留多久后被丢弃?

A 石翔:FISCO BCOS 用的共识算法是pbft,不会分叉哈。不会分叉的共识算法带来很多好处,没有了回滚,同步可以负载均衡了,mpt树可以去了,mpt改成了表结构的存储,表结构间没有数据冲突,就可以并行执行交易了,所以联盟链可以很快。

FISCO BCOS同步模块的优化策略

作者:陈宇杰|FISCO BCOS 核心开发者

作者语

在FISCO BCOS区块链系统中,同步模块身担重任。既是交易传输小能手,将客户端发送的交易输送给所有节点交易池,源源不断地为共识模块提供打包区块的“原材料”;又是“困难户拯救者”,将最新区块同步到块高落后的“困难户”节点,使其能正常参与共识。

当然,由于同步模块的职责大多与网络有关,因此它也是系统“带宽消耗大户”,会导致区块链部分节点带宽负载高。为此,FISCO BCOS开发者们也设计了一系列策略来优化这个“带宽消耗大户”,使其能更优雅地服务于系统。

本文将详细介绍FISCO BCOS同步模块的优化之路。


初识同步模块

交易同步和区块同步是FISCO BCOS区块链系统同步模块的主要职责,这两个职责都和网络相关。

_images/IMG_5252.PNG

如上图所示,交易同步负责将客户端发出的交易发送给所有其他节点,为共识模块提供用于打包区块的交易。为保证交易能到达所有节点,交易同步主要包括交易广播和交易转发两个部分:

  • 交易广播:客户端首先将交易发送给客户端直连节点,该节点将收到的交易广播给所有其他节点;
  • 交易转发:为保证网络断连情况下交易能到达所有节点,收到广播交易的节点,随机选取25%的节点转发收到的交易。

区块同步则负责拯救块高落后的”困难户”,将最新块高同步到块高落后的节点。节点区块高度低于其他节点时,会主动向区块高度更高的节点拉取新区块。

同步模块的不合理网络使用姿势

前面章节提到同步模块是“带宽消耗大户”,下面就来细说这个“带宽消耗大户”的不合理网络使用姿势。

交易同步时,客户端直连节点网络负载高

考虑到gossip协议消息传递速度慢,联盟链场景一般采用节点网络全互联的方式来提升网络效率。为保障客户端发出的交易能快速到达所有节点,客户端直连节点收到交易后,会将交易广播给所有节点。由于区块链节点外网带宽有限,随着节点规模增加,客户端直连节点必然因网络负载高而成为系统瓶颈。

交易转发时,网络利用效率低

为保障在部分节点网络断连的情况下,交易仍然能到达所有节点,同步模块引入了交易转发机制。节点收到交易后,会随机选取若干节点广播收到的交易。

网络全连的情况下,这样会导致部分节点频繁收到重复的数据包,且节点数目越多,因交易转发带来的冗余消息包就越多,无疑造成巨大的网络带宽浪费。

区块同步时,部分节点网络负载高,导致节点规模不可扩展

考虑到目前使用的BFT或CFT类共识算法的区块链复杂度较高、不具有无限可扩展性,大部分业务架构仅有部分节点作为共识节点,其他节点均作为观察节点(不参与共识,但拥有区块链全量数据),如下图所示。

_images/IMG_5253.PNG

在这种架构中,大部分观察节点随机从拥有最新区块的共识节点下载区块。在包含n个共识节点、m个观察节点的区块链系统中,设每个区块大小为block_size,理想情况下(即负载均衡),每共识一个区块,每个共识节点需要向m/n个观察节点发送区块,共识节点出带宽大约是(m/n)block_size;设网络带宽是bandwidth,则每个共识节点最多可向(bandwidth/block_size)个节点同步区块,即区块链最大节点规模是(nbandwidth/block_size)。在公网带宽bandwidth较小、区块较大的情况下,能容纳的节点数有限,因此随机的区块同步策略不具有可扩展性。

同步模块的优化策略

为提升系统带宽使用效率和系统的可扩展性,FISCO BCOS开发者们提出了一系列优化策略来“纠正”同步模块不合理的网络使用姿势,使其能更优雅、更高效地服务于FISCO BCOS区块链系统。

策略一:交易树状广播

为降低客户端直连节点交易广播带来的网络压力,FISCO BCOS秉着负载均衡的原则,设计了交易树状广播策略,将客户端直连节点的压力分摊给其下属子节点,下图展示了优化前后七节点区块链系统的交易广播拓扑:

_images/IMG_5254.PNG

  • **优化前:**节点收到客户端交易后,全量广播给其他节点;
  • **优化后:**节点收到客户端交易后,将其发送给子节点,子节点收到交易后,继续将交易转发给自身的子节点。

采用交易树状广播后,上图所示的客户端直连节点将部分网络负载分摊给子节点,带宽负载降低为原先的一半,达到了负载均衡的目标。并且,由于所有节点广播交易的出带宽仅与树状拓扑的宽度有关,交易树状广播策略具有可扩展性。此外,相较于基于gossip的交易广播机制,树状广播策略提升了交易广播速率的同时,降低了网络中冗余的消息包数量。

策略二:基于状态包的交易转发优化

为消除交易转发带来的带宽消耗,提升网络效率,FISCO BCOS提出了基于状态包的交易转发策略,优化后的交易转发模块不直接转发交易,仅转发交易状态。节点可根据收到的交易状态以及交易池内已有的交易获取缺失的交易,并直接向相应节点拉取缺失交易。

_images/IMG_5255.PNG

上图中,客户端直连node0,但node0与node1、node4断连,此时node0仅能将交易广播给node2和node3。node2和node3收到交易后,将最新交易的列表打包成状态包发送给其他节点。node1和node4收到状态包后,与本地交易池内的交易列表做对比,获取缺失的交易列表,并批量向拥有这些交易的node2或node3请求交易。在全连的网络环境中,所有节点交易状态基本一致,节点间交易请求较少,相较于直接转发交易的策略,大大降低了转发冗余交易引起的带宽浪费。

策略三:区块同步可扩展性优化

为降低多个观察节点向单个共识节点同步区块时,共识节点的网络出带宽对网络规模的影响,提升区块链系统区块同步的可扩展性,FISCO BCOS设计并实现了区块状态树状广播策略。

下图是由3个共识节点、18个观察节点构成的区块链系统沿三叉树进行区块同步的示意图:

_images/IMG_5256.PNG

该策略将观察节点分摊给每个共识节点,并以共识节点为顶点构造一颗三叉树。共识节点出块后,优先向其子观察节点发送最新区块状态,子观察节点同步最新区块后,优先向自己的子节点发送最新区块状态,以此类推。采用了区块状态树状广播策略后,每个节点优先将最新区块状态发送给子节点,子节点优先向父节点同步最新块,设区块大小为block_size,树的宽度为w,则用于区块同步的网络带宽均为(block_size * w),与区块链系统的节点总数无关,具有可扩展性。

此外,考虑到树状拓扑中,节点断连可能导致区块无法到达部分节点,区块状态树状广播优化策略还采用了gossip协议定期同步区块状态,使得树状拓扑中的断连节点也能从其邻居节点处同步最新区块,保障树状区块状态广播的健壮性。

小结

同步模块是交易传输小能手,也是“困难户拯救者”,更是”带宽消耗大户”,这个”带宽消耗大户”无论是在执行同步交易的任务,还是在履行同步区块的职责,都存在浪费带宽、过度使用部分节点带宽的嫌疑。

FISCO BCOS开发者采用一系列优化策略规范了同步模块的带宽使用姿势,尽量减少同步模块冗余消息包的同时,将高负载节点的带宽压力分摊给下属子节点,提升了区块链系统的可扩展性,使得优化后的同步模块能更优雅、更高效、更健壮地服务于区块链系统。

记一次CachedStorage中死锁的调试经历

作者:李陈希|FISCO BCOS 核心开发者

在整合FISCO BCOS非国密单测与国密单测的工作中,我们发现CachedStorage的单测偶然会陷入卡死的状态,且可在本地持续复现。复现方式为循环执行CachedStorage单测200次左右,便会发生一次所有线程均陷入等待状态、单测无法继续执行的情况,我们怀疑在CachedStroage中发生了死锁,故对此进行调试。

Debug思路

中医治病讲究望闻问切,调试bug同样需要遵循寻找线索、合理推断、验证解决的思路。

观察线程栈

在死锁发生时,使用/usr/bin/sample工具(mac平台环境下)将所有的线程的栈打印出来,观察各线程的工作状态。从所有线程的线程栈中观察到有一个线程(此处称为T1)卡在CachedStorage.cpp的第698行的touchCache函数中,点击参考具体的代码实现

_images/IMG_5257.PNG

从代码片段中可以看到,T1在第691行已经获得了m_cachesMutex的读锁:代码RWMutexScoped(some_rw_mutex, false)的意思是获取某个读写锁的读锁;相应地,代码RWMutexScoped(some_rw_mutex, true)的意思是获取某个读写锁的写锁,这里的RWMutex是一个Spin Lock。

随后在第698行处尝试获取某个cache的写锁。除了T1,还有另外一个线程(此处称为T2)卡在CachedStorage.cpp的第691行的touchCache函数中:

_images/IMG_5258.PNG

从代码片段中可以看到,T2在第681行已经获得了某个cache的写锁,随后在第691行处尝试获取m_cachesMutex的读锁。 继续观察后还发现若干线程卡在CachedStorage.cpp第673行的touchCache函数中:

_images/IMG_5259.PNG

最后还有一个Cache清理线程(此处称为T3)卡在CachedStorage.cpp的第734行的removeCache函数中:

_images/IMG_5260.PNG

从代码片段中可以看到,这些线程均没有持任何锁资源,只是在单纯地尝试获取m_cachesMutex的写锁。

读写饥饿问题

初期分析问题时,最诡谲的莫过于:在T1已经获取到m_cachesMutex读锁的情况下,其他同样试图获取m_cachesMutex读锁的线程竟然无法获取到。但是看到T3线程此时正努力尝试获取m_cachesMutex写锁,联想到读写锁饥饿问题,我们认为其他线程获取不到读锁的问题根源很可能就在T3。

所谓读写锁饥饿问题是指,在多线程共用一个读写锁的环境中,如果设定只要有读线程获取读锁,后续想获取读锁的读线程都能共享此读锁,则可能导致想获取写锁的写线程永远无法获得执行机会(因为读写锁一直被其他读线程抢占)。为了解决饥饿问题,部分读写锁会在某些情况下提高写线程的优先级,即由写线程先占用写锁,而其他读线程只能在写线程后乖乖排队直到写线程将读写锁释放出来。

在上述问题中, T1已经获取了m_cachesMutex的读锁,若此时T3恰好获得时间片并执行到CachedStorage.cpp的第734行,会因获取不到m_cachesMutex的写锁而卡住,随后其他线程也开始执行并到了获取m_cachesMutex读锁的代码行。若读写防饥饿策略真的存在,那这些线程(包括T2)的确会在获取读锁阶段卡住,进而导致T2无法释放cache锁,从而T1无法获取到cache锁,此时所有线程均会陷入等待中。

在这个前提下,似乎一切都能解释得通。上述流程的时序图如下所示:

docs/articles/3_features/31_performance/../../../../images/articles/cachedstorage_deadlock_debug/IMG_5261.PNG

我们找到了TBB中Spin RW Lock的实现代码,如下图所示:

获取写锁:

docs/articles/3_features/31_performance/../../../../images/articles/cachedstorage_deadlock_debug/IMG_5262.PNG

获取读锁:

_images/IMG_5263.PNG

在获取写锁的代码中,可以看到写线程如果没有获取到写锁,会置一个WRITER_PENDING标志位,表明此时正有写线程在等待读写锁的释放,其他线程请勿打扰。

而获取的读锁代码中,也可以看到,如果读线程发现锁上被置了WRITER_PENDING标志位,就会老实地循环等待,让写线程优先去获取读写锁。这里读写锁的行为完美符合之前对读写锁防饥饿策略的推测,至此真相大白。既然找到了问题起因,那解决起来就容易多了。在CachedStorage的设计中,Cache清理线程优先级很低,调用频率也不高(约1次/秒),因此给予它高读写锁优先级是不合理的,故将removeCache函数获取m_cachesMutex写锁方式做如下修改:

_images/IMG_5263.PNG

修改后,获取写锁方式跟获取读锁类似:每次获取写锁时,先try_acquire,如果没获取到就放弃本轮时间片下次再尝试,直到获取到写锁为止,此时写线程不会再去置WRITER_PENDING标志位,从而能够不影响其他读线程的正常执行。

相关代码已提交至2.5版本中,该版本将很快与大家见面,敬请期待。

实现效果

修改前循环执行CachedStorage单测200次左右便会发生死锁;修改后循环执行2000+次仍未发生死锁,且各个线程均能有条不紊地工作。

经验总结

从这次调试过程中,总结了一些经验与大家分享。

首先,分析死锁问题最有效的仍然是“两步走”方法,即通过pstack、sample、gdb等工具看线程栈,推测导致发生死锁的线程执行时序。这里的第二步需要多发挥一点想象力。以往的死锁问题往往是两个线程间交互所导致,教科书上也多以两个线程来讲解死锁四要素,但在上述问题中,由于读写锁的特殊性质,需要三个线程按照特殊时序交互才可以引发死锁,算是较为少见的情况。

其次,『只要有线程获取到读锁,那其他想获取读锁的线程一定也能获取读锁』的思维定势是有问题的。至少在上面的问题中,防饥饿策略的存在导致排在写线程后的读线程无法获取读锁。但本文的结论并非放之四海而皆准,要不要防饥饿、怎么防饥饿在各个多线程库的实现中有着不同的取舍。有的文章提到过某些库的实现就是遵循『读线程绝对优先』规则,那这些库就不会遇到这类问题,所以仍然需要具体问题具体分析。

FISCO BCOS流量控制实现

作者:陈宇杰|FISCO BCOS 核心开发者

引言

区块链系统作为分布式系统,面对大数据量突发请求场景,暴涨的请求容易引起区块链服务或接口不可用,严重时可能导致整个区块链系统陷入雪崩状态。

为了提供更稳定可靠、柔性可用的服务,FISCO BCOS v2.5版本引入了流量控制功能,从节点和群组两个维度进行限流,一方面,面对大数据量突发请求时对区块链系统进行保护,保证系统能正常运行,提升系统可用性;另一方面降低区块链节点间、群组间的资源干扰,提升区块链系统的服务质量。

为什么引入流量控制

FISCO BCOS引入流量控制,旨在:

  • 应对大数据量突发请求
  • 降低区块链节点间、群组间的资源干扰
  • 降低模块间的相互影响
应对大数据量突发请求

_images/IMG_5265.PNG

上图对比了无流量控制功能带有流量控制功能的区块链系统面对大数据量突发请求时的处理情况。

假设该区块链系统处理能力为2W,当业务以20W请求速率访问区块链节点时:

  • 无流量控制的场景下,系统对业务请求照单全收,导致内部积压的请求数目越来越多,区块链节点响应速度越来越慢,若业务持续以高于系统处理能力的速率发起请求,最终整个系统可能会陷入雪崩状态,无法响应任何业务请求。
  • 加入了流量控制功能后,流量控制模块会根据****系统处理能力过滤业务请求。在业务请求速率超出系统处理能力时,流量控制模块会拒绝剩余的处理请求,使系统维持”收支平衡”的健康状态;并将请求过载的信息返回给业务,业务可根据该信息自适应地调整请求速率,对区块链系统进行保护。

简而言之,引入流量控制模块就是给区块链系统加上一层安全保护罩,让系统在接收大数据量突发请求的场景下可以健壮工作,正常响应业务请求。

降低区块链节点间/群组间资源干扰

_images/IMG_5266.PNG

注:图中两个节点属于两条不同的链,接入了两个不同服务

如上图,当多个区块链节点部署于同一台机器时,会出现资源竞争的问题,某些节点占用过多系统资源会影响到其他节点的正常服务。

  • t1时刻,业务1持续以1W的请求速率请求左边节点,该节点流量激增,系统接收并处理请求后,使用了90%的CPU
  • 经过t时间间隔,业务2以5000的请求速率请求右边节点,该节点资源匮乏,只能抢占到10%的CPU,响应速度很慢

上述场景中,左边节点因占用过多系统资源影响了右边节点的服务质量。引入流量控制后,可限制每个节点接收请求的速率,控制每个区块链节点的资源占用,避免因区块链节点资源竞争导致的服务质量下降或服务不可用问题。

仍以上图为例:

  • t1时刻,业务1持续以1W的请求速率请求节点1,节点1流量控制模块根据配置的请求阈值拒绝多余的请求(这里设阈值为5000),机器CPU占用率维持在50%
  • 业务1收到”流量过载”的响应后,可将其请求速率调整到5000
  • 经过t时间间隔,业务2以5000的请求速率请求节点2,此时机器还剩余50%的 CPU,足以处理5000个请求,业务2的请求得到正常响应

类似于一台机器上运行多个区块链节点时会发生资源竞争,多群组架构下,群组间也存在资源竞争,某个群组占用过多资源同样会影响到其他群组的服务质量,采用群组级别的流量控制是解决群组间资源竞争的良方。

降低模块间相互影响

同一个节点或群组内的不同模块,也存在资源竞争问题,主要是网络资源竞争,存在网络资源竞争的模块包括:

  • 共识模块
  • 交易同步模块
  • 区块同步模块
  • AMOP模块

其中共识模块、交易同步模块是决定区块链系统服务质量的关键模块,其他模块过多占用网络资源,会影响这些关键模块,进而影响系统可用性。FISCO BCOS实现了模块级别的流量控制,通过控制非关键的网络流量,优先保证关键模块服务质量,提升系统健壮性。

流量控制的功能

FISCO BCOS从节点和群组两个维度实现了业务到节点的请求速率限制和模块粒度的网络流量限制。前者限制业务到节点的请求速率,以应对大数据量突发请求,保证区块链节点的柔性服务;后者通过限制区块同步、AMOP等非关键模块的网络流量,优先保证共识、交易同步等关键模块的性能和稳定性。

_images/IMG_5267.PNG

  • 节点级别请求速率限制:限制业务到节点的总请求速率,当请求速率超过指定阈值后,节点会拒绝业务请求,避免节点过载,防止过多的请求导致节点异常;控制节点资源使用量,降低区块链节点之间的资源竞争
  • 节点级别的流量控制:限制节点的平均出带宽,当节点平均出带宽超过设置阈值后,节点收到区块同步请求后会暂缓发送区块、拒绝收到的AMOP请求,避免区块同步、AMOP消息包发送对节点共识的影响

群组维度上,主要功能包括:

  • 群组级别请求速率限制:限制业务到群组的请求速率,当请求速率超过阈值后,群组会拒绝业务请求,该功能可在大数据量突发请求的场景下保护区块链节点,控制群组资源使用量,降低群组间的资源竞争
  • 群组级别的流量控制:限制每个群组的平均出带宽,当群组平均出带宽流量超过设置阈值后,该群组会暂停区块发送和AMOP请求包转发逻辑,优先将网络流量提供给共识模块使用

当节点和群组都开启请求速率限制时:

节点收到业务发送的请求包时,首先调用节点级别请求速率限制模块判断是否接收该请求,如请求被接收,则进入群组级别请求速率限制模块,通过该模块检查后的请求才会被转发到相应群组,进行处理。

当节点和群组都开启网络流量控制功能时:

1、节点收到客户端AMOP请求,首先调用节点级流量控制模块判断是否接收该AMOP请求

2、当某个群组收到其他节点对应群组的区块请求后,群组在回复区块之前,需要:

  • 调用节点级流量控制模块,判断节点平均出带宽是否超过设置阈值
  • 调用群组级流量控制模块,判断群组出带宽是否超过设置阈值,当且仅当节点级和群组级平均出带宽均未超过设置阈值时,该群组才会回复区块请求
如何使用流量控制功能

流量控制配置分别位于config.ini和group.i.ini配置文件的[flow_control]配置项中,分别对应为节点级别流量控制配置和群组级别流量控制。这里向大家介绍如何启用、关闭、配置流量控制。

节点级流量控制

节点级别的网络流量控制配置项均位于config.ini配置文件中,主要包括:

请求速率限制

节点级别的请求速率限制位于配置项[flow_control].limit_req中,用于限制业务每秒到节点的最大请求数目,当请求数目超过设置阈值时,请求会被拒绝。该配置项默认关闭,若要开启,请将limit_req配置项前面的;去掉。

打开请求速率限制并设计节点每秒可接受2000个业务请求的示例如下:

[flow_control]
  ; restrict QPS of the node
  limit_req=2000
网络流量限制
  • [flow_control].outgoing_bandwidth_limit:节点出带宽限制,单位为Mbit/s,当节点出带宽超过该值时,会暂缓区块发送,也会拒绝客户端发送的AMOP请求,但不会限制区块共识和交易广播的流量。该配置项默认关闭,若要开启,请将outgoing_bandwidth_limit配置项前面的;去掉。

打开节点出带宽流量限制,并将其设置为5MBit/s的配置示例如下:

[flow_control]
  ; Mb, can be a decimal
  ; when the outgoing bandwidth exceeds the limit, the block synchronization operation will not proceed
  outgoing_bandwidth_limit=5
群组级流量控制

群组级别的网络流量控制配置项均位于group.i.ini配置文件中,主要包括:

请求速率限制

群组i的请求速率限制位于group.i.ini的配置项[flow_control].limit_req中,限制业务每秒到群组的最大请求数目,当请求数目超过配置项的值时,请求会被拒绝。该配置项默认关闭,若要开启,请将limit_req配置项前面的;去掉。

打开请求速率限制并配置群组每秒可接受1000个业务请求的示例如下:

[flow_control]
  ; restrict QPS of the group
  limit_req=1000
群组内网络流量限制

[flow_control].outgoing_bandwidth_limit:群组出带宽限制,单位为Mbit/s,当群组出带宽超过该值时,会暂缓发送区块,但不会限制区块共识和交易广播的流量。该配置项默认关闭,若要开启,请将outgoing_bandwidth_limit配置项前面的;去掉。

打开群组出带宽流量限制,并将其设置为2MBit/s的配置示例如下:

[flow_control]
  ; Mb, can be a decimal
  ; when the outgoing bandwidth exceeds the limit, the block synchronization operation will not proceed
  outgoing_bandwidth_limit=2
总结

随着区块链技术的发展,越来越多应用部署于区块链系统中,对区块链系统服务质量的要求也日渐提升,区块链系统的柔性可用、稳定健壮变得更加重要。

FISCO BCOS v2.5引入流量控制功能,是FISCO BCOS对区块链柔性服务探索的重要一步。

社区将持续打磨,优化区块链系统服务质量,希望未来能为海量业务场景提供更好的、高可用的柔性服务。如何做好流量控制的同时,又不影响原本系统性能?敬请关注社区后续文章,为您详解流量控制策略的具体实现原理。欢迎大家共同探讨交流,积极反馈使用的体验与改进建议。

共识算法

共识框架:可插拔设计

共识算法:PBFT、Raft、Rpbft

FISCO BCOS共识优化之路

作者:陈宇杰|FISCO BCOS 核心开发者

作者语

原来的PBFT共识算法在区块打包、交易验签、区块执行、空块处理等方面有持续优化空间,为了让PBFT算法更快更稳,FISCO BCOS做了一系列优化,包括:

  • 打包和共识并发进行;
  • 不重复验签交易;
  • 引入DAG并行交易执行框架,可并行执行区块内交易;
  • 空块快速触发视图切换,并切换Leader,不落盘空块,消除空块落盘存储开销的同时,有效防止了节点作恶;
  • 解决了节点宕机后,无法快速追上其他节点视图的问题,保证了系统的可用性。

本文分别从性能、存储和可用性三个方面,向大家细说FISCO BCOS的共识优化方案。

性能优化

考虑到Leader轮流串行打包交易交易验签速度慢以及区块执行速度慢,都是导致性能问题的主要因素,FISCO BCOS做了如下优化:

打包和共识并发执行

PBFT共识算法在每一轮共识,都包括打包阶段共识阶段,Leader打包新区块时,所有共识节点都处于等待Prepae包的状态中,无法进入共识阶段;共识节点处于共识阶段时,Leader的打包线程不工作,但打包区块和共识是两个独立互斥的过程,可以并发执行。

_images/IMG_4897.PNG

设打包阶段的时间开销为t, 共识阶段时间开销为u,n轮共识的时间开销为n∗(t+u);但若下一轮共识的Leader参与共识阶段的同时,也提前打包区块,并在下一轮共识时,广播已经打包好的区块,则可将共识时间开销缩短为n∗u+t,时间开销降低了(n-1)*t,可以有效提升PBFT共识算法性能。

避免交易重复验签

_images/IMG_4898.PNG

共识节点收到Leader发送的Prepare包后,会从中取出区块,并验证区块内每笔交易签名的有效性,但交易验签是很耗时的操作,会增加PBFT Prepare阶段的时间开销,降低性能。

考虑到交易插入到交易池的时候,会进行一次验签,如下图所示,FISCO BCOS系统做了防止交易重复验签的优化,下面结合整个交易流的处理流程,详细说明FISCO BCOS防止交易重复验签的处理流程:

_images/IMG_4899.PNG

  1. RPC接收客户端发送的交易后,进行交易验签;
  2. 交易验证通过后,被插入到交易池,同步模块广播交易;
  3. 其他节点的同步模块收到其他节点的交易后,并进行验签交易,并将有效交易插入到交易池;
  4. 共识模块收到Prepare包后,解出Prepare包内区块,判断区块内交易是否在交易池内,仅验证不包含在交易池内的交易签名。

经过上述优化后,将Prepare请求内包含10000笔交易的区块解码和验签时间,由2s降低为200ms,大大减少了Prepare阶段的时间开销。

区块并行执行

区块执行是PBFT共识算法的主要时间开销之一,没有做任何并行优化的情况下,PBFT共识算法几乎无法就一个包含上万笔交易的区块达成共识。

为了提升区块链系统TPS,FISCO BCOS系统开发了基于DAG的交易并行执行引擎,并引入了可并行的合约开发框架,支持并行执行交易,达到了上万的TPS。具体可参考这里:《区块链性能腾飞:基于DAG的并行交易执行引擎》。

存储优化

_images/IMG_4900.PNG

为保障系统正常运行、确认Leader可用、防止Leader故意作恶,基于PBFT共识算法的区块链系统在没有交易时,会产生空块,并就空块达成共识。 虽然空块共识是必要的,但考虑到当前区块链系统的QPS不大,落盘空块会耗费存储空间、降低硬盘利用效率(可存储的交易数)。 FISCO BCOS基于PBFT共识算法,实现了高效的空块处理方法,保证空块参与PBFT共识流程的同时,不落盘空块,提升了磁盘利用效率。详细方案可参考这里:《FISCO BCOS的PBFT空块处理》

可用性优化

_images/IMG_4901.PNG

刚启动的节点或者新节点加入区块链网络时,若不能立即和其他节点视图达成一致,会影响系统容错性。

  • case1:4节点区块链系统,node0宕机,剩余三个节点容错节点数为0;node0重启若无法快速追上其他节点视图,系统可容错节点数仍然为0,且node0处于共识异常状态中。
  • case2:2节点区块链系统正常运行,新加入节点node2,若node2无法快速追上其他节点视图,系统会由于1个节点异常(新加入节点)而处于共识异常状态中。

针对上面问题,FISCO BCOS PBFT共识算法引入了快速视图追赶机制,刚启动节点向所有其他共识节点发送视图切换包,其他节点收到包后,向其回复最新视图,从而使得刚启动节点可快速和其他共识节点达成一致视图,系统在加入了新节点后也不会共识异常。

_images/IMG_4902.PNG

如上图所示,核心流程如下:

  • 刚启动节点向所有其他节点广播视图切换请求包ViewChange,请求包内的视图ViewChange.toView为1;
  • 其他节点收到toView远小于当前节点视图的ViewChange请求后,回复包含当前视图(view)的ViewChange包;
  • 刚启动节点收集满2*f+1个ViewChange包后,切换到和其他共识节点一致的视图。
总结

以上详述了FISCO BCOS在共识算法上的优化策略,FISCO BCOS使用系统的方法使得PBFT算法性能和存储效率更高,可用性更强。 当然,除了前面提到的问题外,PBFT算法在网络复杂度上也有持续优化空间,FISCO BCOS开发团队也在积极调研最新的共识算法及共识算法优化策略,并寻求大规模节点共识的解决方案,敬请期待。

FISCO BCOS的PBFT空块处理

作者:陈宇杰|FISCO BCOS 核心开发者

作者语

为了保障系统正常运行、确认Leader可用、防止Leader故意作恶,基于PBFT共识算法的区块链系统 (如Algorand) 在没有交易时,会产生空块。

在常见的区块链网络里,记账者通常根据算法持续出块,目的是为了保障系统正常运行、防止作恶等,即使区块里不包含交易,空区块也会被共识确认和落盘存储。

虽然对空块进行共识有一定作用,但空块落盘会耗费存储空间、降低硬盘利用率 (可存储的交易数),并一定程度上影响面向区块数据的回溯和检索效率。

所以,FISCO BCOS基于PBFT共识算法,实现了高效的空块处理方法,保证每个区块都参与PBFT共识流程的同时,不落盘空块,提升了磁盘利用效率,也确保了系统的安全性、健壮性。

名词解释
节点类型
  • Leader/Primary: 共识节点,负责将交易打包成区块和区块共识,每轮共识过程中有且仅有一个Leader,为防止Leader伪造区块,每轮PBFT共识后,均会切换Leader;
  • Replica: 副本节点,负责区块共识,每轮共识过程中有多个Replica节点,每个Replica节点的处理过程类似;
  • Observer: 观察者节点,负责从共识节点或副本节点获取最新区块,执行并验证区块执行结果后,将产生的区块上链。

其中Leader和Replica统称为共识节点。

视图(view)

PBFT共识算法使用视图view记录每个节点的共识阶段,相同视图节点维护相同的Leader和Replica节点列表。当Leader出现故障,会发生视图切换,并根据新视图选出新Leader。

FISCO BCOS系统中,Leader计算公式如下:

leader_idx = (view + block_number) % node_num
为什么共识空块须切换Leader
防止Leader故意作恶

_images/IMG_5292.PNG

如上图,node0是不可信的当前Leader,若共识空块后不切换Leader,该节点一直向其他节点广播空块,使得Leader一直是node0,导致系统无法处理正常交易。共识空块后,切换Leader,可切换Leader为可信节点,有效防止Leader作恶问题。

防止切换到无交易Leader导致的系统异常

_images/IMG_5293.PNG

如上图,node0是可信的当前Leader,但其交易池内可打包的交易数为0,共识空块后不切换Leader会导致该节点一直向其他节点广播空块,系统Leader一直是node0,无法处理正常交易。共识空块后,切换Leader,可切换Leader为有交易的节点,保障了系统正常运行。

落盘空块存在的问题
浪费存储空间

有些业务在一天中有忙闲的时段之分,比如,在半夜里,可能有大段的时间并没有用户在交易,这个时候如果持续出块,会持续有空块生成。

例:某区块链系统1s出一个块,1天有50%的时间没有交易,每个空块大小为1KB,若这些空块都落盘,则一天空块占据的磁盘空间为:3600s/h * 24h * 50% * 1KB ≈ 43.2MB,1年空块占据约为15.7GB磁盘空间。假设平均每个交易大小为1KB,这15.7GB磁盘空间可用于存储15.7GB/1KB=15,700笔交易。

_images/IMG_5294.PNG

FISCO BCOS PBFT空块处理方案

如下图所示,FISCO BCOS PBFT共识算法,通过空块触发快速视图切换的方法,达到切换Leader目的,同时不落盘空块。

_images/IMG_5295.PNG

核心流程

结合上图,下面介绍FISCO BCOS PBFT空块处理算法的主要流程:

  1. Leader(node0)节点在指定时间间隔(当前是1秒)没有打包到交易,则基于最高块构造一个空块,内含0笔交易;
  2. Leader将空块封装在Prepare包内,并广播给所有其他共识节点;
  3. 其他共识节点收到Prepare包后,取出区块,若区块是空块,则将节点即将切换的视图toView设置为当前视图加一,并相互间广播视图切换请求viewchange_request,viewchange_request中的视图为toView,即视图增加当前view;
  4. 共识节点收集视图切换包:节点收集满n- f (n是共识节点数,至少为3*f+1;f是系统可容错的节点数目) 个来自不同节点、视图与节点toView值一致的视图切换请求后,触发视图切换,将当前视图view切换为toView;
  5. 由于视图切换,下一轮共识的Leader切换为下一个节点(即node1)。
总结

综上可知,FISCO BCOS PBFT共识算法,通过空块触发快速视图切换,并切换Leader优化了空块处理流程,解决了共识空块不切换Leader导致的系统异常,实现了空块不落盘存储,提升了磁盘利用效率,加快了数据追溯的效率,减少了数据分析的复杂度。

我们为什么这样设计RPBFT?

作者:陈宇杰|FISCO BCOS 核心开发者

前言

共识模块是区块链系统的引擎,在保证各区块链节点数据一致性方面起到至关重要的作用。FISCO BCOS引入可插拔的共识引擎后,同时支持PBFT和Raft两种共识算法,相较于采用POW共识算法的区块链系统,交易确认时延更低、吞吐量更高,可满足当前大部分联盟链系统的性能需求。

PBFT共识算法更因其可容忍拜占庭错误而天然适用于区块链系统。可PBFT共识算法也存在可扩展性低的问题。FISCO BCOS团队从2019年开始一直致力于研究新的共识算法,以同时保障区块链系统的性能和可扩展性。FISCO BCOS 2.3版本中发布的RPBFT共识算法为研究成果之一。本文将详细介绍RPBFT共识算法的设计目的与其中的技术实现。

PBFT共识算法挑战

介绍RPBFT共识算法之前,先来看看PBFT共识算法的挑战,以及学术界的对应解决方案。PBFT共识算法起源于上世纪。1999年,Miguel Castro(卡斯特罗)和Barbara Liskov(利斯科夫)提出PBFT共识算法,将BFT算法复杂度从指数级降到多项式级,使得PBFT共识算法就可应用于实际系统中。不同于POW共识算法,PBFT基于分布式一致性原理保障分布式系统最终一致性。由于摒弃了算力,PBFT共识算法性能更高、交易确认时延更低,加之基于密码学技术的防作恶机制,天然适用于联盟区块链系统。可PBFT共识算法也有”软肋”,从其三阶段共识流程得以一窥。

_images/IMG_5296.PNG

从上图可看出,PBFT共识流程中,节点之间需要相互广播共识消息包,且网络复杂度与节点数目的平方成正比,严重限制了PBFT的可扩展性。

下图整理自IBM研究员Marko调研,反映了采用不同共识算法的区块链系统节点规模与交易延迟之间的关系:

_images/IMG_5297.PNG

从中可看出,BFT类共识算法性能很高,但其支撑的节点规模最多不超过1000。2019年,HotStuff成为各区块链平台争相研究的共识算法,相对于PBFT,HotStuff具备算法简单、网络复杂度与节点规模成线性关系等多种优势。下图是HotStuff的核心流程:

_images/IMG_5298.PNG

由于HotStuff复杂度仍然与节点规模成正比,没法从根本上解决共识算法可扩展性问题,且HotStuff每个阶段都依赖于Leader收集、广播消息包,Leader会成为每轮共识的瓶颈。基于以上调研,FISCO BCOS团队先后实现了PBFT分组共识算法、HotStuff共识算法。但随着节点规模的增加,这些共识算法的性能、吞吐率逐渐下降。因此,我们开始探索一种不会因节点数量增加而导致区块链系统性能快速线性下降的共识机制,RPBFT共识算法在这种情况下应运而生。

RPBFT共识算法核心思想

RPBFT共识算法的目标是在保障区块链系统性能、安全性的前提下,将共识算法网络复杂度与共识节点规模解耦,提升区块链系统的可扩展性。为实现这个目标,FISCO BCOS团队参考DPOS思路,在大节点规模下,随机选取部分节点作为“共识委员节点”参与每轮PBFT共识,由于共识委员节点数目固定、与节点规模无关,因此RPBFT共识算法可扩展性更强。此外,为了保障系统安全、防止共识委员节点联合作恶,RPBFT算法会周期性地替换共识委员节点,如下图所示

_images/IMG_5299.PNG

RPBFT共识算法实现方案

RPBFT算法主要包括2个系统参数:

  • epoch_sealer_num:每轮共识过程中参与共识的节点数目,可通过控制台发交易方式动态配置该参数。
  • epoch_block_num: 共识节点替换周期,为防止选取的共识节点联合作恶,RPBFT每出epoch_block_num个区块,会替换若干个共识委员节点,可通过控制台发交易的方式动态配置该参数。

这两个配置项记录在系统配置表中,配置表主要包括配置关键字、配置对应的值、生效块高三个字段,其中生效块高记录了配置最新值的最新生效块高,例:在100块发交易将epoch_sealer_num和epoch_block_num分别设置为4和10000,此时系统配置表如下:

key value enable_num
epoch_sealer_num 4 101
epoch_block_num 10000 101

RPBFT共识算法核心算法流程如下:

确定各共识节点编号IDX

对所有共识节点的NodeID进行排序,如下图,节点排序后的NodeID索引即为该共识节点编号:

_images/IMG_5300.PNG

链初始化

链初始化时,RPBFT需要选取epoch_sealer_num个共识节点到共识委员中参与共识,目前初步实现是选取索引为0到epoch_sealer_num-1的节点参与前epoch_block_num个区块共识。

共识委员节点运行PBFT共识算法

选取的epoch_sealer_num个共识委员节点运行PBFT共识算法,验证节点同步并验证这些验证节点共识产生的区块,验证步骤包括:

  • 校验区块签名列表:每个区块必须至少包含三分之二共识委员节点的签名
  • 校验区块执行结果:本地执行结果须与共识委员产生的区块执行结果一致
动态替换共识委员节点列表

为保障系统安全性,RPBFT算法每出epoch_block_num个区块后,会在共识委员列表中剔除若干个节点,并加入若干个验证节点,下面以每epoch_block_num个区块剔除一个节点为例:

_images/IMG_5301.PNG

在目前的RPBFT算法实现中,共识委员列表节点被轮流替换为验证节点,设当前有序的共识委员会节点列表为CommitteeSealersList,共识节点总数为N,则共识epoch_block_num个区块后,会将CommitteeSealersList[0]剔除出共识委员列表,并加入索引为(CommitteeSealersList[0].IDX + epoch_sealer_num) % N的验证节点。第i轮替换周期,将CommitteeSealersList[i % epoch_sealer_num]剔除出共识委员列表,加入索引为(CommitteeSealersList[i%epoch_sealer_num].IDX + epoch_sealer_num) % N的验证节点。

RPBFT网络优化

考虑到Prepare数据包较大,占网络开销大头,为了进一步提升RPBFT共识算法可扩展性,我们在FISCO BCOS 2.3引入了Prepare包广播优化。将Leader因广播Prepare包产生的出带宽流量分摊给其下属子节点,即:Leader产生Prepare包后,沿着树状拓扑将数据包传播给其他节点,如下图所示:

_images/IMG_5302.PNG

为保证在节点断连情况下开启树状广播时,Prepare包仍能到达每个节点,RPBFT引入了基于状态包的容错机制,如下图所示:

_images/IMG_5303.PNG

主要流程包括:

  1. 节点A收到Prepare后,随机选取33%节点广播Prepare包状态,记为prepareStatus,包括{blockNumber, blockHash, view, idx}。

  2. 节点B收到节点A随机广播过来的prepareStatus后,判断节点A的Prepare包状态是否比节点B当前Prepare包localPrepare状态新。主要判断依据包括:

    (1) prepareStatus.blockNumber是否大于当前块高

    (2) prepareStatus.blockNumber是否大于localPrepare.blockNumber

    (3) prepareStatus.blockNumber等于localPrepare.blockNumber情况下,prepareStatus.view是否大于localPrepare.view

    以上任意一个条件成立,都说明节点A的Prepare包状态比节点B的状态新。

  3. 若节点B的状态落后于节点A,且节点B与其父节点断连,则节点B向节点A发出prepareRequest请求,请求相应的Prepare包。

  4. 若节点B的状态落后于节点A,但节点B与其父节点相连,若节点B最多等待100ms(可配)后,状态仍然落后于节点A,则节点B向节点A发出prepareRequest请求,请求相应的Prepare包。

  5. 节点B收到节点A的prepareRequest请求后,向其回复相应的Prepare消息包。

  6. 节点A收到节点B的Prepare消息包后,执行handlePrepare流程处理收到的Prepare包。

区块落盘后,为降低多个验证节点、观察节点向共识委员节点同步区块时,共识委员节点的网络出带宽对网络可扩展性的影响,RPBFT采用了区块状态树状广播策略,详细可参考FISCO BCOS同步模块的优化策略

RPBFT算法优化展望

FISCO BCOS 2.3初步实现了RPBFT共识算法,消除了节点规模对共识算法复杂度的影响。但目前实现的RPBFT共识算法,仍有待改进的地方,诸如:共识委员节点选取替换规则比较简单等。未来计划引入VRF可验证随机数算法来实现私密、随机、非交互式的共识委员节点选取方法,欢迎大家体验并反馈意见。

小结

本文介绍了BFT类算法的挑战以及FISCO BCOS团队在共识算法领域探索的初步成果。分布式系统共识是大而复杂的领域,FISCO BCOS 2.3发布的RPBFT算法目前仅解耦了节点规模对网络复杂度的影响,是实现高安全、可扩展共识算法的第一步,未来还会引入VRF算法来保证共识委员节点选取的安全性,敬请期待。

关于存储

存储设计:支持KV和SQL 引擎类型:支持leveldb、rocksdb、mysql CRUD接口:提供CRUD接口访问链上数据

FISCO BCOS基于表的存储结构

作者:尹强文|FISCO BCOS 核心开发者

FISCO BCOS底层的存储数据结构,并没有采用传统的MPT存储结构,而是用了基于表结构的方式。一方面,避免了世界状态急剧膨胀而导致的性能下降问题;另一方面,表结构能够兼容各种存储引擎,使得业务开发更加方便。

FISCO BCOS表的分类

FISCO BCOS每张表都有一个主key字段,1个或者多个value字段,表分为系统表(以_sys_开头),用户表(以_user_开头)和StorageState账户表(以_contract_data_开头)。 表结构的所有记录,都会有_id_,_status_,_num_,_hash_内置字段。用户表和StorageState账户表key字段的类型是varchar(255),value的类型为mediumtext。

系统表

系统表默认存在,由节点进程或者amdb-proxy进程启动时,保证系统表的创建,每张表的说明如下。

  • sys_tables

存储所有表的结构,每一张表都会在这张表中有一条记录,记录表的结构,包括表的key和field字段。表结构如下:

字段 table_name key_field value_field
说明 表名 主key名称 value名称列表,以“,”分隔

以表名为_sys_tx_hash_2_block_为例,_sys_tables_中各个字段数据为:

table_name=_sys_tx_hash_2_block_key_field=hashvalue_field=value,index

底层创建表和读取结构表,是基于_sys_tables_这张表,从创建好的_sys_tx_hash_2_block_表中可以看到,这张表包含3个字段,分别是主key字段hash,value字段有2个分别是value和index。

  • sys_consensus

存储共识节点和观察节点的列表。表结构如下:

字段 name type node_id enable_num
说明 主key,固定为node 节点类型,sealer为共识节点,observer为观察节点 节点id 生效块高

例如,某个链包括4个节点,初始化都是共识节点,可以看到四个节点都是sealer(共识节点),生效块高都是0,如图:

_images/IMG_4903.PNG

通过控制台将149f3777a0...这个节点移除并加入到观察者列表,查询_sys_consensus_表的数据,发现该条记录的type已经修改为observer,生效块高已经修改为3。如图:

_images/IMG_4904.PNG

  • sys_current_state

存储当前区块链最新的状态,每次有区块数据存储,这个表都会去更新信息,包括当前分配出去的自增id,当前块高,交易失败数,交易总数。表结构如下:

字段 key value

存储的信息如下:

key 含义
current_id 当前分配出去的自增id
current_number 当前块高
total_failed_transaction_count 交易失败数
total_transaction_count 交易总数
  • sys_config

存储需要共识的群组配置项,表结构同_sys_current_state_,当前配置了2个数值项,分别是一个区块包含的最大交易数及gas值。写入创世块的时候,会从group.[groupid].genesis文件中读取consensus.max_trans_num和tx.gas_limit两个配置项并写入表中。存储的信息如下:

key 含义
tx_count_limit 一个区块包含的最大交易数
tx_gas_limit gas值
  • sys_table_access

存储含有写入权限的外部账户地址。表结构如下:

字段 table_name address enable_num
说明 表名 有写入权限的外部地址 生效块高

默认这个表是没有数据的,表示所有外部账户都有读写权限,通过控制台使用grantDeployAndCreateManager命令为某个账号授权,会在_sys_table_access_这张表中新增一条Entry。

_images/IMG_4905.JPG

同时可以看到,除了被授权的外部账号可以部署合约之外,其他账户部署合约会提示没有权限。

_images/IMG_4906.JPG

  • sys_number_2_hash

存储区块号到区块hash映射,可以根据区块号映射到区块hash值。表结构如下:

字段 number value
说明 区块号 区块hash值
  • sys_hash_2_block

存储hash到序列化的区块数据映射,可以根据区块hash值映射到区块值。表结构如下:

字段 hash value
说明 区块hash值 区块值
  • sys_block_2_nonces

存储区块中交易的nonces,可以根据区块号,映射到该区块生成时所用到的nonces值。表结构如下:

字段 number value
说明 区块号 生成区块时用到的nonces值
  • sys_tx_hash_2_block

存储交易hash到区块号的映射,表结构如下:

字段 hash value index
说明 交易hash 区块号 该交易在区块中的编号

一个区块可能包括多个交易,因此,区块hash和交易hash是一对多的关系,所以一个区块会在这个表里生成多条数据。

  • sys_cns

存储合约名到合约地址的映射,表结构如下:

字段 name version address abi
说明 主key,合约名 合约版本号 合约地址 合约的接口说明,描述了合约字段名称、字段类型、方法名称、参数名称、参数类型、方法返回值类型

用CNS部署的合约,可以通过合约名去调用,具体方式是根据合约名,找到包括多个版本号的合约地址列表,筛选出版本号正确的合约地址,再使用_contract_data_+Address+_作为表名,去查询code的值,执行合约代码。

例如,通过CNS部署的TableTest合约,可以在_sys_cns_表中查询到如下数据:

_images/IMG_4907.JPG

用户表

用户调用CRUD接口所创建的表,以_user_<TableName>为表名,底层自动添加_user_前缀。

表名和表结构由合约决定,例如创建表的合约代码为:

 TableFactory tf = TableFactory(0x1001); int count = tf.createTable("t_test", "name", "item_id,item_name"); return count;

创建出来的表名为_user_t_test,包括3个字段,分别是主key字段name,类型为varchar(255);value包含两个字段,分别是item_iditem_name;如图:

_images/IMG_4908.JPG

StorageState账户表

_contract_data_+Address+_作为表名。表中存储外部账户相关的信息。存储信息如下:

key value
alive
balance
code
codeHash
nonce

以部署TableTest合约为例,deploy TableTest会返回一个地址,如图:

_images/IMG_4909.JPG

同时,可以看到数据库中生成了一个表名为_contract_data_a582f529ff55e6ca2ada7ad3bab3b97e1c7013f2_的表,存储信息如下(和上述表述一致):

_images/IMG_4910.JPG

总结

基于表的存储方式,抽象了区块链的底层存储模型,实现了类SQL的抽象存储接口,支持多种后端数据库。 引入基于表的存储方式后,数据读写请求不经过MPT,直接访问存储,结合缓存机制,存储性能相比基于MPT的存储有大幅提升。MPT数据结构作为可选方案仍然保留。

「群提问」

Q:腾龙(何直群):table 和 智能合约内部存储有优缺点对比吗?

A:Wheat:table是类似传统数据库使用方式的设计,可以让写业务逻辑的开发比较容易上手。数据基于table存储,管理也更方便。相比合约mpt格式存储数据,table格式性能也更高。

Q:王先生:有点疑惑,表存储不就和传统的数据库存储一样了吗,那区块链还有什么用?

A:尹强文:使用什么存储结构,本质上不会改变区块链具有去中心化、不可篡改、不可逆、匿名等特性。只是使用基于表的存储,有一些优势,一是数据基于表存储,管理更方便。相比合约mpt格式存储数据,表格式性能也更高,同时表结构能够兼容各种存储引擎,使得业务开发更加方便。

区块链数据是存在链上还是数据库里?

作者:莫楠|FISCO BCOS 高级架构师

在回答这个问题之前,首先要理清“区块链数据”和“链上数据”的概念。

区块链数据

“区块链数据”广义上包括区块链的区块数据和区块链的状态数据:

  • 区块数据记录了区块链上发生的每一笔交易,譬如小明给小王转账了50元、小王充值了20元等类似这样的交易数据;
  • 状态数据记录了区块链上每个账户或智能合约的当前状态,比如小明当前的余额是50元、小王当前的余额是100元。

无论区块数据还是状态数据,它们都是由区块链节点使用和存储的。区块链节点是一个程序,运行在我们的个人电脑、虚拟机或服务器上。多个分布在不同电脑或服务器上的区块链节点,通过网络互相连接,组成了完整的区块链网络。

区块链节点通常会把区块链数据存储在个人电脑、虚拟机或服务器上,存储区块链数据最常见的介质,就是磁盘。

区块链节点不会直接访问磁盘,它们会通过特定的数据库,如LevelDB、RocksDB或MySQL等单机或分布式数据库来操作数据。相比于直接操作磁盘,数据库抽象了特定的数据访问模型,对区块链节点更为友好。

因此,当我们说:“区块链数据保存在数据库”时,可以认为区块链节点将区块链数据保存在MySQL(或其它数据库),MySQL将区块链数据保存在磁盘。

_images/IMG_5304.JPG

数据库有独立式嵌入式之分:

  • 独立式数据库,如MySQL、Oracle是通常理解的数据库,独立式数据库作为独立的进程运行,需要单独部署和启停。独立式数据库可以与区块链节点部署在同一台服务器,或者部署在不同的服务器,还支持分布式、集群化的部署。无论何种部署方式,独立式数据库都是区块链节点的存储组件,隶属于区块链节点,与区块链网络无关。
  • 嵌入式数据库如LevelDB、RocksDB,它们以动态依赖库或静态依赖库的方式,与区块链节点整合在同一个进程中,同时启停,用户不会明显感受到它们的存在。
链上数据

区块链数据的区块数据和状态数据并不是凭空产生的。区块数据中的交易,是由区块链的用户生成,用户把交易发送到区块链节点,区块链节点将多个交易打包进区块,区块会在区块链网络上广播和共识,区块链网络对区块达成共识后,认同区块中的交易,将交易的执行结果保存到状态数据中。

假设区块链原本的状态数据是:小明当前的余额是50元、小王当前的余额是100元,那么执行了“小明给小王转账了50元”的交易后,状态数据会发生变化,小明当前的余额会变为0元,小王当前的余额变为150元。

区块需要进行区块链共识,状态数据是通过执行区块中的交易生成的,这两类数据都直接或间接跟区块链共识有关系,可以将其称为“链上数据”。那么,“链上数据”的明确定义,就是:链上数据是直接或间接由区块链共识产生的数据。

回到最初的问题

很显然,“链上数据”和“数据库”不是同一个层面的概念,“区块链数据是存在链上还是存在数据库?”这个问题不成立,区块链数据无论是存储在LevelDB、RocksDB、MySQL数据库或直接存储在磁盘,只要是直接或间接由区块链共识产生,都可以视为链上数据。

FISCO BCOS的链上数据

FISCO BCOS的区块链数据,默认是通过RocksDB保存在磁盘中。如果希望把数据保存到MySQL数据库,可以先自行部署一个MySQL数据库,然后修改区块链节点下的群组配置文件,群组配置文件通常位于区块链节点的配置目录下:conf/group.1.ini

[storage]
    type=mysql
    db_ip=127.0.0.1
    db_port=3306
    db_username=root
    db_name=db_Group1_A
    db_passwd=******

其中:

  • type为区块链节点的存储类型,配置为mysql,表示使用MySQL来存储区块链数据;
  • db_ip为MySQL数据库的IP地址,如果部署在本机,就是127.0.0.1;
  • db_port为MySQL数据库的端口,默认为3306;
  • db_username是MySQL数据库的登陆用户名;
  • db_name是MySQL数据库中用于存储区块链数据的数据库名,无需先行创建;
  • db_passwd是MySQL数据库的登陆密码。

其它未提及的配置项,可保留默认值不修改,完成这些信息的填写以后,确保数据库运行正常,然后重启区块链节点,区块链节点就会将区块链数据保存到MySQL数据库中。FISCO BCOS的区块链,无论是保存在RocksDB还是MySQL中,都可视为链上数据。使用MySQL,可以方便地查看链上数据的大小、结构等信息,如区块的大小、账户的大小等等。

总结

FISCO BCOS提供了灵活的数据存储机制,对于追求便利与性能的场景,可以使用默认的RocksDB;对于偏重审计和治理的场景,可以使用MySQL,满足不同的需求。

关于FISCO BCOS存储,请参考FISCO BCOS分布式存储文档


「群聊互动」

【Q】 什么的小世界:区块链上的数据都是增加在上面的,不能删除是么,那么长时间使用,会不会节点的效率不断下降?

【A】 中大刘忠楠:节点header存储的交易、状态数据,是限定长度的根哈希值。

【A】莫楠:区块链确实会随着使用不断增长,但区块链的数据访问模型通常是kv的,kv模型的查询效率受数据量的影响很小,因此不会明显影响性能。

【Q】 什么的小世界:如果在区块链网络之中增加一个节点,该节点会从其他节点通过广播自动拷贝数据过来是么?

【A】莫楠:在区块链网络中增加一个节点,这个节点会自动同步其它节点的数据。

为什么从LevelDB切换到RocksDB?

作者:白兴强|FISCO BCOS 核心开发者

存储模块是区块链底层平台中的核心之一,负责将区块链中所有需要持久化的数据存储到磁盘上。一个优秀的区块链底层平台,必然要有一个强大的存储模块支持。FISCO BCOS存储模块经过多次重构和优化,为性能突破提供了有力支撑。目前,FISCO BCOS单链TPS达到2万+,且支持平行多链的并行扩展。

2.0.0-rc3版本以前,FISCO BCOS支持使用LevelDB和MySQL作为数据存储引擎,rc3之后开始将嵌入式存储引擎从LevelDB切换到RocksDB。为什么要做切换?切换RocksDB之后能带来什么?本文将带大家一起回顾我们作出这个决定时的考虑。

FISCO BCOS存储模块概览
数据提交流程

FISCO BCOS中需要存储的数据可以分为两部分,一部分是经过共识的链上数据,包括交易、收据、区块和合约数据等;另一部分是各节点维持区块链运行所需的数据,包括当前块高、链上交易数和一些交易区块相关的索引信息。区块链上的新区块来自于同步模块和共识模块。以同步模块为例,当拿到新的区块之后,同步模块会调用BlockVerifier模块执行和验证区块,如果验证通过则调用BlockChain模块将区块和执行区块产生的数据提交给存储模块,由存储模块负责将数据序列化写入数据库。

_images/IMG_5305.PNG

存储模块概览

数据提交到存储模块之后,是一种抽象的表结构,存储模块首先将提交的数据加入缓存层,以提高查询性能。完成缓存的更新之后,需要提交的数据会加入提交队列,由缓存层负责异步提交到适配层,如果关闭了缓存设置,则同步提交到适配层。

_images/IMG_5306.PNG

适配层需要把提交的数据从FISCO BCOS的抽象表结构组织形式转换为后端对应存储的组织形式,对于MySQL这种关系型数据库,则直接将存储模块的表结构对应到数据库表结构即可,例如_sys_config_这个表在MySQL中如下图所示。

_images/IMG_5307.PNG

对于RocksDB或LevelDB这种KV的存储模式,将表名和插入时设置的主key拼起来作为数据库的KEY,对应的数据则序列化为VALUE。对应于_sys_config_这个表,以及tx_conut_limit这个主key的数据,其在KV数据库中的KEY为_sys_config__tx_conut_limit,VALUE为对应的数据序列化后的字符串。

为什么选择RocksDB?

FISCO BCOS从1.0版本开始就使用LevelDB作为底层数据存储引擎,在使用过程中我们也碰到一些小问题,例如内存占用高、文件描述符超限导致进程被干掉、节点被kill后可能导致的DB损坏等。

重构2.0版本时,为了更高的性能,我们需要一个更优秀的存储引擎,这个存储引擎应该满足下面这些条件:

  1. 开源且有持续地维护;
  2. 读写性能要比LevelDB更高;
  3. 嵌入式KV数据库,能够支持大数据量场景下的读写;
  4. 与LevelDB类似的接口,降低迁移成本。

基于上述条件,RocksDB进入了我们的视野。

RocksDB fork自LevelDB,开源且由fackbook维护,相比于LevelDB有较明显的性能提升,保持了与LevelDB一致的接口,极低的迁移成本。从资料上看非常符合我们的需求。

LevelDB与RocksDB性能对比

下面的测试数据是在一台4 vCPU E5-26xx 2.4GHz 8G 500GB腾讯云硬盘的机器上获取的,由FISCO BCOS核心开发者尹强文提供。

该测试KEY的长度为16字节,VALUE的长度为100字节,压缩算法使用Snappy,其他参数使用默认值,在1千万条数据和1亿条数据的情况下,可看出LevelDB和RocksDB的性能对比:在两种数据量下,各个场景RocksDB都取得了不比LevelDB差或者更好的表现。

_images/IMG_5308.PNG

_images/IMG_5309.PNG

FISCO BCOS中使用RocksDB

在RocksDB的官方wiki上有一个页面叫做Features Not in LevelDB,这个页面中描述了RocksDB中所有新增的功能,例如对列族的支持,允许我们在逻辑上对数据库分区,对backup和checkpoint的支持,支持备份到HDFS,两种compaction方式允许用户在读放大、写放大、空间放大之间取舍,自带统计模块便于调优,支持了ZSTD等更新的压缩算法等。

官方wiki中也提到RocksDB为提升性能所做的优化,包括多线程Compaction、多线程memtable插入、降低DB锁的持有时间、写锁的优化、跳表搜索时更少的比较操作等。官方文档中指出,在插入key是有序的场景下,RocksDB使用多线程Compaction,使得RocksDB的性能大幅度高于LevelDB。

FISCO BCOS使用RocksDB时只使用了默认参数和与LevelDB兼容的读写接口,并没有做进一步的参数调优,RocksDB在官方文档中有指出,默认参数已经可以达到很好的性能,更进一步的调参并不能带来大幅的性能提升,而用户的业务场景是多种多样的,针对业务场景优化的参数修改对于FISCO BCOS不一定合适。

日后,随着对RocksDB的深入了解,如果发现更优的参数设置,我们也会采用。

总结

为什么换RocksDB,其实就一句话,RocksDB性能更高!凡是能够让FISCO BCOS更加优秀的事情,我们都愿意去做。最近,FISCO BCOS发布了v2.2.0版本,在性能方面作了进一步的优化,每一次的性能提升都是FISCO BCOS的开发者不停死磕的结果,这种死磕我们会一直进行下去,希望社区的同学们也一起参与进来,初学者点个star,进一步了解后可以提一些修复PR或者issue,大家一起让FISCO BCOS更加优秀!


FISCO BCOS CRUD使用指南

作者:廖飞强|FISCO BCOS 核心开发者

本文将介绍 FISCO BCOS的CRUD功能,帮助开发者更高效便捷地开发区块链应用。

为什么设计CRUD功能?

在FISCO BCOS 1.0中,节点采用MPT数据结构,通过LevelDB将数据存储于本地,这种模式受限于本地磁盘大小,当业务量增大时数据会急剧膨胀,要进行数据迁移也非常复杂,给数据存储带来较大成本和维护难度。

为了突破容量和性能瓶颈,FISCO BCOS 2.0针对底层存储进行了重新设计,实现了分布式存储,带来了容量和性能上的提升。得益于分布式存储采用了库表结构,FISCO BCOS 2.0设计一套CRUD(Create增加、Read读取、Update更新和Delete删除),让接口更加顺其自然。CRUD面向库表的开发方式符合业务开发习惯,同时也为业务开发提供了另外一种选择(以往只能用Solidity合约),从而让区块链应用开发更加便利。

CRUD有哪些优势?

CRUD的核心设计思想是提供面向SQL编程的区块链应用开发规范。其好处显而易见,主要体现在两升两降。

提升开发区块链应用的效率

CRUD类似传统业务SQL编程开发模式,大大降低了合约开发难度。开发者将合约当做数据库的存储过程,将区块链数据的读写操作转换为面向表的读写操作,简单易用,极大提升了开发区块链应用的效率。

提升区块链应用的性能

CRUD底层逻辑基于预编译合约实现,其数据存储采用分布式存储,读写数据产生的交易不再进入缓慢的EVM虚拟机执行,而是由高速的预编译合约引擎执行,因此提高了合约的数据读写效率,使得基于CRUD开发的区块链应用具有更高的性能。

降低合约维护和升级的复杂度

CRUD合约的逻辑与存储分离,这样开发者就只需要关心核心业务逻辑,数据不再与特定合约绑定,更方便合约升级。当合约逻辑需要变更时,可以部署新合约,新合约可以读写旧合约创建的表和数据。

降低面向SQL业务的迁移成本

传统商业应用很大一部分通过数据库表的方式管理数据,通过CRUD合约可以将面向SQL设计的商业应用非常顺滑的迁移到区块链上,降低业务的迁移成本。

CRUD如何使用?

介绍了CRUD的两升两降优势,想必大家非常关注CRUD具体如何使用? 其实,CRUD简单易用,目前可以用两种方式使用CRUD功能,分别是CRUD合约和SDK CRUD Service接口。

CRUD合约

开发者与区块链交互的媒介主要是智能合约,CRUD功能自然会集成在智能合约。集成方式非常轻,Solidity合约只需要引入FISCO BCOS官方提供的Table.sol抽象接口合约文件即可。Table.sol包含分布式存储专用的智能合约接口,其接口实现在区块链节点,可以创建表,并对表进行增删改查操作。引入了该抽象接口的合约称为CRUD合约,以区别于没有引用该接口的Solidity合约。

Table.sol抽象接口合约文件包括以下抽象合约接口,下面分别进行介绍。

TableFactory合约

用于创建和打开表,其固定合约地址为0x1001,接口如下:

接口 功能 参数
createTable(string ,string, string) 创建表 表名,主键名,表的其他字段名(字段之间以英文逗号分隔)
opentTable(string) 打开表 表名
Entry合约

Entry代表记录对象,一个Entry对象代表一行记录,其接口如下:

接口 功能 参数
set(string, int) 设置字段 字段名,字段值
set(string, string) 设置字段 字段名,字段值
set(string, address) 设置字段 字段名,字段值
getInt(string) 获取字段值 字段名
getString(string) 获取字段值 字段名
getBytes64(string) 获取字段值 字段名
getBytes32(string) 获取字段值 字段名
getAddress(string) 获取字段值 字段名
Entries合约

Entries是记录集合对象,Entries用于存放Entry对象,其接口如下:

接口 功能 参数
get(int) 获取指定索引的Entry Entries的索引
size() 获取Entries的大小
Condition合约

查询、更新和删除记录时指定的过滤条件对象,其接口如下:

接口 功能 参数
EQ(string, int) 相等条件 字段名,字段值
EQ(string, string) 相等条件 字段名,字段值
NE(string, int) 不等条件 字段名,字段值
NE(string, string) 不等条件 字段名,字段值
GT(string, int) 大于条件 字段名,字段值
GE(string, int) 大于或等于条件 字段名,字段值
LT(string, int) 小于条件 字段名,字段值
LE(string, int) 小于或等于条件 字段名,字段值
limit(int) 记录选取条件 返回多少条记录
limit(int, int) 记录选取条件 记录启始行位置,返回多少条记录
Table合约

用于对表进行增删改查操作的对象,其接口如下:

接口 功能 参数
select(string, Condition) 查询数据 主键值,过滤条件对象
insert(string, Entry) 插入数据 主键值,记录对象
update(string, Entry, Condition) 更新数据 主键值,记录对象,过滤条件对象
remove(string, Condition) 删除数据 主键值,过滤条件对象
newEntry() 创建Entry对象
newCondition() 创建Condition对象

有了以上对Table.sol抽象接口合约的了解,现在可以正式进行CRUD合约的开发。以官方提供的CRUD合约TableTest.sol为示例,介绍其开发的三步曲:

第一步:引入Table.sol

将Table.sol合约放入TableTest.sol同级目录,并在TableTest.sol合约文件中引入Table.sol,其代码如下:

import "./Table.sol";
第二步:创建表

在TableTest.sol合约文件中,创建表的核心代码如下:

// 创建TableFactory对象,其在区块链上的固定地址是0x1001
TableFactory tf =TableFactory(0x1001);
// 创建t_test表,表的主键名为name,其他字段名为item_id和item_name
int count =tf.createTable("t_test", "name","item_id,item_name");

注:

  • createTable执行原理:createTable执行成功之后,将会在区块链系统表_sys_tables_(区块链启动会自动创建该表,专门记录区块链中所有表的信息)中插入t_test的表信息,即表名,主键名和其他字段名,但并没有正式创建该表。当对t_test表进行增删改查操作时,会首先判断t_test表是否存在,若不存在,则会查询_sys_tables_表获取t_test表的信息,如果查询有t_test表信息,则创建该表,否则执行失败。t_test表存在,则继续执行增删改查操作。
  • 这一步是可选操作:比如新合约只是读写旧合约创建的表,则不需创建表这步操作。
第三步:针对表进行CRUD操作

在TableTest.sol合约文件中,插入记录核心代码如下:

TableFactory tf =TableFactory(0x1001);
// 打开创建的t_test表
Table table =tf.openTable("t_test");
// 创建Entry对象,即创建一条空记录
Entry entry = table.newEntry();
// 设置Entry对象,即设置记录的字段值
entry.set("name", name);
entry.set("item_id",item_id);
entry.set("item_name",item_name);
// 调用Table的insert方法插入记录
// 返回插入影响的记录数,值为1则插入成功,否则插入失败
int count = table.insert(name,entry);

查询记录核心代码如下:

TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
// 创建Condition对象,条件为空表示不过滤,也可以根据需要使用条件过滤
Condition condition =table.newCondition();
// 调用Table的select方法查询记录,获得记录集合
Entries entries =table.select(name, condition);
// 获取记录集合的大小
int size = entries.size();
bytes32[] memoryuser_name_bytes_list = new bytes32[](uint256(size));
int[] memory item_id_list = newint[](uint256(size));
bytes32[] memoryitem_name_bytes_list = new bytes32[](uint256(size));
// 遍历记录集合
// 将记录的三个字段值分别存放到三个数组中,方便方法返回查询的数据
for(int i = 0; i< size; ++i) {
// 根据索引获取记录集合中的记录
Entry entry = entries.get(i);
// 根据记录的字段名查询字段值
// 注意字段值的类型不同需要选择相应的get方法
user_name_bytes_list[uint256(i)] =entry.getBytes32("name");
item_id_list[uint256(i)] =entry.getInt("item_id");
item_name_bytes_list[uint256(i)] =entry.getBytes32("item_name");
}

更新记录核心代码如下:

TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
Entry entry = table.newEntry();
entry.set("item_name",item_name);
Condition condition =table.newCondition();
// 设置过滤条件
condition.EQ("name",name);
condition.EQ("item_id",item_id);
// 调用Table的update方法更新记录
// 返回更新影响的记录数,值大于0则更新成功,否则更新失败
int count = table.update(name, entry,condition);

删除记录核心代码如下:

TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
Condition condition =table.newCondition();
condition.EQ("name",name);
condition.EQ("item_id",item_id);
// 调用Table的remove方法删除记录
// 返回删除影响的记录数,值大于0则删除成功,否则删除失败
int count = table.remove(name,condition);
SDKCRUD Service接口

通过CRUD合约,我们可以看到只要合约方法涉及到数据读写操作,则首先打开对应的表,然后调用读写相关接口就可以进行区块链数据的读写。同时,我们注意到,CRUD合约的开发方式还是离不开编写合约、编译合约、部署合约,最后才能调用合约实现相关功能。那么有没有更方便,更简洁的方式呢?比如开发者合约都不用写,不用部署合约,就可以读写区块链上的数据。答案显而易见,我们已经实现了这样的极致易用需求。

FISCO BCOS SDK提供CRUD Service数据上链接口,这些接口实现的原理是调用区块链内置的一个预编译的CRUD合约,专门负责对用户表进行增删改查操作。Java SDKCRUD Service实现在(Python SDK和Nodejs SDK类似)org.fisco.bcos.web3j.precompile.crud.CRUDService类,其接口如下:

接口 功能 参数
createTable(Table table) 创建表 表对象(需要设置其表名,主键字段名和其他字段名。其中,其他字段名是以英文逗号分隔拼接的字符串)
select(Table table, Condition condition) 查询数据 表对象,Condition对象
insert(Table table, Entry entry) 插入数据 表对象,Entry对象
update(Table table, Entry entry, Condition condition) 更新数据 表对象,Entry对象,Condition对象
remove(Table table, Condition condition) 删除数据 表对象,Condition对象
desc(String tableName) 查询表的信息 表名

以上接口覆盖了表的创建、查看和增删改查操作。用户只需要调用SDK的接口就能完成相关操作,具体示例以下地址查看

其中调用写接口会产生与调用CRUD合约接口等效的交易,需要共识节点共识一致后才会落盘存储。值得关注的是,利用CRUD Service接口,FISCO BCOS控制台针对每个接口实现了易用的sql语句命令,欢迎访问以下地址体验

两种CRUD使用方式的比较

可能用户有这样的疑问,CRUDService接口如此简洁易用,那么是不是我们就只依靠这组接口完成区块链业务呢?其实也不尽然,相比于CRUD合约,其局限主要在两点:

  • 数据访问范围受限:CRUDService接口做的事情非常专一,其本质是一组针对区块链用户表数据的读写接口。因此对于不是用户表数据,这组接口无能为力。例如Solidity合约的状态变量,并没有存储在一个用户创建的表中,通过这组接口就查询不到状态变量的数据。然而CRUD合约本质是Solidity合约,只是多引入了一个Table.sol合约文件,使其有了CRUD功能。通过CRUD合约就可以操作Solidity状态数据和用户表数据。
  • 业务处理能力受限:很多区块链业务不仅仅只是利用区块链上链数据,而是需要设计相关合约逻辑,满足相关条件和验证的前提下才可以向区块链提交数据,仅利用CRUD Service接口就难以胜任这样的区块链业务,因此CRUD合约必不可少。
有哪些Effective CRUD最佳实践?

CRUD功能虽然简单易用,但也需要清晰的知道如何高效的使用CRUD。

条款1:绝大多数情况下请选择CRUD合约开发区块链应用

区块链应用开发中,如果确定仅仅只是上链数据且数据只采用用户表存储,不依赖合约执行相关业务逻辑,那么考虑使用CRUD Service接口。否则,推荐使用CRUD合约。另外,为了方便开发和调试,可以随时利用CRUD Service的读接口(select和desc接口)或者利用控制台对应的相关命令进行数据查询。

条款2:Table.sol文件内容不可修改

Table.sol文件定义的是CRUD功能相关的抽象接口,每个接口区块链均有对应的具体实现。如果用户修改该文件,会导致交易执行失败或功能异常等问题。

条款3:CRUD用户表的主键不唯一

用户习惯于关系型数据库中的主键字段是唯一的,因此默认CRUD用户表中的主键也是唯一的,其实并不是。插入多条记录时,可以指定相同的主键。主键不唯一是由于CRUD表中存储的是主键到对应Entries的映射,进而基于主键进行增删改查。因此,调用CRUD接口(插入、更新、查询和删除记录)时,均需要传入主键值(不是主键名)。另外,主键值过长,会影响读写效率,建议主键值不要设置太长。

条款4:使用CRUD合约时,表字段过多,使用数组或struct封装字段参数

由于Solidity合约语言的限制,一个合约方法中的局部变量个数不得超过16个,否则会出现”Stacktoo deep, try removing local variables”编译错误。因此对于表字段比较多的情况,可以考虑将多个字段参数使用数组或struct封装,以减少局部变量个数,使其满足编译条件。

条款5:使用CRUD合约时,方法中如果涉及表操作,均需要先打开表

TableFactory抽象合约有openTable(string)方法,该方法返回打开的表对象。我们可能会将该表对象设置为合约的全局变量,方便下次操作表时直接使用。注意这是错误操作,因为每次打开表时,其获取的表对象注册在一个临时地址上,在下一个区块之后,该地址不再有效,因此不能将表对象设置为全局变量使用,而是当方法需要操作表之前,均需要打开表。另外,由于TableFactory在区块链中注册的是固定地址0x1001,因此可以将TableFactory对象设置为全局变量,不必每次均创建该对象。

条款6:使用CRUD合约,可以采用表和状态变量混合存储数据

CRUD合约是具备CRUD功能的Solidity合约,因此Solidity之前的状态变量存储方式依然可以使用。当存储一个变量值时,可能直接使用一个变量存储比较方便,因此可以既定义几个状态变量,同时也创建几个用户表进行数据存储。对于比较结构化的数据,推荐使用表的存储方式。

条款7:采用权限控制管理用户表

CRUD合约的逻辑与用户表数据分离,因此通过合约接口的限制已控制不住用户表的写操作(包括插入、更新和删除操作)。为了避免任何账户均可以写用户表,推荐使用权限控制来管理用户表(点击参考权限控制具体使用),指定特定账户才有权限写用户表。

网络协议

节点间通信:P2P协议

客户端与节点通信:JsonRPC,Channel协议

消息订阅服务:AMOP协议

FISCO BCOS网络端口讲解

作者:张开翔|FISCO BCOS 首席架构师

作者语

区块链网络由多个互相连接的节点构成,每个节点又与客户端浏览器监控工具等相连;理清各种网络端口的存在,达成网络畅通的同时又保证安全是建立区块链网络的基础。 同时,在搭链的过程中有一些热点问题,比如为什么节点开了这么多端口?或者为什么网络不通?节点无法连接?没有共识出块?正所谓”通则不痛”,网络畅通才能链接一切

本期文章讲的就是网络端口互通这点事,作者从FISCO BCOS的网络端口、FISCO BCOS 2.0的典型网络配置、设计网络安全组的一些策略等角度进行了解析。

FISCO BCOS 2.0网络的三类端口

FISCO BCOS 2.0的网络包括P2P端口、RPC端口、Channel端口。

_images/IMG_4911.PNG

1.P2P端口

P2P端口,用于区块链节点之间的互联,包括机构内的多个节点,以及多机构间节点和节点的互联。如果其他节点在机构外,那么这个连接要监听公网地址,或者监听内网,且由连接公网的网关(如nginx)转发网络连接。 节点之间的连接会由联盟链的准入机制控制,节点之间连接依赖节点证书验证,以排除未经许可的危险连接。这个链路上的数据通过SSL方式加密,采用高强度密钥,可以有效的保护通信安全。

P2P网络详细设计

网络安全和准入控制

2.Channel端口

Channel端口,控制台和客户端SDK连接Channel端口,互相之间要通过证书认证,只有经过认证的客户端才能向节点发起请求,通信数据也是采用SSL方式加密。Channel端口使用了TCP的长连接,用心跳包检测和保持存活,通信效率较高,支持AMOP协议的点对点通信,功能相当灵活强大。 Channel端口应只监听内网IP地址,供机构内其他的应用服务器通过SDK连接,不应监听外网地址或接受公网的连接,以免发生不必要的安全的问题,也不要只监听本地地址(127.0.0.1或localhost),否则其他应用服务器将无法连接到节点上。

SDK文档

AMOP协议

3. RPC端口

RPC是客户端与区块链系统交互的一套协议和接口,用户通过RPC接口可查询区块链相关信息(如块高、区块、节点连接等)和发送交易。

RPC端口接受JSON-RPC格式的请求,格式比较直观清晰,采用CURL、JavaScript、Python、Go等语言都可以组装JSON格式的请求,发送到节点来处理。当然发送交易时,要在客户端实现交易签名。要注意的是,RPC连接没有做证书验证,且网络传输默认是明文的,安全性相对不高,建议只监听内网端口,用于监控、运营管理,状态查询等内部的工作流程上。目前监控脚本,区块链浏览器连接的是RPC端口。

RPC端口文档

一个FISCO BCOS 2.0的典型网络配置

FISCO BCOS 2.0一个典型网络配置如下所示,可以看到RPC和Channel端口共用一个IP,P2P连接单独监听一个IP,即一个区块链节点使用2个IP和3个端口。

节点下的config.ini文件:

[rpc]listen_ip=127.0.0.1channel_listen_port=20200jsonrpc_listen_port=8545[p2p]listen_ip=0.0.0.0listen_port=30300
几种计算机的典型网络地址

1、特殊地址:0.0.0.0,表示监听本机所有的地址,包括本地、内网、公网(如有)地址,也就是全面放开,来者不拒。除非为了方便且确信安全,一般不应监听这个地址。

2、本机地址:127.0.0.1(有的配置可以写成localhost),只有同在本机上的其他进程才能连接到这个地址,其他机器一律连不过来。FISCO BCOS有的示例脚本为了安全和简易起见,默认写的是这个地址,包括build_chain脚本默认配置等。用户有时会发现其他机器运行客户端程序连不过来,大概率是这个原因,或者也可以检查下网络策略是否开通了互联,建议可以用系统的 telnet 【ip】【port】命令来先快速检测下是否能联通。

3、内网地址:通常192.168.xxx.xxx, 172.xxx.xxx.xxx,10.xxx.xxx.xxx开头的地址是内网地址,如监听这个地址,则只有同一局域网的其他机器可以访问它。

4、外网地址:暴露在互联网上的公网地址,或者可以从机构外部网络访问的地址,总之是外部服务器能连接的就是外部地址。

设计网络安全组的一些策略

在不同的网络拓扑上,可能牵涉这样的情况:服务器虽然可以访问外网,但是是由网关、路由器、NAT转发的,这时就需要了解具体的网络结构,进行配置了。如监听一个内网地址,把这个内网地址和监听的端口配置到转发器上,同样也可以接收来自外网的连接。

在网络安全方面,需要仔细的设计网络安全组策略,IP和端口黑白名单,精确的进行双向的连接控制。包括不限于以下策略:

  • 1、设置外部IP白名单,只有这些外部IP(一般是建立了联盟的其他机构)能连接过来;
  • 2、设置IP黑名单,拒绝某些特定IP的连接,而不用等它连接到节点才进行准入控制判断;
  • 3、控制RPC端口,(如8545端口)只对本机开放,其他内网外网服务器都连不到这个端口;
  • 4、控制Channel端口,只对某一个内网网段或某几个IP开放,把自己的应用部署到开放的网段或IP对应的服务器上,内网其他应用不能访问区块链节点;
  • 5、但凡有外网端口,建议设置防DDoS的措施,避免频繁连接、海量并发连接攻击。
总结

三种网络端口的IP地址、端口、作用、安全考量

_images/IMG_4912.PNG


群友问答

Q:节点间采用P2P通讯,同时采用SSL认证,每个节点如何获取和验证链接节点的公钥证书、预置根证书和证书链?节点跟其他节点通讯时,哪方做Server,哪方做Client?

A:在创建链的时候,就分配了链的根证书;每个节点跟其他节点通讯时,互为Server Client。

Q:外网IP建议布防DDoS攻击,这是一种标准的中心化的防御逻辑。区块链的实现逻辑是去中心化,攻击单个节点是否变得没有意义?机构能不能布置更多的节点?

A:关于DDoS的问题,对联盟链来说,一个机构一般部署两个节点,要是被攻击可能是会影响该机构的业务,但不至于影响全网。机构可以布置多个节点,比如4个、5个或者10个。

感谢参与本次话题讨论的小伙伴们!开源社区,因你们更美!

欢迎更多朋友加入FISCO BCOS官方技术交流群,参与更多话题交流。(进群请扫描下方二维码添加小助手【FISCOBCOS010】)

docs/articles/3_features/34_protocol/../../../../images/articles/%E5%B0%8F%E5%8A%A9%E6%89%8B_FISCOBCOS010.png

FISCO BCOS的网络压缩功能如何?

作者:陈宇杰|FISCO BCOS 核心开发者

作者语

外网环境下,区块链系统性能受限于网络带宽,为了尽量减少网络带宽对系统性能的影响,FISCO BCOS从relase-2.0.0-rc2开始支持网络压缩功能,该功能主要在发送端进行网络数据包压缩,在接收端将解包数据,并将解包后的数据传递给上层模块。

本期文章讲的就是FISCO BCOS的网络压缩功能,作者从FISCO BCOS的系统框架、核心实现、处理流程、测试结果等角度进行了解析。

Part 1. 系统框架

网络压缩主要在P2P网络层实现,系统框架如下:

_images/IMG_5310.JPG

网络压缩主要包括两个过程:

  • 发送端压缩数据包:群组层通过P2P层发送数据时,若数据包大小超过1KB,则压缩数据包后,将其发送到目标节点;
  • 接收端解压数据包:节点收到数据包后,首先判断收到的数据包是否被压缩,若数据包是压缩后的数据包,则将其解压后传递给指定群组,否则直接将数据传递给对应群组。
Part 2. 核心实现

综合考虑性能、压缩效率等,我们选取了Snappy来实现数据包压缩和解压功能。

数据压缩标记位

FISCO BCOS的网络数据包结构如下图:

_images/IMG_5311.PNG

网络数据包主要包括包头和数据两部分,包头占了16个字节,各个字段含义如下:

  • Length: 数据包长度
  • Version: 扩展位,用于扩展网络模块功能
  • ProtocolID: 存储了数据包目的群组ID和模块ID,用于多群组数据包路由,目前最多支持32767个群组
  • PaketType: 标记了数据包类型
  • Seq: 数据包序列号

网络压缩模块仅压缩网络数据,不压缩数据包头。

考虑到压缩、解压小数据包无法节省数据空间,而且浪费性能,在数据压缩过程中,不压缩过小的数据包,仅压缩数据包大于c_compressThreshold的数据包.c_compressThreshold默认是1024(1KB)。我们扩展了Version的最高位,作为数据包压缩标志:

_images/IMG_5312.PNG

  • Version最高位为0,表明数据包对应的数据Data是未压缩的数据;
  • Version最高位为1,表明数据包对应的数据Data是压缩后的数据。
Part 3. 处理流程

下面以群组1的一个节点向群组内其他节点发送网络消息包packetA为例(比如发送交易、区块、共识消息包等),详细说明网络压缩模块的关键处理流程。

发送端处理流程:
  • 群组1的群组模块将packetA传入到P2P层;
  • P2P判断packetA的数据包大于c_compressThreshold,则调用压缩接口,对packetA进行压缩,否则直接将packetA传递给编码模块;
  • 编码模块给packetA加上包头,附带上数据压缩信息,即:若packetA是压缩后的数据包,将包头Version的最高位置为1,否则置为0;
  • P2P将编码后的数据包传送到目的节点。
接收端处理流程:
  • 目标机器收到数据包后,解码模块分离出包头,通过包头Version字段的最高位是否为1,判断网络数据是否被压缩;
  • 若网络数据包被压缩过,则调用解压接口,对Data部分数据进行解压,并根据数据包头附带的GID和PID,将解压后的数据传递给指定群组的指定模块;否则直接将数据包传递给上层模块。
Part 4. 配置与兼容
配置说明
  • 开启压缩:2.0.0-rc2及其以上版本 支持网络压缩功能,配置 config.ini[p2p].enable_compresss=true
  • 关闭压缩:config.ini[p2p].enable_compresss=false
兼容性说明
  • 数据兼容:不涉及存储数据的变更;
  • 网络兼容rc1:向前兼容,目前仅release-2.0.0-rc2及其以上版本有网络压缩功能
Part 5. 测试结果

为测试网络压缩效果,分别在内网和外网环境下,以同样的压测程序和QPS压测开启网络压缩和没开启网络压缩的四节点区块链,测试结果如下。

通过测试结果可看出:

  • 内网环境下开启压缩对区块链系统性能影响不大,运行串行solidity压测合约时,网络带宽消耗降低为未开压缩时的三分之二;运行并行precompile压测合约,网络带宽消耗降低到未开压缩时的三分之一
  • 外网环境下:开启压缩可提升区块链系统性能
图一:带宽对比

(关闭压缩和开启压缩情况下,压测并行solidity合约和串行Precompile合约)

_images/IMG_5313.JPG

通过图一可看出,执行串行solidity合约,开启压缩可节省三分之一带宽;执行并行Precompile合约可节省三分 之二带宽

图二:TPS对比

(内网和外网环境下,关闭压缩和开启压缩情况下TPS)

_images/IMG_5314.JPG

通过图二可看出,内网环境下,开启压缩对区块链系统性能影响不大;外网环境下,因为在有限带宽限制下,开启 压缩可处理更多交易,区块链性能提升了约三分之一。

图三:详细数据
内网环境测试结果
串行solidity合约(PerformanceOk) 压缩前 Snappy压缩后
TPS 1961.5 1939.4
入带宽 10.88MBit/s 6.93MBit/s
出带宽 9.08MBit/s 5.70MBit/s
并行Precompile合约(PerformanceDT) 压缩前 Snappy压缩后
TPS 9725 9741
入带宽 76.06MBit/s 22.72MBit/s
出带宽 80.48MBit/s 24.17MBit/s
外网环境测试结果
压测场景 压缩前 Snappy压缩后
四节点,串行solidity合约(PerformanceOk) 1125.8 1740
四节点, 串行solidity合约(PerformanceOkD) 低于1000 1407

群友问答

@無名:测试带宽是用什么软件测的呢?

@陈宇杰:当时测试带宽的时候,是独占的机器,直接用的nload,当然,在多进程环境下,还可以用nethogs等。

@elikong:提两个问题:

1、为啥选snappy,有没有做过压缩性能分析对比,包括压缩率,cpu时间,典型消息等方面。

2、内网情况压缩前后带宽变化很大,但tps提升不明显的原因是什么?

@陈宇杰

1、是有前期调研的,当时调研了各种压缩库的压缩比、压缩和解压速度、license等。初选的是lz4和snappy,并实现了同时支持两种库压缩算法的版本,并且进行了压测,压测结果显示两种库测试结果相差不大。由于我们的系统里已经集成了snappy,为了避免引入额外的库,所以最终选用了snappy 。

2、内网情况下,性能瓶颈是CPU(包括交易执行速度、验签性能等)、IO等,网络不是瓶颈,因此即使开启了压缩,节省了网络资源,对性能影响也不大。当然这也表明压缩、解压本身对性能损耗不大;外网环境下,网络是瓶颈,这个时候大部分时间是耗费在网络上的,开启压缩,节省了很多网络带宽,使得在相同时间内,节点间可传输更多数据包,因而性能有提升。

FISCO BCOS的AMOP使用介绍

作者:尹强文|FISCO BCOS 核心开发者

AMOP简介:链上信使协议AMOP(Advanced Messages Onchain Protocol)旨在为联盟链各个机构提供一个安全高效的消息传输信道,支持跨机构之间,点对点地实时消息通信,为链外系统之间的交互提供标准化接口,AMOP基于SSL通信加密,确保消息无法被窃听,消息收发均有异常重传、超时检测和路径规划机制,确保消息传输的可靠性。

逻辑架构

AMOP使用了FISCO BCOS的P2P底层通信,逻辑架构如下:

_images/IMG_5315.JPG

各区域概述如下:

  • 链外区域:机构内部的业务服务区,此区域内的业务子系统使用区块链SDK,连接到区块链节点。
  • 区块链内部P2P网络区域:此区域为逻辑区域,部署各机构的区块链节点,区块链节点也可部署在机构内部。
核心实现

AMOP的消息收发基于Sub-Pub订阅机制,服务端首先设置一个Topic,客户端往该Topic发送消息,服务端即可收到。

AMOP支持同一个区块链网络中有多个Topic收发消息,支持任意数量的服务端和客户端,当有多个服务端关注同一个Topic时,该Topic的消息将随机下发到其中一个可用的服务端。

AMOP包括两个流程

  1. 服务端设置Topic,用以监听该Topic的消息;
  2. 客户端往该Topic发送消息。

下面以一个例子来说明内部实现,如下图所示,有2个SDK分别为SDK1、SDK2,2个节点分别是Node1、Node2。SDK1连接Node1设置Topic T1,SDK2连接Node2发送Topic为T1的消息。

1. 服务端设置Topic,用以监听该Topic的消息的时序

_images/IMG_5316.JPG

主要流程如下

  1. SDK1发送监听某个Topic消息的请求,到其直连的节点Node1,Node1里维护了节点与Topic列表的映射关系,该映射关系用于消息路由,是一个map结构,key是NodeId,value是一个set,set存储的是该NodeId可以接收消息的Topic列表。
  2. Node1新增一个Topic之后,节点会更新节点与Topic映射表。
  3. Node1更新seq:seq主要用于保证各节点映射表一致,新增一个Topic之后,本节点的seq会加1,节点之间的心跳包会将这个值带上发送到其他节点,其他节点(Node2)接收到心跳包之后对比参数里的seq与本节点的seq,如果不一致会向源节点(Node1)请求节点与Topic列表的映射关系,将最新的映射关系更新到本节点并更新seq。这样就保证了全局映射关系的一致。
2. 客户端往该Topic发送消息时序

_images/IMG_5317.JPG

  • SDK2向Node2上发送消息。
  • Node2从节点与Topic列表的映射关系中查到该Topic可以发往的节点列表,从中随机选择一个节点Node1发送。
  • Node1节点收到消息之后推送给SDK1。
配置文件配置

AMOP无需任何额外配置,以下为Web3Sdk的参考配置,详细请参考文档

_images/IMG_5318.PNG

不同SDK的配置文件差异在于连接的节点地址不同,以文章开头处的逻辑架构图为例,假定Node1的监听地址是127.0.0.1:20200,Node2的监听地址是127.0.0.1:20201,那么SDK1配置为127.0.0.1:20200,SDK2配置127.0.0.1:20201。

测试

按上述说明配置好后,用户指定一个Topic,执行以下两个命令可以进行测试。

启动amop服务端
java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2Server [topic]
启动amop客户端
java -cp 'conf/:apps/*:lib/*' org.fisco.bcos.channel.test.amop.Channel2Client [topic] [消息条数]

客户端和服务器执行后得如下效果:

_images/IMG_5319.JPG

_images/IMG_5320.JPG

常见错误码及问题定位
  • 99:发送消息失败,AMOP经由所有链路的尝试后,消息未能发到服务端,建议使用发送时生成的‘seq’,检查链路上各个节点的处理情况。
  • 100:区块链节点之间经由所有链路的尝试后,消息未能发送到可以接收该消息的节点,和错误码‘99’一样,建议使用发送时生成的‘seq’,检查链路上各个节点的处理情况。
  • 101:区块链节点往Sdk推送消息,经由所有链路的尝试后,未能到达Sdk端,和错误码‘99’一样,建议使用发送时生成的‘seq’,检查链路上各个节点以及Sdk的处理情况。
  • 102:消息超时,建议检查服务端是否正确处理了消息,带宽是否足够。
未来计划

未来将继续丰富AMOP功能,支持二进制传输、消息多播协议以及Topic认证机制等,也欢迎大家使用AMOP,并提出优化建议。

智能合约

合约引擎:支持Solidity和预编译合约

引擎特点:图灵完备,沙盒运行

版本控制:基于CNS支持多版本合约

灰度升级:支持多版本合约共存、灰度升级

生命周期管理:支持合约和账户的冻结、解冻

FISCO BCOS智能合约开发快速入门

作者:张龙|FISCO BCOS 核心开发者

目前,FISCO BCOS平台支持Solidity和Precompiled两种类型的智能合约,同时,提供交互式控制台工具(Console),方便开发者与链进行交互,部署、调用智能合约。 为了让大家快速上手智能合约,FISCO BCOS推出了智能合约系列教程,本文将带大家快速入门,运用FISCO BCOS开发部署一个简单的智能合约。

智能合约简介

众所周知,智能合约的出现,使区块链不仅能处理简单的转账功能,还能实现复杂的业务逻辑,极大地推动了区块链技术发展,加速应用落地。

目前,在众多区块链平台中,大多数集成了以太坊虚拟机,并使用Solidity作为智能合约开发语言。作为一门面向合约的高级编程语言,Solidity借鉴了C++、Python和JavaScript等语言的设计,使用静态类型,不仅支持基础/复杂数据类型操作、逻辑操作,同时提供高级语言的相关特性,比如继承、重载、库和用户自定义类型等。

作为最大最活跃的国产开源联盟链社区,FISCO BCOS无缝支持Solidity合约,并提供从开发、编译、部署到调用的全链路工具和完整解决方案,使智能合约和区块链应用开发变得简单。 除此之外,基于大量探索和实践,FISCO BCOS不仅支持Solidity合约,还支持Precompiled合约,并在用户层提供CRUD合约接口。面向库表开发的CRUD合约不仅更符合用户开发习惯,进一步降低合约开发难度,提升性能,使区块链应用满足高并发场景的诉求。

智能合约分类

FISCO BCOS平台支持两种类型的智能合约:Solidity合约和Precompiled合约。

Solidity合约

Solidity合约运行在EVM上,EVM为以太坊虚拟机,采用哈佛架构,指令、数据和栈完全分离。 在智能合约运行期间,首先创建一个沙盒环境(EVM实例),沙盒环境与外部环境完全隔离,无法访问网络、文件系统其它进程,智能合约在EVM内只允许进行有限的操作。交易执行时,EVM通过获取合约的opcode,将opcode转化为对应的EVM指令,并按照指令进行执行。

从应用落地的数量来看,Solidity合约使用最为广泛,几乎所有区块链平台都支持,但Solidity也有很多缺点。如下:

  • 合约在EVM中串行执行,性能较差;
  • 跨合约调用会新建EVM,内存开销较大;
  • 合约变量和数据存在MPT数中,不便于合约升级;
  • 逻辑和数据耦合,不便于存储扩容。
Precompiled合约

Precompiled合约即预编译合约。预编译合约通过Precompiled引擎执行,采用C++编写合约逻辑,合约编译集成进FISCO BCOS底层节点。 调用合约不进EVM,可并行执行,突破EVM性能瓶颈;提供标准开发框架,只需继承基类,实现call接口即可;适合于逻辑相对确定、追求高并发的场景;数据存在表中,与合约分离,可升级合约逻辑。

当然,预编译合约的使用有一定的门槛。如下:

  • 对于数据的存储,需要创建FISCO BCOS特有的表结构;
  • 编写合约时需继承Precompiled类,然后实现Call接口函数;
  • 完成合约开发后,需在底层为预编译合约注册地址;
  • 编写完成合约后,需要重新编译FISCO BCOS源码。

为了屏蔽预编译合约在开发和使用中的门槛,FISCO BCOS基于预编译合约和分布式存储设计了CRUD合约接口。用户在编写Solidity合约时,只需要引入抽象合约接口文件Table.sol,便可使用CRUD功能,用户不需要关心底层的具体实现。

智能合约开发

本节将基于全球英文认证考试成绩管理作为场景,基于FISCO BCOS平台对智能合约进行开发。全球认证考试包括GRE、TOEFL、IELTS等。为了简化合约逻辑,所有成绩统一由考试管理中心发布和管理,学生可以根据自己的账号(地址)查询自己的考试成绩。

Solidity合约开发

在 Solidity 中,合约类似于面向对象编程语言中的类。Solidity合约有自身的代码结构,由几个部分组成,如下所示。

  • 状态变量:状态变量是永久存储在合约中的值
  • 构造函数:用于部署并初始化合约
  • 事件:事件是能方便地调用以太坊虚拟机日志功能的接口
  • 修饰器:函数修饰器可以用来改变函数的行为,比如自动检查,类似Spring的AOP
  • 函数:函数是合约中代码的可执行单元
创建合约

首先创建一个名为StudentScoreBySol的合约,用于管理学生的成绩。如下代码所示,开头需要引入合约版本。

_images/IMG_4913.PNG

定义状态变量

状态变量用于存储和管理学生的考试成绩。 在当前场景中定义两个变量,其中_owner为合约的创建者,即考试管理中心;_scores用于存储学生成绩,为一个嵌套mapping类型,第一个mapping的key(address)为学生的账户(私钥对应的地址),value也为一个mapping类型,对应为每一科的成绩,在第二个mapping中key(bytes32)为科目名称,如GRE、TOEFL等,value(uint8)为成绩。如下所示。

_images/IMG_4914.PNG

定义事件

定义一个事件setScoreEvent,用于跟踪对学生成绩的新增/修改操作,可以在业务层面对事件进行监听。事件的定义是可选的,如果没有定义也没关系,在业务层面可以根据方法的返回值去判断交易是否成功,但无法做到更精细的问题定位。

_images/IMG_4915.PNG

定义修饰器

智能合约中的修饰器(Modifier)类似面向对象编程中的AOP,满足条件才会执行后续操作。如下所示,修饰器要求必须是合约的Owner才能进行后续操作,其中Owner为考试管理中心。

_images/IMG_4916.PNG

定义构造方法

构造方法用于实例化合约,在当前构造方法中,指定Owner为合约的部署者。

_images/IMG_4917.PNG

编写函数

当前合约中,定义两个函数,setScore函数用于新增/修改/删除(score置为0)学生成绩,并使用了onlyOwner修饰器,只有Owner(考试管理中心)才能操作,并通过setScoreEvent事件跟踪每次操作内容。getScore方法用于成绩查询,其中view修饰符表示不会修改状态变量。

_images/IMG_4918.PNG

Solidity合约完整代码如下所示。基于Solidity语言的合约开发,看似简单,但需要对Solidity编程语言深入学习,才能编写出高可用的合约,具备一定学习成本。 通过FISCO BCOS开源社区推出的智能合约专题,开发者可了解更多运用Solidity编写智能合约的方法与技巧,关注“FISCO BCOS开源社区”公众号可获取专题。 更多细节可参考Solidity官方文档:https://solidity-cn.readthedocs.io/zh/develop/

_images/IMG_4919.PNG

CRUD合约开发

CRUD合约是CRUD功能的核心,用户可以直接在合约中引用CRUD接口文件Table.sol,并在Solidity合约中调用CRUD接口。CRUD合约的开发完全遵从数据库的操作习惯,更容易理解和上手。

CRUD合约更多开发细节可参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/smart_contract.html#crud

创建合约

CRUD创建的合约和Solidity创建的没有太大区别,唯一区别在于需要引入CRUD接口文件Table.sol,如下所示。修饰器和构造方法的作用与Solidity合约相同。

_images/IMG_4920.PNG

事件定义

在Solidity合约中可以通过setScore完成成绩的新增/修改/删除,但在CRUD合约中,需要借助CRUD接口文件的不同接口,通过不同函数实现,所以需要针对不同的函数定义不同时间,如下所示。

_images/IMG_4921.PNG

  • createEvent:用于跟踪创建表操作;
  • insertEvent:用于跟踪插入成绩操作;
  • updateEvent:用于跟踪更新成绩操作;
  • removeEvent:用于跟踪删除成绩操作。
创建表函数

CRUD合约实现业务功能,首先需要像数据库操作一样,创建一张表,用于存放数据。 FISCO BCOS底层提供了TableFactory合约,该合约的地址固定为0x1001,可以通过TableFactory对象提供的方法对表进行创建(createTable)和打开(openTable),如下所示。 createTable接口返回值为0时,说明创建成功。需要注意的是,为了让创建的表可被多个合约共享访问,表名必须是群组内全局可见且唯一的,无法在同一条链上的同一个群组中,创建多个名称相同的表。

_images/IMG_4922.PNG

新增成绩函数

对表进行操作时,首先需要通过TableFactory打开对应的表,获得表对象,然后通过表对象的insert方法进行数据插入。在插入数据前,首先需要构建一个Entry对象实例,代码如下所示。

_images/IMG_4923.PNG

需要注意的是,Table接口合约的insert、remove、update和select函数返回值类似数据库,均为受影响的记录行数,且接口中key的类型为string。 而在当前场景中,学生的studentId为address类型,所以需要在函数内部将address类型转化为string类型,代码如下。

_images/IMG_4924.PNG

更新成绩函数

更新成绩操作步骤包括:通过TableFactory对象打开表,然后,像数据库一样构造筛选条件。 在CRUD合约接口中,提供了Condition对象,该对象提供了诸如大于、等于、小于等一系列条件方法。构造完成条件对象后,可以调用table对象的udpdate接口,完成更新操作,代码如下。

_images/IMG_4925.PNG

删除成绩操作

删除操作和更新操作类似,需要调用table.remove接口完成,不再赘述,代码如下。

_images/IMG_4926.PNG

查询成绩操作

查询成绩操作很简单,需要调用table.select接口完成,代码如下。

_images/IMG_4927.PNG

至此基于CRUD的合约开发完成。

从当前场景的代码行数来看,CRUD合约比较复杂,Solidity合约相对简单。但这只是一个幻觉,实际情况可能并非如此。而且CRUD合约的开发更符合开发者习惯,没有多余的学习成本,更容易理解和上手。

合约部署及调用

开发完智能合约后,需要对合约进行编译和部署,然后才能调用。FISCO BCOS平台提供了交互式控制台工具(Console),可以非常方便地与链进行交互。下面将以上述智能合约为例,采用控制台工具进行部署和调用。 控制台安装及使用可参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/console.html

准备工作

在对合约进行部署和调用前需要做三件事情。首先复制编写好的合约到控制台目录console/contracts/solidity下。如下图所示。

_images/IMG_4928.PNG

其次对合约进行编译,可以采用控制台目录下的sol2java.sh脚本对合约进行编译,编译结束后会在console/contracts/sdk目录下生成如下文件夹,如下图所示。

_images/IMG_4929.PNG

其中abi中存放合约的ABI,bin存放合约的二级制编码文件(BINARY),JAVA文件夹中为对应的JAVA合约,便于通过SDK和链进行交互。 需要注意的是对CRUD合约编译时。需要将CRUD接口合约Table.sol一并放入console/contracts/solidity目录下,不然会报错。 最后,对合约进行部署时,依赖外部账户,所以首先需要生成账户。在控制台中提供了账户生成工具get_account.sh,运行该脚本会在console/accounts目录下生成账户。

我们利用账户生成工具生成两个账户。一个为考试管理中心的账户,用于部署和新增/修改/删除学生成绩;一个为学生账户,用于查看考试成绩。如下所示。

_images/IMG_4930.PNG

Solidity合约部署和调用

首先采用考试管理中心账户启动控制台,如下图所示,即表示控制台启动成功。

_images/IMG_4931.PNG

然后通过deploy命令对合约进行部署,合约部署成功之后会返回合约地址,如下图所示。

_images/IMG_4932.PNG

合约部署完成之后,可以通过控制台的call命令调用合约函数。如下图所示,新增学生的GRE成绩为70(修改和删除均可通过调用setScore方法进行操作),函数返回值为true,则表示交易成功。call命令的具体用法可以通过call -h 查看。

_images/IMG_4933.PNG

采用学生账户启动控制台,通过getScore函数查看成绩,如下图所示,返回值为70,说明没有问题。也可以使用学生账户调用setScore方法,会报错,打印没有权限,不再赘述。

_images/IMG_4934.PNG

CRUD合约部署和调用

CRUD合约的部署和调用和Solidity合约没有区别,这里同样采用控制台进行。

首先采用考试管理中心账户启动控制台,并对StudentScoreByCRUD合约进行部署。如下图所示。

_images/IMG_4935.PNG

合约部署完成之后,调用create函数创建stu_score表,如下所示,返回结果为0,说明创建成功。

_images/IMG_4936.PNG

创建好表之后,就可以调用相关接口对学生成绩进行操作了,首先调用insert函数新增学生成绩。如下所示,返回结果为1,说明插入成绩成功。

_images/IMG_4937.PNG

成绩插入成功之后,关闭当前控制台,用学生账户登录控制台,并调用select函数查询成绩,如下图所示,返回70,说明查询成功。剩余函数测试可自行完成,不再赘述。

_images/IMG_4938.PNG

结束语

本文重点介绍了FISCO BCOS平台的智能合约开发。在FISCO BCOS平台中,既可以采用原生Solidity语言开发智能合约,也可以使用预编译合约模式开发合约。Solidity合约性能差、学习成本高;预编译合约,采用预编译引擎,支持并行计算,性能更高,同时支持存储扩容等。 但由于预编译合约使用存在一定的门槛,基于此,FISCO BCOS平台开发了CRUD合约接口,用户不需要关心底层的实现逻辑,只需要引入Table.sol合约接口文件,通过调用相关接口即可完成合约的开发。

预编译合约

Solidity合约

其他

密码算法和协议

国密算法 支持

国密SSL 支持

哈希算法:Keccak256、SM3

对称加密算法:AES、SM4

非对称加密算法:ECDSA、SM2

非对称加密椭圆曲线:secp256k1、sm2p256v1

FISCO BCOS的国密特性

作者:李昊轩|FISCO BCOS 核心开发者

安全是信任的保障,算法则是安全的基础,节点通信信道的建立、签名的生成、数据的加密等,都需要用到密码学的相应算法。FISCO BCOS采用国密算法,实现安全可控的区块链架构。

本期文章讲解国密算法的相关概念,以及国密算法在区块链中的应用。

区块链中的密码学

在区块链中,一般使用密码学的场景有以下几种:

1. 数据哈希算法

哈希函数是一类单向函数,作用是将任意长度的消息转换为定长的输出值,具有单向性、无碰撞性、确定性、不可逆的性质。 在区块链中,哈希函数用于将消息压缩成定长的输出,以及保证数据真实性,确保数据未被修改。

2. 数据加密解密算法

加解密算法主要分为对称加密和非对称加密两种:

  • 对称加密具有速度快、效率高、加密强度高等特点,使用时需要提前协商密钥,主要对大规模数据进行加密,如FISCO BCOS的节点数据落盘时进行的加密。
  • 非对称加密具有无需协商密钥的特点,相较于对称加密计算效率较低,存在中间人攻击等缺陷,主要用于密钥协商的过程。

针对不同的需求,两者可以互相配合,组合使用。

3. 消息签名的生成和验证

在区块链中,需要对消息进行签名,用于消息防篡改和身份验证。如节点共识过程中,需要对其他节点的身份进行验证,节点需要对链上交易数据进行验证等。

4. 握手建立流程

前面我们讲过节点TLS的握手过程(点击直达),其中需要使用到密码组件和数字证书,都需要使用到相应的密码学算法。

FISCO BCOS的国密算法

国密算法由国家密码局发布,包含SM1\ SM2\ SM3\ SM4\等,为我国自主研发的密码算法标准。 为了充分支持国产密码学算法,金链盟基于国产密码学标准,实现了国密加解密、签名、验签、哈希算法、国密SSL通信协议,并将其集成到FISCO BCOS平台中,实现了对国家密码局认定的商用密码的完全支持。 国密版FISCO BCOS将交易签名验签、p2p网络连接、节点连接、数据落盘加密等底层模块的密码学算法均替换为国密算法。

  1. 节点TLS握手中采用国密SSL算法;
  2. 交易签名生成,验证过程采用国密SM2算法;
  3. 数据加密过程中采用国密SM4算法;
  4. 数据摘要算法采用国密SM3算法。

国密版FISCO BCOS节点之间的认证选用国密SSL 1.1的ECDHE_SM4_SM3密码套件进行SSL链接的建立,差异如下表所示:

OpenSSL 国密SSL
加密套件 采用ECDH、RSA、SHA-256、AES256等密码算法 采用国密算法
PRF算法 SHA-256 SM3
密钥交换方式 传输椭圆曲线参数以及当前报文的签名 当前报文的签名和加密证书
证书模式 OpenSSL证书模式 国密双证书模式,分别为加密证书和签名证书

国密版与标准版FISCO BCOS在数据结构上的差异如下:

算法类型 标准版FISCO BCOS 国密版FISCO BCOS
签名 ECDSA (公钥长度: 512 bits, 私钥长度: 256 bits) SM2 (公钥长度:512 bits, 私钥长度:256 bits)
哈希 SHA3 (哈希串长度: 256 bits) SM3 (哈希串长度: 256 bits)
对称加解密 AES (加密秘钥长度: 256 bits) SM4 (对称密钥长度: 128 bits)
交易长度 520bits(其中标识符8bits,签名长度512bits) 1024bits(128字节,其中公钥512bits,签名长度512bits)
开启国密特性

需要使用国密版FISCO BCOS特性时,有以下几点需要注意:

1. 搭建国密版FISCO BCOS区块链

搭建国密版FISCO BCOS的区块链方式主要有2种:

(1). 使用buildchain.sh脚本搭建

buildchain.sh脚本的-g为国密编译选项,使用成功后会生成国密版的节点。默认从GitHub下载最新稳定版本可执行程序,操作方式:

curl -LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\.[0-9]\.[0-9]\"" | sort -u | tail -n 1 | cut -d \" -f 4`/build_chain.sh && chmod u+x build_chain.sh
./build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545 -g

执行成功后,会搭建本地四节点的国密版FISCO BCOS联盟链。

(2). 使用企业级部署工具搭建

企业级部署工具generator的-g命令为国密相关操作,用户需要在生成相关证书,下载二进制等操作的过程中附带-g选项,操作方式:

参考教程:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/tutorial/enterprise_quick_start.html

./generator --download_fisco ./meta -g
./generator --generate_chain_certificate ./dir_chain_ca -g
./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyB -g
...

以下操作与教程中类似

./generator --build_install_package ./meta/peersB.txt ./nodeA -g
2. 使用国密版sdk/控制台

国密版sdk和控制台的操作命令与普通版相同,在启用国密特性时需要做如下操作:

  • (1). 将applicationContext.xml中的encryptType设置为1;
  • (2). 加载私钥时需要加载国密版的私钥;
  • (3). 控制台还需要获取国密版jar包。
3. 开启国密版落盘加密功能

国密版落盘加密需要同时对节点conf/gmnode.key和conf/origin_cert/node.key进行加密,其余操作与普通版相同。

总结

FISCO BCOS中的主要算法特性对比如下:

标准版FISCO BCOS 国密版FISCO BCOS
SSL链接 Openssl TLSv1.2协议 国密TLSv1.1协议
签名验证 ECDSA签名算法 SM2签名算法
消息摘要算法 SHA-256 SHA-3 SM3消息摘要算法
落盘加密算法 AES-256加密算法 SM4加密算法
证书模式 OpenSSL证书模式 国密双证书模式
合约编译器 以太坊solidity编译器 国密solidity编译器

「用户提问」

Q王刚+云飞微联网+珠海:请问solidity编译器也需要用到密码学算法吗?

A李昊轩:solidity中的abi会用到哈希,需要底层和合约编译器都使用相同的SM3算法。

Q陈小军-江南科友-广州:请问一下国密TLS协议套件是自己实现的吗,还是用了开源的?

A李昊轩:国密TLS我们采用的是TASSL开源库。

Q腾龙(何直群):如果使用国密的节点,因为签名算法都换了,是不是 RPC SDK 都会不一样了?

A李昊轩:是的, sdk也需要开启国密特性。

QKHJ:目前落盘加密的私钥是如何处理的?

Ameng:要自己保存,可以参考这里的说明

安全控制

存储安全:支持落盘数据加密存储

FISCO BCOS的落盘加密

作者:石翔|FISCO BCOS 核心开发者

区块链部署是多方参与的,为了简化多方协作环境的搭建,通常会使用公有云部署区块链。机构将自己的节点部署到云上,让服务与云上的节点进行交互,实现多方协作。在这个架构中,机构内部的安全性是很高的,尤其是在金融机构。 虽然通过网络的隔离机制,将节点限制在“内网”中,通过网络不能轻易地盗取数据,但是数据全部托管在云上,由于所有参与者都会保存一份数据,在网络和系统安全措施有疏漏或操作不当等极端情况下,可能出现某一份数据被越权访问的情况。 为了防止数据盘被攻破或者盗取,避免数据泄露情况的发生,FISCO BCOS引入了“落盘加密”的功能。

背景架构

在联盟链的架构中,机构和机构之间搭建一条区块链,数据在联盟链的各个机构内是可见的。 在某些数据安全性要求较高的场景下,联盟内部的成员并不希望联盟之外的机构能够获取联盟链上的数据。此时,就需要对联盟链上的数据进行访问控制。

联盟链数据的访问控制,主要分为两个方面:

  • 链上通信数据的访问控制
  • 节点存储数据的访问控制

对于链上通信数据的访问控制,FISCO BCOS是通过节点证书和SSL来完成。此处主要介绍节点存储数据的访问控制,即落盘加密。

_images/IMG_4939.PNG

主要思想

落盘加密是在机构内部进行的。在机构的内网环境中,每个机构独立地对节点的硬盘数据进行加密。 当节点所在机器的硬盘被带离机构,并让节点在机构内网之外的网络启动,硬盘数据将无法解密,节点无法启动,进而无法盗取联盟链上的数据。

方案架构

_images/IMG_4940.PNG

落盘加密是在机构内部进行的,每个机构独立管理自己硬盘数据的安全。内网中,每个节点的硬盘数据是被加密的。所有加密数据的访问权限,通过Key Manager来管理。 Key Manager部署在机构内网里,是专门管理节点硬盘数据访问秘钥的服务,外网无法访问。当内网的节点启动时,从Key Manager处获取加密数据的访问秘钥,来对自身的加密数据进行访问。

加密保护的对象包括:

  • 节点本地存储的数据库:rocksdb或leveldb
  • 节点私钥:node.key,gmnode.key(国密)
实现原理

具体的实现过程,是通过节点自身持有的秘钥(dataKey)和Key Manager管理的全局秘钥(superKey)来完成的。

节点
  • 节点用自己的dataKey,对自身加密的数据(Encrypted Space)进行加解密。
  • 节点本身不会在本地磁盘中存储dataKey,而是存储dataKey被加密后的cipherDataKey。
  • 节点启动时,拿cipherDataKey向Key Manager请求,获取dataKey。
  • dataKey只在节点的内存中,当节点关闭后,dataKey自动丢弃。
Key Manager

持有全局的superKey,负责对所有节点启动时的授权请求进行响应。

  • Key Manager在节点启动时必须在线,响应节点的启动请求。
  • 当节点启动时,发来cipherDataKey,Key Manager用superKey对cipherDataKey进行解密,若解密成功,就将节点的dataK返回给节点。
  • Key Manager只能在内网访问,机构外的外网无法访问Key Manager。

_images/IMG_4941.PNG

相关操作
Key Manager操作

在每个机构上启动一个key-manger程序,用如下命令启动,指定Key Manager:

# 参数:端口,superkey
./key-manager 31443 123xyz
节点操作
(1)配置新节点dataKey
# 参数:Key Manager IP,端口,dataKey
bash key-manager/scripts/gen_data_secure_key.sh 127.0.0.1 31443 12345

得到cipherDataKey,脚本自动打印出落盘加密所需要的ini配置(如下)。此时得到节点的cipherDataKey:cipher_data_key=ed157f4588b86d61a2e1745efe71e6ea。

[storage_security]
enable=true
key_manager_ip=127.0.0.1
key_manager_port=31443
cipher_data_key=ed157f4588b86d61a2e1745efe71e6ea

将得到的落盘加密的ini配置,写入节点配置文件(config.ini)中。

(2)加密新节点的私钥

执行脚本,加密节点私钥:

# 参数:ip port 节点私钥文件 cipherDataKey
bash key-manager/scripts/encrypt_node_key.sh 127.0.0.1 31443 
nodes/127.0.0.1/node_127.0.0.1_0/conf/node.key 
ed157f4588b86d61a2e1745efe71e6ea

执行后,节点私钥自动被加密,加密前的文件备份到了文件node.key.bak.xxxxxx中,请将备份私钥妥善保管,并删除节点上生成的备份私钥。

注意

国密版比非国密版需要多加密一个文件:

  • 非国密版:conf/node.key
  • 国密版:conf/gmnode.key和conf/origin_cert/node.key
(3)启动节点

直接启动节点即可。如果这个节点所在的硬盘被带出机房(内网),将无法访问Key Manager。节点拿不到自己的dataKey,无法解密硬盘数据,也无法解密自己的私钥,因而无法启动。

注意事项
  • Key Manager是一个demo版本,目前superKey是通过命令行在启动的时候指定,在实际应用中,需要根据安全要求,自己定制加载superKey的方式,比如使用加密机进行管理。
  • 落盘加密的配置只针对新生成的节点,节点一旦启动,将无法转换成带落盘加密的节点。
  • 国密版的落盘加密比非国密版多加密一个私钥。

参考链接

Key Manager源码

通信安全:支持全流程SSL

证书管理:支持证书颁发、撤销、更新

权限控制:支持细粒度权限控制

隐私保护

物理隔离:群组间数据隔离 隐私保护协议:支持群签名、环签名、同态加密 场景化隐私保护机制:基于WeDPR支持隐匿支付、匿名投票、匿名竞拍、选择性披露等场景

FISCO BCOS隐私特性:群/环签名技术实现

作者:贺双洪|FISCO BCOS 核心开发者

前言

安全与隐私一直是区块链领域的热点话题,也是各大主流区块链平台必争的战略高地。FISCO BCOS从底层到应用、架构到协议、存储到网络等多方面针对安全和隐私作出较大努力,目前已实现账户管理、落盘加密、安全通讯、权限控制等功能模块。本文将对FISCO BCOS隐私特性之一群环签名展开介绍。

群/环签名是一种特殊的签名算法,最初是为了实现隐匿支付而应用在区块链领域。其能很好地隐藏签名者身份,既能让节点验证交易签名的正确性,又不会暴露交易发起者的公钥信息。这一特性在联盟链中有着广阔的应用前景。

什么是群/环签名

了解群/环签名,得从匿名性说起。

在现实世界,匿名性是指主体的行为不会暴露主体的身份,这几乎是人类文明诞生伊始便存在的需求。

从密码学的角度,匿名性有两种含义:

  1. 给定一个密文,无法还原其公钥,主要用于密码算法的安全性分析;
  2. 用户使用密码方案过程中不会发生身份信息的泄露,更符合现实世界的语义。

密码学中最早隐含身份概念的是电子签名,签名者通过私钥对消息签名,验证者可以使用签名者的公钥验证签名的合法性。在实际应用中,公钥往往和证书绑定(基于身份加密除外,因为身份即公钥,所以没有证书),证书的属性天然揭露属主的身份信息,所以传统的签名方案缺少匿名性。

上世纪九十年代初,Chaum和van Heyst在欧密会(EUROCRYPT)上提出了群签名的概念,有效解决了电子签名的身份隐私问题。

群签名中的“群”可以理解成一个组织。组织里有一个leader即群主,负责成员管理,组织里的每个成员都可以匿名地代表组织进行签名。群签名方案主要算法包括:

  • 创建群,由群主执行,生成群主私钥和群公钥;
  • 新增群成员,由群主执行,生成群成员私钥和证书,证书用于证明群成员身份;
  • 生成群签名,群成员通过私钥对信息签名;
  • 验证群签名,验证者可通过群公钥验证签名的合法性,验证者可以判定这个签名确实来自这个群,但无法确定是哪一个群成员的签名;
  • 打开群签名,群主可通过签名信息获取签名者证书,从而追踪到签名者身份。

_images/IMG_4942.PNG

由于群签名存在一个拥有绝对权限的群主角色,所以群签名的匿名性是相对的。这种特性适用于需要监管介入的场景。

为了追求完全匿名性,Rivest于2001年提出了一种无群主方案,任何成员可自发地加入组织。该方案中签名隐含的某个参数按照一定规则组成环状,因而被命名成环签名。本质上“群”和“环”都可理解为多个成员组成的组织,区别在于是否存在一个能打开签名的leader。 环签名算法流程如下:

  • 初始化环,由环成员执行,生成环参数,环参数就好比微信面对面建群的密码,任何知道该参数的成员都可以加入该环;
  • 加入环,由环成员执行,通过环参数获得公私钥对;
  • 生成环签名,环成员使用私钥和随意多个环公钥对信息签名;
  • 验证环签名,验证者可通过环参数验证签名的合法性。

环签名方案将签名者公钥隐藏在了签名使用的公钥列表中,公钥列表越大,匿名性越高,适用于对隐私要求较高的多方协作场景。

FISCO BCOS的技术选型

目前,群/环签名主要应用在投票、竞标、竞拍等场景以保障参与者身份隐私。对联盟链而言,同一联盟内的多个机构有协作也有博弈,在某些场景下,保护用户身份是非常必要的。

FISCO BCOS集成的群/环签名方案,为用户提供一种能够保证身份匿名性的工具。基于对方案复杂度和链上计算成本的考量,链上只保留了最有必要的步骤,即签名验证,其他算法则以独立功能组件形式提供给应用层。

应用于区块链的群签名方案需要满足以下两点:

  1. 为了方便成员管理,需要支持群成员的撤销;
  2. 考虑到区块链存储资源有限,因此签名数据不能太大,可对齐标准的RSA签名。

因此,我们选择了首个兼具上述特性的群签名方案BBS04《Short Group Signatures》,该方案由Boneh于2004年在美密会(CRYPTO)上提出。

环签名不存在能够打开签名的第三方,为了方便追责,防止签名者被诬陷,需要一种具备指责关联性和抗诽谤性的方案,即基于相同公钥列表生成的两个环签名可判断是否来自同一个签名者。基于这层考虑,我们选择了Joseph在04年提出的首个可链接的环签名方案LSAG《Linkable Spontaneous Anonymous Group Signature for Ad Hoc Groups》。

BBS04方案基于双线性对构造,群管理员可根据不同的线性对初始化群。不同线性对类型下的群签名存储和计算开销如下:

_images/IMG_4943.PNG

其中各个线性对的群阶数可自由配置,上述实验使用了默认值。可以看到,链上验证的时间开销差距并不大,用户可根据自己安全性和性能需求,选择合适的线性对类型以及群阶数。

LSAG方案中,不同环大小的环签名存储和计算开销如下:

_images/IMG_4944.PNG

由于环签名长度、签名和验证时间与环成员数目呈线性关系,为防止超gas,环成员数量建议不超过32个。

如何在FISCO BCOS中使用群/环签名

FISCO BCOS 2.3版本以预编译合约的形式集成了BBSO4方案和LSAG方案的签名验证算法。由于这些隐私保护特性默认不开启,要启用这些功能需要打开CRYPTO_EXTENSION编译选项,并重新编译源码。

群/环签名预编译合约地址分配如下:

_images/IMG_4945.PNG

要完成预编译合约的调用,首先需要以solidity合约方式声明合约接口。

群签名
环签名
// GroupSigPrecompiled.sol
pragma solidity ^0.4.24;
contract GroupSigPrecompiled {
     function groupSigVerify(string signature, string message, string gpkInfo, string paramInfo) public constant returns(bool);
}
// RingSigPrecompiled.sol
pragma solidity ^0.4.24;
contract RingSigPrecompiled {
     function ringSigVerify(string signature, string message, string paramInfo) public constant returns(bool);
}

以验证环签名为例(请确保同级目录下已有上述的接口声明合约),在业务合约中通过地址实例化预编译合约对象以完成验证接口的调用:

// TestRingSig.sol
pragma solidity ^0.4.24;
import "./RingSigPrecompiled.sol";

contract TestRingSig {
    RingSigPrecompiled ringSig;
    function TestRingSig() 
    {
        // 实例化RingSigPrecompiled合约
        ringSig = RingSigPrecompiled(0x5005); 
    }
    function verify(string signature, string message, string paramInfo) public constant returns(bool) 
    {
        return ringSig.ringSigVerify(signature, message, paramInfo);
    }
}

除了预编译合约接口,FISCO BCOS还额外提供了两个核心模块供用户使用,分别是一个完整的群/环签名库以及一个群/环签名RPC服务端。签名库和服务端独立于区块链平台,用户也可以基于签名库定制化地开发自己的服务端。签名信息可在链上进行存储,然后通过在合约中调用验证接口,完成签名合法性的验证。

FISCO BCOS为用户提供了一个群/环签名的开发示例,以客户端为操作入口,示例架构如下图所示:

_images/IMG_4946.PNG

群/环签名客户端调用服务端的RPC接口完成群/环的创建、成员的加入以及签名的生成;同时客户端与区块链平台交互,将签名信息上链;最后客户端调用预编译合约验证链上的签名。更多的操作步骤和技术细节,请参阅群/环签名客户端指南。参考链接如下:https://github.com/FISCO-BCOS/group-signature-client

改进的方向

在学术界,群/环签名的发展已经比较成熟,基于不同场景诞生了很多新的方案。

例如,支持群成员主动加入的方案能有效抵抗群主作恶的陷害行为;可撤销匿名性的环签名方案支持签名者在特定场合将环签名转换成一个普通签名,以证明自己的签署者身份;支持前项安全的方案能保证用户私钥泄露不对之前签名的匿名性产生影响。

目前,FISCO BCOS集成的群/环签名方案各有一种,未来对于更复杂的需求,会增加更多支持方案,为用户提供更多选择。同时,针对现有客户端示例可移植性不佳的情况,未来会考虑以插件的方式提供,方便业务快速接入。

结语

安全与隐私是一个复杂、广阔、充满挑战的领域。

群/环签名模块只是为用户身份提供了匿名性保护,如何结合其它密码协议构建更加可靠、健全的安全区块链平台?如何降低用户的使用成本和开销,提供多维度、高可用的隐私保护服务?以上问题需要我们不断研究探索。

最后,欢迎有志之士加入FISCO BCOS安全性建设,为构建牢不可破的隐私之墙添砖加瓦。

跨链协议

SPV:提供获取SPV证明的接口 跨链协议:基于WeCross支持同构、异构跨链

WeCross跨链专题

FISCO BCOS的部署运维与实战工具

FISCO BCOS零基础入门,五步轻松构建应用

作者:李辉忠|FISCO BCOS 高级架构师

本文面向零区块链基础入门 FISCO BCOS的开发者,以高纯度、超浓缩的极简方式,分享如何快速基于 FISCO BCOS 构建你的第一个DAPP应用。

社区经常有人会问:FISCO BCOS项目有10W+行源代码,10W+字说明文档,几十个子项目,我该如何下手,怎样入门 ? 莫慌,五步入门宝典已备好!!!

第一步:构建一条FISCO BCOS的链

安装文档里给了一箩筐事无巨细的介绍,但此文就是要简明扼要地告诉你:三步就能召唤FISCO BCOS !

(请先在home目录创建fisco目录,接下来都在这个目录操作)

$ curl -LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\.[0-9]\.[0-9]\"" | sort -u | tail -n 1 | cut -d \" -f 4`/build_chain.sh && chmod u+x build_chain.sh
$ bash build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545
$ bash nodes/127.0.0.1/start_all.sh

你可以通过执行*ps -ef | grep -v grep | grep fisco-bcos*查看到四个节点已经在运行。

_images/IMG_4947.JPG

当然,如果你还是需要阅读详细文档,请参考【安装】:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/installation.html#fisco-bcos

第二步:安装一个交互式控制台

控制台是一个可以交互式访问区块链,进行区块链数据读写请求的工具。无需太多解释,四步完成控制台安装:

$ bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/download_console.sh)
$ cp -n console/conf/applicationContext-sample.xml console/conf/applicationContext.xml
$ cp nodes/127.0.0.1/sdk/* console/conf/
$ cd console && ./start.sh

此时,你已经进入控制台界面,可以通过help查看命令列表,通过getPeers获取节点连接列表,通过exit或quit命令退出控制台。

同时,控制台内置了一个HelloWorld合约,可以直接调用deploy HelloWorld进行部署,然后调用call HelloWorld进行访问。

_images/IMG_4948.PNG

第三步:编写一个Solidity合约

教程文档依然是循序渐进地指导,但其实!遵循业务合约编写**三部曲:****存储设计->接口设计->逻辑实现,**就足以顺利完成业务合约。

如果你还是习惯阅读详细文档,请参考【教程】:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/tutorial/sdk_application.html

以文档中的资产转移应用为例,支持链上用户的资产登记、查询、转移功能:

  • 存储设计:基于分布式存储,设计存储表结构
account asset_value
alice 10000
bob 20000
  • 接口设计:基于业务需求,设计合约接口
// 查询资产金额
function select(string account) public constant returns(int256, uint256)

// 资产注册
function register(string account, uint256 amount) public returns(int256)

// 资产转移
function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)
  • 逻辑实现:基于CRUD接口,实现业务逻辑

asset.sol合约:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/tutorial/sdk_application.html

第四步:合约编译与部署

Solidity合约需要通过编译器转换成机器(虚拟机)可执行的二进制,这些二进制是一系列OpCode的组合,虚拟机将解析执行这些OpCode实现合约业务逻辑。

编译之后的合约需要通过工具部署到区块链(写入区块链账本),之后才可以根据合约接口描述文件(ABI)进行调用访问。

额,老毛病犯了,又唠唠叨叨讲原理,还是讲讲如何无脑一键完成合约编译与部署吧:

参考说明文档【控制台】的deploy命令:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/console.html

将Assert.sol放置在console/solidity/contract目录,在控制台执行deploy Assert.sol即可完成合约的编译以及部署。

_images/IMG_4949.PNG

第五步:开发业务

继续假设采用Java开发业务,当然也假设你熟悉eclipse、gradle、spring等常用工具。

1、建立一个Gradle Java工程asset-client,通过IntelliJ IDEA或者Eclipse;

2、编译build.gradle,增加maven库依赖;

repositories增加:maven {url “http://maven.aliyun.com/nexus/content/groups/public/”}maven{url “https://dl.bintray.com/ethereum/maven/” }

dependencies增加:compile (‘org.fisco-bcos:web3sdk:2.0.4’){exclude group: ‘org.ethereum’}

3、将第二步中控制台配置目录(console/conf/)的相关配置文件(applicationContext.xml,log.properties,ca.crt,node.crt,node.key)拷贝到asset-client项目的main/resource目录;

4、将第四步中一键编译生成的java文件(console/solidity/java/*)拷贝到asset-client项目的main/java目录;

5、在main/java目录新建AssetClient类,Asset.java已经实现了有deploy,load,select,register,transfer接口,通过调用这些接口实现业务相关逻辑。

具体代码可参考示例工程:https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz

到这里,你已经完成第一个基于FISCO BCOS的应用开发!如你对开发流程有疑问或优化建议,可以通过公众号进入技术交流群和我们一起探讨。

WeBASE

FISCO BCOS迎来区块链中间件平台WeBASE,应用落地提速

随着区块链技术的发展,越来越多的开发者基于稳定和高效的区块链底层平台,结合智能合约和链上接口,开发各种丰富的应用,随之而来的是对区块链系统的易用性、应用开发速度、业务组件丰富性产生了更多的需求。

直面底层平台,“裸写”智能合约和底层代码,在技术上是可行的,而进一步提供“所见即所得”、“开箱即用”这种无门槛体验的动力,则来自社区开发者的反馈,比如:

  • 缺乏好用的智能合约开发工具,合约的开发和调试效率不高,难以便捷地管理链上各节点配置信息,观察其运行状态。
  • 区块链上的区块、交易、回执等数据的呈现方式不够友好,对链上的海量数据难以进行灵活和多维度的分析。
  • 针对参与到业务的各账号及其进行的交易行为,需要通用的审计工具,以便及时发现和杜绝异常。

为响应开源社区里持续的需求,同时将自身长期探索的成果开放分享, FISCO BCOS开源工作组成员单位——微众银行,为社区贡献一条从区块链底层通往应用落地的高速通道。

7月2日,微众银行正式开源自研的区块链中间件平台——WeBASE,该平台适配支持FISCO BCOS底层平台,面向多种对象,如开发者、运营者,并根据不同的场景,包括开发、调试、部署、审计等,打造丰富的功能组件和实用工具,提供友好的、可视化的可操作环境。

基于FISCO BCOS底层平台,部署WeBASE,可以简化区块链应用开发流程,大大降低企业搭建区块链应用,以及进行运营分析的时间成本、人力成本,使开发者可得心应手的操控区块链网络,更专注投入到应用开发和业务落地中。

WeBASE代码仓库地址:https://github.com/WeBankFinTech/WeBASE

WeBASE简介

**WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和FISCO BCOS节点之间搭建的中间件平台,**如下图所示,开发者可在区块链节点上层部署WeBASE的可交互模块如浏览器、管理台和其他工具等,也可以基于WeBASE内置的组件和API,进行应用的开发。

_images/IMG_4950.PNG

WeBASE的“简约而不简单”,体现在以下几个方面:

一 、提供友好的智能合约开发平台,支持在线编译、调试、测试、部署智能合约,具有高效的编辑环境,率先支持Solidity智能合约语言。

_images/IMG_4951.PNG

二、在SDK基础上封装Restful风格的API接口,Restful接口更直观,有良好的可扩展性,可轻松适配多种编程语言。通过接口可对交易数据进行编解码,可在包括网页、手机终端等设备上全方位、多维度展示链上数据细节。

_images/IMG_4952.PNG

三、区块链管理平台是运营管理员的首选工作台,可查看链上的数据统计、每个区块的详情、各个节点多维度的统计数据,对节点的健康度做到全方位监控。

_images/IMG_4953.PNG

四、数据导出组件可配置式的导出链上数据到关系型数据库、大数据处理等系统,以便对链上数据进行多元化处理,如进行数据挖掘,构建商业模型等。

WeBASE的整体架构和设计原则

WeBASE的完整架构如下图所示:

_images/111.PNG

WeBASE的设计理念是一个子系统解决一个问题,无需部署所有子系统就能跑起来,因此在设计之初就遵循如下原则:

按需部署:WeBASE抽象对应用开发通用共性进行抽象,形成各类服务组件,如业务接入、私钥管理、交易队列、合约开发、数据导出、审计等,开发者根据需要部署所需组件。

微服务架构:WeBASE采用微服务架构,基于spring-boot框架,提供Restful风格接口。

零耦合:WeBASE所有子系统独立存在,均可独立部署,独立运行,面向不同的场景提供服务,避免出现“全家桶”式的冗余负担。

可定制:前端体验往往带有开发者自身的业务表现,如不同的样式、不同的交互风格、不同的品牌表现等,因此WeBASE采用前后端分离的设计,后端接口保持稳定和可扩展,前端页面则由开发者自由定制。

基于WeBASE的应用开发流程

基于WeBASE的应用开发流程有全新的体验,下图直观比对两种开发流程的不同:

_images/640.jpeg

显然,基于WeBASE的应用开发,流程大大简化。智能合约开发工具、完善的数据可视化平台、简易的交易上链方式,降低了开发门槛,使得开发效率大幅提升。而且对于应用上线后的交易审计、数据导出、立体监控等方面的管理,WeBASE为其提供一系列完善的组件,能有效避免开发者和企业重复破荒造路。

WeBASE的下一步

今天,WeBASE开源只是一小步,未来,WeBASE将有计划地开放更多功能

  • 提供更多直面商业领域的、经过业务验证的组件,便于集成到有共通模式的应用中,建立区块链应用开发最佳实践和标准架构;
  • 提供各类行业解决方案和参考实现;
  • 为云厂商提供更友好的接入方式。
社区开发者体验
  • 网络技术总监 林冬艺 广州市品高软件股份有限公司
品高软件是一家云计算公司,正在探索云计算与区块链的融合创新。WeBASE具有友好的界面设计、便捷的管理以及云生态的良好融合等特点,使得区块链上云变得更轻松,我们得以更专注于区块链与云计算的业务场景。
  • 架构师 魏巍 宝付网络科技(上海)有限公司
WeBASE操作简便,社区文档非常完善,涵盖企业所需的各类组件,可插拔式的架构设计,根据所需组合使用,帮助我们解决区块链技术的后顾之忧,缩短业务数据化进程,快速实现业务价值。
  • CTO 金兆康 杭州亦笔科技有限公司
WeBASE的联盟治理、前置管理功能使得管理区块链就像管理聊天群一样简单。社区的及时响应、功能的快速迭代,处处体现出国内顶级区块链服务提供商的实力。
  • 区块链架构师 孙耀普 全链通有限公司

WeBASE各系统模块都提供了丰富的部署文档,明确的部署步骤,按照部署文档就可以很快部署和使用,系统的UI设计和布局也非常合理。

注册和登录功能,满足了客户关于区块链信息查询权限管理需求,减少了开发的工作量;系统集成了FISCO BCOS的java接口,提供了丰富的调用接口,减轻了开发工作量,降低了区块链的使用难度。

系统提供智能合约的编辑和编译和部署功能,使智能合约开发更加便捷。

  • 创始人 张泷 聚滴成海(广州)信息技术有限公司

聚滴成海是一家专注区块链深层次挖掘的中间件提供服务商,我们需要直接对客户提供封装好的接口,来降低他们的开发工作量。由于我们产品性质的特殊,需要兼容多种生态、包容多种共识机制,因而我们的开发工作量非常庞大!

但是WeBASE将各种接口已经封装的非常完善,只需稍作暴露就可以快速连接!希望WeBASE可以继续坚持对开源社区的支持,我们也愿意为之不断付出和贡献。

一键搭链脚本工具

FISCO BCOS快速建链实现之路

作者:白兴强|FISCO BCOS 核心开发者

跟很多开发者一样,团队刚开始搭链时,也经历过迷之困惑的阶段:安装哪个版本,怎么编译这么久还容易出错?配几个节点,都用什么IP端口?证书从哪里来,放到哪里去?怎么验证我的链确实搭起来了?…

相信从FISCO BCOS1.X版本上手的工程师们,内心都有一座小火山,面对超长的文档、超多的操作步骤……每次版本部署,耗费大量时间,工程师头上都快能摊熟一枚鸡蛋。而数据表明,一个软件如果15分钟还使用不起来,用户就会流失。 为了浇灭大家心中的小火山,一起愉快地玩耍区块链,FISCO BCOS的易用性优化势在必行。团队的第一个目标是让开发者在5分钟内搭起开发测试链,这就需要一个《哈利•波特》里召唤术一样的命令,我们把它称为build_chain。 本文将聊聊build_chain脚本的诞生记,以及当前脚本能够提供的帮助。

build_chain脚本的诞生

首先是去掉编译步骤,源码编译不但需要安装下载依赖,还需要配置开发环境,即便这两步一切顺利,编译过程中还可能因为内存不足而失败,更何况下载依赖经常受到网速影响导致下载失败。于是我们提供了预编译的二进制发布包,让用户跳过冗长的编译阶段。 马上我们又发现了新的问题,即使针对不同平台都提供二进制发布包,用户的环境是千变万化的,预编译程序所依赖的动态库的安装又成了问题。 于是我们想到了提供静态编译的二进制发布包,兼容多种Linux64位操作系统,不依赖任何其他库,省时又省力。为了实现静态编译,我们不惜重新实现部分功能,以去掉对不提供.a的外部库的依赖。 接下来我们尝试减少部署步骤,减轻对用户的压力。

配置项太多太灵活,我们优化配置,所有配置都提供合适的默认值,删除不需要灵活定制的配置项。 json格式的配置文件阅读不够直观,手工修改容易因为格式问题导致错误,我们替换为更清晰的ini文件。 系统合约手动部署太麻烦,我们借助预编译合约实现内置系统合约来管理链上配置。 手工搭建和工具脚本搭建的节点目录结构不统一,我们整理文档,统一工具创建的目录结构,提供辅助脚本。

经过上述这些优化,我们认为可以有更轻量级的部署方式,可以尝试通过一个脚本来完成部署过程中的所有事情。 相比于大而全的部署工具,脚本更轻更快;而相比于手工部署的繁琐,脚本能够更简单。这样,build_chain脚本就诞生了。

build_chain脚本提供的帮助

这个脚本能够完成环境检查、参数解析、FISCO BCOS二进制发布包下载、公私钥证书生成、配置文件生成和工具脚本生成等功能,支持MacOS、Linux 64bit、docker模式和国密版本搭建。 然而实际使用后,我们发现在家里的网络条件下,脚本下载二进制发布包需要很长时间,导致5分钟内无法完成搭起一条FISCO BCOS的链。 为了能达成5分钟建链的目标,我们又新增了CDN支持,在哪怕网络条件不是很好的情况下,也可以顺畅地在5分钟内完成建链。心中的小火山熄灭了。

具体而言,build_chain脚本能够提供的帮助包括以下几个:

环境检查

build_chain脚本需要使用OpenSSL来生成节点所需要使用到的相关证书文件,而FISCO BCOS 2.0要求使用OpenSSL 1.0.2以上版本。只有找到符合要求的程序版本,脚本才能继续执行。 需要注意的是,MacOS自带的是LibreSSL,所以需要用户使用brew install OpenSSL安装OpenSSL。

解析参数

build_chain脚本支持很多自定义参数,例如-p指定节点使用的端口范围、-f搭建指定配置的网络、-g搭建国密版本、-v指定FISCO BCOS程序版本号、-o指定输出路径等,可参考详情

_images/IMG_4954.PNG

获取FISCO BCOS可执行程序

FISCO BCOS提供标准版和国密版本的预编译可执行程序,可以在大部分x64的Linux机器上运行。另外,为方便开发人员调试,同时提供了MacOS版本的可执行程序。

  • build_chain脚本会根据操作系统和是否国密下载对应的可执行程序。
  • 当从GitHub下载可执行程序较慢时,会自动切换到CDN下载。如下图可以看到fisco-bcos.tar.gz只有7.72M。
  • 当不使用-v选项指定版本时,脚本会自动拉去GitHub上FISCO BCOS发布的最新版本,使用-v选项时,则下载指定版本的可执行程序。

对除官方明确的Ubuntu 16.04+和CentOS 7.2+以外的平台,上生产时建议使用源码编译获得的可执行程序,然后通过-b选项和-f选项搭建区块链网络。

_images/IMG_4955.PNG

生成私钥证书

FISCO BCOS支持证书链,默认使用三级证书链结构,使用自签CA证书作为链的根证书,使用CA签发的机构证书用于区分机构,然后使用机构私钥签发节点所使用的证书。 节点的conf目录下会有ca.crt、node.key、node.crt三个文件,节点使用这三个文件建立双向SSL链接,使用node.key在共识过程中为区块签名。 如果是国密版本,则脚本会下载TaSSL工具,生成国密版本的证书文件。

生成配置文件和工具脚本

build_chain脚本中内置了FISCO BCOS节点所需要使用的配置文件模板,根据用户指定的参数修改,生成节点所使用的配置文件(可查看配置文件的介绍)。

同时,为方便用户启动和停止节点,节点目录下还会生成start.sh和stop.sh(可查看节点目录结构说明)。

总结

下面来总结一下,FISCO BCOS团队提升部署速度、实现快速建链的几个要诀:

  • 1.提供静态编译的二进制发布包,兼容多种操作系统,让用户跳过冗长的编译阶段。
  • 2.简化配置,极大限度采用能保证最大成功率的默认参数,最小化用户需要关注的信息,用户只需关注少量网络配置。
  • 3.标准化目录结构,无论是用一键搭链、企业级搭链、手工搭链…生成的目标都是一样的,降低了差异化带来的复杂度。
  • 4.巧用脚本,build chain脚本可自动串起从准备环境到启动所有链节点一系列常规操作步骤,自动处理各种可能的小异常,让整个过程显得行云流水。
  • 5.优化依赖库地址、网络速度等,极大减少用户的等待消耗,谈笑间,链已经搭好。

对于想通过手工搭建进一步学习的同学和生产环境使用,建议使用我们提供的企业级部署工具generator。 在性能和易用性方面,做再多努力都是值得的,我们会继续努力优化,非常欢迎各位社区的参与者提优化建议和bug。


参考链接

企业级部署工具generator:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/enterprise_tools/index.html

交互式控制台Console

FISCO BCOS 控制台详解,飞一般的区块链体验

作者:廖飞强|FISCO BCOS 核心开发者

我们对Linux系统中的命令行终端已经很熟悉了,通过shell终端可以顺畅地使用Linux。类似地,FISCO BCOS联盟链中,也有这样的命令行终端,称为控制台。控制台是开发者探索区块链世界的助推器,其提供的各种功能有助于跨越横在区块链入门到精通之间的座座高山,带来“开箱即用”的顺滑体验。

Why: 为什么要做控制台?
体验环境影响用户从“入门到放弃”,或者“入门到精通”。

当学习一个新技术或产品时,除了读文档之外,尝试上手操作,获得第一手体验也尤为重要。如果体验环境配置复杂、操作繁琐,很可能导致用户从“入门到放弃”;而体验环境简单易配置、功能丰富,则会为用户迅速打开一扇新世界的大门,加速用户从入门到精通。

选择何种形式承载这种极速且友好的体验方式呢?如果我们有一个控制台,只要输入单条命令或代码,然后按下回车键就可以返回结果显示在用户面前。这种 “开箱即用”的效果,正是我们所期待的体验方式。

可以为FISCO BCOS实现极速体验的控制台吗?

FISCO BCOS 1.3版本其实已经具备快速体验的功能,由两部分组成,分别是ethconsole和Node.js工具。其中,ethconsole可以查询链上信息,包括节点、区块和交易信息;Node.js工具提供了部署和调用合约的模板js文件,协助用户实现合约的部署与调用。

但是这种体验方式还不够友好,那一版的ethconsole只能查询非常有限的链上信息,并不能发送交易和管理区块链,功能比较单一;通过Node.js工具需要手工编写部署和调用合约的模板js文件,操作较为繁琐,体验和ethconsole割裂,不那么顺畅。

因此,FISCO BCOS 2.0版本规划时,重点设计了FISCO BCOS 2.0控制台,目标是做一个易用、友好、且功能强大的全新控制台,给FISCO BCOS提供一种极速体验方式。

What: 控制台实现了哪些功能?

控制台每一个功能的实现都源于一个个朴素的操作,一切按实际需求出发,对用户有价值的功能,一一实现。

需求1:区块链是什么,在哪里?看得见吗?

实现查询区块链相关的一系列命令,让区块链看得见摸得着!例如查询区块高度、区块、交易、节点等等,并且根据不同参数,提供不同的查询方式以满足不同条件下的查询需求。

值得关注的是,对于交易和交易回执信息查询命令(getTransactionByHash,getTransactionReceipt等),提供按ABI定义解析详细数据的功能,让交易的输入、输出和event日志信息以解码的方式呈现,而不再是满屏十六进制的天文数字。

需求2:部署和调用合约是使用区块链最核心的诉求,控制台可以直接部署和调用合约吗?

必须可以。控制台推出之前,部署和调用合约有两种选择:一种是借助Node.js工具,即写Node.js客户端部署和调用合约;一种是使用Java SDK, 写Java 客户端部署和调用合约。这两种方式都很强大,但并不是为极速体验设计的,用户需要编写合约之外的部署和调用代码。

因此,控制台实现的效果是,用户写好合约,放入指定路径,在控制台输入一个命令(deploy)即可完成部署,再用call指令就能调用合约接口,不需要其他任何额外的工作(例如将solidity合约转为java代码,编写部署和调用合约的客户端代码等)。 deploy命令部署合约之后,会显示一个合约地址,考虑到后续调用合约时还会用到这个合约地址,控制台本地会记录部署的合约地址,并提供getDeployLog命令查看部署过的合约地址列表信息。

另外,FISCO BCOS区块链提供CNS功能,即合约命令服务功能。链上可以记录部署的合约名、版本号和对应的合约部署地址;部署合约时,指定合约名和版本号;调用合约时,指定合约名和版本号(若不指定,使用最近部署的合约版本号)。

这是一种更高级的部署和调用合约方式,也是推荐的部署和调用合约方式。因此控制台实现了利用CNS部署合约命令deployByCNS和利用CNS调用合约命令callByCNS。 值得关注的是,为了开发者便于查看处理信息和调试合约,控制台会自动解析合约的输出和event日志信息。

需求3:FISCO BCOS 2.0支持多群组,控制台可以在线切换群组吗?

控制台登录之后,其命令提示符前面显示当前群组号。通过提供switch命令在线切换多群组,即不需要退出控制台就能完成无缝切换。切换之后,命令提示符前面的群组号自动更新,而后可以在切换后的群组中发送命令。

需求4:控制台可以管理区块链吗?

FISCO BCOS 2.0 提供节点管理、系统参数管理、权限管理功能,控制台均提供对应的命令进行操作,方便用户通过简单的命令轻松管理区块链。 其中,节点管理的命令是addSealer(添加共识节点)、addObserver(添加观察节点)、removeNode(移除群组中的节点);系统参数管理的命令是setSystemConfigByKey(设置系统参数);权限管理有一系列的命令,管理区块链系统相关功能的操作权限,其具体是以grant开头的赋予权限命令,以revoke开头的撤销权限命令,以及以list开头的查询权限命令。

具体使用参考这里

需求5:可以不用写CRUD合约,操作区块链中的用户表吗?

FISCO BCOS 2.0提供分布式存储,其核心在于表存储设计。CRUD合约是面向表存储的一种合约写法,合约的数据存储在用户表中,合约接口面向表进行增删改查操作。 为了让用户不用写CRUD合约,就可体验分布式存储功能,控制台提供类似mysql语句的形式,提供创建表(create)、查看表(desc)和表的增删改查(insert, delete, update, select)命令。 创建表和增删改命令均是发送一条需要区块链节点共识的交易,与用户编写CRUD合约操作表等效。

需求6:控制台支持国密方式发生交易吗?

控制台提供修改配置文件的国密开关,并下载国密版合约编译器以及替换,这就成为了国密版控制台。因此,当区块链节点是国密版本时,控制台可以连接国密节点并支持部署和调用国密版合约,发送国密版交易。 另外,控制台默认的合约编译器是0.4.25非国密版本,需要使用0.5.2版本国密或非国密版本可以下载对应合约编译器。

具体操作参考这里

需求7:…等你来提?

欢迎社区用户积极提意见、建议以及需求(issue或微信社群的方式),同时也可以直接提pull request到官方console仓库,修改和新增你们需要的功能,甚至可以fork源码然后单独定制个人或组织的控制台,开源社区的共建和共享依靠社区用户广泛而积极的参与。

Where: 控制台的价值在哪里

现在,用户只要启动控制台,就可以查询丰富的链上信息,快速部署和调用合约,轻松管理区块链,这些特性给用户带来什么价值呢?

  • 面向初学人员:不用搭建复杂的开发环境,只要简单的节点ip和端口配置,非常轻量级,就可以极速体验区块链功能。当启动控制台后便呈现出一个炫酷的FISCO BCOS标志图案时,会顿时感觉自己成功地与FISCO BCOS产生了第一次连接,从而信心倍增地继续探索区块链。

    配置和启动控制台请参考这里

  • 面向开发人员:开发人员可以利用控制台部署和调试合约,当写好合约后,可以在控制台先部署,采用call指令调用验证合约逻辑,观察合约运行过程和结果。如果业务端采用Java开发应用,可以利用控制台的合约编译工具将Solidity合约一键编译为Java客户端代码文件,供客户端的java工程调用。

  • 面向测试和运维人员:可以在搭建区块链环境后,利用控制台查看链状态,操作区块链配置,测试或检查相关区块链功能。

总之,控制台已成为FISCO BCOS的一个极速体验的窗口,一件强有力的利器,为用户持续带来实际价值


1、控制台已是FISCO BCOS SDK的一个标配功能,目前已提供Java版控制台(已单独成立了一个console仓库),Python版控制台和Node.js控制台。上文主要是针对Java版控制台进行的介绍,其他控制台相关功能大体相同。

2、控制台文档列表

Java版控制台使用文档

Python版控制台使用文档

Node.js版控制台使用文档

多语言SDK

麻雀虽小五脏俱全 | 从Python-SDK谈谈FISCO BCOS多语言SDK

作者:张开翔|FISCO BCOS 首席架构师

FISCO BCOS 2.0从发布起就自带官方控制台,经过社区持续使用打磨,已经足够强大、完善、友好。

社区还有用各种开发语言开发的区块链应用,为满足开发者方便管理区块链节点的需求,目前Python-SDK和Nodejs-SDK已经上架,go语言版本已经在路上。


本文以笔者最熟悉的Python-SDK为例,分享一些SDK开发的点滴,涵盖应用开发流程、协议编解码、网络通信和安全事项等。

FISCO BCOS自带快速搭建特性,五分钟一键搭链后,开发者只需连上区块链节点,写合约、发交易。

控制台和SDK的定位,是帮助用户快速访问区块链,开发测试智能合约,实现业务逻辑。根据“奥卡姆剃刀”原则,设计哲学应尽量轻、模块化、浅层次,不引入多余的功能,不给用户和二次开发者造成额外负担。

客户端控制台和SDK就像一辆操控好、配置精的快车,供开发者和用户驾驭,轻松惬意,尽情驰骋于区块链应用之路。

控制台体验

首先结合从准备环境到调用合约的全流程,体验下控制台,命令行交互界面风格如下:

_images/IMG_4956.PNG

1. 准备环境

在开始之前,请先通读使用手册和开发文档(非常重要!链接在文末),根据文档介绍,step by step初始化环境,安装依赖库,目前Python-SDK支持linux/mac/windows操作系统。

为了连上区块链节点,需要修改本地配置文件,填写区块链节点的对应网络端口,如果选择Channel协议,则需要配置相应的客户端证书。

2. 在线体验

配置好网络后,可以运行console的get系列命令。试下手感,与FISCO BCOS亲密接触。确认链在正常工作,常用的指令有getNodeVersion、getBlockNumber、getPeers、getSyncStatus等,可以用console的usage或help命令了解所有支持的指令。

3. 创建账户

创建一个新的帐户,即代表自己身份的公私钥对,发送交易时会采用私钥签名。创建的帐户用keystore格式保存在本地文件系统里,用户可以设定密码保护这个文件,注意记住创建帐户时使用的密码。

控制台提供的帐户相关命令是newaccount, showaccount(参数为账户名和密码)。如果要使用刚创建的新帐户为交易签名,记得把它配置到client_config.py文件的相应位置。

另外,如果账户信息需要高等级保护,则可以进行二次开发。将其放入加密机、TEE等安全区,以及开发秘钥分片、助记词等方案。

4.编写合约

编写一个智能合约,或者参照SDK里自带的智能合约例子修改定制,实现自己的业务逻辑。本文重点关注solidity智能合约,FISCO BCOS还有一种“预编译合约”,采用C++开发,需要和FISCO BCOS底层代码联合编译。

5. 编译部署

对合约进行编译,获得合约的ABI接口文件和BIN二进制代码文件。Python-SDK里有bcos_solc.py文件可帮助开发者简化编译器配置和调用,同时,只要正确配置了合约路径和编译器路径信息,直接运行控制台的部署或调用合约接口指令,也会尝试自动去编译合约,操作体验相当行云流水。

独立部署合约的话,可使用控制台的deploy指令,部署指令成功后会得到新的合约地址。参考命令是./console.py deploy SimpleInfo save,其中SimpleInfo是合约名(不需要带后缀),最后的”save”为可选,如果指定了”save”,则将合约新地址记录到本地文件里,以便后续使用。

6. 调用合约:

用call或sendtx命令,指定合约名、合约地址、方法名、对应的参数,调用链上合约。

参考命令./console.py sendtx SimpleInfo last setbalance 100,即选择SimpleInfo合约,指向其最近部署成功的地址(用”last”指代,可以省掉复制粘贴合约地址的繁琐操作),调用setbalance接口,传入参数100。

交易在链上共识完成后,控制台会自动打印交易回执里的方法返回码、交易Event log信息列表等,供用户查看,如果错误,则打印异常信息。

如果一切正常,到此即可基本走通区块链应用之路。

值得一提的是,FISCO BCOS几个语言版本的控制台,都支持按Tab键提示指令和自动完成,帮助使用者流畅无错地操作,提升用户体验。

再进一步,如果希望有丰富多彩的、可视化交互式页面体验,不妨使用WeBASE中间件平台。

深入了解(Dive Deeper)

整个SDK的模块组合如下,可谓是麻雀虽小五脏俱全。

_images/IMG_4957.PNG

功能接口

支撑控制台等交互模块的,是已经封装完备、开箱即用的功能接口API,包括:

1. get系列

诸多的“get”开头的接口,用于获取链上的各种信息,包括区块、交易、回执、状态、系统信息等等。虽然几十个get接口,但其实现逻辑基本一致,都是指定命令字和参数列表,请求和处理回应,实现起来也很快。

2. call

对应合约的常量方法。所谓常量方法,是指合约里对应代码不修改状态,该请求不会全网广播,仅在指定节点上运行。

3. sendRawTransaction

构建一个交易,用账户私钥签名,发送到链上,这种交易会被广播,进行共识处理,生成的状态数据会被全网确认。部署新合约这个操作,实际上也是一种交易,只是不需要指定目标合约地址。

相关的是sendRawTransactionGetReceipt,名字很长,在sendRawTransaction基础上增加了获取回执的流程**,**用于简化从发交易到获取回执的闭环流程。

4.更多

针对FISCO BCOS的全局系统配置、节点管理、CNS、权限等系统级功能的API,其原理是读写链上的系统合约,详细指令列表见文末。 开发者可以参考控制台和client/bcosclient.py等代码,进行二次开发,实现更多更酷炫的功能。另外,SDK里内置了一系列的开发库和小工具,帮助管理帐户、输出日志、统一异常处理、简单的性能和耗时统计等。

合约开发相关

围绕着合约开发,Python-SDK实现了合约编译部署、合约地址本地化管理、ABI接口文件的管理,支持代码自动生成(参考codegen.py),一个命令行即可生成供业务端直接使用的代码,如

python codegen.py contracts/SimpleInfo.abi。

solidity合约编译后的ABI文件是个好东西。ABI 全称是 Application Binary Interface(应用程序二进制接口),里面详细描述了合约的接口信息,包括方法名、参数列表和类型、方法类型(常量方法,还是交易方法),以及Event log格式定义等等。

对ABI的管理,参见client/datatype_parser.py,加载和解析ABI文件(默认为JSON格式),根据方法名、方法4字节签名、方法类型等维度,灵活查询方法列表和方法定义,并针对方法定义、输入数据等进行编码解码,解析交易返回值、Event logs等。

有ABI定义在手,对合约的操控简直是可以随心所欲,开发者读懂了ABI描述,基本就能全面理解一个合约的输入输出,和合约毫无障碍地对话,这种“面向远程接口编程”的思想,很类似WSDL、IDL、ACE、ProtoBuffer和gRPC等经典软件设计。

事实上,整个SDK中最繁琐的是ABI编解码部分,为了兼容EVM,FISCO BCOS在交易处理时沿用了ABI编码,以及兼容RLP协议。

ABI、RLP制定了严格的规范,对基础数据类型、数组和变长数据、函数方法、参数列表等都有特定的编解码方式,否则组件之间无法通信,数据无法解析,虚拟机“不认识”所输入的交易,则不能执行合约。

如果自行手写这里的编解码,即使是熟手也得花不少时间,还要能保证测试通过、保持版本兼容,所幸github上已经有eth-abi、eth-utils、rlp等一系列开源项目(多为MIT宽松许可协议),可以引入这些项目且根据具体的需要进行修订(保留原作者声明和版权开源许可),能节约不少工作量,向这些项目作者们致谢,开源就是爽!

交易数据结构相关

在搞定了基础编解码之外,还需要实现 FISCO BCOS交易结构,重点注意支持并行处理交易增加的randomid、blocklimit字段,为支持群组特性增加的fiscoChainId和groupId字段,在交易的receipt里增加的交易output等。

其中,交易的blocklimit定义为“交易生命周期,该交易最晚被处理的块高”,SDK需要定期到链上查询当前块高,以确定当前交易的生命周期(比如,此交易允许在后续一百个区块内被处理)。

对于开发者来说,清晰理解交易的输入(tx.input)、交易回执(tx.receipt)、交易输出(tx.output)是非常重要的。

_images/IMG_4958.JPG

交易调用合约里的某一个方法时,首先将方法名字和参数类型列表组合,如**’set(string,uint256,address)’**,对这一段文本进行Keccak-256 (SHA-3)计算,并截取前4个字节做为“方法签名”(signature),然后对传入的参数,根据类型定义依次进行ABI编码,并和”方法签名”拼接一串二进制数据,做为交易的输入数据。

和交易结构体的其他字段(from、to、groupid、randomid等)一起再进行RLP编码,并用帐户私钥进行签名,得到一段二进制请求数据,由sendRawTransaction发往节点,节点收到后,立刻返回交易Hash给到客户端。

交易在链上被网络共识确认,处理完成后,通过getTransactionReceipt接口(传入之前获得的交易Hash),可以获得交易处理的详细结果。

_images/IMG_4959.JPG

在交易回执中,以下几个字段尤为关键:

1. contractAddress

仅在部署合约交易时有效,表示新合约的地址。

2. output

对应方法的return值,可用于判断业务逻辑的最终处理结果(取决于合约的写法)。

3. Logs

如果在合约代码里,写了一些Event log, 则receipt的logs字段里可以解码出详细的信息。Event log可用于帮助客户端监听、跟踪交易的处理结果,甚至可以帮助开发者调试合约执行过程,相当于在合约里打调试日志。当然,在合约正式发布时,应清除调试的Event log,只保留必要的log,避免冗余信息存到链上。

Python-SDK客户端里内置了解析“方法签名”(根据4字节的signature,找到对应的方法定义)、交易input/output、receipt.logs等字段的方法。

在使用控制台命令行时,只要是查询交易和回执的指令,在命令行后面附带合约名(前提是使用者知道这个交易调用的是什么合约),也可以自动解析出相关的数据来,例如:./console.py getTransactionReceipt 0x79b98dbb56d2eea289f756e212d5b6e5c08960beaa8ea8331740fdcfaa8dcab1 SimpleInfo,最后这个“SimpleInfo”为可选合约名,不需要带后缀,要求在contracts/目录下有SimpleInfo.sol文件。

这个贴心小设计,可以帮助开发者直观探秘区块链交易的脉络,对各种信息一目了然,不会迷失在天书一样的十六进制字符海洋里。

网络协议

最后聊聊FISCO BCOS的两种网络协议:JSON RPC和Channel长连接。

_images/IMG_4960.PNG

JSON RPC连接没有证书验证和通信加密,建议在本身安全可信的环境里使用,比如本机或内网,一般用于运维管理和统计分析场合。

JSON RPC的格式相当简单通用,各种语言库都内置了JSON编解码以及HTTP请求协议实现,一般不需要自行开发,甚至可以采用curl、telnet等工具进行收发,如:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getBlockNumber","params":[1],"id":1}' http://127.0.0.1:8545 |jq
// Result
{    "id": 1,    "jsonrpc": "2.0",    "result": "0x1"    }

Channel协议是FISCO BCOS独有的协议,Channel协议的特点是安全高效,支持双向实时通信,可用于远程调用乃至公网的通信。

如果使用Channel长连接方式,则需要从区块链节点上获取SDK证书,放置到SDK项目的对应路径下,证书详见文末。

这个协议的数据包格式示意图如下,是一种TLV(Tag/Length/Value)风格的扩展实现:

_images/IMG_4961.JPG

格式说明:

  1. 所有整形数编码都是网络序,大端(Big endian);
  2. Length实际上包含了从第一个字段到最后一个字段(data)的整个数据包的长度;
  3. 包头(Length+Type+Seq+Result)为定长,为(4+2+32+4) = 42字节;
  4. 数据体的实际长度根据具体内容而变,字节数为Length-42字节。

Channel长连接通信和数据收发的要点如下:

    1. 采用TLSv1.2安全传输,SDK和节点之间需要加载证书,用证书握手、验证后才能建立长连接。
    1. 长连接用心跳包维护,需要定期发起心跳包。
    1. 数据按包为单位,编码成流数据传输,那么在收发数据时,需要持续从socket流里获取数据,按照数据包的格式,判断长度是否合法,数据是否收全,是否能正确的解析,对“部分收取”的数据,要保留在接受缓冲区里,待收取完成后再进行解析,不能丢弃,否则可能导致解析错误。

_images/IMG_4962.JPG

    1. Channel协议支持双向通信,SDK可以主动请求节点,节点也可能往SDK推送消息,如区块链系统通知、数据更新通知、AMOP跨机构消息等。
    1. 设计异步的、队列化、回调式消息处理机制,根据消息的序列号、指令类型、状态码等维度,正确处理消息。Python-SDK用了多线程以及Promise库,以尽量高速优雅地处理各种消息。

对socke流数据编程有一定经验的开发者,理解这个协议和实现它并不会很难。对Channel协议实现,数据包解析参见client/channelpack.py,通信和数据收发参见client/channelhandler.py。

总结

Python-SDK的开发始于今年6月中旬,写出第一个可用版本只花了一个星期,然后雕琢用户交互细节,以及进行代码优化、文档完善,并进行多轮测试保证质量,团队其他同学实现Nodejs版本SDK的用时也差不多。 总的来说,在有一些基础代码参考的前提下,开发一个FISCO BCOS 特定语言版本SDK,还是挺敏捷写意的事情,一点儿也不难,Just for fun。 在各语言版本SDK开发和迭代过程中,FISCO BCOS团队和社区开发者一直保持沟通交流,纳入优质pull request,在体验中持续优化。

欢迎社区开发者根据自身使用场景的实际情况,继续完善现有SDK,或贡献更多语言类型的FISCO BCOS SDK,帮助更多开发者顺畅地走在区块链之路上。 最后,感谢杰哥、安总、小白、wheat等同学,以及多位社区开发者对Python-SDK的重要贡献。


其他工具

FISCO BCOS 交易解析工具指南

作者:廖飞强|FISCO BCOS 核心开发者

本文将介绍 FISCO BCOS的交易解析工具,帮助开发者简单快捷地解析交易和交易回执中的input、output、 logs字段,助力区块链应用开发。

社区用户经常询问:FISCO BCOS的智能合约支持发送交易后,直接获取返回值吗?交易和交易回执中的input、output和logs字段存的是什么,挺好奇,可解吗?怎么解?

现在,让FISCO BCOS交易解析工具来揭开这神秘的面纱!

What: 解析什么?

解析工具解析的是交易和交易回执中的三个重要字段,分别是input、output和logs。这个三个字段分别代表什么,与智能合约有什么关系?下面请出一张图进行分析。

_images/IMG_4963.PNG

图中为了突出重点,只显示TableTest.sol合约中涉及到交易解析字段的关键代码(TableTest.sol合约是控制台提供的示例合约,用于创建示例表t_test,并提供增删改查方法。完整合约代码可以到控制台目录contracts/solidity/下查阅,或直接通过文档查阅,请参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/smart_contract.html#crud)。

交易和交易回执字段,同样只突出要解析的input、output和logs字段,其他字段省略。其中交易信息包含input字段,交易回执信息包含input、output和logs字段**(注:FISCO BCOS 2.0.0及以上版本返回的交易回执包含input字段)。

从图中可以看到,蓝色部分是insert方法的签名,方法签名部分和调用该方法传入的参数,将编码到交易和交易回执的input字段(十六进制字符串)。

绿色部分是方法的返回值,这部分将编码到交易回执的output字段(十六进制字符串)。这里可以解答用户的一个疑问,即FISCO BCOS的智能合约支持发送交易后获取返回值,该返回值将会编码保存在交易回执的output字段,并且利用交易解析工具可以解析返回值

橙色部分是方法调用的event,可以记录event log信息,这部分将编码到交易回执的logs字段(其中address为调用的合约地址,data是event log数据的十六进制编码,topic是event签名的十六进制编码)。

由此可知,合约方法的输入,输出以及event log均编码到了交易和交易回执对应的input、output和logs字段。要知道一个交易或交易回执是调用了合约的哪个方法,输入、输出和event log分别是什么数据,只要解析这个三个字段即可,这正是交易解析工具要解决的问题!

How: 如何使用?

交易解析是web3sdk 2.0.4及以上版本提供的功能。其使用非常简单,三步曲如下:

step 1: 引入web3sdk 2.0.5依赖

Gradle 项目:

compile (‘org.fisco-bcos:web3sdk:2.0.5’)

Maven 项目:

org.fisco-bcos

web3sdk

2.0.5

step 2: 创建交易解析对象TransactionDecoder

使用TransactionDecoderFactory工厂类创建交易解析对象TransactionDecoder,提供两种方式:

  1. TransactionDecoder buildTransactionDecoder(String abi, String bin),传入的参数分别是合约的abi和bin字符串(bin字符串暂不使用,可以直接传入空字符串)。
  2. TransactionDecoder buildTransactionDecoder(String contractName),传入合约名称。需要在应用的根目录下创建solidity目录,将交易相关的合约放在solidity目录,通过指定合约名获取交易解析对象。

注意:创建交易解析对象前,请确定解析该交易对应的合约(即该交易是由调用该合约产生的),可以直接提供solidity合约或者用户自行编译,然后传入合约的abi,两种方式均可创建交易解析对象。

step 3: 调用交易解析对象进行解析任务

TransactionDecoder对input,output和logs的解析结果均分别提供返回java对象和json字符串(java对象的json字符串形式)的方法。详细设计文档请参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/sdk/sdk.html#id11。

java对象方便服务端处理数据,json字符串方便客户端处理数据。

交易解析对象的方法列表如下:

解析input InputAndOutputResult decodeInputReturnObject(String input)
String decodeInputReturnJson(String input)
解析output InputAndOutputResult decodeOutputReturnObject(String input, String output)
String decodeOutputReturnJson(String input, String output)
解析logs Map>> decodeEventReturnObject(List logList)
String decodeEventReturnJson(List logList)

其中InputAndOutputResult类,ResultEntity类和EventResultEntity类的结构如下:

public class InputAndOutputResult {
   private String function;             // 方法签名
   private String methodID;             // 方法签名编码的字符串,用于方法选择器
   private List<ResultEntity> result;   // 数据列表
 }
public class ResultEntity {
   private String name;   // 字段名称
   private String type;   // 字段类型
   private Object data;   // 字段值
 }
public class EventResultEntity extends ResultEntity {
   private boolean indexed;  // indexed标志位,true表示event字段使用了indexed关键字修饰
 }

根据交易对象,可以获取其input字段;根据交易回执对象,可以获取其input、output和logs字段。调用交易解析对象对应的方法即可解析相关字段。

注意:如果解析FISCO BCOS 2.0.0之前的版本(即rc1、rc2、rc3版本)返回的交易回执中的output字段,因为解析output字段的方法需要传入input字段,但交易回执中缺少input字段,则可以根据交易回执中的hash字段,查询一次web3j对象的getTransactionByHash方法获取交易对象,然后从交易对象获取input字段,即可解析output字段。

解析示例:

下面示例解析调用TableTest合约的insert方法,输入参数为(fruit, 1 ,apple),返回值为1(代表新增1条记录)时产生的交易和交易回执。解析其input、outout和logs字段,其解析结果如下:

input java对象:[function=insert(string,int256,string), methodID=0xebf3b24f, result=[ResultEntity [name=name, type=string, data=fruit], ResultEntity [name=item_id, type=int256, data=1], ResultEntity [name=item_name, type=string, data=apple]]]
json字符串:{"function":"insert(string,int256,string)","methodID":"0xebf3b24f","result":[{"name":"name","type":"string","data":"fruit"},{"name":"item_id","type":"int256","data":1},{"name":"item_name","type":"string","data":"apple"}]}
output java对象:[function=insert(string,int256,string), methodID=0xebf3b24f, result=[ResultEntity [name=, type=int256, data=1]]]
json字符串:{"function":"insert(string,int256,string)","methodID":"0xebf3b24f","result":[{"name":"","type":"int256","data":1}]}
logs java对象:{InsertResult(int256)=[[EventResultEntity [name=count, type=int256, data=1, indexed=false]]]}
json字符串:{"InsertResult(int256)":[[{"name":"count","type":"int256","data":1,"indexed":false}]]}

由解析结果可知,根据TableTest.sol合约的abi和交易回执中的input、output以及logs字段,通过交易解析工具可以解析出其调用的合约方法名、参数类型、参数值、返回类型、返回值以及event log数据。这就是我们期待的交易解析效果!

Where: 使用场景在哪里?

是英雄,一定有用武之地!交易解析工具入场的地方包括如下场景:

  • 控制台:控制台1.0.4版本已使用交易解析工具解析查询的交易、交易回执以及调用合约时的相关字段。具体用法
  • 区块链浏览器、WeBASE管理平台:用于解码交易和交易回执中的字段,便于交易详情展示。
  • 基于web3sdk的区块链应用:比较重要的意义是用于获取合约方法的返回值,以往一般对于发送交易的方法,习惯用event记录数据,而方法的返回值处于闲置状态。现在可以使用返回值,并利用交易解析工具解析返回值,助力业务开发。

总之,哪里需要交易字段解析,哪里就可以调用交易解析工具!

压测工具

性能压测工具Caliper在FISCO BCOS平台中的实践

作者:李陈希|FISCO BCOS 核心开发者

Hyperledger Caliper在发布的0.2.0版本中,正式宣布支持FISCO BCOS平台

关于如何使用Caliper对FISCO BCOS进行压测,开发小哥已经呕心沥血对如何部署Caliper及如何自定义测试用例进行了全面总结,欢迎猛戳FISCO BCOS官方文档学习如何使用:

本文将深入解析Caliper,以便大家更好使用Caliper压测FISCO BCOS。

为何要适配Caliper?

对于区块链开发者和用户而言,性能是评价一个区块链平台重要的考量条件。一直以来,FISCO BCOS都是通过Java SDK中自带的压力测试程序及压测脚本来对FISCO BCOS进行持续的性能追踪。

虽然这些压测手段已经完全能够胜任FISCO BCOS底层开发人员评估性能的需求,但是当压测需求从底层开发人员向需要对自定义的合约及场景进行测试的外部用户延伸时,传统的压测方法则会有些力不从心,需要改善的地方主要集中在以下几点。

扩展测试场景的方式需要精简

目前压测程序直接支持的测试场景是具有代表性的转账场景及增删改查数据表的模拟,如果用户想对自定义的合约及场景进行测试,则需要按照模板自行编写测试程序。 SDK自身已经提供了丰富的API来帮助用户编写这类程序,但是仍然需要用户自己处理合约编译及API转换、压力线程池等细节。 尽管一个用户可能只会有一个测试需求点,但是千人千面,当不同用户的需求累积起来时,重复的工作量就变得相当可观。

因此,我们希望有一套测试框架帮助我们处理这些琐碎的细节,从而使我们的精力能更加聚焦在构造压测场景本身

性能指标的定义需要统一

对于性能指标如何计算的问题,FISCO BCOS内部已经形成了一套规范的计算方法,但是当用户自行编写测试程序时,由于测试程序本身的灵活性,用户可以自己定义性能指标该如何计算。 以交易处理能力指标TPS为例,有的用户可能会觉得交易的本地签名时间不应该归到远端节点处理交易的耗时中,也有用户可能会认为总交易量/总时间的计算方式不够精确,而倾向于使用在压测过程中多次采样最后计算平均值的方式。 可以看出,灵活性带来的负面效应是导致各方缺少性能的统一度量衡。 因此,我们希望能有一套自带标准性能指标定义的测试体系,且最好用户无法直接干预性能指标的计算流程

结果的展示方式需要优化

基于命令行的压测程序在编写时更多是面向FISCO BCOS的底层开发人员,因此在使用方式上也显得较为『底层』。当外部用户想使用Java SDK中自带的压力测试程进行测试时,可能会看到如下测试结果:

_images/IMG_4964.JPG

尽管理解每项统计数据的含义并不难,但是如果是向外界进行展示和介绍的话,仍然会显得有一些笨拙和不便。 因此,我们希望有一个测试工具能在测试完成后输出直观的测试报告,方便用户理解和传播 。 秉承『不要重复造轮子』的精神,我们将目光投向了开源社区,以期能有现成的工具解决FISCO BCOS测试工具的痛点。在经过充分调研后,我们发现了Hyperledger Caliper项目。

Caliper是一个通用区块链性能测试工具。”Caliper”一词的原意就是标尺,Caliper旨在为区块链平台的测试提供一个公共的基准线。Caliper完全开源,因此用户不需要担心由于测试工具不开源导致无法对压测结果进行验证的问题。 同时,Hyperledger项目下设了性能及可扩展性工作组(PWSG),专门负责对各种性能指标(TPS、延迟、资源利用率等)进行形式化、规范化的定义,Caliper在设计也采用这一套性能指标体系并内嵌进了框架中。 Caliper能够方便地对接多种区块链平台并屏蔽了底层细节,用户只需要负责设计具体的测试流程,即可获取Caliper输出的可视化性能测试报告。可以看出,拥有这些特点的Caliper,能恰好满足FISCO BCOS对压测工具的需求。

FISCO BCOS对Caliper框架的适配工作随即展开,回顾来看,整体工作量并不算繁重,最主要的时间开销反倒是花在开发小哥因为不熟悉Node.js(Caliper主要使用Node.js进行开发)而学习了一段时间,这也从侧面也印证了Caliper易于集成的特点。

Caliper长啥样?

_images/IMG_4965.PNG

Caliper的架构图如上图所示。在Caliper中,Caliper CLI负责为内部的Caliper Core(接口及核心层)提供方便易用的命令行工具。接口及核心层包含区块链适配接口、资源监控模块、性能分析模块及报告生成模块。

区块链适配API

包含诸如在后端区块链上部署智能合约、调用合约、从账本查询状态等操作的接口,这些接口主要由区块链支配器提供。每个区块链适配器使用相应的区块链SDK或RESTful API来实现这些接口,Caliper也正是通过这些适配器提供的接口实现将区块链系统集成进Caliper框架中,目前除FISCO BCOS外,Caliper还支持Fabric、Iroha等区块链系统。

资源监控模块

提供启动/停止监视器和获取后端区块链系统资源消耗状态的支持,资源监控的范围包括CPU、内存、网络IO等。目前Caliper提供两种监视器,一种是监视本地/远程docker容器,另一种则是监控本地进程。

性能分析模块

提供读取预定义性能统计信息(包括TPS、延迟、成功交易数等)和打印基准测试结果等操作的支持。在调用区块链适配接口时,每个交易的关键指标(如创建交易的时间、交易提交时间、交易返回结果等)都会被记录下来,并用于生成最终的预定义性能指标统计信息。

报告生成模块

主要负责对从性能分析模块获取到的性能数据进行美化加工,生成HTML格式测试报告。 Caliper的上层便是应用层,负责对区块链系统实施测试。每次测试都需要设置对应的测试配置文件以及定义后端区块链网络信息的测试参数。基于这些配置,Caliper便可以完成区块链系统的性能测试。 Caliper预置了一个默认的基准测试引擎以帮助测试人员快速理解框架并实施自己的测试,下一节将介绍如何使用基准测试引擎。当然,测试人员也可以不使用测试框架,直接使用区块链适配API完成自有区块链系统的测试。

测试流程

_images/IMG_4966.PNG

Caliper默认的基准测试引擎采用Master/Client模式来实施测试流程。

整个测试流程由Master进程负责驱动,包含以下三个阶段。

  • 准备阶段:在此阶段,Master进程使用区块链配置文件创建并初始化内部区块链对象,按照配置中指定的参数部署智能合约,并启动监控对象以监控后端区块链系统的资源消耗;
  • 测试阶段:在此阶段,Master进程根据配置文件执行测试,将根据定义的负载生成任务并将其分配给客户端子进程。最后将存储各个客户端返回的性能统计信息以供后续分析。
  • 报告阶段:分析每个测试轮次的所有Client进程的统计数据,并自动生成HTML格式报告。

Client进程主要负责与后端区块链系统进行具体的交互。在Local模式下,Master进程使用Node.js集群模块启动多个本地Client(子进程)来执行实际的测试工作。

由于Node.js本质上是单线程的,因此本地Client子进程集群用来提高Client在多核机器上的性能。在实际使用中,Client子进程的数量越多(在CPU核数能够支持的情况下),Caliper的交易发送及处理能力越高。

在此模式下,总工作负载被平均分配给子进程,每个子进程相当于区块链客户端,子进程拥有临时生成的上下文,可以独立地后端区块链系统交互。子进程上下文通常包含客户端的标识和加密信息,在测试结束后上下文将被自动释放,这些细节并不需要用户关心。

Client进程在第一轮测试时启动,并在完成所有测试后被销毁。

图中最右侧的用户自定义测试模块用于实现交易生成和上链的功能。通过这种方式,测试人员可以实现自己的测试逻辑并将其与基准测试引擎集成。测试模块主要实现3个函数,所有这些函数都应该返回一个Promise对象:

  • init:将在每个测试轮次开始时由Client调用。所需参数包括当前区块链对象、上下文以及从基准配置文件中读取的用户定义的参数。在该函数内可以保存区块链对象和上下文供以后使用,其他初始化工作也可在此处实现;
  • run:使用Caliper的区块链适配API处生成交易并将交易上链。Client将根据工作负载重复调用此函数;
  • end:用于在每轮测试结束时调用,任何结束时需要清理环境的工作都在此处执行。

Caliper使得压测FISCO BCOS的方式变得优雅,同时,FISCO BCOS在适配Caliper的过程中也对Caliper的一些bug和性能问题进行了修复和改进。

Caliper目前仍然在不断地进化中,后续还会陆续添加友好的GUI界面、分布式测试框架、Prometheus监控系统等功能,FISCO BCOS也会持续迭代优化测试工具,以满足用户性能测试的需求。

运维支持

FISCO BCOS权限控制一览

作者:张开翔|FISCO BCOS 首席架构师

作者语

在多方参与的联盟链上,各方分工协作的同时也要做到职责分明、各司其职。链的管理者没有必要“既当裁判又当运动员”的参与业务交易,只参与交易的用户也不必很操心智能合约的开发部署。同时,”DO分离”(开发和运维分离)已经是业界成熟的做法,越权操作会带来风险,最终可能破坏信誉和造成资产损失。

清晰、易用、周全的权限控制能力,无论是对于信息安全,还是为了完善联盟治理,都至关重要。

本期文章讲的就是FISCO BCOS权限控制这点事,作者从FISCO BCOS的权限分类、典型的联盟链角色设计、权限控制操作基本步骤等角度进行了解析。

FISCO BCOS的权限分类

FISCO BCOS在链刚建立起来的时候,为了方便快速地进行开发和体验,默认并没有做任何权限控制。但如果这条链是用来提供企业级服务,则从一开始就要设计权限控制的策略并落实之。 FISCO BCOS的权限分类:

_images/IMG_4967.PNG

1. 链管理员权限

分配权限的权限,如定义账户A作为链管理员,A可以为账户B、C、D分配权限;可以设置多个管理员,如果不设置管理员,任何账户都可以无差别修改各种权限。

2. 系统管理权限

目前包括4种:

  • 节点管理权限(增删共识节点或观察节点)
  • 系统参数修改权限
  • 修改CNS合约命名权限
  • 能否部署合约和建表的权限

其中部署合约和建表是“二合一”的控制项,在使用CRUD合约时,我们建议部署合约的时候一起把合约里用到的表建了(写在合约的构造函数里),否则接下来读写表的交易可能会遇到“缺表”错误。如果业务流程需要动态创建表,动态建表的权限也应该只分配给少数账户,否则链上可能会出现各种废表。

3. 用户表权限

以用户表为粒度,控制某些账户能否改写某个用户表,以避免用户表被他人意外修改,这个权限要依赖FISCO BCOS的CRUD合约写法。另外,读用户表不受权限控制;如果要进行数据的隐私控制,需要引入数据加密、零知识等技术。

4.合约接口权限

一个合约可以包括多个接口,由于合约里的逻辑和业务密切相关,接口粒度的权限控制由开发者实现,开发者可对msg.sender或 tx.orgin进行判断,决定是否允许本次调用继续处理。

FISCO BCOS的控制台提供了一系列控制权限的命令,可以很方便的为用户**授予(grant)、取消(revoke)、查看(list)**各种权限,可以参见控制台的文档。

联盟链中典型的权限管理角色设计

在联盟链里,不同的角色各司其职,分工协作,典型的角色设计可参照:

1. 链管理员

通常是参与链的多方共同选出一个委员会,一个或多个机构可获得管理员权限,以进行人员管理和权限分配。链管理员并不负责任节点管理,修改系统参数,部署合约等系统管理操作。

2. 系统管理员

指定的业务运营人员或系统运维人员,按需分配各种权限,负责日常的链上管理,包括节点增删,系统参数修改等。由链管理员根据大家约定的治理规则来分配权限,比如只允许指定的账户部署合约,则给他们设定合约部署权限,这样其他账户则不能随意部署合约了。

3.交易用户

用户向区块链发送业务交易请求,业务交易主要是调用合约和读写用户表,可以根据业务逻辑,结合用户表权限和合约接口权限来进行灵活的控制。

4. 监管方

给监管方分配哪些系统和用户表权限,可参考具体的监管规则,如监管方只读所有数据,则不用设定特殊的权限。

管理不同角色的帐户又是另一个复杂的问题,既要清晰区分,又要便于使用,还要保证帐号安全;万一帐号丢失后要支持找回,帐号泄漏后重置,以后会另文介绍。

权限控制操作基本步骤
step1

首先采用控制台grantPermissionManager命令设置一个或多个链管理员角色。 Tips:登陆控制台时,命令行里输入的是私钥明文,而使用grantPermissionManager命令输入的是“账户地址”,要注意区分。如:

用户私钥:3bed914595c159cbce70ec5fb6aff3d6797e0c5ee5a7a9224a21cae8932d84a4

对应地址:0xf1585b8d0e08a0a00fff662e24d67ba95a438256

那么登陆控制台的命令行是:

./start.sh 1 3bed914595c159cbce70ec5fb6aff3d6797e0c5ee5a7a9224a21cae8932d84a4

分配管理员权限的命令行是:

grantPermissionManager 0xf1585b8d0e08a0a00fff662e24d67ba95a438256

当这个账户得到了链管理员权限后,退出当前控制台或切到另一个终端窗口,用这个账户的私钥登陆一次,即可作为链管理员进行后续的操作了。

Tips:一定要记住管理员地址和私钥的对应关系,否则一旦设置管理员权限,只有管理员可以为其他账户分配权限,其他账户的设置操作都会报告没有权限。

step2

用链管理员账户登陆控制台,根据管理策略,依次给其他系统管理员账户分配节点管理权限,系统参数修改权限,CNS权限,部署合约和建表权限等。然后用一个有相应权限的系统管理员账户私钥登陆控制台,如有部署和建表权限的账号,以便进行下一步。

step3

开发者编写CRUD的合约时,一旦合约在链上创建了用户表,则可以采用控制台的grantUserTableManager命令,由有权限的系统管理员指定哪些账号可以创建表和增删改该表的数据,命令行是

grantUserTableManager   t_order  0xf1585b8d0e08a0a00fff662e24d67ba95a438256

授权0xf1585b8d0e08a0a00fff662e24d67ba95a438256这个账号可以操作t_order表,而其他账号不行,如要读写该表,继续添加权限,对一个表有读写权限的账号可以有多个。

step4

对solidity合约里的某个接口,可以参考这样的代码进行控制:

 		function testFunction() public returns(int256)
{
        require(msg.sender == tx.origin); //这一行的效果是禁止了合约调合约
         if(msg.sender != address(0x156dff526b422b17c4f576e6c0b243179eaa8407) )  //这里为示例,直接写了账户地址明文,实际上开发时可以灵活处理。
        { return -1;    }  //如果调用者和预设的有权限的调用者不同,则返回
    }

msg.sender是当前合约调用者的地址,可以是用户也可以是另一个合约(合约调合约时);tx.origin则是本次交易的真正发起用户,给交易签名的用户。

小结及参考资料

本文介绍FISCOBCOS在基础层面上提供的一些接口和能力,权限控制的合理性和周密程度最终会取决于使用者,关于不同链的场景化治理和安全控制,可以继续深入探讨,以得出最佳实践。


群友问答

@李翛然:请问几个问题:

1)管理员分配表权限,这个操作是不是在每个节点都可以进行?

2)创建角色,这个操作是不是每个节点都可以操作?

如果以上两个问题答案是肯定的,请问是不是只要有了一个节点的超级权限,就可以修改整个网络的数据?

@光路:是肯定的,建立链之前就先协商好链管理员角色由哪一个或几个账号担任,链建立后第一时间进行角色分配,具体可以参见FISCO BCOS权限控制相关文档。

感谢参与本次话题讨论的小伙伴们!开源社区,因你们更美!

如何参与社区开发协作

揭秘FISCO BCOS开源项目开发协作

作者:石翔|FISCO BCOS 核心开发者

作者语

从FISCO BCOS 2.0的第一个版本开发至今,在各位程序猿(媛)的猛烈输出下,FISCO BCOS的节点代码已经达到了11万多行,并且代码仍在快速迭代着。

如此大的代码输入量,对于开发流程来说,是一个巨大的考验。如何保证代码质量?如何做到井然有序呢?

本文将为大家揭秘提交PR的流程,看看在这个过程中,程序猿(媛)是如何进行开发协作的。

PR是什么?

每个人各自在本地开发自己的代码,当代码准备好后,会向FISCO BCOS主仓库提交一个“请求单”,请求主仓库拉取开发好的代码进行合入,此“请求单”就是PR(Pull Request,拉取代码请求)。

在PR中,其它开发者会Review代码,CI(持续集成工具)会对代码的规范和正确性进行初步的检查。当PR达到要求后,就可合入了。

当我们打开FISCO BCOS的PR列表,我们可以看到很多大家的PR记录。有经验老道的老司机(wheatli)在掌控全局,有曾经都想为他生猴子的廖老师(fqliao)在精益求精,有程序媛小姐姐(cyjseagull)在救死扶伤,还有初来乍到的爱猫人士(vita-dounai)在小试牛刀。

_images/IMG_4968.JPG

下面我们来看看程序媛小姐姐cyjseagull提的一个PR。这个PR处在Open的状态,表示正在审核中。她请求将开发好的代码合入到feature-2.3.0分支。JimmyShi22、wheatli等小哥哥正在Review,同意了,后面会打钩。

_images/IMG_4969.PNG

我们向下继续看这个PR,cyjseagull小姐姐的代码遭受到了小哥哥们的挑战,给出了一些Review的意见。共识模块和同步模块彼此独立,不会有依赖。她开发的节点树状拓扑逻辑TreeTopology.h应该放到更底层的模块中去。小姐姐愉快答应了。

通常来说,一个PR需要根据Review反复的修改后才能合入。

_images/IMG_4970.PNG

此外,PR还会被各种CI(集成测试)检查,cyjseagull小姐姐很稳,通过了所有的检查。但是,一个PR需要至少两个Review的人同意才能合入。

这个PR还缺一个人的同意,合入的按钮是灰的,不能点击。正常情况下,如果同意的人数足够,就可以点击合入按钮,代码合入到相关的分支上。

_images/IMG_4971.PNG

FISCO BCOS分支策略

在明白了PR的概念之后,我们有两个问题需要解决:

  • 多人同时开发,如何做到互不影响的情况下进行代码整合呢?
  • 合入的代码,还需要进一步的人工测试才能发布,测试在什么阶段介入,能够更有效地进行测试,同时不影响其它人的开发呢?

FISCO BCOS采用了经典的分支策略Gitflow去管理整个开发、测试和发布的流程,下面我们来看看Gitflow。

_images/IMG_4972.JPG

在Gitflow中,有5种类型的分支:master、develop、feature、release、hotfix。不同分支具有不同的功能。

FISCO BCOS代码的开发、测试和发布的阶段,则对应了上述的分支。

feature分支

FISCO BCOS的代码开发,是基于一个个特性(feature)的。

多个人同时进行开发,是基于自己的feature-xxx分支进行。在FISCO BCOS的主仓库上,我们可以看到有很多这些特性分支,每个特性分支归属于一个(或多个)程序猿(媛)。

他们平时在自己本地的仓库写代码,写好后以PR的形式提交到各自的feature-xxx分支上。上节中cyjseagull小姐姐提交的PR正是处在这个状态。

当feature-xxx分支开发好后,测试介入,进行“特性测试”。测试期间修的bug同样以PR的方式提交到此feature分支。特性测试的目的,是为了保证此特性的功能正确。

_images/IMG_4973.PNG

develop分支

develop分支(在FISCO BCOS中为dev),是用来合并多个feature分支的分支。

当“特性测试”测试通过后,feature-xxx分支就可合入dev分支了。

合并过程同样以PR的方式进行。多个feature同时合入dev分支时,需按照顺序依次合入。先合入dev的feature分支会给后合入的feature分支带来冲突。后合入的feature分支需要解决冲突后再合入dev。

release分支

当我们积累了一些开发好的特性后,需要将代码发布出去。此时从dev分支拉取出release-xxx分支,进行“发布测试”。

在feature分支合并入dev分支时,只能保证特性的完整,但不能保证特性与特性间的影响。当多个特性合并入dev分支后,需要在发布前做最后的总体测试。此时发现的bug直接以PR的方式合入release分支。

通过此种方式,release分支在进行测试时,不会影响到其它开发者在feature分支上的开发,也不影响feature往dev分支的合入。

master分支

master是主分支,向外提供可用的代码。

当“发布测试”完成后,即可将releaes分支合入master分支。

release分支同样以PR的方式合入master分支。同时,release分支也会合入dev分支。当合入master分支后,根据新的master分支打tag。发布完成!最终发布的版本以tag为准,代码可直接从tag上下载。

_images/IMG_4974.JPG

hotfix分支

当代码发布后,若出现改动较小的bug或进行小的优化,从master分支上拉出一个hotfix-xxx分支,在其上快速修复。

修复并测试完成后,同时合入master和dev分支。master打小版本的tag。若出现改动范围较大的bug,根据目前项目的发布状态,在feature或release分支上进行修复。

FISCO BCOS分支策略

了解了PR与分支策略,接下来就到提PR的阶段。

  • 如果你只是想修改小bug,进行小优化,那么可以直接PR到master分支。
  • 如果你是要针对某一特性进行开发,可以与社区沟通方案后,从dev上拉取出自己的feature-xxx分支,就可以开始撸了!然后用PR来提交代码。为了避免Review时大改,需尽量多的提PR来表明自己的思路。PR不要求功能完全可用,只需要最后开发完成时feature分支可用即可。

提PR的具体步骤也可参考《如何为FISCO BCOS做贡献》

欢迎大家积极提PR,一起让FISCO BCOS变得更好。


FISCO BCOS应用实践

区块链已革新哪些行业?附应用案例下载

据新华社10月25日晚消息,中共中央政治局10月24日下午就区块链技术发展现状和趋势进行第十八次集体学习。中共中央总书记习近平在主持学习时强调,区块链技术的集成应用在新的技术革新和产业变革中起着重要作用。我们要把区块链作为核心技术自主创新的重要突破口,明确主攻方向,加大投入力度,着力攻克一批关键核心技术,加快推动区块链技术和产业创新发展。

报道指出,区块链技术应用已延伸到数字金融、物联网、智能制造、供应链管理、数字资产交易等多个领域。

作为集实践大成的开源底层平台,FISCO BCOS自2017年开源以来,已汇集上万名社区成员、超1000家企业及机构共同参与区块链产业生态构建,在各行各业广泛落地成熟应用案例,包括政务、金融、公益、医疗、教育、交通、版权、商品溯源、供应链、招聘、农业、社交、游戏等。

我们甄选其中较为典型的应用场景出品区块链应用案例精编,带您迅速了解区块链的行业应用现状和前景。

[FISCO BCOS开源社区】公众号后台回复“案例”,可下载高清全本。

《FISCO BCOS案例精编》全文

_images/IMG_5117.JPG

_images/IMG_5118.PNG

_images/IMG_5119.PNG

_images/IMG_5120.PNG

_images/IMG_5121.PNG

_images/IMG_5122.PNG

_images/IMG_5123.PNG

_images/IMG_5124.PNG

_images/IMG_5125.PNG

_images/IMG_5126.PNG

_images/IMG_5127.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5128.PNG

_images/IMG_5129.PNG

_images/IMG_5130.PNG

_images/IMG_5131.PNG

_images/IMG_5132.PNG

_images/IMG_5133.PNG

_images/IMG_5134.PNG

_images/IMG_5135.PNG

_images/IMG_5136.PNG

_images/IMG_5137.PNG

_images/IMG_5138.PNG

_images/IMG_5139.PNG

_images/IMG_5140.PNG

_images/IMG_5141.PNG

_images/IMG_5142.PNG

_images/IMG_5143.PNG

_images/IMG_5144.PNG

_images/IMG_5145.PNG

_images/IMG_5146.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5147.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5148.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5149.PNG

_images/IMG_5150.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5151.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5152.PNG

_images/IMG_5153.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5154.PNG

_images/IMG_5155.PNG

_images/IMG_5156.PNG

_images/IMG_5157.PNG

docs/articles/6_application/../../../images/articles/industry_application_case/IMG_5158.PNG

_images/IMG_5159.PNG

_images/IMG_5160.PNG

_images/IMG_5161.PNG

_images/IMG_5162.PNG

_images/IMG_5163.PNG

_images/IMG_5164.PNG

_images/IMG_5165.PNG

_images/IMG_5166.PNG

_images/IMG_5167.PNG

_images/IMG_5168.PNG

_images/IMG_5169.PNG

_images/IMG_5170.PNG

_images/IMG_5171.PNG

_images/IMG_5172.PNG

_images/IMG_5173.PNG

案例征集

如果您有应用案例或应用计划,敬请通过公众号联系我们,FISCO BCOS开源社区将给予资源、技术等全方位支持。

关于我们

FISCO BCOS是首个由国内企业主导研发、对外开源、安全可控的企业级金融联盟链底层平台,为各行各业开展区块链应用提供可靠、免费的基础设施,源码在github完全开放:https://github.com/fisco-bcos,欢迎下载体验。

平台由金融区块链合作联盟(深圳)(简称:金链盟)成立的开源工作组协作打造,于2017年12月正式对外开源,工作组成员包括博彦科技、华为、深证通、神州数码、四方精创、腾讯、微众银行、亦笔科技和越秀金科等金链盟成员机构。

FISCO BCOS助力深圳破解网贷良性退出难题

读特记者 沈勇

FISCO BCOS区块链底层开源平台在金融领域再添重磅应用!落地网贷机构投票系统,有效解决网贷平台良性退出过程中涉众决策难的问题。该系统今年上半年在深圳率先落地,有望推广至全国。

以下是深圳成功应用的报道,内容整理自读特新闻。原标题:《深圳成功应用区块链技术破解网贷良性退出难题》


虽然区块链技术热度方兴,而深圳已经成功应用于网贷机构良性退出工作中。记者昨日从深圳地方金融监管局获悉,今年上半年,深圳率先全国推出以区块链技术为核心的P2P网贷机构良性退出统一投票表决系统,至今已在全市20家网贷机构投入应用,导入出借人8万余人,待收本金102亿余元。

投票系统让万千出借人有了“表决权”

面对网贷机构退出的兑付方案,普通出借人如何表达自己的意见和诉求?今年上半年,在深圳市、区两级地方金融监管部门的指导和推动下,深圳率先全国推出P2P网贷机构良性退出统一投票表决系统(下称“投票系统”)。

该系统是《深圳市网络借贷信息中介机构良性退出指引》(下称《良性退出指引》)的配套技术支持平台,旨在有效解决平台良性退出过程中涉众决策难的问题,为网贷良性退出工作打响了“开山炮”。

“P2P网贷机构良性退出面临的首要难题是涉众决策难。”深圳市互联网金融协会秘书长曾光介绍,网贷机构管理的资产权属属于出借人,但出借人数量庞大,分布在全国各地,结构复杂诉求不一,平台与出借人沟通成本高、出借人对平台不信任。

为提供涉众决策统一规则,深圳市互联网金融协会率先推出《良性退出指引》,创新设置出借人监督委员会、“三分之二加双过半”的表决规则等一系列良性退出规则要求。

在《良性退出指引》基础上,再度推出“网贷机构投票系统”,使涉及出借人重大利益事项的投票表决成为可能。“在以前的出借人表决实际操作中,往往通过微信群、QQ群、社交网站的投票软件,少数代表的现场会等形式进行投票,其合法性和公正性倍受质疑,执行过程也常有反复。”曾光介绍,在此情况下,新的投票系统为出借人提供投票表决服务,致力于实现公平、公开、透明的投票表决环境。出借人监督委员会负责对相关事项进行审核监督,网贷机构授权协会提供信息披露及投票表决服务。

记者了解到,目前深圳市已经有20家网贷机构使用投票系统。曾光介绍,“使用投票系统的大部分网贷机构都按照《退出指引》要求,积极推动了第一次重大事项投票表决,确认退出流程,选举监委会成员并对清退组和监委会授权。”另外部分平台已通过投票系统完成第二轮投票表决事项,各平台的良性退出工作进展有序。

区块链技术好钢用在刀刃上

宝剑锋从磨砺出,区块链技术的应用,正是“网贷机构投票系统”破解涉众决策难的刀锋。据悉,投票系统是基于FISCO BCOS区块链开源底层平台搭建,同时引入人工智能、生物识别、数字认证等多项国内领先技术,拥有完全自主知识产权。

FISCO BCOS区块链开源底层平台由金融区块链合作联盟(深圳)(简称“金链盟”)开源工作组牵头推出。FISCO BCOS代码已于2017年完全开源并持续迭代更新,目前其开源社区拥有上万名社区成员,超过500家企业参与,成功落地超过60个生产环境应用案例,已经发展成为最大最活跃的国产联盟链开源生态圈。

网贷机构投票系统在设计中成功运用了该平台的技术成果,并融合多项其他前沿技术,集中打造了纯线上、智能化、政府和金融机构共同参与、具备司法仲裁效力的多功能投票决策系统。

首先,投票系统引入智能语音交互机器人和线上人脸识别技术,搭建投票人身份标识链,记录投票人身份标识。一方面,通过利用机器人与出借人进行语音交互,确认债权信息并留存语音记录;另一方面,利用人脸识别和数字证书认证方式确认出借人身份,并将投票人的身份标识记载上区块链,既可防止投票过程中存在他人伪冒的情况,又避免了将投票人的全部敏感信息上链,在保护用户隐私的同时,确保了投票结果的公信力。

其次,投票系统充分利用区块链技术防篡改、去中心化的技术优势,搭建司法存证仲裁链。通过将所有投票记录和结果的数据摘要在链上进行记录,同样在保障用户隐私情况下,使得每笔投票结果准确被记录且无法被篡改;同时,每笔存证均有权威仲裁机构参与链上司法存证,从法律角度确保了投票结果的证据保存,最终保障了全过程投票数据的司法有效和真实可信。

最后,投票系统还搭建了独立的投票验证服务,任何人都可以通过此服务,来验证投票系统的数据和区块链上存证的数据,是否一致,从而从每个人的角度监督整个投票过程,增强投票结果的公信力。

“人民版权”存证原创新闻超200万篇 | FISCO BCOS应用案例

FISCO BCOS开源社区公众号回复【案例】,下载FISCO BCOS最新案例精编

2019年7月,基于FISCO BCOS区块链底层技术研发的一站式版权管理平台“人民版权”正式上线。至2020年第一季度,该平台在版权原创存证、侵权监测、司法维权方面取得一系列成果:

“人民版权”已为超过200万篇新闻稿件进行版权存证;可自动识别的新闻数超过1亿条,相当于3年的新闻总量;全网监测数据量日均近300万条, 全年总监测量超过10亿条。

目前,“人民版权”已经通过国家网信办境内区块链信息服务备案。未来,平台将持续通过区块链、人工智能等创新技术与知识产权保护深度融合,对抗侵权问题,助力构建版权保护新生态。

区块链助力版权保护 存证新闻报道200万篇

近年来,近六成原创媒体作者遭遇过内容侵权,原创稿件被侵权频次高达每篇作品3.64次。网络的虚拟性使得侵权行为难以被及时发现和识别;网络的即时性和裂变传播,令侵权的发展更为迅速,为版权溯源、取证维权也带来了困难。

“人民版权”利用区块链技术的完整性、可追溯性、不可篡改性等特点,综合应用基于区块链的分布式身份解决方案WeIdentity,实现了数字作品链上信息追溯和全网数据监测在内的版权保护全流程管理。

自上线以来,“人民版权”已为2,030,890篇原创新闻报道进行了版权存证保护。全网实时对比监测稿件量达日均2,871,903篇,全年总监测量超过10亿条。识别采集媒体总量达9,988,781家,覆盖了将近全部的电子报刊、网络媒体以及主流客户端。海量信息的实时监测,为快速识别侵权转载并取证上链提供了前提。

首创梯度化司法服务 低成本完成侵权诉讼

“人民版权”首创“梯度化司法综合服务”。面向数字版权维权需求,“人民版权”提供了一套创新司法服务,形成了包含互联网法院、公证、司法鉴定、律师、仲裁为一体的权威司法梯度化服务体系,为版权保护在数权时代的司法维权奠定坚实基础。

此外,与传统的维权模式相比,“人民版权”采用电子证据管理、线上调解、互联网法院诉讼等新模式,以智能电子化代替传统人工模式降低成本,用传统版权服务1/2的价格便可完成确权、维权全流程,帮助用户以最小成本、最高效率维权,提升版权维权的司法效率。

今年初,“人民版权”还正式接入了北京互联网法院“天平链”电子证据平台,成为首个实现版权存证、侵权监测、线上版权交易、司法维权全链条打通的媒体版权平台。

媒体年交易额可达百万 打造线上最大版权交易平台

“人民版权”平台将版权交易环节引入线上。版权交易中心利用区块链技术对内容生产传播全流程的记录,能够进一步帮助媒体实现在信息分发链条上的价值再分配。据悉,线上引入交易环节,媒体单位版权交易年均收益额预估可达七百万元,市场效益可观。

目前,“人民版权”平台已经完成北京歌华有线电视网络股份有限公司、山东数字出版传媒有限公司、微众银行等五大一级节点接入。2019年10月,“北京云·融媒体”成为第一家接入“人民版权”的省级融媒体平台。同时,人民在线/人民网舆情数据中心发起的“人民版权联盟”已有100多家党媒正在对接中。

“人民版权”运用区块链、大数据与AI 识别等创新技术,即将实现视频版权保护功能。目前,“人民版权”正在与西部国家版权交易中心、北方国家版权交易中心、中广电传媒有限公司等进行视频版权方面的合作,携手共促视频版权生态的良性发展。

文章来源:人民网

BSN首批“官方指定区块链应用”出炉,FISCO BCOS社区四个应用入选

历经一个多月的征集,BSN于近日公布了首批“官方指定区块链应用”名单,其中4个应用基于FISCO BCOS区块链底层研发,覆盖存证、防伪溯源、供应链管理等领域。本着“把最合适的区块链应用,展示给最需要的用户”的目的,BSN发展联盟开发者委员会根据应用准入机制对提交作品进行了审核及综合考量,本次优先选出9种分类的12个区块链应用,作为首批入选BSN的指定应用。

基于FISCO BCOS区块链底层研发的4个应用分别是:链动时代区块链存证服务系统、农业产业全过程溯源云平台区块链应用、惠运链、伊OS透明建造平台。

_images/IMG_5268.JPG

链动时代:区块链存证服务系统

链动时代区块链存证服务系统(以下简称“inBC存证系统”)基于BSN上的FISCO BCOS联盟链构建。inBC存证系统帮助用户基于API接口扩展已有业务系统,实现电子证据的存证保全、调用核验。可广泛应用到电子合同、版权保护、证件证书、防伪溯源、公益捐赠等场景和领域。

_images/IMG_5269.PNG

天演维真:农业产业全过程溯源云平台区块链应用

该平台充分结合物联网、区块链、云计算、大数据和地理信息等技术,在图形界面的软件环境下,实现信息采集、审核处理、控制执行、科学决策的“集成化、可视化、网络化和桌面化”。平台通过连接生产、加工、仓储、物流和消费各个环节,梳理统一的产品标准和管控流程,规范企业生产经营行为,提升企业质量管控能力,切实保障产品品质。同时,将这些信息同步开放给消费者,增强消费者认知,打造消费信任。

_images/IMG_5270.PNG

目前,苏州市阳澄湖大闸蟹行业协会集体商标防伪追溯体系、赣南脐橙质量安全追溯示范项目等应用均使用该平台。

安链数据:惠运链

惠运链是安链科技在为物流无车承运平台、保险、银行等企业提供的物流保险供应链金融解决方案。在货运交易和运输物流管理业务场景下,提炼多方协同的单据和信息,通过区块链技术的应用,将物流公司、保险机构、金融机构等生态链节点接入联盟链中,优化资源利用率,提高物流行业整体协作效率,利用可信数据推动保险和金融机构与物流行业融合。

_images/IMG_5271.PNG

目前惠运链合作用户包括南京融贸通智慧物流科技有限公司、江苏新宁现代物流股份有限公司、太平洋保险、招商银行等。

建信筑和:伊OS透明建造平台

伊OS透明建造平台是深圳市建信筑和科技有限公司研发的一款基于区块链技术应用的建造行业全生命周期管理系统。该平台着力为工程项目构建完整的信用生态系统,运用区块链、大数据等前沿科技技术,协助甲方执行项目全生命周期的管理,让项目责任可追溯,项目管理透明化,让过程变得公平公正。目前平台已为中国雄安集团、深圳建科院未来大厦项目提供服务。

_images/IMG_5272.PNG

什么是“BSN官方指定应用”?

BSN发展联盟开发者委员会根据客户需求和业界产品的分布,划分了14个相对通用的区块链应用分类以及“其它”分类,共计15个应用分类。分类覆盖供应链管理、供应链金融、司法存证、电子合同、防伪溯源等方面。每个区块链应用分类下只引入3个有代表性的产品解决方案。方案经开发者委员会审核通过后,将作为BSN官方指定推荐的区块链应用,在BSN的各个渠道进行广泛推荐。第二批指定应用也在紧锣密鼓的上线准备中,如您也想加入BSN官方指定应用,欢迎与社区小助手联系。

长虹启思实验室:制造业生产协同及质量溯源方案

作者:启思实验室

公众号对话框回复【启思实验室】下载完整方案

在近期众多大赛中,社区涌现出许多优质的区块链应用方案,这些方案让大家看到技术本身的蓬勃活力,也折射了区块链助力产业发展的无限潜力。应社区开发者要求,《超话区块链》将陆续邀请获奖的设计团队与大家分享、展示他们的方案,希望可以给大家日常开发提供一些启迪。本期邀请到四川长虹启思实验室与大家分享《基于智能合约的生产协同及质量溯源方案》,这套方案也是BSN第二次开发者大赛获奖项目。

为什么选择生产协同及质量溯源场景?

我们(下文“我们”均指启思实验室团队)的作品是在生产制造数据信息化的基础上,通过区块链技术,在企业内部建立可信的、合约化信息流,再用多级链打通制造企业供应链和产品下游的数据信息管理,建立多个节点,实现制造企业规则合约执行与数据可信管理,从而达到降低成本、提升效益的效果。

说到这,很多朋友可能会好奇,我们为什么选择这个场景切入?首先,启思实验室(由四川长虹电器股份有限公司信息安全实验室的区块链研究小组的成员组成)本身就专注于区块链技术应用研究,且与主流区块链基础技术提供方合作,提供基于区块链的智慧家庭和工业互联网解决方案。同时,在整个工业互联网和5G发展驱动下,传统制造能力面临一次较大程度的迭代升级,团队注意力一直集中在生产制造,力争从中找到突破点,利用区块链技术助力这一波生产制造升级。其次,在传统制造业中,当产品出现质量问题时,往往会面临物料零部件质量无法溯源、难以追责的难点。具体痛点如下图所示:图中A公司为产品公司和销售公司;B公司为A公司产品的委托生产方;供应商C、供应商D和供应商E为A公司的指定物料供应商。

_images/IMG_5273.PNG

经过分析和研究,我们梳理了传统制造业中的几大需求:

需求1:生产计划和物料匹配自动化

B公司根据A公司的订单与现有成品库产品库存,分析出差额后制定生产计划。根据产品的生产数量计算出所需物料。生产物料除去物料库的库存后直接向供应商反馈出所需物料的数量,并且预付款项。

需求2:物料供应商及时响应物料标识上链

供应商C、供应商D、供应商E收到物料需求订单,准备发货入库。同时供应商C、供应商D、供应商E的物料匹配唯一标识,信息上链。

需求3:财务可信清算

在订单下发、物料采购、物流运送、订单交货等环节建立财务自动按约定清算的机制。

需求4:售后质量溯源

售后基于物料唯一标识的可信责任确权和质量溯源,做到及时响应。

底层选型的几点考量

了解需求后,下一步就是技术实现,在底层选型上,团队架构师康红娟主要从以下几点进行考量:

  • 良好的实际可操作性。区块链应用层与实际业务贴合紧密,尤其是在合约层,重新开发部署业务型合约必然会带来反复调试。因此,底层的完备支持,对于项目成功非常重要。
  • 完善的服务层功能组件。用户层与链层的交互,必须经过中间服务层的“嫁接”,而这些“嫁接模块”具备通用性,除了基本的链层功能外,服务层通用组件的完备也至关重要。
  • 友好的开源氛围。对于技术极客而言,最大的快乐就是开源。

通过了解评估,团队最终选择了FISCO BCOS作为底层,主要是两方面的原因:

  • FISCO BCOS是安全可控的国产开源联盟链,能很好满足、贴合国内企业的使用需求;
  • 社区活跃度高,应用场景丰富,对于开发者的技术支持响应及时。

除此之外,FISCO BCOS还具备版本迭代及时、性能强劲、服务中间件丰富等优势。

智能合约解决方案

综上所述,我们从实际业务需求出发,结合区块链技术优势,构建“基础层、核心层、服务层、用户层”等四个关键层级,覆盖核心数据库、业务型合约、数据解析、消息解析、用户管理、业务管理等功能,构建工业互联网领域的订单式生产协同垂直解决方案。

本文我们重点分享其中的智能合约方案,方案如下图。这个方案里包括了产品合约、结算合约、生产合约、备货合约和授权合约,下面将对这几个合约逐一展开介绍。

_images/IMG_5274.JPG

产品合约

本合约用于完成产品生产过程中产品注册及产品所有权变更、产品溯源。

步骤如下:

  • 完成对相关合约的关系设置;
  • 合约的admin向某些address授权成为product生产商;
  • product生产商调用updateProductPrice对产品价格进行设定;
  • customer需要向生产商下产品生产订单前会先getProductPrice来比对价格的高低,然后通过payment合约向生产商下产品生产订单;
  • 生产商通过payment合约获取到订单后,进行产品生产;
  • 在生产产品前,生产商会通过Material合约来检查自己的产品原料储备是否充足,如果充足就消耗原料进行产品的生产注册registerProduct上链;
  • 产品生产商进行交付,customer确认收到货品,确认订单,完成资金及产品所有权的变更。
// 厂商msg.sender更新自己productType产品的价格
function updateProductPrice(uint256 productType, uint256 newPrice) public
​
// 获取厂商 _to的productType的价格
function getProductPrice(address _to, uint256 productType) public view returns(uint256 price)
​
// 设置供应商的原材料合约
function setMaterialContract(address _materialContract) public onlyOwner
​
// 设置支付合约
function setPaymentContract(address _paymentContract) public onlyOwner
​
// 产品生产商设置自己产品的批次号,ID,所用材料批次等信息
function registerProduct(uint256 productType, uint256 id, uint256 batchNumber, uint256[] memory materialBatches) public
​
// 更换产品所有权,交付产品
function transferProducts(address from, address to, uint256 productType, uint256 count) public
​
// 获取产品详情
function details(uint256 id) public view returns(Product memory)
​
// 获取msg.sender所拥有的产品数组
function getMyProducts(uint256 productType) public view returns(uint256[] memory myProductIDs)
​
// 获取某种产品的原材料用于哪些产品了
function trace(uint256 materialBatchNum) public view returns(uint256[] memory ids)
结算合约

负责用户资产,具体步骤如下:

  • 余额与预付款队列;
  • 产品所有权变化(手动确认)过程中的自动结算;
  • 资金如果不足,不能进行诸如备货,入库等的操作;
  • 充值/直接消费/预付款, 余额查询等相关功能。
生产合约

拥有库存生产个数和已拥有产品队列, 和原材料批次队列:

  • 获取待生产订单,判断原料库存是否满足需求,不满足则通过结算合约向备货合约预付款下生产订单;
  • 通过计算原材料队列,外部唯一标识生成,将原材料的批次信息写入产品进行生产(外部)、入库(调用产品合约的生成入口)。对库存生产个数和已拥有产品队列进行维护;
  • 出库(外部调用产品合约的产品所有权变更入口),对本合约的生产个数和已拥有产品队列库存合约的已拥有产品队列进行维护,通过结算合约进行结算。
备货合约

负责对上游厂商进行备货:

  • 通过调用生产备货数对厂家B进行备货,计算已有零件数,外部调用零件入库(填入批次,数量等信息);
  • 出库(外部调用),将零件信息的批次信息写入到厂家B, 并维护自身数据队列,完成后自动调用结算合约进行结算。

补充:因为零件不像产品一物一码,多个零件只对应到某个批次就可以,不需要单独的零件合约对零件进行维护。

授权合约

通过不同的权限等级,对各个合约中的函数进行访问限制,各种合约间角色的约定及调用权限。比如普通用户没有权限去向供货商下订单,而供货商也不可能向普通用户提供生产完成的商品。

// address是否有role权限
function hasRole(bytes32 role, address account) public view returns (bool)
​
// 获取role权限的成员数
function getRoleMemberCount(bytes32 role) public view returns (uint256)
​
// 根据role和index获取成员
function getRoleMember(bytes32 role, uint256 index) public view returns (address)
​
// 获取role角色的管理者
function getRoleAdmin(bytes32 role) public view returns (bytes32)
​
// 向某个account授予role权限
function grantRole(bytes32 role, address account) public virtual
​
// 移除account的role权限
function revokeRole(bytes32 role, address account) public virtual
​
// account放弃持有的role权限
function revokeRole(bytes32 role, address account) public virtual
智能合约解决方案

整个智能合约开发过程中,最主要其实是对整个生产流程的梳理。首先我们先进行了需求梳理,然后针对solidity语言开发出一个可用的版本,在此基础上对相关调用建立一个简单的restful服务器,完成对各个接口的测试,有一套完整的可演示流程,最后进行相关开发完成对合约的复现。

想了解该方案的具体实现,可在公众号后台对话框回复【启思实验室】,获取完整方案,更多行业设计方案请关注社群每周四直播的《超话区块链》,公众号对话框回复【小助手】入群。

多企业间如何实现 “链上协同与治理”

作者:朱立派|区块链资深开发者(深圳市建信筑和科技有限公司 )

公众号对话框回复【UCOB】下载完整方案

在近期众多大赛中,社区涌现出许多优质的区块链应用方案,这些方案让大家看到技术本身的蓬勃活力,也折射了区块链助力产业发展的无限潜力。应社区开发者要求,社群每周四《超话区块链》直播课推出了“对话区块链应用先行者”系列,与大家分享、展示这些获奖团队的技术应用方案,希望可以给大家日常开发提供一些启迪。

本期邀请区块链资深开发者朱立派分享他在BSN第二次开发者大赛上的获奖作品:《United Corporation On Blockchain》(区块链上的联合公司),探讨多企业间如何实现 “链上协同与治理”。

为什么选择企业间多方协同治理场景?

很多朋友可能会好奇,为什么会有“区块链上的联合公司”这样看起来天马行空的想法?首先,区块链技术很适用于“多方协同治理”场景。多中心化自治组织开放式治理能力体现在任何人只要有相应的凭证,就可以公开行使治理。受此启发,我想基于联盟链实现类似功能:联盟各方都具备事先约定的治理能力,通过区块链技术保证治理过程的公平、公正、公开、可追溯和不可抵赖。其次,公司实际业务往来之间对“多方协同治理”存在真实场景需求。

比如一般公司间的业务往来常涉及项目、资金两大类,如果多家公司需要联合管理某个项目,且有资金往来,就可以考虑使用区块链技术实现“链上协同与治理”。大家可保持对项目进展的全局视野一致,同时,任何签字确认的流程都由对应私钥签名后触发,更容易实现责任到人。

链上协同与治理实现思路

各家公司在区块链上以单独的“公司合约”形式存在,只要实现了“公司合约接口”便可自定义公司内部业务逻辑与内部组织关系。公司想要加入联合公司时,首先提出申请,部署自己的“公司合约”;然后由已在联合公司中的成员以新部署的“公司合约地址”作为参数发起提案;在得到联合公司中大多数成员投票通过后,便可以正式成为联合公司中的一员。各家公司参与的项目将单独以“联合项目合约”的形式存在,联合公司内任一家成员公司都可以发起联合项目。

首先依据“项目合约接口”开发“联合项目合约”,部署到区块链上;并以“联合项目合约的地址”作为提案中的参数,发起提案;联合公司中每一家公司可以根据提案中的合约地址查看合约,决定是否投票该提案;当得到联合公司中大多数成员公司投票通过后,即成为“联合项目合约”。

区块链智能合约设计思路与关键逻辑
合约设计思路

在合约设计上,参考了 FISCO BCOS开源社区《智能合约编写之Solidity的编程攻略》文章里的思路,采用“数据、管理、控制”分层的设计方法。本智能合约方案主要有三大模块:联合治理模块、公司模块、项目模块,合约交互主要发生在这三大模块的合约之间。

_images/IMG_5275.PNG

  • 联合治理模块:提案与投票系统,联合公司成员管理系统,联合公司间资金流转系统;
  • 公司模块:单个公司管理系统,单个公司内部资金流转系统;
  • 项目模块:多个公司的联合项目管理,单个公司的内部项目管理。

其中,“联盟管理模块”集中管理“公司模块”合约和“项目模块”合约,管理机制主要为“投票-注册”;公司合约、项目合约在实现对应接口合约方法的基础上自定义业务逻辑,并以单独合约的形式上链。

合约功能上,主要有以下几点:

  • 投票注册功能,只有投票数超过一定比率,新公司才能成为联合公司一员,新项目才能认定为联合项目;
  • 项目管理功能,如项目管理员的设置;
  • 基于角色的权限控制,自定义角色和权限;
  • 资金流转,包括公司之间的资金流转(涉及跨合约调用)和公司内部的资金流转;
  • 资金发行功能,依据投票决定是否发行资金。
关键逻辑的合约代码实现

这里介绍项目中一些关键逻辑的合约代码实现,以“存储类智能合约”的所有权转移为例。本项目采取了“存储、逻辑、控制”分层设计的思路,部署者在部署“存储类智能合约”后必须转移合约所有权关系给控制器类智能合约,存储类合约方法如下:

function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}

上述“newOwner”参数必须为对应的“控制器合约”地址。这样,“存储类智能合约”通过修饰器“modifier onlyOwner()”保证了只有对应的“控制器智能合约”才可以修改“存储类智能合约”的数据。部署完成后在“控制器合约”中通过如下方法可验证是否已具备“存储类合约”的所有权。


function checkUCOBNodeStorageSafety() public view returns (bool) {
return ucobNodeStorage.owner()==address(this);
}

控制器类智能合约的代码逻辑可以升级,通过投票来决定是否升级。


function changeUCOBNodeStorageOwner(bytes32 proposalId) public {
require(proposalPassed(proposalId,...));
...
ucobNodeStorage.transferOwnership(UCOBNodeControllerAddress);
...
}

当投票通过后,“存储类智能合约”的所有权关系会转移到新的“控制器合约”地址上,数据不变,但是业务逻辑“升级”了。

更多项目中的关键逻辑合约代码实现,可以通过社区公众号后台回复【UCOB】获取完整代码及说明。更多行业设计方案请关注社群每周四直播的《超话区块链》,公众号对话框回复【小助手】入群。


嘉宾Q&A

Q :您能分享下作品准备过程中,遇到哪些技术问题?又是如何解决的?

A :给我比较深印象的有两个问题,一个是遇到某些合约方法无法直接使用WeBASE网页端或通过控制台访问的情况。

比如代理调用的方法,首先尝试“sendRawTransaction”接口请求节点服务来调用这个方法,把交易数据编码并签名后,直接发送RLP编码给节点,但是节点日志提示解析错误。

后来在FISCO BCOS官方群里寻求帮助,发现是我的编码方法不对,调整之后,调用就成功了。

还有一个和测试有关,因为项目部署时,有几个合约会依赖其他合约部署后的地址,所以如果测试时发现合约代码不对,就要全部重新部署一遍再测试。我解决的方法是首先在Remix IDE上测试,代码不对就重新部署,还比较快速。全部的逻辑都测试通过没问题了,再放到FISCO BCOS上测,这个时候就是测试SDK与合约的交互了。

Q :在做底层技术选型时,您考虑哪些因素?

A :我主要考虑这几个方面:节点部署简单快速,生态组件丰富易用,社区支持及时有效,再一个就是对初学者而言容易上手。

因为我之前也有研究过国外的某个开源底层平台,我是通过英文文档开始研究的。如果是本身就不了解区块链的人,从英文文档开始,学习成本就很高了。一方面要理解英文,另一方面还要理解英文所表述的专业词汇。像FISCO BCOS这样的国产开源区块链平台,提供完善的中文文档,对一个初学者而言只需要理解专业词汇就好了,没有语言的成本,比较容易上手。

Q :对于国内开源的发展,您怎么看?

A :我看到FISCO BCOS开源社区经常讲“把代码丢出去,把信任建起来”,很让人钦佩。开源社区不仅仅包括社区发起者和运营维护者,更重要的是有广大开发者,“众人拾柴火焰高”,在国内开源社区建设中,能调动开源社区广大开发者力量是很关键的。

打造全场景透明管理,建信筑和联手FISCO BCOS助力建造业数字化

作者:房少君|建信筑和 CTO

_images/IMG_5276.PNG

公众号对话框回复【建信筑和】获取方案PDF

随着工业4.0时代持续推进,区块链逐渐挥别纯粹的技术自赏和金融标签,渗透进各类实体产业。

在传统建造场景下,行业信息不透明、管理协同性差、信息化水平不高等“痼疾”,使得建造项目的推进过程存在管理难、追责难、监管难、信任难的问题。“区块链+建造”的结合,利用区块链技术,瞄准这些行业痛点,提出了应用层面的切实解决方案。

深圳市建信筑和科技有限公司(以下简称:建信筑和)基于FISCO BCOS区块链平台开发的“伊OS透明建造解决方案”,正是区块链技术在建造行业场景化应用的典型代表。

该方案凭借全场景管理模式的领先优势,入选区块链服务网络BSN首批官方指定应用,并在工信部中国电子技术标准化研究院主办的2020年第四届中国区块链开发大赛上,斩获桂冠。

_images/IMG_5277.JPG

信息不对称+管理难协同,建造业痛点待破局

在传统建造业中,“信息不对称+管理难协同”是制约行业发展的重要因素。建造业上下游链条很长,如果产业链上每个环节的参与者之间信息不透明,无形中提高了工作协同的沟通、管理成本。诸如施工方和设计方对于设计方案的扯皮、各主体间款项账期的节点把控模糊等问题层出不穷,拖慢整体工程进度。同时,信息不透明也使甲方难以实现全流程式管理穿透,管理不及时、不到位为项目埋下了违规风险,进一步推高信任成本,反过来又加重了工作协同难度。

在传统行业中,建造业信息化程度一直处于较低水平,建设过程中涉及业主投资方、监管方、代建方、咨询方、设计方、施工方、监理方、运营方等众多单位,同时随着当前各类工程项目投资规模的不断扩大,项目管理和资金监管方面压力大,这些问题制约着建造业生态的健康成长。因此,传统建造业的破局路径之一,就是尽快打破现状,利用信息化手段推动建造业实现项目管理“全场景、全主体、全流程透明协同”,而区块链,则是加速技术化进程的独特手段。

在此背景下,建信筑和加入FISCO BCOS区块链开源生态,基于FISCO BCOS区块链技术打造了面向建造行业的全生命周期管理系统——“伊OS透明建造解决方案”,以推进建造行业实现“全场景透明化管理”。该解决方案运用区块链分布式存储/共享、智能合约等技术,与建造行业已有成熟的IT工具如设计软件、造价系统、智慧工地、BIM系统等进行整合,同时结合大数据和人工智能等先进技术,有效破除了行业痛点,构建了一个具有管理穿透、公开透明、信息共享、信用评价的全过程透明建设平台。

区块链技术助力,建造实现全场景穿透管理

“伊OS透明建造解决方案”采取了场景式存证、数字签名、加密算法、智能合约等区块链技术,打破了原有单线任务框架,实现了去中心化的任务协同和数据流转。将原有项目链条(产业链条)上的参与者,例如项目方、设计、施工、勘察、总包、分包、监理、班组等,置于同一任务场景中,不再具有严格的上下游流程关系,原来极易形成彼此掣肘的“物流、资金流、信息流”也能做到有机协同。

_images/IMG_5278.PNG

同时由于智能合约触发机制的存在,工程资金流向有了明确的监管方向,资金流转的每一步都可透明,这将进一步确保资金使用的合理、合规、合法。同时,工程资金透明也使得工程质量和安全更有保障,形成建造产业良性循环。

“伊OS透明建造解决方案”不仅能够实现单项目多标段管理,还可以实现多项目管理,目前已被广泛运用在建造行业,有效提升项目运行效率。截止2020年5月底,伊OS平台上运行项目超300个,参建单位达400多家。在某集团在建的100+个管网项目中,伊OS系统协助项目负责人全面的管理现场施工的过程、进度、质量、安全进行,通过奖惩机制成功落地执行,让项目问题解决率从60%提升到80%。

建信筑和CTO房少君表示,区块链技术遇见建造行业,不仅可以对项目全过程实施文件共享、资金监管、工程量申报、质量安全、绩效考核等全场景“俯瞰”,而且能有效点亮原先项目管理、工程结算及资金监管上的“盲区”,“事前任务分布-事中实时监督-事后责任追溯”自此形成了高效协同的有机闭环。

万字报告选定FISCO BCOS,携手重塑建造信用生态

为了有效地打造“伊OS透明建造解决方案”,建信筑和选择了FISCO BCOS。“为了甄选区块链底层平台,我们团队专门做了一份过万字的研究报告,最终选定FISCO BCOS,主要是因为它拥有不可多得的语言优势,生态组件、节点部署和技术支持等都非常丰富、易用和高效。”房少君介绍,在FISCO BCOS技术底层上,建信筑和有效地完成了五大业务场景的系统化构建:场景式存证、工程量申报、资金监管、数据交易确权和票据流转。

_images/IMG_5279.PNG

基于FISCO BCOS区块链技术的“伊OS透明建造解决方案”能够确保项目实施过程中物流流转和信息流转一一对应、资金流向透明监管、物流全程可追溯和资金到账的高度匹配,不仅是区块链在建造行业中的项目管理流程再造,更是新型“全场景”式管理模式下的信用生态重塑。


社区长期征集基于FISCO BCOS研发的区块链应用,如果您有正在研发或已经上线的应用,欢迎点击“阅读原文”告诉我们,您的应用值得被更多人看见。

社区开发实践

鲲鹏平台编译并运行FISCO-BCOS 2.6.0

一:申请鲲鹏服务器(已经有鲲鹏服务器略过此步)
1. 注册

打开 鹏城实验室官网 进行账户注册,注册过程中需要填写真实名称、工作单位、邮箱,收到激活邮件后需要手动点击激活连接地址激活账户 _images/11.png

2. 登录

登陆注册的账户以后点击 “开发者云” 按钮 _images/21.png

3. 填写申请订单

在打开的页面中自己填写 “需求申请” 订单,按照自己实际项目需求规划硬件配置、系统版本、使用多长时间 _images/31.png

4. 等待通知

需求订单填写无误提交以后,等待官方批准,批准完成以后会收到批准结果邮件通知_images/41.png

5. 通过申请

登录鲲鹏服务器查看服务器信息,至此鲲鹏服务器准备完成_images/51.png

二:在鲲鹏服务器安装基础软件
1. 更新软件

以 Centos 为例,使用 yum 进行更新(此过程会下载更新包,请保持网络畅通并耐心等待)

yum update 

_images/61.png

2. 查看基础工具版本

_images/7.png

3. 安装基础依赖
yum install flex patch bison gmp-static 

_images/8.png

4. 升级gcc 到9.3
  • 下载gcc-9.3.0.tar.gz
wget http://mirror.hust.edu.cn/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.gz

_images/9.png

  • 解压文件
tar -xf gcc-9.3.0.tar.gz 

_images/10.png

  • 安装gcc 依赖
./contrib/download_prerequisites

_images/111.png

  • 加–noverify选项禁止边下边验证
./contrib/download_prerequisites --no-verify
  • 创建预编译目录
mkdir build && cd build
  • 设置编译选项并编译
../configure --prefix=/usr/local/gcc-9.3.0 --enable-bootstrap --enable-checking=release --enable-languages=c,c++ --disable-multilib 
  • 编译并安装 编译生成makefile文件
make && make install
4.1 安装后的设置
  • 设置环境变量
touch /etc/profile.d/gcc.sh sudo chmod 777 /etc/profile.d/gcc.sh 
sudo echo -e '\nexport PATH=/usr/local/gcc-9.3.0/bin:$PATH\n' >> /etc/profile.d/gcc.sh && source /etc/profile.d/gcc.sh
  • 设置头文件
sudo ln -sv /usr/local/gcc/include/ /usr/include/gcc 
  • 设置库文件
touch /etc/ld.so.conf.d/gcc.conf sudo chmod 777 /etc/ld.so.conf.d/gcc.conf sudo echo -e "/usr/local/gcc/lib64" >> /etc/ld.so.conf.d/gcc.conf 
  • 加载动态连接库
sudo ldconfig -v ldconfig -p |grep gcc 
4.2 测试版本号
  • 测试
gcc -v

_images/12.png

5. 安装鲲鹏版本jdk-1.8

_images/13.png

  • 解压文件到/usr/local中
tar -xf jdk-8u261-linux-arm64-vfp-hflt.tar.gz  -C /usr/local/

_images/14.png

  • 设置环境变量
vim /etc/profile

// 在文件最后添加:
export JAVA_HOME=/usr/local/jdk1.8.0_261
export PATH=$JAVA_HOME/bin:$PATH

//使变量生效
source /etc/profile

_images/15.png

三:编译FISCO-BCOS 2.6.0源码

本文中用码云仓库映射到GitHub 仓库来同步代码,加快下载速度

1. 下载源码
  • 克隆 FISCO BCOS 代码
cd /home 
git clone https://gitee.com/hailong99/FISCO-BCOS-2.6.git

_images/16.png

2. 切换到2.6.0 分支
git branch -a
git checkout remotes/origin/release-2.6.0

_images/17.png

3. 源码编译前配置
cd /home/FISCO-BCOS
mkdir -p build && cd build

// CentOS请执行此命令,其他系统不需要
source /opt/rh/devtoolset-7/enable 

cmake3 .. -DARCH_NATIVE=on
4. 编译源码
make
4.1 解决编译GroupSigLib 报错

_images/18.png

  • 解决方法:
cp/usr/share/automake-1.13/config.guess /home/FISCO-BCOS-2.6/deps/src/GroupSigLib/deps/src/pbc_sig/config.guess

_images/19.png

5. 查看编译结果
  • 编译完成效果 _images/20.png
  • 查看编译结果的版本号 _images/211.png
  • 查看二进制可执行文件类型 _images/22.png
四 : 鲲鹏平台运行2群组3机构6节点底层FISCO-BCOS联盟链服务
1. 创建联盟链底层二进制可执行文件存放目录
mkdir -p /home/soft/fisco-bcos-bin/
2. 复制编译的fisco-bcos 文件到创建的目录中
cp ./fisco-bcos /home/soft/fisco-bcos-bin/

_images/23.png

3. 运行一键搭建2群组3机构6节点底层FISCO-BCOS联盟链服务脚本

_images/24.png build-Bcos_fgmnode.sh _images/25.png

4. 脚本运行完成效果

_images/26.png

5. 查看节点状态
ps -aux |grep bcos

_images/27.png

6. 查看共识状态

_images/28.png

五 : 鲲鹏平台安装FISCO-BCOS 控制台

说明:控制台程序依赖 java-1.8 需要提前安装好鲲鹏版本(arrch64)的java-1.8

1. 下载控制台程序
cd ../generator-A/
./generator --download_console ./ --cdn

_images/29.png

2. 启动控制台
cd console/
./start.sh

_images/30.png

六 : 鲲鹏平台通过控制台查看FISCO-BCOS 2.6.0 版本
  • 查看 FISCO 版本 _images/311.png

多机部署-单群组双机构双节点组网模式实战

作者:蒲沧龙(肖越)|上海对外经贸大学区块链与应用研究中心成员

0.需求分析

有两台服务器,那么每台机子下一个机构生成一个节点,两个连一块,即:双机构双节点单群组。

这样就不能使用官方的一键螺旋快乐飞天脚本了:

_images/2.png

使用generator运维部署工具,

于是就出现了问题(基地爆炸,问题不大)。

1.下载安装运维部署工具
这里假设机子上什么都没有,因为使用源码编译客户端的用户不用最后一步

下载

cd ~/ && git clone https://github.com/FISCO-BCOS/generator.git

安装(此操作要求用户具有sudo权限)

cd ~/generator && bash ./scripts/install.sh

检查是否安装成功,若成功,输出 usage: generator xxx

./generator -h

获取节点二进制 拉取最新fisco-bcos二进制文件到meta中

./generator --download_fisco ./meta

检查二进制版本

若成功,输出 FISCO-BCOS Version : x.x.x-x

./meta/fisco-bcos -v

这里遇到的问题是,二进制文件拉取贼慢。 然后我再本机把generator克隆下来,发现是:

_images/3.png

工具类的download_fisco函数这里卡主了。没有cdn的朋友可以vim修改下这个url为:

fisco官方cdn:
https://www.fisco.com.cn/cdn/fisco-bcos/releases/download/
or直接下载:
https://xiaoyue-blog.oss-cn-hangzhou.aliyuncs.com/fisco-bcos.tar.gz

这是俺的OSS,开放使用师傅们轻点敲啊。

一秒不到就下完了。然后这个就是装好了:

_images/4.png


2.联盟链初始化

来康康拓扑图:

_images/1.png

因为官方教程是在一个机子上配的,是节点1,2。分起来的话实际上没有1,2之分的。因为是在两台机子上,也不会存在端口冲突的情况。如果端口没有打开可能会报错,这边建议把两台机子添加白名单就行了。详情可参考:FSICO BCOS多机部署之端口开放

机构 节点 rpc端口 channel端口 p2p端口
机构A 节点1 8545 20200 30300
机构B 节点2 8545 20200 30300
2.1 机构初始化

初始化机构AB

cd ~/generator
cp -r ~/generator ~/generator-A
cp -r ~/generator ~/generator-B
2.2 链初始化

生成链证书ca.crt&链私钥ca.key

./generator --generate_chain_certificate ./dir_chain_ca

链证书和链私钥在./dir_chain_ca


3.构建群组
3.1 初始化机构A

生成机构A的证书

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyA

证书授权机构将机构证书发送给机构,机构放到meta目录下面

cp ./dir_agency_ca/agencyA/* ~/generator-A/meta/
3.2 初始化机构B

生成机构B的证书

./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyB

证书授权机构将机构证书发送给机构,机构放到meta目录下面

cp ./dir_agency_ca/agencyB/* ~/generator-B/meta/
3.3 机构A修改配置文件

进入机构A修改node_deployment.ini节点配置文件、生成节点配置文件夹。

cd ~/generator-A
cat > ./conf/node_deployment.ini<<EOF
[group]
group_id=1
[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=you_ip
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please seehttps://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=0.0.0.0
channel_ip=0.0.0.0
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545
EOF
3.4 机构B修改配置文件

进入机构B修改node_deployment.ini节点配置文件、生成节点配置文件夹。

cd ~/generator-B
cat > ./conf/node_deployment.ini<<EOF
[group]
group_id=1
[node0]
; host ip for the communication among peers.
; Please use your ssh login ip.
p2p_ip=you_ip_B
; listen ip for the communication between sdk clients.
; This ip is the same as p2p_ip for physical host.
; But for virtual host e.g. vps servers, it is usually different from p2p_ip.
; You can check accessible addresses of your network card.
; Please seehttps://tecadmin.net/check-ip-address-ubuntu-18-04-desktop/
; for more instructions.
rpc_ip=0.0.0.0
channel_ip=0.0.0.0
p2p_listen_port=30300
channel_listen_port=20200
jsonrpc_listen_port=8545
EOF
3.5 机构A生成并发送节点信息

生成机构A节点证书和P2P连接地址文件,根据上面修改的node_depoyment.ini生成

cd ~/generator-A
./generator --generate_all_certificates ./agencyA_node_info

机构生成节点时需要指定其他节点的P2P连接地址,这里A机构把P2P连接机构发送给了B机构

cp ./agencyA_node_info/peers.txt ~/generator-B/meta/peersA.txt
3.6 机构B生成并发送节点信息

生成机构A节点证书和P2P连接地址文件,根据上面修改的node_depoyment.ini生成

cd ~/generator-B
./generator --generate_all_certificates ./agencyB_node_info

因为需要生成创世区块,规定此机构必须要节点证书。B机构除了要发送P2P连接地址之外还要发送节点证书。

cp ./agencyB_node_info/cert*.crt ~/generator-A/meta/
cp ./agencyB_node_info/peers.txt ~/generator-A/meta/peersB.txt
3.7 机构A生成群组1创世区块

来生成创世区块。这里实际可以通过协商由那个机构生成,不一定是A。

cd ~/generator-A
cat > ./conf/group_genesis.ini<< EOF
[group]
group_id=1
;i am xiaoyue
;my blog-s cnmf.net.cn 
;yea i love you~
[nodes]
node0=you_ip:30300
node1=you_ip_b:30300
EOF
./generator --create_group_genesis ./group

把group1的创世区块发送给B机构

cp ./group/group.1.genesis ~/generator-B/meta
3.8 机构A生成所属节点

生成机构A的节点

cd ~/generator-A
./generator --build_install_package ./meta/peersB.txt ./nodeA

启动节点

bash ./nodeA/start_all.sh

上面有两个需要注意的地方:

  1. 在生产节点配置文件和创世区块配置文件时端口一致是没问题的,因为我不在一台机子上做测试,不会存在端口冲突的情况。但这样在复制B机构到B机子上的时候跑不起来就很尴尬。
  2. rpc的ip默认是127.0.0.1,如果硬要开启的话会报警告:

_images/6.png

如果非要开启rpc测试的话,也可以参照上面说的开启防火墙ip白名单。


4.B机构转移并生成节点

压缩:tar cvf B.tar generator-B 解压:tar xvf B.tar 然后上传下载操作

4.1 生成&启动节点:
./generator --build_install_package ./meta/peersA.txt ./nodeB

机构B启动节点:

bash ./nodeB/start_all.sh

Z.查看共识状态
tail -f ./node*/node*/log/log*  | grep +++

正确回显如下:

_images/5.png

这里还有一个问题。就是上面说的对自己自信不进行测试,导致ip输错了导致共识失败,这时候是没有回显的。把后面的正则删掉就行了。能看到日志报错,通过报错去找不能共识的原因。


你可以从以下方式联系到我:

🥇Blog: https://cnmf.net.cn/

🥈GitHub: https://github.com/xiaoyue2019

🥉CSDN: https://blog.csdn.net/xiaoyue2019

欢迎来俺们社区吹水鸭:

_images/7.bmp

参考:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/enterprise_tools/tutorial_detail_operation.html

社区

FISCO BCOS是国内企业主导研发、对外开源、安全可控的企业级金融联盟链底层平台。由金融区块链合作联盟(深圳)(简称:金链盟)成立的开源工作组协作打造,工作组成员包括博彦科技、华为、深证通、神州信息、四方精创、腾讯、微众银行、亦笔科技和越秀金科等金链盟成员机构。

加入FISCO BCOS社区

_images/qr_code1.png _images/qr_code2.1.png _images/changeable_body.png _images/tailer.png