Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(vmsync): introduce SyncStrategy pattern for static/dynamic sync
Extract sync orchestration into a strategy pattern to improve code
organization and separation of concerns.

- Add SyncStrategy interface in client.go for sync orchestration.
- Extract VM finalization logic to finalizer.go with sentinel errors.
- Add staticStrategy for sequential sync without block queueing.
- Add dynamicStrategy wrapping Coordinator for concurrent sync.
- Simplify client.go by delegating to strategies.
- Simplify sync_target.go by removing redundant ID field.
- Move syncer creation to standalone newSyncerRegistry function.
  • Loading branch information
powerslider committed Dec 3, 2025
commit 58abd3ebf0cb3b71840434a9edbda13349d499a5
106 changes: 106 additions & 0 deletions graft/coreth/plugin/evm/vmsync/strategy_dynamic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package vmsync

import (
"context"
"fmt"

"github.com/ava-labs/libevm/log"

"github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/message"
)

var _ SyncStrategy = (*dynamicStrategy)(nil)

// dynamicStrategy runs syncers concurrently with block queueing.
// It wraps [Coordinator] to manage the sync lifecycle.
type dynamicStrategy struct {
coordinator *Coordinator
}

func newDynamicStrategy(registry *SyncerRegistry, finalizer *finalizer, pivotInterval uint64) *dynamicStrategy {
coordinator := NewCoordinator(
registry,
Callbacks{
FinalizeVM: finalizer.finalize,
OnDone: nil, // Set in Start to capture completion.
},
WithPivotInterval(pivotInterval),
)
return &dynamicStrategy{coordinator: coordinator}
}

// Start launches the coordinator and blocks until sync completes or fails.
func (d *dynamicStrategy) Start(ctx context.Context, summary message.Syncable) error {
done := make(chan error, 1)

// Wire up OnDone to signal completion.
d.coordinator.callbacks.OnDone = func(err error) {
if err != nil {
log.Error("dynamic state sync completed with error", "err", err)
} else {
log.Info("dynamic state sync completed successfully")
}
done <- err
}

d.coordinator.Start(ctx, summary)
return <-done
}

// OnBlockAccepted enqueues the block for deferred processing and updates the sync target.
func (d *dynamicStrategy) OnBlockAccepted(b EthBlockWrapper) (bool, error) {
if d.coordinator.CurrentState() == StateExecutingBatch {
// Still enqueue for the next batch, but don't update target.
return d.enqueue(b, OpAccept), nil
}

if !d.enqueue(b, OpAccept) {
return false, nil
}

ethb := b.GetEthBlock()
target := newSyncTarget(ethb.Hash(), ethb.Root(), ethb.NumberU64())
if err := d.coordinator.UpdateSyncTarget(target); err != nil {
// Block is enqueued but target update failed.
return true, fmt.Errorf("block enqueued but sync target update failed: %w", err)
}
return true, nil
}

// OnBlockRejected enqueues the block for deferred rejection.
func (d *dynamicStrategy) OnBlockRejected(b EthBlockWrapper) (bool, error) {
return d.enqueue(b, OpReject), nil
}

// OnBlockVerified enqueues the block for deferred verification.
func (d *dynamicStrategy) OnBlockVerified(b EthBlockWrapper) (bool, error) {
return d.enqueue(b, OpVerify), nil
}

// enqueue adds a block operation to the coordinator's queue.
func (d *dynamicStrategy) enqueue(b EthBlockWrapper, op BlockOperationType) bool {
ok := d.coordinator.AddBlockOperation(b, op)
if !ok {
if ethb := b.GetEthBlock(); ethb != nil {
log.Warn("could not enqueue block operation",
"hash", ethb.Hash(),
"height", ethb.NumberU64(),
"op", op.String(),
)
}
}
return ok
}

// CurrentState returns the coordinator's current state.
func (d *dynamicStrategy) CurrentState() State {
return d.coordinator.CurrentState()
}

// UpdateSyncTarget updates the coordinator's sync target.
func (d *dynamicStrategy) UpdateSyncTarget(target message.Syncable) error {
return d.coordinator.UpdateSyncTarget(target)
}
22 changes: 8 additions & 14 deletions graft/coreth/plugin/evm/vmsync/sync_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,27 @@ import (

var _ message.Syncable = (*syncTarget)(nil)

// syncTarget is a minimal implementation of message.Syncable used internally
// syncTarget is a minimal implementation of [message.Syncable] used internally
// to advance the coordinator's sync target from engine-accepted blocks.
//
// NOTE: Unlike [message.BlockSyncSummary], this is not serializable and should not
// be used for network communication. Only [message.Syncable.GetBlockHash],
// [message.Syncable.GetBlockRoot], and [message.Syncable.Height] are used in practice.
// The other methods are stubs to satisfy the interface.
type syncTarget struct {
id ids.ID
hash common.Hash
root common.Hash
height uint64
}

// Build a sync target from basic fields.
func newSyncTarget(hash common.Hash, root common.Hash, height uint64) message.Syncable {
var id ids.ID
copy(id[:], hash[:])
return &syncTarget{
id: id,
hash: hash,
root: root,
height: height,
}
return &syncTarget{hash: hash, root: root, height: height}
}

// message.Syncable
func (s *syncTarget) GetBlockHash() common.Hash { return s.hash }
func (s *syncTarget) GetBlockRoot() common.Hash { return s.root }

// block.StateSummary
func (s *syncTarget) ID() ids.ID { return s.id }
func (s *syncTarget) ID() ids.ID { return ids.ID(s.hash) }
func (s *syncTarget) Height() uint64 { return s.height }
func (s *syncTarget) Bytes() []byte { return s.hash.Bytes() }
func (*syncTarget) Accept(context.Context) (block.StateSyncMode, error) {
Expand Down