hyperledger/fabric · error

block header is nil

Error message

block header is nil

What it means

NewChain validates the join block when one is supplied (i.e. joining at a non-genesis block). If joinBlock.Header is nil the block is structurally unusable — the follower needs the header to determine height, configuration, and endpoints — so creation fails immediately.

Source

Thrown at orderer/common/follower/follower_chain.go:180

	if ledgerResources.Height() > 0 {
		if err := chain.loadLastConfig(); err != nil {
			return nil, err
		}
		if err := blockPullerFactory.UpdateVerifierFromConfigBlock(chain.lastConfig); err != nil {
			return nil, err
		}
	}

	if joinBlock == nil {
		chain.status = types.StatusActive
		if isMem, _ := chain.clusterConsenter.IsChannelMember(chain.lastConfig); isMem {
			chain.consensusRelation = types.ConsensusRelationConsenter
		}

		chain.logger.Infof("Created with a nil join-block, ledger height: %d", chain.firstHeight)
	} else {
		if joinBlock.Header == nil {
			return nil, errors.New("block header is nil")
		}
		if joinBlock.Data == nil {
			return nil, errors.New("block data is nil")
		}

		// Check the block puller creation function once before we start the follower. This ensures we can extract
		// the endpoints from the join-block.
		puller, err := blockPullerFactory.BlockPuller(joinBlock, nil)
		if err != nil {
			return nil, errors.WithMessage(err, "error creating a block puller from join-block")
		}
		puller.Close()

		if chain.joinBlock.Header.Number < chain.ledgerResources.Height() {
			chain.status = types.StatusActive
		}
		if isMem, _ := chain.clusterConsenter.IsChannelMember(chain.joinBlock); isMem {
			chain.consensusRelation = types.ConsensusRelationConsenter

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Verify the join block file is a valid, complete block (check protoutil.Unmarshal / unmarshal error handling)
  2. Re-export or re-obtain the join block from a valid source (config block from genesis or an orderer)
  3. Initialize Header and Data on programmatically constructed join blocks
  4. Confirm the block was correctly read from disk (size > 0, not truncated)

Example fix

// before
var joinBlock *common.Block
proto.Unmarshal(data, joinBlock) // error ignored -> nil Header
chain, err := follower.NewChain(..., joinBlock, ...)
// after
joinBlock := &common.Block{}
if err := proto.Unmarshal(data, joinBlock); err != nil {
    return nil, errors.Wrap(err, "failed unmarshaling join block")
}
if joinBlock.Header == nil {
    return nil, errors.New("join block has nil header")
}
chain, err := follower.NewChain(..., joinBlock, ...)
Defensive patterns

Strategy: validation

Validate before calling

func validJoinBlock(b *common.Block) error {
    if b == nil {
        return errors.New("join block is nil")
    }
    if b.Header == nil {
        return errors.New("join block header is nil")
    }
    if b.Data == nil {
        return errors.New("join block data is nil")
    }
    return nil
}
// run before follower.NewChain(..., joinBlock, ...)

Type guard

func hasHeaderAndData(b *common.Block) bool {
    return b != nil && b.Header != nil && b.Data != nil
}

Try / catch

chain, err := follower.NewChain(..., joinBlock, ...)
if err != nil {
    if err.Error() == "block header is nil" {
        return fmt.Errorf("invalid join block: re-obtain a complete config block")
    }
    return err
}

Prevention

When it happens

Trigger: Calling NewChain (via createFollower or channel participation API 'join' with a join-block) with a joinBlock whose Header field is nil: unmarshaled from corrupt/empty bytes, wrong file provided, or JoinBlock wiring producing an empty block.

Common situations: osnadmin channel join supplying a malformed join block file; ledger snapshot/bootstrap code passing a block that failed unmarshaling silently; programming error constructing the JoinBlock in tests.

Related errors


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