Fabric 账本数据块结构解析(二)(如何解析账本中的配置块数据)
id:BSN_2021 公众号:BSN 研习社 作者:红枣科技高晨曦
背景:BSN公网Fabric联盟链的出现降低了使用区块链的难度,但在部分特定环境中,仍需要自己搭建Fabric环境时,了解Fabric中的配置信息能够帮助搭建或在运行中调整自己的链环境配置。什么是配置块 【Fabric 账本数据块结构解析(二)(如何解析账本中的配置块数据)】配置块是Fabric Channel的重要数据,里面包含了当前Channel的配置信息,配置块包含初始配置块,以及在链运行过程中的修改配置信息而提交的交易生成的配置区块信息。
目标:了解账本数据结构中的配置信息,更好的维护自己的Fabric环境。
对象: 使用BSN联盟链Fabric的开发人员、运维人员。
它包含了当前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_CONFIG
和HeaderType_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 中链的读写策略,一般包含
Readers
、Writers
、Admins
等,使用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",
""
]
}
}
推荐阅读
- 大数据Hadoop入门教程|大数据Hadoop入门教程 | (二)Linux
- MybatisPlus——实现多数据源操作
- 【SpringBoot实战】手把手教你实现数据报表统计并定时推送
- lesson1-绘制直方图-乘客数据
- 大数据——机器学习|机器学习之用解析解求解多元线性回归模型
- MySQL|如何实现两个数据库之间表的同步更新(增,删,改)
- python数据挖掘代码_最适合初学者的数据挖掘入门课程(4天快速入门python数据挖掘视频+代码)...
- python爬虫数据教程_Python爬虫教程,4天快速入门Python数据挖掘教程分享!-Go语言中文社区...
- 大数据Hadoop入门教程|大数据Hadoop入门教程 | (一)概论
- SQL|SQL Server 数据库之生成与执行 SQL 脚本