# How to Send a Config Update in Fabric-X
*And how we chased a mysterious “identity is unknown” error all the way to a one-field protobuf bug.*
— -
The setup
Fabric-X is a distributed transaction-processing system. Client apps are FSC (Fabric Smart Client) nodes — issuers, endorsers, owners and gateways; that talk to each other and to a committer that validates and commits transactions. The committer validates each endorsement against the channel’s MSP (Membership Service Provider), the same way classic Hyperledger Fabric does.
Now when a node signs a transaction, it can carry its identity in a compact form: just `(mspid, certificateID)` instead of the full certificate (part of the rearchitectures done in Fabric-X). The verifier resolves that certificate-ID through the channel config’s `known_certs` list. If the cert isn’t there, the transaction is rejected with error:
WARN [policies] SignatureSetToValidIdentities -> invalid identity
error=”identity is unknown”
identity=”(mspid=Org1MSP certificateID=a1b30296…)”
The genesis block is created *before* any application identities exist, so `known_certs` starts empty. New nodes enroll via the CA and must be added to `known_certs` at runtime. That is what my**config update** does: a signed `CONFIG_UPDATE` transaction broadcast to the orderer, which packages it into a new config block and hands it to every committer. The committer rebuilds its MSP and — if all went well — the new certificate-ID resolves.
So: how do you send one? I’ve setup the fabric-x-operator to do it for me whenever I register a node through the FSC management API. But the journey to making that work reliably involved two genuine bugs, one of which was beautifully sneaky.
My config_update code here: https://raw.githubusercontent.com/elishambadi/fabric-x-helpers/refs/heads/main/config_update.go
Part 1: The PEM vs DER trap
My first bug was the most humbling: I was writing the wrong bytes.
`FabricMSPConfig.KnownCerts` is repeated bytes of **PEM-encoded certificates**. The MSP setup code reads each entry and does:
pem.Decode(idBytes)
The operator code, however, was converting the freshly-enrolled certificate to raw DER before appending it:
certDER := pemToDER(signCertPEM)
appendCertToMSP(mspConfig, certDER)
The committer’s `getCertFromPem` runs `pem.Decode` on a DER blob, finds no PEM header, and the identity never lands in the `knownIdentities` map. The fix was one line: stop converting, pass the PEM through.
certPEM := signCertPEM // this is correct
Good. That was bug #1, squashed.
Part 2: “But it’s right there in known_certs!”
Then the plot thickened. I had my agent write me a diagnostic tool which confirmed the certs were in the channel config. The diagnostic tool, `cmd/configdump`, dialed the sidecar’s gRPC BlockQueryService, walked to the latest config block, and printed every `known_certs` entry with its SHA-256 certificate-ID:
org Org1MSP known_certs: 3 entries
[ 0] ok len=940 certID=a1b30296… ← buyer
[ 1] ok len=981 certID=e4f1fb1b… ← supplier
[ 2] ok len=948 certID=2eaf12ba…
Clean PEM and correct hashes were present in the config. And yet the verifier kept erring on`identity is unknown` for those exact certificate-IDs. I computed the storage key using the *same* `fabric-x-common` code the verifier uses:
inner FabricMSPConfig: Name=”Org1MSP” RootCerts=1 KnownCerts=2
MSP Setup succeeded
GetKnownDeserializedIdentity(Org1MSP/a1b30296…) -> NOT FOUND // buyer
GetKnownDeserializedIdentity(Org1MSP/e4f1fb1b…) -> NOT FOUND // supplier
The certs were in the config and not in the MSP. Two facts that should be impossible, side by side.
Part 3: Examining the MSP Setup Wrapper
The culprit was hiding in how Fabric stores the MSP value in channel config. It is not a bare FabricMSPConfig. It is a standard msp.MSPConfig wrapper:
Values[“MSP”].Value = msp.MSPConfig {
Type: 0,
Config: <marshaled FabricMSPConfig>, // ← field 2
}
Notice the FabricMSPConfig is inside the msp.MSPConfig. The committer’s setupKnownCerts reads known_certs from the inner FabricMSPConfig (the thing inside Config). My initial config-update code was unmarshaling Values[“MSP”].Value directly as a FabricMSPConfig and appending to its KnownCerts. Protobuf being positional: that append landed on field 12 of the outer message — a field the committer never reads. The certs were present, decodeable, hashed correctly, and yet completely invisible.
Get Elisha Mbadi’s stories in your inbox
Join Medium for free to get updates from this writer.
Dumping the raw protobuffed bytes made it undeniable. The top-level message had:
field 2 = 6168 bytes → the real FabricMSPConfig (with its own 2 known_certs)
field 12 = 940/981/948 → where our appends went
To fix this I had to unwrap the msp.Config wrapper, append my FabricMSPConfig to the inner config and re-wrap.
wrapper := &msp.MSPConfig{}
proto.Unmarshal(mspVal.Value, wrapper)
inner := &msppb.FabricMSPConfig{}
proto.Unmarshal(wrapper.Config, inner)
inner.KnownCerts = append(inner.KnownCerts, certPEM)
b, _ := proto.Marshal(inner)
wrapper.Config = b
mspVal.Value, _ = proto.Marshal(wrapper)The diagnostic went from `NOT FOUND` to `FOUND` for the same certificates, which fixed my dynamic reconfiguration issue.
Conclusion
A config-update in Fabric-X is:
- Load the current config (from sidecar preferred, genesis as fallback)
- Unwrap the MSP wrapper
- Append the PEM cert to the inner FabricMSPConfig.KnownCerts
- Compute the delta
- Sign with the admin cert
- Broadcast to the orderer router.
- The orderer echoes back the transaction-ID, committer rebuilds it’s MSP from the new block and cached cert-ids begins to resolve.
One key lesson here was: Reproduce in-process. Being able to instatiate the exact MSP library the verifier uses and calling `GetKnownDeserializedIdentity` allowed me to get to the root of the issue.
I am Stone. Currently working on blockchain & web applications.










