core/state, core/vm: define set destructible

This commit is contained in:
Gary Rong 2024-04-09 13:52:28 +08:00
parent d68fda5997
commit 2e079b3f03
7 changed files with 66 additions and 27 deletions

View file

@ -128,6 +128,9 @@ type (
account *common.Address account *common.Address
prevcode, prevhash []byte prevcode, prevhash []byte
} }
destructibleChange struct {
account common.Address
}
// Changes to other state values. // Changes to other state values.
refundChange struct { refundChange struct {
@ -254,6 +257,20 @@ func (ch codeChange) copy() journalEntry {
} }
} }
func (ch destructibleChange) revert(s *StateDB) {
s.getStateObject(ch.account).destructible = false
}
func (ch destructibleChange) dirtied() *common.Address {
return nil // destruct-eligible flag is not considered as dirty
}
func (ch destructibleChange) copy() journalEntry {
return destructibleChange{
account: ch.account,
}
}
func (ch storageChange) revert(s *StateDB) { func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
} }

View file

@ -55,7 +55,7 @@ type stateObject struct {
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites originStorage Storage // Storage cache of original entries to de-duplicate rewrites
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction
@ -66,8 +66,11 @@ type stateObject struct {
// account is still accessible in the scope of same transaction. // account is still accessible in the scope of same transaction.
selfDestructed bool selfDestructed bool
// Flag whether the object was created in the current transaction // This is an EIP-6780 flag indicating if the object is eligible for
created bool // self-destruct. Potential scenarios as follows:
// - object is created in the current transaction
// - object was previously existent and is being deployed in current transaction
destructible bool
} }
// empty returns whether the account is considered empty. // empty returns whether the account is considered empty.
@ -77,10 +80,6 @@ func (s *stateObject) empty() bool {
// newObject creates a state object. // newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
var (
origin = acct
created = acct == nil // true if the account was not existent
)
if acct == nil { if acct == nil {
acct = types.NewEmptyStateAccount() acct = types.NewEmptyStateAccount()
} }
@ -88,12 +87,11 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
db: db, db: db,
address: address, address: address,
addrHash: crypto.Keccak256Hash(address[:]), addrHash: crypto.Keccak256Hash(address[:]),
origin: origin, origin: acct,
data: *acct, data: *acct,
originStorage: make(Storage), originStorage: make(Storage),
pendingStorage: make(Storage), pendingStorage: make(Storage),
dirtyStorage: make(Storage), dirtyStorage: make(Storage),
created: created,
} }
} }
@ -246,6 +244,7 @@ func (s *stateObject) finalise(prefetch bool) {
if len(s.dirtyStorage) > 0 { if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage) s.dirtyStorage = make(Storage)
} }
s.destructible = false // unset the flag at the end of transaction
} }
// updateTrie is responsible for persisting cached storage changes into the // updateTrie is responsible for persisting cached storage changes into the
@ -450,7 +449,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj.dirtyStorage = s.dirtyStorage.Copy() obj.dirtyStorage = s.dirtyStorage.Copy()
obj.dirtyCode = s.dirtyCode obj.dirtyCode = s.dirtyCode
obj.selfDestructed = s.selfDestructed obj.selfDestructed = s.selfDestructed
obj.created = s.created obj.destructible = s.destructible
return obj return obj
} }
@ -530,6 +529,16 @@ func (s *stateObject) setNonce(nonce uint64) {
s.data.Nonce = nonce s.data.Nonce = nonce
} }
func (s *stateObject) SetDestructible() {
if s.destructible {
return // might be possible in fuzzing
}
s.db.journal.append(destructibleChange{
account: s.address,
})
s.destructible = true
}
func (s *stateObject) CodeHash() []byte { func (s *stateObject) CodeHash() []byte {
return s.data.CodeHash return s.data.CodeHash
} }

View file

@ -497,7 +497,7 @@ func (s *StateDB) Selfdestruct6780(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
if stateObject.created { if stateObject.destructible {
s.SelfDestruct(addr) s.SelfDestruct(addr)
} }
} }
@ -659,7 +659,17 @@ func (s *StateDB) createObject(addr common.Address) *stateObject {
// exists, this function will silently overwrite it which might lead to a // exists, this function will silently overwrite it which might lead to a
// consensus bug eventually. // consensus bug eventually.
func (s *StateDB) CreateAccount(addr common.Address) { func (s *StateDB) CreateAccount(addr common.Address) {
s.createObject(addr) obj := s.createObject(addr)
obj.SetDestructible()
}
// SetDestructible marks the object with specific address as destructible.
func (s *StateDB) SetDestructible(addr common.Address) {
obj := s.getStateObject(addr)
if obj == nil {
return // might be possible in fuzzing
}
obj.SetDestructible()
} }
// Copy creates a deep, independent copy of the state. // Copy creates a deep, independent copy of the state.
@ -808,7 +818,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect)
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
} else { } else {
obj.created = false
obj.finalise(true) // Prefetch slots in the background obj.finalise(true) // Prefetch slots in the background
s.markUpdate(addr) s.markUpdate(addr)
} }

View file

@ -391,6 +391,18 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
s.SelfDestruct(addr) s.SelfDestruct(addr)
}, },
}, },
{
name: "SelfDestruct6780",
fn: func(a testAction, s *StateDB) {
s.Selfdestruct6780(addr)
},
},
{
name: "SetDestructible",
fn: func(a testAction, s *StateDB) {
s.SetDestructible(addr)
},
},
{ {
name: "AddRefund", name: "AddRefund",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {

View file

@ -461,6 +461,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
if !evm.StateDB.Exist(address) { if !evm.StateDB.Exist(address) {
evm.StateDB.CreateAccount(address) evm.StateDB.CreateAccount(address)
} else {
// The account with the designated address previously existed but is
// still eligible for deployment. Explicitly set it as destructible
// to adhere to EIP-6780.
evm.StateDB.SetDestructible(address)
} }
if evm.chainRules.IsEIP158 { if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1) evm.StateDB.SetNonce(address, 1)

View file

@ -56,6 +56,7 @@ type StateDB interface {
SelfDestruct(common.Address) SelfDestruct(common.Address)
HasSelfDestructed(common.Address) bool HasSelfDestructed(common.Address) bool
SetDestructible(addr common.Address)
Selfdestruct6780(common.Address) Selfdestruct6780(common.Address)

View file

@ -49,11 +49,6 @@ func TestBlockchain(t *testing.T) {
// using 4.6 TGas // using 4.6 TGas
bt.skipLoad(`.*randomStatetest94.json.*`) bt.skipLoad(`.*randomStatetest94.json.*`)
// The tests under Pyspecs are the ones that are published as execution-spect tests.
// We run these tests separately, no need to _also_ run them as part of the
// reference tests.
bt.skipLoad(`^Pyspecs/`)
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
execBlockTest(t, bt, test) execBlockTest(t, bt, test)
}) })
@ -68,15 +63,6 @@ func TestExecutionSpecBlocktests(t *testing.T) {
t.Skipf("directory %s does not exist", executionSpecBlockchainTestDir) t.Skipf("directory %s does not exist", executionSpecBlockchainTestDir)
} }
bt := new(testMatcher) bt := new(testMatcher)
// These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
// no longer delete "leftover storage" when deploying a contract.
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode_create_tx.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/recreate_self_destructed_contract_different_txs.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/delegatecall_from_new_contract_to_pre_existing_contract.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/create_selfdestruct_same_tx.json`)
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) { bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
execBlockTest(t, bt, test) execBlockTest(t, bt, test)
}) })