go-ethereum
go-ethereum copied to clipboard
core/state: changes to journalling, part 1
This is a follow-up to #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.
(content moved to PR description)
Where these changes are leading to is the following:
type scopedJournal struct {
accountChanges map[common.Address][]byte
refund int64
logs []types.Log
....
}
The accountChanges
are a representation of an account (probably rlp-data for types.SlimAccount
). It is the entry-type which replaces individual balanceChange
, nonceChange
, creationChange
, touchChange
and selfdestructChange
. We only ever need it once, per scope. Example:
1. add 1 to X
2. add 2 to X
3. set nonce=500 for X
4. X is selfdestructed
5. x is set to zero balance
6. revert
Instead of collecting this as 6 separate entries, it's sufficient if the very first add 1 to X
stores the pre-state of X
. After that, we can omit expanding the journal for this scope.
Therefore, it is good if we can remove implementation-flags such as destructed
from entering the journal. And it's the same with code
, if we can prevent code
from entering into the journal, that change too can be encompassed by this system.
Example of how it can look:
// journalAccountChange is the common shared implementation for all account-changes.
// These changes all fall back to this method:
// - balance change
// - nonce change
// - creation change (in this case, the account is nil)
func (j *scopedJournal) journaAccountChange(address common.Address, account types.StateAccount) {
// Unless the account has already been journalled, journal it now
if _, ok := accountChanges[address]; !ok {
if account != nil {
accountChanges[address] = types.SlimAccountRLP(account)
} else {
accountChanges[address] = nil
}
j.dirties[address]++
}
}
func (j *scopedJournal) JournalNonceChange(addr common.Address, account types.StateAccount) {
j.journaAccountChange(addr, account)
}
func (j *scopedJournal) JournalBalanceChange(addr common.Address, account types.StateAccount) {
j.journaAccountChange(address, account)
}
func (j *scopedJournal) JournalCreate(addr common.Address) {
j.journaAccountChange(addr, nil)
}
The effect of the changes -- not on this branch, but no the even more experimental journal_changes_pt2
The burntpix benchmark (the execution time differs pretty wildly between 7-11 seconds on both versions).
With master
:
EVM gas used: 1194877856
execution time: 8.303515894s
allocations: 497_562
allocated bytes: 107_153_152
With set-based journal:
EVM gas used: 1194877856
execution time: 9.63126912s
allocations: 30_046
allocated bytes: 28_804_928
Now rebased to follow on top of https://github.com/ethereum/go-ethereum/pull/29627
Rebased