hyperledger/fabric · error

only one transaction action is supported, %d were present

Error message

only one transaction action is supported, %d were present

What it means

The transaction inside the envelope contains multiple (or zero) TransactionActions. Fabric's endorser transaction format parsed by this library supports exactly one chaincode action per transaction, so any other count is rejected. This is a deliberate structural constraint of this parser, not a generic protobuf failure.

Source

Thrown at core/tx/endorser/parser.go:64

	hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(
		txenv.ChannelHeader.Extension,
	)
	if err != nil {
		return nil, err
	}

	if len(txenv.Data) == 0 {
		return nil, errors.New("nil payload data")
	}

	tx, err := protoutil.UnmarshalTransaction(txenv.Data)
	if err != nil {
		return nil, err
	}

	if len(tx.GetActions()) != 1 {
		return nil, errors.Errorf("only one transaction action is supported, %d were present", len(tx.GetActions()))
	}

	txAction := tx.GetActions()[0]

	if txAction == nil {
		return nil, errors.New("nil action")
	}

	if len(txAction.Payload) == 0 {
		return nil, errors.New("empty ChaincodeActionPayload")
	}

	ccActionPayload, err := protoutil.UnmarshalChaincodeActionPayload(txAction.Payload)
	if err != nil {
		return nil, err
	}

	if ccActionPayload.Action == nil {

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Split multi-action transactions into one transaction per action before submitting/parsing.
  2. Regenerate the transaction with the standard Fabric SDK/protoutil path, which produces exactly one action.
  3. If parsing, filter client-side: only feed envelopes whose Transaction.Actions length is 1 to this API.

Example fix

// before
tx.Actions = []*common.TransactionAction{action1, action2}
// after
tx.Actions = []*common.TransactionAction{action1} // one action per tx; submit action2 separately
Defensive patterns

Strategy: validation

Validate before calling

func hasSingleAction(txBytes []byte) bool {
    tx := &common.Transaction{}
    if proto.Unmarshal(txBytes, tx) != nil {
        return false
    }
    return len(tx.GetActions()) == 1
}

Type guard

func isSingleActionTx(tx *common.Transaction) bool {
    return tx != nil && len(tx.GetActions()) == 1 && tx.GetActions()[0] != nil
}

Try / catch

tx, err := parser.UnmarshalEndorserTxAndValidate(env)
if err != nil {
    var af *feeders.Errors
    if strings.Contains(err.Error(), "only one transaction action is supported") {
        // split or reject the multi-action transaction
        return fmt.Errorf("unsupported tx: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: UnmarshalEndorserTxAndValidate receives an envelope whose Transaction.Actions slice has length != 1 — typically a multi-action transaction produced by custom tooling, or a Transaction with no actions at all.

Common situations: Older/custom Fabric SDKs or chaincode tooling that batched multiple actions into one transaction; programmatically assembled transactions with duplicate actions; corrupted protobuf that deserialized with zero actions.

Related errors


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