From 34f00a42f8b979f718665bd17f248b37a2935236 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 3 Jul 2025 07:19:34 +0200 Subject: [PATCH] core/state: add GetStateAndCommittedState (#31585) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves the SSTORE gas calculation a bit. Previously we would pull up the state object twice. This is okay for existing objects, since they are cached, however non-existing objects are not cached, thus we needed to go through all 128 diff layers as well as the disk layer twice, just for the gas calculation ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/vm cpu: AMD Ryzen 9 5900X 12-Core Processor │ /tmp/old.txt │ /tmp/new.txt │ │ sec/op │ sec/op vs base │ Interpreter-24 1118.0n ± 2% 602.8n ± 1% -46.09% (p=0.000 n=10) ``` --------- Co-authored-by: Gary Rong --- core/state/statedb.go | 9 +++++++++ core/state/statedb_hooked.go | 4 ++-- core/vm/gas_table.go | 10 ++++------ core/vm/interface.go | 2 +- core/vm/interpreter_test.go | 20 ++++++++++++++++++++ core/vm/operations_acl.go | 9 ++++----- eth/tracers/logger/logger_test.go | 4 ++++ 7 files changed, 44 insertions(+), 14 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 0d76e8c61c..e805885079 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -389,6 +389,15 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) commo return common.Hash{} } +// GetStateAndCommittedState returns the current value and the original value. +func (s *StateDB) GetStateAndCommittedState(addr common.Address, hash common.Hash) (common.Hash, common.Hash) { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.getState(hash) + } + return common.Hash{}, common.Hash{} +} + // Database retrieves the low level database supporting the lower level trie ops. func (s *StateDB) Database() Database { return s.db diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index a2fdfe9a21..3d1ef15031 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -85,8 +85,8 @@ func (s *hookedStateDB) GetRefund() uint64 { return s.inner.GetRefund() } -func (s *hookedStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - return s.inner.GetCommittedState(addr, hash) +func (s *hookedStateDB) GetStateAndCommittedState(addr common.Address, hash common.Hash) (common.Hash, common.Hash) { + return s.inner.GetStateAndCommittedState(addr, hash) } func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 58f039df9f..c7c1274bf2 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -98,8 +98,8 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( - y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + y, x = stack.Back(1), stack.Back(0) + current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32()) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -139,7 +139,6 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -188,15 +187,14 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( - y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + y, x = stack.Back(1), stack.Back(0) + current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32()) ) value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.SloadGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreSetGasEIP2200, nil diff --git a/core/vm/interface.go b/core/vm/interface.go index 86e8c56ab0..42a72db482 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -50,7 +50,7 @@ type StateDB interface { SubRefund(uint64) GetRefund() uint64 - GetCommittedState(common.Address, common.Hash) common.Hash + GetStateAndCommittedState(common.Address, common.Hash) (common.Hash, common.Hash) GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) common.Hash GetStorageRoot(addr common.Address) common.Hash diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 0b93dd59e7..8ed512316b 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -18,6 +18,7 @@ package vm import ( "math" + "math/big" "testing" "time" @@ -74,3 +75,22 @@ func TestLoopInterrupt(t *testing.T) { } } } + +func BenchmarkInterpreter(b *testing.B) { + var ( + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + evm = NewEVM(BlockContext{BlockNumber: big.NewInt(1), Time: 1, Random: &common.Hash{}}, statedb, params.MergedTestChainConfig, Config{}) + startGas uint64 = 100_000_000 + value = uint256.NewInt(0) + stack = newstack() + mem = NewMemory() + contract = NewContract(common.Address{}, common.Address{}, value, startGas, nil) + ) + stack.push(uint256.NewInt(123)) + stack.push(uint256.NewInt(123)) + gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) + b.ResetTimer() + for i := 0; i < b.N; i++ { + gasSStoreEIP3529(evm, contract, stack, mem, 1234) + } +} diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index ff3875868f..085b018e4c 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -34,10 +34,10 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( - y, x = stack.Back(1), stack.peek() - slot = common.Hash(x.Bytes32()) - current = evm.StateDB.GetState(contract.Address(), slot) - cost = uint64(0) + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot) + cost = uint64(0) ) // Check slot presence in the access list if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { @@ -52,7 +52,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // return params.SloadGasEIP2200, nil return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return cost + params.SstoreSetGasEIP2200, nil diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index b1e38bf627..12000b3b9a 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -39,6 +39,10 @@ func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) co return common.Hash{} } +func (*dummyStatedb) GetStateAndCommittedState(common.Address, common.Hash) (common.Hash, common.Hash) { + return common.Hash{}, common.Hash{} +} + func TestStoreCapture(t *testing.T) { var ( logger = NewStructLogger(nil)