Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
153 changes: 153 additions & 0 deletions vms/evm/sync/customrawdb/accessors_metadata_ext.go
Copy link
Contributor

Choose a reason for hiding this comment

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

What does the _ext suffix on these files mean? Should we remove it?

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package customrawdb

import (
"encoding/json"
"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/log"
"github.com/ava-labs/libevm/params"
"github.com/ava-labs/libevm/rlp"
)

// writeCurrentTimeMarker writes a marker of the current time in the db at `key`.
func writeCurrentTimeMarker(db ethdb.KeyValueStore, key []byte) error {
data, err := rlp.EncodeToBytes(uint64(time.Now().Unix()))
if err != nil {
return err
}
return db.Put(key, data)
}

// readTimeMarker reads the timestamp stored at `key`
func readTimeMarker(db ethdb.KeyValueStore, key []byte) (time.Time, error) {
data, err := db.Get(key)
if err != nil {
return time.Time{}, err
}

var unix uint64
if err := rlp.DecodeBytes(data, &unix); err != nil {
return time.Time{}, err
}

return time.Unix(int64(unix), 0), nil
}

// 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) error {
Copy link
Contributor

@joshua-kim joshua-kim Oct 8, 2025

Choose a reason for hiding this comment

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

I'm noticing some inconsistency that some of our functions return errors, while others don't.

It seems that we're following geth-style naming, so I'm also assuming we intended to follow geth's api style - referencing their state accessors here it seems that they don't return errors... they usually return a zero/nil value and log errors instead of letting the caller how to handle the error. It seems that they use Crit for failed writes to exit, and callers performing reads are expected to check the value returned.

We could deviate from geth's style but since we have inconsistency in our own package I want to ask if this was intended. If we're going to deviate from geth's style, I think it would just be easiest to return the error + let the caller handle the error and not have the caller infer failure from reasoning about the returned value of a read.

return writeCurrentTimeMarker(db, offlinePruningKey)
}

// ReadOfflinePruning reads the most recent timestamp of an attempt to run offline
// pruning if present.
func ReadOfflinePruning(db ethdb.KeyValueStore) (time.Time, error) {
return readTimeMarker(db, offlinePruningKey)
}

// DeleteOfflinePruning deletes any marker of the last attempt to run offline pruning.
func DeleteOfflinePruning(db ethdb.KeyValueStore) error {
return db.Delete(offlinePruningKey)
}

// WritePopulateMissingTries writes a marker for the current attempt to populate
// missing tries.
func WritePopulateMissingTries(db ethdb.KeyValueStore) error {
return writeCurrentTimeMarker(db, populateMissingTriesKey)
}

// ReadPopulateMissingTries reads the most recent timestamp of an attempt to
// re-populate missing trie nodes.
func ReadPopulateMissingTries(db ethdb.KeyValueStore) (time.Time, error) {
return readTimeMarker(db, populateMissingTriesKey)
}

// DeletePopulateMissingTries deletes any marker of the last attempt to
// re-populate missing trie nodes.
func DeletePopulateMissingTries(db ethdb.KeyValueStore) error {
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 {
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) {
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) {
has, err := db.Has(acceptorTipKey)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typically go names these vars ok for existence checks

if err != nil {
return common.Hash{}, err
}
if !has {
// If the index is not present on disk, the [acceptorTipKey] index has not been initialized yet.
Copy link
Contributor

Choose a reason for hiding this comment

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

These [brackets] to my understanding are used to render in godoc and only work on exported code in either imported packages or in the same package - using it in-line in a function won't work and either way this var isn't exported.

return common.Hash{}, nil
}
h, err := db.Get(acceptorTipKey)
if err != nil {
return common.Hash{}, err
}
if len(h) != common.HashLength {
return common.Hash{}, fmt.Errorf("value has incorrect length %d", 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here - i think for vars for a function `backticks` are used

func ReadChainConfig[T any](db ethdb.KeyValueReader, hash common.Hash, upgradeConfig *T) *params.ChainConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment w.r.t the parameter for upgradeConfig.

config := rawdb.ReadChainConfig(db, hash)

upgrade, _ := db.Get(upgradeConfigKey(hash))
if len(upgrade) == 0 {
return config
}

if err := json.Unmarshal(upgrade, upgradeConfig); err != nil {
log.Error("Invalid upgrade config JSON", "err", err)
return nil
}

return config
}

// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function signature looks like it changed from subnetevm and coreth. Why do we pass in upgradeConfig? It looks like in subnet evm we would write whatever was in params.GetExtra and in coreth we would write the config provided.

Copy link
Author

Choose a reason for hiding this comment

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

I changed the signature exactly because I wanted to decouple the params.GetExtra which is local to subnet-evm from the implementation of this function which I want to be here. coreth uses the libevm implementation and subnet-evm uses this one. I will consult with the team if potentially we should keep this code here or maybe do something else.

rawdb.WriteChainConfig(db, hash, config)
if config == nil {
return
}

data, err := json.Marshal(upgradeConfig)
if err != nil {
log.Crit("Failed to JSON encode upgrade config", "err", err)
}
if err := db.Put(upgradeConfigKey(hash), data); err != nil {
log.Crit("Failed to store upgrade config", "err", err)
}
}
46 changes: 46 additions & 0 deletions vms/evm/sync/customrawdb/accessors_snapshot_ext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package customrawdb

import (
"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/core/rawdb"
"github.com/ava-labs/libevm/ethdb"
"github.com/ava-labs/libevm/log"
)

// ReadSnapshotBlockHash retrieves the hash of the block whose state is contained in
// the persisted snapshot.
func ReadSnapshotBlockHash(db ethdb.KeyValueReader) common.Hash {
data, _ := db.Get(snapshotBlockHashKey)
if len(data) != common.HashLength {
return common.Hash{}
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like when reads fail the rest of the geth rawdb Read* apis would error log

}
return common.BytesToHash(data)
}

// WriteSnapshotBlockHash stores the root of the block whose state is contained in
// the persisted snapshot.
func WriteSnapshotBlockHash(db ethdb.KeyValueWriter, blockHash common.Hash) {
if err := db.Put(snapshotBlockHashKey, blockHash[:]); err != nil {
log.Crit("Failed to store snapshot block hash", "err", err)
}
}

// DeleteSnapshotBlockHash deletes the hash of the block whose state is contained in
// the persisted snapshot. Since snapshots are not immutable, this method can
// be used during updates, so a crash or failure will mark the entire snapshot
// invalid.
func DeleteSnapshotBlockHash(db ethdb.KeyValueWriter) {
if err := db.Delete(snapshotBlockHashKey); err != nil {
log.Crit("Failed to remove snapshot block hash", "err", err)
}
}

// IterateAccountSnapshots returns an iterator for walking all of the accounts in the snapshot
func IterateAccountSnapshots(db ethdb.Iteratee) ethdb.Iterator {
it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
keyLen := len(rawdb.SnapshotAccountPrefix) + common.HashLength
return rawdb.NewKeyLengthIterator(it, keyLen)
}
Loading
Loading