mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 19:50:36 +00:00
3775f4a5d4
This builds upon #3453 and #3524, which previously implemented gas optimizations for the access lists generated by Erigon's implementation of the `eth_createAccessList` RPC call. Erigon currently optimizes inclusion of the recipient address based on how many storage keys are accessed, but does not perform the same optimization for sender address and precompiled contract addresses. These changes make the same optimization available for all of these cases. Additionally, this handles the cases of block producer address and created smart contract addresses. If these cases were omitted on purpose since they heavily rely on state, it may still make sense to offer them to users but disable them by default.
606 lines
19 KiB
Go
606 lines
19 KiB
Go
package jsonrpc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/ledgerwatch/log/v3"
|
|
"google.golang.org/grpc"
|
|
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/common/hexutility"
|
|
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
|
txpool_proto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
"github.com/ledgerwatch/erigon-lib/kv/memdb"
|
|
types2 "github.com/ledgerwatch/erigon-lib/types"
|
|
|
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
|
"github.com/ledgerwatch/erigon/core"
|
|
"github.com/ledgerwatch/erigon/core/state"
|
|
"github.com/ledgerwatch/erigon/core/types"
|
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
|
"github.com/ledgerwatch/erigon/core/vm"
|
|
"github.com/ledgerwatch/erigon/crypto"
|
|
"github.com/ledgerwatch/erigon/eth/stagedsync"
|
|
"github.com/ledgerwatch/erigon/eth/tracers/logger"
|
|
"github.com/ledgerwatch/erigon/params"
|
|
"github.com/ledgerwatch/erigon/rpc"
|
|
ethapi2 "github.com/ledgerwatch/erigon/turbo/adapter/ethapi"
|
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
|
"github.com/ledgerwatch/erigon/turbo/trie"
|
|
)
|
|
|
|
var latestNumOrHash = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
|
|
|
// Call implements eth_call. Executes a new message call immediately without creating a transaction on the block chain.
|
|
func (api *APIImpl) Call(ctx context.Context, args ethapi2.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi2.StateOverrides) (hexutility.Bytes, error) {
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
chainConfig, err := api.chainConfig(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
engine := api.engine()
|
|
|
|
if args.Gas == nil || uint64(*args.Gas) == 0 {
|
|
args.Gas = (*hexutil.Uint64)(&api.GasCap)
|
|
}
|
|
|
|
blockNumber, hash, _, err := rpchelper.GetCanonicalBlockNumber(blockNrOrHash, tx, api.filters) // DoCall cannot be executed on non-canonical blocks
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := api.blockWithSenders(tx, hash, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if block == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
stateReader, err := rpchelper.CreateStateReader(ctx, tx, blockNrOrHash, 0, api.filters, api.stateCache, api.historyV3(tx), chainConfig.ChainName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header := block.HeaderNoCopy()
|
|
result, err := transactions.DoCall(ctx, engine, args, tx, blockNrOrHash, header, overrides, api.GasCap, chainConfig, stateReader, api._blockReader, api.evmCallTimeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(result.ReturnData) > api.ReturnDataLimit {
|
|
return nil, fmt.Errorf("call returned result on length %d exceeding --rpc.returndata.limit %d", len(result.ReturnData), api.ReturnDataLimit)
|
|
}
|
|
|
|
// If the result contains a revert reason, try to unpack and return it.
|
|
if len(result.Revert()) > 0 {
|
|
return nil, ethapi2.NewRevertError(result)
|
|
}
|
|
|
|
return result.Return(), result.Err
|
|
}
|
|
|
|
// headerByNumberOrHash - intent to read recent headers only, tries from the lru cache before reading from the db
|
|
func headerByNumberOrHash(ctx context.Context, tx kv.Tx, blockNrOrHash rpc.BlockNumberOrHash, api *APIImpl) (*types.Header, error) {
|
|
_, bNrOrHashHash, _, err := rpchelper.GetCanonicalBlockNumber(blockNrOrHash, tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block := api.tryBlockFromLru(bNrOrHashHash)
|
|
if block != nil {
|
|
return block.Header(), nil
|
|
}
|
|
|
|
blockNum, _, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header, err := api._blockReader.HeaderByNumber(ctx, tx, blockNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// header can be nil
|
|
return header, nil
|
|
}
|
|
|
|
// EstimateGas implements eth_estimateGas. Returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain.
|
|
func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) {
|
|
var args ethapi2.CallArgs
|
|
// if we actually get CallArgs here, we use them
|
|
if argsOrNil != nil {
|
|
args = *argsOrNil
|
|
}
|
|
|
|
dbtx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer dbtx.Rollback()
|
|
|
|
// Binary search the gas requirement, as it may be higher than the amount used
|
|
var (
|
|
lo = params.TxGas - 1
|
|
hi uint64
|
|
gasCap uint64
|
|
)
|
|
// Use zero address if sender unspecified.
|
|
if args.From == nil {
|
|
args.From = new(libcommon.Address)
|
|
}
|
|
|
|
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
|
if blockNrOrHash != nil {
|
|
bNrOrHash = *blockNrOrHash
|
|
}
|
|
|
|
// Determine the highest gas limit can be used during the estimation.
|
|
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
|
|
hi = uint64(*args.Gas)
|
|
} else {
|
|
// Retrieve the block to act as the gas ceiling
|
|
h, err := headerByNumberOrHash(ctx, dbtx, bNrOrHash, api)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if h == nil {
|
|
// if a block number was supplied and there is no header return 0
|
|
if blockNrOrHash != nil {
|
|
return 0, nil
|
|
}
|
|
|
|
// block number not supplied, so we haven't found a pending block, read the latest block instead
|
|
h, err = headerByNumberOrHash(ctx, dbtx, latestNumOrHash, api)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if h == nil {
|
|
return 0, nil
|
|
}
|
|
}
|
|
hi = h.GasLimit
|
|
}
|
|
|
|
var feeCap *big.Int
|
|
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
|
return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
} else if args.GasPrice != nil {
|
|
feeCap = args.GasPrice.ToInt()
|
|
} else if args.MaxFeePerGas != nil {
|
|
feeCap = args.MaxFeePerGas.ToInt()
|
|
} else {
|
|
feeCap = libcommon.Big0
|
|
}
|
|
// Recap the highest gas limit with account's available balance.
|
|
if feeCap.Sign() != 0 {
|
|
cacheView, err := api.stateCache.View(ctx, dbtx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
stateReader := state.NewCachedReader2(cacheView, dbtx)
|
|
state := state.New(stateReader)
|
|
if state == nil {
|
|
return 0, fmt.Errorf("can't get the current state")
|
|
}
|
|
|
|
balance := state.GetBalance(*args.From) // from can't be nil
|
|
available := balance.ToBig()
|
|
if args.Value != nil {
|
|
if args.Value.ToInt().Cmp(available) >= 0 {
|
|
return 0, errors.New("insufficient funds for transfer")
|
|
}
|
|
available.Sub(available, args.Value.ToInt())
|
|
}
|
|
allowance := new(big.Int).Div(available, feeCap)
|
|
|
|
// If the allowance is larger than maximum uint64, skip checking
|
|
if allowance.IsUint64() && hi > allowance.Uint64() {
|
|
transfer := args.Value
|
|
if transfer == nil {
|
|
transfer = new(hexutil.Big)
|
|
}
|
|
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
|
"sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance)
|
|
hi = allowance.Uint64()
|
|
}
|
|
}
|
|
|
|
// Recap the highest gas allowance with specified gascap.
|
|
if hi > api.GasCap {
|
|
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", api.GasCap)
|
|
hi = api.GasCap
|
|
}
|
|
gasCap = hi
|
|
|
|
chainConfig, err := api.chainConfig(dbtx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
engine := api.engine()
|
|
|
|
latestCanBlockNumber, latestCanHash, isLatest, err := rpchelper.GetCanonicalBlockNumber(latestNumOrHash, dbtx, api.filters) // DoCall cannot be executed on non-canonical blocks
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// try and get the block from the lru cache first then try DB before failing
|
|
block := api.tryBlockFromLru(latestCanHash)
|
|
if block == nil {
|
|
block, err = api.blockWithSenders(dbtx, latestCanHash, latestCanBlockNumber)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if block == nil {
|
|
return 0, fmt.Errorf("could not find latest block in cache or db")
|
|
}
|
|
|
|
stateReader, err := rpchelper.CreateStateReaderFromBlockNumber(ctx, dbtx, latestCanBlockNumber, isLatest, 0, api.stateCache, api.historyV3(dbtx), chainConfig.ChainName)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
header := block.HeaderNoCopy()
|
|
|
|
caller, err := transactions.NewReusableCaller(engine, stateReader, nil, header, args, api.GasCap, latestNumOrHash, dbtx, api._blockReader, chainConfig, api.evmCallTimeout)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Create a helper to check if a gas allowance results in an executable transaction
|
|
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
|
result, err := caller.DoCallWithNewGas(ctx, gas)
|
|
if err != nil {
|
|
if errors.Is(err, core.ErrIntrinsicGas) {
|
|
// Special case, raise gas limit
|
|
return true, nil, nil
|
|
}
|
|
|
|
// Bail out
|
|
return true, nil, err
|
|
}
|
|
return result.Failed(), result, nil
|
|
}
|
|
|
|
// Execute the binary search and hone in on an executable gas limit
|
|
for lo+1 < hi {
|
|
mid := (hi + lo) / 2
|
|
failed, _, err := executable(mid)
|
|
// If the error is not nil(consensus error), it means the provided message
|
|
// call or transaction will never be accepted no matter how much gas it is
|
|
// assigened. Return the error directly, don't struggle any more.
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if failed {
|
|
lo = mid
|
|
} else {
|
|
hi = mid
|
|
}
|
|
}
|
|
|
|
// Reject the transaction as invalid if it still fails at the highest allowance
|
|
if hi == gasCap {
|
|
failed, result, err := executable(hi)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if failed {
|
|
if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
|
|
if len(result.Revert()) > 0 {
|
|
return 0, ethapi2.NewRevertError(result)
|
|
}
|
|
return 0, result.Err
|
|
}
|
|
// Otherwise, the specified gas cap is too low
|
|
return 0, fmt.Errorf("gas required exceeds allowance (%d)", gasCap)
|
|
}
|
|
}
|
|
return hexutil.Uint64(hi), nil
|
|
}
|
|
|
|
// maxGetProofRewindBlockCount limits the number of blocks into the past that
|
|
// GetProof will allow computing proofs. Because we must rewind the hash state
|
|
// and re-compute the state trie, the further back in time the request, the more
|
|
// computationally intensive the operation becomes. The staged sync code
|
|
// assumes that if more than 100_000 blocks are skipped, that the entire trie
|
|
// should be re-computed. Re-computing the entire trie will currently take ~15
|
|
// minutes on mainnet. The current limit has been chosen arbitrarily as
|
|
// 'useful' without likely being overly computationally intense. This parameter
|
|
// could possibly be made configurable in the future if needed.
|
|
var maxGetProofRewindBlockCount uint64 = 1_000
|
|
|
|
// GetProof is partially implemented; no Storage proofs, and proofs must be for
|
|
// blocks within maxGetProofRewindBlockCount blocks of the head.
|
|
func (api *APIImpl) GetProof(ctx context.Context, address libcommon.Address, storageKeys []libcommon.Hash, blockNrOrHash rpc.BlockNumberOrHash) (*accounts.AccProofResult, error) {
|
|
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
if api.historyV3(tx) {
|
|
return nil, fmt.Errorf("not supported by Erigon3")
|
|
}
|
|
|
|
blockNr, _, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
header, err := api._blockReader.HeaderByNumber(ctx, tx, blockNr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
latestBlock, err := rpchelper.GetLatestBlockNumber(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if latestBlock < blockNr {
|
|
// shouldn't happen, but check anyway
|
|
return nil, fmt.Errorf("block number is in the future latest=%d requested=%d", latestBlock, blockNr)
|
|
}
|
|
|
|
rl := trie.NewRetainList(0)
|
|
var loader *trie.FlatDBTrieLoader
|
|
if blockNr < latestBlock {
|
|
if latestBlock-blockNr > maxGetProofRewindBlockCount {
|
|
return nil, fmt.Errorf("requested block is too old, block must be within %d blocks of the head block number (currently %d)", maxGetProofRewindBlockCount, latestBlock)
|
|
}
|
|
batch := memdb.NewMemoryBatch(tx, api.dirs.Tmp)
|
|
defer batch.Rollback()
|
|
|
|
unwindState := &stagedsync.UnwindState{UnwindPoint: blockNr}
|
|
stageState := &stagedsync.StageState{BlockNumber: latestBlock}
|
|
|
|
hashStageCfg := stagedsync.StageHashStateCfg(nil, api.dirs, api.historyV3(batch))
|
|
if err := stagedsync.UnwindHashStateStage(unwindState, stageState, batch, hashStageCfg, ctx, api.logger); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
interHashStageCfg := stagedsync.StageTrieCfg(nil, false, false, false, api.dirs.Tmp, api._blockReader, nil, api.historyV3(batch), api._agg)
|
|
loader, err = stagedsync.UnwindIntermediateHashesForTrieLoader("eth_getProof", rl, unwindState, stageState, batch, interHashStageCfg, nil, nil, ctx.Done(), api.logger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx = batch
|
|
} else {
|
|
loader = trie.NewFlatDBTrieLoader("eth_getProof", rl, nil, nil, false)
|
|
}
|
|
|
|
reader, err := rpchelper.CreateStateReader(ctx, tx, blockNrOrHash, 0, api.filters, api.stateCache, api.historyV3(tx), "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a, err := reader.ReadAccountData(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if a == nil {
|
|
a = &accounts.Account{}
|
|
}
|
|
pr, err := trie.NewProofRetainer(address, a, storageKeys, rl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
loader.SetProofRetainer(pr)
|
|
root, err := loader.CalcTrieRoot(tx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if root != header.Root {
|
|
return nil, fmt.Errorf("mismatch in expected state root computed %v vs %v indicates bug in proof implementation", root, header.Root)
|
|
}
|
|
return pr.ProofResult()
|
|
}
|
|
|
|
func (api *APIImpl) tryBlockFromLru(hash libcommon.Hash) *types.Block {
|
|
var block *types.Block
|
|
if api.blocksLRU != nil {
|
|
if it, ok := api.blocksLRU.Get(hash); ok && it != nil {
|
|
block = it
|
|
}
|
|
}
|
|
return block
|
|
}
|
|
|
|
// accessListResult returns an optional accesslist
|
|
// Its the result of the `eth_createAccessList` RPC call.
|
|
// It contains an error if the transaction itself failed.
|
|
type accessListResult struct {
|
|
Accesslist *types2.AccessList `json:"accessList"`
|
|
Error string `json:"error,omitempty"`
|
|
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
|
}
|
|
|
|
// CreateAccessList implements eth_createAccessList. It creates an access list for the given transaction.
|
|
// If the accesslist creation fails an error is returned.
|
|
// If the transaction itself fails, an vmErr is returned.
|
|
func (api *APIImpl) CreateAccessList(ctx context.Context, args ethapi2.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash, optimizeGas *bool) (*accessListResult, error) {
|
|
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
|
if blockNrOrHash != nil {
|
|
bNrOrHash = *blockNrOrHash
|
|
}
|
|
|
|
tx, err := api.db.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
chainConfig, err := api.chainConfig(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
engine := api.engine()
|
|
|
|
blockNumber, hash, latest, err := rpchelper.GetCanonicalBlockNumber(bNrOrHash, tx, api.filters) // DoCall cannot be executed on non-canonical blocks
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := api.blockWithSenders(tx, hash, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if block == nil {
|
|
return nil, nil
|
|
}
|
|
var stateReader state.StateReader
|
|
if latest {
|
|
cacheView, err := api.stateCache.View(ctx, tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stateReader = state.NewCachedReader2(cacheView, tx)
|
|
} else {
|
|
stateReader, err = rpchelper.CreateHistoryStateReader(tx, blockNumber+1, 0, api.historyV3(tx), chainConfig.ChainName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
header := block.Header()
|
|
// If the gas amount is not set, extract this as it will depend on access
|
|
// lists and we'll need to reestimate every time
|
|
nogas := args.Gas == nil
|
|
|
|
var to libcommon.Address
|
|
if args.To != nil {
|
|
to = *args.To
|
|
} else {
|
|
// Require nonce to calculate address of created contract
|
|
if args.Nonce == nil {
|
|
var nonce uint64
|
|
reply, err := api.txPool.Nonce(ctx, &txpool_proto.NonceRequest{
|
|
Address: gointerfaces.ConvertAddressToH160(*args.From),
|
|
}, &grpc.EmptyCallOption{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if reply.Found {
|
|
nonce = reply.Nonce + 1
|
|
}
|
|
args.Nonce = (*hexutil.Uint64)(&nonce)
|
|
}
|
|
to = crypto.CreateAddress(*args.From, uint64(*args.Nonce))
|
|
}
|
|
|
|
if args.From == nil {
|
|
args.From = &libcommon.Address{}
|
|
}
|
|
|
|
// Retrieve the precompiles since they don't need to be added to the access list
|
|
precompiles := vm.ActivePrecompiles(chainConfig.Rules(blockNumber, header.Time))
|
|
excl := make(map[libcommon.Address]struct{})
|
|
for _, pc := range precompiles {
|
|
excl[pc] = struct{}{}
|
|
}
|
|
|
|
// Create an initial tracer
|
|
prevTracer := logger.NewAccessListTracer(nil, excl, nil)
|
|
if args.AccessList != nil {
|
|
prevTracer = logger.NewAccessListTracer(*args.AccessList, excl, nil)
|
|
}
|
|
for {
|
|
state := state.New(stateReader)
|
|
// Retrieve the current access list to expand
|
|
accessList := prevTracer.AccessList()
|
|
log.Trace("Creating access list", "input", accessList)
|
|
|
|
// If no gas amount was specified, each unique access list needs it's own
|
|
// gas calculation. This is quite expensive, but we need to be accurate
|
|
// and it's convered by the sender only anyway.
|
|
if nogas {
|
|
args.Gas = nil
|
|
}
|
|
// Set the accesslist to the last al
|
|
args.AccessList = &accessList
|
|
|
|
var msg types.Message
|
|
|
|
var baseFee *uint256.Int = nil
|
|
// check if EIP-1559
|
|
if header.BaseFee != nil {
|
|
baseFee, _ = uint256.FromBig(header.BaseFee)
|
|
}
|
|
|
|
msg, err = args.ToMessage(api.GasCap, baseFee)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Apply the transaction with the access list tracer
|
|
tracer := logger.NewAccessListTracer(accessList, excl, state)
|
|
config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true}
|
|
blockCtx := transactions.NewEVMBlockContext(engine, header, bNrOrHash.RequireCanonical, tx, api._blockReader)
|
|
txCtx := core.NewEVMTxContext(msg)
|
|
|
|
evm := vm.NewEVM(blockCtx, txCtx, state, chainConfig, config)
|
|
gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas())
|
|
res, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tracer.Equal(prevTracer) {
|
|
var errString string
|
|
if res.Err != nil {
|
|
errString = res.Err.Error()
|
|
}
|
|
accessList := &accessListResult{Accesslist: &accessList, Error: errString, GasUsed: hexutil.Uint64(res.UsedGas)}
|
|
if optimizeGas != nil && *optimizeGas {
|
|
optimizeWarmAddrInAccessList(accessList, *args.From)
|
|
optimizeWarmAddrInAccessList(accessList, to)
|
|
optimizeWarmAddrInAccessList(accessList, header.Coinbase)
|
|
for addr := range tracer.CreatedContracts() {
|
|
if !tracer.UsedBeforeCreation(addr) {
|
|
optimizeWarmAddrInAccessList(accessList, addr)
|
|
}
|
|
}
|
|
}
|
|
return accessList, nil
|
|
}
|
|
prevTracer = tracer
|
|
}
|
|
}
|
|
|
|
// some addresses (like sender, recipient, block producer, and created contracts)
|
|
// are considered warm already, so we can save by adding these to the access list
|
|
// only if we are adding a lot of their respective storage slots as well
|
|
func optimizeWarmAddrInAccessList(accessList *accessListResult, addr libcommon.Address) {
|
|
indexToRemove := -1
|
|
|
|
for i := 0; i < len(*accessList.Accesslist); i++ {
|
|
entry := (*accessList.Accesslist)[i]
|
|
if entry.Address != addr {
|
|
continue
|
|
}
|
|
|
|
// https://eips.ethereum.org/EIPS/eip-2930#charging-less-for-accesses-in-the-access-list
|
|
accessListSavingPerSlot := params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 - params.TxAccessListStorageKeyGas
|
|
|
|
numSlots := uint64(len(entry.StorageKeys))
|
|
if numSlots*accessListSavingPerSlot <= params.TxAccessListAddressGas {
|
|
indexToRemove = i
|
|
}
|
|
}
|
|
|
|
if indexToRemove >= 0 {
|
|
*accessList.Accesslist = removeIndex(*accessList.Accesslist, indexToRemove)
|
|
}
|
|
}
|
|
|
|
func removeIndex(s types2.AccessList, index int) types2.AccessList {
|
|
return append(s[:index], s[index+1:]...)
|
|
}
|