mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-21 06:04:33 +00:00
feat: triedb.Config support for arbitrary backend implementations (#70)
## Why this should be merged
Allow `ava-labs/coreth` to use arbitrary `triedb` database
implementations.
## How this works
Introduces `HashBackend` and `PathBackend` interfaces that
`triedb.Database` type-asserts to instead of `hashdb.Database` and
`pathdb.Backend` respectively. Other interfaces are introduced to avoid
having to modify `{hash,path}db.Database.Reader()` return values.
The explicit `DBOverride` field means that we don't have to modify any
of the `triedb` constructor beyond adding a single line immediately
before the return. This leaves all modifications of original files
entirely mechanistic. This is, however, at the expense of compile-time
guarantees of the overriding database being either a `HashBackend` or a
`PathBackend`.
## How this was tested
Unit test demonstrating override + plumbing.
---------
Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
This commit is contained in:
parent
41a2592b8c
commit
594abd9f42
3 changed files with 167 additions and 9 deletions
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
97
triedb/database.libevm.go
Normal file
97
triedb/database.libevm.go
Normal file
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
54
triedb/database.libevm_test.go
Normal file
54
triedb/database.libevm_test.go
Normal file
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
Loading…
Reference in a new issue