diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index 5578fe043..9b162de78 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -413,6 +413,9 @@ func (e *Executor) produceBlock() error { if err := batch.Commit(); err != nil { return fmt.Errorf("failed to commit batch: %w", err) } + if err := e.store.Sync(context.Background()); err != nil { + return fmt.Errorf("failed to sync store: %w", err) + } // Update in-memory state after successful commit e.setLastState(newState) diff --git a/pkg/store/benchmark_test.go b/pkg/store/benchmark_test.go new file mode 100644 index 000000000..a65332df6 --- /dev/null +++ b/pkg/store/benchmark_test.go @@ -0,0 +1,70 @@ +package store + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/evstack/ev-node/types" +) + +func BenchmarkStoreSync(b *testing.B) { + chainID := "BenchmarkStoreSync" + testCases := []struct { + name string + blockSize int + }{ + {"empty", 0}, + {"small", 10}, + {"large", 1_000}, + } + + for _, tc := range testCases { + header, data := types.GetRandomBlock(1, tc.blockSize, chainID) + signature := &types.Signature{} + + b.Run(fmt.Sprintf("WithoutSync_%s", tc.name), func(b *testing.B) { + tmpDir := b.TempDir() + kv, err := NewDefaultKVStore(tmpDir, "db", "test") + require.NoError(b, err) + store := New(kv) + b.Cleanup(func() { _ = store.Close() }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + batch, err := store.NewBatch(b.Context()) + require.NoError(b, err) + + err = batch.SaveBlockData(header, data, signature) + require.NoError(b, err) + + err = batch.Commit() + require.NoError(b, err) + } + }) + + b.Run(fmt.Sprintf("WithSync_%s", tc.name), func(b *testing.B) { + tmpDir := b.TempDir() + kv, err := NewDefaultKVStore(tmpDir, "db", "test") + require.NoError(b, err) + store := New(kv) + b.Cleanup(func() { _ = store.Close() }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + batch, err := store.NewBatch(b.Context()) + require.NoError(b, err) + + err = batch.SaveBlockData(header, data, signature) + require.NoError(b, err) + + err = batch.Commit() + require.NoError(b, err) + + err = store.Sync(b.Context()) + require.NoError(b, err) + } + }) + } +} diff --git a/pkg/store/store.go b/pkg/store/store.go index 972b94e0e..b36dd7f8e 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -190,6 +190,11 @@ func (s *DefaultStore) GetMetadata(ctx context.Context, key string) ([]byte, err return data, nil } +// Sync flushes the store state to disk +func (s *DefaultStore) Sync(ctx context.Context) error { + return s.db.Sync(ctx, ds.NewKey("/")) +} + // Rollback rolls back block data until the given height from the store. // When aggregator is true, it will check the latest data included height and prevent rollback further than that. // NOTE: this function does not rollback metadata. Those should be handled separately if required. diff --git a/pkg/store/types.go b/pkg/store/types.go index bf1cb6ced..c3f5ec3de 100644 --- a/pkg/store/types.go +++ b/pkg/store/types.go @@ -44,6 +44,9 @@ type Store interface { // NewBatch creates a new batch for atomic operations. NewBatch(ctx context.Context) (Batch, error) + + // Sync flushes the store state to disk + Sync(ctx context.Context) error } type Reader interface { diff --git a/test/mocks/store.go b/test/mocks/store.go index 7f2aa180d..ad05c76f5 100644 --- a/test/mocks/store.go +++ b/test/mocks/store.go @@ -880,3 +880,54 @@ func (_c *MockStore_SetMetadata_Call) RunAndReturn(run func(ctx context.Context, _c.Call.Return(run) return _c } + +// Sync provides a mock function for the type MockStore +func (_mock *MockStore) Sync(ctx context.Context) error { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Sync") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = returnFunc(ctx) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockStore_Sync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Sync' +type MockStore_Sync_Call struct { + *mock.Call +} + +// Sync is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockStore_Expecter) Sync(ctx interface{}) *MockStore_Sync_Call { + return &MockStore_Sync_Call{Call: _e.mock.On("Sync", ctx)} +} + +func (_c *MockStore_Sync_Call) Run(run func(ctx context.Context)) *MockStore_Sync_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockStore_Sync_Call) Return(err error) *MockStore_Sync_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockStore_Sync_Call) RunAndReturn(run func(ctx context.Context) error) *MockStore_Sync_Call { + _c.Call.Return(run) + return _c +}