hyperledger/fabric · critical
block header mismatch between the block we pulled and the bl
Error message
block header mismatch between the block we pulled and the block we joined with, sequence %d
What it means
The follower previously joined the channel with a join block (obtained via the Join REST API) and now compares the block it pulled from the ordering service at the same sequence. The protobuf headers are not equal (proto.Equal fails), meaning the service's chain diverges from the block the node joined with, so appending would create an inconsistent ledger and the pull is aborted.
Source
Thrown at orderer/common/follower/follower_chain.go:550
return n, ErrChainStopped
default:
nextBlock := c.blockPuller.PullBlock(seq)
if nextBlock == nil {
return n, errors.WithMessagef(cluster.ErrRetryCountExhausted, "failed to pull block %d", seq)
}
reportedPrevHash := nextBlock.Header.PreviousHash
if (nextBlock.Header.Number > 0) && !bytes.Equal(reportedPrevHash, actualPrevHash) {
return n, errors.Errorf("block header previous hash mismatch on sequence %d, expected %x, got %x",
nextBlock.Header.Number, actualPrevHash, reportedPrevHash)
}
if c.joinBlock != nil && c.joinBlock.Header.Number == nextBlock.Header.Number {
// We don't need to verify the block.Data because we verify the join-block's DataHash against the
// hash(join-block.Data) when we verify it during the `Join` REST API call
if !proto.Equal(nextBlock.Header, c.joinBlock.Header) {
c.logger.Errorf("Block header mismatch between the block we pulled and the block we joined with, sequence %d", c.joinBlock.Header.Number)
return n, errors.Errorf("block header mismatch between the block we pulled and the block we joined with, sequence %d", c.joinBlock.Header.Number)
}
}
actualPrevHash = protoutil.BlockHeaderHash(nextBlock.Header)
if err := c.ledgerResources.Append(nextBlock); err != nil {
return n, errors.WithMessagef(err, "failed to append block %d to the ledger", nextBlock.Header.Number)
}
if protoutil.IsConfigBlock(nextBlock) {
c.logger.Debugf("Pulled blocks from %d to %d, last block is config", firstBlockToPull, nextBlock.Header.Number)
c.lastConfig = nextBlock
if err := c.blockPullerFactory.UpdateVerifierFromConfigBlock(nextBlock); err != nil {
return n, errors.WithMessagef(err, "failed to update verifier from last config, block number: %d", nextBlock.Header.Number)
}
if updateEndpoints {
endpoints, err := cluster.EndpointconfigFromConfigBlock(nextBlock, c.cryptoProvider)
if err != nil {
return n, errors.WithMessagef(err, "failed to extract endpoints from last config, block number: %d", nextBlock.Header.Number)View on GitHub (pinned to 2736b63f8f)
Solutions
- Regenerate the join block from the current, authoritative ordering service (fetch latest config block) and re-join the channel.
- Confirm the join block's channel ID and network generation match the cluster the orderer is pointing at.
- If a re-genesis occurred, reset the orderer's local ledger and join data, then onboard again from the new genesis.
- Investigate ordering-service membership/config changes between when the join block was captured and now (config block numbers must align).
Defensive patterns
Strategy: validation
Validate before calling
jb, _ := proto.Marshal(joinBlock.Header)
if pb, _ := proto.Marshal(pulledBlock.Header); !bytes.Equal(jb, pb) { return errors.New("join block diverges from service chain; regenerate join block") } Try / catch
if err != nil && strings.Contains(err.Error(), "block header mismatch between the block we pulled and the block we joined with") { regenerate join block and re-join } Prevention
- Generate the join block from the live ordering service, not from stale snapshots
- Re-generate join blocks after any consensus cluster reconfiguration
- Verify channel ID and config block number of the join block before Join
When it happens
Trigger: pullUntilTarget: c.joinBlock != nil and its Header.Number equals the pulled nextBlock's number, but nextBlock.Header differs from c.joinBlock.Header — the ordering service serves a different block at the join sequence than the block supplied in the channel Join configuration.
Common situations: The join block was taken from a snapshot/fork different from the live ordering service's chain; the admin generated the join block from a stale config or wrong channel; ordering service network was re-genesis'd after the join block was created.
Related errors
- cannot enable channel capabilities without orderer support f
- Must set some OrdererAddresses
- failed computing orderer addresses
- orderer is required, but no ordering endpoint or endorser cl
- no orderer endpoints retrieved for channel %s, pass orderer
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/587506caacef2928.
Report an issue: GitHub.