aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2018-09-21 19:16:18 +0800
committerGitHub <noreply@github.com>2018-09-21 19:16:18 +0800
commitee92bc537f159c2202058a33e015684cc2bca04c (patch)
tree8f89d2e71d98f26024069be193023ef5da2a0d3f /core
parent81080bf8cb7f60f59a68cf48998a29a1a2e10cb9 (diff)
parent360a72d54e9ea6fbf8ddb4872cc514d241ea2042 (diff)
downloaddexon-ee92bc537f159c2202058a33e015684cc2bca04c.tar.gz
dexon-ee92bc537f159c2202058a33e015684cc2bca04c.tar.zst
dexon-ee92bc537f159c2202058a33e015684cc2bca04c.zip
Merge pull request #17383 from holiman/eip1283
Eip1283
Diffstat (limited to 'core')
-rw-r--r--core/state/state_object.go44
-rw-r--r--core/state/state_test.go58
-rw-r--r--core/state/statedb.go38
-rw-r--r--core/state/statedb_test.go8
-rw-r--r--core/vm/gas_table.go77
-rw-r--r--core/vm/interface.go2
-rw-r--r--core/vm/logger_test.go5
-rw-r--r--core/vm/noop.go70
8 files changed, 164 insertions, 138 deletions
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 091d24184..f41ab0409 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -77,7 +77,7 @@ type stateObject struct {
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded
- cachedStorage Storage // Storage entry cache to avoid duplicate reads
+ originStorage Storage // Storage cache of original entries to dedup rewrites
dirtyStorage Storage // Storage entries that need to be flushed to disk
// Cache flags.
@@ -115,7 +115,7 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject {
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
data: data,
- cachedStorage: make(Storage),
+ originStorage: make(Storage),
dirtyStorage: make(Storage),
}
}
@@ -159,13 +159,25 @@ func (c *stateObject) getTrie(db Database) Trie {
return c.trie
}
-// GetState returns a value in account storage.
+// GetState retrieves a value from the account storage trie.
func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
- value, exists := self.cachedStorage[key]
- if exists {
+ // If we have a dirty value for this state entry, return it
+ value, dirty := self.dirtyStorage[key]
+ if dirty {
return value
}
- // Load from DB in case it is missing.
+ // Otherwise return the entry's original value
+ return self.GetCommittedState(db, key)
+}
+
+// GetCommittedState retrieves a value from the committed account storage trie.
+func (self *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
+ // If we have the original value cached, return that
+ value, cached := self.originStorage[key]
+ if cached {
+ return value
+ }
+ // Otherwise load the value from the database
enc, err := self.getTrie(db).TryGet(key[:])
if err != nil {
self.setError(err)
@@ -178,22 +190,27 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
}
value.SetBytes(content)
}
- self.cachedStorage[key] = value
+ self.originStorage[key] = value
return value
}
// SetState updates a value in account storage.
func (self *stateObject) SetState(db Database, key, value common.Hash) {
+ // If the new value is the same as old, don't set
+ prev := self.GetState(db, key)
+ if prev == value {
+ return
+ }
+ // New value is different, update and journal the change
self.db.journal.append(storageChange{
account: &self.address,
key: key,
- prevalue: self.GetState(db, key),
+ prevalue: prev,
})
self.setState(key, value)
}
func (self *stateObject) setState(key, value common.Hash) {
- self.cachedStorage[key] = value
self.dirtyStorage[key] = value
}
@@ -202,6 +219,13 @@ func (self *stateObject) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key)
+
+ // Skip noop changes, persist actual changes
+ if value == self.originStorage[key] {
+ continue
+ }
+ self.originStorage[key] = value
+
if (value == common.Hash{}) {
self.setError(tr.TryDelete(key[:]))
continue
@@ -279,7 +303,7 @@ func (self *stateObject) deepCopy(db *StateDB) *stateObject {
}
stateObject.code = self.code
stateObject.dirtyStorage = self.dirtyStorage.Copy()
- stateObject.cachedStorage = self.dirtyStorage.Copy()
+ stateObject.originStorage = self.originStorage.Copy()
stateObject.suicided = self.suicided
stateObject.dirtyCode = self.dirtyCode
stateObject.deleted = self.deleted
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 123559ea9..a09273f3b 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -96,11 +96,15 @@ func (s *StateSuite) TestNull(c *checker.C) {
s.state.CreateAccount(address)
//value := common.FromHex("0x823140710bf13990e4500136726d8b55")
var value common.Hash
+
s.state.SetState(address, common.Hash{}, value)
s.state.Commit(false)
- value = s.state.GetState(address, common.Hash{})
- if value != (common.Hash{}) {
- c.Errorf("expected empty hash. got %x", value)
+
+ if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) {
+ c.Errorf("expected empty current value, got %x", value)
+ }
+ if value := s.state.GetCommittedState(address, common.Hash{}); value != (common.Hash{}) {
+ c.Errorf("expected empty committed value, got %x", value)
}
}
@@ -110,20 +114,24 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
data1 := common.BytesToHash([]byte{42})
data2 := common.BytesToHash([]byte{43})
+ // snapshot the genesis state
+ genesis := s.state.Snapshot()
+
// set initial state object value
s.state.SetState(stateobjaddr, storageaddr, data1)
- // get snapshot of current state
snapshot := s.state.Snapshot()
- // set new state object value
+ // set a new state object value, revert it and ensure correct content
s.state.SetState(stateobjaddr, storageaddr, data2)
- // restore snapshot
s.state.RevertToSnapshot(snapshot)
- // get state storage value
- res := s.state.GetState(stateobjaddr, storageaddr)
+ c.Assert(s.state.GetState(stateobjaddr, storageaddr), checker.DeepEquals, data1)
+ c.Assert(s.state.GetCommittedState(stateobjaddr, storageaddr), checker.DeepEquals, common.Hash{})
- c.Assert(data1, checker.DeepEquals, res)
+ // revert up to the genesis state and ensure correct content
+ s.state.RevertToSnapshot(genesis)
+ c.Assert(s.state.GetState(stateobjaddr, storageaddr), checker.DeepEquals, common.Hash{})
+ c.Assert(s.state.GetCommittedState(stateobjaddr, storageaddr), checker.DeepEquals, common.Hash{})
}
func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
@@ -208,24 +216,30 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
}
- if len(so1.cachedStorage) != len(so0.cachedStorage) {
- t.Errorf("Storage size mismatch: have %d, want %d", len(so1.cachedStorage), len(so0.cachedStorage))
+ if len(so1.dirtyStorage) != len(so0.dirtyStorage) {
+ t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage))
}
- for k, v := range so1.cachedStorage {
- if so0.cachedStorage[k] != v {
- t.Errorf("Storage key %x mismatch: have %v, want %v", k, so0.cachedStorage[k], v)
+ for k, v := range so1.dirtyStorage {
+ if so0.dirtyStorage[k] != v {
+ t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v)
}
}
- for k, v := range so0.cachedStorage {
- if so1.cachedStorage[k] != v {
- t.Errorf("Storage key %x mismatch: have %v, want none.", k, v)
+ for k, v := range so0.dirtyStorage {
+ if so1.dirtyStorage[k] != v {
+ t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
}
}
-
- if so0.suicided != so1.suicided {
- t.Fatalf("suicided mismatch: have %v, want %v", so0.suicided, so1.suicided)
+ if len(so1.originStorage) != len(so0.originStorage) {
+ t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage))
+ }
+ for k, v := range so1.originStorage {
+ if so0.originStorage[k] != v {
+ t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v)
+ }
}
- if so0.deleted != so1.deleted {
- t.Fatalf("Deleted mismatch: have %v, want %v", so0.deleted, so1.deleted)
+ for k, v := range so0.originStorage {
+ if so1.originStorage[k] != v {
+ t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
+ }
}
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 101b03a12..216667ce9 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -169,11 +169,22 @@ func (self *StateDB) Preimages() map[common.Hash][]byte {
return self.preimages
}
+// AddRefund adds gas to the refund counter
func (self *StateDB) AddRefund(gas uint64) {
self.journal.append(refundChange{prev: self.refund})
self.refund += gas
}
+// SubRefund removes gas from the refund counter.
+// This method will panic if the refund counter goes below zero
+func (self *StateDB) SubRefund(gas uint64) {
+ self.journal.append(refundChange{prev: self.refund})
+ if gas > self.refund {
+ panic("Refund counter below zero")
+ }
+ self.refund -= gas
+}
+
// Exist reports whether the given account address exists in the state.
// Notably this also returns true for suicided accounts.
func (self *StateDB) Exist(addr common.Address) bool {
@@ -236,10 +247,20 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
return common.BytesToHash(stateObject.CodeHash())
}
-func (self *StateDB) GetState(addr common.Address, bhash common.Hash) common.Hash {
+// GetState retrieves a value from the given account's storage trie.
+func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
+ stateObject := self.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.GetState(self.db, hash)
+ }
+ return common.Hash{}
+}
+
+// GetCommittedState retrieves a value from the given account's committed storage trie.
+func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
stateObject := self.getStateObject(addr)
if stateObject != nil {
- return stateObject.GetState(self.db, bhash)
+ return stateObject.GetCommittedState(self.db, hash)
}
return common.Hash{}
}
@@ -435,19 +456,14 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
if so == nil {
return
}
-
- // When iterating over the storage check the cache first
- for h, value := range so.cachedStorage {
- cb(h, value)
- }
-
it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil))
for it.Next() {
- // ignore cached values
key := common.BytesToHash(db.trie.GetKey(it.Key))
- if _, ok := so.cachedStorage[key]; !ok {
- cb(key, common.BytesToHash(it.Value))
+ if value, dirty := so.dirtyStorage[key]; dirty {
+ cb(key, value)
+ continue
}
+ cb(key, common.BytesToHash(it.Value))
}
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index e2b349de8..cbd5bc75e 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -381,11 +381,11 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
// Check storage.
if obj := state.getStateObject(addr); obj != nil {
- state.ForEachStorage(addr, func(key, val common.Hash) bool {
- return checkeq("GetState("+key.Hex()+")", val, checkstate.GetState(addr, key))
+ state.ForEachStorage(addr, func(key, value common.Hash) bool {
+ return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
})
- checkstate.ForEachStorage(addr, func(key, checkval common.Hash) bool {
- return checkeq("GetState("+key.Hex()+")", state.GetState(addr, key), checkval)
+ checkstate.ForEachStorage(addr, func(key, value common.Hash) bool {
+ return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
})
}
if err != nil {
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index f9eea319e..10b4f719a 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -117,24 +117,69 @@ func gasReturnDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *
func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
- y, x = stack.Back(1), stack.Back(0)
- val = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
+ y, x = stack.Back(1), stack.Back(0)
+ current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
)
- // This checks for 3 scenario's and calculates gas accordingly
- // 1. From a zero-value address to a non-zero value (NEW VALUE)
- // 2. From a non-zero value address to a zero-value address (DELETE)
- // 3. From a non-zero to a non-zero (CHANGE)
- if val == (common.Hash{}) && y.Sign() != 0 {
- // 0 => non 0
- return params.SstoreSetGas, nil
- } else if val != (common.Hash{}) && y.Sign() == 0 {
- // non 0 => 0
- evm.StateDB.AddRefund(params.SstoreRefundGas)
- return params.SstoreClearGas, nil
- } else {
- // non 0 => non 0 (or 0 => 0)
- return params.SstoreResetGas, nil
+ // The legacy gas metering only takes into consideration the current state
+ if !evm.chainRules.IsConstantinople {
+ // This checks for 3 scenario's and calculates gas accordingly:
+ //
+ // 1. From a zero-value address to a non-zero value (NEW VALUE)
+ // 2. From a non-zero value address to a zero-value address (DELETE)
+ // 3. From a non-zero to a non-zero (CHANGE)
+ switch {
+ case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0
+ return params.SstoreSetGas, nil
+ case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0
+ evm.StateDB.AddRefund(params.SstoreRefundGas)
+ return params.SstoreClearGas, nil
+ default: // non 0 => non 0 (or 0 => 0)
+ return params.SstoreResetGas, nil
+ }
+ }
+ // The new gas metering is based on net gas costs (EIP-1283):
+ //
+ // 1. If current value equals new value (this is a no-op), 200 gas is deducted.
+ // 2. If current value does not equal new value
+ // 2.1. If original value equals current value (this storage slot has not been changed by the current execution context)
+ // 2.1.1. If original value is 0, 20000 gas is deducted.
+ // 2.1.2. Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter.
+ // 2.2. If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
+ // 2.2.1. If original value is not 0
+ // 2.2.1.1. If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0.
+ // 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
+ // 2.2.2. If original value equals new value (this storage slot is reset)
+ // 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
+ // 2.2.2.2. Otherwise, add 4800 gas to refund counter.
+ value := common.BigToHash(y)
+ if current == value { // noop (1)
+ return params.NetSstoreNoopGas, nil
+ }
+ original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x))
+ if original == current {
+ if original == (common.Hash{}) { // create slot (2.1.1)
+ return params.NetSstoreInitGas, nil
+ }
+ if value == (common.Hash{}) { // delete slot (2.1.2b)
+ evm.StateDB.AddRefund(params.NetSstoreClearRefund)
+ }
+ return params.NetSstoreCleanGas, nil // write existing slot (2.1.2)
+ }
+ if original != (common.Hash{}) {
+ if current == (common.Hash{}) { // recreate slot (2.2.1.1)
+ evm.StateDB.SubRefund(params.NetSstoreClearRefund)
+ } else if value == (common.Hash{}) { // delete slot (2.2.1.2)
+ evm.StateDB.AddRefund(params.NetSstoreClearRefund)
+ }
+ }
+ if original == value {
+ if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
+ evm.StateDB.AddRefund(params.NetSstoreResetClearRefund)
+ } else { // reset to original existing slot (2.2.2.2)
+ evm.StateDB.AddRefund(params.NetSstoreResetRefund)
+ }
}
+ return params.NetSstoreDirtyGas, nil
}
func makeGasLog(n uint64) gasFunc {
diff --git a/core/vm/interface.go b/core/vm/interface.go
index d176f5b39..fc15082f1 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -40,8 +40,10 @@ type StateDB interface {
GetCodeSize(common.Address) int
AddRefund(uint64)
+ SubRefund(uint64)
GetRefund() uint64
+ GetCommittedState(common.Address, common.Hash) common.Hash
GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash)
diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go
index 28830c445..cdbb70dc4 100644
--- a/core/vm/logger_test.go
+++ b/core/vm/logger_test.go
@@ -41,11 +41,6 @@ func (d *dummyContractRef) SetBalance(*big.Int) {}
func (d *dummyContractRef) SetNonce(uint64) {}
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
-type dummyStateDB struct {
- NoopStateDB
- ref *dummyContractRef
-}
-
func TestStoreCapture(t *testing.T) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
diff --git a/core/vm/noop.go b/core/vm/noop.go
deleted file mode 100644
index b71ead0d7..000000000
--- a/core/vm/noop.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2016 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 <http://www.gnu.org/licenses/>.
-
-package vm
-
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-func NoopCanTransfer(db StateDB, from common.Address, balance *big.Int) bool {
- return true
-}
-func NoopTransfer(db StateDB, from, to common.Address, amount *big.Int) {}
-
-type NoopEVMCallContext struct{}
-
-func (NoopEVMCallContext) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
- return nil, nil
-}
-func (NoopEVMCallContext) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
- return nil, nil
-}
-func (NoopEVMCallContext) Create(caller ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
- return nil, common.Address{}, nil
-}
-func (NoopEVMCallContext) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
- return nil, nil
-}
-
-type NoopStateDB struct{}
-
-func (NoopStateDB) CreateAccount(common.Address) {}
-func (NoopStateDB) SubBalance(common.Address, *big.Int) {}
-func (NoopStateDB) AddBalance(common.Address, *big.Int) {}
-func (NoopStateDB) GetBalance(common.Address) *big.Int { return nil }
-func (NoopStateDB) GetNonce(common.Address) uint64 { return 0 }
-func (NoopStateDB) SetNonce(common.Address, uint64) {}
-func (NoopStateDB) GetCodeHash(common.Address) common.Hash { return common.Hash{} }
-func (NoopStateDB) GetCode(common.Address) []byte { return nil }
-func (NoopStateDB) SetCode(common.Address, []byte) {}
-func (NoopStateDB) GetCodeSize(common.Address) int { return 0 }
-func (NoopStateDB) AddRefund(uint64) {}
-func (NoopStateDB) GetRefund() uint64 { return 0 }
-func (NoopStateDB) GetState(common.Address, common.Hash) common.Hash { return common.Hash{} }
-func (NoopStateDB) SetState(common.Address, common.Hash, common.Hash) {}
-func (NoopStateDB) Suicide(common.Address) bool { return false }
-func (NoopStateDB) HasSuicided(common.Address) bool { return false }
-func (NoopStateDB) Exist(common.Address) bool { return false }
-func (NoopStateDB) Empty(common.Address) bool { return false }
-func (NoopStateDB) RevertToSnapshot(int) {}
-func (NoopStateDB) Snapshot() int { return 0 }
-func (NoopStateDB) AddLog(*types.Log) {}
-func (NoopStateDB) AddPreimage(common.Hash, []byte) {}
-func (NoopStateDB) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) {}