Skip to content

Commit e639cb4

Browse files
authored
evm: use TransientStore for AccessList (#75)
* evm: use transient store for access list * evm: remove address and slot access list mappings * update tests * update * changelog * update types
1 parent c78d720 commit e639cb4

10 files changed

Lines changed: 146 additions & 457 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
3737

3838
## Unreleased
3939

40+
### State Machine Breaking
41+
42+
* (evm) [tharsis#72](https://github.com/tharsis/ethermint/issues/72) Update `AccessList` to use `TransientStore` instead of map.
43+
4044
### API Breaking
4145

4246
* (eth) [\#845](https://github.com/cosmos/ethermint/pull/845) The `eth` namespace must be included in the list of API's as default to run the rpc server without error.

x/evm/keeper/keeper.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ type Keeper struct {
4545
// Ethermint concrete implementation on the EVM StateDB interface
4646
CommitStateDB *types.CommitStateDB
4747

48-
// Per-transaction access list
49-
// See EIP-2930 for more info: https://eips.ethereum.org/EIPS/eip-2930
50-
// TODO: (@fedekunze) for how long should we persist the entries in the access list?
51-
// same block (i.e Transient Store)? 2 or more (KVStore with module Parameter which resets the state after that window)?
52-
accessList *types.AccessListMappings
53-
5448
// hash header for the current height. Reset during abci.RequestBeginBlock
5549
headerHash common.Hash
5650
}
@@ -73,8 +67,7 @@ func NewKeeper(
7367
bankKeeper: bankKeeper,
7468
storeKey: storeKey,
7569
transientKey: transientKey,
76-
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper),
77-
accessList: types.NewAccessListMappings(),
70+
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, transientKey, paramSpace, ak, bankKeeper),
7871
}
7972
}
8073

x/evm/keeper/statedb.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -493,37 +493,48 @@ func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address,
493493
}
494494
}
495495

496-
// AddressInAccessList returns true if the address is registered on the access list map.
496+
// AddressInAccessList returns true if the address is registered on the transient store.
497497
func (k *Keeper) AddressInAccessList(addr common.Address) bool {
498-
return k.accessList.ContainsAddress(addr)
498+
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
499+
return ts.Has(addr.Bytes())
499500
}
500501

502+
// SlotInAccessList checks if the address and the slots are registered in the transient store
501503
func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
502-
return k.accessList.Contains(addr, slot)
504+
addressOk = k.AddressInAccessList(addr)
505+
slotOk = k.addressSlotInAccessList(addr, slot)
506+
return addressOk, slotOk
503507
}
504508

505-
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
506-
// even if the feature/fork is not active yet
509+
// addressSlotInAccessList returns true if the address's slot is registered on the transient store.
510+
func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool {
511+
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
512+
key := append(addr.Bytes(), slot.Bytes()...)
513+
return ts.Has(key)
514+
}
515+
516+
// AddAddressToAccessList adds the given address to the access list. If the address is already
517+
// in the access list, this function performs a no-op.
507518
func (k *Keeper) AddAddressToAccessList(addr common.Address) {
508-
// NOTE: only update the access list during DeliverTx
509-
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() {
519+
if k.AddressInAccessList(addr) {
510520
return
511521
}
512522

513-
// NOTE: ignore change return bool because we don't have to keep a journal for state changes
514-
_ = k.accessList.AddAddress(addr)
523+
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
524+
ts.Set(addr.Bytes(), []byte{0x1})
515525
}
516526

517-
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
518-
// even if the feature/fork is not active yet
527+
// AddSlotToAccessList adds the given (address, slot) to the access list. If the address and slot are
528+
// already in the access list, this function performs a no-op.
519529
func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
520-
// NOTE: only update the access list during DeliverTx
521-
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() {
530+
k.AddAddressToAccessList(addr)
531+
if k.addressSlotInAccessList(addr, slot) {
522532
return
523533
}
524534

525-
// NOTE: ignore change return booleans because we don't have to keep a journal for state changes
526-
_, _ = k.accessList.AddSlot(addr, slot)
535+
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
536+
key := append(addr.Bytes(), slot.Bytes()...)
537+
ts.Set(key, []byte{0x1})
527538
}
528539

529540
// ----------------------------------------------------------------------------

x/evm/keeper/statedb_test.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ func (suite *KeeperTestSuite) TestAddLog() {
506506
}
507507
}
508508

509-
func (suite *KeeperTestSuite) TestAccessList() {
509+
func (suite *KeeperTestSuite) TestPrepareAccessList() {
510510
dest := tests.GenerateAddress()
511511
precompiles := []common.Address{tests.GenerateAddress(), tests.GenerateAddress()}
512512
accesses := ethtypes.AccessList{
@@ -526,12 +526,52 @@ func (suite *KeeperTestSuite) TestAccessList() {
526526
for _, access := range accesses {
527527
for _, key := range access.StorageKeys {
528528
addrOK, slotOK := suite.app.EvmKeeper.SlotInAccessList(access.Address, key)
529-
suite.Require().True(addrOK)
530-
suite.Require().True(slotOK)
529+
suite.Require().True(addrOK, access.Address.Hex())
530+
suite.Require().True(slotOK, key.Hex())
531531
}
532532
}
533533
}
534534

535+
func (suite *KeeperTestSuite) TestAddAddressToAccessList() {
536+
testCases := []struct {
537+
name string
538+
addr common.Address
539+
}{
540+
{"new address", suite.address},
541+
{"existing address", suite.address},
542+
}
543+
544+
for _, tc := range testCases {
545+
suite.Run(tc.name, func() {
546+
suite.app.EvmKeeper.AddAddressToAccessList(tc.addr)
547+
addrOk := suite.app.EvmKeeper.AddressInAccessList(tc.addr)
548+
suite.Require().True(addrOk, tc.addr.Hex())
549+
})
550+
}
551+
}
552+
553+
func (suite *KeeperTestSuite) AddSlotToAccessList() {
554+
testCases := []struct {
555+
name string
556+
addr common.Address
557+
slot common.Hash
558+
}{
559+
{"new address and slot (1)", tests.GenerateAddress(), common.BytesToHash([]byte("hash"))},
560+
{"new address and slot (2)", suite.address, common.Hash{}},
561+
{"existing address and slot", suite.address, common.Hash{}},
562+
{"existing address, new slot", suite.address, common.BytesToHash([]byte("hash"))},
563+
}
564+
565+
for _, tc := range testCases {
566+
suite.Run(tc.name, func() {
567+
suite.app.EvmKeeper.AddSlotToAccessList(tc.addr, tc.slot)
568+
addrOk, slotOk := suite.app.EvmKeeper.SlotInAccessList(tc.addr, tc.slot)
569+
suite.Require().True(addrOk, tc.addr.Hex())
570+
suite.Require().True(slotOk, tc.slot.Hex())
571+
})
572+
}
573+
}
574+
535575
func (suite *KeeperTestSuite) TestForEachStorage() {
536576
var storage types.Storage
537577

x/evm/types/access_list.go

Lines changed: 0 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,10 @@
11
package types
22

33
import (
4-
"github.com/ethereum/go-ethereum/common"
54
ethcmn "github.com/ethereum/go-ethereum/common"
65
ethtypes "github.com/ethereum/go-ethereum/core/types"
76
)
87

9-
// AccessListMappings is copied from go-ethereum
10-
// https://github.com/ethereum/go-ethereum/blob/cf856ea1ad96ac39ea477087822479b63417036a/core/state/access_list.go#L23
11-
type AccessListMappings struct {
12-
addresses map[common.Address]int
13-
slots []map[common.Hash]struct{}
14-
}
15-
16-
// ContainsAddress returns true if the address is in the access list.
17-
func (al *AccessListMappings) ContainsAddress(address common.Address) bool {
18-
_, ok := al.addresses[address]
19-
return ok
20-
}
21-
22-
// Contains checks if a slot within an account is present in the access list, returning
23-
// separate flags for the presence of the account and the slot respectively.
24-
func (al *AccessListMappings) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
25-
idx, ok := al.addresses[address]
26-
if !ok {
27-
// no such address (and hence zero slots)
28-
return false, false
29-
}
30-
if idx == -1 {
31-
// address yes, but no slots
32-
return true, false
33-
}
34-
35-
if idx >= len(al.slots) {
36-
// return in case of out-of-range
37-
return true, false
38-
}
39-
40-
_, slotPresent = al.slots[idx][slot]
41-
return true, slotPresent
42-
}
43-
44-
// newAccessList creates a new AccessListMappings.
45-
func NewAccessListMappings() *AccessListMappings {
46-
return &AccessListMappings{
47-
addresses: make(map[common.Address]int),
48-
}
49-
}
50-
51-
// Copy creates an independent copy of an AccessListMappings.
52-
func (al *AccessListMappings) Copy() *AccessListMappings {
53-
cp := NewAccessListMappings()
54-
for k, v := range al.addresses {
55-
cp.addresses[k] = v
56-
}
57-
cp.slots = make([]map[common.Hash]struct{}, len(al.slots))
58-
for i, slotMap := range al.slots {
59-
newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
60-
for k := range slotMap {
61-
newSlotmap[k] = struct{}{}
62-
}
63-
cp.slots[i] = newSlotmap
64-
}
65-
return cp
66-
}
67-
68-
// AddAddress adds an address to the access list, and returns 'true' if the operation
69-
// caused a change (addr was not previously in the list).
70-
func (al *AccessListMappings) AddAddress(address common.Address) bool {
71-
if _, present := al.addresses[address]; present {
72-
return false
73-
}
74-
al.addresses[address] = -1
75-
return true
76-
}
77-
78-
// AddSlot adds the specified (addr, slot) combo to the access list.
79-
// Return values are:
80-
// - address added
81-
// - slot added
82-
// For any 'true' value returned, a corresponding journal entry must be made.
83-
func (al *AccessListMappings) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
84-
idx, addrPresent := al.addresses[address]
85-
if !addrPresent || idx == -1 {
86-
// Address not present, or addr present but no slots there
87-
al.addresses[address] = len(al.slots)
88-
slotmap := map[common.Hash]struct{}{slot: {}}
89-
al.slots = append(al.slots, slotmap)
90-
return !addrPresent, true
91-
}
92-
93-
if idx >= len(al.slots) {
94-
// return in case of out-of-range
95-
return false, false
96-
}
97-
98-
// There is already an (address,slot) mapping
99-
slotmap := al.slots[idx]
100-
if _, ok := slotmap[slot]; !ok {
101-
slotmap[slot] = struct{}{}
102-
// journal add slot change
103-
return false, true
104-
}
105-
// No changes required
106-
return false, false
107-
}
108-
109-
// DeleteSlot removes an (address, slot)-tuple from the access list.
110-
// This operation needs to be performed in the same order as the addition happened.
111-
// This method is meant to be used by the journal, which maintains ordering of
112-
// operations.
113-
func (al *AccessListMappings) DeleteSlot(address common.Address, slot common.Hash) {
114-
idx, addrOk := al.addresses[address]
115-
// There are two ways this can fail
116-
if !addrOk {
117-
panic("reverting slot change, address not present in list")
118-
}
119-
slotmap := al.slots[idx]
120-
delete(slotmap, slot)
121-
// If that was the last (first) slot, remove it
122-
// Since additions and rollbacks are always performed in order,
123-
// we can delete the item without worrying about screwing up later indices
124-
if len(slotmap) == 0 {
125-
al.slots = al.slots[:idx]
126-
al.addresses[address] = -1
127-
}
128-
}
129-
130-
// DeleteAddress removes an address from the access list. This operation
131-
// needs to be performed in the same order as the addition happened.
132-
// This method is meant to be used by the journal, which maintains ordering of
133-
// operations.
134-
func (al *AccessListMappings) DeleteAddress(address common.Address) {
135-
delete(al.addresses, address)
136-
}
137-
1388
// AccessList is an EIP-2930 access list that represents the slice of
1399
// the protobuf AccessTuples.
14010
type AccessList []AccessTuple

0 commit comments

Comments
 (0)