暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

fabric交易解析

求真得趣 2021-04-17
856


区块链中不同的链有不同的交易形式和交易结构,本文采用fabric v2.2版本对fabric的交易做一个具体分析,以便跟深入的了解fabric。


fabric的交易结构都是通过protobuf定义,不同的结构之间通过byte数组来链接,交易使用的是GRPC通信。具体的proto文件在项目https://gitclone.com/github.com/hyperledger/fabric-protos中定义,生成的.pb.go文件位于https://gitclone.com/github.com/hyperledger/fabric-protos-go中。通过查看这两个项目里的源码或者向fabric节点请求交易,可以看到返回的结构中基本是字节数组,下面以一个完整的已经处理的交易对象做一个具体的说明。


一个处理的交易由一个交易信封和一个验证码组成,交易信封封装了整个交易,验证码标识这个交易是否有效,其中0表示有效,其他表示无效。
    message ProcessedTransaction {
        // An Envelope which includes a processed transaction
        common.Envelope transactionEnvelope = 1;


        // An indication of whether the transaction was validated or invalidated by committing peer
        int32 validationCode = 2;
    }

    交易信封里包含交易负载字节流和一个签名
      message Envelope {
          // A marshaled Payload
          bytes payload = 1;


          // A signature by the creator specified in the Payload header
          bytes signature = 2;
      }


      通过 protobuf 可以反序列化上面payload 字节数组可以得到交易负载对象,里面包括了交易头部和交易数据

        message Payload {
            // Header is included to provide identity and prevent replay
            Header header = 1;


            // Data, the encoding of which is defined by the type in the header
            bytes data = 2;
        }



        交易头部包含通道头部字节流和签名头部字节流
          message Header {
              bytes channel_header = 1;
              bytes signature_header = 2;
          }

          通过 protobuf 可以反序列化上面 channel_header 字节流为通道头部对象,包含了类型、版本、时间戳、通道ID 、交易ID、 时期、 扩展字节数组、TLS证书Hash
            message ChannelHeader {
            int32 type = 1; // Header types 0-10000 are reserved and defined by HeaderType


                // Version indicates message protocol version
                int32 version = 2;


                // Timestamp is the local time when the message was created
                // by the sender
                google.protobuf.Timestamp timestamp = 3;


                // Identifier of the channel this message is bound for
                string channel_id = 4;


                // An unique identifier that is used end-to-end.
                //  -  set by higher layers such as end user or SDK
                //  -  passed to the endorser (which will check for uniqueness)
                //  -  as the header is passed along unchanged, it will be
                //     be retrieved by the committer (uniqueness check here as well)
                //  -  to be stored in the ledger
                string tx_id = 5;


                // The epoch in which this header was generated, where epoch is defined based on block height
                // Epoch in which the response has been generated. This field identifies a
                // logical window of time. A proposal response is accepted by a peer only if
                // two conditions hold:
                // 1. the epoch specified in the message is the current epoch
                // 2. this message has been only seen once during this epoch (i.e. it hasn't
                //    been replayed)
                uint64 epoch = 6;


                // Extension that may be attached based on the header type
                bytes extension = 7;


                // If mutual TLS is employed, this represents
                // the hash of the client's TLS certificate
                bytes tls_cert_hash = 8;
            }


            通过 protobuf 可以反序列化上面 signature_header 字节流为签名头部对象,包含了一个创作者字节数组和一个随机数

              message SignatureHeader {
                  // Creator of the message, a marshaled msp.SerializedIdentity
                  bytes creator = 1;


                  // Arbitrary number that may only be used once. Can be used to detect replay attacks.
                  bytes nonce = 2;
              }

              通过protobuf 可以反序列化上面 creator 字节流为创作者对象,包含了MSP ID和身份序列化数组,身份序列化数组反序列化后可以获得签名证书里的字段
                message SerializedIdentity {
                    // The identifier of the associated membership service provider
                    string mspid = 1;


                    // the Identity, serialized according to the rules of its MPS
                    bytes id_bytes = 2;
                }

                至此,payload 对象的头部结构反序列化结束




                下面对 payload 对象的数据字节数组进行反序列化


                通过 protobuf 可以反序列化上面payload 里的 data 字节数组可以得到交易对象,里面包括了交易行为数组;每个交易行为包括了头部字节数组和负载字节数组

                  message Transaction {


                  // The payload is an array of TransactionAction. An array is necessary to
                  // accommodate multiple actions per transaction
                  repeated TransactionAction actions = 1;
                  }


                  message TransactionAction {
                  // The header of the proposal action, which is the proposal header
                      bytes header = 1;
                      
                  // The payload of the action as defined by the type in the header For
                  // chaincode, it's the bytes of ChaincodeActionPayload
                  bytes payload = 2;
                  }

                  通过 protobuf 可以反序列化上面 header 字节数组可以得到一个签名头部对象对象,具体结构和上面一样,包含一个创作者字节数组和一个随机数,创造者字节数组包含MSP ID和身份序列化数组,身份序列化数组反序列化后可以获得签名证书里的字段

                  通过 protobuf 可以反序列化上面payload 字节数组可以得到链码行为负载对象,里面包括了链码提议负载字节数组和链码背书行为
                    message ChaincodeActionPayload {
                    // This field contains the bytes of the ChaincodeProposalPayload message from
                    // the original invocation (essentially the arguments) after the application
                    // of the visibility function. The main visibility modes are "full" (the
                    // entire ChaincodeProposalPayload message is included here), "hash" (only
                    // the hash of the ChaincodeProposalPayload message is included) or
                    // "nothing". This field will be used to check the consistency of
                    // ProposalResponsePayload.proposalHash. For the CHAINCODE type,
                    // ProposalResponsePayload.proposalHash is supposed to be H(ProposalHeader ||
                    // f(ChaincodeProposalPayload)) where f is the visibility function.
                    bytes chaincode_proposal_payload = 1;


                    // The list of actions to apply to the ledger
                    ChaincodeEndorsedAction action = 2;
                    }

                    通过 protobuf 可以反序列化上面 chaincode_proposal_payload 字节数组可以得到链码提议负载对象,里面包括了输入字节数组和一个临时表
                      message ChaincodeProposalPayload {
                      // Input contains the arguments for this invocation. If this invocation
                      // deploys a new chaincode, ESCC/VSCC are part of this field.
                      // This is usually a marshaled ChaincodeInvocationSpec
                      bytes input = 1;


                      // TransientMap contains data (e.g. cryptographic material) that might be used
                      // to implement some form of application-level confidentiality. The contents
                      // of this field are supposed to always be omitted from the transaction and
                      // excluded from the ledger.
                      map<string, bytes> TransientMap = 2;
                      }

                      通过 protobuf 可以反序列化上面 input 字节数组可以得到链码调用规范对象,里面包括了链码规范对象;链码规范对象里包含了类型、链码ID对象、链码输入对象和超时时间戳;链码ID对象包含了链码路径、名字和版本;链码输入对象包含了参数字节数组、描述和是否初始化
                        message ChaincodeInvocationSpec {
                        // Prevent removed tag re-use
                        reserved 2;
                        reserved "id_generation_alg";


                        ChaincodeSpec chaincode_spec = 1;
                        }


                        message ChaincodeSpec {


                        enum Type {
                        UNDEFINED = 0;
                        GOLANG = 1;
                        NODE = 2;
                        CAR = 3;
                        JAVA = 4;
                        }


                        Type type = 1;
                        ChaincodeID chaincode_id = 2;
                        ChaincodeInput input = 3;
                        int32 timeout = 4;
                        }


                        message ChaincodeID {
                        //deploy transaction will use the path
                        string path = 1;


                        //all other requests will use the name (really a hashcode) generated by
                        //the deploy transaction
                        string name = 2;


                        //user friendly version name for the chaincode
                        string version = 3;
                        }


                        message ChaincodeInput {
                        repeated bytes args = 1;
                        map<string, bytes> decorations = 2;


                        // is_init is used for the application to signal that an invocation is to be routed
                        // to the legacy 'Init' function for compatibility with chaincodes which handled
                        // Init in the old way. New applications should manage their initialized state
                        // themselves.
                        bool is_init = 3;
                        }

                        下面介绍下链码背书行为对象,背书行为对象包括了一个提议响应负载字节数组和背书对象数组;每个背书对象包含了一个背书身份的字节数组和一个签名字节数组
                          message ChaincodeEndorsedAction {
                          // This is the bytes of the ProposalResponsePayload message signed by the
                          // endorsers. Recall that for the CHAINCODE type, the
                          // ProposalResponsePayload's extenstion field carries a ChaincodeAction
                          bytes proposal_response_payload = 1;


                          // The endorsement of the proposal, basically the endorser's signature over
                          // proposalResponsePayload
                          repeated Endorsement endorsements = 2;
                          }


                          message Endorsement {
                          // Identity of the endorser (e.g. its certificate)
                          bytes endorser = 1;


                          // Signature of the payload included in ProposalResponse concatenated with
                          // the endorser's certificate; ie, sign(ProposalResponse.payload + endorser)
                          bytes signature = 2;
                          }

                          通过 protobuf 可以反序列化上面 proposal_response_payload 字节数组可以得到提议响应负载对象,里面包括了提议哈希和扩展字节数组
                            message ProposalResponsePayload {
                            // Hash of the proposal that triggered this response. The hash is used to
                            // link a response with its proposal, both for bookeeping purposes on an
                            // asynchronous system and for security reasons (accountability,
                            // non-repudiation). The hash usually covers the entire Proposal message
                            // (byte-by-byte).
                            bytes proposal_hash = 1;


                            // Extension should be unmarshaled to a type-specific message. The type of
                            // the extension in any proposal response depends on the type of the proposal
                            // that the client selected when the proposal was initially sent out. In
                            // particular, this information is stored in the type field of a Header. For
                            // chaincode, it's a ChaincodeAction message
                            bytes extension = 2;
                            }

                            通过 protobuf 可以反序列化上面 extension 字节数组可以得到链码行为对象,里面包括了结果字节数组、事件字节数组、响应对象、链码ID对象;事件字节流的解析对象为链码事件,有时为空,这儿不做深入解析;响应对象包括了状态、消息和响应负载字节数组,不同的响应字节数组内容不同有时为空;链码ID对象和上面介绍的一样,包括了链码路径、名字和版本
                              message ChaincodeAction {
                              reserved 5;
                              reserved "token_operations";


                              // This field contains the read set and the write set produced by the
                              // chaincode executing this invocation.
                              bytes results = 1;


                              // This field contains the event generated by the chaincode.
                              // Only a single marshaled ChaincodeEvent is included.
                              bytes events = 2;


                              // This field contains the result of executing this invocation.
                              Response response = 3;


                              // This field contains the ChaincodeID of executing this invocation. Endorser
                              // will set it with the ChaincodeID called by endorser while simulating proposal.
                              // Committer will validate the version matching with latest chaincode version.
                              // Adding ChaincodeID to keep version opens up the possibility of multiple
                              // ChaincodeAction per transaction.
                              ChaincodeID chaincode_id = 4;
                              }


                              message Response {
                              // A status code that should follow the HTTP status codes.
                              int32 status = 1;


                              // A message associated with the response code.
                              string message = 2;


                              // A payload that can be used to include metadata with this response.
                              bytes payload = 3;
                              }

                              通过 protobuf 可以反序列化上面 results 字节数组可以得到交易读写集对象,里面包括了数据模型和命名空间读写集;命名空间读写集包括了一个命名空间、一个读写集字节数组和一个集合哈希读写集
                                message TxReadWriteSet {
                                enum DataModel {
                                KV = 0;
                                }
                                DataModel data_model = 1;
                                repeated NsReadWriteSet ns_rwset = 2;
                                }


                                message NsReadWriteSet {
                                string namespace = 1;
                                bytes rwset = 2; Data model specific serialized proto message (e.g., kvrwset.KVRWSet for KV and Document data models)
                                repeated CollectionHashedReadWriteSet collection_hashed_rwset = 3;
                                }


                                message CollectionHashedReadWriteSet {
                                string collection_name = 1;
                                bytes hashed_rwset = 2; Data model specific serialized proto message (e.g., kvrwset.HashedRWSet for KV and Document data models)
                                bytes pvt_rwset_hash = 3; Hash of entire private read-write set for a specific collection. This helps in authenticating the private read-write set efficiently
                                }

                                通过 protobuf 可以反序列化上面 rwset 字节数组可以得到键值读写集对象,里面包括了键值读对象数组、范围查询对象数组、键值写对象数组和键值元数据写对象数组
                                  message KVRWSet {
                                  repeated KVRead reads = 1;
                                  repeated RangeQueryInfo range_queries_info = 2;
                                  repeated KVWrite writes = 3;
                                  repeated KVMetadataWrite metadata_writes = 4;
                                  }


                                  message KVRead {
                                  string key = 1;
                                  Version version = 2;
                                  }


                                  message KVWrite {
                                  string key = 1;
                                  bool is_delete = 2;
                                  bytes value = 3;
                                  }


                                  message KVMetadataWrite {
                                  string key = 1;
                                  repeated KVMetadataEntry entries = 2;
                                  }


                                  message RangeQueryInfo {
                                  string start_key = 1;
                                  string end_key = 2;
                                  bool itr_exhausted = 3;
                                  oneof reads_info {
                                  QueryReads raw_reads = 4;
                                  QueryReadsMerkleSummary reads_merkle_hashes = 5;
                                  }
                                  }


                                  message QueryReads {
                                  repeated KVRead kv_reads = 1;
                                  }

                                  以上就是一个fabric交易的完整结构解析,相对于其他链的交易结构比较复杂。

                                  文章转载自求真得趣,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                  评论