hyperledger/fabric · error

LSCC can only issue a single putState upon deploy

Error message

LSCC can only issue a single putState upon deploy

What it means

On the DEPLOY path, when private channel data (collections) is not enabled, lscc's write-set must contain exactly one write — the ChaincodeData putState. Any additional (or zero) writes means the transaction does not conform to the expected deploy semantics and is rejected.

Source

Thrown at core/handlers/validation/builtin/v13/lscc_validation_logic.go:512

			/* security check 1 - cc not in the LCCC table of instantiated cc */
			/******************************************************************/
			if ccExistsOnLedger {
				return policyErr(fmt.Errorf("Chaincode %s is already instantiated", cdsArgs.ChaincodeSpec.ChaincodeId.Name))
			}

			/****************************************************************************/
			/* security check 2 - validation of rwset (and of collections if enabled) */
			/****************************************************************************/
			if ac.PrivateChannelData() {
				// 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 deploy"))
				}
			}

			/*****************************************************/
			/* security check 3 - check the instantiation policy */
			/*****************************************************/
			pol := cdRWSet.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 implementation so deploy writes exactly one ChaincodeData record.
  2. If you modified lscc, move any extra writes out of the deploy path.
  3. For hand-built test rwsets, ensure exactly one write on the lscc namespace with a valid ChaincodeData value.
  4. Enable/align private-channel-data handling consistently across peers if collections are intended.

Example fix

// before (custom lscc)
stub.PutState(ccname, cdBytes)
stub.PutState(ccname+"~meta", metaBytes) // extra write
// after
stub.PutState(ccname, cdBytes) // single write only
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight on custom lscc rwsets
lsccWrites := countWrites(nsRwSet["lscc"])
if lsccWrites != 1 {
    return fmt.Errorf("lscc deploy must write exactly 1 key, got %d", lsccWrites)
}

Type guard

func isSingleWrite(ns *rwset.NsRwSet) bool { return ns != nil && len(ns.KvRwSet.Writes) == 1 }

Try / catch

if err != nil && strings.Contains(err.Error(), "single putState upon deploy") { /* strip extra writes from the deploy rwset or use stock lscc */ }

Prevention

When it happens

Trigger: An lscc deploy whose lscc-namespaced rwset contains 0 or 2+ writes — e.g. a modified lscc emitting extra puts, or a hand-crafted rwset for testing.

Common situations: Patched/custom lscc implementations writing extra keys on deploy, integration tests fabricating lscc rwsets manually, mixing deploy-time collection configuration with a non-private-data channel code path.

Related errors


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