mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-14 12:06:40 +00:00
internal/ethapi: eth_multicall (#27720)
This is a successor PR to #25743. This PR is based on a new iteration of the spec: https://github.com/ethereum/execution-apis/pull/484. `eth_multicall` takes in a list of blocks, each optionally overriding fields like number, timestamp, etc. of a base block. Each block can include calls. At each block users can override the state. There are extra features, such as: - Include ether transfers as part of the logs - Overriding precompile codes with evm bytecode - Redirecting accounts to another address ## Breaking changes This PR includes the following breaking changes: - Block override fields of eth_call and debug_traceCall have had the following fields renamed - `coinbase` -> `feeRecipient` - `random` -> `prevRandao` - `baseFee` -> `baseFeePerGas` --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com> Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
parent
83775b1dc7
commit
8f4fac7b86
18 changed files with 2427 additions and 148 deletions
|
|
@ -147,9 +147,14 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
|
||||||
}
|
}
|
||||||
*usedGas += result.UsedGas
|
*usedGas += result.UsedGas
|
||||||
|
|
||||||
|
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeReceipt generates the receipt object for a transaction given its execution result.
|
||||||
|
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
|
||||||
// Create a new receipt for the transaction, storing the intermediate root and gas used
|
// Create a new receipt for the transaction, storing the intermediate root and gas used
|
||||||
// by the tx.
|
// by the tx.
|
||||||
receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
|
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
|
||||||
if result.Failed() {
|
if result.Failed() {
|
||||||
receipt.Status = types.ReceiptStatusFailed
|
receipt.Status = types.ReceiptStatusFailed
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -164,7 +169,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the transaction created a contract, store the creation address in the receipt.
|
// If the transaction created a contract, store the creation address in the receipt.
|
||||||
if msg.To == nil {
|
if tx.To() == nil {
|
||||||
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
|
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +185,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
|
||||||
receipt.BlockHash = blockHash
|
receipt.BlockHash = blockHash
|
||||||
receipt.BlockNumber = blockNumber
|
receipt.BlockNumber = blockNumber
|
||||||
receipt.TransactionIndex = uint(statedb.TxIndex())
|
receipt.TransactionIndex = uint(statedb.TxIndex())
|
||||||
return receipt, err
|
return receipt
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyTransaction attempts to apply a transaction to the given state database
|
// ApplyTransaction attempts to apply a transaction to the given state database
|
||||||
|
|
|
||||||
|
|
@ -142,27 +142,31 @@ type Message struct {
|
||||||
BlobGasFeeCap *big.Int
|
BlobGasFeeCap *big.Int
|
||||||
BlobHashes []common.Hash
|
BlobHashes []common.Hash
|
||||||
|
|
||||||
// When SkipAccountChecks is true, the message nonce is not checked against the
|
// When SkipNonceChecks is true, the message nonce is not checked against the
|
||||||
// account nonce in state. It also disables checking that the sender is an EOA.
|
// account nonce in state.
|
||||||
// This field will be set to true for operations like RPC eth_call.
|
// This field will be set to true for operations like RPC eth_call.
|
||||||
SkipAccountChecks bool
|
SkipNonceChecks bool
|
||||||
|
|
||||||
|
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
|
||||||
|
SkipFromEOACheck bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionToMessage converts a transaction into a Message.
|
// TransactionToMessage converts a transaction into a Message.
|
||||||
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
Nonce: tx.Nonce(),
|
Nonce: tx.Nonce(),
|
||||||
GasLimit: tx.Gas(),
|
GasLimit: tx.Gas(),
|
||||||
GasPrice: new(big.Int).Set(tx.GasPrice()),
|
GasPrice: new(big.Int).Set(tx.GasPrice()),
|
||||||
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
|
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
|
||||||
GasTipCap: new(big.Int).Set(tx.GasTipCap()),
|
GasTipCap: new(big.Int).Set(tx.GasTipCap()),
|
||||||
To: tx.To(),
|
To: tx.To(),
|
||||||
Value: tx.Value(),
|
Value: tx.Value(),
|
||||||
Data: tx.Data(),
|
Data: tx.Data(),
|
||||||
AccessList: tx.AccessList(),
|
AccessList: tx.AccessList(),
|
||||||
SkipAccountChecks: false,
|
SkipNonceChecks: false,
|
||||||
BlobHashes: tx.BlobHashes(),
|
SkipFromEOACheck: false,
|
||||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
BlobHashes: tx.BlobHashes(),
|
||||||
|
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||||
}
|
}
|
||||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||||
if baseFee != nil {
|
if baseFee != nil {
|
||||||
|
|
@ -280,7 +284,7 @@ func (st *StateTransition) buyGas() error {
|
||||||
func (st *StateTransition) preCheck() error {
|
func (st *StateTransition) preCheck() error {
|
||||||
// Only check transactions that are not fake
|
// Only check transactions that are not fake
|
||||||
msg := st.msg
|
msg := st.msg
|
||||||
if !msg.SkipAccountChecks {
|
if !msg.SkipNonceChecks {
|
||||||
// Make sure this transaction's nonce is correct.
|
// Make sure this transaction's nonce is correct.
|
||||||
stNonce := st.state.GetNonce(msg.From)
|
stNonce := st.state.GetNonce(msg.From)
|
||||||
if msgNonce := msg.Nonce; stNonce < msgNonce {
|
if msgNonce := msg.Nonce; stNonce < msgNonce {
|
||||||
|
|
@ -293,6 +297,8 @@ func (st *StateTransition) preCheck() error {
|
||||||
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
|
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
|
||||||
msg.From.Hex(), stNonce)
|
msg.From.Hex(), stNonce)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !msg.SkipFromEOACheck {
|
||||||
// Make sure the sender is an EOA
|
// Make sure the sender is an EOA
|
||||||
codeHash := st.state.GetCodeHash(msg.From)
|
codeHash := st.state.GetCodeHash(msg.From)
|
||||||
if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {
|
if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/consensys/gnark-crypto/ecc"
|
"github.com/consensys/gnark-crypto/ecc"
|
||||||
|
|
@ -46,9 +47,12 @@ type PrecompiledContract interface {
|
||||||
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
|
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrecompiledContracts contains the precompiled contracts supported at the given fork.
|
||||||
|
type PrecompiledContracts map[common.Address]PrecompiledContract
|
||||||
|
|
||||||
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
|
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
|
||||||
// contracts used in the Frontier and Homestead releases.
|
// contracts used in the Frontier and Homestead releases.
|
||||||
var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsHomestead = PrecompiledContracts{
|
||||||
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||||
|
|
@ -57,7 +61,7 @@ var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
|
||||||
|
|
||||||
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
|
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
|
||||||
// contracts used in the Byzantium release.
|
// contracts used in the Byzantium release.
|
||||||
var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsByzantium = PrecompiledContracts{
|
||||||
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||||
|
|
@ -70,7 +74,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
|
||||||
|
|
||||||
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
|
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
|
||||||
// contracts used in the Istanbul release.
|
// contracts used in the Istanbul release.
|
||||||
var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsIstanbul = PrecompiledContracts{
|
||||||
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||||
|
|
@ -84,7 +88,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
|
||||||
|
|
||||||
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
|
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
|
||||||
// contracts used in the Berlin release.
|
// contracts used in the Berlin release.
|
||||||
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsBerlin = PrecompiledContracts{
|
||||||
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||||
|
|
@ -98,7 +102,7 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
|
||||||
|
|
||||||
// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
|
// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
|
||||||
// contracts used in the Cancun release.
|
// contracts used in the Cancun release.
|
||||||
var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsCancun = PrecompiledContracts{
|
||||||
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
common.BytesToAddress([]byte{0x1}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||||
|
|
@ -113,7 +117,7 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
|
||||||
|
|
||||||
// PrecompiledContractsPrague contains the set of pre-compiled Ethereum
|
// PrecompiledContractsPrague contains the set of pre-compiled Ethereum
|
||||||
// contracts used in the Prague release.
|
// contracts used in the Prague release.
|
||||||
var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsPrague = PrecompiledContracts{
|
||||||
common.BytesToAddress([]byte{0x01}): &ecrecover{},
|
common.BytesToAddress([]byte{0x01}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{0x02}): &sha256hash{},
|
common.BytesToAddress([]byte{0x02}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{0x03}): &ripemd160hash{},
|
common.BytesToAddress([]byte{0x03}): &ripemd160hash{},
|
||||||
|
|
@ -169,7 +173,31 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActivePrecompiles returns the precompiles enabled with the current configuration.
|
func activePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
||||||
|
switch {
|
||||||
|
case rules.IsVerkle:
|
||||||
|
return PrecompiledContractsVerkle
|
||||||
|
case rules.IsPrague:
|
||||||
|
return PrecompiledContractsPrague
|
||||||
|
case rules.IsCancun:
|
||||||
|
return PrecompiledContractsCancun
|
||||||
|
case rules.IsBerlin:
|
||||||
|
return PrecompiledContractsBerlin
|
||||||
|
case rules.IsIstanbul:
|
||||||
|
return PrecompiledContractsIstanbul
|
||||||
|
case rules.IsByzantium:
|
||||||
|
return PrecompiledContractsByzantium
|
||||||
|
default:
|
||||||
|
return PrecompiledContractsHomestead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration.
|
||||||
|
func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
||||||
|
return maps.Clone(activePrecompiledContracts(rules))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
|
||||||
func ActivePrecompiles(rules params.Rules) []common.Address {
|
func ActivePrecompiles(rules params.Rules) []common.Address {
|
||||||
switch {
|
switch {
|
||||||
case rules.IsPrague:
|
case rules.IsPrague:
|
||||||
|
|
|
||||||
|
|
@ -41,24 +41,7 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||||
var precompiles map[common.Address]PrecompiledContract
|
p, ok := evm.precompiles[addr]
|
||||||
switch {
|
|
||||||
case evm.chainRules.IsVerkle:
|
|
||||||
precompiles = PrecompiledContractsVerkle
|
|
||||||
case evm.chainRules.IsPrague:
|
|
||||||
precompiles = PrecompiledContractsPrague
|
|
||||||
case evm.chainRules.IsCancun:
|
|
||||||
precompiles = PrecompiledContractsCancun
|
|
||||||
case evm.chainRules.IsBerlin:
|
|
||||||
precompiles = PrecompiledContractsBerlin
|
|
||||||
case evm.chainRules.IsIstanbul:
|
|
||||||
precompiles = PrecompiledContractsIstanbul
|
|
||||||
case evm.chainRules.IsByzantium:
|
|
||||||
precompiles = PrecompiledContractsByzantium
|
|
||||||
default:
|
|
||||||
precompiles = PrecompiledContractsHomestead
|
|
||||||
}
|
|
||||||
p, ok := precompiles[addr]
|
|
||||||
return p, ok
|
return p, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,22 +112,13 @@ type EVM struct {
|
||||||
// available gas is calculated in gasCall* according to the 63/64 rule and later
|
// available gas is calculated in gasCall* according to the 63/64 rule and later
|
||||||
// applied in opCall*.
|
// applied in opCall*.
|
||||||
callGasTemp uint64
|
callGasTemp uint64
|
||||||
|
// precompiles holds the precompiled contracts for the current epoch
|
||||||
|
precompiles map[common.Address]PrecompiledContract
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
|
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
|
||||||
// only ever be used *once*.
|
// only ever be used *once*.
|
||||||
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
|
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
|
||||||
// If basefee tracking is disabled (eth_call, eth_estimateGas, etc), and no
|
|
||||||
// gas prices were specified, lower the basefee to 0 to avoid breaking EVM
|
|
||||||
// invariants (basefee < feecap)
|
|
||||||
if config.NoBaseFee {
|
|
||||||
if txCtx.GasPrice.BitLen() == 0 {
|
|
||||||
blockCtx.BaseFee = new(big.Int)
|
|
||||||
}
|
|
||||||
if txCtx.BlobFeeCap != nil && txCtx.BlobFeeCap.BitLen() == 0 {
|
|
||||||
blockCtx.BlobBaseFee = new(big.Int)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evm := &EVM{
|
evm := &EVM{
|
||||||
Context: blockCtx,
|
Context: blockCtx,
|
||||||
TxContext: txCtx,
|
TxContext: txCtx,
|
||||||
|
|
@ -153,10 +127,18 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
|
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
|
||||||
}
|
}
|
||||||
|
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||||
evm.interpreter = NewEVMInterpreter(evm)
|
evm.interpreter = NewEVMInterpreter(evm)
|
||||||
return evm
|
return evm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPrecompiles sets the precompiled contracts for the EVM.
|
||||||
|
// This method is only used through RPC calls.
|
||||||
|
// It is not thread-safe.
|
||||||
|
func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) {
|
||||||
|
evm.precompiles = precompiles
|
||||||
|
}
|
||||||
|
|
||||||
// Reset resets the EVM with a new transaction context.Reset
|
// Reset resets the EVM with a new transaction context.Reset
|
||||||
// This is not threadsafe and should only be done very cautiously.
|
// This is not threadsafe and should only be done very cautiously.
|
||||||
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
|
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
|
||||||
|
|
|
||||||
|
|
@ -221,8 +221,16 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
||||||
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
|
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
|
||||||
|
|
||||||
dirtyState = opts.State.Copy()
|
dirtyState = opts.State.Copy()
|
||||||
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
|
||||||
)
|
)
|
||||||
|
// Lower the basefee to 0 to avoid breaking EVM
|
||||||
|
// invariants (basefee < feecap).
|
||||||
|
if msgContext.GasPrice.Sign() == 0 {
|
||||||
|
evmContext.BaseFee = new(big.Int)
|
||||||
|
}
|
||||||
|
if msgContext.BlobFeeCap != nil && msgContext.BlobFeeCap.BitLen() == 0 {
|
||||||
|
evmContext.BlobBaseFee = new(big.Int)
|
||||||
|
}
|
||||||
|
evm := vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
||||||
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
|
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
|
||||||
// a dangling goroutine until the outer estimation finishes, create an internal
|
// a dangling goroutine until the outer estimation finishes, create an internal
|
||||||
// context for the lifetime of this method call.
|
// context for the lifetime of this method call.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -955,20 +956,31 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
// Apply the customization rules if required.
|
// Apply the customization rules if required.
|
||||||
if config != nil {
|
if config != nil {
|
||||||
if err := config.StateOverrides.Apply(statedb); err != nil {
|
config.BlockOverrides.Apply(&vmctx)
|
||||||
|
rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time)
|
||||||
|
|
||||||
|
precompiles := vm.ActivePrecompiledContracts(rules)
|
||||||
|
if err := config.StateOverrides.Apply(statedb, precompiles); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.BlockOverrides.Apply(&vmctx)
|
|
||||||
}
|
}
|
||||||
// Execute the trace
|
// Execute the trace
|
||||||
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
|
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
msg = args.ToMessage(vmctx.BaseFee)
|
msg = args.ToMessage(vmctx.BaseFee, true, true)
|
||||||
tx = args.ToTransaction()
|
tx = args.ToTransaction(types.LegacyTxType)
|
||||||
traceConfig *TraceConfig
|
traceConfig *TraceConfig
|
||||||
)
|
)
|
||||||
|
// Lower the basefee to 0 to avoid breaking EVM
|
||||||
|
// invariants (basefee < feecap).
|
||||||
|
if msg.GasPrice.Sign() == 0 {
|
||||||
|
vmctx.BaseFee = new(big.Int)
|
||||||
|
}
|
||||||
|
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||||
|
vmctx.BlobBaseFee = new(big.Int)
|
||||||
|
}
|
||||||
if config != nil {
|
if config != nil {
|
||||||
traceConfig = &config.TraceConfig
|
traceConfig = &config.TraceConfig
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
||||||
t.Fatalf("can't create new node: %v", err)
|
t.Fatalf("can't create new node: %v", err)
|
||||||
}
|
}
|
||||||
// Create Ethereum Service
|
// Create Ethereum Service
|
||||||
config := ðconfig.Config{Genesis: genesis}
|
config := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
|
||||||
ethservice, err := eth.New(n, config)
|
ethservice, err := eth.New(n, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new ethereum service: %v", err)
|
t.Fatalf("can't create new ethereum service: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -328,9 +328,9 @@ func (o BlockOverrides) MarshalJSON() ([]byte, error) {
|
||||||
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
||||||
Time hexutil.Uint64 `json:"time,omitempty"`
|
Time hexutil.Uint64 `json:"time,omitempty"`
|
||||||
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
|
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
|
||||||
Coinbase *common.Address `json:"coinbase,omitempty"`
|
Coinbase *common.Address `json:"feeRecipient,omitempty"`
|
||||||
Random *common.Hash `json:"random,omitempty"`
|
Random *common.Hash `json:"prevRandao,omitempty"`
|
||||||
BaseFee *hexutil.Big `json:"baseFee,omitempty"`
|
BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
output := override{
|
output := override{
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
||||||
t.Fatalf("can't create new node: %v", err)
|
t.Fatalf("can't create new node: %v", err)
|
||||||
}
|
}
|
||||||
// Create Ethereum Service
|
// Create Ethereum Service
|
||||||
config := ðconfig.Config{Genesis: genesis}
|
config := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
|
||||||
ethservice, err := eth.New(n, config)
|
ethservice, err := eth.New(n, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new ethereum service: %v", err)
|
t.Fatalf("can't create new ethereum service: %v", err)
|
||||||
|
|
@ -510,7 +510,7 @@ func TestBlockOverridesMarshal(t *testing.T) {
|
||||||
bo: BlockOverrides{
|
bo: BlockOverrides{
|
||||||
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
||||||
},
|
},
|
||||||
want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`,
|
want: `{"feeRecipient":"0x1111111111111111111111111111111111111111"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bo: BlockOverrides{
|
bo: BlockOverrides{
|
||||||
|
|
@ -520,7 +520,7 @@ func TestBlockOverridesMarshal(t *testing.T) {
|
||||||
GasLimit: 4,
|
GasLimit: 4,
|
||||||
BaseFee: big.NewInt(5),
|
BaseFee: big.NewInt(5),
|
||||||
},
|
},
|
||||||
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`,
|
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFeePerGas":"0x5"}`,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
marshalled, err := json.Marshal(&tt.bo)
|
marshalled, err := json.Marshal(&tt.bo)
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,7 @@ func newGQLService(t *testing.T, stack *node.Node, shanghai bool, gspec *core.Ge
|
||||||
TrieDirtyCache: 5,
|
TrieDirtyCache: 5,
|
||||||
TrieTimeout: 60 * time.Minute,
|
TrieTimeout: 60 * time.Minute,
|
||||||
SnapshotCache: 5,
|
SnapshotCache: 5,
|
||||||
|
RPCGasCap: 1000000,
|
||||||
StateScheme: rawdb.HashScheme,
|
StateScheme: rawdb.HashScheme,
|
||||||
}
|
}
|
||||||
var engine consensus.Engine = ethash.NewFaker()
|
var engine consensus.Engine = ethash.NewFaker()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -474,7 +475,7 @@ func (api *PersonalAccountAPI) signTransaction(ctx context.Context, args *Transa
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble the transaction and sign with the wallet
|
// Assemble the transaction and sign with the wallet
|
||||||
tx := args.ToTransaction()
|
tx := args.ToTransaction(types.LegacyTxType)
|
||||||
|
|
||||||
return wallet.SignTxWithPassphrase(account, passwd, tx, api.b.ChainConfig().ChainID)
|
return wallet.SignTxWithPassphrase(account, passwd, tx, api.b.ChainConfig().ChainID)
|
||||||
}
|
}
|
||||||
|
|
@ -523,7 +524,7 @@ func (api *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transac
|
||||||
return nil, errors.New("nonce not specified")
|
return nil, errors.New("nonce not specified")
|
||||||
}
|
}
|
||||||
// Before actually signing the transaction, ensure the transaction fee is reasonable.
|
// Before actually signing the transaction, ensure the transaction fee is reasonable.
|
||||||
tx := args.ToTransaction()
|
tx := args.ToTransaction(types.LegacyTxType)
|
||||||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
|
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -968,22 +969,54 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp
|
||||||
// if stateDiff is set, all diff will be applied first and then execute the call
|
// if stateDiff is set, all diff will be applied first and then execute the call
|
||||||
// message.
|
// message.
|
||||||
type OverrideAccount struct {
|
type OverrideAccount struct {
|
||||||
Nonce *hexutil.Uint64 `json:"nonce"`
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||||
Code *hexutil.Bytes `json:"code"`
|
Code *hexutil.Bytes `json:"code"`
|
||||||
Balance *hexutil.Big `json:"balance"`
|
Balance *hexutil.Big `json:"balance"`
|
||||||
State map[common.Hash]common.Hash `json:"state"`
|
State map[common.Hash]common.Hash `json:"state"`
|
||||||
StateDiff map[common.Hash]common.Hash `json:"stateDiff"`
|
StateDiff map[common.Hash]common.Hash `json:"stateDiff"`
|
||||||
|
MovePrecompileTo *common.Address `json:"movePrecompileToAddress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateOverride is the collection of overridden accounts.
|
// StateOverride is the collection of overridden accounts.
|
||||||
type StateOverride map[common.Address]OverrideAccount
|
type StateOverride map[common.Address]OverrideAccount
|
||||||
|
|
||||||
|
func (diff *StateOverride) has(address common.Address) bool {
|
||||||
|
_, ok := (*diff)[address]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// Apply overrides the fields of specified accounts into the given state.
|
// Apply overrides the fields of specified accounts into the given state.
|
||||||
func (diff *StateOverride) Apply(statedb *state.StateDB) error {
|
func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.PrecompiledContracts) error {
|
||||||
if diff == nil {
|
if diff == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Tracks destinations of precompiles that were moved.
|
||||||
|
dirtyAddrs := make(map[common.Address]struct{})
|
||||||
for addr, account := range *diff {
|
for addr, account := range *diff {
|
||||||
|
// If a precompile was moved to this address already, it can't be overridden.
|
||||||
|
if _, ok := dirtyAddrs[addr]; ok {
|
||||||
|
return fmt.Errorf("account %s has already been overridden by a precompile", addr.Hex())
|
||||||
|
}
|
||||||
|
p, isPrecompile := precompiles[addr]
|
||||||
|
// The MoveTo feature makes it possible to move a precompile
|
||||||
|
// code to another address. If the target address is another precompile
|
||||||
|
// the code for the latter is lost for this session.
|
||||||
|
// Note the destination account is not cleared upon move.
|
||||||
|
if account.MovePrecompileTo != nil {
|
||||||
|
if !isPrecompile {
|
||||||
|
return fmt.Errorf("account %s is not a precompile", addr.Hex())
|
||||||
|
}
|
||||||
|
// Refuse to move a precompile to an address that has been
|
||||||
|
// or will be overridden.
|
||||||
|
if diff.has(*account.MovePrecompileTo) {
|
||||||
|
return fmt.Errorf("account %s is already overridden", account.MovePrecompileTo.Hex())
|
||||||
|
}
|
||||||
|
precompiles[*account.MovePrecompileTo] = p
|
||||||
|
dirtyAddrs[*account.MovePrecompileTo] = struct{}{}
|
||||||
|
}
|
||||||
|
if isPrecompile {
|
||||||
|
delete(precompiles, addr)
|
||||||
|
}
|
||||||
// Override account nonce.
|
// Override account nonce.
|
||||||
if account.Nonce != nil {
|
if account.Nonce != nil {
|
||||||
statedb.SetNonce(addr, uint64(*account.Nonce))
|
statedb.SetNonce(addr, uint64(*account.Nonce))
|
||||||
|
|
@ -1020,47 +1053,80 @@ func (diff *StateOverride) Apply(statedb *state.StateDB) error {
|
||||||
|
|
||||||
// BlockOverrides is a set of header fields to override.
|
// BlockOverrides is a set of header fields to override.
|
||||||
type BlockOverrides struct {
|
type BlockOverrides struct {
|
||||||
Number *hexutil.Big
|
Number *hexutil.Big
|
||||||
Difficulty *hexutil.Big
|
Difficulty *hexutil.Big // No-op if we're simulating post-merge calls.
|
||||||
Time *hexutil.Uint64
|
Time *hexutil.Uint64
|
||||||
GasLimit *hexutil.Uint64
|
GasLimit *hexutil.Uint64
|
||||||
Coinbase *common.Address
|
FeeRecipient *common.Address
|
||||||
Random *common.Hash
|
PrevRandao *common.Hash
|
||||||
BaseFee *hexutil.Big
|
BaseFeePerGas *hexutil.Big
|
||||||
BlobBaseFee *hexutil.Big
|
BlobBaseFee *hexutil.Big
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply overrides the given header fields into the given block context.
|
// Apply overrides the given header fields into the given block context.
|
||||||
func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||||||
if diff == nil {
|
if o == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if diff.Number != nil {
|
if o.Number != nil {
|
||||||
blockCtx.BlockNumber = diff.Number.ToInt()
|
blockCtx.BlockNumber = o.Number.ToInt()
|
||||||
}
|
}
|
||||||
if diff.Difficulty != nil {
|
if o.Difficulty != nil {
|
||||||
blockCtx.Difficulty = diff.Difficulty.ToInt()
|
blockCtx.Difficulty = o.Difficulty.ToInt()
|
||||||
}
|
}
|
||||||
if diff.Time != nil {
|
if o.Time != nil {
|
||||||
blockCtx.Time = uint64(*diff.Time)
|
blockCtx.Time = uint64(*o.Time)
|
||||||
}
|
}
|
||||||
if diff.GasLimit != nil {
|
if o.GasLimit != nil {
|
||||||
blockCtx.GasLimit = uint64(*diff.GasLimit)
|
blockCtx.GasLimit = uint64(*o.GasLimit)
|
||||||
}
|
}
|
||||||
if diff.Coinbase != nil {
|
if o.FeeRecipient != nil {
|
||||||
blockCtx.Coinbase = *diff.Coinbase
|
blockCtx.Coinbase = *o.FeeRecipient
|
||||||
}
|
}
|
||||||
if diff.Random != nil {
|
if o.PrevRandao != nil {
|
||||||
blockCtx.Random = diff.Random
|
blockCtx.Random = o.PrevRandao
|
||||||
}
|
}
|
||||||
if diff.BaseFee != nil {
|
if o.BaseFeePerGas != nil {
|
||||||
blockCtx.BaseFee = diff.BaseFee.ToInt()
|
blockCtx.BaseFee = o.BaseFeePerGas.ToInt()
|
||||||
}
|
}
|
||||||
if diff.BlobBaseFee != nil {
|
if o.BlobBaseFee != nil {
|
||||||
blockCtx.BlobBaseFee = diff.BlobBaseFee.ToInt()
|
blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeHeader returns a new header object with the overridden
|
||||||
|
// fields.
|
||||||
|
// Note: MakeHeader ignores BlobBaseFee if set. That's because
|
||||||
|
// header has no such field.
|
||||||
|
func (o *BlockOverrides) MakeHeader(header *types.Header) *types.Header {
|
||||||
|
if o == nil {
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
h := types.CopyHeader(header)
|
||||||
|
if o.Number != nil {
|
||||||
|
h.Number = o.Number.ToInt()
|
||||||
|
}
|
||||||
|
if o.Difficulty != nil {
|
||||||
|
h.Difficulty = o.Difficulty.ToInt()
|
||||||
|
}
|
||||||
|
if o.Time != nil {
|
||||||
|
h.Time = uint64(*o.Time)
|
||||||
|
}
|
||||||
|
if o.GasLimit != nil {
|
||||||
|
h.GasLimit = uint64(*o.GasLimit)
|
||||||
|
}
|
||||||
|
if o.FeeRecipient != nil {
|
||||||
|
h.Coinbase = *o.FeeRecipient
|
||||||
|
}
|
||||||
|
if o.PrevRandao != nil {
|
||||||
|
h.MixDigest = *o.PrevRandao
|
||||||
|
}
|
||||||
|
if o.BaseFeePerGas != nil {
|
||||||
|
h.BaseFee = o.BaseFeePerGas.ToInt()
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
// ChainContextBackend provides methods required to implement ChainContext.
|
// ChainContextBackend provides methods required to implement ChainContext.
|
||||||
type ChainContextBackend interface {
|
type ChainContextBackend interface {
|
||||||
Engine() consensus.Engine
|
Engine() consensus.Engine
|
||||||
|
|
@ -1094,9 +1160,16 @@ func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.H
|
||||||
}
|
}
|
||||||
|
|
||||||
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
||||||
if err := overrides.Apply(state); err != nil {
|
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||||||
|
if blockOverrides != nil {
|
||||||
|
blockOverrides.Apply(&blockCtx)
|
||||||
|
}
|
||||||
|
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
|
||||||
|
precompiles := maps.Clone(vm.ActivePrecompiledContracts(rules))
|
||||||
|
if err := overrides.Apply(state, precompiles); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup context so it may be cancelled the call has completed
|
// Setup context so it may be cancelled the call has completed
|
||||||
// or, in case of unmetered gas, setup a context with a timeout.
|
// or, in case of unmetered gas, setup a context with a timeout.
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
|
|
@ -1108,18 +1181,32 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
|
||||||
// Make sure the context is cancelled when the call has completed
|
// Make sure the context is cancelled when the call has completed
|
||||||
// this makes sure resources are cleaned up.
|
// this makes sure resources are cleaned up.
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
return applyMessage(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, precompiles, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext, vmConfig *vm.Config, precompiles vm.PrecompiledContracts, skipChecks bool) (*core.ExecutionResult, error) {
|
||||||
// Get a new instance of the EVM.
|
// Get a new instance of the EVM.
|
||||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil {
|
||||||
if blockOverrides != nil {
|
|
||||||
blockOverrides.Apply(&blockCtx)
|
|
||||||
}
|
|
||||||
if err := args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainID); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
msg := args.ToMessage(blockCtx.BaseFee)
|
msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks)
|
||||||
evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
|
// Lower the basefee to 0 to avoid breaking EVM
|
||||||
|
// invariants (basefee < feecap).
|
||||||
|
if msg.GasPrice.Sign() == 0 {
|
||||||
|
blockContext.BaseFee = new(big.Int)
|
||||||
|
}
|
||||||
|
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||||
|
blockContext.BlobBaseFee = new(big.Int)
|
||||||
|
}
|
||||||
|
evm := b.GetEVM(ctx, msg, state, header, vmConfig, blockContext)
|
||||||
|
if precompiles != nil {
|
||||||
|
evm.SetPrecompiles(precompiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyMessageWithEVM(ctx, evm, msg, state, timeout, gp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, state *state.StateDB, timeout time.Duration, gp *core.GasPool) (*core.ExecutionResult, error) {
|
||||||
// Wait for the context to be done and cancel the evm. Even if the
|
// Wait for the context to be done and cancel the evm. Even if the
|
||||||
// EVM has finished, cancelling may be done (repeatedly)
|
// EVM has finished, cancelling may be done (repeatedly)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -1128,7 +1215,6 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Execute the message.
|
// Execute the message.
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
|
||||||
result, err := core.ApplyMessage(evm, msg, gp)
|
result, err := core.ApplyMessage(evm, msg, gp)
|
||||||
if err := state.Error(); err != nil {
|
if err := state.Error(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1151,7 +1237,6 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
|
||||||
if state == nil || err != nil {
|
if state == nil || err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap)
|
return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1177,6 +1262,41 @@ func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockN
|
||||||
return result.Return(), result.Err
|
return result.Return(), result.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimulateV1 executes series of transactions on top of a base state.
|
||||||
|
// The transactions are packed into blocks. For each block, block header
|
||||||
|
// fields can be overridden. The state can also be overridden prior to
|
||||||
|
// execution of each block.
|
||||||
|
//
|
||||||
|
// Note, this function doesn't make any changes in the state/blockchain and is
|
||||||
|
// useful to execute and retrieve values.
|
||||||
|
func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||||||
|
if len(opts.BlockStateCalls) == 0 {
|
||||||
|
return nil, &invalidParamsError{message: "empty input"}
|
||||||
|
} else if len(opts.BlockStateCalls) > maxSimulateBlocks {
|
||||||
|
return nil, &clientLimitExceededError{message: "too many blocks"}
|
||||||
|
}
|
||||||
|
if blockNrOrHash == nil {
|
||||||
|
n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||||
|
blockNrOrHash = &n
|
||||||
|
}
|
||||||
|
state, base, err := api.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash)
|
||||||
|
if state == nil || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sim := &simulator{
|
||||||
|
b: api.b,
|
||||||
|
state: state,
|
||||||
|
base: base,
|
||||||
|
chainConfig: api.b.ChainConfig(),
|
||||||
|
// Each tx and all the series of txes shouldn't consume more gas than cap
|
||||||
|
gp: new(core.GasPool).AddGas(api.b.RPCGasCap()),
|
||||||
|
traceTransfers: opts.TraceTransfers,
|
||||||
|
validate: opts.Validation,
|
||||||
|
fullTx: opts.ReturnFullTransactions,
|
||||||
|
}
|
||||||
|
return sim.execute(ctx, opts.BlockStateCalls)
|
||||||
|
}
|
||||||
|
|
||||||
// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run
|
// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run
|
||||||
// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if
|
// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if
|
||||||
// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &
|
// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &
|
||||||
|
|
@ -1187,7 +1307,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
|
||||||
if state == nil || err != nil {
|
if state == nil || err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if err = overrides.Apply(state); err != nil {
|
if err := overrides.Apply(state, nil); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
// Construct the gas estimator option from the user input
|
// Construct the gas estimator option from the user input
|
||||||
|
|
@ -1206,7 +1326,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
|
||||||
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
|
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
call := args.ToMessage(header.BaseFee)
|
call := args.ToMessage(header.BaseFee, true, true)
|
||||||
|
|
||||||
// Run the gas estimation and wrap any revertals into a custom return
|
// Run the gas estimation and wrap any revertals into a custom return
|
||||||
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
|
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
|
||||||
|
|
@ -1546,15 +1666,23 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
||||||
statedb := db.Copy()
|
statedb := db.Copy()
|
||||||
// Set the accesslist to the last al
|
// Set the accesslist to the last al
|
||||||
args.AccessList = &accessList
|
args.AccessList = &accessList
|
||||||
msg := args.ToMessage(header.BaseFee)
|
msg := args.ToMessage(header.BaseFee, true, true)
|
||||||
|
|
||||||
// Apply the transaction with the access list tracer
|
// Apply the transaction with the access list tracer
|
||||||
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
|
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
|
||||||
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
|
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
|
||||||
vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil)
|
vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil)
|
||||||
|
// Lower the basefee to 0 to avoid breaking EVM
|
||||||
|
// invariants (basefee < feecap).
|
||||||
|
if msg.GasPrice.Sign() == 0 {
|
||||||
|
vmenv.Context.BaseFee = new(big.Int)
|
||||||
|
}
|
||||||
|
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||||||
|
vmenv.Context.BlobBaseFee = new(big.Int)
|
||||||
|
}
|
||||||
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction().Hash(), err)
|
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).Hash(), err)
|
||||||
}
|
}
|
||||||
if tracer.Equal(prevTracer) {
|
if tracer.Equal(prevTracer) {
|
||||||
return accessList, res.UsedGas, res.Err, nil
|
return accessList, res.UsedGas, res.Err, nil
|
||||||
|
|
@ -1823,7 +1951,7 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
// Assemble the transaction and sign with the wallet
|
// Assemble the transaction and sign with the wallet
|
||||||
tx := args.ToTransaction()
|
tx := args.ToTransaction(types.LegacyTxType)
|
||||||
|
|
||||||
signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
|
signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1843,7 +1971,7 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble the transaction and obtain rlp
|
// Assemble the transaction and obtain rlp
|
||||||
tx := args.ToTransaction()
|
tx := args.ToTransaction(types.LegacyTxType)
|
||||||
data, err := tx.MarshalBinary()
|
data, err := tx.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1911,7 +2039,7 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Before actually sign the transaction, ensure the transaction fee is reasonable.
|
// Before actually sign the transaction, ensure the transaction fee is reasonable.
|
||||||
tx := args.ToTransaction()
|
tx := args.ToTransaction(types.LegacyTxType)
|
||||||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
|
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1969,7 +2097,7 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
|
||||||
if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {
|
if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
matchTx := sendArgs.ToTransaction()
|
matchTx := sendArgs.ToTransaction(types.LegacyTxType)
|
||||||
|
|
||||||
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
|
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
|
||||||
var price = matchTx.GasPrice()
|
var price = matchTx.GasPrice()
|
||||||
|
|
@ -1999,7 +2127,7 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
|
||||||
if gasLimit != nil && *gasLimit != 0 {
|
if gasLimit != nil && *gasLimit != 0 {
|
||||||
sendArgs.Gas = gasLimit
|
sendArgs.Gas = gasLimit
|
||||||
}
|
}
|
||||||
signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction())
|
signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -17,10 +17,12 @@
|
||||||
package ethapi
|
package ethapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -76,3 +78,93 @@ func (e *TxIndexingError) ErrorCode() int {
|
||||||
|
|
||||||
// ErrorData returns the hex encoded revert reason.
|
// ErrorData returns the hex encoded revert reason.
|
||||||
func (e *TxIndexingError) ErrorData() interface{} { return "transaction indexing is in progress" }
|
func (e *TxIndexingError) ErrorData() interface{} { return "transaction indexing is in progress" }
|
||||||
|
|
||||||
|
type callError struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type invalidTxError struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *invalidTxError) Error() string { return e.Message }
|
||||||
|
func (e *invalidTxError) ErrorCode() int { return e.Code }
|
||||||
|
|
||||||
|
const (
|
||||||
|
errCodeNonceTooHigh = -38011
|
||||||
|
errCodeNonceTooLow = -38010
|
||||||
|
errCodeIntrinsicGas = -38013
|
||||||
|
errCodeInsufficientFunds = -38014
|
||||||
|
errCodeBlockGasLimitReached = -38015
|
||||||
|
errCodeBlockNumberInvalid = -38020
|
||||||
|
errCodeBlockTimestampInvalid = -38021
|
||||||
|
errCodeSenderIsNotEOA = -38024
|
||||||
|
errCodeMaxInitCodeSizeExceeded = -38025
|
||||||
|
errCodeClientLimitExceeded = -38026
|
||||||
|
errCodeInternalError = -32603
|
||||||
|
errCodeInvalidParams = -32602
|
||||||
|
errCodeReverted = -32000
|
||||||
|
errCodeVMError = -32015
|
||||||
|
)
|
||||||
|
|
||||||
|
func txValidationError(err error) *invalidTxError {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, core.ErrNonceTooHigh):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeNonceTooHigh}
|
||||||
|
case errors.Is(err, core.ErrNonceTooLow):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeNonceTooLow}
|
||||||
|
case errors.Is(err, core.ErrSenderNoEOA):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeSenderIsNotEOA}
|
||||||
|
case errors.Is(err, core.ErrFeeCapVeryHigh):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
|
||||||
|
case errors.Is(err, core.ErrTipVeryHigh):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
|
||||||
|
case errors.Is(err, core.ErrTipAboveFeeCap):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
|
||||||
|
case errors.Is(err, core.ErrFeeCapTooLow):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
|
||||||
|
case errors.Is(err, core.ErrInsufficientFunds):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds}
|
||||||
|
case errors.Is(err, core.ErrIntrinsicGas):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeIntrinsicGas}
|
||||||
|
case errors.Is(err, core.ErrInsufficientFundsForTransfer):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds}
|
||||||
|
case errors.Is(err, core.ErrMaxInitCodeSizeExceeded):
|
||||||
|
return &invalidTxError{Message: err.Error(), Code: errCodeMaxInitCodeSizeExceeded}
|
||||||
|
}
|
||||||
|
return &invalidTxError{
|
||||||
|
Message: err.Error(),
|
||||||
|
Code: errCodeInternalError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type invalidParamsError struct{ message string }
|
||||||
|
|
||||||
|
func (e *invalidParamsError) Error() string { return e.message }
|
||||||
|
func (e *invalidParamsError) ErrorCode() int { return errCodeInvalidParams }
|
||||||
|
|
||||||
|
type clientLimitExceededError struct{ message string }
|
||||||
|
|
||||||
|
func (e *clientLimitExceededError) Error() string { return e.message }
|
||||||
|
func (e *clientLimitExceededError) ErrorCode() int { return errCodeClientLimitExceeded }
|
||||||
|
|
||||||
|
type invalidBlockNumberError struct{ message string }
|
||||||
|
|
||||||
|
func (e *invalidBlockNumberError) Error() string { return e.message }
|
||||||
|
func (e *invalidBlockNumberError) ErrorCode() int { return errCodeBlockNumberInvalid }
|
||||||
|
|
||||||
|
type invalidBlockTimestampError struct{ message string }
|
||||||
|
|
||||||
|
func (e *invalidBlockTimestampError) Error() string { return e.message }
|
||||||
|
func (e *invalidBlockTimestampError) ErrorCode() int { return errCodeBlockTimestampInvalid }
|
||||||
|
|
||||||
|
type blockGasLimitReachedError struct{ message string }
|
||||||
|
|
||||||
|
func (e *blockGasLimitReachedError) Error() string { return e.message }
|
||||||
|
func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached }
|
||||||
|
|
|
||||||
151
internal/ethapi/logtracer.go
Normal file
151
internal/ethapi/logtracer.go
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// keccak256("Transfer(address,address,uint256)")
|
||||||
|
transferTopic = common.HexToHash("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||||
|
// ERC-7528
|
||||||
|
transferAddress = common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE")
|
||||||
|
)
|
||||||
|
|
||||||
|
// tracer is a simple tracer that records all logs and
|
||||||
|
// ether transfers. Transfers are recorded as if they
|
||||||
|
// were logs. Transfer events include:
|
||||||
|
// - tx value
|
||||||
|
// - call value
|
||||||
|
// - self destructs
|
||||||
|
//
|
||||||
|
// The log format for a transfer is:
|
||||||
|
// - address: 0x0000000000000000000000000000000000000000
|
||||||
|
// - data: Value
|
||||||
|
// - topics:
|
||||||
|
// - Transfer(address,address,uint256)
|
||||||
|
// - Sender address
|
||||||
|
// - Recipient address
|
||||||
|
type tracer struct {
|
||||||
|
// logs keeps logs for all open call frames.
|
||||||
|
// This lets us clear logs for failed calls.
|
||||||
|
logs [][]*types.Log
|
||||||
|
count int
|
||||||
|
traceTransfers bool
|
||||||
|
blockNumber uint64
|
||||||
|
blockHash common.Hash
|
||||||
|
txHash common.Hash
|
||||||
|
txIdx uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIndex uint) *tracer {
|
||||||
|
return &tracer{
|
||||||
|
traceTransfers: traceTransfers,
|
||||||
|
blockNumber: blockNumber,
|
||||||
|
blockHash: blockHash,
|
||||||
|
txHash: txHash,
|
||||||
|
txIdx: txIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) Hooks() *tracing.Hooks {
|
||||||
|
return &tracing.Hooks{
|
||||||
|
OnEnter: t.onEnter,
|
||||||
|
OnExit: t.onExit,
|
||||||
|
OnLog: t.onLog,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) onEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||||
|
t.logs = append(t.logs, make([]*types.Log, 0))
|
||||||
|
if vm.OpCode(typ) != vm.DELEGATECALL && value != nil && value.Cmp(common.Big0) > 0 {
|
||||||
|
t.captureTransfer(from, to, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) onExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
|
||||||
|
if depth == 0 {
|
||||||
|
t.onEnd(reverted)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := len(t.logs)
|
||||||
|
if size <= 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// pop call
|
||||||
|
call := t.logs[size-1]
|
||||||
|
t.logs = t.logs[:size-1]
|
||||||
|
size--
|
||||||
|
|
||||||
|
// Clear logs if call failed.
|
||||||
|
if !reverted {
|
||||||
|
t.logs[size-1] = append(t.logs[size-1], call...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) onEnd(reverted bool) {
|
||||||
|
if reverted {
|
||||||
|
t.logs[0] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) onLog(log *types.Log) {
|
||||||
|
t.captureLog(log.Address, log.Topics, log.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) captureLog(address common.Address, topics []common.Hash, data []byte) {
|
||||||
|
t.logs[len(t.logs)-1] = append(t.logs[len(t.logs)-1], &types.Log{
|
||||||
|
Address: address,
|
||||||
|
Topics: topics,
|
||||||
|
Data: data,
|
||||||
|
BlockNumber: t.blockNumber,
|
||||||
|
BlockHash: t.blockHash,
|
||||||
|
TxHash: t.txHash,
|
||||||
|
TxIndex: t.txIdx,
|
||||||
|
Index: uint(t.count),
|
||||||
|
})
|
||||||
|
t.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) captureTransfer(from, to common.Address, value *big.Int) {
|
||||||
|
if !t.traceTransfers {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
topics := []common.Hash{
|
||||||
|
transferTopic,
|
||||||
|
common.BytesToHash(from.Bytes()),
|
||||||
|
common.BytesToHash(to.Bytes()),
|
||||||
|
}
|
||||||
|
t.captureLog(transferAddress, topics, common.BigToHash(value).Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset prepares the tracer for the next transaction.
|
||||||
|
func (t *tracer) reset(txHash common.Hash, txIdx uint) {
|
||||||
|
t.logs = nil
|
||||||
|
t.txHash = txHash
|
||||||
|
t.txIdx = txIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) Logs() []*types.Log {
|
||||||
|
return t.logs[0]
|
||||||
|
}
|
||||||
418
internal/ethapi/simulate.go
Normal file
418
internal/ethapi/simulate.go
Normal file
|
|
@ -0,0 +1,418 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// maxSimulateBlocks is the maximum number of blocks that can be simulated
|
||||||
|
// in a single request.
|
||||||
|
maxSimulateBlocks = 256
|
||||||
|
|
||||||
|
// timestampIncrement is the default increment between block timestamps.
|
||||||
|
timestampIncrement = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// simBlock is a batch of calls to be simulated sequentially.
|
||||||
|
type simBlock struct {
|
||||||
|
BlockOverrides *BlockOverrides
|
||||||
|
StateOverrides *StateOverride
|
||||||
|
Calls []TransactionArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// simCallResult is the result of a simulated call.
|
||||||
|
type simCallResult struct {
|
||||||
|
ReturnValue hexutil.Bytes `json:"returnData"`
|
||||||
|
Logs []*types.Log `json:"logs"`
|
||||||
|
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||||
|
Status hexutil.Uint64 `json:"status"`
|
||||||
|
Error *callError `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *simCallResult) MarshalJSON() ([]byte, error) {
|
||||||
|
type callResultAlias simCallResult
|
||||||
|
// Marshal logs to be an empty array instead of nil when empty
|
||||||
|
if r.Logs == nil {
|
||||||
|
r.Logs = []*types.Log{}
|
||||||
|
}
|
||||||
|
return json.Marshal((*callResultAlias)(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// simOpts are the inputs to eth_simulateV1.
|
||||||
|
type simOpts struct {
|
||||||
|
BlockStateCalls []simBlock
|
||||||
|
TraceTransfers bool
|
||||||
|
Validation bool
|
||||||
|
ReturnFullTransactions bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulator is a stateful object that simulates a series of blocks.
|
||||||
|
// it is not safe for concurrent use.
|
||||||
|
type simulator struct {
|
||||||
|
b Backend
|
||||||
|
state *state.StateDB
|
||||||
|
base *types.Header
|
||||||
|
chainConfig *params.ChainConfig
|
||||||
|
gp *core.GasPool
|
||||||
|
traceTransfers bool
|
||||||
|
validate bool
|
||||||
|
fullTx bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute runs the simulation of a series of blocks.
|
||||||
|
func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[string]interface{}, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
cancel context.CancelFunc
|
||||||
|
timeout = sim.b.RPCEVMTimeout()
|
||||||
|
)
|
||||||
|
if timeout > 0 {
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
|
} else {
|
||||||
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
|
}
|
||||||
|
// Make sure the context is cancelled when the call has completed
|
||||||
|
// this makes sure resources are cleaned up.
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
blocks, err = sim.sanitizeChain(blocks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Prepare block headers with preliminary fields for the response.
|
||||||
|
headers, err := sim.makeHeaders(blocks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
results = make([]map[string]interface{}, len(blocks))
|
||||||
|
parent = sim.base
|
||||||
|
// Assume same total difficulty for all simulated blocks.
|
||||||
|
td = sim.b.GetTd(ctx, sim.base.Hash())
|
||||||
|
)
|
||||||
|
for bi, block := range blocks {
|
||||||
|
result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
enc := RPCMarshalBlock(result, true, sim.fullTx, sim.chainConfig)
|
||||||
|
enc["totalDifficulty"] = (*hexutil.Big)(td)
|
||||||
|
enc["calls"] = callResults
|
||||||
|
results[bi] = enc
|
||||||
|
|
||||||
|
parent = headers[bi]
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, error) {
|
||||||
|
// Set header fields that depend only on parent block.
|
||||||
|
// Parent hash is needed for evm.GetHashFn to work.
|
||||||
|
header.ParentHash = parent.Hash()
|
||||||
|
if sim.chainConfig.IsLondon(header.Number) {
|
||||||
|
// In non-validation mode base fee is set to 0 if it is not overridden.
|
||||||
|
// This is because it creates an edge case in EVM where gasPrice < baseFee.
|
||||||
|
// Base fee could have been overridden.
|
||||||
|
if header.BaseFee == nil {
|
||||||
|
if sim.validate {
|
||||||
|
header.BaseFee = eip1559.CalcBaseFee(sim.chainConfig, parent)
|
||||||
|
} else {
|
||||||
|
header.BaseFee = big.NewInt(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sim.chainConfig.IsCancun(header.Number, header.Time) {
|
||||||
|
var excess uint64
|
||||||
|
if sim.chainConfig.IsCancun(parent.Number, parent.Time) {
|
||||||
|
excess = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed)
|
||||||
|
} else {
|
||||||
|
excess = eip4844.CalcExcessBlobGas(0, 0)
|
||||||
|
}
|
||||||
|
header.ExcessBlobGas = &excess
|
||||||
|
}
|
||||||
|
blockContext := core.NewEVMBlockContext(header, sim.newSimulatedChainContext(ctx, headers), nil)
|
||||||
|
if block.BlockOverrides.BlobBaseFee != nil {
|
||||||
|
blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt()
|
||||||
|
}
|
||||||
|
precompiles := sim.activePrecompiles(sim.base)
|
||||||
|
// State overrides are applied prior to execution of a block
|
||||||
|
if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
gasUsed, blobGasUsed uint64
|
||||||
|
txes = make([]*types.Transaction, len(block.Calls))
|
||||||
|
callResults = make([]simCallResult, len(block.Calls))
|
||||||
|
receipts = make([]*types.Receipt, len(block.Calls))
|
||||||
|
// Block hash will be repaired after execution.
|
||||||
|
tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0)
|
||||||
|
vmConfig = &vm.Config{
|
||||||
|
NoBaseFee: !sim.validate,
|
||||||
|
Tracer: tracer.Hooks(),
|
||||||
|
}
|
||||||
|
evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, sim.chainConfig, *vmConfig)
|
||||||
|
)
|
||||||
|
sim.state.SetLogger(tracer.Hooks())
|
||||||
|
// It is possible to override precompiles with EVM bytecode, or
|
||||||
|
// move them to another address.
|
||||||
|
if precompiles != nil {
|
||||||
|
evm.SetPrecompiles(precompiles)
|
||||||
|
}
|
||||||
|
for i, call := range block.Calls {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tx := call.ToTransaction(types.DynamicFeeTxType)
|
||||||
|
txes[i] = tx
|
||||||
|
tracer.reset(tx.Hash(), uint(i))
|
||||||
|
// EoA check is always skipped, even in validation mode.
|
||||||
|
msg := call.ToMessage(header.BaseFee, !sim.validate, true)
|
||||||
|
evm.Reset(core.NewEVMTxContext(msg), sim.state)
|
||||||
|
result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, sim.gp)
|
||||||
|
if err != nil {
|
||||||
|
txErr := txValidationError(err)
|
||||||
|
return nil, nil, txErr
|
||||||
|
}
|
||||||
|
// Update the state with pending changes.
|
||||||
|
var root []byte
|
||||||
|
if sim.chainConfig.IsByzantium(blockContext.BlockNumber) {
|
||||||
|
sim.state.Finalise(true)
|
||||||
|
} else {
|
||||||
|
root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes()
|
||||||
|
}
|
||||||
|
gasUsed += result.UsedGas
|
||||||
|
receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root)
|
||||||
|
blobGasUsed += receipts[i].BlobGasUsed
|
||||||
|
logs := tracer.Logs()
|
||||||
|
callRes := simCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)}
|
||||||
|
if result.Failed() {
|
||||||
|
callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed)
|
||||||
|
if errors.Is(result.Err, vm.ErrExecutionReverted) {
|
||||||
|
// If the result contains a revert reason, try to unpack it.
|
||||||
|
revertErr := newRevertError(result.Revert())
|
||||||
|
callRes.Error = &callError{Message: revertErr.Error(), Code: errCodeReverted, Data: revertErr.ErrorData().(string)}
|
||||||
|
} else {
|
||||||
|
callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful)
|
||||||
|
}
|
||||||
|
callResults[i] = callRes
|
||||||
|
}
|
||||||
|
header.Root = sim.state.IntermediateRoot(true)
|
||||||
|
header.GasUsed = gasUsed
|
||||||
|
if sim.chainConfig.IsCancun(header.Number, header.Time) {
|
||||||
|
header.BlobGasUsed = &blobGasUsed
|
||||||
|
}
|
||||||
|
var withdrawals types.Withdrawals
|
||||||
|
if sim.chainConfig.IsShanghai(header.Number, header.Time) {
|
||||||
|
withdrawals = make([]*types.Withdrawal, 0)
|
||||||
|
}
|
||||||
|
b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil))
|
||||||
|
repairLogs(callResults, b.Hash())
|
||||||
|
return b, callResults, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// repairLogs updates the block hash in the logs present in the result of
|
||||||
|
// a simulated block. This is needed as during execution when logs are collected
|
||||||
|
// the block hash is not known.
|
||||||
|
func repairLogs(calls []simCallResult, hash common.Hash) {
|
||||||
|
for i := range calls {
|
||||||
|
for j := range calls[i].Logs {
|
||||||
|
calls[i].Logs[j].BlockHash = hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, header *types.Header, blockContext vm.BlockContext, gasUsed *uint64) error {
|
||||||
|
if call.Nonce == nil {
|
||||||
|
nonce := state.GetNonce(call.from())
|
||||||
|
call.Nonce = (*hexutil.Uint64)(&nonce)
|
||||||
|
}
|
||||||
|
// Let the call run wild unless explicitly specified.
|
||||||
|
if call.Gas == nil {
|
||||||
|
remaining := blockContext.GasLimit - *gasUsed
|
||||||
|
call.Gas = (*hexutil.Uint64)(&remaining)
|
||||||
|
}
|
||||||
|
if *gasUsed+uint64(*call.Gas) > blockContext.GasLimit {
|
||||||
|
return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)}
|
||||||
|
}
|
||||||
|
if err := call.CallDefaults(sim.gp.Gas(), header.BaseFee, sim.chainConfig.ChainID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sim *simulator) activePrecompiles(base *types.Header) vm.PrecompiledContracts {
|
||||||
|
var (
|
||||||
|
isMerge = (base.Difficulty.Sign() == 0)
|
||||||
|
rules = sim.chainConfig.Rules(base.Number, isMerge, base.Time)
|
||||||
|
)
|
||||||
|
return maps.Clone(vm.ActivePrecompiledContracts(rules))
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitizeChain checks the chain integrity. Specifically it checks that
|
||||||
|
// block numbers and timestamp are strictly increasing, setting default values
|
||||||
|
// when necessary. Gaps in block numbers are filled with empty blocks.
|
||||||
|
// Note: It modifies the block's override object.
|
||||||
|
func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) {
|
||||||
|
var (
|
||||||
|
res = make([]simBlock, 0, len(blocks))
|
||||||
|
base = sim.base
|
||||||
|
prevNumber = base.Number
|
||||||
|
prevTimestamp = base.Time
|
||||||
|
)
|
||||||
|
for _, block := range blocks {
|
||||||
|
if block.BlockOverrides == nil {
|
||||||
|
block.BlockOverrides = new(BlockOverrides)
|
||||||
|
}
|
||||||
|
if block.BlockOverrides.Number == nil {
|
||||||
|
n := new(big.Int).Add(prevNumber, big.NewInt(1))
|
||||||
|
block.BlockOverrides.Number = (*hexutil.Big)(n)
|
||||||
|
}
|
||||||
|
diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber)
|
||||||
|
if diff.Cmp(common.Big0) <= 0 {
|
||||||
|
return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)}
|
||||||
|
}
|
||||||
|
if total := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), base.Number); total.Cmp(big.NewInt(maxSimulateBlocks)) > 0 {
|
||||||
|
return nil, &clientLimitExceededError{message: "too many blocks"}
|
||||||
|
}
|
||||||
|
if diff.Cmp(big.NewInt(1)) > 0 {
|
||||||
|
// Fill the gap with empty blocks.
|
||||||
|
gap := new(big.Int).Sub(diff, big.NewInt(1))
|
||||||
|
// Assign block number to the empty blocks.
|
||||||
|
for i := uint64(0); i < gap.Uint64(); i++ {
|
||||||
|
n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1)))
|
||||||
|
t := prevTimestamp + timestampIncrement
|
||||||
|
b := simBlock{BlockOverrides: &BlockOverrides{Number: (*hexutil.Big)(n), Time: (*hexutil.Uint64)(&t)}}
|
||||||
|
prevTimestamp = t
|
||||||
|
res = append(res, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only append block after filling a potential gap.
|
||||||
|
prevNumber = block.BlockOverrides.Number.ToInt()
|
||||||
|
var t uint64
|
||||||
|
if block.BlockOverrides.Time == nil {
|
||||||
|
t = prevTimestamp + timestampIncrement
|
||||||
|
block.BlockOverrides.Time = (*hexutil.Uint64)(&t)
|
||||||
|
} else {
|
||||||
|
t = uint64(*block.BlockOverrides.Time)
|
||||||
|
if t <= prevTimestamp {
|
||||||
|
return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", t, prevTimestamp)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevTimestamp = t
|
||||||
|
res = append(res, block)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeHeaders makes header object with preliminary fields based on a simulated block.
|
||||||
|
// Some fields have to be filled post-execution.
|
||||||
|
// It assumes blocks are in order and numbers have been validated.
|
||||||
|
func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
|
||||||
|
var (
|
||||||
|
res = make([]*types.Header, len(blocks))
|
||||||
|
base = sim.base
|
||||||
|
header = base
|
||||||
|
)
|
||||||
|
for bi, block := range blocks {
|
||||||
|
if block.BlockOverrides == nil || block.BlockOverrides.Number == nil {
|
||||||
|
return nil, errors.New("empty block number")
|
||||||
|
}
|
||||||
|
overrides := block.BlockOverrides
|
||||||
|
|
||||||
|
var withdrawalsHash *common.Hash
|
||||||
|
if sim.chainConfig.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
|
||||||
|
withdrawalsHash = &types.EmptyWithdrawalsHash
|
||||||
|
}
|
||||||
|
var parentBeaconRoot *common.Hash
|
||||||
|
if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
|
||||||
|
parentBeaconRoot = &common.Hash{}
|
||||||
|
}
|
||||||
|
header = overrides.MakeHeader(&types.Header{
|
||||||
|
UncleHash: types.EmptyUncleHash,
|
||||||
|
ReceiptHash: types.EmptyReceiptsHash,
|
||||||
|
TxHash: types.EmptyTxsHash,
|
||||||
|
Coinbase: header.Coinbase,
|
||||||
|
Difficulty: header.Difficulty,
|
||||||
|
GasLimit: header.GasLimit,
|
||||||
|
WithdrawalsHash: withdrawalsHash,
|
||||||
|
ParentBeaconRoot: parentBeaconRoot,
|
||||||
|
})
|
||||||
|
res[bi] = header
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sim *simulator) newSimulatedChainContext(ctx context.Context, headers []*types.Header) *ChainContext {
|
||||||
|
return NewChainContext(ctx, &simBackend{base: sim.base, b: sim.b, headers: headers})
|
||||||
|
}
|
||||||
|
|
||||||
|
type simBackend struct {
|
||||||
|
b ChainContextBackend
|
||||||
|
base *types.Header
|
||||||
|
headers []*types.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *simBackend) Engine() consensus.Engine {
|
||||||
|
return b.b.Engine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *simBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
||||||
|
if uint64(number) == b.base.Number.Uint64() {
|
||||||
|
return b.base, nil
|
||||||
|
}
|
||||||
|
if uint64(number) < b.base.Number.Uint64() {
|
||||||
|
// Resolve canonical header.
|
||||||
|
return b.b.HeaderByNumber(ctx, number)
|
||||||
|
}
|
||||||
|
// Simulated block.
|
||||||
|
for _, header := range b.headers {
|
||||||
|
if header.Number.Uint64() == uint64(number) {
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("header not found")
|
||||||
|
}
|
||||||
120
internal/ethapi/simulate_test.go
Normal file
120
internal/ethapi/simulate_test.go
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimulateSanitizeBlockOrder(t *testing.T) {
|
||||||
|
type result struct {
|
||||||
|
number uint64
|
||||||
|
timestamp uint64
|
||||||
|
}
|
||||||
|
for i, tc := range []struct {
|
||||||
|
baseNumber int
|
||||||
|
baseTimestamp uint64
|
||||||
|
blocks []simBlock
|
||||||
|
expected []result
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{}, {}, {}},
|
||||||
|
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 53}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(13), Time: newUint64(70)}}, {}},
|
||||||
|
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 70}, {number: 14, timestamp: 71}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(11)}}, {BlockOverrides: &BlockOverrides{Number: newInt(14)}}, {}},
|
||||||
|
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 53}, {number: 14, timestamp: 54}, {number: 15, timestamp: 55}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(13)}}, {BlockOverrides: &BlockOverrides{Number: newInt(12)}}},
|
||||||
|
err: "block numbers must be in order: 12 <= 13",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(13), Time: newUint64(52)}}},
|
||||||
|
err: "block timestamps must be in order: 52 <= 52",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(11), Time: newUint64(60)}}, {BlockOverrides: &BlockOverrides{Number: newInt(12), Time: newUint64(55)}}},
|
||||||
|
err: "block timestamps must be in order: 55 <= 60",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseNumber: 10,
|
||||||
|
baseTimestamp: 50,
|
||||||
|
blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(11), Time: newUint64(60)}}, {BlockOverrides: &BlockOverrides{Number: newInt(13), Time: newUint64(61)}}},
|
||||||
|
err: "block timestamps must be in order: 61 <= 61",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
sim := &simulator{base: &types.Header{Number: big.NewInt(int64(tc.baseNumber)), Time: tc.baseTimestamp}}
|
||||||
|
res, err := sim.sanitizeChain(tc.blocks)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == tc.err {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
t.Fatalf("testcase %d: error mismatch. Want '%s', have '%s'", i, tc.err, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil && tc.err != "" {
|
||||||
|
t.Fatalf("testcase %d: expected err", i)
|
||||||
|
}
|
||||||
|
if len(res) != len(tc.expected) {
|
||||||
|
t.Errorf("testcase %d: mismatch number of blocks. Want %d, have %d", i, len(tc.expected), len(res))
|
||||||
|
}
|
||||||
|
for bi, b := range res {
|
||||||
|
if b.BlockOverrides == nil {
|
||||||
|
t.Fatalf("testcase %d: block overrides nil", i)
|
||||||
|
}
|
||||||
|
if b.BlockOverrides.Number == nil {
|
||||||
|
t.Fatalf("testcase %d: block number not set", i)
|
||||||
|
}
|
||||||
|
if b.BlockOverrides.Time == nil {
|
||||||
|
t.Fatalf("testcase %d: block time not set", i)
|
||||||
|
}
|
||||||
|
if uint64(*b.BlockOverrides.Time) != tc.expected[bi].timestamp {
|
||||||
|
t.Errorf("testcase %d: block timestamp mismatch. Want %d, have %d", i, tc.expected[bi].timestamp, uint64(*b.BlockOverrides.Time))
|
||||||
|
}
|
||||||
|
have := b.BlockOverrides.Number.ToInt().Uint64()
|
||||||
|
if have != tc.expected[bi].number {
|
||||||
|
t.Errorf("testcase %d: block number mismatch. Want %d, have %d", i, tc.expected[bi].number, have)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInt(n int64) *hexutil.Big {
|
||||||
|
return (*hexutil.Big)(big.NewInt(n))
|
||||||
|
}
|
||||||
|
|
@ -421,7 +421,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
|
||||||
// core evm. This method is used in calls and traces that do not require a real
|
// core evm. This method is used in calls and traces that do not require a real
|
||||||
// live transaction.
|
// live transaction.
|
||||||
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
|
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
|
||||||
func (args *TransactionArgs) ToMessage(baseFee *big.Int) *core.Message {
|
func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool) *core.Message {
|
||||||
var (
|
var (
|
||||||
gasPrice *big.Int
|
gasPrice *big.Int
|
||||||
gasFeeCap *big.Int
|
gasFeeCap *big.Int
|
||||||
|
|
@ -452,27 +452,42 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int) *core.Message {
|
||||||
accessList = *args.AccessList
|
accessList = *args.AccessList
|
||||||
}
|
}
|
||||||
return &core.Message{
|
return &core.Message{
|
||||||
From: args.from(),
|
From: args.from(),
|
||||||
To: args.To,
|
To: args.To,
|
||||||
Value: (*big.Int)(args.Value),
|
Value: (*big.Int)(args.Value),
|
||||||
GasLimit: uint64(*args.Gas),
|
Nonce: uint64(*args.Nonce),
|
||||||
GasPrice: gasPrice,
|
GasLimit: uint64(*args.Gas),
|
||||||
GasFeeCap: gasFeeCap,
|
GasPrice: gasPrice,
|
||||||
GasTipCap: gasTipCap,
|
GasFeeCap: gasFeeCap,
|
||||||
Data: args.data(),
|
GasTipCap: gasTipCap,
|
||||||
AccessList: accessList,
|
Data: args.data(),
|
||||||
BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
|
AccessList: accessList,
|
||||||
BlobHashes: args.BlobHashes,
|
BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
|
||||||
SkipAccountChecks: true,
|
BlobHashes: args.BlobHashes,
|
||||||
|
SkipNonceChecks: skipNonceCheck,
|
||||||
|
SkipFromEOACheck: skipEoACheck,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTransaction converts the arguments to a transaction.
|
// ToTransaction converts the arguments to a transaction.
|
||||||
// This assumes that setDefaults has been called.
|
// This assumes that setDefaults has been called.
|
||||||
func (args *TransactionArgs) ToTransaction() *types.Transaction {
|
func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction {
|
||||||
var data types.TxData
|
usedType := types.LegacyTxType
|
||||||
switch {
|
switch {
|
||||||
case args.BlobHashes != nil:
|
case args.BlobHashes != nil || defaultType == types.BlobTxType:
|
||||||
|
usedType = types.BlobTxType
|
||||||
|
case args.MaxFeePerGas != nil || defaultType == types.DynamicFeeTxType:
|
||||||
|
usedType = types.DynamicFeeTxType
|
||||||
|
case args.AccessList != nil || defaultType == types.AccessListTxType:
|
||||||
|
usedType = types.AccessListTxType
|
||||||
|
}
|
||||||
|
// Make it possible to default to newer tx, but use legacy if gasprice is provided
|
||||||
|
if args.GasPrice != nil {
|
||||||
|
usedType = types.LegacyTxType
|
||||||
|
}
|
||||||
|
var data types.TxData
|
||||||
|
switch usedType {
|
||||||
|
case types.BlobTxType:
|
||||||
al := types.AccessList{}
|
al := types.AccessList{}
|
||||||
if args.AccessList != nil {
|
if args.AccessList != nil {
|
||||||
al = *args.AccessList
|
al = *args.AccessList
|
||||||
|
|
@ -498,7 +513,7 @@ func (args *TransactionArgs) ToTransaction() *types.Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case args.MaxFeePerGas != nil:
|
case types.DynamicFeeTxType:
|
||||||
al := types.AccessList{}
|
al := types.AccessList{}
|
||||||
if args.AccessList != nil {
|
if args.AccessList != nil {
|
||||||
al = *args.AccessList
|
al = *args.AccessList
|
||||||
|
|
@ -515,7 +530,7 @@ func (args *TransactionArgs) ToTransaction() *types.Transaction {
|
||||||
AccessList: al,
|
AccessList: al,
|
||||||
}
|
}
|
||||||
|
|
||||||
case args.AccessList != nil:
|
case types.AccessListTxType:
|
||||||
data = &types.AccessListTx{
|
data = &types.AccessListTx{
|
||||||
To: args.To,
|
To: args.To,
|
||||||
ChainID: (*big.Int)(args.ChainID),
|
ChainID: (*big.Int)(args.ChainID),
|
||||||
|
|
|
||||||
|
|
@ -616,6 +616,12 @@ web3._extend({
|
||||||
params: 4,
|
params: 4,
|
||||||
inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null],
|
inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null],
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'simulateV1',
|
||||||
|
call: 'eth_simulateV1',
|
||||||
|
params: 2,
|
||||||
|
inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter],
|
||||||
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'getBlockReceipts',
|
name: 'getBlockReceipts',
|
||||||
call: 'eth_getBlockReceipts',
|
call: 'eth_getBlockReceipts',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue