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

Add a notion of zombies to preserve empty account behavior #347

Merged
merged 5 commits into from
Nov 7, 2024
Merged
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
22 changes: 22 additions & 0 deletions core/state/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ type journalEntry interface {
// commit. These are tracked to be able to be reverted in the case of an execution
// exception or request for reversal.
type journal struct {
zombieEntries map[common.Address]int // Arbitrum: number of createZombieChange entries for each address

entries []journalEntry // Current changes tracked by the journal
dirties map[common.Address]int // Dirty accounts and the number of changes
}

// newJournal creates a new initialized journal.
func newJournal() *journal {
return &journal{
zombieEntries: make(map[common.Address]int),

dirties: make(map[common.Address]int),
}
}
Expand All @@ -56,6 +60,10 @@ func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry)
if addr := entry.dirtied(); addr != nil {
j.dirties[*addr]++
// Arbitrum: also track the number of zombie changes
if isZombie(entry) {
j.zombieEntries[*addr]++
}
}
}

Expand All @@ -70,6 +78,13 @@ func (j *journal) revert(statedb *StateDB, snapshot int) {
if addr := j.entries[i].dirtied(); addr != nil {
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
delete(j.dirties, *addr)

// Revert zombieEntries tracking
if isZombie(j.entries[i]) {
if j.zombieEntries[*addr]--; j.zombieEntries[*addr] == 0 {
delete(j.zombieEntries, *addr)
}
}
}
}
}
Expand All @@ -95,6 +110,8 @@ func (j *journal) copy() *journal {
entries = append(entries, j.entries[i].copy())
}
return &journal{
zombieEntries: maps.Clone(j.zombieEntries),

entries: entries,
dirties: maps.Clone(j.dirties),
}
Expand All @@ -106,6 +123,11 @@ type (
account *common.Address
}

// Changes to the account trie without being marked as dirty.
createZombieChange struct {
account *common.Address
}

// createContractChange represents an account becoming a contract-account.
// This event happens prior to executing initcode. The journal-event simply
// manages the created-flag, in order to allow same-tx destruction.
Expand Down
30 changes: 30 additions & 0 deletions core/state/journal_arbitrum.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,33 @@ func (ch EvictWasm) copy() journalEntry {
Debug: ch.Debug,
}
}

// Arbitrum: only implemented by createZombieChange
type possibleZombie interface {
// Arbitrum: return true if this change should, on its own, create an empty account.
// If combined with another non-zombie change the empty account will be cleaned up.
isZombie() bool
}

func isZombie(entry journalEntry) bool {
possiblyZombie, isPossiblyZombie := entry.(possibleZombie)
return isPossiblyZombie && possiblyZombie.isZombie()
}

func (ch createZombieChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account)
}

func (ch createZombieChange) dirtied() *common.Address {
return ch.account
}

func (ch createZombieChange) copy() journalEntry {
return createZombieChange{
account: ch.account,
}
}

func (ch createZombieChange) isZombie() bool {
return true
}
14 changes: 14 additions & 0 deletions core/state/journal_arbitrum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package state

import "testing"

func TestIsZombie(t *testing.T) {
var nonZombie journalEntry = createObjectChange{}
if isZombie(nonZombie) {
t.Error("createObjectChange should not be a zombie")
}
var zombie journalEntry = createZombieChange{}
if !isZombie(zombie) {
t.Error("createZombieChange should be a zombie")
}
}
14 changes: 12 additions & 2 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,15 @@ func (s *StateDB) createObject(addr common.Address) *stateObject {
return obj
}

// createObject creates a new state object. The assumption is held there is no
// existing account with the given address, otherwise it will be silently overwritten.
func (s *StateDB) createZombie(addr common.Address) *stateObject {
obj := newObject(s, addr, nil)
s.journal.append(createZombieChange{account: &addr})
s.setStateObject(obj)
return obj
}

// CreateAccount explicitly creates a new state object, assuming that the
// account did not previously exist in the state. If the account already
// exists, this function will silently overwrite it which might lead to a
Expand Down Expand Up @@ -842,7 +851,8 @@ func (s *StateDB) GetRefund() uint64 {
// into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties))
for addr := range s.journal.dirties {
for addr, dirtyCount := range s.journal.dirties {
isZombie := s.journal.zombieEntries[addr] == dirtyCount
obj, exist := s.stateObjects[addr]
if !exist {
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
Expand All @@ -853,7 +863,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// Thus, we can safely ignore it here
continue
}
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
if obj.selfDestructed || (deleteEmptyObjects && obj.empty() && !isZombie) {
delete(s.stateObjects, obj.address)
s.markDelete(addr)

Expand Down
9 changes: 9 additions & 0 deletions core/state/statedb_arbitrum.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ func (s *StateDB) AddStylusPagesEver(new uint16) {
s.arbExtraData.everWasmPages = common.SaturatingUAdd(s.arbExtraData.everWasmPages, new)
}

// Arbitrum: preserve empty account behavior from old geth and ArbOS versions.
func (s *StateDB) CreateZombieIfDeleted(addr common.Address) {
if s.getStateObject(addr) == nil {
if _, destructed := s.stateObjectsDestruct[addr]; destructed {
s.createZombie(addr)
}
}
}

func NewDeterministic(root common.Hash, db Database) (*StateDB, error) {
sdb, err := New(root, db, nil)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type StateDB interface {
AddStylusPages(new uint16) (uint16, uint16)
AddStylusPagesEver(new uint16)

// Arbitrum: preserve old empty account behavior
CreateZombieIfDeleted(common.Address)

Deterministic() bool
Database() state.Database

Expand Down
Loading