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
This commit is contained in:
Federico Kunze 2021-06-07 06:05:49 -04:00 committed by GitHub
parent c78d720532
commit e639cb4a82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 146 additions and 457 deletions

View File

@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
## Unreleased ## Unreleased
### State Machine Breaking
* (evm) [tharsis#72](https://github.com/tharsis/ethermint/issues/72) Update `AccessList` to use `TransientStore` instead of map.
### API Breaking ### API Breaking
* (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. * (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.

View File

@ -45,12 +45,6 @@ type Keeper struct {
// Ethermint concrete implementation on the EVM StateDB interface // Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB CommitStateDB *types.CommitStateDB
// Per-transaction access list
// See EIP-2930 for more info: https://eips.ethereum.org/EIPS/eip-2930
// TODO: (@fedekunze) for how long should we persist the entries in the access list?
// same block (i.e Transient Store)? 2 or more (KVStore with module Parameter which resets the state after that window)?
accessList *types.AccessListMappings
// hash header for the current height. Reset during abci.RequestBeginBlock // hash header for the current height. Reset during abci.RequestBeginBlock
headerHash common.Hash headerHash common.Hash
} }
@ -73,8 +67,7 @@ func NewKeeper(
bankKeeper: bankKeeper, bankKeeper: bankKeeper,
storeKey: storeKey, storeKey: storeKey,
transientKey: transientKey, transientKey: transientKey,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper), CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, transientKey, paramSpace, ak, bankKeeper),
accessList: types.NewAccessListMappings(),
} }
} }

View File

@ -493,37 +493,48 @@ func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address,
} }
} }
// AddressInAccessList returns true if the address is registered on the access list map. // AddressInAccessList returns true if the address is registered on the transient store.
func (k *Keeper) AddressInAccessList(addr common.Address) bool { func (k *Keeper) AddressInAccessList(addr common.Address) bool {
return k.accessList.ContainsAddress(addr) ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
return ts.Has(addr.Bytes())
} }
// SlotInAccessList checks if the address and the slots are registered in the transient store
func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
return k.accessList.Contains(addr, slot) addressOk = k.AddressInAccessList(addr)
slotOk = k.addressSlotInAccessList(addr, slot)
return addressOk, slotOk
} }
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform // addressSlotInAccessList returns true if the address's slot is registered on the transient store.
// even if the feature/fork is not active yet func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool {
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...)
return ts.Has(key)
}
// AddAddressToAccessList adds the given address to the access list. If the address is already
// in the access list, this function performs a no-op.
func (k *Keeper) AddAddressToAccessList(addr common.Address) { func (k *Keeper) AddAddressToAccessList(addr common.Address) {
// NOTE: only update the access list during DeliverTx if k.AddressInAccessList(addr) {
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() {
return return
} }
// NOTE: ignore change return bool because we don't have to keep a journal for state changes ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
_ = k.accessList.AddAddress(addr) ts.Set(addr.Bytes(), []byte{0x1})
} }
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // AddSlotToAccessList adds the given (address, slot) to the access list. If the address and slot are
// even if the feature/fork is not active yet // already in the access list, this function performs a no-op.
func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) { func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
// NOTE: only update the access list during DeliverTx k.AddAddressToAccessList(addr)
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() { if k.addressSlotInAccessList(addr, slot) {
return return
} }
// NOTE: ignore change return booleans because we don't have to keep a journal for state changes ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
_, _ = k.accessList.AddSlot(addr, slot) key := append(addr.Bytes(), slot.Bytes()...)
ts.Set(key, []byte{0x1})
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -506,7 +506,7 @@ func (suite *KeeperTestSuite) TestAddLog() {
} }
} }
func (suite *KeeperTestSuite) TestAccessList() { func (suite *KeeperTestSuite) TestPrepareAccessList() {
dest := tests.GenerateAddress() dest := tests.GenerateAddress()
precompiles := []common.Address{tests.GenerateAddress(), tests.GenerateAddress()} precompiles := []common.Address{tests.GenerateAddress(), tests.GenerateAddress()}
accesses := ethtypes.AccessList{ accesses := ethtypes.AccessList{
@ -526,12 +526,52 @@ func (suite *KeeperTestSuite) TestAccessList() {
for _, access := range accesses { for _, access := range accesses {
for _, key := range access.StorageKeys { for _, key := range access.StorageKeys {
addrOK, slotOK := suite.app.EvmKeeper.SlotInAccessList(access.Address, key) addrOK, slotOK := suite.app.EvmKeeper.SlotInAccessList(access.Address, key)
suite.Require().True(addrOK) suite.Require().True(addrOK, access.Address.Hex())
suite.Require().True(slotOK) suite.Require().True(slotOK, key.Hex())
} }
} }
} }
func (suite *KeeperTestSuite) TestAddAddressToAccessList() {
testCases := []struct {
name string
addr common.Address
}{
{"new address", suite.address},
{"existing address", suite.address},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.app.EvmKeeper.AddAddressToAccessList(tc.addr)
addrOk := suite.app.EvmKeeper.AddressInAccessList(tc.addr)
suite.Require().True(addrOk, tc.addr.Hex())
})
}
}
func (suite *KeeperTestSuite) AddSlotToAccessList() {
testCases := []struct {
name string
addr common.Address
slot common.Hash
}{
{"new address and slot (1)", tests.GenerateAddress(), common.BytesToHash([]byte("hash"))},
{"new address and slot (2)", suite.address, common.Hash{}},
{"existing address and slot", suite.address, common.Hash{}},
{"existing address, new slot", suite.address, common.BytesToHash([]byte("hash"))},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.app.EvmKeeper.AddSlotToAccessList(tc.addr, tc.slot)
addrOk, slotOk := suite.app.EvmKeeper.SlotInAccessList(tc.addr, tc.slot)
suite.Require().True(addrOk, tc.addr.Hex())
suite.Require().True(slotOk, tc.slot.Hex())
})
}
}
func (suite *KeeperTestSuite) TestForEachStorage() { func (suite *KeeperTestSuite) TestForEachStorage() {
var storage types.Storage var storage types.Storage

View File

@ -1,140 +1,10 @@
package types package types
import ( import (
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
// AccessListMappings is copied from go-ethereum
// https://github.com/ethereum/go-ethereum/blob/cf856ea1ad96ac39ea477087822479b63417036a/core/state/access_list.go#L23
type AccessListMappings struct {
addresses map[common.Address]int
slots []map[common.Hash]struct{}
}
// ContainsAddress returns true if the address is in the access list.
func (al *AccessListMappings) ContainsAddress(address common.Address) bool {
_, ok := al.addresses[address]
return ok
}
// Contains checks if a slot within an account is present in the access list, returning
// separate flags for the presence of the account and the slot respectively.
func (al *AccessListMappings) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
idx, ok := al.addresses[address]
if !ok {
// no such address (and hence zero slots)
return false, false
}
if idx == -1 {
// address yes, but no slots
return true, false
}
if idx >= len(al.slots) {
// return in case of out-of-range
return true, false
}
_, slotPresent = al.slots[idx][slot]
return true, slotPresent
}
// newAccessList creates a new AccessListMappings.
func NewAccessListMappings() *AccessListMappings {
return &AccessListMappings{
addresses: make(map[common.Address]int),
}
}
// Copy creates an independent copy of an AccessListMappings.
func (al *AccessListMappings) Copy() *AccessListMappings {
cp := NewAccessListMappings()
for k, v := range al.addresses {
cp.addresses[k] = v
}
cp.slots = make([]map[common.Hash]struct{}, len(al.slots))
for i, slotMap := range al.slots {
newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
for k := range slotMap {
newSlotmap[k] = struct{}{}
}
cp.slots[i] = newSlotmap
}
return cp
}
// AddAddress adds an address to the access list, and returns 'true' if the operation
// caused a change (addr was not previously in the list).
func (al *AccessListMappings) AddAddress(address common.Address) bool {
if _, present := al.addresses[address]; present {
return false
}
al.addresses[address] = -1
return true
}
// AddSlot adds the specified (addr, slot) combo to the access list.
// Return values are:
// - address added
// - slot added
// For any 'true' value returned, a corresponding journal entry must be made.
func (al *AccessListMappings) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
idx, addrPresent := al.addresses[address]
if !addrPresent || idx == -1 {
// Address not present, or addr present but no slots there
al.addresses[address] = len(al.slots)
slotmap := map[common.Hash]struct{}{slot: {}}
al.slots = append(al.slots, slotmap)
return !addrPresent, true
}
if idx >= len(al.slots) {
// return in case of out-of-range
return false, false
}
// There is already an (address,slot) mapping
slotmap := al.slots[idx]
if _, ok := slotmap[slot]; !ok {
slotmap[slot] = struct{}{}
// journal add slot change
return false, true
}
// No changes required
return false, false
}
// DeleteSlot removes an (address, slot)-tuple from the access list.
// This operation needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *AccessListMappings) DeleteSlot(address common.Address, slot common.Hash) {
idx, addrOk := al.addresses[address]
// There are two ways this can fail
if !addrOk {
panic("reverting slot change, address not present in list")
}
slotmap := al.slots[idx]
delete(slotmap, slot)
// If that was the last (first) slot, remove it
// Since additions and rollbacks are always performed in order,
// we can delete the item without worrying about screwing up later indices
if len(slotmap) == 0 {
al.slots = al.slots[:idx]
al.addresses[address] = -1
}
}
// DeleteAddress removes an address from the access list. This operation
// needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *AccessListMappings) DeleteAddress(address common.Address) {
delete(al.addresses, address)
}
// AccessList is an EIP-2930 access list that represents the slice of // AccessList is an EIP-2930 access list that represents the slice of
// the protobuf AccessTuples. // the protobuf AccessTuples.
type AccessList []AccessTuple type AccessList []AccessTuple

View File

@ -1,242 +0,0 @@
package types
import (
"fmt"
"testing"
"github.com/stretchr/testify/suite"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
)
type AccessListTestSuite struct {
suite.Suite
address ethcmn.Address
accessList *AccessListMappings
}
func (suite *AccessListTestSuite) SetupTest() {
privkey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes())
suite.accessList = NewAccessListMappings()
suite.accessList.addresses[suite.address] = 1
}
func TestAccessListTestSuite(t *testing.T) {
suite.Run(t, new(AccessListTestSuite))
}
func (suite *AccessListTestSuite) TestContainsAddress() {
found := suite.accessList.ContainsAddress(suite.address)
suite.Require().True(found)
}
func (suite *AccessListTestSuite) TestContains() {
testCases := []struct {
name string
malleate func()
expAddrPresent bool
expSlotPresent bool
}{
{"out of range", func() {}, true, false},
{
"address, no slots",
func() {
suite.accessList.addresses[suite.address] = -1
}, true, false,
},
{
"no address, no slots",
func() {
delete(suite.accessList.addresses, suite.address)
}, false, false,
},
{
"address, slot not present",
func() {
suite.accessList.addresses[suite.address] = 0
suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
}, true, false,
},
{
"address, slots",
func() {
suite.accessList.addresses[suite.address] = 0
suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{})
suite.accessList.slots[0][ethcmn.Hash{}] = struct{}{}
}, true, true,
},
}
for _, tc := range testCases {
tc.malleate()
addrPresent, slotPresent := suite.accessList.Contains(suite.address, ethcmn.Hash{})
suite.Require().Equal(tc.expAddrPresent, addrPresent, tc.name)
suite.Require().Equal(tc.expSlotPresent, slotPresent, tc.name)
}
}
func (suite *AccessListTestSuite) TestCopy() {
expAccessList := NewAccessListMappings()
testCases := []struct {
name string
malleate func()
}{
{"empty", func() {
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0)
}},
{
"single address", func() {
expAccessList = NewAccessListMappings()
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0)
expAccessList.addresses[suite.address] = -1
},
},
{
"single address, single slot",
func() {
expAccessList = NewAccessListMappings()
expAccessList.addresses[suite.address] = 0
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
expAccessList.slots[0] = make(map[ethcmn.Hash]struct{})
expAccessList.slots[0][ethcmn.Hash{}] = struct{}{}
},
},
{
"multiple addresses, single slot each",
func() {
expAccessList = NewAccessListMappings()
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10)
for i := 0; i < 10; i++ {
expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i
expAccessList.slots[i] = make(map[ethcmn.Hash]struct{})
expAccessList.slots[i][ethcmn.BytesToHash([]byte(fmt.Sprintf("%d", i)))] = struct{}{}
}
},
},
{
"multiple addresses, multiple slots each",
func() {
expAccessList = NewAccessListMappings()
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10)
for i := 0; i < 10; i++ {
expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i
expAccessList.slots[i] = make(map[ethcmn.Hash]struct{})
for j := 0; j < 10; j++ {
expAccessList.slots[i][ethcmn.BytesToHash([]byte(fmt.Sprintf("%d-%d", i, j)))] = struct{}{}
}
}
},
},
}
for _, tc := range testCases {
tc.malleate()
accessList := expAccessList.Copy()
suite.Require().EqualValues(expAccessList, accessList, tc.name)
}
}
func (suite *AccessListTestSuite) TestAddAddress() {
testCases := []struct {
name string
address ethcmn.Address
ok bool
}{
{"already present", suite.address, false},
{"ok", ethcmn.Address{}, true},
}
for _, tc := range testCases {
ok := suite.accessList.AddAddress(tc.address)
suite.Require().Equal(tc.ok, ok, tc.name)
}
}
func (suite *AccessListTestSuite) TestAddSlot() {
testCases := []struct {
name string
malleate func()
expAddrChange bool
expSlotChange bool
}{
{"out of range", func() {}, false, false},
{
"address not present added, slot added",
func() {
delete(suite.accessList.addresses, suite.address)
}, true, true,
},
{
"address present, slot not present added",
func() {
suite.accessList.addresses[suite.address] = 0
suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{})
}, false, true,
},
{
"address present, slot present",
func() {
suite.accessList.addresses[suite.address] = 0
suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{})
suite.accessList.slots[0][ethcmn.Hash{}] = struct{}{}
}, false, false,
},
}
for _, tc := range testCases {
tc.malleate()
addrChange, slotChange := suite.accessList.AddSlot(suite.address, ethcmn.Hash{})
suite.Require().Equal(tc.expAddrChange, addrChange, tc.name)
suite.Require().Equal(tc.expSlotChange, slotChange, tc.name)
}
}
func (suite *AccessListTestSuite) TestDeleteSlot() {
testCases := []struct {
name string
malleate func()
expPanic bool
}{
{"panics, out of range", func() {}, true},
{"panics, address not found", func() {
delete(suite.accessList.addresses, suite.address)
}, true},
{
"single slot present",
func() {
suite.accessList.addresses[suite.address] = 0
suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{})
suite.accessList.slots[0][ethcmn.Hash{}] = struct{}{}
}, false,
},
}
for _, tc := range testCases {
tc.malleate()
if tc.expPanic {
suite.Require().Panics(func() {
suite.accessList.DeleteSlot(suite.address, ethcmn.Hash{})
}, tc.name)
} else {
suite.Require().NotPanics(func() {
suite.accessList.DeleteSlot(suite.address, ethcmn.Hash{})
}, tc.name)
}
}
}

View File

@ -364,7 +364,7 @@ func (ch accessListAddAccountChange) revert(s *CommitStateDB) {
(addr) at this point, since no storage adds can remain when come upon (addr) at this point, since no storage adds can remain when come upon
a single (addr) change. a single (addr) change.
*/ */
s.accessList.DeleteAddress(*ch.address) // s.accessList.DeleteAddress(*ch.address)
} }
func (ch accessListAddAccountChange) dirtied() *ethcmn.Address { func (ch accessListAddAccountChange) dirtied() *ethcmn.Address {
@ -372,7 +372,7 @@ func (ch accessListAddAccountChange) dirtied() *ethcmn.Address {
} }
func (ch accessListAddSlotChange) revert(s *CommitStateDB) { func (ch accessListAddSlotChange) revert(s *CommitStateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot) // s.accessList.DeleteSlot(*ch.address, *ch.slot)
} }
func (ch accessListAddSlotChange) dirtied() *ethcmn.Address { func (ch accessListAddSlotChange) dirtied() *ethcmn.Address {

View File

@ -104,6 +104,7 @@ func (suite *JournalTestSuite) setup() {
authKey := sdk.NewKVStoreKey(authtypes.StoreKey) authKey := sdk.NewKVStoreKey(authtypes.StoreKey)
paramsKey := sdk.NewKVStoreKey(paramtypes.StoreKey) paramsKey := sdk.NewKVStoreKey(paramtypes.StoreKey)
paramsTKey := sdk.NewTransientStoreKey(paramtypes.TStoreKey) paramsTKey := sdk.NewTransientStoreKey(paramtypes.TStoreKey)
tKey := sdk.NewTransientStoreKey(TransientKey)
bankKey := sdk.NewKVStoreKey(banktypes.StoreKey) bankKey := sdk.NewKVStoreKey(banktypes.StoreKey)
storeKey := sdk.NewKVStoreKey(StoreKey) storeKey := sdk.NewKVStoreKey(StoreKey)
@ -120,6 +121,7 @@ func (suite *JournalTestSuite) setup() {
cms.MountStoreWithDB(paramsKey, sdk.StoreTypeIAVL, db) cms.MountStoreWithDB(paramsKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(storeKey, sdk.StoreTypeIAVL, db) cms.MountStoreWithDB(storeKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(paramsTKey, sdk.StoreTypeTransient, db) cms.MountStoreWithDB(paramsTKey, sdk.StoreTypeTransient, db)
cms.MountStoreWithDB(tKey, sdk.StoreTypeTransient, db)
err = cms.LoadLatestVersion() err = cms.LoadLatestVersion()
suite.Require().NoError(err) suite.Require().NoError(err)
@ -135,7 +137,7 @@ func (suite *JournalTestSuite) setup() {
ak := authkeeper.NewAccountKeeper(cdc, authKey, authSubspace, ethermint.ProtoAccount, nil) ak := authkeeper.NewAccountKeeper(cdc, authKey, authSubspace, ethermint.ProtoAccount, nil)
bk := bankkeeper.NewBaseKeeper(cdc, bankKey, ak, bankSubspace, nil) bk := bankkeeper.NewBaseKeeper(cdc, bankKey, ak, bankSubspace, nil)
suite.ctx = sdk.NewContext(cms, tmproto.Header{ChainID: "ethermint-8"}, false, tmlog.NewNopLogger()) suite.ctx = sdk.NewContext(cms, tmproto.Header{ChainID: "ethermint-8"}, false, tmlog.NewNopLogger())
suite.stateDB = NewCommitStateDB(suite.ctx, storeKey, evmSubspace, ak, bk).WithContext(suite.ctx) suite.stateDB = NewCommitStateDB(suite.ctx, storeKey, tKey, evmSubspace, ak, bk).WithContext(suite.ctx)
suite.stateDB.SetParams(DefaultParams()) suite.stateDB.SetParams(DefaultParams())
} }

View File

@ -40,6 +40,8 @@ const (
prefixTransientBloom prefixTransientBloom
prefixTransientTxIndex prefixTransientTxIndex
prefixTransientRefund prefixTransientRefund
prefixTransientAccessListAddress
prefixTransientAccessListSlot
) )
// KVStore key prefixes // KVStore key prefixes
@ -55,10 +57,12 @@ var (
) )
var ( var (
KeyPrefixTransientSuicided = []byte{prefixTransientSuicided} KeyPrefixTransientSuicided = []byte{prefixTransientSuicided}
KeyPrefixTransientBloom = []byte{prefixTransientBloom} KeyPrefixTransientBloom = []byte{prefixTransientBloom}
KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex} KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
KeyPrefixTransientRefund = []byte{prefixTransientRefund} KeyPrefixTransientRefund = []byte{prefixTransientRefund}
KeyPrefixTransientAccessListAddress = []byte{prefixTransientAccessListAddress}
KeyPrefixTransientAccessListSlot = []byte{prefixTransientAccessListSlot}
) )
// BloomKey defines the store key for a block Bloom // BloomKey defines the store key for a block Bloom

View File

@ -38,6 +38,7 @@ type CommitStateDB struct {
ctx sdk.Context ctx sdk.Context
storeKey sdk.StoreKey storeKey sdk.StoreKey
transientKey sdk.StoreKey
paramSpace paramtypes.Subspace paramSpace paramtypes.Subspace
accountKeeper AccountKeeper accountKeeper AccountKeeper
bankKeeper BankKeeper bankKeeper BankKeeper
@ -73,9 +74,6 @@ type CommitStateDB struct {
validRevisions []revision validRevisions []revision
nextRevisionID int nextRevisionID int
// Per-transaction access list
accessList *AccessListMappings
// mutex for state deep copying // mutex for state deep copying
lock sync.Mutex lock sync.Mutex
} }
@ -86,12 +84,13 @@ type CommitStateDB struct {
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the // CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
// key/value space matters in determining the merkle root. // key/value space matters in determining the merkle root.
func NewCommitStateDB( func NewCommitStateDB(
ctx sdk.Context, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ctx sdk.Context, storeKey, tKey sdk.StoreKey, paramSpace paramtypes.Subspace,
ak AccountKeeper, bankKeeper BankKeeper, ak AccountKeeper, bankKeeper BankKeeper,
) *CommitStateDB { ) *CommitStateDB {
return &CommitStateDB{ return &CommitStateDB{
ctx: ctx, ctx: ctx,
storeKey: storeKey, storeKey: storeKey,
transientKey: tKey,
paramSpace: paramSpace, paramSpace: paramSpace,
accountKeeper: ak, accountKeeper: ak,
bankKeeper: bankKeeper, bankKeeper: bankKeeper,
@ -101,7 +100,6 @@ func NewCommitStateDB(
preimages: []preimageEntry{}, preimages: []preimageEntry{},
hashToPreimageIndex: make(map[ethcmn.Hash]int), hashToPreimageIndex: make(map[ethcmn.Hash]int),
journal: newJournal(), journal: newJournal(),
accessList: NewAccessListMappings(),
} }
} }
@ -253,67 +251,78 @@ func (csdb *CommitStateDB) SubRefund(gas uint64) {
csdb.refund -= gas csdb.refund -= gas
} }
// AddAddressToAccessList adds the given address to the access list // ----------------------------------------------------------------------------
func (csdb *CommitStateDB) AddAddressToAccessList(addr ethcmn.Address) { // Access List // TODO: deprecate
if csdb.accessList.AddAddress(addr) { // ----------------------------------------------------------------------------
csdb.journal.append(accessListAddAccountChange{&addr})
}
}
// AddSlotToAccessList adds the given (address, slot)-tuple to the access list
func (csdb *CommitStateDB) AddSlotToAccessList(addr ethcmn.Address, slot ethcmn.Hash) {
addrMod, slotMod := csdb.accessList.AddSlot(addr, slot)
if addrMod {
// In practice, this should not happen, since there is no way to enter the
// scope of 'address' without having the 'address' become already added
// to the access list (via call-variant, create, etc).
// Better safe than sorry, though
csdb.journal.append(accessListAddAccountChange{&addr})
}
if slotMod {
csdb.journal.append(accessListAddSlotChange{
address: &addr,
slot: &slot,
})
}
}
// AddressInAccessList returns true if the given address is in the access list.
func (csdb *CommitStateDB) AddressInAccessList(addr ethcmn.Address) bool {
return csdb.accessList.ContainsAddress(addr)
}
// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
func (csdb *CommitStateDB) SlotInAccessList(addr ethcmn.Address, slot ethcmn.Hash) (bool, bool) {
return csdb.accessList.Contains(addr, slot)
}
// PrepareAccessList handles the preparatory steps for executing a state transition with // PrepareAccessList handles the preparatory steps for executing a state transition with
// regards to both EIP-2929 and EIP-2930: // regards to both EIP-2929 and EIP-2930:
// //
// - Add sender to access list (2929) // - Add sender to access list (2929)
// - Add destination to access list (2929) // - Add destination to access list (2929)
// - Add precompiles to access list (2929) // - Add precompiles to access list (2929)
// - Add the contents of the optional tx access list (2930) // - Add the contents of the optional tx access list (2930)
// //
// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. // This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
func (csdb *CommitStateDB) PrepareAccessList(sender ethcmn.Address, dst *ethcmn.Address, precompiles []ethcmn.Address, list ethtypes.AccessList) { func (csdb *CommitStateDB) PrepareAccessList(sender ethcmn.Address, dest *ethcmn.Address, precompiles []ethcmn.Address, txAccesses ethtypes.AccessList) {
csdb.AddAddressToAccessList(sender) csdb.AddAddressToAccessList(sender)
if dst != nil { if dest != nil {
csdb.AddAddressToAccessList(*dst) csdb.AddAddressToAccessList(*dest)
// If it's a create-tx, the destination will be added inside evm.create // If it's a create-tx, the destination will be added inside evm.create
} }
for _, addr := range precompiles { for _, addr := range precompiles {
csdb.AddAddressToAccessList(addr) csdb.AddAddressToAccessList(addr)
} }
for _, el := range list { for _, tuple := range txAccesses {
csdb.AddAddressToAccessList(el.Address) csdb.AddAddressToAccessList(tuple.Address)
for _, key := range el.StorageKeys { for _, key := range tuple.StorageKeys {
csdb.AddSlotToAccessList(el.Address, key) csdb.AddSlotToAccessList(tuple.Address, key)
} }
} }
} }
// AddressInAccessList returns true if the address is registered on the access list map.
func (csdb *CommitStateDB) AddressInAccessList(addr ethcmn.Address) bool {
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListAddress)
return ts.Has(addr.Bytes())
}
func (csdb *CommitStateDB) SlotInAccessList(addr ethcmn.Address, slot ethcmn.Hash) (addressOk bool, slotOk bool) {
addressOk = csdb.AddressInAccessList(addr)
slotOk = csdb.addressSlotInAccessList(addr, slot)
return addressOk, slotOk
}
func (csdb *CommitStateDB) addressSlotInAccessList(addr ethcmn.Address, slot ethcmn.Hash) bool {
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...)
return ts.Has(key)
}
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (csdb *CommitStateDB) AddAddressToAccessList(addr ethcmn.Address) {
if csdb.AddressInAccessList(addr) {
return
}
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListAddress)
ts.Set(addr.Bytes(), []byte{0x1})
}
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (csdb *CommitStateDB) AddSlotToAccessList(addr ethcmn.Address, slot ethcmn.Hash) {
csdb.AddAddressToAccessList(addr)
if csdb.addressSlotInAccessList(addr, slot) {
return
}
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...)
ts.Set(key, []byte{0x1})
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Getters // Getters
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -721,7 +730,6 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
csdb.logSize = 0 csdb.logSize = 0
csdb.preimages = []preimageEntry{} csdb.preimages = []preimageEntry{}
csdb.hashToPreimageIndex = make(map[ethcmn.Hash]int) csdb.hashToPreimageIndex = make(map[ethcmn.Hash]int)
csdb.accessList = NewAccessListMappings()
csdb.clearJournalAndRefund() csdb.clearJournalAndRefund()
return nil return nil
@ -824,7 +832,6 @@ func CopyCommitStateDB(from, to *CommitStateDB) {
copy(validRevisions, from.validRevisions) copy(validRevisions, from.validRevisions)
to.validRevisions = validRevisions to.validRevisions = validRevisions
to.nextRevisionID = from.nextRevisionID to.nextRevisionID = from.nextRevisionID
to.accessList = from.accessList.Copy()
// copy the dirty states, logs, and preimages // copy the dirty states, logs, and preimages
for _, dirty := range from.journal.dirties { for _, dirty := range from.journal.dirties {