core/state: add GetStateAndCommittedState (#31585)

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 <garyrong0905@gmail.com>
This commit is contained in:
Marius van der Wijden 2025-07-03 07:19:34 +02:00 committed by GitHub
parent 6eb212b245
commit 34f00a42f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 44 additions and 14 deletions

View file

@ -389,6 +389,15 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) commo
return common.Hash{} 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. // Database retrieves the low level database supporting the lower level trie ops.
func (s *StateDB) Database() Database { func (s *StateDB) Database() Database {
return s.db return s.db

View file

@ -85,8 +85,8 @@ func (s *hookedStateDB) GetRefund() uint64 {
return s.inner.GetRefund() return s.inner.GetRefund()
} }
func (s *hookedStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { func (s *hookedStateDB) GetStateAndCommittedState(addr common.Address, hash common.Hash) (common.Hash, common.Hash) {
return s.inner.GetCommittedState(addr, hash) return s.inner.GetStateAndCommittedState(addr, hash)
} }
func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash {

View file

@ -98,8 +98,8 @@ var (
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var ( var (
y, x = stack.Back(1), stack.Back(0) y, x = stack.Back(1), stack.Back(0)
current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
) )
// The legacy gas metering only takes into consideration the current state // 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) // 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) if current == value { // noop (1)
return params.NetSstoreNoopGas, nil return params.NetSstoreNoopGas, nil
} }
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
if original == current { if original == current {
if original == (common.Hash{}) { // create slot (2.1.1) if original == (common.Hash{}) { // create slot (2.1.1)
return params.NetSstoreInitGas, nil 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 // Gas sentry honoured, do the actual gas calculation based on the stored value
var ( var (
y, x = stack.Back(1), stack.Back(0) y, x = stack.Back(1), stack.Back(0)
current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
) )
value := common.Hash(y.Bytes32()) value := common.Hash(y.Bytes32())
if current == value { // noop (1) if current == value { // noop (1)
return params.SloadGasEIP2200, nil return params.SloadGasEIP2200, nil
} }
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
if original == current { if original == current {
if original == (common.Hash{}) { // create slot (2.1.1) if original == (common.Hash{}) { // create slot (2.1.1)
return params.SstoreSetGasEIP2200, nil return params.SstoreSetGasEIP2200, nil

View file

@ -50,7 +50,7 @@ type StateDB interface {
SubRefund(uint64) SubRefund(uint64)
GetRefund() 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 GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) common.Hash
GetStorageRoot(addr common.Address) common.Hash GetStorageRoot(addr common.Address) common.Hash

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"math" "math"
"math/big"
"testing" "testing"
"time" "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)
}
}

View file

@ -34,10 +34,10 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
} }
// Gas sentry honoured, do the actual gas calculation based on the stored value // Gas sentry honoured, do the actual gas calculation based on the stored value
var ( var (
y, x = stack.Back(1), stack.peek() y, x = stack.Back(1), stack.peek()
slot = common.Hash(x.Bytes32()) slot = common.Hash(x.Bytes32())
current = evm.StateDB.GetState(contract.Address(), slot) current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
cost = uint64(0) cost = uint64(0)
) )
// Check slot presence in the access list // Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
@ -52,7 +52,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
// return params.SloadGasEIP2200, nil // return params.SloadGasEIP2200, nil
return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS
} }
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
if original == current { if original == current {
if original == (common.Hash{}) { // create slot (2.1.1) if original == (common.Hash{}) { // create slot (2.1.1)
return cost + params.SstoreSetGasEIP2200, nil return cost + params.SstoreSetGasEIP2200, nil

View file

@ -39,6 +39,10 @@ func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) co
return common.Hash{} return common.Hash{}
} }
func (*dummyStatedb) GetStateAndCommittedState(common.Address, common.Hash) (common.Hash, common.Hash) {
return common.Hash{}, common.Hash{}
}
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
logger = NewStructLogger(nil) logger = NewStructLogger(nil)