{"record":{"id":"587506caacef2928","repo":"hyperledger/fabric","slug":"block-header-mismatch-between-the-block-we-pulled","errorCode":null,"errorMessage":"block header mismatch between the block we pulled and the block we joined with, sequence %d","messagePattern":"block header mismatch between the block we pulled and the block we joined with, sequence (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"orderer/common/follower/follower_chain.go","lineNumber":550,"sourceCode":"\t\t\treturn n, ErrChainStopped\n\t\tdefault:\n\t\t\tnextBlock := c.blockPuller.PullBlock(seq)\n\t\t\tif nextBlock == nil {\n\t\t\t\treturn n, errors.WithMessagef(cluster.ErrRetryCountExhausted, \"failed to pull block %d\", seq)\n\t\t\t}\n\n\t\t\treportedPrevHash := nextBlock.Header.PreviousHash\n\t\t\tif (nextBlock.Header.Number > 0) && !bytes.Equal(reportedPrevHash, actualPrevHash) {\n\t\t\t\treturn n, errors.Errorf(\"block header previous hash mismatch on sequence %d, expected %x, got %x\",\n\t\t\t\t\tnextBlock.Header.Number, actualPrevHash, reportedPrevHash)\n\t\t\t}\n\n\t\t\tif c.joinBlock != nil && c.joinBlock.Header.Number == nextBlock.Header.Number {\n\t\t\t\t// We don't need to verify the block.Data because we verify the join-block's DataHash against the\n\t\t\t\t// hash(join-block.Data) when we verify it during the `Join` REST API call\n\t\t\t\tif !proto.Equal(nextBlock.Header, c.joinBlock.Header) {\n\t\t\t\t\tc.logger.Errorf(\"Block header mismatch between the block we pulled and the block we joined with, sequence %d\", c.joinBlock.Header.Number)\n\t\t\t\t\treturn n, errors.Errorf(\"block header mismatch between the block we pulled and the block we joined with, sequence %d\", c.joinBlock.Header.Number)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tactualPrevHash = protoutil.BlockHeaderHash(nextBlock.Header)\n\t\t\tif err := c.ledgerResources.Append(nextBlock); err != nil {\n\t\t\t\treturn n, errors.WithMessagef(err, \"failed to append block %d to the ledger\", nextBlock.Header.Number)\n\t\t\t}\n\n\t\t\tif protoutil.IsConfigBlock(nextBlock) {\n\t\t\t\tc.logger.Debugf(\"Pulled blocks from %d to %d, last block is config\", firstBlockToPull, nextBlock.Header.Number)\n\t\t\t\tc.lastConfig = nextBlock\n\t\t\t\tif err := c.blockPullerFactory.UpdateVerifierFromConfigBlock(nextBlock); err != nil {\n\t\t\t\t\treturn n, errors.WithMessagef(err, \"failed to update verifier from last config,  block number: %d\", nextBlock.Header.Number)\n\t\t\t\t}\n\t\t\t\tif updateEndpoints {\n\t\t\t\t\tendpoints, err := cluster.EndpointconfigFromConfigBlock(nextBlock, c.cryptoProvider)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn n, errors.WithMessagef(err, \"failed to extract endpoints from last config,  block number: %d\", nextBlock.Header.Number)","sourceCodeStart":532,"sourceCodeEnd":568,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/common/follower/follower_chain.go#L532-L568","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"jb, _ := proto.Marshal(joinBlock.Header)\nif pb, _ := proto.Marshal(pulledBlock.Header); !bytes.Equal(jb, pb) { return errors.New(\"join block diverges from service chain; regenerate join block\") }","typeGuard":null,"tryCatchPattern":"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 }","preventionTips":["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"],"tags":["orderer","fabric","join-block","chain-divergence"],"backgroundTag":"block-header-mismatch","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}