Skip to content

Commit

Permalink
makes ethereum tests configurable to inject db implementations (ether…
Browse files Browse the repository at this point in the history
…eum#7)

* makes ethereum tests configurable to inject db implementations
* simplifies code by embedding pre state initialisation in factory
  • Loading branch information
kjezek authored Oct 16, 2024
1 parent 7656f60 commit 0dd3a9b
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 9 deletions.
43 changes: 43 additions & 0 deletions tests/db_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package tests

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/holiman/uint256"
)

// TestStateDB allows for switching database implementation for running tests.
// It is an extension of vm.StateDB with additional methods that were originally
// available only at an implementation level of the geth database.
// Not all methods have to be available in all implementations, and clients
// should pair expected method outputs with the actual implementation.
type TestStateDB interface {
vm.StateDB

// Database returns the underlying database.
Database() state.Database

// Logs returns the logs of the current transaction.
Logs() []*types.Log

SetLogger(l *tracing.Hooks)

// SetBalance sets the balance of the given account.
SetBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason)

// IntermediateRoot returns current state root hash.
IntermediateRoot(deleteEmptyObjects bool) common.Hash

// Commit commits the state to the underlying trie database and returns state root hash.
Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error)
}

// TestContextFactory is an interface for creating test configurations.
type TestContextFactory interface {

// NewTestStateDB creates a new StateTestState instance.
NewTestStateDB(accounts types.GenesisAlloc) StateTestState
}
57 changes: 48 additions & 9 deletions tests/state_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,14 @@ func (t *StateTest) checkError(subtest StateSubtest, err error) error {

// Run executes a specific subtest and verifies the post-state and logs
func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string, postCheck func(err error, st *StateTestState)) (result error) {
st, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter, scheme)
factory := newGethFactory(rawdb.NewMemoryDatabase(), snapshotter, scheme)
return t.RunWith(subtest, vmconfig, factory, postCheck)
}

// RunWith executes a specific subtest and verifies the post-state and logs.
// It allows for injecting a custom TestContextFactory configuring state processor components.
func (t *StateTest) RunWith(subtest StateSubtest, vmconfig vm.Config, factory TestContextFactory, postCheck func(err error, st *StateTestState)) (result error) {
st, root, err := t.RunNoVerifyWith(subtest, vmconfig, factory)
// Invoke the callback at the end of function for further analysis.
defer func() {
postCheck(result, &st)
Expand All @@ -222,21 +229,28 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bo
if logs := rlpHash(st.StateDB.Logs()); logs != common.Hash(post.Logs) {
return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
}
st.StateDB, _ = state.New(root, st.StateDB.Database(), st.Snapshots)
return nil
}

// RunNoVerify runs a specific subtest and returns the statedb and post-state root.
// Remember to call state.Close after verifying the test result!
func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (st StateTestState, root common.Hash, err error) {
factory := newGethFactory(rawdb.NewMemoryDatabase(), snapshotter, scheme)
return t.RunNoVerifyWith(subtest, vmconfig, factory)
}

// RunNoVerifyWith runs a specific subtest and returns the statedb and post-state root.
// Remember to call state.Close after verifying the test result!
// It allows for injecting a custom TestContextFactory configuring state processor components.
func (t *StateTest) RunNoVerifyWith(subtest StateSubtest, vmconfig vm.Config, factory TestContextFactory) (st StateTestState, root common.Hash, err error) {
config, eips, err := GetChainConfig(subtest.Fork)
if err != nil {
return st, common.Hash{}, UnsupportedForkError{subtest.Fork}
}
vmconfig.ExtraEips = eips

block := t.genesis(config).ToBlock()
st = MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme)
st = MakePreStateWith(t.json.Pre, factory)

var baseFee *big.Int
if config.IsLondon(new(big.Int)) {
Expand Down Expand Up @@ -446,21 +460,46 @@ func vmTestBlockHash(n uint64) common.Hash {

// StateTestState groups all the state database objects together for use in tests.
type StateTestState struct {
StateDB *state.StateDB
StateDB TestStateDB
TrieDB *triedb.Database
Snapshots *snapshot.Tree
}

// MakePreState creates a state containing the given allocation.
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bool, scheme string) StateTestState {
factory := newGethFactory(db, snapshotter, scheme)
return MakePreStateWith(accounts, factory)
}

// MakePreStateWith creates a state containing the given allocation.
// It allows for injecting a custom TestContextFactory configuring database.
func MakePreStateWith(accounts types.GenesisAlloc, fact TestContextFactory) StateTestState {
st := fact.NewTestStateDB(accounts)
return st
}

// gethFactory is a factory for creating geth database.
type gethFactory struct {
db ethdb.Database
snapshotter bool
scheme string
}

// newGethFactory creates a new gethFactory.
func newGethFactory(db ethdb.Database, snapshotter bool, scheme string) TestContextFactory {
return gethFactory{db, snapshotter, scheme}
}

// NewTestStateDB creates a new StateTestState using geth database.
func (f gethFactory) NewTestStateDB(accounts types.GenesisAlloc) StateTestState {
tconf := &triedb.Config{Preimages: true}
if scheme == rawdb.HashScheme {
if f.scheme == rawdb.HashScheme {
tconf.HashDB = hashdb.Defaults
} else {
tconf.PathDB = pathdb.Defaults
}
triedb := triedb.NewDatabase(db, tconf)
sdb := state.NewDatabaseWithNodeDB(db, triedb)
triedb := triedb.NewDatabase(f.db, tconf)
sdb := state.NewDatabaseWithNodeDB(f.db, triedb)
statedb, _ := state.New(types.EmptyRootHash, sdb, nil)
for addr, a := range accounts {
statedb.SetCode(addr, a.Code)
Expand All @@ -475,14 +514,14 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo

// If snapshot is requested, initialize the snapshotter and use it in state.
var snaps *snapshot.Tree
if snapshotter {
if f.snapshotter {
snapconfig := snapshot.Config{
CacheSize: 1,
Recovery: false,
NoBuild: false,
AsyncBuild: false,
}
snaps, _ = snapshot.New(snapconfig, db, triedb, root)
snaps, _ = snapshot.New(snapconfig, f.db, triedb, root)
}
statedb, _ = state.New(root, sdb, snaps)
return StateTestState{statedb, triedb, snaps}
Expand Down

0 comments on commit 0dd3a9b

Please sign in to comment.