Skip to content

Commit bd16666

Browse files
committed
Optimize snapshot traversal for EVM transactions
1 parent fe062d1 commit bd16666

File tree

2 files changed

+75
-55
lines changed

2 files changed

+75
-55
lines changed

fvm/evm/emulator/state/delta.go

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ var _ types.HotView = &DeltaView{}
6767

6868
// NewDeltaView constructs a new delta view
6969
func NewDeltaView(parent types.ReadOnlyView) *DeltaView {
70+
// The root node will be of type `BaseView`, so we should
71+
// check if we are dealing with `DeltaView`, for parent
72+
// skipping
73+
dt, ok := parent.(*DeltaView)
74+
if ok && !dt.hasData() {
75+
// If my parent doesn't have any writes, I can just
76+
// delegate "upstream" queries to its parent
77+
parent = dt.parent
78+
}
79+
7080
return &DeltaView{
7181
parent: parent,
7282

@@ -478,21 +488,14 @@ func (d *DeltaView) SubRefund(amount uint64) error {
478488

479489
// AddressInAccessList checks if the address is in the access list of
480490
// the current view.
481-
// NOTE: Due to resource constraints (such as CPU & memory), and the
482-
// high-frequency usage of this function from EVM, we do not look up
483-
// the parents until the root view or until we find a view that has
484-
// the address in its local access list.
485-
// As an optimization, the `StateDB.AddressInAccessList` is responsible
486-
// for optimally traversing the views, to check if the address is in
487-
// the access list.
488491
func (d *DeltaView) AddressInAccessList(addr gethCommon.Address) bool {
489492
if d.accessListAddresses != nil {
490493
_, addressFound := d.accessListAddresses[addr]
491494
if addressFound {
492495
return true
493496
}
494497
}
495-
return false
498+
return d.parent.AddressInAccessList(addr)
496499
}
497500

498501
// AddAddressToAccessList adds an address to the access list
@@ -508,13 +511,6 @@ func (d *DeltaView) AddAddressToAccessList(addr gethCommon.Address) bool {
508511

509512
// SlotInAccessList checks if the slot is in the access list of the
510513
// current view.
511-
// NOTE: Due to resource constraints (such as CPU & memory), and the
512-
// high-frequency usage of this function from EVM, we do not look up
513-
// the parents until the root view or until we find a view that has
514-
// the slot in its local access list.
515-
// As an optimization, the `StateDB.SlotInAccessList` is responsible
516-
// for optimally traversing the views, to check if the slot is in
517-
// the access list.
518514
func (d *DeltaView) SlotInAccessList(sk types.SlotAddress) (addressOk bool, slotOk bool) {
519515
addressFound := d.AddressInAccessList(sk.Address)
520516
if d.accessListSlots != nil {
@@ -523,7 +519,7 @@ func (d *DeltaView) SlotInAccessList(sk types.SlotAddress) (addressOk bool, slot
523519
return addressFound, true
524520
}
525521
}
526-
return addressFound, false
522+
return d.parent.SlotInAccessList(sk)
527523
}
528524

529525
// AddSlotToAccessList adds a slot to the access list
@@ -584,3 +580,64 @@ func (d *DeltaView) DirtySlots() map[types.SlotAddress]struct{} {
584580
}
585581
return dirtySlots
586582
}
583+
584+
// hasData returns whether any state modifications are recorded in this view
585+
func (d *DeltaView) hasData() bool {
586+
if len(d.dirtyAddresses) > 0 {
587+
return true
588+
}
589+
590+
if len(d.created) > 0 {
591+
return true
592+
}
593+
594+
if len(d.newContract) > 0 {
595+
return true
596+
}
597+
598+
if len(d.toBeDestructed) > 0 {
599+
return true
600+
}
601+
602+
if len(d.recreated) > 0 {
603+
return true
604+
}
605+
606+
if len(d.balances) > 0 {
607+
return true
608+
}
609+
610+
if len(d.nonces) > 0 {
611+
return true
612+
}
613+
614+
if len(d.codes) > 0 {
615+
return true
616+
}
617+
618+
if len(d.codeHashes) > 0 {
619+
return true
620+
}
621+
622+
if len(d.slots) > 0 {
623+
return true
624+
}
625+
626+
if len(d.transient) > 0 {
627+
return true
628+
}
629+
630+
if len(d.accessListAddresses) > 0 {
631+
return true
632+
}
633+
634+
if len(d.accessListSlots) > 0 {
635+
return true
636+
}
637+
638+
if d.refund > 0 {
639+
return true
640+
}
641+
642+
return false
643+
}

fvm/evm/emulator/state/stateDB.go

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -324,50 +324,13 @@ func (db *StateDB) SetTransientState(addr gethCommon.Address, key, value gethCom
324324

325325
// AddressInAccessList checks if an address is in the access list
326326
func (db *StateDB) AddressInAccessList(addr gethCommon.Address) bool {
327-
// For each static call / call / delegate call, the EVM will create
328-
// a snapshot, so that it can revert to it in case of execution errors,
329-
// such as out of gas etc, using `Snapshot` & `RevertToSnapshot`.
330-
// This can create a long list of views, in the order of 4K for certain
331-
// large transactions. To avoid performance issues with DeltaView checking parents,
332-
// which causes deep stacks and function call overhead, we use a plain for-loop instead.
333-
// We iterate through the views in ascending order (from lowest to highest) as an optimization.
334-
// Since addresses are typically added to the AccessList early during transaction execution,
335-
// this allows us to return early when the needed addresses are found in the initial views.
336-
end := len(db.views)
337-
for i := range end {
338-
view := db.views[i]
339-
if view.AddressInAccessList(addr) {
340-
return true
341-
}
342-
}
343-
344-
return false
327+
return db.latestView().AddressInAccessList(addr)
345328
}
346329

347330
// SlotInAccessList checks if the given (address,slot) is in the access list
348331
func (db *StateDB) SlotInAccessList(addr gethCommon.Address, key gethCommon.Hash) (addressOk bool, slotOk bool) {
349332
slotKey := types.SlotAddress{Address: addr, Key: key}
350-
351-
// For each static call / call / delegate call, the EVM will create
352-
// a snapshot, so that it can revert to it in case of execution errors,
353-
// such as out of gas etc, using `Snapshot` & `RevertToSnapshot`.
354-
// This can create a long list of views, in the order of 4K for certain
355-
// large transactions. To avoid performance issues with DeltaView checking parents,
356-
// which causes deep stacks and function call overhead, we use a plain for-loop instead.
357-
// We iterate through the views in ascending order (from lowest to highest) as an optimization.
358-
// Since slots are typically added to the AccessList early during transaction execution,
359-
// this allows us to return early when the needed slots are found in the initial views.
360-
addressFound := false
361-
end := len(db.views)
362-
for i := range end {
363-
view := db.views[i]
364-
addressFound, slotFound := view.SlotInAccessList(slotKey)
365-
if slotFound {
366-
return addressFound, true
367-
}
368-
}
369-
370-
return addressFound, false
333+
return db.latestView().SlotInAccessList(slotKey)
371334
}
372335

373336
// AddAddressToAccessList adds the given address to the access list.

0 commit comments

Comments
 (0)