mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 21:54:30 +00:00
feat: triedb.Database.Update options via statedb.Commit (#190)
## Why this should be merged To allow more thorough handling of duplicate state roots (or any other info other users would like), additional information can be provided to a call of `statedb.Commit`. This change allows arbitrary types to be sent to `triedb` as well as the `SnapshotTree`. However, this is a breaking change for those using the functionality already, since the snapshot commit option is wrapped with another call. ## How this works See the edited libevm test for usage. ## How this was tested Edited test case to include `TrieDBUpdateOption` and ensures the payload is sent.
This commit is contained in:
parent
ab5ad25dc3
commit
9b97d60230
6 changed files with 142 additions and 21 deletions
|
|
@ -1152,7 +1152,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
|
|||
//
|
||||
// The associated block number of the state transition is also provided
|
||||
// for more chain context.
|
||||
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...stateconf.SnapshotUpdateOption) (common.Hash, error) {
|
||||
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...stateconf.StateDBCommitOption) (common.Hash, error) {
|
||||
// Short circuit in case any database failure occurred earlier.
|
||||
if s.dbErr != nil {
|
||||
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||
|
|
@ -1242,7 +1242,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...statecon
|
|||
start := time.Now()
|
||||
// Only update if there's a state transition (skip empty Clique blocks)
|
||||
if parent := s.snap.Root(); parent != root {
|
||||
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages, opts...); err != nil {
|
||||
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages, stateconf.ExtractSnapshotUpdateOpts(opts...)...); err != nil {
|
||||
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
|
||||
}
|
||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||
|
|
@ -1268,7 +1268,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...statecon
|
|||
if root != origin {
|
||||
start := time.Now()
|
||||
set := triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)
|
||||
if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
|
||||
if err := s.db.TrieDB().Update(root, origin, block, nodes, set, stateconf.ExtractTrieDBUpdateOpts(opts...)...); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
s.originalRoot = root
|
||||
|
|
|
|||
|
|
@ -26,23 +26,48 @@ import (
|
|||
"github.com/ava-labs/libevm/core/rawdb"
|
||||
"github.com/ava-labs/libevm/core/state/snapshot"
|
||||
"github.com/ava-labs/libevm/core/types"
|
||||
"github.com/ava-labs/libevm/ethdb"
|
||||
"github.com/ava-labs/libevm/libevm/stateconf"
|
||||
"github.com/ava-labs/libevm/trie"
|
||||
"github.com/ava-labs/libevm/trie/trienode"
|
||||
"github.com/ava-labs/libevm/trie/triestate"
|
||||
"github.com/ava-labs/libevm/triedb"
|
||||
"github.com/ava-labs/libevm/triedb/database"
|
||||
"github.com/ava-labs/libevm/triedb/hashdb"
|
||||
)
|
||||
|
||||
func TestStateDBCommitPropagatesOptions(t *testing.T) {
|
||||
var rec snapTreeRecorder
|
||||
sdb, err := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), &rec)
|
||||
memdb := rawdb.NewMemoryDatabase()
|
||||
trieRec := &triedbRecorder{Database: hashdb.New(memdb, nil, &trie.MerkleResolver{})}
|
||||
triedb := triedb.NewDatabase(
|
||||
memdb,
|
||||
&triedb.Config{
|
||||
DBOverride: func(_ ethdb.Database) triedb.DBOverride {
|
||||
return trieRec
|
||||
},
|
||||
},
|
||||
)
|
||||
var snapRec snapTreeRecorder
|
||||
sdb, err := New(types.EmptyRootHash, NewDatabaseWithNodeDB(memdb, triedb), &snapRec)
|
||||
require.NoError(t, err, "New()")
|
||||
|
||||
// Ensures that rec.Update() will be called.
|
||||
sdb.SetNonce(common.Address{}, 42)
|
||||
|
||||
const payload = "hello world"
|
||||
opt := stateconf.WithUpdatePayload(payload)
|
||||
_, err = sdb.Commit(0, false, opt)
|
||||
require.NoErrorf(t, err, "%T.Commit(..., %T)", sdb, opt)
|
||||
const snapshotPayload = "hello world"
|
||||
var (
|
||||
parentHash = common.HexToHash("0x0102030405060708090a0b0c0d0e0f1011121314151617181920212223242526")
|
||||
currentHash = common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234")
|
||||
)
|
||||
snapshotOpt := stateconf.WithSnapshotUpdatePayload(snapshotPayload)
|
||||
triedbOpt := stateconf.WithTrieDBUpdatePayload(parentHash, currentHash)
|
||||
_, err = sdb.Commit(0, false, stateconf.WithSnapshotUpdateOpts(snapshotOpt), stateconf.WithTrieDBUpdateOpts(triedbOpt))
|
||||
|
||||
assert.Equalf(t, payload, rec.gotPayload, "%T payload propagated via %T.Commit() to %T.Update()", opt, sdb, rec)
|
||||
require.NoErrorf(t, err, "%T.Commit(..., %T, %T)", sdb, snapshotOpt, triedbOpt)
|
||||
assert.Equalf(t, snapshotPayload, snapRec.gotPayload, "%T payload propagated via %T.Commit() to %T.Update()", snapshotOpt, sdb, snapRec)
|
||||
assert.Truef(t, trieRec.exists, "%T exists propagated via %T.Commit() to %T.Update()", triedbOpt, sdb, trieRec)
|
||||
assert.Equalf(t, parentHash, trieRec.parentBlockHash, "%T parentHash propagated via %T.Commit() to %T.Update()", triedbOpt, sdb, trieRec)
|
||||
assert.Equalf(t, currentHash, trieRec.currentBlockHash, "%T currentHash propagated via %T.Commit() to %T.Update()", triedbOpt, sdb, trieRec)
|
||||
}
|
||||
|
||||
type snapTreeRecorder struct {
|
||||
|
|
@ -59,7 +84,7 @@ func (r *snapTreeRecorder) Update(
|
|||
_ map[common.Hash]struct{}, _ map[common.Hash][]byte, _ map[common.Hash]map[common.Hash][]byte,
|
||||
opts ...stateconf.SnapshotUpdateOption,
|
||||
) error {
|
||||
r.gotPayload = stateconf.ExtractUpdatePayload(opts...)
|
||||
r.gotPayload = stateconf.ExtractSnapshotUpdatePayload(opts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -78,3 +103,26 @@ func (snapshotStub) Account(common.Hash) (*types.SlimAccount, error) {
|
|||
func (snapshotStub) Root() common.Hash {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
type triedbRecorder struct {
|
||||
*hashdb.Database
|
||||
parentBlockHash common.Hash
|
||||
currentBlockHash common.Hash
|
||||
exists bool
|
||||
}
|
||||
|
||||
func (r *triedbRecorder) Update(
|
||||
root common.Hash,
|
||||
parent common.Hash,
|
||||
block uint64,
|
||||
nodes *trienode.MergedNodeSet,
|
||||
states *triestate.Set,
|
||||
opts ...stateconf.TrieDBUpdateOption,
|
||||
) error {
|
||||
r.parentBlockHash, r.currentBlockHash, r.exists = stateconf.ExtractTrieDBUpdatePayload(opts...)
|
||||
return r.Database.Update(root, parent, block, nodes, states)
|
||||
}
|
||||
|
||||
func (r *triedbRecorder) Reader(_ common.Hash) (database.Reader, error) {
|
||||
return r.Database.Reader(common.Hash{})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,48 @@
|
|||
// Package stateconf configures state management.
|
||||
package stateconf
|
||||
|
||||
import "github.com/ava-labs/libevm/libevm/options"
|
||||
import (
|
||||
"github.com/ava-labs/libevm/common"
|
||||
"github.com/ava-labs/libevm/libevm/options"
|
||||
)
|
||||
|
||||
// A StateDBCommitOption configures the behaviour of state.StateDB.Commit()
|
||||
type StateDBCommitOption = options.Option[stateDBCommitConfig]
|
||||
|
||||
type stateDBCommitConfig struct {
|
||||
snapshotOpts []SnapshotUpdateOption
|
||||
triedbOpts []TrieDBUpdateOption
|
||||
}
|
||||
|
||||
// WithSnapshotUpdateOpts returns a StateDBCommitOption carrying a list of
|
||||
// SnapshotUpdateOptions.
|
||||
// If multiple such options are used, only the last will be applied as they overwrite each other.
|
||||
func WithSnapshotUpdateOpts(opts ...SnapshotUpdateOption) StateDBCommitOption {
|
||||
return options.Func[stateDBCommitConfig](func(c *stateDBCommitConfig) {
|
||||
c.snapshotOpts = opts
|
||||
})
|
||||
}
|
||||
|
||||
// ExtractSnapshotUpdateOpts returns the list of SnapshotUpdateOptions carried
|
||||
// by the provided slice of StateDBCommitOption.
|
||||
func ExtractSnapshotUpdateOpts(opts ...StateDBCommitOption) []SnapshotUpdateOption {
|
||||
return options.As(opts...).snapshotOpts
|
||||
}
|
||||
|
||||
// WithTrieDBUpdateOpts returns a StateDBCommitOption carrying a list of
|
||||
// TrieDBUpdateOptions. If multiple such options are used, only the last will be
|
||||
// applied as they overwrite each other.
|
||||
func WithTrieDBUpdateOpts(opts ...TrieDBUpdateOption) StateDBCommitOption {
|
||||
return options.Func[stateDBCommitConfig](func(c *stateDBCommitConfig) {
|
||||
c.triedbOpts = opts
|
||||
})
|
||||
}
|
||||
|
||||
// ExtractTrieDBUpdateOpts returns the list of TrieDBUpdateOptions carried by
|
||||
// the provided slice of StateDBCommitOption.
|
||||
func ExtractTrieDBUpdateOpts(opts ...StateDBCommitOption) []TrieDBUpdateOption {
|
||||
return options.As(opts...).triedbOpts
|
||||
}
|
||||
|
||||
// A SnapshotUpdateOption configures the behaviour of
|
||||
// state.SnapshotTree.Update() implementations. This will be removed along with
|
||||
|
|
@ -28,18 +69,47 @@ type snapshotUpdateConfig struct {
|
|||
payload any
|
||||
}
|
||||
|
||||
// WithUpdatePayload returns a SnapshotUpdateOption carrying an arbitrary
|
||||
// WithSnapshotUpdatePayload returns a SnapshotUpdateOption carrying an arbitrary
|
||||
// payload. It acts only as a carrier to exploit existing function plumbing and
|
||||
// the effect on behaviour is left to the implementation receiving it.
|
||||
func WithUpdatePayload(p any) SnapshotUpdateOption {
|
||||
func WithSnapshotUpdatePayload(p any) SnapshotUpdateOption {
|
||||
return options.Func[snapshotUpdateConfig](func(c *snapshotUpdateConfig) {
|
||||
c.payload = p
|
||||
})
|
||||
}
|
||||
|
||||
// ExtractUpdatePayload returns the payload carried by a [WithUpdatePayload]
|
||||
// ExtractSnapshotUpdatePayload returns the payload carried by a [WithSnapshotUpdatePayload]
|
||||
// option. Only one such option can be used at once; behaviour is otherwise
|
||||
// undefined.
|
||||
func ExtractUpdatePayload(opts ...SnapshotUpdateOption) any {
|
||||
func ExtractSnapshotUpdatePayload(opts ...SnapshotUpdateOption) any {
|
||||
return options.As(opts...).payload
|
||||
}
|
||||
|
||||
// A TrieDBUpdateOption configures the behaviour of triedb.Database.Update() implementations.
|
||||
type TrieDBUpdateOption = options.Option[triedbUpdateConfig]
|
||||
|
||||
type triedbUpdateConfig struct {
|
||||
parentBlockHash *common.Hash
|
||||
currentBlockHash *common.Hash
|
||||
}
|
||||
|
||||
// WithTrieDBUpdatePayload returns a TrieDBUpdateOption carrying two block hashes.
|
||||
// It acts only as a carrier to exploit existing function plumbing and
|
||||
// the effect on behaviour is left to the implementation receiving it.
|
||||
func WithTrieDBUpdatePayload(parent common.Hash, current common.Hash) TrieDBUpdateOption {
|
||||
return options.Func[triedbUpdateConfig](func(c *triedbUpdateConfig) {
|
||||
c.parentBlockHash = &parent
|
||||
c.currentBlockHash = ¤t
|
||||
})
|
||||
}
|
||||
|
||||
// ExtractTrieDBUpdatePayload returns the payload carried by a [WithTrieDBUpdatePayload]
|
||||
// option. Only one such option can be used at once; behaviour is otherwise
|
||||
// undefined.
|
||||
func ExtractTrieDBUpdatePayload(opts ...TrieDBUpdateOption) (common.Hash, common.Hash, bool) {
|
||||
conf := options.As(opts...)
|
||||
if conf.parentBlockHash == nil && conf.currentBlockHash == nil {
|
||||
return common.Hash{}, common.Hash{}, false
|
||||
}
|
||||
return *conf.parentBlockHash, *conf.currentBlockHash, true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/ava-labs/libevm/common"
|
||||
"github.com/ava-labs/libevm/ethdb"
|
||||
"github.com/ava-labs/libevm/libevm/stateconf"
|
||||
"github.com/ava-labs/libevm/log"
|
||||
"github.com/ava-labs/libevm/trie"
|
||||
"github.com/ava-labs/libevm/trie/trienode"
|
||||
|
|
@ -70,7 +71,7 @@ type backend interface {
|
|||
//
|
||||
// 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
|
||||
Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, opts ...stateconf.TrieDBUpdateOption) error
|
||||
|
||||
// Commit writes all relevant trie nodes belonging to the specified state
|
||||
// to disk. Report specifies whether logs will be displayed in info level.
|
||||
|
|
@ -148,11 +149,11 @@ func (db *Database) Reader(blockRoot common.Hash) (database.Reader, error) {
|
|||
//
|
||||
// The passed in maps(nodes, states) will be retained to avoid copying everything.
|
||||
// Therefore, these maps must not be changed afterwards.
|
||||
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
|
||||
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, opts ...stateconf.TrieDBUpdateOption) error {
|
||||
if db.preimages != nil {
|
||||
db.preimages.commit(false)
|
||||
}
|
||||
return db.backend.Update(root, parent, block, nodes, states)
|
||||
return db.backend.Update(root, parent, block, nodes, states, opts...)
|
||||
}
|
||||
|
||||
// Commit iterates over all the children of a particular node, writes them out
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ava-labs/libevm/core/rawdb"
|
||||
"github.com/ava-labs/libevm/core/types"
|
||||
"github.com/ava-labs/libevm/ethdb"
|
||||
"github.com/ava-labs/libevm/libevm/stateconf"
|
||||
"github.com/ava-labs/libevm/log"
|
||||
"github.com/ava-labs/libevm/metrics"
|
||||
"github.com/ava-labs/libevm/rlp"
|
||||
|
|
@ -548,7 +549,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
|
|||
|
||||
// Update inserts the dirty nodes in provided nodeset into database and link the
|
||||
// account trie with multiple storage tries if necessary.
|
||||
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
|
||||
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, _ ...stateconf.TrieDBUpdateOption) error {
|
||||
// Ensure the parent state is present and signal a warning if not.
|
||||
if parent != types.EmptyRootHash {
|
||||
if blob, _ := db.node(parent); len(blob) == 0 {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/ava-labs/libevm/core/rawdb"
|
||||
"github.com/ava-labs/libevm/core/types"
|
||||
"github.com/ava-labs/libevm/ethdb"
|
||||
"github.com/ava-labs/libevm/libevm/stateconf"
|
||||
"github.com/ava-labs/libevm/log"
|
||||
"github.com/ava-labs/libevm/params"
|
||||
"github.com/ava-labs/libevm/trie/trienode"
|
||||
|
|
@ -223,7 +224,7 @@ func (db *Database) Reader(root common.Hash) (layer, error) {
|
|||
//
|
||||
// The passed in maps(nodes, states) will be retained to avoid copying everything.
|
||||
// Therefore, these maps must not be changed afterwards.
|
||||
func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
|
||||
func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, _ ...stateconf.TrieDBUpdateOption) error {
|
||||
// Hold the lock to prevent concurrent mutations.
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
|
|
|||
Loading…
Reference in a new issue