From bda20e69a60d61990fa8cf62b2bd8b912f861cd2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 11:15:47 +0000 Subject: [PATCH 1/2] fix: accept legacy CommitHash in ValidateBasic to allow v6.3->v6.4 upgrade The Commit.Hash() algorithm 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, ValidateBasic recomputes the hash with the new algorithm, causing a mismatch panic. Add Commit.LegacyHash() that uses the old algorithm, and fall back to it in ValidateBasic when the new hash doesn't match. Co-authored-by: Masih H. Derkani --- sei-tendermint/types/block.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/sei-tendermint/types/block.go b/sei-tendermint/types/block.go index 21acdea1de..a918ed7c43 100644 --- a/sei-tendermint/types/block.go +++ b/sei-tendermint/types/block.go @@ -89,7 +89,9 @@ 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) + 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. @@ -902,6 +904,25 @@ func (commit *Commit) Hash() tmbytes.HexBytes { return commit.hash } +// 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) +} + // StringIndented returns a string representation of the commit. func (commit *Commit) StringIndented(indent string) string { if commit == nil { From 111c0d317cffcfbb5aedb5c6b4bab6e0d84be62d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 11:38:48 +0000 Subject: [PATCH 2/2] fix: copy mmap'd byte slices in historical RPC queries to prevent use-after-free When serving historical queries, the Query method opens a read-only memiavl instance (mmap'd), queries it, and defers Close(). The response byte slices (Key, Value) may point directly into the mmap'd region. When the deferred Close() unmaps the memory before JSON marshaling reads the response, the process segfaults (SIGSEGV). Copy res.Key and res.Value for non-latest queries so they survive the mmap unmap. Co-authored-by: Masih H. Derkani --- sei-cosmos/storev2/rootmulti/store.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sei-cosmos/storev2/rootmulti/store.go b/sei-cosmos/storev2/rootmulti/store.go index baa0c14efc..1ead10c963 100644 --- a/sei-cosmos/storev2/rootmulti/store.go +++ b/sei-cosmos/storev2/rootmulti/store.go @@ -638,6 +638,19 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { res := store.Query(req) + // When serving from a historical read-only memiavl instance, the response + // byte slices (Key, Value) may point directly into mmap'd memory that will + // be unmapped when the deferred scStore.Close() runs. Copy them so the + // caller doesn't hit a use-after-free segfault during JSON marshaling. + if !latest { + if len(res.Key) > 0 { + res.Key = append([]byte{}, res.Key...) + } + if len(res.Value) > 0 { + res.Value = append([]byte{}, res.Value...) + } + } + // If underlying query failed (e.g. invalid height/path) or doesn' need proof, return as-is. if res.Code != 0 || !needProof { return res