Hyperchain文档¶
平台介绍¶
产品简介¶
趣链区块链平台是趣链科技自主研发的企业级联盟区块链平台,是实现多方可信协作、价值互联互通的分布式商业基础设施。 作为国内首个国产自主可控的联盟链,平台提供自适应共识算法、多语言智能合约引擎、全国密支持、多维隐私保护、软硬协同一体化等多项核心技术功能, 可支撑十万级节点分层组网,千个共识节点组网,日均TB级数据上链,GB级图片、音频文件的存储,吞吐量可达5万TPS, 支持预言机、数据索引、文件保险箱、数据归档等数据管理功能, 提供分区共识、TEE账本加密等安全隐私保护技术, 是全球单链峰值速度最快、支持节点最多、存储容量最大的联盟链基础设施,满足企业级应用在高安全、高性能、可扩展、易运维、规范监管等方面的需求, 是目前行业内落地应用最多、节点分布最广、承载业务规模最大的区块链平台。
趣链区块链平台是国内第一批通过工信部标准院与信通院区块链标准测试的区块链平台, 在2019年中国信通院区块链测评中,获得功能测试和性能测试双项第一,并在2017-2020年的中国信通院可信区块链测评中连续荣获第一, 符合中国人民银行《金融分布式账本技术安全规范》。
整体架构¶
平台具有万级TPS吞吐量和毫秒级系统延迟,支持交易级别的隐私数据保护、混合型数据存储、可信执行环境、联盟自治、预言机以及可视化运维等特性。其技术架构图如下:
- 基础物理层 :包括物理机云平台等基础资源,并配以硬件加密机、密码卡等安全设配,物联网硬件设备,使平台可在云服务、软硬件结合、物联网等多种场景下安全稳定运行;
- 核心协议层 :区块链的核心组成部分,主要包括共识算法、P2P网络、智能合约引擎以及存储引擎等组件,为整个区块链网络提供安全可信的支撑环境;
- 扩展组件层 :构建于核心协议层之上,基于区块链网络从数据管理、治理审计、安全隐私、运维管理、智能合约五个方面为应用扩展提供安全、高效、友好、易用的功能特性,适应多样化应用场景,打造最佳用户体验;
- 接口管理层 :面向区块链用户,支持多种协议的 RPC/API 接口以及SDK软件开发工具,提供应用与区块链交互的桥梁。
核心核心功能¶
- 自适应共识算法
平台支持RBFT、NoxBFT、RAFT等多种共识算法,用户可以根据区块链中不同的网络环境和业务场景采用最优的共识算法。
- RBFT :一种高鲁棒性拜占庭共识算法。可以在节点数据强一致性的情况下支持万级TPS和毫秒级延迟。并且通过内部Recovery机制支持节点动态管理和失效数据恢复,很好的满足区块链商业应用中高性能、高鲁棒性、高可用的需求。
- NoxBFT :一种支持大规模组网的新型共识算法。可支持上千节点规模共识组网。通过聚合签名、活性机制等方法将网络复杂度由O(n2)降低至O(n),有效解决大规模节点组网场景下共识效率低下、可扩展性不强的问题。
- RAFT :一种高可信分布式共识算法,相比RBFT可以容忍更多的错误节点,整体性能优于RBFT,但在其共识网络环境下不能容忍拜占庭错误(节点作恶,发送错误消息等),适用于高可信的联盟链网络环境或者企业内部私有化部署。
- P2P网络
平台平台支持gRPC网络协议,通过自适应路由进行网络节点自发现,支持跨域转发机制,降低网络连接数。
- 加密机制
平台采用可插拔多级加密机制,从不同层级保证平台安全。消息摘要(SHA3/SHA-256/SM3)保证数字安全,数字签名(ECDSA/ED25519/SM2)保证身份安全,密钥协商(ECDH/SM2)、密文传输(AES/SM4)与TLS保证通信安全,并实现基于GPU/FPGA加速的验签算法,以及基于ED25519的批量验签,满足大规模并发计算的需求。同时集成硬件密码卡,提供密钥存储和随机数生成等功能。
- 存储模型
平台自研区块数据专用存储引擎FileLog,支状态数据存储引擎LevelDB、并设计状态数据多级缓存机制Multicache,现已支持日均TB级数据量链上存储。
- 执行引擎
平台支持Java、Go等多种主流合约语言,并配以HVM、EVM、BVM等多种合约执行引擎,提供完善的合约全生命周期管理,具有编程友好、合约安全、执行高效的特性。其中自主研发的HVM支持Java语言合约编写、分层调用模式、合约访问控制和丰富的工具方法集等特性。
- 拓展架构
- 大规模组网 :大规模组网模型支持多类型节点的分层部署,以共识节点层为中心,凭借非共识节点层实现区块链数据网络扩展,依靠轻节点层实现区块链验证网络扩展,最后通过轻客户端层将数以万计的物联网终端设备接入区块链,实现数十万不同类型网络节点的大规模部署。
- 读写分离 :平台提供非验证节点NVP,不参与共识,仅同步账本数据,通过提供交易转发、查询等服务,实现读写分离,减轻VP节点及共识网络压力,保证区块链系统的高效运行。
- 轻节点 :平台提供轻节点SPV,用于分担网络的压力,作为未来的主力节点,只存储网络的少量数据,通过证明来实现功能。
- 分区扁平化 :分区完全扁平化,四个节点及以上可以组成一个分区,网络中不一定需要global分区的存在,分区内的节点建立物理和逻辑连接。分区间业务隔离,实现了交易数据对其他分区不可见,同时,由于交易并行执行,分区性能也不会随分区数量增加而显著下降。
- 数字资产
- NFT :平台原生支持数字资产账户,满足NFT场景的需求功能,提供内置与NFT相关的功能接口。
- 隐私保护
- 账本加密 :账本加密通过TEE可信执行环境将用户的账户信息和业务数据进行按需加密,在保证安全性的同时做到可查验可审计。
- 零知识证明 :证明方能够在不向验证方提供任何有用的信息的情况下,使验证方相信某个陈述是正确的。
- 治理审计
- 身份认证/准入机制 :平台采取集中式认证体系、分布式认证体系两种方式实现准入控制。集中式认证体系包括自建CA和CFCA两种证书体系,其中CFCA满足对于证书系统安全性与权威性有较高要求的银行或金融机构的需求。分布式认证体系将证书管理权限由中心机构转移到联盟链各参与方,具有去中心、自动化、高效等优点。
- 用户账户体系 :不同的应用场景对账户的使用需求和管理需求各不相同,针对不同的账户体系需求,趣链区块链平台提供两类账户标识(普通账户标识、DID账户标识)以及三类用户账户(普通账户、证书账户、DID账户)。
- 多级权限管理 :将链上账户角色划分为链级管理员、节点管理员、审计管理员、合约管理员以及普通用户,通过链级联盟自治管理CAF、节点级证书授权访问控制、用户级合约权限访问控制等方式,实现多层级管理和限制,为系统及账本数据管理提供全方位安全性保障。
- 联盟自治CAF :采取在联盟链网络中创建联盟链自治成员组织,通过提案和投票的形式在组织内部表决联盟中的状态行为(节点管理、成员管理、合约管理)的方式,提供区块链原生的联盟规则协商管理机制。
- 安全审计 :提供实时全面的区块链系统一站式安全审计服务,允许审计方对账本数据访问/变更/同步、共识历史、系统异常等全量系统事件开展精确有效的审计工作,符合金融级审计要求。
- 合约命名 :CNS通过简单合约地址命名与合约地址&abi等合约信息进行映射。当用户调用合约接口时,传入合约映射的name、接口名称等信息。用户仅需简单使用命名后的合约名称即可调用合约。
- 证书链上吊销 :平台设计了证书链上吊销功能,证书吊销操作以交易的形式在链上被执行,将证书生效后的管理能力与区块链交易结合,实现链上证书管理。
- 数据管理
- 数据归档 :将旧的线上区块数据归档移到线下转存,同时提供Archive Reader用于归档数据浏览。
- 数据索引 :提供高效安全的业务数据自定义条件检索功能,支持精准、匹配(模糊)、多条件查询等多种检索模式,极大简化了上层业务系统开发和维护复杂度。
- 文件保险箱 :支持GB级的文件可信存储、安全共享与高效查询,同时支持用户按需存取文件,并提供多级文件存取权限管理能力。
- 预言机 :通过提供Oracle预言机服务,完成区块链与链外信息互通。
- 链上SQL :支持使用符合MySQL语法规则的SQL语句进行链上数据的增删改查。
- 分区共识 :将业务场景中互不相关的敏感交易通过分区进行数据存储和执行空间的隔离,数据存储和执行过程在不同分区之间不可见,通过分区隔离实现隐私保护。
- 运维管理
- 网络流控 :平台提供交易拦截、消息分发、带宽限流等多维度网络流量控制服务,在请求激增场景下保证系统的稳定运行,提高系统可用性。
- 数据监控 :平台提供一站式数据可视化监控服务,满足业务数据大屏展示需求,同时帮助运维人员轻松了解底层平台运行情况,快速定位问题。
- 灾备切换 :平台提供灾备节点CVP,在必要时可快速升级为共识节点VP,有效降低运维门槛,保障系统高可用。
- 完备生态组件
- 多语言SDK :平台提供JavaSDK、GoSDK、JSSDK、CSSDK以及LuaSDk等多语言SDK工具集,支持不同语言的开发工程师更方便快速地开发区块链应用。
- Hyperbench测试框架 :平台自研通用区块链测试框架Hyperbench,支持针对趣链区块链平台、Fabric等主流联盟链平台的性能测试。
- Archive-reader浏览器 :平台提供了Archive-reader浏览器用于查阅归档数据。该浏览器无需与区块链部署在同一服务器上,用户可在独立的服务器上运行Archive-reader浏览器,并导入相关归档数据即可开始查阅。
- 消息订阅 :支持rabbit MQ和KafKa双模式,以便外部系统捕获、监听区块链平台的状态变化,实现链上链下的消息互通。
国产自主可控¶
平台集成国产自主可控的服务器硬件,打造符合国家信创标准的区块链软硬件一体机设备,为开发者提供强隐私、高性能、高安全、即用即上链的区块链技术服务;针对政务、军事等对安全性有严格要求的场景,进一步集成区块链密码卡、网络共识加速器、可信执行环境等硬件设备与技术;针对硬件服务器的安全性要求,构建区块链领域专用安全自主可控设备包括:申泰 RM5000-F服务器、申泰 RM5020-L服务器、华为TaiShan 2280服务器等。
节点类型¶
平台节点分为验证节点(VP,Validate Peer)、非验证节点(NVP,Non-Validate Peer)、轻节点(SPV)、热备节点(CVP,Candidate VP)三类:
- VP指区块链网络中参与共识验证的节点;
- NVP指区块链网络中不参与共识验证,仅同步账本数据的节点;
- SPV指区块链网络重分担网络压力的节点,只存储网络的少量数据,通过证明来实现功能;
- CVP指区块链网络中提供灾备服务的节点,在必要时可升级为VP。
交易流程¶
平台在进行数据操作时,通常以一笔交易的形式在区块链网络中进行流转,用户存储的数据和合约都存储在交易结构当中,其具体的运转流程如下图所示:
- 交易发起 :在客户端发起一笔交易之前,需要拿到证书管理中心(CA)颁发的准入证书,并且所需智能合约已经部署在区块链节点上。联盟机构通过部署在机构内部的后台应用服务进行交易,然后通过调用SDK发交易的API接口生成一条交易,SDK会用客户端指定的用户私钥对交易进行签名,再对交易进行JSON-RPC协议封装。
- 交易共识 :当节点接收到客户端的交易时,首先会对交易进行验签操作,验签通过后,交易请求会汇集到统一的消息分发事件总线,事件总线收到消息后会转发给共识模块,执行共识流程,如RBFT算法共识即为三阶段流程共识。
- 交易执行 :交易共识完成后,证明多方已经确认该笔交易顺序及内容的正确性,在此之后会将打包的交易提交给虚拟机执行引擎,进行执行操作,交易执行会将交易体结构进行解析,解析的主要内容为交易的主体信息(交易数据)以及交易中的合约信息(合约数据),执行引擎会根据交易的内容和合约的业务逻辑进行相关业务操作。
- 交易存储 :交易执行之后,会对交易信息(区块数据)和合约信息(状态数据)进行持久化操作,同步写入区块和账本,最终持久化到FileLog和LevelDB中,至此完成整个交易流程。
版本信息¶
趣链区块链平台 2.7 新特性¶
趣链区块链浏览器¶
趣链区块链浏览器(内嵌版BAAS),可基于web页面实现区块查看、节点管理、合约管理等功能
联盟链激励技术¶
联盟链激励技术支持,支持根据gas值进行交易打包排序;支持RBFT共识算法线下切换为NoxBFT共识算法
轻节点¶
可减轻网络压力,轻节点只存储少量数据,通过证明来实现功能
异常情况恢复的数据完整性保证¶
异常情况恢复的数据完整性保证,提供在服务器宕机重启情况下的数据恢复和一致性保障【注:该功能可通过配置项开启或关闭,默认开启以保障生产数据的安全性,但是会带来明显的性能降低,如对性能要求较高,可通过修改相关配置项为false以关闭此功能】
账本数据验证及验证结果查询¶
GRPC消息推送功能¶
应用层可直接基于GRPC接口构建消息推送服务,无需借助第三方MQ【注:GRPC接口目前仅支持交易类接口,具体接口清单可参考GRPC接口文档】
SGX WASM和零知识证明¶
新增功能并统一了TEE和零知识证明的对外接口【注:SGX WASM功能需基于特定硬件】
优化功能¶
- NoxBFT共识算法的全面优化重构,包括检查点机制、批量验签、持久化和数据恢复等方面,在效率和稳定性方面均有提升
- 交易池性能优化,提高共识效率
- Solo版本性能优化,优化后无验签版本可达10wTPS,带验签版本可达6wTPS
- syncchain流程的优化和重构,为灰度升级做准备
- 存储模块检查点机制的优化,提升存储对上层模块的请求响应速度
- 存储模块各db迭代器一致性修改,优化代码逻辑
- API层增加验签功能,以实现在接口层面过滤非法交易
- 对网络层逻辑握手代码进行优化重构,提升对重复消息的容错能力,并且对握手流程中出现的错误,可以根据握手状态进行纠正
- 完成部分链级配置上链,为灰度升级做准备
- 节点增删功能优化,简化了中心CA场景下的增删节点流程
- 对称加解密算法、哈希算法的解耦合:关键密码算法通过接口调用,算法类型统一配置,降低模块间耦合
- 对kvsql执行引擎的gas机制重构优化:解决锁争用带来的性能问题;优化out of gas信息返回的方式;解决部分gas扣费不合理的问题
趣链区块链平台 2.3 新特性¶
优化功能¶
- 优化EVM虚拟机,允许在EVM合约使用create2指令,实现在未部署合约的情况下预留出合约地址并与地址交互
- 优化数据同步功能,可支持Flato新增节点同步旧版hyperchain历史数据
- 优化节点发现频率,以解决在某些异常场景下节点长时间无法被其他节点发现的问题
- 优化虚拟机模块,完成HVM和kvsql模块(支持链上SQL执行)的浮点数问题修复
- 优化密码模块,所有map数据结构均替换为syncmap,以解决并发问题
- 优化rbft模块,新增stable-checkpoint签名机制,为后续rbft与noxbft两种共识算法的整合做准备。
- 对签名长度做校验,优化设置签名内容错误的情况下,导致节点panic的问题
- 修复license替换,节点重启失败后,没有报license相关的错误提醒
- 修复节点定期检查license失败的情况
- 修复向非vp节点注册signed类型的队列会导致节点panic的问题
- 修复分布式CA新增节点过程中执行ns stop命令时,命令卡住问题
- 修复nvp与vp重连时多次出现duplicate metrics collector registration
- 优化/修改time.Timer的用法,加快物理连接重连速度
- 修复 cvp在与vp建立连接后可能出现db closed的错误
- 修复mq模块的内存泄漏问题
趣链区块链平台 2.2 新特性¶
数字资产底链改造¶
- 增加对数字资产账户支持
- 新增合约数字资产接口以及链账户操作接口
- 新增ToC场景下的账户、权限管理体系
完成子公司合约权限管理功能以及License Server改造¶
- 支持对子公司部署的链进行合约部署权限的管控
- 对试用版license的在线管控
创世状态上链¶
完成创世状态上链需求,以更好的支持节点增删等操作,并为数字资产功能以及后续的灰度升级功能打下基础
零知识证明二期¶
- 完成noxbft配置交易功能
- 新增grpc接口服务,支持grpc双向流模式
优化功能¶
- 完成非法交易重构,非法交易在所有节点上落盘,以解决交易去重结果不一致的风险
- 完成namespace启停优化,规范了各组件的启停顺序以及组件内部的启停流程,避免在单个namespace启停时出现卡住、panic等问题
- 完成共识交易池重构,提升共识性能
- 完成kvsql(一键链改)性能优化,分别从预处理、IO和执行三方面进行了优化,提升kvsql执行性能
- 完成节点发现优化功能对于分布式CA的适配
趣链区块链平台 2.0.11 新特性¶
一键链改¶
趣链区块链平台 V2.0.11新增一键链改功能,支持 mysql 历史数据平滑迁移至底层平台 sql 执行引擎,并支持使用 SQL 语句进行增删改查。
NoxBFT¶
趣链区块链平台 V2.0.11接入NoxBFT共识算法。 NoxBFT共识算法可有效解决大规模节点组网场景下共识效率低下、可扩展性不强的问题。
CVP节点功能优化¶
趣链区块链平台 V2.0.11优化了CVP节点功能,新增了拦截器以及证书同步功能。
节点发现功能优化¶
趣链区块链平台 V2.0.11优化了节点发现问题,增大了新增节点场景下组网成功的概率。
HVM性能优化¶
趣链区块链平台 V2.0.11优化了HVM性能,并优化了HVM合约升级时的处理逻辑,防范不规范的合约升级行为。
趣链区块链平台 2.0.8 新特性¶
DID数字身份¶
趣链区块链平台 V2.0.8新增DID数字身份功能,自主可控、全域自发现的分布式数字身份,将用户身份的管控权归还用户,并打破跨平台间的信息屏障。一个用户数字身份由DID、DID文档、可验凭证三部分组成,每一个DID都必须拥有唯一的DID文档,但拥有不定数量的可验证凭证。
更多关于DID数字身份的介绍及使用方法,请参考《DID证书账户使用手册》
CVP灾备切换¶
趣链区块链平台 V2.0.8新增CVP灾备切换功能,提供专门的灾备节点CVP,能在VP发生异常宕机时可快速升级为VP,参与共识,并提供和原VP同等的区块链服务,备份内容包括账本数据与配置文件。
更多关于CVP灾备切换的介绍及使用方法,请参考《CVP使用手册》
分区扁平化¶
趣链区块链平台 V2.0.8新增分区扁平化功能,不同分区间完全隔离,节点无需加入global分区即可自行组建分区及连通网络,且宕机不影响其他分区正常运行。
在大规模多分区部署区块链网络的场景下,如果所有节点都在global分区下,那么其网络架构将非常复杂,因此需要进行分区扁平化的设计。尤其在主侧链场景中,侧链之间的网络是很难通信的,也无需通信,所以需要扁平化。因此设计了该功能,以实现:
- 每四个及以上个数节点可以组成一个分区,分区内的节点建立物理和逻辑连接;
- 支持自由模式和管理模式;自由模式:所有节点可以自行建立分区;管理模式:节点建立分区需依赖于global。
- 分区节点可增删,管理模式下分区节点的增删需依赖于global。
更多关于分区扁平化的介绍及使用方法,请参考《分区共识使用手册》
节点发现¶
趣链区块链平台 V2.0.8新增节点发现功能,不需要显式配置所有直连节点的网络地址,只需要在集群网络已经是一个连通图的前提下,配置集群内一个或以上个节点的 IP 地址和端口号即可让正在启动节点与集群里的同一个分区(namespace)下的所有节点建立起网络连接,从而达到网络配置简化的目的。
更多关于节点发现的介绍及使用方法,请参考《跨域网络使用手册》
可信文件共享¶
趣链区块链平台 V2.0.8新增可信文件共享功能,可信文件共享是区块链技术与文件存储技术高度结合的产物,是文件存储与共享的安全港,具有安全存储、可信共享两大特征。一个文件保险箱的组成部分有区块链网络、区块链节点、文件空间。其中索引信息不仅包括文件的查询索引,还记录着文件的节点存储权限(哪些节点可以存储该文件)以及用户下载权限(哪些用户可以下载该文件),该权限经区块链共识记录,不可篡改。
更多关于可信文件共享的介绍及使用方法,请参考 《文件保险箱-用区块链保护文件》<https://mp.weixin.qq.com/s/py_-0Dtkba1wrdfVOMIlvA>
链上证书吊销¶
趣链区块链平台 V2.0.8新增链上证书吊销功能,解决CA吊销证书到通知区块链各节点可能存在较长时间差的时效性问题及无法实现证书吊销后不再被网络所信任的原子性问题,实现:
- 证书吊销操作以交易的形式在链上被执行,执行后,证书失效,相关逻辑连接断开;
- 链上将记录失效证书;
- 将证书生效后的管理能力与区块链交易结合,实现链上证书管理。
验签优化¶
趣链区块链平台 V2.0.8对验签性能进行了优化,一个是从API接收到的交易直接进行验签,一个是从其他节点转发过来的交易会在执行阶段进行验签。
一体机TEE开发¶
趣链区块链平台 V2.0.8对一体机TEE开发进行了优化,在一体机环境下,账本加密针对用户的账户信息和业务数据进行按需加密操作,将密钥存储在TEE硬件可信执行环境中,账本数据通过TEE的密钥进行加解密。
分布式ca新增节点组网优化¶
趣链区块链平台 V2.0.8优化了分布式ca新增节点组网。解决新增节点组网时,老节点由于异常导致等待提案超时,从而给新节点发送reject消息,导致新节点宕机问题。
网络重构物理连接建立机制¶
趣链区块链平台 V2.0.8优化了网络重构物理连接建立机制,V2.0.8以前版本 规定物理连接只能字典序大的节点作为发起方(也就是client端),V2.0.8版本不再有这个硬性要求,即只要是连接发起方,就可作为client端,而不需要关心hostname字典序。
RPC拦截器优化¶
趣链区块链平台 V2.0.8优化了RPC拦截器,解决由于Before() 和 After() 方法实现上存在相互依赖关系而导致的问题。
CNS需求优化改造¶
趣链区块链平台 V2.0.8优化了CNS。为确保场景合理性,V2.0.8变更为CNS管理由合约管理员负责,并进行其相关联的CNS提案发起者、投票者等整体变更。
通用概念¶
基础术语¶
- 交易(Transaction):区块链上的一个事务请求,用来承载具体业务操作数据的结构。区块链上所有针对世界状态的变化操作均是基于交易来完成的。
- 区块(Block):区块链上存储打包交易数据以及交易执行结果数据的一种组织形式。区块彼此之间通过前向的应用彼此链接形成区块链。 每个区块记录着上一个区块的哈希值、本区块中的交易集合、本区块的哈希等基础数据。
- 账本(Ledger):所有存储到区块链上的数据称之为账本。
- 区块哈希(Block Hash):本区块体的哈希值,区块链通过哈希算法对一个交易区块中的交易信息进行加密,并把信息压缩成由一串数字和字母组成的散列字符串。
- 地址(Address):区块链中用地址来标识一条交易的发起方以及接收方。
- 交易回执(Transaction Receipt):交易的执行结果。区块链是异步的系统,交易执行后需要共识,与传统架构不同,不能直接返回交易执行是否成功,因此需在回执中查看最终交易结果。
- 智能合约(Smart Conctract):智能合约是指在区块链网络中自动执行的程序,智能合约是区块链的重要特征,区块链实现业务逻辑的重要载体。
- 共识算法(Consensus Algorithm):保障分布式系统中各节点间达成一致采用的计算方法,常见的算法包括PoW、PoS、DPoS、PBFT、RAFT、RBFT等。
- 账户(Account):区块链上的基本操作对象,区块链上的所有交易操作均需要基于链上已存在的账户来实现。可分为普通账户和合约账户。
- 合约账户(Contract Account):合约账户是智能合约的操作载体,表示智能合约部署成功之后分配的合约地址以及这个合约对应的属性的集合。
- 区块高度(Block Height):区块高度,简称块高,用来识别区块在区块链中的位置,并据此找到和这个区块相关的所有基础属性和交易记录。
- 证书颁发机构(Certificate Authority):数字证书颁发机构是受信任的第三方机构,颁发的数字证书是为最终用户数据加密的公共密钥。
- 私钥(Private Key):与公钥相伴而生,公钥是由私钥通过特定的非对称加密算法生成,而用户需要保存好私钥,用于对该账户在区块链上所有发起的交易的签名。
- 国密算法(National Secret Algorithm ):国家密码局认定的国产密码算法,其中包括了对称加密算法,椭圆曲线非对称加密算法,杂凑算法。具体包括SM1,SM2,SM3等。
- 共识节点(Validate Peer):也称VP节点,参与共识过程,验证数据一致性后将数据同步存储在区块链上的节点,具有投票权。
- 非共识节点(Non-Validate Peer):也称NVP节点、查询节点或备份节点,可以连接区块链内任意一个共识节点,同步链上数据,但该节点只提供查询服务,不能参与共识过程,不能对数据进行校验,没有投票权;该节点的存在可以帮助系统分担查询服务的网络流量压力,同时可以实现数据的快速反馈,减少响应时间,提供更好的交互体验。
- 对等网络(Peer-to-Peer Networks):一种仅包含对控制和操作能力等效的节点的计算机网络。
- 节点(Node):提供分布式账本的所有功能或者部分功能的实体。
准备工作¶
操作系统版本要求¶
以下图表说明了趣链区块链平台对于不同操作系统的版本要求。
不同平台版本要求:
操作系统 | 系统版本 | 系统架构 |
---|---|---|
RHEL | 6 及以上 | amd64, 386 |
CentOS | 6 及以上 | amd64, 386 |
SLES | 11SP3 及以上 | amd64, 386 |
Ubuntu | 14.04 及以上 | amd64, 386 |
macOS | 10.8 及以上 | amd64, 386 |
硬件标准:
最低配置要求:CPU 1.8GHz、内存 2GB、核心 2核、带宽 2Mb。
推荐配置要求:CPU 2.3GHz、内存 16GB、核心 8核、带宽 20Mb。
注意:如果是本地部署带宽无要求
安装合约编译器(可选)¶
趣链区块链平台支持用Solidity编写的智能合约,然后将它编译为字节码并部署到区块链中。
鉴于我们是用Solidity语言编写的合约,所以需要确保我们已经安装名为solc
的合约编译器。
我们已经在源码中提供了一些平台的通用安装包,您可以直接使用他们来快速安装
solc
,您也可以参考官方文档来完成安装 -
安装Solidity.
最高支持solidity编译版本为0.8.x,需添加编译选项 --evm-version=homestead
另外,针对使用Java语言编写的智能合约,我们需要安装JDK环境和maven构建工具。可以参考官方文档来完成安装 - 安装JDK. 安装Maven.
共识机制¶
共识算法是用于保证分布式系统一致性的机制。这里的一致性可以是交易顺序的一致性、账本一致性、节点状态的一致性等。一般地,我们根据容错类型将共识算法分为两类。
- 拜占庭容错 : 拜占庭容错强调的是能够容忍部分区块链节点由于硬件错误、网络拥塞或断开以及遭到恶意攻击等情况出现的不可预料的行为。BFT系列算法是典型的拜占庭容错算法,比如PBFT、HotStuff等。
- 非拜占庭容错 : 非拜占庭容错通常指能够容忍部分区块链节点出现宕机错误,但不容忍出现不可预料的恶意行为导致的系统故障。常见的CFT共识算法有Paxos、Raft等。
平台采用 自适应共识机制 ,支持RBFT、NoxBFT(BFT类)以及RAFT(CFT类)等多种共识算法,以满足不同的业务场景需求。下文将主要介绍RAFT、RBFT和NoxBFT两类共识算法。
RBFT¶
相关变量¶
在一个由N个节点(N>=4)组成的共识网络中,RBFT最多能容忍f个节点的拜占庭错误,其中:
能够保证达成共识的节点个数为:
常规流程¶
共识算法RBFT的核心在于保证了区块链各节点以 相同的顺序 处理来自客户端的交易。下图为最少集群节点数下的共识流程,其中N=4,f=1。图中的Primary1为共识节点动态选举出来的主节点,负责对客户端发来的交易进行排序打包,Replica2,3,4为从节点。所有节点执行交易的逻辑相同并能够在主节点失效时参与新主节点的选举。
- 交易转发阶段 :客户端Client将交易发送到区块链中的任意节点;Replica节点将接收到的交易广播给所有节点,节点将收到的交易放入交易缓存池;
- Preprepare :Primary会选择交易缓存池交易进行打包,构造交易哈希的batch;Primary通过batch构造PrePrepare消息广播给其他节点;
- Prepare阶段 :Replica接收来自Primary的PrePrepare消息之后,对batch中的交易哈希进行验证,验证无误后构造Prepare消息发送给其他Replica节点,表明该节点接收到来自主节点的PrePrepare消息并认可主节点的batch排序。
- Commit阶段 :Replica接收到2f个节点的Prepare消息之后对batch的消息进行合法性验证,验证通过之后向其他节点广播Commit消息,表示本节点同意Primary节点的验证结果。
- 写入账本 :Replica节点接收到2f+1个Commit之后执行batch中的交易并写入本地账本。
需要注意的是,主节点除负责对交易排序打包外,与从节点功能无异。并且当从节点不认可主节点的排序结果时可以发起相应请求,集齐Quorum个该请求即可切换主节点。
- 检查点机制
平台设计检查点机制用于对执行结果进行校验。检查点的大小K默认设置为10个区块,节点在写入到K的整数倍个区块后达到一个检查点,广播该检查点的信息对账本一致性进行校验,校验通过后,平台就达到了一个稳定检查点(stable checkpoint)。检查点的大小可按需配置。
- 交易缓存池
交易缓存池用于共识节点进行交易缓存,一方面可以限制客户端发送交易的频率,另一方面减少了主节点的带宽压力。首先,通过限制交易池的缓存大小,平台可以在交易池达到上限后拒绝接收来自客户端的交易,因此在合理评估机器性能的情况下,可通过合理设置交易缓存大小,从而最大限度地利用机器性能而又不至于出现异常。其次,共识节点在接收到来自客户端的交易后先将其存入本地交易池,随后向全网其他共识节点广播该交易,保证所有共识节点都维护了一份完整的交易列表;主节点在打包后只需要将交易哈希列表放到PrePrepare消息中进行广播即可,而不用将完整的交易列表打包进行广播,从而大大减轻了主节点的出口带宽压力。如果从节点在验证之前发现缺少了某些交易,也只需要向主节点索取缺少的那些交易而不用索取整个区块里面所有的交易。
视图更换流程¶
视图更换(ViewChange)是指因原Primary节点失效而Replica节点参与新Primary节点选举的过程。视图变更能够解决主节点成为拜占庭节点的问题,是保证整个共识算法健壮性的关键。当前可检测到的拜占庭行为有以下情况:
- 节点停止工作
不再发送任何消息: 这种错误可以通过nullRequest机制保证,行为正确的主节点会在没有交易发生时向所有从节点发送nullRequest表明仍在正常工作,如果从节点在规定时间内没有收到主节点的nullRequest,则会引发ViewChange行为选举新的Primary。
- 节点发送错误的消息
错误可能是消息内容不正确、包含恶意交易的消息等,需要注意的是,这里的消息类型不仅是batch,也有可能是用于ViewChange的功能性消息。这种错误的解决方案是从节点在接收主节点的消息时,都会对内容进行相应的验证,如果发现主节点的交易包含不符合相应格式的交易或者恶意交易,即验证不通过的时候,会发起ViewChange选举新的Primary。
Viewchange流程如下所示:
- Replica节点检测到主节点有以上异常情况,向全网广播ViewChange消息;
- 当新主节点收到Quorum个ViewChange消息时,会发送NewView消息。视图切换成功,新的主节点也切换成功。
算法优势¶
RBFT基于PBFT做了一系列的优化,交易吞吐量可达万级TPS,延迟为毫秒级别。通过新增以下特性大大增强了共识模块的的可用性与稳定性:
- 动态数据自动恢复机制 :保证网络异常后能快速恢复工作,提升稳定性;
- 动态节点增删机制 :支持在系统正常运行时动态增删共识节点,提升可扩展性;
- 共享交易池 :通过预先的交易广播和共享,仅共识交易哈希,降低了主节点的出口带宽。
- 动态数据失效恢复
区块链网络在运行过程中由于网络抖动、突然断电、磁盘故障等原因,可能会导致部分节点的执行速度落后于大多数节点或者直接宕机。在这种场景下,节点需要能够做到自动恢复并将账本同步到当前区块链的最新账本状态,才能参与后续的交易执行。为了解决这类数据恢复问题,RBFT算法提供了一种动态数据自动恢复机制。
RBFT的自动恢复机制通过主动索取区块和正在共识的区块信息使自身节点的存储尽快和系统中的最新存储状态一致。自动恢复机制大大增强了整个区块链系统的可用性。RBFT为了恢复的方便,对执行的数据设置检查点机制。这样可以确保每个节点检查点之前的数据都是一致的。除了检查点之外,还有部分数据是当前还未共识的本地执行数据。在恢复过程中,首先需要本节点的检查点与区块链其他正在正常服务节点的检查点同步。其次,需要恢复检查点之外的部分数据。
自动恢复机制的基本处理流程如下所示:
- 图中的Replica 4为新启动节点或者其他需要做数据自动恢复的节点,运行中节点为集群中其他正常运行的节点。
- Replica 4自动恢复流程如下:
- Replica 4 首先广播NegotiateView消息,获取当前其余节点的视图信息;
- 其余三个节点向Replica 4发送NegotiateViewResponse,返回当前视图信息;
- Replica 4 收到Quorum个NegotiateViewResponse消息后,更新本节点的视图;
- Replica 4 广播RecoveryInit消息到其余节点,通知其他节点本节点需要进行自动恢复,请求其余节点的检查点信息和最新区块信息;
- 正常运行节点在收到RecoveryInit消息之后,发送RecoveryResponse,将自身的检查点信息以及最新区块信息返回给Replica 4节点;
- Replica 4节点在收到Quorum个RecoveryResponse消息后,开始尝试从这些response中寻找一个全网共识的最高的检查点,随后将自身的状态更新到该检查点;
- Replica 4节点向正常运行节点索要检查点之后的PQC数据,最终同步至全网最新的状态。
- 节点动态增删
在联盟链的场景下,由于联盟的扩展或者某些成员的退出,需要联盟链支持成员的动态治理服务,而传统的PBFT算法不支持节点的动态增删。RBFT为了能够更便捷地管控联盟成员的准入和准出,基于PBFT增加了保持集群非停机情况下动态增删节点的功能。
新增节点
新增节点流程如下所示(New为新增节点):
- 首先,新的节点需要获取证书颁发机构颁发的证书,然后向联盟中的所有节点发送NewNode请求;
- 各个节点确认同意后会向联盟中的其他节点进行全网广播,发送AgreeAdd消息;当一个节点得到Quorum个同意加入的回复后会与新的节点建立连接,随后开始回应新增节点的共识消息请求(在此之前,新增节点的所有共识消息是不予处理的);
- 随后,当新的节点和N-f(N为区块链联盟节点总数)个节点建立连接后就可以执行主动恢复算法,同步区块链联盟成员的最新状态。之后向其他节点广播ReadyForN请求;
- 现有节点在收到ReadyForN请求后,重新计算新增节点加入之后的N,view等信息,随后将其与PQC消息封装到AgreeUpdateN消息中,进行全网广播;
- New加入后的共识网络会产生一个新的主节点,该主节点在收到N-f个AgreeUpdateN消息后,以新的主节点的身份发送UpdateN消息;
- 全网所有节点在收到UpdateN消息之后确认消息的正确性,进行VCReset;
- 每个节点完成VCReset后,全网广播FinishVcReset消息;
- 节点在收到N-f个FinishVcReset消息后,处理后续请求,完成新增节点流程。
删除节点
RBFT节点的动态删除和节点的动态增加流程类似,流程如下所示(Replica5为删除节点):
- 节点管理员通过调用RPC请求得到删除节点的哈希值,然后发起删除节点请求;
- 接收到删除请求的节点管理员确认同意该节点退出,然后向全网广播AgreeDel消息,表明自己同意该节点退出整个区块链共识的请求;
- 当现有节点收到Quorum个AgreeDel消息后,该节点更新连接信息,断开与请求退出的节点间的连接;并在断开连接之后向全网广播AgreeUpdateN消息,表明请求整个系统暂停执行交易的处理行为,为更新整个系统参与共识的N,view做准备;
- 当节点收到Quorum个AgreeUpdateN消息后,更新节点系统状态,与增加节点步骤5)及之后的流程一样,不再重复。至此,请求退出节点正式退出区块链系统。
NoxBFT¶
联盟链一般采用RAFT、BFT类共识算法,性能方面能得到一定的保证,但随着节点数量增多到几百甚至上千个共识节点的规模,所需要交换的信息量也呈指数级增长,最终导致系统负载增加及网络通信量增大,性能下降会很明显,可扩展性问题也随之产生。
为了解决大规模节点组网场景下共识效率低下、可扩展性不强的问题,平台自研NoxBFT,借鉴Hotstuff算法,将全网网络复杂度由O (n2)降低至O (n),并在Hotstuff算法基础上,在算法的活性、可靠性、数字签名性能方面进一步进行优化,支持大规模节点扩展,在1000节点规模下吞吐量可达3000TPS。
NoxBFT中的Nox是Node of X的缩写,意指节点数不限,适用于大规模节点的BFT类共识算法。
共识主流程¶
共识主流程指的是共识算法运行良好的情况下,共识推进的流程,在NoxBFT中,主要是Proposal提案阶段与Vote投票阶段的循环,正常的共识流程如下所示:
- Transaction&Broadcast :任意节点收到交易之后,首先将其存入到本地mempool中,随后将其广播给其他所有节点,收到广播的节点也会将其存入到各自的mempool中。每个节点在接收到交易后,都会进行交易的去重判断,剔除重复交易之后才能进入到节点的mempool中,需要注意的是,现在交易的接收与广播流程并不在共识主流程中,而是由mempool负责进行;
- Proposal :当前轮次的主节点负责进行打包,从mempool中取出若干笔符合要求的交易打包成一个batch,并附带上一轮的QC封装成一个proposal,广播给其他节点;
- Vote :所有的节点(包括主节点)在监听到提案消息后,都会验证proposal的合法性(safety rules),验证通过后,首先检查该proposal中的QC是否能够提交前序的区块,如果达到了3-chain安全性提交规则(commit rules),则直接提交区块,等待区块执行完成之后将其中的交易从mempool中移除(CommitTxs)。最后,节点会将投票(vote)信息发送至下一轮的主节点,其中下一轮的主节点选择策略定义在liveness活性规则中。需要注意的是,每个节点的投票中都会附带上节点签名;
- Proposal :下一轮的主节点收到quorum个vote后,聚合成一个QC,并开始下一轮打包,并重复步骤2与步骤3,一直到出现超时的情况。
超时轮换主节点流程¶
当主节点由于网络原因或者其他因素导致从节点无法按期收到Proposal进行投票时,NoxBFT就会触发超时机制,通过Pacemaker活性模块让全网快速地进入到下一个round继续共识。超时轮换主节点的流程如下所示:
- Transaction&Broadcast&Proposal :所有共识节点接收交易并且广播交易,当前的主节点正常的进行打包并广播proposal;
- Round Timeout :由于网络原因,导致主节点proposal并没有及时地发送到从节点,因此从节点不会对本轮次进行投票;
- Broadcast TimeoutMsg :所有节点都无法按期收到本轮的Proposal,导致超时,全网广播TimeoutVote消息,其中会附带上本节点当前所处的轮次号以及节点的签名;
- Proposal :下一轮的主节点在一定时间内收到 quorum个TimeoutVote消息,构造成TC(Timeout cert),并从mempool中取出若干笔合法交易打包成batch,即可将TC与batch封装成一个新的提案proposal进行广播。
算法优势¶
- 活性机制优化
活性机制是保证共识能够持续推进的关键所在。在HotStuff的原始论文中,对于活性机制的定义较为模糊,只用了一个全局一致的超时时间来确定轮次的超时。
而在NoxBFT中,我们设计并实现了一个更加灵活的超时机制来应对实际互联网环境中不稳定的延迟与断网情况。具体的:每个节点在进入到新的轮次(R)时,各自启动一个超时器,超时时间初始值为initial_timeout(该值可配置),如果本轮能够正常收到主节点的QC的话,则正常进入下一轮,并重启一个时长为initial_timeout的超时器,如果本轮超时的话,则节点不断广播超时消息TimeoutMsg,直到收到quorum个TimeoutMsg进入下一轮(R+1),此时启动一个initial_timeout*K的超时器,其中K值大于1(该值可配置),如果R+1轮连续超时进入R+2轮的话,则R+2轮的超时时间为initial_timeout*(K^2)。以此类推,如果节点因为系统网络不稳定导致进入多轮超时的话,不会频繁地进行轮次切换,而是以一个逐渐放缓的速率进行轮次切换,大大减少了轮次切换的次数。
- 交易缓存池
在区块链中,为了防止交易丢失,需要设计一个交易缓存池用于缓存客户端发送过来的交易。发起提案时,共识模块会从交易缓存池中取出一定量的交易进行打包,作为提案消息发送给其他节点。交易缓存池不仅能用于交易缓存,还可进行交易去重。这里说的交易去重是指相同的交易不会被执行两次,也即防止双花。通过设置交易缓存池,共识阶段就可以发现重复交易,不会将重复交易作为提案消息通过网络发送给其他节点,从源头上杜绝重复交易的发生。
在NoxBFT中,我们设计的交易的唯一性标准是通过交易内容的hash值来确定的。同时,我们将所有已经上链的交易hash值写入到布隆过滤器中,通过布隆过滤器的去重特性可以达到快速的交易去重的目的,只有比较小的概率需要通过读取账本数据库来确定交易不存在。
- 快速恢复机制
网络波动可能导致共识节点丢失部分共识消息,从而落后于其他共识节点。在HotStuff的原始论文中,作者并没有显式地去描述状态同步的流程,而将其作为工程实现的一部分抛给了实现者。为了实现一个工程可 用的算法,让落后的共识节点恢复正常的定序功能,我们提供了状态同步功能StateSync来拉取最新的区块、账本信息等。落后的节点将分两阶段来进行同步:
- 当节点落后足够多的时候,我们会通过直接拉取区块执行的方式恢复到一个最新的稳定检查点stable checkpoint高度;
- 当节点落后足够少的时候,我们可以通过直接向其他节点所要QC的方式来快速恢复共识进度。
此外,为了提高同步的效率,我们采用了并行向不同源节点拉取区块的机制。并行的数量n采用可配置的方式来设置,落后节点将向分数最高的前n个节点并行发送请求分别向不同节点拉取不同的区块片段。落后节点收到源节点的响应信息后,首先会将拉取到区块在本地进行持久化,随后按序地执行落后的交易,并提高对应源节点的分数,以便下次高效地选取源节点。通过该机制,我们可以以最快的速度拉取所有丢失的交易等待执行,减少了整个等待执行的时间。
- 聚合签名
在HotStuff论文中,作者提出了对于共识消息中的签名和验签可以通过聚合签名进行加速。但是,HotStuff本身并没有实现聚合签名,而只是使用了最基本的椭圆曲线进行签名和验签。
NoxBFT则实现并改进了Ed25519的聚合签名算法。一方面我们将椭圆曲线计算过程中的一些可以进行预先进行计算的数据在编译过程中就提前计算出来,加速运行时的计算速度;另一方面,我们实现了一个大数类型专门用于加速Ed25519的计算过程。通用的大数类型由于要无限扩展,所以需要使用链表,这不利于缓存命中。而我们的大数类型则充分考虑了Ed25519使用的大数长度是确定性的,从而采用了数组的形式,并且尽可能压缩大数的存储。最终,我们的Ed25519算法比官方提供的库要快2.5倍。基于我们的Ed25519算法实现的聚合签名算法同样也比基于官方库的实现性能更高。
RAFT¶
RAFT介绍¶
由前文所了解, 分布式共识算法可以分为两类,即拜占庭容错(Byzantine Fault Tolerance,BFT)和非拜占庭容错类共识(Crash Fault Tolerance,CFT)。与BFT类共识算法相比,CFT共识,尤其是Raft共识算法,从性能、可理解性和可实现性等方面来说具有一定优势。
平台目前已经支持的RBFT共识为BFT类共识算法,与不限制共识成员的公链不同,联盟链中所有参与节点的身份都是已知的,每个节点有很高的可信度,故在某些可信度高的业务场景下可采用不容拜占庭节点的传统共识算法,如RAFT、ZAB等共识协议。基于此,我们同时支持Raft共识机制。
共识流程¶
角色介绍¶
首先在Raft共识机制中,节点共分为三种角色:
- 领导者(Leader) : 接受客户端请求,并向从节点同步请求日志,当日志同步到大多数节点上后将提交日志,并广播给从节点。
- 从节点(Follower) : 单向接收并持久化主节点同步的日志。
- 候选节点(Candidate) : 主节点选举过程中的过渡角色,当从节点在规定的超时时间内没有收到主节点的任何消息,将转变为候选节点,并广播选举消息,且只有候选状态的节点才会接收选举投票的消息。候选节点有可能被选举为主节点,也有可能回退为从节点。
在同一时刻,集群中只有一个Leader,负责生成日志数据(对应在区块链中即负责打包)并广播给Follower节点,为了保证共识的正确性和简单性,所有Follower节点只能单向接收从Leader发来的日志数据,即Leader节点不会接收Follower节点发来的日志数据。
具体流程¶
Raft算法共识流程分为主节点选举和日志同步两步。将时间分为一个个的任期(term),每一个term以Leader选举开始。在成功选举Leader之后,Leader会在整个term内管理整个集群。如果Leader选举失败,该term就会因为没有Leader而结束,任期选举图如图所示。
领导人选举¶
Raft 使用心跳(heartbeat)触发Leader选举。当服务器启动时,Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间(避免同一时刻有多个Candidate参与竞选导致系统可能出现的多次选举失败)后发起一次Leader选举。
Follower将其当前term加一然后转换为Candidate。它首先给自己投票并且给集群中的其他服务器发送RequestVoteRPC。结果有以下三种情况:
- 赢得了多数的选票,成功选举为Leader;
- 收到了Leader的消息,表示有其它服务器已经抢先当选了Leader;
- 没有服务器赢得多数的选票,Leader选举失败,等待选举时间超时后发起下一次选举。
选举出Leader后,Leader通过定期向所有Followers发送心跳信息维持其统治。若Follower一段时间未收到Leader的心跳则认为Leader可能已经挂了,再次发起Leader选举过程。Raft保证选举出的Leader上一定具有最新的已提交的日志。
日志同步¶
Leader选出后,就开始接收客户端的请求。Leader把请求作为日志条目(Log entries)加入到它的日志中,然后并行的向其他Follower节点发起 AppendEntries RPC以复制该日志条目。当这条日志被复制到大多数服务器上,Leader将这条日志应用到它的状态机并向客户端返回执行结果。具体步骤如下:
- Client将transaction发给任意一个节点;
- 节点接收到的transaction后,将其封装在一个Propose提案中,并抛给Leader节点;
- Leader节点收到新区块的提案消息后,将提案信息(即log entry)append到自己的log日志集中,并广播对应的log entry给Follower节点;
- Follower节点接收到Leader节点的 log entry消息后,将其append到自己的log中,并向Leader发送append response消息,表明自己已经收到该log entry并同意其排序;
- Leader节点收到n/2+1个append response消息后,该 log entry达到committed状态(此时Leader可apply log entry中的transaction并写入区块);
- Leader节点再次广播append 消息给Follower节点,通知其他节点该log entry已经是committed状态;
- Follower节点接收到Leader节点的append消息后,该log entry达到committed状态,随后可apply该log entry中的transaction并写入区块。
- 继续处理下一次Request。
注意:
- 某些Followers可能没有成功的复制日志,Leader会无限的重试 AppendEntries RPC直到所有的Followers最终存储了所有的日志条目。
- 日志由有序编号(log index)的日志条目组成。每个日志条目包含它被创建时的任期号(term),和用于状态机执行的命令。如果一个日志条目被复制到大多数服务器上,就被认为可以提交(commit)了。
Raft日志同步保证如下两点:
- 如果不同日志中的两个条目有着相同的索引和任期号,则它们所存储的命令是相同的。
- 如果不同日志中的两个条目有着相同的索引和任期号,则它们之前的所有条目都是完全一样的。另外,对于安全的保证,Raft增加了如下两条限制:
- 拥有最新的已提交的log entry的Follower才有资格成为Leader。
- Leader只能推进commit index来提交当前term的已经复制到大多数服务器上的日志,旧term日志的提交要等到提交当前term的日志来间接提交(log index 小于 commit index的日志被间接提交)。
优势¶
Raft的优势在于使用过程中拥有与Paxos相近的效率,但是比Paxos算法更易理解,而且更有利于工程化实现;可以直接从Leader的角度描述协议的流程与论证正确性。同时,还提供了协议的安全性证明和形式化证明,在可信场景中拥有良好的性能。
网络协议¶
网络协议作为平台的基础协议,主要负责节点发现、节点安全连接的建立、节点握手协商、消息安全传输等功能,同时需要支持弱网和复杂网络环境下,网络连接的可用性、可恢复性和稳定性。在整体设计上,网络模块仅仅负责管理节点间的连接,专注于网络协议的开发,而不参与区块链协议层的逻辑处理,是一个无状态模块。
区块链网络是由按照区块链协议运行的一系列节点的集合,这些节点共同完成特定的计算任务,并共同维护区块链账本的安全性、一致性和不可篡改性。目前,在平台网络模块支持的三种网络模型。
- DIRECT :全连接网络,节点启动之前需要配置网络里所有节点的信息。
- DISCOVER :自发现全连接网络,节点启动之前配置网络里部分节点的信息,同一个域下的节点可实现自发现全连接。
- RELAY :自发现转发网络,节点启动之前配置网络里部分节点的信息,且两个节点不在同一域下,未建立直接物理连接,那么两个节点想要相互发送消息,只要网络拓扑里有一条可达路径,就可通过中间节点进行消息转发。
全连接网络拓扑¶
所有节点间两两互连,消息的发送不需要经过中间节点传播直接到达对端,因此,具有实现简单、通信高效的优点,但是网络扩展性不高,而且节点在启动之前需要配置网络里所有的节点网络连接相关信息。
自发现全连接¶
自发现全连接网络拓扑图如下所示,无需配置全部节点的连接信息,可依据对端节点的连接信息自动识别同域节点信息并建立物理连接(图中灰色连接线为自发现建立的连接),需要注意的是,这种情况下,只可自动在同域节点间建立连接。
自发现转发网络拓扑¶
自发现转发网络的拓扑如下图所示,不需要所有节点两两互连,无需配置所有节点信息,只要整个网络拓扑形成一个连通图即可,因此理论上,网络可无限扩展,但是实现较为复杂且通信可能有一定的延迟。考虑到拜占庭节点的情况,自发现转发网络模型对网络拓扑图的要求很高,网络拓扑需要支持满足拜占庭容错要求,平台提供相应工具工具,可自动生成支持拜占庭容错的网络拓扑。
gRPC消息推送¶
gPRC消息推送主要替代依赖于第三方mq进行消息推送的问题,消息推送主要包含grpc层、mq模块层及sdk端的调用,调用方式主要支持普通调用模式、服务端单向流调用模式、客户端单向流调用模式及双向流调用模式。
- 普通调用模式 :最为传统的调用方式,即客户端发起一次请求,服务端响应一个数据;
- 服务端单向调用模式 :这种模式是客户端发起一次请求,服务端返回一段连续的数据流;
- 客户端单向调用模式 :与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应;
- 双向流调用模式 :客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。
执行引擎¶
平台支持合约安全、执行高效、编程友好的合约执行沙盒环境,研发了三种合约执行引擎:HVM(HyperVM)、EVM(HyperEVM)、BVM等,分别支持 Java、Solidity、Go等编程语言,提供完善的合约生命周期管理。
HVM¶
HVM概述¶
由于当前Java语言的流行以及其强大的生态,使用Java语言编写合约无疑会让合约开发更加便捷且易于推广。HVM为趣链科技首创支持Java语言的智能合约执行引擎,支持符合Java编写规范多种数据结构,内置数据表结构,可以实现业务数据可视化,在保证智能合约执行的安全性、确定性、可终止性的前提下,提供了一系列灵活的应用模式和工具方法集,以满足复杂多样的业务场景需求,为广泛的区块链开发人员提供更便捷、灵活、安全的区块链应用开发模式。
HVM使用¶
HVM执行机制从外部来看主要负责合约执行的操作。从SDK调用一笔HVM的合约,首先需要共识模块将通过共识的区块交易发送给执行模块,然后执行模块调用HVM暴露出来的合约接口,最后合约执行完成后会将结果返回,将执行结果写入账本中。在架构层面,HVM自上而下主要分为三个部分:合约操作层、库函数层以及虚拟机层。

- 合约操作层
这一层与用户直接相关联,主要包括合约部署、调用等全生命周期管理,对于合约的操作通过会对链上的合约数据状态产生影响,所以平台采取了灵活的合约管理提案申请-阈值投票-提案执行的策略,通过合约管理员对合约操作进行控制,保证合约管理操作的公平与安全。
- 合约部署 :编写Java智能合约,并通过SDK发交易的形式将其部署到区块链上;
- 合约调用 : 根据合约地址,调用相应合约中的逻辑。
- 合约升级 : 因业务要求或者实现逻辑更新,需要对合约进行升级操作时,需要用新合约来替换掉旧合约,由于升级合约是一个链级操作(改变整个链上的状态),所以需要采用CAF联盟自治框架才可以进行合约升级,保证链上合约的安全控制。
- 合约冻结/解冻 :将链上的合约冻结,在合约所有者解冻之前,禁止任何人调用,冻结不同于销毁,其具备一个可逆的过程,可以通过合约解冻的操作重新使用。
- 合约销毁 :将链上原来部署的合约进行废止操作,不同于合约冻结,合约销毁是一个不可逆的操作,被销毁的合约不能够被访问,不可以恢复,不允许再进行任何操作,但合约销毁后的数据仍然会存在链的底层账本中,仅用于监管审计。
- 库函数层
库函数包括数据结构、账本操作、日志信息以及加解密等功能。
- 数据结构符合 Java 编写范式:HyperList、HyperMap为平台独立研发,为了方便Java 软件开发者习惯,使其无需感知区块链底层 KV 结构即可编写相应业务逻辑代码。HyperMap 和 HyperList 的使用类似于开发者所熟知的 HashMap 和 ArrayList,但做了原创性地优化,在减少内存使用的同时也提高了更新账本的插入效率。
- **内置数据表结构 **:为了满足复杂业务场景下数据类型多样化、业务数据可视化与可分析的需求,智能合约需要支持复杂的表结构数据组织形式。HVM 提供了内置数据结构 HyperTable,支持在合约内部按照表的形式组织业务数据,便于业务数据可视化以及后续的数据分析与价值挖掘。这种结构可以让原Solidity 语言中复杂嵌套的数据操作简单化,同时在性能方面,能有效解决序列化、反序列化造成的性能瓶颈,整体维护成本更低、使用更高效。
- 内置嵌套Map数据结构:HyperList、HyperMap数据结构都无法满足复杂数据组织结构的需求;HyperTable的表结构拥有严格的层级格式(表-行-列簇-列-值),缺乏在复杂结构下的灵活性(例如:不可只有列,没有列簇)。针对上述问题,HVM推出新型的数据结构——NestedMap,支持用户按需进行灵活的数据组织,并且实现对多层映射数据更高的读写性能。
- 虚拟机层
虚拟机层主要是在合约执行过程中,对于合约解析执行的内部操作。为了提高整体的执行效率,HVM设计定制类加载器,类加载缓存提供合约地址到合约类加载器的映射,一个合约类加载器保存合约的字节码和合约类实例,采用最近最少使用淘汰策略(LRU)减少类重复加载带来的开销;指令解析从开始的每次对指令进行解析到将指令做成单例,并进行栈帧复用,大量节省指令执行时间,提高整体执行效率。
HVM优势¶
- 支持多级日志
日志在应用开发过程中的作用至关重要,能帮助开发者快速定位和发现问题。由于 EVM 未对出现的异常进行详细定位,给编译调试造成极大的难度。而 HVM 通过内置日志工具类,支持六种日志级别:critical、error、warning、notice、info、debug。可以为每种常见的错误进行合理的提示,方便使用者 对合作操作过程中产生的异常进行debug,方便开发和运维快速定位问题。
- 分层调用模式
HVM采取分层调用的模式,可以有效降低合约升级的成本。其实现方法主要通过InvokeBean的方式在业务调用层在不更新合约的情况下定义丰富的业务逻辑, 合约层只实现最核心、最基本的原子操作。以转账场景为例,合约层只有增加余额和减少余额的方法,在InvokeBean调用层定义转账的逻辑:如余额是否充足、 减少转让方余额和增加接收方余额。
- 支持加解密工具
一些业务场景需要在智能合约中进行签名验签逻辑处理,从而进行身份认证,便于进行权限判断或者后续业务的开展。因此 HVM 设计了基于 TEE的加解密工具, 支持在合约中调用存储于 TEE 的公私钥完成签名、验签操作,并支持 ECDSA国标系列、SM 国密系列等多种加解密算法,具有方便友好安全的特性。
- 支持合约访问控制
合约编码者可以通过智能合约和访问控制策略来限制访问数据的角色和用户,在合约中针对节点、角色、用户定制不同的合约函数访问权限。合约编码者可以在 合约中为一些高权限的函数设置权限控制,使得该函数只能被固定地址的调用者调用,从而实现访问权限控制。
EVM¶
EVM概述¶
Hyper EVM在合约执行方面,优化执行的内部细节,包括跳转表的初始化逻辑优化、EVM初始化上下文流程优化,减少了内存的频繁分配和对象的创建过程,加快 了EVM初始化速度。同时,HyperEVM会跟随以太坊EVM版本迭代更新,适配最新的以太坊EVM特性。
BVM¶
BVM概述¶
BVM全称是 Built-in Virtual Machine ,是用于处理内置合约的虚拟机类型。BVM的出现让开发者自主定义一些内置合约(即是合约代码由开发人员预先写好,在平台启动时直接创建对象加载,无需用户手动部署),提供用户所需的专属功能。具有性能优良、无需(额外)部署、权限灵活等特性。
BVM支持内置的合约¶
- 存证类内置合约 : 表示存证场景下文件哈希的存储形式。合约中只有两个操作:存和取,对应Set方法和Get方法。
- 提案类内置合约 : 包括更改配置和权限管理等事件,如新增节点投票。提案合约提供创建提案、取消提案、提案投票以及执行提案的操作,分别对应Create、Cancel、Vote、Execute方法。
BVM优势¶
- 性能优良:由于嵌入系统中,所以可以接近原生代码执行速度。
- 无需部署:无需用户额外部署,可以理解为平台刚启动就被 “部署”在某个固定地址上。
- 权限灵活:系统安全,不属于任何用户,任何用户都不可以对该合约进行升级或冻结、解冻的相关操作。
能量机制¶
能量主要作用是一种合约交易中用来度量执行合约逻辑复杂度的值,并通过限制复杂度大小来进行合约执行环境的停机和提供计费参考。每一笔交易都会包含两个与能量相关的关键信息,分别是能量上限和能量单价,能量上限决定了当前这笔交易所能消耗的能量数量,能量单价指定了当前这笔交易每消耗了一个单位的能量所需要支付的”价格”,即一笔交易所需要支付的交易能量价值 = 交易消耗的能量数量 * 能量单价。消耗的能量将被用于激励联盟链的组织者。
每一笔交易需要消耗的能量由智能合约执行时按照占用的CPU、内存和存储资源来动态计算得出。CPU资源体现在虚拟机执行每一个合约逻辑都需要进行指令调用,以操作数栈的形式对数据进行处理;内存资源体现在临时存储数据的局部变量表上的内存大小占用;存储资源则是对于合约状态数据存入账本的磁盘空间占用的消耗。当交易指定的能量上限低于交易执行所消耗的能量时,则将停止当前交易的执行,无论成功与否,交易发起者都需要为交易支付所消耗的能量费。
能量单价决定了当前这笔交易消耗的每一个单位的能量所需要支付的费用,由交易发起者来进行指定。当前区块链将有一个由CAF联盟投票决定的一个最低能量价格标准值,若交易中指定的能量价格低于这个标准值,则将拒绝执行交易。同时在交易打包时,将高能量价格的交易进行优先打包,优先打包的交易将有优先执行权。能力无法凭空产生,能量需由联盟链组织者为普通用户的区块链地址来进行增发,同时规定了对于能量转移的权限,只能由联盟链CAF组织成员来进行能量的增发,普通用户在购买能量后只能用于交易的手续费,无法进行流转。
结合实际场景,如某应用方对接了平台的链服务,用户在该应用上使用了相关链服务(如上链存证、部署合约、NFT购买等),则需要向应用方支付对应的上链服务费(即能量),以完成交易的打包确权(任何一次链上的操作都可理解为一笔交易行为)。而支付的能量则会进入到能量池,应用方可根据相关的激励策略按一定周期分配给联盟节点,共享联盟生态价值,推动联盟更加正向健康发展。
存储模型¶
区块链数据通过多节点冗余存储实现账本可信,而且随着区块的不断追加,数据量会不断增长,整体读写性能也会受到明显影响,如何保证区块链存储的可拓展性是亟需解决的一大问题。
平台为适配多种特性,对数据类型进行了进一步的拆分和分类,主要分为连续型数据和K/V型(Key-Value键值对)数据。
数据结构¶
连续型数据¶
连续型数据包括区块数据、交易回执以及修改集数据:
- 区块数据 :区块数据即所有区块的集合,主包括区块头内的区块元数据(区块哈希、父区块哈希、时间戳等),以及交易列表信息,是最重要的数据;
- 回执数据 :回执数据以区块为单位组织,记录所有合法交易的回执;
- 修改集数据 :修改集数据以区块为单位组织,记录这一区块对账本数据的全量修改信息。
其中,区块数据主要通过区块的形式进行串联,所有区块被从后向前有序地链接在一个链条里,每一个区块都指向其父区块,其主要存储内容如下表所示:
- 区块定义
- 区块头 :区块元数据集
- 交易列表 :收录在区块里的交易信息
- 区块头定义
- 版本信息:区块结构定义版本信息
- 父区块哈希:父区块哈希值
- 区块哈希:区块内容的哈希标识
- 区块号:区块高度
- 区块时间戳:主节点构造区块的近似时间
- 合约状态哈希:所有合约账户状态的哈希标识
- 交易集哈希:区块中收录的交易列表哈希标识
- 回执集哈希:执行交易产生的回执列表哈希标识
- 其他:区块执行时间戳,区块入链时间戳等
- 区块的交易列表中存储了被收录的交易数据,每条交易包含以下字段:
- 版本信息:交易结构定义版本信息
- 交易哈希值:根据交易内容生产的哈希标识
- 交易发起者地址:用于标识发起者
- 交易接收者地址:用于标识接收者
- 合约调用信息:调用合约函数标志及调用参数编码后内容
- 交易时间戳:节点收到交易的近似时间
- 交易保留字段:用于存储业务相关数据
- 随机数:随机产生的64位整数
- 用户签名:用户对交易内容签名生成的签名信息
K/V型数据¶
K/V型数据主要包括区块链元数据、账户数据以及合约数据:
- 区块链元数据 :区块链元数据存储当前链高度、交易数量、索引信息等辅助信息;
- 账户数据 :账户数据包含所有账户信息,包括普通账户和合约账户;
- 合约数据 :合约数据存储了合约的具体信息,包括合约源码与所有合约内部数据,存储于合约数据库中。
与比特币系统采用UTXO模型不同,平台采用账户模型来表示系统状态。当一笔执行交易结束后,会更改相应账户的状态,例如某用户A发起一笔合约调用交易,使得某合约中的变量a的值由0变为1,并持久化到合约账户数据库中存储。
因此,每一笔交易的执行,即意味着账户状态的一次转移,也代表着区块链系统账本的一次状态转移。相对于创世状态,最新的区块链账本状态被称为世界状态。
合约账户定义
- 合约地址 :用于标识合约账户的唯一标识
- 合约存储空间哈希 :利用Merkle树计算合约存储空间所得的标识
- 合约代码哈希 :合约可执行代码哈希产生的标识
- 余额 :账户余额
- 创建者 :创建该合约的账户地址
- 创建区块高度 :合约被部署时的区块高度
- 合约状态 :当前合约的可访问状态(正常或冻结)
除以上数据外,合约账户还有两个数据字段:合约源码以及变量存储空间。合约源码就是一段用字节数组编码的指令集,每一次合约的调用其实就是一次合约代码的运行。合约内部定义的变量则会被存储在合约所属的存储空间中。一个简化版的合约账户结构如下所示:
存储模式¶
为了突破区块链系统的存储瓶颈,平台自研混合存储引擎,针对不同的数据类型设计相应存储模式。
针对顺序型的区块数据,设计区块链专用存储引擎Filelog;针对随机性较强的K/V型区块链状态数据,选用具备很高随机写顺序读性能的存储引擎LevelDB,并设计多级缓存机制Multicache,实现状态数据的高效存取,以此保证在数据量不断增大的情况下,系统读写性能不受影响。
其中,平台自研的Filelog是一种Append-only形式的数据库,用来存储区块链场景中按照区块号严格递增的数据,因此Filelog的基本操作仅包括read/write两种。Filelog具有顺序写、随机读的特点,相比于LevelDB,Filelog对区块数据写入性能的提升高达90%。
加密机制¶
目前平台支持国际标准(standard)和国密标准(gm)两种密码体系。采用可插拔的多级加密机制对于业务完整生命周期所涉及的数据、通信传输、物理连接等都进行了不同策略的加密,保障系统的安全性。
哈希算法¶
哈希是一种散列函数,把任意长度的输入通过哈希算法,变换成固定长度的输出(哈希值),哈希值的空间通常远小于输入的空间,并且哈希函数具有不可逆性,根据哈希值无法反推输入原文的内容。
哈希算法在平台中有着广泛运用,例如交易的摘要、合约的地址、用户地址等都运用了哈希算法。平台提供了可拔插的、不同安全级别的哈希算法选项。安全等级由低到高分别有SHA2-256、SHA3-256、SHA2-512、SHA3-512等,同时平台也支持相应的国密哈希算法SM3。这些哈希算法都可以保证为消息生成体积可控、不可逆推的数字指纹,保证平台的数据安全。
交易签名¶
为了防止交易被篡改,平台采用成熟的椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm,简称ECDSA)对交易进行签名,保证平台的身份安全。同时也支持基于椭圆曲线密码的国密标准算法SM2。
平台使用了secp256k1曲线、secp256r1曲线(SM2则使用国家密码标准推荐参数)实现了数字签名算法,可供用户自行选择后对平台交易进行签名验签,保证交易的正确性以及完整性。同时平台支持该算法对节点间消息进行签名验证,保证节点间消息通信的完整性以及正确性。
密钥协商¶
在网络通信过程中,使用会话密钥对传输的信息进行加密,可以防止黑客窃听机密消息进行欺诈等行为。平台通过实现椭圆曲线Diffie-Hellman(ECDH)密钥协商协议完成会话密钥的建立和网络中用户之间的相互认证,同时支持国家密码标准的密钥协商算法SM2,保证通信双方可以在不安全的公共媒体上创建共享的机密协议,而不必事先交换任何私有信息。
密钥协商在身份认证和交易安全中都具有重要的作用,通过密钥协商建立起的安全通信信道能够实现安全的信息交换,保证了平台的通信安全。以安全身份认证为前提建立的密钥协商安全信道,首先能够确认通信双方的身份合法,再次,通过对称加密能够大大提高通信效率,因为并不需要每次通信都去认证身份,信道就能够确定通信双方的身份。
基于对称加密的密文传输¶
平台在通信双方协商出一个机密共享密钥后,再基于对称加密算法保证节点间的密文传输,使得计算上破解传输内容的难度更高,从而保证平台消息传输的高安全性。
对称加密也称常规加密,私钥、或者单钥加密,一个完整的对称加密方案由五个部分组成:
- 明文(plaintext):原始的消息或者数据,作为算法输入;
- 加密算法(encryption algorithm):加密算法对明文进行各种替换和转换;
- 秘密密钥(secret key):算法输入,算法进行替换和转换都依赖于秘密密钥。
- 密文(ciphertext):已被打乱的消息,作为加密算法的输出,取决于明文和秘密密钥。对于一个给定的消息,两个不同的秘密密钥会产成不同的密文。
- 解密算法(decryption algorithm):本质上是加密算法的逆运算。使用密文和秘密密钥产生原始明文。
平台支持AES(Advanced Encryption Standard)算法,即高级加密标准,该算法是一个基于排列和置换运算、迭代的、对称密钥分组的密码。它可以使用128、192 和 256 位密钥,并且用 128 位(16字节)分组加密和解密数据。同时也支持国家密码标准的对称加密算法SM4。
传输层安全¶
除了上述提到的密钥协商与密文传输以外,平台节点间还通过传输层安全TLS(Transport Layer Security)来保证通信安全。TLS 能够在传输层保障信息传输的安全性,是目前较为通用的网络传输实施标准,在几乎所有的网络安全传输中都采用了该技术,如google、淘宝、百度、微信等。
硬件安全¶
平台为了保证平台的安全性,采用软硬一体化的设计,将平台软件与硬件相结合。软件集合即为平台软件主体,负责区块链网络账户交易等正常操作;硬件部分为硬件密码卡、密码钥匙(广州江南科友科技股份有限公司研发的型号为SJK1862-G 的PCIE密码卡和提供给用户保管自身私钥的SJK1905智能密码钥匙)、TEE可信执行环境,负责随机数生成、密钥存储、账本数据加密等操作。
密码机中间件¶
针对有特殊密码安全的商业场景,区块链部署机构拥有成熟的密码学硬件组合(如:密码机)来支持各类密码学算法的使用。趣链区块链平台通过完整解耦密码学模块的九大功能,支持区块链部署机构按需配置自有密码学硬件作为密码学插件实现平台的密码学功能。
密码模块共有九大功能,分别是随机数生成、哈希摘要、对称加解密、生成签名验签密钥对、签名、验签、生成加密解密密钥对、公钥加密、公钥解密。针对上述九大功能,区块链部署机构可按需对每一个功能选取三种策略模式中的一种:
- default模式 —— 通过对密码功能配置“default”模式,该密码功能将使用平台软件密码学模块来提供服务;
- priority模式 —— 当平台接入了多个密码学插件时,支持使用对密码功能配置“priority”模式。配置为该模式时,用户可对密码学插件所支持的功能进行优先级分值设置,平台将根据分值筛选出分值最高的密码学插件来提供该密码功能的服务;
- 指定插件模式 —— 通过对密码功能配置指定的密码学插件,该功能将使用指定的密码学插件来提供服务。
架构拓展¶
大规模组网¶
随着联盟规模不断扩大,多样化业务模式不断扩展,传统的对等网络已经无法满足应用需求。比如在工业互联网、物联网场景中,传统的对等网络模式难以支撑海量终端设备接入区块链,混合式分层级的新型组网模型才能更好的应对这种大规模终端接入的应用场景。
基于上述问题,平台相应提出大规模分层组网模型, 通过分层架构设计,有效实现整体网络节点水平扩展,并提供实时计算与验证服务,实现数十万级不同类型网络节点的大规模部署。层级结构如下所示:
- 共识节点层 :由VP(Validation Peer)组成,该层级节点全部参与共识,负责区块链网络的共识验证与账本一致性保证。
- 非共识节点层 :由NVP(Non-Validation Peer)组成,同步共识节点账本数据,不参与共识,并通过网络自发现转发模型实现大规模NVP节点组网,实现区块链数据网络水平扩展。
- 轻节点层 :包含微数据中心、IOT网关等,靠近边缘设备终端,提供轻量级的计算功能,具备数据缓存以及本地计算的能力,将各种边缘设备与区块链网络桥接起来,赋予边缘计算能力,提高数据的处理效率,降低整体响应延迟。
- 终端设备层 :包括感知器、通信模组、摄像头等各类IOT设备,负责数据采集与转发上链,解决数据真实性的“第一公里”问题。
读写分离(NVP)¶
在数据新基建的大潮流下,随着人民链、星火链网、BSN等国家级区块链平台的相继推出,未来区块链需要支撑的应用场景规模将会越来越大,这势必会带来性能与扩展性的矛盾。试想如果区块链网络中都为共识节点,一方面,网络复杂度的增加对网络连通性和稳定性提出了更高的要求,但在真实落地场景中,出于网络安全和建设成本的考虑理想化的网络环境往往很难实现;另一方面,共识节点的数量增加会导致共识效率的降低,从而拖慢系统的整体性能。针对这些问题,趣链区块链平台通过非共识节点NVP提供了一种新的解题思路。
NVP定位于轻量级服务节点,不参与共识,仅通过信任的VP来同步账本数据,并对外提供交易转发、查询等服务。NVP拥有完善的数据恢复机制,当由于网络异常等原因导致节点落后时,能及时同步数据,恢复到最新的账本状态,提高了节点的可用性。此外,NVP提供的区块链服务是独立于VP的,除了交易上链、查询、验证等基础功能外,还支持数据索引、数据归档、可信文件存储、接口权限管理等功能,适应更加多样化的应用场景。趣链区块链平台支持一个VP连接多个NVP,用户可选择同步VP的全量账本数据或某个NS的账本数据,同步范围灵活可配。
NVP适用于多种应用场景:
- 针对数据低频写、高度读的存证类场景,机构内部可在VP的基础上再增设NVP,将一部分数据读压力转移到NVP上,让VP专注于数据写入,通过读写分离来分担VP压力,实现区块链系统的高效运行。
- 针对大规模、分层级、跨区域的国家级基础设施类场景,下属机构或中小型企业可用NVP替代VP,从平台角度考虑,一方面减轻了共识网络的压力,在保证性能的提前下提高了区块链网络的可扩展性,另一方面网络复杂度的降低也更利于平台落地;从机构及企业角度考虑,在满足业务需求的同时也降低了设备及运维成本。
- 针对海量终端设备接入的城市级物联网场景,以共识节点层为中心,通过NVP非共识节点层实现数据网络的扩展,优化分摊系统压力。
轻节点(LP)¶
在数据新基建的大潮流下,一系列国家级、省市级和行业级的区块链基础设施平台不断涌现,未来区块链需要支撑的应用场景规模将会越来越大,具体特征包括参与机构数量多、上链数据量大、性能要求高等。
就如目前存在的部分数据同步的问题,部分数据同步指不存储某个分区下的全量区块或账本数据,仅选择性存储部分数据。而当前联盟链场景中,每个参与机构都期望部署自己的节点,所有交易的上链、查询操作也将基于该节点进行。一方面这些机构都期望在业务层面成为链的共同建设者和维护者,另一方面若不部署节点,每次查询时需要跨域访问共识节点,在网络条件较差的环境中,可能遇到带宽资源不足、网络传输耗时过长、网络质量不可控导致的查询任务响应慢、中断、失败等风险,同时在并发量较大的情况下会增加共识节点压力。
非共识节点是否能满足需求?
- 非共识节点存储全量数据,若大部分是跟自身业务无关的数据,会造成存储资源的浪费和数据维护成本的增加,另外,因业务相关或无关的交易数据可能存储在同一区块内,所以无法通过数据归档去解决。
- 同一个分区下的数据无法再按需清分,单分区至少需要4个共识节点才能启动,同时上级或总部节点可能需要同时加入数量众多的分区,而单节点承载的分区数量有限,最终会导致设备和运维成本的增加,存在数据隐私安全问题。
LP节点,也就是轻节点,主要任务是分担网络的压力,作为未来的主力节点,只存储网络的少量数据,通过证明来实现功能。目前,轻节点的主要功能为同步区块头,主要应用场景为轻节点刚启动的区块头同步以及新增区块头的数据同步,整体包含功能包括:1.同步区块头:节点启动数据同步以及新数据推送;2. 转发交易:轻节点转发交易;3. 查询区块头数据:轻节点向共识节点请求交易和回执的证明。除此之外,轻节点不支持同步区块。
分区扁平化¶
现有分区共识的设计是在global分区的基础上,按需选择节点创建各自自定义分区。即创建新的分区的前提是,已加入global分区,而加入global分区的前提是,需要各节点间能够建立连接。在大规模多分区部署区块链网络的场景下,如果所有节点都在global分区下,那么其网络架构将非常复杂,尤其在主侧链场景中,侧链之间的网络是很难通信的,也无需通信,所以平台进行了分区扁平化的设计。
分区扁平化包含以下功能点设计:
- 整体架构扁平化:分区完全扁平化,四个节点及以上可以组成一个分区,网络中不一定需要global分区的存在,分区内的节点建立物理和逻辑连接。
- 网络扁平化:不在同一个分区中的节点之间无需建立p2p连接,将DNS、Topotogy、Ping服务和broadcast挪到ns级别。Network划分成多个子网,每个子网代表一个ns。
- 锚节点管理:组网阶段不考虑锚节点,锚节点通过后续注册的方式进行管理,锚节点的注册通过配置交易进行。
- 证书级别调整:节点证书中新增namespace字段,防止同一个节点的分区借用其他分区的证书。
可验证计算¶
最安全的区块链隐私保护方案是在保证最敏感的隐私数据不离开本地存储的同时,让区块链能够利用到该数据的价值。区块链公开透明的特性在实际商用场景落地过程中往往限制了业务的扩展,为了应对实际场景中隐私保护的需求,平台提供TEE账本加密、零知识证明两种隐私保护方案。
TEE账本加密¶
账本加密针对用户的账户信息和业务数据进行按需加密操作,将密钥存储在TEE硬件可信执行环境中,账本数据通过TEE的密钥进行加解密,由于在整个过程中账本加密的密钥都在TEE中而没有向外暴露,所以可以保证账本数据加密的安全性;而且平台会确保硬件执行环境的安全性,这一层面外部用户无需感知;另外如果需要参与方和审计方具有权限查看数据,则可通过赋予其数据访问权限,通过密码学算法如哈希校验等进行对加密信息的有效性和正确性验证。
账本加密可应用于密钥存储和数据加密两种场景。
密钥存储,密钥存储某种程度上可以理解为CPU担任了UKey的功能。他是将关键密钥信息托管到可信计算环境中不再导出以便保护关键密钥。这里能够保存的密钥包括平台ecert、rcert的私钥,特殊用途的密钥等,最终的密钥保存在配置文件指定的加密文件中。密钥存储的子功能主要包括:密钥生成、加密解密、密钥导入、签名。
数据加密,数据加密是利用了密钥存储的加密解密的功能,在此基础上专门提供特定于节点的密钥加密功能。提供公共的一组加密解密接口供对共识数据加密使用。密钥对不同的节点是不同的而且只有一份拷贝,但是考虑到加密的数据在其他节点也能找回,因此不用担心密钥丢失的问题。
零知识证明¶
零知识证明是指证明方能够在不向验证方提供任何有用的信息的情况下,使验证方相信某个陈述是正确的。证明者(prover)有可能在不透露具体数据的情况下让验证者(verifier)相信数据的真实性。零知识证明可以是交互式的,即证明者面对每个验证者都要证明一次数据的真实性;也可以是非交互式的,即证明者创建一份证明,任何使用这份证明的人都可以进行验证。平台零知识证明目前支持groth16算法和plonk算法,并支持使用bn254曲线,对setup签名可以使用secp256曲线。另外,支持intel sgx 硬件的方式,实现将wasm核心执行引擎接入sgx,并实现ecall核心功能、远程证明模块以及计算验证模块。
零知识证明有三个基本特征,即:
- 完整性 :如果statement为true,则诚实的验证者可以相信诚实的证明者确实拥有正确的信息。
- 可靠性 :如果statement为false,则任何不诚实的证明者都无法说服诚实的验证者相信他拥有正确的信息。
- 零知识性 :如果statement为true,则验证者除了从证明者那里得知statement为true以外,什么都不知道。
治理与审计¶
身份认证/准入机制¶
平台使用CA证书体系进行联盟成员的准入控制。主要分为集中式CA认证体系、分布式CA认证体系两种方式。根据业务需求,任一节点可能参与一个或多个业务分区,所有在同一个分区中的节点都处于同一条链上,不同的分区处于不同的链上。不同的分区将拥有各自的证书体系,分区间不共享证书体系。
证书 | 说明 |
---|---|
Root.ca | CA体系的信任锚持有根证书Root.ca,具有签发各类证书的权限。在节点连接时,若节点的信任锚与其他链上节点的信任锚不同,则无法与其他节点成功建立连接。 |
ECert | ECert是节点准入证书,持有ECert的节点为共识节点,可以同链上其他节点同步数据并参与共识。在节点连接时,若节点的ECert无法通过证书合法性校验,则无法与其他节点成功建立连接。 |
RCert | RCert主要是用于证明该节点是受信任的准入节点,该节点为非共识节点(NVP),不可参与共识验证,仅允许向特定共识节点同步账本数据。在节点连接时,若节点的RCert无法通过证书合法性校验,则无法与其他节点成功建立连接。 |
SDKCert | SDKCert 为客户端的准入证书,用于证明客户端的合法性,非法的客户端将无法向节点发出请求。每一个SDKCert将对应连接一个节点。若一个客户端需要连接多个节点,则该客户端连接每个节点都需要一个SDKCert。在交易发送时,若客户端的SDKCert无法通过证书合法性校验,则交易将无法被节点接收。 |
IDCert | IDCert为账户证书,用于维持账户地址与账户密钥间的映射关系。依此证书,账户将获得注册、冻结/解冻、密钥重置、注销的账户生命周期管理功能。 |
TLSCert | TLSCert(Transport Layer Security Cert) 为安全传输层协议证书。TlsCert 用于传输层安全协议证书,即在传输网络传输过程中需要验证传输层安全协议证书的安全性,验证通过即可以进行正常网络通信,反之则无法进行网络通信。 |
中心化CA¶
中心化CA认证体系可以由可信机构提供,也可通过自建CA体系实现。平台已通过集成CFCA(China Financial Certification Authority)实现数字证书管理功能,适用于对于证书系统安全性与权威性有较高要求的银行或金融机构;自建CA体系需要建设独立的PKI/CA系统,并且建立完整的运营管理体系。中心化CA证书体系的证书类型与签发关系如下图所示:
在中心化CA模型下,每个节点需配置其参与的每个分区所信任的CA,将CA的根证书配置到成员管理文件夹(MSP)中,后续该节点在与其他节点建立P2P网络连接时(节点加入区块链阶段),该节点将信任由成员管理文件夹中所记录的CA根证书所签发的数字证书。因为,数字证书签发能力由若干CA掌握,因此我们称其为中心化CA。
分布式CA¶
分布式CA体系认证管理是将证书管理权限由中心机构移到联盟链各参与方,联盟网络节点互相颁发准入证书给其他网络节点,在建立连接阶段完成证书认证。分布式CA治理具有去中心、自动化、高效等优点。如:网络中有四个节点(node1、node2、node3、node4),node1需要与其他三个节点交互需要三个节点各向它签发一个证书。在该结构下,暂不支持非共识节点(NVP)。分布式CA证书体系的证书类型与签发关系如下图所示
由上述两种身份认证CA模型可以看出,平台的身份准入控制是通过多类型的数字证书进行区分与控制的。平台所涉及的身份证书说明如下:
- RootCA :RootCA(Root Certificate Authority)为根证书颁发机构,代表PKI体系中的信任锚。在节点连接时,若节点的信任锚与其他链上节点的信任锚不同,则无法与其他节点成功建立连接。
- ECert :ECert是节点准入证书,持有ECert的节点为共识节点,可以同链上其他节点同步数据并参与共识。在节点连接时,若节点的ECert无法通过证书合法性校验,则无法与其他节点成功建立连接。
- RCert :RCert主要是用于证明该节点是受信任的准入节点,该节点为非共识节点(NVP),不可参与共识验证,仅允许向特定共识节点同步账本数据。在节点连接时,若节点的RCert无法通过证书合法性校验,则无法与其他节点成功建立连接。
- SDKCert :SDKCert 为客户端的准入证书,用于证明客户端的合法性,非法的客户端将无法向节点发出请求。每一个SDKCert将对应连接一个节点。若一个客户端需要连接多个节点,则该客户端连接每个节点都需要一个SDKCert。在交易发送时,若客户端的SDKCert无法通过证书合法性校验,则交易将无法被节点接收。
- IDCert :IDCert为账户证书,用于维持账户地址与账户密钥间的映射关系。依此证书,账户将获得注册、冻结/解冻、密钥重置、注销的账户生命周期管理功能。
- TLSCert :TLSCert(Transport Layer Security Cert) 为安全传输层协议证书。TlsCert 用于传输层安全协议证书,即在传输网络传输过程中需要验证传输层安全协议证书的安全性,验证通过即可以进行正常网络通信,反之则无法进行网络通信。
在分布式CA模型下,每个节点将拥有独立的根证书,并使用该根证书签发网络连接身份证明所需的数字证书。在初次加入区块链网络时,节点将与其他区块链节点两两互换证书,节点增删事项以提案的形式提交至区块链中进行投票共识决议。因为该模式下,节点是两两相互签发认证证书的,无需指定的CA进行签发,具有分布式认证的特征,遂称为分布式CA。在分布式CA体系下,SDKCert和IDCert皆由某一节点的根证书签发。
用户账户体系¶
公私钥对是用户在联盟链平台中进行交易发起、账户操作的主要元素,为标识用户在联盟链中的账户进而基于公钥通过数学运算生成一个账户地址,该账户地址即为用户在区块链上的标识。不同的应用场景对账户的使用需求和管理需求各不相同,针对不同的账户体系需求,趣链区块链平台提供两类账户标识、三类用户账户。两类账户标识分别是普通账户标识和DID账户标识,三类用户账户分别是普通账户、证书账户、DID账户。
- 普通账户 使用普通账户标识,为链上的基础账户,是另外两类账户的基础,但该账户不具备账户生命周期管理能力,亦不具备密钥更换的能力,因此使用普通账户的用户应妥善保管其私钥。
- 证书账户 使用普通账户标识,为链上的高安全账户。普通账户经CA认证获得IDCert后可升级为证书账户,用户也可直接向CA申请IDCert注册证书账户。证书账户具有账户生命周期管理功能,且支持密钥更换,具有高安全的特点,但对区块链的开销较大,不建议注册大量证书账户,适用于业务场景中具有强安全需求的账户。
- DID账户 使用DID账户标识,每一个DID账户标识都有一个普通账户标识与之对应。用户需要基于DID账户使用分布式数字身份的功能。用户可自主将普通账户升级为DID账户,亦可在初次注册时直接注册DID账户。DID账户支持账户生命周期管理能力,可为每个DID账户定义不同的管理员账户,支持密钥更换,具有灵活、安全、易用的特点。
分布式数字身份(DID)¶
趣链提供用户自主可控、全域自发现的分布式数字身份(Decentralized Identity, DID),将用户身份的管控权还给用户,并打破跨平台间的信息屏障。一个用户的数字身份由DID、DID文档、可验证凭证三个部分组成。每一个DID都必须拥有唯一的DID文档,但拥有不定数量的可验证凭证。
- DID 为用户在数字世界的标识,对每一个所使用的数字应用,用户都将有一个DID数字身份。
- DID文档 为DID在数字世界的身份档案,包含账户公钥、账户状态、授权管理账户等。倘若用户未注册DID账户,链上将无DID文档的记录,则无法签发和接收可验证凭证,他人也无法在该区块链中寻得未注册的DID账户。
- 可验证凭证 为桥接用户数字世界身份与现实世界身份的桥梁,用于表述、证明DID账户持有人在现实世界的身份属性,具有描述性、可验证的特点。用户可部署智能合约,通过智能合约进行可验证凭证的验证和使用。
多级权限管理¶
联盟链的准入机制虽然能一定程度上保障安全性,但缺乏针对不同角色的细粒度的权限管理。因此,为了支撑更丰富复杂的商业应用场景需求,平台建立了多层级权限管理体系,进一步保障商业隐私和安全。
目前,平台设有系统管理员、审计管理员、节点管理员、合约操作员以及用户五个主要账户角色:
- 系统管理员 :参与区块链级别的权限管理,包括节点管理、系统升级、合约升级的权限控制,往往是各联盟机构指定的内部超级管理员。节点准入、系统升级这种链级别的操作权限需由系统管理员代表联盟各机构投票决定。链级权限管理需要借助联盟自治框架进行提案发起与提案投票,详见章节联盟自治CAF。
- 节点管理员 :往往是各联盟机构指定的运维管理员,参与节点级别的访问权限管理。节点管理员责任包括:向受节点信任的客户端颁发SDK证书,配置用户权限表,分配用户访问SDK的权限,比如访问调用合约的权限、获取区块权限等。
- 合约管理员 :负责对链上已部署的智能合约进行运维管理。平台支持合约管理员或合约部署者对智能合约发起合约升级、合约冻结、合约解冻、合约禁用、合约访问权限的操作提案。合约管理员需借助联盟自治框架对上述提案进行发起与投票,投票通过的提案由平台执行提案。
- 用户 :普通用户,可按规则参与链上交易,可被赋予上述管理员角色。
联盟自治框架CAF¶
为了满足灵活多变、迭代更替的业务规则,平台提出了一种具备联盟协商、迭代升级的联盟自治框架CAF(Consortium Autonomous Framework),由管理员共同形成“联盟自治成员组织”,以联盟协商的形式管理联盟链。该框架适用于一切链级的变更,如节点增删、合约升级等。
- 系统管理委员会:由系统管理员组成,负责系统级提案及节点增删提案的商议与投票。
- 合约管理委员会:由合约管理员组成,负责合约管理与合约升级等提案的商议与投票。
提案从创建到执行的整个流程,具体如下:
- 系统管理或合约管理的行为,会被申请人以提案形式提交给相应的委员会;
- 所有组织成员根据自身制定的投票策略对每个提案作出判断;
- 当组织中同意该提案的成员数量达到预设阈值时,提案被通过;
- 所有链上节点根据通过提案的内容自动变更节点状态。
其中,提案的提出、表决、通过等核心逻辑都依托于智能合约实现。由于智能合约在不同节点间的状态由平台的共识算法保证,能保持时刻同步且严格一致,因此借助智能合约为载体,可以高效实现提案事件的分发以及投票结果的聚合。
目前,CAF可灵活用于共识节点管理、成员角色管理以及智能合约管理三类应用场景:
- 共识节点管理 :现有联盟链的节点变更往往与管理机构身份认证绑定,身份认证一般都是由CA授权认证,成为多中心区块链系统中的身份认证中心。CAF利用预编译智能合约,支持系统不停机的进行联盟成员增删提案发起、提案投票、提案执行,使成员变更流程具有多中心化的特点,同时整个协商过程也公开透明。
- 成员角色管理 :联盟链中系统管理员的增删、合约管理员的增删都需由系统管理员进行提案,并由提案前的系统管理委员会进行投票表决,平台对符合预设投票规则的提案予以执行,完成成员角色管理。
- 智能合约管理 :传统的合约升级方式只能由合约部署者发起合约升级交易完成,这种方式一方面存在单点故障风险,导致合约无法升级,另一方面合约部署者可能在没征得其余参与方同意的情况下,单方面进行合约升级。基于CAF,组织成员可事先指定升级策略写入智能合约,在需要升级时由部署者或合约管理员发起提案,借助权限受控的合约自升级指令,解决智能合约的升级问题。
安全审计服务¶
随着区块链商业应用的步伐加快,如何通过有效审计来保障区块链系统及数据安全是区块链行业发展需要重点关注的问题。央行于今年发布了《金融分布式账本技术安全规范》和《区块链技术金融应用评估规则》,被业内视为区块链在金融行业内应用的重要标准,两份标准都对区块链系统的安全审计提出了明确要求。
为此,平台推出了区块链系统安全审计服务,帮助审计方对系统事件展开精确有效的审计工作,全面符合金融级审计要求。
作为审计对象的系统事件包括账本数据访问记录、变更记录、同步记录,节点共识历史以及系统异常事件等。事件在平台内部以日志形式进行记录和采集,再通过外接日志分析系统,实现日志的快速检索、风险告警以及可视化展示功能。
平台的审计日志中记录了丰富的事件信息,一方面支持对异常操作和安全事故进行追本溯源,另一方面也帮助用户建立一套贴合实际业务场景的安全告警机制,及时防范风险。这里的告警规则是灵活可配的,为了降低使用门槛,平台预先将审计事件分为NORMAL、WARN、RISK三个风险等级,并以字段形式记录在审计日志中,用户可按需对不同等级的事件进行告警。此外,平台也支持用户根据审计日志中的IP、用户、时间、操作内容等字段信息,按需设置用户访问黑白名单等规则,适应多样化的应用场景。
安全审计服务的使用十分便捷,用户只需要在启动平台之前修改配置文件开启审计即可。平台现已适配GaryLog和ELK两种常用日志分析系统,用户可根据实际需求进行选择。
除了符合《金融分布式账本技术安全规范》以及《区块链技术金融应用评估规则》的要求,该功能可在供应链金融等多个领域发挥潜在价值。以区块链供应链金融服务平台为例,借助安全审计平台,一方面可以帮助用户及时识别节点异常、数据一致性校验失败等威胁系统正常运行的特征事件,避免风险进一步扩大导致服务中断;另一方面,事后能快速追踪链上数字资产的异常变更和访问操作,为虚拟交易、非法融资等提供便捷的追责功能。
合约命名CNS¶
智能合约在区块链业务处理过程中扮演着重要的角色,区块链系统中针对不同业务会有不同的智能合约,而当前对于这些智能合约的操作管理都是通过“合约地址/Contract Address”来桥接实现,若想调用某合约,可能要配置”0x62524766cd4b022c7e84bac128bbbf71e26666668”类似这样一个地址,相信大家已经发现了他的规律:就是没有规律。并且存在以下问题:
- 合约地址冗长无规律(magic number),不方便记忆、存储以及校验,丢失后合约无法访问。
- 合约ABI描述会是很长的JSON字符串, 是调用端需要, 对使用者来说并不友好。
- 合约重新部署过程繁琐:合约因为业务逻辑的变化而被重新升级部署后,需要逐一去修改之前已经引用这个合约地址的所有业务模块,以指向新的合约的地址,整个过程繁琐且容易错漏。
- 不便于进行合约版本管理以及合约灰度升级。
这会导致日常在使用时要确认这一串合约地址的完整性、正确性,才能正确调用这个地址上的合约接口,同样也增加了运维时候配置使用的难度,由此,合约命名服务CNS(Contract Name Service)应运而生。
CNS意在解决合约生命周期管理以及运维配置过程中因合约地址冗长无规律导致的一系列操作繁琐问题。其灵感来源可以追溯到互联网域名系统,域名之于IP地址相当于合约命名之于合约地址。CNS通过简单合约地址命名与合约地址&abi等合约信息进行映射。当用户调用合约接口时,传入合约映射的name、接口名称等信息。在CNS服务模块维护name与合约信息的映射关系,将根据调用传入的name 、接口、参数, 转换底层虚拟机需要的字节码进行调用,通过这种命名关系友好化的方式让用户不再关于冗长的合约地址,只需要操作简单的命名就可以使用合约。
CNS命名服务的初衷是用户在涉及到合约地址的使用过程中,可以不用去管复杂的合约地址相关信息,只需要一个简单的合约名name就可以完成原有的相关操作,具体的流程如下图:假设用户发起一笔交易调用HelloC.sol合约,这时To的地址就会变成我们的合约名称HelloC,交易执行时会首先访问CNS服务,解析出真实的合约信息,再对业务合约进行真实的调用,最终将访问结果返回给客户端。
证书链上吊销¶
证书是节点加入网络、客户端连接节点的合法性凭证,证书的签发者为CA。CA并不诞生或注册在链上,区块链仅以配置的方式记录受信任的CA,以此判断未来需验证的证书是否来源于受信任的CA。然而证书的吊销如今并非在链上完成的,具有两大缺点:
- 时效性 :CA吊销证书到通知区块链各节点可能存在较长时间的时间差。
- 原子性 :CA吊销节点证书或客户端证书后,理论上该节点/客户端应在证书吊销生效的同时不再被网络所信任,但如今无法实现这样的效果。
针对上述缺点,结合CA证书体系和区块链的关系,平台设计了证书链上吊销功能,以实现:证书吊销操作以交易的形式在链上被执行,执行后,证书失效,相关逻辑连接断开;链上将记录失效证书;将证书生效后的管理能力与区块链交易结合,实现链上证书管理。
链上证书吊销分为节点证书吊销、sdk证书吊销,详述如下:
- 节点证书吊销 :节点证书的吊销不提供单独的用户接口,但将作为节点删除的附加操作,即当执行删除节点的操作时,被删节点的证书也将自动转为吊销状态。该被删节点如需重新启动,应更换节点目录下的相关证书。
- sdk证书吊销 :使用sdk接口发起一笔调用bvm合约的普通交易,完成证书的吊销操作。当发起操作的角色为管理员时,无需提供sdk证书私钥的签名;若为普通角色则需要通过验签证明自己为该证书私钥的所有者。
数据管理¶
数据归档¶
随着区块链运行时间的增长,区块链系统存储的数据容量将高速增长,且数据的增长的速度甚至会超过存储介质容量增长的速度,从而限制区块链技术的发展。为此,平台实现了区块链数据归档,在不停机的情况下,将一部分线上数据迁移到线下存储。以此解决在有限存储空间下区块链数据的存储问题。平台也提供归档数据浏览器,支持用户查阅归档数据。平台也实现了归档数据恢复的功能,在不影响链上正常工作的情况下,支持运维人员将线下存储的数据恢复至线上。
数据归档针对的主体是区块数据,同时包括区块对应的日志数据、索引数据、交易回执数据。平台支持用户自定义归档范围,用户可以直接指定一个已经被提交的区块号作为参数,创世区块到入参区块的数据将被归档至线下。另外,考虑到直接归档的流程较为耗时,直接归档的请求返回和归档流程将异步进行,用户可通过查询归档结果的接口查询归档是否成功。数据归档功能支持各节点自主定义归档范围,节点间无需同步归档。数据归档对区块链的变化如下图所示:
数据索引¶
数据查询是区块链应用中最普遍的场景,但由于区块链独特的账本结构,一直以来都缺少一种高效安全的查询手段。
区块链底层的账本数据以Key-Value键值对模型进行存储,目前只能提供基于key 的精确检索,而在实际应用场景存在更广泛的基于value(业务数据)的检索需求。比如在商品溯源场景中,我们可能要通过商品ID来检索出该商品在整个流通过程中的交易,受限于区块链底层数据的存储特点,只能通过遍历区块的方式来检索,效率十分低下。为了提高检索效率,目前有两种解决思路,一种是将商品ID到交易哈希的映射关系存储在外部数据库当中,但数据安全难以得到有效保障;另一种是将映射关系存储在智能合约中,但随着数据量增大,合约会遇到性能瓶颈,检索效率依然难以得到提升。
为了解决上述问题,平台依托底层索引数据库,从区块中提取关键信息存储到索引数据库中,实现高效安全的业务数据自定义条件检索功能,极大简化了上层业务系统开发和维护复杂度。平台采用内嵌式索引数据库,保证检索结果可信的同时,极大提升了业务数据的检索效率,检索速度达毫秒级。为了更加贴合实际使用场景,平台支持自定义多条业务索引信息,满足更加精准的检索需求,同时支持精准、匹配(模糊)、多条件查询等多种检索模式,并支持大数据量分页查询,避免出现查询接口OOM问题。
数据索引的应用十分广泛,特别是在查询频繁的存证类场景中。以茶品溯源场景为例,当茶品溯源平台将茶品采摘、加工、质检、物流、销售流程中的相关信息以交易的形式上链存证时,用户可在交易的ExtraID字段中输入茶品批次、类型、原产地、经销地点等自定义索引信息,这些索引信息将存储在底层专用索引数据库当中。随后,用户可调用相应接口,通过精确检索模式查询某一批次编号的产品全流程交易信息,或通过多条件检索模式查询云南产普洱茶的交易信息。
文件保险箱¶
中心化的文件存储与共享存在易篡改、低可信、单点故障等问题,而区块链因其可信任、安全不可篡改、冗余备份、多方协作等特性在文件可信存储与共享方面具有天然的优势,但目前区块链对于商业化文件存储与共享存在成本高、容量小、性能差、结构单一等问题,具体表现在:
- 文件容量大导致系统性能下降 。目前的区块链技术架构,大容量的文本、文件由每个节点全量直接链上存储不仅需要消耗大量的存储资源,还会严重影响平台性能,无法满足商业场景的需求。
- 链上存储结构单一 。区块链的存储结构对结构化文件友好,但不适于图片、音视频等非结构化文件的直接存储,无法适应涉及大量非结构化文件的应用场景。
为此,平台通过自研文件保险箱功能,通过链上存证、链下传输的文件分离存储模型,实现了文件可信存储、安全共享与高效查询。在保证平台性能和稳定性不受影响的情况下,能支持GB级别图片、音视频等非结构化文件的可信存储,并通过区块链网络定向流传输共享。向用户提供节点白名单和用户白名单,支持用户多维度自定义授权存储节点与用户下载权限,允许用户按需查询、下载文件,有效降低链上非结构性文件存储与共享的成本并增强可用性、可控性。可信文件共享由一方上传文件,多方同步文件索引信息、定向共享文件,功能示意图如下所示:
为保障文件的安全存储和可信共享,文件保险箱功能架构图如下:

- 传输路径 :文件将由客户端上传至节点存储。任意节点间的文件传输都是通过P2P网络完成的,没有“中间商”能窃取文件。
- 落盘加密 :在节点中独立设立文件空间(或对接文件系统),将节点收到的文件落盘至文件空间中存储,落盘时对文件进行加密处理,保障文件明文无法不通过区块链获取。
- 存储权限 :节点和节点间共享文件时,对请求文件的节点进行权限查验,节点的权限将记录在区块链上,保障权限设置全局统一。
- 下载权限 :节点向用户提供文件下载时,对请求文件的用户进行权限查验,用户的权限将记录在区块链上,保障权限设置全局统一。
- 文件校验 :文件的传输可分为三个环节,文件上传、文件节点间传输、用户下载文件。其中文件上传后,区块链账本中将记录不可篡改的文件数字指纹。在任意环节的文件传输,节点都将自动的对所传输的文件与账本中的文件指纹进行比对校验。
预言机¶
在中国人民银行发布的《区块链能做什么?不能做什么?》的报告中,预言机定义如下:区块链外信息写入区块链内的机制,一般被成为预言机。预言机解决的是链外数据不可信、不确定而导致不可用的问题,由于区块链对于用户来说是一个黑盒,只了解输入和输出但不知晓内部的细节,系统内部为了满一致性要求,需保持合约运行结果的一致性和确定性,但如Web天气服务、航班动态信息、随机数等都是变化的数据,区块链想使用这些动态变化的数据就需要预言机。预言机作为区块链和外部世界的桥梁,通过可信计算技术以及建立信任的约束机制,让区块链系统打破了封闭,使区块链主动获取外部数据成为现实。
预言机使用过程中首先需要事先部署oracle合约,负责与预言机服务交互,用户编写自己的用户合约去调用oracle合约对外部预言机合约请求数据,预言机服务向外部请求数据再将数据返回给oracle合约,最终返回给用户合约触发合约执行逻辑(详见下图预言机示例)。
对于数据源获取:Oracle预言机可以从外部引入世界状态的信息,包括网站数据、传感器采集数据、随机数、跨链数据等实时、动态、可变的数据;
对于预言机服务:平台支持硬件TEE可信以及软件可信“双可信”预言机服务,保证预言机服务在数据处理,数据传输过程中的真实可信;
对于数据安全:支持HTTPS、TLS等安全传输协议,保证数据传输过程中防篡改且真实性可查验。
链上SQL¶
针对区块链数据操作方式与联盟链业务人员使用习惯不符的问题,为提升平台的易用性、降低用户对使用区块链的学习成本,平台自研链上SQL执行引擎,支持业务人员使用熟悉的SQL语句进行数据操作。链上SQL为链原生的SQL语句执行引擎,支持使用符合MySQL语法规则的SQL语句进行链上数据的增删改查。

链上SQL具有以下功能特性:
- 应用快速链改
- 提供支持标准JDBC协议的区块链专用JDBC驱动,支持通过变更驱动,快速完成业务应用系统的链改。
- 提供数据迁移工具,帮助用户将存储在传统关系型/非关系型数据库的数据迁移至区块链中,快速完成数据链改。
- 简单、易用、可信
- 支持符合MySQL语法规则的SQL语句,满足数据操作需求。
- SQL语句链上执行,保障分布式数据操作一致性。
分区共识¶
区块链冗余存储的特性决定了区块链网络中的每个节点都需要存储一份全量账本数据,而在商业应用中,存在敏感数据只能在有限节点间进行共享,或者不同业务间数据需要相互隔离的需求。
为此,平台设计了分区共识(Namespace)机制,不同分区间的交易共识、执行、存储完全解耦,不仅实现了交易数据对其他分区不可见,同时,由于交易并行执行,分区性能也不会随分区数量增加而显著下降。平台支持分区及分区成员的动态管理,允许用户在不停机的情况下,进行分区以及分区节点的增删,快速适应业务需求变化。
通过分区共识机制,需要进行隔离的数据可以仅在指定分区内进行共享和存储。原本通过建不同的链实现数据隔离的模式,需要在每次建链时重新部署节点,十分繁琐,而现在的分区隔离模式只需单次部署节点,便可按照实际需求选择加入一个或多个分区,在实现数据隔离的同时有效降低了部署及运维复杂度。
图中描述了分区共识在政务数据治理场景中的应用。区块链政务数据治理平台的数据协作链承载了民生服务、企业服务等业务,不同业务数据间存在隔离需求。建链时,各委办局部署各自节点,并通过配置确立初始分区及成员节点。随着试点范围的扩大,牵头部门可在民生服务和企业服务分区的基础上,通过IPC命令动态新增政务服务分区,并通过发送交易动态新增其他市/区委办局节点,逐步完善整个政务数据治理平台的搭建。
运维管理¶
网络流控¶
区块链系统作为分布式系统,任意时间内到来的请求往往随机不可控,而系统处理能力有限,因此需要根据系统处理能力对流量进行控制,否则,暴涨的突发请求容易引起区块链服务或接口不可用,严重时可能导致整个区块链系统陷入雪崩状态。为了提供更稳定可靠、柔性可用的服务,平台提供交易拦截、消息分发、带宽限流等多维度网络流量控制服务,在请求激增场景下保证系统的稳定运行。
- 交易拦截:在系统最外层及早对交易进行拦截,阻止交易渗透到主流程花费不必要的系统开销;
- 消息分发器:在P2P网络层将消息带权分发给对应模块处理,降低各模块由于处理能力差异而相互干扰,保证核心模块正常运行;
- 网络带宽限流:限制节点最大出口带宽流量,使得系统整体带宽使用量可控,不需要另外搭建nginx服务器或者通过root权限修改操作系统配置来进行带宽流控,适用于对网络带宽有限制需求的场景。
数据监控¶
平台提供一站式的数据可视化监控服务,帮助用户轻松了解底层平台运行情况,及时识别并处理异常。
数据监控平台由数据生成层趣链区块链平台、数据监控层Prometheus以及数据展示层Grapana三部组成。趣链区块链平台负责生成需要监控的数据并推送到Prometheus服务器中;Prometheus负责存储趣链区块链平台推送出来的数据,并对外提供读取接口;Grapana:负责从Prometheus服务器中读取数据,并在web上进行展示。其中,数据展示层除了Grapana,还支持对接BaaS及业务层的可视化展示模块,适应客户多样化部署环境,减少上层开发及维护成本。
平台现已支持100 +业务及系统层面的监控指标,指标开关灵活可配,具体可参考使用手册中的指标清单。此外,借助Prometheus的AlterManager组件,平台实现了节点license过期、磁盘空间不足、共识状态异常等告警功能,保障系统的持续稳定运行。
灾备切换(CVP)¶
完善的灾备方案是政府、企业信息化建设过程中的重要组成部分,能在信息化时代有效防范灾难、降低损失,而区块链作为一种新型信息基础设施,增设灾备节点的意义也愈发凸显。
尽管区块链本身具有冗余存储的特性,且RBFT共识算法支持拜占庭容错,天然具有灾备特性,但在实际落地过程中仍存在一些亟待解决的问题。首先,在目前的联盟链中,节点大多归机构私有,即尽管每个节点拥有全量账本数据,但某一机构的交易上链、查询、验证等操作只基于该机构拥有的节点进行,若只拥有一个节点,当节点故障后,由于无法通过其他机构节点接入区块链网络,会导致该机构的区块链服务中断。此外,当故障节点数超过一定阈值时(如4节点共识网络中该阈值为1),会导致区块链系统整个服务不可用。因此,仍有必要为区块链节点提供灾备服务,保障节点及系统高可用。
为此,平台提供专门的灾备节点CVP,能在VP发生异常宕机时可快速升级为VP,参与共识,并提供和原VP同等的区块链服务。CVP对VP的备份包括两部分,一部分是账本数据,CVP同样具有数据恢复机制,当CVP断连恢复后,能快速同步数据,恢复到和VP相同的账本状态,确保任意时刻的数据安全;另一部分是配置文件,涉及到配置变更的操作,如创建NS、增删节点、开启权限等,会影响到CVP升级后是否能避免数据被越权访问,是否能保证系统的正常运行,故这类配置变更需要在CVP和VP间保持强一致,CVP拥有一套完善的一致性保证机制和操作指令恢复机制,帮助CVP升级后能准确复制VP的行为状态。
CVP的升级操作十分便捷,当维护人员作出确认升级判断后,只需通过一条运维命令便可完成升级。原VP恢复后,可重新配置为现VP的CVP,最大限度利用节点资源,节约成本。
CVP的优势在于由平台保证数据和配置的一致性,当故障或灾难发生时支持一键快速升级,即降低了运维门槛,也避免了人为操作失误带来的额外损失,极大缩短了RTO时间(Recovery Time Objective,系统宕机导致业务停顿到恢复正常运行间的时间),在金融、国网等对系统高用性及容灾能力有硬性要求的场景中具有很大的应用潜力。
区块链生态组件¶
多语言SDK¶
SDK作为与趣链区块链平台交互的工具,是上层应用和区块链平台连接的桥梁。SDK功能强大,用户不仅可以通过SDK快速访问区块链、开发合约编写业务逻辑,还可以通过SDK进行用户管理、事件订阅以及灵活配置。通常,SDK会提供外部API接口,将应用给出的参数进行封装、加密、签名等,形成http请求后发送给平台,平台收到请求后返回结果,然后SDK对返回的结果进行解析,返回给上层,形成上层应用与区块链一个完整的交互流程。为适应不同语言开发工程师管理区块链平台,平台具有多语言SDK支持,如JavaSDK、GoSDK、JSSDK、CSDK、C#SDK等,优先推荐使用JavaSDK。目前的SDK架构具有完成的功能接口支以及灵活易用的工具支持。
SDK整体架构主要分为基本架构和扩展工具两大部分。基本架构可以支持完备高效的主流程操作,包括账户证书管理、合约部署调用、区块交易查询、数据编解码以及安全网络通讯,来进行合约开发的一系列工作;工具支持是基于SDK基本架构构建的一个完备的系统生态,对证书、日志级别、网络协议进行灵活的配置,支持合约、查询、日志、异常管理等基本工具,为了方便用户区块链数据的查询和管理,SDK提供了数据归档、消息订阅以及外部数据源等扩展功能,总而言之,SDK可以满足用户对区块链操作的各种需求。
Archive-Reader浏览器¶
数据归档中介绍了平台支持对区块数据的归档与恢复。当数据完成归档后,此部分数据将无法在区块链上查阅,因此平台提供了Archive-reader浏览器用于查阅归档数据。该浏览器无需与区块链部署在同一服务器上,用户可在独立的服务器上运行Archive-reader浏览器,并导入相关归档数据即可开始查阅。同时,Archive-reader浏览器支持包括数据索引功能在内的快速数据检索服务。由此,平台实现了归档数据持续可查、灵活查阅的高易用性。
状态证明组件使用archive Reader二进制,通过在其上增加api的形式提供账本数据验证及验证结果查询功能。
趣链区块链平台v2.7版本发布了趣链区块链浏览器(内嵌版Baas),趣链区块链浏览器是管理人员可视化管理hyperchain相关链上业务的可视化工具,支持基于web页面查看区块链上信息、节点/合约实例可视化管理等核心操作。
消息订阅¶
趣链区块链平台作为一个“共享状态”的区块链实现,其运转通过不断的状态变迁实现。每一次状态变迁,都会产生相应的一系列事件作为本次状态变迁的标志。
为了方便外部业务系统捕获、监听区块链的状态变化,我们提供了消息订阅功能,现已支持rabbit MQ和KafKa双模式。外部可以监听到的事件类型包括:
- 区块事件 :每产生新的区块都将主动向订阅者推送最新区块信息;
- 合约事件 :合约相关的事件触发(比如合约上账户余额变动)将会向订阅者推送消息;
- 交易事件 :写入区块的交易的交易体与交易回执都会主动推送给订阅者;
- 系统异常事件 :当平台有异常抛出或者系统状态改变的时候,便会向订阅者主动推送消息。
为保障消息推送内容及推送顺序的正确性,平台将在区块链达到共识稳定点前,将上一共识稳定点至今的待推送消息进行临时存储。当区块链达到共识稳定点后,将对待推送消息按区块顺序进行排序,并按顺序对外推送消息,保障了用户接收到的消息内容及顺序的正确性。
HyperBench测试框架¶
HyperBench是趣链科技自主研发的通用的区块链性能测试框架,支持趣链区块链平台、Fabric等主流联盟链性能测试。用户可自定义测试场景针对配置好的区块链网络进行性能测试,获取一系列的测试结果并生成测试报告,主要分为交易发送、数据统计、资源监控、报告生成四大功能。
1.交易发送
交易发送包括测试环境配置、测试用例设计、交易封装发送、受测平台适配四个步骤。
- 测试环境配置:用户可通过测试参数配置以及平台连接配置自定义压力测试的环境。其中,测试参数配置主要涉及配置文件中各参数的设置,平台连接配置指对测试时的底层适配器进行配置。
- 受测平台适配:性能测试框架只提供适配接口,受测平台在Hyperbench提供的统一SDK接口基础上,继承类Blockchain并重写相应方法进行底层适配。
2.数据统计
本地化数据统计,记录交易执行过程中系统各类型数据,压测结束之后,在visual/index.html页面有压测过程中统计的数据,可用浏览器打开进行可视化查阅(默认是关闭数据统计的,需要在配置文件中开启)。
3.资源监控
在区块链系统中植入资源监控的功能,对服务器进行资源、用量指标的监控和采样。
4.报告生成
性能测试框架在按照设定的测试时长正常退出之后会自动生成一份测试报告。报告分成Overview和Details两个部分。Overview中会记录测试的总体数据,并且大致展示压测交易的执行情况。Details会展示一些本地的总体统计数据,系统根据调用的函数名进行聚合统计。
5.HyperBench 白皮书 https://upload.hyperchain.cn/HyperBench%E7%99%BD%E7%9A%AE%E4%B9%A6.pdf
链级权限管理¶
平台基于 联盟自治CAF 框架实现链级权限管理,具体包括链级配置变更、链级角色管理、节点增删管理、合约权限管理、合约生命周期管理、合约命名服务以及账户生命周期管理。
下文将介绍CAF框架下提案-投票流程的具体使用方式及相关接口说明,其他功能的使用说明请前往相应界面:
其他功能使用说明¶
功能概述¶
联盟自治框架CAF(Consortium Autonomous Framework)由管理员共同形成 “联盟自治成员组织” ,以联盟协商的形式管理联盟链。该框架适用于一切链级别的变更,如节点增删、合约管理等。
CAF的管理委员会包括 系统管理委员会 和 合约管理委员会 。前者由系统管理员组成,负责链级配置变更、链级角色管理、节点增删等提案的商议与投票;后者由合约管理员组成,负责合约访问权限管理、合约生命周期管理、合约命名服务CNS等提案的商议与投票。
提案的提出、表决、通过等核心逻辑都依托于 内置智能合约(BVM) 实现,具体流程如下:
- 系统管理或合约管理的行为,会被申请人以提案形式提交给相应的委员会;
- 所有组织成员根据自身制定的投票策略对每个提案作出判断;
- 当组织中同意该提案的成员数量达到预设阈值时,提案被通过;
- 所有链上节点根据通过提案的内容自动变更节点状态。
安装及初始化¶
使用litesdk与节点交互分为以下几步:
- 首先需要创建 HttpProvider 对象管理与节点的连接;
- 然后创建 ProviderManager 对象负责集成、管理 HttpProvider ;
- 然后再根据实际的需要创建相应的服务 Service 的具体实现类;
- 最后将请求发送出去拿到响应结果。
初始化的流程中litesdk的主文档中有详细介绍,此次不再赘述。此外,执行合约需要创建的是 Service 的实现类 ContractService 。
使用说明¶
BVM合约接口¶
BVM合约主要通过 ContractService 提供的 invoke 接口执行,对于特殊的通过bvm合约管理合约生命周期是通过 ContractService 提供的 manageContractByVote 接口执行(主要是为了与默认的非bvm管理合约生命周期的方式区分开来)。
ContractService 的中的接口声明如下:
public interface ContractService {
Request<TxHashResponse> deploy(Transaction transaction, int... nodeIds);
Request<TxHashResponse> invoke(Transaction transaction, int... nodeIds);
Request<TxHashResponse> maintain(Transaction transaction, int... nodeIds);
Request<TxHashResponse> manageContractByVote(Transaction transaction, int... nodeIds);
}
BVM交易体¶
LiteSDK 使用 Builder 模式来负责对 Transaction 的创建,通过调用 build() 函数来获取到 Transaction 实例。针对BVM有相应的 BVMBuilder ,集成自父类 Builder , BVMBuilder 提供了 invoke 接口用于构造参数,其接口声明如下:
class BVMBuilder extends Builder {
Builder invoke(BuiltinOperation opt)
}
BVMBuilder 提供的 invoke 方法接收一个 BuiltinOperation 类型的对象,这个对象集成自统一的父类 Operation , 在 Operation 中封装了这个操作要调用的合约方法以及需要的参数;其定义如下:
public abstract class Operation {
public void setArgs(String... args) ;
public void setMethod(ContractMethod method) ;
public String[] getArgs() ;
public ContractMethod getMethod() ;
}
BuiltinOpetation 继承自 Operation ,增加了要调用的合约地址的封装,其定义如下:
public abstract class BuiltinOperation extends Operation {
public String getAddress() ;
public void setAddress(String address) ;
}
由于bvm中有多种合约,一个合约中也有多个合约方法,为此提供了相应的 Builder 来构造相应的操作,封装了一个父类的 BuilderOperationBuilder 用于构造内置操作 BuiltinOperation ,其定义如下:
public abstract class BuiltinOperationBuilder {
/**
* return build BuiltinOperation.
*
* @return {@link BuiltinOperation}
*/
public BuiltinOperation build() ;
}
针对不同的合约地址中不同的合约方法调用有封装相应的实现类,目前bvm提供的合约有: HashContract 、 ProposalContract 两种,分别有 BuiltinOperation 的实现类 HashOperation 和 ProposalOperation ,相应的也提供了 HashBuilder 和 ProposalBuilder 用于创建相应的操作。
HashContract¶
HashContract 中提供的合约方法如下:
- Set : Set方法接收两个参数,一个参数为key,一个参数为value,用于存储键值对。
- Get : Get方法接收一个参数key,用于取出HashContract中与之对应的value值。
构造 HashContract 操作的构造器 HashBuilder 提供了 set 和 get 方法,分别用于构造 HashContract 合约中的 Set 和 Get 方法,其定义如下:
public static class HashBuilder extends BuiltinOperationBuilder {
/**
* create set HashOperation to set hash.
*
* @param key the key value to set hash
* @param value the value mapping with key to set hash
* @return {@link HashBuilder}
*/
public HashBuilder set(String key, String value);
/**
* create get HashOperation to get hash.
*
* @param key the key to get hash
* @return {@link HashBuilder}
*/
public HashBuilder get(String key);
}
ProposalContract¶
ProposalContract 中提供的合约方法如下:
- Create : Create方法接收两个参数,一个是提案内容,一个是提案类型,用于创建提案
- Vote : Vote方法接收两个参数,一个是投票的提案id,一个是投赞同票还是反对票,用于对提案进行投票
- Cancel : Cancel方法接收一个参数,要取消的提案id,用于取消提案
- Execute : Execute方法接收一个参数,要执行的提案id,用于执行提案
注意:
- 提案总共有六个状态:等待投票、驳回、等待执行、已完成、取消和超时。
- 只有创建者有权取消提案
- 提案创建后通过投票可进入等待执行、驳回状态
- 处于等待执行的提案可有发起者发起执行操作,执行完成进入已完成状态
- 提案处于等待投票、等待执行状态时无法创建新的提案
- 目前提案的默认阈值为链级管理员的总个数,即提案创建后,每个管理员都铜牌同意此提案则通过投票
- 目前提案的默认超时时间为5分钟(当设置当超时时间小于5分钟时,会设置为5分钟),即创建提案的交易打包时间+5分钟则为提案超时时间
- 每次执行提案交易都会拿到当前系统中最新的提案,将当前执行的交易的打包时间与提案的超时时间进行对比,如果大于超时时间,则将提案状态置为超时。(不能单纯的认为提案创建后,过了超时时长就一定超时了,没过超时时长就一定没超时。交易的打包时间是主节点中打包交易时取的当前系统时间,主节点的系统时间可能不是正常的时间序列,例如:如果提案创建后发生了viewchange,viewchange之前的主节点在当前时刻的系统时间为1:00,viewchange之后的主节点中当前时刻的系统时间为1:30,这时即使提案创建之后没有超过超时时长,对提案进行投票,由于新的主节点的时间为1:30,打包的时间戳也为1:30对应的时间戳,在执行提案对比是否超时时,判断的结果就为超时。目前主节点时间变更除了viewchange之外,还有主节点主动作恶、或根据需要主动变更系统时间。)
根据提案的状态以及对提案的操作,可得出一个提案的状态迁移如下图所示:
对于提案可根据提案内容划分为以下几类:
- 配置类, ptype为 config ,data则为配置项操作列表;
- 权限类,ptype为 permission ,data为权限操作列表;
- 节点类,ptype为 node ,data为节点操作列表;
- 合约命名类,ptype为 cns ,data为合约命名操作列表;
- 合约生命周期管理类,ptype为 contract ,data为合约生命周期管理操作列表。
构造 ProposalContract 操作的构造器 ProposalBuilder 提供了 createForNode 、 createForCNS 、 createForPermission 、 createForContract 、 createForConfig 、 vote 、 cancel 和 execute 方法分别用于创建节点类提案、创建合约命名类提案、创建权限类提案、创建配置类提案、提案投票、取消提案和执行提案的提案操作,其定义如下:
public static class ProposalBuilder extends BuiltinOperationBuilder {
/**
* create creat ProposalOperation for node to create node proposal.
*
* @param opts node operations
* @return {@link ProposalBuilder}
*/
public ProposalBuilder createForNode(NodeOperation... opts);
/**
* create creat ProposalOperation for cns to create cns proposal.
*
* @param opts cns operations
* @return {@link ProposalBuilder}
*/
public ProposalBuilder createForCNS(CNSOperation... opts);
/**
* create creat ProposalOperation for permission to create permission proposal.
*
* @param opts permission operations
* @return {@link ProposalBuilder}
*/
public ProposalBuilder createForPermission(PermissionOperation... opts);
/**
* create creat ProposalOperation for permission to create contract proposal.
*
* @param opts contract operations
* @return {@link ProposalBuilder}
*/
public ProposalBuilder createForContract(ContractOperation... opts);
/**
* create creat ProposalOperation for permission to create config proposal.
*
* @param opts config operations
* @return {@link ProposalBuilder}
*/
public ProposalBuilder createForConfig(ConfigOperation... opts);
/**
* create vote ProposalOperation to vote proposal.
*
* @param proposalID proposal id
* @param vote vote value, true means agree; false means refuse
* @return {@link ProposalBuilder}
*/
public ProposalBuilder vote(int proposalID, boolean vote);
/**
* create cancel ProposalOperation to cancel proposal.
*
* @param proposalID proposal id
* @return {@link ProposalBuilder}
*/
public ProposalBuilder cancel(int proposalID);
/**
* create execute ProposalOperation to cancel proposal.
*
* @param proposalID proposal id
* @return {@link ProposalBuilder}
*/
public ProposalBuilder execute(int proposalID);
}
配置类操作¶
配置的操作分以下几种:
- SetFilterEnable,设置filter.enable的值,即是否开启交易拦截过滤器
- SetFilterRules,设置filter.rules的值,即交易拦截过滤规则
- SetConsensusAlgo,设置consensus.algo的值,即共识算法(目前只是修改了配置文件,还没有实现同步切换共识算法。对于整个系统而言,共识算法并没有切换过来,重启之后才会真正切换)
- SetConsensusSetSize,设置consensus.set.set_size的值,即一个节点一次广播的最大交易数(目前只是修改了配置文件,还没有实现同步切换共识配置参数。对于整个系统而言,并没有切换过来,重启之后才会真正切换)
- SetConsensusBatchSize,设置consensus.pool.batch_size的值,即共识打包的最大交易数(目前只是修改了配置文件,还没有实现同步切换共识配置参数。对于整个系统而言,并没有切换过来,重启之后才会真正切换)
- SetConsensusPoolSize,设置consensus.pool.pool_size的值,即节点的交易池存储的最大交易数(目前只是修改了配置文件,还没有实现同步切换共识配置参数。对于整个系统而言,并没有切换过来,重启之后才会真正切换)
- SetProposalTimeout,设置proposal.timeout的值,即提案超时时间(默认超时时间为5分钟,即最短超时时间,当设置当超时时间小于最短超时时间时,会设置为最短超时时间)
- SetProposalThreshold,设置proposal.threshold的值,即提案的投票阈值(默认值为链级管理员总个数)
- SetContractVoteEnable,设置proposal.contract.vote.enable的值,即是否开启通过投票管理合约生命周期,默认关闭
- SetContractVoteThreshold,设置proposal.contract.vote.threshold的值,即合约生命周期管理提案的投票阈值(默认值为合约管理员总个数)
构造配置类操作 ConfigOperation 的构造器 ConfigBuilder 提供了 setFilterEnable 、 setFilterRules 、 setConsensusAlgo 、 setConsensusSetSize 、 setConsensusBatchSize 、 setConsensusPoolSize 、 setProposalTimeout 、 setProposalThreshold 、 setContractVoteEnable 、 setContractVoteThreshold 以及 build 方法,其定义如下:
public static class ConfigBuilder {
/**
* create ConfigBuilder to set filter.enable.
*
* @param enable the enable value
* @return {@link ConfigOperation}
*/
public ConfigBuilder setFilterEnable(boolean enable);
/**
* create ConfigBuilder to set filter.rules.
*
* @param rules namespace filter rules
* @return {@link ConfigOperation}
*/
public ConfigBuilder setFilterRules(List<NsFilterRule> rules);
/**
* create ConfigBuilder to set consensus.algo.
*
* @param algo consensus algorithm
* @return {@link ConfigOperation}
*/
public ConfigBuilder setConsensusAlgo(String algo);
/**
* create ConfigBuilder to set consensus.set.set_size.
*
* @param size the value of consensus.set.set_size
* @return {@link ConfigOperation}
*/
public ConfigBuilder setConsensusSetSize(int size);
/**
* create ConfigBuilder to set consensus.pool.batch_size.
*
* @param size the value of consensus.pool.batch_size
* @return {@link ConfigOperation}
*/
public ConfigBuilder setConsensusBatchSize(int size);
/**
* create ConfigBuilder to set consensus.pool.pool_size.
*
* @param size the value of consensus.pool.pool_size
* @return {@link ConfigOperation}
*/
public ConfigBuilder setConsensusPoolSize(int size);
/**
* create ConfigBuilder to set proposal.timeout.
*
* @param timeout the value of proposal.timeout
* @return {@link ConfigOperation}
*/
public ConfigBuilder setProposalTimeout(Duration timeout);
/**
* create ConfigBuilder to set proposal.threshold.
*
* @param threshold the value of proposal.threshold
* @return {@link ConfigOperation}
*/
public ConfigBuilder setProposalThreshold(int threshold);
/**
* create ConfigBuilder to set proposal.contract.vote.enable.
*
* @param enable the value of proposal.contract.vote.enable
* @return {@link ConfigOperation}
*/
public ConfigBuilder setContractVoteEnable(boolean enable);
/**
* create ConfigBuilder to set proposal.contract.vote.threshold.
*
* @param threshold the value of proposal.contract.vote.threshold
* @return {@link ConfigOperation}
*/
public ConfigBuilder setContractVoteThreshold(int threshold);
/**
* return build ConfigOperation.
*
* @return {@link ConfigOperation}
*/
public ConfigOperation build();
}
权限类操作¶
权限的操作分以下几种:
- CreateRole,创建角色。其中 admin、contractManager、nodeOfVP 为内置角色,合约初始化时默认创建。其中 admin 为链级管理员, contractManager 为合约管理员, nodeOfVP 代表VP节点
- DeleteRole,删除角色。其中 admin、contractManager、nodeOfVP 角色不能被删除。
- Grant,授予账户某角色
- Revoke,回收账户的某角色
构造权限类操作 PermissionOperation 的构造器 PermissionBuilder 提供了 createRole 、 deleteRole 、 grant 、 revoke 以及 build 方法,其定义如下:
public static class PermissionBuilder {
/**
* create PermissionBuilder to create role.
*
* @param role role name
* @return {@link PermissionBuilder}
*/
public PermissionBuilder createRole(String role);
/**
* create PermissionBuilder to delete role.
*
* @param role role name
* @return {@link PermissionBuilder}
*/
public PermissionBuilder deleteRole(String role);
/**
* create PermissionBuilder to grant role to address.
*
* @param role
role name
* @param address account address
* @return {@link PermissionBuilder}
*/
public PermissionBuilder grant(String role, String address);
/**
* create PermissionBuilder to revoke role from address.
*
* @param role
role name
* @param address account address
* @return {@link PermissionBuilder}
*/
public PermissionBuilder revoke(String role, String address);
/**
* return build PermissionOperation.
*
* @return {@link PermissionOperation}
*/
public PermissionOperation build();
}
节点管理类操作¶
节点的操作分为以下几种:
- AddNode,增加建立连接的节点,即将节点加到hosts中(此时没有加入共识)
- AddVP,增加VP节点,即将节点加入共识
- RemoveVP,删除共识VP节点,同时断开此节点在此namespace中与其他节点建立的连接,如果节点没有加入其他namespace,则将节点停掉
构造节点管理类操作 NodeOperation 的构造器 NodeBuilder 提供了 addNode 、 addVP 、 removeVP 以及 build 方法,其定义如下:
public static class NodeBuilder {
/**
* create NodeBuilder to add node with give params.
*
* @param pub
public key of new node
* @param hostname host name of new node
* @param role
node role
* @param namespace namespace
* @return {@link NodeBuilder}
*/
public NodeBuilder addNode(byte[] pub, String hostname, String role, String namespace);
/**
* create NodeBuilder to add vp.
*
* @param hostname host name of new node
* @param namespace namespace the new node will add
* @return {@link NodeBuilder}
*/
public NodeBuilder addVP(String hostname, String namespace);
/**
* create NodeBuilder to remove vp.
*
* @param hostname host name of remove node
* @param namespace namespace the node will be removed
* @return {@link NodeBuilder}
*/
public NodeBuilder removeVP(String hostname, String namespace);
/**
* return build NodeOperation.
*
* @return {@link NodeOperation}
*/
public NodeOperation build();
}
合约命名操作¶
合约命名的操作分以下几种:
- SetCName,设置合约命名,即为某一合约地址设置合约命名
构造合约命名类操作 CNSOperation 的构造器 CNSBuilder 提供了 setCName 和 build 方法,其定义如下:
public static class CNSBuilder {
/**
* create CNSOperation to set contract name for contract address.
*
* @param address contract address
* @param name
contract name
* @return {@link CNSBuilder}
*/
public CNSBuilder setCName(String address, String name);
/**
* return build CNSOperation.
*
* @return {@link CNSOperation}
*/
public CNSOperation build();
}
合约生命周期管理操作¶
合约生命周期管理的操作分以下几种:
- DeployContract,部署合约,即将合约部署到区块链上以供使用。返回值为部署是否成功以及相应的合约地址。
- UpgradeContract,升级合约,即对已有的合约进行升级
- MaintainContract,维护合约,即对已有的合约进行冻结、解冻和销毁操作
构造合约生命周期管理类操作 ContractOperation 的构造器 ContractBuilder 提供了 deploy 、 upgrade 、 upgradeByName 、 maintain 、 maintainByName 以及 build 方法,其定义如下:
public static class ContractBuilder {
/**
* create ContractOperation to deploy contract.
*
* @param source
contract source
* @param bin
contract bin
* @param vmType
vm type
* @param compileOpt contract compile option(the compile option to compile source to bin)
* @return {@link ContractBuilder}
*/
public ContractBuilder deploy(String source, String bin, VMType vmType, Map<String, String> compileOpt);
/**
* create ContractOperation to upgrade contract by contract address.
*
* @param source
contract source
* @param bin
contract bin
* @param vmType
vm type
* @param addr
contract address
* @param compileOpt contract compile option(the compile option to compile source to bin)
* @return {@link ContractBuilder}
*/
public ContractBuilder upgrade(String source, String bin, VMType vmType, String addr,
Map<String, String> compileOpt);
/**
* create ContractOperation to upgrade contract by contract name.
*
* @param source
contract source
* @param bin
contract bin
* @param vmType
vm type
* @param name
contract name
* @param compileOpt contract compile option(the compile option to compile source to bin)
* @return {@link ContractBuilder}
*/
public ContractBuilder upgradeByName(String source, String bin, VMType vmType, String name,
Map<String, String> compileOpt);
/**
* create ContractOperation to maintain contract by contract address.
*
* @param vmType vm type
* @param addr
contract address
* @param opCode operation code, 2 means freeze, 3 means unfreeze, 5 means destroy
* @return {@link ContractBuilder}
*/
public ContractBuilder maintain(VMType vmType, String addr, int opCode);
/**
* create ContractOperation to maintain contract by contract name.
*
* @param vmType vm type
* @param name
contract address
* @param opCode operation code, 2 means freeze, 3 means unfreeze, 5 means destroy
* @return {@link ContractBuilder}
*/
public ContractBuilder maintainByName(VMType vmType, String name, int opCode);
/**
* return build ContractOperation.
*
* @return {@link ContractOperation}
*/
public ContractOperation build();
}
bvm的合约操作创建好之后,使用 BVMBuilder 提供的 invoke 方法构造bvm的交易体,使用 build 方法构造出交易 transaction ,并为交易设置 txVersion 并使用 sign 方法签名,得到最终可以发送执行的交易体。
创建请求¶
这个过程分为两步,先创建 ContractService 对象,再制定之前构造的交易体调用相应的服务接口。示例如下:
ContractService contractService = ServiceManager.getContractService(providerManager);
Request<TxHashResponse> contractRequest = contractService.deploy(transaction);
发送交易体¶
这个过程实际分为两步,调用 send() 部署合约拿到响应,再对响应解析拿到 ReceiptResponse (执行结果),这是合约相关接口独有的,其他接口一般只需要调用 send() 方法拿到响应就结束了。
ReceiptResponse receiptResponse = contractRequest.send().polling();
解析回执¶
在 Decoder 类中,提供了 decodeBVM 的方法用于解析bvm交易回执,其定义如下:
/**
* decode bvm receipt result to bvm.Result.
*
* @param encode receipt result
* @return {@link Result}
*/
public static Result decodeBVM(String encode);
其中 Result 中含有三个字段, success 表示是否成功(指的是对HashContract以及ProposalContract的操作是否成功,对于ProposalContract而言,提案内容中包含的每个操作在执行时是否成功,在 ret 字段中展示,因为提案支持批量操作), err 表示错误信息, ret 为返回的相应数据。
当需要解析 result.ret 的值时(创建提案失败,或执行提案的时候), Decoder 类中提供了 decodeBVMResult 的方法,其定义如下:
/**
* decode ret in bvm.Result to bvm.OperationResult list.
*
* @param resultRet the list of bvm.OperationResult
* @return {@link List<OperationResult/>}
*/
public static List<OperationResult> decodeBVMResult(String resultRet);
其中 OperationResult 中含有两个字段, code 表示执行结果(200为成功), msg 为相应的错误信息(code不为200时)或操作返回值(部署合约操作的合约地址)。列表中 OperationResult 的顺序与创建提案时,提案中包含的操作顺序一致。
使用示例¶
HashContract¶
HashContract中有两个方法可供调用,Set和Get方法。
SetSet方法接收两个参数,一个参数为key,一个参数为value,用于存储键值对。使用HashBuilder 提供的set 方法构造一个BuiltinOperation ,然后使用BVMBuilder 提供的invoke 方法设置参数,使用build 方法构造Transaction ,然后使用ContractService 提供的invoke 方法构造请求,最后将请求发出拿到响应结果,其示例如下:
String key = "0x123";
String value = "0x456";
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).
invoke(new HashOperation.HashBuilder().set(key, value).build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
GetGet方法接收一个参数key,用于取出HashContract中与之对应的value值。其示例如下:
String key = "0x123";
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).
invoke(new HashOperation.HashBuilder().get(key).build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
ProposalContract¶
ProposalContract提供创建提案、取消提案、提案投票以及执行提案的操作,分别对应Create、Cancel、Vote、Execute方法。
创建提案¶
配置类
创建配置类提案时,先使用 ConfigBuilder 构造配置类的操作,然后使用 ProposalBuilder 提供的 createForConfig 构造创建提案的操作,再使用 BVMBuilder 提供的 invoke 封装操作到交易中,使用 build 方法构造交易,然后创建请求、发送请求、解析结果,其示例如下:
ArrayList<NsFilterRule> rules = new ArrayList<>();
rules.add(new NsFilterRule());Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.BVMBuilder(ac.getAddress()).invoke(new ProposalOperation.ProposalBuilder().createForConfig(
new ConfigOperation.ConfigBuilder().setFilterEnable(false).build(),
new ConfigOperation.ConfigBuilder().setFilterRules(rules).build(),
new ConfigOperation.ConfigBuilder().setConsensusAlgo("rbft").build(),
new ConfigOperation.ConfigBuilder().setConsensusBatchSize(100).build(),
new ConfigOperation.ConfigBuilder().setConsensusPoolSize(200).build(),
new ConfigOperation.ConfigBuilder().setConsensusSetSize(50).build(),
new ConfigOperation.ConfigBuilder().setProposalThreshold(4).build(),
new ConfigOperation.ConfigBuilder().setProposalTimeout(Duration.ofMinutes(8).abs()).build(),
new ConfigOperation.ConfigBuilder().setContractVoteThreshold(3).build(),
new ConfigOperation.ConfigBuilder().setContractVoteEnable(true).build() ).build()).
).build()).build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
权限类
创建权限类提案时,先使用 PermissionBuilder 构造权限类的操作,然后使用 ProposalBuilder 提供的 createForPermission 构造创建提案的操作,再使用 BVMBuilder 提供的 invoke 封装操作到交易中,使用 build 方法构造交易,然后创建请求、发送请求、解析结果,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).invoke(new ProposalOperation.ProposalBuilder().createForPermission(
new PermissionOperation.PermissionBuilder().createRole("managerA").build(),
new PermissionOperation.PermissionBuilder().grant("managerA", account.getAddress()).build(),
new PermissionOperation.PermissionBuilder().revoke("managerA", account.getAddress()).build(),
new PermissionOperation.PermissionBuilder().deleteRole("managerA").build()
).build()).build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result= Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
节点类
创建节点类提案时,先使用 NodeBuilder 构造节点类的操作,然后使用 ProposalBuilder 提供的 createForNode 构造创建提案的操作,再使用 BVMBuilder 提供的 invoke 封装操作到交易中,使用 build 方法构造交易,然后创建请求、发送请求、解析结果,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
List<NodeOperation> nodeOpts = new ArrayList<>();
for (int i = 1; i < 5; i++) {
nodeOpts.add(new NodeOperation.NodeBuilder().addNode(("pub" + i).getBytes(), "node" + i, "vp", "global").build());
nodeOpts.add(new NodeOperation.NodeBuilder().addVP("node" + i, "global").build());
}
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).invoke(new ProposalOperation.ProposalBuilder().
createForNode(nodeOpts.toArray(new NodeOperation[nodeOpts.size()])).build()).build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
合约命名类
创建合约命名类提案时,先使用 CNSBuilder 构造合约命名类的操作,然后使用 ProposalBuilder 提供的 createForCNS 构造创建提案的操作,再使用 BVMBuilder 提供的 invoke 封装操作到交易中,使用 build 方法构造交易,然后创建请求、发送请求、解析结果,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.BVMBuilder(ac.getAddress()).
invoke(new ProposalOperation.
ProposalBuilder().
createForCNS(new CNSOperation.
CNSBuilder().
setCName("0x0000000000000000000000000000000000ffff01", "HashContract").
build()).
build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
合约生命周期管理类
创建合约生命周期管理类提案时,先使用 ContractBuilder 构造合约生命周期管理类的操作,然后使用 ProposalBuilder 提供的 createForContract 构造创建提案的操作,再使用 BVMBuilder 提供的 invoke 封装操作到交易中,使用 build 方法构造交易,然后创建请求、发送请求、解析结果,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).
invoke(new ProposalOperation.ProposalBuilder().createForContract(
// deploy
new ContractOperation.ContractBuilder().deploy("source", bin, VMType.EVM, null).build()
// upgrade
// new ContractOperation.ContractBuilder().upgrade("source", bin, VMType.EVM, address,
null).build(),
// freeze
// new ContractOperation.ContractBuilder().maintain(VMType.EVM, address, 2).build()
// unfreeze by name
// new ContractOperation.ContractBuilder().maintainByName(VMType.EVM, name, 3).build(),
// upgrade by name
// new ContractOperation.ContractBuilder().upgradeByName("source", bin, VMType.EVM, name,
null).build()).build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
提案投票¶
提案创建后,具有相应权限的用户可以对提案进行投票(合约管理员可对合约生命周期管理类提案进行投票,链级管理员可对其他类型提案进行投票,目前默认genesis账户既是链级管理员又是合约管理员,后续可通过权限类提案进行变动),同意此提案内容则投赞同票,不同意为反对票,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).
invoke(new ProposalOperation.ProposalBuilder().
// 赞同票
vote(1, true).
build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
取消提案¶
创建后的提案如果处于投票中 Voting 、等待执行 Wating_Exe 时,提案创建者可以发送交易取消提案,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).
invoke(new ProposalOperation.ProposalBuilder().
cancel(1).
build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
执行提案¶
提案创建者可对处于等待执行 Wating_exe 的提案发起执行操作,其示例如下:
Account ac = accountService.fromAccountJson(accountJsons[5]);
Transaction transaction = new Transaction.
BVMBuilder(ac.getAddress()).
invoke(new ProposalOperation.ProposalBuilder().
execute(1).
build()).
build();
transaction.sign(ac);
ReceiptResponse receiptResponse = contractService.invoke(transaction).send().polling();
Result result = Decoder.decodeBVM(receiptResponse.getRet());
System.out.println(result);
相关接口¶
查询提案¶
参数:
- nodeIds 请求向哪些节点发送
Request<ProposalResponse> getProposal(int... nodeIds);
拿到 ProposalResponse 后,可通过 getProposal 方法拿到提案信息 Proposal ,其定义如下:
public class ProposalResponse extends Response {
public class Proposal {
private int id;// 提案id
private String code; //提案内容
private int timestamp; // 创建时间
private int timeout;// 超时时间
private String status;// 提案状态
private List<VoteInfo> assentor;//赞同列表
private List<VoteInfo> objector;// 反对列表
private int threshold; // 阈值
private int score;// 赞同权重
private String creator;// 创建者
private String version;// 版本
private String type;// 类型
private String completed;// 执行提案的交易hash
private String cancel;// 取消提案的交易hash
}
public class VoteInfo {
private String addr;// 投票者地址
private String txHash;// 投票的交易hash
}
}
查询配置¶
参数:
- nodeIds 请求向哪些节点发送
Request<ConfigResponse> getConfig(int... nodeIds);
拿到 ConfigProposal 后,通过 getConfig 方法拿到配置信息,配置信息是 toml 格式的字符串。
查询连接的节点信息¶
参数:
- role 节点角色(目前只支持查询vp节点)
- nodeIds 请求向哪些节点发送
Request<HostsResponse> getHosts(String role, int... nodeIds);
拿到 HostsResponse 后,通过 getHosts 方法拿到节点信息。 getHosts 方法返回的是key为节点名,value为节点公钥的map。
查询参与共识的节点信息¶
参数:
- nodeIds 请求向哪些节点发送
Request<VSetResponse> getVSet(int... nodeIds);
拿到 VSetResponse 后,通过 getVSet 拿到共识的节点信息。 getVSet 方法返回的是所有参与共识的节点列表。
查询所有角色信息¶
参数:
- nodeIds 请求向哪些节点发送
Request<AllRolesResponse> getAllRoles(int... nodeIds);
拿到 AllRolesResponse 后,通过 getAllRoles 方法拿到所有的角色信息。 getAllRoles 方法返回的是key为角色名称,value为角色权重的map。
查询角色是否存在¶
参数:
- role 要查询的角色名称
- nodeIds 请求向哪些节点发送
Request<RoleExistResponse> isRoleExist(String role, int... nodeIds);
拿到 RoleExistResponse 后,通过 isRoleExist 方法拿到角色是否存在的结果。 isRoleExist 方法返回的是一个布尔值, true 表示存在, false 表示不存在。
根据合约地址查询合约命名¶
参数:
- address 合约地址
- nodeIds 请求向哪些节点发送
Request<NameResponse> getNameByAddress(String address, int... nodeIds);
拿到 NameResponse 后,通过 getName 方法拿到合约命名。 getName 方法返回的是一个字符串。
根据合约命名查询合约地址¶
参数:
- name 合约命名
- nodeIds 请求向哪些节点发送
Request<AddressResponse> getAddressByName(String name, int... nodeIds);
拿到 AddressResponse 后,通过 getAddress 方法拿到合约地址。 getAddress 方法返回的是一个字符串。
查询所有合约地址到合约名的映射¶
参数:
- nodeIds 请求向哪些节点发送
Request<AllCNSResponse> getAllCNS(int... nodeIds);
拿到 AllCNSResponse 后,通过 getAllCNS 方法拿到所以的合约地址到合约命名的映射关系。 getAllCNS 方法返回的是key为合约地址,value为合约命名的map。
节点及分区管理¶
共识相关使用手册¶
跨域网络使用手册¶
1. 引言¶
1.1 编写目的¶
此文档描述Hyperchain网络里的跨域和节点发现相关概念、介绍网络地址配置项含义,使软件开发人员能清楚地了解Hyperchain的网络配置,便于Hyperchain的组网部署。
1.2 相关配置¶
网络地址相关的配置有两个地方,分别位于系统级别 configuration/dynamic.toml 配置文件和分区级别 configuration/<分区名>/ns_dynamic.toml 配置文件里。
1.2.1 dynamic.toml¶
dynamic.toml 与网络地址相关的配置如下代码所示,主要用来配置本地节点的域相关信息:
[p2p]
[p2p.ip.self]
# domain 用于指定本地节点目前处在的网络域名称,比如本地节点目前
# 所处的域名称为“domain1”
domain = "domain1"
# addrs 用于指定本地节点网络可互通的网络域有哪些,并且指定了这些域
# 下的节点应该使用哪个 IP 地址来连自己(即本地节点),这个 IP 地址可能为本地
# 节点的 IP 地址,也可能是代理设备的地址
addrs = [
"domain1 192.168.10.1:50011",
"domain2 118.19.0.1:50011",
"domain3 35.10.208.183:50011",
]
1.2.2 ns_dynamic.toml¶
ns_dynamic.toml 与网络地址相关的配置如下代码所示,主要用来配置本地节点在该分区下的要去连接的对端节点地址信息:
[p2p]
[p2p.ip.remote]
# hosts 用于指定本地节点启动后要向哪些节点发起连接,并且指定了通往
# 这些节点的可连通地址,这个地址可能是对端节点的 IP 地址,也可能是
# 代理转发设备的地址。
# 如果该列表里指定了本地节点自己的hostname和地址,则自动忽略该项。
hosts = [
"node1 127.0.0.1:50011",
"node2 127.0.0.1:50012",
"node3 127.0.0.1:50013",
"node4 127.0.0.1:50014",
]
当本地节点向 [p2p.ip.remote.hosts] 里配置的对端节点发起连接后,对端节点会把本地节点 dynamic.toml 里的域列表信息广播给网络里的其他节点,如果其他节点与本地节点还没建立起连接,则从收到的域列表里挑选名称与自己所在域一样的网络域下对应的地址,自动发起网络连接。
2. 功能说明¶
在跨域网络里,存在网络域和网络域地址列表两个配置项,分别用于配置本地节点目前处在的网络域名称和本地节点在不同网络域下可被访问的 IP 地址和端口号,也可以理解为用于指定这些域下的节点应该使用哪个 IP 地址来连本地节点自己。
在 hyperchain v2.0.8 以及之后的版本,系统集成了节点发现功能,默认开启。 节点发现使得新加入的节点在启动之前,网络地址配置得到极大简化,并且对于仅仅开放了单向网络通道的两个节点,不管该单向网络通道是 新节点->老节点 还是 新节点 <- 老节点,都只需要做好新节点网络地址的配置就行,而不需要对老节点的网络配置进行人工修改 。
下面将分别介绍 跨域通信 和 节点发现 功能。
2.1 跨域通信¶
网络域,与局域网的概念类似,在同一个网络域下的节点可以互相连通,不在同一个网络域名下的节点无法互通。网络域与计算机网络里的局域网差别在于,这里的网络域,指的是逻辑上的网络域,即虽然节点在同一个局域网内且IP互通,但是可以被划分为逻辑上不同的网络域。
在 hyperchain v2.0.8 之前版本 的设计里,节点间在建立连接的时候,虽然IP是可通的,但是因为逻辑上处在不同的网络域,如果网络域地址列表里面不包含 彼此 的网络域,则不能成功建立连接,连接会被拒绝掉。网络拓扑示意图如下所示,网络域 domainA 内的节点和网络域 domainB 内的节点的网络域地址列表里都只有一个网络域,因此 node1、node2、node3、node4 均不是跨域节点,domainA 下的节点无法与 domainB 下的节点建立物理连接:(通过在建立物理连接的过程中,去检查彼此的网络域地址列表来决定是否拒绝连接)
然而,通过修改网络域地址列表,让处在不同网络域下的节点的网络域地址列表里都配有可跨的网络域,这两个网络域的节点才能成功建立起连接,此时,node1、node2、node3、node4 均称为 跨域节点 ,如下图所示:
在 hyperchain v2.0.8 及之后的版本 里,在上述设计的基础上,针对新增节点并且新增网络域的场景做了优化,允许老节点的网络域地址列表里没有新域的情况下,在新节点网络配置正确的前提下,新节点依旧可以与老节点建立起网络连接,同时老节点会动态新增网络域,在自己的网络域地址列表里回写新节点所在的域。也就是说, 网络连接是否能建立起来,完全取决于 [p2p.ip.remote.hosts] , 只要 [p2p.ip.remote.hosts] 配了对端节点的可连通地址,不管网络域地址列表配置如何、是否配全,都可以建立起网络连接,但是不同情况下节点可能有不一样的行为表现 。为了便于读者理解,下面使用一个具体例子来说明:假设两个跨域节点分别为 node1 和 node2,node1 在 domainA 下,node2 在 domainB 下,且 node1 确实有 domainB 下所能连得通的 IP,node2 也是拥有 domainA 下所能连得通的 IP:
- 场景一: node1 的网络域地址列表配置了 domainB 下的 IP,node2 的网络域地址列表没有配置 domainA 下的 IP。
如果此时 node1 主动连接 node2,这种情况下只要拨号地址的物理网络本身是可到达的,则可以成功建立连接,并且 node2 的网络域地址列表会自动回写 domainA 。
如果此时 node2 主动连接 node1,这种情况下只要拨号地址的物理网络本身是可到达的,则可以成功建立连接,但是 node2 日志会定时打印找不到 domainA 下的 IP 的警告提示,这条警告信息并不会影响节点的正常运行,若要消除警告日志,需要运维人员手动使用 node2 的 IPC 命令线上增加 domainA 网络域以消除警告日志。
- 场景二: node1 的网络域地址列表没有配置 domainB 下的 IP,node2 的网络域地址列表配置了 domainA 下的 IP。
情况和上述一样,node1 会发生和上述描述的 node2 发生的情况。
- 场景三: 双方的网络域地址列表都没有配置对方域下的 IP。
如果此时 node1 主动连接 node2 ,这种情况下只要拨号地址的物理网络本身是可到达的,则可以成功建立连接,并且 node2 的网络域地址列表会自动回写 domainA,node1 日志会出现找不到 domainB 下的 IP 提示,这时候需要运维人员手动使用 node1 的 IPC 命令线上增加 domainB 网络域以消除警告日志。
反过来,如果此时 node2 主动连接 node1,这种情况下只要拨号地址的物理网络本身是可到达的,则可以成功建立连接,并且 node1 的网络域地址列表会自动回写 domainB,node2 日志会出现找不到 domainA 下的 IP 提示,这时候需要运维人员手动使用 node2 的 IPC 命令线上增加 domainA 网络域以消除警告日志。
综上所述,总结一下,不同局域网内的节点肯定处在不同的网络域,同时,通过网络域地址列表,可以让处在同一个局域网内的节点处于不同的网络域,这些 网络域 都是 人为划分 的逻辑上的网络域,由区块链系统部署人员或者运维人员来定义区块链集群的网络域。而一个节点是否为 跨域节点 ,同样也是 人为指定 ,由部署人员或运维人员来指定并且设置节点网络域地址列表,网络域地址列表有两个及以上不同 domain 下的 IP 的节点即为跨域节点。部署人员或运维人员也可以通过IPC命令 线上增加或修改网络域及其地址 。
一般来说,一个内网定义为一个网络域,不同内网的节点想要进行通信,则称之为跨域通信。实际生产环境中的网络比前文举的例子要复杂很多,本文第三章将对不同部署场景下的跨域配置进行举例。
2.2 节点发现¶
在 hyperchain v2.0.8 以及之后的版本,系统集成了节点发现功能,默认开启。节点发现使得节点想要与区块链系统的其他节点组网,其启动前的网络地址配置得到极大简化,并且对于仅仅开放了单向网络通道的两个节点,不管该单向网络通道是 新节点-> 老节点 还是 新节点 <- 老节点,都只需要做好新节点网络地址的配置就行,而不需要对老节点的网络配置进行人工修改。
2.2.2节 和 2.2.3节 分别就不同场景对网络地址配置进行说明。
2.2.1 相关配置¶
节点发现是系统自带功能,与其相关的配置有:
ns_static.toml 文件:
[p2p]
[p2p.discover]
# discoverInterval 为每次发送 discovery reqeust 去获取网络里与
# 自己domain相通的节点信息的时间间隔,默认为 10m
discoverInterval = "10m"
# 为了避免网络消息的冗余,本地节点有一块缓存去记录自己曾经向哪些节点发送过
# 哪些peer信息,以此来避免重复发送一样的内容。discoverEmptyInterval 即
# 为清理这块缓存的时间间隔。默认为 12h
discoverEmptyInterval = "12h"
2.2.2 新节点加入-双向打通¶
假设现在有一个新节点要加入分区网络,并且这个新节点与对端节点的网络通道已经双向打通,新节点网络地址如何配置呢?
推荐方法一:
- dynamic.toml 里的 [p2p.ip.self.addrs] 配全当前区块链系统里的 所有网络域及对应的地址 ,对应的地址指的是该域下面的节点要向本地节点发起连接时应该使用的 IP 和 Port;
- ns_dynamic.toml 里的 [p2p.ip.remote.hosts] 配全了本地节点所有要连接的 所有对端节点及其地址 ,地址指的是本地节点向对端节点发起连接时应该使用的 IP 和 Port;
推荐方法二(简化):
- dynamic.toml 里的 [p2p.ip.self.addrs] 只配置了新域及对应的地址,对应的地址指的是该域下面的节点要向本地节点发起连接时应该使用的 IP 和 Port;
- ns_dynamic.toml 里的 [p2p.ip.remote.hosts] 配全当前区块链系统里所有对端节点及其地址。
新节点与其他对端节点网络连接建立完成后:1. 由于新节点 ns_dynamic.toml [p2p.ip.remote.hosts] 配全了所有对端节点及其地址,因此,其他对端节点 dynamic.toml 里的 [p2p.ip.self.addrs] 将自动动态回写新节点携带过来的新域及对应地址(如果新节点处在一个新域名下的话) 。2. 由于新节点 dynamic.toml [p2p.ip.self.addrs] 只配置了新域及对应的地址, 因此,新节点日志文件里将打印缺少其他domain信息的警告,这个提示不会影响节点的正常运行,可以参考IPC使用手册,在新节点上执行新增域信息的命令,来消除这个警告日志。
推荐方法三(简化):
- dynamic.toml 里的 [p2p.ip.self.addrs] 配全当前区块链系统里的 所有网络域及对应的地址 ,对应的地址指的是该域下面的节点要向本地节点发起连接时应该使用的 IP 和 Port;
- ns_dynamic.toml 里的 [p2p.ip.remote.hosts] 至少配了一个对端节点及其地址。如果 hosts 里指定的对端节点与区块链系统里的其他对端节点所构成的网络连接拓扑是一个连通图,则通过节点发现,本地节点将与其他所有对端节点自动建立起网络连接。
2.2.3 新节点加入-单向打通¶
假设现在有一个新节点要加入分区网络,并且这个新节点打通了到对端节点的单向网络通道(后文简称“正向通道”),而对端节点到新节点的网络通道未打通(后文简称“反向通道”),新节点网络地址如何配置呢?
推荐方法一:
- dynamic.toml 里的 [p2p.ip.self.addrs] 配置只打通了反向通道和打通了正向通道与反向通道的域的地址信息;
- ns_dynamic.toml 里的 [p2p.ip.remote.hosts] 配置只打通了正向通道但是未打通反向通道的节点地址信息和打通了正向通道与反向通道的节点地址信息,至少配一个对端节点及其地址。
推荐方法二(简化):
- dynamic.toml 里的 [p2p.ip.self.addrs] 只配置打通了反向通道的域的地址信息;
- ns_dynamic.toml 里的 [p2p.ip.remote.hosts] 必须 配置只打通正向通道但是未打通反向通道的节点地址信息,至少配一个对端节点及其地址。
3. 配置举例¶
本节根据不同部署场景提供具体配置示例供部署人员参考,部署场景包括:
- 内外网 :无Nginx、无代理设备,同一个内网里的节点通过内网 IP 地址通信,与外网的节点通过公网 IP 地址通信。
- 全Nginx代理 :同一个内网里的节点通过内网 IP 地址通信(或 nginx 代理内网 IP 通信);与外网的节点通过 nginx 代理公网 IP 通信,即节点不管是访问外网的节点还是被外网节点访问,都需要通过 nginx 进行代理。
- 全Nginx反向代理 :同一个内网里的节点通过内网 IP 地址通信;通过公网 IP 地址访问外网的节点,外网的节点通过 nginx 代理访问本地节点,即节点通过公网 IP 地址访问外网的节点,但是被外网节点访问,则需要通过 nginx 进行代理;
- 混合型代理 :前面三种的混合存在。
同时,本节将对上述任一部署场景下的全量配置和简化配置都进行配置说明。
- 全量配置:指的是节点的 [p2p.ip.remote.hosts] 配置里配了所有直连节点的网络地址。
- 简化配置:指的是节点的 [p2p.ip.remote.hosts] 配置里只配了部分直连节点的网络地址,通过节点发现来与其他直连节点建立起网络连接。
3.1 内外网¶
无Nginx、无代理设备,同一个内网里的节点通过内网 IP 地址通信,与外网的节点通过公网 IP 地址通信。
3.1.1 全量配置¶
domain1 是 机构A 所在的网络域,domain2 是 机构B 所在的网络域,domain3 是 机构C 所在的网络域。每个节点都拥有一个内网地址和一个外网地址,中间没有架设任何的代理服务器。因此,内网节点可以使用内网 IP 访问内网里的节点,使用公网 IP 访问外网节点。
在上面的例子中,各个节点的网络配置理应如下:
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 62.219.0.1:50011",
"domain3 62.219.0.1:50011" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node3 118.19.0.1:50011",
"node4 10.10.0.2:50011",
"node5 117.17.0.1:50012",
"node6 118.19.0.1:50012", ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50011",
"domain2 172.16.0.1:50011",
"domain3 117.17.0.1:50011" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node5 172.16.0.2:50011",
"node6 118.19.0.1:50012" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50011",
"domain2 118.19.0.1:50011",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 117.17.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 62.219.0.1:50012",
"domain3 62.219.0.1:50012" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 117.17.0.1:50011",
"node3 118.19.0.1:50011",
"node5 117.17.0.1:50012",
"node6 118.19.0.1:50012" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50012",
"domain2 172.16.0.2:50011",
"domain3 117.17.0.1:50012" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 172.16.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node6 118.19.0.1:50012" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50012",
"domain2 118.19.0.1:50012",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 117.17.0.1:50011",
"node3 192.168.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012" ]
3.1.2 简化配置¶
( 注:虽不用配置全部hosts,但配置也需保证整个网络拓扑图为连通图,即不存在网络分区的现象 )。
使用与 3.1.1 全量配置 一样的例子,网络配置不需要配全量直连节点的地址,即 node1 和 node3 虽然都没有配置彼此的IP,但他们最终可以通过其他节点互相发现对方。各个节点的网络配置详见下文。
当然,网络配置也可以更加简化,只要保证两个节点之间有共同连接的节点,那么他们就不用配彼此的IP,可以通过这个共同节点发现对方,这样便可以简化对于 hosts 的配置。
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 62.219.0.1:50011",
"domain3 62.219.0.1:50011" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node4 10.10.0.2:50011",
"node6 118.19.0.1:50012", ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50011",
"domain2 172.16.0.1:50011",
"domain3 117.17.0.1:50011" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node4 62.219.0.1:50012",
"node5 172.16.0.2:50011",
"node6 118.19.0.1:50012" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50011",
"domain2 118.19.0.1:50011",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 62.219.0.1:50012",
"domain3 62.219.0.1:50012" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 117.17.0.1:50011",
"node3 118.19.0.1:50011",
"node6 118.19.0.1:50012" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50012",
"domain2 172.16.0.2:50011",
"domain3 117.17.0.1:50012" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 172.16.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node6 118.19.0.1:50012" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50012",
"domain2 118.19.0.1:50012",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 117.17.0.1:50011",
"node3 192.168.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012" ]
3.2 全Nginx代理¶
同一个内网里的节点通过内网 IP 地址通信(或 nginx 代理内网 IP 通信);与外网的节点通过 nginx 代理公网 IP 通信,即节点不管是访问外网的节点还是被外网节点访问,都需要通过 nginx 进行代理。
3.2.1 全量配置¶
如上图所示,domain1 是 机构A 所在的网络域,domain2 是 机构B 所在的网络域,domain3 是 机构C 所在的网络域,并且它们都各自架设了一台 nginx 做转发,这样的好处是节点不需要连接外网也可相互通信,机构内的节点都只需根据内网 IP 进行连接即可。说明一下图中 nginx 的映射规则:
- 机构A:nginxA:62.219.0.1 (外) --> 10.20.0.1 (内) - node1 和 node4 在 nginxA 上的映射分别为: - nginxA-->node1: 62.219.0.1:50011 (nginxA外) --> 10.20.0.1:50011 (nginx内) --> 10.10.0.1:50011 (内) - nginxA-->node4: 62.219.0.1:50012 (nginxA外) --> 10.20.0.1:50012 (nginx内) --> 10.10.0.2:50011 (内) - 而机构A内的节点想要访问机构B中的节点 node2 和 node5,则在机构A中的 nginxA 上对 node2 和 node5 做映射, - 即使用 niginxA 映射到 node2 和 node5 所在机构B的 nginxB 的公网IP,使得机构A通过访问nginxA的内网就可以将流量转发到机构B中的节点 - node2 和 node5 在机构A中架设的 nginxA 上的映射为: - nginxA-->node2: 10.20.0.1:50013 (nginxA内) --> 117.17.0.1:50011 (nginxB外) - nginxA-->node5: 10.20.0.1:50014 (nginxA内) --> 117.17.0.1:50012 (nginxB外) - 同理,node3 和 node6 在机构A中架设的 nginxA 上的映射为: - nginxA-->node3: 10.20.0.1:50015 (nginxA内) --> 118.19.0.1:50011 (nginxC外) - nginxA-->node6: 10.20.0.1:50016 (nginxA内) --> 118.19.0.1:50012 (nginxC外)
- 机构B:nginxB: 117.17.0.1 (外) –> 10.30.0.1 (内)
- node2 和 node5 在 nginxB 的映射分别为:
- nginxB–>node1: 117.17.0.1:50011 (nginxB外) –> 10.30.0.1:50011 (nginxB内) –> 172.16.0.1:50011 (内)
- nginxB–>node4: 117.17.0.1:50012 (nginxB外) –> 10.30.0.1:50012 (nginxB内) –> 172.16.0.2:50011 (内)
- 而机构B内的节点想要访问机构A中的节点 node1 和 node4,则在机构B中的 nginxB 上对 node1 和 node4 做映射,
- 即使用 niginxB 映射到 node1 和 node4 所在机构A的 nginxA 的公网IP,使得机构B通过访问nginxB的内网就可以将流量转发到机构A中的节点
- node1 和 node4 在机构B中架设的 nginxB 上的映射为:
- nginxB–>node1: 10.30.0.1:50013 (nginxB内) –> 62.219.0.1:50011 (nginxA外)
- nginxB–>node4: 10.30.0.1:50014 (nginxB内) –> 62.219.0.1:50012 (nginxA外)
- 同理,node3 和 node6 在机构B中架设的 nginxB 上的映射为:
- nginxB–>node3: 10.30.0.1:50015 (nginxA内) –> 118.19.0.1:50011 (nginxC外)
- nginxB–>node6: 10.30.0.1:50016 (nginxA内) –> 118.19.0.1:50012 (nginxC外)
- 机构C:nginxC: 118.19.0.1 (外) –> 10.40.0.1 (内)
- node3 和 node6 在 nginxC 的映射分别为:
- nginxC–>node3: 118.19.0.1:50011 (nginxC外) –> 10.40.0.1:50011 (nginxC内) –> 192.168.0.1:50011 (内)
- nginxC–>node6: 118.19.0.1:50012 (nginxC外) –> 10.40.0.1:50012 (nginxC内) –> 192.168.0.2:50011 (内)
- node1 和 node4 在 nginxC 上的映射分别为:
- nginxC–>node1: 10.40.0.1:50013 (nginxC内) –> 62.219.0.1:50011 (nginxA外)
- nginxC–>node4: 10.40.0.1:50014 (nginxC内) –> 62.219.0.1:50012 (nginxA外)
- node2 和 node5 在 nginxC 上的映射分别为:
- nginxC–>node2: 10.40.0.1:50015 (nginxA内) –> 117.17.0.1:50011 (nginxB外)
- nginxC–>node5: 10.40.0.1:50016 (nginxA内) –> 117.17.0.1:50012 (nginxB外)
在上面的例子中,各个节点的网络配置理应如下:
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 10.30.0.1:50013",
"domain3 10.40.0.1:50013" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 10.20.0.1:50013",
"node3 10.20.0.1:50015",
"node4 10.10.0.2:50011",
"node5 10.20.0.1:50014",
"node6 10.20.0.1:50016" ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50013",
"domain2 172.16.0.1:50011",
"domain3 10.40.0.1:50015" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.30.0.1:50013",
"node3 10.30.0.1:50015",
"node4 10.30.0.1:50014",
"node5 172.16.0.2:50011",
"node6 10.30.0.1:50016" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50015",
"domain2 10.30.0.1:50015",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.40.0.1:50013",
"node2 10.40.0.1:50015",
"node4 10.40.0.1:50014",
"node5 10.40.0.1:50016",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 10.30.0.1:50014",
"domain3 10.40.0.1:50014" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 10.20.0.1:50013",
"node3 10.20.0.1:50015",
"node5 10.20.0.1:50014",
"node6 10.20.0.1:50016" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50014",
"domain2 172.16.0.2:50011",
"domain3 10.40.0.1:50016" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.30.0.1:50013",
"node2 172.16.0.1:50011",
"node3 10.30.0.1:50015",
"node4 10.30.0.1:50014",
"node6 10.30.0.1:50016" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50016",
"domain2 10.30.0.1:50016",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.40.0.1:50013",
"node2 10.40.0.1:50015",
"node3 192.168.0.1:50011",
"node4 10.40.0.1:50014",
"node5 10.40.0.1:50016", ]
3.2.2 简化配置¶
( 注:虽不用配置全部hosts,但配置也需保证整个网络topo图为连通图,即不存在网络分区的现象 )。
使用与 3.2.1 全量配置 一样的例子,网络配置不需要配全量直连节点的地址,即 node1 和 node3 虽然都没有配置彼此的IP,但他们最终可以通过其他节点互相发现对方,各个节点的网络配置详见下文。
当然, 网络配置也可以更加简化,只要保证两个节点之间有共同连接的节点,那么他们就不用配彼此的IP,可以通过这个共同节点发现对方,这样便可以简化对于 hosts 的配置。
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 10.30.0.1:50013",
"domain3 10.40.0.1:50013" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 10.20.0.1:50013",
"node4 10.10.0.2:50011",
"node6 10.20.0.1:50016" ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50013",
"domain2 172.16.0.1:50011",
"domain3 10.40.0.1:50015" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.30.0.1:50013",
"node3 10.30.0.1:50015",
"node5 172.16.0.2:50011" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50015",
"domain2 10.30.0.1:50015",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 10.40.0.1:50015",
"node4 10.40.0.1:50014",
"node5 10.40.0.1:50016",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 10.30.0.1:50014",
"domain3 10.40.0.1:50014" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 10.20.0.1:50013",
"node6 10.20.0.1:50016" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50014",
"domain2 172.16.0.2:50011",
"domain3 10.40.0.1:50016" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.30.0.1:50013",
"node2 172.16.0.1:50011",
"node3 10.30.0.1:50015",
"node4 10.30.0.1:50014",
"node6 10.30.0.1:50016" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50016",
"domain2 10.30.0.1:50016",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.40.0.1:50013",
"node2 10.40.0.1:50015",
"node3 192.168.0.1:50011",
"node4 10.40.0.1:50014",
"node5 10.40.0.1:50016", ]
3.3 全Nginx反向代理¶
nginx 反向代理指的是外域节点访问本域节点需要通过本域下的 nginx 做转发,而本域节点想要访问外域节点,可以直接访问外网地址,不需要通过自己域下的 nginx 做转发。这种网络情况比全 nginx 代理要简单的多。
3.3.1 全量配置¶
如上图所示,domain1 是 机构A 所在的网络域,domain2 是 机构B 所在的网络域,domain3 是 机构C 所在的网络域,并且它们都各自架设了一台 nginx 做转发
说明一下图中 nginx 的映射规则:
- 机构A:62.219.0.1 (外)
- node1 62.219.0.1:50011 (外) --> 10.10.0.1:50011 (内)
- node4 62.219.0.1:50012 (外) --> 10.10.0.2:50011 (内)
- 机构B:117.17.0.1 (外)
- node2 117.17.0.1:50011 (外) --> 172.16.0.1:50011 (内)
- node5 117.17.0.1:50012 (外) --> 172.16.0.2:50011 (内)
- 机构C:118.19.0.1 (外)
- node3 118.19.0.1:50011 (外) --> 192.168.0.1:50011 (内)
- node6 118.19.0.1:50012 (外) --> 192.168.0.2:50011 (内)
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 62.219.0.1:50011",
"domain3 62.219.0.1:50011" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node3 118.19.0.1:50011",
"node4 10.10.0.2:50011",
"node5 117.17.0.1:50012",
"node6 118.19.0.1:50012", ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50011",
"domain2 172.16.0.1:50011",
"domain3 117.17.0.1:50011" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node5 172.16.0.2:50011",
"node6 118.19.0.1:50012" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50011",
"domain2 118.19.0.1:50011",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 117.17.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 62.219.0.1:50012",
"domain3 62.219.0.1:50012" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 117.17.0.1:50011",
"node3 118.19.0.1:50011",
"node5 117.17.0.1:50012",
"node6 118.19.0.1:50012" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50012",
"domain2 172.16.0.2:50011",
"domain3 117.17.0.1:50012" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 172.16.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node6 118.19.0.1:50012" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50012",
"domain2 118.19.0.1:50012",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 117.17.0.1:50011",
"node3 192.168.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012" ]
3.3.2 简化配置¶
( 注:虽不用配置全部hosts,但配置也需保证整个网络topo图为连通图,即不存在网络分区的现象 )。
使用与 3.3.1 全量配置 一样的例子,网络配置不需要配全量直连节点的地址,即 node1 和 node3 虽然都没有配置彼此的IP,但他们最终可以通过其他节点互相发现对方,各个节点的网络配置详见下文。
当然, 网络配置也可以更加简化,只要保证两个节点之间有共同连接的节点,那么他们就不用配彼此的IP,可以通过这个共同节点发现对方,这样便可以简化对于 hosts 的配置。
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 62.219.0.1:50011",
"domain3 62.219.0.1:50011" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node4 10.10.0.2:50011",
"node5 117.17.0.1:50012",
"node6 118.19.0.1:50012" ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50011",
"domain2 172.16.0.1:50011",
"domain3 117.17.0.1:50011" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node5 172.16.0.2:50011",
"node6 118.19.0.1:50012" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50011",
"domain2 118.19.0.1:50011",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 62.219.0.1:50012",
"domain3 62.219.0.1:50012" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 117.17.0.1:50011",
"node3 118.19.0.1:50011",
"node5 117.17.0.1:50012",
"node6 118.19.0.1:50012" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 117.17.0.1:50012",
"domain2 172.16.0.2:50011",
"domain3 117.17.0.1:50012" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 172.16.0.1:50011",
"node3 118.19.0.1:50011",
"node4 62.219.0.1:50012",
"node6 118.19.0.1:50012" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 118.19.0.1:50012",
"domain2 118.19.0.1:50012",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 62.219.0.1:50011",
"node2 117.17.0.1:50011",
"node3 192.168.0.1:50011",
"node4 62.219.0.1:50012",
"node5 117.17.0.1:50012" ]
3.4 混合型网络¶
3.4.1 全量配置¶
domian1为机构A所在的网络域,domain2为机构B所在的网络域,domain3是机构C所在的网络域,机构A要求使用nginx做转发,机构B使用nginx做反向代理,机构C无代理服务器,如上图所示,机构A,B,C都部署了两个节点,并且机构A,B都架设了一台nginx做转发。
- 机构A:nginxA:62.219.0.1 (外) --> 10.20.0.1 (内) - node1和node4在nginxA上的映射分别为: - nginxA-->node1: 62.219.0.1:50011 (nginxA外) --> 10.20.0.1:50011 (nginx内) --> 10.10.0.1:50011 (内) - nginxA-->node4: 62.219.0.1:50012 (nginxA外) --> 10.20.0.1:50012 (nginx内) --> 10.10.0.2:50011 (内) - 而机构A想要访问机构B中的节点node2和node5,机构A又不想访问公网IP,则在机构A中的nginxA上对node2和node5做映射,即使用niginxA映射到node2和node5的公网IP - 使得机构A通过访问nginxA的内网就可以访问到机构B中的节点 - node2和node5在机构A中架设的nginxA上的映射为: - nginxA-->node2: 10.20.0.1:50013 (nginxA内) --> 117.17.0.1:50011 (domain2外) - nginxA-->node5: 10.20.0.1:50014 (nginxA内) --> 117.17.0.1:50012 (domain2外)
- 机构B:117.17.0.1 (外)
- nginxB–>node2 117.17.0.1:50011 (外) –> 172.16.0.1:50011 (内)
- nginxB–>node5 117.17.0.1:50012 (外) –> 172.16.0.2:50011 (内)
- 而机构B想要访问机构A中的节点node1和node4,直接访问对应的公网IP即可
- 机构C:118.19.0.1 (外)
- node3 118.19.0.1:50011 (外) –> 192.168.0.1:50011 (内)
- node6 118.19.0.1:50012 (外) –> 192.168.0.2:50011 (内)
在上面的例子中,结合第一章节和第二章节中介绍的映射规则,也即对于domain2中的节点,别的域要想连接domain2中的节点,就必须向它提供自己的外网地址。配置理应如下:
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 69.219.0.1:50011",
"domain3 69.219.0.1:50011" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 10.20.0.1:50013",
"node3 10.20.0.1:50015",
"node4 10.10.0.2:50011",
"node5 10.20.0.1:50014",
"node6 10.20.0.1:50016" ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50013",
"domain2 172.16.0.1:50011",
"domain3 117.17.0.1:50011" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node3 118.19.0.1:50011",
"node4 69.219.0.1:50012",
"node5 172.16.0.2:50011",
"node6 118.19.0.1:50012" ]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50015",
"domain2 118.19.0.1:50011",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node2 117.17.0.1:50011",
"node4 69.219.0.1:50012",
"node5 117.17.0.1:50012",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 69.219.0.1:50012",
"domain3 69.219.0.1:50012" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 10.20.0.1:50013",
"node3 10.20.0.1:50015",
"node5 10.20.0.1:50014",
"node6 10.20.0.1:50016" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50014",
"domain2 172.16.0.2:50011",
"domain3 117.17.0.1:50012" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node2 172.16.0.1:50011",
"node3 118.19.0.1:50011",
"node4 69.219.0.1:50012",
"node6 118.19.0.1:50012" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50016",
"domain2 118.19.0.1:50012",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node2 172.16.0.1:50011",
"node3 192.168.0.1:50011",
"node4 69.219.0.1:50012",
"node5 117.17.0.1:50012" ]
3.4.2 简化配置¶
( 注:虽不用配置全部hosts,但配置也需保证整个网络topo图为连通图,即不存在网络分区的现象 )。
同样的例子,即可和上述配置一样,也可如下配置:
node1:
# dynamic.toml
self = "node1"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.1:50011",
"domain2 69.219.0.1:50011",
"domain3 69.219.0.1:50011" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 10.20.0.1:50013",
"node4 10.10.0.2:50011",
"node5 10.20.0.1:50014" ]
node2:
# dynamic.toml
self = "node2"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50013",
"domain2 172.16.0.1:50011",
"domain3 117.17.0.1:50011" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node3 118.19.0.1:50011",
"node4 69.219.0.1:50012",
"node5 172.16.0.2:50011"]
node3:
# dynamic.toml
self = "node3"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50015",
"domain2 118.19.0.1:50011",
"domain3 192.168.0.1:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node2 117.17.0.1:50011",
"node4 69.219.0.1:50012",
"node6 192.168.0.2:50011" ]
node4:
# dynamic.toml
self = "node4"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.10.0.2:50011",
"domain2 69.219.0.1:50012",
"domain3 69.219.0.1:50012" ]
domain = "domain1"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 10.10.0.1:50011",
"node2 10.20.0.1:50013",
"node3 10.20.0.1:50015",
"node6 10.20.0.1:50016" ]
node5:
# dynamic.toml
self = "node5"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50014",
"domain2 172.16.0.2:50011",
"domain3 117.17.0.1:50012" ]
domain = "domain2"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node3 118.19.0.1:50011",
"node4 69.219.0.1:50012",
"node6 118.19.0.1:50012" ]
node6:
# dynamic.toml
self = "node6"
[p2p]
[p2p.ip.self]
addrs = [ "domain1 10.20.0.1:50016",
"domain2 118.19.0.1:50012",
"domain3 192.168.0.2:50011" ]
domain = "domain3"
# ns_dynamic.toml
[p2p]
[p2p.ip.remote]
hosts = [ "node1 69.219.0.1:50011",
"node2 172.16.0.1:50011",
"node3 192.168.0.1:50011",
"node4 69.219.0.1:50012",
"node5 117.17.0.1:50012" ]
node1和node3都没有配置彼此的IP,但他们最终可以通过其他节点互相发现对方。当然也可以更加简化,只要保证两个节点之间有共同连接的节点,那么他们就可以不用配彼此的IP,可以通过这个共同节点发现对方,这样便可以简化对于 p2p.ip.remote.hosts 的配置。
安全审计¶
功能概述¶
系统审计指对一个信息系统的运行状况进行检查与评价,以判断信息系统是否能够保证资产的安全、数据的完整以及有效率利用组织的资源并有效果地实现组织目标。
审计功能对区块链系统进行审计,对整个系统的运行过程进行数据采集记录,审计员可以根据这些审计记录进行分析以完成对区块链系统的审计。在趣链区块链平台中, 审计日志统一以审计事件的形式表示 ,审计事件主要为平台中各种的消息通信事件,例如外部的请求调用事件,各个模块之间的消息通信事件、账本数据变更事件、系统异常事件等。
审计后端¶
审计后端的功能是将平台产生的审计日志进行存储,目前平台支持的审计后端有三种类型:
- 文件 :文件后端直接将审计日志以json的格式写入日志文件;
- filelog :将审计日志写入filelog存储,支持存储加密;
- webhook :将审计日志发送到外部。
目前,webhook方式对两个常用的分析平台做了适配:
- ELK :将审计日志直接发送到elk(集群)的URL;
- GrayLog :将审计日志直接发送到graylog(集群)的URL。
审计级别¶
当前审计功能提供如下四种级别的审计:
- None :不记录审计日志;
- Metadata :只提供基础的事件信息,包括事件类型,时间戳,事件具体内容等;如果是外部请求事件,则还会包括ip,证书,请求url,响应码;
- ResponseBody :对于api外部请求(如客户端jsonRPC调用)增加请求响应,对于内部事件为事件结果(对于能同步获取结果的事件),比如数据同步的时间消耗;
- StorageObjec :最全记录方式,提供状态修改的变更。
用户可根据审计需求开启适当的审计级别。 注意:审计级别越高则输出的内容越多,对平台的性能影响也越大。
审计事件格式¶
审计事件包含字段如下:
{
"source":"node1", # 审计日志来源节点名称
"namespace":"global", # 产生审计日志的namespace
"audit_type_id":400, # 审计记录的类型ID,参照审计类型表
"tags":[
"xxx" # 审计记录的tag,可方便检索
],
"risk_level":0, # 审计日志等级
"user":{ # 审计日志发起者,外部请求会填充此字段
"ip":"127.0.0.1:50444","cert":"xxx"
},
"event_body":{ # 审计日志主题,包含审计级别request的主要内容
"value":{ # value字段为对象类型,body为额外的key,value类型
"replica_id":3,"view":2
},
"body":{
"nodeHash":"c82a71a88c58540c62fc119e78306e7fdbe114d9b840c47ab564767cb1c706e2"
}
},
"event_response":{ # 对应审计级别responsebody的主要内容
"body":{
"jsonrpc":"2.0","namespace":"global","id":1,"code":-32003,"message":"Invalid signature: tx hash 0x30693b679e8e5cb6fb61c3c4fed96616fb6aedcde37928d23de983dc2aec75e4"
}
}
}
审计日志等级¶
目前将审计日志的级别划分为三种,级别包括 危险(RISK)、警告(WARN)、普通(NORAMAL) ,分别对应 risk_level 字段的数字为 0、1、2 。
- RISK:指系统出现的难以恢复异常或严重错误,会影响系统的正常运行,如写块错误;
- WARN:指系统出现的可恢复异常,不会影响系统的正常运行,如交易执行失败,一些异常行为如viewchange等;
- NORAMAL:一般系统事件,如正常请求。
审计日志标签¶
日志的标签用于将审计事件归类,以便于在搜索审计事件的时候能快速定位以及区分审计事件,为 Tags 字段。
当前日志标签的包含有如下:
ReceivedMessage = "ReceivedMessage" // 代表(从其他节点或模块)接收到的消息
SentMessage = "SentMessage" // 代表发出的消息
每个审计事件可以有多个标签。
安装及初始化¶
配置说明¶
安全审计功能是可选的(默认不开启),即可以通过节点开关配置来决定节点是否启用审计功能,相关配置位于 configuration/global/system.toml
中。
平台默认不开启安全审计功能, 如果您想启用该功能,请将下述配置复制到system.toml中,并做好相应配置 。
[audit] backend = "graylog" #审计后端类型,可选项包括"file", "filelog" ,"graylog"和"elk",实际不建议选用前两者 level = "none" #审计的级别,级别越高输出的内容越详细,从低到高的级别依次是:none, metadata, responsebody, storageobject,其中如果将审计级别配置为none,则代表不开启审计,平台的审计服务将不会启动 [audit.conn.pool] urls = ["172.16.5.5:12202"] #外部URL 可配置一个或者多个,根据实际使用的审计后端配置,格式为"IP+端口号"
Filelog说明¶
审计会使用到filelog的场景有两种:
- 将filelog作为审计后端 :将filelog作为审计后端时,审计日志会存储在节点的指定filelog数据库中,支持filelog加密存储。 实际场景中,不推荐使用file或者filelog作为审计后端。
- 使用GrayLog或者ELK作为审计后端 :当使用Graylog或者ELK作为审计后端时,平台会使用filelog作为审计消息发送失败时的临时存储。当配置的所有url的网络连接暂时不可用时,平台会将产生的审计日志临时写入filelog,当后续与审计后端的网络连接恢复成功,filelog中的审计日志会自动恢复到配置的审计后端。
Graylog安装及使用¶
- 使用Graylog作为审计后端需将配置项 backend 设置为 graylog 。 注意:在平台向外部URL发送失败时会暂时使用filelog存储,因此使用GrayLog作为后端仍然需要配置filelog。
- 使用Glaylog作为后端需要事先搭建好graylog平台,详情请参考:Graylog审计使用手册。
- 搭建好之后将graylog的URL配置在[audit.conn.pool]配置之下的 urls 配置项之中即可。
数据监控¶
功能概述¶
Metrics数据监控平台提供一站式的数据可视化监控服务,实现数据的自动化采集及展示,帮助用户轻松了解底层平台运行情况,及时识别并处理异常。
数据监控平台由数据生成层Hyperchain、数据监控层Prometheus以及数据展示层Grafana三部组成。Hyperchain负责生成需要监控的数据并推送到Prometheus服务器中;Prometheus负责存储Hyperchian推送出来的数据,并对外提供读取接口;Grafana:负责从Prometheus服务器中读取数据,并在web上进行展示。
平台现已支持100+业务及系统层面的监控指标,指标开关灵活可配,具体可参考附录中的数据监控指标清单。
安装及初始化¶
为了能够完整的使用并展示数据监控的数据,除了需要应用层(即趣链区块链平台)适配并暴露一些监控数据之外,还需要下载并使用额外的两个组件:Prometheus(用于记录监控数据)以及Grafana(用于展示监控数据)。
安装Prometheus¶
Prometheus
Prometheus是一种时序数据库(TSDB, time-series database),用于记录平台推送出去的监控数据。
详细介绍请参考:https://prometheus.io/docs/introduction/overview/
安装下载请参考:https://prometheus.io/download/(建议安装最新版本)
Prometheus安装完成之后,需要配置好需要监听的区块链节点的地址与端口号,其配置文件默认为: prometheus.yml
node_exporter
如果用户希望监控系统资源的使用情况(例如CPU、磁盘、网络等),我们推荐使用Prometheus官方的工具node_exporter进行监控。
相关配置¶
数据监控功能是可选的,即可以通过节点开关配置来决定节点是否启用数据监控,相关配置位于configuration/debug.toml中。
平台默认不开启数据监控功能, 如果您想启用该功能,请将下述配置复制到debug.toml中,并做好相应配置。
metrics.enable
:是否开启监控服务(true, false);metrics.enable_expensive
:是否开启资源消耗较大的监控服务,实际不建议开启,需谨慎选择;metrics.port
:监控服务端口。
[metrics]
enable = true
enable_expensive = false
# visit "http://127.0.0.1:'port'" to get metrics data
port = 9001
数据指标¶
指标说明¶
数据指标按用途不同分为外部指标和内部指标;按对平台性能影响程度又分为normal级别与expensive级别。其中,normal级别的指标不影响平台的运行, expensive级别的指标打开后会降低平台整体的性能,因此需要用户谨慎选择开启。
有关于系统资源的使用情况,建议使用Prometheus官方推荐的node_exporter工具进行监控。
系统相关指标¶
所有指标以”flato_namespace”(“flato”为趣链区块链平台新版英文简称)为前缀,例如在global这namespace下所有下述指标都要加上前缀”flato_global”(“flato”为趣链区块链平台新版英文简称)。
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
execMgr_commit_txs | counter | normal | 此次启动后新增的交易数量 | 可以通过rate/irate命令计算出交易TPS |
execMgr_total_blocks | gauge | normal | 当前链高度 | 可以通过rate/irate命令计算出区块BPS |
rbft_batch_to_commit_duration | histogram | normal | 共识区块处理时间(rbft从打包到提交的时间) | 由于主从节点间有时差,因此以主节点的数据为主 |
execMgr_validate_tx_time | histogram | expensive | 单笔交易执行时间 | 交易级别的平均处理时间 |
execMgr_commit_time | histogram | normal | 写入区块处理时间(写块时间,包括区块组成、写入等等) | 区块级别的平均写入时间 |
共识相关指标¶
所有指标以”flato_namespace”(“flato”为趣链区块链平台新版英文简称)为前缀,例如在global这个Namespace下所有下述指标都要加上前缀”flato_global”(“flato”为趣链区块链平台新版英文简称)。
RBFT¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
rbft_ID | gauge | normal | rbft节点ID | |
rbft_version | gauge | normal | rbft共识协议版本号 | |
rbft_epoch | gauge | normal | rbft当前共识所处的epoch | |
rbft_view | gauge | normal | rbft当前共识所处的view | |
rbft_cluster_size | gauge | normal | rbft当前共识节点的个数 | |
rbft_quorum_size | gauge | normal | rbft当前quorum大小 | |
rbft_status_normal | gauge | normal | rbft是否处于normal状态on:1;off:0 | |
rbft_status_conf_change | gauge | normal | rbft是否处于配置区块状态on:2;off:0 | |
rbft_status_viewchange | gauge | normal | rbft是否处于vc状态on:3;off:0 | |
rbft_status_recovery | gauge | normal | rbft是否处于recovery状态on:4;off:0 | |
rbft_status_state_update | gauge | normal | rbft是否处于state update状态on:5;off:0 | |
rbft_status_pool_full | gauge | normal | rbft是否处于pool full状态on:6;off:0 | |
rbft_status_pending | gauge | normal | rbft是否处于pending状态on:7;off:0 | |
rbft_committed_block_number | counter | normal | rbft提交的区块数 | |
rbft_committed_config_block_number | counter | normal | rbft提交的配置区块数 | |
rbft_committed_empty_block_number | counter | normal | rbft提交的空块数(由vc导致的) | |
rbft_committed_txs | counter | normal | rbft提交的交易个数 | |
rbft_txs_per_block | histogram | normal | rbft提交的每个区块的交易个数 | |
rbft_batch_persist_duration | histogram | normal | rbft从打包到提交的时间 | |
rbft_batche_number | gauge | normal | rbft当前缓存的batch个数 | |
rbft_outstanding_batche_number | gauge | normal | rbft当前正在共识的batch个数,无负载时=0 | |
rbft_state_update_times | counter | normal | rbft触发的StateUpdate的次数 | |
rbft_cache_batch_number | gauge | normal | rbft当前缓存的已打包但是不能共识的batch个数,仅主节点有该值无负载时=0 | |
rbft_fetch_missing_txs_times | counter | normal | rbft向主节点索取缺失交易的次数 | |
rbft_fetch_request_batch_times | counter | normal | rbft向其他节点索取batch的次数 | |
txset_incoming_txs | counter | normal | txset模块接收到的来自API的交易(这里包括了NVP转发过来的) | |
txset_pending_txs | counter | normal | txset模块等待共识的交易个数 | |
rbft_incoming_local_tx_sets | counter | normal | rbft接收到的本地生成的txSet的个数 | |
rbft_incoming_remote_tx_sets | counter | normal | rbft接收到的其他VP节点转发过来的txSet的个数 | |
rbft_incoming_local_txs | counter | normal | rbft接收到的来自API的交易(这里包括了NVP转发过来的) | |
rbft_incoming_remote_txs | counter | normal | rbft接收到的其他VP节点转发过来的交易 | |
rbft_rejected_local_txs | counter | normal | rbft拒收的来自API的交易 | |
rbft_rejected_remote_txs | counter | normal | rbft拒收的其他VP节点转发过来的交易 |
注意:
- 由于交易进入consensus模块之后需要经由txSet模块打包成一个set才能进入rbft模块,因此有txset_incoming_txs >= rbft_incoming_local_txs
txpool¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
txpool_incoming_txs | counter | normal | txpool接收到的交易总数 | |
txpool_duplicate_txs | counter | normal | txpool中检测到的重复交易 | |
txpool_nonBatched_txs | gauge | normal | 当前交易池中未打包的交易个数,无负载时=0 | |
txpool_batched_txs | gauge | normal | 当前交易池中已打包的交易个数 | |
txpool_batches | gauge | normal | 当前交易池中的batch个数,无负载时<20(共识缓存最多20个batch)有负载时从节点<50,主节点理论上无上限 |
注意:
- txpool接收的交易是经由rbft模块传递下来的,因此有
txpool_incoming_txs = rbft_incoming_local_txs + rbft_incoming_remote_txs - rbft_reject_txs
存储相关指标¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
db_accountdb_batchCommitTime | histogram | normal | 写入accountdb的返回时间 | |
db_statedb_batchCommitTime | histogram | normal | 写入statedb的返回时间 | |
db_metadb_batchCommitTime | histogram | normal | 写入metadb的返回时间 | |
db_chaindb_batchCommitTime | histogram | normal | 写入chaindb的返回时间 | |
db_blockdb_batchCommitTime | histogram | normal | 写入blockdb的返回时间 | |
db_journaldb_batchCommitTime | histogram | normal | 写入journaldb的返回时间 | |
db_receiptdb_batchCommitTime | histogram | normal | 写入receiptdb的返回时间 | |
db_indexdb_batchCommitTime | histogram | normal | 写入indexdb的返回时间 | |
db_dbtype_multicache_memSize | gauge | normal | 多级缓存内存占用大小 | |
db_dbtype_multicache_persist_time | histogram | normal | 多级缓存持久化一个区块数据至底层数据库所需时间 | |
db_dbtype_multicache_walPersist_time | histogram | normal | 多级缓存写一个seqNo对应的wal的耗时 | |
db_dbtype_multicache_cache_get | gauge | normal | multicache尝试从自身的读缓存中读取数据的次数 | |
db_dbtype_multicache_cache_set | gauge | normal | multicache向自身的读缓存中插入数据的次数 | |
db_dbtype_multicache_cache_hit | gauge | normal | multicache缓存命中的次数 | |
db_dbtype_leveldb_compaction_occurrence | gauge | normal | 底层leveldb compaction的次数 | |
db_dbtype_leveldb_size | gauge | normal | 底层leveldb的数据量大小 | |
db_dbtype_filelog_fdNumber | gauge | normal | filelog中处于open状态的句柄数 | |
db_dbtype_filelog_readTime | histogram | expensive | filelog读取一个元素的耗时 | |
db_dbtype_filelog_fsyncTime | histogram | normal | filelog在一个log文件写完后,做一次fsync的耗时 |
执行相关指标¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
bloomFilter_memSize | gauge | normal | 布隆过滤器占用的内存大小 | |
bloomFilter_lookCounter | counter | normal | 布隆过滤器查询次数 | 有「source」标签说明调用查询的模块,分为“namespace表示共识去重”、“executor表示执行层去重”、“API表示接口层去重” |
bloomFilter_lookExistCounter | counter | normal | 布隆过滤器中,查询到存在的交易数量,即会穿透布隆过滤器进行db查询的次数 | 有「source」标签说明调用查询的模块,分为“namespace表示共识去重”、“executor表示执行层去重”、“API表示接口层去重” |
execMgr_validate_bloom_readValidDBTime | histogram | normal | 布隆过滤器发现交易可能重复后,查询合法交易的时间 | |
execMgr_validate_bloom_readInvalidDBTime | histogram | normal | 布隆过滤器发现交易可能重复后,查询非法交易的时间(会先查询合法交易,再查询非法交易,查询次数可能比合法交易少一些) | |
execMgr_validate_tx_time | histogram | expensive | 单笔交易执行时间 | |
execMgr_total_blocks | gauge | normal | 当前链高度 | |
execMgr_total_txs | gauge | normal | 区块链所有交易总量 | |
execMgr_online_txs | gauge | normal | 区块链归档点后所有交易总量 | |
execMgr_commit_txs | counter | normal | 此次启动后新增的交易数量 | |
execMgr_commit_time | histogram | normal | 写块时间,包括区块组成、写入等等 | |
execMgr_commit_writeDB_time | histogram | normal | 区块batch写入数据库的时间 |
网络相关指标¶
P2P¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
grpc_stream_request_send_total | counter | normal | grpc发送的流请求数目 | |
grpc_stream_request_received_total | counter | normal | grpc收到的流请求数目 | |
grpc_stream_request_completed_total | counter | normal | grpc完成的流请求的数目 | |
grpc_stream_message_send_total | counter | normal | grpc发送的流消息的数目 | |
grpc_stream_message_recv_total | counter | normal | grpc收到的流消息的数目 | |
grpc_conn_opened_total | counter | normal | grpc连接打开数目。打开数目减去关闭数目便是目前活跃的连接数。 | |
grpc_conn_closed_total | counter | normal | grpc连接关闭数目。打开数目减去关闭数目便是目前活跃的连接数。 | |
grpc_stream_opened_total | counter | normal | grpc流打开数目。打开数目减去关闭数目便是目前活跃的流数。 | |
grpc_stream_closed_total | counter | normal | grpc流关闭数目。打开数目减去关闭数目便是目前活跃的流数。 | |
msg_dropped_count_total | counter | normal | 某个节点在某个message channel弃的消息的数目 | |
grpc_stream_message_send_time_microseconds | histogram | expensive | grpc发送一条消息所需要的时间 | |
logic_conn_opened_count_total | counter | normal | 逻辑连接打开数目。打开数目减去关闭数目便是目前活跃的连接数。 | |
logic_conn_closed_count_total | counter | normal | 逻辑连接关闭数目。打开数目减去关闭数目便是目前活跃的连接数。 | |
grpc_network_receive_bytes_total | counter | normal | grpc网络接收到的消息大小总量 | |
grpc_network_send_bytes_total | counter | normal | grpc网络发送的消息大小总量 |
消息分发¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
dispatcher_%s_writeMsg_ch_size_usage | gauge | normal | 消息分发器发送消息通道对应节点的模块消息数量 | |
dispatcher_%s_writeMsg_ch_mem_usage | gauge | normal | 消息分发器发送消息通道对应节点的模块消息占用内存的大小 | |
dispatcher_%s_readMsg_ch_size_usage | gauge | normal | 消息分发器接收消息通道对应节点的模块消息数量 | |
dispatcher_%s_readMsg_ch_mem_usage | gauge | normal | 消息分发器接收消息通道对应节点的模块消息占用内存的大小 |
API相关指标¶
名称 | 类型 | 级别 | 描述 |
---|---|---|---|
jsonrpc_request_received_total | counter | normal | 接收的jsonrpc请求个数 |
jsonrpc_new_tx_request_success_total | counter | normal | 成功处理的发送交易相关请求的个数 |
jsonrpc_new_tx_request_error_total | counter | normal | 发送交易相关请求处理失败,不同失败原因的请求个数 |
jsonrpc_received_request_bytes_total | counter | normal | 请求总流量统计单位byte |
jsonrpc_sent_response_bytes_total | counter | normal | 响应总流量统计单位byte |
jsonrpc_new_tx_request_consensus_abnormal_error_total | counter | normal | 由于共识状态异常原因导致请求失败的个数 |
密码相关指标¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
verify_tube_signature_in_total | counter | normal | 输入验签管道的总签名数 | |
verify_tube_signature_out_total | counter | normal | 输出验签管道的总签名数 | |
vt_liner_duration_cycle | Histogram | normal | 完成批量验证两段子操作的时间 | |
vt_logic_duration_cycle | Histogram | normal | 验签中每段操作的时间 | |
vt_hash_duration_cycle | Histogram | normal | 完成哈希的时间 | |
verify_tube_batch_size | Gauge | normal | 每批次的签名数量 | |
crypto_verify_channel_length | Gauge | normal | 每个阶段的缓冲池大小 |
Go相关指标¶
名称 | 类型 | 级别 | 描述 | 备注 |
---|---|---|---|---|
go_goroutines | gauge | normal | ||
go_threads | gauge | normal | ||
go_gc_duration_seconds | summary | normal | ||
go_info | gauge | normal | ||
alloc_bytes | gauge | normal | ||
alloc_bytes_total | counter | normal | ||
sys_bytes | gauge | normal | ||
lookups_total | counter | normal | ||
mallocs_total | counter | normal | ||
frees_total | counter | normal | ||
heap_alloc_bytes | gauge | normal | ||
heap_sys_bytes | gauge | normal | ||
heap_idle_bytes | gauge | normal | ||
heap_inuse_bytes | gauge | normal | ||
heap_released_bytes | gauge | normal | ||
heap_objects | gauge | normal | ||
stack_inuse_bytes | gauge | normal | ||
stack_sys_bytes | gauge | normal | ||
mspan_inuse_bytes | gauge | normal | ||
mspan_sys_bytes | gauge | normal | ||
mcache_inuse_bytes | gauge | normal | ||
mcache_sys_bytes | gauge | normal | ||
buck_hash_sys_bytes | gauge | normal | ||
gc_sys_bytes | gauge | normal | ||
other_sys_bytes | gauge | normal | ||
next_gc_bytes | gauge | normal | ||
last_gc_time_seconds | gauge | normal | ||
gc_cpu_fraction | gauge | normal |
趣链区块链浏览器¶
1. 功能概述¶
趣链区块链浏览器是管理人员可视化管理hyperchain相关链上业务的可视化工具,支持查看区块链上信息、节点/合约实例可视化管理等核心操作。
本使用手册将从启动趣链区块链浏览器、查看区块链浏览器、节点管理、合约实例管理等方面展开介绍。
2. 使用说明¶
2.1 快速启动¶
2.1.1 配置说明¶
使用区块链浏览器功能需要在hyperchain启动之前进行相关配置。主要配置项如下:
- system.toml
[baas] enable = true
在system.toml配置文件下,将bass.enable配置设置为true
- dynamic.toml
[port] ...... baas = 12001
在dynamic.toml配置文件下,配置baas的访问端口,默认为12001。即在hyperchain启动成功后,可以在浏览器访问 ip:12001 来打开区块链浏览器。
- 证书配置
趣链区块链浏览器通过sdk访问节点,故需要在节点的baas/conf目录下配置相关证书。
需要配置的证书及目录结构如下:
conf
├── certs
│ ├── sdkcert.cert
│ ├── sdkcert.priv
│ ├── unique.priv
│ └── unique.pub
│ ├── tls
│ │ ├── tls_peer.cert
│ │ ├── tls_peer.priv
│ │ └── tlsca.ca
注意,证书的配置取决于链上相关证书是否开启。
2.1.2 启动趣链区块链浏览器¶
您在部署hyperchain后,可获取目标节点的IP和端口信息(具体方式参见hyperchain部署手册),在相同域下的电脑设备内打开网页浏览器,在地址栏中输入对应的IP和端口,即可进入对应节点的趣链区块链浏览器。
示例:
2.2 区块链浏览器¶
区块链浏览器页面内,支持查看 实时总览数据 和 网络拓扑图 ,更进一步地,您可以从“区块”、“交易”两个维度进一步查看区块链数据。
2.2.1 查看区块¶
步骤一: 在搜索框中输入区块高度或区块哈希,或者点击【区块】tab下的【区块高度】,进入区块详情页。
步骤二: 在区块详情页中,可以查看区块详情和该区块下进行的交易,还可以继续搜索想要查看的区块和交易。其中,父区块哈希和交易哈希可以点击跳转至对应详情页,区块哈希、父区块哈希、Merkle树的根哈希可复制。
2.2.2 查看交易¶
步骤一: 在搜索框中输入交易哈希,或者点击【交易】tab下的【交易哈希】,进入交易详情页。
步骤二: 在交易详情页中,可以查看交易详情,还可以继续搜索想要查看的区块和交易。其中,区块哈希可以点击跳转至对应详情页,交易哈希、区块哈希、发起地址、接收地址可复制。
2.3 节点管理-节点信息¶
趣链区块链浏览器提供【节点信息】和【节点配置】两个可视化的节点管理页面。下文将介绍【节点信息】的使用说明,【节点配置】请参考2.4节内容。
2.3.2 节点连接状态管理¶
- 建立节点连接
步骤一: 点击【节点管理】下【节点信息】,进入节点信息页面。
步骤二: 在节点信息页面中,可以看到节点关联列表,点击【建立节点连接】。
步骤三: 在弹窗中填写待与当前节点关联的新节点参数,点击【确定】开始建立连接。
参数说明如下:
- 节点名称 :新节点的节点名称,不可与当前节点关联列表的节点名称相同;
- 节点IP地址 :新节点的端口信息,系统检验可连接后,即可建立新节点与当前节点的网络连接。
步骤四: 连接成功后,可在节点关联列表中查看。
- 重新连接节点
步骤一: 点击【节点管理】下【节点信息】,进入节点信息页面。
步骤二: 在节点信息页面中,可以看到节点关联列表,选择与当前节点建立连接的其他节点,点击【…】操作列的【重新连接】。
步骤三: 在弹窗内,点击“确定”,即可重新建立所选节点与当前节点之间的连接。
- 断开节点连接
步骤一: 点击【节点管理】下【节点信息】,进入节点信息页面。
步骤二: 在节点信息页面中,可以看到节点关联列表,选择与当前节点建立连接的其他节点,点击【…】操作列的【断开连接】。
步骤三: 在弹窗内,点击“确定”,即可断开所选节点与当前节点之间的连接,断开连接后该所选的节点将会从关联节点列表中消失,请谨慎操作。
- 节点链路检测
步骤一: 点击【节点管理】下【节点信息】,进入节点信息页面。
步骤二: 在节点信息页面中,可以看到节点关联列表,选择与当前节点建立连接的其他节点,点击【…】操作列的【链路检测】。
步骤三: 在弹窗内,您可以查看到链路检测结果。
2.4 节点管理-节点配置¶
2.4.1 节点网络配置¶
节点网络配置是针对节点Domain配置,支持新增Domain和修改Domain操作。
- 新增Domain
步骤一: 点击【节点配置】中【网络配置】,进入网络配置页面。
步骤二: 点击【新增Domain】,在新增行中,填写Domain参数,点击【保存】。
步骤三: 保存后,点击【更新网络配置】,网络配置更新成功。
- 修改Domain
步骤一: 点击【节点配置】中【网络配置】tab,进入网络配置页面。
步骤二: 选择列表中某个Domain,点击【修改】。在修改框中,重新填写Domain参数,点击【保存】。
步骤三: 保存后,点击【更新网络配置】,网络配置更新成功。
2.4.2 节点日志等级配置¶
- 查看当前日志等级
步骤一: 点击【节点配置】中,【日志等级】tab,进入日志等级配置页面。
步骤二: 在选择框中,选择系统级别或Namespace级别的日志等级,查看不同模块下日志等级配置。
- 修改日志等级配置
步骤一: 点击【节点配置】中,【日志等级】tab,进入日志等级配置页面。
步骤二: 点击【修改日志等级】,在弹窗中填写修改参数,点击【确定】,可修改日志等级。
每个分区模块的日志级别,日志级别由高到低为CRITICAL/ ERROR/WARNING/NOTICE INFO,级别越低日志内容越详细,对系统性能影响越大。
2.4.3 节点命名空间配置¶
节点命名空间配置提供可视化的 命名空间启动或停止 操作。
步骤一: 点击【节点配置】中,【命名空间】tab,进入命名空间配置页面。
步骤二: 选择某个命名空间,点击【启动】/【停止】按钮。
步骤三: 该节点会启动/停止在命名空间中的运行。
2.4.4 节点全量配置¶
步骤一: 点击【节点配置】中,【全量配置】tab,进入全量配置界面。
步骤二: 在左侧文件目录下,选择文件,进入编辑框修改配置。
步骤三: 修改完成后,点击【更新高级配置】,链将重启并完成配置更新。
2.5 合约实例¶
本模块提供对合约实例的管理操作。目前,支持Solidity合约实例和Java合约实例管理。
2.5.1 部署合约实例¶
前置条件:
- 已编写完成的合约文件,并有编译文件;
- 准备私钥文件(用于当前账户部署的合约实例的操作授权);
- 当前节点连接正常;
- 联盟链正常运行。
操作步骤:
- 点击【合约实例】tab,进入合约实例页面;
- 点击【上传私钥】,上传合约部署、升级等操作时需要使用的私钥,点击【确定】;
- 私钥上传完成后,点击【部署合约实例】,在弹窗中填写部署参数,点击【确定】,开始部署合约;
参数说明:
- 合约实例类型: 支持Solidity和Java两种合约类型
- 可选入参: 部署合约实例的初始化参数,如没有可不填
- 合约文件: 对于Solidity合约需分别上传编译后的abi文件和bin文件;对于Java合约需分别上传编译后的abi文件和jar文件
- 部署成功后,合约实例列表中将自动更新该合约实例,其状态为【运行中】。
2.5.2 查看合约实例¶
前置条件 :合约已完成部署。
操作步骤 :
- 点击【合约实例】tab,进入合约实例页面;
- 选择目标合约实例,点击【查看】,可进入合约实例详情页;
- 如下所示,在详情页可查看合约实例地址、合约类型、部署地址、合约调用记录等。
2.5.3 调用合约实例¶
前置条件 :
- 在合约实例列表中,已有合约实例,且状态为【运行中】;
- 联盟链处于正常运行状态。
操作步骤 :
- 点击【合约实例】tab,进入合约实例页面;
- 选择某个合约实例,点击【调用】;
- 弹窗中,填写调用参数,点击【确定】。
Solidity合约调用参数说明
参数名 | 填写说明 |
---|---|
函数名 | 该合约实例的调用函数名 |
可选入参 | 该合约的调用参数,如没有可不填 |
合约文件 | 该合约编译后的abi文件 |
Solidity合约调用示例
Java 合约调用参数说明
参数名 | 填写说明 |
---|---|
BeanName | 该合约实例的调用方法名 |
可选入参 | 该合约的调用参数,如没有可不填 |
合约文件 | 该合约编译后的abi文件 |
Java合约调用示例
- 调用成功,会跳出调用结果的详情弹窗,可查看调用信息,点击蓝色字符串【交易哈希】,可进行跳转至对应的区块链浏览器交易详情页,或者您也可在合约详情中查看调用记录。
2.5.4 升级合约实例¶
前置条件:
- 在合约实例列表中,已有合约实例,且状态为【运行中】;
- 链处于正常运行状态。
操作步骤 :
- 点击【合约实例】tab,进入合约实例页面;
- 选择目标合约实例,点击【升级】;
- 弹窗中填写升级参数,点击【确定】,合约开始升级;
Solidity合约升级参数说明
参数名 | 填写说明 |
---|---|
可选入参 | 该合约的调用参数,如没有可不填 |
合约文件 | 分别上传该合约编译后的abi文件与bin文件 |
Java 合约升级参数说明
参数名 | 填写说明 |
---|---|
可选入参 | 该合约的调用参数,如没有可不填 |
合约文件 | 分别上传该合约编译后的abi文件与bin文件 |
- 升级成功后,列表将自动刷新,且在合约实例详情中将新增相应的升级记录信息。
2.5.5 冻结合约实例¶
由于区块链网络中,数据一旦上链就不可篡改,那么对于合约也一样,一旦部署上链就不可以被删除。但是,会出现用户不需要使用合约或合约出现问题需要暂停使用的情况,针对这一问题,平台提供了合约冻结功能,可以暂停合约的使用。
前置条件 :
- 在合约实例列表中,已有合约实例,且状态为【运行中】;
- 链处于正常运行状态。
操作步骤 :
- 点击【合约实例】tab,进入合约实例页面;
- 选择某个合约实例,点击【冻结】;
- 冻结成功后,列表将自动刷新,合约状态更新为【已冻结】。
2.5.6 解冻合约实例¶
前置条件 :
- 在合约实例列表中,已有合约实例,且状态为【已冻结】;
- 联盟链处于正常运行状态。
操作步骤 :
- 点击【合约实例】tab,进入合约实例页面;
- 选择某个【已冻结】的合约实例,点击【解冻】;
- 在弹窗中点击确定;
- 解冻成功后,列表将自动更新,合约状态更新为【运行中】。
3. 注意事项¶
- 区块链浏览器为节点级别功能,目前监控功能仅针对global分区,后续版本将完善
- cvp节点暂不支持区块链浏览器开启
- 区块链浏览器相关日志保存在节点目录下的baas/logs
- 当前版本暂不支持部署java合约
(用户)智能合约使用手册¶
HVM使用手册¶
HVM是趣链底层区块链平台自主研发的执行Java智能合约的执行引擎
[主文件]HVM使用手册¶
HVM跨合约功能使用手册¶
HVM合约内置方法和工具方法使用手册¶
HVM ABI插件使用手册¶
HVM 合约账本数据结构使用手册¶
HVM JDK支持列表¶
HVM 合约demo文件¶
KVSQL使用手册¶
本文档用于介绍KVSQL功能的使用方法。面向的用户为应用开发人员,为其在将现有应用进行”一键链改”或者直接基于一键链改功能进行新应用的开发时作为指导和参考。
KVSQL使用手册: Hyperchain KVSQL
一键链改Demo说明¶
Expression使用手册¶
数据类型Type使用手册¶
Operators&Funtions使用手册¶
Simulate使用手册¶
提供 Simulate交易 的使用方式。
应用案例¶
金融领域¶
趣链科技把握金融科技风口,建设以区块链为核心的新型基础设施,致力于构建立体化产业金融生态圈与服务平台,提供融资服务、支付结算、监管支撑等多领域解决方案,为经济发展注入新动能。
区块链将在 “信任强化,监管治理” 、 “信息共享,打破壁垒” 、 “产品创新,业务重塑” 三个方面赋能金融领域。
接下来,以应收账款为典型应用场景介绍我们的落地方案。
应收账款¶
通过区块链联合多方构建多中心的ABS管理平台, 将企业应收账款转化为标准化数字资产凭证,在平台中实现应收账款的灵活流转、拆分和融资。实现基于核心企业信用的应收账款凭证在供应链上的多级流转,支持拆分、转让和融资,有效惠及除一级供应商外的多级供应商,实现了核心企业信用的多级传导,通过引入外部金融机构,为应收账款提供低成本融资利率。
应收账款存在以下痛点问题:
- 原始贸易数据伪造 。物流信息、仓单、应收账款伪造,业务风险和金融风险高。
- 四流难合一 。企业间系统不互通,线下、纸质化传递,企业信息孤岛,风控难度大。
- 核心企业信用难传递 。传递只在一级,无法传递其他供应商,生态服务范围受限。
本方案具有以下价值:
- 降低整个产业的融资资本 。通过区块链打造的多级供应商融资体系,促进全链条信息共享,实现供应链金融可视化,能够依托核心企业的信用,降低中小企业的融资成本,提高资金流转的效率,间接降低整体的生产成本,让企业的产品更有竞争优势。
- 挖掘优质资源 。区块链使业务场景的真实性得到保障,同时依托核心企业的付款,使得整个产业链条上的企业都能融资,且是安全的融资,让金融机构能够更高效、便捷、稳健地将服务贯穿整个产业链,因此,区块链是优质资产的“挖掘机”。
- 穿透式监管 。由于区块链分布式账本技术具有不可篡改、可追溯的特性,监管部门的穿透式监管更容易实现,更多的金融机构可以安心服务实体经济,确保资金流向实体经济。同时,区块链的数据共享方式,可以防止重复质押和空单质押,推动了供应链金融健康稳定发展。
落地方案架构如下:
更多关于金融领域的应用请参考 https://www.hyperchain.cn/industry/finance
民生领域¶
趣链科技借助区块链技术特性构建民生领域协同平台,提升业务效率,保障数据隐私性、安全性、可靠性基础上的数据共享,推动区块链技术在民生领域更广泛、更深入地应用,实实在在地增进人民群众的民生福祉。
区块链将在 “构建民生协同平台,提高协作效率” 、 “数据信息安全可靠,建设可信体系” 、 “推动民生领域广泛应用,促进民生改善” 三个方面赋能民生领域。
接下来,以疫苗溯源为典型应用场景介绍我们的落地方案。
疫苗溯源¶
以区块链+疫苗IoT技术为基础的疫苗流转监管体系,可视化完成问题疫苗一键锁定、一键回收,帮助快速锁定批次和大箱的监管码。对相关数据的全生命周期实现确权管理。数据从何而来、由谁进行,都会有精准记录,并且不可篡改,实现疫苗信息化数据的互联互通。
疫苗溯源存在以下痛点问题:
- 疫苗信息不可信 。假疫苗现象在医疗领域诟病已久,疫苗流转全流程信息不透明,各机构物流渠道流通不公开,疫苗信息真实性无法保证。
- 信息跟踪不及时 。疫苗生产、记录、流转等情况经过多方,数据追踪效果差,传统模式多为线下协同更是难以追溯可信信息。
- 医疗产品监管难 。医疗信息化产品准入标准缺失,监管制度模糊。医疗机构、药企、医疗器械企业等都有自己的管理系统,协同效率低,监管难度大。
本方案具有以下价值:
- 数据真实可信 。基于区块链技术实现疫苗从生产到接种全流程信息的真实、可靠、不可篡改,解决长期困扰的假疫苗问题。
- 信息跟踪追溯 。利用区块链技术可以记录疫苗渠道流通情况、物流信息,货物运输中断或丢失,可快速跟踪追溯进行处理。
- 加强质量监管 。智能合约自动执行,加强质量监管,杜绝质检不合格、流通存储不规范疫苗继续流通;跨机构间的交易与管理全程可追溯便于监管。
落地方案架构如下:
更多关于民生领域的应用请参考 https://www.hyperchain.cn/industry/livelihood
政务领域¶
趣链科技深入探索政务领域,基于国产自主可控的区块链底层技术,为政务提供多方协作、真实可信、数据资产化的基础设施,结合业务形成成熟、有价值的解决方案。目前已覆盖全国多个城市,实现跨部门跨机构的可信数据共享以及高效业务协作,提高政务服务效率,助力政务数字化改革。
区块链将在 助理数字政务 、 优化便民企业 、 推动信息建设 三个方面赋能政务领域。
接下来,以政务数据治理为典型应用场景介绍我们的落地方案。
政务数据治理¶
基于区块链打造的政务数据治理平台,包含部署在市级的“数据协作链”、以及部署在区级的“应用管理链”两层区块链架构,数据协作链的主要价值在于确定数据的“权力”与“责任”,应用管理链的主要价值在于确定数据的“利用”流程,两层架构并行最终可以确认数据的“责权利”,并以此为依据进行后续的数据治理工作。通过建立一整套合理健全的数据共享机制来保证各委办局的数据安全,以及可以在高位调度的基础下,形成一套透明可追溯的数据共享工作奖惩机制。
政务数据治理存在以下痛点问题:
- 政府部门共享信息过程复杂 。存在数据隐私、数据安全问题,涉及多部门协调沟通,审批流程冗长。
- 数据权责划分不清 。目录变更随意、共享随意,存在数据缺位、越位的问题。
- 共享时效性弱 。数据和目录“两张皮”,目录数据不全、不准、用管不同步、更新不实时。
本方案具有以下价值:
- 全区数据高效汇集,状态可见、可用、可查 。取代效率低下且准确率低的线下收集方式,通过各个委办局自行在平台上数据建立目录并挂载数据,有效降低了人员投入成本,增加准确性,提高整体工作效率。
- 数据使用全流程可追溯,明确了数据“责权利” 。通过数据协作链与应用管理链的两层架构,使得数据的来源、流动、去向等信息全部进行有效记录并不可篡改,同时各方进行数据获取之前需要数据所有者进行授权,保证了数据所有者的权力,促进了多方参与情况下的数据治理工作。
- 完善的联盟链积分体系,为工作推进保驾护航 。凭借区块链多方共识、难以篡改的特性,在高位调度统筹的基础上建立一套完善的联盟链积分体系,从而激励各方进行数据共享,各委办局使用积分进行各方数据的交换,并具有相应的考核机制,在推动工作的同时,让整个区块链生态形成了良性循环。
方案架构图如下:
更多关于政务领域的应用请参考 https://www.hyperchain.cn/solution/government
司法领域¶
趣链科技针对司法领域现有难题进行深度探索,基于国产自主可控的区块链底层技术,为司法领域提供区块链可信基础设施与相应的解决方案,以技术赋能司法,实现司法数据可信互通、业务可信协作、流程可信追溯,提高司法业务效率,促进司法规范化、标准化、公开化转型。
区块链将在 助力电子数据可信固证 、 推动司法业务规范化、透明化 、 促进跨机构业务高效协同 三方面赋能司法领域。
接下来,以公检法司联盟协作为典型应用场景介绍我们的落地方案。
公检法司联盟协作¶
通过构建公检法司联盟链,并基于该联盟链建设公检法司协作系统。通过区块链+数据共享打破各机构间信息孤岛,实现案件数据实时可信共享互通。案件数据、流转数据全流程记录在多方共识的分布式账本上,并在各个关键环节进行数据校验,确保数据真实、不可篡改,同时加强公检法司多部门相互监督、相互制约。
公检法司联盟协作存在以下痛点问题:
- 业务数据不互通 。公检法的业务协作依赖多方数据互通,而目前尚存在存在数据转移单向非共享、数据不互通、交互不流畅的情况。
- 业务协作不规范 。公检法司工作开展过程,各机关在处理案件过程中,存在业务不合规、监管不到位等问题。
- 业务衔接流程不通畅 。由于数据孤岛以及司法资源短缺,刑事案件与执法结果在各机构间的协作流转往往不够及时,存在业务断层,传递成本、沟通成本较高。
本方案具有以下价值:
- 打破数据孤岛 。案件数据在链上数字化实时高效可信互通,打通各司法机构间系统数据壁垒,免除纸质材料线下送转,简化案件材料移送、信息调阅流程。
- 提高协作效率 。各司法机构系统通过区块链相互链接进行业务协作,实现案件线上移送、案件退回提醒,及时获取案件状态等功能,提高机构间业务协作效率。
- 增强监管能力 。全程记录案件移送、退回情况,便于后续定位阻滞环节,从而对各司法机构进行监督管理,形成公检法司业务数据闭环,实现穿透式监管。
- 挖掘数据价值 。通过各机构业务数据共享,形成案件数据流,便于进行案件数据统计与分析,深度挖掘数据价值,优化公检法司业务流程与服务质量。
方案架构图如下:
更多关于司法领域的应用请参考 https://www.hyperchain.cn/industry/judicatory
能源领域¶
趣链科技结合能源行业分布式交易系统和清洁能源普及两大趋势,利用区块链技术提高能源生产效率,降低管理成本,增加监测准确度;提供安全交易保障并降低沟通成本、实时支付清结算系统。
区块链将在 助力数据电子化、标准化 、 实现数据可追溯,易于协同 、 提高交易效率与安全 三个方面赋能能源领域。
接下来,以新能源充电桩为典型应用场景介绍我们的落地方案。
新能源充电桩¶
通过区块链互联充电桩多运营主体,实现高效的充电桩支付结算服务,提升用户体验。基于区块链的分布式移动数字身份共享充电桩平台以移动智能终端身份认证技术为基础,通过移动智能终端作为电动汽车车主身份认证与密钥的载体,使电动汽车车主在充电时,可以对充电行为进行有效确权,从而为支付清算提供基础。
新能源充电桩存在以下痛点问题:
- 多主体清算难 。充电桩多运营主体间的数据协同要求高,传统充电桩未建立数据共享形式,多主体间难以进行清结算。
- 支付结算服务要求高 。车主、供电商与运营商之间需搭建信息共享通道,打通业务流水线,支付结算服务效率要求较高。
- 新基建新要求 。新能源汽车充电桩作为“新基建”七大领域之一,行业利好信号再度加强,需求缺口大且充电桩区域分布较为不均衡亟待提高。
本方案具有以下价值:
- 可信数据交换 。构建车主与运营商间的可信数据交换通道,利用区块链去中心化特性,保证数据在存储、传输、验证过程中均基于分布式的系统结构,保证了充电桩数据交换的完整性与可信度。
- 可信数据传输 。实现多方间的可信数据传输,以更好地进行协作与清算,利用区块链密码学原理,交易不可被篡改的特性,保证数据传输的可信环境。
- 资金数据协同 。实现充电行为资金流与数据流的“二流合一”,通过区块链记录充电信息,通过智能合约进行充电服务的清结算。
方案架构图如下:
更多关于能源领域的应用请参考 https://www.hyperchain.cn/industry/energy
数字藏品合约编写¶
前言¶
数字藏品作为近期区块链领域的热点之一而备受大众关注,因其利用区块链技术,数字藏品便有了唯一不可篡改的凭证,在保护其数字版权的基础上,实现真实可信的数字化发行、购买、收藏和使用。但是数字藏品是如何产生的、它是如何流转的,以及又是如何与区块链进行结合起来保护数字藏品的安全的这些问题却不被大众所了解,区块链上的数字藏品对普通人而言还存在着一定的门槛。
概念介绍¶
智能合约?¶
智能合约,是一段写在区块链上的代码,一旦某个事件触发合约中的条款,代码即自动执行。也就是说,满足条件就执行,不需要人为操控。
简单来说,智能合约就是一种把我们生活中的合约数字化,当满足一定条件后,可以由程序自动执行的技术。我们的生活中处处充满着合约,就好比你跟我做了一个约定,我们订好了奖惩措施,但由于种种原因可能没法履行其中的条款,出现了无法履约的情况,而在进入赔付环节,往往会出现毁约,失约,耍赖的情况,最后弄得有理说不清。但是如果我们把约定通过代码的形式,录入区块链中,一旦触发约定时的条件,就会有程序来自动执行,这就是智能合约。
数字藏品?¶
NFT全称为Non-Fungible Tokens,即“非同质化代币”。
同质化代币又称可互换型代币,各代币之间没有任何区别,可以随意交换及拆分整合,每个单位彼此等效,类似于每个面额相同的硬币一样。在以太坊上,有三种代币协议,使用ERC-20协议开发的智能合约生成的token是同质化代币也就是大家说的最多的加密货币,而使用ERC-721和ERC-1155协议开发的智能合约生成的token是非同质化代币也就是大家说的NFT。
NFT实质是一种数字藏品,不以融资为目的,不具有等价交换物特征。数字藏品的创新在于使用区块链技术进行加密编码,在中心化服务之外重新定义原生数字藏品所有权的标记方式,最大特点在于其唯一性、不可拆分性,每一个数字藏品都单独存在,无法相互替代。数字藏品的特性决定了其不可能成为一般等价物或统一的记账单位,因此也不可能成为虚拟货币或代币,它的交易和变现必须通过货币实现。
数字藏品可以通过区块链技术将艺术品、声音、图像、文字、游戏中的物品等任何有价值的内容通证化,生成一个无法篡改的独特编码,将物品转化为数字化抽象物,从而确保其唯一性和真实性,该数字藏品的所有权可实时追溯。这也表明,数字藏品本身并不是交易的对象,通过数字藏品技术转变为数字藏品的商品才是被交易的对象,相应价值也是由数字商品来实现的。
数字藏品合约功能?¶
假如你想发一套属于自己的系列数字藏品,需要怎么写合约?,这个合约将有可能需要什么功能?
- ”铸造“功能(mint):数字藏品是非同质化代币,即一种token,所谓铸造,就是在区块链上记载一个token的ID和其拥有者的地址。
- 转移功能:即拥有者把一个token转移给另一个人。
- 查询功能:查询某个token在谁手里,某个人有多少个token等查询功能。
- 元数据功能:描述某事物各种属性的信息,比如一个人的属性就有姓名、性别、年龄、肤色、身份证号、职业等。一个数字藏品的元数据就比如人物的发型、肤色、性别、年龄等。一般情况下,都先读取某个
- 合约元数据功能:在数字藏品平台上的一些基本属性设置,其与元数据不同是针对于整个数字藏品系列来说的。
- 其他:例如团队分账、白名单预售等等。
数字藏品协议¶
工欲善其事必先利其器,在对数字藏品进行深入解剖前,需要先了解下数字藏品协议。数字藏品协议是一种约定当前数字藏品项目规范的方式,因为不同的数字藏品项目的需求都不同,其对外提供的接口服务也会因为其项目自身的特殊性而不同。但是没有统一的标准就不适合一个行业的产生,一旦每个数字藏品项目都有其自己的对外提供服务接口,那么对于区块链浏览器、数字钱包和买卖交易双方都将造成很大的困扰,需要为每个数字藏品项目都进行适配,那显然是不合适的。因此需要有数字藏品项目的规范来约定一个数字藏品项目应该提供怎么样的功能以及其接口的出入参格式,方便第三方进行适配统一才能让数字藏品变得流通,所以一个具备标准通用的数字藏品协议就变得十分迫切。
类似以太坊中的ERC721协议,我们在HVM中也提供了相同功能的HPC721协议,让我们来看看协议中的具体内容:
public interface HPC721 {
// 声明发生数字藏品转移时触发的event事件
void eventTransfer(String from, String to, long id);
// 声明发生某个数字藏品授权时触发的event事件
void eventApproval(String owner, String approved, long id);
// 声明发生某个账户授权其所有数字藏品时触发的event事件
void eventApprovalForAll(String owner, String operator, boolean approved);
// 查询一个账户拥有的数字藏品数量
long balanceOf(String owner);
// 通过数字藏品凭证查询其所有者
String ownerOf(long id);
// 转移数字藏品
void transferFrom(String from, String to, long id);
// 转移数字藏品,并记录额外调用信息
void transferFrom(String from, String to, long id, byte[] calldata);
// 授权数字藏品给某个账户
void approve(String to, long id);
// 获取某个数字藏品的授权人
String getApproved(long id);
// 设置是否将所有数字藏品给予某个账户权限
void setApprovalForAll(String operator, boolean approved);
// 查询某个账户是否具有另一账户的授权
boolean isApprovedForAll(String owner, String operator);
}
同时还有HPC721的扩展协议,用来定义数字藏品项目的扩展信息(可按需实现):
public interface HPC721Metadata extends HPC721 {
// 获取数字藏品项目名称
String name();
// 获取数字藏品项目的含义
String symbol();
// 获取某个数字藏品的uri
String uri(long id);
}
简单来讲数字藏品项目就是一个数字藏品合约,而数字藏品协议就是一个合约接口,指定了合约所提供的功能。
实现一个数字藏品合约¶
了解了数字藏品协议以后我们就可以基于上述接口来实现一个数字藏品合约。首先是构建一个HVM合约项目,将合约主类名称之为PropertyContract并实现HPC721协议接口:
public class PropertyContract extends BaseContract implements HPC721 {
// ...
}
定义好合约主类后,我们便可以填充合约主类的内容了,首先是 定义存储合约数据的账本数据结构
// 记录账户地址下的数字藏品数量
@StoreField(hvmType = StoreField.TypeNestedMap)
private NestedMap<String, Long> balances;
// 记录某个数字藏品是否授权给某个账户地址
@StoreField(hvmType = StoreField.TypeNestedMap)
private NestedMap<Long, String> propertyApprovals;
// 记录某个账户是否给另一个账户全部数字藏品的授权
@StoreField(hvmType = StoreField.TypeNestedMap)
private NestedMap<String, NestedMap<String, Boolean>> operatorApprovals;
分别定义了三个NestedMap结构来存储以下信息:账户地址下的数字藏品数量、某个数字藏品是否授权给某个账户地址和某个账户是否给另一个账户全部数字藏品的授权,用于满足协议中的方法所定义的功能。此处可能会发现与一般的数字藏品合约不同,没有一个存储数字藏品的数据结构,也没有存储数字藏品ID到所有者的对应关系,这是因为我们在HVM中引入了PropertyAccount藏品账户的概念,藏品账户是一种将数字藏品映射为区块链底层账户模型的一种方式,用一个区块链底层账户来存储数字藏品,既能提高执行效率又能提高数据的安全性。
在对合约方法进行正式编写前,还需要定义好合约事件,在某些情况下触发合约事件,外部可以通过合约事件来解析在智能合约中发生的行为,以此一些第三方在获取合约数据时,便有了一个统一方便的方式。 合约中需要定义的事件 如下:
@Override
public void eventTransfer(String from, String to, long id) {
event(null, "Transfer", from, to, String.valueOf(id));
}
@Override
public void eventApproval(String owner, String approved, long id) {
event(null, "Approval", owner, approved, String.valueOf(id));
}
@Override
public void eventApprovalForAll(String owner, String operator, boolean approved) {
event(null, "ApprovalForAll", owner, operator, String.valueOf(approved));
}
三个事件分别在发生数字藏品转移(包括铸造)、授权某个数字藏品和对某个账户的全部数字藏品授权这三个场景下。定义好了事件内容后,便可以完善合约方法的部分了,同时将上述提到的账本数据结构、藏品账户和合约事件融入到合约方法中。
首先是进行 数字藏品铸造 。由于铸造数字藏品并不属于协议内容,因此对数字藏品的铸造逻辑的自由度就会大一些:
public void mintProperty(long id, String owner, String meta) {
// 检查参数合法性
if (StringUtil.checkEmpty(owner) || meta == null) {
throw new RuntimeException("the emit param is illegal");
}
// 通过emit0方法铸造藏品账户
byte[] identity = ByteUtil.longToBytes(id);
emit0(identity, owner, meta.getBytes());
// 对owner拥有的藏品数量进行更新
if (balances.get(owner) == null) {
balances.put(owner, 1L);
} else {
balances.put(owner, balances.get(owner)+1);
}
// 发送铸造成功事件
eventTransfer("", owner, id);
}
在示例中我们通过了内置的emit0方法铸造生产了一个藏品账户,在藏品账户铸造时要求传入藏品账户的ID、所有者和meta元数据信息,随后更新藏品数量和发送铸造合约事件,因此完成对一个数字藏品的铸造,将来可通过ID来查询到当前合约中的数字藏品藏品账户。
有了数字藏品后我们便可以将数字藏品托管给第三方帮忙进行交易,这里托管的方式有两种,对单一数字藏品进行托管或对账户下所有数字藏品都进行托管,后续的转移将只能由数字藏品所有者或其指定的托管人才有权限操作。
单一数字藏品托管
public void approve(String to, long id) {
// 判断被授权人与拥有者是否一致
String owner = ownerOf(id);
if (owner.equals(to)) {
throw new RuntimeException("approval to current owner");
}
// 判断授权人是否为拥有者或被拥有者指定的管理人
if (!(getSender().equals(owner) && isApprovedForAll(owner, getSender()))) {
throw new RuntimeException("approve caller is not token owner or approved for all");
}
// 更新数字藏品的授权人
propertyApprovals.put(id, to);
// 发生数字藏品授权事件
eventApproval(owner, to, id);
}
所有数字藏品托管
public void setApprovalForAll(String operator, boolean approved) {
// 判断被授权人与拥有者是否一致
String owner = getSender();
if (owner.equals(operator)) {
throw new RuntimeException("approve to caller");
}
// 标记授权人下的所有藏品是否授权给operator
if (operatorApprovals.get(owner) == null) {
NestedMap<String, Boolean> nestedMap = new NestedMap<>();
operatorApprovals.put(owner, nestedMap);
nestedMap.put(operator, approved);
} else {
NestedMap<String, Boolean> nestedMap = operatorApprovals.get(owner);
nestedMap.put(operator, approved);
}
// 发送所有数字藏品授权事件
eventApprovalForAll(owner, operator, approved);
}
托管后可通过 getApproved 和 isApprovedForAll 来查询是否托管的信息:
查询单个数字藏品托管信息
public String getApproved(long id) {
// 判断数字藏品ID是否存在
getPropertyNotNull(id);
return propertyApprovals.get(id);
}
public PropertyV1 getPropertyNotNull(long id) {
// 通过getProperty0传入ID读取藏品账户信息
PropertyV1 property = getProperty0(ByteUtil.longToBytes(id));
if (property == null) {
throw new RuntimeException("the property is not exist");
}
return property;
}
查询所有数字藏品托管信息:
public boolean isApprovedForAll(String owner, String operator) {
if (operatorApprovals.get(owner) != null) {
return operatorApprovals.get(owner).get(operator);
}
return false;
}
有了托管功能后便可将数字藏品交由第三方代为管理,当然拥有者仍然具有权限进行管理。
接着我们就可以为 合约定义转移功能 了:
public void transferFrom(String from, String to, long id) {
// 判断交易操作者是否为藏品的拥有者或者授权人
if (!isApprovedOrOwner(getSender(), id)) {
throw new RuntimeException("caller is not token owner or approved");
}
// 获取到藏品账户,判断参数from是否为藏品的拥有者
PropertyV1 property = getPropertyNotNull(id);
if (!from.equals(property.getOwner())) {
throw new RuntimeException("transfer from incorrect owner");
}
if (StringUtil.checkEmpty(to)) {
throw new RuntimeException("transfer to the zero address");
}
// 移除藏品ID的授权信息以及更新拥有者的藏品数量
propertyApprovals.remove(id);
balances.put(from, balances.get(from)-1);
if (balances.get(to) == null) {
balances.put(to, 1L);
} else {
balances.put(to, balances.get(to)+1);
}
// 更新藏品的拥有者信息
property.setOwner(to);
// 发送转移藏品事件
eventTransfer(from, to, id);
}
由此我们便拥有了一个数字藏品合约项目所需要主要功能,最后再将 查询数字藏品信息 的功能补充上,例如查询账户下的藏品数量和查询藏品拥有者信息:
public long balanceOf(String owner) {
Long l = balances.get(owner);
if (l == null) {
return 0;
}
return l;
}
public String ownerOf(long id) {
PropertyV1 property = getPropertyNotNull(id);
return property.getOwner();
}
调用数字藏品合约¶
最后,我们便可以通过SDK来发起对合约的调用了:
String contractAddress = deployContract();
// 铸造ID为1的数字藏品,在藏品meta元信息中填入数字藏品源文件在ipfs上的链接
{
InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("mintProperty")
.addlong(1)
.addString(account.getAddress())
.addString("https://ipfs.io/ipfs/QmPTtdDthdzPM6gWiLhsBUs79LdMmXgTVTcz978c5JsDJF?filename=Image.png")
.build();
invokeContract(account, invokeDirectlyParams, String.class, contractAddress);
System.out.println("mint id 1 success");
}
// 将账户下的数字藏品转移给另一个账户
{
Account a1 = accountService.genAccount(Algo.SMRAW);
InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("transferFrom")
.addString(account.getAddress())
.addString(a1.getAddress())
.addlong(1)
.build();
invokeContract(account, invokeDirectlyParams, String.class, contractAddress);
System.out.println("transfer id 1 from " + account.getAddress() + " to " + a1.getAddress() + " successfully");
}
// 查询账户下当前拥有的数字藏品数量,应变为0
{
InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("balanceOf").addString(account.getAddress()).build();
String ret = invokeContract(account, invokeDirectlyParams, String.class, contractAddress);
System.out.println("balanceOf " + account.getAddress() + " is " + ret);
}
// 查询ID为1的数字藏品的拥有者,应为藏品被转入的账户地址
{
InvokeDirectlyParams invokeDirectlyParams = new InvokeDirectlyParams.ParamBuilder("ownerOf").addlong(1).build();
String ret = invokeContract(account, invokeDirectlyParams, String.class, contractAddress);
System.out.println("ownerOf id 1 is " + ret);
}
该调用逻辑模拟了账号A铸造了一个ID为1的数字藏品,并在数字藏品的meta元数据中记录了源文件所在的ipfs地址,随后将数字藏品转移给账户B,之后向合约进行查询发现账户A下的数字藏品数量变为0,且ID为1的数字藏品的拥有者变为了账户B。进行完整的合约调用后将获得如下输出:
总结¶
至此我们完成了通过HVM来编写一个数字藏品合约项目,了解了数字藏品协议的内容和作用,并基于数字藏品协议补充了数字藏品合约的内容,最后通过SDK模拟场景调用了编写的合约方法,进行了数字藏品铸造、转移和查询。从这一系列过程中我们发现数字藏品其实也没有那么难理解,其本质上是通过区块链技术进行背书的数字凭证,通过区块链来明确数字藏品的所有权和保障数据安全。
社区与资源¶
开源资源¶
hyperbench开源链接:https://github.com/meshplus/hyperbench