From a19bd5e0103690501eff17d18c9a500e59de46bf Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Mon, 17 Nov 2025 15:13:54 +0200 Subject: [PATCH 01/14] fix(vmsync): log context cancellation as INFO instead of ERROR during shutdown During graceful shutdown, syncers cancelled via context cancellation were being logged as ERROR level. This is misleading since cancellation during shutdown is expected behavior, not an error condition. - Use `errors.Is()` to detect `context.Canceled` and `context.DeadlineExceeded` (handles wrapped errors) and log as INFO instead of ERROR - Separate `RunSyncerTasks()` logic into a synchronous wrapper and `StartAsync()` method for async execution to gain more flexibility and handle more use cases. - Add early return optimization when context is already cancelled. Test improvements: - Add tests for cancellation scenarios (`Canceled`, `DeadlineExceeded`, wrapped errors, early return). - Fix flakiness by adding WaitGroup synchronization and replacing channel-based coordination. - Refactor tests to use `t.Context()` and extract common helpers. resolves #1410 --- graft/coreth/plugin/evm/vmsync/registry_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/graft/coreth/plugin/evm/vmsync/registry_test.go b/graft/coreth/plugin/evm/vmsync/registry_test.go index 55f17d15d01f..062235ac3292 100644 --- a/graft/coreth/plugin/evm/vmsync/registry_test.go +++ b/graft/coreth/plugin/evm/vmsync/registry_test.go @@ -149,7 +149,7 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { } }, }, { - name: "error returned", + name: "error returned and wrapped", syncers: []syncerConfig{ {"Syncer1", errFoo}, {"Syncer2", nil}, @@ -182,6 +182,12 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { require.ErrorIs(t, err, tt.expectedError) + // Verify error wrapping for real errors (not cancellation). + if tt.expectedError != nil { + require.NotEqual(t, tt.expectedError, err, "error should be wrapped") + require.Contains(t, err.Error(), "Syncer1 failed", "error message should include syncer name") + } + // Use custom assertion function for each test case. tt.assertState(t, mockSyncers) }) From 746ab4b6e62c181da4a0c4af11e62909dfa32a0d Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Mon, 17 Nov 2025 17:31:16 +0200 Subject: [PATCH 02/14] fix(vmsync): improve on remarks --- .../coreth/plugin/evm/vmsync/registry_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graft/coreth/plugin/evm/vmsync/registry_test.go b/graft/coreth/plugin/evm/vmsync/registry_test.go index 062235ac3292..439fc1b0280c 100644 --- a/graft/coreth/plugin/evm/vmsync/registry_test.go +++ b/graft/coreth/plugin/evm/vmsync/registry_test.go @@ -182,12 +182,6 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { require.ErrorIs(t, err, tt.expectedError) - // Verify error wrapping for real errors (not cancellation). - if tt.expectedError != nil { - require.NotEqual(t, tt.expectedError, err, "error should be wrapped") - require.Contains(t, err.Error(), "Syncer1 failed", "error message should include syncer name") - } - // Use custom assertion function for each test case. tt.assertState(t, mockSyncers) }) @@ -338,7 +332,19 @@ func TestSyncerRegistry_ContextCancellationErrors(t *testing.T) { startedWG := registerCancelAwareSyncers(t, registry, tt.numSyncers, tt.syncerTimeout) +<<<<<<< HEAD ctx, cancel := newTestContext(t, tt.wantErr, tt.timeout) +======= + var ( + ctx context.Context + cancel context.CancelFunc + ) + if tt.wantErr == context.DeadlineExceeded { + ctx, cancel = context.WithTimeout(t.Context(), tt.timeout) + } else { + ctx, cancel = context.WithCancel(t.Context()) + } +>>>>>>> 3b38cf0708 (fix(vmsync): improve on remarks) t.Cleanup(cancel) doneCh := startSyncersAsync(registry, ctx, newTestClientSummary(t)) From eaef2adf375fac181192ae005a03df1877e77463 Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Mon, 17 Nov 2025 17:48:47 +0200 Subject: [PATCH 03/14] fix: remove checking if error is wrapped --- graft/coreth/plugin/evm/vmsync/registry_test.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/graft/coreth/plugin/evm/vmsync/registry_test.go b/graft/coreth/plugin/evm/vmsync/registry_test.go index 439fc1b0280c..55f17d15d01f 100644 --- a/graft/coreth/plugin/evm/vmsync/registry_test.go +++ b/graft/coreth/plugin/evm/vmsync/registry_test.go @@ -149,7 +149,7 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { } }, }, { - name: "error returned and wrapped", + name: "error returned", syncers: []syncerConfig{ {"Syncer1", errFoo}, {"Syncer2", nil}, @@ -332,19 +332,7 @@ func TestSyncerRegistry_ContextCancellationErrors(t *testing.T) { startedWG := registerCancelAwareSyncers(t, registry, tt.numSyncers, tt.syncerTimeout) -<<<<<<< HEAD ctx, cancel := newTestContext(t, tt.wantErr, tt.timeout) -======= - var ( - ctx context.Context - cancel context.CancelFunc - ) - if tt.wantErr == context.DeadlineExceeded { - ctx, cancel = context.WithTimeout(t.Context(), tt.timeout) - } else { - ctx, cancel = context.WithCancel(t.Context()) - } ->>>>>>> 3b38cf0708 (fix(vmsync): improve on remarks) t.Cleanup(cancel) doneCh := startSyncersAsync(registry, ctx, newTestClientSummary(t)) From cd986bc1d9aca6d7d40b8fb914d15887d1f0035f Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Mon, 17 Nov 2025 18:04:02 +0200 Subject: [PATCH 04/14] style: improvements --- graft/coreth/plugin/evm/vmsync/registry_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/graft/coreth/plugin/evm/vmsync/registry_test.go b/graft/coreth/plugin/evm/vmsync/registry_test.go index 55f17d15d01f..062235ac3292 100644 --- a/graft/coreth/plugin/evm/vmsync/registry_test.go +++ b/graft/coreth/plugin/evm/vmsync/registry_test.go @@ -149,7 +149,7 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { } }, }, { - name: "error returned", + name: "error returned and wrapped", syncers: []syncerConfig{ {"Syncer1", errFoo}, {"Syncer2", nil}, @@ -182,6 +182,12 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { require.ErrorIs(t, err, tt.expectedError) + // Verify error wrapping for real errors (not cancellation). + if tt.expectedError != nil { + require.NotEqual(t, tt.expectedError, err, "error should be wrapped") + require.Contains(t, err.Error(), "Syncer1 failed", "error message should include syncer name") + } + // Use custom assertion function for each test case. tt.assertState(t, mockSyncers) }) From e5cbefc8dc914afa6f53f2c4cdd9a8b76c1cff6a Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Tue, 18 Nov 2025 12:36:30 +0200 Subject: [PATCH 05/14] fix: add context cancellation check to block syncer loop --- graft/coreth/sync/blocksync/syncer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graft/coreth/sync/blocksync/syncer.go b/graft/coreth/sync/blocksync/syncer.go index 2f7340741234..de48a29fd1b1 100644 --- a/graft/coreth/sync/blocksync/syncer.go +++ b/graft/coreth/sync/blocksync/syncer.go @@ -94,6 +94,11 @@ func (s *BlockSyncer) Sync(ctx context.Context) error { // them to disk. batch := s.db.NewBatch() for fetched := uint64(0); fetched < blocksToFetch && (nextHash != common.Hash{}); { + // Check for context cancellation before making network requests. + if err := ctx.Err(); err != nil { + return err + } + log.Info("fetching blocks from peer", "fetched", fetched, "total", blocksToFetch) blocks, err := s.client.GetBlocks(ctx, nextHash, nextHeight, blocksPerRequest) if err != nil { From 51f258a54c199f4bf0da0f1a17cd94972c7e2749 Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Tue, 18 Nov 2025 13:07:48 +0200 Subject: [PATCH 06/14] fix(statesync): add context cancellation checks to prevent shutdown hang During graceful shutdown, the State Syncer was hanging because multiple blocking operations did not check context cancellation. When shutdown occurred, these operations would block indefinitely, preventing syncers from detecting cancellation and exiting gracefully. - Add context.Context parameter to LeafSyncTask.OnLeafs() interface to enable context propagation through the leaf processing call chain. - Update CodeQueue.AddCode() to accept context and check ctx.Done() before blocking on channel sends, preventing indefinite blocking when Code Syncer stops consuming during shutdown. - Update all OnLeafs implementations (mainTrieTask, storageTrieTask, trieSegment, atomic syncer) to accept and pass context through the call chain. - Add context parameter to startSyncing() and createSegments() methods, checking cancellation before blocking channel sends to the segments work queue. - Add context cancellation check in BlockSyncer before checking blocks on disk, ensuring it responds during the initial scan phase. - Update sync/client/leaf_syncer.go to pass context to OnLeafs() callbacks. This ensures all syncers detect cancellation immediately and exit gracefully instead of hanging until timeout. --- graft/coreth/sync/blocksync/syncer.go | 5 +++++ graft/coreth/sync/statesync/code_queue.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/graft/coreth/sync/blocksync/syncer.go b/graft/coreth/sync/blocksync/syncer.go index de48a29fd1b1..b1ada7dedf2d 100644 --- a/graft/coreth/sync/blocksync/syncer.go +++ b/graft/coreth/sync/blocksync/syncer.go @@ -78,6 +78,11 @@ func (s *BlockSyncer) Sync(ctx context.Context) error { // first, check for blocks already available on disk so we don't // request them from peers. for blocksToFetch > 0 { + // Check for context cancellation before checking each block. + if err := ctx.Err(); err != nil { + return err + } + blk := rawdb.ReadBlock(s.db, nextHash, nextHeight) if blk == nil { // block was not found diff --git a/graft/coreth/sync/statesync/code_queue.go b/graft/coreth/sync/statesync/code_queue.go index e61ff321f75a..b737ca9b074d 100644 --- a/graft/coreth/sync/statesync/code_queue.go +++ b/graft/coreth/sync/statesync/code_queue.go @@ -144,6 +144,12 @@ func (q *CodeQueue) AddCode(ctx context.Context, codeHashes []common.Hash) error } for _, h := range codeHashes { + // Check context cancellation before attempting to send to avoid blocking + // on a full channel when shutdown occurs. + if err := ctx.Err(); err != nil { + return err + } + select { case q.in <- h: // guaranteed to be open or nil, but never closed case <-q.quit: From 7190c8055e895de6eccc3cb766f10e2689c65e05 Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Wed, 19 Nov 2025 13:09:54 +0200 Subject: [PATCH 07/14] fix(statesync): remove pre-select context err check in code_queue.go --- graft/coreth/sync/statesync/code_queue.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/graft/coreth/sync/statesync/code_queue.go b/graft/coreth/sync/statesync/code_queue.go index b737ca9b074d..e61ff321f75a 100644 --- a/graft/coreth/sync/statesync/code_queue.go +++ b/graft/coreth/sync/statesync/code_queue.go @@ -144,12 +144,6 @@ func (q *CodeQueue) AddCode(ctx context.Context, codeHashes []common.Hash) error } for _, h := range codeHashes { - // Check context cancellation before attempting to send to avoid blocking - // on a full channel when shutdown occurs. - if err := ctx.Err(); err != nil { - return err - } - select { case q.in <- h: // guaranteed to be open or nil, but never closed case <-q.quit: From d3f9869137340444ac21eb0106735f09c95904ff Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Fri, 21 Nov 2025 16:37:36 +0200 Subject: [PATCH 08/14] feat(statesync): introduce Finalizer interface for syncer cleanup Add a `Finalizer` interface to provide explicit cleanup operations for syncers. This ensures cleanup (like flushing batches to disk) is performed reliably even on cancellation or early returns. - Add `Finalizer` interface to `sync/types.go` for explicit cleanup. - Attach `Finalize()` in `CodeQueue` that finalizes code fetching to this new interface. - Gather finalization logic in a `Finalize()` for StateSyncer to flush in-progress trie batches. - Implement `Finalize()` for AtomicSyncer to commit pending database changes. - Add `FinalizeAll()` to SyncerRegistry with defer to ensure cleanup runs. - Remove `OnFailure` callback mechanism (replaced by `Finalizer`). resolves #1089 Signed-off-by: Tsvetan Dimitrov (tsvetan.dimitrov23@gmail.com) --- graft/coreth/plugin/evm/atomic/sync/syncer.go | 12 +++++++++++- graft/coreth/plugin/evm/vmsync/registry.go | 16 ++++++++++++++++ graft/coreth/sync/blocksync/syncer.go | 10 ---------- graft/coreth/sync/client/leaf_syncer.go | 9 ++++----- graft/coreth/sync/statesync/code_queue.go | 3 +++ graft/coreth/sync/statesync/state_syncer.go | 13 ++++++------- graft/coreth/sync/types.go | 7 +++++++ 7 files changed, 47 insertions(+), 23 deletions(-) diff --git a/graft/coreth/plugin/evm/atomic/sync/syncer.go b/graft/coreth/plugin/evm/atomic/sync/syncer.go index 17099e545838..c0c24fb7c1c2 100644 --- a/graft/coreth/plugin/evm/atomic/sync/syncer.go +++ b/graft/coreth/plugin/evm/atomic/sync/syncer.go @@ -33,6 +33,7 @@ const ( var ( _ sync.Syncer = (*Syncer)(nil) _ syncclient.LeafSyncTask = (*syncerLeafTask)(nil) + _ sync.Finalizer = (*Syncer)(nil) errTargetHeightRequired = errors.New("target height must be > 0") ) @@ -125,7 +126,6 @@ func NewSyncer(client syncclient.LeafClient, db *versiondb.Database, atomicTrie syncer.syncer = syncclient.NewCallbackLeafSyncer(client, tasks, &syncclient.LeafSyncerConfig{ RequestSize: cfg.requestSize, NumWorkers: cfg.numWorkers, - OnFailure: func() {}, // No-op since we flush progress to disk at the regular commit interval. }) return syncer, nil @@ -146,6 +146,16 @@ func (s *Syncer) Sync(ctx context.Context) error { return s.syncer.Sync(ctx) } +// Finalize commits any pending database changes to disk. +// This ensures that even if the sync is cancelled or fails, we preserve +// the progress up to the last fully synced height. +func (s *Syncer) Finalize() error { + if s.db == nil { + return nil + } + return s.db.Commit() +} + // addZeroes returns the big-endian representation of `height`, prefixed with [common.HashLength] zeroes. func addZeroes(height uint64) []byte { // Key format is [height(8 bytes)][blockchainID(32 bytes)]. Start should be the diff --git a/graft/coreth/plugin/evm/vmsync/registry.go b/graft/coreth/plugin/evm/vmsync/registry.go index 5e9bc6031c3b..83c3c9ddcfc5 100644 --- a/graft/coreth/plugin/evm/vmsync/registry.go +++ b/graft/coreth/plugin/evm/vmsync/registry.go @@ -53,6 +53,10 @@ func (r *SyncerRegistry) Register(syncer syncpkg.Syncer) error { // RunSyncerTasks executes all registered syncers synchronously. func (r *SyncerRegistry) RunSyncerTasks(ctx context.Context, summary message.Syncable) error { + // Ensure finalization runs regardless of how this function exits. + // This guarantees cleanup even on early returns or panics. + defer r.FinalizeAll(summary) + // Early return if context is already canceled (e.g., during shutdown). if err := ctx.Err(); err != nil { return err @@ -102,3 +106,15 @@ func (r *SyncerRegistry) StartAsync(ctx context.Context, summary message.Syncabl return g } + +// FinalizeAll iterates over all registered syncers and calls Finalize on those that implement the Finalizer interface. +// Errors are logged but not returned to ensure best-effort cleanup of all syncers. +func (r *SyncerRegistry) FinalizeAll(summary message.Syncable) { + for _, task := range r.syncers { + if f, ok := task.syncer.(syncpkg.Finalizer); ok { + if err := f.Finalize(); err != nil { + log.Error("failed to finalize syncer", "syncer", task.name, "err", err, "summary", summary.GetBlockHash().Hex(), "height", summary.Height()) + } + } + } +} diff --git a/graft/coreth/sync/blocksync/syncer.go b/graft/coreth/sync/blocksync/syncer.go index b1ada7dedf2d..2f7340741234 100644 --- a/graft/coreth/sync/blocksync/syncer.go +++ b/graft/coreth/sync/blocksync/syncer.go @@ -78,11 +78,6 @@ func (s *BlockSyncer) Sync(ctx context.Context) error { // first, check for blocks already available on disk so we don't // request them from peers. for blocksToFetch > 0 { - // Check for context cancellation before checking each block. - if err := ctx.Err(); err != nil { - return err - } - blk := rawdb.ReadBlock(s.db, nextHash, nextHeight) if blk == nil { // block was not found @@ -99,11 +94,6 @@ func (s *BlockSyncer) Sync(ctx context.Context) error { // them to disk. batch := s.db.NewBatch() for fetched := uint64(0); fetched < blocksToFetch && (nextHash != common.Hash{}); { - // Check for context cancellation before making network requests. - if err := ctx.Err(); err != nil { - return err - } - log.Info("fetching blocks from peer", "fetched", fetched, "total", blocksToFetch) blocks, err := s.client.GetBlocks(ctx, nextHash, nextHeight, blocksPerRequest) if err != nil { diff --git a/graft/coreth/sync/client/leaf_syncer.go b/graft/coreth/sync/client/leaf_syncer.go index 68053cce6e13..8ec06b48175d 100644 --- a/graft/coreth/sync/client/leaf_syncer.go +++ b/graft/coreth/sync/client/leaf_syncer.go @@ -37,7 +37,6 @@ type LeafSyncTask interface { type LeafSyncerConfig struct { RequestSize uint16 // Number of leafs to request from a peer at a time NumWorkers int // Number of workers to process leaf sync tasks - OnFailure func() // Callback for handling errors during sync } type CallbackLeafSyncer struct { @@ -159,9 +158,9 @@ func (c *CallbackLeafSyncer) Sync(ctx context.Context) error { }) } - err := eg.Wait() - if err != nil { - c.config.OnFailure() + if err := eg.Wait(); err != nil { + return err } - return err + + return nil } diff --git a/graft/coreth/sync/statesync/code_queue.go b/graft/coreth/sync/statesync/code_queue.go index e61ff321f75a..bff82fdc9825 100644 --- a/graft/coreth/sync/statesync/code_queue.go +++ b/graft/coreth/sync/statesync/code_queue.go @@ -15,11 +15,14 @@ import ( "github.com/ava-labs/libevm/libevm/options" "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb" + syncpkg "github.com/ava-labs/avalanchego/graft/coreth/sync" ) const defaultQueueCapacity = 5000 var ( + _ syncpkg.Finalizer = (*CodeQueue)(nil) + errFailedToAddCodeHashesToQueue = errors.New("failed to add code hashes to queue") errFailedToFinalizeCodeQueue = errors.New("failed to finalize code queue") ) diff --git a/graft/coreth/sync/statesync/state_syncer.go b/graft/coreth/sync/statesync/state_syncer.go index a286431dd1aa..80a5a19bd83b 100644 --- a/graft/coreth/sync/statesync/state_syncer.go +++ b/graft/coreth/sync/statesync/state_syncer.go @@ -106,7 +106,6 @@ func NewSyncer(client syncclient.Client, db ethdb.Database, root common.Hash, co ss.syncer = syncclient.NewCallbackLeafSyncer(client, ss.segments, &syncclient.LeafSyncerConfig{ RequestSize: leafsRequestSize, NumWorkers: defaultNumWorkers, - OnFailure: ss.onSyncFailure, }) if codeQueue == nil { @@ -301,19 +300,19 @@ func (t *stateSync) removeTrieInProgress(root common.Hash) (int, error) { return len(t.triesInProgress), nil } -// onSyncFailure is called if the sync fails, this writes all -// batches of in-progress trie segments to disk to have maximum -// progress to restore. -func (t *stateSync) onSyncFailure() { +// Finalize checks if there are any in-progress tries and flushes their batches to disk +// to preserve progress. This is called by the syncer registry on sync failure or cancellation. +func (t *stateSync) Finalize() error { t.lock.RLock() defer t.lock.RUnlock() for _, trie := range t.triesInProgress { for _, segment := range trie.segments { if err := segment.batch.Write(); err != nil { - log.Error("failed to write segment batch on sync failure", "err", err) - return + log.Error("failed to write segment batch on finalize", "err", err) + return err } } } + return nil } diff --git a/graft/coreth/sync/types.go b/graft/coreth/sync/types.go index 04b8b75bdf77..63418aa1f5a8 100644 --- a/graft/coreth/sync/types.go +++ b/graft/coreth/sync/types.go @@ -31,6 +31,13 @@ type Syncer interface { ID() string } +// Finalizer provides a mechanism to perform cleanup operations after a sync operation. +// This is useful for handling inflight requests, flushing to disk, or other cleanup tasks. +type Finalizer interface { + // Finalize performs any necessary cleanup operations. + Finalize() error +} + // SummaryProvider is an interface for providing state summaries. type SummaryProvider interface { StateSummaryAtBlock(ethBlock *types.Block) (block.StateSummary, error) From 07546b513077095792db9afffebb6176aa433943 Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Wed, 26 Nov 2025 20:58:11 +0200 Subject: [PATCH 09/14] fix: necessary cleanup in registry_test.go --- graft/coreth/plugin/evm/vmsync/registry_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/graft/coreth/plugin/evm/vmsync/registry_test.go b/graft/coreth/plugin/evm/vmsync/registry_test.go index 062235ac3292..55f17d15d01f 100644 --- a/graft/coreth/plugin/evm/vmsync/registry_test.go +++ b/graft/coreth/plugin/evm/vmsync/registry_test.go @@ -149,7 +149,7 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { } }, }, { - name: "error returned and wrapped", + name: "error returned", syncers: []syncerConfig{ {"Syncer1", errFoo}, {"Syncer2", nil}, @@ -182,12 +182,6 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) { require.ErrorIs(t, err, tt.expectedError) - // Verify error wrapping for real errors (not cancellation). - if tt.expectedError != nil { - require.NotEqual(t, tt.expectedError, err, "error should be wrapped") - require.Contains(t, err.Error(), "Syncer1 failed", "error message should include syncer name") - } - // Use custom assertion function for each test case. tt.assertState(t, mockSyncers) }) From ce4f9e76b541f82f332319353feccac8bef8e79a Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Wed, 26 Nov 2025 21:04:12 +0200 Subject: [PATCH 10/14] style: gci format --- graft/coreth/sync/statesync/code_queue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graft/coreth/sync/statesync/code_queue.go b/graft/coreth/sync/statesync/code_queue.go index bff82fdc9825..8e628bd19ddd 100644 --- a/graft/coreth/sync/statesync/code_queue.go +++ b/graft/coreth/sync/statesync/code_queue.go @@ -9,12 +9,12 @@ import ( "fmt" "sync" + "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/ethdb" "github.com/ava-labs/libevm/libevm/options" - "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb" syncpkg "github.com/ava-labs/avalanchego/graft/coreth/sync" ) From 5796e07ef4ea39bf321576a779015c727af7dd5e Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Wed, 26 Nov 2025 23:51:13 +0200 Subject: [PATCH 11/14] style: gci format --- graft/coreth/sync/statesync/code_queue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graft/coreth/sync/statesync/code_queue.go b/graft/coreth/sync/statesync/code_queue.go index 8e628bd19ddd..bff82fdc9825 100644 --- a/graft/coreth/sync/statesync/code_queue.go +++ b/graft/coreth/sync/statesync/code_queue.go @@ -9,12 +9,12 @@ import ( "fmt" "sync" - "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/ethdb" "github.com/ava-labs/libevm/libevm/options" + "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb" syncpkg "github.com/ava-labs/avalanchego/graft/coreth/sync" ) From cbd0706a704f311980b7159512e6444d5faefd3e Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Thu, 27 Nov 2025 12:59:28 +0200 Subject: [PATCH 12/14] style: gci format --- graft/coreth/sync/statesync/code_queue.go | 1 + 1 file changed, 1 insertion(+) diff --git a/graft/coreth/sync/statesync/code_queue.go b/graft/coreth/sync/statesync/code_queue.go index bff82fdc9825..41475815b04d 100644 --- a/graft/coreth/sync/statesync/code_queue.go +++ b/graft/coreth/sync/statesync/code_queue.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/libevm/libevm/options" "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb" + syncpkg "github.com/ava-labs/avalanchego/graft/coreth/sync" ) From 0bde2b949be349dc9cf789da08cb9ee16855fa61 Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Mon, 1 Dec 2025 15:07:55 +0200 Subject: [PATCH 13/14] fix: add an early return for the success case in Finalize() in the state syncer --- graft/coreth/sync/statesync/state_syncer.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/graft/coreth/sync/statesync/state_syncer.go b/graft/coreth/sync/statesync/state_syncer.go index 80a5a19bd83b..01f75d5ab5dc 100644 --- a/graft/coreth/sync/statesync/state_syncer.go +++ b/graft/coreth/sync/statesync/state_syncer.go @@ -301,11 +301,19 @@ func (t *stateSync) removeTrieInProgress(root common.Hash) (int, error) { } // Finalize checks if there are any in-progress tries and flushes their batches to disk -// to preserve progress. This is called by the syncer registry on sync failure or cancellation. +// to preserve progress. On successful sync completion, triesInProgress will be empty +// and this is effectively a no-op. This is called by the syncer registry regardless +// of sync success or failure. func (t *stateSync) Finalize() error { t.lock.RLock() defer t.lock.RUnlock() + // Nothing to flush if no tries are in progress. + // This is the case after a successful sync. + if len(t.triesInProgress) == 0 { + return nil + } + for _, trie := range t.triesInProgress { for _, segment := range trie.segments { if err := segment.batch.Write(); err != nil { From 841ef64dd476caedc1ab218312a41fadf71f11ee Mon Sep 17 00:00:00 2001 From: Tsvetan Dimitrov Date: Mon, 1 Dec 2025 15:14:14 +0200 Subject: [PATCH 14/14] fix: remove unnecessary defensive db nil check in the atomic syncer Finalize() --- graft/coreth/plugin/evm/atomic/sync/syncer.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/graft/coreth/plugin/evm/atomic/sync/syncer.go b/graft/coreth/plugin/evm/atomic/sync/syncer.go index c0c24fb7c1c2..a2963a6d7708 100644 --- a/graft/coreth/plugin/evm/atomic/sync/syncer.go +++ b/graft/coreth/plugin/evm/atomic/sync/syncer.go @@ -150,9 +150,6 @@ func (s *Syncer) Sync(ctx context.Context) error { // This ensures that even if the sync is cancelled or fails, we preserve // the progress up to the last fully synced height. func (s *Syncer) Finalize() error { - if s.db == nil { - return nil - } return s.db.Commit() }