Skip to content

Commit 219a778

Browse files
authored
Merge pull request ethereum#347 from OffchainLabs/zombies-for-v1.14.2
Add a notion of zombies to preserve empty account behavior
2 parents 5c63736 + 5cbfbaf commit 219a778

File tree

6 files changed

+90
-2
lines changed

6 files changed

+90
-2
lines changed

core/state/journal.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,17 @@ type journalEntry interface {
4040
// commit. These are tracked to be able to be reverted in the case of an execution
4141
// exception or request for reversal.
4242
type journal struct {
43+
zombieEntries map[common.Address]int // Arbitrum: number of createZombieChange entries for each address
44+
4345
entries []journalEntry // Current changes tracked by the journal
4446
dirties map[common.Address]int // Dirty accounts and the number of changes
4547
}
4648

4749
// newJournal creates a new initialized journal.
4850
func newJournal() *journal {
4951
return &journal{
52+
zombieEntries: make(map[common.Address]int),
53+
5054
dirties: make(map[common.Address]int),
5155
}
5256
}
@@ -56,6 +60,10 @@ func (j *journal) append(entry journalEntry) {
5660
j.entries = append(j.entries, entry)
5761
if addr := entry.dirtied(); addr != nil {
5862
j.dirties[*addr]++
63+
// Arbitrum: also track the number of zombie changes
64+
if isZombie(entry) {
65+
j.zombieEntries[*addr]++
66+
}
5967
}
6068
}
6169

@@ -70,6 +78,13 @@ func (j *journal) revert(statedb *StateDB, snapshot int) {
7078
if addr := j.entries[i].dirtied(); addr != nil {
7179
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
7280
delete(j.dirties, *addr)
81+
82+
// Revert zombieEntries tracking
83+
if isZombie(j.entries[i]) {
84+
if j.zombieEntries[*addr]--; j.zombieEntries[*addr] == 0 {
85+
delete(j.zombieEntries, *addr)
86+
}
87+
}
7388
}
7489
}
7590
}
@@ -95,6 +110,8 @@ func (j *journal) copy() *journal {
95110
entries = append(entries, j.entries[i].copy())
96111
}
97112
return &journal{
113+
zombieEntries: maps.Clone(j.zombieEntries),
114+
98115
entries: entries,
99116
dirties: maps.Clone(j.dirties),
100117
}
@@ -106,6 +123,11 @@ type (
106123
account *common.Address
107124
}
108125

126+
// Changes to the account trie without being marked as dirty.
127+
createZombieChange struct {
128+
account *common.Address
129+
}
130+
109131
// createContractChange represents an account becoming a contract-account.
110132
// This event happens prior to executing initcode. The journal-event simply
111133
// manages the created-flag, in order to allow same-tx destruction.

core/state/journal_arbitrum.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,33 @@ func (ch EvictWasm) copy() journalEntry {
7878
Debug: ch.Debug,
7979
}
8080
}
81+
82+
// Arbitrum: only implemented by createZombieChange
83+
type possibleZombie interface {
84+
// Arbitrum: return true if this change should, on its own, create an empty account.
85+
// If combined with another non-zombie change the empty account will be cleaned up.
86+
isZombie() bool
87+
}
88+
89+
func isZombie(entry journalEntry) bool {
90+
possiblyZombie, isPossiblyZombie := entry.(possibleZombie)
91+
return isPossiblyZombie && possiblyZombie.isZombie()
92+
}
93+
94+
func (ch createZombieChange) revert(s *StateDB) {
95+
delete(s.stateObjects, *ch.account)
96+
}
97+
98+
func (ch createZombieChange) dirtied() *common.Address {
99+
return ch.account
100+
}
101+
102+
func (ch createZombieChange) copy() journalEntry {
103+
return createZombieChange{
104+
account: ch.account,
105+
}
106+
}
107+
108+
func (ch createZombieChange) isZombie() bool {
109+
return true
110+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package state
2+
3+
import "testing"
4+
5+
func TestIsZombie(t *testing.T) {
6+
var nonZombie journalEntry = createObjectChange{}
7+
if isZombie(nonZombie) {
8+
t.Error("createObjectChange should not be a zombie")
9+
}
10+
var zombie journalEntry = createZombieChange{}
11+
if !isZombie(zombie) {
12+
t.Error("createZombieChange should be a zombie")
13+
}
14+
}

core/state/statedb.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,15 @@ func (s *StateDB) createObject(addr common.Address) *stateObject {
695695
return obj
696696
}
697697

698+
// createObject creates a new state object. The assumption is held there is no
699+
// existing account with the given address, otherwise it will be silently overwritten.
700+
func (s *StateDB) createZombie(addr common.Address) *stateObject {
701+
obj := newObject(s, addr, nil)
702+
s.journal.append(createZombieChange{account: &addr})
703+
s.setStateObject(obj)
704+
return obj
705+
}
706+
698707
// CreateAccount explicitly creates a new state object, assuming that the
699708
// account did not previously exist in the state. If the account already
700709
// exists, this function will silently overwrite it which might lead to a
@@ -842,7 +851,8 @@ func (s *StateDB) GetRefund() uint64 {
842851
// into the tries just yet. Only IntermediateRoot or Commit will do that.
843852
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
844853
addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties))
845-
for addr := range s.journal.dirties {
854+
for addr, dirtyCount := range s.journal.dirties {
855+
isZombie := s.journal.zombieEntries[addr] == dirtyCount
846856
obj, exist := s.stateObjects[addr]
847857
if !exist {
848858
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
@@ -853,7 +863,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
853863
// Thus, we can safely ignore it here
854864
continue
855865
}
856-
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
866+
if obj.selfDestructed || (deleteEmptyObjects && obj.empty() && !isZombie) {
857867
delete(s.stateObjects, obj.address)
858868
s.markDelete(addr)
859869

core/state/statedb_arbitrum.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ func (s *StateDB) AddStylusPagesEver(new uint16) {
142142
s.arbExtraData.everWasmPages = common.SaturatingUAdd(s.arbExtraData.everWasmPages, new)
143143
}
144144

145+
// Arbitrum: preserve empty account behavior from old geth and ArbOS versions.
146+
func (s *StateDB) CreateZombieIfDeleted(addr common.Address) {
147+
if s.getStateObject(addr) == nil {
148+
if _, destructed := s.stateObjectsDestruct[addr]; destructed {
149+
s.createZombie(addr)
150+
}
151+
}
152+
}
153+
145154
func NewDeterministic(root common.Hash, db Database) (*StateDB, error) {
146155
sdb, err := New(root, db, nil)
147156
if err != nil {

core/vm/interface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type StateDB interface {
4545
AddStylusPages(new uint16) (uint16, uint16)
4646
AddStylusPagesEver(new uint16)
4747

48+
// Arbitrum: preserve old empty account behavior
49+
CreateZombieIfDeleted(common.Address)
50+
4851
Deterministic() bool
4952
Database() state.Database
5053

0 commit comments

Comments
 (0)