@@ -5,15 +5,99 @@ package run
55import (
66 "testing"
77
8+ storetypes "cosmossdk.io/store/types"
9+ abci "github.com/cometbft/cometbft/abci/types"
810 cmttypes "github.com/cometbft/cometbft/types"
911 dbm "github.com/cosmos/cosmos-db"
1012 "github.com/cosmos/cosmos-sdk/client"
1113 simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
1214 "github.com/stretchr/testify/require"
1315
16+ "github.com/altuslabsxyz/blockstm-sim/compare"
1417 "github.com/altuslabsxyz/blockstm-sim/sdkhook"
1518)
1619
20+ // stubCMS is a minimal CommitMultiStore for testing loadAndTruncate in isolation.
21+ // It simulates the rootmulti behaviour where LatestVersion() reads from the DB
22+ // (dbLatest) before LoadVersion is called, and from lastCommitInfo (loaded) after.
23+ type stubCMS struct {
24+ storetypes.CommitMultiStore
25+ dbLatest int64 // returned before loadCalled; simulates GetLatestVersion(db)
26+ loaded int64 // returned after loadCalled; simulates lastCommitInfo.Version
27+ loadCalled bool
28+ rolledBack int64 // last RollbackToVersion target, 0 if never called
29+ }
30+
31+ func (s * stubCMS ) LatestVersion () int64 {
32+ if ! s .loadCalled {
33+ return s .dbLatest
34+ }
35+ return s .loaded
36+ }
37+
38+ func (s * stubCMS ) RollbackToVersion (version int64 ) error {
39+ s .rolledBack = version
40+ s .loaded = version
41+ return nil
42+ }
43+
44+ // stubApp implements sdkhook.App using stubCMS.
45+ // Only CommitMultiStore and LoadVersion are used by loadAndTruncate.
46+ type stubApp struct { cms * stubCMS }
47+
48+ func (a * stubApp ) CommitMultiStore () storetypes.CommitMultiStore { return a .cms }
49+ func (a * stubApp ) LoadVersion (v int64 ) error {
50+ a .cms .loadCalled = true
51+ a .cms .loaded = v
52+ return nil
53+ }
54+ func (a * stubApp ) FinalizeBlock (* abci.RequestFinalizeBlock ) (* abci.ResponseFinalizeBlock , error ) {
55+ panic ("not used" )
56+ }
57+ func (a * stubApp ) Commit () (* abci.ResponseCommit , error ) { panic ("not used" ) }
58+ func (a * stubApp ) SetLifecycleObserver (compare.LifecycleObserver ) {}
59+ func (a * stubApp ) SetBlockSTMTxRunner (sdkhook.STMRunner ) {}
60+ func (a * stubApp ) UnsetBlockSTMTxRunner () {}
61+ func (a * stubApp ) SetDisableBlockGasMeter (bool ) {}
62+ func (a * stubApp ) GetStoreKeys () []storetypes.StoreKey { return nil }
63+
64+ // TestLoadAndTruncate_RollsBackWhenDBAboveTarget is the regression test for
65+ // the bug where LoadVersion(n) sets LatestVersion()=n, causing the
66+ // `LatestVersion() > n` guard to always be false and skipping RollbackToVersion.
67+ func TestLoadAndTruncate_RollsBackWhenDBAboveTarget (t * testing.T ) {
68+ // DB has version 100 (production node committed up to 100).
69+ // Target is version 50 (we want to truncate to 50).
70+ // Before fix: LoadVersion(50) → LatestVersion()=50 → 50>50=false → no rollback.
71+ // After fix: dbLatest=100 captured before LoadVersion → 100>50 → rollback.
72+ cms := & stubCMS {dbLatest : 100 }
73+ app := & stubApp {cms : cms }
74+
75+ require .NoError (t , loadAndTruncate (app , 50 ))
76+
77+ require .Equal (t , int64 (50 ), cms .rolledBack , "RollbackToVersion must be called with target version" )
78+ require .Equal (t , int64 (50 ), cms .LatestVersion ())
79+ }
80+
81+ func TestLoadAndTruncate_SkipsRollbackWhenAlreadyAtTarget (t * testing.T ) {
82+ // DB is pre-pruned: dbLatest already equals loadVersion.
83+ cms := & stubCMS {dbLatest : 50 }
84+ app := & stubApp {cms : cms }
85+
86+ require .NoError (t , loadAndTruncate (app , 50 ))
87+
88+ require .Equal (t , int64 (0 ), cms .rolledBack , "RollbackToVersion must NOT be called when DB is already at target" )
89+ }
90+
91+ func TestLoadAndTruncate_SkipsRollbackForZeroVersion (t * testing.T ) {
92+ // loadVersion=0 (meta.Start==1 case); rootmulti rejects RollbackToVersion(0).
93+ cms := & stubCMS {dbLatest : 0 }
94+ app := & stubApp {cms : cms }
95+
96+ require .NoError (t , loadAndTruncate (app , 0 ))
97+
98+ require .Equal (t , int64 (0 ), cms .rolledBack )
99+ }
100+
17101// testPruneFactory returns an AppFactory suitable for PruneSnapshot calls in
18102// tests. Full round-trip pruning behaviour (bootstrap → prune → reload) is
19103// exercised by chain-side integration tests against real `blockstm-sim extract`
0 commit comments