diff --git a/core/state/state.libevm.go b/core/state/state.libevm.go
new file mode 100644
index 000000000000..c08a6a9b5fa4
--- /dev/null
+++ b/core/state/state.libevm.go
@@ -0,0 +1,64 @@
+// Copyright 2024 the libevm authors.
+//
+// The libevm additions to go-ethereum are free software: you can redistribute
+// them and/or modify them under the terms of the GNU Lesser General Public License
+// as published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The libevm additions are distributed in the hope that they will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see
+// .
+
+package state
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+// GetExtra returns the extra payload from the [types.StateAccount] associated
+// with the address, or a zero-value `SA` if not found. The
+// [types.ExtraPayloads] MUST be sourced from [types.RegisterExtras].
+func GetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address) SA {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return p.FromStateAccount(&stateObject.data)
+ }
+ var zero SA
+ return zero
+}
+
+// SetExtra sets the extra payload for the address. See [GetExtra] for details.
+func SetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address, extra SA) {
+ stateObject := s.getOrNewStateObject(addr)
+ if stateObject != nil {
+ setExtraOnObject(stateObject, p, addr, extra)
+ }
+}
+
+func setExtraOnObject[SA any](s *stateObject, p types.ExtraPayloads[SA], addr common.Address, extra SA) {
+ s.db.journal.append(extraChange[SA]{
+ payloads: p,
+ account: &addr,
+ prev: p.FromStateAccount(&s.data),
+ })
+ p.SetOnStateAccount(&s.data, extra)
+}
+
+// extraChange is a [journalEntry] for [SetExtra] / [setExtraOnObject].
+type extraChange[SA any] struct {
+ payloads types.ExtraPayloads[SA]
+ account *common.Address
+ prev SA
+}
+
+func (e extraChange[SA]) dirtied() *common.Address { return e.account }
+
+func (e extraChange[SA]) revert(s *StateDB) {
+ e.payloads.SetOnStateAccount(&s.getStateObject(*e.account).data, e.prev)
+}
diff --git a/core/state/state.libevm_test.go b/core/state/state.libevm_test.go
new file mode 100644
index 000000000000..696d527c0b33
--- /dev/null
+++ b/core/state/state.libevm_test.go
@@ -0,0 +1,172 @@
+// Copyright 2024 the libevm authors.
+//
+// The libevm additions to go-ethereum are free software: you can redistribute
+// them and/or modify them under the terms of the GNU Lesser General Public License
+// as published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The libevm additions are distributed in the hope that they will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see
+// .
+
+package state_test
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/snapshot"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb/memorydb"
+ "github.com/ethereum/go-ethereum/libevm/ethtest"
+ "github.com/ethereum/go-ethereum/triedb"
+)
+
+func TestGetSetExtra(t *testing.T) {
+ type accountExtra struct {
+ // Data is a pointer to test deep copying.
+ Data *[]byte // MUST be exported; I spent 20 minutes investigating failing tests because I'm an idiot
+ }
+
+ types.TestOnlyClearRegisteredExtras()
+ t.Cleanup(types.TestOnlyClearRegisteredExtras)
+ // Just as its Data field is a pointer, the registered type is a pointer to
+ // test deep copying.
+ payloads := types.RegisterExtras[*accountExtra]()
+
+ rng := ethtest.NewPseudoRand(42)
+ addr := rng.Address()
+ nonce := rng.Uint64()
+ balance := rng.Uint256()
+ buf := rng.Bytes(8)
+ extra := &accountExtra{Data: &buf}
+
+ views := newWithSnaps(t)
+ stateDB := views.newStateDB(t, types.EmptyRootHash)
+
+ assert.Nilf(t, state.GetExtra(stateDB, payloads, addr), "state.GetExtra() returns zero-value %T if before account creation", extra)
+ stateDB.CreateAccount(addr)
+ stateDB.SetNonce(addr, nonce)
+ stateDB.SetBalance(addr, balance)
+ assert.Nilf(t, state.GetExtra(stateDB, payloads, addr), "state.GetExtra() returns zero-value %T if after account creation but before SetExtra()", extra)
+ state.SetExtra(stateDB, payloads, addr, extra)
+ require.Equal(t, extra, state.GetExtra(stateDB, payloads, addr), "state.GetExtra() immediately after SetExtra()")
+
+ root, err := stateDB.Commit(1, false) // arbitrary block number
+ require.NoErrorf(t, err, "%T.Commit(1, false)", stateDB)
+ require.NotEqualf(t, types.EmptyRootHash, root, "root hash returned by %T.Commit() is not the empty root", stateDB)
+
+ t.Run(fmt.Sprintf("retrieve from %T", views.snaps), func(t *testing.T) {
+ iter, err := views.snaps.AccountIterator(root, common.Hash{})
+ require.NoErrorf(t, err, "%T.AccountIterator(...)", views.snaps)
+ defer iter.Release()
+
+ require.Truef(t, iter.Next(), "%T.Next() (i.e. at least one account)", iter)
+ require.NoErrorf(t, iter.Error(), "%T.Error()", iter)
+
+ t.Run("types.FullAccount()", func(t *testing.T) {
+ got, err := types.FullAccount(iter.Account())
+ require.NoErrorf(t, err, "types.FullAccount(%T.Account())", iter)
+
+ want := &types.StateAccount{
+ Nonce: nonce,
+ Balance: balance,
+ Root: types.EmptyRootHash,
+ CodeHash: types.EmptyCodeHash[:],
+ }
+ payloads.SetOnStateAccount(want, extra)
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("types.FullAccount(%T.Account()) diff (-want +got):\n%s", iter, diff)
+ }
+ })
+
+ require.Falsef(t, iter.Next(), "%T.Next() after first account (i.e. only one)", iter)
+ })
+
+ t.Run(fmt.Sprintf("retrieve from new %T", stateDB), func(t *testing.T) {
+ s := views.newStateDB(t, root)
+ assert.Equalf(t, nonce, s.GetNonce(addr), "%T.GetNonce()", s)
+ assert.Equalf(t, balance, s.GetBalance(addr), "%T.GetBalance()", s)
+ assert.Equal(t, extra, state.GetExtra(s, payloads, addr), "state.GetExtra()")
+ })
+
+ t.Run("reverting to snapshot", func(t *testing.T) {
+ s := views.newStateDB(t, root)
+ snap := s.Snapshot()
+
+ oldExtra := extra
+ buf := append(*oldExtra.Data, rng.Bytes(8)...)
+ newExtra := &accountExtra{Data: &buf}
+
+ state.SetExtra(s, payloads, addr, newExtra)
+ assert.Equalf(t, newExtra, state.GetExtra(s, payloads, addr), "state.GetExtra() after overwriting with new value")
+ s.RevertToSnapshot(snap)
+ assert.Equalf(t, oldExtra, state.GetExtra(s, payloads, addr), "state.GetExtra() after reverting to snapshot")
+ })
+
+ t.Run(fmt.Sprintf("%T.Copy()", stateDB), func(t *testing.T) {
+ require.Equalf(t, reflect.Pointer, reflect.TypeOf(extra).Kind(), "extra-payload type")
+ require.Equalf(t, reflect.Pointer, reflect.TypeOf(extra.Data).Kind(), "extra-payload field")
+
+ orig := views.newStateDB(t, root)
+ cp := orig.Copy()
+
+ oldExtra := extra
+ buf := append(*oldExtra.Data, rng.Bytes(8)...)
+ newExtra := &accountExtra{Data: &buf}
+
+ assert.Equalf(t, oldExtra, state.GetExtra(orig, payloads, addr), "GetExtra([original %T]) before setting", orig)
+ assert.Equalf(t, oldExtra, state.GetExtra(cp, payloads, addr), "GetExtra([copy of %T]) returns the same payload", orig)
+ state.SetExtra(orig, payloads, addr, newExtra)
+ assert.Equalf(t, newExtra, state.GetExtra(orig, payloads, addr), "GetExtra([original %T]) returns overwritten payload", orig)
+ assert.Equalf(t, oldExtra, state.GetExtra(cp, payloads, addr), "GetExtra([copy of %T]) returns original payload despite overwriting on original", orig)
+ })
+}
+
+// stateViews are different ways to access the same data.
+type stateViews struct {
+ snaps *snapshot.Tree
+ database state.Database
+}
+
+func (v stateViews) newStateDB(t *testing.T, root common.Hash) *state.StateDB {
+ t.Helper()
+ s, err := state.New(root, v.database, v.snaps)
+ require.NoError(t, err, "state.New()")
+ return s
+}
+
+func newWithSnaps(t *testing.T) stateViews {
+ t.Helper()
+ empty := types.EmptyRootHash
+ kvStore := memorydb.New()
+ ethDB := rawdb.NewDatabase(kvStore)
+ snaps, err := snapshot.New(
+ snapshot.Config{
+ CacheSize: 16, // Mb (arbitrary but non-zero)
+ },
+ kvStore,
+ triedb.NewDatabase(ethDB, nil),
+ empty,
+ )
+ require.NoError(t, err, "snapshot.New()")
+
+ return stateViews{
+ snaps: snaps,
+ database: state.NewDatabase(ethDB),
+ }
+}
diff --git a/core/state/state_object.go b/core/state/state_object.go
index fc26af68dbe7..2b8c0255ce7b 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -93,7 +93,7 @@ type stateObject struct {
// empty returns whether the account is considered empty.
func (s *stateObject) empty() bool {
- return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes())
+ return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) && s.data.Extra.IsZero()
}
// newObject creates a state object.
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 8092155ce595..04f6e684ae79 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -19,6 +19,7 @@ package state
import (
"fmt"
+ "reflect"
"sort"
"time"
@@ -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:
@@ -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.
@@ -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
diff --git a/core/types/rlp_payload.libevm.go b/core/types/rlp_payload.libevm.go
index 3a2957edd14f..f37fb8cbee00 100644
--- a/core/types/rlp_payload.libevm.go
+++ b/core/types/rlp_payload.libevm.go
@@ -207,3 +207,7 @@ func (e *StateAccountExtra) Format(s fmt.State, verb rune) {
}
_, _ = s.Write([]byte(out))
}
+
+func (e *StateAccountExtra) IsZero() bool {
+ return e == nil || e.t == nil || e.t.IsZero()
+}
diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go
index d7e10173cf27..0421a45e3533 100644
--- a/eth/tracers/native/prestate.go
+++ b/eth/tracers/native/prestate.go
@@ -290,6 +290,16 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
// it to the prestate of the given contract. It assumes `lookupAccount`
// has been performed on the contract before.
func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) {
+ // TODO: Refactor coreth test outside of eth/tracers/internal
+ // See https://github.com/ava-labs/coreth/pull/286 for context.
+ // lookupStorage assumes that lookupAccount has already been called.
+ // This assumption is violated for some historical blocks by the NativeAssetCall
+ // precompile. To fix this, we perform an extra call to lookupAccount here to ensure
+ // that the pre-state account is populated before attempting to read from the Storage
+ // map. When the invariant is maintained properly (since de-activation of the precompile),
+ // lookupAccount is a no-op. When the invariant is broken by the precompile, this avoids
+ // the panic and correctly captures the account prestate before the next opcode is executed.
+ t.lookupAccount(addr)
if _, ok := t.pre[addr].Storage[key]; ok {
return
}
diff --git a/go.mod b/go.mod
index 7a54b1ff7ca9..af6b9a0b399f 100644
--- a/go.mod
+++ b/go.mod
@@ -32,6 +32,7 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
+ github.com/google/go-cmp v0.5.9
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.2
@@ -106,7 +107,6 @@ require (
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
- github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
diff --git a/params/config.go b/params/config.go
index 2762a504c44b..0ed94cd2f9f9 100644
--- a/params/config.go
+++ b/params/config.go
@@ -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
@@ -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
diff --git a/params/config_test.go b/params/config_test.go
index bf8ce2fc5e24..fa444a1d0b76 100644
--- a/params/config_test.go
+++ b/params/config_test.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common/math"
+ "github.com/stretchr/testify/require"
)
func TestCheckCompatible(t *testing.T) {
@@ -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)")
+}
diff --git a/triedb/database.go b/triedb/database.go
index 939a21f1478b..3fb82f31b816 100644
--- a/triedb/database.go
+++ b/triedb/database.go
@@ -27,15 +27,14 @@ import (
"github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/triedb/database"
"github.com/ethereum/go-ethereum/triedb/hashdb"
- "github.com/ethereum/go-ethereum/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
@@ -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 {
@@ -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
@@ -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 {
@@ -117,7 +129,11 @@ 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
}
@@ -125,13 +141,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
// 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
@@ -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")
}
@@ -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")
}
@@ -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")
}
@@ -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")
}
@@ -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")
}
@@ -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")
}
@@ -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")
}
@@ -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")
}
@@ -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")
}
diff --git a/triedb/database/database.go b/triedb/database/database.go
index 18a8f454e2f4..95a54fd53dad 100644
--- a/triedb/database/database.go
+++ b/triedb/database/database.go
@@ -18,6 +18,8 @@ package database
import (
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/trie/trienode"
+ "github.com/ethereum/go-ethereum/trie/triestate"
)
// Reader wraps the Node method of a backing trie reader.
@@ -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
+}
diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go
index e45ccdba32ca..671edd8e997b 100644
--- a/triedb/hashdb/database.go
+++ b/triedb/hashdb/database.go
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate"
+ "github.com/ethereum/go-ethereum/triedb/database"
)
var (
@@ -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.
@@ -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)
}
diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go
index f2d6cea635a9..c5e2ecff2303 100644
--- a/triedb/pathdb/database.go
+++ b/triedb/pathdb/database.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate"
+ "github.com/ethereum/go-ethereum/triedb/database"
)
const (
@@ -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 {
@@ -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)