hyperledger/fabric · error

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

Error message

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

What it means

The parsed ChannelHeader has a non-zero Version field. Fabric endorser transactions use version 0 for the channel header; a non-zero version means the header was constructed incorrectly or by tooling using a different versioning scheme. The parser rejects it to guarantee the transaction matches Fabric's expected format.

Source

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

		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")
	}

	if e.ChaincodeID == nil {
		return errors.New("nil ChaincodeId")
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Leave ChannelHeader.Version unset (zero value) when building the transaction header.
  2. Use protoutil.BuildChannelHeader to construct headers instead of manual structs.
  3. Check for header reuse across transaction types and create a fresh header for endorser txs.

Example fix

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

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

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

Prevention

When it happens

Trigger: UnmarshalEndorserTxAndValidate is given an envelope whose ChannelHeader.Version != 0 — e.g. a header built with Version set manually or reused from another header type (e.g. config updates that use versioning).

Common situations: Copying headers from config or block transactions that legitimately set Version; SDK/tooling that defaults Version to 1; version-skew after Fabric upgrades where custom builders picked wrong values.

Related errors


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