hyperledger/fabric · error

invalid header type %s

Error message

invalid header type %s

What it means

Validate() checks the proposal's channel header Type; only ENDORSER_TRANSACTION (and tolerated CONFIG reads in legacy paths) may reach the Propose API. Any other header type (e.g. CONFIG_UPDATE, TRANSACTION, QUERY) is rejected because proposals to the endorser are by definition endorsement requests.

Source

Thrown at core/endorser/msgvalidation.go:138

	}, nil
}

func (up *UnpackedProposal) Validate(idDeserializer msp.IdentityDeserializer) error {
	logger := decorateLogger(endorserLogger, &ccprovider.TransactionParams{
		ChannelID: up.ChannelHeader.ChannelId,
		TxID:      up.TxID(),
	})

	// validate the header type
	switch common.HeaderType(up.ChannelHeader.Type) {
	case common.HeaderType_ENDORSER_TRANSACTION:
	case common.HeaderType_CONFIG:
		// The CONFIG transaction type has _no_ business coming to the propose API.
		// In fact, anything coming to the Propose API is by definition an endorser
		// transaction, so any other header type seems like it ought to be an error... oh well.

	default:
		return errors.Errorf("invalid header type %s", common.HeaderType(up.ChannelHeader.Type))
	}

	// ensure the epoch is 0
	if up.ChannelHeader.Epoch != 0 {
		return errors.Errorf("epoch is non-zero")
	}

	// ensure that there is a nonce
	if len(up.SignatureHeader.Nonce) == 0 {
		return errors.Errorf("nonce is empty")
	}

	// ensure that there is a creator
	if len(up.SignatureHeader.Creator) == 0 {
		return errors.New("creator is empty")
	}

	expectedTxID := protoutil.ComputeTxID(up.SignatureHeader.Nonce, up.SignatureHeader.Creator)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Ensure the proposal is built as an ENDORSER_TRANSACTION (ChannelHeader.Type = HeaderType_ENDORSER_TRANSACTION), which SDKs do automatically.
  2. Do not submit config-update or orderer envelopes to the endorser Propose API; use the orderer/system channel APIs instead.
  3. Check that custom protobuf assembly code sets the header type explicitly to 1 (ENDORSER_TRANSACTION).

Example fix

// before
chdr.Type = int32(common.HeaderType_TRANSACTION)
// after
chdr.Type = int32(common.HeaderType_ENDORSER_TRANSACTION)
Defensive patterns

Strategy: validation

Validate before calling

if hdr.Type != int32(common.HeaderType_ENDORSER_TRANSACTION) {
    return fmt.Errorf("only ENDORSER_TRANSACTION proposals allowed, got %d", hdr.Type)
}

Type guard

func isEndorserTx(h *common.ChannelHeader) bool {
    return h != nil && h.Type == int32(common.HeaderType_ENDORSER_TRANSACTION)
}

Prevention

When it happens

Trigger: ProcessProposal -> preProcess -> Validate on a SignedProposal whose ChannelHeader.Type is not ENDORSER_TRANSACTION — e.g. reusing a config-update envelope or an already-committed transaction envelope as a proposal.

Common situations: Feeding orderer envelopes (BlockData transactions) back into the peer's ProcessProposal, misconfigured custom clients setting the header type manually, replaying fabric messages across the wrong APIs.

Related errors


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