hyperledger/fabric · error
the BlockToLive in the following existing collections must n
Error message
the BlockToLive in the following existing collections must not be modified: %v
What it means
BlockToLive (the number of blocks after which private data is purged) is immutable once a collection is defined. checkForModifiedCollectionsBTL compares old and new BlockToLive values for every existing collection and rejects the upgrade transaction if any differ, listing the offending collections.
Source
Thrown at core/handlers/validation/builtin/v13/lscc_validation_logic.go:175
// modified for the existing collections.
for _, oldCollectionConfig := range oldCollectionConfigs {
oldCollection := oldCollectionConfig.GetStaticCollectionConfig()
// It cannot be nil
if oldCollection == nil {
return policyErr(fmt.Errorf("unknown collection configuration type"))
}
oldCollectionName := oldCollection.GetName()
newCollection := newCollectionsMap[oldCollectionName]
// BlockToLive cannot be changed
if newCollection.GetBlockToLive() != oldCollection.GetBlockToLive() {
modifiedCollectionsBTL = append(modifiedCollectionsBTL, oldCollectionName)
}
}
if len(modifiedCollectionsBTL) > 0 {
return policyErr(fmt.Errorf("the BlockToLive in the following existing collections must not be modified: %v",
modifiedCollectionsBTL))
}
return nil
}
func validateNewCollectionConfigsAgainstOld(newCollectionConfigs []*pb.CollectionConfig, oldCollectionConfigs []*pb.CollectionConfig,
) error {
newCollectionsMap := make(map[string]*pb.StaticCollectionConfig, len(newCollectionConfigs))
for _, newCollectionConfig := range newCollectionConfigs {
newCollection := newCollectionConfig.GetStaticCollectionConfig()
// Collection object itself is stored as value so that we can
// check whether the block to live is changed -- FAB-7810
newCollectionsMap[newCollection.GetName()] = newCollection
}
if err := checkForMissingCollections(newCollectionsMap, oldCollectionConfigs); err != nil {View on GitHub (pinned to 2736b63f8f)
Solutions
- Restore the original blockToLive value for every existing collection in the new config
- Keep the old collections file as the base for upgrades and only add new collections with new blockToLive values
- Remember retention is fixed at creation; use new collections or off-chain purge strategies if retention must change
Example fix
// before (upgrade changing existing collection)
[{"name": "coll1", "blockToLive": 1000}]
// after (keep original; new collection may set its own)
[{"name": "coll1", "blockToLive": 0}] Defensive patterns
Strategy: validation
Validate before calling
function validateBtlUnchanged(oldConfigs, newConfigs) {
const newMap = new Map(newConfigs.map(c => [c.name, c]));
const changed = oldConfigs
.filter(c => newMap.has(c.name) && newMap.get(c.name).blockToLive !== c.blockToLive)
.map(c => c.name);
if (changed.length) throw new Error(`blockToLive must not change for: ${changed.join(', ')}`);
} Type guard
function btlIsImmutable(oldConfigs, newConfigs) {
const newMap = new Map(newConfigs.map(c => [c.name, c]));
return oldConfigs.every(c => !newMap.has(c.name) || newMap.get(c.name).blockToLive === c.blockToLive);
} Try / catch
try {
await contract.submitTransaction('UpgradeChaincode', ...args);
} catch (err) {
if (String(err).includes('BlockToLive')) {
// restore original blockToLive values and resubmit
}
throw err;
} Prevention
- Keep the original collections file as the base for all upgrades and only append new collections
- Treat blockToLive as immutable at collection design time; choose retention values carefully
- Diff old vs new configs (name + blockToLive) in CI before every upgrade
When it happens
Trigger: Submitting a chaincode upgrade where a collection keeps its name but its blockToLive value differs from the previously committed definition, e.g. changing blockToLive from 0 to 1000.
Common situations: Editing an exported collections JSON to 'tune' retention and reusing it in an upgrade; changing blockToLive to try to purge existing private data faster; tooling that regenerates the config with different defaults (e.g. omitting a previously set blockToLive).
Related errors
- the following existing collections are missing in the new co
- purge private data is not enabled, channel application capab
- tx creator does not have read access permission on privateda
- tx creator does not have write access permission on privated
- private data APIs are not allowed in chaincode Init()
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/d27ea1165c729211.
Report an issue: GitHub.