Skip to content

Commit 686f465

Browse files
authored
feat(store): allow rollback past included height for sync node + improve rollback cmd (#2699)
rollback cmd <!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. Ex: Closes #<issue number> -->
1 parent 9fd66d0 commit 686f465

File tree

7 files changed

+115
-90
lines changed

7 files changed

+115
-90
lines changed

apps/evm/single/cmd/rollback.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import (
1717

1818
// NewRollbackCmd creates a command to rollback ev-node state by one height.
1919
func NewRollbackCmd() *cobra.Command {
20-
var height uint64
20+
var (
21+
height uint64
22+
skipP2PStores bool
23+
syncNode bool
24+
)
2125

2226
cmd := &cobra.Command{
2327
Use: "rollback",
@@ -61,10 +65,15 @@ func NewRollbackCmd() *cobra.Command {
6165
}
6266

6367
// rollback ev-node main state
64-
if err := evolveStore.Rollback(goCtx, height); err != nil {
68+
if err := evolveStore.Rollback(goCtx, height, !syncNode); err != nil {
6569
return fmt.Errorf("failed to rollback ev-node state: %w", err)
6670
}
6771

72+
if skipP2PStores {
73+
fmt.Printf("Rolled back ev-node state to height %d\n", height)
74+
return nil
75+
}
76+
6877
// rollback ev-node goheader state
6978
headerStore, err := goheaderstore.NewStore[*types.SignedHeader](
7079
evolveDB,
@@ -108,5 +117,8 @@ func NewRollbackCmd() *cobra.Command {
108117
}
109118

110119
cmd.Flags().Uint64Var(&height, "height", 0, "rollback to a specific height")
120+
cmd.Flags().BoolVar(&syncNode, "sync-node", false, "sync node (no aggregator)")
121+
cmd.Flags().BoolVar(&skipP2PStores, "skip-p2p-stores", false, "skip rollback p2p stores (goheaderstore)")
122+
111123
return cmd
112124
}

apps/testapp/cmd/rollback.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import (
1818

1919
// NewRollbackCmd creates a command to rollback ev-node state by one height.
2020
func NewRollbackCmd() *cobra.Command {
21-
var height uint64
21+
var (
22+
height uint64
23+
skipP2PStores bool
24+
syncNode bool
25+
)
2226

2327
cmd := &cobra.Command{
2428
Use: "rollback",
@@ -67,10 +71,15 @@ func NewRollbackCmd() *cobra.Command {
6771
}
6872

6973
// rollback ev-node main state
70-
if err := evolveStore.Rollback(goCtx, height); err != nil {
74+
if err := evolveStore.Rollback(goCtx, height, !syncNode); err != nil {
7175
return fmt.Errorf("failed to rollback ev-node state: %w", err)
7276
}
7377

78+
if skipP2PStores {
79+
fmt.Printf("Rolled back ev-node state to height %d\n", height)
80+
return nil
81+
}
82+
7483
// rollback ev-node goheader state
7584
headerStore, err := goheaderstore.NewStore[*types.SignedHeader](
7685
evolveDB,
@@ -119,5 +128,7 @@ func NewRollbackCmd() *cobra.Command {
119128
}
120129

121130
cmd.Flags().Uint64Var(&height, "height", 0, "rollback to a specific height")
131+
cmd.Flags().BoolVar(&syncNode, "sync-node", false, "sync node (no aggregator)")
132+
cmd.Flags().BoolVar(&skipP2PStores, "skip-p2p-stores", false, "skip rollback p2p stores (goheaderstore)")
122133
return cmd
123134
}

pkg/store/store.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,10 @@ func (s *DefaultStore) GetMetadata(ctx context.Context, key string) ([]byte, err
262262
}
263263

264264
// Rollback rolls back block data until the given height from the store.
265+
// When aggregator is true, it will check the latest data included height and prevent rollback further than that.
265266
// NOTE: this function does not rollback metadata. Those should be handled separately if required.
266267
// Other stores are not rolled back either.
267-
func (s *DefaultStore) Rollback(ctx context.Context, height uint64) error {
268+
func (s *DefaultStore) Rollback(ctx context.Context, height uint64, aggregator bool) error {
268269
batch, err := s.db.Batch(ctx)
269270
if err != nil {
270271
return fmt.Errorf("failed to create a new batch: %w", err)
@@ -285,7 +286,16 @@ func (s *DefaultStore) Rollback(ctx context.Context, height uint64) error {
285286
} else if len(daIncludedHeightBz) == 8 { // valid height stored, so able to check
286287
daIncludedHeight := binary.LittleEndian.Uint64(daIncludedHeightBz)
287288
if daIncludedHeight > height {
288-
return fmt.Errorf("DA included height is greater than the rollback height: cannot rollback a finalized height")
289+
// an aggregator must not rollback a finalized height, DA is the source of truth
290+
if aggregator {
291+
return fmt.Errorf("DA included height is greater than the rollback height: cannot rollback a finalized height")
292+
} else { // in case of syncing issues, rollback the included height is OK.
293+
bz := make([]byte, 8)
294+
binary.LittleEndian.PutUint64(bz, height)
295+
if err := batch.Put(ctx, ds.NewKey(getMetaKey(DAIncludedHeightKey)), bz); err != nil {
296+
return fmt.Errorf("failed to update DA included height: %w", err)
297+
}
298+
}
289299
}
290300
}
291301

pkg/store/store_test.go

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ func TestRollback(t *testing.T) {
671671

672672
// Execute rollback to height 7
673673
rollbackToHeight := uint64(7)
674-
err = store.Rollback(ctx, rollbackToHeight)
674+
err = store.Rollback(ctx, rollbackToHeight, true)
675675
require.NoError(err)
676676

677677
// Verify new height
@@ -718,7 +718,7 @@ func TestRollbackToSameHeight(t *testing.T) {
718718
require.NoError(err)
719719

720720
// Execute rollback to same height
721-
err = store.Rollback(ctx, height)
721+
err = store.Rollback(ctx, height, true)
722722
require.NoError(err)
723723

724724
// Verify height unchanged
@@ -753,7 +753,7 @@ func TestRollbackToHigherHeight(t *testing.T) {
753753

754754
// Execute rollback to higher height
755755
rollbackToHeight := uint64(10)
756-
err = store.Rollback(ctx, rollbackToHeight)
756+
err = store.Rollback(ctx, rollbackToHeight, true)
757757
require.NoError(err)
758758

759759
// Verify height unchanged
@@ -778,7 +778,7 @@ func TestRollbackBatchError(t *testing.T) {
778778
}
779779
store := New(mock)
780780

781-
err := store.Rollback(ctx, uint64(5))
781+
err := store.Rollback(ctx, uint64(5), true)
782782
require.Error(err)
783783
require.Contains(err.Error(), "failed to create a new batch")
784784
}
@@ -795,7 +795,7 @@ func TestRollbackHeightError(t *testing.T) {
795795
}
796796
store := New(mock)
797797

798-
err := store.Rollback(ctx, uint64(5))
798+
err := store.Rollback(ctx, uint64(5), true)
799799
require.Error(err)
800800
require.Contains(err.Error(), "failed to get current height")
801801
}
@@ -806,7 +806,7 @@ func TestRollbackDAIncludedHeightValidation(t *testing.T) {
806806
require := require.New(t)
807807

808808
// Test case 1: Rollback to height below DA included height should fail
809-
t.Run("rollback below DA included height fails", func(t *testing.T) {
809+
t.Run("rollback below DA included height fails as aggregator", func(t *testing.T) {
810810
ctx := context.Background()
811811
store := New(mustNewInMem())
812812

@@ -844,11 +844,59 @@ func TestRollbackDAIncludedHeightValidation(t *testing.T) {
844844
require.NoError(err)
845845

846846
// Rollback to height below DA included height should fail
847-
err = store.Rollback(ctx, uint64(6))
847+
err = store.Rollback(ctx, uint64(6), true)
848848
require.Error(err)
849849
require.Contains(err.Error(), "DA included height is greater than the rollback height: cannot rollback a finalized height")
850850
})
851851

852+
// Test case 2: Rollback to height below DA included height should succeed as sync node
853+
t.Run("rollback below DA included succeed as sync node", func(t *testing.T) {
854+
ctx := context.Background()
855+
store := New(mustNewInMem())
856+
857+
// Setup: create and save multiple blocks
858+
chainID := "test-rollback-da-sync-success"
859+
maxHeight := uint64(10)
860+
861+
for h := uint64(1); h <= maxHeight; h++ {
862+
header, data := types.GetRandomBlock(h, 2, chainID)
863+
sig := &header.Signature
864+
865+
err := store.SaveBlockData(ctx, header, data, sig)
866+
require.NoError(err)
867+
868+
err = store.SetHeight(ctx, h)
869+
require.NoError(err)
870+
871+
// Create and update state for this height
872+
state := types.State{
873+
ChainID: chainID,
874+
InitialHeight: 1,
875+
LastBlockHeight: h,
876+
LastBlockTime: header.Time(),
877+
AppHash: header.AppHash,
878+
}
879+
err = store.UpdateState(ctx, state)
880+
require.NoError(err)
881+
}
882+
883+
// Set DA included height to 8
884+
daIncludedHeight := uint64(8)
885+
heightBytes := make([]byte, 8)
886+
binary.LittleEndian.PutUint64(heightBytes, daIncludedHeight)
887+
err := store.SetMetadata(ctx, DAIncludedHeightKey, heightBytes)
888+
require.NoError(err)
889+
890+
// Rollback to height below DA included height should fail
891+
err = store.Rollback(ctx, uint64(6), false)
892+
require.NoError(err)
893+
894+
// Verify height was rolled back to 6
895+
currentHeight, err := store.Height(ctx)
896+
require.NoError(err)
897+
require.Equal(uint64(6), currentHeight)
898+
})
899+
852900
// Test case 2: Rollback to height equal to DA included height should succeed
853901
t.Run("rollback to DA included height succeeds", func(t *testing.T) {
854902
ctx := context.Background()
@@ -888,7 +936,7 @@ func TestRollbackDAIncludedHeightValidation(t *testing.T) {
888936
require.NoError(err)
889937

890938
// Rollback to height equal to DA included height should succeed
891-
err = store.Rollback(ctx, uint64(8))
939+
err = store.Rollback(ctx, uint64(8), true)
892940
require.NoError(err)
893941

894942
// Verify height was rolled back to 8
@@ -936,7 +984,7 @@ func TestRollbackDAIncludedHeightValidation(t *testing.T) {
936984
require.NoError(err)
937985

938986
// Rollback to height above DA included height should succeed
939-
err = store.Rollback(ctx, uint64(9))
987+
err = store.Rollback(ctx, uint64(9), true)
940988
require.NoError(err)
941989

942990
// Verify height was rolled back to 9
@@ -982,7 +1030,7 @@ func TestRollbackDAIncludedHeightNotSet(t *testing.T) {
9821030

9831031
// Don't set DA included height - it should not exist
9841032
// Rollback should succeed since no DA included height is set
985-
err := store.Rollback(ctx, uint64(3))
1033+
err := store.Rollback(ctx, uint64(3), true)
9861034
require.NoError(err)
9871035

9881036
// Verify height was rolled back to 3
@@ -1031,7 +1079,7 @@ func TestRollbackDAIncludedHeightInvalidLength(t *testing.T) {
10311079
require.NoError(err)
10321080

10331081
// Rollback should succeed since invalid length data is ignored
1034-
err = store.Rollback(ctx, uint64(3))
1082+
err = store.Rollback(ctx, uint64(3), true)
10351083
require.NoError(err)
10361084

10371085
// Verify height was rolled back to 3
@@ -1074,7 +1122,7 @@ func TestRollbackDAIncludedHeightGetMetadataError(t *testing.T) {
10741122
mock.getMetadataError = errors.New("metadata retrieval failed")
10751123

10761124
// Rollback should fail due to GetMetadata error
1077-
err = store.Rollback(ctx, uint64(1))
1125+
err = store.Rollback(ctx, uint64(1), true)
10781126
require.Error(err)
10791127
require.Contains(err.Error(), "failed to get DA included height")
10801128
require.Contains(err.Error(), "metadata retrieval failed")

pkg/store/types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ type Store interface {
4747
GetMetadata(ctx context.Context, key string) ([]byte, error)
4848

4949
// Rollback deletes x height from the ev-node store.
50-
Rollback(ctx context.Context, height uint64) error
50+
// Aggregator is used to determine if the rollback is performed on the aggregator node.
51+
Rollback(ctx context.Context, height uint64, aggregator bool) error
5152

5253
// Close safely closes underlying data storage, to ensure that data is actually saved.
5354
Close() error

test/mocks/external/hstore.go

Lines changed: 0 additions & 63 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)