Skip to content

Commit 93a26e8

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

File tree

2 files changed

+301
-1
lines changed

2 files changed

+301
-1
lines changed

fvm/evm/emulator/state/delta.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ var _ types.HotView = &DeltaView{}
6767

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

@@ -83,7 +94,7 @@ func NewDeltaView(parent types.ReadOnlyView) *DeltaView {
8394
slots: make(map[types.SlotAddress]gethCommon.Hash),
8495

8596
// for refund we just copy the data
86-
refund: parent.GetRefund(),
97+
refund: refund,
8798
}
8899
}
89100

@@ -584,3 +595,52 @@ func (d *DeltaView) DirtySlots() map[types.SlotAddress]struct{} {
584595
}
585596
return dirtySlots
586597
}
598+
599+
// HasData returns whether any state modifications are recorded in this view
600+
func (d *DeltaView) HasData() bool {
601+
if len(d.dirtyAddresses) > 0 {
602+
return true
603+
}
604+
605+
if len(d.created) > 0 {
606+
return true
607+
}
608+
609+
if len(d.newContract) > 0 {
610+
return true
611+
}
612+
613+
if len(d.toBeDestructed) > 0 {
614+
return true
615+
}
616+
617+
if len(d.recreated) > 0 {
618+
return true
619+
}
620+
621+
if len(d.balances) > 0 {
622+
return true
623+
}
624+
625+
if len(d.nonces) > 0 {
626+
return true
627+
}
628+
629+
if len(d.codes) > 0 {
630+
return true
631+
}
632+
633+
if len(d.codeHashes) > 0 {
634+
return true
635+
}
636+
637+
if len(d.slots) > 0 {
638+
return true
639+
}
640+
641+
if len(d.transient) > 0 {
642+
return true
643+
}
644+
645+
return false
646+
}

fvm/evm/emulator/state/delta_test.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package state_test
22

33
import (
44
"fmt"
5+
"math/big"
56
"testing"
67

78
gethCommon "github.com/ethereum/go-ethereum/common"
@@ -743,6 +744,245 @@ func TestDeltaView(t *testing.T) {
743744
emptyValue := gethCommon.Hash{}
744745
require.Equal(t, emptyValue, vret)
745746
})
747+
748+
t.Run("test HasData for all combinations", func(t *testing.T) {
749+
view := state.NewDeltaView(
750+
&MockedReadOnlyView{
751+
GetRefundFunc: emptyRefund,
752+
},
753+
)
754+
require.False(t, view.HasData())
755+
756+
view = state.NewDeltaView(
757+
&MockedReadOnlyView{
758+
GetRefundFunc: emptyRefund,
759+
IsCreatedFunc: func(a gethCommon.Address) bool {
760+
return false
761+
},
762+
ExistFunc: func(a gethCommon.Address) (bool, error) {
763+
return false, nil
764+
},
765+
HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *uint256.Int) {
766+
return false, new(uint256.Int)
767+
},
768+
},
769+
)
770+
// This will set the `dirtyAddresses` & `created` maps
771+
err := view.CreateAccount(gethCommon.Address{0x12})
772+
require.NoError(t, err)
773+
require.True(t, len(view.DirtyAddresses()) > 0)
774+
require.True(t, view.IsCreated(gethCommon.Address{0x12}))
775+
require.True(t, view.HasData())
776+
777+
view = state.NewDeltaView(
778+
&MockedReadOnlyView{
779+
GetRefundFunc: emptyRefund,
780+
},
781+
)
782+
// This will set the `newContract` map
783+
view.CreateContract(gethCommon.Address{0x10})
784+
require.True(t, view.IsNewContract(gethCommon.Address{0x10}))
785+
require.True(t, view.HasData())
786+
787+
view = state.NewDeltaView(
788+
&MockedReadOnlyView{
789+
GetRefundFunc: emptyRefund,
790+
ExistFunc: func(a gethCommon.Address) (bool, error) {
791+
return true, nil
792+
},
793+
HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *uint256.Int) {
794+
return false, new(uint256.Int)
795+
},
796+
GetBalanceFunc: func(a gethCommon.Address) (*uint256.Int, error) {
797+
return uint256.MustFromBig(big.NewInt(100)), nil
798+
},
799+
},
800+
)
801+
// This will set the `toBeDestructed` map
802+
err = view.SelfDestruct(gethCommon.Address{0x12})
803+
require.NoError(t, err)
804+
hasSelfDestructed, _ := view.HasSelfDestructed(gethCommon.Address{0x12})
805+
require.True(t, hasSelfDestructed)
806+
require.True(t, view.HasData())
807+
808+
view = state.NewDeltaView(
809+
&MockedReadOnlyView{
810+
GetRefundFunc: emptyRefund,
811+
IsCreatedFunc: func(a gethCommon.Address) bool {
812+
return false
813+
},
814+
ExistFunc: func(a gethCommon.Address) (bool, error) {
815+
return true, nil
816+
},
817+
HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *uint256.Int) {
818+
return false, new(uint256.Int)
819+
},
820+
GetBalanceFunc: func(a gethCommon.Address) (*uint256.Int, error) {
821+
return uint256.MustFromBig(big.NewInt(100)), nil
822+
},
823+
},
824+
)
825+
// This will set the `recreated` map
826+
err = view.CreateAccount(gethCommon.Address{0x12})
827+
require.NoError(t, err)
828+
require.True(t, view.HasData())
829+
830+
view = state.NewDeltaView(
831+
&MockedReadOnlyView{
832+
GetRefundFunc: emptyRefund,
833+
IsCreatedFunc: func(a gethCommon.Address) bool {
834+
return false
835+
},
836+
ExistFunc: func(a gethCommon.Address) (bool, error) {
837+
return true, nil
838+
},
839+
HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *uint256.Int) {
840+
return true, uint256.MustFromBig(big.NewInt(100))
841+
},
842+
GetBalanceFunc: func(a gethCommon.Address) (*uint256.Int, error) {
843+
return uint256.MustFromBig(big.NewInt(0)), nil
844+
},
845+
},
846+
)
847+
// This will set the `balances` map
848+
err = view.AddBalance(gethCommon.Address{0x12}, uint256.MustFromBig(big.NewInt(100)))
849+
require.NoError(t, err)
850+
require.True(t, view.HasData())
851+
852+
view = state.NewDeltaView(
853+
&MockedReadOnlyView{
854+
GetRefundFunc: emptyRefund,
855+
},
856+
)
857+
// This will set the `nonces` map
858+
err = view.SetNonce(gethCommon.Address{0x10}, 3)
859+
require.NoError(t, err)
860+
nonce, err := view.GetNonce(gethCommon.Address{0x10})
861+
require.NoError(t, err)
862+
require.Equal(t, uint64(3), nonce)
863+
require.True(t, view.HasData())
864+
865+
view = state.NewDeltaView(
866+
&MockedReadOnlyView{
867+
GetRefundFunc: emptyRefund,
868+
},
869+
)
870+
// This will set the `codes` & `codeHashes` map
871+
err = view.SetCode(gethCommon.Address{0x10}, []byte{0x1, 0x10, 0x55, 0x16, 0x20})
872+
require.NoError(t, err)
873+
code, err := view.GetCode(gethCommon.Address{0x10})
874+
require.NoError(t, err)
875+
require.Equal(t, []byte{0x1, 0x10, 0x55, 0x16, 0x20}, code)
876+
require.True(t, view.HasData())
877+
878+
view = state.NewDeltaView(
879+
&MockedReadOnlyView{
880+
GetRefundFunc: emptyRefund,
881+
GetStateFunc: func(sa types.SlotAddress) (gethCommon.Hash, error) {
882+
return gethCommon.Hash{}, nil
883+
},
884+
},
885+
)
886+
sk := types.SlotAddress{
887+
Address: gethCommon.Address{0x10},
888+
Key: gethCommon.Hash{0x2},
889+
}
890+
// This will set the `slots` map
891+
previousVal, err := view.SetState(sk, gethCommon.Hash{0x55})
892+
require.NoError(t, err)
893+
require.Equal(t, gethCommon.Hash{}, previousVal)
894+
stateVal, err := view.GetState(sk)
895+
require.NoError(t, err)
896+
require.Equal(t, gethCommon.Hash{0x55}, stateVal)
897+
require.True(t, view.HasData())
898+
899+
view = state.NewDeltaView(
900+
&MockedReadOnlyView{
901+
GetRefundFunc: emptyRefund,
902+
},
903+
)
904+
sk = types.SlotAddress{
905+
Address: gethCommon.Address{0x15},
906+
Key: gethCommon.Hash{0x20},
907+
}
908+
// This will set the `transient`
909+
view.SetTransientState(sk, gethCommon.Hash{0xfa})
910+
require.Equal(t, gethCommon.Hash{0xfa}, view.GetTransientState(sk))
911+
require.True(t, view.HasData())
912+
})
913+
914+
t.Run("test get refund is carried over", func(t *testing.T) {
915+
ledger := testutils.GetSimpleValueStore()
916+
rootView, err := state.NewBaseView(ledger, rootAddr)
917+
require.NoError(t, err)
918+
require.Equal(t, uint64(0), rootView.GetRefund())
919+
920+
view := state.NewDeltaView(rootView)
921+
refund := uint64(100)
922+
err = view.AddRefund(refund)
923+
924+
require.NoError(t, err)
925+
require.Equal(t, refund, view.GetRefund())
926+
require.False(t, view.HasData())
927+
928+
childView1 := state.NewDeltaView(view)
929+
childView2 := state.NewDeltaView(childView1)
930+
childView3 := state.NewDeltaView(childView2)
931+
childView4 := state.NewDeltaView(childView3)
932+
childView5 := state.NewDeltaView(childView4)
933+
childView6 := state.NewDeltaView(childView5)
934+
childView7 := state.NewDeltaView(childView6)
935+
childView8 := state.NewDeltaView(childView7)
936+
childView9 := state.NewDeltaView(childView8)
937+
childView10 := state.NewDeltaView(childView9)
938+
childView11 := state.NewDeltaView(childView10)
939+
940+
require.Equal(t, refund, childView11.GetRefund())
941+
})
942+
943+
t.Run("test parent traversal", func(t *testing.T) {
944+
ledger := testutils.GetSimpleValueStore()
945+
rootView, err := state.NewBaseView(ledger, rootAddr)
946+
require.NoError(t, err)
947+
948+
view := state.NewDeltaView(rootView)
949+
sk := types.SlotAddress{
950+
Address: gethCommon.Address{0x10},
951+
Key: gethCommon.Hash{0x2},
952+
}
953+
previousVal, err := view.SetState(sk, gethCommon.Hash{0x55})
954+
require.NoError(t, err)
955+
require.Equal(t, gethCommon.Hash{}, previousVal)
956+
require.True(t, view.HasData())
957+
958+
childView1 := state.NewDeltaView(view)
959+
childView2 := state.NewDeltaView(childView1)
960+
childView3 := state.NewDeltaView(childView2)
961+
childView4 := state.NewDeltaView(childView3)
962+
childView5 := state.NewDeltaView(childView4)
963+
childView6 := state.NewDeltaView(childView5)
964+
965+
stateVal, err := childView6.GetState(sk)
966+
require.NoError(t, err)
967+
require.False(t, childView6.HasData())
968+
require.Equal(t, gethCommon.Hash{0x55}, stateVal)
969+
970+
previousVal, err = childView6.SetState(sk, gethCommon.Hash{0x32})
971+
require.NoError(t, err)
972+
require.Equal(t, stateVal, previousVal)
973+
require.True(t, childView6.HasData())
974+
975+
childView7 := state.NewDeltaView(childView6)
976+
childView8 := state.NewDeltaView(childView7)
977+
childView9 := state.NewDeltaView(childView8)
978+
childView10 := state.NewDeltaView(childView9)
979+
childView11 := state.NewDeltaView(childView10)
980+
981+
stateVal, err = childView11.GetState(sk)
982+
require.NoError(t, err)
983+
require.False(t, childView11.HasData())
984+
require.Equal(t, gethCommon.Hash{0x32}, stateVal)
985+
})
746986
}
747987

748988
type MockedReadOnlyView struct {

0 commit comments

Comments
 (0)