Skip to content

Commit 594abd9

Browse files
ARR4NDarioush Jalali
andauthored
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>
1 parent 41a2592 commit 594abd9

File tree

3 files changed

+167
-9
lines changed

3 files changed

+167
-9
lines changed

triedb/database.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type Config struct {
3636
IsVerkle bool // Flag whether the db is holding a verkle tree
3737
HashDB *hashdb.Config // Configs for hash-based scheme
3838
PathDB *pathdb.Config // Configs for experimental path-based scheme
39+
40+
DBOverride DBConstructor // Injects an arbitrary backend-database implementation
3941
}
4042

4143
// HashDefaults represents a config for using hash-based scheme with
@@ -104,6 +106,9 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
104106
diskdb: diskdb,
105107
preimages: preimages,
106108
}
109+
if db.overrideBackend(diskdb, config) {
110+
return db
111+
}
107112
if config.HashDB != nil && config.PathDB != nil {
108113
log.Crit("Both 'hash' and 'path' mode are configured")
109114
}
@@ -126,6 +131,8 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
126131
// An error will be returned if the requested state is not available.
127132
func (db *Database) Reader(blockRoot common.Hash) (database.Reader, error) {
128133
switch b := db.backend.(type) {
134+
case ReaderProvider:
135+
return b.Reader(blockRoot)
129136
case *hashdb.Database:
130137
return b.Reader(blockRoot)
131138
case *pathdb.Database:
@@ -221,7 +228,7 @@ func (db *Database) InsertPreimage(preimages map[common.Hash][]byte) {
221228
//
222229
// It's only supported by hash-based database and will return an error for others.
223230
func (db *Database) Cap(limit common.StorageSize) error {
224-
hdb, ok := db.backend.(*hashdb.Database)
231+
hdb, ok := db.backend.(HashDB)
225232
if !ok {
226233
return errors.New("not supported")
227234
}
@@ -237,7 +244,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
237244
//
238245
// It's only supported by hash-based database and will return an error for others.
239246
func (db *Database) Reference(root common.Hash, parent common.Hash) error {
240-
hdb, ok := db.backend.(*hashdb.Database)
247+
hdb, ok := db.backend.(HashDB)
241248
if !ok {
242249
return errors.New("not supported")
243250
}
@@ -248,7 +255,7 @@ func (db *Database) Reference(root common.Hash, parent common.Hash) error {
248255
// Dereference removes an existing reference from a root node. It's only
249256
// supported by hash-based database and will return an error for others.
250257
func (db *Database) Dereference(root common.Hash) error {
251-
hdb, ok := db.backend.(*hashdb.Database)
258+
hdb, ok := db.backend.(HashDB)
252259
if !ok {
253260
return errors.New("not supported")
254261
}
@@ -261,7 +268,7 @@ func (db *Database) Dereference(root common.Hash) error {
261268
// corresponding trie histories are existent. It's only supported by path-based
262269
// database and will return an error for others.
263270
func (db *Database) Recover(target common.Hash) error {
264-
pdb, ok := db.backend.(*pathdb.Database)
271+
pdb, ok := db.backend.(PathDB)
265272
if !ok {
266273
return errors.New("not supported")
267274
}
@@ -279,7 +286,7 @@ func (db *Database) Recover(target common.Hash) error {
279286
// recovered. It's only supported by path-based database and will return an
280287
// error for others.
281288
func (db *Database) Recoverable(root common.Hash) (bool, error) {
282-
pdb, ok := db.backend.(*pathdb.Database)
289+
pdb, ok := db.backend.(PathDB)
283290
if !ok {
284291
return false, errors.New("not supported")
285292
}
@@ -292,7 +299,7 @@ func (db *Database) Recoverable(root common.Hash) (bool, error) {
292299
//
293300
// It's only supported by path-based database and will return an error for others.
294301
func (db *Database) Disable() error {
295-
pdb, ok := db.backend.(*pathdb.Database)
302+
pdb, ok := db.backend.(PathDB)
296303
if !ok {
297304
return errors.New("not supported")
298305
}
@@ -302,7 +309,7 @@ func (db *Database) Disable() error {
302309
// Enable activates database and resets the state tree with the provided persistent
303310
// state root once the state sync is finished.
304311
func (db *Database) Enable(root common.Hash) error {
305-
pdb, ok := db.backend.(*pathdb.Database)
312+
pdb, ok := db.backend.(PathDB)
306313
if !ok {
307314
return errors.New("not supported")
308315
}
@@ -314,7 +321,7 @@ func (db *Database) Enable(root common.Hash) error {
314321
// flattening everything down (bad for reorgs). It's only supported by path-based
315322
// database and will return an error for others.
316323
func (db *Database) Journal(root common.Hash) error {
317-
pdb, ok := db.backend.(*pathdb.Database)
324+
pdb, ok := db.backend.(PathDB)
318325
if !ok {
319326
return errors.New("not supported")
320327
}
@@ -325,7 +332,7 @@ func (db *Database) Journal(root common.Hash) error {
325332
// It's only supported by path-based database and will return an error for
326333
// others.
327334
func (db *Database) SetBufferSize(size int) error {
328-
pdb, ok := db.backend.(*pathdb.Database)
335+
pdb, ok := db.backend.(PathDB)
329336
if !ok {
330337
return errors.New("not supported")
331338
}

triedb/database.libevm.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2024 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package triedb
18+
19+
import (
20+
"github.com/ava-labs/libevm/common"
21+
"github.com/ava-labs/libevm/ethdb"
22+
"github.com/ava-labs/libevm/log"
23+
"github.com/ava-labs/libevm/trie/triestate"
24+
"github.com/ava-labs/libevm/triedb/database"
25+
"github.com/ava-labs/libevm/triedb/hashdb"
26+
"github.com/ava-labs/libevm/triedb/pathdb"
27+
)
28+
29+
// BackendDB defines the intersection of methods shared by [hashdb.Database] and
30+
// [pathdb.Database]. It is defined to export an otherwise internal type used by
31+
// the non-libevm geth implementation.
32+
type BackendDB backend
33+
34+
// A ReaderProvider exposes its underlying Reader as an interface. Both
35+
// [hashdb.Database] and [pathdb.Database] return concrete types so Go's lack of
36+
// support for [covariant types] means that this method can't be defined on
37+
// [BackendDB].
38+
//
39+
// [covariant types]: https://go.dev/doc/faq#covariant_types
40+
type ReaderProvider interface {
41+
Reader(common.Hash) (database.Reader, error)
42+
}
43+
44+
// A DBConstructor constructs alternative backend-database implementations.
45+
type DBConstructor func(ethdb.Database, *Config) DBOverride
46+
47+
// A DBOverride is an arbitrary implementation of a [Database] backend. It MUST
48+
// be either a [HashDB] or a [PathDB].
49+
type DBOverride interface {
50+
BackendDB
51+
ReaderProvider
52+
}
53+
54+
func (db *Database) overrideBackend(diskdb ethdb.Database, config *Config) bool {
55+
if config.DBOverride == nil {
56+
return false
57+
}
58+
if config.HashDB != nil || config.PathDB != nil {
59+
log.Crit("Database override provided when 'hash' or 'path' mode are configured")
60+
}
61+
62+
db.backend = config.DBOverride(diskdb, config)
63+
switch db.backend.(type) {
64+
case HashDB:
65+
case PathDB:
66+
default:
67+
log.Crit("Database override is neither hash- nor path-based")
68+
}
69+
return true
70+
}
71+
72+
var (
73+
// If either of these break then the respective interface SHOULD be updated.
74+
_ HashDB = (*hashdb.Database)(nil)
75+
_ PathDB = (*pathdb.Database)(nil)
76+
)
77+
78+
// A HashDB mirrors the functionality of a [hashdb.Database].
79+
type HashDB interface {
80+
BackendDB
81+
82+
Cap(limit common.StorageSize) error
83+
Reference(root common.Hash, parent common.Hash)
84+
Dereference(root common.Hash)
85+
}
86+
87+
// A PathDB mirrors the functionality of a [pathdb.Database].
88+
type PathDB interface {
89+
BackendDB
90+
91+
Recover(root common.Hash, loader triestate.TrieLoader) error
92+
Recoverable(root common.Hash) bool
93+
Disable() error
94+
Enable(root common.Hash) error
95+
Journal(root common.Hash) error
96+
SetBufferSize(size int) error
97+
}

triedb/database.libevm_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2024 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package triedb
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/require"
23+
24+
"github.com/ava-labs/libevm/common"
25+
"github.com/ava-labs/libevm/ethdb"
26+
"github.com/ava-labs/libevm/triedb/database"
27+
)
28+
29+
func TestDBOverride(t *testing.T) {
30+
config := &Config{
31+
DBOverride: func(d ethdb.Database, c *Config) DBOverride {
32+
return override{}
33+
},
34+
}
35+
36+
db := NewDatabase(nil, config)
37+
got, err := db.Reader(common.Hash{})
38+
require.NoError(t, err)
39+
if _, ok := got.(reader); !ok {
40+
t.Errorf("with non-nil %T.DBOverride, %T.Reader() got concrete type %T; want %T", config, db, got, reader{})
41+
}
42+
}
43+
44+
type override struct {
45+
PathDB
46+
}
47+
48+
type reader struct {
49+
database.Reader
50+
}
51+
52+
func (override) Reader(common.Hash) (database.Reader, error) {
53+
return reader{}, nil
54+
}

0 commit comments

Comments
 (0)