这份文档是带有权限的区块链的工业界实现的协议规范。它不会详细的解释实现细节,而是描述系统和应用之间的接口和关系。
这份规范的目标读者包括:
下面这些作者编写了这份分档:BinhQNguyen,ElliAndroulaki,AngeloDeCaro,SheehanAnderson,ManishSethi,ThorstenKramp,AlessandroSorniotti,MarkoVukolic,FlorianSimonSchubert,JasonKYellick,KonstantinosChristidis,SrinivasanMuralidharan,AnnaDDerbakova,DulcePonceleon,DavidKravitz,DiegoMasini.
下面这些评审人评审了这份文档:FrankLu,JohnWolpert,BishopBrock,NitinGaur,SharonWeed,KonradPabjan.
下面这些贡献者对这份规范提供了技术支持:GennaroCuomo,JosephALatone,ChristianCachin
这份文档规范了适用于工业界的区块链的概念,架构和协议。
fabric是在系统中数字事件,交易调用,不同参与者共享的总账。总账只能通过共识的参与者来更新,而且一旦被记录,信息永远不能被修改。每一个记录的事件都可以根据参与者的协议进行加密验证。
交易是安全的,私有的并且可信的。每个参与者通过向网络membership服务证明自己的身份来访问系统。交易是通过发放给各个的参与者,不可连接的,提供在网络上完全匿名的证书来生成的。交易内容通过复杂的密钥加密来保证只有参与者才能看到,确保业务交易私密性。
fabric是区块链技术的一种实现,比特币是可以在fabric上构建的一种简单应用。它通过模块化的架构来允许组件的“插入-运行”来实现这份协议规范。它具有强大的容器技术来支持任何主流的语言来开发智能合约。利用熟悉的和被证明的技术是fabric的座右铭。
以下术语在此规范的有限范围内定义,以帮助读者清楚准确的了解这里所描述的概念。
交易(Transaction)是区块链上执行功能的一个请求。功能是使用链节点(chainnode)来实现的。
交易者(Transactor)是向客户端应用这样发出交易的实体。
总账(Ledger)是一系列包含交易和当前世界状态(WorldState)的加密的链接块。
世界状态(WorldState)是包含交易执行结果的变量集合。
验证Peer(ValidatingPeer)是网络中负责达成共识,验证交易并维护总账的一个计算节点。
非验证Peer(Non-validatingPeer)是网络上作为代理把交易员连接到附近验证节点的计算节点。非验证Peer只验证交易但不执行它们。它还承载事件流服务和REST服务。
带有权限的总账(PermissionedLedger)是一个由每个实体或节点都是网络成员所组成的区块链网络。匿名节点是不允许连接的。
隐私(Privacy)是链上的交易者需要隐瞒自己在网络上身份。虽然网络的成员可以查看交易,但是交易在没有得到特殊的权限前不能连接到交易者。
可审计性(Auditability)作为商业用途的区块链需要遵守法规,很容易让监管机构审计交易记录。所以区块链是必须的。
fabric是由下面这个小节所描述的核心组件所组成的。
区块链服务通过HTTP/2上的点对点(peer-to-peer)协议来管理分布式总账。为了提供最高效的哈希算法来维护世界状态的复制,数据结构进行了高度的优化。每个部署中可以插入和配置不同的共识算法(PBFT,Raft,PoW,PoS)。
链码服务提供一个安全的,轻量的沙箱在验证节点上执行链码。环境是一个“锁定的”且安全的包含签过名的安全操作系统镜像和链码语言,Go,Java和Node.js的运行时和SDK层。可以根据需要来启用其他语言。
验证peers和链码可以向在网络上监听并采取行动的应用发送事件。这是一些预定义好的事件集合,链码可以生成客户化的事件。事件会被一个或多个事件适配器消费。之后适配器可能会把事件投递到其他设备,如Webhooks或Kafka。
fabric的主要接口是RESTAPI,并通过Swagger2.0来改变。API允许注册用户,区块链查询和发布交易。链码与执行交易的堆间的交互和交易的结果查询会由API集合来规范。
CLI包含RESTAPI的一个子集使得开发者能更快的测试链码或查询交易状态。CLI是通过Go语言来实现,并可在多种操作系统上操作。
fabric的一个部署是由成员服务,多个验证peers、非验证peers和一个或多个应用所组成一个链。也可以有多个链,各个链具有不同的操作参数和安全要求。
功能上讲,一个非验证peer是验证peer的子集;非验证peer上的功能都可以在验证peer上启用,所以在最简单的网络上只有一个验证peer组成。这个配置通常使用在开发环境:单个验证peer在编辑-编译-调试周期中被启动。
单个验证peer不需要共识,默认情况下使用noops插件来处理接收到的交易。这使得在开发中,开发人员能立即收到返回。
生产或测试网络需要有多个验证和非验证peers组成。非验证peer可以为验证peer分担像API请求处理或事件处理这样的压力。
网状网络(每个验证peer需要和其它验证peer都相连)中的验证peer来传播信息。一个非验证peer连接到附近的,允许它连接的验证peer。当应用可能直接连接到验证peer时,非验证peer是可选的。
验证和非验证peer的各个网络组成一个链。可以根据不同的需求创建不同的链,就像根据不同的目的创建不同的Web站点。
消息在节点之间通过Messageproto结构封装来传递的,可以分为4种类型:发现(Discovery),交易(Transaction),同步(Synchronization)和共识(Consensus)。每种类型在payload中定义了多种子类型。
messageMessage{enumType{UNDEFINED=0;DISC_HELLO=1;DISC_DISCONNECT=2;DISC_GET_PEERS=3;DISC_PEERS=4;DISC_NEWMSG=5;CHAIN_STATUS=6;CHAIN_TRANSACTION=7;CHAIN_GET_TRANSACTIONS=8;CHAIN_QUERY=9;SYNC_GET_BLOCKS=11;SYNC_BLOCKS=12;SYNC_BLOCK_ADDED=13;SYNC_STATE_GET_SNAPSHOT=14;SYNC_STATE_SNAPSHOT=15;SYNC_STATE_GET_DELTAS=16;SYNC_STATE_DELTAS=17;RESPONSE=20;CONSENSUS=21;}Typetype=1;bytespayload=2;google.protobuf.Timestamptimestamp=3;}payload是由不同的消息类型所包含的不同的像Transaction或Response这样的对象的不透明的字节数组。例如:type为CHAIN_TRANSACTION那么payload就是一个Transaction对象。
在启动时,如果CORE_PEER_DISCOVERY_ROOTNODE被指定,那么peer就会运行发现协议。CORE_PEER_DISCOVERY_ROOTNODE是网络(任意peer)中扮演用来发现所有peer的起点角色的另一个peer的IP地址。协议序列以payload是一个包含:
messageHelloMessage{PeerEndpointpeerEndpoint=1;uint64blockNumber=2;}messagePeerEndpoint{PeerIDID=1;stringaddress=2;enumType{UNDEFINED=0;VALIDATOR=1;NON_VALIDATOR=2;}Typetype=3;bytespkiID=4;}messagePeerID{stringname=1;}这样的端点的HelloMessage对象的DISC_HELLO消息开始的。
域的定义:
如果收到的DISC_HELLO消息的块的高度比当前peer的块的高度高,那么它马上初始化同步协议来追上当前的网络。
DISC_HELLO之后,peer会周期性的发送DISC_GET_PEERS来发现任意想要加入网络的peer。收到DISC_GET_PEERS后,peer会发送payload包含PeerEndpoint的数组的DISC_PEERS作为响应。这是不会使用其它的发现消息类型。
有三种不同的交易类型:部署(Deploy),调用(Invoke)和查询(Query)。部署交易向链上安装指定的链码,调用和查询交易会调用部署号的链码。另一种需要考虑的类型是创建(Create)交易,其中部署好的链码是可以在链上实例化并寻址的。这种类型在写这份文档时还没有被实现。
CHAIN_TRANSACTION和CHAIN_QUERY类型的消息会在payload带有Transaction对象:
交易安全的详细信息可以在第四节找到
一个交易通常会关联链码定义及其执行环境(像语言和安全上下文)的链码规范。现在,有一个使用Go语言来编写链码的实现。将来可能会添加新的语言。
当peer收到chaincodeSpec后以合适的交易消息包装它并广播到网络
部署交易的类型是CHAINCODE_DEPLOY,且它的payload包含ChaincodeDeploymentSpec对象。
当验证peer部署链码时,它通常会校验codePackage的哈希来保证交易被部署到网络后没有被篡改。
调用交易的类型是CHAINCODE_DEPLOY,且它的payload包含ChaincodeInvocationSpec对象。
messageChaincodeInvocationSpec{ChaincodeSpecchaincodeSpec=1;}3.1.2.5查询交易查询交易除了消息类型是CHAINCODE_QUERY其它和调用交易一样
同步协议以3.1.1节描述的,当peer知道它自己的区块落后于其它peer或和它们不一样后所发起的。peer广播SYNC_GET_BLOCKS,SYNC_STATE_GET_SNAPSHOT或SYNC_STATE_GET_DELTAS并分别接收SYNC_BLOCKS,SYNC_STATE_SNAPSHOT或SYNC_STATE_DELTAS。
安装的共识插件(如:pbft)决定同步协议是如何被应用的。每个小时是针对具体的状态来设计的:
SYNC_GET_BLOCKS是一个SyncBlockRange对象,包含一个连续区块的范围的payload的请求。
messageSyncBlockRange{uint64start=1;uint64end=2;uint64end=3;}接收peer使用包含SyncBlocks对象的payload的SYNC_BLOCKS信息来响应
messageSyncBlocks{SyncBlockRangerange=1;repeatedBlockblocks=2;}start和end标识包含的区块的开始和结束,返回区块的顺序由start和end的值定义。如:当start=3,end=5时区块的顺序将会是3,4,5。当start=5,end=3时区块的顺序将会是5,4,3。
SYNC_STATE_GET_SNAPSHOT请求当前世界状态的快照。payload是一个SyncStateSnapshotRequest对象
messageSyncStateSnapshotRequest{uint64correlationId=1;}correlationId是请求peer用来追踪响应消息的。接受peer回复payload为SyncStateSnapshot实例的SYNC_STATE_SNAPSHOT信息
messageSyncStateSnapshot{bytesdelta=1;uint64sequence=2;uint64blockNumber=3;SyncStateSnapshotRequestrequest=4;}这条消息包含快照或以0开始的快照流序列中的一块。终止消息是len(delta)==0的块
SYNC_STATE_GET_DELTAS请求连续区块的状态变化。默认情况下总账维护500笔交易变化。delta(j)是block(i)和block(j)之间的状态转变,其中i=j-1。payload包含SyncStateDeltasRequest实例
messageSyncStateDeltasRequest{SyncBlockRangerange=1;}接收peer使用包含SyncStateDeltas实例的payload的SYNC_STATE_DELTAS信息来响应
messageSyncStateDeltas{SyncBlockRangerange=1;repeatedbytesdeltas=2;}delta可能以顺序(从i到j)或倒序(从j到i)来表示状态转变
共识处理交易,一个CONSENSUS消息是由共识框架接收到CHAIN_TRANSACTION消息时在内部初始化的。框架把CHAIN_TRANSACTION转换为CONSENSUS然后以相同的payload广播到验证peer。共识插件接收这条消息并根据内部算法来处理。插件可能创建自定义的子类型来管理共识有穷状态机。3.4节会介绍详细信息。
总账由两个主要的部分组成,一个是区块链,一个是世界状态。区块链是在总账中的一系列连接好的用来记录交易的区块。世界状态是一个用来存储交易执行状态的键-值(key-value)数据库
区块链是由一个区块链表定义的,每个区块包含它在链中前一个区块的哈希。区块包含的另外两个重要信息是它包含区块执行所有交易后的交易列表和世界状态的哈希
NonHashData消息是用来存储不需要所有peer都具有相同值的块元数据。他们是建议值。
messageNonHashData{google.protobuf.TimestamplocalLedgerCommitTimestamp=1;repeatedTransactionResulttransactionResults=2;}messageTransactionResult{stringuuid=1;bytesresult=2;uint32errorCode=3;stringerror=4;}3.2.1.4交易执行一个交易定义了它们部署或执行的链码。区块中的所有交易都可以在记录到总账中的区块之前运行。当链码执行时,他们可能会改变世界状态。之后世界状态的哈希会被记录在区块中。
peer的世界状态涉及到所有被部署的链码的状态集合。进一步说,链码的状态由键值对集合来表示。所以,逻辑上说,peer的世界状态也是键值对的集合,其中键有元组{chaincodeID,ckey}组成。这里我们使用术语key来标识世界状态的键,如:元组{chaincodeID,ckey},而且我们使用cKey来标识链码中的唯一键。
为了下面描述的目的,假定chaincodeID是有效的utf8字符串,且ckey和value是一个或多个任意的字节的序列
当网络活动时,很多像交易提交和同步peer这样的场合可能需要计算peer观察到的世界状态的加密-哈希。例如,共识协议可能需要保证网络中最小数量的peer观察到同样的世界状态。
应为计算世界状态的加密-哈希是一个非常昂贵的操作,组织世界状态来使得当它改变时能高效效的计算加密-哈希是非常可取的。将来,可以根据不同的负载条件来设计不同的组织形式。
由于fabric是被期望在不同的负载条件下都能正常工作,所以需要一个可拔插的机制来支持世界状态的组织。
Bucket-tree是世界状态的组织方式的实现。为了下面描述的目的,世界状态的键被表示成两个组件(chaincodeIDandckey)的通过nil字节的级联,如:key=chaincodeID+nil+cKey。
这个方法的模型是一个merkle-tree在hashtable桶的顶部来计算世界状态的加密-哈希
这个方法的核心是世界状态的key-values被假定存储在由预先决定的桶的数量(numBuckets)所组成的哈希表中。一个哈希函数(hashFunction)被用来确定包含给定键的桶数量。注意hashFunction不代表SHA3这样的加密-哈希方法,而是决定给定的键的桶的数量的正规的编程语言散列函数。
为了对merkle-tree建模,有序桶扮演了树上的叶子节点-编号最低的桶是树中的最左边的叶子节点。为了构造树的最后第二层,叶子节点的预定义数量(maxGroupingAtEachLevel),从左边开始把每个这样的分组组合在一起,一个节点被当作组中所有叶子节点的共同父节点来插入到最后第二层中。注意最后的父节点的数量可能会少于maxGroupingAtEachLevel这个构造方式继续使用在更高的层级上直到树的根节点被构造。
下面这个表展示的在{numBuckets=10009andmaxGroupingAtEachLevel=10}的配置下会得到的树在不同层级上的节点数。
为了计算世界状态的加密-哈希,需要计算每个桶的加密-哈希,并假设它们是merkle-tree的叶子节点的加密-哈希。为了计算桶的加密-哈希,存储在桶中的键值对首先被序列化为字节码并在其上应用加密-哈希函数。为了序列化桶的键值对,所有具有公共chaincodeID前缀的键值对分别序列化并以chaincodeID的升序的方式追加在一起。为了序列化一个chaincodeID的键值对,会涉及到下面的信息:
对于上面列表的所有数值类型项(如:chaincodeID的长度),使用protobuf的变体编码方式。上面这种编码方式的目的是为了桶中的键值对的字节表示方式不会被任意其他键值对的组合所产生,并减少了序列化字节码的总体大小。
例如:考虑具有chaincodeID1_key1:value1,chaincodeID1_key2:value2,和chaincodeID2_key1:value1这样名字的键值对的桶。序列化后的桶看上去会像:12+chaincodeID1+2+4+key1+6+value1+4+key2+6+value2+12+chaincodeID2+1+4+key1+6+value1
如果桶中没有键值对,那么加密-哈希为nil。
中间节点和根节点的加密-哈希与标准merkle-tree的计算方法一样,即:应用加密-哈希函数到所有子节点的加密-哈希从左到右级联后得到的字节码。进一步说,如果一个子节点的加密-哈希为nil,那么这个子节点的加密-哈希在级联子节点的加密-哈希是就被省略。如果它只有一个子节点,那么它的加密-哈希就是子节点的加密-哈希。最后,根节点的加密-哈希就是世界状态的加密-哈希。
上面这种方法在状态中少数键值对改变时计算加密-哈希是有性能优势的。主要的优势包括:-那些没有变化的桶的计算会被跳过-merkle-tree的宽度和深度可以通过配置numBuckets和maxGroupingAtEachLevel参数来控制。树的不同深度和宽度对性能和不同的资源都会产生不同的影响。
在一个具体的部署中,所有的peer都期望使用相同的numBuckets,maxGroupingAtEachLevel,和hashFunction的配置。进一步说,如果任何一个配置在之后的阶段被改变,那么这些改变需要应用到所有的peer中,来保证peer节点之间的加密-哈希的比较是有意义的。即使,这可能会导致基于实现的已有数据的迁移。例如:一种实现希望存储树中所有节点最后计算的加密-哈希,那么它就需要被重新计算。
链码是在交易(参看3.1.2节)被部署是分发到网络上,并被所有验证peer通过隔离的沙箱来管理的应用级代码。尽管任意的虚拟技术都可以支持沙箱,现在是通过Docker容器来运行链码的。这节中描述的协议可以启用不同虚拟实现的插入与运行。
一个实现VM接口的虚拟机
typeVMinterface{build(ctxtcontext.Context,idstring,args[]string,env[]string,attachstdinbool,attachstdoutbool,readerio.Reader)errorstart(ctxtcontext.Context,idstring,args[]string,env[]string,attachstdinbool,attachstdoutbool)errorstop(ctxtcontext.Context,idstring,timeoutuint,dontkillbool,dontremovebool)error}fabric在处理链码上的部署交易或其他交易时,如果这个链码的VM未启动(崩溃或之前的不活动导致的关闭)时实例化VM。每个链码镜像通过build函数构建,通过start函数启动,并使用stop函数停止。
一旦链码容器被启动,它使用gRPC来连接到启动这个链码的验证peer,并为链码上的调用和查询交易建立通道。
验证peer和它的链码之间是通过gRPC流来通信的。链码容器上有shim层来处理链码与验证peer之间的protobuf消息协议。
messageChaincodeMessage{enumType{UNDEFINED=0;REGISTER=1;REGISTERED=2;INIT=3;READY=4;TRANSACTION=5;COMPLETED=6;ERROR=7;GET_STATE=8;PUT_STATE=9;DEL_STATE=10;INVOKE_CHAINCODE=11;INVOKE_QUERY=12;RESPONSE=13;QUERY=14;QUERY_COMPLETED=15;QUERY_ERROR=16;RANGE_QUERY_STATE=17;}Typetype=1;google.protobuf.Timestamptimestamp=2;bytespayload=3;stringuuid=4;}域的定义:-Type是消息的类型-payload是消息的payload.每个payload取决于Type.-uuid消息唯一的ID
消息的类型在下面的小节中描述
链码实现被验证peer在处理部署,调用或查询交易时调用的Chaincode接口
typeChaincodeinterface{Invoke(stub*ChaincodeStub,functionstring,args[]string)(error)Query(stub*ChaincodeStub,functionstring,args[]string)([]byte,error)}Init,Invoke和Query函数使用functionandargs参数来支持多种交易。Init是构造函数,它只在部署交易是被执行。Query函数是不允许修改链码的状态的;它只能读取和计算并以byte数组的形式返回。
当部署时(链码容器已经启动),shim层发送一次性的具有包含ChaincodeID的payload的REGISTER消息给验证peer。然后peer以REGISTERED或ERROR来响应成功或失败。当收到ERROR后shim关闭连接并退出。
注册之后,验证peer发送具有包含ChaincodeInput对象的INIT消息。shim使用从ChaincodeInput获得的参数来调用Init函数,通过像设置持久化状态这样操作来初始化链码。
shim根据Init函数的返回值,响应RESPONSE或ERROR消息。如果没有错误,那么链码初始化完成,并准备好接收调用和查询交易。
当处理调用交易时,验证peer发送TRANSACTION消息给链码容器的shim,由它来调用链码的Invoke函数,并传递从ChaincodeInput得到的参数。shim响应RESPONSE或ERROR消息来表示函数完成。如果接收到ERROR函数,payload包含链码所产生的错误信息。
与调用交易一样,验证peer发送QUERY消息给链码容器的shim,由它来调用链码的Query函数,并传递从ChaincodeInput得到的参数。Query函数可能会返回状态值或错误,它会把它通过RESPONSE或ERROR消息来传递给验证peer。
每个链码可能都定义了它自己的持久化状态变量。例如,一个链码可能创建电视,汽车或股票这样的资产来保存资产属性。当Invoke函数处理时,链码可能会更新状态变量,例如改变资产所有者。链码会根据下面这些消息类型类操作状态变量:
链码发送一个payload包含PutStateInfo对象的PU_STATE消息来保存键值对。
messagePutStateInfo{stringkey=1;bytesvalue=2;}GET_STATE链码发送一个由payload指定要获取值的键的GET_STATE消息。
链码发送一个由payload指定要删除值的键的DEL_STATE消息。
链码发送一个payload包含RANGE_QUERY_STATE对象的RANGE_QUERY_STATE来获取一个范围内的值。
messageRangeQueryState{stringstartKey=1;stringendKey=2;}startKey和endKey假设是通过字典排序的.验证peer响应一个payload是RangeQueryStateResponse对象的RESPONSE消息
messageRangeQueryStateResponse{repeatedRangeQueryStateKeyValuekeysAndValues=1;boolhasMore=2;stringID=3;}messageRangeQueryStateKeyValue{stringkey=1;bytesvalue=2;}如果相应中hasMore=true,这表示有在请求的返回中还有另外的键。链码可以通过发送包含与响应中ID相同的ID的RangeQueryStateNext消息来获取下一集合。
messageRangeQueryStateNext{stringID=1;}当链码结束读取范围,它会发送带有ID的RangeQueryStateClose消息来期望它关闭。
messageRangeQueryStateClose{stringID=1;}INVOKE_CHAINCODE链码可以通过发送payload包含ChaincodeSpec对象的INVOKE_CHAINCODE消息给验证peer来在相同的交易上下文中调用另一个链码
链码可以通过发送payload包含ChaincodeSpec对象的QUERY_CHAINCODE消息给验证peer来在相同的交易上下文中查询另一个链码
共识框架定义了每个共识插件都需要实现的接口:
就像下面描述的细节一样,consensus.LedgerStack封装了其他接口,consensus.Executor接口是共识框架的核心部分。换句话说,consensus.Executor接口允许一个(批量)交易启动,执行,根据需要回滚,预览和提交。每一个共识插件都需要满足以所有验证peer上全序的方式把批量(块)交易(通过consensus.Executor.CommitTxBatch)被提交到总账中(参看下面的consensus.Executor接口获得详细细节)。
当前,共识框架由consensus,controller和helper这三个包组成。使用controller和helper包的主要原因是防止Go语言的“循环引入”和当插件更新时的最小化代码变化。
这里有2个共识插件提供:pbft和noops:
定义:
typeConsenterinterface{RecvMsg(msg*pb.Message)error}Consenter接口是插件对(外部的)客户端请求的入口,当处理共识时,共识消息在内部(如从共识模块)产生。NewConsenter创建Consenter插件。RecvMsg`以到达共识的顺序来处理进来的交易。
阅读下面的helper.HandleMessage来理解peer是如何和这个接口来交互的。
typeCPIinterface{InquirerCommunicatorSecurityUtilsLedgerStack}CPI允许插件和栈交互。它是由helper.Helper对象实现的。回想一下这个对象是:
typeInquirerinterface{GetNetworkInfo()(self*pb.PeerEndpoint,network[]*pb.PeerEndpoint,errerror)GetNetworkHandles()(self*pb.PeerID,network[]*pb.PeerID,errerror)}这个接口是consensus.CPI接口的一部分。它是用来获取网络中验证peer的(GetNetworkHandles)处理,以及那些验证peer的明细(GetNetworkInfo):
注意pees由pb.PeerID对象确定。这是一个protobuf消息,当前定义为(注意这个定义很可能会被修改):
messagePeerID{stringname=1;}3.4.4Communicator接口定义:
typeCommunicatorinterface{Broadcast(msg*pb.Message)errorUnicast(msg*pb.Message,receiverHandle*pb.PeerID)error}这个接口是consensus.CPI接口的一部分。它是用来与网络上其它peer通信的(helper.Broadcast,helper.Unicast):
typeSecurityUtilsinterface{Sign(msg[]byte)([]byte,error)Verify(peerID*pb.PeerID,signature[]byte,message[]byte)error}这个接口是consensus.CPI接口的一部分。它用来处理消息签名(Sign)的加密操作和验证签名(Verify)
typeLedgerStackinterface{ExecutorLedgerRemoteLedgers}CPI接口的主要成员,LedgerStack组与fabric的其它部分与共识相互作用,如执行交易,查询和更新总账。这个接口支持对本地区块链和状体的查询,更新本地区块链和状态,查询共识网络上其它节点的区块链和状态。它是有Executor,Ledger和RemoteLedgers这三个接口组成的。下面会描述它们。
typeExecutorinterface{BeginTxBatch(idinterface{})errorExecTXs(idinterface{},txs[]*pb.Transaction)([]byte,[]error)CommitTxBatch(idinterface{},transactions[]*pb.Transaction,transactionsResults[]*pb.TransactionResult,metadata[]byte)errorRollbackTxBatch(idinterface{})errorPreviewCommitTxBatchBlock(idinterface{},transactions[]*pb.Transaction,metadata[]byte)(*pb.Block,error)}executor接口是LedgerStack接口最常使用的部分,且是共识网络工作的必要部分。接口允许交易启动,执行,根据需要回滚,预览和提交。这个接口由下面这些方法组成。
ExecTXs(idinterface{},txs[]*pb.Transaction)([]byte,[]error)这个调用根据总账当前的状态接受一组交易,并返回带有对应着交易组的错误信息组的当前状态的哈希。注意一个交易所产生的错误不影响批量交易的安全提交。当遇到失败所采用的策略取决与共识插件的实现。这个接口调用多次是安全的。
RollbackTxBatch(idinterface{})error这个调用忽略了批量执行。这会废弃掉对当前状态的操作,并把总账状态回归到之前的状态。批量是从BeginBatchTx开始的,如果需要开始一个新的就需要在执行任意交易之前重新创建一个。
PreviewCommitTxBatchBlock(idinterface{},transactions[]*pb.Transaction,metadata[]byte)(*pb.Block,error)这个调用是共识插件对非确定性交易执行的测试时最有用的方法。区块返回的哈希表部分会保证,当CommitTxBatch被立即调用时的区块是同一个。这个保证会被任意新的交易的执行所打破。
CommitTxBatch(idinterface{},transactions[]*pb.Transaction,transactionsResults[]*pb.TransactionResult,metadata[]byte)error这个调用提交区块到区块链中。区块必须以全序提交到区块链中,CommitTxBatch结束批量交易,在执行或提交任意的交易之前必须先调用BeginTxBatch。
定义:
typeLedgerinterface{ReadOnlyLedgerUtilLedgerWritableLedger}Ledger接口是为了允许共识插件询问或可能改变区块链当前状态。它是由下面描述的三个接口组成的
typeReadOnlyLedgerinterface{GetBlock(iduint64)(block*pb.Block,errerror)GetCurrentStateHash()(stateHash[]byte,errerror)GetBlockchainSize()(uint64,error)}ReadOnlyLedger接口是为了查询总账的本地备份,而不会修改它。它是由下面这些函数组成的。
GetBlockchainSize()(uint64,error)这个函数返回区块链总账的长度。一般来说,这个函数永远不会失败,在这种不太可能发生情况下,错误被传递给调用者,由它确定是否需要恢复。具有最大区块值的区块的值为GetBlockchainSize()-1
注意在区块链总账的本地副本是腐坏或不完整的情况下,这个调用会返回链中最大的区块值+1。这允许节点在旧的块是腐坏或丢失的情况下能继续操作当前状态/块。
GetBlock(iduint64)(block*pb.Block,errerror)这个调用返回区块链中块的数值id。一般来说这个调用是不会失败的,除非请求的区块超出当前区块链的长度,或者底层的区块链被腐坏了。GetBlock的失败可能可以通过状态转换机制来取回它。
GetCurrentStateHash()(stateHash[]byte,errerror)这个盗用返回总账的当前状态的哈希。一般来说,这个函数永远不会失败,在这种不太可能发生情况下,错误被传递给调用者,由它确定是否需要恢复。
typeUtilLedgerinterface{HashBlock(block*pb.Block)([]byte,error)VerifyBlockchain(start,finishuint64)(uint64,error)}UtilLedger接口定义了一些由本地总账提供的有用的功能。使用mock接口来重载这些功能在测试时非常有用。这个接口由两个函数构成。会会
HashBlock(block*pb.Block)([]byte,error)尽管*pb.Block定义了GetHash方法,为了mock测试,重载这个方法会非常有用。因此,建议GetHash方法不直接调用,而是通过UtilLedger.HashBlock接口来调用这个方法。一般来说,这个函数永远不会失败,但是错误还是会传递给调用者,让它决定是否使用适当的恢复。
VerifyBlockchain(start,finishuint64)(uint64,error)这个方法是用来校验区块链中的大的区域。它会从高的块start到低的块finish,返回第一个块的PreviousBlockHash与块的前一个块的哈希不相符的块编号以及错误信息。注意,它一般会标识最后一个好的块的编号,而不是第一个坏的块的编号。
typeWritableLedgerinterface{PutBlock(blockNumberuint64,block*pb.Block)errorApplyStateDelta(idinterface{},delta*statemgmt.StateDelta)errorCommitStateDelta(idinterface{})errorRollbackStateDelta(idinterface{})errorEmptyState()error}WritableLedger接口允许调用者更新区块链。注意这NOT不是共识插件的通常用法。当前的状态需要通过Executor接口执行交易来修改,新的区块在交易提交时生成。相反的,这个接口主要是用来状态改变和腐化恢复。特别的,这个接口下的函数永远不能直接暴露给共识消息,这样会导致打破区块链所承诺的不可修改这一概念。这个结构包含下面这些函数。
-PutBlock(blockNumberuint64,block*pb.Block)error这个函数根据给定的区块编号把底层区块插入到区块链中。注意这是一个不安全的接口,所以它不会有错误返回或返回。插入一个比当前区块高度更高的区块是被允许的,通用,重写一个已经提交的区块也是被允许的。记住,由于哈希技术使得创建一个链上的更早的块是不可行的,所以这并不影响链的可审计性和不可变性。任何尝试重写区块链的历史的操作都能很容易的被侦测到。这个函数一般只用于状态转移API。
-ApplyStateDelta(idinterface{},delta*statemgmt.StateDelta)error
这个函数接收状态变化,并把它应用到当前的状态。变化量的应用会使得状态向前或向后转变,这取决于状态变化量的构造,与`Executor`方法一样,`ApplyStateDelta`接受一个同样会被传递给`CommitStateDelta`or`RollbackStateDelta`不透明的接口`id`-CommitStateDelta(idinterface{})error
这个方法提交在`ApplyStateDelta`中应用的状态变化。这通常是在调用者调用`ApplyStateDelta`后通过校验由`GetCurrentStateHash()`获得的状态哈希之后调用的。这个函数接受与传递给`ApplyStateDelta`一样的`id`。-RollbackStateDelta(idinterface{})error
这个函数撤销在`ApplyStateDelta`中应用的状态变化量。这通常是在调用者调用`ApplyStateDelta`后与由`GetCurrentStateHash()`获得的状态哈希校验失败后调用的。这个函数接受与传递给`ApplyStateDelta`一样的`id`。-EmptyState()error
这个函数将会删除整个当前状态,得到原始的空状态。这通常是通过变化量加载整个新的状态时调用的。这一样只对状态转移API有用。3.4.9RemoteLedgers接口定义:
typeRemoteLedgersinterface{GetRemoteBlocks(peerIDuint64,start,finishuint64)(<-chan*pb.SyncBlocks,error)GetRemoteStateSnapshot(peerIDuint64)(<-chan*pb.SyncStateSnapshot,error)GetRemoteStateDeltas(peerIDuint64,start,finishuint64)(<-chan*pb.SyncStateDeltas,error)}RemoteLedgers接口的存在主要是为了启用状态转移,和向其它副本询问区块链的状态。和WritableLedger接口一样,这不是给正常的操作使用,而是为追赶,错误恢复等操作而设计的。这个接口中的所有函数调用这都有责任来处理超时。这个接口包含下面这些函数:
这个函数尝试从由peerID指定的peer中取出由start和finish标识的范围中的*pb.SyncBlocks流。一般情况下,由于区块链必须是从结束到开始这样的顺序来验证的,所以start是比finish更高的块编号。由于慢速的结构,其它请求的返回可能出现在这个通道中,所以调用者必须验证返回的是期望的块。第二次以同样的peerID来调用这个方法会导致第一次的通道关闭。
这个函数尝试从由peerID指定的peer中取出*pb.SyncStateSnapshot流。为了应用结果,首先需要通过WritableLedger的EmptyState调用来清空存在在状态,然后顺序应用包含在流中的变化量。
-
GetRemoteStateDeltas(peerIDuint64,start,finishuint64)(<-chan*pb.SyncStateDeltas,error)这个函数尝试从由peerID指定的peer中取出由start和finish标识的范围中的*pb.SyncStateDeltas流。由于慢速的结构,其它请求的返回可能出现在这个通道中,所以调用者必须验证返回的是期望的块变化量。第二次以同样的peerID来调用这个方法会导致第一次的通道关闭。
签名:
funcNewConsenter(cpiconsensus.CPI)(consenterconsensus.Consenter)这个函数读取为peer过程指定的core.yaml配置文件中的peer.validator.consensus的值。键peer.validator.consensus的有效值指定运行noops还是obcpbft共识。(注意,它最终被改变为noops或custom。在custom情况下,验证peer将会运行由consensus/config.yaml中定义的共识插件)
插件的作者需要编辑函数体,来保证路由到它们包中正确的构造函数。例如,对于obcpbft我们指向obcpft.GetPlugin构造器。
这个函数是当设置返回信息处理器的consenter域时,被helper.NewConsensusHandler调用的。输入参数cpi是由helper.NewHelper构造器输出的,并实现了consensus.CPI接口
验证peer通过helper.NewConsesusHandler函数(一个处理器工厂),为每个连接的peer建立消息处理器(helper.ConsensusHandler)。每个进来的消息都会检查它的类型(helper.HandleMessage);如果这是为了共识必须到达的消息,它会传递到peer的共识对象(consensus.Consenter)。其它的信息会传递到栈中的下一个信息处理器。
注意,fabric/peer/peer.go定义了peer.MessageHandler(接口),和peer.MessageHandlerCoordinator(接口)类型。
funcNewConsensusHandler(coordpeer.MessageHandlerCoordinator,streampeer.ChatStream,initiatedStreambool,nextpeer.MessageHandler)(peer.MessageHandler,error)创建一个helper.ConsensusHandler对象。为每个coordinator设置同样的消息处理器。同时把consenter设置为controller.NewConsenter(NewHelper(coord))
typeHelperstruct{coordinatorpeer.MessageHandlerCoordinator}包含验证peer的coordinator的引用。对象是否为peer实现了consensus.CPI接口。
funcNewHelper(mhcpeer.MessageHandlerCoordinator)consensus.CPI返回coordinator被设置为输入参数mhc(helper.ConsensusHandler消息处理器的coordinator域)的helper.Helper对象。这个对象实现了consensus.CPI接口,从而允许插件与栈进行交互。
回忆一下,helper.NewConsensusHandler返回的helper.ConsesusHandler对象实现了peer.MessageHandler接口:
typeMessageHandlerinterface{RemoteLedgerHandleMessage(msg*pb.Message)errorSendMessage(msg*pb.Message)errorTo()(pb.PeerEndpoint,error)Stop()error}在共识的上下文中,我们只关心HandleMessage方法。签名:
func(handler*ConsensusHandler)HandleMessage(msg*pb.Message)error这个函数检查进来的Message的Type。有四种情况:
事件框架提供了生产和消费预定义或自定义的事件的能力。它有3个基础组件:-事件流-事件适配器-事件结构
事件流是用来发送和接收事件的gRPC通道。每个消费者会与事件框架建立事件流,并快速传递它感兴趣的事件。事件生成者通过事件流只发送合适的事件给连接到生产者的消费者。
事件流初始化缓冲和超时参数。缓冲保存着几个等待投递的事件,超时参数在缓冲满时有三个选项:
事件生产者暴露函数Send(e*pb.Event)来发送事件,其中Event可以是预定义的Block或Generic事件。将来会定义更多的事件来包括其它的fabric元素。
messageGeneric{stringeventType=1;bytespayload=2;}eventType和payload是由事件生产者任意定义的。例如,JSON数据可能被用在payload中。链码或插件发出Generic事件来与消费者通讯。
adapter=
引用的实现提供了Golang指定语言绑定
EventAdapterinterface{GetInterestedEvents()([]*ehpb.Interest,error)Recv(msg*ehpb.Event)(bool,error)Disconnected(errerror)}把gRPC当成事件总线协议来使用,允许事件消费者框架对于不同的语言的绑定可移植而不影响事件生成者框架。
这节详细描述了事件系统的消息结构。为了简单起见,消息直接使用Golang描述。
事件消费者和生产者之间通信的核心消息是事件。
messageEvent{oneofEvent{//consumereventsRegisterregister=1;//producereventsBlockblock=2;Genericgeneric=3;}}每一个上面的定义必须是Register,Block或Generic中的一种。
messageInterest{enumResponseType{//don'tsendevents(usedtocancelinterest)DONTSEND=0;//sendprotobufobjectsPROTOBUF=1;//marshallintoJSONstructureJSON=2;}stringeventType=1;ResponseTyperesponseType=2;}事件可以通过protobuf结构直接发送,也可以通过指定适当的responseType来发送JSON结构。
当前,生产者框架可以生成Block和Generic事件。Block是用来封装区块链中区块属性的消息。
这一节将讨论下面的图所展示的设置描述。特别的,系统是由下面这些实体构成的:成员管理基础架构,如从一个实体集合中区分出不同用户身份的职责(使用系统中任意形式的标识,如:信用卡,身份证),为这个用户注册开户,并生成必要的证书以便通过fabric成功的创建交易,部署或调用链码。
希望使用fabric的用户,通过提供之前所讨论的身份所有权,在成员管理系统中开立一个账户,新的链码被链码创建者(开发)以开发者的形式通过客户端软件部署交易的手段,公布到区块链网络中。这样的交易是第一次被peer或验证器接收到,并流传到整个验证器网络中,这个交易被区块链网络执行并找到自己的位置。用户同样可以通过调用交易调用一个已经部署了的链码
下一节提供了由商业目标所驱动的安全性需求的摘要。然后我们游览一下安全组件和它们的操作,以及如何设计来满足安全需求。
为了充分的支持实际业务的需求,有必要超越确保加密连续性来进行演进。一个可工作的B2B系统必须致力于证明/展示身份或其他属性来开展业务。商业交易和金融机构的消费交互需要明确的映射到账户的所有者。商务合同通常需要与特定的机构和/或拥有交易的其他特定性质的各方保证有从属关系。身份管理是此类系统的关键组成部分的两个原因是问责制和不可陷害的。
此外,一个B2B系统需要具有可再生性和灵活性,以满足参与者角色和/或从属关系的改变。
交易隐私.
通过身份管理协调交易隐私.
就像文档之后描述的那样,这里所采用的方法是使户隐私来协调身份管理,并使有竞争力的机构可以像下面一样在公共的区块链(用于内部和机构间交易)上快速的交易:
审计支持.商业系统偶尔会受到审核。在这种情况下,将给予审计员检查某些交易,某组交易,系统中某些特定用户或系统自己的一些操作的手段。因此,任意与商业伙伴通过合同协议进行交易的系统都应该提供这样的能力。
成员管理服务是由网络上管理用户身份和隐私的几个基础架构来组成的。这些服务验证用户的身份,在系统中注册用户,并为他/她提供所有作为可用、兼容的参数者来创建和/或调用交易所需要的证书。公告密钥体系(PublicKeyInfrastructure,PKI)是一个基于不仅对公共网络上交换的数据的加密而且能确认对方身份的公共密钥加密的。PKI管理密钥和数字证书的生成,发布和废止。数字证书是用来建立用户证书,并对消息签名的。使用证书签名的消息保证信息不被篡改。典型的PKI有一个证书颁发机构(CA),一个登记机构(RA),一个证书数据库,一个证书的存储。RA是对用户进行身份验证,校验数据的合法性,提交凭据或其他证据来支持用户请求一个或多个人反映用户身份或其他属性的可信任机构。CA根据RA的建议为特定的用户发放根CA能直接或分级的认证的数字证书。另外,RA的面向用户的通信和尽职调查的责任可以看作CA的一部分。成员服务由下图展示的实体组成。整套PKI体系的引入加强了B2B系统的强度(超过,如:比特币)
根证书颁发机构(根CA):它代表PKI体系中的信任锚。数字证书的验证遵循信任链。根CA是PKI层次结构中最上层的CA。
登记机构(RA):它是一个可以确定想要加入到带权限区块链的用户的有效性和身份的可信实体。它是负责与用户外的带外通信来验证他/她的身份和作用。它是负责与用户进行频外通信来验证他/她的身份和角色。它创建登记时所需要的注册证书和根信任上的信息。
注册证书颁发机构(ECA):负责给通过提供的注册凭证验证的用户颁发注册证书(ECerts)
交易认证中心(TCA):负责给提供了有效注册证书的用户颁发交易证书(TCerts)
TLS证书颁发机构(TLS-CA):负责签发允许用户访问其网络的TLS证书和凭证。它验证用户提供的包含该用户的特定信息的,用来签发TLS证书的,证书或证据。
注册证书(ECerts)ECerts是长期证书。它们是颁发给所有角色的,如用户,非验证peer,验证peer。在给用户颁发的情况下,谁向区块链提交候选人申请谁就拥有TCerts(在下面讨论),ECerts有两种可能的结构和使用模式:
然而,审计能力和问责制的要求TCA能够获取给定身份的TCert,或者获取指定TCert的所有者。有关TCerts如何在部署和调用交易中使用的详细信息参见4.3节,交易安全是在基础设施层面提供的。
TCerts可容纳的加密或密钥协议的公共密钥(以及数字签名的验证公钥)。如果配备好TCert,那么就需要注册证书不能包含加密或密钥协议的公钥。
这样的密钥协议的公钥,Key_Agreement_TCertPub_Key,可以由交易认证机构(TCA)使用与生成Signature_Verification_TCertPub_Key同样的方法,使用TCertIndex+1而不是TCertIndex来作为索引个值来生成,其中TCertIndex是由TCA为了恢复而隐藏在TCert中的。
Example1:
Example2:
如果TCert与上述的ECert模型A的结合使用,那么使用K不发送给TCert的所有者的方案(c)就足够了。如果TCert与上述的ECert模型A的结合使用,那么TCert中的密钥协商的公钥域可能就不需要了。
交易认证中心(TCA)以批量的方式返回TCert,每个批量包含不是每个TCert都有的,但是和TCert的批量一起传递到客户端的KeyDF_Key(Key-Derivation-FunctionKey)(通用TLS)。KeyDF_Key允许TCert的拥有者派生出真正用于从AES_EncryptTCertOwner_EncryptKey(TCertIndex||已知的填充/校验检查向量)的TCertIndex恢复的TCertOwner_EncryptKey。
TLS证书(TLS-Certs)TLS-Certs是用于系统/组件到系统/组件间通讯所使用的证书。他们包含所有者的身份信息,使用是为了保证网络基本的安全。
下面这个图高度概括了用户注册过程,它具有离线和在线阶段。
离线处理:在第一步中,每个用户/非验证peer/验证peer有权在线下将较强的识别凭证(身份证明)到导入到注册机构(RA)。这需要在频外给RA提供为用户创建(存储)账号的证据凭证。第二步,RA返回对应的用户名/密码和信任锚点(这个实现中是TLS-CACert)给用户。如果用户访问了本地客户端,那么这是客户端可以以TLS-CA证书作为信任锚点来提供安全保障的一种方法。
在线阶段:第三步,用户连接客户端来请求注册到系统中。用户发送它的用户名和密码给客户端。客户端代表用户发送请求给PKI框架。第四步,接受包,第五步,包含其中一些对应于由客户端私有/秘密密钥的若干证书。一旦客户端验证包中所有加密材料是正确/有效的,他就把证书存储在本地并通知用户。这时候用户注册就完成了。
图4展示了注册的详细过程。PKI框架具有RA,ECA,TCA和TLS-CA这些实体。第一步只收,RA调用“AddEntry”函数为它的数据库输入(用户名/密码)。这时候用户已正式注册到系统数据库中。客户端需要TLS-CA证书(当作信任锚点)来验证与服务器之间的TLS握手是正确的。第四步,客户端发送包含注册公钥和像用户名,密码这样的附加身份信息的注册请求到ECA(通过TLS备案层协议)。ECA验证这个用户是否真实存在于数据库。一旦它确认用户有权限提交他/她的注册公钥,那么ECA就会验证它。这个注册信息是一次性的。ECA会更新它的数据库来标识这条注册信息(用户名,密码)不能再被使用。ECA构造,签名并送回一张包含用户注册公钥的(步骤5)注册证书(ECert)。它同样会发送将来会用到(客户端需要向TCA证明他/她的ECert使用争取的ECA创建的)的ECA证书(ECA-Cert))。(尽管ECA-Cert在最初的实现中是自签名的,TCA,TLS-CA和ECA是共址)第六步,客户端验证ECert中的公钥是最初由客户端提交的(即ECA没有作弊)。它同样验证ECert中的所有期望的信息存在且形式正确。
同样的,在第七步,客户端发送包含它的公钥和身份的注册信息到TLS-CA。TLS-CA验证该用户在数据库中真实存在。TLS-CA生成,签名包含用户TLS公钥的一张TLS-Cert(步骤8)。TLS-CA发送TLS-Cert和它的证书(TLS-CACert)。第九步类似于第六步,客户端验证TLSCert中的公钥是最初由客户端提交的,TLSCert中的信息是完整且形式正确。在第十步,客户端在本地存储中保存这两张证书的所有证书。这时候用户就注册完成了。
在这个版本的实现中验证器的注册过程和peer的是一样的。尽管,不同的实现可能使得验证器直接通过在线过程来注册。
客户端:请求TCert批量需要包含(另外计数),ECert和使用ECert私钥签名的请求(其中ECert的私钥使用本地存储获取的)
TCA为批量生成TCerts:生成密钥派生函数的密钥,KeyDF_Key,当作HMAC(TCA_KDF_Key,EnrollPub_Key).为每张TCert生成公钥(使用TCertPub_Key=EnrollPub_Key+ExpansionValueG,其中384位的ExpansionValue=HMAC(Expansion_Key,TCertIndex)和384位的Expansion_Key=HMAC(KeyDF_Key,“2”)).生成每个AES_EncryptTCertOwner_EncryptKey(TCertIndex||已知的填充/校验检查向量),其中||表示连接,且TCertOwner_EncryptKey被当作[HMAC(KeyDF_Key,“1”)]派生256位截断.
客户端:为部署,调用和查询,根据TCert来生成TCert的私钥:KeyDF_Key和ECert的私钥需要从本地存储中获取。KeyDF_Key是用来派生被当作[HMAC(KeyDF_Key,“1”)]256位截断的TCertOwner_EncryptKey;TCertOwner_EncryptKey是用来对TCert中的AES_EncryptTCertOwner_EncryptKey(TCertIndex||已知的填充/校验检查向量)域解密的;TCertIndex是用来派生TCert的私钥的:TCertPriv_Key=(EnrollPriv_Key+ExpansionValue)模n,其中384位的ExpansionValue=HMAC(Expansion_Key,TCertIndex),384位的Expansion_Key=HMAC(KeyDF_Key,“2”)。
对于指定类型的交易,系统交易有效周期标识是用来一起向区块链公布有效期满的。系统交易涉及已经在创世纪块被定义和作为基础设施的一部分的合同。有效周期标识是由TCA周期性的调用链码来更新的。注意,只有TCA允许更新有效期。TCA通过给定义了有效期区间的‘not-before’和‘not-after’这两个域设置合适的整数值来为每个交易证书设置有效期。
ECert过期:注册证书与交易证书具有不同的有效期长度。
废止是由证书废止列表(CRLs)来支持的,CRLs鉴定废止的证书。CRLs的改变,增量的差异通过区块链来公布
fabric中的交易是通过提交用户-消息来引入到总账中的。就像之前章节讨论的那样,这些信息具有指定的结构,且允许用户部署新的链码,调用已经存在的链码,或查询已经存在的链码的状态。因此交易的方式被规范,公布和处理在整个系统提供的隐私和安全中起着重要的作用。
一方面我们的成员服务通过检查交易是由系统的有效用户创建的来提供验证交易的手段,为了把用户身份和交易撇清,但是在特定条件下又需要追踪特定个体的交易(执法,审计)。也就是说,成员服务提供结合用户隐私与问责制和不可抵赖性来提供交易认证机制。
另一方面,fabric的成员服务不能单独提供完整的用户活动隐私。首先fabric提供完整的隐私保护条款,隐私保护认证机制需要通过交易保密协同。很明显,如果认为链码的内容可能会泄漏创建者的信息,那么这就打破了链码创建者的隐私要求。第一小节讨论交易的保密性。
为链码的调用强制访问控制是一个重要的安全要求。fabric暴露给应用程序(例如,链码创建者)这意味着当应用利用fabric的成员服务是,需要应用自己调用访问控制。4.4节详细阐述了这一点。
重放攻击是链码安全的另一个重要方面,作为恶意用户可能复制一个之前的,已经加入到区块链中的交易,并向网络重放它来篡改它的操作。这是第4.3.3节的话题。
本节的其余部分介绍了基础设施中的安全机制是如何纳入到交易的生命周期中,并分别详细介绍每一个安全机制。
交易在客户端创建。客户端可以是普通的客户端,或更专用的应用,即,通过区块链处理(服务器)或调用(客户端)具体链码的软件部分。这样的应用是建立在平台(客户端)上的,并在4.4节中详细介绍。
新链码的开发者可以通过这些fabric的基础设施来新部署交易:*希望交易符合保密/安全的版本和类型*希望访问部分链码的用户有适当的(读)访问权限*链码规范*代码元数据,包含的信息需要在链码执行时传递给它(即,配置参数),和*附加在交易结构上的并只在应用部署链码时使用的交易元数据
具有保密限制的链码的调用和查询交易都是用类似的方式创建。交易者提供需要执行的链码的标识,要调用的函数的名称及其参数。可选的,调用者可以传递在链码执行的时候所需要提供的代码调用元数据给交易创建函数。交易元数据是调用者的应用程序或调用者本身为了它自己的目的所使用的另外一个域。
任何候选的解决方案的附加要求是,满足并支持底层的成员服务的隐私和安全规定。此外,在fabric中他不应该阻止任何链码函数的调用访问控制,或在应用上实现强制的访问控制机制(参看4.4小结)。
下面提供了以用户的粒度来设置的交易机密性机制的规范。最后小结提供了一些如何在验证器的层次来扩展这个功能的方针。当前版本所支持的特性和他的安全条款可以在4.7节中找到。
目标是达到允许任意的子集实体被允许或限制访问链码的下面所展示的部分:1.链码函数头,即,包含在链码中函数的原型2.链码[调用&]状态,即,当一个或多个函数被调用时,连续更新的特定链码的状态。3.所有上面所说的
注意,这样的设计为应用提供利用fabric的成员管理基础设施和公钥基础设施来建立自己的访问控制策略和执法机制的能力。
为了支持细粒度的保密控制,即,为链码创建者定义的用户的子集,限制链码的明文读权限,一条绑定到单个长周期的加密密钥对的链(PKchain,SKchain)。
搭建.在注册阶段,用户获取(像之前一样)一张注册证书,为用户ui标记为Certui,其中每个验证器vj获取的注册证书标记为Certvj。注册会给用户或验证器发放下面这些证书:
因此,注册证书包含两个密钥对的公共部分:*一个签名密钥对[为验证器标记为(spkvj,sskvj),为用户标记为(spkui,sskui)]和*一个加密密钥对[为验证器标记为(epkvj,eskvj),为用户标记为(epkui,eskui)]
链,验证器和用户注册公钥是所有人都可以访问的。
除了注册证书,用户希望通过交易证书的方式匿名的参与到交易中。用户的简单交易证书ui被标记为TCertui。交易证书包含的签名密钥对的公共部分标记为(tpkui,tskui)。
下面的章节概括性的描述了如何以用户粒度的方式提供访问控制。
部署交易的结构.下图描绘了典型的启用了保密性的部署交易的结构。
整个交易由链码的创建者的证书签名,即:有信函决定使用注册还是交易证书。两个值得注意的要点:*交易中的信息是以加密的方式存储的,即,code-functions,*code-hdrs在使用TCert加密整个交易之前会用想用的TCert签名,或使用不同的TCert或ECert(如果交易的部署需要带上用户的身份。一个绑定到底层交易的载体需要包含在签名信息中,即,交易的TCert的哈希是签名的,因此mix\&match攻击是不可能的。我们在4.4节中详细讨论这样的攻击,在这种情况下,攻击者不能从他看到的交易中分离出对应的密文,即,代码信息,并在另一个交易中使用它。很明显,这样会打乱整个系统的操作,链码首先有用户A创建,现在还属于恶意用户B(可能没有权限读取它)*为了给用户提供交叉验证的能力,会给他们访问正确密钥的权限,即给其他用户相同的密钥,使用密钥K对交易加密成密文,伴随着对K的承诺,而这一承诺值开放给所有在合同中有权访问K的用户,和链验证器。在这种情况下,谁有权访问该密钥,谁就可以验证密钥是否正确传递给它。为了避免混乱,这部分在上图中省略了。
调用交易的结构.下图结构化描述了,交易调用链码会触发使用用户指定的参数来执行链码中的函数
调用交易和部署交易一样由一个基本信息,代码信息,链验证器和一个合同用户,并使用一张调用者的交易证书对所有进行签名。
查询交易的结构.查询交易和调用交易具有同样的格式。唯一的区别是查询交易对链码的状态没有影响,且不需要在执行完成之后获取(解密的)并/或更新(加密的)状态。
这节阐述了如何处理当前链中的不同(或子集)集合的验证器下的一些交易的执行。本节中抑制IP限制,将在接下的几个星期中进行扩展。
其他资产管理系统,即比特币,虽然没有直接处理重放攻击,但它防止了重放。在管理(数字)资产的系统中,状态是基于每个资产来维护的,即,验证器只保存谁拥有什么的记录。因为交易的重放根据协议(因为只能由资产/硬币旧的所有者衍生出来)可以直接认为无效的,所以防重放攻击是这种方式的直接结果。尽管这合适资产管理系统,但是这并不表示在更一般的资产管理中需要比特币系统。
在fabric中,防重放攻击使用混合方法。这就是,用户在交易中添加一个依赖于交易是匿名(通过交易证书签名)或不匿名(通过长期的注册证书签名)来生成的nonce。更具体的:*用户通过注册证书来提交的交易需要包含nonce。其中nonce是在之前使用同一证书的交易中的nonce函数(即计数器或哈希)。包含在每张注册证书的第一次交易中的nonce可以是系统预定义的(即,包含在创始块中)或由用户指定。在第一种情况中,创世区块需要包含nonceall,即,一个固定的数字和nonce被用户与身份IDA一起用来为他的第一笔注册证书签名的交易将会
存储开销:
存储开销(这里只影响验证器):O(m),其中m近似于有效期内的交易和对应的有效标识的数量(见下方)
应用是抱在区块链客户端软件上的一个具有特定功能的软件。如餐桌预订。应用软件有一个版本开发商,使后者可以生成和管理一些这个应用所服务的行业所需要的链码,而且,客户端版本可以允许应用的终端用户调用这些链码。应用可以选择是否对终端用户屏蔽区块链。
本节介绍应用中如何使用链码来实现自己的访问控制策略,并提供如何使用成员服务来达到相同的目的。
这个报告可以根据应用分为调用访问控制,和读取访问控制。
为了允许应用在应用层安全的实现自己的访问问控制,fabric需要提供特定的支持。在下面的章节中,我们详细的说明的fabric为了达到这个目的而给应用提供的工具,并为应用如何来使用它们使得后者能安全的执行访问控制提供方针。
来自基础设施的支持.把链码的创建者标记为uc,为了安全的实现应用层自己的调用访问控制,fabric必须需要提供特定的支持。更具体的,fabric层提供下面的访问能力:
CertificateHandler接口允许使用底层证书的密钥对来对任意消息进行签名和验证。证书可以是TCert或ECert。
Client接口更通用,提供之前接口实例的手段。
typeClientinterface{...//GetEnrollmentCertHandlerreturnsaCertificateHandlerwhosecertificateistheenrollmentcertificateGetEnrollmentCertificateHandler()(CertificateHandler,error)//GetTCertHandlerNextreturnsaCertificateHandlerwhosecertificateisthenextavailableTCertGetTCertificateHandlerNext()(CertificateHandler,error)//GetTCertHandlerFromDERreturnsaCertificateHandlerwhosecertificateistheonepassedGetTCertificateHandlerFromDER(der[]byte)(CertificateHandler,error)}为了向链码调用控制提供应用级别的的访问控制列表,fabric的交易和链码指定的格式需要存储在应用特定元数据的额外的域。这个域在图1中通过元数据展示出来。这个域的内容是由应用在交易创建的时候决定的。fabric成把它当作非结构化的字节流。
messageChaincodeSpec{...ConfidentialityLevelconfidentialityLevel;bytesmetadata;...}messageTransaction{...bytespayload;bytesmetadata;...}为了帮助链码执行,在链码调用的时候,验证器为链码提供额外信息,如元数据和绑定。
应用调用访问控制.这一节描述应用如何使用fabric提供的手段在它的链码函数上实现它自己的访问控制。这里考虑的情况包括:
链码处理:验证器,从ui处接受到的执行交易将提供以下信息:
注意sigma是被调用函数参数的一部分,或者是存储在调用交易的代码元数据内部的(被客户端应用合理的格式化)。应用ACL包含在代码元数据段中,在执行时同样被传递给链码。函数hello负责检查sigma的确是通过TCertui在’M||txBinding’上的有效签名。
这节描述fabric基础设施如何支持应用在用户层面执行它自己的读访问控制策略。和调用访问控制的情况一样,第一部分描述了可以被应用程序为实现此目的利用的基础设施的功能,接下来介绍应用使用这些工具的方法。
为了说明这个问题,我们使用和指点一样的例子,即:
来自基础设施的支持.为了让uA在应用层安全的实现自己的读取访问控制我们的基础设施需要像下面描述的那样来支持代码的部署和调用交易格式。
更具体的fabric层需要提供下面这些功能:
应用读访问控制.应用可以请求并获取访问用户ur的公共加密密钥,我们把它标记为PKur。可选的,ur可能提供uA的一张证书给应用,使应用可以利用,标记为TCertur。如:为了跟踪用户关于应用的链码的交易。TCertur和PKur实在频外渠道交换的。
部署时,应用uA执行下面步骤:
ImportantNote:要注意的是验证器在整个执行链码过程中不提供任何解密预测。对payload解密由基础设施自己负责(以及它附近的代码元数据域)。并提供他们给部署/执行的容器。
为了通过在线钱包服务注册,用户必须提供下面请求对象到钱包服务:
AccountRequest/*accountrequestofu\*/{OBCSecCtx,/*credentialsassociatedtonetwork\*/AccPubu,/*accountidentifierofu\*/AccSecProofu/*proofofAccSecu\*/}OBCSecCtx指向用户证书,它依赖于注册过程中的阶段。可以是用户的注册ID和密码,
对于后续的请求,用户u需要提供给钱包服务的请求与虾子面这个格式类似。
TransactionRequest/*accountrequestofu\*/{TxDetails,/*specificationsforthenewtransaction\*/AccPubu,/*accountidentifierofu\*/AccSecProofu/*proofofAccSecu\*/}这里,TxDetails指向在线服务代表用户构造交易所需要的信息,如类型,和用户指定的交易的内容。
AccSecProofu是对应请求中剩下的域的使用共享密钥的HMAC。Nonce-based方法与我们在fabric中一样可以防止重放攻击。
TLS连接可以用在每种服务器端认证的情况,在网络层对请求加密(保密,防止重放攻击,等)
TLSCA需要给(非验证)peer,验证器,和单独的客户端(或具有存储私钥的游览器)发放TLS证书的能力。最好,这些证书可以使用之前的类型来区分。各个类型的CA(如TLSCA,ECA,TCA)的TLS证书有可以通过中间CA(如,一个根CA的下属CA)发放。这里没有特定流量分析的问题,任意给定的TLS连接都可以相互验证,除了请求TLSCA的TLS证书的时候。
在当前的实现中,唯一的信任锚点是TLSCA的自签名证书来适应与所有三个(共址)服务器(即TLSCA,TCA和ECA)进行通信的单个端口限制。因此,与TLSCA的TLS握手来与TLSCA建立连接,所得到的会话密钥会传递给共址的TCA和ECA。因此,TCA和ECA的自签名证书的有效性的信任继承自对TLSCA的信任。在不提高TLSCA在其他CA之上的实现中,信任锚点需要由TLSCA和其他CA都认证的根CA替代。
客户端的注册和交易的创建是由非验证peer以在线钱包的角色全部执行的。集体的,终端用户利用注册证书
更具体的,当前使用对称密钥加密来提供交易保密性。在这种背景下,一个最主要的挑战是特定于区块链的设置,验证器需要在区块链的状态上打成共识,即,除了交易本身,还包括个人合同或链码的状态更新。虽然对于非机密链码这是微不足道的,对于机密链码,需要设计状态的加密机制,使得所得的密文语义安全,然而,如果明文状态是相同的那么他们就相等。
方法描述:成员服务为总账生成对称密钥(Kchain),这是在注册到区块链系统所有实体时发布的,如,客户端和验证实体已通过链的成员服务发放证书。在注册阶段,用户获取(像之前一样)一张注册证书,为用户ui记作Certui,每个验证器vj获取它的被记作Certvj的证书。
实体注册将得到提高,如下所示。除了注册证书,用户希望以匿名方式参与交易发放交易证书。为了简化我们把用户ui的交易证书记作TCertui。交易证书包含签名密钥对的公共部分记作(tpkui,tskui)。
为了防止密码分析和执行保密,下面的密钥层级被用来生成和验证保密的交易:为了提交保密交易(Tx)到总账,客户端首先选择一个nonce(N),这是需要提交区块链的所有交易中是唯一的,并通过以Kchain作为密钥,nonce作为输入的HMAC函数生成一个交易对称密钥(KTx))KTx=HMAC(Kchain,N)。对于KTx,客户端生成两个AES密钥:KTxCID当作HMAC(KTx,c1),KTxP当作HMAC(KTx,c2))分别加密链码名称或标识CID和代码(或payload)P.c1,c2是公共常量。nonce,加密的链码ID(ECID)和加密的Payload(EP)被添加到交易Tx结构中,即最终签名和认证的。下面的图显示了如何产生用于客户端的事务的加密密钥。这张图中的剪头表示HMAC的应用,源由密钥锁定和使用在箭头中的数量作为参数。部署/调用交易的密钥键分别用d/I表示。
为了验证客户端提交到区块链的保密交易Tx,验证实体首先通过和之前一样的Kchain和Tx.Nonce再生成KTxCID和KTxP来解密ECID和EP。一旦链码和Payload被恢复就可以处理交易了。
当V验证一个机密事务,相应的链码可以访问和修改链码的状态。V保持链码的状态加密。为了做到这一点,V生成如上图所示的对称密钥。让iTX是一个之前由保密交易dTx部署的保密的交易调用一个函数(注意iTx可以是dTx本身)在这种情况下,例如,dTx具有初始化链码状态的设置函数。然后V像下面一样生成两个对称密钥KIV和Kstate:
为了加密状态变量S,验证器首先生成IV像HMAC(KIV,crtstate)正确截断,其中crtstate是计数器值,并在每次同样链码调用时请求状态更新时增加。当链码执行终止是计数器丢弃。IV产生之后,V认证加密(即,GSM模式)S的值连接Nstate(实际上,Nstate只需要认证而不需要加密)。在所得的密文(CT),Nstate和IV被追加。为了解密加密状态CT||Nstate’,验证器首次生成对称密钥KdTX’,Kstate‘,然后解密CT。
IV的生成:任何底层共识算法是不可知的,所有的验证各方需要一种方法以产生相同的确切密文。为了做到这一点,需要验证使用相同的IV。重用具有相同的对称密钥相同的IV完全打破了底层密码的安全性。因此,前面所描述的方法制备。特别是,V首先通过计算HMAC(KdTX,c4||Nstate)派生的IV生成密钥KIV,其中c4是一个常数,并为(dTx,iTx)保存计数器crtstate初始设置为0。然后,每次必须生成一个新的密文,验证器通过计算HMAC(KIV,crtstate)作为输出生成新的IV,然后为crtstate增加1。
上述密钥层次结构的另一个好处是控制了审计的能力。例如,当发布Kchain会提供对整个供应链的读取权限,当只为交易的(dTx,iTx)发布Kstate访问只授予由iTx更新的状态,等等
下图展示一个部署和调用交易在目前在代码中的形式。
可以注意到,部署和调用交易由两部分组成:
obcpbft插件提供实现了CPI接口的模块,他可以配置运行PBFT还是Sieve共识协议。模块化来自于,在内部,obcpbft定义了innerCPI接口(即,innerconsensusprogramminginterface),现在包含在pbft-core.go中。
该innerCPI接口定义的所有PBFT内部共识(这里称为corePBFT并在pbft-core.go实现)和使用corePBFT的外部共识之间的相互作用。obcpbft包包含几个corePBFT消费者实现
总之,除了调用发送消息给其他peer(innerCPI.broadcast和innerCPI.unicast),innerCPI接口定义了给消费者暴露的共识协议。这使用了用来表示信息的原子投递的innerCPI.execute调用的一个经典的总序(原子)广播API[2]。经典的总序广播在externalvaliditychecks[2]中详细讨论(innerCPI.verify)和一个功能相似的对不可靠的领导失败的检查Ω[3](innerCPI.viewChange).
除了innerCPI,corePBFT定义了corePBFT的方法。corePBFT最重要的方法是request有效地调用总序广播原语[2]。在下文中,我们首先概述corePBFT的方法和innerCPI接口的明细。然后,我们简要地描述,这将在更多的细节Sieve共识协议。
下面的函数使用非递归锁来控制并发,因此可以从多个并行线程调用。然而,函数一般运行到完成,可能调用从CPI传入的函数。必须小心,以防止活锁。
接口中传递的consumer和ledger参数是一旦它们全部排好序后用来查询应用状态和调用应用请求的。参阅下面这些接口的相应部分。
fabric的主要接口是RESTAPI。RESTAPI允许应用注册用户,查询区块链,并发布交易。CLI为了开发,同样提供有效API的子集。CLI允许开发人员能够快速测试链码或查询交易状态。
应用程序通过RESTAPI与非验证的peer节点,这将需要某种形式的认证,以确保实体有适当的权限进行交互。该应用程序是负责实现合适的身份验证机制和peer节点随后将使用客户身份对发出消息签名。
fabricAPI设计涵盖的类别如下,虽然当前版本的其中一些实现不完整。[RESTAPI(#62-REST的API)节将说明API当前支持。
REST服务可以(通过配置)在验证和非验证peer被启用,但是建议在生产环境中只启用非验证peer的REST服务。
funcStartOpenchainRESTServer(server*oc.ServerOpenchain,devops*oc.Devops)这个函数读取core.yaml``peer处理的配置文件中的rest.address。rest.address键定义了peer的HTTPREST服务默认监听的地址和端口。
假定REST服务接收来已经认证的终端用户的应用请求。
块检索请求:
GEThost:port/chain/blocks/173块检索响应:
messageBlockchainInfo{uint64height=1;bytescurrentBlockHash=2;bytespreviousBlockHash=3;}区块链检索请求:
GEThost:port/chain区块链检索响应:
{"height":174,"currentBlockHash":"lIfbDax2NZMU3rG3cDR11OGicPLp1yebIkia33Zte9AnfqvffK6tsHRyKwsw0hZFZkCGIa9wHVkOGyFTcFxM5w==","previousBlockHash":"Vlz6Dv5OSy0OZpJvijrU1cmY2cNS5Ar3xX5DxAi/seaHHRPdssrljDeppDLzGx6ZVyayt8Ru6jO+E68IwMrXLQ=="}6.2.1.3链码API使用链码API来部署,调用和查询链码部署请求需要客户端提供path参数,执行文件系统中链码的目录。部署请求的响应要么是包含成功的链码部署确认消息要么是包含失败的原因的错误。它还含有所生成的链码的name域在消息中,这是在随后的调用和查询交易中使用的已部署链码的唯一标识。
部署请求:
POSThost:port/chaincode{"jsonrpc":"2.0","method":"deploy","params":{"type":"GOLANG","chaincodeID":{"path":"github.com/hyperledger/fabic/examples/chaincode/go/chaincode_example02"},"ctorMsg":{"function":"init","args":["a","1000","b","2000"]}},"id":"1"}部署响应:
启用安全的部署请求:
POSThost:port/chaincode{"jsonrpc":"2.0","method":"deploy","params":{"type":"GOLANG","chaincodeID":{"path":"github.com/hyperledger/fabic/examples/chaincode/go/chaincode_example02"},"ctorMsg":{"function":"init","args":["a","1000","b","2000"]},"secureContext":"lukas"},"id":"1"}该调用请求要求客户端提供一个name参数,这是之前从部署交易响应得到的。调用请求的响应要么是包含成功执行的确认消息,要么是包含失败的原因的错误。
调用请求:
POSThost:port/chaincode{"jsonrpc":"2.0","method":"invoke","params":{"type":"GOLANG","chaincodeID":{"name":"52b0d803fc395b5e34d8d4a7cd69fb6aa00099b8fabed83504ac1c5d61a425aca5b3ad3bf96643ea4fdaac132c417c37b00f88fa800de7ece387d008a76d3586"},"ctorMsg":{"function":"invoke","args":["a","b","100"]}},"id":"3"}调用响应:
启用安全的调用请求:
{"jsonrpc":"2.0","method":"invoke","params":{"type":"GOLANG","chaincodeID":{"name":"52b0d803fc395b5e34d8d4a7cd69fb6aa00099b8fabed83504ac1c5d61a425aca5b3ad3bf96643ea4fdaac132c417c37b00f88fa800de7ece387d008a76d3586"},"ctorMsg":{"function":"invoke","args":["a","b","100"]},"secureContext":"lukas"},"id":"3"}查询请求需要在客户端提供一个name参数,这是之前在部署交易响应中得到了。查询请求的响应取决于链码的实现。响应要么是包含成功执行的确认消息,要么是包含失败的原因的错误。在成功执行的情况下,响应将包含链码请求的状态变量的值
查询请求:
POSThost:port/chaincode/{"jsonrpc":"2.0","method":"query","params":{"type":"GOLANG","chaincodeID":{"name":"52b0d803fc395b5e34d8d4a7cd69fb6aa00099b8fabed83504ac1c5d61a425aca5b3ad3bf96643ea4fdaac132c417c37b00f88fa800de7ece387d008a76d3586"},"ctorMsg":{"function":"query","args":["a"]}},"id":"5"}查询响应:
启用安全的查询请求:
{"jsonrpc":"2.0","method":"query","params":{"type":"GOLANG","chaincodeID":{"name":"52b0d803fc395b5e34d8d4a7cd69fb6aa00099b8fabed83504ac1c5d61a425aca5b3ad3bf96643ea4fdaac132c417c37b00f88fa800de7ece387d008a76d3586"},"ctorMsg":{"function":"query","args":["a"]},"secureContext":"lukas"},"id":"5"}6.2.1.4网络API使用网络API来获取组成区块链fabric的peer节点的网络信息
/network/peers端点返回的目标peer节点的所有现有的网络连接的列表。该列表包括验证和非验证peer。peer的列表被返回类型PeersMessage是包含PeerEndpoint的数组,在第[3.1.1](#311-discovery-messages发现的消息)定义。
messagePeersMessage{repeatedPeerEndpointpeers=1;}网络请求:
GEThost:port/network/peers网络响应:
/registrar端点使用与CA注册用户所需的秘密payload定义如下。注册请求的响应可以是一个成功的注册的确认或包含失败的原因的错误。
messageSecret{stringenrollId=1;stringenrollSecret=2;}注册请求:
POSThost:port/registrar{"enrollId":"lukas","enrollSecret":"NPKYL39uKbkj"}注册响应:
注册验证请求:
GEThost:port/registrar/jim注册验证返回:
{"OK":"Userjimisalreadyloggedin."}注册验证请求:
GEThost:port/registrar/alex注册验证返回:
删除注册请求:
DELETEhost:port/registrar/lukas删除注册返回:
{"OK":"Deletedlogintokenanddirectoryforuserlukas."}GET/registrar/{enrollmentID}/ecert端点用于检索从本地存储给定用户的登记证书。如果目标用户已与CA注册,响应将包括注册证书的URL-encoded版本。如果目标用户尚未注册,将返回一个错误。如果客户希望使用检索后返回的注册证书,请记住,它必须是URL-decoded。
注册证书检索请求:
GEThost:port/registrar/jim/ecert注册证书检索响应:
交易证书检索请求:
GEThost:port/registrar/jim/tcert交易证书检索响应:
GEThost:port/registrar/jim/tcertcount=5交易证书检索响应:
交易检索请求:
GEThost:port/transactions/f5978e82-6d8c-47d1-adec-f18b794f570e交易检索响应:
ToseewhatCLIcommandsarecurrentlyavailableintheimplementation,executethefollowing:
要查看当前可用的CLI命令,执行如下命令
cd$GOPATH/src/github.com/hyperledger/fabic/peer./peer你可以获得和下面类似的响应:
Usage:peer[command]AvailableCommands:peerRunthepeer.statusStatusofthepeer.stopStopthepeer.loginLoginuseronCLI.vmVMfunctionalityonthefabric.chaincodechaincodespecificcommands.helpHelpaboutanycommandFlags:-h,--help[=false]:helpUse"peer[command]--help"formoreinformationaboutacommand.Someoftheavailablecommandlineargumentsforthepeercommandarelistedbelow:
上述所有命令并非完全在当前版本中实现。如下所述全面支持的命令是有助于链码的开发和调试的。
所有peer节点的设置都被列在core.yaml这个peer处理的配置文件中,可能通过命令行的环境变量而被修改。如,设置peer.id或peer.ddressAutoDetect,只需要传递CORE_PEER_ID=vp1和CORE_PEER_ADDRESSAUTODETECT=true给命令行。
peerCLI命令在开发和生产环境中都会执行peer处理。开发模式会在本地运行单个peer节点和本地的链码部署。这使得在链码开修改和调试代码,不需要启动一个完整的网络。在开发模式启动peer的一个例子:
./peerpeer--peer-chaincodedev在生产环境中启动peer进程,像下面一样修改上面的命令:
./peerloginjim该命令会提示输入密码,密码必须为此用户使用证书颁发机构注册登记的密码相匹配。如果输入的密码不正确的密码匹配,将导致一个错误。
22:21:31.246[main]login->INFO001CLIclientlogin...22:21:31.247[main]login->INFO002LocaldatastoreforclientloginToken:/var/hyperledger/production/client/Enterpasswordforuser'jim':************22:21:40.183[main]login->INFO003Logginginuser'jim'onCLIinterface...22:21:40.623[main]login->INFO004Storinglogintokenforuser'jim'.22:21:40.624[main]login->INFO005Loginsuccessfulforuser'jim'.您也可以与-p参数来提供用户的密码。下面是一个例子。
./peerloginjim-p1234566.3.1.3链码部署deployCLI命令为链码和接下来的部署包到验证peer创建docker镜像。如下面的例子。
./peerchaincodedeploy-ujim-pgithub.com/hyperledger/fabric/example/chaincode/go/chaincode_example02-c'{"Function":"init","Args":["a","100","b","200"]}'6.3.1.4链码调用invokeCLI命令执行目标来代码中的指定函数。如下:
./peerchaincodeinvoke-ujim-n
例如,使用Node.js的一个BluemixPaaS的应用程序可能有一个Web前端用户界面或与Cloudant数据服务后端模型中的原生移动应用。控制逻辑可以被1或多个链码交互以处理对区块链交易。