hyperledger/fabric · error

not a config block

Error message

not a config block

What it means

ConsensusMetadataFromConfigBlock validates its input with protoutil.IsConfigBlock and returns this error if the block's last envelope is not of config type. The function only reads Raft consensus metadata from configuration blocks, so any ordinary transaction block is rejected.

Source

Thrown at orderer/consensus/etcdraft/util.go:183

	switch channelHeader.GetType() {
	case int32(common.HeaderType_ORDERER_TRANSACTION):
		return nil, errors.Errorf("unsupported legacy system channel header type: %v", channelHeader.GetType())
	case int32(common.HeaderType_CONFIG):
		return envelope, nil
	default:
		return nil, errors.Errorf("unexpected header type: %v", channelHeader.GetType())
	}
}

// ConsensusMetadataFromConfigBlock reads consensus metadata updates from the configuration block
func ConsensusMetadataFromConfigBlock(block *common.Block) (*etcdraft.ConfigMetadata, *orderer.ConsensusType, error) {
	if block == nil {
		return nil, nil, errors.New("nil block")
	}

	if !protoutil.IsConfigBlock(block) {
		return nil, nil, errors.New("not a config block")
	}

	configEnvelope, err := ConfigEnvelopeFromBlock(block)
	if err != nil {
		return nil, nil, errors.Wrap(err, "cannot read config update")
	}

	payload, err := protoutil.UnmarshalPayload(configEnvelope.GetPayload())
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to extract payload from config envelope")
	}
	// get config update
	configUpdate, err := configtx.UnmarshalConfigUpdateFromPayload(payload)
	if err != nil {
		return nil, nil, errors.Wrap(err, "could not read config update")
	}

	return MetadataFromConfigUpdate(configUpdate)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Check protoutil.IsConfigBlock(block) at the call site and skip non-config blocks.
  2. Subscribe to config-update events or filter delivered blocks on HeaderType_CONFIG before parsing.
  3. Ensure the block passed is the actual latest config block from the channel's config history.

Example fix

// before
for block := range blocks {
    meta, _, err := ConsensusMetadataFromConfigBlock(block)
    ...
}

// after
for block := range blocks {
    if !protoutil.IsConfigBlock(block) {
        continue // ordinary tx block, not a config update
    }
    meta, _, err := ConsensusMetadataFromConfigBlock(block)
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

if !protoutil.IsConfigBlock(block) {
    return errors.New("input is not a config block")
}

Type guard

func isUsableConfigBlock(block *common.Block) bool {
    return block != nil && protoutil.IsConfigBlock(block)
}

Try / catch

meta, _, err := ConsensusMetadataFromConfigBlock(block)
if err != nil {
    if err.Error() == "not a config block" {
        return nil // non-config block: safe to ignore
    }
    return err
}

Prevention

When it happens

Trigger: Passing a regular (transaction) block — not HeaderType_CONFIG — into ConsensusMetadataFromConfigBlock, e.g. forwarding every delivered block instead of only config blocks.

Common situations: Block-deliver handlers that fail to filter on header type; using the wrong block number when inspecting raft config changes; replaying a channel from a snapshot and handing the first non-config block to the parser.

Related errors


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