{"record":{"id":"418312d240c13fb4","repo":"hyperledger/fabric","slug":"the-blocktolive-in-an-existing-collection-s-mod","errorCode":null,"errorMessage":"the BlockToLive in an existing collection [%s] modified. Existing value [%d]","messagePattern":"the BlockToLive in an existing collection \\[(.+?)\\] modified\\. Existing value \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/chaincode/lifecycle/scc.go","lineNumber":952,"sourceCode":"\t\tproposedCollsMap[c.Name] = c\n\t}\n\n\t// In the new collection config package, ensure that there is one entry per old collection. Any\n\t// number of new collections are allowed.\n\tfor _, committedCollConfig := range committedCollConfPkg.Config {\n\t\tcommittedColl := committedCollConfig.GetStaticCollectionConfig()\n\t\t// It cannot be nil\n\t\tif committedColl == nil {\n\t\t\treturn errors.Errorf(\"unknown collection configuration type\")\n\t\t}\n\n\t\tnewCollection, ok := proposedCollsMap[committedColl.Name]\n\t\tif !ok {\n\t\t\treturn errors.Errorf(\"existing collection [%s] missing in the proposed collection configuration\", committedColl.Name)\n\t\t}\n\n\t\tif newCollection.BlockToLive != committedColl.BlockToLive {\n\t\t\treturn errors.Errorf(\"the BlockToLive in an existing collection [%s] modified. Existing value [%d]\", committedColl.Name, committedColl.BlockToLive)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (i *Invocation) createOpaqueStates() ([]OpaqueState, error) {\n\tif i.ApplicationConfig == nil {\n\t\treturn nil, errors.Errorf(\"no application config for channel '%s'\", i.Stub.GetChannelID())\n\t}\n\torgs := i.ApplicationConfig.Organizations()\n\topaqueStates := make([]OpaqueState, 0, len(orgs))\n\tfor _, org := range orgs {\n\t\topaqueStates = append(opaqueStates, &ChaincodePrivateLedgerShim{\n\t\t\tCollection: implicitcollection.NameForOrg(org.MSPID()),\n\t\t\tStub:       i.Stub,\n\t\t})\n\t}\n\treturn opaqueStates, nil","sourceCodeStart":934,"sourceCodeEnd":970,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/chaincode/lifecycle/scc.go#L934-L970","documentation":"Hyperledger Fabric's lifecycle SCC rejects any attempt to modify the BlockToLive field of an already-committed private data collection. Once a collection is committed, BlockToLive is immutable; validateCollConfigsAgainstCommittedDef compares the proposed collection against the committed definition and fails when the values differ. The committed (existing) value is reported in the message.","triggerScenarios":"Calling _commitUpdateCollectionConfig (via chaincode Invoke on the _lifecycle SCC, e.g. through CommitChaincodeDefinition or a collection-config update transaction) with a CollectionConfigPackage where a collection with the same name has a different BlockToLive than the one on the ledger.","commonSituations":"Operators editing a collections_config.json to tighten or extend private data retention (e.g. changing BlockToLive from 100000 to 0 or a larger value) and redeploying; CLI tooling regenerating collection configs with different defaults; SDKs re-submitting configs where BlockToLive was changed to try to expire old private data.","solutions":["Restore the BlockToLive value in the proposed config to match the committed value exactly (the existing value is printed in the error message)","Retrieve the committed collection config (QSCC GetChaincodeCollectionConfig or QueryChaincodeDefinition) and base edits on it","If retention must change, create a new collection with a new name (data migration) instead of mutating the existing one","Use the peer's pre-check via CheckCommitReadiness to validate the update before committing"],"exampleFix":"// before (collections_config.json)\n{\"name\":\"coll1\",\"blockToLive\":0}\n// after\n{\"name\":\"coll1\",\"blockToLive\":100000}  // must equal committed value","handlingStrategy":"validation","validationCode":"// compare proposed vs committed before submitting update\ncommitted, _ := getCommittedCollectionConfig(channel, ccName)\nfor _, proposed := range proposedColls {\n  for _, c := range committed {\n    if c.Name == proposed.Name && c.BlockToLive != proposed.BlockToLive {\n      return fmt.Errorf(\"BlockToLive for %s is immutable (committed=%d)\", c.Name, c.BlockToLive)\n    }\n  }\n}","typeGuard":"func blockToLiveUnchanged(committed, proposed uint64) bool { return committed == proposed }","tryCatchPattern":null,"preventionTips":["Always fetch the committed collection config before proposing updates","Treat BlockToLive as immutable in any config-editing tooling","Use CheckCommitReadiness as a dry run before commit","Create new collections for retention changes instead of editing existing ones"],"tags":["fabric","private-data","collection-config","immutable-field"],"backgroundTag":"immutable-collection-config-modified","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}