Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move atomic sync #723

Open
wants to merge 12 commits into
base: seperate-atomic-pkg-base
Choose a base branch
from
Open

Move atomic sync #723

wants to merge 12 commits into from

Conversation

ceyonur
Copy link
Collaborator

@ceyonur ceyonur commented Dec 28, 2024

Why this should be merged

Decouples atomic logic from syncervm server/client to atomic pkg.

How this works

This pull request introduces a new atomic sync mechanism for the EVM plugin. The changes include the creation of new types and interfaces for atomic sync summaries, along with the implementation of the sync process. The most important changes are summarized below:

Introduction of Atomic Sync Mechanism:

  • plugin/evm/atomic/atomic_sync_extender.go: Added the atomicSyncExtender type and its methods to handle the synchronization process, including Sync, OnFinishBeforeCommit, and OnFinishAfterCommit.

  • plugin/evm/atomic/atomic_sync_provider.go: Introduced the atomicSyncProvider type to provide state summaries at specific heights.

  • plugin/evm/atomic/syncable.go: Defined the AtomicBlockSyncSummary type and related functions to parse and create sync summaries.

Refactoring and Code Organization:

  • plugin/evm/message/codec.go: Updated the codec manager to register the new sync summary type and introduced a variable to hold the sync summary type. [1] [2]

  • plugin/evm/message/syncable.go: Refactored the BlockSyncSummary to implement the Syncable interface and added a parser for sync summaries.

File and Type Renaming:

  • plugin/evm/sync/syncervm_client.go: Renamed from plugin/evm/syncervm_client.go and updated to use the new Syncable interface and atomicSyncExtender for the sync process. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]

How this was tested

Existing UTs should cover

Need to be documented?

No

Need to update RELEASES.md?

No

@@ -74,13 +79,13 @@ type StateSyncClientConfig struct {
type stateSyncerClient struct {
*StateSyncClientConfig

resumableSummary message.SyncSummary
resumableSummary message.Syncable
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be nil and we do check it


cancel context.CancelFunc
wg sync.WaitGroup

// State Sync results
syncSummary message.SyncSummary
syncSummary message.Syncable
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be nil, but we never use it when it's nil.

var Codec codec.Manager
var (
Codec codec.Manager
// TODO: Remove this once we have a better way to register types (i.e use a different codec version or use build flags)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can move init to a function like (Initialize) and call in vm.Initialize like Codec.Initialize(SyncSummaryType), but then tests should be doing the same thing everytime. But it might be the only way once we merge subnet-evm since two VMs should be using two different codecs since subnet-evm do not have atomic types (and thus codec has different order of type IDs).

plugin/evm/message/syncable.go Show resolved Hide resolved
plugin/evm/vm.go Outdated Show resolved Hide resolved
plugin/evm/vm.go Outdated Show resolved Hide resolved
@ceyonur ceyonur marked this pull request as ready for review December 30, 2024 10:19
@ceyonur ceyonur requested review from darioush and a team as code owners December 30, 2024 10:19
Copy link
Collaborator

@qdm12 qdm12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tidying up 😉 And thanks for that thick detailed PR body 👍

I just left a few comments, mostly non-important things to be fair. Most of them you can apply as suggestions directly or discard them in case we should keep similar code with subnet-evm at this time.

plugin/evm/atomic/atomic_sync_extender.go Outdated Show resolved Hide resolved
plugin/evm/atomic/atomic_sync_extender.go Outdated Show resolved Hide resolved
plugin/evm/atomic/atomic_sync_extender.go Outdated Show resolved Hide resolved
plugin/evm/atomic/atomic_sync_extender.go Outdated Show resolved Hide resolved
log.Info("atomic tx: sync starting", "root", atomicSyncSummary)
atomicSyncer, err := a.backend.Syncer(client, atomicSyncSummary.AtomicRoot, atomicSyncSummary.BlockNumber, a.stateSyncRequestSize)
if err != nil {
return err
Copy link
Collaborator

@qdm12 qdm12 Dec 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return err
return fmt.Error("creating syncer: %s", err)

PS: I also used to (like you I think?) wrap all the errors with %w so the callers can use errors.Is if they want to, until very recently. The problem is you then expose API that might not even come from your own package (Arran pointed me to this), so having them as dumb strings %s is better, unless you actually need to check the errors programmatically.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but isn't that the case if we use %s in any of chained (wrapped) errs, then we would lose all the ability to check with errors.Is?

Copy link
Collaborator

@darioush darioush Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO:

  • There's no need to wrap all errors. It seems quite cumbersome to wrap every single error and in shared codebase with geth is doesn't seem feasible. I'm not sold on the advantages.
  • Seems best to wrap the error (%w) for consistency with the case that doesn't wrap the error (return err)

Given shared code, I suggest we follow the current convention of adding a wrapped error when the context has proven useful for debugging.

Additionally the geth codebase use a mix of panics, returning wrapped & unwrapped errs, and log.Crit, so I don't think this is an issue we need to address now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to wrap all errors. It seems quite cumbersome to wrap every single error
Additionally the geth codebase use a mix of panics, returning wrapped & unwrapped errs, and log.Crit, so I don't think this is an issue we need to address now.

I disagree, wrapping errors helps debugging or the user understand an error message. For example, I saw recently avalanche-cli complain about some obscure error, which, as a user, was very confusing, and, as someone who looked in the code, I couldn't really figure out where it was coming from either. Of course we don't want to modify geth code, but I don't think we should not make our part of the code better (unless geth checks the error with ==, which really it shouldn't - I might PR this to geth)

Seems best to wrap the error (%w) for consistency with the case that doesn't wrap the error (return err)

Actually I think geth do check errors directly with == whereas wrapping errors with %w only works with errors.Is, not ==, so two different operations, although errors.Is works with unwrapped (sentinel) errors. Really geth should always use errors.Is (works for both cases) and not == to check errors.

plugin/evm/message/syncable.go Outdated Show resolved Hide resolved
plugin/evm/message/syncable.go Outdated Show resolved Hide resolved
plugin/evm/message/syncable.go Outdated Show resolved Hide resolved
plugin/evm/message/syncable.go Outdated Show resolved Hide resolved
plugin/evm/vm.go Outdated Show resolved Hide resolved
@ceyonur ceyonur self-assigned this Jan 6, 2025

"github.com/ava-labs/avalanchego/snow/engine/snowman/block"

"github.com/ava-labs/coreth/core"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't import core in the atomic package this will couple it to libevm bindings again.
IMO we can have a pacakge like atomic/sync.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base automatically changed from move-atomic-state to seperate-atomic-pkg-base January 7, 2025 13:45
stateSyncRequestSize uint16
}

func NewAtomicSyncExtender(backend state.AtomicBackend, stateSyncRequestSize uint16) *AtomicSyncExtender {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit you could define an interface AtomicBackend in this package instead of using state.AtomicBackend so that:

  • reduces dependency on state package
  • narrower interface: you only need the Root method here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved them to interfaces pkg.

var _ sync.SummaryProvider = &AtomicSyncProvider{}

type AtomicSyncProvider struct {
chain *core.BlockChain
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could define an interface Chain for this chain field with only GetBlockByNumber and HasState, so that it removes the dependency on the core package.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately it goes deeper than that as GetBlockByNumber returns types.Block so we end-up importing the core package anyway. We can create an adapter and wrap the passed blockchain here, but I think having a separate pkg for sync should be good enough.

}
}

func (a *AtomicSyncExtender) Sync(ctx context.Context, client syncclient.Client, verDB *versiondb.Database, syncSummary message.Syncable) error {
Copy link
Collaborator

@qdm12 qdm12 Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client syncclient.Client can be the narrower interface client syncclient.LeafClient (Extender interface needs that update as well).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

a.stateSyncRequestSize,
)
if err != nil {
return err
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
return err
return fmt.Errorf("failed to create atomic syncer: %w", err)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return nil, fmt.Errorf("atomic trie root not found for height (%d)", height)
}

blk := a.chain.GetBlockByNumber(height)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit perhaps (makes more sense to me I'd say)

  1. alias block to snowblock
  2. then rename blk to block

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems weird to rename a package import to avoid namespace collision with a local variable. (seems one would usually rename a local variable to avoid aliasing an imported package)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep this as it is.

Comment on lines +32 to +34
// BlockSyncSummary provides the information necessary to sync a node starting
// at the given block.
type SyncSummary struct {
type BlockSyncSummary struct {
Copy link
Collaborator

@qdm12 qdm12 Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we need to rename SyncSummary to BlockSyncSummary? 🤔
It seems like unneeded changes, unless there is a reason?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to distinguish between AtomicBlockSyncSummary

return nil, fmt.Errorf("atomic trie root not found for height (%d)", height)
}

blk := a.chain.GetBlockByNumber(height)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems weird to rename a package import to avoid namespace collision with a local variable. (seems one would usually rename a local variable to avoid aliasing an imported package)

@@ -56,7 +57,7 @@ func TestSkipStateSync(t *testing.T) {
stateSyncMinBlocks: 300, // must be greater than [syncableInterval] to skip sync
syncMode: block.StateSyncSkipped,
}
vmSetup := createSyncServerAndClientVMs(t, test, StateSyncParentsToFetch)
vmSetup := createSyncServerAndClientVMs(t, test, vmsync.StateSyncParentsToFetch)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems we can rename these to drop the StateSync prefix with this refactor

plugin/evm/vm.go Outdated
Comment on lines 288 to 289
vmsync.StateSyncServer
vmsync.StateSyncClient
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems these can also be renamed to vmsync.Server / vmsync.Client

}

func init() {
message.SyncSummaryType = &AtomicBlockSyncSummary{}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this work in conjunction with the call to .RegisterType(SyncSummaryType)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is indeed an ugly assumption that assumes var is called before init blocks.

Comment on lines 389 to 391
if err := client.ExtraSyncer.OnFinishBeforeCommit(client.LastAcceptedHeight, client.syncSummary); err != nil {
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: maybe move this out of updateVMMarkers so OnFinishBeforeCommit/OnFinishAfterCommit are closer to each other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants