refactor: abstract unsupported-method checks out of triedb.Database

Previously, `triedb.Database` would type-assert between hash- and path-based implementations to check for method support. This had two downsides:

1. The repeated type checks were noisy and reduced code clarity; and
2. It was impossible to experiment with different implementations as method support was conditioned on an explicit concrete type.

By moving the methods onto the `backend` interface type, each concrete implementation now declares its own lack of support.

This cleans up the `triedb.Database` method implementations and consigns "chore" code into the `{hash,path}db/unsupported.go` files to be conveniently ignored :). Although not in scope for this PR, these changes allow the `backend` to be embedded in `triedb.Database` to further remove repeated code by exporting `type Backend interface` and entirely removing individual methods.
This commit is contained in:
Arran Schlosberg 2024-10-11 15:38:18 +01:00
parent 16bf471151
commit ab86490b3c
No known key found for this signature in database
GPG key ID: 5DD5567C12C5F312
7 changed files with 149 additions and 75 deletions

View file

@ -17,8 +17,6 @@
package triedb
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
@ -86,6 +84,21 @@ type backend interface {
// 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.
Reader(root common.Hash) (database.Reader, error)
// These methods have a one-to-one correspondence with methods exposed by
// [Database]. Refer to the respective documentation for their behavior.
Cap(limit common.StorageSize) error
Reference(root, parent common.Hash) error
Dereference(root common.Hash) error
Recover(target common.Hash) error
Recoverable(root common.Hash) (bool, error)
Disable() error
Enable(root common.Hash) error
Journal(root common.Hash) error
SetBufferSize(int) error
AccountHistory(_ common.Address, start, end uint64) (*pathdb.HistoryStats, error)
StorageHistory(_ common.Address, slot common.Hash, start, end uint64) (*pathdb.HistoryStats, error)
HistoryRange() (uint64, uint64, error)
}
// Database is the wrapper of the underlying backend which is shared by different
@ -221,14 +234,10 @@ 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)
if !ok {
return errors.New("not supported")
}
if db.preimages != nil {
db.preimages.commit(false)
}
return hdb.Cap(limit)
return db.backend.Cap(limit)
}
// Reference adds a new reference from a parent node to a child node. This function
@ -237,23 +246,13 @@ 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)
if !ok {
return errors.New("not supported")
}
hdb.Reference(root, parent)
return nil
return db.backend.Reference(root, parent)
}
// 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)
if !ok {
return errors.New("not supported")
}
hdb.Dereference(root)
return nil
return db.backend.Dereference(root)
}
// Recover rollbacks the database to a specified historical point. The state is
@ -261,22 +260,14 @@ 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)
if !ok {
return errors.New("not supported")
}
return pdb.Recover(target)
return db.backend.Recover(target)
}
// Recoverable returns the indicator if the specified state is enabled to be
// 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)
if !ok {
return false, errors.New("not supported")
}
return pdb.Recoverable(root), nil
return db.backend.Recoverable(root)
}
// Disable deactivates the database and invalidates all available state layers
@ -285,21 +276,13 @@ 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)
if !ok {
return errors.New("not supported")
}
return pdb.Disable()
return db.backend.Disable()
}
// 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)
if !ok {
return errors.New("not supported")
}
return pdb.Enable(root)
return db.backend.Enable(root)
}
// Journal commits an entire diff hierarchy to disk into a single journal entry.
@ -307,22 +290,14 @@ 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)
if !ok {
return errors.New("not supported")
}
return pdb.Journal(root)
return db.backend.Journal(root)
}
// SetBufferSize sets the node buffer size to the provided value(in bytes).
// 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)
if !ok {
return errors.New("not supported")
}
return pdb.SetBufferSize(size)
return db.backend.SetBufferSize(size)
}
// IsVerkle returns the indicator if the database is holding a verkle tree.

View file

@ -217,12 +217,13 @@ func (db *Database) node(hash common.Hash) ([]byte, error) {
// Reference adds a new reference from a parent node to a child node.
// This function is used to add reference between internal trie node
// and external node(e.g. storage trie root), all internal trie nodes
// are referenced together by database itself.
func (db *Database) Reference(child common.Hash, parent common.Hash) {
// are referenced together by database itself. It always returns a nil error.
func (db *Database) Reference(child common.Hash, parent common.Hash) error {
db.lock.Lock()
defer db.lock.Unlock()
db.reference(child, parent)
return nil
}
// reference is the private locked version of Reference.
@ -250,12 +251,13 @@ func (db *Database) reference(child common.Hash, parent common.Hash) {
db.childrenSize += common.HashLength
}
// Dereference removes an existing reference from a root node.
func (db *Database) Dereference(root common.Hash) {
// Dereference removes an existing reference from a root node. It always returns
// a nil error.
func (db *Database) Dereference(root common.Hash) error {
// Sanity check to ensure that the meta-root is not removed
if root == (common.Hash{}) {
log.Error("Attempted to dereference the trie cache meta root")
return
return nil
}
db.lock.Lock()
defer db.lock.Unlock()
@ -273,6 +275,7 @@ func (db *Database) Dereference(root common.Hash) {
log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start),
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
return nil
}
// dereference is the private locked version of Dereference.

View file

@ -0,0 +1,60 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 hashdb
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/triedb/pathdb"
)
// These methods are implemented by pathdb but not supported by hashdb. They are
// included for interface parity to avoid triedb having to check concrete types.
var errUnsupported = errors.New("method not supported by hashdb")
// Recover isn't supported and always returns an error.
func (*Database) Recover(target common.Hash) error { return errUnsupported }
// Recoverable isn't supported and always returns an error.
func (*Database) Recoverable(root common.Hash) (bool, error) { return false, errUnsupported }
// Disable isn't supported and always returns an error.
func (*Database) Disable() error { return errUnsupported }
// Enable isn't supported and always returns an error.
func (*Database) Enable(root common.Hash) error { return errUnsupported }
// Journal isn't supported and always returns an error.
func (*Database) Journal(root common.Hash) error { return errUnsupported }
// SetBufferSize isn't supported and always returns an error.
func (*Database) SetBufferSize(int) error { return errUnsupported }
// AccountHistory isn't supported and always returns an error.
func (*Database) AccountHistory(_ common.Address, start, end uint64) (*pathdb.HistoryStats, error) {
return nil, errUnsupported
}
// StorageHistory isn't supported and always returns an error.
func (*Database) StorageHistory(_ common.Address, slot common.Hash, start, end uint64) (*pathdb.HistoryStats, error) {
return nil, errUnsupported
}
// HistoryRange isn't supported and always returns an error.
func (*Database) HistoryRange() (uint64, uint64, error) { return 0, 0, errUnsupported }

View file

@ -17,8 +17,6 @@
package triedb
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/triedb/pathdb"
)
@ -33,11 +31,7 @@ import (
//
// This function is only supported by path mode database.
func (db *Database) AccountHistory(address common.Address, start, end uint64) (*pathdb.HistoryStats, error) {
pdb, ok := db.backend.(*pathdb.Database)
if !ok {
return nil, errors.New("not supported")
}
return pdb.AccountHistory(address, start, end)
return db.backend.AccountHistory(address, start, end)
}
// StorageHistory inspects the storage history within the specified range.
@ -52,11 +46,7 @@ func (db *Database) AccountHistory(address common.Address, start, end uint64) (*
//
// This function is only supported by path mode database.
func (db *Database) StorageHistory(address common.Address, slot common.Hash, start uint64, end uint64) (*pathdb.HistoryStats, error) {
pdb, ok := db.backend.(*pathdb.Database)
if !ok {
return nil, errors.New("not supported")
}
return pdb.StorageHistory(address, slot, start, end)
return db.backend.StorageHistory(address, slot, start, end)
}
// HistoryRange returns the block numbers associated with earliest and latest
@ -64,9 +54,5 @@ func (db *Database) StorageHistory(address common.Address, slot common.Hash, sta
//
// This function is only supported by path mode database.
func (db *Database) HistoryRange() (uint64, uint64, error) {
pdb, ok := db.backend.(*pathdb.Database)
if !ok {
return 0, 0, errors.New("not supported")
}
return pdb.HistoryRange()
return db.backend.HistoryRange()
}

View file

@ -366,7 +366,7 @@ func (db *Database) Recover(root common.Hash) error {
}
// Short circuit if the target state is not recoverable.
root = types.TrieRootHash(root)
if !db.Recoverable(root) {
if !db.recoverable(root) {
return errStateUnrecoverable
}
// Apply the state histories upon the disk layer in order.
@ -397,8 +397,15 @@ func (db *Database) Recover(root common.Hash) error {
return nil
}
// Recoverable returns the indicator if the specified state is recoverable.
func (db *Database) Recoverable(root common.Hash) bool {
// Recoverable returns the indicator if the specified state is recoverable. It
// always returns a nil error.
func (db *Database) Recoverable(root common.Hash) (bool, error) {
return db.recoverable(root), nil
}
// recoverable is the internal implementation of [Database.Recoverable], which
// has to return an error for conformity with the triedb.backend interface.
func (db *Database) recoverable(root common.Hash) bool {
// Ensure the requested state is a known state.
root = types.TrieRootHash(root)
id := rawdb.ReadStateID(db.diskdb, root)

View file

@ -469,7 +469,13 @@ func TestDatabaseRecoverable(t *testing.T) {
{tester.roots[index+1], false},
}
for i, c := range cases {
result := tester.db.Recoverable(c.root)
result, err := tester.db.Recoverable(c.root)
if err != nil {
// Recoverable() always returns nil so this should never happen. The
// signature only includes a returned error to conform with a
// triedb-internal interface.
t.Fatalf("case: %d, %T.Recoverable() got error %v", i, tester.db, err)
}
if result != c.expect {
t.Fatalf("case: %d, unexpected result, want %t, got %t", i, c.expect, result)
}

View file

@ -0,0 +1,37 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 pathdb
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
// These methods are implemented by hashdb but not supported by pathdb. They are
// included for interface parity to avoid triedb having to check concrete types.
var errUnsupported = errors.New("method not supported by pathdb")
// Cap isn't supported and always returns an error.
func (*Database) Cap(limit common.StorageSize) error { return errUnsupported }
// Reference isn't supported and always returns an error.
func (*Database) Reference(root, parent common.Hash) error { return errUnsupported }
// Dereference isn't supported and always returns an error.
func (*Database) Dereference(root common.Hash) error { return errUnsupported }