{"record":{"id":"749e54d93402090a","repo":"hyperledger/fabric","slug":"lscc-can-only-issue-a-single-putstate-upon-upgrade-749e54","errorCode":null,"errorMessage":"LSCC can only issue a single putState upon upgrade","messagePattern":"LSCC can only issue a single putState upon upgrade","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/handlers/validation/builtin/v13/lscc_validation_logic.go","lineNumber":561,"sourceCode":"\t\t\t/**********************************************************/\n\t\t\tif cdLedger.Version == cdsArgs.ChaincodeSpec.ChaincodeId.Version {\n\t\t\t\treturn policyErr(fmt.Errorf(\"Existing version of the cc on the ledger (%s) should be different from the upgraded one\", cdsArgs.ChaincodeSpec.ChaincodeId.Version))\n\t\t\t}\n\n\t\t\t/****************************************************************************/\n\t\t\t/* security check 3 validation of rwset (and of collections if enabled) */\n\t\t\t/****************************************************************************/\n\t\t\t// Only in v1.2, a collection can be updated during a chaincode upgrade\n\t\t\tif ac.V1_2Validation() {\n\t\t\t\t// do extra validation for collections\n\t\t\t\terr := vscc.validateRWSetAndCollection(lsccrwset, cdRWSet, lsccArgs, lsccFunc, ac, chid)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// there can only be a single ledger write\n\t\t\t\tif len(lsccrwset.Writes) != 1 {\n\t\t\t\t\treturn policyErr(fmt.Errorf(\"LSCC can only issue a single putState upon upgrade\"))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/*****************************************************/\n\t\t\t/* security check 4 - check the instantiation policy */\n\t\t\t/*****************************************************/\n\t\t\tpol := cdLedger.InstantiationPolicy\n\t\t\tif pol == nil {\n\t\t\t\treturn policyErr(fmt.Errorf(\"No instantiation policy was specified\"))\n\t\t\t}\n\t\t\t// FIXME: could we actually pull the cds package from the\n\t\t\t// file system to verify whether the policy that is specified\n\t\t\t// here is the same as the one on disk?\n\t\t\t// PROS: we prevent attacks where the policy is replaced\n\t\t\t// CONS: this would be a point of non-determinism\n\t\t\terr := vscc.checkInstantiationPolicy(chid, env, pol, payl)\n\t\t\tif err != nil {\n\t\t\t\treturn err","sourceCodeStart":543,"sourceCodeEnd":579,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/handlers/validation/builtin/v13/lscc_validation_logic.go#L543-L579","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (custom lscc upgrade)\nstub.PutState(ccname, cdBytes)\nstub.PutState(\"upgrade-log\", logBytes) // extra\n// after\nstub.PutState(ccname, cdBytes) // exactly one write","handlingStrategy":"validation","validationCode":"// pre-flight on upgrade rwsets\nif n := countWrites(nsRwSet[\"lscc\"]); n != 1 {\n    return fmt.Errorf(\"lscc upgrade must write exactly 1 key, got %d\", n)\n}","typeGuard":"func isSingleUpgradeWrite(ns *rwset.NsRwSet) bool { return ns != nil && len(ns.KvRwSet.Writes) == 1 }","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"single putState upon upgrade\") { /* remove extra writes or run stock lscc and retry */ }","preventionTips":["Use stock lscc for upgrades","Move supplementary writes out of the upgrade transaction","Align private-channel-data capability settings across peers"],"tags":["hyperledger-fabric","lscc","rwset","upgrade"],"backgroundTag":"invalid-lscc-rwset","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"}