Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core, params: implement EIP-1087, net storage gas metering #17208

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
return nil, errBlockNumberUnsupported
}
statedb, _ := b.blockchain.State()
val := statedb.GetState(contract, key)
val, _ := statedb.GetState(contract, key)
return val[:], nil
}

Expand Down
8 changes: 5 additions & 3 deletions core/state/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,10 @@ type (
prev uint64
}
storageChange struct {
account *common.Address
key, prevalue common.Hash
account *common.Address
key common.Hash
prevValue common.Hash
prevDirty bool
}
codeChange struct {
account *common.Address
Expand Down Expand Up @@ -196,7 +198,7 @@ func (ch codeChange) dirtied() *common.Address {
}

func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
s.getStateObject(*ch.account).setState(ch.key, ch.prevValue, ch.prevDirty)
}

func (ch storageChange) dirtied() *common.Address {
Expand Down
59 changes: 41 additions & 18 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -159,17 +159,22 @@ func (c *stateObject) getTrie(db Database) Trie {
return c.trie
}

// GetState returns a value in account storage.
func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
value, exists := self.cachedStorage[key]
if exists {
return value
// GetState returns a value from account storage, and also whether the slot is
// dirty in the current transaction execution context.
func (self *stateObject) GetState(db Database, key common.Hash) (common.Hash, bool) {
value, dirty := self.dirtyStorage[key]
if dirty {
return value, true
}
value, cached := self.originStorage[key]
if cached {
return value, false
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(key[:])
if err != nil {
self.setError(err)
return common.Hash{}
return common.Hash{}, false
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
Expand All @@ -178,30 +183,48 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
}
value.SetBytes(content)
}
self.cachedStorage[key] = value
return value
self.originStorage[key] = value
return value, false
}

// 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 and don't mark dirty
prev, dirty := self.GetState(db, key)
if prev == value {
return
}
// New value is different, update and journal the change
self.setState(key, value, true)

self.db.journal.append(storageChange{
account: &self.address,
key: key,
prevalue: self.GetState(db, key),
account: &self.address,
key: key,
prevValue: prev,
prevDirty: dirty,
})
self.setState(key, value)
}

func (self *stateObject) setState(key, value common.Hash) {
self.cachedStorage[key] = value
self.dirtyStorage[key] = value
func (self *stateObject) setState(key, value common.Hash, dirty bool) {
if dirty {
self.dirtyStorage[key] = value
} else {
delete(self.dirtyStorage, key)
}
}

// updateTrie writes cached storage modifications into the object's storage trie.
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
Expand Down Expand Up @@ -279,7 +302,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
Expand Down
57 changes: 41 additions & 16 deletions core/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,17 @@ 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{})

value, dirty := s.state.GetState(address, common.Hash{})
if value != (common.Hash{}) {
c.Errorf("expected empty hash. got %x", value)
}
if dirty {
c.Errorf("expected non-dirty, got dirty")
}
}

func (s *StateSuite) TestSnapshot(c *checker.C) {
Expand All @@ -110,20 +115,27 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
data1 := common.BytesToHash([]byte{42})
data2 := common.BytesToHash([]byte{43})

// set initial state object value
// snapshot the genesis state
genesis := s.state.Snapshot()

// set initial state object value and snapshot it
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)
value, dirty := s.state.GetState(stateobjaddr, storageaddr)
c.Assert(data1, checker.DeepEquals, value)
c.Assert(true, checker.DeepEquals, dirty)

// revert up to the genesis state and ensure correct content
s.state.RevertToSnapshot(genesis)

c.Assert(data1, checker.DeepEquals, res)
value, dirty = s.state.GetState(stateobjaddr, storageaddr)
c.Assert(common.Hash{}, checker.DeepEquals, value)
c.Assert(false, checker.DeepEquals, dirty)
}

func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
Expand Down Expand Up @@ -208,17 +220,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.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.dirtyStorage {
if so1.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
}
}
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.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.originStorage {
if so0.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[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.originStorage {
if so1.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
}
}

Expand Down
53 changes: 38 additions & 15 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
Expand Down Expand Up @@ -236,12 +237,12 @@ 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 {
func (self *StateDB) GetState(addr common.Address, bhash common.Hash) (common.Hash, bool) {
stateObject := self.getStateObject(addr)
if stateObject != nil {
return stateObject.GetState(self.db, bhash)
}
return common.Hash{}
return common.Hash{}, false
}

// Database retrieves the low level database supporting the lower level trie ops.
Expand Down Expand Up @@ -430,24 +431,19 @@ func (self *StateDB) CreateAccount(addr common.Address) {
}
}

func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) {
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash, dirty bool) bool) {
so := db.getStateObject(addr)
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, true)
continue
}
cb(key, common.BytesToHash(it.Value), false)
}
}

Expand Down Expand Up @@ -524,9 +520,36 @@ func (self *StateDB) RevertToSnapshot(revid int) {
self.validRevisions = self.validRevisions[:idx]
}

// GetRefund returns the current value of the refund counter.
func (self *StateDB) GetRefund() uint64 {
return self.refund
// GetRefund returns the current value of the refund counter and any out-of-band
// refunds at the end of the transaction.
func (self *StateDB) GetRefund(netSstoreMeter bool) uint64 {
// If legacy gas metering is used, return the current refund counter
refund := self.refund
if !netSstoreMeter {
return refund
}
// If we're already using the Constantinople net sstore gas metering, refund noops
for addr := range self.journal.dirties {
object := self.stateObjects[addr]
for key, value := range object.dirtyStorage {
// If the slot didn't change in the end, refund most gas
if value == object.originStorage[key] {
if value == (common.Hash{}) {
refund += params.NetSstoreRefundNoopZero
} else {
refund += params.NetSstoreRefundNoopNonZero
}
continue
}
// If the slot did change, but was set to zero, refund
if value == (common.Hash{}) {
refund += params.NetSstoreRefund
continue
}
// Sorry, no refund for this slot
}
}
return refund
}

// Finalise finalises the state by removing the self destructed objects
Expand Down
24 changes: 17 additions & 7 deletions core/state/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,21 +381,31 @@ 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, dirty bool) bool {
v, d := checkstate.GetState(addr, key)
if ok := checkeq("GetState("+key.Hex()+").value", value, v); ok {
return ok
}
return checkeq("GetState("+key.Hex()+").dirty", dirty, d)
})
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, dirty bool) bool {
v, d := state.GetState(addr, key)
if ok := checkeq("GetState("+key.Hex()+").value", v, value); ok {
return ok
}
return checkeq("GetState("+key.Hex()+").dirty", d, dirty)
})
}
if err != nil {
return err
}
}

if state.GetRefund() != checkstate.GetRefund() {
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
state.GetRefund(), checkstate.GetRefund())
for _, netGasMetering := range []bool{false, true} {
if state.GetRefund(netGasMetering) != checkstate.GetRefund(netGasMetering) {
return fmt.Errorf("got GetRefund(%v) == %d, want GetRefund(%v) == %d",
netGasMetering, state.GetRefund(netGasMetering), netGasMetering, checkstate.GetRefund(netGasMetering))
}
}
if !reflect.DeepEqual(state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) {
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
Expand Down
8 changes: 5 additions & 3 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo

func (st *StateTransition) refundGas() {
// Apply refund counter, capped to half of the used gas.
refund := st.gasUsed() / 2
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
netSstoreMeter := st.evm.ChainConfig().IsConstantinople(st.evm.BlockNumber)

refund := st.state.GetRefund(netSstoreMeter)
if refund > st.gasUsed()/2 {
refund = st.gasUsed() / 2
}
st.gas += refund

Expand Down
Loading