hyperledger/fabric · error
LSCC can only issue a single putState upon upgrade
Error message
LSCC can only issue a single putState upon upgrade
What it means
Mirror of the deploy check on the UPGRADE path: when private channel data handling is not in effect, lscc's write-set must contain exactly one write (the updated ChaincodeData). Any other number of writes means the upgrade rwset deviates from the expected contract and the transaction fails validation.
Solutions
- Use the stock lscc so the upgrade writes exactly one updated ChaincodeData record.
- Remove extra putState calls from any patched upgrade path or move them into separate transactions.
- For hand-built test rwsets, provide exactly one lscc write with a valid ChaincodeData value.
- Align the private-channel-data (V1.2 collection capability) setting across all channel peers if collection updates during upgrade are intended.
Example fix
// before (custom lscc upgrade)
stub.PutState(ccname, cdBytes)
stub.PutState("upgrade-log", logBytes) // extra
// after
stub.PutState(ccname, cdBytes) // exactly one write Defensive patterns
Strategy: validation
Validate before calling
// pre-flight on upgrade rwsets
if n := countWrites(nsRwSet["lscc"]); n != 1 {
return fmt.Errorf("lscc upgrade must write exactly 1 key, got %d", n)
} Type guard
func isSingleUpgradeWrite(ns *rwset.NsRwSet) bool { return ns != nil && len(ns.KvRwSet.Writes) == 1 } Try / catch
if err != nil && strings.Contains(err.Error(), "single putState upon upgrade") { /* remove extra writes or run stock lscc and retry */ } Prevention
- Use stock lscc for upgrades
- Move supplementary writes out of the upgrade transaction
- Align private-channel-data capability settings across peers
When it happens
Trigger: An lscc upgrade whose lscc-namespaced rwset has 0 or 2+ writes — produced by a modified lscc, fabricated test rwsets, or extra putState calls added to the upgrade flow.
Common situations: Custom lscc forks writing supplementary keys during upgrade, unit/integration tests composing upgrade rwsets by hand, a collection-update flow run against peers whose PrivateChannelData() capability is off.
Related errors
- Existing version of the cc on the ledger
- LSCC can only issue a single putState upon deploy
- LSCC can only issue a single putState upon upgrade
- LSCC can only issue one or two putState upon deploy
- LSCC invocation is attempting to write to namespace
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/749e54d93402090a.
Report an issue: GitHub.
Appendix: source
Thrown at core/handlers/validation/builtin/v13/lscc_validation_logic.go:561
/**********************************************************/
if cdLedger.Version == cdsArgs.ChaincodeSpec.ChaincodeId.Version {
return policyErr(fmt.Errorf("Existing version of the cc on the ledger (%s) should be different from the upgraded one", cdsArgs.ChaincodeSpec.ChaincodeId.Version))
}
/****************************************************************************/
/* security check 3 validation of rwset (and of collections if enabled) */
/****************************************************************************/
// Only in v1.2, a collection can be updated during a chaincode upgrade
if ac.V1_2Validation() {
// do extra validation for collections
err := vscc.validateRWSetAndCollection(lsccrwset, cdRWSet, lsccArgs, lsccFunc, ac, chid)
if err != nil {
return err
}
} else {
// there can only be a single ledger write
if len(lsccrwset.Writes) != 1 {
return policyErr(fmt.Errorf("LSCC can only issue a single putState upon upgrade"))
}
}
/*****************************************************/
/* security check 4 - check the instantiation policy */
/*****************************************************/
pol := cdLedger.InstantiationPolicy
if pol == nil {
return policyErr(fmt.Errorf("No instantiation policy was specified"))
}
// FIXME: could we actually pull the cds package from the
// file system to verify whether the policy that is specified
// here is the same as the one on disk?
// PROS: we prevent attacks where the policy is replaced
// CONS: this would be a point of non-determinism
err := vscc.checkInstantiationPolicy(chid, env, pol, payl)
if err != nil {
return errView on GitHub (pinned to 2736b63f8f)