hyperledger/fabric · error

empty ChaincodeActionPayload

Error message

empty ChaincodeActionPayload

What it means

The TransactionAction's Payload bytes are empty, so there is no ChaincodeActionPayload to unmarshal. A valid endorser transaction action must carry a serialized ChaincodeActionPayload containing the proposal hash, endorsements, and response. An empty payload means the action was never populated.

Source

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

	}

	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 {
		return nil, errors.New("nil ChaincodeEndorsedAction")
	}

	if len(ccActionPayload.Action.ProposalResponsePayload) == 0 {
		return nil, errors.New("empty ProposalResponsePayload")
	}

	proposalResponsePayload, err := protoutil.UnmarshalProposalResponsePayload(
		ccActionPayload.Action.ProposalResponsePayload,
	)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Populate TransactionAction.Payload with protoutil.MarshalOrPanic(&peer.ChaincodeActionPayload{...}) before building the envelope.
  2. Regenerate the transaction through the standard SDK submit flow (proposal -> endorse -> assemble).
  3. Check for byte truncation in the path that serialized/transmitted the envelope.

Example fix

// before
action := &common.TransactionAction{} // Payload empty
// after
action := &common.TransactionAction{
    Payload: protoutil.MarshalOrPanic(chaincodeActionPayload),
}
Defensive patterns

Strategy: validation

Validate before calling

func hasActionPayload(tx *common.Transaction) bool {
    a := tx.GetActions()[0]
    return a != nil && len(a.Payload) > 0
}

Type guard

func hasPayload(a *common.TransactionAction) bool {
    return a != nil && len(a.Payload) > 0
}

Try / catch

tx, err := parser.UnmarshalEndorserTxAndValidate(env)
if err != nil {
    if strings.Contains(err.Error(), "empty ChaincodeActionPayload") {
        // action payload missing: regenerate transaction via SDK flow
        return ErrBadTransaction
    }
    return err
}

Prevention

When it happens

Trigger: UnmarshalEndorserTxAndValidate receives a transaction where tx.Actions[0].Payload is a zero-length byte slice — the TransactionAction struct was created but its Payload field was never assigned.

Common situations: Custom transaction builders skipping the ChaincodeActionPayload step; truncation of protobuf bytes during transport/storage; mock/test envelopes used in production paths.

Related errors


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