Skip to content

Commit 57da628

Browse files
holimanrjl493456442
authored andcommitted
core/state: semantic journalling (part 1) (ethereum#28880)
This is a follow-up to ethereum#29520, and a preparatory PR to a more thorough change in the journalling system. ### API methods instead of `append` operations This PR hides the journal-implementation details away, so that the statedb invokes methods like `JournalCreate`, instead of explicitly appending journal-events in a list. This means that it's up to the journal whether to implement it as a sequence of events or aggregate/merge events. ### Snapshot-management inside the journal This PR also makes it so that management of valid snapshots is moved inside the journal, exposed via the methods `Snapshot() int` and `RevertToSnapshot(revid int, s *StateDB)`. ### SetCode JournalSetCode journals the setting of code: it is implicit that the previous values were "no code" and emptyCodeHash. Therefore, we can simplify the setCode journal. ### Selfdestruct The self-destruct journalling is a bit strange: we allow the selfdestruct operation to be journalled several times. This makes it so that we also are forced to store whether the account was already destructed. What we can do instead, is to only journal the first destruction, and after that only journal balance-changes, but not journal the selfdestruct itself. This simplifies the journalling, so that internals about state management does not leak into the journal-API. ### Preimages Preimages were, for some reason, integrated into the journal management, despite not being a consensus-critical data structure. This PR undoes that. --------- Co-authored-by: Gary Rong <[email protected]>
1 parent 573fd7b commit 57da628

File tree

5 files changed

+173
-129
lines changed

5 files changed

+173
-129
lines changed

core/state/journal.go

Lines changed: 133 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@
1717
package state
1818

1919
import (
20+
"fmt"
2021
"maps"
22+
"slices"
23+
"sort"
2124

2225
"github.com/ethereum/go-ethereum/common"
26+
"github.com/ethereum/go-ethereum/core/types"
2327
"github.com/holiman/uint256"
2428
)
2529

30+
type revision struct {
31+
id int
32+
journalIndex int
33+
}
34+
2635
// journalEntry is a modification entry in the state change journal that can be
2736
// reverted on demand.
2837
type journalEntry interface {
@@ -42,6 +51,9 @@ type journalEntry interface {
4251
type journal struct {
4352
entries []journalEntry // Current changes tracked by the journal
4453
dirties map[common.Address]int // Dirty accounts and the number of changes
54+
55+
validRevisions []revision
56+
nextRevisionId int
4557
}
4658

4759
// newJournal creates a new initialized journal.
@@ -51,6 +63,40 @@ func newJournal() *journal {
5163
}
5264
}
5365

66+
// reset clears the journal, after this operation the journal can be used anew.
67+
// It is semantically similar to calling 'newJournal', but the underlying slices
68+
// can be reused.
69+
func (j *journal) reset() {
70+
j.entries = j.entries[:0]
71+
j.validRevisions = j.validRevisions[:0]
72+
clear(j.dirties)
73+
j.nextRevisionId = 0
74+
}
75+
76+
// snapshot returns an identifier for the current revision of the state.
77+
func (j *journal) snapshot() int {
78+
id := j.nextRevisionId
79+
j.nextRevisionId++
80+
j.validRevisions = append(j.validRevisions, revision{id, j.length()})
81+
return id
82+
}
83+
84+
// revertToSnapshot reverts all state changes made since the given revision.
85+
func (j *journal) revertToSnapshot(revid int, s *StateDB) {
86+
// Find the snapshot in the stack of valid snapshots.
87+
idx := sort.Search(len(j.validRevisions), func(i int) bool {
88+
return j.validRevisions[i].id >= revid
89+
})
90+
if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid {
91+
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
92+
}
93+
snapshot := j.validRevisions[idx].journalIndex
94+
95+
// Replay the journal to undo changes and remove invalidated snapshots
96+
j.revert(s, snapshot)
97+
j.validRevisions = j.validRevisions[:idx]
98+
}
99+
54100
// append inserts a new modification entry to the end of the change journal.
55101
func (j *journal) append(entry journalEntry) {
56102
j.entries = append(j.entries, entry)
@@ -95,11 +141,90 @@ func (j *journal) copy() *journal {
95141
entries = append(entries, j.entries[i].copy())
96142
}
97143
return &journal{
98-
entries: entries,
99-
dirties: maps.Clone(j.dirties),
144+
entries: entries,
145+
dirties: maps.Clone(j.dirties),
146+
validRevisions: slices.Clone(j.validRevisions),
147+
nextRevisionId: j.nextRevisionId,
148+
}
149+
}
150+
151+
func (j *journal) logChange(txHash common.Hash) {
152+
j.append(addLogChange{txhash: txHash})
153+
}
154+
155+
func (j *journal) createObject(addr common.Address) {
156+
j.append(createObjectChange{account: &addr})
157+
}
158+
159+
func (j *journal) createContract(addr common.Address) {
160+
j.append(createContractChange{account: addr})
161+
}
162+
163+
func (j *journal) destruct(addr common.Address) {
164+
j.append(selfDestructChange{account: &addr})
165+
}
166+
167+
func (j *journal) storageChange(addr common.Address, key, prev, origin common.Hash) {
168+
j.append(storageChange{
169+
account: &addr,
170+
key: key,
171+
prevvalue: prev,
172+
origvalue: origin,
173+
})
174+
}
175+
176+
func (j *journal) transientStateChange(addr common.Address, key, prev common.Hash) {
177+
j.append(transientStorageChange{
178+
account: &addr,
179+
key: key,
180+
prevalue: prev,
181+
})
182+
}
183+
184+
func (j *journal) refundChange(previous uint64) {
185+
j.append(refundChange{prev: previous})
186+
}
187+
188+
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
189+
j.append(balanceChange{
190+
account: &addr,
191+
prev: previous.Clone(),
192+
})
193+
}
194+
195+
func (j *journal) setCode(address common.Address) {
196+
j.append(codeChange{account: &address})
197+
}
198+
199+
func (j *journal) nonceChange(address common.Address, prev uint64) {
200+
j.append(nonceChange{
201+
account: &address,
202+
prev: prev,
203+
})
204+
}
205+
206+
func (j *journal) touchChange(address common.Address) {
207+
j.append(touchChange{
208+
account: &address,
209+
})
210+
if address == ripemd {
211+
// Explicitly put it in the dirty-cache, which is otherwise generated from
212+
// flattened journals.
213+
j.dirty(address)
100214
}
101215
}
102216

217+
func (j *journal) accessListAddAccount(addr common.Address) {
218+
j.append(accessListAddAccountChange{&addr})
219+
}
220+
221+
func (j *journal) accessListAddSlot(addr common.Address, slot common.Hash) {
222+
j.append(accessListAddSlotChange{
223+
address: &addr,
224+
slot: &slot,
225+
})
226+
}
227+
103228
type (
104229
// Changes to the account trie.
105230
createObjectChange struct {
@@ -114,9 +239,7 @@ type (
114239
}
115240

116241
selfDestructChange struct {
117-
account *common.Address
118-
prev bool // whether account had already self-destructed
119-
prevbalance *uint256.Int
242+
account *common.Address
120243
}
121244

122245
// Changes to individual accounts.
@@ -135,8 +258,7 @@ type (
135258
origvalue common.Hash
136259
}
137260
codeChange struct {
138-
account *common.Address
139-
prevcode, prevhash []byte
261+
account *common.Address
140262
}
141263

142264
// Changes to other state values.
@@ -146,9 +268,6 @@ type (
146268
addLogChange struct {
147269
txhash common.Hash
148270
}
149-
addPreimageChange struct {
150-
hash common.Hash
151-
}
152271
touchChange struct {
153272
account *common.Address
154273
}
@@ -200,8 +319,7 @@ func (ch createContractChange) copy() journalEntry {
200319
func (ch selfDestructChange) revert(s *StateDB) {
201320
obj := s.getStateObject(*ch.account)
202321
if obj != nil {
203-
obj.selfDestructed = ch.prev
204-
obj.setBalance(ch.prevbalance)
322+
obj.selfDestructed = false
205323
}
206324
}
207325

@@ -211,9 +329,7 @@ func (ch selfDestructChange) dirtied() *common.Address {
211329

212330
func (ch selfDestructChange) copy() journalEntry {
213331
return selfDestructChange{
214-
account: ch.account,
215-
prev: ch.prev,
216-
prevbalance: new(uint256.Int).Set(ch.prevbalance),
332+
account: ch.account,
217333
}
218334
}
219335

@@ -263,19 +379,15 @@ func (ch nonceChange) copy() journalEntry {
263379
}
264380

265381
func (ch codeChange) revert(s *StateDB) {
266-
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
382+
s.getStateObject(*ch.account).setCode(types.EmptyCodeHash, nil)
267383
}
268384

269385
func (ch codeChange) dirtied() *common.Address {
270386
return ch.account
271387
}
272388

273389
func (ch codeChange) copy() journalEntry {
274-
return codeChange{
275-
account: ch.account,
276-
prevhash: common.CopyBytes(ch.prevhash),
277-
prevcode: common.CopyBytes(ch.prevcode),
278-
}
390+
return codeChange{account: ch.account}
279391
}
280392

281393
func (ch storageChange) revert(s *StateDB) {
@@ -344,20 +456,6 @@ func (ch addLogChange) copy() journalEntry {
344456
}
345457
}
346458

347-
func (ch addPreimageChange) revert(s *StateDB) {
348-
delete(s.preimages, ch.hash)
349-
}
350-
351-
func (ch addPreimageChange) dirtied() *common.Address {
352-
return nil
353-
}
354-
355-
func (ch addPreimageChange) copy() journalEntry {
356-
return addPreimageChange{
357-
hash: ch.hash,
358-
}
359-
}
360-
361459
func (ch accessListAddAccountChange) revert(s *StateDB) {
362460
/*
363461
One important invariant here, is that whenever a (addr, slot) is added, if the

core/state/state_object.go

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,7 @@ func (s *stateObject) markSelfdestructed() {
114114
}
115115

116116
func (s *stateObject) touch() {
117-
s.db.journal.append(touchChange{
118-
account: &s.address,
119-
})
120-
if s.address == ripemd {
121-
// Explicitly put it in the dirty-cache, which is otherwise generated from
122-
// flattened journals.
123-
s.db.journal.dirty(s.address)
124-
}
117+
s.db.journal.touchChange(s.address)
125118
}
126119

127120
// getTrie returns the associated storage trie. The trie will be opened if it's
@@ -252,16 +245,11 @@ func (s *stateObject) SetState(key, value common.Hash) {
252245
return
253246
}
254247
// New value is different, update and journal the change
255-
s.db.journal.append(storageChange{
256-
account: &s.address,
257-
key: key,
258-
prevvalue: prev,
259-
origvalue: origin,
260-
})
248+
s.db.journal.storageChange(s.address, key, prev, origin)
249+
s.setState(key, value, origin)
261250
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
262251
s.db.logger.OnStorageChange(s.address, key, prev, value)
263252
}
264-
s.setState(key, value, origin)
265253
}
266254

267255
// setState updates a value in account dirty storage. The dirtiness will be
@@ -511,10 +499,7 @@ func (s *stateObject) SubBalance(amount *uint256.Int, reason tracing.BalanceChan
511499
}
512500

513501
func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
514-
s.db.journal.append(balanceChange{
515-
account: &s.address,
516-
prev: new(uint256.Int).Set(s.data.Balance),
517-
})
502+
s.db.journal.balanceChange(s.address, s.data.Balance)
518503
if s.db.logger != nil && s.db.logger.OnBalanceChange != nil {
519504
s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason)
520505
}
@@ -590,14 +575,10 @@ func (s *stateObject) CodeSize() int {
590575
}
591576

592577
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
593-
prevcode := s.Code()
594-
s.db.journal.append(codeChange{
595-
account: &s.address,
596-
prevhash: s.CodeHash(),
597-
prevcode: prevcode,
598-
})
578+
s.db.journal.setCode(s.address)
599579
if s.db.logger != nil && s.db.logger.OnCodeChange != nil {
600-
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code)
580+
// TODO remove prevcode from this callback
581+
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), nil, codeHash, code)
601582
}
602583
s.setCode(codeHash, code)
603584
}
@@ -609,10 +590,7 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
609590
}
610591

611592
func (s *stateObject) SetNonce(nonce uint64) {
612-
s.db.journal.append(nonceChange{
613-
account: &s.address,
614-
prev: s.data.Nonce,
615-
})
593+
s.db.journal.nonceChange(s.address, s.data.Nonce)
616594
if s.db.logger != nil && s.db.logger.OnNonceChange != nil {
617595
s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce)
618596
}

0 commit comments

Comments
 (0)