mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 05:11:37 +00:00
core/state: exntend the journal with mutation kind tracking
This commit is contained in:
parent
0b35ad95f5
commit
b10ea05432
4 changed files with 258 additions and 80 deletions
|
|
@ -18,7 +18,6 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
|
@ -32,15 +31,116 @@ type revision struct {
|
||||||
journalIndex int
|
journalIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// journalMutation represents a set of mutations applied to a certain account.
|
||||||
|
type journalMutation uint8
|
||||||
|
|
||||||
|
// journalMutationKind indicates the type of account mutation.
|
||||||
|
type journalMutationKind uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
journalMutationKindTouch journalMutationKind = iota + 1
|
||||||
|
journalMutationKindCreate
|
||||||
|
journalMutationKindSelfDestruct
|
||||||
|
journalMutationKindBalance
|
||||||
|
journalMutationKindNonce
|
||||||
|
journalMutationKindCode
|
||||||
|
journalMutationKindStorage
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k journalMutationKind) mask() journalMutation {
|
||||||
|
if k == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return journalMutation(1) << (k - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type journalMutationCounts struct {
|
||||||
|
touch int
|
||||||
|
create int
|
||||||
|
selfDestruct int
|
||||||
|
balance int
|
||||||
|
nonce int
|
||||||
|
code int
|
||||||
|
storage int
|
||||||
|
}
|
||||||
|
|
||||||
|
type journalMutationState struct {
|
||||||
|
mask journalMutation
|
||||||
|
counts journalMutationCounts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *journalMutationState) add(kind journalMutationKind) {
|
||||||
|
s.counts.add(kind)
|
||||||
|
s.mask |= kind.mask()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *journalMutationState) remove(kind journalMutationKind) bool {
|
||||||
|
if s.counts.remove(kind) {
|
||||||
|
s.mask &^= kind.mask()
|
||||||
|
}
|
||||||
|
return s.mask == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s journalMutationState) copy() *journalMutationState {
|
||||||
|
cpy := s
|
||||||
|
return &cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *journalMutationCounts) add(kind journalMutationKind) {
|
||||||
|
switch kind {
|
||||||
|
case journalMutationKindTouch:
|
||||||
|
c.touch++
|
||||||
|
case journalMutationKindCreate:
|
||||||
|
c.create++
|
||||||
|
case journalMutationKindSelfDestruct:
|
||||||
|
c.selfDestruct++
|
||||||
|
case journalMutationKindBalance:
|
||||||
|
c.balance++
|
||||||
|
case journalMutationKindNonce:
|
||||||
|
c.nonce++
|
||||||
|
case journalMutationKindCode:
|
||||||
|
c.code++
|
||||||
|
case journalMutationKindStorage:
|
||||||
|
c.storage++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *journalMutationCounts) remove(kind journalMutationKind) bool {
|
||||||
|
switch kind {
|
||||||
|
case journalMutationKindTouch:
|
||||||
|
c.touch--
|
||||||
|
return c.touch == 0
|
||||||
|
case journalMutationKindCreate:
|
||||||
|
c.create--
|
||||||
|
return c.create == 0
|
||||||
|
case journalMutationKindSelfDestruct:
|
||||||
|
c.selfDestruct--
|
||||||
|
return c.selfDestruct == 0
|
||||||
|
case journalMutationKindBalance:
|
||||||
|
c.balance--
|
||||||
|
return c.balance == 0
|
||||||
|
case journalMutationKindNonce:
|
||||||
|
c.nonce--
|
||||||
|
return c.nonce == 0
|
||||||
|
case journalMutationKindCode:
|
||||||
|
c.code--
|
||||||
|
return c.code == 0
|
||||||
|
case journalMutationKindStorage:
|
||||||
|
c.storage--
|
||||||
|
return c.storage == 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// journalEntry is a modification entry in the state change journal that can be
|
// journalEntry is a modification entry in the state change journal that can be
|
||||||
// reverted on demand.
|
// reverted on demand.
|
||||||
type journalEntry interface {
|
type journalEntry interface {
|
||||||
// revert undoes the changes introduced by this journal entry.
|
// revert undoes the changes introduced by this journal entry.
|
||||||
revert(*StateDB)
|
revert(*StateDB)
|
||||||
|
|
||||||
// dirtied returns the Ethereum address modified by this journal entry.
|
// mutation returns the account mutation introduced by this entry.
|
||||||
// indicates false if no address was changed.
|
// It indicates false if no tracked account mutation was made.
|
||||||
dirtied() (common.Address, bool)
|
mutation() (common.Address, journalMutationKind, bool)
|
||||||
|
|
||||||
// copy returns a deep-copied journal entry.
|
// copy returns a deep-copied journal entry.
|
||||||
copy() journalEntry
|
copy() journalEntry
|
||||||
|
|
@ -50,8 +150,8 @@ type journalEntry interface {
|
||||||
// commit. These are tracked to be able to be reverted in the case of an execution
|
// commit. These are tracked to be able to be reverted in the case of an execution
|
||||||
// exception or request for reversal.
|
// exception or request for reversal.
|
||||||
type journal struct {
|
type journal struct {
|
||||||
entries []journalEntry // Current changes tracked by the journal
|
entries []journalEntry // Current changes tracked by the journal
|
||||||
dirties map[common.Address]int // Dirty accounts and the number of changes
|
mutations map[common.Address]*journalMutationState // Account mutation state accumulated across entries
|
||||||
|
|
||||||
validRevisions []revision
|
validRevisions []revision
|
||||||
nextRevisionId int
|
nextRevisionId int
|
||||||
|
|
@ -60,7 +160,7 @@ type journal struct {
|
||||||
// newJournal creates a new initialized journal.
|
// newJournal creates a new initialized journal.
|
||||||
func newJournal() *journal {
|
func newJournal() *journal {
|
||||||
return &journal{
|
return &journal{
|
||||||
dirties: make(map[common.Address]int),
|
mutations: make(map[common.Address]*journalMutationState),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +170,7 @@ func newJournal() *journal {
|
||||||
func (j *journal) reset() {
|
func (j *journal) reset() {
|
||||||
j.entries = j.entries[:0]
|
j.entries = j.entries[:0]
|
||||||
j.validRevisions = j.validRevisions[:0]
|
j.validRevisions = j.validRevisions[:0]
|
||||||
clear(j.dirties)
|
clear(j.mutations)
|
||||||
j.nextRevisionId = 0
|
j.nextRevisionId = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,33 +201,70 @@ func (j *journal) revertToSnapshot(revid int, s *StateDB) {
|
||||||
// append inserts a new modification entry to the end of the change journal.
|
// append inserts a new modification entry to the end of the change journal.
|
||||||
func (j *journal) append(entry journalEntry) {
|
func (j *journal) append(entry journalEntry) {
|
||||||
j.entries = append(j.entries, entry)
|
j.entries = append(j.entries, entry)
|
||||||
if addr, dirty := entry.dirtied(); dirty {
|
if addr, kind, dirty := entry.mutation(); dirty {
|
||||||
j.dirties[addr]++
|
state := j.mutations[addr]
|
||||||
|
if state == nil {
|
||||||
|
state = new(journalMutationState)
|
||||||
|
j.mutations[addr] = state
|
||||||
|
}
|
||||||
|
state.add(kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// revert undoes a batch of journalled modifications along with any reverted
|
// revert undoes a batch of journalled modifications along with any reverted
|
||||||
// dirty handling too.
|
// mutation tracking too.
|
||||||
func (j *journal) revert(statedb *StateDB, snapshot int) {
|
func (j *journal) revert(statedb *StateDB, snapshot int) {
|
||||||
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
||||||
// Undo the changes made by the operation
|
// Undo the changes made by the operation
|
||||||
j.entries[i].revert(statedb)
|
j.entries[i].revert(statedb)
|
||||||
|
|
||||||
// Drop any dirty tracking induced by the change
|
// Drop any mutation tracking induced by the change.
|
||||||
if addr, dirty := j.entries[i].dirtied(); dirty {
|
if addr, kind, dirty := j.entries[i].mutation(); dirty {
|
||||||
if j.dirties[addr]--; j.dirties[addr] == 0 {
|
state := j.mutations[addr]
|
||||||
delete(j.dirties, addr)
|
if state == nil {
|
||||||
|
panic(fmt.Errorf("journal mutation tracking missing for %x", addr[:]))
|
||||||
|
}
|
||||||
|
if state.remove(kind) {
|
||||||
|
delete(j.mutations, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
j.entries = j.entries[:snapshot]
|
j.entries = j.entries[:snapshot]
|
||||||
}
|
}
|
||||||
|
|
||||||
// dirty explicitly sets an address to dirty, even if the change entries would
|
// ripemdMagic explicitly keeps RIPEMD160 in the mutation set with a touch change.
|
||||||
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
|
//
|
||||||
// precompile consensus exception.
|
// Ethereum Mainnet contains an old empty-account touch/revert quirk for address
|
||||||
func (j *journal) dirty(addr common.Address) {
|
// 0x03. If we only relied on the journal entry above, the revert path would
|
||||||
j.dirties[addr]++
|
// remove the account from the mutation set together with the touch.
|
||||||
|
//
|
||||||
|
// Keep an explicit touch marker so tx finalisation still sees RIPEMD160
|
||||||
|
// on the mutation pass when replaying that historical case.
|
||||||
|
func (j *journal) ripemdMagic() {
|
||||||
|
state := j.mutations[ripemd]
|
||||||
|
if state == nil {
|
||||||
|
state = new(journalMutationState)
|
||||||
|
j.mutations[ripemd] = state
|
||||||
|
}
|
||||||
|
state.add(journalMutationKindTouch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *journal) mutation(addr common.Address) journalMutation {
|
||||||
|
if state := j.mutations[addr]; state != nil {
|
||||||
|
return state.mask
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *journal) mutationSet() map[common.Address]journalMutation {
|
||||||
|
if j.mutations == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make(map[common.Address]journalMutation, len(j.mutations))
|
||||||
|
for addr, state := range j.mutations {
|
||||||
|
out[addr] = state.mask
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// length returns the current number of entries in the journal.
|
// length returns the current number of entries in the journal.
|
||||||
|
|
@ -143,12 +280,23 @@ func (j *journal) copy() *journal {
|
||||||
}
|
}
|
||||||
return &journal{
|
return &journal{
|
||||||
entries: entries,
|
entries: entries,
|
||||||
dirties: maps.Clone(j.dirties),
|
mutations: copyMutationStates(j.mutations),
|
||||||
validRevisions: slices.Clone(j.validRevisions),
|
validRevisions: slices.Clone(j.validRevisions),
|
||||||
nextRevisionId: j.nextRevisionId,
|
nextRevisionId: j.nextRevisionId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyMutationStates(src map[common.Address]*journalMutationState) map[common.Address]*journalMutationState {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dst := make(map[common.Address]*journalMutationState, len(src))
|
||||||
|
for addr, state := range src {
|
||||||
|
dst[addr] = state.copy()
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
func (j *journal) logChange(txHash common.Hash) {
|
func (j *journal) logChange(txHash common.Hash) {
|
||||||
j.append(addLogChange{txhash: txHash})
|
j.append(addLogChange{txhash: txHash})
|
||||||
}
|
}
|
||||||
|
|
@ -212,9 +360,18 @@ func (j *journal) touchChange(address common.Address) {
|
||||||
account: address,
|
account: address,
|
||||||
})
|
})
|
||||||
if address == ripemd {
|
if address == ripemd {
|
||||||
// Explicitly put it in the dirty-cache, which is otherwise generated from
|
// Preserve the historical RIPEMD160 precompile consensus exception.
|
||||||
// flattened journals.
|
//
|
||||||
j.dirty(address)
|
// Mainnet contains an old empty-account touch/revert quirk for address
|
||||||
|
// 0x03. If we only relied on the journal entry above, the revert path
|
||||||
|
// would remove the account from the dirty set together with the touch.
|
||||||
|
// Keep an explicit dirty marker so tx finalisation still sees the
|
||||||
|
// account on the dirty pass when replaying that historical case.
|
||||||
|
//
|
||||||
|
// This does not force deletion by itself: Finalise will still delete the
|
||||||
|
// account only if the state object is present at tx end and qualifies for
|
||||||
|
// deletion there.
|
||||||
|
j.ripemdMagic()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,8 +452,8 @@ func (ch createObjectChange) revert(s *StateDB) {
|
||||||
delete(s.stateObjects, ch.account)
|
delete(s.stateObjects, ch.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) dirtied() (common.Address, bool) {
|
func (ch createObjectChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindCreate, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) copy() journalEntry {
|
func (ch createObjectChange) copy() journalEntry {
|
||||||
|
|
@ -309,8 +466,8 @@ func (ch createContractChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).newContract = false
|
s.getStateObject(ch.account).newContract = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createContractChange) dirtied() (common.Address, bool) {
|
func (ch createContractChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createContractChange) copy() journalEntry {
|
func (ch createContractChange) copy() journalEntry {
|
||||||
|
|
@ -326,8 +483,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) dirtied() (common.Address, bool) {
|
func (ch selfDestructChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindSelfDestruct, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) copy() journalEntry {
|
func (ch selfDestructChange) copy() journalEntry {
|
||||||
|
|
@ -341,8 +498,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||||
func (ch touchChange) revert(s *StateDB) {
|
func (ch touchChange) revert(s *StateDB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch touchChange) dirtied() (common.Address, bool) {
|
func (ch touchChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindTouch, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch touchChange) copy() journalEntry {
|
func (ch touchChange) copy() journalEntry {
|
||||||
|
|
@ -355,8 +512,8 @@ func (ch balanceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setBalance(ch.prev)
|
s.getStateObject(ch.account).setBalance(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch balanceChange) dirtied() (common.Address, bool) {
|
func (ch balanceChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindBalance, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch balanceChange) copy() journalEntry {
|
func (ch balanceChange) copy() journalEntry {
|
||||||
|
|
@ -370,8 +527,8 @@ func (ch nonceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setNonce(ch.prev)
|
s.getStateObject(ch.account).setNonce(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch nonceChange) dirtied() (common.Address, bool) {
|
func (ch nonceChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindNonce, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch nonceChange) copy() journalEntry {
|
func (ch nonceChange) copy() journalEntry {
|
||||||
|
|
@ -385,8 +542,8 @@ func (ch codeChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) dirtied() (common.Address, bool) {
|
func (ch codeChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindCode, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) copy() journalEntry {
|
func (ch codeChange) copy() journalEntry {
|
||||||
|
|
@ -400,8 +557,8 @@ func (ch storageChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) dirtied() (common.Address, bool) {
|
func (ch storageChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindStorage, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) copy() journalEntry {
|
func (ch storageChange) copy() journalEntry {
|
||||||
|
|
@ -417,8 +574,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
|
||||||
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) dirtied() (common.Address, bool) {
|
func (ch transientStorageChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) copy() journalEntry {
|
func (ch transientStorageChange) copy() journalEntry {
|
||||||
|
|
@ -433,8 +590,8 @@ func (ch refundChange) revert(s *StateDB) {
|
||||||
s.refund = ch.prev
|
s.refund = ch.prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch refundChange) dirtied() (common.Address, bool) {
|
func (ch refundChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch refundChange) copy() journalEntry {
|
func (ch refundChange) copy() journalEntry {
|
||||||
|
|
@ -453,8 +610,8 @@ func (ch addLogChange) revert(s *StateDB) {
|
||||||
s.logSize--
|
s.logSize--
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch addLogChange) dirtied() (common.Address, bool) {
|
func (ch addLogChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch addLogChange) copy() journalEntry {
|
func (ch addLogChange) copy() journalEntry {
|
||||||
|
|
@ -476,8 +633,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteAddress(ch.address)
|
s.accessList.DeleteAddress(ch.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
|
func (ch accessListAddAccountChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) copy() journalEntry {
|
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||||
|
|
@ -490,8 +647,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteSlot(ch.address, ch.slot)
|
s.accessList.DeleteSlot(ch.address, ch.slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
|
func (ch accessListAddSlotChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) copy() journalEntry {
|
func (ch accessListAddSlotChange) copy() journalEntry {
|
||||||
|
|
|
||||||
|
|
@ -760,7 +760,7 @@ type removedAccountWithBalance struct {
|
||||||
// before the Finalise.
|
// before the Finalise.
|
||||||
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
||||||
var list []removedAccountWithBalance
|
var list []removedAccountWithBalance
|
||||||
for addr := range s.journal.dirties {
|
for addr := range s.journal.mutations {
|
||||||
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
||||||
list = append(list, removedAccountWithBalance{
|
list = append(list, removedAccountWithBalance{
|
||||||
address: obj.address,
|
address: obj.address,
|
||||||
|
|
@ -785,16 +785,16 @@ func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
||||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
|
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
|
||||||
for addr := range s.journal.dirties {
|
for addr := range s.journal.mutations {
|
||||||
obj, exist := s.stateObjects[addr]
|
obj, exist := s.stateObjects[addr]
|
||||||
if !exist {
|
if !exist {
|
||||||
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
|
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
|
||||||
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
|
// mainnet consensus exception around empty-account touch/revert
|
||||||
// touch-event will still be recorded in the journal. Since ripeMD is a special snowflake,
|
// handling. That marker survives journal revert, so the account may
|
||||||
// it will persist in the journal even though the journal is reverted. In this special circumstance,
|
// remain in s.journal.mutations even though its state object was rolled
|
||||||
// it may exist in `s.journal.dirties` but not in `s.stateObjects`.
|
// back and no longer exists. In that case there is nothing to
|
||||||
// Thus, we can safely ignore it here
|
// finalise or delete, so ignore it here.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
// that state change hooks will be invoked in deterministic
|
// that state change hooks will be invoked in deterministic
|
||||||
// order when the accounts are deleted below
|
// order when the accounts are deleted below
|
||||||
var selfDestructedAddrs []common.Address
|
var selfDestructedAddrs []common.Address
|
||||||
for addr := range s.inner.journal.dirties {
|
for addr := range s.inner.journal.mutations {
|
||||||
obj := s.inner.stateObjects[addr]
|
obj := s.inner.stateObjects[addr]
|
||||||
if obj == nil || !obj.selfDestructed {
|
if obj == nil || !obj.selfDestructed {
|
||||||
// Not self-destructed, keep searching.
|
// Not self-destructed, keep searching.
|
||||||
|
|
|
||||||
|
|
@ -662,22 +662,8 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
||||||
state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0))
|
state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0))
|
||||||
}
|
}
|
||||||
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
|
if !maps.Equal(state.journal.mutationSet(), checkstate.journal.mutationSet()) {
|
||||||
getKeys := func(dirty map[common.Address]int) string {
|
return fmt.Errorf("journal mutation set mismatch.\nhave:\n%v\nwant:\n%v\n", state.journal.mutationSet(), checkstate.journal.mutationSet())
|
||||||
var keys []common.Address
|
|
||||||
out := new(strings.Builder)
|
|
||||||
for key := range dirty {
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
slices.SortFunc(keys, common.Address.Cmp)
|
|
||||||
for i, key := range keys {
|
|
||||||
fmt.Fprintf(out, " %d. %v\n", i, key)
|
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
have := getKeys(state.journal.dirties)
|
|
||||||
want := getKeys(checkstate.journal.dirties)
|
|
||||||
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -691,12 +677,47 @@ func TestTouchDelete(t *testing.T) {
|
||||||
snapshot := s.state.Snapshot()
|
snapshot := s.state.Snapshot()
|
||||||
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
||||||
|
|
||||||
if len(s.state.journal.dirties) != 1 {
|
if len(s.state.journal.mutationSet()) != 1 {
|
||||||
t.Fatal("expected one dirty state object")
|
t.Fatal("expected one mutated state object")
|
||||||
}
|
}
|
||||||
s.state.RevertToSnapshot(snapshot)
|
s.state.RevertToSnapshot(snapshot)
|
||||||
if len(s.state.journal.dirties) != 0 {
|
if len(s.state.journal.mutationSet()) != 0 {
|
||||||
t.Fatal("expected no dirty state object")
|
t.Fatal("expected no journal mutations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJournalMutationTracking(t *testing.T) {
|
||||||
|
state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||||
|
addr := common.HexToAddress("0x01")
|
||||||
|
key := common.HexToHash("0x02")
|
||||||
|
|
||||||
|
if got := state.journal.mutation(addr); got != 0 {
|
||||||
|
t.Fatalf("unexpected initial mutation set: %v", got)
|
||||||
|
}
|
||||||
|
snapshot := state.Snapshot()
|
||||||
|
|
||||||
|
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
|
||||||
|
state.SetNonce(addr, 2, tracing.NonceChangeUnspecified)
|
||||||
|
state.SetCode(addr, []byte{0x1}, tracing.CodeChangeUnspecified)
|
||||||
|
state.SetState(addr, key, common.Hash{0x3})
|
||||||
|
|
||||||
|
want := journalMutationKindCreate.mask() |
|
||||||
|
journalMutationKindBalance.mask() |
|
||||||
|
journalMutationKindNonce.mask() |
|
||||||
|
journalMutationKindCode.mask() |
|
||||||
|
journalMutationKindStorage.mask()
|
||||||
|
if got := state.journal.mutation(addr); got != want {
|
||||||
|
t.Fatalf("mutation set mismatch: have %08b, want %08b", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy := state.Copy()
|
||||||
|
if got := copy.journal.mutation(addr); got != want {
|
||||||
|
t.Fatalf("copy mutation set mismatch: have %08b, want %08b", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.RevertToSnapshot(snapshot)
|
||||||
|
if got := state.journal.mutation(addr); got != 0 {
|
||||||
|
t.Fatalf("unexpected mutation set after revert: %08b", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue