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

When private channel data (collections) is not enabled, lscc deploy must produce exactly one ledger write (the ChaincodeData record). Multiple writes in the lscc rwset violate the expected deploy semantics and are rejected.

Source

Thrown at core/handlers/validation/builtin/v12/validation_logic.go:654

			/* 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. Enable the V1_2 (or later) PrivateChannelData capability in the channel config if you need collections.
  2. Remove collection configuration arguments from the instantiate call when the channel lacks the capability.
  3. Run stock lscc so only a single ChaincodeData write is issued on non-capability channels.
  4. Align all peers' channel capability versions.

Example fix

// before
request['collections-config'] = collectionsConfig // channel lacks PrivateChannelData capability
// after: enable V1_2 capability in channel config, or drop collections config
request = baseInstantiateRequest
Defensive patterns

Strategy: validation

Validate before calling

// client-side: only send collections config when the channel has the PrivateChannelData capability
if !channelConfig.Capabilities.PrivateChannelData() && request.CollectionsConfig != nil {
    return fmt.Errorf("channel lacks PrivateChannelData capability; drop collections config or upgrade channel config")
}

Type guard

func collectionsAllowed(ac capabilities.ApplicationCapabilities) bool {
    return ac != nil && ac.PrivateChannelData()
}

Try / catch

if err := submitTx(envelope); err != nil {
    if strings.Contains(err.Error(), "can only issue a single putState upon deploy") {
        // either enable V1_2 channel capability or remove collections-config from instantiate
    }
}

Prevention

When it happens

Trigger: lsccrwset.Writes has length != 1 during DEPLOY on a channel without PrivateChannelData — e.g. custom lscc writing collection configs to lscc's namespace on a channel where private data is disabled.

Common situations: Passing collection-config arguments to instantiate on a channel where the capability for private channel data is disabled; modified lscc; peer with mismatched channel capabilities (V1_2 vs V1_1).

Related errors


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