-
Notifications
You must be signed in to change notification settings - Fork 817
feat(sync/customrawdb): migrate customrawdb package from coreth #4387
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
Merged
StephenButtolph
merged 31 commits into
master
from
powerslider/4386-migrate-customrawdb-coreth
Oct 20, 2025
+1,178
−0
Merged
Changes from 21 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
daafc3c
feat(sync/customrawdb): migrate customrawdb package from coreth
powerslider f185407
fix: migrate missing code
powerslider 136ba64
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider b79eeab
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider e6f5461
fix: add missing code needed by subnet-evm
powerslider bf62dfb
fix: add generics to ReadChainConfig and WriteChainConfig to eliminat…
powerslider fff1df9
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider 7a1e70d
test(customrawdb): add table-driven accessor tests and sentinel error…
powerslider 04094ec
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider 65d6032
fix: remove unused InspectDatabase function
powerslider aef0bb6
chore(customrawdb): switch from geth-style to explicit error returns,…
powerslider 02e8079
chore(customrawdb): switch from geth-style to explicit error returns,…
powerslider e5492d9
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider 019b3d3
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider 1db8ca0
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider d69f871
refactor(customrawdb)!: restructure package and align metadata respon…
powerslider 58e091d
refactor(customrawdb): tighten APIs and strengthen tests for sync pro…
powerslider b84267e
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider dcf2460
test: more improvements
powerslider e45d607
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider fcc919c
test: more improvements
powerslider a44d4af
test: refinining nits
powerslider 357c07d
test: remove all require.ElementsMatch calls
powerslider 0c196cf
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider 1adf32c
test: split clear no keys table test into separate tests for segments…
powerslider 00542bc
test: more small remarks
powerslider 9c81503
style: improvements
powerslider d8d4a38
Merge branch 'master' into powerslider/4386-migrate-customrawdb-coreth
powerslider 791d277
style: more improvements
powerslider abce825
fix: unexport a function
powerslider 7295327
fix: small remarks
powerslider File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. | ||
| // See the file LICENSE for licensing terms. | ||
|
|
||
| package customrawdb | ||
|
|
||
| import ( | ||
| "errors" | ||
|
|
||
| "github.com/ava-labs/libevm/core/rawdb" | ||
| "github.com/ava-labs/libevm/ethdb" | ||
| ) | ||
|
|
||
| var ( | ||
| // ErrEntryNotFound indicates the requested key/value was not present in the DB. | ||
| ErrEntryNotFound = errors.New("entry not found") | ||
| // errStateSchemeConflict indicates the provided state scheme conflicts with what is on disk. | ||
| errStateSchemeConflict = errors.New("state scheme conflict") | ||
| // FirewoodScheme is the scheme for the Firewood storage scheme. | ||
| FirewoodScheme = "firewood" | ||
| ) | ||
|
|
||
| // ParseStateScheme parses the state scheme from the provided string. | ||
| func ParseStateScheme(provided string, db ethdb.Database) (string, error) { | ||
| // Check for custom scheme | ||
| if provided == FirewoodScheme { | ||
| if diskScheme := rawdb.ReadStateScheme(db); diskScheme != "" { | ||
| // Valid scheme on db mismatched | ||
| return "", errStateSchemeConflict | ||
| } | ||
| // If no conflicting scheme is found, is valid. | ||
| return FirewoodScheme, nil | ||
| } | ||
|
|
||
| // Check for valid eth scheme | ||
| return rawdb.ParseStateScheme(provided, db) | ||
| } | ||
joshua-kim marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. | ||
| // See the file LICENSE for licensing terms. | ||
|
|
||
| package customrawdb | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/ava-labs/libevm/core/rawdb" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestParseStateScheme(t *testing.T) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No action required, but I'd probably slightly prefer this as a test vector... Admittedly this might be more succinct as is. |
||
| db := rawdb.NewMemoryDatabase() | ||
|
|
||
| // Provided Firewood on empty disk -> allowed. | ||
| scheme, err := ParseStateScheme(FirewoodScheme, db) | ||
| require.NoError(t, err) | ||
| require.Equal(t, FirewoodScheme, scheme) | ||
|
|
||
| // Simulate disk has non-empty path scheme by writing persistent state id. | ||
| rawdb.WritePersistentStateID(db, 1) | ||
| scheme2, _ := ParseStateScheme(FirewoodScheme, db) | ||
| require.Empty(t, scheme2) | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Pass-through to rawdb for non-Firewood using a fresh empty DB. | ||
| db2 := rawdb.NewMemoryDatabase() | ||
| scheme, err = ParseStateScheme("hash", db2) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "hash", scheme) | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
joshua-kim marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. | ||
| // See the file LICENSE for licensing terms. | ||
|
|
||
| package customrawdb | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/ava-labs/libevm/common" | ||
| "github.com/ava-labs/libevm/core/rawdb" | ||
| "github.com/ava-labs/libevm/ethdb" | ||
| "github.com/ava-labs/libevm/params" | ||
| "github.com/ava-labs/libevm/rlp" | ||
| ) | ||
|
|
||
| var ( | ||
| // errInvalidData indicates the stored value exists but is malformed or undecodable. | ||
| errInvalidData = errors.New("invalid data") | ||
| errFailedToGetUpgradeConfig = errors.New("failed to get upgrade config") | ||
| errFailedToMarshalUpgradeConfig = errors.New("failed to marshal upgrade config") | ||
|
|
||
| upgradeConfigPrefix = []byte("upgrade-config-") | ||
| // offlinePruningKey tracks runs of offline pruning. | ||
| offlinePruningKey = []byte("OfflinePruning") | ||
| // populateMissingTriesKey tracks runs of trie backfills. | ||
| populateMissingTriesKey = []byte("PopulateMissingTries") | ||
| // pruningDisabledKey tracks whether the node has ever run in archival mode | ||
| // to ensure that a user does not accidentally corrupt an archival node. | ||
| pruningDisabledKey = []byte("PruningDisabled") | ||
| // acceptorTipKey tracks the tip of the last accepted block that has been fully processed. | ||
| acceptorTipKey = []byte("AcceptorTipKey") | ||
| // snapshotBlockHashKey tracks the block hash of the last snapshot. | ||
| snapshotBlockHashKey = []byte("SnapshotBlockHash") | ||
| ) | ||
|
|
||
| // WriteOfflinePruning writes a time marker of the last attempt to run offline pruning. | ||
| // The marker is written when offline pruning completes and is deleted when the node | ||
| // is started successfully with offline pruning disabled. This ensures users must | ||
| // disable offline pruning and start their node successfully between runs of offline | ||
| // pruning. | ||
| func WriteOfflinePruning(db ethdb.KeyValueStore, ts time.Time) error { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return writeTimeMarker(db, offlinePruningKey, ts) | ||
| } | ||
|
|
||
| // ReadOfflinePruning reads the most recent timestamp of an attempt to run offline | ||
| // pruning if present. | ||
| func ReadOfflinePruning(db ethdb.KeyValueStore) (time.Time, error) { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return readTimeMarker(db, offlinePruningKey) | ||
| } | ||
|
|
||
| // DeleteOfflinePruning deletes any marker of the last attempt to run offline pruning. | ||
| func DeleteOfflinePruning(db ethdb.KeyValueStore) error { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return db.Delete(offlinePruningKey) | ||
| } | ||
|
|
||
| // WritePopulateMissingTries writes a marker for the current attempt to populate | ||
| // missing tries. | ||
| func WritePopulateMissingTries(db ethdb.KeyValueStore, ts time.Time) error { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return writeTimeMarker(db, populateMissingTriesKey, ts) | ||
| } | ||
|
|
||
| // ReadPopulateMissingTries reads the most recent timestamp of an attempt to | ||
| // re-populate missing trie nodes. | ||
| func ReadPopulateMissingTries(db ethdb.KeyValueStore) (time.Time, error) { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return readTimeMarker(db, populateMissingTriesKey) | ||
| } | ||
|
|
||
| // DeletePopulateMissingTries deletes any marker of the last attempt to | ||
| // re-populate missing trie nodes. | ||
| func DeletePopulateMissingTries(db ethdb.KeyValueStore) error { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return db.Delete(populateMissingTriesKey) | ||
| } | ||
|
|
||
| // WritePruningDisabled writes a marker to track whether the node has ever run | ||
| // with pruning disabled. | ||
| func WritePruningDisabled(db ethdb.KeyValueStore) error { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return db.Put(pruningDisabledKey, nil) | ||
| } | ||
|
|
||
| // HasPruningDisabled returns true if there is a marker present indicating that | ||
| // the node has run with pruning disabled at some point. | ||
| func HasPruningDisabled(db ethdb.KeyValueStore) (bool, error) { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return db.Has(pruningDisabledKey) | ||
| } | ||
|
|
||
| // WriteAcceptorTip writes `hash` as the last accepted block that has been fully processed. | ||
| func WriteAcceptorTip(db ethdb.KeyValueWriter, hash common.Hash) error { | ||
| return db.Put(acceptorTipKey, hash[:]) | ||
| } | ||
|
|
||
| // ReadAcceptorTip reads the hash of the last accepted block that was fully processed. | ||
| // If there is no value present (the index is being initialized for the first time), then the | ||
| // empty hash is returned. | ||
| func ReadAcceptorTip(db ethdb.KeyValueReader) (common.Hash, error) { | ||
| ok, err := db.Has(acceptorTipKey) | ||
| if err != nil { | ||
| return common.Hash{}, err | ||
| } | ||
| if !ok { | ||
| return common.Hash{}, ErrEntryNotFound | ||
| } | ||
| h, err := db.Get(acceptorTipKey) | ||
| if err != nil { | ||
| return common.Hash{}, err | ||
| } | ||
| if len(h) != common.HashLength { | ||
| return common.Hash{}, fmt.Errorf("%w: length %d", errInvalidData, len(h)) | ||
| } | ||
| return common.BytesToHash(h), nil | ||
| } | ||
|
|
||
| // ReadChainConfig retrieves the consensus settings based on the given genesis hash. | ||
| // The provided `upgradeConfig` (any JSON-unmarshalable type) will be populated if present on disk. | ||
| func ReadChainConfig[T any](db ethdb.KeyValueReader, hash common.Hash, upgradeConfig T) (*params.ChainConfig, error) { | ||
| config := rawdb.ReadChainConfig(db, hash) | ||
| if config == nil { | ||
| return nil, ErrEntryNotFound | ||
| } | ||
|
|
||
| upgrade, err := db.Get(upgradeConfigKey(hash)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w: %w", errFailedToGetUpgradeConfig, err) | ||
| } | ||
|
|
||
| if len(upgrade) == 0 { | ||
| return config, nil | ||
| } | ||
|
|
||
| if err := json.Unmarshal(upgrade, &upgradeConfig); err != nil { | ||
| return nil, errInvalidData | ||
| } | ||
|
|
||
| return config, nil | ||
| } | ||
|
|
||
| // WriteChainConfig writes the chain config settings to the database. | ||
| // The provided `upgradeConfig` (any JSON-marshalable type) will be stored alongside the chain config. | ||
| func WriteChainConfig[T any](db ethdb.KeyValueWriter, hash common.Hash, config *params.ChainConfig, upgradeConfig T) error { | ||
powerslider marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| rawdb.WriteChainConfig(db, hash, config) | ||
| if config == nil { | ||
| return nil | ||
| } | ||
|
|
||
| data, err := json.Marshal(upgradeConfig) | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %w", errFailedToMarshalUpgradeConfig, err) | ||
| } | ||
| if err := db.Put(upgradeConfigKey(hash), data); err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // NewAccountSnapshotsIterator returns an iterator for walking all of the accounts in the snapshot | ||
| func NewAccountSnapshotsIterator(db ethdb.Iteratee) ethdb.Iterator { | ||
| it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) | ||
| keyLen := len(rawdb.SnapshotAccountPrefix) + common.HashLength | ||
| return rawdb.NewKeyLengthIterator(it, keyLen) | ||
| } | ||
|
|
||
| // ReadSnapshotBlockHash retrieves the hash of the block whose state is contained in | ||
| // the persisted snapshot. | ||
| func ReadSnapshotBlockHash(db ethdb.KeyValueReader) (common.Hash, error) { | ||
| ok, err := db.Has(snapshotBlockHashKey) | ||
| if err != nil { | ||
| return common.Hash{}, err | ||
| } | ||
| if !ok { | ||
| return common.Hash{}, ErrEntryNotFound | ||
| } | ||
|
|
||
| data, err := db.Get(snapshotBlockHashKey) | ||
| if err != nil { | ||
| return common.Hash{}, err | ||
| } | ||
| if len(data) != common.HashLength { | ||
| return common.Hash{}, fmt.Errorf("%w: length %d", errInvalidData, len(data)) | ||
| } | ||
| return common.BytesToHash(data), nil | ||
| } | ||
|
|
||
| // WriteSnapshotBlockHash stores the root of the block whose state is contained in | ||
| // the persisted snapshot. | ||
| func WriteSnapshotBlockHash(db ethdb.KeyValueWriter, blockHash common.Hash) error { | ||
| if err := db.Put(snapshotBlockHashKey, blockHash[:]); err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // DeleteSnapshotBlockHash deletes the hash of the block whose state is contained in | ||
| // the persisted snapshot. Since snapshots are not immutable, this method can | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // be used during updates, so a crash or failure will mark the entire snapshot | ||
| // invalid. | ||
| func DeleteSnapshotBlockHash(db ethdb.KeyValueWriter) error { | ||
| if err := db.Delete(snapshotBlockHashKey); err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| func writeTimeMarker(db ethdb.KeyValueStore, key []byte, ts time.Time) error { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| data, err := rlp.EncodeToBytes(uint64(ts.Unix())) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return db.Put(key, data) | ||
| } | ||
|
|
||
| func readTimeMarker(db ethdb.KeyValueStore, key []byte) (time.Time, error) { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Check existence first to map missing marker to a stable sentinel error. | ||
| ok, err := db.Has(key) | ||
| if err != nil { | ||
| return time.Time{}, err | ||
| } | ||
| if !ok { | ||
| return time.Time{}, ErrEntryNotFound | ||
| } | ||
|
|
||
| data, err := db.Get(key) | ||
| if err != nil { | ||
| return time.Time{}, err | ||
| } | ||
| if len(data) == 0 { | ||
powerslider marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return time.Time{}, ErrEntryNotFound | ||
| } | ||
|
|
||
| var unix uint64 | ||
| if err := rlp.DecodeBytes(data, &unix); err != nil { | ||
| return time.Time{}, fmt.Errorf("%w: %w", errInvalidData, err) | ||
| } | ||
|
|
||
| return time.Unix(int64(unix), 0), nil | ||
| } | ||
|
|
||
| func upgradeConfigKey(hash common.Hash) []byte { | ||
| return append(upgradeConfigPrefix, hash.Bytes()...) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.