From 0abd674062d214dcb93ff6ca227d0042c5208ec1 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 20 Mar 2026 15:31:40 +0000 Subject: [PATCH] Fix last commit hash missmatch during replay (#3097) The Commit.Hash() algorithm was changed in CON-76 (#2600) to include Height, Round, and BlockID in the Merkle tree alongside signatures. Blocks stored by v6.3 nodes have LastCommitHash computed with the old signatures-only algorithm. When v6.4 loads these blocks during ABCI handshake replay, ValidateBasic recomputes the hash with the new algorithm, producing a different value and causing a panic first observed on `arctic-1`. Adds Commit.LegacyHash() that uses the old (signatures-only) algorithm, and updates Block.ValidateBasic() to fall back to it when the new hash doesn't match. Note that release 6.5 would make this fallback path entirely redundant, and it should be removed in the next release. Separated out from https://github.com/sei-protocol/sei-chain/pull/3095 to capture a clear commit history of changes for the future archeologists. (cherry picked from commit 512976dda41f8bdcefbc0f6f0c4b97aea8ce9148) --- sei-tendermint/types/block.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/sei-tendermint/types/block.go b/sei-tendermint/types/block.go index 21acdea1de..212154836b 100644 --- a/sei-tendermint/types/block.go +++ b/sei-tendermint/types/block.go @@ -89,7 +89,10 @@ func (b *Block) ValidateBasic() error { } if w, g := b.LastCommit.Hash(), b.LastCommitHash; !bytes.Equal(w, g) { - return fmt.Errorf("wrong Header.LastCommitHash. Expected %X, got %X", w, g) + // Fall back to legacy hash calculation pre-6.4. + if wLegacy := b.LastCommit.legacyHash(); !bytes.Equal(wLegacy, g) { + return fmt.Errorf("wrong Header.LastCommitHash. Expected %X, got %X", w, g) + } } // NOTE: b.Data.Txs may be nil, but b.Data.Hash() still works fine. @@ -1040,6 +1043,25 @@ func (ec *Commit) IsCommit() bool { return len(ec.Signatures) != 0 } +// legacyHash computes the commit hash using the pre-v6.4 algorithm, which +// only includes signatures (not Height, Round, or BlockID). This is needed +// to validate blocks that were created before the CommitHash change. +func (commit *Commit) legacyHash() tmbytes.HexBytes { + if commit == nil { + return nil + } + bs := make([][]byte, len(commit.Signatures)) + for i, commitSig := range commit.Signatures { + pbcs := commitSig.ToProto() + bz, err := pbcs.Marshal() + if err != nil { + panic(err) + } + bs[i] = bz + } + return merkle.HashFromByteSlices(bs) +} + //------------------------------------- // Data contains the set of transactions included in the block