Skip to content

Commit 4d93f2d

Browse files
almk-devvladjdkEric-WarehimeAlex | Interchain LabsAdityaSripal
authored
feature: Add eth_createAccessList method and implementation (cosmos#346)
* apply 3eb2135 * apply 4f9b8c2 * apply 0e7fd50 * apply 5cfe96d * apply 0e7fd50: fix lint workflow * apply 679305d * Fix middleware test * Format * apply 9fae984: fix conflicts * consolidate diverging test suite structures * apply 9fae984: fix conflicts * consolidate diverging test suite structures * apply b9a4d41 * apply 96ad38f * fix lint * apply b9a4d41 * apply 96ad38f * apply 578b346 * apply fd7badc * apply f7a3922 * apply 0e511d3 * apply 09e1895 * apply fb7e407 * apply 8cd184f * apply 9b96d1f * apply 029ed3b * apply e6fe094 * apply 76d8d10 * apply 0517dcf * apply af6f8d4 * apply 17cb0d5 * apply cb64292 * apply cbae613 * apply e57a44e * apply 66dd661 * apply 16ec2d0 * apply ec1a928 * apply 058eb6d * apply 82a7981 * apply 71d26cf * apply 4c27407 * apply bda7ad0 * apply bb811f4 * apply 6644e35 * apply 8d21baf * apply ebcaefa * apply 4913831 * apply 76819ba * apply cd9cd1a * apply 60a6cd2 * apply 2518c1b * apply bb9d02e * apply 5e7359e * apply a8112f4 * apply 388b5c0 * apply 727407e * apply a5ddd15 * apply 03ede6c * apply 9ad7d66 * apply e921750 * apply 17c65a7 * apply 9ddd976 * apply cffad65 * apply 6085578 * apply 099f44e * apply 2934281 * apply f9ca3ea * apply b826e3a * apply 5024129 * apply bfa2f05 * apply fb20472 * apply 4b0d478 * apply b065764 * apply ebc4b6c * apply 0f22f9f * clean up gh workflows * add-test-chain-id * pointer to precise bank keeper * cover all * apply balance handler patch * fix lint * fix solidity test * add evmd tests fix * add omitted fractional balance clear * fix lint * clean up panic message * try 16 core depot * try 8 core * Add debug flags to init-node.sh * More logging * Fix denom * Remove logging additions * default runner * update hardhat config * fix * re-opening branch to merge into main * add solidity to push group * regen contracts and fix solidity group * use default runner for solidity (again) * rebase and add stubs * add trace call with accesslist tracer config * fix lint: * conform to auto registration * revert tracer logic * fix signatures * fix nil pointer issues * rev local node pruning settings * remove custom pruning settings * rename var to be clearer * fix typo * remove unsigned tx logic from tests * group vars * add debug logs * update test pointer * fix nil gas * clean up traceTx conditional handling * fix vmerr * fix lint * add debug logs * add error and info logs * add test suite with actual backend mock * lint: add helper marker * standardize vars * remove unnecessary flag in run json compat * add back to exclusion * add godocs * add permalink * clarify auth checks * add changelog --------- Co-authored-by: Vlad J <[email protected]> Co-authored-by: Eric Warehime <[email protected]> Co-authored-by: Alex | Interchain Labs <[email protected]> Co-authored-by: Aditya <[email protected]> Co-authored-by: Haber <[email protected]> Co-authored-by: Hyunwoo Lee <[email protected]>
1 parent 642c8e9 commit 4d93f2d

File tree

13 files changed

+524
-13
lines changed

13 files changed

+524
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
### FEATURES
1717

18+
- [\#346](https://github.com/cosmos/evm/pull/346) Add eth_createAccessList method and implementation
19+
1820
### STATE BREAKING
1921

2022
### API-BREAKING

crypto/hd/utils_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,7 @@ func (w *Wallet) derivePrivateKey(path accounts.DerivationPath) (*ecdsa.PrivateK
135135
if w.fixIssue172 && key.IsAffectedByIssue172() {
136136
key, err = key.Derive(n)
137137
} else {
138-
//lint:ignore SA1019 this is used for testing only
139-
key, err = key.DeriveNonStandard(n) //nolint:staticcheck
138+
key, err = key.DeriveNonStandard(n) //nolint:staticcheck // SA1019 this is used for testing only
140139
}
141140
if err != nil {
142141
return nil, err

evmd/cmd/evmd/config/evmd_config.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ var maccPerms = map[string][]string{
5858
func BlockedAddresses() map[string]bool {
5959
blockedAddrs := make(map[string]bool)
6060

61-
maps.Clone(maccPerms)
6261
maccPerms := GetMaccPerms()
6362
accs := make([]string, 0, len(maccPerms))
6463
for acc := range maccPerms {

local_node.sh

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,6 @@ if [[ $overwrite == "y" || $overwrite == "Y" ]]; then
287287
sed -i.bak 's/"voting_period": "172800s"/"voting_period": "30s"/g' "$GENESIS"
288288
sed -i.bak 's/"expedited_voting_period": "86400s"/"expedited_voting_period": "15s"/g' "$GENESIS"
289289

290-
# pruning
291-
sed -i.bak 's/pruning = "default"/pruning = "custom"/g' "$APP_TOML"
292-
sed -i.bak 's/pruning-keep-recent = "0"/pruning-keep-recent = "100"/g' "$APP_TOML"
293-
sed -i.bak 's/pruning-interval = "0"/pruning-interval = "10"/g' "$APP_TOML"
294-
295290
# fund validator (devs already funded in the loop)
296291
evmd genesis add-genesis-account "$VAL_KEY" 100000000000000000000000000atest --keyring-backend "$KEYRING" --home "$CHAINDIR"
297292

rpc/backend/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ type EVMBackend interface {
107107
GetTransactionLogs(hash common.Hash) ([]*ethtypes.Log, error)
108108
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
109109
GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
110+
CreateAccessList(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccessListResult, error)
110111

111112
// Send Transaction
112113
Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error)

rpc/backend/tx_info.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"github.com/ethereum/go-ethereum/common"
1010
"github.com/ethereum/go-ethereum/common/hexutil"
1111
ethtypes "github.com/ethereum/go-ethereum/core/types"
12+
"github.com/ethereum/go-ethereum/core/vm"
13+
"github.com/ethereum/go-ethereum/eth/tracers/logger"
14+
"github.com/ethereum/go-ethereum/params"
1215
"github.com/pkg/errors"
1316

1417
cmtrpcclient "github.com/cometbft/cometbft/rpc/client"
@@ -375,3 +378,158 @@ func (b *Backend) GetTransactionByBlockAndIndex(block *cmtrpctypes.ResultBlock,
375378
b.EvmChainID,
376379
)
377380
}
381+
382+
// CreateAccessList returns the list of addresses and storage keys used by the transaction (except for the
383+
// sender account and precompiles), plus the estimated gas if the access list were added to the transaction.
384+
func (b *Backend) CreateAccessList(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccessListResult, error) {
385+
accessList, gasUsed, vmErr, err := b.createAccessList(args, blockNrOrHash)
386+
if err != nil {
387+
return nil, err
388+
}
389+
390+
hexGasUsed := hexutil.Uint64(gasUsed)
391+
result := rpctypes.AccessListResult{
392+
AccessList: &accessList,
393+
GasUsed: &hexGasUsed,
394+
}
395+
if vmErr != nil {
396+
result.Error = vmErr.Error()
397+
}
398+
return &result, nil
399+
}
400+
401+
// createAccessList creates the access list for the transaction.
402+
// It iteratively expands the access list until it converges.
403+
// If the access list has converged, the access list is returned.
404+
// If the access list has not converged, an error is returned.
405+
// If the transaction itself fails, an vmErr is returned.
406+
func (b *Backend) createAccessList(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash) (ethtypes.AccessList, uint64, error, error) {
407+
args, err := b.SetTxDefaults(args)
408+
if err != nil {
409+
b.Logger.Error("failed to set tx defaults", "error", err)
410+
return nil, 0, nil, err
411+
}
412+
413+
blockNum, err := b.BlockNumberFromComet(blockNrOrHash)
414+
if err != nil {
415+
b.Logger.Error("failed to get block number", "error", err)
416+
return nil, 0, nil, err
417+
}
418+
419+
addressesToExclude, err := b.getAccessListExcludes(args, blockNum)
420+
if err != nil {
421+
b.Logger.Error("failed to get access list excludes", "error", err)
422+
return nil, 0, nil, err
423+
}
424+
425+
prevTracer, traceArgs, err := b.initAccessListTracer(args, blockNum, addressesToExclude)
426+
if err != nil {
427+
b.Logger.Error("failed to init access list tracer", "error", err)
428+
return nil, 0, nil, err
429+
}
430+
431+
// iteratively expand the access list
432+
for {
433+
accessList := prevTracer.AccessList()
434+
traceArgs.AccessList = &accessList
435+
res, err := b.DoCall(*traceArgs, blockNum)
436+
if err != nil {
437+
b.Logger.Error("failed to apply transaction", "error", err)
438+
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", traceArgs.ToTransaction(ethtypes.LegacyTxType).Hash(), err)
439+
}
440+
441+
// Check if access list has converged (no new addresses/slots accessed)
442+
newTracer := logger.NewAccessListTracer(accessList, addressesToExclude)
443+
if newTracer.Equal(prevTracer) {
444+
b.Logger.Info("access list converged", "accessList", accessList)
445+
var vmErr error
446+
if res.VmError != "" {
447+
b.Logger.Error("vm error after access list converged", "vmError", res.VmError)
448+
vmErr = errors.New(res.VmError)
449+
}
450+
return accessList, res.GasUsed, vmErr, nil
451+
}
452+
prevTracer = newTracer
453+
}
454+
}
455+
456+
// getAccessListExcludes returns the addresses to exclude from the access list.
457+
// This includes the sender account, the target account (if provided), precompiles,
458+
// and any addresses in the authorization list.
459+
func (b *Backend) getAccessListExcludes(args evmtypes.TransactionArgs, blockNum rpctypes.BlockNumber) (map[common.Address]struct{}, error) {
460+
header, err := b.HeaderByNumber(blockNum)
461+
if err != nil {
462+
b.Logger.Error("failed to get header by number", "error", err)
463+
return nil, err
464+
}
465+
466+
// exclude sender and precompiles
467+
addressesToExclude := make(map[common.Address]struct{})
468+
addressesToExclude[args.GetFrom()] = struct{}{}
469+
if args.To != nil {
470+
addressesToExclude[*args.To] = struct{}{}
471+
}
472+
473+
isMerge := b.ChainConfig().MergeNetsplitBlock != nil
474+
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isMerge, header.Time))
475+
for _, addr := range precompiles {
476+
addressesToExclude[addr] = struct{}{}
477+
}
478+
479+
// check if enough gas was provided to cover all authorization lists
480+
maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas
481+
if uint64(len(args.AuthorizationList)) > maxAuthorizations {
482+
b.Logger.Error("insufficient gas to process all authorizations", "maxAuthorizations", maxAuthorizations)
483+
return nil, errors.New("insufficient gas to process all authorizations")
484+
}
485+
486+
for _, auth := range args.AuthorizationList {
487+
// validate authorization (duplicating stateTransition.validateAuthorization() logic from geth: https://github.com/ethereum/go-ethereum/blob/bf8f63dcd27e178bd373bfe41ea718efee2851dd/core/state_transition.go#L575)
488+
nonceOverflow := auth.Nonce+1 < auth.Nonce
489+
invalidChainID := !auth.ChainID.IsZero() && auth.ChainID.CmpBig(b.ChainConfig().ChainID) != 0
490+
if nonceOverflow || invalidChainID {
491+
b.Logger.Error("invalid authorization", "auth", auth)
492+
continue
493+
}
494+
if authority, err := auth.Authority(); err == nil {
495+
addressesToExclude[authority] = struct{}{}
496+
}
497+
}
498+
499+
b.Logger.Debug("access list excludes created", "addressesToExclude", addressesToExclude)
500+
return addressesToExclude, nil
501+
}
502+
503+
// initAccessListTracer initializes the access list tracer for the transaction.
504+
// It sets the default call arguments and creates a new access list tracer.
505+
// If an access list is provided in args, it uses that instead of creating a new one.
506+
func (b *Backend) initAccessListTracer(args evmtypes.TransactionArgs, blockNum rpctypes.BlockNumber, addressesToExclude map[common.Address]struct{}) (*logger.AccessListTracer, *evmtypes.TransactionArgs, error) {
507+
header, err := b.HeaderByNumber(blockNum)
508+
if err != nil {
509+
b.Logger.Error("failed to get header by number", "error", err)
510+
return nil, nil, err
511+
}
512+
513+
if args.Nonce == nil {
514+
pending := blockNum == rpctypes.EthPendingBlockNumber
515+
nonce, err := b.getAccountNonce(args.GetFrom(), pending, blockNum.Int64(), b.Logger)
516+
if err != nil {
517+
b.Logger.Error("failed to get account nonce", "error", err)
518+
return nil, nil, err
519+
}
520+
nonce64 := hexutil.Uint64(nonce)
521+
args.Nonce = &nonce64
522+
}
523+
if err = args.CallDefaults(b.RPCGasCap(), header.BaseFee, b.ChainConfig().ChainID); err != nil {
524+
b.Logger.Error("failed to set default call args", "error", err)
525+
return nil, nil, err
526+
}
527+
528+
tracer := logger.NewAccessListTracer(nil, addressesToExclude)
529+
if args.AccessList != nil {
530+
tracer = logger.NewAccessListTracer(*args.AccessList, addressesToExclude)
531+
}
532+
533+
b.Logger.Debug("access list tracer initialized", "tracer", tracer)
534+
return tracer, &args, nil
535+
}

0 commit comments

Comments
 (0)