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.

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 err

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Use the stock lscc so the upgrade writes exactly one updated ChaincodeData record.
  2. Remove extra putState calls from any patched upgrade path or move them into separate transactions.
  3. For hand-built test rwsets, provide exactly one lscc write with a valid ChaincodeData value.
  4. 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

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


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/749e54d93402090a. Report an issue: GitHub.