Fabric 账本数据块结构解析(二)(如何解析账本中的配置块数据)

id:BSN_2021 公众号:BSN 研习社 作者:红枣科技高晨曦

背景:BSN公网Fabric联盟链的出现降低了使用区块链的难度,但在部分特定环境中,仍需要自己搭建Fabric环境时,了解Fabric中的配置信息能够帮助搭建或在运行中调整自己的链环境配置。
目标:了解账本数据结构中的配置信息,更好的维护自己的Fabric环境。
对象: 使用BSN联盟链Fabric的开发人员、运维人员。
什么是配置块 【Fabric 账本数据块结构解析(二)(如何解析账本中的配置块数据)】配置块是Fabric Channel的重要数据,里面包含了当前Channel的配置信息,配置块包含初始配置块,以及在链运行过程中的修改配置信息而提交的交易生成的配置区块信息。
它包含了当前channel的证书信息、策略信息、以及其他关键链配置信息。
如何查询配置块 在前一篇中我们了解到在区块的Metadata中包含了5组数据,其中第一组为Ordere签名信息,第二组即为当前channel的最后一个配置块的块号。其中Orderer签名信息中也包含最后一个配置块信息,
common.LastConfig序列化后的Bytes,我们可以查询最新的区块,得到最后一个配置块号,然后直接查询该块即可。
// OrdererBlockMetadata defines metadata that is set by the ordering service. type OrdererBlockMetadata struct { LastConfig*LastConfig `protobuf:"bytes,1,opt,name=last_config,json=lastConfig,proto3" json:"last_config,omitempty"` ConsenterMetadata[]byte`protobuf:"bytes,2,opt,name=consenter_metadata,json=consenterMetadata,proto3" json:"consenter_metadata,omitempty"` }// LastConfig is the encoded value for the Metadata message which is encoded in the LAST_CONFIGURATION block metadata index type LastConfig struct { Indexuint64`protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` }

在查到配置块之后,我们解析区块内的交易信息,根据交易的ChannelHeader中的Type来判断该交易是否是配置信息。HeaderType包含以下类型:
const ( HeaderType_MESSAGEHeaderType = 0 HeaderType_CONFIGHeaderType = 1 HeaderType_CONFIG_UPDATEHeaderType = 2 HeaderType_ENDORSER_TRANSACTION HeaderType = 3 HeaderType_ORDERER_TRANSACTIONHeaderType = 4 HeaderType_DELIVER_SEEK_INFOHeaderType = 5 HeaderType_CHAINCODE_PACKAGEHeaderType = 6 )

其中HeaderType_CONFIGHeaderType_CONFIG_UPDATE是配置块交易类型。
配置块包含哪些数据 当交易配型为HeaderType_CONFIG或者HeaderType_CONFIG_UPDATE时,Envelope的data即为ConfigEnvelope序列化之后的Bytes,
以下为proto中的ConfigEnvelope结构
// ConfigEnvelope is designed to contain _all_ configuration for a chain with no dependency // on previous configuration transactions. // // It is generated with the following scheme: //1. Retrieve the existing configuration //2. Note the config properties (ConfigValue, ConfigPolicy, ConfigGroup) to be modified //3. Add any intermediate ConfigGroups to the ConfigUpdate.read_set (sparsely) //4. Add any additional desired dependencies to ConfigUpdate.read_set (sparsely) //5. Modify the config properties, incrementing each version by 1, set them in the ConfigUpdate.write_set //Note: any element not modified but specified should already be in the read_set, so may be specified sparsely //6. Create ConfigUpdate message and marshal it into ConfigUpdateEnvelope.update and encode the required signatures //a) Each signature is of type ConfigSignature //b) The ConfigSignature signature is over the concatenation of signature_header and the ConfigUpdate bytes (which includes a ChainHeader) //5. Submit new Config for ordering in Envelope signed by submitter //a) The Envelope Payload has data set to the marshaled ConfigEnvelope //b) The Envelope Payload has a header of type Header.Type.CONFIG_UPDATE // // The configuration manager will verify: //1. All items in the read_set exist at the read versions //2. All items in the write_set at a different version than, or not in, the read_set have been appropriately signed according to their mod_policy //3. The new configuration satisfies the ConfigSchema type ConfigEnvelope struct { Config*Config`protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` LastUpdate*Envelope `protobuf:"bytes,2,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` }// Config represents the config for a particular channel type Config struct { Sequenceuint64`protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` ChannelGroup*ConfigGroup `protobuf:"bytes,2,opt,name=channel_group,json=channelGroup,proto3" json:"channel_group,omitempty"` }// ConfigGroup is the hierarchical data structure for holding config type ConfigGroup struct { Versionuint64`protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` Groupsmap[string]*ConfigGroup`protobuf:"bytes,2,rep,name=groups,proto3" json:"groups,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Valuesmap[string]*ConfigValue`protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Policiesmap[string]*ConfigPolicy `protobuf:"bytes,4,rep,name=policies,proto3" json:"policies,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` ModPolicystring`protobuf:"bytes,5,opt,name=mod_policy,json=modPolicy,proto3" json:"mod_policy,omitempty"` }

其中ConfigEnvelope.Config对象为当前块的配置信息,LastUpdate为最后一个的配置更新在下面的配置更新中会讲到它。
首先来看一下Config对象,其中ConfigGroup中存储的即为当前Channel的主要配置,它与通过cryptogen命令初始化网络时的configtx.yaml配置文件中的项相对应,主要包含以下几项:
Version
配置的版本号,每次更新配置,Version将加1
Groups
Groups 中为下级配置项,依然是ConfigGroup对象,通常包含以下几种:
Consortiums:联盟配置,包含整个网络的组织配置信息,一般出现在genesischannel中,包含整个网络的联盟配置以及各个联盟下的组织信息配置
Orderer: Orderer组织的配置信息,一般出现在genesischannel中,它包含整个链中的各个Orderer组织配置信息
Application: 应用channel的配置信息,一般出现在应用channel中,他包含了应用channel中的各个组织的配置信息。
Values
// ConfigValue represents an individual piece of config data type ConfigValue struct { Versionuint64`protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` Value[]byte`protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` ModPolicystring`protobuf:"bytes,3,opt,name=mod_policy,json=modPolicy,proto3" json:"mod_policy,omitempty"` }

Values 中存放的是链的一些基本配置信息,为ConfigValue对象存储,它通常包含链的配置信息等。
例如链的证书信息,节点信息,区块配置参数等
BatchSize举例,在configtx.yaml中,我们通常这么配置,
#写入区块内的交易大小 BatchSize: #消息的最大个数 MaxMessageCount: 10000 #交易的最大字节数,任何时候均不能超过 AbsoluteMaxBytes: 98 MB #批量交易的建议字节数 PreferredMaxBytes: 10 MB

在配置块中为
"values": { "BatchSize": { "mod_policy": "Admins", "value": { "absolute_max_bytes": 102760448, "max_message_count": 10000, "preferred_max_bytes": 10485760 }, "version": "0" } }

Policies
type ConfigPolicy struct { Versionuint64`protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` Policy*Policy`protobuf:"bytes,2,opt,name=policy,proto3" json:"policy,omitempty"` ModPolicystring`protobuf:"bytes,3,opt,name=mod_policy,json=modPolicy,proto3" json:"mod_policy,omitempty"` }// Policy expresses a policy which the orderer can evaluate, because there has been some desire expressed to support // multiple policy engines, this is typed as a oneof for now type Policy struct { Typeint32`protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` Value[]byte`protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` }

Policies 中链的读写策略,一般包含ReadersWritersAdmins等,使用ConfigPolicy对象存储。
它在链的配置中示例如下:
Policies: &OrgbNodeOrgPolicies Readers: Type: Signature Rule: "OR('OrgbNodeMSP.admin', 'OrgbNodeMSP.peer', 'OrgbNodeMSP.client')" Writers: Type: Signature Rule: "OR('OrgbNodeMSP.admin', 'OrgbNodeMSP.client')" Admins: Type: Signature Rule: "OR('OrgbNodeMSP.admin')"

如何修改配置块 当我们需要修改一个channel的配置时,例如增加一个组织,修改读写其策略,或者需要在channel中吊销某个用户的证书时,需要根据当前的配置信息,以及修改后的配置信息生成修改配置的Envelope,签名之后,提交给Orderer完成配置的修改。
在Fabric的源码中为我们提供了生成ConfigUpdate的方法,它在github.com/hyperledger/fabric/internal/configtxlator/update包中,可以根据当前的配置和修改后的配置生成提交修改的配置更新项。它包含了我们对配置修改的读写集合。
func Compute(original, updated *cb.Config) (*cb.ConfigUpdate, error) { if original.ChannelGroup == nil { return nil, fmt.Errorf("no channel group included for original config") }if updated.ChannelGroup == nil { return nil, fmt.Errorf("no channel group included for updated config") }readSet, writeSet, groupUpdated := computeGroupUpdate(original.ChannelGroup, updated.ChannelGroup) if !groupUpdated { return nil, fmt.Errorf("no differences detected between original and updated config") } return &cb.ConfigUpdate{ ReadSet:readSet, WriteSet: writeSet, }, nil }

根据生成的ConfigUpdate,可以参考下面的代码生成Envelope对象,提交至Orderer后即可完成对配置的修改。
updt, err := update.Compute(&cb.Config{ChannelGroup: original}, &cb.Config{ChannelGroup: updated}) if err != nil { return errors.WithMessage(err, "could not compute update") } updt.ChannelId = channelIDnewConfigUpdateEnv := &cb.ConfigUpdateEnvelope{ ConfigUpdate: protoutil.MarshalOrPanic(updt), }updateTx, err := protoutil.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, nil, newConfigUpdateEnv, 0, 0)

配置块示例 本次的示例是一个转换为json格式的配置块信息,删除了过长的证书信息和重复的组织信息,保留了基本的配置字段,以供参考。
{ "data": { "data": [ { "payload": { "data": { "config": { "channel_group": { "groups": { "Consortiums": { "groups": { "SampleConsortium": { "groups": { "OrgaNodeMSP": { "groups": {}, "mod_policy": "Admins", "policies": { "Admins": { "mod_policy": "Admins", "policy": { "type": 1, "value": { "identities": [ { "principal": { "msp_identifier": "OrgaNodeMSP", "role": "ADMIN" }, "principal_classification": "ROLE" } ], "rule": { "n_out_of": { "n": 1, "rules": [ { "signed_by": 0 } ] } }, "version": 0 } }, "version": "0" }, "Readers": {}, "Writers": {} }, "values": { "MSP": { "mod_policy": "Admins", "value": { "config": { "admins": [ "LS0 ... 0tLS0K===" ], "crypto_config": { "identity_identifier_hash_function": "SHA256", "signature_hash_family": "SHA2" }, "fabric_node_ous": { "admin_ou_identifier": null, "client_ou_identifier": { "certificate": "LS0 ... 0tLS0K=", "organizational_unit_identifier": "client" }, "enable": true, "orderer_ou_identifier": null, "peer_ou_identifier": { "certificate": "LS0 ... 0tLS0K=", "organizational_unit_identifier": "peer" } }, "intermediate_certs": [ "LS0 ... 0tLS0K=", "LS0 ... 0tLS0K=" ], "name": "OrgaNodeMSP", "organizational_unit_identifiers": [], "revocation_list": [], "root_certs": [ "LS0 ... 0tLS0K==" ], "signing_identity": null, "tls_intermediate_certs": [], "tls_root_certs": [] }, "type": 0 }, "version": "0" } }, "version": "0" } }, "mod_policy": "/Channel/Orderer/Admins", "policies": {}, "values": { "ChannelCreationPolicy": { "mod_policy": "/Channel/Orderer/Admins", "value": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Admins" } }, "version": "0" } }, "version": "2" } }, "mod_policy": "/Channel/Orderer/Admins", "policies": { "Admins": { "mod_policy": "/Channel/Orderer/Admins", "policy": { "type": 1, "value": { "identities": [], "rule": { "n_out_of": { "n": 0, "rules": [] } }, "version": 0 } }, "version": "0" } }, "values": {}, "version": "0" }, "Orderer": { "groups": { "OrdererMSP": { "groups": {}, "mod_policy": "Admins", "policies": { "Admins": { "mod_policy": "Admins", "policy": { "type": 1, "value": { "identities": [ { "principal": { "msp_identifier": "OrdererMSP", "role": "ADMIN" }, "principal_classification": "ROLE" } ], "rule": { "n_out_of": { "n": 1, "rules": [ { "signed_by": 0 } ] } }, "version": 0 } }, "version": "0" }, "Readers": { }, "Writers": { } }, "values": { "MSP": { "mod_policy": "Admins", "value": { "config": { "admins": [ "LS0 ... 0tLS0K" ], "crypto_config": { "identity_identifier_hash_function": "SHA256", "signature_hash_family": "SHA2" }, "fabric_node_ous": null, "intermediate_certs": [ "LS0 ... 0tLS0K", "LS0 ... 0tLS0K" ], "name": "OrdererMSP", "organizational_unit_identifiers": [], "revocation_list": [], "root_certs": [ "LS0t ... S0tLQo=" ], "signing_identity": null, "tls_intermediate_certs": [ "LS0 ... 0tLS0K", "LS0 ... 0tLS0K" ], "tls_root_certs": [ "LS0 ... 0tLS0K" ] }, "type": 0 }, "version": "0" } }, "version": "0" } }, "mod_policy": "Admins", "policies": { "Admins": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Admins" } }, "version": "0" }, "BlockValidation": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Writers" } }, "version": "0" }, "Readers": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Readers" } }, "version": "0" }, "Writers": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Writers" } }, "version": "0" } }, "values": { "BatchSize": { "mod_policy": "Admins", "value": { "absolute_max_bytes": 102760448, "max_message_count": 10000, "preferred_max_bytes": 10485760 }, "version": "0" }, "BatchTimeout": { "mod_policy": "Admins", "value": { "timeout": "1s" }, "version": "0" }, "Capabilities": { "mod_policy": "Admins", "value": { "capabilities": { "V1_4_2": {} } }, "version": "0" }, "ChannelRestrictions": { "mod_policy": "Admins", "value": null, "version": "0" }, "ConsensusType": { "mod_policy": "Admins", "value": { "metadata": { "consenters": [ { "client_tls_cert": "LS0 ... 0tLS0K=", "host": "order1.ordernode.bsnbase.com", "port": 17051, "server_tls_cert": "LS0 ... 0tLS0K=" }, { "client_tls_cert": "LS0 ... 0tLS0K=", "host": "order2.ordernode.bsnbase.com", "port": 17052, "server_tls_cert": "LS0 ... 0tLS0K=" }, { "client_tls_cert": "LS0 ... 0tLS0K=", "host": "order3.ordernode.bsnbase.com", "port": 17053, "server_tls_cert": "LS0 ... 0tLS0K=" } ], "options": { "election_tick": 10, "heartbeat_tick": 1, "max_inflight_blocks": 5, "snapshot_interval_size": 200, "tick_interval": "500ms" } }, "state": "STATE_NORMAL", "type": "etcdraft" }, "version": "0" } }, "version": "0" } }, "mod_policy": "Admins", "policies": { "Admins": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Admins" } }, "version": "0" }, "Readers": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Readers" } }, "version": "0" }, "Writers": { "mod_policy": "Admins", "policy": { "type": 3, "value": { "rule": "ANY", "sub_policy": "Writers" } }, "version": "0" } }, "values": { "BlockDataHashingStructure": { "mod_policy": "Admins", "value": { "width": 4294967295 }, "version": "0" }, "Capabilities": { "mod_policy": "Admins", "value": { "capabilities": { "V1_4_2": {} } }, "version": "0" }, "HashingAlgorithm": { "mod_policy": "Admins", "value": { "name": "SHA256" }, "version": "0" }, "OrdererAddresses": { "mod_policy": "/Channel/Orderer/Admins", "value": { "addresses": [ "order1.ordernode.bsnbase.com:17051" ] }, "version": "0" } }, "version": "0" }, "sequence": "2" }, "last_update": { "payload": { "data": { "config_update": { "channel_id": "genesischannel", "isolated_data": {}, "read_set": { "groups": { }, "mod_policy": "", "policies": {}, "values": {}, "version": "0" }, "write_set": { "groups": { }, "mod_policy": "", "policies": {}, "values": {}, "version": "0" } }, "signatures": [ { "signature": "MEUCIQChKw0GLbCI3Mg3oN14hw25ETgzZOMGOycuPQYwbAEulgIgIxihwa1x/5/mRhEqSUatBPafbyYzlPNRRdA0syYYALA=", "signature_header": { "creator": { "id_bytes": "LS0 ... 0tLS0K", "mspid": "OrdererMSP" }, "nonce": "4UQFkmPE3ajSubYgeSp8TwBHqQQ7Jq3+" } } ] }, "header": { "channel_header": { "channel_id": "genesischannel", "epoch": "0", "extension": null, "timestamp": "2021-11-11T05:42:43Z", "tls_cert_hash": null, "tx_id": "", "type": 2, "version": 0 }, "signature_header": { "creator": { "id_bytes": "LS0 ... 0tLS0K", "mspid": "OrdererMSP" }, "nonce": "XKHFVcxjbdp/Jp+TwPpKvtPw7wogD27H" } } }, "signature": "MEUCIQDn3u47EfAjbqJjqkwks+bB4gCuyDcmLEhUurVeyJqcjQIgIW7/28nFgAyNBXYEtMC/qZjfriL7xCEijLuRlHqRJuo=" } }, "header": { "channel_header": { "channel_id": "genesischannel", "epoch": "0", "extension": null, "timestamp": "2021-11-11T05:42:43Z", "tls_cert_hash": null, "tx_id": "", "type": 1, "version": 0 }, "signature_header": { "creator": { "id_bytes": "LS0 ... 0tLS0K=", "mspid": "OrdererMSP" }, "nonce": "B/92hIoLno1ewPDddf9EmDfYMGdSO668" } } }, "signature": "MEUCIQCpSlOWFD+fBCqFk8DVw4m+qkLwspqe0vGqmqh6tv/OlQIgatIGPHJzhmdPzbwU7zPy9e+KtpghTdGiFpyi4UqpqBo=" } ] }, "header": { "data_hash": "mgHIsx3RqeeybaKDYmetYvypDqVxKSB5O0YzqwnG/IM=", "number": "34", "previous_hash": "I5IMydQTa5njnHojXNnsDdHLlDkF/RlPIsO48iUI4cc=" }, "metadata": { "metadata": [ "ChIK ... kJZ+j9Pr", "CgIIIg==", "", "CgoKAwECAxAEGIwb", "" ] } }

    推荐阅读