fisco-bcos
2023-08-23 09:44:19 0 举报
AI智能生成
FISCO BCOS 平台是金融区块链合作联盟(深圳)(以下简称:金链盟)开源工作组以金融业务实践为参考样本,在 BCOS 开源平台基础上进行模块升级与功能重塑,深度定制的安全可控、适用于金融行业且完全开源的区块链底层平台。
作者其他创作
大纲/内容
<b>区块链概念</b>
<b>区块</b>:区块是按照时间次序构建的数据结构,区块链第一个区块称为“创世块”,后续生成的区块用“高度”<br>标识,每个区块高度注意递增,新区块会引入前一个区块的hash信息,在用hash算法和本区块的数据生成<br>唯一的数据指纹,从而形成环环相扣的块链状结构,称为“BlockChain”。<br><br>一个区块的数据结构是区块头和区块体,区块头包含区块高度,hash,出块者签名,状态树根等一些基本信息。<br>区块体包含一批交易数据列表以及相关的回执信息,根据交易列表大小,整个区块的大小会有所不同。1M~几M字节之间
<b>交易: </b>交易可认为是一段发往区块链系统的请求数据,用于部署合约,调用合约接口,维护合约的生命周期,以及管理资产,进行价值交换。<br>交易的基本数据结构包含发送者,接受者,交易数据等。用户可以构建一个交易,用自己的私钥给交易签名,发送到链上(通过sendRawTransaction)<br>等接口。由多个节点的共识机制处理,执行相关的智能合约代码,生成交易指定的状态数据,然后将交易打包到区块里和状态数据一起落盘存储,该交易<br>即被确认。被确认的交易被认为具备了事务性和一致性。<br><br>随着交易确认相应还有交易回执(receipt)产生,和交易一一对应且保存在区块里。用于保存一些交易执行过程生成的信息如结果码,日志,消耗的gas量,<br>用户可以使用交易hash检查交易回执,判定交易是否完成。<br><br>和“写操作”交易对应,还有一种“只读”调用方式,用于读取链上数据,节点收到请求后会根据请求的参数访问状态信息并返回,并不会将请求加入共识流程,<br>也不会导致修改链上的数据。<br>
<b>账户</b>:在采用账户模型设计的区块链系统里,<b>账户这个术语代表着用户、智能合约</b>的唯一性存在。<br><br>在采用公私钥体系的区块链系统里,用户创建一个公私钥对,经过hash算法换算即得到一个唯一性的地址串,代表着这个用户的账户,用户用该私钥管理这个<br>账户里的资产,用户账户在链上不一定有对应的存储空间,而是由智能合约管理用户在链上的数据,因此这种账户也会被称为“外部账户”。<br><br>对智能合约来说,一旦智能合约被部署后,在链上就有了一个唯一的地址,被称为合约账户,指向这个合约的状态位,二进制代码,相关状态数据的索引等。<br><b>智能合约运行过程中,会通过这个地址加载二进制代码,根据状态数据索引去访问世界状态存储里对应的数据,根据运行结果将数据写入世界状态存储,更新合约账户里的状态数据索引。智能合约被注销后,主要是更新合约账户里的合约状态位,将其置为无效,一般不会直接清除该账户的实际数据。</b>
<b>世界状态:</b>Fisco Bcos采用“账户模型”设计,即除了区块和交易的存储空间外,还会有一块保存智能合约运行结果的存储空间。智能合约执行过程产生的状态数据,经过共识机制确认,分布式的保存在各节点上,数据全局一致,可验证难篡改,所以称为“世界状态”。<br><br> 状态存储空间的存在,使得区块链上可以保存各种丰富的数据,包括用户账户信息如余额,智能合约二进制码,智能合约运行结果等相关各种数据,智能合约执行过程中会从状态存储中获取一些数据参与运算,为实现复杂的合约逻辑提供了基础。<br><br> 另一方面,维护状态数据需要付出不少存储成本,随着链的持续运行,状态数据会持续膨胀,如采用复杂的数据结构如帕特里夏树,状态数据的容量会进一步扩大,根据场景需要,可对状态数据进行裁剪优化,或采用分布式数据仓库等存储方案,以支持海量的状态数据容量。
<b>共识机制:</b>共识机制是区块链领域核心概念,无共识,不区块链。区块链作为一个分布式系统,可以由不同节点共同参与计算,共同见证交易的执行过程,并确认最终计算结果。协同这些松散耦合,互不信任的参与者达成信任关系,并保障一致性,持续性协作过程,可以抽象为“共识”过程,所牵涉的算法和策略统称为共识机制。
<b>节点:</b>安装了区块链系统所需软硬件,加入到区块链网络里的计算机。节点参与区块链系统网络通信,逻辑运算,数据验证,验证和保存区块,交易,状态等数据。<b>节点的标识采用公私钥机制,生成一串唯一的NodeId,以保证他在网络上的唯一性。</b>
<b>共识节点</b>:参与整个共识过程,作为记账者打包区块,作为验证者验证区块以<br>完成共识过程。
<b>观察节点:</b>不参与共识,同步数据,进行验证并保存,可以作为数据服务者提供服务。
<b>共识算法</b>:常见算法工作量证明(POW),权益证明(POS)联盟链常用的PBFT,raft等。
<b>解决的核心问题</b>
选出整个系统中具有记账权的角色,作为leader发起一次记账
参与者采用不可否认和不能篡改的算法,进行多层面验证后,采纳leader给出的记账
通过数据同步和分布式一致性协作,保证所有参与者最终结果一致,无误
<b>智能合约生命周期:</b>设计,开发,部署,运行,升级,销毁等步骤。<br><br>合约通过测试后,采用部署指令发布到链上,经过共识算法确认后,合约生效并被后续的交易调用。<br><br><b>合约升级更新:</b>重复以上开发到部署的步骤,发布新版合约,新版合约会有一个新的地址和独立的存储空间,并不是覆盖掉旧合约。<br>新版合约可以通过旧版本数据接口访问旧版本合约里保存的数据,或者通过数据迁移的方式将旧版本合约的数据迁移到新的合约存储里。<br><br><b>销毁一个合约</b>并不意味着清除合约的所有数据,只是将其状态置为“无效”,该合约不可再被调用。
<b>智能合约虚拟机:</b>为了运行数字智能合约,<b>区块链系统必须具备可编译、解析、执行计算机代码的编译器和执行器,统称为虚拟机体系</b>。合约编写完毕后,用编译器编译,发送部署交易将合约部署到区块链系统上,部署交易共识通过后,系统给合约分配一个唯一地址和保存合约的二进制代码,当某个合约被另一个交易调用后,虚拟机执行器从合约存储里加载代码并执行,输出执行结果。
<b>联盟链概念分析</b>
<b>概念</b>:联盟链从组建、加入、运营、交易等环节有准入和身份管理,在链上的操作可以有权限进行管控,共识方面一般采用PBFT等基于多方多轮验证投票的<br>共识机制。不采用POW挖矿的高耗能机制。网络规模相对可控,在交易时延性、事务一致性和确定性、并发和容量方面都可以进行大幅的优化。<br><br><b>联盟链在继承区块链技术优势的同时,更适合性能容量要求高,强调监管、合规的敏感业务场景,如金融、司法、以及大量和实体经济相关的业务。联盟链的路线兼顾了业务合规稳定和业务创新。</b>
<b>性能(TPS):</b>区块链领域的性能指标除了TPS外,还有确认时延,网络规模大小等。<br><br><b>确认时延</b>:交易发送到区块链网络后,经过验证、运算和共识等一系列流程后,到被确认所用的时间,如比特币网络一个区块是10分钟,交易被大概率确认需要6个区块,即一个小时,采用PBFT算法,可以使交易在秒级被确认,一旦确认即具有最终确定行,更适合金融等业务需求。<br><br><b>网络规模:</b>保证一定TPS和确认时延前提下,能<b>支持多少个共识节点协同工作,业界一般认为采用PBFT共识算法的系统,节点规模在百级左右</b>,再增加就会导致TPS下降,确认时延增加,目前业界有通过随机数算法选择记账组的共识机制,可以改善这个问题。
<b>性能优化:</b>性能的优化有两个方向,向上扩展(Scale Up)和平行扩展(Scale Out),向上扩展指在有限资源基础上优化软硬件配置,极大提升处理能力,如采用更有效率的算法,采用硬件加速等。平行扩展指系统架构具有更好的扩展性,可以采用分片、分区的方式承载不同的用户、业务流的处理。<br>
<b>安全性:构建在分布式网络上多方参与的区块链系统,在系统层面,需要关注网络攻击,系统渗透,数据破坏和泄漏的问题。在业务层面需要关注越权操作,逻辑错误、系统稳定性造成的资产损失,隐私侵害。</b>
<b>准入机制</b>:机构或者个人在组建和加入链之前,需要满足身份可知,资质可信,技术可靠的标准。主体信息多方共同审核后,才会启动联盟链组建工作,然后经过审核的主体节点加入到网络。在准入完成后,机构,节点、人员的信息都会登记到链上或可靠的信息服务里,链上的一切行为可以追溯到人。
<b>整体架构</b>
<b>架构图:</b>
<b>基础层</b>:提供区块链的基础数据结构和算法库
<b>核心层: </b>实现了区块链的核心逻辑
<b>链核心层</b>:实现区块链的链式数据结构,交易执行引擎和存储驱动。
<b>互联核心层: </b>实现区块链的基础P2P网络通信,共识机制和区块同步机制。
<b>管理层:</b>实现区块链的管理功能,包括参数配置,账本管理和AMOP
<b>接口层:</b>面向区块链的用户,提供多种RPC接口,SDK和交互式控制台。
<b>群组架构:</b> 多群组架构,<b>支持区块链节点启动多个群组,群组间交易处理,数据存储,区块共识相互隔离,保障区块链系统稳定性的同时,降低了系统运维复杂度。不同群组间的交易可以并行执行,提升了性能。<br><br><font color="#e74f4c">举例:机构A、B、C所有节点构成一个区块链网络,运行业务1,一段时间后,机构A、B启动业务2,且不希望该业务相关数据,交易处理被机构C感知。</font><br><font color="#e74f4c"> 方案一: 机构A、B重新搭建一条链</font><br><font color="#e74f4c"> 方案二:机构A和B新建一个群组运行业务2。</font><br><br></b><br><b>多群组架构中,群组间共享网络,通过<font color="#e74f4c">网络准入和账本白名单</font>实现各账本间网络消息隔离。<br><br>群组间数据隔离,每个群组独立运行各自的共识算法,<font color="#e74f4c">不同群组可以使用不同的共识算法</font>,每个账本模块自底向上包括核心层,接口层和调度层,这三层相互协作</b>
<b>网络准入和账本白名单示意图</b>
<b>账本模块分层</b>
<b>核心层</b>:负责将群组的区块数据,区块信息,系统表以及区块执行结果写入底层数据库。<br><br>存储分为世界状态(State)和分布式存储(AMDB)两部分,世界状态包括MPTState和StorageState,负责存储交易执行的<br>状态信息,StorageSate性能高于MPTState,但不存储区块历史信息,AMDB则向外暴露查询,提交和更新接口。负责操作合约表、<br>系统表和用户表,具有可插拔性,后端可支持多种数据库类型。目前支持RocksDb数据库和Mysql Storage。
<b>接口层:</b>包括交易池,区块链和区块执行器三个模块
<b>交易池:</b>与网络层以及调度层交互,<b>负责缓存客户端或者其他节点广播交易</b>,调度层(主要是同步和共识模块)从交易池中取出交易<br>进行广播或者区块打包。
<b>区块链</b>:与核心层和调度层交互,是<b>调度层访问底层存储的唯一入口,调度层通过区块链接口查询快高,获取指定区块,提交区块</b>。
<b>区块执行器</b>:与调度层交互,负责执行从调度层传入的区块,并将区块执行结果返回给调度层。
<b>调度层:</b>
<b>共识模块</b>:包括sealer线程和Engine线程,分别负责打包交易,执行共识流程<b>,Sealer线程从交易池取交易,并打包成新区块。</b><br><b>Engine线程执行共识流程,共识过程会执行区块,共识成功后,将区块以及区块执行结果提交到区块链</b>,<b>区块链统一将这些信息写入<br>底层存储,并触发交易池删除上链区块中包含的所有交易,</b>将交易执行结果以回调形式通知客户端。
<b>同步模块</b>:负责广播交易和获取新区块,在共识过程中,leader负责打包区块,而leader随时有可能切换,因此客户端的交易必须尽可能发送给<br>每个区块链节点,<b>节点收到交易后,同步模块将这些新交易广播给所有其他节点</b>,<b>考虑到区块链网络中机器性能不一致或者新加入节点都会导致部分节点区块高度落后于其他节点,同步模块提供了区块同步功能,该模块向其他节点发送自己节点的最新快高,其他节点发现快高落后于其他节点后,会主动下载最新区块。</b>
<b>交易流</b>
<b>总体方案</b>:用户通过SDK或者CURL命令向节点发起RPC请求<b>以发起交易,节点收到交易后将交易附加到交易池中,打包器不断从交易池中取出交易<br>并通过一定条件触发将取出交易打包为区块,生成区块后,由共识引擎进行验证以及共识,验证区块无误且节点间达成共识后,将区块上链。当节点通过同步<br>模块从其他节点处下载缺失的区块时,会同样对区块进行执行以及验证。</b>
<b>整体架构:共识引擎调用BlockVerfier对<font color="#e74f4c">区块进行验证并在网络中进行共识</font>。<br>BlockVerifier调用Executor执行区块中的每笔交易。当区块验证无误且网络中<br>节点达成一致后,共识引擎将区块发送至BlockChain</b>
<b>方案流程:</b>执行引擎基于执行上下文执行单个交易,其中执行上下文由区块验证器创建用于缓存暂存区块执行过程中执行引擎产生的所有数据,<br>执行引擎同时<b>支持EVM合约与预编译合约</b>,其中<b>EVM合约可以通过交易创建合约、合约创建合约</b>两种方式来创建。
<b>预编译合约</b>
<b>永久预编译合约</b>:整合在底层或插件中,合约地址固定
<b>临时预编译合约:</b>EVM合约或预编译合约执行时动态创建,合约地址在执行上下文内自增,临时<br>预编译合约仅在执行上下文内有效预编译合约没有Storage变量,只能操作表。
<b>区块链交易流程</b>
<b>概念:</b>负责记录区块链上发生的一切,区<b>块链引入智能合约后,交易其更加精准的定义应该是区块链中一次事务的数字记录</b>,无论事务大小,都需要交易的参与。
<b>交易生命周期</b>
<b>交易生成:</b>
<b>关键信息</b>
<b>发送地址</b>:用户自己的账户,用于表明交易来自何处。
<b>接收地址:</b>
部署合约的交易:交易并没有特定接受对象,因此规定这类交易的接受地址固定为0x0
调用合约的交易:链上合约地址
<b>交易相关数据:</b>一笔交易往往需要一些用户提供的输入来执行用户期望的操作,这些输入会以二进制的形式被编码到交易中。
<b>交易签名:</b>表明交易确实由自己发送,用户会向sdk提供私钥来让客户端对交易进行签名,其中私钥和用户账户一一对应。
之后,区块链客户端再向交易填充一些必要信息,如用于防治交易重放的交易ID以及blocklimit,交易构造完成后,<br>通过Channel或者RP信道将交易发送给节点。
<b>交易池:</b> 区块链交易被发送到节点后,节点通过验证交易签名的方式来验证交易是否合法。若一笔交易合法,则节点进一步检查交易是否重复出现过,若从未出现过,则将交易加入交易池缓存,若交易不合法或交易重复出现,则丢弃交易。
<b>交易广播:节点收到交易后,除了将交易换存在交易池外,节点还会将交易广播只该节点已知的其他节点</b>。<br><br>为了能让交易尽可能达到所有节点,<b>其他收到广播过来的交易节点,也会根据策略选择一些节点,将交易进一步广播</b>。比如:<br>对于从其他节点转发过来的交易,节点只会随机选择25%节点在此广播,因为这种情况一般意味着交易已经开始在网络中被节点接力传递,缩减广播的规模有助于避免因网络中冗余的交易太多而出现的广播风暴问题。
<b>交易打包:</b>为了确定交易之后的执行顺序保证事务性,当交易池有交易时,<b>Sealer线程负责从交易池中按照先进先出的顺序取出一定数量的交易,<br>组装成待共识区块,随后待共识区块会被发往各个节点进行处理。</b>
<b>交易执行:</b>节点收到区块后,调用区块验证器把交易从区块中逐一拿出来执行,如果是预编译合约代码,验证其中的执行引擎直接调用<br>相应的C++功能,否则执行引擎会把交易发给EVM(以太坊虚拟机)执行。<br><br>交易可能会因为逻辑错误或者Gas不足原因执行失败,交易执行的结果和状态会封装在交易回执中返回。
<b>交易共识:</b>采用PBFT算法保证系统一致性。流程是各个节点先独立执行相同区块,随后节点间交换各自的执行结果,如果发现2/3<br>节点得到相同执行结果,则说明这个区块在大多数节点上取得了一致,节点便会开始出块。
<b>交易落盘:</b>共识出块后,节点需要将区块中的交易及执行结果写入硬盘永久保存,并更新区块高度与区块hash的映射表内容,<br>然后节点从交易迟中剔除已落盘的交易,以开始新一轮的出块流程。用户可以通过交易哈希等信息,在链上历史数据中查询交易数据<br>以及交易回执。
<b>核心模块</b>
<b>共识算法</b>
<b>框架:</b>
<b>Sealer线程</b>:交易打包线程<b>,负责从交易池取出交易,并基于节点最高快打包交易,产生新区块</b>,产生的新区块交给Engine线程处理,PBFT和Raft<br>交易打包线程分别为PBFTSealer和RaftSealer
<b>Engine线程</b>:共识线程,<b>负责从本地或通过网络接收新区块,并根据接受的共识消息包完成共识流程,最终将达成共识的新区块写入区块链,<br>区块上链后,从交易池中删除已经上链的交易</b>,PBFT和Raft共识线程分别为PBFTEngine和RaftEngine。
<b>PBFT基础流程:</b>可以在少数节点作恶(如伪造消息)场景中达成共识,保证在一个由(3f+1)节点的系统中,<br>只要有不少于(2f+1)的诚实节点,系统就能达成一致性。
<b>重要概念</b>
<b>节点类型</b>
<b>Leader/Primary:共识节点,</b>负责<b>将交易打包成区块和区块标识</b>,每轮共识过程中仅有一个<br>leader,为了防止leader伪造区块,每轮PBFT共识后,均会切换leader。
<b>Replica:</b> 副本节点,<b>负责区块共识,每轮共识过程中有多个Replica节点</b>,每个Replica节点的处理过程类似
<b>Observer:</b>观察者节点,<b>负责从共识节点或副本节点获取最新区块,执行并验证区块执行结果后,将产生的区块上链</b>。
<b>节点ID&节点索引</b>:为了防止节点作恶,PBFT共识过程中每个<br>共识节点均对其发送的消息进行签名,对收到的消息包进行验签名,<br>因此每<b>个节点均维护一份公私钥对,私钥用于对发送的消息进行签名<br>,公钥作为节点ID,用于标识和验签</b>。
<b>节点ID</b>:共识节点签名公钥和共识节点唯一标识,一般是64字节二进制串,<b>其他节点使用消息包发送者的节点Id<br>对消息包进行验签。</b>
<b>节点索引</b>:考虑到节点ID很长,在共识消息中包含该字段会耗费部分网络带宽,FISCO BCOS引入了节点索引,<br><b>每个共识节点维护一份公共的共识节点列表,节点索引记录了每个共识节点ID在这个列表中的位置,发送网络<br>消息包时,只需要带上节点索引,其他节点即可以从公共的共识节点列表中索引出节点的ID,进而对消息进行验签</b>:<br><br>每个共识节点ID在这个公共节点ID列表中的位置<br>
<b>视图</b>:PBFT算法使用视图记录每个节点的共识状态,<b>相同视图节点维护相同的Leader和Replicas节点列表,当Leader出现故障,<br>会发生视图切换,若视图切换成功(至少2f+1个节点达到相同视图)</b>,则根据新的视图选出新leader,新leader开始出块,否则继续<br>进行视图切换,直至全网大部分节点达到一致试图。leader索引计算公式。<font color="#e74f4c"><b>leader_idx = (view + block_number) % node_num</b></font><br>
<b>视图切换过程</b>
<b>过程描述</b>:<br><b>前三轮共识:</b> node0、node1、node2为leader,且非恶意节点数目等于2*f+1,节点正常出块共识<br><br><b>第四轮共识,</b>node3为leader,但node3为拜占庭节点,<b>node0-node2在给定时间内未收到node3打包的区块,触发视图切换,<br>视图切换到view=view+1的新视图,并相互间广播viewchange包,节点收集满在view_new上的2f+1个viewchange之后</b>,将自己的view切换为view_new。<br>并计算出新的leader。<br><br><b>第五轮共识:</b>node0为leader,继续打包出块。<br>
<b>共识消息: 具体分为四种</b>
<b>PrepareReqPacket:</b> 包含区块的请求包,<b>由leader产生并向所有Replica节点广播</b>,Replica节点收到Prepare包后,<br>验证PrepareReq签名、执行区块并缓存区块执行结果,达到防止拜占庭节点作恶、保证区块执行结果最终确定性的目的。<br>
<b>SignReqPacket</b>:<b>带有区块执行结果的签名请求,由收到Prepare包并执行完区块的共识节点产生</b>,SignReq请求带有执行后区块的hash以及<br>该hash的签名,分别记为SginReq.block_hash和SignReq.sign,节点将SignReq广播到所有其他共识节点后,其他节点对SignReq(即区块执行结果)进行共识;<br>
<b>CommitReqPacket</b>:用于<b>确认区块执行结果的提交请求,由收集满(2*f+1)个block_hash相同且来自不同节点SignReq请求的节点产生</b>,<b>CommitReq被广播给所有其他共识节点,其他节点收集满(2*f+1)个block_hash相同、来自不同节点的CommitReq后,将本地节点缓存的最新区块上链;</b>
<b>ViewChangeReqPacket:</b>视图切换请求,当leader无法提供正常服务, 其他共识节点会主动触发视图切换,<b>ViewChangeReq中带有该节点即将切换到的视图(记为toView,为当前视图加一),某节点收集满(2*f+1)个视图等于toView、来自不同节点的ViewChangeReq后,会将当前视图切换为toView</b>
<b>系统框架</b>
<b>PBFTSealer:</b>PBFT打包线程,<b>负责从交易池取交易,并将打包好的区块封装成PBFT Prepare包,交给PBFTEngine处理;</b>
<b>PBFTEngine</b>:PBFT共识线程,从PBFTSealer或者P2P网络接收PBFT共识消息包,区块验证器(Blockverifier)负责开始执行区块,完成共识流程,将达成共识的区块写入区块链,区块上链后,从交易池中删除已经上链的交易。
<b>核心流程</b>
<b>leader打包区块:</b>PBFT共识算法中,共识节点轮流出块<br>每一轮共识仅有一个leader打包区块,leader索引通过公式<br><b>(block_number + current_view) % consensus_node_num</b>计算得出,<br><b>节点计算当前leader索引与自己索引相同后,由PBFTSealer线程打包区块。</b>
<br>
<b>产生新的空块</b>:通过区块链获取当前最高块,并基于最高块产生新空块<br>(将新区块父哈希置为最高块哈希,时间戳置为当前时间,交易清空)
<b>从交易池打包交易</b>:产生新空块后,从交易池获取交易,并将获取的交易插入到新产生的新区块中
<b>组装新区块:</b>Sealer线程打包到交易后,将新区块的打包者(Sealer字段)置为自己索引,并根据打包的交易计算出所有交易的<br>transactionRoot
<b>产生Prepare包</b>:将<b>组装的新区块编码到prepare包,通过PBFTEngine线程广播给组内所有共识节点,</b>其他共识节点收到Prepare包后,<br>开始进行三阶段共识。
<b>pre-prepare阶段</b>
<b>Prepare包合法性判断:</b>主要判断<b>是否是重复的Prepare包</b>、<b>Prepare请求中包含的区块父哈希是否是当前节点最高块哈希</b>(防止分叉)、Prepare请求中包含区块的块高是否等于最高块高加一
<b>缓存合法的Prepare包</b>:若prepare请求合法,则将其缓存到本地,用于过滤重复的prepare请求。
<b>空块判断:</b>若Prepare请求包含的区块中交易数为0,则触发空块视图切换,将当前视图加1,并向所有其他节点广播视图切换请求。
<b>执行区块并缓存区块执行结果</b>:若prepare请求包含的区块中交易数大于0,则调用BlockVerifier区块执行器执行区块,并缓存执行后的区块。
<b>产生并广播签名包:</b>基于执行后的区块哈希,产生并<b>广播签名包</b>,表明本节点已经完成区块执行和验证。
<b>prepare阶段</b>
<b>签名包合法性判断</b>:主要判断签名包的哈希与pre-prepare阶段缓存的执行后的区块哈希相同,若不相同,则判断该请求是否属于未来块签名请求(产生未来块的原因是本节点处理性能低于其他节点,还在进行上一轮共识,判断未来块的条件是:签名包的height字段大于本地最高块高加一)。若请求也非未来块,则是非法的签名请求,节点直接拒绝该请求。
<b>缓存合法的签名包</b>
<b>判断pre-prepare阶段缓存的区块对应的签名包缓存是否达到2f+1,若收集满签名包,广播commit包</b>
<b>若收集满签名包,备份pre-prepare阶段缓存的prepare包落盘:</b>为了防止Commit阶段区块未提交到数据库之前超过2f+1个节点宕机,这些节点启动后重新出块,导致区块链分叉(剩余的节点最新区块与这些节点最高区块不同),还需要备份pre-prepare阶段缓存的Prepare包到数据库,节点重启后,优先处理备份的Prepare包。
<b>commit阶段</b>
<b>Commit包合法性判断</b>:主要判断Commit包的哈希与pre-prepare阶段缓存的执行后的区块哈希相同,若不相同,则继续判断该请求是否属于未来块Commit请求(产生未来块的原因是本节点处理性能低于其他节点,还在进行上一轮共识,判断未来块的条件是:Commit的height字段大于本地最高块高加一),若请求也非未来块,则是非法的Commit请求,节点直接拒绝该请求;
<b>缓存合法的Commit包</b>
<b>判断pre-prepare阶段缓存的区块对应的commit包缓存是否达到2f+1,若收集满commit包,则新区块罗盘</b>
<b>视图切换处理流程:</b>当PBFT三阶段共识超时或节点收到空块时,PBFTEngine会试图切换到更高的视图,并触发ViewChange处理流程,节点收到ViewChange包时,触发ViewChange处理流程。
<b>判断ViewChange包是否有效</b>:有效的ViewChange请求中带有块高值必须不小于当前节点最高块高,视图必须大于当前节点视图
<b>缓存有效的ViewChange包</b>:防止相同的ViewChange请求被处理多次,也作为判断节点是否可以切换视图的统计依据
<b>收集viewChange包:</b>若收到的ViewChange包中附带的view等于本节点的即将切换到的视图toView且本节点收集满<br>2*f+1来自不同节点view等于toView的ViewChange包,则说明超过三分之二的节点要切换到toView视图,切换当前视图到toView。
<b>PBFT网络优化</b>
<b>Raft:</b>允许网络分区的一致性协议<b>,保证了在一个由N个节点构成的系统<br>中有(N+1)/2(向上取整)个节点正常工作的情况下系统的一致性</b>,<br>比如一个5节点的系统中允许2个节点出现拜占庭错误。
<b>关键概念</b>
<b>节点类型</b>
<b>Leader:</b>主要负责与外界交互,由Follower节点选举而来,每一次共识过程中有且仅有一个leader节点,<br>由leader全权负责从交易池中取出交易,打包交易组成区块并将区块上链。
<b>Follower</b>:以Leader节点为准进行同步,并在Leader节点失效时举行选举以选出新的Leader节点
<b>Candidate</b>:Follower节点在竞选Leader时拥有的临时身份。
<b>节点Id以及索引:</b>Raft算法中,每个网络节点都会有一个固定且全剧唯一的节点身份Id,称为节点Id,同时每个共识节点还会<br>维护一份公共的共识节点列表,这个猎豹记录了每个共识节点的Id,而自己在这个列表的位置称为节点索引
<b>任期</b>:Raft算法将时间化为不定长度的任期Terms,Terms为连续的数字,每个Term以选举开始,如果选举成功,则由当前<br>Leader负责出块,如果选举失败,并没有选举出新的单一Leader,则会开启新的Term,重新选举。
<b>消息:网络节点间通过发送消息进行通讯。</b>
<b>投票请求:</b>由Candidate节点主动发出,用于向网络中其他节点请求投票以竞选Leader。
<b>投票响应:</b>节点收到投票请求后,用于对投票请求进行响应,同意或者拒绝该请求。
<b>心跳</b>: Leader节点主动周期发出
<b>维护Leader节点身份,只要Leader能一直发送心跳且被其他节点相应,<br>Leader身份就不会发生变化。</b>
<b>区块数据复制,当Leader节点成功打包一个区块后,会将区块数据编码至<br>心跳中以将区块进行广播,其他节点在收到该心跳后解码出区块数据并将区块<br>放入自己的缓冲区。</b>
<b>心跳响应:</b>在节点收到心跳后,用于对心跳进行响应,<b>特别的,当收到一个包含区块数据的心跳时,该心跳的响应中会<br>带上该区块的哈希。</b>
<b>系统框架</b>
<b>架构图</b>
<b>RaftSealer: </b>负责从交易池取出交易并打包成区块,并发送至Raft Engine进行共识.区块上链后,Raft Sealer<br>负责从交易池中删除已上链交易。
<b>RaftEngine</b>:负责在共识节点进行共识,将达成共识的区块上链。
<b>核心流程</b>
<b>转换关系图</b>
<b>选举: </b>共识模块中采用心跳机制来触发Leader选举,当节点启动时,节点自动称为Follower且将Term置为0,<br><b>只要Follower从Leader或者Candidate收到有效的HeartBeat或者RequestVote消息,其就会保持在Follower状态,</b><br>如果<b>Follower在一段时间内(这段时间称为Election Timeout)没收到上述消息,则他会假设系统当前的Leader已经<br>失活,然后增加自己的Term并转换为Candidate,开启新一轮的Leader选举流程</b>
Follower增加当前Term,转换为Candidate
Candidate将票投给自己,并广播RequestVote到其他节点请求投票
Candidate节点保持在Candidate状态,直到下面三种情况中的一种发生
节点赢得选举
在等待选举期间,Candidate收到了其他节点的Heartbeat;
经过Election Timeout后,没有Leader被选出
<b>投票</b>:节点收到VoteReq消息后,会根据消息内容选择不同的响应策略
<b>VoteReq的Term小于或者等于自己的Term</b>
如果节点是Leader,则拒绝该请求,Candidate收到此响应后会放弃选举转变为Follower,并增加投票超时
如果节点不是Leader
如果VoteReq的Term小于自己的Term,则拒绝该请求,如果candidate收到超过半数的该种响应则表明<br>其已经过时,此时Candidate会放弃选举转变为Follower,并增加投票超时
如果VoteReq的Term等于自己的Term,则拒绝该投票请求,对于该投票请求不做任何处理,对于每个节点而言,<br>只能按照先到先得的原则投票给一个Candidate,从而保证每一轮选举中至多只有一个Candidate被选为Leader。
<b>VoteReq的LastLeaderTerm小于自己的LastLeaderTerm:</b>每个节点中会有一个lastLeaderTerm字段表示该节点见过的最后一个Leader的Term,<b>lastLeaderTerm仅能由Heartbeat进行更新</b>。<b>如果VoteReq中的lastLeaderTerm小于自己的lastLeaderTerm,表明Leader访问这个Candidate存在问题,如果此时Candidate处于网络孤岛的环境中,会不断向外提起投票请求,因此需要打断它的投票请求,所以此时节点会拒绝该投票请求</b>
<b>VoteReq的lastBlockNumber小于自己LastBlockNumber:</b>每个节点中会有一个lastBlockNumber字段表示节点见到过的最新块的块高。在出块过程中,节点间会进行区块复制,在区块复制的过程中,可能有部分节点收到了较新的区块数据而部分没有,从而导致不同节点的lastBlockNumber不一致。为了使系统能够达成一致,需要要求节点必须把票投给拥有较新数据的节点,因此在这种情况下节点会拒绝该投票请求。<br>
<b>节点是第一次投票</b>:为了避免出现Follower因为网络抖动导致重新发起选举,<b>规定如果节点是第一次投票,直接拒绝该投票请求,同时会将自己的firstVote字段置为该Candidate的节点索引</b>
<b>上述四个步骤中都没有拒绝投票请求:同意该请求</b>
<b>心跳超时:</b>在Leader称为网络孤岛时,Leader可以发出心跳,Follower可以收到心跳但是Leader收不到心跳回应,这种情况下Leader此时已经出现网络异常,但是由于一直可以向外发送心跳包导致Follower无法切换状态进行选举,系统陷入停滞。为了避免第二种情况发生,<b>模块中设置了心跳超时机制,Leader每次收到心跳回应时会进行相应记录。一旦一段时间后记录没有更新则leader放弃leader身份并转换为follower节点。</b>
<b>区块复制:</b> Raft协议强依赖Leader节点的可用性来保证集群数据的一致性,因为数据只能从Leader节点向Follower节点转移。当Raft Sealer向集群Leader提交区块数据后,Leader将数据置为未提交状态,<b>接着Leader节点会通过在Heartbeat中附加数据的形式向所有Follower节点复制数据并等待接受响应,在确保网络中超过半数节点已接收到数据后,在将区块数据写入底层存储中,此时区块数据状态已经进入已提交状态,此后,Leader节点通过sync模块向其他follower节点广播该区块数据。</b>
<b>rPBFT</b>
<b>当前区块链共识困境</b>
<b>POW算法</b>
性能低:10分钟出一个区块,交易确认时延长,耗电多
无最终一致性保障
吞吐量低
<b>基于分布式一致性原理的共识算法</b>
优点:分布式一致性算法包括BFT,CFT等具有秒级交易确认时延,最终一致性,吞吐量高,不耗电的优势,尤其是BFT类共识算法<br>还可以应对节点作恶的场景,在性能,安全性方面可达到联盟链要求。
缺点:与节点规模相关,可支撑的网络规模有限,极大限制了联盟链节点规模
<b>rPBFT共识算法</b>
<b>节点类型</b>
<b>共识委员: </b>执行PBFT共识流程的节点,有轮流出块权限
<b>验证节点:</b>不执行共识流程,验证共识节点是否合法,验证区块,经过若干轮共识后,会切换为共识节点
<b>核心思想</b>
<b>概念图</b>
<b>概念</b>:rPBFT算法<b>每轮共识流程仅选举若干个共识节点出块,并根据区<br>块高度周期性的替换共识节点,保障系统安全,主要包括2个系统参数</b>
<b>epoch_sealer_num</b>:每轮共识过程参与共识节点数,
<b>epoch_block_num:</b>共识节点替换周期,为防止选举的共识节点联合作恶,rPBFT每出<br>epoch_block_num个区块,会替换一个共识节点。
<b>算法流程</b>
<b>确定各共识节点编号IDX</b>:对所有共识节点的NodeId进行排序,排序后的NodeId索引即为该共识节点编号。
<b>链初始化:</b>rPBFT需要选取epoch_sealer_num个共识节点到共识委员中参与共识,目前初步实现是选取索引为0到epoch_sealer_num-1的节点<br>参与前epoch_block_num个区块共识。
<b>共识委员节点进行PBFT共识算法:</b> 选取的epoch_sealer_num个共识委员节点进行PBFT共识算法,<b>验证节点同步并验证这些共识委员节点<br>共识产生的区块,验证节点的步骤包括</b>:
<b>校验区块签名列表</b>:每个区块必须至少包含三分之二共识委员的签名
<b>校验区块执行结果</b>:本地区块执行结果须与共识委员在区块头记录的执行结果一致
<b>动态替换共识委员列表:</b>为保障系统安全性,rPBFT算法每出epoch_block_num个区块后,会从共识委员列表中剔除一个节点作为验证节点,并加入一个验证节点到共识委员列表中,如图所示
<b>节点重启:</b>rPBF算法需要快速确定共识委员列表,由于epoch_block_num可通过控制台动态更新,需要结合epoch_block_num最新配置生效块高获取共识委员列表
<b>算法分析</b>
<b>网络复杂度</b>:O(epoch_sealer_num*epoch_sealer_num),与节点规模无关,可扩展性强于PBFT共识算法
<b>性能</b>:可秒级确认,由于算法复杂度与节点数无关,性能衰减小于PBFT
<b>一致性、安全性要求:</b>至少三分之二的共识委员节点正常工作,系统才可正常共识
<b>安全性:</b>未来将引入CRF算法,随机、私密的替换共识委员,增强共识算法安全性。
<b>网络优化</b>
<b>Prepare包广播优化:采用树状广播</b>
根据共识节点索引,构成完全二叉树(默认三)
Leader产生Prepare包,沿着树状拓扑将Prepare包转发给其下属子节点
<b>优势</b>
传播速度比gossip快,无冗余消息包
分而治之,可扩展性强
<b>劣势</b>:中间节点是单节点,需要额外的容错策略
<b>基于状态包的容错方案:</b>为保证节点断连情况下,开启树状广播时,Prepare<br>包能达到每个节点,rPBFT引入了基于状态包的容错机制。
<b>概念图</b>
<b>主要流程</b>
节点A收到Prepare后,随机选取<b>33%</b>节点广播Prepare包状态,记为prepareStatus,<br>包括{blockNumber, blockHash, view, idx}
节点B收到节点A随机广播过来的prepareStatus后,判断节点A的Prepare包状态是否<br>比节点B当前Prepare包localPrepare状态新,主要判断包括
prepareStatus.blockNumber是否大于当前块高
prepareStatus.blockNumber是否大于localPrepare.blockNumber
prepareStatus.blockNumber等于localPrepare.blockNumber情况下,<br>prepareStatus.view是否大于localPrepare.view<br>
节点B的状态落后于节点A,且节点B与其父节点断连,则节点B向节点A发出prepareRequest请求,请求相应的Prepare包
若节点B的状态落后于节点A,但节点B与其父节点相连,若节点B最多等待100ms(可配)后,状态仍然落后于节点A,<br>则节点B向节点A发出prepareRequest请求,请求相应的Prepare包
节点B收到节点A的prepareRequest请求后,向其回复相应的Prepare消息包
节点A收到节点B的Prepare消息包后,执行handlePrepare流程处理收到的Prepare包
<b>对等网络</b>
<b>交易并行</b>
<b>DAG概念</b>:一个无环的有向图称为有向无环图(DAG),在一批交易中,可以通过一定方法识别出每笔交易需要占用的互斥资源,在根据交易在block中的顺序以及互斥资源的占用关系构造出一个交易依赖DAG图,<b>凡是入度为0(无被依赖的前序任务)的交易均可以并行执行</b><br>
<b>模块架构</b>
用户直接或间接通过SDK发起交易,交易可以是能够并行执行的交易和不能并行执行的交易。
交易进入节点的交易池中,等待打包
交易被sealer打包为区块,经过共识后,发送至blockVerifier进行验证。
blockVerifier根据区块中的交易列表生成交易DAG
BlockVerifier构造执行上下文,并行执行交易DAG
区块验证通过后,区块上链
<b>数据同步</b>
<b>同步基础流程</b>
<b>概念: </b>同步,是区块链节点非常重要的功能,是共识的辅助,给共识提供必需的运行条件。<b>同步分为交易的同步和状态的同步。<br>交易的同步,确保每笔交易能正确到达每个节点上,状态同步,能确保区块落后的节点正确的回到最新的状态。只有持有最新区块状态的节点,<br>才能参与到共识中去。</b>
<b>交易同步</b>
<b>概念:</b>让区块链上的交易尽可能的<b>达到所有的节点,为共识中将交易打包成区块提供基础</b>。一笔交易,<br>从客户端发往某个节点,<b>节点在收到交易后,会将交易放入自身的交易池中供共识去打包</b>。与此同时,节点<br>会将交易广播给其他节点,其他节点收到交易后,也会将交易放到自身的交易池中,<b>交易在发送的过程中,会有<br>丢失的情况,为了能让交易尽可能达到所有节点,收到广播过来交易的节点,根据一定的策略,选择其他节点,在进行<br>一次广播。</b>
<b>交易广播策略</b>:如果每个节点都没有限制的转发/广播收到的交易,带宽将被占满,出现交易广播雪崩的问题。根据经验,fisco选择了<br>较为精巧的交易广播策略,在尽可能保证交易可达性前提下,尽可能减少重复的交易广播。
对于SDK来的交易,广播给所有节点
对于其他节点广播来的交易,随机选择25%节点再次广播
一条交易在一个节点上,只广播一次,当收到了重复的交易,不会进行二次广播。
<b>状态同步</b>
<b>概念:</b>是让区块链节点状态保持在最新,区块链状态的新旧,是指区块链节点当前持有数据的新旧<b>。即节点持有的当前区块<br>块高的高低,若一个节点的块高是区块链的最高块高,则此节点就拥有区块链的最新状态。只有拥有最新状态的节点,才能参与<br>到共识中去,进行下一个新区块的共识。</b>在一个全新的节点加入到区块链上,或一个已经断网的节点恢复网络时,此节点的区块<br>落后于其他区块,状态不是最新的,此时就需要进行状态同步。如图,<b>需要同步的节点1,会主动向其他节点请求下载区块,整个下载过程<br>会将下载的负载分散到多个节点上。</b>
<b>状态同步与下载队列:区块链节点在运行时,会定时向其他节点广播自身的最高块高,节点收到其他节点广播过来的块高后,会和自身的块高进行比较,若自身的块高落后于此块高,就会启动区块下载流程。</b><br><br>区块的下载通过请求的方式完成,进入下载流程的节点,会随机挑选满足要求的节点,发送需要下载的区块区间,收到下载请求的节点,会根据请求的内容,回复相应的区块。<br><br>收到回复的区块的节点<b>,在本地维护一个下载队列,用来对下载下来的区块进行缓冲和排序,下载队列是一个以块高为顺序的优先队列,下载下来的区块,会不断的插入到下载队列中。当队列中的区块能连接上当前本地的区块链,则将区块从下载队列中取出,真正的连接到当前本地的区块链上。</b>
<b>同步场景</b>
<b>交易同步,一笔交易被广播到所有节点的过程</b>
<b>一笔交易通过channel或者RPC发送到某节点上</b>
<b>收到交易的节点全量广播此交易给其他节点</b>
<b>其他节点收到交易后,为保险起见,选择25%的节点再次广播</b>
<b>节点收到广播过的交易,不会再次广播</b>
<b>状态同步</b>
<b>节点出块时的广播逻辑</b>
<b>某个节点出块</b>
<b>此节点将自己最新的状态(最新块高、最高块哈希、创世块哈希)广播给所有的节点</b>
<b>其他节点收到peer的状态后,更新在本地管理的peer数据</b>
<b>组内成员的同步</b>:组内成员在某时刻意外关闭,但其他成员在出块,<br>在此组员在此启动时,发现区块落后于其他成员
组员再次启动
收到其他组员发来的状态包
比较发现自己的最高块高落后于其他组员,启动下载流程
将相差的区块按区间划分成多个下载请求包,发送给多个组员,负载均衡
等待其他节点回复区块包
其他节点接受响应,从自己的区块链上查询出区块,回复给启动的节点
节点收到区块,放入下载队列
节点从下载队列中将区块拿出,写到区块链上
若下载未结束,则继续请求,若下载结束,则切换自身状态,开启交易同步,开启共识
<b>新组员的同步</b>:非组员作为一个新组员加入到某个<br>组织中,且此节点第一次启动,从原来的组员中同步区块
非组员未被注册到组中,但非组员先启动
此时发现自己不在组中,不进行状态广播,<br>也不进行交易广播,只等待其他组员发来状态信息
此时组员中并没有此新组员,不会向新组员广播状态
管理员将新组员加入到组中
组员向新组员广播自身状态
新组员收到组员状态,比较自身块高(为0),启动下载流程
之后的下载流程,与组内成员区块同步流程相同
<b>区块同步优化</b>
<b>优化方案</b>
为了降低单个节点的出带宽,消除网络带宽对网络规模的限制,<b>支持更大的网络规模,采用树状拓扑进行区块同步</b>
<b>采用gossip协议来保障树状拓扑区块同步的健壮性,定期同步区块状态,使得在部分节点网络断连的情况下,所有节点均能同步到最新区块状态。</b>
<b>背景:</b>考虑到目前使用BFT类共识算法的区块链网络复杂度较高、不具有无限可扩展性,因此大部分业务架构仅有部分节点作为共识节点,其他节点均作为观察节点(不参与共识,但拥有区块链全量数据)。<br><br><br>在这种架构中,<b>大部分观察节点均随机从拥有最新区块的共识节点同步区块</b>,在包含n个共识节点、m个观察节点的区块链系统中,设每个区块大小为block_size,理想情况下(负载均衡),每个共识节点需要向m/n个观察节点发送区块,共识节点出带宽大约是(m/n)*block_size,设网络带宽是bandwidth,则每个共识节点最多可向(bandwidth/block_size)个节点同步区块,即区块链网络规模最大是<b>(n*bandwidth/block_size),在公网带宽bandwidth较小,区块较大的情况下,能容纳的节点数有限,因此随机的区块同步策略不具有可扩展性。</b>
<b>区块状态树状广播</b>
<b>概念:</b>为降低多个观察节点向单个共识节点同步区块时,共识节点的网络出带宽对网络规模的影响,fisco实现了区块树状广播策略。
<b>示意图: </b>该策略将观察节点分摊给每个共识节点,并以共识节点为定点构造一颗三叉树,<b>共识节点出块后,优先向其<br>子观察节点发送最新区块状态,子观察节点同步最新区块后,优先向自己的子节点发送最新区块状态</b>,以此类推。采用了区块状态树状<br>广播策略后,<b>每个节点仅将最新区块状态发送给子节点,设区</b>块大小为block_size,树的高度为w,则用于区块同步的带宽均为(block_size * w),<br>与区块链的节点数无关,具有可扩展性。
<b>树状广播工作流程</b>
共识节点共识提交新区块后,若其与子节点联通,则向其子节点同步最新区块状态,包括高度和区块哈希,记为{i,block_hash(i)},<br>否则递归判断是否与孙子节点联通,若连通,则向孙子节点同步最新区块状态。
子节点收到共识节点的区块状态后,判断接收到的区块状态{i,block_hash(i)}比自身区块状态新,<br>则向共识节点发送区块请求,共识节点收到请求后,向该节点发送对应的区块
子节点收到共识节点的区块后,验证成功后将其落盘,继续向自己的子节点发送自身的区块状态,同样,若该节点与自己的子节点断连,<br>会递归判断是否与孙子节点连通,并向连通的孙子节点发送最新区块状态。
收到新区块状态的子节点,重复步骤2,进行区块同步
<b>树状广播方案带来的时延问题:</b>设n个区块,每个区块提交时延为t,则根节点(共识节点)提交n个区块的时延为n*t,第一层节点(观察者节点)同步并提交区块的时延为n*t + t,第二层节点(观察者节点)同步并提交区块的时延为n*t + 2*t,叶子节点同步并提交区块的时延为n*t + d * t,与共识节点的时延差为d*t,n远大于d时,这个时延几乎可以忽略,因此该策略对观察者节点TPS的影响非常小。
<b>定期同步区块状态</b>
<b>概念:</b>考虑到若<b>树状拓扑中部分节点断连,可能会导致区块无法到达部分节点</b>,区块状态树状广播优化策略还采用了gossip协议定期同步区块状态。<br><br>即:<b>随机挑选若干个节点,同步最新区块状态信息。由于最终区块状态信息会收敛所有区块链节点</b>,树状拓扑中断连节点也能从其邻居节点同步最新区块,保证了树状区块状态广播的健壮性。<br>
<b>定期同步流程</b>
每个区块链节点每2s随机选择三个邻居节点广播当前区块状态,包括(区块高度,区块哈希)
节点收到这些区块状态后,更新本地缓存的各个节点区块状态到最新
若某节点区块高度高于本节点区块高度,该节点会向拥有更高区块的节点同步区块
<b>带宽对比:</b>整个区块链网络中包含10个共识节点,90个观察者节点,树的度设置为2。优化前,观察者节点主要从10个共识节点下载区块,共识节点的出流量可达到5000MB;优化后,部分下载流量分摊到了观察者节点,节点由区块下载带来的流量开销降低到了1400MB,降低了3倍多,基本接近最优(最优的情况是优化前峰值出带宽是优化后峰值出带宽的4.5倍,由于gossip协议导致的区块随机拉取情况的存在,无法达到最优,只能接近最优)。
<b>交易同步优化</b>
<b>概念:</b>为了保证客户端发送的交易能够达到所有节点,sdk直连的区块链节点需要将收到交易广播给其他节点。问题:<b>SDK直连的节点出带宽与区块链节点总数成正比,随着区块链系统节点数的增加,该节点必然称为整个系统的瓶颈,此外,为保障节点网络断连情况下,交易也能达到所有节点,还引入了交易转发逻辑,节点收到其他节点广播过来的交易后,随机选取25%的邻居节点转发收到的交易,在网络全连的情况下,这种交易转发策略无疑会带来巨大的带宽浪费,且节点数越多,因交易转发带来的数据包冗余越多。</b>
<b>交易广播优化策略</b>
<b>优化方案</b>
优化前:节点收到SDK的交易后,全量广播给其他节点;
优化后:节点收到SDK的交易后,将其发送给子节点,子节点收到交易后,继续将其发送给自身的子节点。
<b>交易转发优化策略</b>
<b>概念:</b>优化后的交易转发策略不直接转发交易,仅转发交易状态,节点根据其他节点的交易状态,<br>获取缺失的交易,然后直接向对应节点请求交易。
<b>交易转发流程</b>
节点收到新交易txs后,获取所有新交易的哈希,记为txs_hash_list,并将其打包成状态包,随机发送给<br>25%节点。
节点node_x收到某节点node_i交易状态包后,从中解出交易哈希列表txs_hash_list,并将其与本地交易池中的<br>交易列表做对比,获取缺失的交易列表,记为missed_txs_hash_list,将其打包成交易请求,向node_i发出交易请求
node_i接收到交易请求后,从交易池中取出missed_txs_hash_list对应的所有交易,回复给node_x。
<b>智能合约</b>
<b>概念:</b>交易的执行是区块链节点上的一个重要功能<b>,交易的执行,是把交易中的智能合约二进制代码取出来,用执行器(Executor)执行。共识模块(Consensus)是把交易从交易池(TxPool)中取出。打包成区块,并调用执行器去执行区块中的交易。在交易执行过程中,会对区块链状态进行修改,形成新区块的状态存储下来(Storage).执行器在这个过程中,类似于一个黑盒,输入的是智能合约代码,输出的是状态的改变。</b>
<b>EVM 以太坊虚拟机</b>
<b>概念: </b>当智能合约被编译成二进制文件后,被部署到区块链上,用户<b>通过调用智能合约的接口,来触发智能合约的执行操作。EVM执行<br>智能合约的代码,修改当前区块链上的数据(状态).被修改的数据,会被共识,确保一致性。</b>
<b>EVMC</b>
<b>概念:</b>新版本的以太坊将EVM从节点代码中剥离出来,形成一个独立的模块。<b>EVM与节点的交互,抽象出EVMC接口标准</b>。通过EVMC,节点可以对接多种虚拟机,而不仅限于传统的基于solidity的虚拟机。<br>
<b>EVMC接口</b>
<b>Instance接口:</b>节点调用EVM接口<br><br>定义了节点对虚拟机的操作,包括创建,销毁,设置等
<b>Callback接口:</b>EVM回调节点的接口<br><br>定义了EVM对节点的操作,主要是对state读写,区块信息的读写
<b>EVM执行</b>
<b>EVM指令</b>: solidity是合约的执行语言,<b>solidity被solc编译后,变成类似于汇编的EVM指令。</b>Interpreter定义了<br>一整套完整的指令集,solidity被编译后,生成二进制文件,二进制文件就是EVM指令的集合。<b>交易以二进制的形式发往节点,<br>节点收到后,通过EVMC调用EVM执行指令,在EVM中,用代码模拟实现了这些指令的逻辑。</b>
<b>算数指令</b>
<b>跳转指令</b>
<b>总结:EVM是一个状态执行的机器,输入是solidity编译后的二进制指令和节点的状态数据,输出是节点状态的改变</b>。以太坊通过EVMC实现了多种虚拟机的兼容。
<b>预编译合约</b>
<b>概念</b>:预编译合约提供一种使用c++编写合约的方法,合约逻辑与数据分离,相比于solidity合约性能更好
<b>Gas</b>
<b>概念</b>:EVM虚拟机有一整套Gas机制来衡量每笔交易上链消耗的cpu,内存和存储资源。<br><br>EVM原始的Gas机制中,交易的主要Gas消耗来源于存储,考虑到联盟场景更关注CPU和内存消耗,<br>fisco调整了存储Gas,引入Free Storage Gas衡量模式,提升CPU和内存在交易Gas消耗中的占比
<b>Precompiled合约支持Gas计算</b>
<b>模块架构:</b>PrecompiledGas主要记录了每个交易执行Precompiled合约过程中调用的基础操作、<br>占用内存消耗的Gas,交易调用Precompiled合约时Gas计算机制如下:
虚拟机执行交易调用Precompiled合约的call接口时,<b>每调用一个基础操作,<br>会将其对应的OPCode添加到PrecompiledGas的运行时指令集合中</b>
虚拟机执行交易调用Precompiled合约的call接口时,<b>基础操作占用的内存变化时,<br>会更新PrecompiledGas的运行时消耗的内存</b>
Precompiled合约执行完毕后,可调用接口,<b>根据运行Precompiled合约过程中执行的指令集合、<br>消耗的内存,计算出该Precompiled合约Gas消耗</b>。<br>
<b>Gas衡量标准</b>
<b>合约内存Gas计算:</b>Precompiled合约<b>内存消耗主要来自于<font color="#e74f4c">输入、输出以及运行时产生的额外内存消耗</font></b>。某笔交易消耗的总内存为txMemUsed时,其对应的内存Gas计算公式如下。即:每32字节增加memoryGasUnit个Gas,memoryGasUnit的值为3.<br><br>MemoryGas(txMemUsed) = memoryGasUnit * txMemUsed / 32 + (txMemUsed * txMemUsed)/512<br>
<b>CPU、存储Gas计算</b>
<b>Precompiled合约基础操作对应的操作码</b>
<b>Precompiled合约基础操作衡量标准</b>
<b>Gas衡量标准插件化</b>
<b>存储模块</b>
<b>概念:</b>fisco继承了以太坊存储的同时,引入了高扩展性、<br>高吞吐量、高可用、高性能的分布式存储,存储模块主要包括两部分
<b>世界状态</b>
<b>MPTState:</b>使用MPT树存储账户状态,与以太坊一致。
<b>StorageState:</b>使用分布式存储的表结构存储账户状态,不存历史信息,去掉了对MPT树的依赖,性能更高。
<b>分布式存储(AMDB)</b>:通过抽象表结构,实现了SQL和NOSQL的统一,通过实现对应的存储驱动,可以支持各类数据库,目前已支持LevelDB,RocksDb和Mysql。
<b>AMDB</b>
<b>概念</b>:分布式存储(AMDB)通过对表结构的设计,既可以对应到关系型数据库的表,<br>又可以拆分使用KV数据库存储。通过实现对应于不同数据库的存储驱动,<br>AMDB理论上可以支持所有关系型和KV的数据库。
CRUD数据、区块数据默认情况下都保存在AMDB,无需配置,<br>合约局部变量存储可根据需要配置为MPTState或StorageState,<br>无论配置哪种State,合约代码都不需要变动。
当使用MPTState时,合约局部变量保存在MPT树中。当使用StorageState时,<br>合约局部变量保存在AMDB表中。
尽管MPTState和AMDB最终数据都会写向RocksDB,但二者使用不同的RocksDB实例,<br>没有事务性,因此当配置成使用MPTState时,提交数据时异常可能导致两个RocksDB<br>数据不一致。
<b>名词解释</b>
<b>Table:</b>存储表中的所有数据。Table中存储AMDB主key到对应Entries的映射,可以基于AMDB主key进行增删改查,支持条件筛选
<b>Entries:</b>Entries中存放主Key相同的Entry,数组。AMDB的主Key与Mysql中的主key不同,AMDB主key用于标示Entry属于哪个key,<br>相同key的Entry会存放在同一个Entries中
<b>Entry:</b>对应于表中的一行,每行以列名作为key,对应的值作为value,构成KV结构。每个Entry拥有自己的AMDB主key,不同Entry允许拥有相同的AMDB主key
<b>Condition:</b>Table中的删改查接口支持传入条件,这三种接口会返回根据条件筛选后的结果。如果条件为空,则不做任何筛选。
数据更新或者插入过程中,需要根据主Key获取数据并将对数据更新或者append操作,然后再写回存储系统。因此,<b>在一个主Key对应的Entries中Entry个数很多的时候,执行效率会受到影响;同时,会加大内存的使用。所以,实际生产过程中主Key对应的Entries中Entry个数不宜过多。</b>
<b>举例说明</b>
<b>AMDB表分类</b>
<b>系统表</b>
<b>用户表</b>
<b>StorageState账户表</b>
<b>StorageState:</b>是一种使用AMDB实现的存储账户状态的方式,相比于MPTState主要区别如下
<b>MPTState每个账户使用MPT树存储其数据,当历史数据逐渐增多时,会因为存储方式和磁盘IO导致性能问题</b>。StorageState每个账户对应一个Table存储其相关数据,包括账户的nonce,code,balance等内容,而<b>AMDB可以通过实现对应的存储驱动支持不同的数据库以提高性能,我们使用RocksDB测试发现,StorageState性能大约是MPTState的两倍。</b>
<b>MPTState:</b>
<b>概念:</b>MPTState是以太坊上经典的数据存储方式,通过MPT树的方式,<br>将所有合约的数据组织起来,实现对数据的查找和追溯。
<b>MPT树</b>
<b>概念:</b>
从宏观上来说,MPT树是一颗前缀树,用key查询value。通过key去查询value,就是用key去在MPT树上进行索引,在经过多个<br>中间节点后,最终到达存储数据的叶子节点。
从细节上来说,<b>MPT树,是一颗Merkle树,每个树上节点的索引,都是这个节点的hash值</b>,在用key查找value的时候,是根据key在<br>某节点内部,获取下一个需要跳转的节点的hash值,拿到下一个节点的hash值,才能从底层的数据库中取出下一个节点的数据,之后在用key,去<br>下一个节点中查询下下个节点的hash值,直至达到value所在的叶子节点。
当MPT树上某个叶子节点的数据更新后,此叶子节点的hash也会更新,随之而来的,是这个叶子节点回溯到根节点的所有中间节点的hash都会更新。最终,MPT根节点的hash也会更新。当要索引这个新的数据时,用MPT新的根节点hash,从底层数据库查出新的根节点,再往后一层层遍历,最终找到新的数据。而如果要查询历史数据,则可用老的树根hash,从底层数据库取出老的根节点,再往下遍历,就可查询到历史的数据
<b>实现图</b>
<b>状态State</b>
在以太坊上,数据是以account为单位存储的,每个account内,保存着这个合约(用户)的代码、参数、nonce等数据。account的数据,通过account的地址(address)进行索引。以太坊上用MPT将这些address作为查询的key,实现了对account的查询。
随着account数据的改变,account的hash也进行改变。于此同时,MPT的根的hash也会改变。不同的时候,account的数据不同,对应的MPT的根就不同。<br>此处,以太坊把这层含义进行了具体化,提出了“状态”的概念。<b>把MPT根的hash,叫state root</b>。不同的state root,对应着不同的“状态”,对应查询到不同的MPT根节点,再用account的address从不同的MPT根节点查询到此状态下的account数据。不同的state,拿到的MPT根节点不同,查询的account也许会有不同。<br>
state root是区块中的一个字段,每个区块对应着不同的“状态”。<b>区块中的交易会对account进行操作,进而改变account中的数据</b>。不同的区块下,account的数据有所不同,即此区块的状态有所不同,具体的,是state root不同。从某个区块中取出这个区块的state root,查询到MPT的根节点,就能索引到这个区块当时account的数据历史。
<b>安全控制</b>
<b>网络层面安全控制</b>
<b>节点使用SSL连接,保障通信数据的机密性</b>
引入<b>网络准入机制</b>,将指定群组的作恶节点从共识节点列表或群组中删除,保障系统安全性
通过<b>群组白名单机制</b>,保证每个群组仅可接受相应群组的消息,保证群组间通信数据的隔离性
<b>CA黑名单机制</b>,可及时与作恶节点断开网络连接
提出<b>分布式存储权限控制</b>,灵活,细粒度的控制外部账户部署合约和创建、插入、删除和更新用户表的权限。
<b>存储层面安全控制</b>
<b>具体说明</b>
<b>节点准入管理机制</b>
<b>概述</b>
<b>单链多账本:</b>通过引入群组的概念,使得联盟链从原有一链一账本的存储/执行机制扩展为一链多账本的存储/执行机制,<br>基于群组唯独实现同一链上的数据隔离和保密。
<b>节点准入机制:</b>基于群组概念的引入,节点准入管理可分为<b>网络准入机制</b>和<b>群组准入机制</b>。准入机制的规则记录在配置中,<br>节点启动后将读取配置信息实现网络及群组的准入判断
<b>名词解释</b>
<b>节点类型</b>
<b>群组节点</b>:完成网络准入并加入群组的节点,群组节点只能是共识节点和观察节点两者之一,其中共识节点参与共识出块和交易/区块<br>同步,观察节点只参与区块同步。<b>群组节点准入过程涉及动态增删节点的交易发送。</b>
<b>游离节点</b>:完成网络准入但没有加入群组的节点,<b>游离节点尚未通过群组准入,不参与共识和同步</b>。
<b>配置类型</b>
<b>节点准入配置项</b>
<b>模块架构</b>
<b>核心流程</b>
<b>一般初始化流程</b>
<b>首次初始化流程</b>
<b>接口及配置描述</b>
<b>CA黑白名单</b>
<b>基于角色的权限控制</b>
<b>权限控制</b>
远程过程调用(RPC)
收藏
收藏
0 条评论
下一页