diff --git a/triedb/database.go b/triedb/database.go index 4ce4de54a5d..6ec8b157a74 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -36,6 +36,8 @@ type Config struct { 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 + + DBOverride DBConstructor // Injects an arbitrary backend-database implementation } // HashDefaults represents a config for using hash-based scheme with @@ -104,6 +106,9 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database { diskdb: diskdb, preimages: preimages, } + if db.overrideBackend(diskdb, config) { + return db + } if config.HashDB != nil && config.PathDB != nil { log.Crit("Both 'hash' and 'path' mode are configured") } @@ -126,6 +131,8 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database { // 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 ReaderProvider: + return b.Reader(blockRoot) case *hashdb.Database: return b.Reader(blockRoot) case *pathdb.Database: @@ -221,7 +228,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.(HashDB) if !ok { return errors.New("not supported") } @@ -237,7 +244,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.(HashDB) if !ok { return errors.New("not supported") } @@ -248,7 +255,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.(HashDB) if !ok { return errors.New("not supported") } @@ -261,7 +268,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.(PathDB) if !ok { return errors.New("not supported") } @@ -279,7 +286,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.(PathDB) if !ok { return false, errors.New("not supported") } @@ -292,7 +299,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.(PathDB) if !ok { return errors.New("not supported") } @@ -302,7 +309,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.(PathDB) if !ok { return errors.New("not supported") } @@ -314,7 +321,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.(PathDB) if !ok { return errors.New("not supported") } @@ -325,7 +332,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.(PathDB) if !ok { return errors.New("not supported") } diff --git a/triedb/database.libevm.go b/triedb/database.libevm.go new file mode 100644 index 00000000000..01998d46455 --- /dev/null +++ b/triedb/database.libevm.go @@ -0,0 +1,97 @@ +// 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 triedb + +import ( + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/log" + "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" +) + +// BackendDB defines the intersection of methods shared by [hashdb.Database] and +// [pathdb.Database]. It is defined to export an otherwise internal type used by +// the non-libevm geth implementation. +type BackendDB backend + +// A ReaderProvider exposes its underlying Reader as an interface. Both +// [hashdb.Database] and [pathdb.Database] return concrete types so Go's lack of +// support for [covariant types] means that this method can't be defined on +// [BackendDB]. +// +// [covariant types]: https://go.dev/doc/faq#covariant_types +type ReaderProvider interface { + Reader(common.Hash) (database.Reader, error) +} + +// A DBConstructor constructs alternative backend-database implementations. +type DBConstructor func(ethdb.Database, *Config) DBOverride + +// A DBOverride is an arbitrary implementation of a [Database] backend. It MUST +// be either a [HashDB] or a [PathDB]. +type DBOverride interface { + BackendDB + ReaderProvider +} + +func (db *Database) overrideBackend(diskdb ethdb.Database, config *Config) bool { + if config.DBOverride == nil { + return false + } + if config.HashDB != nil || config.PathDB != nil { + log.Crit("Database override provided when 'hash' or 'path' mode are configured") + } + + db.backend = config.DBOverride(diskdb, config) + switch db.backend.(type) { + case HashDB: + case PathDB: + default: + log.Crit("Database override is neither hash- nor path-based") + } + return true +} + +var ( + // If either of these break then the respective interface SHOULD be updated. + _ HashDB = (*hashdb.Database)(nil) + _ PathDB = (*pathdb.Database)(nil) +) + +// A HashDB mirrors the functionality of a [hashdb.Database]. +type HashDB interface { + BackendDB + + Cap(limit common.StorageSize) error + Reference(root common.Hash, parent common.Hash) + Dereference(root common.Hash) +} + +// A PathDB mirrors the functionality of a [pathdb.Database]. +type PathDB interface { + BackendDB + + 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/database.libevm_test.go b/triedb/database.libevm_test.go new file mode 100644 index 00000000000..6eedcb77a77 --- /dev/null +++ b/triedb/database.libevm_test.go @@ -0,0 +1,54 @@ +// 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 triedb + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/triedb/database" +) + +func TestDBOverride(t *testing.T) { + config := &Config{ + DBOverride: func(d ethdb.Database, c *Config) DBOverride { + return override{} + }, + } + + db := NewDatabase(nil, config) + got, err := db.Reader(common.Hash{}) + require.NoError(t, err) + if _, ok := got.(reader); !ok { + t.Errorf("with non-nil %T.DBOverride, %T.Reader() got concrete type %T; want %T", config, db, got, reader{}) + } +} + +type override struct { + PathDB +} + +type reader struct { + database.Reader +} + +func (override) Reader(common.Hash) (database.Reader, error) { + return reader{}, nil +}