Skip to content

Commit 4b236d6

Browse files
s1narjl493456442holiman
authored andcommitted
internal/ethapi: eth_multicall (ethereum#27720)
This is a successor PR to ethereum#25743. This PR is based on a new iteration of the spec: ethereum/execution-apis#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 <[email protected]> Co-authored-by: Martin Holst Swende <[email protected]>
1 parent 94a17ef commit 4b236d6

18 files changed

+2427
-148
lines changed

core/state_processor.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,14 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
147147
}
148148
*usedGas += result.UsedGas
149149

150+
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), nil
151+
}
152+
153+
// MakeReceipt generates the receipt object for a transaction given its execution result.
154+
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 {
150155
// Create a new receipt for the transaction, storing the intermediate root and gas used
151156
// by the tx.
152-
receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
157+
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
153158
if result.Failed() {
154159
receipt.Status = types.ReceiptStatusFailed
155160
} else {
@@ -164,7 +169,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
164169
}
165170

166171
// If the transaction created a contract, store the creation address in the receipt.
167-
if msg.To == nil {
172+
if tx.To() == nil {
168173
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
169174
}
170175

@@ -180,7 +185,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
180185
receipt.BlockHash = blockHash
181186
receipt.BlockNumber = blockNumber
182187
receipt.TransactionIndex = uint(statedb.TxIndex())
183-
return receipt, err
188+
return receipt
184189
}
185190

186191
// ApplyTransaction attempts to apply a transaction to the given state database

core/state_transition.go

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,27 +142,31 @@ type Message struct {
142142
BlobGasFeeCap *big.Int
143143
BlobHashes []common.Hash
144144

145-
// When SkipAccountChecks is true, the message nonce is not checked against the
146-
// account nonce in state. It also disables checking that the sender is an EOA.
145+
// When SkipNonceChecks is true, the message nonce is not checked against the
146+
// account nonce in state.
147147
// This field will be set to true for operations like RPC eth_call.
148-
SkipAccountChecks bool
148+
SkipNonceChecks bool
149+
150+
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
151+
SkipFromEOACheck bool
149152
}
150153

151154
// TransactionToMessage converts a transaction into a Message.
152155
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
153156
msg := &Message{
154-
Nonce: tx.Nonce(),
155-
GasLimit: tx.Gas(),
156-
GasPrice: new(big.Int).Set(tx.GasPrice()),
157-
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
158-
GasTipCap: new(big.Int).Set(tx.GasTipCap()),
159-
To: tx.To(),
160-
Value: tx.Value(),
161-
Data: tx.Data(),
162-
AccessList: tx.AccessList(),
163-
SkipAccountChecks: false,
164-
BlobHashes: tx.BlobHashes(),
165-
BlobGasFeeCap: tx.BlobGasFeeCap(),
157+
Nonce: tx.Nonce(),
158+
GasLimit: tx.Gas(),
159+
GasPrice: new(big.Int).Set(tx.GasPrice()),
160+
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
161+
GasTipCap: new(big.Int).Set(tx.GasTipCap()),
162+
To: tx.To(),
163+
Value: tx.Value(),
164+
Data: tx.Data(),
165+
AccessList: tx.AccessList(),
166+
SkipNonceChecks: false,
167+
SkipFromEOACheck: false,
168+
BlobHashes: tx.BlobHashes(),
169+
BlobGasFeeCap: tx.BlobGasFeeCap(),
166170
}
167171
// If baseFee provided, set gasPrice to effectiveGasPrice.
168172
if baseFee != nil {
@@ -280,7 +284,7 @@ func (st *StateTransition) buyGas() error {
280284
func (st *StateTransition) preCheck() error {
281285
// Only check transactions that are not fake
282286
msg := st.msg
283-
if !msg.SkipAccountChecks {
287+
if !msg.SkipNonceChecks {
284288
// Make sure this transaction's nonce is correct.
285289
stNonce := st.state.GetNonce(msg.From)
286290
if msgNonce := msg.Nonce; stNonce < msgNonce {
@@ -293,6 +297,8 @@ func (st *StateTransition) preCheck() error {
293297
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
294298
msg.From.Hex(), stNonce)
295299
}
300+
}
301+
if !msg.SkipFromEOACheck {
296302
// Make sure the sender is an EOA
297303
codeHash := st.state.GetCodeHash(msg.From)
298304
if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {

core/vm/contracts.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/binary"
2222
"errors"
2323
"fmt"
24+
"maps"
2425
"math/big"
2526

2627
"github.com/consensys/gnark-crypto/ecc"
@@ -46,9 +47,12 @@ type PrecompiledContract interface {
4647
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
4748
}
4849

50+
// PrecompiledContracts contains the precompiled contracts supported at the given fork.
51+
type PrecompiledContracts map[common.Address]PrecompiledContract
52+
4953
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
5054
// contracts used in the Frontier and Homestead releases.
51-
var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
55+
var PrecompiledContractsHomestead = PrecompiledContracts{
5256
common.BytesToAddress([]byte{0x1}): &ecrecover{},
5357
common.BytesToAddress([]byte{0x2}): &sha256hash{},
5458
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
@@ -57,7 +61,7 @@ var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
5761

5862
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
5963
// contracts used in the Byzantium release.
60-
var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
64+
var PrecompiledContractsByzantium = PrecompiledContracts{
6165
common.BytesToAddress([]byte{0x1}): &ecrecover{},
6266
common.BytesToAddress([]byte{0x2}): &sha256hash{},
6367
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
@@ -70,7 +74,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
7074

7175
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
7276
// contracts used in the Istanbul release.
73-
var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
77+
var PrecompiledContractsIstanbul = PrecompiledContracts{
7478
common.BytesToAddress([]byte{0x1}): &ecrecover{},
7579
common.BytesToAddress([]byte{0x2}): &sha256hash{},
7680
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
@@ -84,7 +88,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
8488

8589
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
8690
// contracts used in the Berlin release.
87-
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
91+
var PrecompiledContractsBerlin = PrecompiledContracts{
8892
common.BytesToAddress([]byte{0x1}): &ecrecover{},
8993
common.BytesToAddress([]byte{0x2}): &sha256hash{},
9094
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
@@ -98,7 +102,7 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
98102

99103
// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
100104
// contracts used in the Cancun release.
101-
var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
105+
var PrecompiledContractsCancun = PrecompiledContracts{
102106
common.BytesToAddress([]byte{0x1}): &ecrecover{},
103107
common.BytesToAddress([]byte{0x2}): &sha256hash{},
104108
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
@@ -113,7 +117,7 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
113117

114118
// PrecompiledContractsPrague contains the set of pre-compiled Ethereum
115119
// contracts used in the Prague release.
116-
var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
120+
var PrecompiledContractsPrague = PrecompiledContracts{
117121
common.BytesToAddress([]byte{0x01}): &ecrecover{},
118122
common.BytesToAddress([]byte{0x02}): &sha256hash{},
119123
common.BytesToAddress([]byte{0x03}): &ripemd160hash{},
@@ -169,7 +173,31 @@ func init() {
169173
}
170174
}
171175

172-
// ActivePrecompiles returns the precompiles enabled with the current configuration.
176+
func activePrecompiledContracts(rules params.Rules) PrecompiledContracts {
177+
switch {
178+
case rules.IsVerkle:
179+
return PrecompiledContractsVerkle
180+
case rules.IsPrague:
181+
return PrecompiledContractsPrague
182+
case rules.IsCancun:
183+
return PrecompiledContractsCancun
184+
case rules.IsBerlin:
185+
return PrecompiledContractsBerlin
186+
case rules.IsIstanbul:
187+
return PrecompiledContractsIstanbul
188+
case rules.IsByzantium:
189+
return PrecompiledContractsByzantium
190+
default:
191+
return PrecompiledContractsHomestead
192+
}
193+
}
194+
195+
// ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration.
196+
func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
197+
return maps.Clone(activePrecompiledContracts(rules))
198+
}
199+
200+
// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
173201
func ActivePrecompiles(rules params.Rules) []common.Address {
174202
switch {
175203
case rules.IsPrague:

core/vm/evm.go

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,7 @@ type (
4141
)
4242

4343
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
44-
var precompiles map[common.Address]PrecompiledContract
45-
switch {
46-
case evm.chainRules.IsVerkle:
47-
precompiles = PrecompiledContractsVerkle
48-
case evm.chainRules.IsPrague:
49-
precompiles = PrecompiledContractsPrague
50-
case evm.chainRules.IsCancun:
51-
precompiles = PrecompiledContractsCancun
52-
case evm.chainRules.IsBerlin:
53-
precompiles = PrecompiledContractsBerlin
54-
case evm.chainRules.IsIstanbul:
55-
precompiles = PrecompiledContractsIstanbul
56-
case evm.chainRules.IsByzantium:
57-
precompiles = PrecompiledContractsByzantium
58-
default:
59-
precompiles = PrecompiledContractsHomestead
60-
}
61-
p, ok := precompiles[addr]
44+
p, ok := evm.precompiles[addr]
6245
return p, ok
6346
}
6447

@@ -129,22 +112,13 @@ type EVM struct {
129112
// available gas is calculated in gasCall* according to the 63/64 rule and later
130113
// applied in opCall*.
131114
callGasTemp uint64
115+
// precompiles holds the precompiled contracts for the current epoch
116+
precompiles map[common.Address]PrecompiledContract
132117
}
133118

134119
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
135120
// only ever be used *once*.
136121
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
137-
// If basefee tracking is disabled (eth_call, eth_estimateGas, etc), and no
138-
// gas prices were specified, lower the basefee to 0 to avoid breaking EVM
139-
// invariants (basefee < feecap)
140-
if config.NoBaseFee {
141-
if txCtx.GasPrice.BitLen() == 0 {
142-
blockCtx.BaseFee = new(big.Int)
143-
}
144-
if txCtx.BlobFeeCap != nil && txCtx.BlobFeeCap.BitLen() == 0 {
145-
blockCtx.BlobBaseFee = new(big.Int)
146-
}
147-
}
148122
evm := &EVM{
149123
Context: blockCtx,
150124
TxContext: txCtx,
@@ -153,10 +127,18 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
153127
chainConfig: chainConfig,
154128
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
155129
}
130+
evm.precompiles = activePrecompiledContracts(evm.chainRules)
156131
evm.interpreter = NewEVMInterpreter(evm)
157132
return evm
158133
}
159134

135+
// SetPrecompiles sets the precompiled contracts for the EVM.
136+
// This method is only used through RPC calls.
137+
// It is not thread-safe.
138+
func (evm *EVM) SetPrecompiles(precompiles PrecompiledContracts) {
139+
evm.precompiles = precompiles
140+
}
141+
160142
// Reset resets the EVM with a new transaction context.Reset
161143
// This is not threadsafe and should only be done very cautiously.
162144
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {

eth/gasestimator/gasestimator.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,16 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
221221
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
222222

223223
dirtyState = opts.State.Copy()
224-
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
225224
)
225+
// Lower the basefee to 0 to avoid breaking EVM
226+
// invariants (basefee < feecap).
227+
if msgContext.GasPrice.Sign() == 0 {
228+
evmContext.BaseFee = new(big.Int)
229+
}
230+
if msgContext.BlobFeeCap != nil && msgContext.BlobFeeCap.BitLen() == 0 {
231+
evmContext.BlobBaseFee = new(big.Int)
232+
}
233+
evm := vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
226234
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
227235
// a dangling goroutine until the outer estimation finishes, create an internal
228236
// context for the lifetime of this method call.

eth/tracers/api.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/json"
2323
"errors"
2424
"fmt"
25+
"math/big"
2526
"os"
2627
"runtime"
2728
"sync"
@@ -955,20 +956,31 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
955956
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
956957
// Apply the customization rules if required.
957958
if config != nil {
958-
if err := config.StateOverrides.Apply(statedb); err != nil {
959+
config.BlockOverrides.Apply(&vmctx)
960+
rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time)
961+
962+
precompiles := vm.ActivePrecompiledContracts(rules)
963+
if err := config.StateOverrides.Apply(statedb, precompiles); err != nil {
959964
return nil, err
960965
}
961-
config.BlockOverrides.Apply(&vmctx)
962966
}
963967
// Execute the trace
964968
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
965969
return nil, err
966970
}
967971
var (
968-
msg = args.ToMessage(vmctx.BaseFee)
969-
tx = args.ToTransaction()
972+
msg = args.ToMessage(vmctx.BaseFee, true, true)
973+
tx = args.ToTransaction(types.LegacyTxType)
970974
traceConfig *TraceConfig
971975
)
976+
// Lower the basefee to 0 to avoid breaking EVM
977+
// invariants (basefee < feecap).
978+
if msg.GasPrice.Sign() == 0 {
979+
vmctx.BaseFee = new(big.Int)
980+
}
981+
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
982+
vmctx.BlobBaseFee = new(big.Int)
983+
}
972984
if config != nil {
973985
traceConfig = &config.TraceConfig
974986
}

ethclient/ethclient_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
219219
t.Fatalf("can't create new node: %v", err)
220220
}
221221
// Create Ethereum Service
222-
config := &ethconfig.Config{Genesis: genesis}
222+
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
223223
ethservice, err := eth.New(n, config)
224224
if err != nil {
225225
t.Fatalf("can't create new ethereum service: %v", err)

ethclient/gethclient/gethclient.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ func (o BlockOverrides) MarshalJSON() ([]byte, error) {
328328
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
329329
Time hexutil.Uint64 `json:"time,omitempty"`
330330
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
331-
Coinbase *common.Address `json:"coinbase,omitempty"`
332-
Random *common.Hash `json:"random,omitempty"`
333-
BaseFee *hexutil.Big `json:"baseFee,omitempty"`
331+
Coinbase *common.Address `json:"feeRecipient,omitempty"`
332+
Random *common.Hash `json:"prevRandao,omitempty"`
333+
BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"`
334334
}
335335

336336
output := override{

ethclient/gethclient/gethclient_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
5757
t.Fatalf("can't create new node: %v", err)
5858
}
5959
// Create Ethereum Service
60-
config := &ethconfig.Config{Genesis: genesis}
60+
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
6161
ethservice, err := eth.New(n, config)
6262
if err != nil {
6363
t.Fatalf("can't create new ethereum service: %v", err)
@@ -510,7 +510,7 @@ func TestBlockOverridesMarshal(t *testing.T) {
510510
bo: BlockOverrides{
511511
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
512512
},
513-
want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`,
513+
want: `{"feeRecipient":"0x1111111111111111111111111111111111111111"}`,
514514
},
515515
{
516516
bo: BlockOverrides{
@@ -520,7 +520,7 @@ func TestBlockOverridesMarshal(t *testing.T) {
520520
GasLimit: 4,
521521
BaseFee: big.NewInt(5),
522522
},
523-
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`,
523+
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFeePerGas":"0x5"}`,
524524
},
525525
} {
526526
marshalled, err := json.Marshal(&tt.bo)

graphql/graphql_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ func newGQLService(t *testing.T, stack *node.Node, shanghai bool, gspec *core.Ge
453453
TrieDirtyCache: 5,
454454
TrieTimeout: 60 * time.Minute,
455455
SnapshotCache: 5,
456+
RPCGasCap: 1000000,
456457
StateScheme: rawdb.HashScheme,
457458
}
458459
var engine consensus.Engine = ethash.NewFaker()

0 commit comments

Comments
 (0)