FISCO BCOS 技术文档¶
FISCO BCOS 是一个稳定、高效、安全的区块链底层平台,经过多家机构、多个应用,长时间在生产环境运行的实际检验。
概览
- 基于FISCO BCOS 2.0快速构建区块链系统,请参考 安装
- 基于FISCO BCOS 2.0部署多群组区块链、构建第一个区块链应用,请参考 教程
- 深入了解FISCO BCOS 2.0功能请看 配置文件和配置项、节点准入、并行交易、分布式存储、国密 等请参考 使用手册
- 控制台:交互式命令行工具,可访问区块链节点,查询区块链状态,部署并调用合约等。
- 企业级部署工具(Generator):支持建链、扩容等操作,推荐构建企业级区块链时使用,快速使用方法可参考 教程
- Web3SDK:提供访问节点状态、修改区块链系统配置以及节点发送交易等接口。
- 浏览器详细介绍请参考 浏览器
- JSON-RPC接口可参考 JSON-RPC API
- 系统设计文档请参考 系统设计
重要
平台介绍¶
FISCO BCOS平台是金融区块链合作联盟(深圳)(以下简称:金链盟)开源工作组以金融业务实践为参考样本,在BCOS开源平台基础上进行模块升级与功能重塑,深度定制的安全可控、适用于金融行业且完全开源的区块链底层平台。金链盟开源工作组的首批成员包括以下单位:微众银行、深证通、腾讯、华为、神州数码、四方精创、博彦科技、越秀金科、亦笔科技等9家单位。
联盟链的升华:分布式商业与公众联盟链¶
商业,本身是一种竞争、自由的经济活动。而自由竞争的结果,天然就容易导致优胜劣汰、垄断集中、甚至寻租。尤其是2008年全球金融危机发生后,“大而不倒Too Big to Fail”的弊病显现,也因此引发了一系列的技术变革与商业变革,启动了一轮从“集中式”走向“分布式”的时代浪潮。
在这个背景下,区块链技术在2008年萌芽成型,并逐渐发展成熟。通过区块链技术解决方案中的共识机制、分布式账本、加密算法、智能合约、点对点通信、分布式计算架构、分布式存储、隐私保护算法、跨链协议等技术模块,可以让商业模式中的参与各方实现了地位对等和互信合作,从而推动了从“信息互联网”到“信任互联网”的时代进步,也令商业模式全面走向“分布式”成为可能。
新型的“分布式商业”模式,按微众银行整理给出的定义,是一种由多个具有对等地位的商业利益共同体所建立的新型生产关系,是通过预设的透明规则进行组织管理、职能分工、价值交换、共同提供商品与服务并分享收益的新型经济活动行为。在主要表现特征上,分布式商业显现出多方参与、共享资源、智能协同、价值整合、模式透明、跨越国界等特点。一个成熟的分布式商业场景具备生产资料由多方持有、产品和服务能力由多方共同构建、商业过程中的相互关系对等,产品和利益分配规则透明等要求。
分布式商业与此前流行的连锁加盟型商业模式及共享商业模式的最大不同之处在于,起到中间链接桥梁作用的不是人或产品、不是信息平台、而只是客观的技术本身。诚然,如果技术不开源,确实也可能演变成新的垄断。因此,发展分布式商业必须始终保持技术开源的态度,各个参与方通过开源社区进行分工合作,就将不再存在话语权集中和垄断的可能性,弱肉强食的“丛林法则”在此就不复存在。这有助于中小微企业真正成为商业价值链的主角,从而激发经济增长动力、广泛提升就业、鼓励创业和创新,实现“反垄断”的人类商业终极理想。
发展开源区块链技术的深远意义已不言而喻,但技术路线的选择也至关重要。虽然最原始的区块链技术起源于虚拟货币及公有链项目,但公有链的项目方往往以融资为目的,其用户则是以价格交易获利为目标,导致各方更多是关注币价的涨跌而非区块链的真正应用能力。由于公有链的代币实质上是“类货币”与“类证券”,已经被中国的监管部门严厉叫停。当潮水退去、大浪淘沙后,联盟链技术已肩负起推动区块链技术继续前行的重任。2018年,业界更是提出“公众联盟链”的发展路线,呼吁联盟链应该积极开放开源,从较为封闭的联盟内或公司内走向大众,让普罗大众真正感受到区块链带来的体验提升、效率提升、成本下降、信任增强、数据互换、责任追溯等好处,实现分布式商业的愿景。
新一代的公众联盟链,对于区块链底层技术提出了新的要求,除了标准的区块链特性之外,还有几个方面仍需要重点加强:首先,由于公众联盟链并不是单一链条,所以需要具备支持多链并行以及跨链通信的技术,同时需能够支撑来自互联网海量交易请求的能力。其次,需要具备快速、低成本地组建联盟和建链的能力,便于各个需求方能够高效建立联盟链网络,让企业间建链合作变得像现在建立“聊天群”一样高效便捷。最后,需要开源和开放,实现联盟成员之间的充分信任。公众联盟链不仅有利于降低企业快速试错的成本,有效提升商业上的容错性,也促进商业社会朝着可信化、透明化的方向深化发展,全面降低合作的操作风险、道德风险、信用风险、信息保护风险等。秉持以上的目标与愿景,我们正式发布了基于“公众联盟链”技术路线的FISCO BCOS 2.0版本。
FISCO BCOS 2.0¶
FISCO BCOS 2.0版本在原有基础上进行架构升级和优化,在可扩展性、性能、易用性等方面取得了重大突破,其中包括:
- 实现群组架构,在多个节点组成的一个全局网络中,可以存在多个节点子集组成的子网络,这些子网络维护一个独立的账本。这些账本之间的共识、存储都是相互独立的,具备良好的扩展性和安全性。在群组架构中,可以更好地实现平行扩展,满足金融级高频交易场景的需求。同时,群组架构可以快速支持组链需求,极大降低运维难度,真正能够实现企业间建链就像建“聊天群”一样简便。
- 支持分布式存储,使存储突破单机限制,支持横向扩展。计算和存储分离,提高了系统健壮性,即使节点执行服务器故障,数据也不会受影响。分布式存储定义了标准的数据访问CRUD接口,可以适配多种存储系统,同时支持SQL和NoSQL两种数据管理方式,可以更简便地支持多种业务场景。
- 实现预编译合约框架,突破EVM性能瓶颈。支持交易并发处理,大幅提升交易处理吞吐量。预编译合约采用C++实现,内置于底层系统中,区块链自动识别调用合约的交易互斥信息,构建DAG依赖,规划出一个高效的并行交易执行路径。最佳情况下,性能提升N倍(N=CPU核数)。
- 另外,FISCO BCOS 2.0版本持续在网络传输模型、计算存储流程等方面进行优化,对性能提升提供巨大帮助。在架构方面,在存储、网络、计算三个角度,围绕高可用性和高易用性进行持续升级。基于模块化、分层、可插拔等设计原则,持续对核心模块进行重塑升级,保证系统健壮性。
更多2.0版本的特性将在后续章节深入展开介绍,请看2.0新版介绍。
FISCO BCOS 1.0¶
回顾FISCO BCOS的演进历程,我们一直致力于达到性能、安全、可用性与合规的平衡。
- 在性能方面,FISCO BCOS 在整体架构和交易处理等方面都进行了大量的优化,包括采用了高效的共识算法,把能并行的计算并行化,减少重复计算,对关键计算单元进行升级等。更进一步地,其性能的核心突破点不仅仅在于单链,更在于基于单链性能优化架构设计,并实现灵活、高效、可靠、安全的并行计算和可平行扩展的能力。这帮助开发者能够灵活地根据自己业务场景的实际需要,通过简单增加机器,达到自己需要的性能。总体上,FISCO BCOS平台优化了网络通信模型,采用拜占庭容错共识机制,结合多链架构和跨链交互方案,可解决并发访问和热点账户的性能痛点,从而满足金融级高频交易场景需求。
- 在安全性方面,FISCO BCOS 平台通过节点准入控制、可靠的密钥管理、灵活的权限控制,在应用、存储、网络、主机层实现全面的安全保障。在隐私保护的设计上,支持权限管理、物理隔离,支持国密算法(国家密码局认证的标准算法),同时也对外开源了包括同态加密、零知识证明、群签名、环签名等多种隐私保护算法的实现方案。
- 在可用性方面,FISCO BCOS设计为7×24小时运行,达到金融级高可用性。在监管支持方面,可支持监管和审计机构作为观察节点加入,获取实时数据进行监管审计。此外,还提供了各种开发接口,方便开发者编写和调用智能合约。
总结¶
实践之中出真知,FISCO BCOS经过了外部多家机构、多个应用,长时间在生产环境运行的实际检验,已经成长为一个稳定、高效、安全的区块链底层平台。
本文档后续内容将详细介绍FISCO BCOS 2.0版本的构建、安装、智能合约部署、调用等教程,以及深入介绍FISCO BCOS 2.0版本整体架构和各模块的设计方案。
2.0版本新特性¶
群组架构¶
群组架构是FISCO BCOS 2.0众多新特性中的主线,创造灵感来源于人人都熟悉的群聊模式——群的建立非常灵活,几个人就可以快速拉个主题群进行交流。同一个人可以参与到自己感兴趣的多个群里,并行地收发信息。现有的群也可以继续增加成员。
采用群组架构的网络中,根据业务场景的不同,可存在多个不同的账本,区块链节点可以根据业务关系选择群组加入,参与到对应账本的数据共享和共识过程中。该架构的特点是:
- 各群组独立执行共识流程,由群组内参与者决定如何进行共识,一个群组内的共识不受其他群组影响,各群组拥有独立的账本,维护自己的交易事务和数据,使得各群组之间解除耦合独立运作,可以达成更好的隐私隔离;
- 机构的节点只需部署一次,通过群组设置即可参与到不同的多方协作业务中,或将一个业务按用户、时间等维度分到各群组,群组架构可快速地平行扩展,在扩大了业务规模同时,极大简化了运维复杂度,降低管理成本。
分布式存储¶
FISCO BCOS 2.0新增了对分布式数据存储的支持,节点可将数据存储在远端分布式系统中,克服了本地化数据存储的诸多限制。该方案有以下优点:
- 支持多种存储引擎,选用高可用的分布式存储系统,可以支持数据简便快速地扩容;
- 将计算和数据隔离,节点故障不会导致数据异常;
- 数据在远端存储,数据可以在更安全的隔离区存储,这在很多场景中非常有意义;
- 分布式存储不仅支持Key-Value形式,还支持SQL方式,使得业务开发更为简便;
- 世界状态的存储从原来的MPT存储结构转为分布式存储,避免了世界状态急剧膨胀导致性能下降的问题;
- 优化了数据存储的结构,更节约存储空间。
同时,2.0版本仍然兼容1.0版本的本地存储模式。更多关于存储介绍,请参考分布式存储操作手册
并行计算模型¶
2.0版本中新增了合约交易的并行处理机制,进一步提升了合约的并发吞吐量。
1.0版本以及大部分业界传统区块链平台,交易是被打包成一个区块,在一个区块中交易顺序串行执行的。 2.0版本基于预编译合约,实现一套并行交易处理模型,基于这个模型可以自定义交易互斥变量。 在区块执行过程中,系统将会根据交易互斥变量自动构建交易依赖关系图——DAG,基于DAG并行执行交易,最好情况下性能可提升数倍(取决于CPU核数)。
预编译合约¶
FISCO BCOS 2.0提供预编译合约框架,支持采用C++编写合约,其优势是合约调用响应更快,运行速度更高,消耗资源更少,更易于并行计算,极大提升整个系统的效率。FISCO BCOS内置了多个系统级的合约,提供准入控制、权限管理、系统配置、CRUD式的数据存取等功能,这些功能天然集成在底层平台里,无需手动部署。
FISCO BCOS提供标准化接口和示例,帮助用户进行二次开发,便于用户编写高性能的业务合约,并方便地部署到FISCO BCOS里运行。预编译合约框架兼容EVM引擎,形成了“双引擎”架构,熟悉EVM引擎的用户可以选择将Solidity合约和预编译合约结合,在满足业务逻辑的同时获得巨大的效率提升。
CRUD合约¶
FISCO BCOS 2.0新增符合CRUD接口的合约接口规范,简化了将主流的面向SQL设计的商业应用迁移到区块链上的成本。其好处显而易见:
- 与传统业务开发模式类似,降低了合约开发学习成本;
- 合约只需关心核心逻辑,存储与计算分离,方便合约升级;
- CRUD底层逻辑基于预编译合约实现,数据存储采用分布式存储,效率更高;
同时,2.0版本仍然兼容1.0版本的合约,更多关于CRUD接口的介绍,请参考使用CRUD接口。
控制台¶
FISCO BCOS 2.0新增控制台,作为FISCO BCOS 2.0的交互式客户端工具。
控制台安装简单便捷,简单配置后即可和链节点进行通信,拥有丰富的命令和良好的交互体验,用户可以通过控制台查询区块链状态、读取和修改配置、管理区块链节点、部署并调用合约。控制台给用户管理、开发、运维区块链带来了巨大的便利,降低了操作繁琐性和使用门槛。
相比于传统的nodejs等脚本工具,控制台安装简单、使用体验更好。详细请查看控制台使用手册。
虚拟机¶
2.0版本引入了最新的以太坊虚拟机版本,支持Solidity 0.5版本。同时,引入了EVMC扩展框架,支持扩展不同虚拟机引擎。 底层内部集成支持interpreter虚拟机,未来可扩展支持WASM/JIT等虚拟机。
更多关于虚拟机的介绍,请参考虚拟机设计文档
密钥管理服务¶
2.0版本对落盘加密进行了重塑升级,开启落盘加密功能时,依赖KeyManager服务进行密钥管理,安全性更强。
KeyManager在Github开源发布,节点与KeyManager的交互协议是开放的,支持机构设计实现符合自身密钥管理规范的KeyManager服务,比如采用硬件加密机技术。 该部分更详细的文档请参考使用文档和设计文档
准入控制¶
2.0版本对准入机制进行了重塑升级,包括网络准入机制和群组准入机制,在不同维度对链和数据访问进行安全控制。
采用新的权限控制体系,基于表进行访问权限的设计,另外还支持CA黑名单机制,可以实现对作恶/故障节点的屏蔽。 详情请查看准入机制设计文档
异步事件¶
2.0版本同时支持交易上链异步通知、区块上链异步通知以及自定义的AMOP消息通知等机制。
模块重塑¶
2.0版本对核心模块进行升级重塑,进行模块化的单元测试和端对端集成测试,支持自动化持续集成和持续部署。
版本及兼容¶
- 新增特性
- 变更描述、兼容及升级说明
FISCO BCOS 1.x Releases
- FISCO BCOS 1.3 正式版:
- FISCO BCOS 1.2 正式版:
- FISCO BCOS 1.1 正式版:
- FISCO BCOS 1.0 正式版:
- FISCO BCOS 预览版:
查看节点和数据版本
- 查看节点二进制版本:
./fisco-bcos --version
- 数据格式和通信协议的版本:通过配置文件 config.ini的supported_version配置项 获取
FISCO BCOS 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
变更描述¶
新特性
- 分布式存储:新增支持底层通过数据库连接池直连MySQL
- 分布式存储:新增支持RocksDB引擎,搭建新链时存储默认采用RocksDB
- 分布式存储:新增CRUD接口支持,控制台1.0.3以上版本提供类SQL语句读写区块链数据
更新
- 完善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 | 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 | 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
变更描述¶
架构
- 新增群组架构:各群组独立共识和存储,在较低运维成本基础上实现系统吞吐能力横向扩展。
- 新增分布式数据存储:支持节点将数据存储在远端分布式系统中,实现计算与数据隔离、高速扩容、数据安全等级提升等目标。
- 新增对预编译合约的支持:底层基于C++实现预编译合约框架,兼容solidity调用方式,提升智能合约执行性能。
- 引入evmc扩展框架:支持扩展不同虚拟机引擎。
- 升级重塑P2P、共识、同步、交易执行、交易池、区块管理模块。
协议
- 实现一套CRUD基本数据访问接口规范合约,基于CRUD接口编写业务合约,实现传统面向SQL方式的业务开发流程。
- 支持交易上链异步通知、区块上链异步通知以及自定义的AMOP消息通知等机制。
- 升级以太坊虚拟机版本,支持Solidity 0.5.2版本。
- 升级RPC模块。
安全
- 升级落盘加密,提供密钥管理服务。开启落盘加密功能时,依赖KeyManager服务进行密钥管理。
- 升级准入机制,通过引入网络准入机制和群组准入机制,在不同维度对链和数据访问进行安全控制。
- 升级权限控制体系,基于表进行访问权限的设计。
其他
- 提供入门级的搭链工具。
- 提供模块化的单元测试和端对端集成测试,支持自动化持续集成和持续部署。
兼容性说明¶
兼容版本 | 说明 | |
---|---|---|
节点 | 不兼容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实验性功能,可以抢先体验,功能稳定后将在正式版本发布。
交易解析¶
FISCO BCOS的交易是一段发往区块链系统的请求数据,用于部署合约,调用合约接口,维护合约的生命周期以及管理资产,进行价值交换等。当交易确认后会产生交易回执,交易回执和交易均保存在区块里,用于记录交易执行过程生成的信息,如结果码、日志、消耗的gas量等。用户可以使用交易哈希查询交易回执,判定交易是否完成。
交易回执包含三个关键字段,分别是input(目前dev分支编译的fisco bcos包含该字段,后续合入2.0.0版本), output , logs:
字段 | 类型 | 描述 |
---|---|---|
input | String | 交易输入的ABI编码十六进制字符串 |
output | String | 交易返回的ABI编码十六进制字符串 |
logs | List |
event log列表,保存交易的event信息 |
交易解析功能帮助用户解析这三个字段为json数据和java对象。
重要
代码 - fisco bcos
- web3sdk
分支:https://github.com/FISCO-BCOS/web3sdk/tree/feature-txdecode
maven版本:2.0.33-SNAPSHOT
引入jar包¶
解析工具类在web3sdk中,应用需要首先在build.gradle配置文件中增加如下配置,引入web3sdk jar包。
repositories {
maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
maven { url "https://dl.bintray.com/ethereum/maven/" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
mavenCentral()
}
compile group: "org.fisco-bcos", name: "web3sdk", version: "2.0.34-SNAPSHOT"
接口说明¶
代码包路径org.fisco.bcos.web3j.tx.txdecode
,使用TransactionDecoderFactory
工厂类建立交易解析对象TransactionDecoder
,有两种方式:
TransactionDecoder buildTransactionDecoder(String abi, String bin);
abi:合约的ABI
bin:合约bin,暂无使用,可以直接传入空字符串””
TransactionDecoder buildTransactionDecoder(String contractName);
contractName:合约名称,在应用的根目录下创建
solidity
目录,将交易相关的合约放在solidity
目录,通过指定合约名获取交易解析对象
交易解析对象TransactionDecoder
接口列表:
String decodeInputReturnJson(String input)
解析input,将结果封装为json字符串,json格式
{"data":[{"name":"","type":"","data":} ... ],"function":"","methodID":""}
function : 函数签名字符串 methodID : 函数选择器
List<ResultEntity> decodeInputReturnObject(String input)
解析input,返回java List对象,ResultEntity结构
public class ResultEntity { private String name; // 字段名称, 解析output返回时,值为空字符串 private String type; // 字段类型 private Object data; // 字段值 }
String decodeOutputReturnJson(String input, String output)
解析output,将结果封装为json字符串,格式同
decodeInputReturnJson
List<ResultEntity> decodeOutputReturnObject(String input, String output)
解析output,返回java List对象
String decodeEventReturnJson(List<Log> logList)
解析event列表,将结果封装为json字符串,json格式
{"event1签名":[[{"name":"_u","type":"","data":}...]...],"event2签名":[[{"name":"_u","type":"","data":}...]...]...}
Map<String, List<List<ResultEntity>>> decodeEventReturnObject(List<Log> logList)
解析event列表,返回java Map对象,key为event签名字符串,List<List
>为交易中所有的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 = TransactionDecoder.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 章鱼小丸子ljjkl;adjsfkljlkjl sadfljkjkljkl ]
// function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs)
String input = “0x406d373b000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000”;
String jsonResult = txDecodeSampleDecoder.decodeInputReturnJson(input);
List<ResultEntity> listResult = txDecodeSampleDecoder.decodeInputReturnObject(input);
System.out.println("json => \n" + jsonResult);
System.out.println("list => \n" + listResult);
输出:
json =>
{
"data": [
{
"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": "章鱼小丸子ljjkl;adjsfkljlkjl"
},
{
"name": "_bs",
"type": "bytes",
"data": "sadfljkjkljkl"
}
],
"function": "echo(uint256,int256,bool,address,bytes32,string,bytes)",
"methodID": "0x406d373b"
}
list =>
[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=章鱼小丸子ljjkl;adjsfkljlkjl], ResultEntity [name=_bs, type=bytes, data=sadfljkjkljkl]]
解析output¶
调用function echo(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs)
接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji 章鱼小丸子ljjkl;adjsfkljlkjl sadfljkjkljkl ]
,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 = "000000000000000000000000000000000000000000000000000000000001b207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef0bb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000692a70d2e424a56d2c6c27aa97d1a86395877b3a6162636465666768696162636465666768696162636465666768696162686a6900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000021e7aba0e9b1bce5b08fe4b8b8e5ad906c6a6a6b6c3b61646a73666b6c6a6c6b6a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d736164666c6a6b6a6b6c6a6b6c00000000000000000000000000000000000000";
String jsonResult = txDecodeSampleDecoder.decodeOutputReturnJson(input, output);
List<ResultEntity> listResult = txDecodeSampleDecoder.decodeOutputReturnObject(input, output);
System.out.println("json => \n" + jsonResult);
System.out.println("list => \n" + listResult);
结果:
json =>
[
{
"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": "章鱼小丸子ljjkl;adjsfkljlkjl"
},
{
"name": "",
"type": "bytes",
"data": "sadfljkjkljkl"
}
]
list =>
[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=章鱼小丸子ljjkl;adjsfkljlkjl], ResultEntity [name=, type=bytes, data=sadfljkjkljkl]]
解析event logs¶
调用function do_event(uint256 _u,int256 _i,bool _b,address _addr,bytes32 _bs32, string _s,bytes _bs)
接口,输入参数为[ 111111 -1111111 false 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a abcdefghiabcdefghiabcdefghiabhji 章鱼小丸子ljjkl;adjsfkljlkjl sadfljkjkljkl ]
,解析交易中的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);
结果:
jsonResult =>
{
"Event1(uint256,int256,bool,address,bytes32,string,bytes)": [
[
{
"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": "章鱼小丸子ljjkl;adjsfkljlkjl"
},
{
"name": "_bs",
"type": "bytes",
"data": "sadfljkjkljkl"
}
]
],
"Event2(uint256,int256,bool,address,bytes32,string,bytes)": [
[
{
"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": "章鱼小丸子ljjkl;adjsfkljlkjl"
},
{
"name": "_bs",
"type": "bytes",
"data": "sadfljkjkljkl"
}
]
]
}
map =>
{"Event1(uint256,int256,bool,address,bytes32,string,bytes)":[[{"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":"章鱼小丸子ljjkl;adjsfkljlkjl"},{"name":"_bs","type":"bytes","data":"sadfljkjkljkl"}]],"Event2(uint256,int256,bool,address,bytes32,string,bytes)":[[{"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":"章鱼小丸子ljjkl;adjsfkljlkjl"},{"name":"_bs","type":"bytes","data":"sadfljkjkljkl"}]]}
安装¶
本章将介绍使用FISCO BCOS所需的必要安装和配置。本章通过在单机上部署一条4节点的FISCO BCOS联盟链,帮助用户掌握FISCO BCOS部署流程。
单群组FISCO BCOS联盟链的搭建¶
本节以搭建单群组FISCO BCOS链为例操作。使用build_chain.sh
脚本在本地搭建一条4节点的FISCO BCOS链,以Ubuntu 16.04
系统为例操作。
注解
- 搭建多群组的链操作类似,感兴趣可以 参考这里 。
- 本节使用预编译的静态`fisco-bcos`二进制文件,在CentOS 7和Ubuntu 16.04上经过测试。
准备环境¶
- 安装依赖
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/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\." | sort -u | tail -n 1 | cut -d \" -f 4`/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
注解
- 其中-p选项指定起始端口,分别是p2p_port,channel_port,jsonrpc_port,出于安全考虑jsonrpc/channel默认监听127.0.0.1,需要外网访问请添加-i参数。
如果命令执行成功会输出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] 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 : /home/ubuntu16/fisco/nodes
[INFO] CA Key Path : /home/ubuntu16/fisco/nodes/cert/ca.key
==============================================================
[INFO] All completed. Files in /home/ubuntu16/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/fisco/fisco/nodes/127.0.0.1/node0/../fisco-bcos -c config.ini
fisco 5459 1 1 17:11 pts/0 00:00:02 /home/fisco/fisco/nodes/127.0.0.1/node1/../fisco-bcos -c config.ini
fisco 5464 1 1 17:11 pts/0 00:00:02 /home/fisco/fisco/nodes/127.0.0.1/node2/../fisco-bcos -c config.ini
fisco 5476 1 1 17:11 pts/0 00:00:02 /home/fisco/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,size=3
info|2019-01-21 17:31:08.316922| [P2P][Service] heartBeat connected count,size=3
info|2019-01-21 17:31:18.317105| [P2P][Service] heartBeat connected count,size=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节点,实现查询区块链状态、部署调用合约等功能,能够快速获取到所需要的信息。控制台指令详细介绍参考这里。
准备依赖¶
# 回到fisco目录
$ cd ~/fisco
# 安装openjdk macOS执行 brew cask install java 安装java
$ sudo apt install -y default-jdk
# 获取控制台
$ bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/download_console.sh)
# 拷贝控制台配置文件,若节点未采用默认端口,请将文件中的20200替换成其他端口
$ cp -n console/conf/applicationContext-sample.xml console/conf/applicationContext.xml
# 配置控制台证书
$ cp nodes/127.0.0.1/sdk/* console/conf/
启动控制台¶
# 回到控制台目录
$ cd ~/fisco/console
# 启动控制台
$ ./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]> getNodeVersion
{
"Build Time":"20190121 06:21:05",
"Build Type":"Linux/clang/Debug",
"FISCO-BCOS Version":"2.0.0",
"Git Branch":"master",
"Git Commit Hash":"c213e033328631b1b8c2ee936059d7126fd98d1a"
}
# 获取节点链接信息
[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合约已经内置于控制台中,位于控制台目录下solidity/contracts/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
注: 部署合约还可以通过deployByCNS
命令,可以指定部署的合约版本号,使用方式参考这里。调用合约通过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检查交易回执,判定交易是否完成。
和“写操作”的交易对应,还有一种”只读”调用方式,用于读取链上数据,这种方式构建请求方式和交易类似,但采用的是call方法调用,节点收到后会根据请求的参数访问状态信息并返回,并不会将请求加入共识流程,也不会导致修改链上的数据。
账户¶
在采用账户模型设计的区块链系统里,账户这个术语代表着用户、智能合约的唯一性存在。
在采用公私钥体系的区块链系统里,用户创建一个公私钥对,经过hash等算法换算即得到一个唯一性的地址串,代表这个用户的账户,用户用该私钥管理这个账户里的资产。用户账户在链上不一定有对应的存储空间,而是由智能合约管理用户在链上的数据,因此这种用户账户也会被称为“外部账户”。
对智能合约来说,一个智能合约被部署后,在链上就有了一个唯一的地址,也称为合约账户,指向这个合约的状态位、二进制代码、相关状态数据的索引等。智能合约运行过程中,会通过这个地址加载二进制代码,根据状态数据索引去访问世界状态存储里对应的数据,根据运行结果将数据写入世界状态存储,更新合约账户里的状态数据索引。智能合约被注销时,主要是更新合约账户里的合约状态位,将其置为无效,一般不会直接清除该合约账户的实际数据。
世界状态¶
FISCO BCOS采用“账户模型”的设计,即除了区块和交易的存储空间外,还会有一块保存智能合约运行结果的存储空间。智能合约执行过程产生的状态数据,经过共识机制确认,分布式的保存在各节点上,数据全局一致,可验证难篡改,所以称为“世界状态”。
状态存储空间的存在,使得区块链上可以保存各种丰富的数据,包括用户账户信息如余额等,智能合约二进制码,智能合约运行结果等相关的各种数据,智能合约执行过程中会从状态存储中获取一些数据参与运算,为实现复杂的合约逻辑提供了基础。
另一方面,维护状态数据需要付出不少存储成本,随着链的持续运行,状态数据会持续膨胀,如采用复杂的数据结构如帕特里夏树(Patricia Tree),状态数据的的容量会进一步扩大,根据不同的场景需要,可对状态数据进行裁剪优化,或采用分布式数据仓库等方案存储,以支持更海量的状态数据容量。
共识机制¶
共识机制是区块链领域的核心概念,无共识,不区块链。区块链作为一个分布式系统,可以由不同的节点共同参与计算、共同见证交易的执行过程,并确认最终计算结果。协同这些松散耦合、互不信任的参与者达成信任关系,并保障一致性,持续性协作的过程,可以抽象为“共识”过程,所牵涉的算法和策略统称为共识机制。
节点¶
安装了区块链系统所需软硬件,加入到区块链网络里的计算机,可以称为一个“节点”。节点参与到区块链系统的网络通信、逻辑运算、数据验证,验证和保存区块、交易、状态等数据,并对客户端提供交易处理和数据查询的接口。节点的标识采用公私钥机制,生成一串唯一的NodeID,以保证它在网络上的唯一性。
根据对计算的参与程度和数据的存量,节点可分为共识节点、观察节点、轻节点等类型,共识节点会参与到整个共识过程,做为记账者打包区块、做为验证者验证区块以完成共识过程。观察节点不参与共识,同步数据,进行验证并保存,可以做为数据服务者提供服务。轻节点只同步区块头以及少量的交易和状态数据,为某一次在线交易或数据查询提供验证。
共识算法¶
共识算法需要解决的几个核心问题是:
- 选出在整个系统中具有记账权的角色,做为leader发起一次记账。
- 参与者采用不可否认和不能篡改的算法,进行多层面验证后,采纳Leader给出的记账。
- 通过数据同步和分布式一致性协作,保证所有参与者最终收到的结果都是一致的,无错的。
区块链领域常见的共识算法有公链常用的工作量证明(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)设计,一个参考设计是将角色分为运营管理者,交易操作员,应用开发者,运维管理者,监管方,每个角色还可以根据需要细分层级,完备的模型可能会很庞大复杂,可以根据场景需要进行适当的设计,能达到业务安全可控的程度即可。
隐私保护¶
基于区块链架构的业务场景要求各参与方都输出和共享相关数据,以共同计算和验证,在复杂的商业环境中,机构希望自己的商业数据受控,在越来越被重视的个人数据隐私保护的形势下,个人对隐私保护的诉求也日益增强。如何对共享的数据牵涉隐私的部分进行保护,以及在避免运作过程泄漏隐私,是一个很重要的问题。
隐私保护首先是个管理问题,要求在构建系统开展业务时,把握“最小授权,明示同意的原则”,对数据的收集、存储、应用、披露、删除、恢复全生命周期进行管理,建立日常管理和应急管理制度,在高敏感业务场景设定监管角色,引入第三方检视和审计,从事先事中事后全环节进行管控。
在技术上,可以采用数据脱敏,业务隔离或者系统物理隔离等方式控制数据分发范围,同时也可以引入密码学方法如零知识证明、安全多方计算、环签名、群签名、盲签名等,对数据进行高强度的加密保护。
物理隔离¶
这个概念主要用于隐私保护领域,“物理隔离”是避免隐私数据泄露的彻底手段,物理隔离指只有共享数据的参与者在网络通信层互通,不参与共享数据的参与者在网络互相都不能通信,不交换哪怕一个字节的数据。
相对而言的是逻辑隔离,参与者可以接收到和自己无关的数据,但数据本身带上权限控制或加密保护,使得没有授权或密钥的参与者不能访问和修改。但随着技术的发展,所受到的权限受控数据或加密数据在若干年后依旧有可能被破解。
对极高敏感性的数据,可以采用“物理隔离”的策略,从根源上杜绝被破解的可能性。相应的成本是需要仔细甄别数据的敏感级别,对隔离策略进行周密的规划,并分配足够的硬件资源承载不同的数据。
治理与监管¶
联盟链治理¶
联盟链治理牵涉到多参与方协调工作,激励机制,安全运营,监管审计等一系列的问题,核心是理清各参与方的责权利,工作流程,构建顺畅的开发和运维体系,以及保障业务的合法合规,对包括安全性在内的问题能事先防范事后应急处理。为达成治理,需要制定相关的规则且保证各参与方达成共识并贯彻执行。
一个典型的联盟链治理参考模型是各参与方共同组建联盟链委员会,共同讨论和决议,根据场景需要设定各种角色和分配任务,如某些机构负责开发,某些机构参与运营管理,所有机构参与交易和运维,采用智能合约实现管理规则和维护系统数据,委员会和监管机构可掌握一定的管理权限,对业务、机构、人员进行审核和设置,并在出现紧急情况时,根据事先约定的流程,通过共识过的智能合约规则,进行应急操作,如账户重置,业务调整等,在需要进行系统升级时,委员会负责协调各方进行系统更新。
在具备完善治理机制的联盟链上,各参与方根据规则进行点对点的对等合作,包r括资产交易、数据交换,极大程度提升运作效率,促进业务创新,同时合规性和安全性等方面也得到了保障。
快速部署¶
构建一个区块链系统的大致步骤包括:获取硬件资源包括服务器、网络、内存、硬盘存储等,进行环境配置包括选择指定操作系统、开通网络端口和相关策略、带宽规划、存储空间分配等,获取区块链二进制可运行软件或者从源码进行编译,然后进行区块链系统的配置,包括创世块配置、运行时参数配置,日志配置等,进行多方互联配置,包括节点准入配置、端口发现、共识参与方列表等,客户端和开发者工具配置,包括控制台、SDK等,这个过程会包括许多细节,如各种证书和公私钥的管理等,很容易出现环境、版本、配置的差错,导致整个过程复杂、繁琐和反复,形成了较高的使用门槛。
如何将以上步骤简化和加速,使构建和组链过程变得简便,快速,不容易出错,且低成本,需要从以下几方面进行考虑: 首先,标准化目标部署平台,事先将操作系统、依赖软件列表、网络带宽和存储容量、网络策略等关键的软硬件准备好,对齐版本和参数,使得平台可用,依赖完备。当下流行的云服务,docker等方式都可以帮助构建这样的标准化平台。
然后,从使用者的视角出发,优化区块链软件的构建、配置和组链流程,提供快速构建,自动组链的工具,使得使用者不需要关注诸多细节,简单的几步操作即可运行起供开发调试、上线运行的链。
FISCO BCOS非常重视使用者的部署体验,提供了一键部署的命令行,帮助开发者快速搭建开发调试环境,提供企业级搭链工具,面向多机构联合组链的场景,灵活的进行主机、网络等参数配置,管理相关的证书,便于多个企业之间协同工作。经过快速部署的优化,将使用者搭起区块链的时间缩短到几分钟到半小时以内。
数据治理¶
区块链强调数据层层验证,历史记录可追溯,常见的方案是从创世块以来,所有的数据都会保存在所有的参与节点上(轻节点之外),导致的结果是数据膨胀,容量紧张,尤其是在承载海量服务的场景里,在一定时间之后,一般的存储方案已经无法容纳数据,而海量存储成本很高,另一个角度是安全性,全量数据永久保存,可能面临历史数据泄露的风险,所以需要在数据治理方面进行设计。
数据治理主要是几个策略:裁剪迁移,平行扩容,分布式存储。如何选择需要结合场景分析。
对具有较强时间特征的数据,如某业务的清结算周期是一个星期,那么一个星期前的数据不需要参与在线计算和验证,旧的数据则可以从节点迁移到大数据存储里,满足数据可查询可验证的需求以及业务保存年限的要求,线上节点的数据压力大幅降低,历史数据离线保存,在安全策略上也可以进行更严密的保护。
对规模持续扩大的业务,如用户数或合同存证量剧增,可以针对不同的用户和合同,分配到不同的逻辑分区,每个逻辑分区有独立的存储空间,只承载一定容量的数据,当接近容量的上限,则再分配更多资源容纳新的数据。分区的的设计使得在资源调配,成本管理上都更容易把控。
结合数据裁剪迁移和平行扩容,数据的容量成本,安全级别都得到很好的控制,便于开展海量规模的业务。
运维监控¶
区块链系统从构建和运行逻辑上都具有较高一致性,不同节点的软硬件系统基本一致。其标准化特性给运维人员带来了便利,可使用通用的工具、运维策略和运维流程等对区块链系统进行构建、部署、配置、故障处理,从而降低运维成本以及提升效率。
运维人员对联盟链的操作会被权限系统控制,运维人员有修改系统配置、启停进程、查看运行日志、排查故障等权限,但不参与到业务交易中,也不能直接查看具有较高安全隐私等级的用户数据,交易数据。
系统运行过程中,可通过监控系统对各种运行指标进行监控,对系统的健康程度进行评估,当出现故障时发出告警通知,便于运维快速反应,进行处理。
监控的维度包括基础环境监控,如CPU占比、系统内存占比和增长、磁盘IO情况、网络连接数和流量等。
区块链系统监控包括如区块高度、交易量和虚拟机计算量,共识节点出块投票情况等。
接口监控包括如接口调用计数、接口调用耗时情况、接口调用成功率等。
监控数据可以通过日志或网络接口进行输出,便于和机构的现有的监控系统进行对接,复用机构的监控能力和既有的运维流程。运维人员收到告警后,采用联盟链提供的运维工具,查看系统信息、修改配置、启停进程、处理故障等。
监管审计¶
随着区块链技术和业务形态探索的发展,需要在区块链技术平台上提供支持监管的功能,避免区块链系统游离于法律法规以及行业规则之外,成为洗钱、非法融资或犯罪交易的载体。
审计功能主要用于满足区块链系统的审计内控、责任鉴定和事件追溯等要求,需要以有效的技术手段,配合业务所属的行业标准进行精确的审计管理。
监管者可以做为节点接入到区块链系统里,或者通过接口和区块链系统进行交互,监管者可同步到所有的数据进行审计分析,跟踪全局的业务流程,如发现异常,可以向区块链发出具备监管权限的指令,对业务、参与人、账户等进行管控,实现“穿透式监管”。
FISCO BCOS在角色和权限设计,功能接口,审计工具等方面都对监管审计进行了支持。
构建第一个区块链应用¶
本章将会介绍一个基于FISCO BCOS区块链的业务应用场景开发全过程,从业务场景分析,到合约的设计实现,然后介绍合约编译以及如何部署到区块链,最后介绍一个应用模块的实现,通过我们提供的Web3SDK实现对区块链上合约的调用访问。
本教程要求用户熟悉Linux操作环境,具备Java开发的基本技能,能够使用Gradle工具,熟悉Solidity语法。通过学习教程,你将会了解到以下内容:
- 如何将一个业务场景的逻辑用合约的形式表达
- 如何将Solidity合约转化成Java类
- 如何配置Web3SDK
- 如何构建一个应用,并集成Web3SDK到应用工程
- 如何通过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.java
和Table.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
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 // 项目配置文件
| |-- ca.crt // 区块链ca证书
| |-- node.crt // 区块链ca证书
| |-- node.key // 节点证书
| |-- 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.0.3')
证书与配置文件¶
- 区块链节点证书配置
拷贝区块链节点对应的SDK证书
# 进入~目录
# 拷贝节点证书到项目的资源目录
$ cd ~
$ cp fisco/nodes/127.0.0.1/sdk/* asset-app/src/test/resources/
- applicationContext.xml
注意: 如果搭链时设置的rpc_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 succesfully, contract address is 0xd09ad04220e40bb8666e885730c8c460091a4775
- 注册资产
$ bash asset_run.sh register Alice 100000
Register account succesfully => account: Alice, value: 100000
$ bash asset_run.sh register Bob 100000
Register account succesfully => 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联盟区块链的应用。
部署多群组架构¶
本章主要以星形组网和并行多组组网拓扑为例,指导您了解如下内容:
- 了解如何使用
build_chain.sh
创建多群组区块链安装包; - 了解
build_chain.sh
创建的多群组区块链安装包目录组织形式; - 学习如何启动该区块链节点,并通过日志查看各群组共识状态;
- 学习如何向各群组发送交易,并通过日志查看群组出块状态;
- 了解群组内节点管理,包括节点入网、退网等;
- 了解如何新建群组。
重要
- build_chain.sh适用于开发者和体验者快速搭链使用,不支持扩容操作
- 搭建企业级业务链,推荐使用 企业搭链工具
星形拓扑和并行多组¶
如下图,星形组网拓扑和并行多组组网拓扑是区块链应用中使用较广泛的两种组网方式。
- 星形拓扑:中心机构节点同时属于多个群组,运行多家机构应用,其他每家机构属于不同群组,运行各自应用;
- 并行多组:区块链中每个节点均属于多个群组,可用于多方不同业务的横向扩展,或者同一业务的纵向扩展。
下面以构建七节点星形拓扑和四节点并行多组区块链为例,详细介绍多群组操作方法。
安装依赖¶
部署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/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\." | sort -u | tail -n 1 | cut -d \" -f 4`/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.sh
和stop_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
查看群组共识状态
不发交易时,共识正常的节点会输出+++
日志,本例中,node0
、node1
同时属于group1
、group2
和group3
;node2
、node3
属于group1
;node4
、node5
属于group2
;node6
、node7
属于group3
,可通过tail -f node*/log/* | grep "++"
查看各节点是否正常。
重要
- 节点正常共识打印
+++
日志,+++
日志字段含义: g:
:群组IDblkNum
: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以上版本。
#回到fisco目录
$ cd ~/fisco
# 获取控制台
$ bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/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:
:群组IDnum
:出块高度;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/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\." | 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" -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 group.2.genesis
# 修改群组ID
$ sed -i "s/id=1/id=2/g" group.2.genesis
$ cat group.2.genesis | grep "id"
# 已修改到 id=2
# 将配置拷贝到各个节点
$ 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
# 重启各个节点
$ 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
# 获取控制台
$ bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/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
使用企业级部署工具¶
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群组的组网模式,演示企业间如何相互配合,搭建区块链。
机器环境¶
每个节点的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 | |
机构B | 节点2 | 群组1 | 127.0.0.1:30302 | 127.0.0.1:8547/:20202 |
节点3 | 群组1 | 27.0.0.1:30303 | 127.0.0.1:8548/:20203 | |
机构C | 节点4 | 群组2 | 127.0.0.1:30304 | 127.0.0.1:8549/:20204 |
节点5 | 群组2 | 127.0.0.1:30305 | 127.0.0.1:8550/:20205 |
重要
针对云服务器中的vps服务器,RPC监听地址需要写网卡中的真实地址(如内网地址或127.0.0.1),可能与用户登录的ssh服务器不一致。
关键流程¶
本流程简要的给出证书颁发机构,节点机构间如何相互配合搭建区块链。
一、初始化链证书¶
- 证书颁发机构操作:
- 生成链证书
二、生成群组1¶
- 证书颁发机构操作:颁发机构证书
- 生成机构证书
- 发送证书
- 机构间独立操作
- 修改配置文件
node_deployment.ini
- 生成节点证书及节点P2P端口地址文件
- 修改配置文件
- 选取其中一个机构为群组生成创世块
- 收集群组内所有节点证书
- 修改配置文件
group_genesis.ini
- 为群组生成创世块文件
- 分发创世块文件
- 机构间独立操作:生成节点
- 收集群组其他节点的P2P端口地址文件
- 生成节点
- 启动节点
三、初始化新机构¶
- 证书颁发机构操作:颁发新机构证书
- 生成机构证书
- 发送证书
四、生成群组2¶
- 新机构独立操作
- 修改配置文件
node_deployment.ini
- 生成节点证书及节点P2P端口地址文件
- 修改配置文件
- 选取其中一个机构为群组生成创世块
- 收集群组内所有节点证书
- 修改配置文件
group_genesis.ini
- 为群组生成创世块文件
- 分发创世块文件
- 新机构独立操作:生成节点
- 收集群组其他节点的P2P端口地址文件
- 生成节点
- 启动节点
- 已有机构操作:配置新群组
- 收集群组其他节点的P2P端口地址文件
- 配置新群组与新增节点的P2P端口地址
- 重启节点
五、现有节点加入群组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 cert.cnf
机构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-agency.crt ca.crt cert.cnf
发送链证书、机构证书、机构私钥至机构A,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下
cp ./dir_chain_ca/ca.crt ./dir_agency_ca/agencyA/agency.crt ./dir_agency_ca/agencyA/agency.key ~/generator-A/meta/
初始化机构B¶
在证书生成机构目录下操作:
cd ~/generator
生成机构B证书:
./generator --generate_agency_certificate ./dir_agency_ca ./dir_chain_ca agencyB
发送链证书、机构证书、机构私钥至机构B,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下
cp ./dir_chain_ca/ca.crt ./dir_agency_ca/agencyB/agency.crt ./dir_agency_ca/agencyB/agency.key ~/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
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
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
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
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.crt
,cert_127.0.0.1_30301.crt
,cert_127.0.0.1_30302.crt
,cert_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的操作:
证书授权机构初始化机构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-agency.crt ca.crt cert.cnf
发送链证书、机构证书、机构私钥至机构A,示例是通过文件拷贝的方式,从证书授权机构将机构证书发送给对应的机构,放到机构的工作目录的meta子目录下
cp ./dir_chain_ca/ca.crt ./dir_agency_ca/agencyC/agency.crt ./dir_agency_ca/agencyC/agency.key ~/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
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
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构建:
扩展教程–机构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,教程中以控制台为例:
注意:此命令会根据用户配置的node_deployment.ini
中节点及群组完成了控制台的配置,用户可以直接启动控制台,启动前请确保已经安装java
./generator --download_console ./
查看机构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
至此 我们完成了所示构建教程中的所有操作。
通过本节教程,我们在本机生成一个网络拓扑结构为3机构2群组6节点的多群组架构联盟链。
如果使用该教程遇到问题,请查看FAQ
创建和管理账户¶
FISCO BCOS使用账户来标识和区分每一个独立的用户。在采用公私钥体系的区块链系统里,每一个账户对应着一对公钥和私钥。其中,由公钥经哈希等安全的单向性算法计算后得到地址字符串被用作该账户的账户名,即账户地址,为了与智能合约的地址相区别和一些其他的历史原因,账户地址也常被称之外部账户地址。而仅有用户知晓的私钥则对应着传统认证模型中的密码。用户需要通过安全的密码学协议证明其知道对应账户的私钥,来声明其对于该账户的所有权,以及进行敏感的账户操作。
重要
在之前的其他教程中,为了简化操作,使用了工具提供的默认的账户进行操作。但在实际应用部署中,用户需要创建自己的账户,并妥善保存账户私钥,避免账户私钥泄露等严重的安全问题。
本文将具体介绍账户的创建、存储和使用方式,要求阅读者有一定的Linux操作基础。
FISCO BCOS提供了脚本和Web3SDK用以创建账户,同时也提供了Web3SDK和控制台来存储账户私钥。用户可以根据需求选择将账户私钥存储为PEM或者PKCS12格式的文件。其中,PEM格式使用明文存储私钥,而PKCS12使用用户提供的口令加密存储私钥。
账户的创建¶
使用脚本创建账户¶
1. 获取脚本¶
curl -LO https://media.githubusercontent.com/media/FISCO-BCOS/LargeFiles/master/tools/get_account.sh && chmod u+x get_account.sh && bash get_account.sh -h
执行上面的指令,看到如下输出则下载到了正确的脚本,否则请重试。
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
使用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 = Credentials.create(p12KeyPair);
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 = Credentials.create(pemKeyPair);
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
使用手册¶
本章提供了FISCO BCOS平台的使用手册,使用手册介绍FISCO BCOS平台各种功能使用方式。
获取可执行程序¶
用户可以自由选择以下任一方式获取FISCO BCOS可执行程序。推荐从GitHub下载预编译二进制。
- 官方提供的静态链接的预编译文件,可以在Ubuntu 16.04和CentOS 7.2以上版本运行。
- 官方提供docker镜像,欢迎使用。docker-hub地址
- 源码编译获取可执行程序,参考源码编译。
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构建系统生成特定平台的构建文件,这意味着无论您使用什么操作系统工作流都非常相似:
- 安装构建工具和依赖包(依赖于平台)。
- 从FISCO BCOS克隆代码。
- 运行
cmake
生成构建文件并编译。
安装依赖¶
- Ubuntu
推荐Ubuntu 16.04以上版本,16.04以下的版本没有经过测试,源码编译时依赖于编译工具和libssl
。
$ sudo apt install -y g++ libssl-dev openssl cmake git build-essential autoconf texinfo
- CentOS
推荐使用CentOS7以上版本。
$ sudo yum install -y epel-release
$ sudo yum install -y openssl-devel openssl cmake3 gcc-c++ git
- macOS
推荐xcode10以上版本。macOS依赖包安装依赖于Homebrew。
$ brew install openssl git
克隆代码¶
$ 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
# CentOS请使用cmake3
$ cmake ..
# 高性能机器可添加-j4使用4核加速编译
$ make
编译选项介绍¶
BUILD_GM,默认off,国密编译开关。通过
cmake -DBUILD_GM=on ..
打开国密开关。TESTS,默认off,单元测试编译开关。通过
cmake -DTESTS=on ..
打开单元测试开关。DEMO,默认off,测试程序编译开关。通过
cmake -DDEMO=on ..
打开单元测试开关。BUILD_STATIC,默认off,静态编译开关,只支持Ubuntu。通过
cmake -DBUILD_STATIC=on ..
打开静态编译开关。生成源码文档。
# 安装Doxygen $ sudo apt install -y doxygen graphviz # 生成源码文档 生成的源码文档位于build/doc $ make doc
建链脚本¶
重要
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
和-i
选项,-T
开启log级别到DEBUG,-i
设置RPC和channel监听0.0.0.0
,p2p模块默认监听0.0.0.0
。
帮助¶
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". eg "127.0.0.1:4 agency1 1,2"
-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
-i <Host ip> Default 127.0.0.1. If set -i, listen 0.0.0.0
-v <FISCO-BCOS binary version> Default get version from https://github.com/FISCO-BCOS/FISCO-BCOS/releases. If set, use specificd version binary
-s <DB type> Default rocksdb. Options can be rocksdb / mysql / external, rocksdb is recommended
-d <docker mode> Default off. If set -d, build with docker
-c <Consensus Algorithm> Default PBFT. If set -c, use Raft
-m <MPT State type> Default storageState. if set -m, use mpt state
-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
-T <Enable debug log> Default off. If set -T, enable debug log
-F <Disable log auto flush> Default on. If set -F, disable log auto flush
-h Help
e.g
./tools/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:2 agency1 1,2
192.168.0.1:2 agency1 1,3
192.168.0.2:3 agency2 1
192.168.0.3:5 agency3 2,3
192.168.0.4:2 agency2 3
假设上述文件名为ipconf
,则使用下列命令建链,表示使用配置文件,设置日志级别为DEBUG
,监听0.0.0.0
。
$ bash build_chain.sh -f ipconf -T -i
e
选项[Optional] 用于指定fisco-bcos
二进制所在的完整路径,脚本会将fisco-bcos
拷贝以IP为名的目录下。不指定时,默认从GitHub下载master
分支最新的二进制程序。
# 从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
i
选项[Optional] 无参数选项,设置该选项时,设置节点的RPC和channel监听0.0.0.0
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 run -d --rm --name ${nodePath} -v ${nodePath}:/data --network=host -w=/data fiscoorg/fiscobcos:latest -c config.ini
m
选项[Optional] 无参数选项,设置该选项时,节点使用mptstate存储合约局部变量,默认使用storagestate存储合约局部变量。s
选项[Optional] 有参数选项,参数为db名,目前支持rocksdb、mysql、external三种模式。默认使用RocksDB。其中mysql需要在群组ini文件中配置mysql相关信息,external需要配置topic信息并启动AMDB代理。c
选项[Optional] 无参数选项,设置该选项时,设置节点的共识算法为Raft,默认设置为PBFT。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。使用g
选项时要求二进制fisoc-bcos为国密版本。z
选项[Optional] 无参数选项,设置该选项时,生成节点的tar包。t
选项[Optional] 该选项用于指定生成证书时的证书配置文件。T
选项[Optional] 无参数选项,设置该选项时,设置节点的log级别为DEBUG。log相关配置参考这里。
节点文件组织结构¶
- cert文件夹下存放链的根证书和机构证书。
- 以IP命名的文件夹下存储该服务器所有节点相关配置、
fisco-bcos
可执行程序、SDK所需的证书文件。 - 每个IP文件夹下的
node*
文件夹下存储节点所需的配置文件。其中config.ini
为节点的主配置,conf
目录下存储证书文件和群组相关配置。配置文件详情,请参考这里。每个节点中还提供start.sh
和stop.sh
脚本,用于启动和停止节点。 - 每个IP文件夹下的提供
start_all.sh
和stop_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需要用到的
│ │ ├── ca.crt # 链根证书
│ │ ├── node.crt # SKD所需的证书文件,建立连接时使用
│ │ └── node.key # SDK所需的私钥文件,建立连接时使用
├── cert # 证书文件夹
│ ├── agency # 机构证书文件夹
│ │ ├── agency.crt # 机构证书
│ │ ├── agency.key # 机构私钥
│ │ ├── agency.srl
│ │ ├── ca-agency.crt
│ │ ├── ca.crt
│ │ └── cert.cnf
│ ├── ca.crt # 链证书
│ ├── ca.key # 链私钥
│ ├── ca.srl
│ └── cert.cnf
使用举例¶
单服务器单群组¶
构建本机上4节点的FISCO BCOS联盟链,使用默认起始端口30300,20200,8545
(4个节点会占用30300-30303
,20200-20203
,8545-8548
),监听外网Channel
和jsonrpc
端口,允许外网通过SDK或API与节点交互。
# 构建FISCO BCOS联盟链
$ bash build_chain.sh -l "127.0.0.1:4" -i
# 生成成功后,输出`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 : 0.0.0.0
[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
证书说明¶
FISCO-BCOS网络采用面向CA的准入机制,支持任意多级的证书结构,保障信息保密性、认证性、完整性、不可抵赖性。
FISCO BCOS使用x509协议的证书格式,根据现有业务场景,默认采用三级的证书结构,自上而下分别为链证书、机构证书、节点证书。
在多群组架构中,一条链拥有一个链证书及对应的链私钥,链私钥由联盟链委员会共同管理。联盟链委员会可以使用机构的证书请求文件agency.csr
,签发机构证书agency.crt
。
机构私钥由机构管理员持有,可以对机构下属节点签发节点证书。
节点证书是节点身份的凭证,用于与其他持有合法证书的节点间建立SSL连接,并进行加密通讯。
sdk证书是sdk与节点通信的凭证,机构生成sdk证书,允许sdk与节点进行通信。
FISCO BCOS节点运行时的文件后缀介绍如下:
后缀 | 说明 |
---|---|
.key | 私钥文件 |
.crt | 证书文件 |
.csr | 证书请求文件 |
角色定义¶
FISCO-BCOS的证书结构中,共有四种角色,分别是联盟链委员会管理员、机构、节点和SDK。
联盟链委员会¶
- 联盟链委员会管理链的私钥,并根据机构的证书请求文件
agency.csr
为机构颁发机构证书。
ca.crt 链证书
ca.key 链私钥
FISCO BCOS进行SSL加密通信时,拥有相同链证书ca.crt
的节点才可建立连接。
节点/SDK¶
- FISCO BCOS节点包括节点证书和私钥,用于建立节点间SSL加密连接;
- SDK包括SDK证书和私钥,用于与区块链节点建立SSL加密连接。
ca.crt 链证书
node.crt 节点/SDK证书
node.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颁发证书node.crt
配置文件与配置项¶
FISCO BCOS支持多账本,每条链包括多个独立账本,账本间数据相互隔离,群组间交易处理相互隔离,每个节点包括一个主配置config.ini
和多个账本配置group.group_id.genesis
、group.group_id.ini
。
config.ini
:主配置文件,主要配置RPC、P2P、SSL证书、账本配置文件路径、兼容性等信息。group.group_id.genesis
:群组配置文件,群组内所有节点一致,节点启动后,不可手动更改该配置。主要包括群组共识算法、存储类型、最大gas限制等配置项。group.group_id.ini
:群组可变配置文件,包括交易池大小等,配置后重启节点生效。
硬件要求¶
注解
由于节点多群组共享网络带宽、CPU和内存资源,因此为了保证服务的稳定性,一台机器上不推荐配置过多节点。
下表是单群组单节点推荐的配置,节点耗费资源与群组个数呈线性关系,您可根据实际的业务需求和机器资源,合理地配置节点数目。
配置 | 最低配置 | 推荐配置 |
---|---|---|
CPU | 1.5GHz | 2.4GHz |
内存 | 1GB | 8GB |
核心 | 1核 | 4核 |
带宽 | 1Mb | 10Mb |
主配置文件config.ini¶
config.ini
采用ini
格式,主要包括 rpc、p2p、group、network_security和log 配置项。
重要
- 云主机的公网IP均为虚拟IP,若listen_ip填写外网IP,会绑定失败,须填写0.0.0.0
- RPC/P2P/Channel监听端口必须位于1024-65535范围内,且不能与机器上其他应用监听端口冲突
配置RPC¶
listen_ip
: 安全考虑,建链脚本默认监听127.0.0.1,如果需要外网访问RPC或外网使用SDK请监听节点的外网IP或0.0.0.0
;channel_listen_port
: Channel端口,对应到Web3SDK配置中的channel_listen_port
;jsonrpc_listen_port
: JSON-RPC端口。
RPC配置示例如下:
[rpc]
listen_ip=127.0.0.1
channel_listen_port=30301
jsonrpc_listen_port=30302
配置P2P¶
当前版本FISCO BCOS必须在config.ini
配置中配置连接节点的IP
和Port
,P2P相关配置包括:
listen_ip
:P2P监听IP,默认设置为0.0.0.0
。listen_port
:节点P2P监听端口。node.*
: 节点需连接的所有节点IP:port
。enable_compress
:开启网络压缩的配置选项,配置为true,表明开启网络压缩功能,配置为false,表明关闭网络压缩功能,网络压缩详细介绍请参考这里。
P2P配置示例如下:
[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
配置账本文件路径¶
[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时需要。
[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,主要配置项如下:
enable
: 启用/禁用日志,设置为true
表示启用日志;设置为false
表示禁用日志,默认设置为true,性能测试可将该选项设置为false
,降低打印日志对测试结果的影响log_path
:日志文件路径。level
: 日志级别,当前主要包括trace
、debug
、info
、warning
、error
五种日志级别,设置某种日志级别后,日志文件中会输大于等于该级别的日志,日志级别从大到小排序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 2.0所有版本向前兼容,可通过config.ini
中的[compatibility]
配置节点的兼容性,改配置项建链时工具会自动生成,用户不需修改。
supported_version
:当前节点运行的版本
重要
- 可通过
./fisco-bcos --version | grep "FISCO-BCOS Version" | cut -d':' -f2 | sed s/[[:space:]]//g
命令查看FISCO BCOS的当前支持的最高版本 - build_chain.sh生成的区块链节点配置中,supported_version配置为FISCO BCOS当前的最高版本
- 旧节点升级为新节点时,直接将旧的FISCO BCOS二进制替换为最新FISCO BCOS二进制即可,
release-2.0.0-rc3
节点的[compatibility]
配置如下:
[compatibility]
supported_version=release-2.0.0-rc3
可选配置:落盘加密¶
为了保障节点数据机密性,FISCO BCOS引入落盘加密保障节点数据的机密性,落盘加密操作手册请参考这里。
config.ini
中的storage_security
用于配置落盘加密,主要包括:
enable
: 是否开启落盘加密,默认不开启;key_manager_ip
:Key Manager服务的部署IP;key_manager_port
:Key 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
群组系统配置说明¶
每个群组都有单独的配置文件,按照启动后是否可更改,可分为群组系统配置和群组可变配置。
群组系统配置一般位于节点的conf
目录下.genesis
后缀配置文件中。
如:group1
的系统配置一般命名为group.1.genesis
,群组系统配置主要包括群组ID、共识、存储和gas相关的配置。
重要
配置系统配置时,需注意:
- 配置群组内一致 :群组系统配置用于产生创世块(第0块),因此必须保证群组内所有节点的该配置一致
- 节点启动后不可更改 :系统配置已经作为创世块写入了系统表,链初始化后不可更改
- 链初始化后,即使更改了genesis配置,新的配置不会生效,系统仍然使用初始化链时的genesis配置
- 由于genesis配置要求群组内所有节点一致,建议使用 build_chain 生成该配置
共识配置¶
[consensus]
涉及共识相关配置,包括:
consensus_type
:共识算法类型,目前支持PBFT和Raft,默认使用PBFT共识算法;max_trans_num
:一个区块可打包的最大交易数,默认是1000,链初始化后,可通过控制台动态调整该参数;node.idx
:共识节点列表,配置了参与共识节点的Node ID,节点的Node ID可通过${data_path}/node.nodeid
文件获取(其中${data_path}
可通过主配置config.ini
的[network_security].data_path
配置项获取)
; 共识协议配置
[consensus]
; 共识算法,目前支持PBFT(consensus_type=pbft)和Raft(consensus_type=raft)
consensus_type=pbft
; 单个块最大交易数
max_trans_num=1000
; leader节点的ID列表
node.0=123d24a998b54b31f7602972b83d899b5176add03369395e53a5f60c303acb719ec0718ef1ed51feb7e9cf4836f266553df44a1cae5651bc6ddf50
e01789233a
node.1=70ee8e4bf85eccda9529a8daf5689410ff771ec72fc4322c431d67689efbd6fbd474cb7dc7435f63fa592b98f22b13b2ad3fb416d136878369eb41
3494db8776
node.2=7a056eb611a43bae685efd86d4841bc65aefafbf20d8c8f6028031d67af27c36c5767c9c79cff201769ed80ff220b96953da63f92ae83554962dc2
922aa0ef50
node.3=fd6e0bfe509078e273c0b3e23639374f0552b512c2bea1b2d3743012b7fed8a9dec7b47c57090fa6dcc5341922c32b89611eb9d967dba5f5d07be7
4a5aed2b4a
状态模式配置¶
state用于存储区块链状态信息,位于genesis文件中[state]
:
type
:state类型,目前支持storage state和MPT state,默认为storage state,storage state将交易执行结果存储在系统表中,效率较高,MPT state将交易执行结果存储在MPT树中,效率较低,但包含完整的历史信息。
重要
推荐使用 storage state ,除有特殊需求,不建议使用MPT 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
账本可变配置说明¶
账本可变配置位于节点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。
公共配置项¶
type
:存储的DB类型,支持RocksDB
、MySQL
和External
。DB类型为RocksDB时,区块链系统所有数据存储于RocksDB本地数据库中;type为MySQL
时,type为external时,节点根据配置访问mysql数据库。区块链系统所有数据存储于mysql数据库中,要配置AMDB代理访问mysql数据库,AMDB代理配置请参考这里。max_capacity
:配置允许节点用于内存缓存的空间大小。max_forward_block
:配置允许节点用于内存区块的大小,当节点还出的区块超出该数值时,节点停止共识等待区块写入数据库。
数据库相关配置项¶
topic
:当type为External
时,需要配置该字段,表示区块链系统关注的AMDB代理topic,详细请参考这里。max_retry
:当type为External
时,需要配置该字段,表示写入失败时的重试次数,详细请参考这里。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 / external, 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将交易池容量配置开放给用户,用户可根据自己的业务规模需求、稳定性需求以及节点的硬件配置动态调整交易池大小。
交易池配置示例如下:
[tx_pool]
limit=150000
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
并行交易配置¶
FISCO BCOS支持交易的并行执行。开启交易并行执行开关,能够让区块内的交易被并行的执行,提高吞吐量,交易并行执行仅在storage state模式下生效。
[tx_execute]
enable_parallel=true
动态配置系统参数¶
FISCO BCOS系统目前主要包括如下系统参数(未来会扩展其他系统参数):
系统参数 | 默认值 | 含义 |
---|---|---|
tx_count_limit | 1000 | 一个区块中可打包的最大交易数目 |
tx_gas_limit | 300000000 | 一个区块最大gas限制 |
控制台提供 setSystemConfigByKey 命令来修改这些系统参数,getSystemConfigByKey 命令可查看系统参数的当前值:
重要
不建议随意修改tx_count_limit和tx_gas_limit,如下情况可修改这些参数:
- 机器网络或CPU等硬件性能有限:调小tx_count_limit,或降低业务压力;
- 业务逻辑太复杂,执行区块时gas不足:调大tx_gas_limit。
# 设置一个区块可打包最大交易数为500
[group:1]> setSystemConfigByKey tx_count_limit 500
# 查询tx_count_limit
[group:1]> getSystemConfigByKey tx_count_limit
[500]
# 设置区块gas限制为400000000
[group:1]> getSystemConfigByKey tx_gas_limit 400000000
[group:1]> getSystemConfigByKey
[400000000]
控制台¶
控制台是FISCO BCOS 2.0重要的交互式客户端工具,它通过Web3SDK与区块链节点建立连接,实现对区块链节点数据的读写访问请求。控制台拥有丰富的命令,包括查询区块链状态、管理区块链节点、部署并调用合约等。此外,控制台提供一个合约编译工具,用户可以方便快捷的将Solidity合约文件编译为Java合约文件。
控制台命令¶
控制台命令由两部分组成,即指令和指令相关的参数:
- 指令: 指令是执行的操作命令,包括查询区块链相关信息,部署合约和调用合约的指令等,其中部分指令调用JSON-RPC接口,因此与JSON-RPC接口同名。 使用提示: 指令可以使用tab键补全,并且支持按上下键显示历史输入指令。
- 指令相关的参数: 指令调用接口需要的参数,指令与参数以及参数与参数之间均用空格分隔,与JSON-RPC接口同名命令的输入参数和获取信息字段的详细解释参考JSON-RPC API。
常用命令链接¶
合约相关命令¶
其他命令¶
- 查询区块高度:getBlockNumber
- 查询共识节点列表:getSealerList
- 查询交易回执信息: getTransactionReceipt
- 切换群组: switch
快捷键¶
Ctrl+A
:光标移动到行首Ctrl+D
:退出控制台Ctrl+E
:光标移动到行尾Ctrl+R
:搜索输入的历史命令- ↑:向前浏览历史命令
- ↓:向后浏览历史命令
控制台响应¶
当发起一个控制台命令时,控制台会获取命令执行的结果,并且在终端展示执行结果,执行结果分为2类:
控制台配置与运行¶
获取控制台¶
$ cd ~ && mkdir -p fisco && cd fisco
# 获取控制台
$ bash <(curl -s https://raw.githubusercontent.com/FISCO-BCOS/console/master/tools/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合约需要引入的Table合约接口
| -- console # 控制台部署合约时编译的合约abi, bin,java文件目录
| -- sdk # sol2java.sh脚本编译的合约abi, bin,java文件目录
|-- start.sh # 控制台启动脚本
|-- get_account.sh # 账户生成脚本
|-- sol2java.sh # solidity合约文件编译为java合约文件的开发工具脚本
|-- replace_solc_jar.sh # 编译jar包替换脚本
合约编译工具¶
控制台提供一个专门的编译合约工具,方便开发者将solidity合约文件编译为java合约文件。 使用该工具,分为两步:
将solidity合约文件放在
contracts/solidity
目录下。通过运行
sol2java.sh
脚本(需要指定一个java的包名)完成编译合约任务。例如,contracts/solidity
目录下已有HelloWorld.sol
、TableTest.sol
、Table.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.java
、TableTest.java
和Table.java
。其中HelloWorld.java
和TableTest.java
是java应用所需要的java合约文件。
注: 下载的控制台其console/lib
目录下包含solcJ-all-0.4.25.jar
,因此支持0.4版本的合约编译。如果使用0.5版本合约编译器或国密合约编译器,请下载相关合约编译器jar包,然后替换console/lib
目录下的solcJ-all-0.4.25.jar
。可以通过./replace_solc_jar.sh
脚本进行替换,指定下载的编译器jar包路径,命令如下:
# 下载solcJ-all-0.5.2.jar放在console目录下,示例用法如下
$ ./replace_solc_jar.sh solcJ-all-0.5.2.jar
下载合约编译jar包¶
0.4版本合约编译jar包
$ curl -LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/solcj/solcJ-all-0.4.25.jar
0.5版本合约编译jar包
$ curl -LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/solcj/solcJ-all-0.5.2.jar
国密0.4版本合约编译jar包
$ curl -LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/solcj/solcJ-all-0.4.25-gm.jar
国密0.5版本合约编译jar包
$ curl -LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/solcj/solcJ-all-0.5.2-gm.jar
配置控制台¶
- 区块链节点和证书的配置:
- 将节点sdk目录下的
ca.crt
、node.crt
和node.key
文件拷贝到conf
目录下。 - 将
conf
目录下的applicationContext-sample.xml
文件重命名为applicationContext.xml
文件。配置applicationContext.xml
文件,其中添加注释的内容根据区块链节点配置做相应修改。提示:如果搭链时设置的listen_ip为127.0.0.1或者0.0.0.0,channel_port为20200, 则applicationContext.xml
配置不用修改。
- 将节点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>
配置项详细说明参考这里。
重要
控制台配置说明
如果控制台配置正确,但是在CentOS系统上启动控制台出现如下错误:
Failed to connect to the node. Please check the node status and the console configruation.
- 当控制台配置文件在一个群组内配置多个节点连接时,由于群组内的某些节点在操作过程中可能退出群组,因此控制台轮询节点查询时,其返回信息可能不一致,属于正常现象。建议使用控制台时,配置一个节点或者保证配置的节点始终在群组中,这样在同步时间内查询的群组内信息保持一致。
启动控制台¶
在节点正在运行的情况下,启动控制台:
$ ./start.sh
# 输出下述信息表明启动成功
=====================================================================================
Welcome to FISCO BCOS console(1.0.3)!
Type 'help' or 'h' for help. Type 'quit' or 'q' to quit console.
________ ______ ______ ______ ______ _______ ______ ______ ______
| | \/ \ / \ / \ | \ / \ / \ / \
| $$$$$$$$\$$$$$| $$$$$$| $$$$$$| $$$$$$\ | $$$$$$$| $$$$$$| $$$$$$| $$$$$$\
| $$__ | $$ | $$___\$| $$ \$| $$ | $$ | $$__/ $| $$ \$| $$ | $| $$___\$$
| $$ \ | $$ \$$ \| $$ | $$ | $$ | $$ $| $$ | $$ | $$\$$ \
| $$$$$ | $$ _\$$$$$$| $$ __| $$ | $$ | $$$$$$$| $$ __| $$ | $$_\$$$$$$\
| $$ _| $$_| \__| $| $$__/ | $$__/ $$ | $$__/ $| $$__/ | $$__/ $| \__| $$
| $$ | $$ \\$$ $$\$$ $$\$$ $$ | $$ $$\$$ $$\$$ $$\$$ $$
\$$ \$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$
=====================================================================================
启动脚本说明¶
查看当前控制台版本:¶
./start.sh --version
console version: 1.0.3
账户使用方式¶
控制台提供账户生成脚本get_account.sh(脚本用法请参考账户管理文档,生成的的账户文件在accounts目录下,控制台加载的账户文件必须放置在该目录下。 控制台启动方式有如下几种:
./start.sh
./start.sh groupID
./start.sh groupID -pem pemName
./start.sh groupID -p12 p12Name
- 使用指定的pem文件的账户启动,输入参数:群组号、-pem、pem文件路径
./start.sh 1 -pem accounts/0xebb824a1122e587b17701ed2e512d8638dfb9c88.pem
- 使用指定的p12文件的账户,需要输入密码,输入参数:群组号、-p12、p12文件路径
./start.sh 1 -p12 accounts/0x5ef4df1b156bc9f077ee992a283c2dbb0bf045c0.p12
Enter Export Password:
控制台命令¶
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.
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.
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.
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.
grantPermissionManager Grant permission for permission 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.
listPermissionManager Query permission information for permission 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.
revokePermissionManager Revoke permission for permission configuration by address.
revokeSysConfigManager Revoke permission for system configuration by address.
revokeUserTableManager Revoke permission for user table by table name and address.
setSystemConfigByKey Set a system config.
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和端口正确,该节点正常运行。
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
]
getConsensusStatus¶
运行getConsensusStatus,查看共识状态。
[group:1]> getConsensusStatus
[
{
"accountType":1,
"allowFutureBlocks":true,
"cfgErr":false,
"connectedNodes":3,
"consensusedBlockNumber":6,
"currentView":40,
"groupId":1,
"highestblockHash":"0xb99703130e24702d3b580111b0cf4e39ff60ac530561dd9eb0678d03d7acce1d",
"highestblockNumber":5,
"leaderFailed":false,
"max_faulty_leader":1,
"node index":3,
"nodeId":"ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec",
"nodeNum":4,
"omitEmptyBlock":true,
"protocolId":264,
"sealer.0":"0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278",
"sealer.1":"2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108",
"sealer.2":"cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd",
"sealer.3":"ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec",
"toView":40
},
[
{
"0471101bcf033cd9e0cbd6eef76c144e6eff90a7a0b1847b5976f8ba32b2516c0528338060a4599fc5e3bafee188bca8ccc529fbd92a760ef57ec9a14e9e4278":39
},
{
"2b08375e6f876241b2a1d495cd560bd8e43265f57dc9ed07254616ea88e371dfa6d40d9a702eadfd5e025180f9d966a67f861da214dd36237b58d72aaec2e108":36
},
{
"cf93054cf524f51c9fe4e9a76a50218aaa7a2ca6e58f6f5634f9c2884d2e972486c7fe1d244d4b49c6148c1cb524bcc1c99ee838bb9dd77eb42f557687310ebd":37
},
{
"ed1c85b815164b31e895d3f4fc0b6e3f0a0622561ec58a10cc8f3757a73621292d88072bf853ac52f0a9a9bbb10a54bdeef03c3a8a42885fe2467b9d13da9dec":40
}
],
{
"prepareCache_blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"prepareCache_height":-1,
"prepareCache_idx":"65535",
"prepareCache_view":"9223372036854775807"
},
{
"rawPrepareCache_blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"rawPrepareCache_height":-1,
"rawPrepareCache_idx":"65535",
"rawPrepareCache_view":"9223372036854775807"
},
{
"committedPrepareCache_blockHash":"0xbbf80db21fa393143280e01b4b711eaddd54103e95f370b389af5c0504b1eea5",
"committedPrepareCache_height":5,
"committedPrepareCache_idx":"1",
"committedPrepareCache_view":"17"
},
{
"signCache_cachedSize":"0"
},
{
"commitCache_cachedSize":"0"
},
{
"viewChangeCache_cachedSize":"0"
}
]
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
]
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开头的交易哈希值。
[group:1]> getTransactionByHash 0xed82e2cda98db8614677aba1fa8a795820bd7f68a5919a2f85018ba8c10952ac
{
"blockHash":"0x77e5b6d799edabaeae654ac5cea9baacd6f8e7ace33531d40c7ed65192de1f02",
"blockNumber":"0x5a",
"from":"0x7a5b31b49c6e944e9e1768785b1bc9a96cea0c17",
"gas":"0x1c9c380",
"gasPrice":"0x1",
"hash":"0xed82e2cda98db8614677aba1fa8a795820bd7f68a5919a2f85018ba8c10952ac",
"input":"0x10009562616c6963650000000000000000000000000000000000000000000000000000006a6f726500000000000000000000000000000000000000000000000000000000",
"nonce":"0x18711fff2ea68dc8b8dce4a3d3845c62a0630766a448bd9453a9127efe6f9e5",
"to":"0x738eedd873bb9722173194ab990c5b9a6c0beb25",
"transactionIndex":"0x0",
"value":"0x0"
}
getTransactionByBlockHashAndIndex¶
运行getTransactionByBlockHashAndIndex,通过区块哈希和交易索引查询交易信息。参数:
- 区块哈希:0x开头的区块哈希值。
- 交易索引:十进制整数。
[group:1]> getTransactionByBlockHashAndIndex 0x77e5b6d799edabaeae654ac5cea9baacd6f8e7ace33531d40c7ed65192de1f02 0
{
"blockHash":"0x77e5b6d799edabaeae654ac5cea9baacd6f8e7ace33531d40c7ed65192de1f02",
"blockNumber":"0x5a",
"from":"0x7a5b31b49c6e944e9e1768785b1bc9a96cea0c17",
"gas":"0x1c9c380",
"gasPrice":"0x1",
"hash":"0xed82e2cda98db8614677aba1fa8a795820bd7f68a5919a2f85018ba8c10952ac",
"input":"0x10009562616c6963650000000000000000000000000000000000000000000000000000006a6f726500000000000000000000000000000000000000000000000000000000",
"nonce":"0x18711fff2ea68dc8b8dce4a3d3845c62a0630766a448bd9453a9127efe6f9e5",
"to":"0x738eedd873bb9722173194ab990c5b9a6c0beb25",
"transactionIndex":"0x0",
"value":"0x0"
}
getTransactionByBlockNumberAndIndex¶
运行getTransactionByBlockNumberAndIndex,通过区块高度和交易索引查询交易信息。参数:
- 区块高度:十进制整数。
- 交易索引:十进制整数。
[group:1]> getTransactionByBlockNumberAndIndex 2 0
{
"blockHash":"0x77e5b6d799edabaeae654ac5cea9baacd6f8e7ace33531d40c7ed65192de1f02",
"blockNumber":"0x5a",
"from":"0x7a5b31b49c6e944e9e1768785b1bc9a96cea0c17",
"gas":"0x1c9c380",
"gasPrice":"0x1",
"hash":"0xed82e2cda98db8614677aba1fa8a795820bd7f68a5919a2f85018ba8c10952ac",
"input":"0x10009562616c6963650000000000000000000000000000000000000000000000000000006a6f726500000000000000000000000000000000000000000000000000000000",
"nonce":"0x18711fff2ea68dc8b8dce4a3d3845c62a0630766a448bd9453a9127efe6f9e5",
"to":"0x738eedd873bb9722173194ab990c5b9a6c0beb25",
"transactionIndex":"0x0",
"value":"0x0"
}
getTransactionReceipt¶
运行getTransactionReceipt,通过交易哈希查询交易回执。参数:
- 交易哈希:0x开头的交易哈希值。
- 合约名:可选,发送交易产生该交易回执的合约名称,使用该参数可以将交易回执中的event log解析并输出。
- event名:可选,event名称,指定该参数则输出指定的event log信息。
- event索引号:可选,event索引,指定该参数则输出指定event索引位置的event log信息。
[group:1]> getTransactionReceipt 0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1
{
"blockHash":"0x68a1f47ca465acc89edbc24115d1b435cb39fa0def53e8d0ad8090cf1827cafd",
"blockNumber":"0x5",
"contractAddress":"0x0000000000000000000000000000000000000000",
"from":"0xc44e7a8a4ae20d6afaa43221c6120b5e1e9f9a72",
"gasUsed":"0x8be5",
"logs":[
{
"address":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"data":"0x0000000000000000000000000000000000000000000000000000000000000001",
"topics":[
"0x66f7705280112a4d1145399e0414adc43a2d6974b487710f417edcf7d4a39d71"
]
}
],
"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000",
"output":"0x0000000000000000000000000000000000000000000000000000000000000001",
"status":"0x0",
"to":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"transactionHash":"0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1",
"transactionIndex":"0x0"
}
[group:1]> getTransactionReceipt 0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1 TableTest
{
"blockHash":"0x68a1f47ca465acc89edbc24115d1b435cb39fa0def53e8d0ad8090cf1827cafd",
"blockNumber":"0x5",
"contractAddress":"0x0000000000000000000000000000000000000000",
"from":"0xc44e7a8a4ae20d6afaa43221c6120b5e1e9f9a72",
"gasUsed":"0x8be5",
"logs":[
{
"address":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"data":"0x0000000000000000000000000000000000000000000000000000000000000001",
"topics":[
"0x66f7705280112a4d1145399e0414adc43a2d6974b487710f417edcf7d4a39d71"
]
}
],
"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000",
"output":"0x0000000000000000000000000000000000000000000000000000000000000001",
"status":"0x0",
"to":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"transactionHash":"0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1",
"transactionIndex":"0x0"
}
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
InsertResult index: 0
count = 1
---------------------------------------------------------------------------------------------
[group:1]> getTransactionReceipt 0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1 TableTest InsertResult
{
"blockHash":"0x68a1f47ca465acc89edbc24115d1b435cb39fa0def53e8d0ad8090cf1827cafd",
"blockNumber":"0x5",
"contractAddress":"0x0000000000000000000000000000000000000000",
"from":"0xc44e7a8a4ae20d6afaa43221c6120b5e1e9f9a72",
"gasUsed":"0x8be5",
"logs":[
{
"address":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"data":"0x0000000000000000000000000000000000000000000000000000000000000001",
"topics":[
"0x66f7705280112a4d1145399e0414adc43a2d6974b487710f417edcf7d4a39d71"
]
}
],
"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000",
"output":"0x0000000000000000000000000000000000000000000000000000000000000001",
"status":"0x0",
"to":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"transactionHash":"0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1",
"transactionIndex":"0x0"
}
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
InsertResult index: 0
count = 1
---------------------------------------------------------------------------------------------
[group:1]> getTransactionReceipt 0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1 TableTest InsertResult 0
{
"blockHash":"0x68a1f47ca465acc89edbc24115d1b435cb39fa0def53e8d0ad8090cf1827cafd",
"blockNumber":"0x5",
"contractAddress":"0x0000000000000000000000000000000000000000",
"from":"0xc44e7a8a4ae20d6afaa43221c6120b5e1e9f9a72",
"gasUsed":"0x8be5",
"logs":[
{
"address":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"data":"0x0000000000000000000000000000000000000000000000000000000000000001",
"topics":[
"0x66f7705280112a4d1145399e0414adc43a2d6974b487710f417edcf7d4a39d71"
]
}
],
"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000",
"output":"0x0000000000000000000000000000000000000000000000000000000000000001",
"status":"0x0",
"to":"0xd653139b9abffc3fe07573e7bacdfd35210b5576",
"transactionHash":"0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1",
"transactionIndex":"0x0"
}
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
InsertResult index: 0
count = 1
---------------------------------------------------------------------------------------------
getCode¶
运行getCode,根据合约地址查询合约二进制代码。参数:
- 合约地址:0x的合约地址(部署合约可以获得合约地址)。
[group:1]> getCode 0x97b8c19598fd781aaeb53a1957ef9c8acc59f705
0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806366c99139146100465780636d4ce63c14610066575bfe5b341561004e57fe5b610064600480803590602001909190505061008c565b005b341561006e57fe5b61007661028c565b6040518082815260200191505060405180910390f35b8060006001015410806100aa57506002600101548160026001015401105b156100b457610289565b806000600101540360006001018190555080600260010160008282540192505081905550600480548060010182816100ec919061029a565b916000526020600020906004020160005b608060405190810160405280604060405190810160405280600881526020017f32303137303431330000000000000000000000000000000000000000000000008152508152602001600060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200185815250909190915060008201518160000190805190602001906101ec9291906102cc565b5060208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550606082015181600301555050505b50565b600060026001015490505b90565b8154818355818115116102c7576004028160040283600052602060002091820191016102c6919061034c565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061030d57805160ff191683800117855561033b565b8280016001018555821561033b579182015b8281111561033a57825182559160200191906001019061031f565b5b50905061034891906103d2565b5090565b6103cf91905b808211156103cb57600060008201600061036c91906103f7565b6001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600382016000905550600401610352565b5090565b90565b6103f491905b808211156103f05760008160009055506001016103d8565b5090565b90565b50805460018160011615610100020316600290046000825580601f1061041d575061043c565b601f01602090049060005260206000209081019061043b91906103d2565b5b505600a165627a7a723058203c1f95b4a803493db0120df571d9f5155734152548a532412f2f9fa2dbe1ac730029
getTotalTransactionCount¶
运行getTotalTransactionCount,查询当前块高和累计交易数(从块高为0开始)。
[group:1]> getTotalTransactionCount
{
"blockNumber":1,
"txSum":1
}
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的create接口创建用户表t_test,create接口调用了createResult event,event log会输出。
# event log由event名称,event日志索引号和event的变量组成,方便用户查看发送交易后的变量状态。createResult event记录的是create接口创建表返回的值count
[group:1]> call TableTest.sol 0xd653139b9abffc3fe07573e7bacdfd35210b5576 create
transaction hash:0x895980dd6ef37004bb32a7f417daa3b5d0bdb1f16e8a62cc9251e5948c612bb5
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
CreateResult index: 0
count = 0
---------------------------------------------------------------------------------------------
# 调用TableTest的insert接口插入记录,字段为name, item_id, item_name
[group:1]> call TableTest.sol 0xd653139b9abffc3fe07573e7bacdfd35210b5576 insert "fruit" 1 "apple"
transaction hash:0x6393c74681f14ca3972575188c2d2c60d7f3fb08623315dbf6820fc9dcc119c1
---------------------------------------------------------------------------------------------
Event logs
---------------------------------------------------------------------------------------------
InsertResult index: 0
count = 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.0
或HelloWorld.sol:1.0
。当省略合约版本号时,例如HelloWorld
或HelloWorld.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
。这个两个系统参数的键名可以通过tab键补全:
- tx_count_limit:区块最大打包交易数
- tx_gas_limit:交易执行允许消耗的最大gas数
参数:
- 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"
}
[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子句必须提供表的主键字段值。
- 输入的值带标点符号、空格或者以数字开头的包含字母的字符串,需要加上双引号,双引号中不允许再用双引号。
附录:Java环境配置¶
Ubuntu环境安装Java¶
# 安装默认Java版本(Java 8或以上)
sudo apt install -y default-jdk
# 查询Java版本
java -version
CentOS环境安装Java¶
# 查询centos原有的Java版本
$ rpm -qa|grep java
# 删除查询到的Java版本
$ rpm -e --nodeps java版本
# 查询Java版本,没有出现版本号则删除完毕
$ java -version
# 创建新的文件夹,安装Java 8或以上的版本,将下载的jdk放在software目录
# 从openJDK官网(https://jdk.java.net/java-se-ri/8)或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-linux-x64.tar.gz
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平台目前支持Solidity、CRUD、Precompiled三种智能合约形式。
- Solidity合约与以太坊相同,支持最新版本。
- CRUD接口通过在Solidity合约中支持分布式存储预编译合约,可以实现将Solidity合约中数据存储在FISCO BCOS平台AMDB的表结构中,实现合约逻辑与数据的分离。
- 预编译(Precompiled)合约使用C++开发,内置于FISCO BCOS平台,相比于Solidity合约具有更好的性能,其合约接口需要在编译时预先确定,适用于逻辑固定但需要共识的场景,例如群组配置。关于预编译合约的开发将在下一节进行介绍。
使用合约CRUD接口¶
访问 AMDB 需要使用 AMDB 专用的智能合约Table.sol
接口,该接口是数据库合约,可以创建表,并对表进行增删改查操作。
注解
为实现AMDB创建的表可被多个合约共享访问,其表名是群组内全局可见且唯一的,所以无法在同一条链上的同一个群组中,创建多个表名相同的表
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 set(string, int) public;
function set(string, string) 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);
}
提供一个合约案例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放入 Web3SDK 的src/test/resources/contract目录下,通过 Web3SDK 的编译脚本生成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函数中实现各个接口的调用行为。
// libblockverifier/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
需要实现HelloWorld合约的功能,接口与HelloWorld接口相同, HelloWorldPrecompiled的接口合约:
pragma solidity ^0.4.24;
contract HelloWorldPrecompiled {
function get() public constant returns(string) {}
function set(string _m) {}
}
HelloWorldPrecompiled需要存储set的字符串值,所以涉及到存储操作,需要设计存储的表结构。
表名: _ext_hello_world_
表结构:
key | value |
---|---|
hello_key | hello_value |
该表只存储一对键值对,key字段为hello_key,value字段为hello_value 存储对应的字符串值,可以通过set(string)接口修改,通过get()接口获取。
添加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;
}
- 注册开发的预编译合约。修改
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预编译合约¶
在控制台solidity/contracts创建HelloWorldPrecompiled.sol文件,文件内容是HelloWorld预编译合约的接口声明,如下
pragma solidity ^0.4.24;
contract HelloWorldPrecompiled{
function get() public constant returns(string);
function set(string n);
}
使用编译出的二进制搭建节点后,部署控制台v1.0.2以上版本,然后执行下面语句即可调用
3.2 solidity调用¶
我们尝试在Solidity合约中创建预编译合约对象并调用其接口。在控制台solidity/contracts创建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合约的接口,结果如下
存储安全¶
联盟链的数据,只对联盟内部成员可见。落盘加密,保证了运行联盟链的数据,在硬盘上的安全性。一旦硬盘被带出联盟链自己的内网环境,数据将无法被解密。
落盘加密是对节点存储在硬盘上的内容进行加密,加密的内容包括:合约的数据、节点的私钥。
具体的落盘加密介绍,可参考:落盘加密的介绍
部署Key Manager¶
每个机构一个Key Manager,具体的部署步骤,可参考Key Manager README
生成节点¶
用build_chain.sh
脚本,用普通的操作方法,先生成节点。
cd FISCO-BCOS/tools
bash build_chain.sh -l "127.0.0.1:4" -p 12300 -e ../build/bin/fisco-bcos
重要
节点生成后,不能启动,待dataKey配置后,再启动。节点在第一次运行前,必须配置好是否采用落盘加密。一旦节点开始运行,无法切换状态。
启动Key Manager¶
直接启动key-manager
。若未部署key-manager
,可参考Key Manager README
# 参数:端口,superkey
./key-manager 31443 123xyz
启动成功,打印日志
[1546501342949][TRACE][Load]key-manager stared,port=31443
配置dataKey¶
重要
配置dataKey的节点,必须是新生成,未启动过的节点。
执行脚本,定义dataKey
,获取cipherDataKey
cd key-manager/scripts
bash gen_data_secure_key.sh 127.0.0.1 31443 123456
得到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)中。
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
正确性判断¶
(1)节点正常运行,正常共识,不断输出共识打包信息。
tail -f nodes/node0/log/* | grep +++
(2)key-manager
在节点每次启动时,都会打印一条日志。例如,节点在一次启动时,Key Manager直接输出的日志如下。
[1546504272699][TRACE][Dec]Respond
{
"dataKey" : "313233343536",
"error" : 0,
"info" : "success"
}
节点准入¶
为保证区块链安全性,FISCO BCOS引入了游离节点、观察者节点和共识节点,这三种节点类型可通过控制台相互转换。
操作命令¶
控制台提供了 addSealer 、addObserver 和 removeNode 三类命令将指定节点转换为共识节点、观察者节点和游离节点,并可使用 getSealerList、getObserverList 和 getNodeIDList 查看当前组的共识节点列表、观察者节点列表和组内所有节点列表。
- addSealer:根据节点NodeID设置对应节点为共识节点;
- addObserver:根据节点NodeID设置对应节点为观察节点;
- removeNode:根据节点NodeID设置对应节点为游离节点;
- getSealerList:查看群组中共识节点列表;
- getObserverList:查看群组中观察节点列表;
- getNodeIDList:查看节点已连接的所有其他节点的NodeID。
例: 将指定节点分别转换成共识节点、观察者节点、游离节点,主要操作命令如下:
重要
节点准入操作前,请确保:
- 操作节点Node ID存在,节点Node ID可在节点目录下执行 cat conf/node.nodeid获取
- 节点加入的区块链所有节点共识正常:正常共识的节点会输出+++日志
# 设节点位于~/fisco/nodes/192.168.0.1/node0目录下
$ mkdir -p ~/fisco && cd ~/fisco
# 获取节点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是否与其他群组存在交集节点,不影响以下操作过程的通用性。
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为将生成的节点目录名
$ ./gen_node_cert.sh -c nodes/cert/agency -o node2
2 . 拷贝node2到nodes/127.0.0.1/
下,与其他节点目录(node0
、node1
)同级;
$ cp -r ./node2/ nodes/127.0.0.1/
3 . 进入nodes/127.0.0.1/
,拷贝node0/config.ini
、node0/start.sh
和node0/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]
模块,修改listen_ip
、channel_listen_port
和jsonrpc_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.ini
到node2/conf
目录下,不需改动;
$ cp node1/conf/group.3.genesis node2/
$ cp node1/conf/group.3.ini node2/
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
注解
- 从节点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黑名单避免这种情况。
A节点加入群组¶
场景描述:
群组Group3原有节点1和节点2,两节点轮流出块,现在将节点3加入群组。
操作顺序:
- 节点3加入网络;
- 使用控制台addSealer根据节点3的nodeID设置节点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退出群组。
操作顺序:
- 使用控制台removeNode根据节点3的NodeID设置节点3为游离节点;
- 使用控制台getSealerList查询group3的共识节点中是否包含节点3的nodeID,如已消失,退出群组操作完成。
补充说明:
注解
- 节点3可以共识节点或观察节点的身份执行退出操作。
权限控制¶
本文档描述权限控制的实践操作,有关权限控制的详细设计请参考权限控制设计文档。
重要
推荐管理员机制:由于系统默认无权限设置记录,因此任何账户均可以使用权限设置功能。例如当账户1设置账户1有权限部署合约,但是账户2也可以设置账户2有权限部署合约。那么账户1的设置将失去控制的意义,因为其他账户可以自由添加权限。因此,搭建联盟链之前,推荐确定权限使用规则。可以使用grantPermissionManager指令设置链管理员账户,即指定特定账户可以使用权限分配功能,非链管理员账户无权限分配功能。
权限控制工具¶
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没有部署合约的权限,部署合约失败。
- 注意: 其中部署合约和创建用户表是“二合一”的控制项,在使用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黑名单介绍》。
CA黑名单的操作包括一节点将他节点列入/移除CA黑名单,通过修改配置文件重启实现。
修改范围¶
节点config.ini
配置有[certificate_blacklist]
路径(可选)。[certificate_blacklist]
为节点NodeID列表,node.X为本节点拒绝连接的对方节点NodeID。
修改示例¶
网络中存在三个节点,均互联,节点相关信息为:
节点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节点将B节点列入CA黑名单¶
场景描述:
节点1和节点2在群组同一群组中,与群组其余节点轮流出块,现在节点1将节点2加入自身黑名单。
操作顺序:
1 . 对于节点node0,将节点node1的公钥nodeID加入自身的CA黑名单;
$ cat node1/conf/node.nodeid
aab37e73489bbd277aa848a99229ab70b6d6d4e1b81a715a22608a62f0f5d4270d7dd887394e78bd02d9f31b8d366ce4903481f50b1f44f0e4fda67149208943
$ vim node0/config.ini
;certificate blacklist
[certificate_blacklist]
;crl.0 should be nodeid, nodeid's length is 128
crl.0=aab37e73489bbd277aa848a99229ab70b6d6d4e1b81a715a22608a62f0f5d4270d7dd887394e78bd02d9f31b8d366ce4903481f50b1f44f0e4fda67149208943
2 . 重启节点1;
# 在node1目录下执行
$ ./stop.sh
$ ./start.sh
nohup: appending output to ‘nohup.out’
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
补充说明:
- 节点node0添加节点node1到自身CA黑名单的操作,node0将断开与节点node1的网络连接及AMOP通信;
A节点将B节点移除CA黑名单¶
场景描述:
节点1的CA黑名单中有节点2的nodeID,节点2的CA黑名单中没有节点1的nodeID,现在节点1将节点2移除自身的CA黑名单。
操作顺序:
- 对于节点1,将节点2的公钥NodeID从自身的CA黑名单移除;
- 重启节点1;
- 通过日志确认确认节点1与节点2重新建立连接,移除黑名单操作完成。
链上信使协议¶
介绍¶
链上信使协议AMOP(Advanced Messages Onchain Protocol)系统旨在为联盟链提供一个安全高效的消息信道,联盟链中的各个机构,只要部署了区块链节点,无论是共识节点还是观察节点,均可使用AMOP进行通讯,AMOP有如下优势:
- 实时:AMOP消息不依赖区块链交易和共识,消息在节点间实时传输,延时在毫秒级。
- 可靠:AMOP消息传输时,自动寻找区块链网络中所有可行的链路进行通讯,只要收发双方至少有一个链路可用,消息就保证可达。
- 高效:AMOP消息结构简洁、处理逻辑高效,仅需少量cpu占用,能充分利用网络带宽。
- 安全:AMOP的所有通讯链路使用SSL加密,加密算法可配置。
- 易用:使用AMOP时,无需在SDK做任何额外配置。
逻辑架构¶
以银行典型IDC架构为例,各区域概述:
- SF区:机构内部的业务服务区,此区域内的业务子系统使用区块链SDK,如无DMZ区,配置SDK连接到区块链节点,反之配置SDK连接到DMZ区的区块链前置。
- DMZ区:机构内部的外网隔离区,非必须,如有,该区域部署区块链前置。
- 区块链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>
</bean>
区块链前置配置,如有DMZ区:
<?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="proxyServer" class="org.fisco.bcos.channel.proxy.Server">
<property name="remoteConnections">
<bean class="org.fisco.bcos.channel.handler.ChannelConnections">
<property name="connectionsStr">
<list>
<value>127.0.0.1:5051</value><!-- 格式:IP:端口 -->
</list>
</property>
</bean>
</property>
<property name="localConnections">
<bean class="org.fisco.bcos.channel.handler.ChannelConnections">
</bean>
</property>
<!-- 区块链前置监听端口配置,区块链SDK连接用 -->
<property name="bindPort" value="30333"/>
</bean>
</beans>
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(PushCallback2.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());
}
}
}
测试¶
按上述说明配置好后,用户指定一个主题: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] [消息条数]
错误码¶
- 99:发送消息失败,AMOP经由所有链路的尝试后,消息未能发到服务端,建议使用发送时生成的
seq
,检查链路上各个节点的处理情况。 - 102:消息超时,建议检查服务端是否正确处理了消息,带宽是否足够。
并行交易¶
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()
,注册可以并行的接口即可。
先给出完整的举例,例子中的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是互斥参数
- 接口访问了全局数组,数组的下标是互斥参数
- 接口访问了简单类型的全局变量,所有简单类型的全局变量共用一个互斥参数,用不同的变量名作为互斥对象
确定参数类型和顺序
确定互斥参数后,根据规则确定参数类型和顺序,规则为:
- 接口参数仅限: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); // transfer接口,前1个四互斥参数
}
// 注销并行合约接口
function disableParallel() public
{
unregisterParallelFunction("transfer(string,string,uint256)");
unregisterParallelFunction("set(string,uint256)");
}
(4)部署/执行并行合约
部署合约
[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会根据你的硬件配置、操作系统和网络带宽有所变化。
分布式存储¶
安装MySQL¶
当前支持的分布式数据库是MySQL,在使用分布式存储之前,需要先搭建MySQL服务,在Ubuntu和CentOS服务器上的配置方式如下:
Ubuntu:执行下面三条命令,安装过程中,配置 root 账户密码。
sudo apt install -y mysql-server mysql-client libmysqlclient-dev
启动 MySQL 服务并登陆: root 账户密码。
sudo 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
mysql> set password for root@localhost = password('123456');
节点直连MySQL¶
FISCO-BCOS在2.0.0-rc3之后,支持节点通过连接池直连MySQL,相对于代理访问MySQL方式,配置简单,不需要手动创建数据库。配置方法请参考:
逻辑架构图¶
多群组架构是指区块链节点支持启动多个群组,群组间交易处理、数据存储、区块共识相互隔离的。因此群组下的每一个节点对应一个数据库实例,例如,区块链网络中,有三个节点A,B,C,其中A,B属于Group1,B,C属于Group2。节点A和C分别对应1个数据库实例,B节点对应了2个数据库实例,逻辑架构图如下
如上图所示,节点B属于多个群组,不同群组下的同一个节点,对应的数据库实例是分开的,为了区分不同群组下的同一个节点,将A,B,C三个节点,分别用Group1_A(Group1下的A节点,下同),Group1_B,Group2_B,Group2_C表示。
下面以上图为例,描述搭建配置过程。
节点搭建¶
使用分布式存储之前,需要完成联盟链的搭建和多群组的配置,具体参考如下步骤。
准备依赖¶
mkdir -p ~/fisco_direct && cd ~/fisco_direct
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\." | sort -u | tail -n 1 | cut -d \" -f 4`/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搭建区块链¶
### 搭建区块链(请先确认30700~30702,20700~20702,8575~8577端口没有被占用)
### 这里区别是在命令后面追加了参数"-s MySQL" 以及换了端口。
bash build_chain.sh -f ipconf -p 30700,20700,8575 -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_direct/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_direct/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_direct/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_direct/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_direct/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_direct/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_direct/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_direct/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_direct;
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/
修改配置文件¶
将~/fisco_direct/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:20700</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
启用控制台¶
cd ~/fisco_direct/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 |
+----------------------------------------------------------+
| _contract_data_8c17cf316c1063ab6c89df875e96c9f0f5b2f744_ |
| _contract_data_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)
在控制台中调用create接口。
#创建表
call TableTest 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 create
0xab1160f0c8db2742f8bdb41d1d76d7c4e2caf63b6fdcc1bbfc69540a38794429
查看数据库中的表情况
show tables;
+----------------------------------------------------------+
| Tables_in_db_Group1_A |
+----------------------------------------------------------+
| _contract_data_8c17cf316c1063ab6c89df875e96c9f0f5b2f744_ |
| _contract_data_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_ |
| _user_t_test |
+----------------------------------------------------------+
往表里面插入一条数据
#往表里插入数据
call TableTest 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 insert "fruit" 100 "apple"
0x082ca6a5a292f1f7b20abeb3fb03f45e0c6f48b5a79cc65d1246bfe57be358d1
打开MySQL客户端,查询_user_t_test表数据
#查看用户表中的数据
select * from _user_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)
通过代理访问MySQL¶
本使用手册仅对节点版本为2.0.0-rc3的有效,如果需要在2.0.0-rc2中使用“通过代理访问MySQL”的访问方式去搭建分布式存储环境。请参考文档分布式存储搭建方法
逻辑架构图¶
多群组架构是指区块链节点支持启动多个群组,群组间交易处理、数据存储、区块共识相互隔离的。因此群组下的每一个节点对应一个AMDB实例,例如,区块链网络中,有三个节点A,B,C,其中A,B属于群组1,B,C属于群组2。节点A和C分别对应1个数据库实例,B节点对应了2个数据库实例,逻辑架构图如下:
如上图所示,节点B属于多个群组,不同群组下的同一个节点,对应的AMDB服务和MySQL是分开的,为了区分不同群组下的同一个节点,将A,B,C三个节点,分别用Group1_A(Group1下的A节点,下同),Group1_B,Group2_B,Group2_C表示。 下面以上图为例,描述搭建配置过程。
节点搭建¶
配置AMDB服务之前,需要完成联盟链的搭建和多群组的配置,具体参考如下步骤。
准备依赖¶
- 创建文件夹
mkdir -p ~/fisco && cd ~/fisco
- 获取
build_chain
脚本
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\." | sort -u | tail -n 1 | cut -d \" -f 4`/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搭建区块链¶
### 搭建区块链(请先确认30600~30602,20800~20802,8565~8567端口没有被占用)
bash build_chain.sh -f ipconf -p 30600,20800,8565
==============================================================
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文件¶
修改~/fisco/nodes/127.0.0.1/node0/conf/group.1.ini文件中[storage]段的内容,设置为如下内容
[storage]
type=external
topic=DB_Group1_A
max_retry=100
修改~/fisco/nodes/127.0.0.1/node1/conf/group.1.ini文件中[storage]段的内容,设置为如下内容
[storage]
type=external
topic=DB_Group1_B
max_retry=100
修改~/fisco/nodes/127.0.0.1/node1/conf/group.2.ini文件中[storage]段的内容,设置为如下内容
[storage]
type=external
topic=DB_Group2_B
max_retry=100
修改~/fisco/nodes/127.0.0.1/node2/conf/group.2.ini文件中[storage]段的内容,设置为如下内容
[storage]
type=external
topic=DB_Group2_C
max_retry=100
准备amdb代理¶
源码获取¶
cd ~/fisco;
git clone https://github.com/FISCO-BCOS/AMDB.git
源码编译¶
cd AMDB;gradle build
编译完成之后,会生成一个dist目录,文件结构如下:
├── apps
│ └── AMDB.jar
├── conf
│ ├── amdb.properties
│ ├── applicationContext.xml
│ ├── contracts
│ │ ├── Table.sol
│ │ ├── TableTest.sol
│ ├── db.properties
│ ├── doc
│ │ ├── amop.png
│ │ ├── leveldb.png
│ │ └── README.md
│ ├── log4j2.xml
│ └── mappers
│ └── data_mapper.xml
├── lib
├── log
└── start.sh
配置amdb代理¶
AMDB与节点连接过程,AMDB是client,节点是server,启动过程是AMDB服务主动连接节点,节点只需要配置AMDB关注的topic即可,关于topic的介绍请参考AMOP,AMDB需要通过证书准入。
cp ~/fisco/nodes/127.0.0.1/sdk/* ~/fisco/AMDB/dist/conf/
cd ~/fisco;
###dist_Group1_A是节点Group1_A对应的amdb实例
cp AMDB/dist/ dist_Group1_A -R
###dist_Group1_B是节点Group1_B对应的amdb实例
cp AMDB/dist/ dist_Group1_B -R
###dist_Group2_B是节点Group2_B对应的amdb实例
cp AMDB/dist/ dist_Group2_B -R
###dist_Group2_C是节点Group2_C对应的amdb实例
cp AMDB/dist/ dist_Group2_C -R
经过上述步骤,可以看到~/fisco目录的文件结构如下:
drwxrwxr-x 8 fisco fisco 4096 May 7 15:53 AMDB
-rwxrw-r-- 1 fisco fisco 37539 May 7 14:58 build_chain.sh
drwxrwxr-x 5 fisco fisco 4096 May 7 15:58 dist_Group1_A
drwxrwxr-x 5 fisco fisco 4096 May 7 15:58 dist_Group1_B
drwxrwxr-x 5 fisco fisco 4096 May 7 15:59 dist_Group2_B
drwxrwxr-x 5 fisco fisco 4096 May 7 15:59 dist_Group2_C
-rw-rw-r-- 1 fisco fisco 68 May 7 14:59 ipconf
drwxrwxr-x 4 fisco fisco 4096 May 7 15:08 nodes
MySQL -uroot -p123456
CREATE DATABASE `bcos_Group1_A`;
CREATE DATABASE `bcos_Group1_B`;
CREATE DATABASE `bcos_Group2_B`;
CREATE DATABASE `bcos_Group2_C`;
配置文件配置¶
amdb.properties配置AMDB服务需要连接的节点信息,db.properties配置数据库的连接信息。这里假设MySQL的配置信息如下:
|节点|db_ip|db_port|db_username|db_passwd|db_name|
|Group1_A|127.0.0.1|3306|root|123456|bcos_Group1_A|
|Group1_B|127.0.0.1|3306|root|123456|bcos_Group1_B|
|Group2_B|127.0.0.1|3306|root|123456|bcos_Group2_B|
|Group2_C|127.0.0.1|3306|root|123456|bcos_Group2_C|
将~/fisco/dist_Group1_A/conf/amdb.properties配置为如下内容:
node.ip=127.0.0.1
node.listen_port=20800
node.topic=DB_Group1_A
将~/fisco/dist_Group1_A/conf/db.properties配置为如下内容:
db.ip=127.0.0.1
db.port=3306
db.user=root
db.password=123456
db.database=bcos_Group1_A
将~/fisco/dist_Group1_A/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:20800</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="DBChannelService" class="org.fisco.bcos.channel.client.Service">
<property name="groupId" value="1" />
<property name="orgID" value="fisco" />
<property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
<property name="topics">
<list>
<value>${node.topic}</value>
</list>
</property>
<property name="pushCallback" ref="DBHandler"/>
</bean>
将~/fisco/dist_Group1_B/conf/amdb.properties配置为如下内容:
node.ip=127.0.0.1
node.listen_port=20801
node.topic=DB_Group1_B
将~/fisco/dist_Group1_B/conf/db.properties配置为如下内容:
db.ip=127.0.0.1
db.port=3306
db.user=root
db.password=123456
db.database=bcos_Group1_B
将~/fisco/dist_Group1_B/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:20801</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="DBChannelService" class="org.fisco.bcos.channel.client.Service">
<property name="groupId" value="1" />
<property name="orgID" value="fisco" />
<property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
<property name="topics">
<list>
<value>${node.topic}</value>
</list>
</property>
<property name="pushCallback" ref="DBHandler"/>
</bean>
将~/fisco/dist_Group2_B/conf/amdb.properties配置为如下内容:
node.ip=127.0.0.1
node.listen_port=20801
node.topic=DB_Group2_B
将~/fisco/dist_Group2_B/conf/db.properties配置为如下内容:
db.ip=127.0.0.1
db.port=3306
db.user=root
db.password=123456
db.database=bcos_Group2_B
将~/fisco/dist_Group2_B/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:20801</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="DBChannelService" class="org.fisco.bcos.channel.client.Service">
<property name="groupId" value="2" />
<property name="orgID" value="fisco" />
<property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
<!-- communication topic configuration of the node -->
<property name="topics">
<list>
<value>${node.topic}</value>
</list>
</property>
<property name="pushCallback" ref="DBHandler"/>
</bean>
将~/fisco/dist_Group2_C/conf/amdb.properties配置为如下内容:
node.ip=127.0.0.1
node.listen_port=20802
node.topic=DB_Group2_C
将~/fisco/dist_Group2_C/conf/db.properties配置为如下内容:
db.ip=127.0.0.1
db.port=3306
db.user=root
db.password=123456
db.database=bcos_Group2_C
将~/fisco/dist_Group2_C/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:20802</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="DBChannelService" class="org.fisco.bcos.channel.client.Service">
<property name="groupId" value="2" />
<property name="orgID" value="fisco" />
<property name="allChannelConnections" ref="groupChannelConnectionsConfig"></property>
<!-- communication topic configuration of the node -->
<property name="topics">
<list>
<value>${node.topic}</value>
</list>
</property>
<property name="pushCallback" ref="DBHandler"/>
</bean>
启动amdb代理¶
cd ~/fisco/dist_Group1_A;sh start.sh
cd ~/fisco/dist_Group1_B;sh start.sh
cd ~/fisco/dist_Group2_B;sh start.sh
cd ~/fisco/dist_Group2_C;sh start.sh
启动节点¶
cd ~/fisco/nodes/127.0.0.1;sh start_all.sh
检查进程¶
ps -ef|grep org.bcos.amdb.server.Main|grep -v grep
fisco 110734 1 1 17:25 ? 00:00:10 java -cp conf/:apps/*:lib/* org.bcos.amdb.server.Main
fisco 110778 1 1 17:25 ? 00:00:11 java -cp conf/:apps/*:lib/* org.bcos.amdb.server.Main
fisco 110803 1 1 17:25 ? 00:00:10 java -cp conf/:apps/*:lib/* org.bcos.amdb.server.Main
fisco 122676 1 16 17:38 ? 00:00:08 java -cp conf/:apps/*:lib/* org.bcos.amdb.server.Main
ps -ef|grep fisco-bcos|grep -v grep
fisco 111061 1 0 17:25 pts/0 00:00:04 /data/home/fisco/nodes/127.0.0.1/node2/../fisco-bcos -c config.ini
fisco 111065 1 0 17:25 pts/0 00:00:04 /data/home/fisco/nodes/127.0.0.1/node0/../fisco-bcos -c config.ini
fisco 122910 1 1 17:38 pts/0 00:00:02 /data/home/fisco/nodes/127.0.0.1/node1/../fisco-bcos -c config.ini
启动成功,会看到有4个java进程,3个fisco-bcos进程。不成功的话请参考日志确认配置是否正确。
检查日志输出¶
执行下面指令,查看节点node0链接的节点数(其他节点类似)
tail -f nodes/127.0.0.1/node0/log/log* | grep connected
正常情况会看到类似下面的输出,从输出可以看出node0与另外2个节点有连接。
info|2019-05-07 21:47:22.849910| [P2P][Service] heartBeat connected count,size=2
info|2019-05-07 21:47:32.849970| [P2P][Service] heartBeat connected count,size=2
info|2019-05-07 21:47:42.850024| [P2P][Service] heartBeat connected count,size=2
执行下面指令,检查是否在共识
tail -f nodes/127.0.0.1/node0/log/log* | grep +++
正常情况会不停输出++++Generating seal表示共识正常。
info|2019-05-07 21:48:54.942111| [g:1][p:65544][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=6,tx=0,nodeIdx=1,hash=355790f7...
info|2019-05-07 21:48:56.946022| [g:1][p:65544][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=6,tx=0,nodeIdx=1,hash=4ef772bb...
info|2019-05-07 21:48:58.950222| [g:1][p:65544][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=6,tx=0,nodeIdx=1,hash=48341ee5...
国密支持¶
为了充分支持国产密码学算法,金链盟基于国产密码学标准,在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/`curl -s https://api.github.com/repos/FISCO-BCOS/FISCO-BCOS/releases | grep "\"v2\." | sort -u | tail -n 1 | cut -d \" -f 4`/build_chain.sh && chmod u+x build_chain.sh
执行完上述步骤后,fisco目录下结构如下:
fisco
├── bin
│ └── fisco-bcos
└── 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 国密编译选项,使用成功后会生成国密版的节点。默认从GitHub下载最新稳定版本可执行程序
$ ./build_chain.sh -l "127.0.0.1:4" -p 30300,20200,8545 -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
国密落盘加密配置¶
国密版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 generator为企业用户提供了部署、管理和监控多机构多群组联盟链的便捷工具。
- 本工具降低了机构间生成与维护区块链的复杂度,提供了多种常用的部署方式。
- 本工具考虑了机构间节点安全性需求,所有机构间仅需要共享节点的证书,同时对应节点的私钥由各机构自己维护,不需要向机构外节点透露。
- 本工具考虑了机构间节点的对等性需求,多机构间可以通过交换数字证书对等安全地部署自己的节点。
设计背景
在联盟链中,多个对等机构是不完全信任的。联盟链的节点之间需要使用数字证书互相进行身份认证。
证书是机构对外身份的凭证,生成证书的过程中需要使用机构本身的公钥和私钥对。私钥即为机构在互联网上的身份信息,是私密的,不可对外告诉其他人的。节点在启动、运行过程中,需要使用私钥对数据包进行签名,从而完成身份认证过程。假设私钥泄露,则任何人都可以伪装成对应的机构,在不经过该机构授权行使该机构的权利。
重要
即在联盟链部署、运行过程中,机构节点的私钥是不应该告诉任何人,应当只能由本机构生成和保管。
在FISCO BCOS的群组初始化过程中,需要多个节点协商生成群组的创世区块。创世区块在同一个群组中是唯一的,其中包含了初始节点身份信息的区块。这些身份信息需要通过交换数字证书的方式来构建。
现有的联盟链运维管理工具在初始化时都没有考虑联盟链间多个企业地位对等安全的诉求。联盟链在初始化时,需要协商创世节点中包含的节点信息。因此谁来生成这些信息就显得十分重要。现有做法为某一机构生成自己的节点信息,启动区块链,再加入其它机构的节点;或是由权威第三方机构直接生成所有机构内的节点信息,并将节点配置文件夹发送给各机构。
另一方面,FISCO BCOS 2.0引入了隐私性和可扩展性更强的多群组架构。在群组架构下,群组间数据、交易相互隔离,每个群组运行独立的共识算法,可满足区块链场景中的隐私保护需求。
在上述模式中,总有一个机构会优先加入到联盟链之中;并且在这种模式中,总有一个机构会获得所有节点的私钥。
如何保证企业间如何对等、安全、隐私地新建群组。新建群组之后如何保证节点可靠,有效的运行;群组账本的隐私性和安全性,以及企业建立群组、使用群组操作的隐私性都需要一个有效的方式来保证。
设计思路
FISCO BCOS generator从上述背景出发,根据灵活、安全、易用、对等的原则,从不同机构对等部署、新建群组的角度考虑,设计了解决上述问题的解决方案。
灵活:
- 无需安装即可使用
- 支持多种部署上报方式
- 支持多种架构改动
安全:
- 支持多种架构改动
- 节点私钥不出内网
- 机构间只需协商证书
易用:
- 支持多种组网模式
- 多种命令满足不同需求
- 监控审计脚本
对等:
- 机构地位对等
- 所有机构共同产生创世区块
- 机构对等管理所属群组
针对同一根证书的联盟链,本工具可以快速配置链内的多个群组,满足不同企业的不同业务需求。
不同机构间通过协商节点证书、IP、端口号等数据的模式,填写配置项,每个机构都可以在本地生成不含节点私钥的节点配置文件夹,节点的私钥可以不出内网,即使节点配置文件丢失,防止恶意攻击者伪装节点的同事,不会泄露链上任何信息。使用这种方式,在保证节点可用的同时,保护节点的安全性。
用户通过协商生成创世区块,生成节点配置文件夹后钥,启动节点,节点会根据用户配置信息进行多群组组网。
下载安装¶
下载安装¶
下载
$ git clone https://github.com/FISCO-BCOS/generator.git
安装
$ cd generator
$ bash ./scripts/install.sh
检查是否安装成功
$ ./generator -h
# 若成功,输出 usage: generator xxx
一键部署¶
本章主要以部署2机构1群组4节点的组网模式,为用户讲解单机构一键部署企业级部署工具的使用方法。
本教程适用于单机构搭建所有节点的部署方式,企业级部署工具多机构部署教程可以参考使用企业级部署工具。
示例分析¶
在本节中,我们将在本机IP为127.0.0.1
生成一个如图所示网络拓扑结构为2机构1群组4节点的组网模式,每个节点的ip,端口号分别为:
机构 | 节点 | 所属群组 | P2P地址 | RPC/channel监听地址 |
---|---|---|---|---|
机构A | 节点0 | 群组1 | 127.0.0.1:30300 | 127.0.0.1:8545/:20200 |
节点1 | 群组1 | 127.0.0.1:30301 | 127.0.0.1:8546/:20201 | |
机构B | 节点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服务器不一致。
使用说明¶
使用前用户需准备如图如one_click
的文件夹,在文件夹下分别拥有不同机构的目录,每个机构目录下需要有对应的配置文件node_deployment.ini
。使用前需要保证generator的meta文件夹没有进行过任何操作。
查看一键部署模板文件夹:
ls ./one_click
# 参数解释
# 如需多个机构,需要手动创建该文件夹
one_click # 用户指定进行一键部署操作的文件夹
├── agencyA # 机构A目录,命令执行后会在该目录下生成机构A的节点及相关文件
│ └── node_deployment.ini # 机构A节点配置文件,一键部署命令会根据该文件生成相应节点
└── agencyB # 机构B目录,命令执行后会在该目录下生成机构B的节点及相关文件
└── node_deployment.ini # 机构B节点配置文件,一键部署命令会根据该文件生成相应节点
机构填写节点信息¶
教程中将配置文件放置与one_click文件夹下的agencyA, agencyB下
cat > ./one_click/agencyAnode_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
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
EOF
cat > ./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
; 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=30302
channel_listen_port=20202
jsonrpc_listen_port=8547
[node1]
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
搭建节点¶
bash ./one_click_generator.sh ./one_click
执行完毕后,./one_click文件夹结构如下:
查看执行后的一键部署模板文件夹:
ls ./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
启动节点¶
调用脚本启动节点:
bash ./one_click/agencyA/node/start_all.sh
bash ./one_click/agencyB/node/start_all.sh
查看节点进程:
ps -ef | grep fisco
# 命令解释
# 可以看到如下进程
fisco 15347 1 0 17:22 pts/2 00:00:00 ~/generator/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/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/one_click/agencyA/node/node_127.0.0.1_30302/fisco-bcos -c config.ini
fisco 15456 1 0 17:22 pts/2 00:00:00 ~/generator/one_click/agencyA/node/node_127.0.0.1_30303/fisco-bcos -c config.ini
查看节点运行状态¶
查看节点log:
tail -f ~/generator/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...
使用控制台¶
由于控制台体积较大,一键部署中没有直接集成,用户可以使用以下命令获取控制台
获取控制台,可能需要较长时间:
./generator --download_console ./
配置A机构控制台:
拷贝控制台至对应机构A:
cp -rf ./console ./one_click/agencyA/console
配置机构A控制台对应文件,控制台启动时需要相关证书、私钥以及控制台配置文件:
cp ./one_click/agencyA/sdk/* ./one_click/agencyA/console/conf
启动控制台,需要安装java:
cd ./one_click/agencyA/console && bash ./start.sh 1
同理,配置B机构控制台:
cd ~/generator && cp -rf ./console ./one_click/agencyB/console
配置机构B控制台对应文件,控制台启动时需要相关证书、私钥以及控制台配置文件:
cp ./one_click/agencyB/sdk/* ./one_click/agencyB/console/conf
新建群组及扩容¶
企业部署工具的后续操作与企业部署工具教程相同。
后续节点的扩容及新群组的划分操作,可以参考操作手册,或企业工具对等部署教程。
新建群组的操作用户可以在执行click2start.sh
脚本的目录下,通过修改./conf/group_genesis.ini
文件,并执行--create_group_genesis
命令。
扩容新节点的操作可以通过修改./conf/node_deployment.ini
文件,先使用--generate_all_certificates
生成证书,再使用--build_install_package
生成节点。
如果使用该教程遇到问题,请查看FAQ
配置文件¶
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
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
读取节点配置的命令,如生成节点证书和节点配置文件夹等会读取该配置文件。
模板文件夹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
。
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 = 192.167.1.1:30300
node.3 = 192.167.1.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 = 192.167.1.1:30302
node.3 = 192.167.1.1:30303
node.4 = 192.167.1.1:30300
node.5 = 192.167.1.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
二进制文件。
使用示例:
$./generator --download_fisco ./meta
执行完成后会在./meta文件夹下下载fisco-bcos
可执行二进制文件
download_console¶
使用–download_console可以指定的目录下下载并配置控制台。
使用示例:
$./generator --download_console ./meta
执行完成后会在./meta文件夹下根据node_deployment.ini
完成对控制台的配置
get_sdk_file¶
使用–get_sdk_file可以指定的目录下下获取控制台和sdk配置所需要的node.crt
、node.key
、ca.crt
及applicationContext.xml
。
使用示例:
$./generator --get_sdk_file ./sdk
执行完成后会在./sdk文件夹下根据node_deployment.ini
生成上述配置文件
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
用途如下:
- 监控节点是否存活, 并且可以重新启动挂掉的节点.
- 获取节点的块高和view信息, 判断节点共识是否正常.
- 分析最近一分钟的节点日志打印, 收集日志关键错误打印信息, 准实时判断节点的状态.
- 指定日志文件或者指定时间段, 分析节点的共识消息处理, 出块, 交易数量等信息, 判断节点的健康度.
配置告警服务¶
用户使用前,首先需要配置告警信息服务,这里以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/develop/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
, 验证失败会提示异常。
系统设计¶
本章介绍FISCO BCOS平台的设计思路,包括每个模块的结构以及实现,面向FISCO BCOS平台开发者。
整体架构¶
整体架构上,FISCO BCOS划分成基础层、核心层、管理层和接口层:
- 基础层:提供区块链的基础数据结构和算法库
- 核心层: 实现了区块链的核心逻辑,核心层分为两大部分:
- 链核心层: 实现区块链的链式数据结构、交易执行引擎和存储驱动
- 互联核心层: 实现区块链的基础P2P网络通信、共识机制和区块同步机制
- 管理层: 实现区块链的管理功能,包括参数配置、账本管理和AMOP
- 接口层: 面向区块链用户,提供多种协议的RPC接口、SDK和交互式控制台
FISCO BCOS基于多群组架构实现了强扩展性的群组多账本,基于清晰的模块设计,构建了稳定、健壮的区块系统。
本章重点介绍FISCO BCOS的群组架构和系统运行时的交易流(包括交易提交、打包、执行和上链)。

群组架构¶
考虑到真实的业务场景需求,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具有更好的扩展性、可运维性和灵活性。
多群组架构中,群组间共享网络,通过网络准入和账本白名单实现各账本间网络消息隔离。
群组间数据隔离,每个群组独立运行各自的共识算法,不同群组可使用不同的共识算法。每个账本模块自底向上主要包括核心层、接口层和调度层三层,这三层相互协作,FISCO BCOS可保证单个群组独立健壮地运行。
核心层¶
核心层负责将群组的区块数据、区块信息、系统表以及区块执行结果写入底层数据库。
存储分为世界状态(State)和分布式存储(AMDB)两部分,世界状态包括MPTState和StorageState,负责存储交易执行的状态信息,StorageState性能高于MPTState,但不存储区块历史信息;AMDB则向外暴露简单的查询(select)、提交(commit)和更新(update)接口,负责操作合约表、系统表和用户表,具有可插拔特性,后端可支持多种数据库类型,目前仅支持LevelDB数据库,后期会把基于mysql的AMDB集成到系统中。
接口层¶
接口层包括交易池(TxPool)、区块链(BlockChain)和区块执行器(BlockVerifier)三个模块。
- 交易池(TxPool): 与网络层以及调度层交互,负责缓存客户端或者其他节点广播的交易,调度层(主要是同步和共识模块)从交易池中取出交易进行广播或者区块打包;
- 区块链(BlockChain): 与核心层和调度层交互,是调度层访问底层存储的唯一入口,调度层(同步、共识模块)可通过区块链接口查询块高、获取指定区块、提交区块;
- 区块执行器(BlockVerifier): 与调度层交互,负责执行从调度层传入的区块,并将区块执行结果返回给调度层。
调度层¶
调度层包括共识模块(Consensus)和同步模块(Sync)。
- 共识模块:包括Sealer线程和Engine线程,分别负责打包交易、执行共识流程。Sealer线程从交易池(TxPool)取交易,并打包成新区块;Engine线程执行共识流程,共识过程会执行区块,共识成功后,将区块以及区块执行结果提交到区块链(BlockChain),区块链统一将这些信息写入底层存储,并触发交易池删除上链区块中包含的所有交易、将交易执行结果以回调的形式通知客户端,目前FISCO BCOS主要支持PBFT和Raft共识算法;
- 同步模块:负责广播交易和获取最新区块, 考虑到共识过程中,leader负责打包区块,而leader随时有可能切换,因此必须保证客户端的交易尽可能发送到每个区块链节点,节点收到新交易后,同步模块将这些新交易广播给所有其他节点;考虑到区块链网络中机器性能不一致或者新节点加入都会导致部分节点区块高度落后于其他节点,同步模块提供了区块同步功能,该模块向其他节点发送自己节点的最新块高,其他节点发现块高落后于其他节点后,会主动下载最新区块。
交易流¶
1 总体方案¶
用户通过SDK或curl命令向节点发起RPC请求以发起交易,节点收到交易后将交易附加到交易池中,打包器不断从交易池中取出交易并通过一定条件触发将取出交易打包为区块。生成区块后,由共识引擎进行验证及共识,验证区块无误且节点间达成共识后,将区块上链。当节点通过同步模块从其他节点处下载缺失的区块时,会同样对区块进行执行及验证。
2 整体架构¶
整体架构如下图所示:
Node:区块节点
TxPool:交易池,节点自身维护的、用于暂存收到的交易的内存区域
Sealer:打包器
Consensus Engine:共识引擎
BlockVerifier:区块验证器,用于验证一个区块的正确性
Executor:执行引擎,执行单个交易
BlockChain:区块链管理模块,是唯一有写权限的模块,提交区块接口需要同时传入区块数据和执行上下文数据,区块链管理将两种数据整合成一个事务提交到底层存储
Storage:底层存储
主要关系如下:
- 用户通过操作SDK或直接编写curl命令向所连接的节点发起交易。
- 节点收到交易后,若当前交易池未满则将交易附加至TxPool中并向自己所连的节点广播该交易;否则丢弃交易并输出告警。
- Sealer会不断从交易池中取出交易,并立即将收集到的交易打包为区块并发送至共识引擎。
- 共识引擎调用BlockVerifier对区块进行验证并在网络中进行共识,BlockVerifier调用Executor执行区块中的每笔交易。当区块验证无误且网络中节点达成一致后,共识引擎将区块发送至BlockChain。
- BlockChain收到区块,对区块信息(如块高等)进行检查,并将区块数据与表数据写入底层存储中,完成区块上链。
3 方案流程¶
3.1 合约执行流程¶
执行引擎基于执行上下文(Executive Context)执行单个交易,其中执行上下文由区块验证器创建用于缓存暂存区块执行过程中执行引擎产生的所有数据,执行引擎同时支持EVM合约与预编译合约,其中EVM合约可以通过交易创建合约、合约创建合约两种方式来创建,其执行流程如下:
EVM合约创建后,保存到执行上下文的_sys_contracts_表中,EVM合约的地址在区块链全局状态内自增,从0x1000001开始(可定制),EVM合约执行过程中,Storage变量保存到执行上下文的_contract_data_(合约地址)_表中。
预编译合约分永久和临时两种:(1) 永久预编译合约,整合在底层或插件中,合约地址固定;(2) 临时预编译合约,EVM合约或预编译合约执行时动态创建,合约地址在执行上下文内自增,从0x1000开始,至0x1000000截止,临时预编译合约仅在执行上下文内有效预编译合约没有Storage变量,只能操作表,其执行流程如下:
P2P网络¶
设计目标¶
FISCO BCOS P2P模块提供高效、通用和安全的网络通信基础功能,支持区块链消息的单播、组播和广播,支持区块链节点状态同步,支持多种协议。
P2P主要功能¶
- 区块链节点标识
通过区块链节点标识唯一标识一个区块链节点,在区块链网络上通过区块链节点标识对区块链节点进行寻址
- 管理网络连接
维持区块链网络上区块链节点间的TCP长连接,自动断开异常连接,自动发起重连
- 消息收发
在区块链网络的区块链节点间,进行消息的单播、组播或广播
- 状态同步
在区块链节点间同步状态
区块链节点标识¶
区块链节点标识由ECC算法的公钥生成,每个区块链节点必须有唯一的ECC密钥对,区块链节点标识在区块链网络中唯一标识一个区块链节点
通常情况下,一个节点要加入区块链网络,至少要准备三个文件:
- node.key 节点密钥,ECC格式
- node.crt 节点证书,由CA颁发
- ca.crt CA证书,CA机构提供
区块链节点除了有唯一区块链节点标识,还能关注Topic,供寻址使用
区块链节点寻址:
- 区块链节点标识寻址
通过区块链节点标识,在区块链网络中定位唯一的区块链节点
- Topic寻址
通过Topic,在区块链网络中定位一组关注该Topic的节点
管理网络连接¶
区块链节点间,会自动发起和维持TCP长连接,在系统故障、网络异常时,主动发起重连
区块链节点间建立连接时,会使用CA证书进行认证
连接建立流程¶
消息收发¶
区块链节点间消息支持单播、组播和广播
- 单播,单个区块链节点向单个区块链节点发送消息,通过区块链节点标识寻址
- 组播,单个区块链节点向一组区块链节点发送消息,通过Topic寻址
- 广播,单个区块链节点向所有区块链节点发送消息
单播流程¶
组播流程¶
广播流程¶
状态同步¶
每个节点会维护自身的状态,并将状态的Seq在全网定时广播,与其它节点同步
同步¶
同步,是区块链节点非常重要的功能。它是共识的辅助,给共识提供必需的运行条件。同步分为交易的同步和状态的同步。交易的同步,确保了每笔交易能正确的到达每个节点上。状态的同步,能确保区块落后的节点能正确的回到最新的状态。只有持有最新区块状态的节点,才能参与到共识中去。
交易同步¶
交易同步,是让区块链的上的交易尽可能的到达所有的节点。为共识中将交易打包成区块提供基础。
一笔交易(tx1),从客户端上发往某个节点,节点在接收到交易后,会将交易放入自身的交易池(Tx Pool)中供共识去打包。与此同时,节点会将交易广播给其它的节点,其它节点收到交易后,也会将交易放到自身的交易池中。交易在发送的过程中,会有丢失的情况,为了能让交易尽可能的到达所有的节点,收到广播过来交易的节点,会根据一定的策略,选择其它的节点,再进行一次广播。
交易广播策略
如果每个节点都没有限制的转发/广播收到的交易,带宽将被占满,出现交易广播雪崩的问题。为了避免交易广播的雪崩,FISCO BCOS根据经验,选择了较为精巧的交易广播策略。在尽可能保证交易可达性的前提下,尽量的减少重复的交易广播。
- 对于SDK来的交易,广播给所有的节点
- 对于其它节点广播来的交易,随机选择25%的节点再次广播
- 一条交易在一个节点上,只广播一次,当收到了重复的交易,不会进行二次广播
通过上述的策略,能够尽量的让交易到达所有的节点,但也会在极小的概率下出现某交易无法到达某节点的情况。此情况是允许的。交易尽可能到达更多的节点,是为了让此交易尽快的被打包、共识、确认,尽量的让交易能够更快的得到执行的结果。当交易未到达某个节点时,只会使得交易的执行时间变长,不会影响交易的正确性。
状态同步¶
状态同步,是让区块链节点的状态保持在最新。区块链的状态的新旧,是指区块链节点当前持有数据的新旧,即节点持有的当前区块块高的高低。若一个节点的块高是区块链的最高块高,则此节点就拥有区块链的最新状态。只有拥有最新状态的节点,才能参与到共识中去,进行下一个新区块的共识。
在一个全新的节点加入到区块链上,或一个已经断网的节点恢复了网络时,此节点的区块落后于其它节点,状态不是最新的。此时就需要进行状态同步。如图,需要状态同步的节点(Node 1),会主动向其它节点请求下载区块。整个下载的过程会将下载的负载分散到多个节点上。
状态同步与下载队列
区块链节点在运行时,会定时向其它节点广播自身的最高块高。节点收到其它节点广播过来的块高后,会和自身的块高进行比较,若自身的块高落后于此块高,就会启动区块下载流程。
区块的下载通过请求的方式完成。进入下载流程的节点,会随机的挑选满足要求的节点,发送需要下载的区块区间。收到下载请求的节点,会根据请求的内容,回复相应的区块。
收到回复区块的节点,在本地维护一个下载队列,用来对下载下来的区块进行缓冲和排序。下载队列是一个以块高为顺序的优先队列。下载下来的区块,会不断的插入到下载队列中,当队列中的区块能连接上节点当前本地的区块链,则将区块从下载队列中取出,真正的连接到当前本地的区块链上。
同步场景举例¶
交易同步¶
一笔交易被广播到所有节点的过程:
- 一笔交易通过channel或RPC发送到某节点上
- 收到交易的节点全量广播此节点给其它节点
- 其它节点收到交易后,为了保险起见,选择25%的节点再广播一次
- 节点收到广播过的交易,不会再次广播
状态同步¶
节点出块时的广播逻辑
- 某个节点出块
- 此节点将自己最新的状态(最新块高,最高块哈希,创世块哈希)广播给所有的节点
- 其它的节点收到peer的状态后,更新在本地管理的peer数据
组内成员的同步
组内成员在某时刻意外关闭,但其它成员在出块,当此组员再次启动时,发现区块落后于其它组员:
- 组员再次启动
- 收到其它组员发来的状态包
- 比较发现自己的最高块高落后于其它组员,启动下载流程
- 将相差的区块按区间划分成多个下载请求包,发送给多个组员,负载均衡
- 等待其它节点回复区块包
- 其它节点接受响应,从自己的区块链上查询出区块,回复给启动的节点
- 节点收到区块,放入下载队列
- 节点从下载队列中将区块拿出,写到区块链上
- 若下载未结束,则继续请求,若下载结束,则切换自身状态,开启交易同步,开启共识
新组员的同步
非组员作为一个新组员加入到某个组中,且此节点第一次启动,从原来的组员中同步区块:
- 非组员未被注册到组中,但非组员先启动
- 此时发现自己不在组中,不进行状态广播,也不进行交易广播,只等待其它组员发来状态消息
- 此时组员中并没有此新组员,不会向新组员广播状态
- 管理员将新组员加入到组中
- 组员向新组员广播自身状态
- 新组员收到组员状态,比较自身快高(为0),启动下载流程
- 之后的下载流程,与组内成员区块同步流程相同
共识算法¶
区块链系统通过共识算法保障系统一致性。 理论上,共识是对某个提案(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) 共识算法,共识模块框架如下图:
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)为拜占庭节点情况下,视图切换过程:
- 前三轮共识: 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. 系统框架¶
系统框架如下图:
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包后,直接将本地缓存的最新区块提交到数据库。
下图详细介绍了PBFT各个阶段的具体流程:
3.1 leader打包区块¶
PBFT共识算法中,共识节点轮流出块,每一轮共识仅有一个leader打包区块,leader索引通过公式(block_number + current_view) % consensus_node_num
计算得出。
节点计算当前leader索引与自己索引相同后,就开始打包区块。区块打包主要由PBFTSealer线程完成,Sealer线程的主要工作如下图所示:
- 产生新的空块: 通过区块链(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,否则若至少有三分之一的节点达到其他视图,则将本节点视图切换到这些节点的视图。
Raft¶
1 名词解释¶
1.1 Raft¶
Raft(Replication and Fault Tolerant)是一个允许网络分区(Partition Tolerant)的一致性协议,它保证了在一个由N个节点构成的系统中有(N+1)/2(向上取整)个节点正常工作的情况下的系统的一致性,比如在一个5个节点的系统中允许2个节点出现非拜占庭错误,如节点宕机、网络分区、消息延时。Raft相比于Paxos更容易理解,且被证明可以提供与Paxos相同的容错性以及性能,其详细介绍可见官网及动态演示。
1.2 节点类型¶
在Raft算法中,每个网络节点只能如下三种身份之一:Leader、Follower以及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,重新开始选举。
1.5 消息¶
在Raft算法中,每个网络节点间通过发送消息进行通讯,当前Raft模块包括四种消息:VoteReq、VoteResp、Heartbeat、HeartbeatResp,其中:
- 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 系统框架¶
系统框架如下图所示:
- Raft Sealer:负责从交易池取出交易并打包成区块,并发送至Raft Engine进行共识。区块上链后,Raft Sealer负责从交易池中删除已上链交易;
- Raft Engine:负责在共识节点进行共识,将达成共识的区块上链。
3 核心流程¶
3.1 节点状态转换¶
节点类型之间转换关系如下图所示,每种状态转换形式将在接下来的各个小节进行阐述:
Raft共识模块中使用心跳机制来触发Leader选举。当节点启动时,节点自动成为Follower且将Term置0。只要Follower从Leader或者Candidate收到有效的Heartbeat或RequestVote消息,其就会保持在Follower状态,如果Follower在一段时间内(这段时间称为 Election Timeout)没收到上述消息,则它会假设系统当前的Leader已经失活,然后增加自己的Term并转换为Candidiate,开启新一轮的Leader选举流程,流程如下:
- Follower增加当前的Term,转换为Candidate;
- Candidate将票投给自己,并广播RequestVote到其他节点请求投票;
- Candidate节点保持在Candidate状态,直到下面三种情况中的一种发生:(1)该节点赢得选举;(2) 在等待选举期间,Candidate收到了其他节点的Heartbeat;(3) 经过Election Timeout后,没有Leader被选出。Raft算法采用随机定时器的方法来避免节点选票出现平均瓜分的情况以保证大多数时候只会有一个节点超时进入Candidate状态并获得大部分节点的投票成为Leader。
节点在收到VoteReq消息后,会根据消息的内容选择不同的响应策略:
VoteReq的Term小于或等于自己的Term
- 如果节点是Leader,则拒绝该投票请求,Candidate收到此响应后会放弃选举转变为Follower,并增加投票超时;
- 如果节点不是Leader:
- 如果VoteReq的Term小于自己的Term,则拒绝该投票请求,如果Candidate收到超过半数的该种响应则表明其已经过时,此时Candidate会放弃选举转变为Follower,并增加投票超时;
- 如果VoteReq的Term等于自己的Term,则拒绝该投票请求,对于该投票请求不作任何处理。对于每个节点而言,只能按照先到先得的原则投票给一个Candidate,从而保证每轮选举中至多只有一个Candidate被选为Leader。
VoteReq的lastLeaderTerm小于自己的lastLeaderTerm
每个节点中会有一个lastLeaderTerm字段表示该节点见过的最后一个Leader的Term,lastLeaderTerm仅能由Heartbeat进行更新。如果VoteReq中的lastLeaderTerm小于自己的lastLeaderTerm,表明Leader访问这个Candidate存在问题,如果此时Candidate处于网络孤岛的环境中,会不断向外提起投票请求,因此需要打断它的投票请求,所以此时节点会拒绝该投票请求。
VoteReq的lastBlockNumber小于自己的lastBlockNumber
每个节点中会有一个lastBlockNumber字段表示节点见到过的最新块的块高。在出块过程中,节点间会进行区块复制(详见3.2节),在区块复制的过程中,可能有部分节点收到了较新的区块数据而部分没有,从而导致不同节点的lastBlockNumber不一致。为了使系统能够达成一致,需要要求节点必须把票投给拥有较新数据的节点,因此在这种情况下节点会拒绝该投票请求。
节点是第一次投票
为了避免出现Follower因为网络抖动导致重新发起选举,规定如果节点是第一次投票,直接拒绝该投票请求,同时会将自己的firstVote字段置为该Candidate的节点索引。
1~4步骤中都没有拒绝投票请求
同意该投票请求。
在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节点广播该区块数据,区块复制及提交的流程图如下图所示:
其中RaftSealer验证是否当前是否能打包交易的验证条件包括:(1) 是否为Leader;(2) 是否存在尚未完成同步的peer; (3) uncommitBlock字段是否为空,只有三个条件均符合才允许打包。
虚拟机与合约¶
交易的执行是区块链节点上的一个重要的功能。交易的执行,是把交易中的智能合约二进制代码取出来,用执行器(Executor)执行。共识模块(Consensus)把交易从交易池(TxPool)中取出,打包成区块,并调用执行器去执行区块中的交易。在交易的执行过程中,会对区块链的状态(State)进行修改,形成新区块的状态储存下来(Storage)。执行器在这个过程中,类似于一个黑盒,输入是智能合约代码,输出是状态的改变。
随着技术的发展,人们开始关注执行器的性能和易用性。一方面,人们希望智能合约在区块链上能有更快的执行速度,满足大规模交易的需求。另一方面,人们希望能用更熟悉更好用的语言进行开发。进而出现了一些替代传统的执行器(EVM)的方案,如:JIT、 WASM_甚至JVM。然而,传统的EVM是耦合在节点代码中的。首先要做的,是将执行器的接口抽象出来,兼容各种虚拟机的实现。因此,EVMC被设计出来。
EVMC (Ethereum Client-VM Connector API),是以太坊抽象出来的执行器的接口,旨在能够对接各种类型的执行器。FISCO BCOS目前采用了以太坊的智能合约语言Solidity,因此也沿用了以太坊对执行器接口的抽象。

在节点上,共识模块会调用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接口,对节点的状态进行操作。
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合约。
关键流程¶
- 执行预编译合约时首先需要根据合约地址获取到预编译合约的对象。
- 每个预编译合约对象都会实现
call
接口,预编译合约的具体逻辑在该接口中实现。 call
根据交易的abi编码,获取到Function Selector
和参数,然后执行对应的逻辑。
接口定义¶
每个预编译合约都必须实现自己的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编码 |
存储模块¶
FISCO BCOS继承以太坊存储的同时,引入了高扩展性、高吞吐量、高可用、高性能的分布式存储。存储模块主要包括两部分:
世界状态: 可进一步划分成 MPTState 和 StorageState
- MPTState: 使用MPT树存储账户的状态,与以太坊一致
- StorageState: 使用分布式存储的表结构存储账户状态,不存历史信息,去掉了对MPT树的依赖,性能更高
分布式存储(Advanced Mass Database,AMDB): 通过抽象表结构,实现了SQL和NOSQL的统一,通过实现对应的存储驱动,可以支持各类数据库,目前已经支持LevelDB和MySQL。

AMDB¶
分布式存储(Advanced Mass Database,AMDB)通过对表结构的设计,既可以对应到关系型数据库的表,又可以拆分使用KV数据库存储。通过实现对应于不同数据库的存储驱动,AMDB理论上可以支持所有关系型和KV的数据库。
- CRUD数据、区块数据和合约代码数据存储默认情况下都保存在AMDB,无需配置,合约局部变量存储可根据需要配置为MPTState或StorageState,无论配置哪种State,合约代码都不需要变动。
- 当使用MPTState时,合约局部变量保存在MPT树中。当使用StorageState时,合约局部变量保存在AMDB表中。
- 尽管MPTState和AMDB最终数据都会写向LevelDB,但二者使用不同的LevelDB实例,没有事务性,因此当配置成使用MPTState时,提交数据时异常可能导致两个LevelDB数据不一致。
名词解释¶
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中的删改查接口支持传入条件,这三种接口会返回根据条件筛选后的结果。如果条件为空,则不做任何筛选。
举例¶
以某公司员工领用物资登记表为例,解释上述名词。
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}
- Entry1:
- Table中以Name为主key,存有3个Entries对象。第1个Entries中存有Alice的2条记录,第2个Entries中存有Bob的1条记录,第3个Entries中存有Chris的一条记录。
- 调用Table类的查询接口时,查接口需要指定AMDB主key和条件,设置查询的AMDB主key为Alice,条件为
price > 40
,会查询出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接口所创建的表,以_user_<TableName>
为表名,底层自动添加_user_
前缀。
StorageState账户表¶
_contract_data_
+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可以通过实现对应的存储驱动支持不同的数据库以提高性能,我们使用LevelDB测试发现,StorageState性能大约是MPTState的两倍。
MPT State¶
MPT State是以太坊上级经典的数据存储方式。通过MPT树的方式,将所有合约的数据组织起来,实现了对数据的查找和追溯。
MPT树¶
MPT(Merkle Paricia 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树的实现图(图片来自以太坊黄皮书)
状态 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的数据历史。
(图片来自以太坊白皮书)
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通过引入群组概念,使联盟链从原有一链一账本的存储/执行机制扩展为一链多账本的存储/执行机制,基于群组维度实现同一条链上的数据隔离和保密。
如上图所示,节点ABC加入蓝色群组,并共同维护蓝色账本; 节点B和C加入粉色群组并维护粉红色账本; 节点A和B加入黄色群组并维护黄色账本。三个群组间共享公共的网络服务,但各群组有各自独立的账本存储及交易执行环境。客户端将交易发到节点所属的某个群组上,该群组内部对交易及数据进行共识并存储,其他群组对该交易无感知不可见。
节点准入机制¶
基于群组概念的引入,节点准入管理可分为网络准入机制和群组准入机制。准入机制的规则记录在配置中,节点启动后将读取配置信息实现网络及群组的准入判断。
名词解释¶
节点类型¶
本文档所讨论的节点为已完成网络准入可进行P2P通信的节点。网络准入过程涉及P2P节点连接列表添加和证书验证。
- 群组节点:完成网络准入并加入群组的节点。群组节点只能是共识节点和观察节点两者之一。其中共识节点参与共识出块和交易/区块同步,观察节点只参与区块同步。群组节点准入过程涉及动态增删节点的交易发送。
- 游离节点:完成网络准入但没有加入群组的节点。游离节点尚未通过群组准入,不参与共识和同步。
节点关系如下:
配置类型¶
划分维度 | 配置类型 | |
影响范围 | 网络配置 | 全局性质的配置,节点的配置影响该节点所在的整个网络,节点对整个网络使用同一份配置, 文件名为config.* |
群组配置 | 节点的配置影响该节点所在的单个群组,每个群组各有一份配置, 文件名为group.X.*,其中X为群组号 |
|
是否可改 | 固定配置 | 只使用首次配置内容,后续对配置的修改无效, 文件后缀为.genesis |
可改配置 | 配置后续可改动,节点重启生效, 文件后缀为.ini |
|
存放位置 | 本地存储 | 配置存放在本地文件,用户可直接修改, 用户修改自身文件能重启生效的配置项 |
链上存储 | 配置存放在区块链上,对其修改需群组共识,目前没有需全网共识的内容, 需新链重置或通过交易修改生效的配置项 |
节点准入配置项¶
涉及节点转入管理相关的配置项有:P2P节点连接列表,节点证书,CA黑名单,群组节点初始列表和群组节点系统表。
P2P节点连接列表 | 记录本节点期望与哪些节点建立网络通信 | 网络配置 | 可改配置 | 本地存储 |
节点证书 | 证明自己是由可信第三方许可的节点 | 网络配置 | 可改配置 | 本地存储 |
CA黑名单 | 记录本节点禁止与哪些节点建立网络通信 | 网络配置 | 可改配置 | 本地存储 |
群组节点初始列表 | 记录创世块阶段参与共识/同步的节点列表 | 群组配置 | 固定配置 | 本地存储 |
群组节点系统表 | 记录当前参与一群组共识/同步的节点列表 | 群组配置 | 可改配置 | 链上存储 |
核心流程¶
首次初始化流程¶
节点在首次启动时,对其所属的各个群组,以群组为单位将固定配置文件的内容写入第0块并直接提交上链。初始化的具体逻辑为:
这一阶段需写入的与节点准入管理相关的配置内容有:群组节点初始列表->群组节点系统表。
说明:
- 同一账本的所有节点的第0块需一致,即固定配置文件均一致;
- 节点后续的每次启动均检查第0块信息是否与固定配置文件一致。如果固定配置文件被修改,节点再次启动将输出告警信息,但不会影响群组正常运作。
基于CA黑名单的节点建连流程¶
SSL认证用于确定节点之间是否许可加入某条链。一条链上的节点均信任可信的第三方(节点证书的颁发者)。
FISCO BCOS要求实现SSL双向认证。节点在handshake过程中,从对方节点提供的证书中获取对方节点的nodeID,检查该nodeID是否在自身的CA黑名单。如存在,关闭该connection,如不在,建立session。
CA黑名单机制也支持SSL单向认证的场景,作用时机是:节点在session建立后,可从session中获取对方节点的nodeID进行判断,如果nodeID在自身的CA黑名单中,将已建立的session断连。
接口及配置描述¶
节点配置文件层级¶
配置文件的组织规则为:各群组的配置独立、固定配置和可改配置相独立。目前使用的文件有网络可改配置文件config.ini
、群组固定配置文件group.N.genesis
和群组可改配置文件group.N.ini
,其中N
为节点所在的群组号。对于网络/群组可改配置文件,如果文件中没有显式定义某配置项的值,程序将使用该配置项的默认值。
配置文件示例¶
对于网络可改配置文件config.ini
,节点准入管理涉及P2P节点连接列表[p2p]
、节点证书[network_security]
、CA黑名单[certificate_blacklist]
。[certificate_blacklist]
可缺少。配置项举例如下:
[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
群组节点系统表定义¶
name | string | No | PRI | 各行同一值,分布式存储基于此key实现全表查询 |
type | string | No | 节点类型(sealer/observer) | |
node_id | string | No | 节点NodeID | |
enable_num | string | No | 该节点类型生效的区块高度 | |
_status_ | string | No | 分布式存储通用字段,“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黑名单所属的配置类型:
- 基于作用范围(网络配置/账本配置)维度可划分为网络配置,影响整个网络的节点连接建立过程;
- 基于是否可改(可改配置/固定配置)维度可划分为可改配置,内容可改,重启后生效;
- 基于存放位置(本地存储/链上存储)维度可划分为本地存储,内容记录在本地,不存于链上。
核心流程¶
底层实现SSL双向验证。节点在handshake过程中,通过对方提供的证书获取对方节点的nodeID,检查该nodeID是否在自身的CA黑名单。如果在,关闭该connection,继续后续流程。
影响范围¶
- CA黑名单对网络层的P2P节点连接及AMOP功能有显著影响,使之失效;
- 对账本层的共识和同步功能有潜在影响,影响共识及同步消息/数据的转发。
配置格式¶
节点config.ini
配置中增加[certificate_blacklist]
路径([certificate_blacklist]
在配置中可选)。CA黑名单内容为节点NodeID列表,node.X为本节点拒绝连接的对方节点NodeID。CA黑名单的配置格式示例如下。
[certificate_blacklist]
crl.0=4d9752efbb1de1253d1d463a934d34230398e787b3112805728525ed5b9d2ba29e4ad92c6fcde5156ede8baa5aca372a209f94dc8f283c8a4fa63e3787c338a4
crl.1=af57c506be9ae60df8a4a16823fa948a68550a9b6a5624df44afcd3f75ce3afc6bb1416bcb7018e1a22c5ecbd016a80ffa57b4a73adc1aeaff4508666c9b633a
功能展望¶
- CA黑名单目前为修改后重启生效,后续可实现动态加载,修改实时生效;
- CA黑名单目前实现了基于节点的黑名单,后续可考虑基于机构的黑名单。
权限控制¶
权限控制介绍¶
与可自由加入退出、自由交易、自由检索的公有链相比,联盟链有准入许可、交易多样化、基于商业上隐私及安全考虑、高稳定性等要求。因此,联盟链在实践过程中需强调“权限”及“控制”的理念。
为体现“权限”及“控制”理念,FISCO BCOS平台基于分布式存储,提出分布式存储权限控制的机制,可以灵活,细粒度的方式进行有效的权限控制,为联盟链的治理提供重要的技术手段。分布式权限控制基于外部账户(tx.origin)的访问机制,对包括合约部署,表的创建,表的写操作(插入、更新和删除)进行权限控制,表的读操作不受权限控制。 在实际操作中,每个账户使用独立且唯一的公私钥对,发起交易时使用其私钥进行签名,接收方可通过公钥验签知道交易具体是由哪个账户发出,实现交易的可控及后续监管的追溯。
权限控制规则¶
权限控制规则如下:
- 权限控制的最小粒度为表,基于外部账户进行控制。
- 使用白名单机制,未配置权限的表,默认完全放开,即所有外部账户均有读写权限。
- 权限设置利用权限表(_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”时表示已删除,即表示权限记录处于失效状态。
权限控制设计¶
权限控制功能设计¶
根据交易信息确定外部账户,待操作的表以及操作方式。待操作的表为用户表或系统表。系统表用于控制区块链的系统功能,用户表用于控制区块链的业务功能,如下图所示。外部账户通过查询权限表获取权限相关信息,确定权限后再操作相关的用户表和权限表,从而可以控制相关的系统功能和业务功能。
权限控制流程设计¶
权限控制的流程如下:首先由客户端发起交易请求,节点获取交易数据,从而确定外部账户和待操作的表以及操作表的方式。如果判断操作方式为写操作,则检查该外部账户针对操作的表的权限信息(权限信息从权限表中查询获取)。若检查有权限,则执行写操作,交易正常执行;若检查无权限,则拒绝写操作,返回无权限信息。如果判断操作方式为读操作,则不检查权限信息,正常执行读操作,返回查询数据。流程图如下。
RPC¶
RPC(Remote Procedure Call,远程过程调用)是客户端与区块链系统交互的一套协议和接口。用户通过RPC接口可查询区块链相关信息(如块高、区块、节点连接等)和发送交易。
1 名词解释¶
2 模块架构¶
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成员,但两个成员不能同时包含。
4 RPC接口的设计¶
FISCO BCOS提供丰富的RPC接口供客户端调用。其中分为3类:
- 以get开头命名的查询接口:例如
[getBlockNumber]
接口,查询最新的区块高度。 [sendRawTransaction]
接口: 执行一笔签名的交易,将等待区块链共识后才返回响应。[call]
接口: 执行一个请求将不会创建一笔交易,不需要区块链共识,而是获取响应立刻返回。
交易并行¶
1 名词解释¶
1.1 DAG¶
一个无环的有向图称做有向无环图(Directed Acyclic Graph),简称DAG图。在一批交易中,可以通过一定方法识别出每笔交易需要占用的互斥资源,再根据交易在Block中的顺序及互斥资源的占用关系构造出一个交易依赖DAG图,如下图所示,凡是入度为0(无被依赖的前序任务)的交易均可以并行执行。如下图所示,基于左图的原始交易列表的顺序进行拓扑排序后,可以得到右图的交易DAG。
2 模块架构¶
其中主要流程包括:
- 用户直接或间接通过SDK发起交易。交易可以是能够并行执行的交易和不能并行执行的交易;
- 交易进入节点的交易池中,等待打包;
- 交易被Sealer打包为区块,经过共识后,发送至BlockVerifier进行验证;
- BlockVerifier根据区块中的交易列表生成交易DAG;
- BlockVerifier构造执行上下文,并行执行交易DAG;
- 区块验证通过后,区块上链。
3 重要流程¶
3.1 交易DAG构建¶
3.1.1 DAG数据结构¶
方案中所用到的DAG数据结构如下:
其中:
- 顶点(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构造流程¶
流程如下:
- 从打包好的区块从取出区块中的所有交易;
- 将交易数量作为最大顶点数量初始化一个DAG实例;
- 按序读出所有交易,如果一笔交易是可并行交易,则解析其冲突域,并检查是否有之前的交易与该交易冲突,如果有,则在相应交易间构造依赖边;若该交易不可并行,则认为其必须在前序的所有交易都执行完后才能执行,因此在该交易与其所有前序交易间建立一条依赖边。
3.2 DAG执行流程¶
流程如下:
- 主线程会首先根据硬件核数初始化一个相应大小的线程组,若获取硬件核数失败,则不创建其他线程;
- 当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方案¶
概述¶
调用以太坊智能合约的流程包括:
- 编写合约;
- 编译合约得到合约接口abi描述;
- 部署合约得到合约地址address;
- 封装合约的abi和地址,通过SDK等工具实现对合约的调用。
从合约调用流程可知,调用之前必须准备合约abi以及合约地址address。这种使用方式存在以下的问题:
- 合约abi为较长的JSON字符串,调用方不需直接感知;
- 合约地址为20字节的魔数,不方便记忆,若丢失后将导致合约不可访问;
- 合约重新部署后,一个或多个调用方都需更新合约地址;
- 不便于进行版本管理以及合约灰度升级。
为解决以上问题,给调用者提供良好的智能合约调用体验,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不需支持。
核心流程¶
用户调用SDK部署合约及调用合约流程如下:
- 部署合约时,SDK生成合约对应的Java类,调用类的deploy接口发布合约获得合约地址,然后调用CNS合约insert接口上链CNS信息。
- 调用合约时,SDK引入合约的Java类,并加载实例化。load加载接口可传入合约地址(原有以太坊方式)或合约名称和合约版本的组合(CNS方式),SDK处理CNS方式时通过调用CNS模块查询链上信息来获取合约地址。
- 对于缺少版本号的合约调用,由SDK实现默认调用合约的最新版本。
- 上链的合约abi信息属于可选字段。
数据结构¶
CNS表结构¶
CNS信息以系统表的方式进行存储,各账本独立。CNS表定义如下:
name | string | No | PRI | 合约名称,name和version为联合主键 |
version | string | No | 合约名称,name和version为联合主键 | |
address | string | No | 合约地址 | |
abi | string | YES | 合约abi | |
_status_ | string | No | 分布式存储通用字段,“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,返回表中该合约该版本的唯一地址。
预编译合约是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默认调用使用合约的最新版本进行查询
注意:
- 在调用接口前,需将sol合约转换Java类,并将生成的Java类以及abi、bin文件置于正确的目录,详细使用方法请参考Web3SDK;
- 两个接口的使用例子可参考ConsoleImpl.java中的deployByCNS和callByCNS接口实现。
国密支持方案¶
设计目标¶
为了充分支持国产密码学算法,金链盟基于国产密码学标准,实现了国密加解密、签名、验签、哈希算法、国密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均基于国产密码学标准开发)
国密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来完成。此处主要介绍的是节点存储数据的访问控制,即落盘加密。
主要思想¶
落盘加密是在机构内部进行的。在机构的内网环境中,每个机构独立地对节点的硬盘数据进行加密。当节点所在机器的硬盘被带离机构,并让节点在机构内网之外的网络启动,硬盘数据将无法解密,节点无法启动。进而无法盗取联盟链上的数据。
方案架构¶
落盘加密是在机构内部进行的,每个机构独立管理自己硬盘数据的安全。内网中,每个节点的硬盘数据是被加密的。所有加密数据的访问权限,通过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.
方案流程¶
方案流程分为节点初始配置和节点安全运行。
节点初始配置¶
节点启动前,需要为节点配置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加密,再写入。
网络压缩¶
外网环境下,区块链系统性能受限于网络带宽,为了尽量减少网络带宽对系统性能的影响,FISCO BCOS从relase-2.0.0-rc2
开始支持网络压缩功能,该功能主要在发送端进行网络数据包压缩,在接收端将解包数据,并将解包后的数据传递给上层模块。
系统框架¶
网络压缩主要在P2P网络层实现,系统框架如下:
网络压缩主要包括两个过程:
- 发送端压缩数据包:群组层通过P2P层发送数据时,若数据包大小超过1KB,则压缩数据包后,将其发送到目标节点;
- 接收端解压数据包:节点收到数据包后,首判断收到的数据包是否被压缩,若数据包是压缩后的数据包,则将其解压后传递给指定群组,否则直接将数据传递给对应群组。
核心实现¶
综合考虑性能、压缩效率等,我们选取了Snappy来实现数据包压缩和解压功能。本节主要介绍网络压缩的实现。
数据压缩标记位¶
FISCO BCOS的网络数据包结构如下图:
网络数据包主要包括包头和数据两部分,包头占了16个字节,各个字段含义如下:
- Length: 数据包长度
- Version: 扩展位,用于扩展网络模块功能
- ProtocolID: 存储了数据包目的群组ID和模块ID,用于多群组数据包路由,目前最多支持32767个群组
- PaketType: 标记了数据包类型
- Seq: 数据包序列号
网络压缩模块仅压缩网络数据,不压缩数据包头。
考虑到压缩、解压小数据包无法节省数据空间,而且浪费性能,在数据压缩过程中,不压缩过小的数据包,仅压缩数据包大于c_compressThreshold
的数据包.c_compressThreshold
默认是1024(1KB)。我们扩展了Version的最高位,作为数据包压缩标志:
- 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节点具有网络压缩功能。
协议描述¶
交易结构及其RLP编码描述¶
FISCO BCOS的交易结构在原以太坊的交易结构的基础上,有所增减字段。FISCO BCOS 2.0.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/交易唯一标识)的生成流程如下:
RC2的生成流程也类似,只是在第一步rlp+hash
的transaction结构体中增加chainId、groupId和extraData三个字段。
区块结构及其RLP编码描述¶
FISCO BCOS的区块由以下五部分组成:
name | description | RLP index |
---|---|---|
blockHeader | 区块头RLP编码 | 0 |
transactions | 交易列表RLP编码 | 1 |
transactionReceipts | 交易回执列表RLP编码 | 2 |
hash | 区块头RLP编码后的哈希值 | 3 |
sigList | PBFT共识落盘阶段收集到的节点签名信息,Raft不使用 | 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新增字段 | - |
网络传输协议¶
FISCO BCOS 目前有两类数据包格式,节点与节点间通信的数据包为P2PMessage格式,节点与SDK间通信的数据包为ChannelMessage格式。
P2PMessage: v2.0.0-rc1¶
v2.0.0-rc1 P2PMessage包头长度为12字节,消息包具体格式如下:
name | type | description |
---|---|---|
length | uint32_t | 数据包长度,含包头和数据 |
groupID | int8_t | 群组ID,范围1-127 |
ModuleID | uint8_t | 模块ID,范围1-255 |
packetType | uint16_t | 数据包类型,同一模块ID下的子协议标识 |
seq | uint32_t | 数据包序列号,每个数据包自增 |
data | vector |
数据本身,长度为lenght-12 |
模块ID划分如下:
ModuleID | message |
---|---|
1 | P2P的AMOP子模块 |
2 | P2P的Topic子模块 |
3~7 | P2P其他子模块预留 |
8 | 共识的PBFT子模块 |
9 | 区块同步模块 |
10 | 交易池模块 |
11 | 共识的Raft子模块 |
12~255 | 其他模块预留 |
P2PMessage: v2.0.0-rc2¶
v2.0.0-rc2扩展了群组ID和模块ID范围,最多支持32767个群组,且新增了Version字段来支持其他特性(如网络压缩),包头大小为16字节,v2.0.0-rc2的网络数据包结构如下:
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 |
补充
- P2PMessage不限制包大小,由上层调用模块(共识/同步/AMOP等)进行包大小管理;
- 群组ID和模块ID可唯一标识协议ID(protocolID),三者关系为
protocolID = (groupID << sizeof(groupID)*8) | ModuleID
; - 数据包通过protocolID所在的16位二进制数值来区分请求包和响应包,大于0为请求包,小于0为相应包。
- 目前AMOP使用的packetType有
SendTopicSeq = 1,RequestTopics = 2,SendTopics = 3
。
ChannelMessage¶
name | type | description |
---|---|---|
lenght | uint32_t | 数据包长度,含包头和数据,最大长度为10M Byte |
type | uint16_t | 数据包类型 |
seq | string | 数据包序列号,32字节,SDK传入 |
result | int | 处理结果 |
data | vector |
数据本身 |
数据包类型枚举值及其对应的含义如下:
code | message | direction |
---|---|---|
0x12 | 以太坊消息 | SDK->节点 |
0x13 | 心跳包 | SDK->节点 |
0x30 | AMOP请求包 | SDK->节点 |
0x31 | AMOP响应包 | SDK->节点 |
0x32 | 上报Topic信息 | SDK->节点 |
0x10000 | 交易上链回调 | 节点->SDK |
处理结果枚举值及其对应的含义如下:
code | message |
---|---|
0 | 成功 |
100 | 节点不可达 |
101 | SDK不可达 |
102 | 超时 |
Web3SDK¶
Web3SDK可以支持访问节点、查询节点状态、修改系统设置和发送交易等功能。该版本(2.0)的技术文档只适用Web3SDK 2.0及以上版本(与FISCO BCOS 2.0及以上版本适配),1.2.x版本的技术文档请查看Web3SDK 1.2.x版本技术文档。
2.0版本主要特性包括:
- 提供调用FISCO BCOS JSON-RPC的Java API
- 支持预编译(Precompiled)合约管理区块链
- 支持链上信使协议为联盟链提供安全高效的消息信道
- 支持使用国密算法发送交易
环境要求¶
重要
- java版本
- FISCO BCOS区块链环境搭建
- 网络连通性
检查Web3SDK连接的FISCO BCOS节点channel_listen_port是否能telnet通,若telnet不通,需要检查网络连通性和安全策略。
Java应用引入SDK¶
通过gradle或maven引入SDK到java应用
gradle:
compile ('org.fisco-bcos:web3sdk:2.0.3')
maven:
<dependency>
<groupId>org.fisco-bcos</groupId>
<artifactId>web3sdk</artifactId>
<version>2.0.3</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节点证书配置¶
FISCO BCOS作为联盟链,其SDK连接区块链节点需要通过证书(ca.crt、node.crt)和私钥(node.key)进行双向认证。因此需要将节点所在目录nodes/${ip}/sdk
下的ca.crt
、node.crt
和node.key
文件拷贝到项目的资源目录,供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">
<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区块链参考这里)
- groupChannelConnectionsConfig:
- 配置待连接的群组,可以配置一个或多个群组,每个群组需要配置群组ID
- 每个群组可以配置一个或多个节点,设置群组节点的配置文件config.ini中
[rpc]
部分的listen_ip
和channel_listen_port
。
- channelService: 通过指定群组ID配置SDK实际连接的群组,指定的群组ID是groupChannelConnectionsConfig配置中的群组ID。SDK会与群组中配置的节点均建立连接,然后随机选择一个节点发送请求。
Spring Boot项目配置¶
提供Spring Boot项目中关于application.yml
的配置如下所示。
encrypt-type: 0 # 0:standard, 1:guomi
group-channel-connections-config:
all-channel-connections:
- group-id: 1 #group ID
connections-str:
- 127.0.0.1:20200 # node listen_ip:channel_listen_port
- 127.0.0.1:20201
- group-id: 2
connections-str:
- 127.0.0.1:20202 # node listen_ip:channel_listen_port
- 127.0.0.1:20203
channel-service:
group-id: 1 # The specified group to which the SDK connects
agency-name: fisco # agency name
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 = Credentials.create(p12KeyPair);
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 = Credentials.create(pemKeyPair);
System.out.println("PEM Address: " + credentialsPEM.getAddress());
通过SDK部署并调用合约¶
部署并调用合约¶
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区块链请参考国密使用手册。
- 启用国密功能:application.xml/application.yml配置文件中将encryptType属性设置为1。
国密版SDK调用API的方式与普通版SDK调用API的方式相同,其差异在于国密版SDK需要生成国密版的Java合约文件。国密版的编译器jar包下载请参考这里,用于将Solidity合约文件转为国密版的Java合约文件。可以在项目src目录下新建一个lib目录,将下载的国密版jar包放置在lib目录下。然后修改项目的build.gradle文件,移除普通版编译器jar包,引入国密编译器jar包。
compile ("org.fisco-bcos:web3sdk:x.x.x"){ //如:web3sdk:2.0.0
exclude module: 'solcJ-all'
}
// 0.4版国密合约编译器jar包
compile files('lib/solcJ-all-0.4.25-gm.jar')
// 0.5版本国密合约编译器jar包
// compile files('lib/solcJ-all-0.5.2-gm.jar')
Solidity合约文件转换为国密版Java合约文件的步骤、部署和调用国密版合约的方法均与普通版SDK相同。
Web3SDK API¶
Web3SDK API主要分为Web3j API和Precompiled Service API。其中Web3j API可以查询区块链相关的状态,发送和查询交易信息;Precompiled Service 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): 根据表名查询表的信息,主要包含表的主键和其他属性字段。返回表类型,主要包含表的主键字段名和其他属性字段名。
区块链浏览器¶
一、描述¶
1.1、基本描述¶
全新适配FISCO-BCOS 2.0.0版本,如果使用FISCO-BCOS 1.2或1.3版本请用v1.2.1版本。
区块链浏览器将区块链中的数据可视化,并进行实时展示。方便用户以Web页面的方式,获取当前区块链中的信息。本浏览器版本适配FISCO-BCOS 2.0,关于2.0版本的特性可以参考此链接。在使用本浏览器之前需要先理解2.0版本的群组特性,详情可以参考此链接。
二、使用前提¶
2.1、群组搭建¶
区块链浏览器展示的数据是从区块链上同步下来的。为了同步数据需要初始化配置(添加群组信息和节点信息),故在同步数据展示前需要用户先搭建好区块链群组。FISCO-BCOS 2.0提供了多种便捷的群组搭建方式。
- 如果是开发者进行开发调试,建议使用build_chain。
- 如果是开发企业级应用,建议使用企业部署工具FISCO-BCOS generator。
两者的主要区别在于build_chain为了使体验更好,搭建速度更快,辅助生成了群组内各个节点的私钥;但企业部署工具出于安全的考虑不辅助生成私钥,需要用户自己生成并设置。
三、区块链浏览器搭建¶
区块链浏览器分为两个部分:后台服务fisco-bcos-browser、前端web页面fisco-bcos-browser-front。
当前版本我们提供了两种搭建方式:一键搭建和手动搭建。
四、初始化环境¶
4.1、添加群组¶
服务搭建成功后,可使用网页浏览器访问nginx配置的前端IP和前端端口,进入到浏览器页面。未初始化群组的浏览器页面会引导大家到新建群组配置页面,新建群组需要配置群组ID,群组名称,描述。
群组ID需要和区块链群组ID保持一致。 群组ID有多种查看方式,1、rpc接口获取。2、控制台命令。
群组名称是为群组ID取的一个有意义,便于理解的名字。
描述字段是对名称的进一步说明。
4.2、添加节点¶
添加群组所在的节点信息,用于区块链浏览器连接拉取相关展示信息。节点的rpc端口信息和p2p端口信息可以从节点的 config.ini配置文件中获取。
为了使用方便,新添加的群组会自动同步添加其他群组已经配置的共用节点信息。
JSON-RPC API¶
下列接口的示例中采用curl命令,curl是一个利用url语法在命令行下运行的数据传输工具,通过curl命令发送http post请求,可以访问FISCO BCOS的JSON RPC接口。curl命令的url地址设置为节点配置文件[rpc]
部分的[listen_ip]
和[jsonrpc listen port]
端口。为了格式化json,使用jq工具进行格式化显示。错误码参考RPC设计文档。交易回执状态列表参考这里。
getClientVersion¶
返回节点的版本信息
参数¶
无
返回值¶
object
- 版本信息,字段如下:Build Time
:string
- 编译时间Build Type
:string
- 编译机器环境FISCO-BCOS Version
:string
- FISCO BCOS版本Git Branch
:string
- 版本分支Git Commit Hash
:string
- 版本最新commit哈希
- 示例
// 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
- 账户类型allowFutureBlocks
:bool
- 允许未来块标志cfgErr
:bool
- 配置错误标志connectedNodes
:unsigned int
- 连接的节点数consensusedBlockNumber
:unsigned int
- 下一个共识的最新块高currentView
:unsigned int
- 当前视图groupId
:unsigned int
- 群组IDhighestblockHash
:string
- 最新块哈希highestblockNumber
:unsigned int
- 最新区块高度leaderFailed
:bool
- leader失败标志max_faulty_leader
:unsigned int
- 最大容错节点数sealer.index
:string
- 节点序号为index的nodeIdnode index
:unsigned int
- 节点的序号nodeId
:string
- 节点的IDnodeNum
:unsigned int
- 节点的数omitEmptyBlock
:bool
- 忽略空块标志位protocolId
:unsigned int
- 协议ID号toView
:unsigned int
- 目前到达的view值prepareCache_blockHash
:string
- prepareCache哈希prepareCache_height
:int
- prepareCache高度prepareCache_idx
:unsigned int
- prepareCache序号prepareCache_view
:unsigned int
- prepareCache视图rawPrepareCache_blockHash
:string
- rawPrepareCache哈希rawPrepareCache_height
:int
- rawPrepareCache高度rawPrepareCache_idx
:unsigned int
- rawPrepareCache序号rawPrepareCache_view
:unsigned int
- rawPrepareCache视图committedPrepareCache_blockHash
:string
- committedPrepareCache哈希committedPrepareCache_height
:int
- committedPrepareCache高度committedPrepareCache_idx
:unsigned int
- committedPrepareCache序号committedPrepareCache_view
:unsigned int
- committedPrepareCache视图futureCache_blockHash
:string
-futureCache哈希futureCache_height
:int
- futureCache高度futureCache_idx
:unsigned int
- futureCache序号signCache_cachedSize
:unsigned int
- signCache_cached大小commitCache_cachedSize
:unsigned int
- commitCache_cached大小viewChangeCache_cachedSize
:unsigned int
- viewChangeCache_cached大小
- 当共识机制为Raft时(Raft详细设计参考Raft设计文档),字段如下:
accountType
:unsigned int
- 账户类型allowFutureBlocks
:bool
- 允许未来块标志cfgErr
:bool
- 配置错误标志consensusedBlockNumber
:unsigned int
- 下一个共识的最新块高groupId
:unsigned int
- 群组IDhighestblockHash
:string
- 最新块哈希highestblockNumber
:unsigned int
- 最新区块高度leaderId
:string
- leader的nodeIdleaderIdx
:unsigned int
- leader的序号max_faulty_leader
:unsigned int
- 最大容错节点数sealer.index
:string
- 节点序号为index的nodeIdnode index
:unsigned int
- 节点的indexnodeId
:string
- 节点的IDnodeNum
: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":4,
"currentView":153,
"groupId":1,
"highestblockHash":"0x98e186095a88f7b1b4cd02e3c405f031950577626dab55b639e024b9f2f8788b",
"highestblockNumber":3,
"leaderFailed":false,
"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":264,
"toView":153
},
{
"prepareCache_blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"prepareCache_height":-1,
"prepareCache_idx":"65535",
"prepareCache_view":"9223372036854775807"
},
{
"rawPrepareCache_blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"rawPrepareCache_height":-1,
"rawPrepareCache_idx":"65535",
"rawPrepareCache_view":"9223372036854775807"
},
{
"committedPrepareCache_blockHash":"0x2e4c63cfac7726691d1fe436ec05a7c5751dc4150d724822ff6c36a608bb39f2",
"committedPrepareCache_height":3,
"committedPrepareCache_idx":"2",
"committedPrepareCache_view":"60"
},
{
"futureCache_blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"futureCache_height":-1,
"futureCache_idx":"65535",
"futureCache_view":"9223372036854775807"
},
{
"signCache_cachedSize":"0"
},
{
"commitCache_cachedSize":"0"
},
{
"viewChangeCache_cachedSize":"0"
}
]
}
// 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
- 节点的IDprotocolId
: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
- 节点的IDTopic
:array
- 节点关注的topic信息
- 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getPeers","params":[1],"id":1}' http://127.0.0.1:8545 |jq
// Result
格式化JSON:
{
"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
- 群组IDblockHash
:string
- 区块哈希includeTransactions
:bool
- 包含交易标志(true显示交易详细信息,false仅显示交易的hash)
返回值¶
object
- 区块信息,字段如下:extraData
:array
- 附加数据gasLimit
:string
- 区块中允许的gas最大值gasUsed
:string
- 区块中所有交易消耗的gashash
:string
- 区块哈希logsBloom
:string
- log的布隆过滤器值number
:string
- 区块高度parentHash
:string
- 父区块哈希sealer
:string
- 共识节点序号sealerList
:array
- 共识节点列表stateRoot
:string
- 状态根哈希timestamp
:string
- 时间戳transactions
:array
- 交易列表,当includeTransactions
为false
时,显示交易的哈希。当includeTransactions
为true
时,显示交易详细信息(详细字段见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
- 群组IDblockNumber
:string
- 区块高度(十进制字符串或0x开头的十六进制字符串)includeTransactions
:bool
- 包含交易标志(true显示交易详细信息,false仅显示交易的hash)
返回值¶
- 示例
// 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
getBlockHashByNumber¶
返回根据区块高度查询的区块哈希
参数¶
groupID
:unsigned int
- 群组IDblockNumber
: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
- 群组IDtransactionHash
:string
- 交易哈希
返回值¶
object
: - 交易信息,其字段如下:blockHash
:string
- 包含该交易的区块哈希blockNumber
:string
- 包含该交易的区块高度from
:string
- 发送者的地址gas
:string
- 发送者提供的gasgasPrice
:string
- 发送者提供的gas的价格hash
:string
- 交易哈希input
:string
- 交易的输入nonce
:string
- 交易的nonce值to
:string
- 接收者的地址,创建合约交易的该值为nulltransactionIndex
: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
- 群组IDblockHash
:string
- 区块哈希transactionIndex
:string
- 交易序号
返回值¶
- 示例
// 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
- 群组IDblockNumber
:string
- 区块高度(十进制字符串或0x开头的十六进制字符串)transactionIndex
:string
- 交易序号
返回值¶
- 示例
// 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
- 群组IDtransactionHash
:string
- 交易哈希
返回值¶
object
: - 交易信息,其字段如下:blockHash
:string
- 包含该交易的区块哈希blockNumber
:string
- 包含该交易的区块哈希contractAddress
:string
- 合约地址,如果创建合约,则为”0x0000000000000000000000000000000000000000”from
:string
- 发送者的地址input
:string
- 交易输入gasUsed
:string
- 交易消耗的gaslogs
:array
- 交易产生的loglogsBloom
:string
- log的布隆过滤器值status
:string
- 交易的状态值,参考:交易回执状态to
:string
- 接收者的地址,创建合约交易的该值为nulltransactionHash
:string
- 交易哈希transactionIndex
:string
- 交易序号
- 示例
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"getTransactionReceipt","params":[1,"0x7536cf1286b5ce6c110cd4fea5c891467884240c9af366d678eb4191e1c31c6f"],"id":1}' http://127.0.0.1:8545 |jq
// Result
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"blockHash": "0x10bfdc1e97901ed22cc18a126d3ebb8125717c2438f61d84602f997959c631fa",
"blockNumber": "0x1",
"contractAddress": "0x0000000000000000000000000000000000000000",
"from": "0x6bc952a2e4db9c0c86a368d83e9df0c6ab481102",
"gasUsed": "0x64d8",
"logs": [ ],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"status": "0x0",
"to": "0xd6f1a71052366dbae2f7ab2d5d5845e77965cf0d",
"transactionHash": "0x7536cf1286b5ce6c110cd4fea5c891467884240c9af366d678eb4191e1c31c6f",
"transactionIndex": "0x0"
}
}
getPendingTransactions¶
返回待打包的交易信息
参数¶
groupID
:unsigned int
- 群组ID
返回值¶
object
: - 带打包的交易信息,其字段如下:blockHash
:string
- 包含该交易的区块哈希blockNumber
:string
- 包含该交易的区块哈希contractAddress
:string
- 合约地址,如果创建合约,则为”0x0000000000000000000000000000000000000000”from
:string
- 发送者的地址gas
:string
- 发送者提供的gasgasPrice
:string
- 发送者提供的gas的价格hash
:string
- 交易哈希input
:string
- 交易的输入nonce
:string
- 交易的nonce值to
:string
- 接收者的地址,创建合约交易的该值为nullvalue
: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
- 群组IDaddress
: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
: - 当前交易总数和区块高度信息,其字段如下:txSum
:string
- 交易总数blockNumber
: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": {
"txSum": "0x1",
"blockNumber": "0x1"
}
}
getSystemConfigByKey¶
返回根据key值查询的value值
参数¶
groupID
:unsigned int
- 群组IDkey
: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
- 群组IDobject
: - 请求信息,其字段如下:from
:string
- 发送者的地址to
:string
- 接收者的地址value
:string
- (可选)转移的值data
:string
- (可选)编码的参数,编码规范参考Ethereum Contract ABI
返回值¶
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": "0x1",
"output": "0x"
}
}
sendRawTransaction¶
执行一个签名的交易,需要区块链共识
参数¶
groupID
:unsigned int
- 群组IDrlp
: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
错误码描述¶
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 |
非群组内节点发起无效的请求 |
交易回执状态¶
status(十进制/十六进制) | message | 含义 |
---|---|---|
0(0x0) | None | 正常 |
1(0x1) | Unknown | 未知异常 |
2(0x2) | BadRLP | 无效RLP异常 |
3(0x3) | InvalidFormat | 无效格式异常 |
4(0x4) | OutOfGasIntrinsic | gas不足异常 |
5(0x5) | InvalidSignature | 无效的签名异常 |
6(0x6) | InvalidNonce | 无效nonce异常 |
7(0x7) | NotEnoughCash | cash不足异常 |
8(0x8) | OutOfGasBase | gas不足异常 |
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 | 被调用的合约地址不存在 |
Precompiled Service API 错误码¶
错误码 | 消息内容 | 备注 |
---|---|---|
0 | success | |
-50000 | permission denied | |
-50001 | table name already exist | |
-50100 | unknow function call | |
-50101 | table does not exist | |
-51000 | table name and address already exist | |
-51001 | table name and address does not exist | |
-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 string length exceeds the maximum limit | SDK错误码 |
-51300 | invalid configuration entry | |
-51500 | contract name and version already exist | |
-51501 | condition parse error | |
-51502 | condition operation undefined |
常见问题解答¶
版本相关¶
问:
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支持多种搭建方式,常用方式有:
问:
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查询。
Web3SDK¶
问:
Web3SDK对Java版本有要求吗?
答:
要求JDK8或以上
CentOS的yum仓库的OpenJDK由于缺少JCE(Java Cryptography Extension),导致Web3SDK无法正常连接区块链节点,在使用CentOS操作系统时,推荐从OpenJDK网站自行下载。 安装指南
问:
Web3SDK配置完成,发送交易失败的原因是什么?
答:
applicationContext.xml中的ip、端口、群组号填错或者是缺少节点的ca.crt、node.crt和node.key文件。
企业级部署工具¶
问: 企业级部署工具使用时出现找不到pip
答: 企业级部署工具依赖python pip,使用以下命令安装:
$ python -m pip install
问: 企业级部署工具使用时出现
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