diff --git a/core/state/statedb.go b/core/state/statedb.go index ac67ac66c7bf..2a39cbb029e1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -389,6 +389,16 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) commo return common.Hash{} } +// GetStateAndCommittedState retrieves the current and committed values from the +// given account's storage trie. +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 547c6c26e402..1941d6013115 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -84,8 +84,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/state/statedb_test.go b/core/state/statedb_test.go index 5b3d1fa5bafc..bdeb60eeb62f 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1056,6 +1056,41 @@ func TestCommitCopy(t *testing.T) { } } +func TestGetStateAndCommittedState(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase()) + state, _ := New(types.EmptyRootHash, db) + + addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") + key := common.HexToHash("a1") + committed := common.HexToHash("b1") + dirty := common.HexToHash("b2") + + current, original := state.GetStateAndCommittedState(addr, key) + if current != (common.Hash{}) || original != (common.Hash{}) { + t.Fatalf("empty slot mismatch: have current=%x original=%x", current, original) + } + + state.SetState(addr, key, committed) + current, original = state.GetStateAndCommittedState(addr, key) + if current != committed || original != (common.Hash{}) { + t.Fatalf("dirty slot mismatch: have current=%x original=%x want current=%x original=%x", current, original, committed, common.Hash{}) + } + + root, _ := state.Commit(0, false) + state, _ = New(root, db) + + current, original = state.GetStateAndCommittedState(addr, key) + if current != committed || original != committed { + t.Fatalf("committed slot mismatch: have current=%x original=%x want current=%x original=%x", current, original, committed, committed) + } + + state.SetState(addr, key, dirty) + current, original = state.GetStateAndCommittedState(addr, key) + if current != dirty || original != committed { + t.Fatalf("updated dirty slot mismatch: have current=%x original=%x want current=%x original=%x", current, original, dirty, committed) + } +} + // Tests that account and storage tries are flushed in the correct order and that // no data loss occurs. func TestFlushOrderDataLoss(t *testing.T) { diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 022bc747170d..ed6ef3c7d4e8 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -100,8 +100,8 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi return 0, ErrWriteProtection } 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) @@ -140,7 +140,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 @@ -190,15 +189,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/gas_table_test.go b/core/vm/gas_table_test.go index 2062e776bfb2..26092676a0d0 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -111,6 +111,58 @@ func TestEIP2200(t *testing.T) { } } +type countingStateDB struct { + *state.StateDB + getStateCalls int + getCommittedStateCalls int + getCombinedStateCalls int +} + +func (s *countingStateDB) GetState(addr common.Address, key common.Hash) common.Hash { + s.getStateCalls++ + return s.StateDB.GetState(addr, key) +} + +func (s *countingStateDB) GetCommittedState(addr common.Address, key common.Hash) common.Hash { + s.getCommittedStateCalls++ + return s.StateDB.GetCommittedState(addr, key) +} + +func (s *countingStateDB) GetStateAndCommittedState(addr common.Address, key common.Hash) (common.Hash, common.Hash) { + s.getCombinedStateCalls++ + return s.StateDB.GetStateAndCommittedState(addr, key) +} + +func TestEIP2200UsesCombinedStateGetter(t *testing.T) { + address := common.BytesToAddress([]byte("contract")) + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb.CreateAccount(address) + statedb.SetCode(address, hexutil.MustDecode("0x6002600055")) + statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{1})) + statedb.Finalise(true) + + countingDB := &countingStateDB{StateDB: statedb} + vmctx := BlockContext{ + CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + evm := NewEVM(vmctx, countingDB, nil, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) + + _, _, err := evm.Call(common.Address{}, address, nil, math.MaxUint64, new(uint256.Int)) + if err != nil { + t.Fatalf("call failed: %v", err) + } + if countingDB.getCombinedStateCalls == 0 { + t.Fatalf("expected GetStateAndCommittedState to be used") + } + if countingDB.getStateCalls != 0 { + t.Fatalf("expected GetState to be bypassed, got %d calls", countingDB.getStateCalls) + } + if countingDB.getCommittedStateCalls != 0 { + t.Fatalf("expected GetCommittedState to be bypassed, got %d calls", countingDB.getCommittedStateCalls) + } +} + var createGasTests = []struct { code string eip3860 bool diff --git a/core/vm/interface.go b/core/vm/interface.go index 8559bb8d475a..da6d4a972b0d 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -48,7 +48,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 new file mode 100644 index 000000000000..c6fc242f1b10 --- /dev/null +++ b/core/vm/interpreter_test.go @@ -0,0 +1,95 @@ +// Copyright 2021 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 . + +package vm + +import ( + "math" + "math/big" + "testing" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/holiman/uint256" +) + +var loopInterruptTests = []string{ + // infinite loop using JUMP: push(2) jumpdest dup1 jump + "60025b8056", + // infinite loop using JUMPI: push(1) push(4) jumpdest dup2 dup2 jumpi + "600160045b818157", +} + +func TestLoopInterrupt(t *testing.T) { + address := common.BytesToAddress([]byte("contract")) + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + + for i, tt := range loopInterruptTests { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.CreateAccount(address) + statedb.SetCode(address, common.Hex2Bytes(tt)) + statedb.Finalise(true) + + evm := NewEVM(vmctx, statedb, nil, params.AllEthashProtocolChanges, Config{}) + + errChannel := make(chan error) + timeout := make(chan bool) + + go func(evm *EVM) { + _, _, err := evm.Call(common.Address{}, address, nil, math.MaxUint64, new(uint256.Int)) + errChannel <- err + }(evm) + + go func() { + <-time.After(time.Second) + timeout <- true + }() + + evm.Cancel() + + select { + case <-timeout: + t.Errorf("test %d timed out", i) + case err := <-errChannel: + if err != nil { + t.Errorf("test %d failure: %v", i, err) + } + } + } +} + +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, nil, 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) + for b.Loop() { + gasSStoreEIP3529(evm, contract, stack, mem, 1234) + } +} diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index cb75a4a3c9ab..a770de1b7602 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -37,10 +37,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 { @@ -55,7 +55,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 22a80fa9eaeb..ef14e94b248e 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -40,6 +40,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{} +} + type dummyOpContext struct{} func (dummyOpContext) MemoryData() []byte { return nil }