Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
26 changes: 24 additions & 2 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package state

import (
"fmt"
"reflect"
"sort"
"time"

Expand Down Expand Up @@ -47,6 +48,19 @@ type revision struct {
journalIndex int
}

type snapshotTree interface {
Snapshot(root common.Hash) snapshot.Snapshot
Update(
blockRoot common.Hash,
parentRoot common.Hash,
destructs map[common.Hash]struct{},
accounts map[common.Hash][]byte,
storage map[common.Hash]map[common.Hash][]byte,
) error
StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (snapshot.StorageIterator, error)
Cap(root common.Hash, layers int) error
}

// StateDB structs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
Expand All @@ -63,7 +77,7 @@ type StateDB struct {
prefetcher *triePrefetcher
trie Trie
hasher crypto.KeccakState
snaps *snapshot.Tree // Nil if snapshot is not available
snaps snapshotTree // Nil if snapshot is not available
snap snapshot.Snapshot // Nil if snapshot is not available

// originalRoot is the pre-state root, before any changes were made.
Expand Down Expand Up @@ -141,7 +155,15 @@ type StateDB struct {
}

// New creates a new state from a given trie.
func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
func New(root common.Hash, db Database, snaps snapshotTree) (*StateDB, error) {
if snaps != nil {
// XXX: Make sure we treat incoming `nil` ptrs as `nil` values, not an
// interface to a nil ptr
v := reflect.ValueOf(snaps)
if v.Kind() == reflect.Ptr && v.IsNil() {
snaps = nil
}
}
tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
Expand Down
12 changes: 10 additions & 2 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCo
NewTime: newtime,
RewindToTime: 0,
}
if rew != nil {
if rew != nil && *rew != 0 {
err.RewindToTime = *rew - 1
}
return err
Expand All @@ -890,7 +890,15 @@ func (err *ConfigCompatError) Error() string {
if err.StoredBlock != nil {
return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock)
}
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, err.StoredTime, err.NewTime, err.RewindToTime)

if err.StoredTime == nil && err.NewTime == nil {
return ""
} else if err.StoredTime == nil && err.NewTime != nil {
return fmt.Sprintf("mismatching %s in database (have timestamp nil, want timestamp %d, rewindto timestamp %d)", err.What, *err.NewTime, err.RewindToTime)
} else if err.StoredTime != nil && err.NewTime == nil {
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp nil, rewindto timestamp %d)", err.What, *err.StoredTime, err.RewindToTime)
}
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, *err.StoredTime, *err.NewTime, err.RewindToTime)
}

// Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions
Expand Down
18 changes: 18 additions & 0 deletions params/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"github.com/ava-labs/libevm/common/math"
"github.com/stretchr/testify/require"
)

func TestCheckCompatible(t *testing.T) {
Expand Down Expand Up @@ -137,3 +138,20 @@ func TestConfigRules(t *testing.T) {
t.Errorf("expected %v to be shanghai", stamp)
}
}

func TestTimestampCompatError(t *testing.T) {
require.Equal(t, new(ConfigCompatError).Error(), "")

errWhat := "Shanghai fork timestamp"
require.Equal(t, newTimestampCompatError(errWhat, nil, newUint64(1681338455)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp nil, want timestamp 1681338455, rewindto timestamp 1681338454)")

require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), nil).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp nil, rewindto timestamp 1681338454)")

require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), newUint64(600624000)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp 600624000, rewindto timestamp 600623999)")

require.Equal(t, newTimestampCompatError(errWhat, newUint64(0), newUint64(1681338455)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 0, want timestamp 1681338455, rewindto timestamp 0)")
}
56 changes: 33 additions & 23 deletions triedb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@ import (
"github.com/ava-labs/libevm/trie/triestate"
"github.com/ava-labs/libevm/triedb/database"
"github.com/ava-labs/libevm/triedb/hashdb"
"github.com/ava-labs/libevm/triedb/pathdb"
)

// Config defines all necessary options for database.
type Config struct {
Preimages bool // Flag whether the preimage of node key is recorded
IsVerkle bool // Flag whether the db is holding a verkle tree
HashDB *hashdb.Config // Configs for hash-based scheme
PathDB *pathdb.Config // Configs for experimental path-based scheme
Preimages bool // Flag whether the preimage of node key is recorded
IsVerkle bool // Flag whether the db is holding a verkle tree
HashDB hashBackender // Configs for hash-based scheme
PathDB pathBackender // Configs for experimental path-based scheme
}

// HashDefaults represents a config for using hash-based scheme with
Expand All @@ -45,6 +44,15 @@ var HashDefaults = &Config{
HashDB: hashdb.Defaults,
}

type Backend backend

type hashBackender interface {
New(diskdb ethdb.Database, resolver hashdb.ChildResolver) database.HashBackend
}
type pathBackender interface {
New(diskdb ethdb.Database) database.PathBackend
}

// backend defines the methods needed to access/update trie nodes in different
// state scheme.
type backend interface {
Expand Down Expand Up @@ -76,6 +84,10 @@ type backend interface {

// Close closes the trie database backend and releases all held resources.
Close() error

// Reader returns a node reader associated with the specific state.
// An error will be returned if the specified state is not available.
Reader(stateRoot common.Hash) (database.Reader, error)
}

// Database is the wrapper of the underlying backend which is shared by different
Expand Down Expand Up @@ -108,7 +120,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
log.Crit("Both 'hash' and 'path' mode are configured")
}
if config.PathDB != nil {
db.backend = pathdb.New(diskdb, config.PathDB)
db.backend = config.PathDB.New(diskdb)
} else {
var resolver hashdb.ChildResolver
if config.IsVerkle {
Expand All @@ -117,21 +129,19 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
} else {
resolver = trie.MerkleResolver{}
}
db.backend = hashdb.New(diskdb, config.HashDB, resolver)
if config.HashDB == nil {
// some tests don't set this yet pass a non-nil config
config.HashDB = hashdb.Defaults
}
db.backend = config.HashDB.New(diskdb, resolver)
}
return db
}

// Reader returns a reader for accessing all trie nodes with provided state root.
// An error will be returned if the requested state is not available.
func (db *Database) Reader(blockRoot common.Hash) (database.Reader, error) {
switch b := db.backend.(type) {
case *hashdb.Database:
return b.Reader(blockRoot)
case *pathdb.Database:
return b.Reader(blockRoot)
}
return nil, errors.New("unknown backend")
return db.backend.Reader(blockRoot)
}

// Update performs a state transition by committing dirty nodes contained in the
Expand Down Expand Up @@ -221,7 +231,7 @@ func (db *Database) InsertPreimage(preimages map[common.Hash][]byte) {
//
// It's only supported by hash-based database and will return an error for others.
func (db *Database) Cap(limit common.StorageSize) error {
hdb, ok := db.backend.(*hashdb.Database)
hdb, ok := db.backend.(database.HashBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -237,7 +247,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
//
// It's only supported by hash-based database and will return an error for others.
func (db *Database) Reference(root common.Hash, parent common.Hash) error {
hdb, ok := db.backend.(*hashdb.Database)
hdb, ok := db.backend.(database.HashBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -248,7 +258,7 @@ func (db *Database) Reference(root common.Hash, parent common.Hash) error {
// Dereference removes an existing reference from a root node. It's only
// supported by hash-based database and will return an error for others.
func (db *Database) Dereference(root common.Hash) error {
hdb, ok := db.backend.(*hashdb.Database)
hdb, ok := db.backend.(database.HashBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -261,7 +271,7 @@ func (db *Database) Dereference(root common.Hash) error {
// corresponding trie histories are existent. It's only supported by path-based
// database and will return an error for others.
func (db *Database) Recover(target common.Hash) error {
pdb, ok := db.backend.(*pathdb.Database)
pdb, ok := db.backend.(database.PathBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -279,7 +289,7 @@ func (db *Database) Recover(target common.Hash) error {
// recovered. It's only supported by path-based database and will return an
// error for others.
func (db *Database) Recoverable(root common.Hash) (bool, error) {
pdb, ok := db.backend.(*pathdb.Database)
pdb, ok := db.backend.(database.PathBackend)
if !ok {
return false, errors.New("not supported")
}
Expand All @@ -292,7 +302,7 @@ func (db *Database) Recoverable(root common.Hash) (bool, error) {
//
// It's only supported by path-based database and will return an error for others.
func (db *Database) Disable() error {
pdb, ok := db.backend.(*pathdb.Database)
pdb, ok := db.backend.(database.PathBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -302,7 +312,7 @@ func (db *Database) Disable() error {
// Enable activates database and resets the state tree with the provided persistent
// state root once the state sync is finished.
func (db *Database) Enable(root common.Hash) error {
pdb, ok := db.backend.(*pathdb.Database)
pdb, ok := db.backend.(database.PathBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -314,7 +324,7 @@ func (db *Database) Enable(root common.Hash) error {
// flattening everything down (bad for reorgs). It's only supported by path-based
// database and will return an error for others.
func (db *Database) Journal(root common.Hash) error {
pdb, ok := db.backend.(*pathdb.Database)
pdb, ok := db.backend.(database.PathBackend)
if !ok {
return errors.New("not supported")
}
Expand All @@ -325,7 +335,7 @@ func (db *Database) Journal(root common.Hash) error {
// It's only supported by path-based database and will return an error for
// others.
func (db *Database) SetBufferSize(size int) error {
pdb, ok := db.backend.(*pathdb.Database)
pdb, ok := db.backend.(database.PathBackend)
if !ok {
return errors.New("not supported")
}
Expand Down
58 changes: 58 additions & 0 deletions triedb/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package database

import (
"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/trie/trienode"
"github.com/ava-labs/libevm/trie/triestate"
)

// Reader wraps the Node method of a backing trie reader.
Expand Down Expand Up @@ -46,3 +48,59 @@ type Database interface {
// An error will be returned if the specified state is not available.
Reader(stateRoot common.Hash) (Reader, error)
}

// Backend defines the methods needed to access/update trie nodes in different
// state scheme.
type Backend interface {
// Scheme returns the identifier of used storage scheme.
Scheme() string

// Initialized returns an indicator if the state data is already initialized
// according to the state scheme.
Initialized(genesisRoot common.Hash) bool

// Size returns the current storage size of the diff layers on top of the
// disk layer and the storage size of the nodes cached in the disk layer.
//
// For hash scheme, there is no differentiation between diff layer nodes
// and dirty disk layer nodes, so both are merged into the second return.
Size() (common.StorageSize, common.StorageSize)

// Update performs a state transition by committing dirty nodes contained
// in the given set in order to update state from the specified parent to
// the specified root.
//
// The passed in maps(nodes, states) will be retained to avoid copying
// everything. Therefore, these maps must not be changed afterwards.
Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error

// Commit writes all relevant trie nodes belonging to the specified state
// to disk. Report specifies whether logs will be displayed in info level.
Commit(root common.Hash, report bool) error

// Close closes the trie database backend and releases all held resources.
Close() error

// Reader returns a node reader associated with the specific state.
// An error will be returned if the specified state is not available.
Reader(stateRoot common.Hash) (Reader, error)
}

type HashBackend interface {
Backend

Cap(limit common.StorageSize) error
Reference(root common.Hash, parent common.Hash)
Dereference(root common.Hash)
}

type PathBackend interface {
Backend

Recover(root common.Hash, loader triestate.TrieLoader) error
Recoverable(root common.Hash) bool
Disable() error
Enable(root common.Hash) error
Journal(root common.Hash) error
SetBufferSize(size int) error
}
7 changes: 6 additions & 1 deletion triedb/hashdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/ava-labs/libevm/rlp"
"github.com/ava-labs/libevm/trie/trienode"
"github.com/ava-labs/libevm/trie/triestate"
"github.com/ava-labs/libevm/triedb/database"
)

var (
Expand Down Expand Up @@ -79,6 +80,10 @@ var Defaults = &Config{
CleanCacheSize: 0,
}

func (c *Config) New(diskdb ethdb.Database, resolver ChildResolver) database.HashBackend {
return New(diskdb, c, resolver)
}

// Database is an intermediate write layer between the trie data structures and
// the disk database. The aim is to accumulate trie writes in-memory and only
// periodically flush a couple tries to disk, garbage collecting the remainder.
Expand Down Expand Up @@ -631,7 +636,7 @@ func (db *Database) Scheme() string {

// Reader retrieves a node reader belonging to the given state root.
// An error will be returned if the requested state is not available.
func (db *Database) Reader(root common.Hash) (*reader, error) {
func (db *Database) Reader(root common.Hash) (database.Reader, error) {
if _, err := db.node(root); err != nil {
return nil, fmt.Errorf("state %#x is not available, %v", root, err)
}
Expand Down
7 changes: 6 additions & 1 deletion triedb/pathdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/ava-labs/libevm/params"
"github.com/ava-labs/libevm/trie/trienode"
"github.com/ava-labs/libevm/trie/triestate"
"github.com/ava-labs/libevm/triedb/database"
)

const (
Expand Down Expand Up @@ -92,6 +93,10 @@ type Config struct {
ReadOnly bool // Flag whether the database is opened in read only mode.
}

func (c *Config) New(diskdb ethdb.Database) database.PathBackend {
return New(diskdb, c)
}

// sanitize checks the provided user configurations and changes anything that's
// unreasonable or unworkable.
func (c *Config) sanitize() *Config {
Expand Down Expand Up @@ -208,7 +213,7 @@ func New(diskdb ethdb.Database, config *Config) *Database {
}

// Reader retrieves a layer belonging to the given state root.
func (db *Database) Reader(root common.Hash) (layer, error) {
func (db *Database) Reader(root common.Hash) (database.Reader, error) {
l := db.tree.get(root)
if l == nil {
return nil, fmt.Errorf("state %#x is not available", root)
Expand Down