Skip to content
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
a2b5450
feat(sync/vm): parallelize syncer execution with errgroup
powerslider Aug 19, 2025
2b8b9e3
Merge branch 'master' into powerslider/1130-concurrent-syncers
powerslider Aug 19, 2025
13f7c4a
test(sync/vm): simplify registry tests using utilstest
powerslider Aug 20, 2025
292f8b2
test(sync/vm): remove unnecessary test cleanups
powerslider Aug 20, 2025
47e71af
test: forgot to add FuncSyncer implementations
powerslider Aug 20, 2025
f6174a4
test: improve comments
powerslider Aug 21, 2025
9772d76
test: use require.FailNow instead of t.Fatal
powerslider Aug 21, 2025
f3e03b8
test: make the base test timeout be derived by t.Deadline
powerslider Aug 21, 2025
0d62f6d
Merge branch 'master' into powerslider/1130-concurrent-syncers
powerslider Aug 21, 2025
90d2911
fix: gci formatting
powerslider Aug 21, 2025
5c49827
fix: do not assume on first syncer failing
powerslider Aug 21, 2025
40b7313
fix: more remarks
powerslider Aug 22, 2025
a6497d2
Merge branch 'master' into powerslider/1130-concurrent-syncers
powerslider Aug 22, 2025
489cb79
feat(sync): decouple code syncer from state syncer and run concurrently
powerslider Aug 22, 2025
8645bc3
fix: split table test into separate tests to reduce complexity
powerslider Aug 25, 2025
5436497
Merge branch 'powerslider/1130-concurrent-syncers' into powerslider/1…
powerslider Aug 25, 2025
0451ace
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Aug 27, 2025
d4be816
chore: refactor and rename code syncer interfaces
powerslider Aug 27, 2025
e569f56
fix: more renaming
powerslider Aug 27, 2025
f86373b
fix: goimports
powerslider Aug 27, 2025
bab7f02
fix: gci format
powerslider Aug 27, 2025
49f3c53
chore(sync): add Ready() to CodeFetcher and gate state sync startup
powerslider Aug 27, 2025
c422987
fix: broken test and lint issue
powerslider Aug 27, 2025
19fe953
chore: rename FinishCodeCollection to Finalize
powerslider Aug 27, 2025
b3c04be
docs: document CodeFetcher
powerslider Aug 27, 2025
9f2f658
fix: ignore require_no_error_inline_func due to messing with the cont…
powerslider Aug 27, 2025
ed714de
fix: workaround for require_no_error_inline_func due to messing with …
powerslider Aug 27, 2025
78f536e
fix: modify test_require_no_error_inline_func to flag only single-LHS…
powerslider Aug 27, 2025
b7d98a3
fix: remove atomic name in client.go
powerslider Aug 28, 2025
0ec9d1d
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Aug 28, 2025
f20fe23
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Aug 29, 2025
01aeeee
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 1, 2025
14fe808
feat(sync): add Name() and ID() to Syncer interface to make syncer im…
powerslider Sep 2, 2025
5063873
feat(statesync): introduce CodeFetcherQueue and decouple CodeSyncer c…
powerslider Sep 3, 2025
c7b86db
fix: move getCodeToFetchFromDB to code fetcher
powerslider Sep 3, 2025
e8fee23
fix: add CodeFetcherOption type
powerslider Sep 3, 2025
3a447b6
fix: some formatting
powerslider Sep 3, 2025
bd5be21
fix: PR remarks on tests
powerslider Sep 3, 2025
af55380
fix: more PR remarks on tests
powerslider Sep 4, 2025
1f971aa
fix: add ability to track in-flight AddCode calls to coordinate clean…
powerslider Sep 4, 2025
401b564
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 5, 2025
5be557a
refactor(sync/statesync): harden code fetcher lifecycle, improve shut…
powerslider Sep 5, 2025
ee8b54f
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 9, 2025
51873a1
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 11, 2025
2097dbc
fix: apply recommended optimizations
powerslider Sep 12, 2025
4c64aa5
feat(sync/statesync): adopt single forwarder pattern for CodeQueue
powerslider Sep 12, 2025
8492d1e
fix: channel close races
powerslider Sep 15, 2025
af18251
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 15, 2025
63b8dbd
fix: linter error and simplify in tests
powerslider Sep 15, 2025
3151ef3
fix: some naming changes
powerslider Sep 15, 2025
be20084
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 16, 2025
aed34f1
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 17, 2025
0b35e75
refactor(plugin/evm): move syncer client/registry to plugin/evm/vmsyn…
powerslider Sep 17, 2025
5487e18
Remove libevm `cmd/*` packages. (#1224)
JonathanOppenheimer Sep 17, 2025
ed6bb70
Remove libevm `signer` package (#1225)
JonathanOppenheimer Sep 17, 2025
35e6b12
Remove duplicate cancun checks (#1223)
alarso16 Sep 17, 2025
446f283
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 18, 2025
f8a7e4c
fix: some naming changes
powerslider Sep 23, 2025
adadd3c
refactor: simplify `CodeQueue` fan-in (#1254)
powerslider Sep 23, 2025
e5d0c7d
refactor(statesync): simplify CodeQueue, remove auto-init, rename cap…
powerslider Sep 23, 2025
76e1ce0
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 23, 2025
41f199e
refactor(statesync): move code deduplication from queue (producer) to…
powerslider Sep 23, 2025
8f779fd
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 23, 2025
dc046e8
fix: some naming changes
powerslider Sep 23, 2025
3e9f945
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 24, 2025
416a906
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 24, 2025
54c2592
fix(statesync): prevent data race by serializing out close with enqueues
powerslider Sep 24, 2025
6b1a6e3
fix(statesync): eliminate send/close race in code queue
powerslider Sep 24, 2025
3b596bb
fix: remove unused method
powerslider Sep 24, 2025
ebfbed4
Merge branch 'master' into powerslider/1139-code-syncer-decoupling
powerslider Sep 24, 2025
aea5f06
fix(statesync): serialize code ownership and release after durable co…
powerslider Sep 25, 2025
c895e3d
fix: handle error for `Finalize` call
powerslider Sep 25, 2025
a40996b
fix(statesync): make Finalize idempotent via CAS; error only on quit
powerslider Sep 25, 2025
f535b36
refactor(statesync): remove `CodeRequestQueue` interface and use `Cod…
powerslider Sep 25, 2025
1aad3e7
refactor(statesync): simplify CodeQueue by removing AddCode state che…
powerslider Sep 25, 2025
13d652e
fix(statesync): add fix to align with `AddCode` contract
powerslider Sep 25, 2025
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
6 changes: 6 additions & 0 deletions plugin/evm/atomic/sync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ var (
_ syncclient.LeafSyncTask = (*syncerLeafTask)(nil)
)

// Name returns the human-readable name for this sync task.
func (*syncer) Name() string { return "Atomic State Syncer" }

// ID returns the stable identifier for this sync task.
func (*syncer) ID() string { return "state_atomic_sync" }

// Config holds the configuration for creating a new atomic syncer.
type Config struct {
// TargetHeight is the target block height to sync to.
Expand Down
78 changes: 51 additions & 27 deletions plugin/evm/vmsync/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,13 @@ import (
"github.com/ava-labs/coreth/sync/blocksync"
"github.com/ava-labs/coreth/sync/statesync"

synccommon "github.com/ava-labs/coreth/sync"
syncpkg "github.com/ava-labs/coreth/sync"
syncclient "github.com/ava-labs/coreth/sync/client"
)

const (
// BlocksToFetch is the number of the block parents the state syncs to.
// The last 256 block hashes are necessary to support the BLOCKHASH opcode.
BlocksToFetch = 256

atomicStateSyncOperationName = "Atomic State Sync"
blockSyncOperationName = "Block Sync"
evmStateSyncOperationName = "EVM State Sync"
)
// BlocksToFetch is the number of the block parents the state syncs to.
// The last 256 block hashes are necessary to support the BLOCKHASH opcode.
const BlocksToFetch = 256

var stateSyncSummaryKey = []byte("stateSyncSummary")

Expand Down Expand Up @@ -67,7 +61,7 @@ type ClientConfig struct {
Parser message.SyncableParser

// Extender is an optional extension point for the state sync process, and can be nil.
Extender synccommon.Extender
Extender syncpkg.Extender
Client syncclient.Client
StateSyncDone chan struct{}

Expand Down Expand Up @@ -169,52 +163,82 @@ func (client *client) stateSync(ctx context.Context) error {

func (client *client) registerSyncers(registry *SyncerRegistry) error {
// Register block syncer.
syncer, err := client.createBlockSyncer(client.summary.GetBlockHash(), client.summary.Height())
blockSyncer, err := client.createBlockSyncer(client.summary.GetBlockHash(), client.summary.Height())
if err != nil {
return fmt.Errorf("failed to create block syncer: %w", err)
}
if err := registry.Register(blockSyncOperationName, syncer); err != nil {
return fmt.Errorf("failed to register block syncer: %w", err)

codeQueue, err := client.createCodeQueue()
if err != nil {
return fmt.Errorf("failed to create code queue: %w", err)
}

// Register EVM state syncer.
evmSyncer, err := client.createEVMSyncer()
codeSyncer, err := client.createCodeSyncer(codeQueue.CodeHashes())
if err != nil {
return fmt.Errorf("failed to create EVM syncer: %w", err)
return fmt.Errorf("failed to create code syncer: %w", err)
}

if err := registry.Register(evmStateSyncOperationName, evmSyncer); err != nil {
return fmt.Errorf("failed to register EVM syncer: %w", err)
stateSyncer, err := client.createEVMSyncer(codeQueue)
if err != nil {
return fmt.Errorf("failed to create EVM state syncer: %w", err)
}

// Register atomic syncer.
var atomicSyncer syncpkg.Syncer
if client.Extender != nil {
atomicSyncer, err := client.createAtomicSyncer()
atomicSyncer, err = client.createAtomicSyncer()
if err != nil {
return fmt.Errorf("failed to create atomic syncer: %w", err)
}
}

if err := registry.Register(atomicStateSyncOperationName, atomicSyncer); err != nil {
return fmt.Errorf("failed to register atomic syncer: %w", err)
syncers := []syncpkg.Syncer{
blockSyncer,
codeSyncer,
stateSyncer,
}
if atomicSyncer != nil {
syncers = append(syncers, atomicSyncer)
}

for _, s := range syncers {
if err := registry.Register(s); err != nil {
return fmt.Errorf("failed to register %s syncer: %w", s.Name(), err)
}
}

return nil
}

func (client *client) createBlockSyncer(fromHash common.Hash, fromHeight uint64) (synccommon.Syncer, error) {
func (client *client) createBlockSyncer(fromHash common.Hash, fromHeight uint64) (syncpkg.Syncer, error) {
return blocksync.NewSyncer(client.Client, client.ChainDB, blocksync.Config{
FromHash: fromHash,
FromHeight: fromHeight,
BlocksToFetch: BlocksToFetch,
})
}

func (client *client) createEVMSyncer() (synccommon.Syncer, error) {
return statesync.NewSyncer(client.Client, client.ChainDB, client.summary.GetBlockRoot(), statesync.NewDefaultConfig(client.RequestSize))
func (client *client) createEVMSyncer(fetcher syncpkg.CodeRequestQueue) (syncpkg.Syncer, error) {
return statesync.NewSyncer(
client.Client,
client.ChainDB,
client.summary.GetBlockRoot(),
fetcher,
statesync.NewDefaultConfig(client.RequestSize),
)
}

func (client *client) createCodeQueue() (syncpkg.CodeRequestQueue, error) {
return statesync.NewCodeQueue(
client.ChainDB,
client.StateSyncDone,
)
}

func (client *client) createCodeSyncer(codeHashes <-chan common.Hash) (syncpkg.Syncer, error) {
return statesync.NewCodeSyncer(client.Client, client.ChainDB, codeHashes)
}

func (client *client) createAtomicSyncer() (synccommon.Syncer, error) {
func (client *client) createAtomicSyncer() (syncpkg.Syncer, error) {
return client.Extender.CreateSyncer(client.Client, client.VerDB, client.summary)
}

Expand Down
8 changes: 8 additions & 0 deletions plugin/evm/vmsync/doubles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"errors"
"sync"
"time"

syncpkg "github.com/ava-labs/coreth/sync"
)

// FuncSyncer adapts a function to the simple Syncer shape used in tests. It is
Expand All @@ -19,6 +21,12 @@ type FuncSyncer struct {
// Sync calls the wrapped function and returns its result.
func (f FuncSyncer) Sync(ctx context.Context) error { return f.fn(ctx) }

// Name returns the provided name or a default if unspecified.
func (FuncSyncer) Name() string { return "Test Name" }
func (FuncSyncer) ID() string { return "test_id" }

var _ syncpkg.Syncer = FuncSyncer{}

// NewBarrierSyncer returns a syncer that, upon entering Sync, calls wg.Done() to
// signal it has started, then blocks until either:
// - `releaseCh` is closed, returning nil; or
Expand Down
13 changes: 7 additions & 6 deletions plugin/evm/vmsync/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type SyncerTask struct {
// SyncerRegistry manages a collection of syncers for sequential execution.
type SyncerRegistry struct {
syncers []SyncerTask
registeredNames map[string]bool // Track registered names to prevent duplicates.
registeredNames map[string]bool // Track registered IDs to prevent duplicates.
}

// NewSyncerRegistry creates a new empty syncer registry.
Expand All @@ -36,13 +36,14 @@ func NewSyncerRegistry() *SyncerRegistry {

// Register adds a syncer to the registry.
// Returns an error if a syncer with the same name is already registered.
func (r *SyncerRegistry) Register(name string, syncer synccommon.Syncer) error {
if r.registeredNames[name] {
return fmt.Errorf("syncer with name '%s' is already registered", name)
func (r *SyncerRegistry) Register(syncer synccommon.Syncer) error {
id := syncer.ID()
if r.registeredNames[id] {
return fmt.Errorf("syncer with id '%s' is already registered", id)
}

r.registeredNames[name] = true
r.syncers = append(r.syncers, SyncerTask{name, syncer})
r.registeredNames[id] = true
r.syncers = append(r.syncers, SyncerTask{syncer.Name(), syncer})

return nil
}
Expand Down
32 changes: 22 additions & 10 deletions plugin/evm/vmsync/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ func (m *mockSyncer) Sync(context.Context) error {
return m.syncError
}

func (m *mockSyncer) Name() string { return m.name }
func (m *mockSyncer) ID() string { return m.name }

// namedSyncer adapts an existing syncer with a provided name to satisfy Syncer with Name().
type namedSyncer struct {
name string
syncer syncpkg.Syncer
}

func (n *namedSyncer) Sync(ctx context.Context) error { return n.syncer.Sync(ctx) }
func (n *namedSyncer) Name() string { return n.name }
func (n *namedSyncer) ID() string { return n.name }

// syncerConfig describes a test syncer setup for RunSyncerTasks table tests.
type syncerConfig struct {
name string
Expand Down Expand Up @@ -67,12 +80,12 @@ func TestSyncerRegistry_Register(t *testing.T) {
expectedCount: 2,
},
{
name: "duplicate name registration",
name: "duplicate id registration",
registrations: []*mockSyncer{
newMockSyncer("Syncer1", nil),
newMockSyncer("Syncer1", nil),
},
expectedError: "syncer with name 'Syncer1' is already registered",
expectedError: "syncer with id 'Syncer1' is already registered",
expectedCount: 1,
},
{
Expand All @@ -93,7 +106,7 @@ func TestSyncerRegistry_Register(t *testing.T) {

// Perform registrations.
for _, reg := range tt.registrations {
err := registry.Register(reg.name, reg)
err := registry.Register(reg)
if err != nil {
errLast = err
break
Expand Down Expand Up @@ -163,7 +176,7 @@ func TestSyncerRegistry_RunSyncerTasks(t *testing.T) {
for i, syncerConfig := range tt.syncers {
mockSyncer := newMockSyncer(syncerConfig.name, syncerConfig.syncError)
mockSyncers[i] = mockSyncer
require.NoError(t, registry.Register(syncerConfig.name, mockSyncer))
require.NoError(t, registry.Register(mockSyncer))
}

ctx, cancel := utilstest.NewTestContext(t)
Expand Down Expand Up @@ -200,8 +213,8 @@ func TestSyncerRegistry_ConcurrentStart(t *testing.T) {

for i := 0; i < numBarrierSyncers; i++ {
name := fmt.Sprintf("BarrierSyncer-%d", i)
syncer := NewBarrierSyncer(&allStartedWG, releaseCh)
require.NoError(t, registry.Register(name, syncer))
s := &namedSyncer{name: name, syncer: NewBarrierSyncer(&allStartedWG, releaseCh)}
require.NoError(t, registry.Register(s))
}

doneCh := make(chan error, 1)
Expand All @@ -224,7 +237,7 @@ func TestSyncerRegistry_ErrorPropagatesAndCancelsOthers(t *testing.T) {
// Error syncer
trigger := make(chan struct{})
errFirst := errors.New("test error")
require.NoError(t, registry.Register("ErrorSyncer-0", NewErrorSyncer(trigger, errFirst)))
require.NoError(t, registry.Register(&namedSyncer{name: "ErrorSyncer-0", syncer: NewErrorSyncer(trigger, errFirst)}))

// Cancel-aware syncers to verify cancellation propagation
const numCancelSyncers = 2
Expand All @@ -237,8 +250,7 @@ func TestSyncerRegistry_ErrorPropagatesAndCancelsOthers(t *testing.T) {
cancelChans = append(cancelChans, canceledCh)
startedChans = append(startedChans, startedCh)
name := fmt.Sprintf("CancelSyncer-%d", i)

require.NoError(t, registry.Register(name, NewCancelAwareSyncer(startedCh, canceledCh, 4*time.Second)))
require.NoError(t, registry.Register(&namedSyncer{name: name, syncer: NewCancelAwareSyncer(startedCh, canceledCh, 4*time.Second)}))
}

doneCh := make(chan error, 1)
Expand Down Expand Up @@ -280,7 +292,7 @@ func TestSyncerRegistry_FirstErrorWinsAcrossMany(t *testing.T) {
errFirst = errInstance
}
name := fmt.Sprintf("ErrorSyncer-%d", i)
require.NoError(t, registry.Register(name, NewErrorSyncer(trigger, errInstance)))
require.NoError(t, registry.Register(&namedSyncer{name: name, syncer: NewErrorSyncer(trigger, errInstance)}))
}

doneCh := make(chan error, 1)
Expand Down
12 changes: 10 additions & 2 deletions scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,17 @@ function test_require_error_is_no_funcs_as_params {
}

function test_require_no_error_inline_func {
if grep -R -zo -P '\t+err :?= ((?!require|if).|\n)*require\.NoError\((t, )?err\)' "${DEFAULT_FILES[@]}"; then
# Flag only when a single variable whose name contains "err" or "Err"
# (e.g., err, myErr, parseError) is assigned from a call (:= or =), and later
# that same variable is passed to require.NoError(...). We explicitly require
# no commas on the LHS to avoid flagging multi-return assignments like
# "val, err := f()" or "err, val := f()".
#
# Capture the variable name and enforce it matches in the subsequent require.NoError.
local -r pattern='^\s*([A-Za-z_][A-Za-z0-9_]*[eE]rr[A-Za-z0-9_]*)\s*:?=\s*[^,\n]*\([^)]*\).*\n(?:(?!^\s*(?:if|require)).*\n)*^\s*require\.NoError\((?:t,\s*)?\1\)'
if grep -R -zo -P "$pattern" "${DEFAULT_FILES[@]}"; then
echo ""
echo "Checking that a function with a single error return doesn't error should be done in-line."
echo "Checking that a function with a single error return doesn't error should be done in-line (single LHS var containing 'err')."
echo ""
return 1
fi
Expand Down
6 changes: 6 additions & 0 deletions sync/blocksync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ const blocksPerRequest = 32

var _ synccommon.Syncer = (*blockSyncer)(nil)

// Name returns the human-readable name for this sync task.
func (*blockSyncer) Name() string { return "Block Syncer" }

// ID returns the stable identifier for this sync task.
func (*blockSyncer) ID() string { return "state_block_sync" }

type Config struct {
FromHash common.Hash // `FromHash` is the most recent
FromHeight uint64
Expand Down
Loading
Loading