hyperledger/fabric · error

nil transaction

Error message

nil transaction

What it means

After successfully unmarshalling the data bytes into a common.Transaction proto, validateEndorserTransaction checks that the resulting Transaction object is not nil. This catches the pathological case where the bytes unmarshal 'successfully' into an empty/nil message, which cannot carry any transaction actions.

Source

Thrown at core/common/validation/msgvalidation.go:181

// validateEndorserTransaction validates the payload of a
// transaction assuming its type is ENDORSER_TRANSACTION
func validateEndorserTransaction(data []byte, hdr *common.Header) error {
	putilsLogger.Debugf("validateEndorserTransaction starts for data %p, header %s", data, hdr)

	// check for nil argument
	if data == nil || hdr == nil {
		return errors.New("nil arguments")
	}

	// if the type is ENDORSER_TRANSACTION we unmarshal a Transaction message
	tx, err := protoutil.UnmarshalTransaction(data)
	if err != nil {
		return err
	}

	// check for nil argument
	if tx == nil {
		return errors.New("nil transaction")
	}

	// TODO: validate tx.Version

	// TODO: validate ChaincodeHeaderExtension

	// hlf version 1 only supports a single action per transaction
	if len(tx.Actions) != 1 {
		return errors.Errorf("only one action per transaction is supported, tx contains %d", len(tx.Actions))
	}

	putilsLogger.Debugf("validateEndorserTransaction info: there are %d actions", len(tx.Actions))

	for _, act := range tx.Actions {
		// check for nil argument
		if act == nil {
			return errors.New("nil action")
		}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Rebuild the transaction so it contains at least one TransactionAction before signing/submitting (tx.Actions != nil)
  2. Check the client SDK serialization path that produced the data bytes
  3. Add a pre-submission check that the marshalled transaction is non-empty

Example fix

// before
tx := &common.Transaction{}
raw, _ := proto.Marshal(tx)
// after
tx := &common.Transaction{Actions: []*common.TransactionAction{action}}
raw, _ := proto.Marshal(tx)
Defensive patterns

Strategy: validation

Validate before calling

tx, err := protoutil.UnmarshalTransaction(data)
if err != nil || tx == nil || len(tx.Actions) == 0 { return errors.New("transaction empty or nil") }

Type guard

func isNonEmptyTransaction(data []byte) bool {
	tx, err := protoutil.UnmarshalTransaction(data)
	return err == nil && tx != nil
}

Prevention

When it happens

Trigger: ValidateTransaction given data bytes that decode to an empty Transaction proto (e.g. zero-length or empty protobuf encoding); exercised directly by TestInvocationsBadArgs.

Common situations: Protobuf serialization bugs on the client side (signing an empty transaction), protobuf version mismatches producing empty messages, or marshalling a Transaction struct without setting Actions.

Related errors


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