当前位置:首页 » 区块链精品文章 » 正文

3.4 消息协议结构

1729 人参与  2018年10月15日 11:06  分类 : 区块链精品文章  评论

3.4.1 信封消息结构

信封消息是认证内容中最基本的单元。它由一个消息负载(Payload)和一个签名(Signature)组成。


// 信封包含一个带有签名的负载,以便认证该消息
message Envelope {
   // 编组的负载
   bytes payload = 1;

   // 负载头中指定创建者签名
   bytes signature = 2;
}
   // 负载是消息内容(允许签名)
message Payload {
   // 负载头部,提供身份验证并防止重放
   Header header = 1;

   // 数据,其编码由头的类型定义
   bytes data = 2;
}


负载包含:

1)消息头部。头部带有类型,描述负载的性质以及如何解组数据字段。此外,头部还包含创建者的信息和随机数,以及用来标识时间逻辑窗口的时期信息。只有在两个条件都成立的情况下,Peer节点才能接受一个信封。

①消息中指定的时期信息是当前窗口期;

②该负载在该周期内只看到一次(即没有重放)。

2)数据字段的类型由头部指定。头部消息的组织方式如下所示:


message Header {
   bytes channel_header = 1;
   bytes signature_header = 2;
}
   // 通道头是一个通用的预防重放和身份标识的消息,它包含在一个被签名的负载之中
message ChannelHeader {
   int32 type = 1; // 头类型0-10000由HeaderType保留和定义

   // 版本指示消息协议版本
   int32 version = 2;

   // 时间戳是发件人创建消息的本地时间
   google.protobuf.Timestamp timestamp = 3;

   // 该消息绑定通道的标识符
   string channel_id = 4;

   // 端到端使用唯一的标识符
   //  -  由较高层设置,如最终用户或SDK
   //  -  传递给背书节点(将检查唯一性)
   //  -  当消息正确传递时,它将被记账节点检索(也会检查唯一性)
   //  -  存储于账本中
   string tx_id = 5;

   // 时期信息基于区块高度而定义,此字段标识时间的逻辑窗口
   // 只有在两个条件都成立的情况下,对方才接受提案响应
   // 1. 消息中指定的时期信息是当前时期
   // 2. 该消息在该时期内只看到一次(即没有重放)
   uint64 epoch = 6;

   // 根据消息头类型附加的扩展
   bytes extension = 7;
}
enum HeaderType {
   MESSAGE = 0;                   // 非透明消息
   CONFIG = 1;                    // 通道配置
   CONFIG_UPDATE = 2;             // 通道配置更新
   ENDORSER_TRANSACTION = 3;      // SDK提交背书
   ORDERER_TRANSACTION = 4;       // 排序管理内部使用
   DELIVER_SEEK_INFO = 5;         // 指示Deliver API查找信息
   CHAINCODE_PACKAGE = 6;         // 链码打包安装
}
message SignatureHeader {
   // 消息的创建者,链的证书
   bytes creator = 1;

   // 只能使用一次的任意数字,可用于检测重放攻击
   bytes nonce = 2;
}


信封消息结构对于验证负载的签名是必要的。否则,对于大载荷消息,就必须连接所有的载荷再进行签名验证,这往往成本很高。

经过排序后,批量的信封消息交付给记账节点进行验证,通过验证后的数据被记录到账本之中。

3.4.2 配置管理结构

区块链有与之相关的配置,配置设置在创世区块之中,但可能在后续被修改。该配置信息在类型为 CONFIGURATION_TRANSACTION的信封消息中编码。配置信息本身就是区块的一个单独交易。配置信息交易没有任何依赖,所以每个配置信 息交易必须包含对于链的全量数据,而不是增量数据。使用全量数据更容易引导新的peer或排序节点,也便于未来进行裁剪工作。

CONFIGURATION_TRANSACTION类型的信封消息具有ConfigurationEnvelope类型的负载数据。它定义为:


message ConfigurationEnvelope {
   repeated SignedConfigurationItem Items = 3;
}


配置信息的信封消息有与之关联的序列号和链ID。每次修改配置序列号必须递增,这可以作为防止重放攻击的一个简单机制。配置信息的信封中会嵌入一系列的SignedConfigurationItems,定义如下:


message SignedConfigurationItem {
   bytes ConfigurationItem = 1;
   repeated Envelope Signatures = 2;
}


因为SignedConfigurationItem必须支持多个签名,所以它包含一组重复的信封消息。这些消息中每个都有一个类型为CONFIGURATION_ITEM的头部。负载的数据部分在ConfigurationItem中保存,定义为:


message ConfigurationItem {
   enum ConfigurationType {
       Policy = 0;
       Chain = 1;
       Orderer = 2;
       Fabric = 3;
           }
   bytes ChainID = 1;
   uint64 LastModified = 2;
   ConfigurationType Type = 3;
   string ModificationPolicy = 4;
   string Key = 5;
   bytes Value = 6;
}


Type提供了配置项的范围和编码信息。LastModified字段设置为上一次配置项被修改时配置信息信封中 的序列号。ModificationPolicy指向一个已经命名的策略,用来对将来的签名进行评估,以确定修改是否被授权。Key和Value字段分别 用作引用配置项及其内容。

修改配置包含以下内容:

·检索现有配置;

·递增配置信息信封消息中的序列号;

·修改所需的配置项,将每个配置项的LastModified字段设置为递增后的序列号;

·更新SignedConfigurationItem中的签名信息;

·将签名后的信封信息提交给排序节点。

配置管理员将验证:

·所有配置项和信封都指向正确的链;

·添加或修改了哪些配置项;

·有没有现有的配置项被忽略(即提交的数据是全集);

·所有配置更改的LastModification都等于信封消息中的序列号;

·所有配置更改均符合相应的修改策略。

任何有权更改配置项的角色都可以构建新的配置信息交易。修改配置项将更新序列号并产生新的创始区块,这将引导新加入网络的各种节点。

3.4.3 背书流程结构

当Envelope.data中携带与链码相关的消息时,使用ENDORSER_TRANSACTION类型。

获得批准的ENDORSER_TRANSACTION负载流程如下。

首先,客户端向所有相关的背书节点发送提案消息(提案基本上是要进行一些影响账本的动作)。

然后,每个背书节点向客户端发送一个提案响应消息。提案响应包含背书结果的成功/错误码、应答负载和签名。应答负载之中包含提案的哈希值信息,用此信息可以将提案和针对该提案的应答安全地连接起来。

最后,客户端将背书结果写入交易中,签名并发送到排序服务。

在接下来的章节中,我们将详细介绍消息及其流程。

1.交易提案结构

一个提案消息包含头部(包含描述它的一些元数据,例如类型、调用者的身份、时间、链的ID、加密的随机数)和不透明的负载:


message SignedProposal {
   // 提案
   bytes proposal_bytes = 1;
   // 对提案进行签名,该签名将和头部的创建者标识进行验证
   bytes signature = 2;
}
message Proposal {
   // 提案头部
   bytes header = 1;
   // 提案负载,具体结构由头部的类型决定
   bytes payload = 2;
   // 提案的可选扩展。对于CHAINCODE类型的消息,其内容可能是ChaincodeAction消息
   bytes extension = 3;
}


一个提案发送给背书节点进行背书。该提案包含:

·头部,可以解组为头部信息;

·负载,由头部的类型决定;

·扩展,由头部的类型决定。

和信封消息结构类似,这种SignedProposal消息结构也是重要的。否则,对于大载荷消息,我们必须连接所有的载荷再进行签名验证,这往往成本很高。

当背书节点收到签名后的提案消息后,它将验证消息中的签名。验证签名需要以下步骤。

1)预验证用户生成签名证书的有效性。一旦SignedProposal.proposal_bytes和Proposal.header都解组成功,就可以认为证书基本是可用的。虽然这种在证书验证前的解组操作可能并不太理想,但是在这个阶段可以过滤掉证书过期的情况。

2)验证证书是否可信(证书是否由受信任的CA签名),并允许交易(能否通过ACL检查)。

3)验证SignedProposal.proposal_bytes上的签名是否有效。

4)检测重放攻击。

以下是当ChainHeader的类型为ENDORSER_TRANSACTION时的消息:


message ChaincodeHeaderExtension {
   // 控制提案的负载在最终交易和账本中的可见程度
   bytes payload_visibility = 1;
   // 要定位的链代码ID
   ChaincodeID chaincode_id = 2;
}


ChaincodeHeaderExtension消息用于指定要调用的链码以及应在账本中呈现的内容。理想情况下,payload_visibility是可配置的,支持至少3种主要可见性模式:

·负载所有字节都可见;

·只有负载的哈希值可见;

·任何东西都不可见。

注意,可见性功能可能也会由ESCC设置,此时本字段将会被覆盖。另外本字段也将影响ProposalResponsePayload.proposalHash的内容。


message ChaincodeProposalPayload {
   // 包含调用链码的参数,
   bytes input  = 1;
   // 用于实现某些应用程序级的加密数据
   map<string, bytes> TransientMap = 2;
}


ChaincodeProposalPayload消息包含调用链码的参数。TransientMap字段的内容应始终从信封消息中省略掉,并不记录到账本之中。


message ChaincodeAction {
   // 调用链码产生的读/写集
   bytes results = 1;
   // 调用链码产生的事件
   bytes events = 2;
   // 调用链码的结果
   Response response = 3;
   // 含链ID、背书节点在模拟执行提案时设置
   // 账本节点将验证版本号是否与链码最新版本匹配,含有链ID信息将支持单个交易打开多个链码
   ChaincodeID chaincode_id = 4;
}


ChaincodeAction消息包含执行链码所产生的动作和事件。results字段包含读取集合,events字段包含由链码执行生成的事件。

2.提案响应结构

提案响应消息从背书节点返回给提案客户端。背书节点使用该消息表达对于交易提案的处理结果。应答结果可能是成功也可能是失败,另外还会包含动作描述和背书节点的签名。如果足够数量的背书节点同意相同的动作并进行签名,则可以生成负载消息,并发送给排序节点。


message ProposalResponse {
   // 消息协议版本
   int32 version = 1;
   // 消息创建时间,由消息发送者定义
   google.protobuf.Timestamp timestamp = 2;
   // 某个动作的背书是否成功
   Response response = 4;
   // 负载,ProposalResponsePayload字节序列
   bytes payload = 5;
   // 提案的具体背书内容,基本上就是背书节点的签名
   Endorsement endorsement = 6;
}
message ProposalResponsePayload {
   // 触发此应答交易提案的哈希值
   bytes proposal_hash = 1;
   // 扩展内容,应该解组为特定类型的消息
   bytes extension = 2;
}
message Endorsement {
   // 背书节点身份(例如,证书信息)
   bytes endorser = 1;
   // 签名,对提案应答负载和背书节点证书这两个内容进行签名
   bytes signature = 2;
}


ProposalResponsePayload消息是提案响应的负载部分。这个消息是客户端请求和背书节点动作之间的“桥梁”。对于链码来说,它包含一个表示提议的哈希值proposal_hash,以及表示链码状态变化和事件extension字段。

proposal_hash字段将交易提案和提案响应两者对应起来,即为了实现异步系统的记账功能也为了追责和抗抵赖的安全诉求。哈希值通常会覆盖整个提案消息的所有字节中。但是,这样实现就意味着只有获得完整的提案消息才能验证哈希值的正确性。

出于保密原因,使用链码不太可能将提案的负载直接存储在账本中。例如,类型为ENDORSER_TRANSACTION的消息,需要将提案的头部和负载分开进行处理:头部总是进行完整散列的,而负载则可能进行完整散列或对哈希值再进行散列,或者根本不进行散列。

3.背书交易结构

客户端获得足够的背书后,可以将这些背书组合成一个交易信息。这个交易信息可以设置为负载信息的数据字段。以下是在这种情况下要使用的具体消息:


message Transaction {
   // 负载是一个TransactionAction数组,每个交易需要一个数组来适应多个动作
   repeated TransactionAction actions = 1;
}

message TransactionAction {
   // 提案头部
   bytes header = 1;
   // 负载由头部类型决定,它是ChaincodeActionPayload字节序列
   bytes payload = 2;
}


TransactionAction消息将提案绑定到其动作。它的头部是SignatureHeader消息,它的负载是ChaincodeActionPayload消息。


message ChaincodeActionPayload {
   // ChaincodeProposalPayload消息的字节序列,内容来自链码原始调用的参数
   bytes chaincode_proposal_payload = 1;
   // 应用于账本的动作列表
   ChaincodeEndorsedAction action = 2;
}


ChaincodeActionPayload消息携带chaincodeProposalPayload和已经 通过背书的动作以应用于账本。主要的可见性模式是“full”(整个ChaincodeProposalPayload消息包含在这里)、“hash” (仅包含ChaincodeProposalPayload消息的哈希值)或“nothing”。该字段将用于检查 ProposalResponsePayload.proposalHash的一致性。此外,action字段包含应用于账本的动作列表。


message ChaincodeEndorsedAction {
   // 由背书节点签名的ProposalResponsePayload消息字节序列
   bytes proposal_response_payload = 1;
   // 提案背书,基本上是背书节点的签名
   repeated Endorsement endorsements = 2;
}


ChaincodeEndorsedAction消息承载有关具体提案的背书信息。 proposalResponsePayload是由背书节点签名的,对于ENDORSER_TRANSACTION类 型,ProposalResponsePayload的extenstion字段会带有一个ChaincodeAction。此 外,endorsements字段包含提案已经收到的背书信息。

来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=1083

区块链是什么  

微信号:qq444848023    QQ号:444848023

加入【我是码农】QQ群:864689844(加群验证:我是码农)

<< 上一篇 下一篇 >>

网站分类

标签列表

最近发表

全站首页 | 数据结构 | 区块链| 大数据 | 机器学习 | 物联网和云计算 | 面试笔试

本站资源大部分来自互联网,版权归原作者所有!