hyperledger/fabric · error

invalid epoch in ChannelHeader. Expected 0, got [%d]

Error message

invalid epoch in ChannelHeader. Expected 0, got [%d]

What it means

The parsed ChannelHeader has a non-zero Epoch field. Fabric endorser transactions must have Epoch set to 0; a non-zero epoch indicates the header was built incorrectly or by non-standard tooling. This parser enforces the Fabric spec requirement strictly.

Source

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

	return &EndorserTx{
		ComputedTxID: computedTxID,
		ChannelID:    txenv.ChannelHeader.ChannelId,
		Creator:      txenv.SignatureHeader.Creator,
		Response:     ccAction.Response,
		Events:       ccAction.Events,
		Results:      ccAction.Results,
		Endorsements: ccActionPayload.Action.Endorsements,
		ChaincodeID:  hdrExt.ChaincodeId,
		Type:         txenv.ChannelHeader.Type,
		Version:      txenv.ChannelHeader.Version,
		Epoch:        txenv.ChannelHeader.Epoch,
		Nonce:        txenv.SignatureHeader.Nonce,
	}, nil
}

func (e *EndorserTx) validate() error {
	if e.Epoch != 0 {
		return errors.Errorf("invalid epoch in ChannelHeader. Expected 0, got [%d]", e.Epoch)
	}

	if e.Version != 0 {
		return errors.Errorf("invalid version in ChannelHeader. Expected 0, got [%d]", e.Version)
	}

	if err := ValidateChannelID(e.ChannelID); err != nil {
		return err
	}

	if len(e.Nonce) == 0 {
		return errors.New("empty nonce")
	}

	if len(e.Creator) == 0 {
		return errors.New("empty creator")
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Leave ChannelHeader.Epoch unset (zero value) when building the transaction header.
  2. Regenerate the header with protoutil.BuildChannelHeader, which does not set epoch.
  3. If the envelope comes from an external system, have it produce epoch-0 headers.

Example fix

// before
ch := &common.ChannelHeader{ChannelId: "mychannel", Epoch: 3}
// after
ch := &common.ChannelHeader{ChannelId: "mychannel"} // Epoch zero value
Defensive patterns

Strategy: validation

Validate before calling

func hasZeroEpoch(ch *common.ChannelHeader) bool {
    return ch.GetEpoch() == 0
}
// check header before submission: if !hasZeroEpoch(header) { fix header }

Type guard

func isEpochZero(header *common.ChannelHeader) bool {
    return header != nil && header.Epoch == 0
}

Try / catch

tx, err := parser.UnmarshalEndorserTxAndValidate(env)
if err != nil {
    if strings.Contains(err.Error(), "invalid epoch") {
        // rebuild the header with epoch 0 and resubmit
        return ErrInvalidHeader
    }
    return err
}

Prevention

When it happens

Trigger: UnmarshalEndorserTxAndValidate is given an envelope whose ChannelHeader.Epoch != 0 — typically set manually when constructing the header or copied from a config/other header type.

Common situations: Custom transaction builders hardcoding an epoch value; headers copied from block/config transactions where epoch carries meaning; ported code from other Fabric components that populate Epoch.

Related errors


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