Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/state: construct access list from current state #22289

Closed
11 changes: 11 additions & 0 deletions core/state/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ type (
address *common.Address
slot *common.Hash
}
accessListDeleteAccountChange struct {
address *common.Address
}
)

func (ch createObjectChange) revert(s *StateDB) {
Expand Down Expand Up @@ -267,3 +270,11 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil
}

func (ch accessListDeleteAccountChange) revert(s *StateDB) {
s.accessList.AddAddress(*ch.address)
}

func (ch accessListDeleteAccountChange) dirtied() *common.Address {
return nil
}
44 changes: 44 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,27 @@ func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
}
}

// UnprepareAccessList unprepares an access list.
// It reverts the preparation steps from EIP-2929
// - Delete sender from access list (2929)
// - Delete receiver from access list (2929)
// - Delete precompiles from access list (2929)
func (s *StateDB) UnprepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address) {
s.DeleteAddressFromAccessList(sender)
if dst != nil {
s.DeleteAddressFromAccessList(*dst)
}
for _, addr := range precompiles {
s.DeleteAddressFromAccessList(addr)
}
}

// DeleteAddressFromAccessList deletes the given address from the access list
func (s *StateDB) DeleteAddressFromAccessList(addr common.Address) {
s.accessList.DeleteAddress(addr)
s.journal.append(accessListDeleteAccountChange{&addr})
}

// AddressInAccessList returns true if the given address is in the access list.
func (s *StateDB) AddressInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddress(addr)
Expand All @@ -1043,3 +1064,26 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool {
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot)
}

// AccessList returns the current access list for the StateDB.
// If the precompiles, sender and receiver are to be removed from the access list,
// a prior call to `UnprepareAccessList` is required.
func (s *StateDB) AccessList() *types.AccessList {
list := s.accessList.Copy()
acl := make([]types.AccessTuple, 0, len(list.addresses))
for addr, idx := range list.addresses {
var tuple types.AccessTuple
tuple.Address = addr
// addresses without slots are saved as -1
if idx > 0 {
keys := make([]common.Hash, 0, len(list.slots[idx]))
for key := range list.slots[idx] {
keys = append(keys, key)
}
tuple.StorageKeys = keys
}
acl = append(acl, tuple)
}
cast := types.AccessList(acl)
return &cast
}
3 changes: 3 additions & 0 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type StateDB interface {
Empty(common.Address) bool

PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
UnprepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address)
AddressInAccessList(addr common.Address) bool
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
Expand All @@ -74,6 +75,8 @@ type StateDB interface {
AddPreimage(common.Hash, []byte)

ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error

AccessList() *types.AccessList
}

// CallContext provides a basic interface for the EVM calling conventions. The EVM
Expand Down
21 changes: 21 additions & 0 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,24 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc
}
return dirty, nil
}

func (api *PublicDebugAPI) CreateAccessList(blockNrOrHash rpc.BlockNumberOrHash, reexec uint64, args *ethapi.SendTxArgs) (*types.AccessList, error) {
ctx := context.Background()
var block *types.Block
if number, ok := blockNrOrHash.Number(); ok {
if number == rpc.PendingBlockNumber {
block, _ = api.eth.miner.Pending()
} else {
if number == rpc.LatestBlockNumber {
block = api.eth.blockchain.CurrentBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(number))
}
}
} else if hash, ok := blockNrOrHash.Hash(); ok {
block = api.eth.blockchain.GetBlockByHash(hash)
} else {
return nil, errors.New("either block number or block hash must be specified")
}
return api.eth.APIBackend.AccessList(ctx, block, reexec, args)
}
44 changes: 44 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package eth
import (
"context"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts"
Expand All @@ -34,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
Expand Down Expand Up @@ -343,3 +345,45 @@ func (b *EthAPIBackend) StatesInRange(ctx context.Context, fromBlock *types.Bloc
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) {
return b.eth.stateAtTransaction(block, txIndex, reexec)
}

func (b *EthAPIBackend) AccessList(ctx context.Context, block *types.Block, reexec uint64, args *ethapi.SendTxArgs) (*types.AccessList, error) {
if block == nil {
block = b.CurrentBlock()
}
db, release, err := b.eth.stateAtBlock(block, reexec)
defer release()
if err != nil {
return nil, err
}
args.SetDefaults(ctx, b)
var (
gas uint64
accessList = args.AccessList
msg types.Message
)
for i := 0; i < 10; i++ {
// Copy the original db so we don't modify it
statedb := db.Copy()
// If we have an accesslist, use it
if accessList != nil {
msg = types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), *args.Data, *accessList, false)
} else {
msg = types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), *args.Data, nil, false)
}
// Apply the transaction
context := core.NewEVMBlockContext(block.Header(), b.eth.blockchain, nil)
vmenv := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, b.eth.blockchain.Config(), vm.Config{})
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if err != nil {
return nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction().Hash(), err)
}
if res.UsedGas == gas {
vmenv.StateDB.UnprepareAccessList(args.From, args.To, vmenv.ActivePrecompiles())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why we need this Unprepare, whicih also is journalled.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was a question I had, whether I should unprepare the access list inside or outside of the statedb.
If I unprepare inside of the statedb (the way it currently is) then this should be journalled. But I guess I should unprepare it outside of the statedb.
We need unprepare to remove sender, recipient and the precompiles from the access list
Note to myself, we should only remove the recipient if it has no other state accesses

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to myself, we should only remove the recipient if it has no other state accesses

Yes. only if the slotlist is empty.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But really, you only need to 'clean it' as the very last step before returning it to the user.

return vmenv.StateDB.AccessList(), nil
}
accessList = vmenv.StateDB.AccessList()
gas = res.UsedGas
}

return accessList, errors.New("failed to create accesslist")
}
30 changes: 15 additions & 15 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,11 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg
return nil, err
}
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
if err := args.SetDefaults(ctx, s.b); err != nil {
return nil, err
}
// Assemble the transaction and sign with the wallet
tx := args.toTransaction()
tx := args.ToTransaction()

return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID)
}
Expand Down Expand Up @@ -1501,8 +1501,8 @@ type SendTxArgs struct {
ChainID *hexutil.Big `json:"chainId,omitempty"`
}

// setDefaults fills in default values for unspecified tx fields.
func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
// SetDefaults fills in default values for unspecified tx fields.
func (args *SendTxArgs) SetDefaults(ctx context.Context, b Backend) error {
if args.GasPrice == nil {
price, err := b.SuggestPrice(ctx)
if err != nil {
Expand Down Expand Up @@ -1567,9 +1567,9 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
return nil
}

// toTransaction converts the arguments to a transaction.
// ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
func (args *SendTxArgs) toTransaction() *types.Transaction {
func (args *SendTxArgs) ToTransaction() *types.Transaction {
var input []byte
if args.Input != nil {
input = *args.Input
Expand Down Expand Up @@ -1651,11 +1651,11 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
}

// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
if err := args.SetDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet
tx := args.toTransaction()
tx := args.ToTransaction()

signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
if err != nil {
Expand All @@ -1668,11 +1668,11 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
// and returns it to the caller for further processing (signing + broadcast)
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
if err := args.SetDefaults(ctx, s.b); err != nil {
return nil, err
}
// Assemble the transaction and obtain rlp
tx := args.toTransaction()
tx := args.ToTransaction()
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
Expand Down Expand Up @@ -1734,14 +1734,14 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
if args.Nonce == nil {
return nil, fmt.Errorf("nonce not specified")
}
if err := args.setDefaults(ctx, s.b); err != nil {
if err := args.SetDefaults(ctx, s.b); err != nil {
return nil, err
}
// Before actually sign the transaction, ensure the transaction fee is reasonable.
if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil {
return nil, err
}
tx, err := s.sign(args.From, args.toTransaction())
tx, err := s.sign(args.From, args.ToTransaction())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1781,10 +1781,10 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
if sendArgs.Nonce == nil {
return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec")
}
if err := sendArgs.setDefaults(ctx, s.b); err != nil {
if err := sendArgs.SetDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
matchTx := sendArgs.toTransaction()
matchTx := sendArgs.ToTransaction()

// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
var price = matchTx.GasPrice()
Expand Down Expand Up @@ -1814,7 +1814,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit
}
signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction())
signedTx, err := s.sign(sendArgs.From, sendArgs.ToTransaction())
if err != nil {
return common.Hash{}, err
}
Expand Down
6 changes: 6 additions & 0 deletions internal/web3ext/web3ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ web3._extend({
call: 'debug_freezeClient',
params: 1,
}),
new web3._extend.Method({
name: 'createAccessList',
call: 'debug_createAccessList',
params: 3,
inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, null, null],
}),
],
properties: []
});
Expand Down
2 changes: 1 addition & 1 deletion tests/testdata
Submodule testdata updated 5928 files