From 62d33d5a60fdfd88cddb5ebdde92f16e67ef2feb Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 26 May 2023 18:19:41 +0200 Subject: [PATCH 001/119] internal/ethapi: add multicall method --- internal/ethapi/api.go | 100 +++++++++++++++++++-- internal/ethapi/api_test.go | 172 ++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 5ebb6924efe4..1abca0aef990 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -997,6 +997,10 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash if err := overrides.Apply(state); err != nil { return nil, err } + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + if blockOverrides != nil { + blockOverrides.Apply(&blockCtx) + } // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. var cancel context.CancelFunc @@ -1008,17 +1012,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() + return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx) +} +func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext) (*core.ExecutionResult, error) { // Get a new instance of the EVM. - msg, err := args.ToMessage(globalGasCap, header.BaseFee) + msg, err := args.ToMessage(gp.Gas(), header.BaseFee) if err != nil { return nil, err } - blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) - if blockOverrides != nil { - blockOverrides.Apply(&blockCtx) - } - evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) + evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, blockContext) // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1028,7 +1031,6 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash }() // Execute the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { return nil, err @@ -1092,6 +1094,90 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO return result.Return(), result.Err } +// CallBatch is a batch of calls to be simulated sequentially. +type CallBatch struct { + BlockOverrides *BlockOverrides + StateOverrides *StateOverride + Calls []TransactionArgs +} + +type callResult struct { + ReturnValue hexutil.Bytes `json:"return"` + Logs []*types.Log `json:"logs"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Error string `json:"error"` +} + +// Multicall 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 (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash) ([][]callResult, error) { + state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + + // Setup context so it may be cancelled before the calls completed + // or, in case of unmetered gas, setup a context with a timeout. + var ( + cancel context.CancelFunc + timeout = s.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 ( + results = make([][]callResult, len(blocks)) + // Each tx and all the series of txes shouldn't consume more gas than cap + globalGasCap = s.b.RPCGasCap() + gp = new(core.GasPool).AddGas(globalGasCap) + ) + for bi, block := range blocks { + // State overrides are applied prior to execution of a block + if err := block.StateOverrides.Apply(state); err != nil { + return nil, err + } + blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + if block.BlockOverrides != nil { + block.BlockOverrides.Apply(&blockContext) + } + results[bi] = make([]callResult, len(block.Calls)) + for i, call := range block.Calls { + // Hack to get logs from statedb which stores logs by txhash. + txhash := common.BigToHash(big.NewInt(int64(i))) + state.SetTxContext(txhash, i) + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack it. + if len(result.Revert()) > 0 { + result.Err = newRevertError(result) + } + logs := state.GetLogs(txhash, blockContext.BlockNumber.Uint64(), common.Hash{}) + // Clear the garbage txhash that was filled in. + for _, l := range logs { + l.TxHash = common.Hash{} + } + callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} + if result.Err != nil { + callRes.Error = result.Err.Error() + } + results[bi][i] = callRes + } + } + return results, nil +} + func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 61024900b20f..54f986c8d367 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -596,6 +596,178 @@ func TestCall(t *testing.T) { } } +func TestMulticall(t *testing.T) { + t.Parallel() + // Initialize test accounts + var ( + accounts = newAccounts(3) + genBlocks = 10 + signer = types.HomesteadSigner{} + genesis = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + ) + api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, accounts[0].key) + b.AddTx(tx) + })) + var ( + randomAccounts = newAccounts(3) + ) + type res struct { + ReturnValue string `json:"return"` + Error string + Logs string + GasUsed string + } + var testSuite = []struct { + blocks []CallBatch + expectErr error + want [][]res + }{ + // First value transfer OK after state override, second one should succeed + // because of first transfer. + { + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(1000))}, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, { + From: &randomAccounts[1].addr, + To: &randomAccounts[2].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }}, + }}, + want: [][]res{{ + res{ + ReturnValue: "0x", + GasUsed: "0x5208", + }, + res{ + ReturnValue: "0x", + GasUsed: "0x5208", + }, + }, + }, + }, { + // Block overrides should work, each call is simulated on a different block number + blocks: []CallBatch{{ + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(10)), + }, + Calls: []TransactionArgs{ + { + From: &accounts[0].addr, + Input: &hexutil.Bytes{ + 0x43, // NUMBER + 0x60, 0x00, 0x52, // MSTORE offset 0 + 0x60, 0x20, 0x60, 0x00, 0xf3, + }, + }, + }, + }, { + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(11)), + }, + Calls: []TransactionArgs{{ + From: &accounts[1].addr, + Input: &hexutil.Bytes{ + 0x43, // NUMBER + 0x60, 0x00, 0x52, // MSTORE offset 0 + 0x60, 0x20, 0x60, 0x00, 0xf3, + }, + }}, + }}, + want: [][]res{{ + res{ + ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000a", + GasUsed: "0xe891", + }, + }, { + res{ + ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", + GasUsed: "0xe891", + }, + }}, + }, + // Test on solidity storage example. Set value in one call, read in next. + { + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: OverrideAccount{ + Code: hex2Bytes("608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033"), + }, + }, + Calls: []TransactionArgs{{ + // Set value to 5 + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Input: hex2Bytes("6057361d0000000000000000000000000000000000000000000000000000000000000005"), + }, { + // Read value + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Input: hex2Bytes("2e64cec1"), + }, + }, + }}, + want: [][]res{{{ + ReturnValue: "0x", + GasUsed: "0xaacc", + }, { + ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000005", + GasUsed: "0x5bb7", + }}}, + }, + } + + for i, tc := range testSuite { + result, err := api.Multicall(context.Background(), tc.blocks, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) + if tc.expectErr != nil { + if err == nil { + t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) + continue + } + if !errors.Is(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + } + continue + } + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + // Turn result into res-struct + var have [][]res + resBytes, _ := json.Marshal(result) + if err := json.Unmarshal(resBytes, &have); err != nil { + t.Fatalf("failed to unmarshal result: %v", err) + } + if !reflect.DeepEqual(have, tc.want) { + t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, tc.want) + } + } +} + type Account struct { key *ecdsa.PrivateKey addr common.Address From 80df50db7e4936f865065644f190c847cbe5e407 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 5 Jun 2023 13:09:38 +0200 Subject: [PATCH 002/119] add test case for logs --- internal/ethapi/api_test.go | 44 ++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 54f986c8d367..57de5c6ab4ff 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -628,21 +628,24 @@ func TestMulticall(t *testing.T) { })) var ( randomAccounts = newAccounts(3) + latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) ) type res struct { ReturnValue string `json:"return"` Error string - Logs string + Logs []types.Log GasUsed string } var testSuite = []struct { blocks []CallBatch + tag rpc.BlockNumberOrHash expectErr error want [][]res }{ // First value transfer OK after state override, second one should succeed // because of first transfer. { + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(1000))}, @@ -670,6 +673,7 @@ func TestMulticall(t *testing.T) { }, }, { // Block overrides should work, each call is simulated on a different block number + tag: latest, blocks: []CallBatch{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(10)), @@ -680,7 +684,7 @@ func TestMulticall(t *testing.T) { Input: &hexutil.Bytes{ 0x43, // NUMBER 0x60, 0x00, 0x52, // MSTORE offset 0 - 0x60, 0x20, 0x60, 0x00, 0xf3, + 0x60, 0x20, 0x60, 0x00, 0xf3, // RETURN }, }, }, @@ -711,6 +715,7 @@ func TestMulticall(t *testing.T) { }, // Test on solidity storage example. Set value in one call, read in next. { + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ @@ -738,10 +743,43 @@ func TestMulticall(t *testing.T) { GasUsed: "0x5bb7", }}}, }, + // Test logs output. + { + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: OverrideAccount{ + // Yul code: + // object "Test" { + // code { + // let hash:u256 := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + // log1(0, 0, hash) + // return (0, 0) + // } + // } + Code: hex2Bytes("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80600080a1600080f3"), + }, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + }}, + }}, + want: [][]res{{{ + ReturnValue: "0x", + Logs: []types.Log{{ + Address: randomAccounts[2].addr, + Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, + BlockNumber: 10, + Data: []byte{}, + }}, + GasUsed: "0x5508", + }}}, + }, } for i, tc := range testSuite { - result, err := api.Multicall(context.Background(), tc.blocks, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) + result, err := api.Multicall(context.Background(), tc.blocks, tc.tag) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) From c3f149425a77e8bc9e16cfa952ae686a769f063d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 6 Jun 2023 11:16:37 +0200 Subject: [PATCH 003/119] add ecrecover override --- internal/ethapi/api.go | 19 ++++++++----- internal/ethapi/api_test.go | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1abca0aef990..9a74777bad93 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1012,16 +1012,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx) + return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}) } -func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext) (*core.ExecutionResult, error) { +func doCall(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) (*core.ExecutionResult, error) { // Get a new instance of the EVM. msg, err := args.ToMessage(gp.Gas(), header.BaseFee) if err != nil { return nil, err } - evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, blockContext) + evm, vmError := b.GetEVM(ctx, msg, state, header, vmConfig, blockContext) // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1096,9 +1096,10 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // CallBatch is a batch of calls to be simulated sequentially. type CallBatch struct { - BlockOverrides *BlockOverrides - StateOverrides *StateOverride - Calls []TransactionArgs + BlockOverrides *BlockOverrides + StateOverrides *StateOverride + ECRecoverOverride *hexutil.Bytes // Override bytecode for ecrecover precompile. + Calls []TransactionArgs } type callResult struct { @@ -1146,6 +1147,10 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block if err := block.StateOverrides.Apply(state); err != nil { return nil, err } + // ECRecover replacement code will be fetched from statedb and executed as a normal EVM bytecode. + if block.ECRecoverOverride != nil { + state.SetCode(common.BytesToAddress([]byte{1}), *block.ECRecoverOverride) + } blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) if block.BlockOverrides != nil { block.BlockOverrides.Apply(&blockContext) @@ -1155,7 +1160,7 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block // Hack to get logs from statedb which stores logs by txhash. txhash := common.BigToHash(big.NewInt(int64(i))) state.SetTxContext(txhash, i) - result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext) + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, &vm.Config{NoBaseFee: true, DisableECRecover: true}) if err != nil { return nil, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 57de5c6ab4ff..17d3ce1e14e6 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -26,6 +26,7 @@ import ( "math/big" "reflect" "sort" + "strings" "testing" "time" @@ -776,6 +777,60 @@ func TestMulticall(t *testing.T) { GasUsed: "0x5508", }}}, }, + // Test ecrecover override + { + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: OverrideAccount{ + // Yul code that returns ecrecover(0, 0, 0, 0). + // object "Test" { + // code { + // // Free memory pointer + // let free_ptr := mload(0x40) + // + // // Initialize inputs with zeros + // mstore(free_ptr, 0) // Hash + // mstore(add(free_ptr, 0x20), 0) // v + // mstore(add(free_ptr, 0x40), 0) // r + // mstore(add(free_ptr, 0x60), 0) // s + // + // // Call ecrecover precompile (at address 1) with all 0 inputs + // let success := staticcall(gas(), 1, free_ptr, 0x80, free_ptr, 0x20) + // + // // Check if the call was successful + // if eq(success, 0) { + // revert(0, 0) + // } + // + // // Return the recovered address + // return(free_ptr, 0x14) + // } + // } + Code: hex2Bytes("6040516000815260006020820152600060408201526000606082015260208160808360015afa60008103603157600080fd5b601482f3"), + }, + }, + BlockOverrides: &BlockOverrides{}, + // Yul code that returns the address of the caller. + // object "Test" { + // code { + // let c := caller() + // mstore(0, c) + // return(0xc, 0x14) + // } + // } + ECRecoverOverride: hex2Bytes("33806000526014600cf3"), + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + }}, + }}, + want: [][]res{{{ + // Caller is in this case the contract that invokes ecrecover. + ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), + GasUsed: "0x52f6", + }}}, + }, } for i, tc := range testSuite { From a21920617b8616d1354a53ed93eb102fce18c570 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 6 Jun 2023 13:35:57 +0200 Subject: [PATCH 004/119] forgot to commit other files --- core/vm/evm.go | 6 ++++++ core/vm/interpreter.go | 1 + 2 files changed, 7 insertions(+) diff --git a/core/vm/evm.go b/core/vm/evm.go index 01017572d178..9f7735ac6dc5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -52,6 +52,12 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { default: precompiles = PrecompiledContractsHomestead } + // ECRecoverCode can be set only through RPC calls + if evm.Config.DisableECRecover { + if addr == common.BytesToAddress([]byte{1}) { + return nil, false + } + } p, ok := precompiles[addr] return p, ok } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 5b2082bc9e90..0f11f2839a83 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -26,6 +26,7 @@ import ( // Config are the configuration options for the Interpreter type Config struct { Tracer EVMLogger // Opcode logger + DisableECRecover bool // Debug flag to remove ecrecover from precompiles. Used for call simulations. NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled From 0285f81af381c4a10fb87e3980cb1a546962d015 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 6 Jun 2023 21:51:59 +0200 Subject: [PATCH 005/119] dont abort on call validation error --- internal/ethapi/api.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9a74777bad93..477ba4011863 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1162,7 +1162,8 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block state.SetTxContext(txhash, i) result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, &vm.Config{NoBaseFee: true, DisableECRecover: true}) if err != nil { - return nil, err + results[bi][i] = callResult{Error: err.Error()} + continue } // If the result contains a revert reason, try to unpack it. if len(result.Revert()) > 0 { From c301ce432d035b970ecf7254c8a458f2d9eee33d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 6 Jun 2023 21:54:17 +0200 Subject: [PATCH 006/119] add state buildup test --- internal/ethapi/api_test.go | 65 ++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 17d3ce1e14e6..ec307716853b 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -22,6 +22,7 @@ import ( "crypto/ecdsa" "encoding/json" "errors" + "fmt" "hash" "math/big" "reflect" @@ -628,7 +629,7 @@ func TestMulticall(t *testing.T) { b.AddTx(tx) })) var ( - randomAccounts = newAccounts(3) + randomAccounts = newAccounts(4) latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) ) type res struct { @@ -643,8 +644,9 @@ func TestMulticall(t *testing.T) { expectErr error want [][]res }{ - // First value transfer OK after state override, second one should succeed - // because of first transfer. + // State build-up over calls: + // First value transfer OK after state override. + // Second one should succeed because of first transfer. { tag: latest, blocks: []CallBatch{{ @@ -670,7 +672,62 @@ func TestMulticall(t *testing.T) { ReturnValue: "0x", GasUsed: "0x5208", }, - }, + }}, + }, { + // State build-up over blocks. + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(2000))}, + }, + Calls: []TransactionArgs{ + { + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, { + From: &randomAccounts[0].addr, + To: &randomAccounts[3].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + }, + }, { + StateOverrides: &StateOverride{ + randomAccounts[3].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(0))}, + }, + Calls: []TransactionArgs{ + { + From: &randomAccounts[1].addr, + To: &randomAccounts[2].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, { + From: &randomAccounts[3].addr, + To: &randomAccounts[2].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + }, + }}, + want: [][]res{ + { + res{ + ReturnValue: "0x", + GasUsed: "0x5208", + }, + res{ + ReturnValue: "0x", + GasUsed: "0x5208", + }, + }, { + res{ + ReturnValue: "0x", + GasUsed: "0x5208", + }, + res{ + ReturnValue: "0x", + GasUsed: "0x0", + Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), + }, + }, }, }, { // Block overrides should work, each call is simulated on a different block number From 922f72c6de48225916e03e104305ea4d741ed470 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 6 Jun 2023 22:11:03 +0200 Subject: [PATCH 007/119] test block override defaults to base --- internal/ethapi/api_test.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index ec307716853b..3cc27e28d003 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -734,7 +734,7 @@ func TestMulticall(t *testing.T) { tag: latest, blocks: []CallBatch{{ BlockOverrides: &BlockOverrides{ - Number: (*hexutil.Big)(big.NewInt(10)), + Number: (*hexutil.Big)(big.NewInt(11)), }, Calls: []TransactionArgs{ { @@ -748,7 +748,7 @@ func TestMulticall(t *testing.T) { }, }, { BlockOverrides: &BlockOverrides{ - Number: (*hexutil.Big)(big.NewInt(11)), + Number: (*hexutil.Big)(big.NewInt(12)), }, Calls: []TransactionArgs{{ From: &accounts[1].addr, @@ -758,15 +758,32 @@ func TestMulticall(t *testing.T) { 0x60, 0x20, 0x60, 0x00, 0xf3, }, }}, + }, { + // No block number override, should use latest + Calls: []TransactionArgs{ + { + From: &accounts[0].addr, + Input: &hexutil.Bytes{ + 0x43, // NUMBER + 0x60, 0x00, 0x52, // MSTORE offset 0 + 0x60, 0x20, 0x60, 0x00, 0xf3, // RETURN + }, + }, + }, }}, want: [][]res{{ res{ - ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000a", + ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", }, }, { res{ - ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", + ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", + GasUsed: "0xe891", + }, + }, { + res{ + ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000a", GasUsed: "0xe891", }, }}, From 80bcb16e8818332729fa259a5bc0a0d1c7d1218f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 6 Jun 2023 22:41:23 +0200 Subject: [PATCH 008/119] check block nums are in order --- internal/ethapi/api.go | 16 ++++++++---- internal/ethapi/api_test.go | 49 +++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 477ba4011863..d8103c926a7c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -919,7 +919,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { // BlockOverrides is a set of header fields to override. type BlockOverrides struct { Number *hexutil.Big - Difficulty *hexutil.Big + Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. Time *hexutil.Uint64 GasLimit *hexutil.Uint64 Coinbase *common.Address @@ -1141,8 +1141,18 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block // Each tx and all the series of txes shouldn't consume more gas than cap globalGasCap = s.b.RPCGasCap() gp = new(core.GasPool).AddGas(globalGasCap) + prevNumber = header.Number.Uint64() ) for bi, block := range blocks { + blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + if block.BlockOverrides != nil { + block.BlockOverrides.Apply(&blockContext) + } + // TODO: Consider hoisting this check up + if blockContext.BlockNumber.Uint64() < prevNumber { + return nil, fmt.Errorf("block numbers must be in order") + } + prevNumber = blockContext.BlockNumber.Uint64() // State overrides are applied prior to execution of a block if err := block.StateOverrides.Apply(state); err != nil { return nil, err @@ -1151,10 +1161,6 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block if block.ECRecoverOverride != nil { state.SetCode(common.BytesToAddress([]byte{1}), *block.ECRecoverOverride) } - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) - if block.BlockOverrides != nil { - block.BlockOverrides.Apply(&blockContext) - } results[bi] = make([]callResult, len(block.Calls)) for i, call := range block.Calls { // Hack to get logs from statedb which stores logs by txhash. diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 3cc27e28d003..12682afb33ec 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -758,18 +758,6 @@ func TestMulticall(t *testing.T) { 0x60, 0x20, 0x60, 0x00, 0xf3, }, }}, - }, { - // No block number override, should use latest - Calls: []TransactionArgs{ - { - From: &accounts[0].addr, - Input: &hexutil.Bytes{ - 0x43, // NUMBER - 0x60, 0x00, 0x52, // MSTORE offset 0 - 0x60, 0x20, 0x60, 0x00, 0xf3, // RETURN - }, - }, - }, }}, want: [][]res{{ res{ @@ -781,12 +769,38 @@ func TestMulticall(t *testing.T) { ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", }, + }}, + }, + // Block numbers must be in order. + { + tag: latest, + blocks: []CallBatch{{ + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(12)), + }, + Calls: []TransactionArgs{{ + From: &accounts[1].addr, + Input: &hexutil.Bytes{ + 0x43, // NUMBER + 0x60, 0x00, 0x52, // MSTORE offset 0 + 0x60, 0x20, 0x60, 0x00, 0xf3, // RETURN + }, + }}, }, { - res{ - ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000a", - GasUsed: "0xe891", + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(11)), }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + Input: &hexutil.Bytes{ + 0x43, // NUMBER + 0x60, 0x00, 0x52, // MSTORE offset 0 + 0x60, 0x20, 0x60, 0x00, 0xf3, // RETURN + }, + }}, }}, + want: [][]res{}, + expectErr: errors.New("block numbers must be in order"), }, // Test on solidity storage example. Set value in one call, read in next. { @@ -915,7 +929,10 @@ func TestMulticall(t *testing.T) { continue } if !errors.Is(err, tc.expectErr) { - t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + // Second try + if !reflect.DeepEqual(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + } } continue } From 4478f20135f0de2e35ca5e1db4999211e01b34bb Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 8 Jun 2023 10:43:58 +0200 Subject: [PATCH 009/119] add method to web3ext --- internal/web3ext/web3ext.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index a2fc3b5a9040..19dc2d2e1405 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -611,6 +611,12 @@ web3._extend({ params: 4, inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null], }), + new web3._extend.Method({ + name: 'multicall', + call: 'eth_multicall', + params: 2, + inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter], + }), ], properties: [ new web3._extend.Property({ From 2ae2fe50d01bad5b40f87df48bd779d423e34b2d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 8 Jun 2023 16:47:55 +0200 Subject: [PATCH 010/119] add transfers --- internal/ethapi/api.go | 19 +++++++++--- internal/ethapi/api_test.go | 62 ++++++++++++++++++++++++++++++++----- internal/web3ext/web3ext.go | 4 +-- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d8103c926a7c..8bf32260cb05 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1105,6 +1105,7 @@ type CallBatch struct { type callResult struct { ReturnValue hexutil.Bytes `json:"return"` Logs []*types.Log `json:"logs"` + Transfers []transfer `json:"transfers,omitempty"` GasUsed hexutil.Uint64 `json:"gasUsed"` Error string `json:"error"` } @@ -1116,12 +1117,11 @@ type callResult struct { // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash) ([][]callResult, error) { +func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([][]callResult, error) { state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } - // Setup context so it may be cancelled before the calls completed // or, in case of unmetered gas, setup a context with a timeout. var ( @@ -1166,7 +1166,14 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block // Hack to get logs from statedb which stores logs by txhash. txhash := common.BigToHash(big.NewInt(int64(i))) state.SetTxContext(txhash, i) - result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, &vm.Config{NoBaseFee: true, DisableECRecover: true}) + vmConfig := &vm.Config{NoBaseFee: true} + if block.ECRecoverOverride != nil { + vmConfig.DisableECRecover = true + } + if includeTransfers != nil && *includeTransfers { + vmConfig.Tracer = newTracer() + } + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) if err != nil { results[bi][i] = callResult{Error: err.Error()} continue @@ -1180,7 +1187,11 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block for _, l := range logs { l.TxHash = common.Hash{} } - callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} + var transfers []transfer + if includeTransfers != nil && *includeTransfers { + transfers = vmConfig.Tracer.(*tracer).Transfers() + } + callRes := callResult{ReturnValue: result.Return(), Logs: logs, Transfers: transfers, GasUsed: hexutil.Uint64(result.UsedGas)} if result.Err != nil { callRes.Error = result.Err.Error() } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 12682afb33ec..4da72ce6c900 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -629,20 +629,23 @@ func TestMulticall(t *testing.T) { b.AddTx(tx) })) var ( - randomAccounts = newAccounts(4) - latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + randomAccounts = newAccounts(4) + latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + includeTransfers = true ) type res struct { ReturnValue string `json:"return"` Error string Logs []types.Log GasUsed string + Transfers []transfer } var testSuite = []struct { - blocks []CallBatch - tag rpc.BlockNumberOrHash - expectErr error - want [][]res + blocks []CallBatch + tag rpc.BlockNumberOrHash + includeTransfers *bool + expectErr error + want [][]res }{ // State build-up over calls: // First value transfer OK after state override. @@ -919,10 +922,55 @@ func TestMulticall(t *testing.T) { GasUsed: "0x52f6", }}}, }, + // Test ether transfers. + { + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[0].addr: OverrideAccount{ + Balance: newRPCBalance(big.NewInt(100)), + // Yul code that transfers 100 wei to address passed in calldata: + // object "Test" { + // code { + // let recipient := shr(96, calldataload(0)) + // let value := 100 + // let success := call(gas(), recipient, value, 0, 0, 0, 0) + // if eq(success, 0) { + // revert(0, 0) + // } + // } + // } + Code: hex2Bytes("60003560601c606460008060008084865af160008103601d57600080fd5b505050"), + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[0].addr, + Value: (*hexutil.Big)(big.NewInt(50)), + Input: hex2Bytes(strings.TrimPrefix(randomAccounts[1].addr.String(), "0x")), + }}, + }}, + includeTransfers: &includeTransfers, + want: [][]res{{{ + ReturnValue: "0x", + GasUsed: "0xd984", + Transfers: []transfer{ + { + From: accounts[0].addr, + To: randomAccounts[0].addr, + Value: big.NewInt(50), + }, { + From: randomAccounts[0].addr, + To: randomAccounts[1].addr, + Value: big.NewInt(100), + }, + }, + }}}, + }, } for i, tc := range testSuite { - result, err := api.Multicall(context.Background(), tc.blocks, tc.tag) + result, err := api.Multicall(context.Background(), tc.blocks, tc.tag, tc.includeTransfers) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 19dc2d2e1405..943854a7dac1 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -614,8 +614,8 @@ web3._extend({ new web3._extend.Method({ name: 'multicall', call: 'eth_multicall', - params: 2, - inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter], + params: 3, + inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter, null], }), ], properties: [ From 0aaaf34825a72ee5ec2c3f62fc7c67506df31e15 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 8 Jun 2023 16:52:33 +0200 Subject: [PATCH 011/119] forgot the tracer --- internal/ethapi/transfertracer.go | 80 +++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 internal/ethapi/transfertracer.go diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go new file mode 100644 index 000000000000..a853e38e6ef1 --- /dev/null +++ b/internal/ethapi/transfertracer.go @@ -0,0 +1,80 @@ +// 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 . + +package ethapi + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/vm" +) + +type callLog struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` +} + +type transfer struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Value *big.Int `json:"value"` +} + +type tracer struct { + transfers []transfer +} + +func newTracer() *tracer { + return &tracer{transfers: make([]transfer, 0)} +} + +func (t *tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + if value.Cmp(common.Big0) > 0 { + t.transfers = append(t.transfers, transfer{From: from, To: to, Value: value}) + } + +} + +func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +} + +func (t *tracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +} + +func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +} + +func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + toCopy := to + if value.Cmp(common.Big0) > 0 { + t.transfers = append(t.transfers, transfer{From: from, To: toCopy, Value: value}) + } +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error) {} + +func (t *tracer) CaptureTxStart(gasLimit uint64) {} + +func (t *tracer) CaptureTxEnd(restGas uint64) {} + +func (t *tracer) Transfers() []transfer { + return t.transfers +} From 2db37b9471ba1e5fc61a9f1e0ceffbcc8531d6c8 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 8 Jun 2023 16:53:38 +0200 Subject: [PATCH 012/119] minor --- internal/ethapi/transfertracer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go index a853e38e6ef1..4db95887831c 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/transfertracer.go @@ -36,6 +36,8 @@ type transfer struct { Value *big.Int `json:"value"` } +// tracer is a simple tracer that records all ether transfers. +// This includes tx value, call value, and self destructs. type tracer struct { transfers []transfer } @@ -48,7 +50,6 @@ func (t *tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addres if value.Cmp(common.Big0) > 0 { t.transfers = append(t.transfers, transfer{From: from, To: to, Value: value}) } - } func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, err error) { From 04965a36a07ec8bb9d600863347dd00bf7c663ac Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 15 Jun 2023 15:27:01 +0200 Subject: [PATCH 013/119] fix marshaling of empty values --- internal/ethapi/api.go | 12 +++++++++++- internal/ethapi/api_test.go | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8bf32260cb05..55faca07755f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -19,6 +19,7 @@ package ethapi import ( "context" "encoding/hex" + "encoding/json" "errors" "fmt" "math/big" @@ -1107,7 +1108,16 @@ type callResult struct { Logs []*types.Log `json:"logs"` Transfers []transfer `json:"transfers,omitempty"` GasUsed hexutil.Uint64 `json:"gasUsed"` - Error string `json:"error"` + Error string `json:"error,omitempty"` +} + +func (r *callResult) MarshalJSON() ([]byte, error) { + type callResultAlias callResult + // 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)) } // Multicall executes series of transactions on top of a base state. diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 4da72ce6c900..3efc90a82de6 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -670,10 +670,12 @@ func TestMulticall(t *testing.T) { res{ ReturnValue: "0x", GasUsed: "0x5208", + Logs: []types.Log{}, }, res{ ReturnValue: "0x", GasUsed: "0x5208", + Logs: []types.Log{}, }, }}, }, { @@ -715,19 +717,23 @@ func TestMulticall(t *testing.T) { res{ ReturnValue: "0x", GasUsed: "0x5208", + Logs: []types.Log{}, }, res{ ReturnValue: "0x", GasUsed: "0x5208", + Logs: []types.Log{}, }, }, { res{ ReturnValue: "0x", GasUsed: "0x5208", + Logs: []types.Log{}, }, res{ ReturnValue: "0x", GasUsed: "0x0", + Logs: []types.Log{}, Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), }, }, @@ -766,11 +772,13 @@ func TestMulticall(t *testing.T) { res{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", + Logs: []types.Log{}, }, }, { res{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", + Logs: []types.Log{}, }, }}, }, @@ -830,9 +838,11 @@ func TestMulticall(t *testing.T) { want: [][]res{{{ ReturnValue: "0x", GasUsed: "0xaacc", + Logs: []types.Log{}, }, { ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000005", GasUsed: "0x5bb7", + Logs: []types.Log{}, }}}, }, // Test logs output. @@ -920,6 +930,7 @@ func TestMulticall(t *testing.T) { // Caller is in this case the contract that invokes ecrecover. ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), GasUsed: "0x52f6", + Logs: []types.Log{}, }}}, }, // Test ether transfers. @@ -965,6 +976,7 @@ func TestMulticall(t *testing.T) { Value: big.NewInt(100), }, }, + Logs: []types.Log{}, }}}, }, } From c78330d32c121c104fcb9e16103f16894254101f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 30 Jun 2023 17:22:32 +0200 Subject: [PATCH 014/119] rename to multicallV1 --- internal/ethapi/api.go | 2 +- internal/ethapi/api_test.go | 4 ++-- internal/web3ext/web3ext.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 55faca07755f..627cae0239b7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1127,7 +1127,7 @@ func (r *callResult) MarshalJSON() ([]byte, error) { // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([][]callResult, error) { +func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([][]callResult, error) { state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 3efc90a82de6..eeba0830da6b 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -598,7 +598,7 @@ func TestCall(t *testing.T) { } } -func TestMulticall(t *testing.T) { +func TestMulticallV1(t *testing.T) { t.Parallel() // Initialize test accounts var ( @@ -982,7 +982,7 @@ func TestMulticall(t *testing.T) { } for i, tc := range testSuite { - result, err := api.Multicall(context.Background(), tc.blocks, tc.tag, tc.includeTransfers) + result, err := api.MulticallV1(context.Background(), tc.blocks, tc.tag, tc.includeTransfers) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 943854a7dac1..9455367a919d 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -612,8 +612,8 @@ web3._extend({ inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null], }), new web3._extend.Method({ - name: 'multicall', - call: 'eth_multicall', + name: 'multicallV1', + call: 'eth_multicallV1', params: 3, inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter, null], }), From 8fff7fd10130c18d1779cb7e0ceb422a96cac00b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 3 Jul 2023 18:07:36 +0200 Subject: [PATCH 015/119] return block info except blockhash --- internal/ethapi/api.go | 34 +++++- internal/ethapi/api_test.go | 214 +++++++++++++++++++++++------------- 2 files changed, 164 insertions(+), 84 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 627cae0239b7..2c3573855284 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1103,6 +1103,17 @@ type CallBatch struct { Calls []TransactionArgs } +type blockResult struct { + Number hexutil.Uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Time hexutil.Uint64 `json:"timestamp"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"feeRecipient"` + BaseFee *hexutil.Big `json:"baseFeePerGas"` + Calls []callResult `json:"calls"` +} + type callResult struct { ReturnValue hexutil.Bytes `json:"return"` Logs []*types.Log `json:"logs"` @@ -1120,14 +1131,14 @@ func (r *callResult) MarshalJSON() ([]byte, error) { return json.Marshal((*callResultAlias)(r)) } -// Multicall executes series of transactions on top of a base state. +// MulticallV1 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 (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([][]callResult, error) { +func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([]blockResult, error) { state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err @@ -1147,7 +1158,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo // this makes sure resources are cleaned up. defer cancel() var ( - results = make([][]callResult, len(blocks)) + results = make([]blockResult, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap globalGasCap = s.b.RPCGasCap() gp = new(core.GasPool).AddGas(globalGasCap) @@ -1171,7 +1182,16 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo if block.ECRecoverOverride != nil { state.SetCode(common.BytesToAddress([]byte{1}), *block.ECRecoverOverride) } - results[bi] = make([]callResult, len(block.Calls)) + results[bi] = blockResult{ + Number: hexutil.Uint64(blockContext.BlockNumber.Uint64()), + Hash: common.Hash{}, // TODO + Time: hexutil.Uint64(blockContext.Time), + GasLimit: hexutil.Uint64(blockContext.GasLimit), + FeeRecipient: blockContext.Coinbase, + BaseFee: (*hexutil.Big)(blockContext.BaseFee), + Calls: make([]callResult, len(block.Calls)), + } + gasUsed := uint64(0) for i, call := range block.Calls { // Hack to get logs from statedb which stores logs by txhash. txhash := common.BigToHash(big.NewInt(int64(i))) @@ -1185,7 +1205,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo } result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) if err != nil { - results[bi][i] = callResult{Error: err.Error()} + results[bi].Calls[i] = callResult{Error: err.Error()} continue } // If the result contains a revert reason, try to unpack it. @@ -1205,8 +1225,10 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo if result.Err != nil { callRes.Error = result.Err.Error() } - results[bi][i] = callRes + results[bi].Calls[i] = callRes + gasUsed += result.UsedGas } + results[bi].GasUsed = hexutil.Uint64(gasUsed) } return results, nil } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index eeba0830da6b..dae567deaa40 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -633,19 +633,29 @@ func TestMulticallV1(t *testing.T) { latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) includeTransfers = true ) - type res struct { + type callRes struct { ReturnValue string `json:"return"` Error string Logs []types.Log GasUsed string Transfers []transfer } + type blockRes struct { + Number string + Hash string + // Ignore timestamp + GasLimit string + GasUsed string + FeeRecipient string + BaseFee string + Calls []callRes + } var testSuite = []struct { blocks []CallBatch tag rpc.BlockNumberOrHash includeTransfers *bool expectErr error - want [][]res + want []blockRes }{ // State build-up over calls: // First value transfer OK after state override. @@ -666,17 +676,21 @@ func TestMulticallV1(t *testing.T) { Value: (*hexutil.Big)(big.NewInt(1000)), }}, }}, - want: [][]res{{ - res{ + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0xa410", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, - }, - res{ + }, { ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, - }, + }}, }}, }, { // State build-up over blocks. @@ -712,32 +726,38 @@ func TestMulticallV1(t *testing.T) { }, }, }}, - want: [][]res{ - { - res{ - ReturnValue: "0x", - GasUsed: "0x5208", - Logs: []types.Log{}, - }, - res{ - ReturnValue: "0x", - GasUsed: "0x5208", - Logs: []types.Log{}, - }, + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0xa410", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x5208", + Logs: []types.Log{}, }, { - res{ - ReturnValue: "0x", - GasUsed: "0x5208", - Logs: []types.Log{}, - }, - res{ - ReturnValue: "0x", - GasUsed: "0x0", - Logs: []types.Log{}, - Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), - }, - }, - }, + ReturnValue: "0x", + GasUsed: "0x5208", + Logs: []types.Log{}, + }}, + }, { + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0x5208", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x5208", + Logs: []types.Log{}, + }, { + ReturnValue: "0x", + GasUsed: "0x0", + Logs: []types.Log{}, + Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), + }}, + }}, }, { // Block overrides should work, each call is simulated on a different block number tag: latest, @@ -768,18 +788,28 @@ func TestMulticallV1(t *testing.T) { }, }}, }}, - want: [][]res{{ - res{ + want: []blockRes{{ + Number: "0xb", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0xe891", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", Logs: []types.Log{}, - }, + }}, }, { - res{ + Number: "0xc", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0xe891", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", Logs: []types.Log{}, - }, + }}, }}, }, // Block numbers must be in order. @@ -810,7 +840,7 @@ func TestMulticallV1(t *testing.T) { }, }}, }}, - want: [][]res{}, + want: []blockRes{}, expectErr: errors.New("block numbers must be in order"), }, // Test on solidity storage example. Set value in one call, read in next. @@ -835,15 +865,22 @@ func TestMulticallV1(t *testing.T) { }, }, }}, - want: [][]res{{{ - ReturnValue: "0x", - GasUsed: "0xaacc", - Logs: []types.Log{}, - }, { - ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000005", - GasUsed: "0x5bb7", - Logs: []types.Log{}, - }}}, + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0x10683", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0xaacc", + Logs: []types.Log{}, + }, { + ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000005", + GasUsed: "0x5bb7", + Logs: []types.Log{}, + }}, + }}, }, // Test logs output. { @@ -867,16 +904,23 @@ func TestMulticallV1(t *testing.T) { To: &randomAccounts[2].addr, }}, }}, - want: [][]res{{{ - ReturnValue: "0x", - Logs: []types.Log{{ - Address: randomAccounts[2].addr, - Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, - BlockNumber: 10, - Data: []byte{}, + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0x5508", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + Logs: []types.Log{{ + Address: randomAccounts[2].addr, + Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, + BlockNumber: 10, + Data: []byte{}, + }}, + GasUsed: "0x5508", }}, - GasUsed: "0x5508", - }}}, + }}, }, // Test ecrecover override { @@ -926,12 +970,19 @@ func TestMulticallV1(t *testing.T) { To: &randomAccounts[2].addr, }}, }}, - want: [][]res{{{ - // Caller is in this case the contract that invokes ecrecover. - ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), - GasUsed: "0x52f6", - Logs: []types.Log{}, - }}}, + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0x52f6", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + // Caller is in this case the contract that invokes ecrecover. + ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), + GasUsed: "0x52f6", + Logs: []types.Log{}, + }}, + }}, }, // Test ether transfers. { @@ -962,22 +1013,29 @@ func TestMulticallV1(t *testing.T) { }}, }}, includeTransfers: &includeTransfers, - want: [][]res{{{ - ReturnValue: "0x", - GasUsed: "0xd984", - Transfers: []transfer{ - { - From: accounts[0].addr, - To: randomAccounts[0].addr, - Value: big.NewInt(50), - }, { - From: randomAccounts[0].addr, - To: randomAccounts[1].addr, - Value: big.NewInt(100), + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0xd984", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0xd984", + Transfers: []transfer{ + { + From: accounts[0].addr, + To: randomAccounts[0].addr, + Value: big.NewInt(50), + }, { + From: randomAccounts[0].addr, + To: randomAccounts[1].addr, + Value: big.NewInt(100), + }, }, - }, - Logs: []types.Log{}, - }}}, + Logs: []types.Log{}, + }}, + }}, }, } @@ -1001,7 +1059,7 @@ func TestMulticallV1(t *testing.T) { continue } // Turn result into res-struct - var have [][]res + var have []blockRes resBytes, _ := json.Marshal(result) if err := json.Unmarshal(resBytes, &have); err != nil { t.Fatalf("failed to unmarshal result: %v", err) From 637acc5fa10f0e179a4e0f61929a2611f0a55b23 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 13 Jul 2023 15:17:14 +0200 Subject: [PATCH 016/119] Finalise state after call, add selfdestruct test --- internal/ethapi/api.go | 1 + internal/ethapi/api_test.go | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2c3573855284..ee8d42a95232 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1227,6 +1227,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo } results[bi].Calls[i] = callRes gasUsed += result.UsedGas + state.Finalise(true) } results[bi].GasUsed = hexutil.Uint64(gasUsed) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index dae567deaa40..09012be4f467 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -605,12 +605,21 @@ func TestMulticallV1(t *testing.T) { accounts = newAccounts(3) genBlocks = 10 signer = types.HomesteadSigner{} + cac = common.HexToAddress("0x0000000000000000000000000000000000000cac") genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + // Yul: + // object "Test" { + // code { + // let dad := 0x0000000000000000000000000000000000000dad + // selfdestruct(dad) + // } + // } + cac: {Balance: big.NewInt(params.Ether), Code: common.Hex2Bytes("610dad80ff")}, }, } ) @@ -1037,6 +1046,51 @@ func TestMulticallV1(t *testing.T) { }}, }}, }, + // Tests selfdestructed contract. + { + tag: latest, + blocks: []CallBatch{{ + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &cac, + }, { + From: &accounts[0].addr, + // Check that cac is selfdestructed and balance transferred to dad. + // object "Test" { + // code { + // let cac := 0x0000000000000000000000000000000000000cac + // let dad := 0x0000000000000000000000000000000000000dad + // if gt(balance(cac), 0) { + // revert(0, 0) + // } + // if gt(extcodesize(cac), 0) { + // revert(0, 0) + // } + // if eq(balance(dad), 0) { + // revert(0, 0) + // } + // } + // } + Input: hex2Bytes("610cac610dad600082311115601357600080fd5b6000823b1115602157600080fd5b6000813103602e57600080fd5b5050"), + }}, + }}, + want: []blockRes{{ + Number: "0xa", + Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + GasLimit: "0x47e7c4", + GasUsed: "0x1b83f", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0xd166", + Logs: []types.Log{}, + }, { + ReturnValue: "0x", + GasUsed: "0xe6d9", + Logs: []types.Log{}, + }}, + }}, + }, } for i, tc := range testSuite { From d23eb894d62ac3cd0dfb87683c8bf5156ff95026 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 13 Jul 2023 16:03:52 +0200 Subject: [PATCH 017/119] Add status field to call result --- internal/ethapi/api.go | 8 ++++++-- internal/ethapi/api_test.go | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ee8d42a95232..1704bad8abf7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1119,6 +1119,7 @@ type callResult struct { Logs []*types.Log `json:"logs"` Transfers []transfer `json:"transfers,omitempty"` GasUsed hexutil.Uint64 `json:"gasUsed"` + Status hexutil.Uint64 `json:"status"` Error string `json:"error,omitempty"` } @@ -1205,7 +1206,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo } result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) if err != nil { - results[bi].Calls[i] = callResult{Error: err.Error()} + results[bi].Calls[i] = callResult{Error: err.Error(), Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue } // If the result contains a revert reason, try to unpack it. @@ -1222,8 +1223,11 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo transfers = vmConfig.Tracer.(*tracer).Transfers() } callRes := callResult{ReturnValue: result.Return(), Logs: logs, Transfers: transfers, GasUsed: hexutil.Uint64(result.UsedGas)} - if result.Err != nil { + if result.Failed() { + callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) callRes.Error = result.Err.Error() + } else { + callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) } results[bi].Calls[i] = callRes gasUsed += result.UsedGas diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 09012be4f467..db5c2cdb2057 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -647,6 +647,7 @@ func TestMulticallV1(t *testing.T) { Error string Logs []types.Log GasUsed string + Status string Transfers []transfer } type blockRes struct { @@ -695,10 +696,12 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, + Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, + Status: "0x1", }}, }}, }, { @@ -745,10 +748,12 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, + Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, + Status: "0x1", }}, }, { Number: "0xa", @@ -760,10 +765,12 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x", GasUsed: "0x5208", Logs: []types.Log{}, + Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0x0", Logs: []types.Log{}, + Status: "0x0", Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), }}, }}, @@ -807,6 +814,7 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", Logs: []types.Log{}, + Status: "0x1", }}, }, { Number: "0xc", @@ -818,6 +826,7 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", Logs: []types.Log{}, + Status: "0x1", }}, }}, }, @@ -884,10 +893,12 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x", GasUsed: "0xaacc", Logs: []types.Log{}, + Status: "0x1", }, { ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000005", GasUsed: "0x5bb7", Logs: []types.Log{}, + Status: "0x1", }}, }}, }, @@ -928,6 +939,7 @@ func TestMulticallV1(t *testing.T) { Data: []byte{}, }}, GasUsed: "0x5508", + Status: "0x1", }}, }}, }, @@ -990,6 +1002,7 @@ func TestMulticallV1(t *testing.T) { ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), GasUsed: "0x52f6", Logs: []types.Log{}, + Status: "0x1", }}, }}, }, @@ -1042,7 +1055,8 @@ func TestMulticallV1(t *testing.T) { Value: big.NewInt(100), }, }, - Logs: []types.Log{}, + Logs: []types.Log{}, + Status: "0x1", }}, }}, }, @@ -1084,10 +1098,12 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x", GasUsed: "0xd166", Logs: []types.Log{}, + Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0xe6d9", Logs: []types.Log{}, + Status: "0x1", }}, }}, }, From a7af5a900270a0e166beda01b859387c18426286 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 13 Jul 2023 18:06:40 +0200 Subject: [PATCH 018/119] wrap params into an object --- internal/ethapi/api.go | 12 +++++++++--- internal/ethapi/api_test.go | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1704bad8abf7..fcc4164ba0fb 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1132,6 +1132,11 @@ func (r *callResult) MarshalJSON() ([]byte, error) { return json.Marshal((*callResultAlias)(r)) } +type multicallOpts struct { + Blocks []CallBatch + TraceTransfers bool +} + // MulticallV1 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 @@ -1139,7 +1144,7 @@ func (r *callResult) MarshalJSON() ([]byte, error) { // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([]blockResult, error) { +func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blockNrOrHash rpc.BlockNumberOrHash) ([]blockResult, error) { state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err @@ -1149,6 +1154,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo var ( cancel context.CancelFunc timeout = s.b.RPCEVMTimeout() + blocks = opts.Blocks ) if timeout > 0 { ctx, cancel = context.WithTimeout(ctx, timeout) @@ -1201,7 +1207,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo if block.ECRecoverOverride != nil { vmConfig.DisableECRecover = true } - if includeTransfers != nil && *includeTransfers { + if opts.TraceTransfers { vmConfig.Tracer = newTracer() } result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) @@ -1219,7 +1225,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, blocks []CallBatch, blo l.TxHash = common.Hash{} } var transfers []transfer - if includeTransfers != nil && *includeTransfers { + if opts.TraceTransfers { transfers = vmConfig.Tracer.(*tracer).Transfers() } callRes := callResult{ReturnValue: result.Return(), Logs: logs, Transfers: transfers, GasUsed: hexutil.Uint64(result.UsedGas)} diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index db5c2cdb2057..9651f58236fc 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1110,7 +1110,11 @@ func TestMulticallV1(t *testing.T) { } for i, tc := range testSuite { - result, err := api.MulticallV1(context.Background(), tc.blocks, tc.tag, tc.includeTransfers) + opts := multicallOpts{Blocks: tc.blocks} + if tc.includeTransfers != nil && *tc.includeTransfers { + opts.TraceTransfers = true + } + result, err := api.MulticallV1(context.Background(), opts, tc.tag) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) From 98882fc2795adb5db82b4e79b676f8360747a6f7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 13 Jul 2023 18:16:48 +0200 Subject: [PATCH 019/119] blockhash = keccak(blockNum) --- internal/ethapi/api.go | 3 ++- internal/ethapi/api_test.go | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fcc4164ba0fb..2f5c9a8cb9a7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1189,9 +1189,10 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if block.ECRecoverOverride != nil { state.SetCode(common.BytesToAddress([]byte{1}), *block.ECRecoverOverride) } + hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) results[bi] = blockResult{ Number: hexutil.Uint64(blockContext.BlockNumber.Uint64()), - Hash: common.Hash{}, // TODO + Hash: hash, Time: hexutil.Uint64(blockContext.Time), GasLimit: hexutil.Uint64(blockContext.GasLimit), FeeRecipient: blockContext.Coinbase, diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 9651f58236fc..243deb3258ea 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -622,6 +622,7 @@ func TestMulticallV1(t *testing.T) { cac: {Balance: big.NewInt(params.Ether), Code: common.Hex2Bytes("610dad80ff")}, }, } + n10hash = crypto.Keccak256Hash([]byte{0xa}).Hex() ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] @@ -688,7 +689,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -740,7 +741,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -757,7 +758,7 @@ func TestMulticallV1(t *testing.T) { }}, }, { Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x5208", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -806,7 +807,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: crypto.Keccak256Hash([]byte{0xb}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe891", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -818,7 +819,7 @@ func TestMulticallV1(t *testing.T) { }}, }, { Number: "0xc", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe891", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -885,7 +886,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x10683", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -926,7 +927,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x5508", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -993,7 +994,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x52f6", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -1037,7 +1038,7 @@ func TestMulticallV1(t *testing.T) { includeTransfers: &includeTransfers, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xd984", FeeRecipient: "0x0000000000000000000000000000000000000000", @@ -1090,7 +1091,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xa", - Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x1b83f", FeeRecipient: "0x0000000000000000000000000000000000000000", From b86582478776607da12ee02dde69005a7453f61c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 17 Jul 2023 16:01:12 +0200 Subject: [PATCH 020/119] name tests --- internal/ethapi/api_test.go | 84 +++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 243deb3258ea..65e9d4fc2353 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -662,6 +662,7 @@ func TestMulticallV1(t *testing.T) { Calls []callRes } var testSuite = []struct { + name string blocks []CallBatch tag rpc.BlockNumberOrHash includeTransfers *bool @@ -672,7 +673,8 @@ func TestMulticallV1(t *testing.T) { // First value transfer OK after state override. // Second one should succeed because of first transfer. { - tag: latest, + name: "simple", + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(1000))}, @@ -707,7 +709,8 @@ func TestMulticallV1(t *testing.T) { }}, }, { // State build-up over blocks. - tag: latest, + name: "simple-multi-block", + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(2000))}, @@ -777,7 +780,8 @@ func TestMulticallV1(t *testing.T) { }}, }, { // Block overrides should work, each call is simulated on a different block number - tag: latest, + name: "block-overrides", + tag: latest, blocks: []CallBatch{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(11)), @@ -833,7 +837,8 @@ func TestMulticallV1(t *testing.T) { }, // Block numbers must be in order. { - tag: latest, + name: "block-number-order", + tag: latest, blocks: []CallBatch{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(12)), @@ -864,7 +869,8 @@ func TestMulticallV1(t *testing.T) { }, // Test on solidity storage example. Set value in one call, read in next. { - tag: latest, + name: "storage-contract", + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ @@ -905,7 +911,8 @@ func TestMulticallV1(t *testing.T) { }, // Test logs output. { - tag: latest, + name: "logs", + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ @@ -946,7 +953,8 @@ func TestMulticallV1(t *testing.T) { }, // Test ecrecover override { - tag: latest, + name: "ecrecover-override", + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ @@ -1009,7 +1017,8 @@ func TestMulticallV1(t *testing.T) { }, // Test ether transfers. { - tag: latest, + name: "transfer-logs", + tag: latest, blocks: []CallBatch{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{ @@ -1063,7 +1072,8 @@ func TestMulticallV1(t *testing.T) { }, // Tests selfdestructed contract. { - tag: latest, + name: "selfdestruct", + tag: latest, blocks: []CallBatch{{ Calls: []TransactionArgs{{ From: &accounts[0].addr, @@ -1111,37 +1121,37 @@ func TestMulticallV1(t *testing.T) { } for i, tc := range testSuite { - opts := multicallOpts{Blocks: tc.blocks} - if tc.includeTransfers != nil && *tc.includeTransfers { - opts.TraceTransfers = true - } - result, err := api.MulticallV1(context.Background(), opts, tc.tag) - if tc.expectErr != nil { - if err == nil { - t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) - continue + t.Run(tc.name, func(t *testing.T) { + opts := multicallOpts{Blocks: tc.blocks} + if tc.includeTransfers != nil && *tc.includeTransfers { + opts.TraceTransfers = true } - if !errors.Is(err, tc.expectErr) { - // Second try - if !reflect.DeepEqual(err, tc.expectErr) { - t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + result, err := api.MulticallV1(context.Background(), opts, tc.tag) + if tc.expectErr != nil { + if err == nil { + t.Fatalf("test %d: want error %v, have nothing", i, tc.expectErr) } + if !errors.Is(err, tc.expectErr) { + // Second try + if !reflect.DeepEqual(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + } + } + return } - continue - } - if err != nil { - t.Errorf("test %d: want no error, have %v", i, err) - continue - } - // Turn result into res-struct - var have []blockRes - resBytes, _ := json.Marshal(result) - if err := json.Unmarshal(resBytes, &have); err != nil { - t.Fatalf("failed to unmarshal result: %v", err) - } - if !reflect.DeepEqual(have, tc.want) { - t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, tc.want) - } + if err != nil { + t.Fatalf("test %d: want no error, have %v", i, err) + } + // Turn result into res-struct + var have []blockRes + resBytes, _ := json.Marshal(result) + if err := json.Unmarshal(resBytes, &have); err != nil { + t.Fatalf("failed to unmarshal result: %v", err) + } + if !reflect.DeepEqual(have, tc.want) { + t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, tc.want) + } + }) } } From da3102e5ae4d0002bed9c7a9f726778a2828c65d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 17 Jul 2023 16:01:52 +0200 Subject: [PATCH 021/119] persist precompiles in evm --- core/vm/contracts.go | 22 +++++++++++++++++----- core/vm/evm.go | 35 +++++++++++++++++------------------ 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index aa4a3f13df5e..f6b16b2b44fb 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -40,9 +40,12 @@ type PrecompiledContract interface { Run(input []byte) ([]byte, error) // Run runs the precompiled contract } +// PrecompiledContractByName contains the precompiled contracts supported at the given fork. +type PrecompiledContracts map[common.Address]PrecompiledContract + // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ +var PrecompiledContractsHomestead = PrecompiledContracts{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -51,7 +54,7 @@ var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ +var PrecompiledContractsByzantium = PrecompiledContracts{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -64,7 +67,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ +var PrecompiledContractsIstanbul = PrecompiledContracts{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -78,7 +81,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. -var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ +var PrecompiledContractsBerlin = PrecompiledContracts{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -92,7 +95,7 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ // PrecompiledContractsBLS contains the set of pre-compiled Ethereum // contracts specified in EIP-2537. These are exported for testing purposes. -var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ +var PrecompiledContractsBLS = PrecompiledContracts{ common.BytesToAddress([]byte{10}): &bls12381G1Add{}, common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, @@ -126,6 +129,15 @@ func init() { } } +// Copy returns a copy of the precompiled contracts. +func (p PrecompiledContracts) Copy() PrecompiledContracts { + c := make(PrecompiledContracts) + for k, v := range p { + c[k] = v + } + return c +} + // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { diff --git a/core/vm/evm.go b/core/vm/evm.go index 9f7735ac6dc5..59e65ef715b3 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -41,24 +41,7 @@ type ( ) func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { - var precompiles map[common.Address]PrecompiledContract - switch { - case evm.chainRules.IsBerlin: - precompiles = PrecompiledContractsBerlin - case evm.chainRules.IsIstanbul: - precompiles = PrecompiledContractsIstanbul - case evm.chainRules.IsByzantium: - precompiles = PrecompiledContractsByzantium - default: - precompiles = PrecompiledContractsHomestead - } - // ECRecoverCode can be set only through RPC calls - if evm.Config.DisableECRecover { - if addr == common.BytesToAddress([]byte{1}) { - return nil, false - } - } - p, ok := precompiles[addr] + p, ok := evm.precompiles[addr] return p, ok } @@ -125,6 +108,8 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. 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 @@ -138,6 +123,20 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } + switch { + case evm.chainRules.IsBerlin: + evm.precompiles = PrecompiledContractsBerlin.Copy() + case evm.chainRules.IsIstanbul: + evm.precompiles = PrecompiledContractsIstanbul.Copy() + case evm.chainRules.IsByzantium: + evm.precompiles = PrecompiledContractsByzantium.Copy() + default: + evm.precompiles = PrecompiledContractsHomestead.Copy() + } + // ECRecoverCode can be set only through RPC calls + if config.DisableECRecover { + delete(evm.precompiles, common.BytesToAddress([]byte{1})) + } evm.interpreter = NewEVMInterpreter(evm) return evm } From 6a2c96c490caa5817848177442f63b8aa4d88f10 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 18 Jul 2023 15:17:56 +0200 Subject: [PATCH 022/119] add storage clearing test case for eth_call --- internal/ethapi/api_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 65e9d4fc2353..187fd2b6d697 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -434,12 +434,20 @@ func TestCall(t *testing.T) { // Initialize test accounts var ( accounts = newAccounts(3) + dad = common.HexToAddress("0x0000000000000000000000000000000000000dad") genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + dad: { + Balance: big.NewInt(params.Ether), + Nonce: 1, + Storage: map[common.Hash]common.Hash{ + common.Hash{}: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), + }, + }, }, } genBlocks = 10 @@ -572,6 +580,32 @@ func TestCall(t *testing.T) { blockOverrides: BlockOverrides{Number: (*hexutil.Big)(big.NewInt(11))}, want: "0x000000000000000000000000000000000000000000000000000000000000000b", }, + // Clear storage trie + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[1].addr, + // Yul: + // object "Test" { + // code { + // let dad := 0x0000000000000000000000000000000000000dad + // if eq(balance(dad), 0) { + // revert(0, 0) + // } + // let slot := sload(0) + // mstore(0, slot) + // return(0, 32) + // } + // } + Input: hex2Bytes("610dad6000813103600f57600080fd5b6000548060005260206000f3"), + }, + overrides: StateOverride{ + dad: OverrideAccount{ + State: &map[common.Hash]common.Hash{}, + }, + }, + want: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, } for i, tc := range testSuite { result, err := api.Call(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) From b3efbad58536aa040f773d6ef1680bb66b0ce0ae Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 18 Jul 2023 15:26:17 +0200 Subject: [PATCH 023/119] implement precompile override and moveTo feature --- core/vm/contracts.go | 16 ++++++- core/vm/evm.go | 22 ++++----- internal/ethapi/api.go | 91 +++++++++++++++++++++++++++++++++++-- internal/ethapi/api_test.go | 20 ++++---- 4 files changed, 120 insertions(+), 29 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index f6b16b2b44fb..cb6f5de93c45 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -138,7 +138,21 @@ func (p PrecompiledContracts) Copy() PrecompiledContracts { return c } -// ActivePrecompiles returns the precompiles enabled with the current configuration. +// ActivePrecompiledContracts returns precompiled contracts enabled with the current configuration. +func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { + switch { + case rules.IsBerlin: + return PrecompiledContractsBerlin + case rules.IsIstanbul: + return PrecompiledContractsIstanbul + case rules.IsByzantium: + return PrecompiledContractsByzantium + default: + return PrecompiledContractsHomestead + } +} + +// ActivePrecompiles returns the precompile addresses enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { case rules.IsBerlin: diff --git a/core/vm/evm.go b/core/vm/evm.go index 59e65ef715b3..a72822fcec71 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -123,24 +123,18 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - switch { - case evm.chainRules.IsBerlin: - evm.precompiles = PrecompiledContractsBerlin.Copy() - case evm.chainRules.IsIstanbul: - evm.precompiles = PrecompiledContractsIstanbul.Copy() - case evm.chainRules.IsByzantium: - evm.precompiles = PrecompiledContractsByzantium.Copy() - default: - evm.precompiles = PrecompiledContractsHomestead.Copy() - } - // ECRecoverCode can be set only through RPC calls - if config.DisableECRecover { - delete(evm.precompiles, common.BytesToAddress([]byte{1})) - } + evm.precompiles = ActivePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(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 // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2f5c9a8cb9a7..13c56b57d494 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -873,6 +873,7 @@ type OverrideAccount struct { Balance **hexutil.Big `json:"balance"` State *map[common.Hash]common.Hash `json:"state"` StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` + MoveTo *common.Address `json:"moveTo"` } // StateOverride is the collection of overridden accounts. @@ -917,6 +918,80 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { return nil } +// ApplyMulticall overrides the fields of specified accounts into the given state. +func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.PrecompiledContracts) error { + if diff == nil { + return nil + } + for addr, account := range *diff { + p, isPrecompile := precompiles[addr] + // The MoveTo feature makes it possible to replace precompiles and EVM + // contracts in all the following configurations: + // 1. Precompile -> Precompile + // 2. Precompile -> EVM contract + // 3. EVM contract -> Precompile + // 4. EVM contract -> EVM contract + if account.MoveTo != nil { + if isPrecompile { + // Clear destination account which may be an EVM contract. + if !state.Empty(*account.MoveTo) { + state.SetCode(*account.MoveTo, nil) + state.SetNonce(*account.MoveTo, 0) + state.SetBalance(*account.MoveTo, big.NewInt(0)) + state.SetStorage(*account.MoveTo, map[common.Hash]common.Hash{}) + } + // If destination is a precompile, it will be simply replaced. + precompiles[*account.MoveTo] = p + } else { + state.SetBalance(*account.MoveTo, state.GetBalance(addr)) + state.SetNonce(*account.MoveTo, state.GetNonce(addr)) + state.SetCode(*account.MoveTo, state.GetCode(addr)) + // TODO: copy storage over + //state.SetState(*account.MoveTo, state.GetState(addr)) + // Clear source storage + state.SetStorage(addr, map[common.Hash]common.Hash{}) + if precompiles[*account.MoveTo] != nil { + delete(precompiles, *account.MoveTo) + } + } + } + if isPrecompile { + // Now that the contract is moved it can be deleted. + delete(precompiles, addr) + } + // Override account nonce. + if account.Nonce != nil { + state.SetNonce(addr, uint64(*account.Nonce)) + } + // Override account(contract) code. + if account.Code != nil { + state.SetCode(addr, *account.Code) + } + // Override account balance. + if account.Balance != nil { + state.SetBalance(addr, (*big.Int)(*account.Balance)) + } + if account.State != nil && account.StateDiff != nil { + return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + } + // Replace entire state if caller requires. + if account.State != nil { + state.SetStorage(addr, *account.State) + } + // Apply state diff into specified accounts. + if account.StateDiff != nil { + for key, value := range *account.StateDiff { + state.SetState(addr, key, value) + } + } + } + // Now finalize the changes. Finalize is normally performed between transactions. + // By using finalize, the overrides are semantically behaving as + // if they were created in a transaction just before the tracing occur. + state.Finalise(false) + return nil +} + // BlockOverrides is a set of header fields to override. type BlockOverrides struct { Number *hexutil.Big @@ -1013,16 +1088,19 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}) + return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, nil) } -func doCall(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) (*core.ExecutionResult, error) { +func doCall(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) (*core.ExecutionResult, error) { // Get a new instance of the EVM. msg, err := args.ToMessage(gp.Gas(), header.BaseFee) if err != nil { return nil, err } evm, vmError := b.GetEVM(ctx, msg, state, header, vmConfig, blockContext) + if precompiles != nil { + evm.SetPrecompiles(precompiles) + } // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1170,9 +1248,12 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo globalGasCap = s.b.RPCGasCap() gp = new(core.GasPool).AddGas(globalGasCap) prevNumber = header.Number.Uint64() + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + precompiles = vm.ActivePrecompiledContracts(rules).Copy() ) for bi, block := range blocks { - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) if block.BlockOverrides != nil { block.BlockOverrides.Apply(&blockContext) } @@ -1182,7 +1263,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } prevNumber = blockContext.BlockNumber.Uint64() // State overrides are applied prior to execution of a block - if err := block.StateOverrides.Apply(state); err != nil { + if err := block.StateOverrides.ApplyMulticall(state, precompiles); err != nil { return nil, err } // ECRecover replacement code will be fetched from statedb and executed as a normal EVM bytecode. @@ -1211,7 +1292,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if opts.TraceTransfers { vmConfig.Tracer = newTracer() } - result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles) if err != nil { results[bi].Calls[i] = callResult{Error: err.Error(), Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 187fd2b6d697..6b85d0f3582a 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1018,17 +1018,19 @@ func TestMulticallV1(t *testing.T) { // } Code: hex2Bytes("6040516000815260006020820152600060408201526000606082015260208160808360015afa60008103603157600080fd5b601482f3"), }, + common.BytesToAddress([]byte{0x01}): OverrideAccount{ + // Yul code that returns the address of the caller. + // object "Test" { + // code { + // let c := caller() + // mstore(0, c) + // return(0xc, 0x14) + // } + // } + Code: hex2Bytes("33806000526014600cf3"), + }, }, BlockOverrides: &BlockOverrides{}, - // Yul code that returns the address of the caller. - // object "Test" { - // code { - // let c := caller() - // mstore(0, c) - // return(0xc, 0x14) - // } - // } - ECRecoverOverride: hex2Bytes("33806000526014600cf3"), Calls: []TransactionArgs{{ From: &randomAccounts[0].addr, To: &randomAccounts[2].addr, From 9aed710aa94db449c452417e82a5dff0a540c871 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 18 Jul 2023 17:08:54 +0200 Subject: [PATCH 024/119] copy storage over in MoveTo --- internal/ethapi/api.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 13c56b57d494..bdd2c7a5ed57 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -946,8 +946,11 @@ func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.P state.SetBalance(*account.MoveTo, state.GetBalance(addr)) state.SetNonce(*account.MoveTo, state.GetNonce(addr)) state.SetCode(*account.MoveTo, state.GetCode(addr)) - // TODO: copy storage over - //state.SetState(*account.MoveTo, state.GetState(addr)) + // Copy storage over + state.ForEachStorage(addr, func(key, value common.Hash) bool { + state.SetState(*account.MoveTo, key, value) + return true + }) // Clear source storage state.SetStorage(addr, map[common.Hash]common.Hash{}) if precompiles[*account.MoveTo] != nil { From 2c25ab65b875fe9fd90b0befc2e62591c3a7f1d0 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 24 Jul 2023 17:24:40 +0200 Subject: [PATCH 025/119] add todo comments --- internal/ethapi/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bdd2c7a5ed57..3d044d600e7d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -919,6 +919,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { } // ApplyMulticall overrides the fields of specified accounts into the given state. +// TODO: consider MoveTo by address mapping func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.PrecompiledContracts) error { if diff == nil { return nil @@ -947,6 +948,7 @@ func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.P state.SetNonce(*account.MoveTo, state.GetNonce(addr)) state.SetCode(*account.MoveTo, state.GetCode(addr)) // Copy storage over + // TODO: Use snaps state.ForEachStorage(addr, func(key, value common.Hash) bool { state.SetState(*account.MoveTo, key, value) return true From 5d351c9f33fe32bd104e0747d26358bd3315a946 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 31 Jul 2023 15:27:01 +0200 Subject: [PATCH 026/119] add testcase moveto precompile --- internal/ethapi/api_test.go | 60 +++++++++++++++++++++++++++++++++++-- tests/testdata | 2 +- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 6b85d0f3582a..453d233ca3c8 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -656,7 +656,8 @@ func TestMulticallV1(t *testing.T) { cac: {Balance: big.NewInt(params.Ether), Code: common.Hex2Bytes("610dad80ff")}, }, } - n10hash = crypto.Keccak256Hash([]byte{0xa}).Hex() + n10hash = crypto.Keccak256Hash([]byte{0xa}).Hex() + sha256Address = common.BytesToAddress([]byte{0x02}) ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] @@ -1051,6 +1052,61 @@ func TestMulticallV1(t *testing.T) { }}, }}, }, + // Test moving the sha256 precompile. + { + name: "precompile-move", + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + sha256Address: OverrideAccount{ + // Yul code that returns the calldata. + // object "Test" { + // code { + // let size := calldatasize() // Get the size of the calldata + // + // // Allocate memory to store the calldata + // let memPtr := msize() + // + // // Copy calldata to memory + // calldatacopy(memPtr, 0, size) + // + // // Return the calldata from memory + // return(memPtr, size) + // } + // } + Code: hex2Bytes("365981600082378181f3"), + MoveTo: &randomAccounts[2].addr, + }, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Input: hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), + }, { + From: &randomAccounts[0].addr, + To: &sha256Address, + Input: hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), + }}, + }}, + want: []blockRes{{ + Number: "0xa", + Hash: n10hash, + GasLimit: "0x47e7c4", + GasUsed: "0xa58c", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0xec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5", + GasUsed: "0x52dc", + Logs: []types.Log{}, + Status: "0x1", + }, { + ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000001", + GasUsed: "0x52b0", + Logs: []types.Log{}, + Status: "0x1", + }}, + }}, + }, // Test ether transfers. { name: "transfer-logs", @@ -1218,7 +1274,7 @@ func newRPCBalance(balance *big.Int) **hexutil.Big { } func hex2Bytes(str string) *hexutil.Bytes { - rpcBytes := hexutil.Bytes(common.Hex2Bytes(str)) + rpcBytes := hexutil.Bytes(common.FromHex(str)) return &rpcBytes } diff --git a/tests/testdata b/tests/testdata index bac70c50a579..ee3fa4c86d05 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit bac70c50a579197af68af5fc6d8c7b6163b92c52 +Subproject commit ee3fa4c86d05f99f2717f83a6ad08008490ddf07 From 63f4f6e5a8b8476abef328037f860ba4c63bea1d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 31 Jul 2023 15:37:38 +0200 Subject: [PATCH 027/119] rm ecrecover override field --- core/vm/interpreter.go | 1 - internal/ethapi/api.go | 14 +++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 0f11f2839a83..5b2082bc9e90 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -26,7 +26,6 @@ import ( // Config are the configuration options for the Interpreter type Config struct { Tracer EVMLogger // Opcode logger - DisableECRecover bool // Debug flag to remove ecrecover from precompiles. Used for call simulations. NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3d044d600e7d..3cd9529b6d4b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1180,10 +1180,9 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // CallBatch is a batch of calls to be simulated sequentially. type CallBatch struct { - BlockOverrides *BlockOverrides - StateOverrides *StateOverride - ECRecoverOverride *hexutil.Bytes // Override bytecode for ecrecover precompile. - Calls []TransactionArgs + BlockOverrides *BlockOverrides + StateOverrides *StateOverride + Calls []TransactionArgs } type blockResult struct { @@ -1271,10 +1270,6 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if err := block.StateOverrides.ApplyMulticall(state, precompiles); err != nil { return nil, err } - // ECRecover replacement code will be fetched from statedb and executed as a normal EVM bytecode. - if block.ECRecoverOverride != nil { - state.SetCode(common.BytesToAddress([]byte{1}), *block.ECRecoverOverride) - } hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) results[bi] = blockResult{ Number: hexutil.Uint64(blockContext.BlockNumber.Uint64()), @@ -1291,9 +1286,6 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo txhash := common.BigToHash(big.NewInt(int64(i))) state.SetTxContext(txhash, i) vmConfig := &vm.Config{NoBaseFee: true} - if block.ECRecoverOverride != nil { - vmConfig.DisableECRecover = true - } if opts.TraceTransfers { vmConfig.Tracer = newTracer() } From 80506eaa9c474d02c16abaed3e2e9f5aa6fcfba5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 31 Jul 2023 15:48:03 +0200 Subject: [PATCH 028/119] rename blocks to blockStateCalls --- internal/ethapi/api.go | 6 +++--- internal/ethapi/api_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3cd9529b6d4b..cc1a15a85126 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1215,8 +1215,8 @@ func (r *callResult) MarshalJSON() ([]byte, error) { } type multicallOpts struct { - Blocks []CallBatch - TraceTransfers bool + BlockStateCalls []CallBatch + TraceTransfers bool } // MulticallV1 executes series of transactions on top of a base state. @@ -1236,7 +1236,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo var ( cancel context.CancelFunc timeout = s.b.RPCEVMTimeout() - blocks = opts.Blocks + blocks = opts.BlockStateCalls ) if timeout > 0 { ctx, cancel = context.WithTimeout(ctx, timeout) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 453d233ca3c8..78238d96bd7e 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1214,7 +1214,7 @@ func TestMulticallV1(t *testing.T) { for i, tc := range testSuite { t.Run(tc.name, func(t *testing.T) { - opts := multicallOpts{Blocks: tc.blocks} + opts := multicallOpts{BlockStateCalls: tc.blocks} if tc.includeTransfers != nil && *tc.includeTransfers { opts.TraceTransfers = true } From fd786c081fe66b46fb6fb6a5cb0c9579d8dfa0fd Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 1 Aug 2023 12:48:31 +0200 Subject: [PATCH 029/119] rename to moveToAddress --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index cc1a15a85126..fda4f812d2d6 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -873,7 +873,7 @@ type OverrideAccount struct { Balance **hexutil.Big `json:"balance"` State *map[common.Hash]common.Hash `json:"state"` StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` - MoveTo *common.Address `json:"moveTo"` + MoveTo *common.Address `json:"moveToAddress"` } // StateOverride is the collection of overridden accounts. From 6845b5eff6b0a7776f7cc016682ff57bf567fc28 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 1 Aug 2023 17:26:00 +0200 Subject: [PATCH 030/119] add optional validation of txes --- eth/tracers/api.go | 2 +- internal/ethapi/api.go | 11 +++++---- internal/ethapi/api_test.go | 37 +++++++++++++++++++++++++++++ internal/ethapi/transaction_args.go | 9 +++++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5e90180df8d5..1be3a36b6ad3 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -908,7 +908,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc config.BlockOverrides.Apply(&vmctx) } // Execute the trace - msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) + msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee(), true) if err != nil { return nil, err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fda4f812d2d6..8676ed6f4c60 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1093,12 +1093,12 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, nil) + return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, nil, false) } -func doCall(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) (*core.ExecutionResult, error) { +func doCall(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, validate bool) (*core.ExecutionResult, error) { // Get a new instance of the EVM. - msg, err := args.ToMessage(gp.Gas(), header.BaseFee) + msg, err := args.ToMessage(gp.Gas(), header.BaseFee, !validate) if err != nil { return nil, err } @@ -1217,6 +1217,7 @@ func (r *callResult) MarshalJSON() ([]byte, error) { type multicallOpts struct { BlockStateCalls []CallBatch TraceTransfers bool + Validation bool } // MulticallV1 executes series of transactions on top of a base state. @@ -1289,7 +1290,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if opts.TraceTransfers { vmConfig.Tracer = newTracer() } - result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles) + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { results[bi].Calls[i] = callResult{Error: err.Error(), Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue @@ -1722,7 +1723,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH statedb := db.Copy() // Set the accesslist to the last al args.AccessList = &accessList - msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee) + msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee, true) if err != nil { return nil, 0, nil, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 78238d96bd7e..668a1e3f8e21 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -677,6 +677,7 @@ func TestMulticallV1(t *testing.T) { randomAccounts = newAccounts(4) latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) includeTransfers = true + validation = true ) type callRes struct { ReturnValue string `json:"return"` @@ -701,6 +702,7 @@ func TestMulticallV1(t *testing.T) { blocks []CallBatch tag rpc.BlockNumberOrHash includeTransfers *bool + validation *bool expectErr error want []blockRes }{ @@ -1210,6 +1212,33 @@ func TestMulticallV1(t *testing.T) { }}, }}, }, + // Enable validation checks. + { + name: "validation-checks", + tag: latest, + blocks: []CallBatch{{ + Calls: []TransactionArgs{{ + From: &accounts[2].addr, + To: &cac, + Nonce: newUint64(2), + }}, + }}, + validation: &validation, + want: []blockRes{{ + Number: "0xa", + Hash: n10hash, + GasLimit: "0x47e7c4", + GasUsed: "0x0", + FeeRecipient: "0x0000000000000000000000000000000000000000", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x0", + Logs: []types.Log{}, + Status: "0x0", + Error: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 10000000)", accounts[2].addr), + }}, + }}, + }, } for i, tc := range testSuite { @@ -1218,6 +1247,9 @@ func TestMulticallV1(t *testing.T) { if tc.includeTransfers != nil && *tc.includeTransfers { opts.TraceTransfers = true } + if tc.validation != nil && *tc.validation { + opts.Validation = true + } result, err := api.MulticallV1(context.Background(), opts, tc.tag) if tc.expectErr != nil { if err == nil { @@ -1278,6 +1310,11 @@ func hex2Bytes(str string) *hexutil.Bytes { return &rpcBytes } +func newUint64(v uint64) *hexutil.Uint64 { + rpcUint64 := hexutil.Uint64(v) + return &rpcUint64 +} + // testHasher is the helper tool for transaction/receipt list hashing. // The original hasher is trie, in order to get rid of import cycle, // use the testing hasher instead. diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index c74f540b761b..0cef8956c4fb 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -200,7 +200,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ // ToMessage converts the transaction arguments to the Message type used by the // core evm. This method is used in calls and traces that do not require a real // live transaction. -func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (*core.Message, error) { +func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, skipChecks bool) (*core.Message, error) { // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") @@ -259,6 +259,10 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (* if args.Value != nil { value = args.Value.ToInt() } + var nonce uint64 + if args.Nonce != nil { + nonce = uint64(*args.Nonce) + } data := args.data() var accessList types.AccessList if args.AccessList != nil { @@ -268,13 +272,14 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (* From: addr, To: args.To, Value: value, + Nonce: nonce, GasLimit: gas, GasPrice: gasPrice, GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, Data: data, AccessList: accessList, - SkipAccountChecks: true, + SkipAccountChecks: skipChecks, } return msg, nil } From c5e33fac0e3e6d9043c94ab289cabc1560bf49aa Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 2 Aug 2023 13:04:49 +0200 Subject: [PATCH 031/119] test coinbase --- internal/ethapi/api_test.go | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 668a1e3f8e21..24e9525c2ff4 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -640,6 +640,7 @@ func TestMulticallV1(t *testing.T) { genBlocks = 10 signer = types.HomesteadSigner{} cac = common.HexToAddress("0x0000000000000000000000000000000000000cac") + coinbase = "0x000000000000000000000000000000000000ffff" genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{ @@ -660,6 +661,7 @@ func TestMulticallV1(t *testing.T) { sha256Address = common.BytesToAddress([]byte{0x02}) ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.HexToAddress(coinbase)) // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei @@ -731,7 +733,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -784,7 +786,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -801,7 +803,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x5208", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -821,7 +823,8 @@ func TestMulticallV1(t *testing.T) { tag: latest, blocks: []CallBatch{{ BlockOverrides: &BlockOverrides{ - Number: (*hexutil.Big)(big.NewInt(11)), + Number: (*hexutil.Big)(big.NewInt(11)), + Coinbase: &cac, }, Calls: []TransactionArgs{ { @@ -851,7 +854,7 @@ func TestMulticallV1(t *testing.T) { Hash: crypto.Keccak256Hash([]byte{0xb}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe891", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: strings.ToLower(cac.String()), Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", @@ -863,7 +866,7 @@ func TestMulticallV1(t *testing.T) { Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe891", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", @@ -932,7 +935,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x10683", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xaacc", @@ -974,7 +977,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x5508", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", Logs: []types.Log{{ @@ -1044,7 +1047,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x52f6", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ // Caller is in this case the contract that invokes ecrecover. ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), @@ -1095,7 +1098,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xa58c", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0xec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5", GasUsed: "0x52dc", @@ -1144,7 +1147,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0xd984", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd984", @@ -1198,7 +1201,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x1b83f", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd166", @@ -1229,7 +1232,7 @@ func TestMulticallV1(t *testing.T) { Hash: n10hash, GasLimit: "0x47e7c4", GasUsed: "0x0", - FeeRecipient: "0x0000000000000000000000000000000000000000", + FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x0", @@ -1241,7 +1244,7 @@ func TestMulticallV1(t *testing.T) { }, } - for i, tc := range testSuite { + for _, tc := range testSuite { t.Run(tc.name, func(t *testing.T) { opts := multicallOpts{BlockStateCalls: tc.blocks} if tc.includeTransfers != nil && *tc.includeTransfers { @@ -1253,18 +1256,18 @@ func TestMulticallV1(t *testing.T) { result, err := api.MulticallV1(context.Background(), opts, tc.tag) if tc.expectErr != nil { if err == nil { - t.Fatalf("test %d: want error %v, have nothing", i, tc.expectErr) + t.Fatalf("test %s: want error %v, have nothing", tc.name, tc.expectErr) } if !errors.Is(err, tc.expectErr) { // Second try if !reflect.DeepEqual(err, tc.expectErr) { - t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + t.Errorf("test %s: error mismatch, want %v, have %v", tc.name, tc.expectErr, err) } } return } if err != nil { - t.Fatalf("test %d: want no error, have %v", i, err) + t.Fatalf("test %s: want no error, have %v", tc.name, err) } // Turn result into res-struct var have []blockRes @@ -1273,7 +1276,7 @@ func TestMulticallV1(t *testing.T) { t.Fatalf("failed to unmarshal result: %v", err) } if !reflect.DeepEqual(have, tc.want) { - t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, tc.want) + t.Errorf("test %s, result mismatch, have\n%v\n, want\n%v\n", tc.name, have, tc.want) } }) } From d4e332e268c6bc859f02ce41bd5a0596975a8eb0 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 3 Aug 2023 16:20:17 +0200 Subject: [PATCH 032/119] fix storage clearing --- core/state/state_object.go | 9 +++ core/state/statedb.go | 3 + internal/ethapi/api_test.go | 117 ++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/core/state/state_object.go b/core/state/state_object.go index 1e28b4c12e5a..bde64d541fd6 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -418,6 +418,15 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { return stateObject } +// clearStorageCache clears the various storage caches +// belonging to the state object. +// It is only used for RPC. +func (s *stateObject) clearStorageCache() { + s.dirtyStorage = make(Storage) + s.originStorage = make(Storage) + s.pendingStorage = make(Storage) +} + // // Attribute accessors // diff --git a/core/state/statedb.go b/core/state/statedb.go index 9dc3b9839f6f..777fc17d13cd 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -448,6 +448,9 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // to a previous incarnation of the object. s.stateObjectsDestruct[addr] = struct{}{} stateObject := s.GetOrNewStateObject(addr) + // If object was already in memory, it might have cached + // storage slots. These should be cleared. + stateObject.clearStorageCache() for k, v := range storage { stateObject.SetState(s.db, k, v) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 24e9525c2ff4..01afa588560c 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -640,6 +640,7 @@ func TestMulticallV1(t *testing.T) { genBlocks = 10 signer = types.HomesteadSigner{} cac = common.HexToAddress("0x0000000000000000000000000000000000000cac") + bab = common.HexToAddress("0x0000000000000000000000000000000000000bab") coinbase = "0x000000000000000000000000000000000000ffff" genesis = &core.Genesis{ Config: params.TestChainConfig, @@ -655,6 +656,30 @@ func TestMulticallV1(t *testing.T) { // } // } cac: {Balance: big.NewInt(params.Ether), Code: common.Hex2Bytes("610dad80ff")}, + bab: { + Balance: big.NewInt(1), + // object "Test" { + // code { + // let value1 := sload(1) + // let value2 := sload(2) + // + // // Shift value1 by 128 bits to the left by multiplying it with 2^128 + // value1 := mul(value1, 0x100000000000000000000000000000000) + // + // // Concatenate value1 and value2 + // let concatenatedValue := add(value1, value2) + // + // // Store the result in memory and return it + // mstore(0, concatenatedValue) + // return(0, 0x20) + // } + // } + Code: common.FromHex("0x600154600254700100000000000000000000000000000000820291508082018060005260206000f3"), + Storage: map[common.Hash]common.Hash{ + common.BigToHash(big.NewInt(1)): common.BigToHash(big.NewInt(10)), + common.BigToHash(big.NewInt(2)): common.BigToHash(big.NewInt(12)), + }, + }, }, } n10hash = crypto.Keccak256Hash([]byte{0xa}).Hex() @@ -1195,6 +1220,11 @@ func TestMulticallV1(t *testing.T) { // } Input: hex2Bytes("610cac610dad600082311115601357600080fd5b6000823b1115602157600080fd5b6000813103602e57600080fd5b5050"), }}, + }, { + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + Input: hex2Bytes("610cac610dad600082311115601357600080fd5b6000823b1115602157600080fd5b6000813103602e57600080fd5b5050"), + }}, }}, want: []blockRes{{ Number: "0xa", @@ -1213,6 +1243,18 @@ func TestMulticallV1(t *testing.T) { Logs: []types.Log{}, Status: "0x1", }}, + }, { + Number: "0xa", + Hash: n10hash, + GasLimit: "0x47e7c4", + GasUsed: "0xe6d9", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0xe6d9", + Logs: []types.Log{}, + Status: "0x1", + }}, }}, }, // Enable validation checks. @@ -1242,6 +1284,76 @@ func TestMulticallV1(t *testing.T) { }}, }}, }, + // Clear storage. + { + name: "clear-storage", + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: { + Code: newBytes(genesis.Alloc[bab].Code), + StateDiff: &map[common.Hash]common.Hash{ + common.BigToHash(big.NewInt(1)): common.BigToHash(big.NewInt(2)), + common.BigToHash(big.NewInt(2)): common.BigToHash(big.NewInt(3)), + }, + }, + bab: { + State: &map[common.Hash]common.Hash{ + common.BigToHash(big.NewInt(1)): common.BigToHash(big.NewInt(1)), + }, + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + }, { + From: &accounts[0].addr, + To: &bab, + }}, + }, { + StateOverrides: &StateOverride{ + randomAccounts[2].addr: { + State: &map[common.Hash]common.Hash{ + common.BigToHash(big.NewInt(1)): common.BigToHash(big.NewInt(5)), + }, + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + }}, + }}, + want: []blockRes{{ + Number: "0xa", + Hash: n10hash, + GasLimit: "0x47e7c4", + GasUsed: "0xc542", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x0000000000000000000000000000000200000000000000000000000000000003", + GasUsed: "0x62a1", + Logs: []types.Log{}, + Status: "0x1", + }, { + ReturnValue: "0x0000000000000000000000000000000100000000000000000000000000000000", + GasUsed: "0x62a1", + Logs: []types.Log{}, + Status: "0x1", + }}, + }, { + Number: "0xa", + Hash: n10hash, + GasLimit: "0x47e7c4", + GasUsed: "0x62a1", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x0000000000000000000000000000000500000000000000000000000000000000", + GasUsed: "0x62a1", + Logs: []types.Log{}, + Status: "0x1", + }}, + }}, + }, } for _, tc := range testSuite { @@ -1318,6 +1430,11 @@ func newUint64(v uint64) *hexutil.Uint64 { return &rpcUint64 } +func newBytes(b []byte) *hexutil.Bytes { + rpcBytes := hexutil.Bytes(b) + return &rpcBytes +} + // testHasher is the helper tool for transaction/receipt list hashing. // The original hasher is trie, in order to get rid of import cycle, // use the testing hasher instead. From b0e78695f509e1e6fe002b55978890f059ec5f67 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 4 Sep 2023 12:28:56 +0200 Subject: [PATCH 033/119] Rename block override field names --- internal/ethapi/api.go | 26 +++++++++++++------------- internal/ethapi/api_test.go | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8676ed6f4c60..e1a5fdec0043 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -999,13 +999,13 @@ func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.P // BlockOverrides is a set of header fields to override. type BlockOverrides struct { - Number *hexutil.Big - Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. - Time *hexutil.Uint64 - GasLimit *hexutil.Uint64 - Coinbase *common.Address - Random *common.Hash - BaseFee *hexutil.Big + Number *hexutil.Big + Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. + Time *hexutil.Uint64 + GasLimit *hexutil.Uint64 + FeeRecipient *common.Address + PrevRandao *common.Hash + BaseFeePerGas *hexutil.Big } // Apply overrides the given header fields into the given block context. @@ -1025,14 +1025,14 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if diff.GasLimit != nil { blockCtx.GasLimit = uint64(*diff.GasLimit) } - if diff.Coinbase != nil { - blockCtx.Coinbase = *diff.Coinbase + if diff.FeeRecipient != nil { + blockCtx.Coinbase = *diff.FeeRecipient } - if diff.Random != nil { - blockCtx.Random = diff.Random + if diff.PrevRandao != nil { + blockCtx.Random = diff.PrevRandao } - if diff.BaseFee != nil { - blockCtx.BaseFee = diff.BaseFee.ToInt() + if diff.BaseFeePerGas != nil { + blockCtx.BaseFee = diff.BaseFeePerGas.ToInt() } } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 01afa588560c..42a3a1f2ca06 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -848,8 +848,8 @@ func TestMulticallV1(t *testing.T) { tag: latest, blocks: []CallBatch{{ BlockOverrides: &BlockOverrides{ - Number: (*hexutil.Big)(big.NewInt(11)), - Coinbase: &cac, + Number: (*hexutil.Big)(big.NewInt(11)), + FeeRecipient: &cac, }, Calls: []TransactionArgs{ { From 56b4402f0c9621e1bc1db28d35a0ca716d758c47 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 4 Sep 2023 15:00:05 +0200 Subject: [PATCH 034/119] auto-set and validate block number and timestamp --- internal/ethapi/api.go | 50 ++++++++++++++++++++++++------- internal/ethapi/api_test.go | 59 ++++++++++++++++++------------------- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e1a5fdec0043..c061b1700ce9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1247,31 +1247,26 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() + blockContexts, err := makeBlockContexts(ctx, s.b, blocks, header) + if err != nil { + return nil, err + } var ( results = make([]blockResult, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap globalGasCap = s.b.RPCGasCap() gp = new(core.GasPool).AddGas(globalGasCap) - prevNumber = header.Number.Uint64() blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) precompiles = vm.ActivePrecompiledContracts(rules).Copy() ) for bi, block := range blocks { - blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) - if block.BlockOverrides != nil { - block.BlockOverrides.Apply(&blockContext) - } - // TODO: Consider hoisting this check up - if blockContext.BlockNumber.Uint64() < prevNumber { - return nil, fmt.Errorf("block numbers must be in order") - } - prevNumber = blockContext.BlockNumber.Uint64() + blockContext = blockContexts[bi] + hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) // State overrides are applied prior to execution of a block if err := block.StateOverrides.ApplyMulticall(state, precompiles); err != nil { return nil, err } - hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) results[bi] = blockResult{ Number: hexutil.Uint64(blockContext.BlockNumber.Uint64()), Hash: hash, @@ -1324,6 +1319,39 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo return results, nil } +func makeBlockContexts(ctx context.Context, b Backend, blocks []CallBatch, header *types.Header) ([]vm.BlockContext, error) { + res := make([]vm.BlockContext, len(blocks)) + var ( + prevNumber = header.Number.Uint64() + prevTimestamp = header.Time + ) + for bi, block := range blocks { + blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + if block.BlockOverrides == nil { + block.BlockOverrides = new(BlockOverrides) + } + if block.BlockOverrides.Number == nil { + n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) + block.BlockOverrides.Number = (*hexutil.Big)(n) + } + if block.BlockOverrides.Time == nil { + t := prevTimestamp + 1 + block.BlockOverrides.Time = (*hexutil.Uint64)(&t) + } + block.BlockOverrides.Apply(&blockContext) + if blockContext.BlockNumber.Uint64() <= prevNumber { + return nil, fmt.Errorf("block numbers must be in order") + } + prevNumber = blockContext.BlockNumber.Uint64() + if blockContext.Time <= prevTimestamp { + return nil, fmt.Errorf("timestamps must be in order") + } + prevTimestamp = blockContext.Time + res[bi] = blockContext + } + return res, nil +} + func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 42a3a1f2ca06..bb4296436afe 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -682,7 +682,7 @@ func TestMulticallV1(t *testing.T) { }, }, } - n10hash = crypto.Keccak256Hash([]byte{0xa}).Hex() + n11hash = crypto.Keccak256Hash([]byte{0xb}).Hex() sha256Address = common.BytesToAddress([]byte{0x02}) ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { @@ -754,8 +754,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", FeeRecipient: coinbase, @@ -807,8 +807,8 @@ func TestMulticallV1(t *testing.T) { }, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", FeeRecipient: coinbase, @@ -824,8 +824,8 @@ func TestMulticallV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xa", - Hash: n10hash, + Number: "0xc", + Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0x5208", FeeRecipient: coinbase, @@ -876,7 +876,7 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: crypto.Keccak256Hash([]byte{0xb}).Hex(), + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xe891", FeeRecipient: strings.ToLower(cac.String()), @@ -956,8 +956,8 @@ func TestMulticallV1(t *testing.T) { }, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x10683", FeeRecipient: coinbase, @@ -998,8 +998,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x5508", FeeRecipient: coinbase, @@ -1008,7 +1008,7 @@ func TestMulticallV1(t *testing.T) { Logs: []types.Log{{ Address: randomAccounts[2].addr, Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, - BlockNumber: 10, + BlockNumber: 11, Data: []byte{}, }}, GasUsed: "0x5508", @@ -1061,15 +1061,14 @@ func TestMulticallV1(t *testing.T) { Code: hex2Bytes("33806000526014600cf3"), }, }, - BlockOverrides: &BlockOverrides{}, Calls: []TransactionArgs{{ From: &randomAccounts[0].addr, To: &randomAccounts[2].addr, }}, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x52f6", FeeRecipient: coinbase, @@ -1119,8 +1118,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xa58c", FeeRecipient: coinbase, @@ -1168,8 +1167,8 @@ func TestMulticallV1(t *testing.T) { }}, includeTransfers: &includeTransfers, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xd984", FeeRecipient: coinbase, @@ -1227,8 +1226,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x1b83f", FeeRecipient: coinbase, @@ -1244,8 +1243,8 @@ func TestMulticallV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xa", - Hash: n10hash, + Number: "0xc", + Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe6d9", FeeRecipient: coinbase, @@ -1270,8 +1269,8 @@ func TestMulticallV1(t *testing.T) { }}, validation: &validation, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x0", FeeRecipient: coinbase, @@ -1324,8 +1323,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xa", - Hash: n10hash, + Number: "0xb", + Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xc542", FeeRecipient: coinbase, @@ -1341,8 +1340,8 @@ func TestMulticallV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xa", - Hash: n10hash, + Number: "0xc", + Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0x62a1", FeeRecipient: coinbase, From 7e17e4cf3a52fd39d296129a8d6777621bda7355 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 4 Sep 2023 17:06:41 +0200 Subject: [PATCH 035/119] implement transfers as logs --- internal/ethapi/api.go | 22 ++--- internal/ethapi/api_test.go | 38 ++++++--- internal/ethapi/transfertracer.go | 135 +++++++++++++++++++++++++----- 3 files changed, 144 insertions(+), 51 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c061b1700ce9..4fdb138ccc34 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1199,7 +1199,6 @@ type blockResult struct { type callResult struct { ReturnValue hexutil.Bytes `json:"return"` Logs []*types.Log `json:"logs"` - Transfers []transfer `json:"transfers,omitempty"` GasUsed hexutil.Uint64 `json:"gasUsed"` Status hexutil.Uint64 `json:"status"` Error string `json:"error,omitempty"` @@ -1278,12 +1277,9 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } gasUsed := uint64(0) for i, call := range block.Calls { - // Hack to get logs from statedb which stores logs by txhash. - txhash := common.BigToHash(big.NewInt(int64(i))) - state.SetTxContext(txhash, i) - vmConfig := &vm.Config{NoBaseFee: true} - if opts.TraceTransfers { - vmConfig.Tracer = newTracer() + vmConfig := &vm.Config{ + NoBaseFee: true, + Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), hash, common.Hash{}, uint(i)), } result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { @@ -1294,16 +1290,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if len(result.Revert()) > 0 { result.Err = newRevertError(result) } - logs := state.GetLogs(txhash, blockContext.BlockNumber.Uint64(), common.Hash{}) - // Clear the garbage txhash that was filled in. - for _, l := range logs { - l.TxHash = common.Hash{} - } - var transfers []transfer - if opts.TraceTransfers { - transfers = vmConfig.Tracer.(*tracer).Transfers() - } - callRes := callResult{ReturnValue: result.Return(), Logs: logs, Transfers: transfers, GasUsed: hexutil.Uint64(result.UsedGas)} + logs := vmConfig.Tracer.(*tracer).Logs() + callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) callRes.Error = result.Err.Error() diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index bb4296436afe..8ace919007fd 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -712,7 +712,6 @@ func TestMulticallV1(t *testing.T) { Logs []types.Log GasUsed string Status string - Transfers []transfer } type blockRes struct { Number string @@ -1010,6 +1009,7 @@ func TestMulticallV1(t *testing.T) { Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, BlockNumber: 11, Data: []byte{}, + BlockHash: hex2Hash(n11hash), }}, GasUsed: "0x5508", Status: "0x1", @@ -1175,18 +1175,28 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd984", - Transfers: []transfer{ - { - From: accounts[0].addr, - To: randomAccounts[0].addr, - Value: big.NewInt(50), - }, { - From: randomAccounts[0].addr, - To: randomAccounts[1].addr, - Value: big.NewInt(100), + Logs: []types.Log{{ + Address: common.Address{}, + Topics: []common.Hash{ + transferTopic, + accounts[0].addr.Hash(), + randomAccounts[0].addr.Hash(), }, - }, - Logs: []types.Log{}, + Data: common.BigToHash(big.NewInt(50)).Bytes(), + BlockNumber: 11, + BlockHash: hex2Hash(n11hash), + }, { + Address: common.Address{}, + Topics: []common.Hash{ + transferTopic, + randomAccounts[0].addr.Hash(), + randomAccounts[1].addr.Hash(), + }, + Data: common.BigToHash(big.NewInt(100)).Bytes(), + BlockNumber: 11, + BlockHash: hex2Hash(n11hash), + Index: 1, + }}, Status: "0x1", }}, }}, @@ -1532,3 +1542,7 @@ func TestRPCMarshalBlock(t *testing.T) { } } } + +func hex2Hash(s string) common.Hash { + return common.BytesToHash(common.FromHex(s)) +} diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go index 4db95887831c..62b6febd4fa4 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/transfertracer.go @@ -17,38 +17,57 @@ package ethapi import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" ) -type callLog struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data hexutil.Bytes `json:"data"` -} - -type transfer struct { - From common.Address `json:"from"` - To common.Address `json:"to"` - Value *big.Int `json:"value"` -} +// keccak256("Transfer(address,address,uint256)") +var transferTopic = common.HexToHash("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") -// tracer is a simple tracer that records all ether transfers. -// This includes tx value, call value, and self destructs. +// 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 +// +// TODO: embed noopTracer type tracer struct { - transfers []transfer + logs []*types.Log + traceTransfers bool + // TODO: replace with tracers.Context once extended tracer PR is merged. + blockNumber uint64 + blockHash common.Hash + txHash common.Hash + txIdx uint } -func newTracer() *tracer { - return &tracer{transfers: make([]transfer, 0)} +func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIdx uint) *tracer { + return &tracer{ + logs: make([]*types.Log, 0), + traceTransfers: traceTransfers, + blockNumber: blockNumber, + blockHash: blockHash, + txHash: txHash, + txIdx: txIdx, + } } func (t *tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { if value.Cmp(common.Big0) > 0 { - t.transfers = append(t.transfers, transfer{From: from, To: to, Value: value}) + t.captureTransfer(from, to, value) } } @@ -56,6 +75,34 @@ func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, err error) { } func (t *tracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + // skip if the previous op caused an error + if err != nil { + return + } + // TODO: Use OnLog instead of CaptureState once extended tracer PR is merged. + switch op { + case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4: + size := int(op - vm.LOG0) + + stack := scope.Stack + stackData := stack.Data() + + // Don't modify the stack + mStart := stackData[len(stackData)-1] + mSize := stackData[len(stackData)-2] + topics := make([]common.Hash, size) + for i := 0; i < size; i++ { + topic := stackData[len(stackData)-2-(i+1)] + topics[i] = common.Hash(topic.Bytes32()) + } + + data, err := getMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64())) + if err != nil { + // mSize was unrealistically large + return + } + t.captureLog(scope.Contract.Address(), topics, data) + } } func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { @@ -63,8 +110,8 @@ func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope * func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { toCopy := to - if value.Cmp(common.Big0) > 0 { - t.transfers = append(t.transfers, transfer{From: from, To: toCopy, Value: value}) + if value != nil && value.Cmp(common.Big0) > 0 { + t.captureTransfer(from, toCopy, value) } } @@ -76,6 +123,50 @@ func (t *tracer) CaptureTxStart(gasLimit uint64) {} func (t *tracer) CaptureTxEnd(restGas uint64) {} -func (t *tracer) Transfers() []transfer { - return t.transfers +func (t *tracer) captureLog(address common.Address, topics []common.Hash, data []byte) { + t.logs = append(t.logs, &types.Log{ + Address: address, + Topics: topics, + Data: data, + BlockNumber: t.blockNumber, + BlockHash: t.blockHash, + TxHash: t.txHash, + TxIndex: t.txIdx, + Index: uint(len(t.logs)), + }) +} + +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(common.Address{}, topics, common.BigToHash(value).Bytes()) +} + +func (t *tracer) Logs() []*types.Log { + return t.logs +} + +// TODO: remove once extended tracer PR is merged. +func getMemoryCopyPadded(m *vm.Memory, offset, size int64) ([]byte, error) { + if offset < 0 || size < 0 { + return nil, fmt.Errorf("offset or size must not be negative") + } + if int(offset+size) < m.Len() { // slice fully inside memory + return m.GetCopy(offset, size), nil + } + paddingNeeded := int(offset+size) - m.Len() + if paddingNeeded > 1024*1024 { + return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded) + } + cpy := make([]byte, size) + if overlap := int64(m.Len()) - offset; overlap > 0 { + copy(cpy, m.GetPtr(offset, overlap)) + } + return cpy, nil } From fe3bb832bfb28b5bdcaff1914ca60d965961473e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 5 Sep 2023 13:37:36 +0200 Subject: [PATCH 036/119] add prevrandao to result --- internal/ethapi/api.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4fdb138ccc34..8e58cd63cea4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1193,6 +1193,7 @@ type blockResult struct { GasUsed hexutil.Uint64 `json:"gasUsed"` FeeRecipient common.Address `json:"feeRecipient"` BaseFee *hexutil.Big `json:"baseFeePerGas"` + PrevRandao common.Hash `json:"prevRandao"` Calls []callResult `json:"calls"` } @@ -1275,6 +1276,9 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo BaseFee: (*hexutil.Big)(blockContext.BaseFee), Calls: make([]callResult, len(block.Calls)), } + if blockContext.Random != nil { + results[bi].PrevRandao = *blockContext.Random + } gasUsed := uint64(0) for i, call := range block.Calls { vmConfig := &vm.Config{ From 906d93a330d6fad7f9ce893005ba3307e6d9b97f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 5 Sep 2023 16:47:42 +0200 Subject: [PATCH 037/119] clear logs on call fail --- internal/ethapi/transfertracer.go | 37 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go index 62b6febd4fa4..2a7b76f55b56 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/transfertracer.go @@ -45,7 +45,10 @@ var transferTopic = common.HexToHash("ddf252ad1be2c89b69c2b068fc378daa952ba7f163 // // TODO: embed noopTracer type tracer struct { - logs []*types.Log + // logs keeps logs for all open call frames. + // This lets us clear logs for failed logs. + logs [][]*types.Log + count int traceTransfers bool // TODO: replace with tracers.Context once extended tracer PR is merged. blockNumber uint64 @@ -56,7 +59,7 @@ type tracer struct { func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIdx uint) *tracer { return &tracer{ - logs: make([]*types.Log, 0), + logs: make([][]*types.Log, 1), traceTransfers: traceTransfers, blockNumber: blockNumber, blockHash: blockHash, @@ -72,6 +75,9 @@ func (t *tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addres } func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + if err != nil { + t.logs[0] = nil + } } func (t *tracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { @@ -109,6 +115,7 @@ func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope * } func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.logs = append(t.logs, make([]*types.Log, 0)) toCopy := to if value != nil && value.Cmp(common.Big0) > 0 { t.captureTransfer(from, toCopy, value) @@ -117,14 +124,29 @@ func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Addr // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error) {} +func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error) { + 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 err == nil { + t.logs[size-1] = append(t.logs[size-1], call...) + } +} func (t *tracer) CaptureTxStart(gasLimit uint64) {} -func (t *tracer) CaptureTxEnd(restGas uint64) {} +func (t *tracer) CaptureTxEnd(restGas uint64) { +} func (t *tracer) captureLog(address common.Address, topics []common.Hash, data []byte) { - t.logs = append(t.logs, &types.Log{ + t.logs[len(t.logs)-1] = append(t.logs[len(t.logs)-1], &types.Log{ Address: address, Topics: topics, Data: data, @@ -132,8 +154,9 @@ func (t *tracer) captureLog(address common.Address, topics []common.Hash, data [ BlockHash: t.blockHash, TxHash: t.txHash, TxIndex: t.txIdx, - Index: uint(len(t.logs)), + Index: uint(t.count), }) + t.count++ } func (t *tracer) captureTransfer(from, to common.Address, value *big.Int) { @@ -149,7 +172,7 @@ func (t *tracer) captureTransfer(from, to common.Address, value *big.Int) { } func (t *tracer) Logs() []*types.Log { - return t.logs + return t.logs[0] } // TODO: remove once extended tracer PR is merged. From 8e026758feca803e8d433bc331449f0a1fc5243e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 7 Sep 2023 17:59:53 +0200 Subject: [PATCH 038/119] add txhash to logs --- internal/ethapi/api.go | 20 +++++++++- internal/ethapi/api_test.go | 73 +++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8e58cd63cea4..c6b2716b0e6c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1281,9 +1281,27 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } gasUsed := uint64(0) for i, call := range block.Calls { + // setDefaults will consult txpool's nonce tracker. Work around that. + 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 { + leftGas := gp.Gas() + call.Gas = (*hexutil.Uint64)(&leftGas) + } + if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { + call.GasPrice = (*hexutil.Big)(big.NewInt(0)) + } + // TODO: Tx and message used for executing will probably have different values. + if err := call.setDefaults(ctx, s.b); err != nil { + return nil, err + } + tx := call.ToTransaction() vmConfig := &vm.Config{ NoBaseFee: true, - Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), hash, common.Hash{}, uint(i)), + Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), hash, tx.Hash(), uint(i)), } result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 8ace919007fd..a5ab9a74cac9 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -241,7 +241,7 @@ func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { panic("implement me") } -func (b testBackend) CurrentHeader() *types.Header { panic("implement me") } +func (b testBackend) CurrentHeader() *types.Header { return b.chain.CurrentHeader() } func (b testBackend) CurrentBlock() *types.Header { panic("implement me") } func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { if number == rpc.LatestBlockNumber { @@ -706,10 +706,21 @@ func TestMulticallV1(t *testing.T) { includeTransfers = true validation = true ) + type log struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + // Skip txHash + //TxHash common.Hash `json:"transactionHash" gencodec:"required"` + TxIndex hexutil.Uint `json:"transactionIndex"` + BlockHash common.Hash `json:"blockHash"` + Index hexutil.Uint `json:"logIndex"` + } type callRes struct { ReturnValue string `json:"return"` Error string - Logs []types.Log + Logs []log GasUsed string Status string } @@ -761,12 +772,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0x5208", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, @@ -814,12 +825,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0x5208", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }, { @@ -831,12 +842,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0x0", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x0", Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), }}, @@ -882,7 +893,7 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }, { @@ -894,7 +905,7 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, @@ -963,12 +974,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xaacc", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000005", GasUsed: "0x5bb7", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, @@ -1004,11 +1015,11 @@ func TestMulticallV1(t *testing.T) { FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", - Logs: []types.Log{{ + Logs: []log{{ Address: randomAccounts[2].addr, Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, - BlockNumber: 11, - Data: []byte{}, + BlockNumber: hexutil.Uint64(11), + Data: hexutil.Bytes{}, BlockHash: hex2Hash(n11hash), }}, GasUsed: "0x5508", @@ -1076,7 +1087,7 @@ func TestMulticallV1(t *testing.T) { // Caller is in this case the contract that invokes ecrecover. ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), GasUsed: "0x52f6", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, @@ -1126,12 +1137,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0xec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5", GasUsed: "0x52dc", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x0000000000000000000000000000000000000000000000000000000000000001", GasUsed: "0x52b0", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, @@ -1175,15 +1186,15 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd984", - Logs: []types.Log{{ + Logs: []log{{ Address: common.Address{}, Topics: []common.Hash{ transferTopic, accounts[0].addr.Hash(), randomAccounts[0].addr.Hash(), }, - Data: common.BigToHash(big.NewInt(50)).Bytes(), - BlockNumber: 11, + Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), + BlockNumber: hexutil.Uint64(11), BlockHash: hex2Hash(n11hash), }, { Address: common.Address{}, @@ -1192,10 +1203,10 @@ func TestMulticallV1(t *testing.T) { randomAccounts[0].addr.Hash(), randomAccounts[1].addr.Hash(), }, - Data: common.BigToHash(big.NewInt(100)).Bytes(), - BlockNumber: 11, + Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), + BlockNumber: hexutil.Uint64(11), BlockHash: hex2Hash(n11hash), - Index: 1, + Index: hexutil.Uint(1), }}, Status: "0x1", }}, @@ -1244,12 +1255,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd166", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x", GasUsed: "0xe6d9", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }, { @@ -1261,7 +1272,7 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xe6d9", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, @@ -1287,7 +1298,7 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x0", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x0", Error: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 10000000)", accounts[2].addr), }}, @@ -1341,12 +1352,12 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x0000000000000000000000000000000200000000000000000000000000000003", GasUsed: "0x62a1", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }, { ReturnValue: "0x0000000000000000000000000000000100000000000000000000000000000000", GasUsed: "0x62a1", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }, { @@ -1358,7 +1369,7 @@ func TestMulticallV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x0000000000000000000000000000000500000000000000000000000000000000", GasUsed: "0x62a1", - Logs: []types.Log{}, + Logs: []log{}, Status: "0x1", }}, }}, From a0d2706907fb98f7a7ad8f7321ca7f5d599683aa Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 11 Sep 2023 19:51:53 +0200 Subject: [PATCH 039/119] moveTo only for precompiles --- eth/tracers/api.go | 6 +- internal/ethapi/api.go | 106 ++++++++---------------------------- internal/ethapi/api_test.go | 4 +- 3 files changed, 28 insertions(+), 88 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 1be3a36b6ad3..1e55d8dcfe18 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -902,10 +902,12 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) // Apply the customization rules if required. 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 } - config.BlockOverrides.Apply(&vmctx) } // Execute the trace msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee(), true) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c6b2716b0e6c..48715bc305c4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -868,100 +868,35 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address // if statDiff is set, all diff will be applied first and then execute the call // message. type OverrideAccount struct { - Nonce *hexutil.Uint64 `json:"nonce"` - Code *hexutil.Bytes `json:"code"` - Balance **hexutil.Big `json:"balance"` - State *map[common.Hash]common.Hash `json:"state"` - StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` - MoveTo *common.Address `json:"moveToAddress"` + Nonce *hexutil.Uint64 `json:"nonce"` + Code *hexutil.Bytes `json:"code"` + Balance **hexutil.Big `json:"balance"` + State *map[common.Hash]common.Hash `json:"state"` + StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` + MovePrecompileTo *common.Address `json:"movePrecompileToAddress"` } // StateOverride is the collection of overridden accounts. type StateOverride map[common.Address]OverrideAccount // Apply overrides the fields of specified accounts into the given state. -func (diff *StateOverride) Apply(state *state.StateDB) error { - if diff == nil { - return nil - } - for addr, account := range *diff { - // Override account nonce. - if account.Nonce != nil { - state.SetNonce(addr, uint64(*account.Nonce)) - } - // Override account(contract) code. - if account.Code != nil { - state.SetCode(addr, *account.Code) - } - // Override account balance. - if account.Balance != nil { - state.SetBalance(addr, (*big.Int)(*account.Balance)) - } - if account.State != nil && account.StateDiff != nil { - return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) - } - // Replace entire state if caller requires. - if account.State != nil { - state.SetStorage(addr, *account.State) - } - // Apply state diff into specified accounts. - if account.StateDiff != nil { - for key, value := range *account.StateDiff { - state.SetState(addr, key, value) - } - } - } - // Now finalize the changes. Finalize is normally performed between transactions. - // By using finalize, the overrides are semantically behaving as - // if they were created in a transaction just before the tracing occur. - state.Finalise(false) - return nil -} - -// ApplyMulticall overrides the fields of specified accounts into the given state. -// TODO: consider MoveTo by address mapping -func (diff *StateOverride) ApplyMulticall(state *state.StateDB, precompiles vm.PrecompiledContracts) error { +func (diff *StateOverride) Apply(state *state.StateDB, precompiles vm.PrecompiledContracts) error { if diff == nil { return nil } for addr, account := range *diff { p, isPrecompile := precompiles[addr] - // The MoveTo feature makes it possible to replace precompiles and EVM - // contracts in all the following configurations: - // 1. Precompile -> Precompile - // 2. Precompile -> EVM contract - // 3. EVM contract -> Precompile - // 4. EVM contract -> EVM contract - if account.MoveTo != nil { - if isPrecompile { - // Clear destination account which may be an EVM contract. - if !state.Empty(*account.MoveTo) { - state.SetCode(*account.MoveTo, nil) - state.SetNonce(*account.MoveTo, 0) - state.SetBalance(*account.MoveTo, big.NewInt(0)) - state.SetStorage(*account.MoveTo, map[common.Hash]common.Hash{}) - } - // If destination is a precompile, it will be simply replaced. - precompiles[*account.MoveTo] = p - } else { - state.SetBalance(*account.MoveTo, state.GetBalance(addr)) - state.SetNonce(*account.MoveTo, state.GetNonce(addr)) - state.SetCode(*account.MoveTo, state.GetCode(addr)) - // Copy storage over - // TODO: Use snaps - state.ForEachStorage(addr, func(key, value common.Hash) bool { - state.SetState(*account.MoveTo, key, value) - return true - }) - // Clear source storage - state.SetStorage(addr, map[common.Hash]common.Hash{}) - if precompiles[*account.MoveTo] != nil { - delete(precompiles, *account.MoveTo) - } + // 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()) } + precompiles[*account.MovePrecompileTo] = p } if isPrecompile { - // Now that the contract is moved it can be deleted. delete(precompiles, addr) } // Override account nonce. @@ -1075,13 +1010,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash if state == nil || err != nil { return nil, err } - if err := overrides.Apply(state); err != nil { - return nil, err - } 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 := vm.ActivePrecompiledContracts(rules).Copy() + if err := overrides.Apply(state, precompiles); err != nil { + return nil, err + } + // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. var cancel context.CancelFunc @@ -1264,7 +1202,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo blockContext = blockContexts[bi] hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) // State overrides are applied prior to execution of a block - if err := block.StateOverrides.ApplyMulticall(state, precompiles); err != nil { + if err := block.StateOverrides.Apply(state, precompiles); err != nil { return nil, err } results[bi] = blockResult{ diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index a5ab9a74cac9..2887b0624ca3 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1114,8 +1114,8 @@ func TestMulticallV1(t *testing.T) { // return(memPtr, size) // } // } - Code: hex2Bytes("365981600082378181f3"), - MoveTo: &randomAccounts[2].addr, + Code: hex2Bytes("365981600082378181f3"), + MovePrecompileTo: &randomAccounts[2].addr, }, }, Calls: []TransactionArgs{{ From 32312912c935288cd911a39ca5e6b7cd1a24e695 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 20 Sep 2023 17:06:21 +0200 Subject: [PATCH 040/119] optional block param --- internal/ethapi/api.go | 8 ++++++-- internal/ethapi/api_test.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 13ac8cb273e2..cc3f7d09ad4b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1243,8 +1243,12 @@ type multicallOpts struct { // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blockNrOrHash rpc.BlockNumberOrHash) ([]blockResult, error) { - state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) +func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]blockResult, error) { + if blockNrOrHash == nil { + n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + blockNrOrHash = &n + } + state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) if state == nil || err != nil { return nil, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index b1894ad80dfa..6766dff434d8 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1659,7 +1659,7 @@ func TestMulticallV1(t *testing.T) { if tc.validation != nil && *tc.validation { opts.Validation = true } - result, err := api.MulticallV1(context.Background(), opts, tc.tag) + result, err := api.MulticallV1(context.Background(), opts, &tc.tag) if tc.expectErr != nil { if err == nil { t.Fatalf("test %s: want error %v, have nothing", tc.name, tc.expectErr) From 3df0fec8e2d06eae3dcd443ea97504274970aa33 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 20 Sep 2023 17:12:24 +0200 Subject: [PATCH 041/119] rename return to returnData --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index cc3f7d09ad4b..afcbce8a2e22 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1214,7 +1214,7 @@ type blockResult struct { } type callResult struct { - ReturnValue hexutil.Bytes `json:"return"` + ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` GasUsed hexutil.Uint64 `json:"gasUsed"` Status hexutil.Uint64 `json:"status"` From 760f5e5b2414039d15d8a2c412cd9e9da42b7eb2 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 20 Sep 2023 18:54:43 +0200 Subject: [PATCH 042/119] fix tx gaslimit and type default --- internal/ethapi/api.go | 24 ++++++++++++------------ internal/ethapi/api_test.go | 6 +++--- internal/ethapi/transaction_args.go | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index afcbce8a2e22..47687fb24238 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -449,7 +449,7 @@ func (s *PersonalAccountAPI) signTransaction(ctx context.Context, args *Transact return nil, err } // Assemble the transaction and sign with the wallet - tx := args.toTransaction() + tx := args.toTransaction(false) return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID) } @@ -492,7 +492,7 @@ func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transacti return nil, errors.New("nonce not specified") } // Before actually signing the transaction, ensure the transaction fee is reasonable. - tx := args.toTransaction() + tx := args.toTransaction(false) if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } @@ -1299,7 +1299,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if blockContext.Random != nil { results[bi].PrevRandao = *blockContext.Random } - gasUsed := uint64(0) + var gasUsed uint64 for i, call := range block.Calls { // setDefaults will consult txpool's nonce tracker. Work around that. if call.Nonce == nil { @@ -1308,8 +1308,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } // Let the call run wild unless explicitly specified. if call.Gas == nil { - leftGas := gp.Gas() - call.Gas = (*hexutil.Uint64)(&leftGas) + remaining := blockContext.GasLimit - gasUsed + call.Gas = (*hexutil.Uint64)(&remaining) } if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { call.GasPrice = (*hexutil.Big)(big.NewInt(0)) @@ -1318,7 +1318,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo if err := call.setDefaults(ctx, s.b); err != nil { return nil, err } - tx := call.ToTransaction() + tx := call.ToTransaction(true) vmConfig := &vm.Config{ NoBaseFee: true, Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), hash, tx.Hash(), uint(i)), @@ -1853,7 +1853,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) 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(false).Hash(), err) } if tracer.Equal(prevTracer) { return accessList, res.UsedGas, res.Err, nil @@ -2121,7 +2121,7 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr return common.Hash{}, err } // Assemble the transaction and sign with the wallet - tx := args.toTransaction() + tx := args.toTransaction(false) signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) if err != nil { @@ -2139,7 +2139,7 @@ func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionAr return nil, err } // Assemble the transaction and obtain rlp - tx := args.toTransaction() + tx := args.toTransaction(false) data, err := tx.MarshalBinary() if err != nil { return nil, err @@ -2205,7 +2205,7 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr return nil, err } // Before actually sign the transaction, ensure the transaction fee is reasonable. - tx := args.toTransaction() + tx := args.toTransaction(false) if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } @@ -2253,7 +2253,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g if err := sendArgs.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } - matchTx := sendArgs.toTransaction() + matchTx := sendArgs.toTransaction(false) // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. var price = matchTx.GasPrice() @@ -2283,7 +2283,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction()) + signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction(false)) if err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 6766dff434d8..56b08fc59e02 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -992,7 +992,7 @@ func TestMulticallV1(t *testing.T) { Index hexutil.Uint `json:"logIndex"` } type callRes struct { - ReturnValue string `json:"return"` + ReturnValue string `json:"returnData"` Error string Logs []log GasUsed string @@ -1123,7 +1123,7 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x0", Logs: []log{}, Status: "0x0", - Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 9937000)", randomAccounts[3].addr.String()), + Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), }}, }}, }, { @@ -1574,7 +1574,7 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x0", Logs: []log{}, Status: "0x0", - Error: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 10000000)", accounts[2].addr), + Error: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), }}, }}, }, diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 7ce61b78d378..95983d797cd1 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -286,10 +286,10 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, sk // toTransaction converts the arguments to a transaction. // This assumes that setDefaults has been called. -func (args *TransactionArgs) toTransaction() *types.Transaction { +func (args *TransactionArgs) toTransaction(type2 bool) *types.Transaction { var data types.TxData switch { - case args.MaxFeePerGas != nil: + case args.MaxFeePerGas != nil || type2: al := types.AccessList{} if args.AccessList != nil { al = *args.AccessList @@ -331,6 +331,6 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { // ToTransaction converts the arguments to a transaction. // This assumes that setDefaults has been called. -func (args *TransactionArgs) ToTransaction() *types.Transaction { - return args.toTransaction() +func (args *TransactionArgs) ToTransaction(type2 bool) *types.Transaction { + return args.toTransaction(type2) } From b3996e4719b32e05be1f01046742ecd8416f1d1b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 20 Sep 2023 19:25:36 +0200 Subject: [PATCH 043/119] dont set defaults for calls --- internal/ethapi/api.go | 7 +-- internal/ethapi/transaction_args.go | 59 ++++++++++++++++-------- internal/ethapi/transaction_args_test.go | 14 ++++-- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 47687fb24238..a1d29ec52363 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1312,10 +1312,11 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo call.Gas = (*hexutil.Uint64)(&remaining) } if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { - call.GasPrice = (*hexutil.Big)(big.NewInt(0)) + call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) + call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) } - // TODO: Tx and message used for executing will probably have different values. - if err := call.setDefaults(ctx, s.b); err != nil { + // TODO: check chainID and against current header for london fees + if err := call.validateAll(); err != nil { return nil, err } tx := call.ToTransaction(true) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 95983d797cd1..7bad196f029a 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -74,8 +74,46 @@ func (args *TransactionArgs) data() []byte { return nil } +func (args *TransactionArgs) validateAll() error { + if err := args.validate(); err != nil { + return err + } + return args.validateFees() +} + +func (args *TransactionArgs) validate() error { + if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { + return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) + } + if args.To == nil && len(args.data()) == 0 { + return errors.New(`contract creation without any data provided`) + } + return nil +} + +func (args *TransactionArgs) validateFees() error { + // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error. + if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { + return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } + // If the tx has completely specified a fee mechanism, no default is needed. This allows users + // who are not yet synced past London to get defaults for other tx values. See + // https://github.com/ethereum/go-ethereum/pull/23274 for more information. + eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil + if (args.GasPrice != nil && !eip1559ParamsSet) || (args.GasPrice == nil && eip1559ParamsSet) { + // Sanity check the EIP-1559 fee parameters if present. + if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { + return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) + } + } + return nil +} + // setDefaults fills in default values for unspecified tx fields. func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { + if err := args.validate(); err != nil { + return err + } if err := args.setFeeDefaults(ctx, b); err != nil { return err } @@ -89,12 +127,6 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { } args.Nonce = (*hexutil.Uint64)(&nonce) } - if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { - return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) - } - if args.To == nil && len(args.data()) == 0 { - return errors.New(`contract creation without any data provided`) - } // Estimate the gas usage if necessary. if args.Gas == nil { // These fields are immutable during the estimation, safe to @@ -133,19 +165,10 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { // setFeeDefaults fills in default fee values for unspecified tx fields. func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) error { - // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error. - if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { - return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + if err := args.validateFees(); err != nil { + return err } - // If the tx has completely specified a fee mechanism, no default is needed. This allows users - // who are not yet synced past London to get defaults for other tx values. See - // https://github.com/ethereum/go-ethereum/pull/23274 for more information. - eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil - if (args.GasPrice != nil && !eip1559ParamsSet) || (args.GasPrice == nil && eip1559ParamsSet) { - // Sanity check the EIP-1559 fee parameters if present. - if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { - return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) - } + if args.GasPrice != nil && args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas == nil { return nil } // Now attempt to fill in default value depending on whether London is active or not. diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 9161d5e681f2..d4b7d83b41ce 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -19,6 +19,7 @@ package ethapi import ( "context" "errors" + "fmt" "math/big" "reflect" "testing" @@ -195,10 +196,15 @@ func TestSetFeeDefaults(t *testing.T) { } got := test.in err := got.setFeeDefaults(ctx, b) - if err != nil && err.Error() == test.err.Error() { - // Test threw expected error. - continue - } else if err != nil { + fmt.Printf("err: %v\n", err) + if err != nil { + if test.err == nil { + t.Fatalf("test %d (%s): unexpected error: %s", i, test.name, err) + } + if err.Error() == test.err.Error() { + // Test threw expected error. + continue + } t.Fatalf("test %d (%s): unexpected error: %s", i, test.name, err) } if !reflect.DeepEqual(got, test.want) { From 100c593ad9d4fe2f557cddf6fbf06c9026d7a240 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 29 Sep 2023 16:34:45 +0200 Subject: [PATCH 044/119] fix crasher --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a1d29ec52363..772aaf5684e0 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1303,7 +1303,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo for i, call := range block.Calls { // setDefaults will consult txpool's nonce tracker. Work around that. if call.Nonce == nil { - nonce := state.GetNonce(*call.From) + nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) } // Let the call run wild unless explicitly specified. From 523e3d24fa653acb1156861afe5b3d3f7083b7b8 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 29 Sep 2023 16:44:10 +0200 Subject: [PATCH 045/119] cover it in test case --- internal/ethapi/api_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 56b08fc59e02..31636ced3e16 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1035,6 +1035,8 @@ func TestMulticallV1(t *testing.T) { From: &randomAccounts[1].addr, To: &randomAccounts[2].addr, Value: (*hexutil.Big)(big.NewInt(1000)), + }, { + To: &randomAccounts[3].addr, }}, }}, want: []blockRes{{ From d5c0ff4eae7bccd23d0b029efa0e2f94c78cec0e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 4 Oct 2023 16:11:49 +0300 Subject: [PATCH 046/119] add error codes to calls --- internal/ethapi/api.go | 11 ++++-- internal/ethapi/api_test.go | 17 ++++++--- internal/ethapi/errors.go | 70 +++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 internal/ethapi/errors.go diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 772aaf5684e0..bcdf357a9969 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1218,7 +1218,7 @@ type callResult struct { Logs []*types.Log `json:"logs"` GasUsed hexutil.Uint64 `json:"gasUsed"` Status hexutil.Uint64 `json:"status"` - Error string `json:"error,omitempty"` + Error *callError `json:"error,omitempty"` } func (r *callResult) MarshalJSON() ([]byte, error) { @@ -1326,7 +1326,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } result, err := applyMessage(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { - results[bi].Calls[i] = callResult{Error: err.Error(), Status: hexutil.Uint64(types.ReceiptStatusFailed)} + callErr := callErrorFromError(err) + results[bi].Calls[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue } // If the result contains a revert reason, try to unpack it. @@ -1337,7 +1338,11 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) - callRes.Error = result.Err.Error() + if errors.Is(result.Err, vm.ErrExecutionReverted) { + callRes.Error = &callError{Message: result.Err.Error(), Code: -32000} + } else { + callRes.Error = &callError{Message: result.Err.Error(), Code: -32015} + } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 31636ced3e16..9b61c84e4e52 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -991,9 +991,13 @@ func TestMulticallV1(t *testing.T) { BlockHash common.Hash `json:"blockHash"` Index hexutil.Uint `json:"logIndex"` } + type callErr struct { + Message string + Code int + } type callRes struct { ReturnValue string `json:"returnData"` - Error string + Error callErr Logs []log GasUsed string Status string @@ -1043,7 +1047,7 @@ func TestMulticallV1(t *testing.T) { Number: "0xb", Hash: n11hash, GasLimit: "0x47e7c4", - GasUsed: "0xa410", + GasUsed: "0xf618", FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", @@ -1055,6 +1059,11 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x5208", Logs: []log{}, Status: "0x1", + }, { + ReturnValue: "0x", + GasUsed: "0x5208", + Logs: []log{}, + Status: "0x1", }}, }}, }, { @@ -1125,7 +1134,7 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x0", Logs: []log{}, Status: "0x0", - Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), + Error: callErr{Message: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), Code: errCodeInsufficientFunds}, }}, }}, }, { @@ -1576,7 +1585,7 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x0", Logs: []log{}, Status: "0x0", - Error: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), + Error: callErr{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, }}, }}, }, diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go new file mode 100644 index 000000000000..6ea92823a240 --- /dev/null +++ b/internal/ethapi/errors.go @@ -0,0 +1,70 @@ +// 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 . + +package ethapi + +import ( + "errors" + + "github.com/ethereum/go-ethereum/core" +) + +type callError struct { + Message string `json:"message"` + Code int `json:"code"` +} + +const ( + errCodeNonceTooHigh = -38011 + errCodeNonceTooLow = -38010 + errCodeInsufficientFunds = -38014 + errCodeIntrinsicGas = -38013 + errCodeInternalError = -32603 + errCodeInvalidParams = -32602 +) + +func callErrorFromError(err error) *callError { + if err == nil { + return nil + } + switch { + case errors.Is(err, core.ErrNonceTooHigh): + return &callError{Message: err.Error(), Code: errCodeNonceTooHigh} + case errors.Is(err, core.ErrNonceTooLow): + return &callError{Message: err.Error(), Code: errCodeNonceTooLow} + case errors.Is(err, core.ErrSenderNoEOA): + // TODO + case errors.Is(err, core.ErrFeeCapVeryHigh): + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrTipVeryHigh): + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrTipAboveFeeCap): + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrFeeCapTooLow): + // TODO + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrInsufficientFunds): + return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + case errors.Is(err, core.ErrIntrinsicGas): + return &callError{Message: err.Error(), Code: errCodeIntrinsicGas} + case errors.Is(err, core.ErrInsufficientFundsForTransfer): + return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + } + return &callError{ + Message: err.Error(), + Code: errCodeInternalError, + } +} From 45ba37ecc9aad81d65aa47b82eb82b15abdfdeaf Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 5 Oct 2023 17:50:36 +0300 Subject: [PATCH 047/119] compute block hash based on header --- internal/ethapi/api.go | 145 +++++++++++++++++++++++++++--------- internal/ethapi/api_test.go | 27 +------ 2 files changed, 115 insertions(+), 57 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bcdf357a9969..fd02923de09a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1044,6 +1044,34 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) { } } +// ApplyToHeader overrides the given fields into a header. +func (diff *BlockOverrides) ApplyToHeader(header *types.Header) { + if diff == nil { + return + } + if diff.Number != nil { + header.Number = diff.Number.ToInt() + } + if diff.Difficulty != nil { + header.Difficulty = diff.Difficulty.ToInt() + } + if diff.Time != nil { + header.Time = uint64(*diff.Time) + } + if diff.GasLimit != nil { + header.GasLimit = uint64(*diff.GasLimit) + } + if diff.FeeRecipient != nil { + header.Coinbase = *diff.FeeRecipient + } + if diff.PrevRandao != nil { + header.MixDigest = *diff.PrevRandao + } + if diff.BaseFeePerGas != nil { + header.BaseFee = diff.BaseFeePerGas.ToInt() + } +} + // ChainContextBackend provides methods required to implement ChainContext. type ChainContextBackend interface { Engine() consensus.Engine @@ -1213,6 +1241,20 @@ type blockResult struct { Calls []callResult `json:"calls"` } +func mcBlockResultFromHeader(header *types.Header, callResults []callResult) blockResult { + return blockResult{ + Number: hexutil.Uint64(header.Number.Uint64()), + Hash: header.Hash(), + Time: hexutil.Uint64(header.Time), + GasLimit: hexutil.Uint64(header.GasLimit), + GasUsed: hexutil.Uint64(header.GasUsed), + FeeRecipient: header.Coinbase, + BaseFee: (*hexutil.Big)(header.BaseFee), + PrevRandao: header.MixDigest, + Calls: callResults, + } +} + type callResult struct { ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` @@ -1267,7 +1309,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - blockContexts, err := makeBlockContexts(ctx, s.b, blocks, header) + headers, err := makeHeaders(ctx, s.b.ChainConfig(), blocks, header) if err != nil { return nil, err } @@ -1281,27 +1323,25 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo precompiles = vm.ActivePrecompiledContracts(rules).Copy() ) for bi, block := range blocks { - blockContext = blockContexts[bi] + prevHeader := header + header = headers[bi] + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + // TODO: GetHashFn hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) // State overrides are applied prior to execution of a block if err := block.StateOverrides.Apply(state, precompiles); err != nil { return nil, err } - results[bi] = blockResult{ - Number: hexutil.Uint64(blockContext.BlockNumber.Uint64()), - Hash: hash, - Time: hexutil.Uint64(blockContext.Time), - GasLimit: hexutil.Uint64(blockContext.GasLimit), - FeeRecipient: blockContext.Coinbase, - BaseFee: (*hexutil.Big)(blockContext.BaseFee), - Calls: make([]callResult, len(block.Calls)), - } - if blockContext.Random != nil { - results[bi].PrevRandao = *blockContext.Random - } - var gasUsed uint64 + + var ( + gasUsed uint64 + root common.Hash + txes = make([]*types.Transaction, len(block.Calls)) + callResults = make([]callResult, len(block.Calls)) + ) for i, call := range block.Calls { - // setDefaults will consult txpool's nonce tracker. Work around that. + // TODO: Track nonce by counting txes from sender + // Because then we can pre-populate the tx object. if call.Nonce == nil { nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) @@ -1320,6 +1360,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo return nil, err } tx := call.ToTransaction(true) + txes[i] = tx + // TODO: repair log block hashes post execution. vmConfig := &vm.Config{ NoBaseFee: true, Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), hash, tx.Hash(), uint(i)), @@ -1327,7 +1369,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo result, err := applyMessage(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { callErr := callErrorFromError(err) - results[bi].Calls[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} + callResults[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue } // If the result contains a revert reason, try to unpack it. @@ -1346,44 +1388,79 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) } - results[bi].Calls[i] = callRes + callResults[i] = callRes gasUsed += result.UsedGas - state.Finalise(true) + root = state.IntermediateRoot(true) + } + // If last non-phantom block is parent of current block, set parent hash. + if header.Number.Uint64()-prevHeader.Number.Uint64() == 1 { + header.ParentHash = prevHeader.Hash() } - results[bi].GasUsed = hexutil.Uint64(gasUsed) + header.Root = root + header.GasUsed = gasUsed + header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) + results[bi] = mcBlockResultFromHeader(header, callResults) } return results, nil } -func makeBlockContexts(ctx context.Context, b Backend, blocks []CallBatch, header *types.Header) ([]vm.BlockContext, error) { - res := make([]vm.BlockContext, len(blocks)) +func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallBatch, base *types.Header) ([]*types.Header, error) { + res := make([]*types.Header, len(blocks)) var ( - prevNumber = header.Number.Uint64() - prevTimestamp = header.Time + prevNumber = base.Number.Uint64() + prevTimestamp = base.Time + header = base ) for bi, block := range blocks { - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if block.BlockOverrides == nil { block.BlockOverrides = new(BlockOverrides) } + // Sanitize block number and timestamp if block.BlockOverrides.Number == nil { n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) block.BlockOverrides.Number = (*hexutil.Big)(n) + } else if block.BlockOverrides.Number.ToInt().Uint64() <= prevNumber { + return nil, fmt.Errorf("block numbers must be in order") } + prevNumber = block.BlockOverrides.Number.ToInt().Uint64() + if block.BlockOverrides.Time == nil { t := prevTimestamp + 1 block.BlockOverrides.Time = (*hexutil.Uint64)(&t) - } - block.BlockOverrides.Apply(&blockContext) - if blockContext.BlockNumber.Uint64() <= prevNumber { - return nil, fmt.Errorf("block numbers must be in order") - } - prevNumber = blockContext.BlockNumber.Uint64() - if blockContext.Time <= prevTimestamp { + } else if time := (*uint64)(block.BlockOverrides.Time); *time <= prevTimestamp { return nil, fmt.Errorf("timestamps must be in order") } - prevTimestamp = blockContext.Time - res[bi] = blockContext + prevTimestamp = uint64(*block.BlockOverrides.Time) + + // ParentHash for non-phantom blocks can only be computed + // after the previous block is executed. + var ( + parentHash = common.Hash{} + baseFee *big.Int + ) + // Calculate parentHash for phantom blocks. + if block.BlockOverrides.Number.ToInt().Uint64()-header.Number.Uint64() > 1 { + // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) + hashData, err := rlp.EncodeToBytes([][]byte{header.Hash().Bytes(), block.BlockOverrides.Number.ToInt().Bytes()}) + if err != nil { + return nil, err + } + parentHash = crypto.Keccak256Hash(hashData) + } + if config.IsLondon(block.BlockOverrides.Number.ToInt()) { + baseFee = eip1559.CalcBaseFee(config, header) + } + header = &types.Header{ + ParentHash: parentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: base.Coinbase, + Difficulty: base.Difficulty, + GasLimit: base.GasLimit, + //MixDigest: header.MixDigest, + BaseFee: baseFee, + } + block.BlockOverrides.ApplyToHeader(header) + res[bi] = header } return res, nil } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 9b61c84e4e52..cd0e912a7630 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -956,7 +956,6 @@ func TestMulticallV1(t *testing.T) { }, }, } - n11hash = crypto.Keccak256Hash([]byte{0xb}).Hex() sha256Address = common.BytesToAddress([]byte{0x02}) ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { @@ -987,9 +986,9 @@ func TestMulticallV1(t *testing.T) { BlockNumber hexutil.Uint64 `json:"blockNumber"` // Skip txHash //TxHash common.Hash `json:"transactionHash" gencodec:"required"` - TxIndex hexutil.Uint `json:"transactionIndex"` - BlockHash common.Hash `json:"blockHash"` - Index hexutil.Uint `json:"logIndex"` + TxIndex hexutil.Uint `json:"transactionIndex"` + //BlockHash common.Hash `json:"blockHash"` + Index hexutil.Uint `json:"logIndex"` } type callErr struct { Message string @@ -1004,7 +1003,7 @@ func TestMulticallV1(t *testing.T) { } type blockRes struct { Number string - Hash string + //Hash string // Ignore timestamp GasLimit string GasUsed string @@ -1045,7 +1044,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xf618", FeeRecipient: coinbase, @@ -1103,7 +1101,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xa410", FeeRecipient: coinbase, @@ -1120,7 +1117,6 @@ func TestMulticallV1(t *testing.T) { }}, }, { Number: "0xc", - Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0x5208", FeeRecipient: coinbase, @@ -1171,7 +1167,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xe891", FeeRecipient: strings.ToLower(cac.String()), @@ -1183,7 +1178,6 @@ func TestMulticallV1(t *testing.T) { }}, }, { Number: "0xc", - Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe891", FeeRecipient: coinbase, @@ -1252,7 +1246,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x10683", FeeRecipient: coinbase, @@ -1294,7 +1287,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x5508", FeeRecipient: coinbase, @@ -1305,7 +1297,6 @@ func TestMulticallV1(t *testing.T) { Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, BlockNumber: hexutil.Uint64(11), Data: hexutil.Bytes{}, - BlockHash: hex2Hash(n11hash), }}, GasUsed: "0x5508", Status: "0x1", @@ -1364,7 +1355,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x52f6", FeeRecipient: coinbase, @@ -1415,7 +1405,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xa58c", FeeRecipient: coinbase, @@ -1464,7 +1453,6 @@ func TestMulticallV1(t *testing.T) { includeTransfers: &includeTransfers, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xd984", FeeRecipient: coinbase, @@ -1480,7 +1468,6 @@ func TestMulticallV1(t *testing.T) { }, Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), BlockNumber: hexutil.Uint64(11), - BlockHash: hex2Hash(n11hash), }, { Address: common.Address{}, Topics: []common.Hash{ @@ -1490,7 +1477,6 @@ func TestMulticallV1(t *testing.T) { }, Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), BlockNumber: hexutil.Uint64(11), - BlockHash: hex2Hash(n11hash), Index: hexutil.Uint(1), }}, Status: "0x1", @@ -1533,7 +1519,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x1b83f", FeeRecipient: coinbase, @@ -1550,7 +1535,6 @@ func TestMulticallV1(t *testing.T) { }}, }, { Number: "0xc", - Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0xe6d9", FeeRecipient: coinbase, @@ -1576,7 +1560,6 @@ func TestMulticallV1(t *testing.T) { validation: &validation, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0x0", FeeRecipient: coinbase, @@ -1630,7 +1613,6 @@ func TestMulticallV1(t *testing.T) { }}, want: []blockRes{{ Number: "0xb", - Hash: n11hash, GasLimit: "0x47e7c4", GasUsed: "0xc542", FeeRecipient: coinbase, @@ -1647,7 +1629,6 @@ func TestMulticallV1(t *testing.T) { }}, }, { Number: "0xc", - Hash: crypto.Keccak256Hash([]byte{0xc}).Hex(), GasLimit: "0x47e7c4", GasUsed: "0x62a1", FeeRecipient: coinbase, From 973116c1d2b34e65c6bacd96d83ace7fb6abab93 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 5 Oct 2023 20:07:31 +0300 Subject: [PATCH 048/119] repair logs --- internal/ethapi/api.go | 53 +++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fd02923de09a..1eab6aa20001 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1326,6 +1326,10 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo prevHeader := header header = headers[bi] blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) + blockContext.GetHash = func(uint64) common.Hash { + // TODO + return header.Hash() + } // TODO: GetHashFn hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) // State overrides are applied prior to execution of a block @@ -1335,13 +1339,10 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo var ( gasUsed uint64 - root common.Hash txes = make([]*types.Transaction, len(block.Calls)) callResults = make([]callResult, len(block.Calls)) ) for i, call := range block.Calls { - // TODO: Track nonce by counting txes from sender - // Because then we can pre-populate the tx object. if call.Nonce == nil { nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) @@ -1390,20 +1391,33 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } callResults[i] = callRes gasUsed += result.UsedGas - root = state.IntermediateRoot(true) + state.Finalise(true) } // If last non-phantom block is parent of current block, set parent hash. if header.Number.Uint64()-prevHeader.Number.Uint64() == 1 { header.ParentHash = prevHeader.Hash() } - header.Root = root + header.Root = state.IntermediateRoot(true) header.GasUsed = gasUsed header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) results[bi] = mcBlockResultFromHeader(header, callResults) + repairLogs(results, header.Hash()) } return results, nil } +// repairLogs updates the block hash in the logs present in a multicall +// result object. This is needed as during execution when logs are collected +// the block hash is not known. +func repairLogs(results []blockResult, blockHash common.Hash) { + for i := range results { + for j := range results[i].Calls { + for k := range results[i].Calls[j].Logs { + results[i].Calls[j].Logs[k].BlockHash = blockHash + } + } + } +} func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallBatch, base *types.Header) ([]*types.Header, error) { res := make([]*types.Header, len(blocks)) var ( @@ -1412,25 +1426,26 @@ func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallB header = base ) for bi, block := range blocks { - if block.BlockOverrides == nil { - block.BlockOverrides = new(BlockOverrides) + overrides := new(BlockOverrides) + if block.BlockOverrides != nil { + overrides = block.BlockOverrides } // Sanitize block number and timestamp - if block.BlockOverrides.Number == nil { + if overrides.Number == nil { n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) - block.BlockOverrides.Number = (*hexutil.Big)(n) - } else if block.BlockOverrides.Number.ToInt().Uint64() <= prevNumber { + overrides.Number = (*hexutil.Big)(n) + } else if overrides.Number.ToInt().Uint64() <= prevNumber { return nil, fmt.Errorf("block numbers must be in order") } - prevNumber = block.BlockOverrides.Number.ToInt().Uint64() + prevNumber = overrides.Number.ToInt().Uint64() - if block.BlockOverrides.Time == nil { + if overrides.Time == nil { t := prevTimestamp + 1 - block.BlockOverrides.Time = (*hexutil.Uint64)(&t) - } else if time := (*uint64)(block.BlockOverrides.Time); *time <= prevTimestamp { + overrides.Time = (*hexutil.Uint64)(&t) + } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { return nil, fmt.Errorf("timestamps must be in order") } - prevTimestamp = uint64(*block.BlockOverrides.Time) + prevTimestamp = uint64(*overrides.Time) // ParentHash for non-phantom blocks can only be computed // after the previous block is executed. @@ -1439,15 +1454,15 @@ func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallB baseFee *big.Int ) // Calculate parentHash for phantom blocks. - if block.BlockOverrides.Number.ToInt().Uint64()-header.Number.Uint64() > 1 { + if overrides.Number.ToInt().Uint64()-header.Number.Uint64() > 1 { // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) - hashData, err := rlp.EncodeToBytes([][]byte{header.Hash().Bytes(), block.BlockOverrides.Number.ToInt().Bytes()}) + hashData, err := rlp.EncodeToBytes([][]byte{header.Hash().Bytes(), overrides.Number.ToInt().Bytes()}) if err != nil { return nil, err } parentHash = crypto.Keccak256Hash(hashData) } - if config.IsLondon(block.BlockOverrides.Number.ToInt()) { + if config.IsLondon(overrides.Number.ToInt()) { baseFee = eip1559.CalcBaseFee(config, header) } header = &types.Header{ @@ -1459,7 +1474,7 @@ func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallB //MixDigest: header.MixDigest, BaseFee: baseFee, } - block.BlockOverrides.ApplyToHeader(header) + overrides.ApplyToHeader(header) res[bi] = header } return res, nil From a5ffd9adf24e12aa5e7ad7cf9f13011273243100 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 6 Oct 2023 16:54:35 +0300 Subject: [PATCH 049/119] serve blockhash requests --- internal/ethapi/api.go | 111 ++++++++++++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1eab6aa20001..e526507efd40 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1290,7 +1290,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &n } - state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) + state, base, err := s.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) if state == nil || err != nil { return nil, err } @@ -1309,7 +1309,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - headers, err := makeHeaders(ctx, s.b.ChainConfig(), blocks, header) + headers, err := makeHeaders(ctx, s.b.ChainConfig(), blocks, base) if err != nil { return nil, err } @@ -1318,25 +1318,34 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // Each tx and all the series of txes shouldn't consume more gas than cap globalGasCap = s.b.RPCGasCap() gp = new(core.GasPool).AddGas(globalGasCap) + header = base blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) precompiles = vm.ActivePrecompiledContracts(rules).Copy() + numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + // Cache for simulated and phantom block hashes. + hashes = make([]common.Hash, numHashes) ) for bi, block := range blocks { - prevHeader := header header = headers[bi] blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) - blockContext.GetHash = func(uint64) common.Hash { - // TODO - return header.Hash() + // Respond to BLOCKHASH requests. + blockContext.GetHash = func(n uint64) common.Hash { + var ( + h common.Hash + err error + ) + h, hashes, err = getHash(ctx, n, base, headers, s.b, hashes) + if err != nil { + log.Warn(err.Error()) + return common.Hash{} + } + return h } - // TODO: GetHashFn - hash := crypto.Keccak256Hash(blockContext.BlockNumber.Bytes()) // State overrides are applied prior to execution of a block if err := block.StateOverrides.Apply(state, precompiles); err != nil { return nil, err } - var ( gasUsed uint64 txes = make([]*types.Transaction, len(block.Calls)) @@ -1365,7 +1374,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // TODO: repair log block hashes post execution. vmConfig := &vm.Config{ NoBaseFee: true, - Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), hash, tx.Hash(), uint(i)), + // Block hash will be repaired after execution. + Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, tx.Hash(), uint(i)), } result, err := applyMessage(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { @@ -1393,10 +1403,15 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo gasUsed += result.UsedGas state.Finalise(true) } - // If last non-phantom block is parent of current block, set parent hash. - if header.Number.Uint64()-prevHeader.Number.Uint64() == 1 { - header.ParentHash = prevHeader.Hash() + var ( + parentHash common.Hash + err error + ) + parentHash, hashes, err = getHash(ctx, header.Number.Uint64()-1, base, headers, s.b, hashes) + if err != nil { + return nil, err } + header.ParentHash = parentHash header.Root = state.IntermediateRoot(true) header.GasUsed = gasUsed header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) @@ -1406,6 +1421,59 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo return results, nil } +// getHash returns the hash for the block of the given number. Block can be +// part of the canonical chain, a simulated block or a phantom block. +// Note getHash assumes `n` is smaller than the last already simulated block +// and smaller than the last block to be simulated. +func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, []common.Hash, error) { + getIndex := func(n uint64) int { + return int(n - base.Number.Uint64()) + } + index := getIndex(n) + if h := hashes[index]; h != (common.Hash{}) { + return h, hashes, nil + } + if n == base.Number.Uint64() { + return base.Hash(), hashes, nil + } else if n < base.Number.Uint64() { + h, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(n)) + if err != nil { + return common.Hash{}, hashes, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) + } + return h.Hash(), hashes, nil + } + h := base + for i, _ := range headers { + tmp := headers[i] + // BLOCKHASH will only allow numbers prior to current block + // so no need to check that condition. + if tmp.Number.Uint64() == n { + hash := tmp.Hash() + hashes[index] = hash + return hash, hashes, nil + } else if tmp.Number.Uint64() > n { + // Phantom block. + var lastNonPhantomHash common.Hash + if hash := hashes[getIndex(h.Number.Uint64()-1)]; hash != (common.Hash{}) { + lastNonPhantomHash = hash + } else { + lastNonPhantomHash = h.Hash() + hashes[getIndex(h.Number.Uint64()-1)] = lastNonPhantomHash + } + // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) + hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) + if err != nil { + return common.Hash{}, hashes, err + } + hash := crypto.Keccak256Hash(hashData) + hashes[index] = hash + return hash, hashes, nil + } + h = tmp + } + return common.Hash{}, hashes, errors.New("requested block is in future") +} + // repairLogs updates the block hash in the logs present in a multicall // result object. This is needed as during execution when logs are collected // the block hash is not known. @@ -1447,26 +1515,11 @@ func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallB } prevTimestamp = uint64(*overrides.Time) - // ParentHash for non-phantom blocks can only be computed - // after the previous block is executed. - var ( - parentHash = common.Hash{} - baseFee *big.Int - ) - // Calculate parentHash for phantom blocks. - if overrides.Number.ToInt().Uint64()-header.Number.Uint64() > 1 { - // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) - hashData, err := rlp.EncodeToBytes([][]byte{header.Hash().Bytes(), overrides.Number.ToInt().Bytes()}) - if err != nil { - return nil, err - } - parentHash = crypto.Keccak256Hash(hashData) - } + var baseFee *big.Int if config.IsLondon(overrides.Number.ToInt()) { baseFee = eip1559.CalcBaseFee(config, header) } header = &types.Header{ - ParentHash: parentHash, UncleHash: types.EmptyUncleHash, Coinbase: base.Coinbase, Difficulty: base.Difficulty, From cc1158ae6d1f23ae95b4152a25e418ec2f3a34c7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Oct 2023 12:55:08 +0200 Subject: [PATCH 050/119] possibly fix crasher --- internal/ethapi/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e526507efd40..49d2d54f9cdc 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1454,11 +1454,11 @@ func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types } else if tmp.Number.Uint64() > n { // Phantom block. var lastNonPhantomHash common.Hash - if hash := hashes[getIndex(h.Number.Uint64()-1)]; hash != (common.Hash{}) { + if hash := hashes[getIndex(h.Number.Uint64())]; hash != (common.Hash{}) { lastNonPhantomHash = hash } else { lastNonPhantomHash = h.Hash() - hashes[getIndex(h.Number.Uint64()-1)] = lastNonPhantomHash + hashes[getIndex(h.Number.Uint64())] = lastNonPhantomHash } // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) From 2690445207baa73783024c7d5201e466984ba415 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Oct 2023 15:53:55 +0200 Subject: [PATCH 051/119] fix block hash cache --- internal/ethapi/api.go | 46 ++++++++++-------- internal/ethapi/api_test.go | 95 +++++++++++++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 23 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 49d2d54f9cdc..5fe169cb4d87 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1322,8 +1322,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) precompiles = vm.ActivePrecompiledContracts(rules).Copy() - numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() - // Cache for simulated and phantom block hashes. + numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 + // Cache for the block hashes. hashes = make([]common.Hash, numHashes) ) for bi, block := range blocks { @@ -1426,21 +1426,35 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // Note getHash assumes `n` is smaller than the last already simulated block // and smaller than the last block to be simulated. func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, []common.Hash, error) { + // getIndex returns the index of the hash in the hashes cache. + // The cache potentially includes 255 blocks prior to the base. getIndex := func(n uint64) int { - return int(n - base.Number.Uint64()) + first := base.Number.Uint64() - 255 + return int(n - first) } index := getIndex(n) if h := hashes[index]; h != (common.Hash{}) { return h, hashes, nil } + h, err := computeHash(ctx, n, base, headers, backend, hashes) + if err != nil { + return common.Hash{}, hashes, err + } + if h != (common.Hash{}) { + hashes[index] = h + } + return h, hashes, nil +} + +func computeHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, error) { if n == base.Number.Uint64() { - return base.Hash(), hashes, nil + return base.Hash(), nil } else if n < base.Number.Uint64() { h, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(n)) if err != nil { - return common.Hash{}, hashes, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) + return common.Hash{}, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) } - return h.Hash(), hashes, nil + return h.Hash(), nil } h := base for i, _ := range headers { @@ -1449,29 +1463,23 @@ func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types // so no need to check that condition. if tmp.Number.Uint64() == n { hash := tmp.Hash() - hashes[index] = hash - return hash, hashes, nil + return hash, nil } else if tmp.Number.Uint64() > n { // Phantom block. - var lastNonPhantomHash common.Hash - if hash := hashes[getIndex(h.Number.Uint64())]; hash != (common.Hash{}) { - lastNonPhantomHash = hash - } else { - lastNonPhantomHash = h.Hash() - hashes[getIndex(h.Number.Uint64())] = lastNonPhantomHash + lastNonPhantomHash, _, err := getHash(ctx, h.Number.Uint64(), base, headers, backend, hashes) + if err != nil { + return common.Hash{}, err } // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) if err != nil { - return common.Hash{}, hashes, err + return common.Hash{}, err } - hash := crypto.Keccak256Hash(hashData) - hashes[index] = hash - return hash, hashes, nil + return crypto.Keccak256Hash(hashData), nil } h = tmp } - return common.Hash{}, hashes, errors.New("requested block is in future") + return common.Hash{}, errors.New("requested block is in future") } // repairLogs updates the block hash in the logs present in a multicall diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index cd0e912a7630..82375f32d5e3 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1640,6 +1640,91 @@ func TestMulticallV1(t *testing.T) { }}, }}, }, + { + name: "blockhash-opcode", + tag: latest, + blocks: []CallBatch{{ + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(12)), + }, + StateOverrides: &StateOverride{ + randomAccounts[2].addr: { + Code: hex2Bytes("600035804060008103601057600080fd5b5050"), + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // Phantom block after base. + Input: uint256ToBytes(uint256.NewInt(11)), + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // Canonical block. + Input: uint256ToBytes(uint256.NewInt(8)), + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // base block. + Input: uint256ToBytes(uint256.NewInt(10)), + }}, + }, { + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(16)), + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // blocks[0] + Input: uint256ToBytes(uint256.NewInt(12)), + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // Phantom after blocks[0] + Input: uint256ToBytes(uint256.NewInt(13)), + }}, + }}, + want: []blockRes{{ + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xf864", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }, { + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }, { + + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }}, + }, { + Number: "0x10", + GasLimit: "0x47e7c4", + GasUsed: "0xa598", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }, { + + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }}, + }}, + }, } for _, tc := range testSuite { @@ -1715,6 +1800,12 @@ func newBytes(b []byte) *hexutil.Bytes { return &rpcBytes } +func uint256ToBytes(v *uint256.Int) *hexutil.Bytes { + b := v.Bytes32() + r := hexutil.Bytes(b[:]) + return &r +} + func TestRPCMarshalBlock(t *testing.T) { t.Parallel() var ( @@ -1924,10 +2015,6 @@ func TestRPCMarshalBlock(t *testing.T) { } } -func hex2Hash(s string) common.Hash { - return common.BytesToHash(common.FromHex(s)) -} - func TestRPCGetBlockOrHeader(t *testing.T) { t.Parallel() From d1ce690e763f9ae4f0aa0cf247faf58c7b997947 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Oct 2023 16:34:48 +0200 Subject: [PATCH 052/119] move multicall logic to struct --- internal/ethapi/api.go | 308 +------------------------------ internal/ethapi/multicall.go | 348 +++++++++++++++++++++++++++++++++++ 2 files changed, 350 insertions(+), 306 deletions(-) create mode 100644 internal/ethapi/multicall.go diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 5fe169cb4d87..3a91dde4dec2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -19,7 +19,6 @@ package ethapi import ( "context" "encoding/hex" - "encoding/json" "errors" "fmt" "math/big" @@ -1222,62 +1221,6 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO return result.Return(), result.Err } -// CallBatch is a batch of calls to be simulated sequentially. -type CallBatch struct { - BlockOverrides *BlockOverrides - StateOverrides *StateOverride - Calls []TransactionArgs -} - -type blockResult struct { - Number hexutil.Uint64 `json:"number"` - Hash common.Hash `json:"hash"` - Time hexutil.Uint64 `json:"timestamp"` - GasLimit hexutil.Uint64 `json:"gasLimit"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - FeeRecipient common.Address `json:"feeRecipient"` - BaseFee *hexutil.Big `json:"baseFeePerGas"` - PrevRandao common.Hash `json:"prevRandao"` - Calls []callResult `json:"calls"` -} - -func mcBlockResultFromHeader(header *types.Header, callResults []callResult) blockResult { - return blockResult{ - Number: hexutil.Uint64(header.Number.Uint64()), - Hash: header.Hash(), - Time: hexutil.Uint64(header.Time), - GasLimit: hexutil.Uint64(header.GasLimit), - GasUsed: hexutil.Uint64(header.GasUsed), - FeeRecipient: header.Coinbase, - BaseFee: (*hexutil.Big)(header.BaseFee), - PrevRandao: header.MixDigest, - Calls: callResults, - } -} - -type callResult 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 *callResult) MarshalJSON() ([]byte, error) { - type callResultAlias callResult - // 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)) -} - -type multicallOpts struct { - BlockStateCalls []CallBatch - TraceTransfers bool - Validation bool -} - // MulticallV1 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 @@ -1290,255 +1233,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &n } - state, base, err := s.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) - if state == nil || err != nil { - return nil, err - } - // Setup context so it may be cancelled before the calls completed - // or, in case of unmetered gas, setup a context with a timeout. - var ( - cancel context.CancelFunc - timeout = s.b.RPCEVMTimeout() - blocks = opts.BlockStateCalls - ) - 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() - headers, err := makeHeaders(ctx, s.b.ChainConfig(), blocks, base) - if err != nil { - return nil, err - } - var ( - results = make([]blockResult, len(blocks)) - // Each tx and all the series of txes shouldn't consume more gas than cap - globalGasCap = s.b.RPCGasCap() - gp = new(core.GasPool).AddGas(globalGasCap) - header = base - blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) - rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) - precompiles = vm.ActivePrecompiledContracts(rules).Copy() - numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 - // Cache for the block hashes. - hashes = make([]common.Hash, numHashes) - ) - for bi, block := range blocks { - header = headers[bi] - blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) - // Respond to BLOCKHASH requests. - blockContext.GetHash = func(n uint64) common.Hash { - var ( - h common.Hash - err error - ) - h, hashes, err = getHash(ctx, n, base, headers, s.b, hashes) - if err != nil { - log.Warn(err.Error()) - return common.Hash{} - } - return h - } - // State overrides are applied prior to execution of a block - if err := block.StateOverrides.Apply(state, precompiles); err != nil { - return nil, err - } - var ( - gasUsed uint64 - txes = make([]*types.Transaction, len(block.Calls)) - callResults = make([]callResult, len(block.Calls)) - ) - for i, call := range block.Calls { - 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 call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { - call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) - call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) - } - // TODO: check chainID and against current header for london fees - if err := call.validateAll(); err != nil { - return nil, err - } - tx := call.ToTransaction(true) - txes[i] = tx - // TODO: repair log block hashes post execution. - vmConfig := &vm.Config{ - NoBaseFee: true, - // Block hash will be repaired after execution. - Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, tx.Hash(), uint(i)), - } - result, err := applyMessage(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) - if err != nil { - callErr := callErrorFromError(err) - callResults[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} - continue - } - // If the result contains a revert reason, try to unpack it. - if len(result.Revert()) > 0 { - result.Err = newRevertError(result) - } - logs := vmConfig.Tracer.(*tracer).Logs() - callRes := callResult{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) { - callRes.Error = &callError{Message: result.Err.Error(), Code: -32000} - } else { - callRes.Error = &callError{Message: result.Err.Error(), Code: -32015} - } - } else { - callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) - } - callResults[i] = callRes - gasUsed += result.UsedGas - state.Finalise(true) - } - var ( - parentHash common.Hash - err error - ) - parentHash, hashes, err = getHash(ctx, header.Number.Uint64()-1, base, headers, s.b, hashes) - if err != nil { - return nil, err - } - header.ParentHash = parentHash - header.Root = state.IntermediateRoot(true) - header.GasUsed = gasUsed - header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) - results[bi] = mcBlockResultFromHeader(header, callResults) - repairLogs(results, header.Hash()) - } - return results, nil -} - -// getHash returns the hash for the block of the given number. Block can be -// part of the canonical chain, a simulated block or a phantom block. -// Note getHash assumes `n` is smaller than the last already simulated block -// and smaller than the last block to be simulated. -func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, []common.Hash, error) { - // getIndex returns the index of the hash in the hashes cache. - // The cache potentially includes 255 blocks prior to the base. - getIndex := func(n uint64) int { - first := base.Number.Uint64() - 255 - return int(n - first) - } - index := getIndex(n) - if h := hashes[index]; h != (common.Hash{}) { - return h, hashes, nil - } - h, err := computeHash(ctx, n, base, headers, backend, hashes) - if err != nil { - return common.Hash{}, hashes, err - } - if h != (common.Hash{}) { - hashes[index] = h - } - return h, hashes, nil -} - -func computeHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, error) { - if n == base.Number.Uint64() { - return base.Hash(), nil - } else if n < base.Number.Uint64() { - h, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(n)) - if err != nil { - return common.Hash{}, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) - } - return h.Hash(), nil - } - h := base - for i, _ := range headers { - tmp := headers[i] - // BLOCKHASH will only allow numbers prior to current block - // so no need to check that condition. - if tmp.Number.Uint64() == n { - hash := tmp.Hash() - return hash, nil - } else if tmp.Number.Uint64() > n { - // Phantom block. - lastNonPhantomHash, _, err := getHash(ctx, h.Number.Uint64(), base, headers, backend, hashes) - if err != nil { - return common.Hash{}, err - } - // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) - hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) - if err != nil { - return common.Hash{}, err - } - return crypto.Keccak256Hash(hashData), nil - } - h = tmp - } - return common.Hash{}, errors.New("requested block is in future") -} - -// repairLogs updates the block hash in the logs present in a multicall -// result object. This is needed as during execution when logs are collected -// the block hash is not known. -func repairLogs(results []blockResult, blockHash common.Hash) { - for i := range results { - for j := range results[i].Calls { - for k := range results[i].Calls[j].Logs { - results[i].Calls[j].Logs[k].BlockHash = blockHash - } - } - } -} -func makeHeaders(ctx context.Context, config *params.ChainConfig, blocks []CallBatch, base *types.Header) ([]*types.Header, error) { - res := make([]*types.Header, len(blocks)) - var ( - prevNumber = base.Number.Uint64() - prevTimestamp = base.Time - header = base - ) - for bi, block := range blocks { - overrides := new(BlockOverrides) - if block.BlockOverrides != nil { - overrides = block.BlockOverrides - } - // Sanitize block number and timestamp - if overrides.Number == nil { - n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) - overrides.Number = (*hexutil.Big)(n) - } else if overrides.Number.ToInt().Uint64() <= prevNumber { - return nil, fmt.Errorf("block numbers must be in order") - } - prevNumber = overrides.Number.ToInt().Uint64() - - if overrides.Time == nil { - t := prevTimestamp + 1 - overrides.Time = (*hexutil.Uint64)(&t) - } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { - return nil, fmt.Errorf("timestamps must be in order") - } - prevTimestamp = uint64(*overrides.Time) - - var baseFee *big.Int - if config.IsLondon(overrides.Number.ToInt()) { - baseFee = eip1559.CalcBaseFee(config, header) - } - header = &types.Header{ - UncleHash: types.EmptyUncleHash, - Coinbase: base.Coinbase, - Difficulty: base.Difficulty, - GasLimit: base.GasLimit, - //MixDigest: header.MixDigest, - BaseFee: baseFee, - } - overrides.ApplyToHeader(header) - res[bi] = header - } - return res, nil + mc := &multicall{b: s.b, blockNrOrHash: *blockNrOrHash} + return mc.execute(ctx, opts) } // executeEstimate is a helper that executes the transaction under a given gas limit and returns diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go new file mode 100644 index 000000000000..0da48541af2b --- /dev/null +++ b/internal/ethapi/multicall.go @@ -0,0 +1,348 @@ +// 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 . + +package ethapi + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +// CallBatch is a batch of calls to be simulated sequentially. +type CallBatch struct { + BlockOverrides *BlockOverrides + StateOverrides *StateOverride + Calls []TransactionArgs +} + +type blockResult struct { + Number hexutil.Uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Time hexutil.Uint64 `json:"timestamp"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"feeRecipient"` + BaseFee *hexutil.Big `json:"baseFeePerGas"` + PrevRandao common.Hash `json:"prevRandao"` + Calls []callResult `json:"calls"` +} + +func mcBlockResultFromHeader(header *types.Header, callResults []callResult) blockResult { + return blockResult{ + Number: hexutil.Uint64(header.Number.Uint64()), + Hash: header.Hash(), + Time: hexutil.Uint64(header.Time), + GasLimit: hexutil.Uint64(header.GasLimit), + GasUsed: hexutil.Uint64(header.GasUsed), + FeeRecipient: header.Coinbase, + BaseFee: (*hexutil.Big)(header.BaseFee), + PrevRandao: header.MixDigest, + Calls: callResults, + } +} + +type callResult 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 *callResult) MarshalJSON() ([]byte, error) { + type callResultAlias callResult + // 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)) +} + +type multicallOpts struct { + BlockStateCalls []CallBatch + TraceTransfers bool + Validation bool +} + +type multicall struct { + blockNrOrHash rpc.BlockNumberOrHash + b Backend + hashes []common.Hash +} + +func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockResult, error) { + state, base, err := mc.b.StateAndHeaderByNumberOrHash(ctx, mc.blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + // Setup context so it may be cancelled before the calls completed + // or, in case of unmetered gas, setup a context with a timeout. + var ( + cancel context.CancelFunc + timeout = mc.b.RPCEVMTimeout() + blocks = opts.BlockStateCalls + ) + 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() + headers, err := makeHeaders(mc.b.ChainConfig(), blocks, base) + if err != nil { + return nil, err + } + var ( + results = make([]blockResult, len(blocks)) + // Each tx and all the series of txes shouldn't consume more gas than cap + globalGasCap = mc.b.RPCGasCap() + gp = new(core.GasPool).AddGas(globalGasCap) + header = base + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) + rules = mc.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + precompiles = vm.ActivePrecompiledContracts(rules).Copy() + numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 + ) + // Cache for the block hashes. + mc.hashes = make([]common.Hash, numHashes) + for bi, block := range blocks { + header = headers[bi] + blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) + // Respond to BLOCKHASH requests. + blockContext.GetHash = func(n uint64) common.Hash { + h, err := mc.getBlockHash(ctx, n, base, headers) + if err != nil { + log.Warn(err.Error()) + return common.Hash{} + } + return h + } + // State overrides are applied prior to execution of a block + if err := block.StateOverrides.Apply(state, precompiles); err != nil { + return nil, err + } + var ( + gasUsed uint64 + txes = make([]*types.Transaction, len(block.Calls)) + callResults = make([]callResult, len(block.Calls)) + ) + for i, call := range block.Calls { + 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 call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { + call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) + call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) + } + // TODO: check chainID and against current header for london fees + if err := call.validateAll(); err != nil { + return nil, err + } + tx := call.ToTransaction(true) + txes[i] = tx + // TODO: repair log block hashes post execution. + vmConfig := &vm.Config{ + NoBaseFee: true, + // Block hash will be repaired after execution. + Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, tx.Hash(), uint(i)), + } + result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) + if err != nil { + callErr := callErrorFromError(err) + callResults[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} + continue + } + // If the result contains a revert reason, try to unpack it. + if len(result.Revert()) > 0 { + result.Err = newRevertError(result) + } + logs := vmConfig.Tracer.(*tracer).Logs() + callRes := callResult{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) { + callRes.Error = &callError{Message: result.Err.Error(), Code: -32000} + } else { + callRes.Error = &callError{Message: result.Err.Error(), Code: -32015} + } + } else { + callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) + } + callResults[i] = callRes + gasUsed += result.UsedGas + state.Finalise(true) + } + var ( + parentHash common.Hash + err error + ) + parentHash, err = mc.getBlockHash(ctx, header.Number.Uint64()-1, base, headers) + if err != nil { + return nil, err + } + header.ParentHash = parentHash + header.Root = state.IntermediateRoot(true) + header.GasUsed = gasUsed + header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) + results[bi] = mcBlockResultFromHeader(header, callResults) + repairLogs(results, header.Hash()) + } + return results, nil +} + +// getBlockHash returns the hash for the block of the given number. Block can be +// part of the canonical chain, a simulated block or a phantom block. +// Note getBlockHash assumes `n` is smaller than the last already simulated block +// and smaller than the last block to be simulated. +func (mc *multicall) getBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { + // getIndex returns the index of the hash in the hashes cache. + // The cache potentially includes 255 blocks prior to the base. + getIndex := func(n uint64) int { + first := base.Number.Uint64() - 255 + return int(n - first) + } + index := getIndex(n) + if h := mc.hashes[index]; h != (common.Hash{}) { + return h, nil + } + h, err := mc.computeBlockHash(ctx, n, base, headers) + if err != nil { + return common.Hash{}, err + } + if h != (common.Hash{}) { + mc.hashes[index] = h + } + return h, nil +} + +func (mc *multicall) computeBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { + if n == base.Number.Uint64() { + return base.Hash(), nil + } else if n < base.Number.Uint64() { + h, err := mc.b.HeaderByNumber(ctx, rpc.BlockNumber(n)) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) + } + return h.Hash(), nil + } + h := base + for i, _ := range headers { + tmp := headers[i] + // BLOCKHASH will only allow numbers prior to current block + // so no need to check that condition. + if tmp.Number.Uint64() == n { + hash := tmp.Hash() + return hash, nil + } else if tmp.Number.Uint64() > n { + // Phantom block. + lastNonPhantomHash, err := mc.getBlockHash(ctx, h.Number.Uint64(), base, headers) + if err != nil { + return common.Hash{}, err + } + // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) + hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) + if err != nil { + return common.Hash{}, err + } + return crypto.Keccak256Hash(hashData), nil + } + h = tmp + } + return common.Hash{}, errors.New("requested block is in future") +} + +// repairLogs updates the block hash in the logs present in a multicall +// result object. This is needed as during execution when logs are collected +// the block hash is not known. +func repairLogs(results []blockResult, blockHash common.Hash) { + for i := range results { + for j := range results[i].Calls { + for k := range results[i].Calls[j].Logs { + results[i].Calls[j].Logs[k].BlockHash = blockHash + } + } + } +} +func makeHeaders(config *params.ChainConfig, blocks []CallBatch, base *types.Header) ([]*types.Header, error) { + res := make([]*types.Header, len(blocks)) + var ( + prevNumber = base.Number.Uint64() + prevTimestamp = base.Time + header = base + ) + for bi, block := range blocks { + overrides := new(BlockOverrides) + if block.BlockOverrides != nil { + overrides = block.BlockOverrides + } + // Sanitize block number and timestamp + if overrides.Number == nil { + n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) + overrides.Number = (*hexutil.Big)(n) + } else if overrides.Number.ToInt().Uint64() <= prevNumber { + return nil, fmt.Errorf("block numbers must be in order") + } + prevNumber = overrides.Number.ToInt().Uint64() + + if overrides.Time == nil { + t := prevTimestamp + 1 + overrides.Time = (*hexutil.Uint64)(&t) + } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { + return nil, fmt.Errorf("timestamps must be in order") + } + prevTimestamp = uint64(*overrides.Time) + + var baseFee *big.Int + if config.IsLondon(overrides.Number.ToInt()) { + baseFee = eip1559.CalcBaseFee(config, header) + } + header = &types.Header{ + UncleHash: types.EmptyUncleHash, + Coinbase: base.Coinbase, + Difficulty: base.Difficulty, + GasLimit: base.GasLimit, + //MixDigest: header.MixDigest, + BaseFee: baseFee, + } + overrides.ApplyToHeader(header) + res[bi] = header + } + return res, nil +} From e23025876fa70e9e1fbf1a304143f4d6c24fdbdc Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Oct 2023 16:46:50 +0200 Subject: [PATCH 053/119] move precompile list to own func --- internal/ethapi/multicall.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index 0da48541af2b..f6151f09fbb8 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -126,19 +126,15 @@ func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockRe var ( results = make([]blockResult, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap - globalGasCap = mc.b.RPCGasCap() - gp = new(core.GasPool).AddGas(globalGasCap) - header = base - blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) - rules = mc.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) - precompiles = vm.ActivePrecompiledContracts(rules).Copy() - numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 + gp = new(core.GasPool).AddGas(mc.b.RPCGasCap()) + precompiles = mc.activePrecompiles(ctx, base) + numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 ) // Cache for the block hashes. mc.hashes = make([]common.Hash, numHashes) for bi, block := range blocks { - header = headers[bi] - blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) + header := headers[bi] + blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) // Respond to BLOCKHASH requests. blockContext.GetHash = func(n uint64) common.Hash { h, err := mc.getBlockHash(ctx, n, base, headers) @@ -288,6 +284,14 @@ func (mc *multicall) computeBlockHash(ctx context.Context, n uint64, base *types return common.Hash{}, errors.New("requested block is in future") } +func (mc *multicall) activePrecompiles(ctx context.Context, base *types.Header) vm.PrecompiledContracts { + var ( + blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, mc.b), nil) + rules = mc.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + ) + return vm.ActivePrecompiledContracts(rules).Copy() +} + // repairLogs updates the block hash in the logs present in a multicall // result object. This is needed as during execution when logs are collected // the block hash is not known. From c02d29d8e4ca89609545dec8cd1c85a63b51796a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 24 Oct 2023 17:53:14 +0200 Subject: [PATCH 054/119] limit number of blocks --- internal/ethapi/api.go | 7 +++++++ internal/ethapi/multicall.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3a91dde4dec2..9238fd008671 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1229,6 +1229,13 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]blockResult, error) { + if len(opts.BlockStateCalls) == 0 { + // TODO return error code. + return nil, errors.New("invalid params") + } else if len(opts.BlockStateCalls) > maxMulticallBlocks { + // TODO return error code. + return nil, errors.New("invalid params") + } if blockNrOrHash == nil { n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &n diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index f6151f09fbb8..c3e20da04d4f 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -37,6 +37,12 @@ import ( "github.com/ethereum/go-ethereum/trie" ) +const ( + // maxMulticallBlocks is the maximum number of blocks that can be simulated + // in a single request. + maxMulticallBlocks = 256 +) + // CallBatch is a batch of calls to be simulated sequentially. type CallBatch struct { BlockOverrides *BlockOverrides From e4eb56a4a9f4f401d44cd11fd44f72f12c62b382 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 24 Oct 2023 18:14:04 +0200 Subject: [PATCH 055/119] add error code for blocks length check --- internal/ethapi/api.go | 6 ++---- internal/ethapi/errors.go | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9238fd008671..d2c5b983a5c9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1230,11 +1230,9 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // useful to execute and retrieve values. func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]blockResult, error) { if len(opts.BlockStateCalls) == 0 { - // TODO return error code. - return nil, errors.New("invalid params") + return nil, &invalidParamsError{message: "empty input"} } else if len(opts.BlockStateCalls) > maxMulticallBlocks { - // TODO return error code. - return nil, errors.New("invalid params") + return nil, &invalidParamsError{message: "too many blocks"} } if blockNrOrHash == nil { n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index 6ea92823a240..fae01418d291 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -68,3 +68,8 @@ func callErrorFromError(err error) *callError { Code: errCodeInternalError, } } + +type invalidParamsError struct{ message string } + +func (e *invalidParamsError) Error() string { return e.message } +func (e *invalidParamsError) ErrorCode() int { return errCodeInvalidParams } From ed378218a60410242aea3c959d0f449f010f002b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 24 Oct 2023 18:26:03 +0200 Subject: [PATCH 056/119] rename types --- internal/ethapi/api.go | 2 +- internal/ethapi/api_test.go | 30 +++++++++++++++--------------- internal/ethapi/multicall.go | 36 ++++++++++++++++++------------------ 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d2c5b983a5c9..688bb337c85a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1228,7 +1228,7 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]blockResult, error) { +func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts mcOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]mcBlockResult, error) { if len(opts.BlockStateCalls) == 0 { return nil, &invalidParamsError{message: "empty input"} } else if len(opts.BlockStateCalls) > maxMulticallBlocks { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 82375f32d5e3..da4ca698e8a2 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1013,7 +1013,7 @@ func TestMulticallV1(t *testing.T) { } var testSuite = []struct { name string - blocks []CallBatch + blocks []mcBlock tag rpc.BlockNumberOrHash includeTransfers *bool validation *bool @@ -1026,7 +1026,7 @@ func TestMulticallV1(t *testing.T) { { name: "simple", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(1000))}, }, @@ -1068,7 +1068,7 @@ func TestMulticallV1(t *testing.T) { // State build-up over blocks. name: "simple-multi-block", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(2000))}, }, @@ -1137,7 +1137,7 @@ func TestMulticallV1(t *testing.T) { // Block overrides should work, each call is simulated on a different block number name: "block-overrides", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(11)), FeeRecipient: &cac, @@ -1193,7 +1193,7 @@ func TestMulticallV1(t *testing.T) { { name: "block-number-order", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(12)), }, @@ -1225,7 +1225,7 @@ func TestMulticallV1(t *testing.T) { { name: "storage-contract", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ Code: hex2Bytes("608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033"), @@ -1266,7 +1266,7 @@ func TestMulticallV1(t *testing.T) { { name: "logs", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ // Yul code: @@ -1307,7 +1307,7 @@ func TestMulticallV1(t *testing.T) { { name: "ecrecover-override", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ // Yul code that returns ecrecover(0, 0, 0, 0). @@ -1371,7 +1371,7 @@ func TestMulticallV1(t *testing.T) { { name: "precompile-move", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ sha256Address: OverrideAccount{ // Yul code that returns the calldata. @@ -1425,7 +1425,7 @@ func TestMulticallV1(t *testing.T) { { name: "transfer-logs", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{ Balance: newRPCBalance(big.NewInt(100)), @@ -1487,7 +1487,7 @@ func TestMulticallV1(t *testing.T) { { name: "selfdestruct", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ Calls: []TransactionArgs{{ From: &accounts[0].addr, To: &cac, @@ -1550,7 +1550,7 @@ func TestMulticallV1(t *testing.T) { { name: "validation-checks", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ Calls: []TransactionArgs{{ From: &accounts[2].addr, To: &cac, @@ -1576,7 +1576,7 @@ func TestMulticallV1(t *testing.T) { { name: "clear-storage", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: { Code: newBytes(genesis.Alloc[bab].Code), @@ -1643,7 +1643,7 @@ func TestMulticallV1(t *testing.T) { { name: "blockhash-opcode", tag: latest, - blocks: []CallBatch{{ + blocks: []mcBlock{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(12)), }, @@ -1729,7 +1729,7 @@ func TestMulticallV1(t *testing.T) { for _, tc := range testSuite { t.Run(tc.name, func(t *testing.T) { - opts := multicallOpts{BlockStateCalls: tc.blocks} + opts := mcOpts{BlockStateCalls: tc.blocks} if tc.includeTransfers != nil && *tc.includeTransfers { opts.TraceTransfers = true } diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index c3e20da04d4f..44c79895ec1e 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -43,14 +43,14 @@ const ( maxMulticallBlocks = 256 ) -// CallBatch is a batch of calls to be simulated sequentially. -type CallBatch struct { +// mcBlock is a batch of calls to be simulated sequentially. +type mcBlock struct { BlockOverrides *BlockOverrides StateOverrides *StateOverride Calls []TransactionArgs } -type blockResult struct { +type mcBlockResult struct { Number hexutil.Uint64 `json:"number"` Hash common.Hash `json:"hash"` Time hexutil.Uint64 `json:"timestamp"` @@ -59,11 +59,11 @@ type blockResult struct { FeeRecipient common.Address `json:"feeRecipient"` BaseFee *hexutil.Big `json:"baseFeePerGas"` PrevRandao common.Hash `json:"prevRandao"` - Calls []callResult `json:"calls"` + Calls []mcCallResult `json:"calls"` } -func mcBlockResultFromHeader(header *types.Header, callResults []callResult) blockResult { - return blockResult{ +func mcBlockResultFromHeader(header *types.Header, callResults []mcCallResult) mcBlockResult { + return mcBlockResult{ Number: hexutil.Uint64(header.Number.Uint64()), Hash: header.Hash(), Time: hexutil.Uint64(header.Time), @@ -76,7 +76,7 @@ func mcBlockResultFromHeader(header *types.Header, callResults []callResult) blo } } -type callResult struct { +type mcCallResult struct { ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` GasUsed hexutil.Uint64 `json:"gasUsed"` @@ -84,8 +84,8 @@ type callResult struct { Error *callError `json:"error,omitempty"` } -func (r *callResult) MarshalJSON() ([]byte, error) { - type callResultAlias callResult +func (r *mcCallResult) MarshalJSON() ([]byte, error) { + type callResultAlias mcCallResult // Marshal logs to be an empty array instead of nil when empty if r.Logs == nil { r.Logs = []*types.Log{} @@ -93,8 +93,8 @@ func (r *callResult) MarshalJSON() ([]byte, error) { return json.Marshal((*callResultAlias)(r)) } -type multicallOpts struct { - BlockStateCalls []CallBatch +type mcOpts struct { + BlockStateCalls []mcBlock TraceTransfers bool Validation bool } @@ -105,7 +105,7 @@ type multicall struct { hashes []common.Hash } -func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockResult, error) { +func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, error) { state, base, err := mc.b.StateAndHeaderByNumberOrHash(ctx, mc.blockNrOrHash) if state == nil || err != nil { return nil, err @@ -130,7 +130,7 @@ func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockRe return nil, err } var ( - results = make([]blockResult, len(blocks)) + results = make([]mcBlockResult, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap gp = new(core.GasPool).AddGas(mc.b.RPCGasCap()) precompiles = mc.activePrecompiles(ctx, base) @@ -157,7 +157,7 @@ func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockRe var ( gasUsed uint64 txes = make([]*types.Transaction, len(block.Calls)) - callResults = make([]callResult, len(block.Calls)) + callResults = make([]mcCallResult, len(block.Calls)) ) for i, call := range block.Calls { if call.Nonce == nil { @@ -188,7 +188,7 @@ func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockRe result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { callErr := callErrorFromError(err) - callResults[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} + callResults[i] = mcCallResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue } // If the result contains a revert reason, try to unpack it. @@ -196,7 +196,7 @@ func (mc *multicall) execute(ctx context.Context, opts multicallOpts) ([]blockRe result.Err = newRevertError(result) } logs := vmConfig.Tracer.(*tracer).Logs() - callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} + callRes := mcCallResult{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) { @@ -301,7 +301,7 @@ func (mc *multicall) activePrecompiles(ctx context.Context, base *types.Header) // repairLogs updates the block hash in the logs present in a multicall // result object. This is needed as during execution when logs are collected // the block hash is not known. -func repairLogs(results []blockResult, blockHash common.Hash) { +func repairLogs(results []mcBlockResult, blockHash common.Hash) { for i := range results { for j := range results[i].Calls { for k := range results[i].Calls[j].Logs { @@ -310,7 +310,7 @@ func repairLogs(results []blockResult, blockHash common.Hash) { } } } -func makeHeaders(config *params.ChainConfig, blocks []CallBatch, base *types.Header) ([]*types.Header, error) { +func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Header) ([]*types.Header, error) { res := make([]*types.Header, len(blocks)) var ( prevNumber = base.Number.Uint64() From f5ac0393e237a264ee3a8a50a75a30cf0c1fb14e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 26 Oct 2023 18:05:43 +0200 Subject: [PATCH 057/119] fix error codes --- internal/ethapi/api.go | 2 +- internal/ethapi/api_test.go | 60 +++++++++++++++++++---------- internal/ethapi/errors.go | 75 +++++++++++++++++++++++++++--------- internal/ethapi/multicall.go | 20 ++++++---- 4 files changed, 109 insertions(+), 48 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 688bb337c85a..180e10ac1b39 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1232,7 +1232,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts mcOpts, blockNrOrH if len(opts.BlockStateCalls) == 0 { return nil, &invalidParamsError{message: "empty input"} } else if len(opts.BlockStateCalls) > maxMulticallBlocks { - return nil, &invalidParamsError{message: "too many blocks"} + return nil, &clientLimitExceededError{message: "too many blocks"} } if blockNrOrHash == nil { n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index da4ca698e8a2..975a41e908f4 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1092,10 +1092,6 @@ func TestMulticallV1(t *testing.T) { From: &randomAccounts[1].addr, To: &randomAccounts[2].addr, Value: (*hexutil.Big)(big.NewInt(1000)), - }, { - From: &randomAccounts[3].addr, - To: &randomAccounts[2].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), }, }, }}, @@ -1125,12 +1121,45 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x5208", Logs: []log{}, Status: "0x1", - }, { + }}, + }}, + }, { + // insufficient funds + name: "insufficient-funds", + tag: latest, + blocks: []mcBlock{{ + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }}, + }}, + want: nil, + expectErr: &invalidTxError{Message: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4712388)", randomAccounts[0].addr.String()), Code: errCodeInsufficientFunds}, + }, { + // EVM error + name: "evm-error", + tag: latest, + blocks: []mcBlock{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: OverrideAccount{Code: hex2Bytes("f3")}, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + }}, + }}, + want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x47e7c4", + FeeRecipient: coinbase, + Calls: []callRes{{ ReturnValue: "0x", - GasUsed: "0x0", + Error: callErr{Message: "stack underflow (0 <=> 2)", Code: errCodeVMError}, + GasUsed: "0x47e7c4", Logs: []log{}, Status: "0x0", - Error: callErr{Message: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), Code: errCodeInsufficientFunds}, }}, }}, }, { @@ -1219,7 +1248,7 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{}, - expectErr: errors.New("block numbers must be in order"), + expectErr: &invalidBlockNumberError{message: fmt.Sprintf("block numbers must be in order: 11 <= 12")}, }, // Test on solidity storage example. Set value in one call, read in next. { @@ -1558,19 +1587,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, validation: &validation, - want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - FeeRecipient: coinbase, - Calls: []callRes{{ - ReturnValue: "0x", - GasUsed: "0x0", - Logs: []log{}, - Status: "0x0", - Error: callErr{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, - }}, - }}, + want: nil, + expectErr: &invalidTxError{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, }, // Clear storage. { diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index fae01418d291..9a88aa8a89f6 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -27,43 +27,60 @@ type callError struct { Code int `json:"code"` } +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 - errCodeInsufficientFunds = -38014 - errCodeIntrinsicGas = -38013 - errCodeInternalError = -32603 - errCodeInvalidParams = -32602 + 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 callErrorFromError(err error) *callError { +func txValidationError(err error) *invalidTxError { if err == nil { return nil } switch { case errors.Is(err, core.ErrNonceTooHigh): - return &callError{Message: err.Error(), Code: errCodeNonceTooHigh} + return &invalidTxError{Message: err.Error(), Code: errCodeNonceTooHigh} case errors.Is(err, core.ErrNonceTooLow): - return &callError{Message: err.Error(), Code: errCodeNonceTooLow} + return &invalidTxError{Message: err.Error(), Code: errCodeNonceTooLow} case errors.Is(err, core.ErrSenderNoEOA): - // TODO + return &invalidTxError{Message: err.Error(), Code: errCodeSenderIsNotEOA} case errors.Is(err, core.ErrFeeCapVeryHigh): - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrTipVeryHigh): - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrTipAboveFeeCap): - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrFeeCapTooLow): - // TODO - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrInsufficientFunds): - return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds} case errors.Is(err, core.ErrIntrinsicGas): - return &callError{Message: err.Error(), Code: errCodeIntrinsicGas} + return &invalidTxError{Message: err.Error(), Code: errCodeIntrinsicGas} case errors.Is(err, core.ErrInsufficientFundsForTransfer): - return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds} + case errors.Is(err, core.ErrMaxInitCodeSizeExceeded): + return &invalidTxError{Message: err.Error(), Code: errCodeMaxInitCodeSizeExceeded} } - return &callError{ + return &invalidTxError{ Message: err.Error(), Code: errCodeInternalError, } @@ -73,3 +90,23 @@ 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 } diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index 44c79895ec1e..31395d612ed6 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -164,6 +164,13 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) } + var gas uint64 + if call.Gas != nil { + gas = uint64(*call.Gas) + } + if gasUsed+gas > blockContext.GasLimit { + return nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)} + } // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := blockContext.GasLimit - gasUsed @@ -187,9 +194,8 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, } result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { - callErr := callErrorFromError(err) - callResults[i] = mcCallResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} - continue + txErr := txValidationError(err) + return nil, txErr } // If the result contains a revert reason, try to unpack it. if len(result.Revert()) > 0 { @@ -200,9 +206,9 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) if errors.Is(result.Err, vm.ErrExecutionReverted) { - callRes.Error = &callError{Message: result.Err.Error(), Code: -32000} + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeReverted} } else { - callRes.Error = &callError{Message: result.Err.Error(), Code: -32015} + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError} } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) @@ -327,7 +333,7 @@ func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Heade n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) overrides.Number = (*hexutil.Big)(n) } else if overrides.Number.ToInt().Uint64() <= prevNumber { - return nil, fmt.Errorf("block numbers must be in order") + return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", overrides.Number.ToInt().Uint64(), prevNumber)} } prevNumber = overrides.Number.ToInt().Uint64() @@ -335,7 +341,7 @@ func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Heade t := prevTimestamp + 1 overrides.Time = (*hexutil.Uint64)(&t) } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { - return nil, fmt.Errorf("timestamps must be in order") + return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", *time, prevTimestamp)} } prevTimestamp = uint64(*overrides.Time) From 23ef9c6ca815ac52bcf2ee7c06975f7ff6cd1606 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 13 Nov 2023 19:24:27 +0300 Subject: [PATCH 058/119] minor fix --- core/state/statedb.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 9e58f0145574..dcb365efa417 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -433,7 +433,8 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common } stateObject := s.GetOrNewStateObject(addr) // If object was already in memory, it might have cached - // storage slots. These should be cleared. + // storage slots. These might not be + // overridden in the new storage set and should be cleared. stateObject.clearStorageCache() for k, v := range storage { stateObject.SetState(k, v) From 6da92026f7aa832a187d5ca7fa0204b493347731 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 14 Dec 2023 12:20:16 +0330 Subject: [PATCH 059/119] Fix extra delegatecall value log --- internal/ethapi/transfertracer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go index 2a7b76f55b56..7489d12bfd19 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/transfertracer.go @@ -117,7 +117,7 @@ func (t *tracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope * func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { t.logs = append(t.logs, make([]*types.Log, 0)) toCopy := to - if value != nil && value.Cmp(common.Big0) > 0 { + if typ != vm.DELEGATECALL && value != nil && value.Cmp(common.Big0) > 0 { t.captureTransfer(from, toCopy, value) } } From 12d59b58e2dffb3cb7a034d651a208f9f6ef3c6c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 14 Dec 2023 12:25:18 +0330 Subject: [PATCH 060/119] update transfer log address --- internal/ethapi/api_test.go | 4 ++-- internal/ethapi/transfertracer.go | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 975a41e908f4..6a87bd784bd8 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1489,7 +1489,7 @@ func TestMulticallV1(t *testing.T) { ReturnValue: "0x", GasUsed: "0xd984", Logs: []log{{ - Address: common.Address{}, + Address: transferAddress, Topics: []common.Hash{ transferTopic, accounts[0].addr.Hash(), @@ -1498,7 +1498,7 @@ func TestMulticallV1(t *testing.T) { Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), BlockNumber: hexutil.Uint64(11), }, { - Address: common.Address{}, + Address: transferAddress, Topics: []common.Hash{ transferTopic, randomAccounts[0].addr.Hash(), diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go index 7489d12bfd19..96eb78e120e9 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/transfertracer.go @@ -25,8 +25,12 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -// keccak256("Transfer(address,address,uint256)") -var transferTopic = common.HexToHash("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") +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 @@ -168,7 +172,7 @@ func (t *tracer) captureTransfer(from, to common.Address, value *big.Int) { common.BytesToHash(from.Bytes()), common.BytesToHash(to.Bytes()), } - t.captureLog(common.Address{}, topics, common.BigToHash(value).Bytes()) + t.captureLog(transferAddress, topics, common.BigToHash(value).Bytes()) } func (t *tracer) Logs() []*types.Log { From 7fff1c40effb135f6d49ea13c1e8395c71834011 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 14 Dec 2023 18:17:45 +0330 Subject: [PATCH 061/119] Add cancun to precompiled contracts --- core/vm/contracts.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 2da5d93c3903..4059bb52f0cb 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -162,6 +162,8 @@ func (p PrecompiledContracts) Copy() PrecompiledContracts { // ActivePrecompiledContracts returns precompiled contracts enabled with the current configuration. func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { switch { + case rules.IsCancun: + return PrecompiledContractsCancun case rules.IsBerlin: return PrecompiledContractsBerlin case rules.IsIstanbul: From bf7809495fd5dbc06e65682ed73e44c8feb60f4e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 14 Dec 2023 18:22:55 +0330 Subject: [PATCH 062/119] safer api for getting active precompiles --- core/vm/contracts.go | 8 ++++++-- core/vm/evm.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 4059bb52f0cb..8cc3051bae4e 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -159,8 +159,7 @@ func (p PrecompiledContracts) Copy() PrecompiledContracts { return c } -// ActivePrecompiledContracts returns precompiled contracts enabled with the current configuration. -func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { +func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { switch { case rules.IsCancun: return PrecompiledContractsCancun @@ -175,6 +174,11 @@ func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { } } +// ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration. +func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { + return activePrecompiledContracts(rules).Copy() +} + // ActivePrecompiles returns the precompile addresses enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { diff --git a/core/vm/evm.go b/core/vm/evm.go index 8531283ea2ff..0cd5d7dab451 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -134,7 +134,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - evm.precompiles = ActivePrecompiledContracts(evm.chainRules) + evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm) return evm } From c594f7436bb48fe0508044bd1e7bd7d514e91b9d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 3 Jan 2024 17:53:00 +0330 Subject: [PATCH 063/119] fix godoc comment --- core/vm/contracts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 8cc3051bae4e..6e278fdff8f4 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -42,7 +42,7 @@ type PrecompiledContract interface { Run(input []byte) ([]byte, error) // Run runs the precompiled contract } -// PrecompiledContractByName contains the precompiled contracts supported at the given fork. +// 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 From 11133e0d0a8147c93c30ec169ed3cbba9891ab53 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 3 Jan 2024 17:58:13 +0330 Subject: [PATCH 064/119] fix precompileMove in eth_call --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index cf253b88b808..ffdbb00d0360 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1130,7 +1130,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - return applyMessage(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, nil, false) + return applyMessage(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, precompiles, false) } 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, validate bool) (*core.ExecutionResult, error) { From ad24d2ed46b8a440301ed4f72dbcfcaadac67371 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 3 Jan 2024 18:00:33 +0330 Subject: [PATCH 065/119] rename block overrides receiver name --- internal/ethapi/api.go | 68 +++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ffdbb00d0360..027bbea54fc8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1018,61 +1018,61 @@ type BlockOverrides struct { } // Apply overrides the given header fields into the given block context. -func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) { - if diff == nil { +func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { + if o == nil { return } - if diff.Number != nil { - blockCtx.BlockNumber = diff.Number.ToInt() + if o.Number != nil { + blockCtx.BlockNumber = o.Number.ToInt() } - if diff.Difficulty != nil { - blockCtx.Difficulty = diff.Difficulty.ToInt() + if o.Difficulty != nil { + blockCtx.Difficulty = o.Difficulty.ToInt() } - if diff.Time != nil { - blockCtx.Time = uint64(*diff.Time) + if o.Time != nil { + blockCtx.Time = uint64(*o.Time) } - if diff.GasLimit != nil { - blockCtx.GasLimit = uint64(*diff.GasLimit) + if o.GasLimit != nil { + blockCtx.GasLimit = uint64(*o.GasLimit) } - if diff.FeeRecipient != nil { - blockCtx.Coinbase = *diff.FeeRecipient + if o.FeeRecipient != nil { + blockCtx.Coinbase = *o.FeeRecipient } - if diff.PrevRandao != nil { - blockCtx.Random = diff.PrevRandao + if o.PrevRandao != nil { + blockCtx.Random = o.PrevRandao } - if diff.BaseFeePerGas != nil { - blockCtx.BaseFee = diff.BaseFeePerGas.ToInt() + if o.BaseFeePerGas != nil { + blockCtx.BaseFee = o.BaseFeePerGas.ToInt() } - if diff.BlobBaseFee != nil { - blockCtx.BlobBaseFee = diff.BlobBaseFee.ToInt() + if o.BlobBaseFee != nil { + blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt() } } // ApplyToHeader overrides the given fields into a header. -func (diff *BlockOverrides) ApplyToHeader(header *types.Header) { - if diff == nil { +func (o *BlockOverrides) ApplyToHeader(header *types.Header) { + if o == nil { return } - if diff.Number != nil { - header.Number = diff.Number.ToInt() + if o.Number != nil { + header.Number = o.Number.ToInt() } - if diff.Difficulty != nil { - header.Difficulty = diff.Difficulty.ToInt() + if o.Difficulty != nil { + header.Difficulty = o.Difficulty.ToInt() } - if diff.Time != nil { - header.Time = uint64(*diff.Time) + if o.Time != nil { + header.Time = uint64(*o.Time) } - if diff.GasLimit != nil { - header.GasLimit = uint64(*diff.GasLimit) + if o.GasLimit != nil { + header.GasLimit = uint64(*o.GasLimit) } - if diff.FeeRecipient != nil { - header.Coinbase = *diff.FeeRecipient + if o.FeeRecipient != nil { + header.Coinbase = *o.FeeRecipient } - if diff.PrevRandao != nil { - header.MixDigest = *diff.PrevRandao + if o.PrevRandao != nil { + header.MixDigest = *o.PrevRandao } - if diff.BaseFeePerGas != nil { - header.BaseFee = diff.BaseFeePerGas.ToInt() + if o.BaseFeePerGas != nil { + header.BaseFee = o.BaseFeePerGas.ToInt() } } From c77da8c4885dbe6749a387ea651cb5dbcbfb0b79 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 3 Jan 2024 18:09:09 +0330 Subject: [PATCH 066/119] rename validate param --- internal/ethapi/api.go | 7 +++---- internal/ethapi/multicall.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 027bbea54fc8..7844fd2c01cf 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1130,12 +1130,12 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - return applyMessage(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), &blockCtx, &vm.Config{NoBaseFee: true}, precompiles, false) + 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, validate bool) (*core.ExecutionResult, error) { +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. - msg, err := args.ToMessage(gp.Gas(), header.BaseFee, !validate) + msg, err := args.ToMessage(gp.Gas(), header.BaseFee, skipChecks) if err != nil { return nil, err } @@ -1174,7 +1174,6 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash if state == nil || err != nil { return nil, err } - return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap) } diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index 8bea9850da12..0b189eeea011 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -195,7 +195,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, // Block hash will be repaired after execution. Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, tx.Hash(), uint(i)), } - result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) + result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, !opts.Validation) if err != nil { txErr := txValidationError(err) return nil, txErr From 3d29c1f39d1d35342837020e7733ffd7d2eb6eb7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 3 Jan 2024 18:34:25 +0330 Subject: [PATCH 067/119] copy header prior to applying overrides --- internal/ethapi/api.go | 23 +++++++++++++---------- internal/ethapi/multicall.go | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7844fd2c01cf..f39b6ff64484 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1048,32 +1048,35 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { } } -// ApplyToHeader overrides the given fields into a header. -func (o *BlockOverrides) ApplyToHeader(header *types.Header) { +// MakeHeader returns a new header object with the overridden +// fields. +func (o *BlockOverrides) MakeHeader(header *types.Header) *types.Header { if o == nil { - return + return header } + h := types.CopyHeader(header) if o.Number != nil { - header.Number = o.Number.ToInt() + h.Number = o.Number.ToInt() } if o.Difficulty != nil { - header.Difficulty = o.Difficulty.ToInt() + h.Difficulty = o.Difficulty.ToInt() } if o.Time != nil { - header.Time = uint64(*o.Time) + h.Time = uint64(*o.Time) } if o.GasLimit != nil { - header.GasLimit = uint64(*o.GasLimit) + h.GasLimit = uint64(*o.GasLimit) } if o.FeeRecipient != nil { - header.Coinbase = *o.FeeRecipient + h.Coinbase = *o.FeeRecipient } if o.PrevRandao != nil { - header.MixDigest = *o.PrevRandao + h.MixDigest = *o.PrevRandao } if o.BaseFeePerGas != nil { - header.BaseFee = o.BaseFeePerGas.ToInt() + h.BaseFee = o.BaseFeePerGas.ToInt() } + return h } // ChainContextBackend provides methods required to implement ChainContext. diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index 0b189eeea011..aaae2f1b7002 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -319,6 +319,7 @@ func repairLogs(results []mcBlockResult, blockHash common.Hash) { } } } + func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Header) ([]*types.Header, error) { res := make([]*types.Header, len(blocks)) var ( @@ -360,8 +361,7 @@ func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Heade //MixDigest: header.MixDigest, BaseFee: baseFee, } - overrides.ApplyToHeader(header) - res[bi] = header + res[bi] = overrides.MakeHeader(header) } return res, nil } From 31e2cc9c6ddaad556ac415f545071ec7b1410fde Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Jan 2024 13:11:24 +0330 Subject: [PATCH 068/119] legacy tx if gasPrice provided --- internal/ethapi/multicall.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index aaae2f1b7002..60e5fb05b510 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -187,7 +187,12 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, if err := call.validateAll(); err != nil { return nil, err } - tx := call.ToTransaction(true) + // Default to dynamic-fee transaction type. + type2 := true + if call.GasPrice != nil { + type2 = false + } + tx := call.ToTransaction(type2) txes[i] = tx // TODO: repair log block hashes post execution. vmConfig := &vm.Config{ From 2b1cf221c4f23a6a2d3cf478c90a93c7bb98bf88 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Jan 2024 16:32:53 +0330 Subject: [PATCH 069/119] fix graphql test --- graphql/graphql_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index f91229d01597..9eb37cac1399 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -452,6 +452,7 @@ func newGQLService(t *testing.T, stack *node.Node, shanghai bool, gspec *core.Ge TrieDirtyCache: 5, TrieTimeout: 60 * time.Minute, SnapshotCache: 5, + RPCGasCap: 1000000, } var engine consensus.Engine = ethash.NewFaker() if shanghai { From fd0f626f67e9289fcd278d54462a8372438ccbdb Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Jan 2024 16:54:49 +0330 Subject: [PATCH 070/119] fix rpcgascap for ethclient tests --- ethclient/ethclient_test.go | 2 +- ethclient/gethclient/gethclient_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 0f87ad5f5cd3..70938e30a272 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -219,7 +219,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { t.Fatalf("can't create new node: %v", err) } // Create Ethereum Service - config := ðconfig.Config{Genesis: genesis} + config := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000} ethservice, err := eth.New(n, config) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index fdd94a7d734d..b86dcad8e806 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -57,7 +57,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { t.Fatalf("can't create new node: %v", err) } // Create Ethereum Service - config := ðconfig.Config{Genesis: genesis} + config := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000} ethservice, err := eth.New(n, config) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) From 1e7b497fb30ba6c183dff34c0348d2cf5565da0b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Jan 2024 17:05:02 +0330 Subject: [PATCH 071/119] fix block overrides in gethclient --- ethclient/gethclient/gethclient.go | 6 +++--- ethclient/gethclient/gethclient_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index e2c0ef3ed02e..457b0845485d 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -313,9 +313,9 @@ func (o BlockOverrides) MarshalJSON() ([]byte, error) { Difficulty *hexutil.Big `json:"difficulty,omitempty"` Time hexutil.Uint64 `json:"time,omitempty"` GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"` - Coinbase *common.Address `json:"coinbase,omitempty"` - Random *common.Hash `json:"random,omitempty"` - BaseFee *hexutil.Big `json:"baseFee,omitempty"` + Coinbase *common.Address `json:"feeRecipient,omitempty"` + Random *common.Hash `json:"prevRandao,omitempty"` + BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"` } output := override{ diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index b86dcad8e806..5a306a626d83 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -510,7 +510,7 @@ func TestBlockOverridesMarshal(t *testing.T) { bo: BlockOverrides{ Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"), }, - want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`, + want: `{"feeRecipient":"0x1111111111111111111111111111111111111111"}`, }, { bo: BlockOverrides{ @@ -520,7 +520,7 @@ func TestBlockOverridesMarshal(t *testing.T) { GasLimit: 4, 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) From 589107b6f5172b3e380670223bc18af94093e538 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 5 Jan 2024 17:08:58 +0330 Subject: [PATCH 072/119] rename to blobGasPrice --- internal/ethapi/api.go | 10 +++++----- internal/ethapi/multicall.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index f39b6ff64484..92d56f663be5 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1012,9 +1012,7 @@ type BlockOverrides struct { FeeRecipient *common.Address PrevRandao *common.Hash BaseFeePerGas *hexutil.Big - // TODO: is this right? should we override this since it doesn't exist - // in header or excessBlobGas which is used to calculate the base fee? - BlobBaseFee *hexutil.Big + BlobGasPrice *hexutil.Big } // Apply overrides the given header fields into the given block context. @@ -1043,13 +1041,15 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if o.BaseFeePerGas != nil { blockCtx.BaseFee = o.BaseFeePerGas.ToInt() } - if o.BlobBaseFee != nil { - blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt() + if o.BlobGasPrice != nil { + blockCtx.BlobBaseFee = o.BlobGasPrice.ToInt() } } // MakeHeader returns a new header object with the overridden // fields. +// Note: MakeHeader ignores blobGasPrice if set. That's because +// header has no such field. func (o *BlockOverrides) MakeHeader(header *types.Header) *types.Header { if o == nil { return header diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index fecc23afac56..0e53147350e0 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -141,8 +141,8 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, for bi, block := range blocks { header := headers[bi] blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) - if block.BlockOverrides != nil && block.BlockOverrides.BlobBaseFee != nil { - blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt() + if block.BlockOverrides != nil && block.BlockOverrides.BlobGasPrice != nil { + blockContext.BlobBaseFee = block.BlockOverrides.BlobGasPrice.ToInt() } // Respond to BLOCKHASH requests. blockContext.GetHash = func(n uint64) common.Hash { From 89df0e3b2cfccf20fcb6e264250fe8eee9cb5157 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 11 Jan 2024 14:35:09 +0330 Subject: [PATCH 073/119] rename method to eth_simulateV1 --- internal/ethapi/api.go | 4 ++-- internal/ethapi/api_test.go | 4 ++-- internal/ethapi/{multicall.go => simulate.go} | 0 internal/web3ext/web3ext.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename internal/ethapi/{multicall.go => simulate.go} (100%) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 92d56f663be5..6bc5babc71d5 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1233,14 +1233,14 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO return result.Return(), result.Err } -// MulticallV1 executes series of transactions on top of a base state. +// 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 (s *BlockChainAPI) MulticallV1(ctx context.Context, opts mcOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]mcBlockResult, error) { +func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts mcOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]mcBlockResult, error) { if len(opts.BlockStateCalls) == 0 { return nil, &invalidParamsError{message: "empty input"} } else if len(opts.BlockStateCalls) > maxMulticallBlocks { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 707f10a62c05..ea012465fac6 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -946,7 +946,7 @@ func TestCall(t *testing.T) { } } -func TestMulticallV1(t *testing.T) { +func TestSimulateV1(t *testing.T) { t.Parallel() // Initialize test accounts var ( @@ -1794,7 +1794,7 @@ func TestMulticallV1(t *testing.T) { if tc.validation != nil && *tc.validation { opts.Validation = true } - result, err := api.MulticallV1(context.Background(), opts, &tc.tag) + result, err := api.SimulateV1(context.Background(), opts, &tc.tag) if tc.expectErr != nil { if err == nil { t.Fatalf("test %s: want error %v, have nothing", tc.name, tc.expectErr) diff --git a/internal/ethapi/multicall.go b/internal/ethapi/simulate.go similarity index 100% rename from internal/ethapi/multicall.go rename to internal/ethapi/simulate.go diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 7ee01fce9add..76b71989e3e9 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -618,8 +618,8 @@ web3._extend({ inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null], }), new web3._extend.Method({ - name: 'multicallV1', - call: 'eth_multicallV1', + name: 'simulateV1', + call: 'eth_simulateV1', params: 3, inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter, null], }), From 1a28a17b66489e46e7f15aa7ad8969888e2c47f3 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 15 Jan 2024 16:04:49 +0330 Subject: [PATCH 074/119] move call sanitization to own method --- internal/ethapi/simulate.go | 51 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 0e53147350e0..307e7d7ec8fd 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "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/crypto" @@ -163,33 +164,13 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, callResults = make([]mcCallResult, len(block.Calls)) ) for i, call := range block.Calls { - if call.Nonce == nil { - nonce := state.GetNonce(call.from()) - call.Nonce = (*hexutil.Uint64)(&nonce) - } - var gas uint64 - if call.Gas != nil { - gas = uint64(*call.Gas) - } - if gasUsed+gas > blockContext.GasLimit { - return nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)} - } - // Let the call run wild unless explicitly specified. - if call.Gas == nil { - remaining := blockContext.GasLimit - gasUsed - call.Gas = (*hexutil.Uint64)(&remaining) - } - // TODO: check chainID and against current header for london fees - if err := call.validateAll(mc.b); err != nil { + if err := mc.sanitizeCall(&call, state, &gasUsed, blockContext); err != nil { return nil, err } // Default to dynamic-fee transaction type. type2 := true if call.GasPrice != nil { type2 = false - } else if call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { - call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) - call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) } tx := call.ToTransaction(type2) txes[i] = tx @@ -242,6 +223,34 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, return results, nil } +func (mc *multicall) sanitizeCall(call *TransactionArgs, state *state.StateDB, gasUsed *uint64, blockContext vm.BlockContext) error { + if call.Nonce == nil { + nonce := state.GetNonce(call.from()) + call.Nonce = (*hexutil.Uint64)(&nonce) + } + var gas uint64 + if call.Gas != nil { + gas = uint64(*call.Gas) + } + if *gasUsed+gas > blockContext.GasLimit { + return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)} + } + // Let the call run wild unless explicitly specified. + if call.Gas == nil { + remaining := blockContext.GasLimit - *gasUsed + call.Gas = (*hexutil.Uint64)(&remaining) + } + // TODO: check chainID and against current header for london fees + if err := call.validateAll(mc.b); err != nil { + return err + } + if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { + call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) + call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) + } + return nil +} + // getBlockHash returns the hash for the block of the given number. Block can be // part of the canonical chain, a simulated block or a phantom block. // Note getBlockHash assumes `n` is smaller than the last already simulated block From 115117c6829fc4b6552763355bf540c80e58cba6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 15 Jan 2024 16:11:24 +0330 Subject: [PATCH 075/119] move tx type detection check --- internal/ethapi/simulate.go | 7 +------ internal/ethapi/transaction_args.go | 6 ++++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 307e7d7ec8fd..280156bb7729 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -167,12 +167,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, if err := mc.sanitizeCall(&call, state, &gasUsed, blockContext); err != nil { return nil, err } - // Default to dynamic-fee transaction type. - type2 := true - if call.GasPrice != nil { - type2 = false - } - tx := call.ToTransaction(type2) + tx := call.ToTransaction() txes[i] = tx // TODO: repair log block hashes post execution. vmConfig := &vm.Config{ diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index afc866d0e993..906210f95762 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -366,7 +366,9 @@ func (args *TransactionArgs) toTransaction(type2 bool) *types.Transaction { } // ToTransaction converts the arguments to a transaction. +// It defaults to a dynamic fee transaction unless legacy gas price +// is explicitly provided. // This assumes that setDefaults has been called. -func (args *TransactionArgs) ToTransaction(type2 bool) *types.Transaction { - return args.toTransaction(type2) +func (args *TransactionArgs) ToTransaction() *types.Transaction { + return args.toTransaction(args.GasPrice == nil) } From df147182918e38c5875ffcafd97b8323f13da6c7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 16 Jan 2024 12:55:51 +0330 Subject: [PATCH 076/119] compute receipts --- core/state_processor.go | 11 +++++--- internal/ethapi/api.go | 4 +++ internal/ethapi/simulate.go | 42 ++++++++++++++++++++++++------- internal/ethapi/transfertracer.go | 7 ++++++ 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 9a4333f72330..e63107e88439 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -124,9 +124,14 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta } *usedGas += result.UsedGas + return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), err +} + +// 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 // 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() { receipt.Status = types.ReceiptStatusFailed } else { @@ -141,7 +146,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta } // 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()) } @@ -151,7 +156,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, err + return receipt } // ApplyTransaction attempts to apply a transaction to the given state database diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 6bc5babc71d5..ae3c17172da7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1147,6 +1147,10 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s 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 // EVM has finished, cancelling may be done (repeatedly) go func() { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 280156bb7729..13821759aeb5 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -162,29 +162,54 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, gasUsed uint64 txes = make([]*types.Transaction, len(block.Calls)) callResults = make([]mcCallResult, len(block.Calls)) + receipts = make([]*types.Receipt, len(block.Calls)) + tracer = newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) + config = mc.b.ChainConfig() + vmConfig = &vm.Config{ + NoBaseFee: true, + // Block hash will be repaired after execution. + Tracer: tracer, + } + evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, state, config, *vmConfig) ) + if precompiles != nil { + evm.SetPrecompiles(precompiles) + } for i, call := range block.Calls { + // TODO: Pre-estimate nonce and gas + // TODO: Move gas fees sanitizing to beginning of func if err := mc.sanitizeCall(&call, state, &gasUsed, blockContext); err != nil { return nil, err } tx := call.ToTransaction() txes[i] = tx - // TODO: repair log block hashes post execution. - vmConfig := &vm.Config{ - NoBaseFee: true, - // Block hash will be repaired after execution. - Tracer: newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, tx.Hash(), uint(i)), + + msg, err := call.ToMessage(gp.Gas(), header.BaseFee, !opts.Validation) + if err != nil { + return nil, err } - result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, !opts.Validation) + tracer.reset(tx.Hash(), uint(i)) + evm.Reset(core.NewEVMTxContext(msg), state) + result, err := applyMessageWithEVM(ctx, evm, msg, state, timeout, gp) if err != nil { txErr := txValidationError(err) return nil, txErr } + // Update the state with pending changes. + var root []byte + if config.IsByzantium(blockContext.BlockNumber) { + state.Finalise(true) + } else { + root = state.IntermediateRoot(config.IsEIP158(blockContext.BlockNumber)).Bytes() + } + gasUsed += result.UsedGas + receipt := core.MakeReceipt(evm, result, state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) + receipts[i] = receipt // If the result contains a revert reason, try to unpack it. if len(result.Revert()) > 0 { result.Err = newRevertError(result.Revert()) } - logs := vmConfig.Tracer.(*tracer).Logs() + logs := tracer.Logs() callRes := mcCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) @@ -197,8 +222,6 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) } callResults[i] = callRes - gasUsed += result.UsedGas - state.Finalise(true) } var ( parentHash common.Hash @@ -212,6 +235,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, header.Root = state.IntermediateRoot(true) header.GasUsed = gasUsed header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) + header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) results[bi] = mcBlockResultFromHeader(header, callResults) repairLogs(results, header.Hash()) } diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/transfertracer.go index 96eb78e120e9..e4d547818b21 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/transfertracer.go @@ -175,6 +175,13 @@ func (t *tracer) captureTransfer(from, to common.Address, value *big.Int) { 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 = make([][]*types.Log, 1) + t.txHash = txHash + t.txIdx = txIdx +} + func (t *tracer) Logs() []*types.Log { return t.logs[0] } From ae335662af43630630e8ce52ac608cdcfaac9dfa Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 17 Jan 2024 14:02:35 +0330 Subject: [PATCH 077/119] finish renaming to simulate --- internal/ethapi/api.go | 6 +-- internal/ethapi/api_test.go | 34 ++++++------ internal/ethapi/simulate.go | 104 ++++++++++++++++++------------------ 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ae3c17172da7..d0cff254d494 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1244,17 +1244,17 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts mcOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]mcBlockResult, error) { +func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]simBlockResult, error) { if len(opts.BlockStateCalls) == 0 { return nil, &invalidParamsError{message: "empty input"} - } else if len(opts.BlockStateCalls) > maxMulticallBlocks { + } else if len(opts.BlockStateCalls) > maxSimulateBlocks { return nil, &clientLimitExceededError{message: "too many blocks"} } if blockNrOrHash == nil { n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &n } - mc := &multicall{b: s.b, blockNrOrHash: *blockNrOrHash} + mc := &simulator{b: s.b, blockNrOrHash: *blockNrOrHash} return mc.execute(ctx, opts) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index ea012465fac6..af5c6cf8cfd9 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1053,7 +1053,7 @@ func TestSimulateV1(t *testing.T) { } var testSuite = []struct { name string - blocks []mcBlock + blocks []simBlock tag rpc.BlockNumberOrHash includeTransfers *bool validation *bool @@ -1066,7 +1066,7 @@ func TestSimulateV1(t *testing.T) { { name: "simple", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(1000))}, }, @@ -1108,7 +1108,7 @@ func TestSimulateV1(t *testing.T) { // State build-up over blocks. name: "simple-multi-block", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(2000))}, }, @@ -1167,7 +1167,7 @@ func TestSimulateV1(t *testing.T) { // insufficient funds name: "insufficient-funds", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ Calls: []TransactionArgs{{ From: &randomAccounts[0].addr, To: &randomAccounts[1].addr, @@ -1180,7 +1180,7 @@ func TestSimulateV1(t *testing.T) { // EVM error name: "evm-error", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{Code: hex2Bytes("f3")}, }, @@ -1206,7 +1206,7 @@ func TestSimulateV1(t *testing.T) { // Block overrides should work, each call is simulated on a different block number name: "block-overrides", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(11)), FeeRecipient: &cac, @@ -1262,7 +1262,7 @@ func TestSimulateV1(t *testing.T) { { name: "block-number-order", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(12)), }, @@ -1294,7 +1294,7 @@ func TestSimulateV1(t *testing.T) { { name: "storage-contract", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ Code: hex2Bytes("608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033"), @@ -1335,7 +1335,7 @@ func TestSimulateV1(t *testing.T) { { name: "logs", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ // Yul code: @@ -1376,7 +1376,7 @@ func TestSimulateV1(t *testing.T) { { name: "ecrecover-override", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: OverrideAccount{ // Yul code that returns ecrecover(0, 0, 0, 0). @@ -1440,7 +1440,7 @@ func TestSimulateV1(t *testing.T) { { name: "precompile-move", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ sha256Address: OverrideAccount{ // Yul code that returns the calldata. @@ -1494,7 +1494,7 @@ func TestSimulateV1(t *testing.T) { { name: "transfer-logs", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[0].addr: OverrideAccount{ Balance: newRPCBalance(big.NewInt(100)), @@ -1556,7 +1556,7 @@ func TestSimulateV1(t *testing.T) { { name: "selfdestruct", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ Calls: []TransactionArgs{{ From: &accounts[0].addr, To: &cac, @@ -1619,7 +1619,7 @@ func TestSimulateV1(t *testing.T) { { name: "validation-checks", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ Calls: []TransactionArgs{{ From: &accounts[2].addr, To: &cac, @@ -1634,7 +1634,7 @@ func TestSimulateV1(t *testing.T) { { name: "clear-storage", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: { Code: newBytes(genesis.Alloc[bab].Code), @@ -1701,7 +1701,7 @@ func TestSimulateV1(t *testing.T) { { name: "blockhash-opcode", tag: latest, - blocks: []mcBlock{{ + blocks: []simBlock{{ BlockOverrides: &BlockOverrides{ Number: (*hexutil.Big)(big.NewInt(12)), }, @@ -1787,7 +1787,7 @@ func TestSimulateV1(t *testing.T) { for _, tc := range testSuite { t.Run(tc.name, func(t *testing.T) { - opts := mcOpts{BlockStateCalls: tc.blocks} + opts := simOpts{BlockStateCalls: tc.blocks} if tc.includeTransfers != nil && *tc.includeTransfers { opts.TraceTransfers = true } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 13821759aeb5..ba7bc34846c0 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -39,32 +39,32 @@ import ( ) const ( - // maxMulticallBlocks is the maximum number of blocks that can be simulated + // maxSimulateBlocks is the maximum number of blocks that can be simulated // in a single request. - maxMulticallBlocks = 256 + maxSimulateBlocks = 256 ) -// mcBlock is a batch of calls to be simulated sequentially. -type mcBlock struct { +// simBlock is a batch of calls to be simulated sequentially. +type simBlock struct { BlockOverrides *BlockOverrides StateOverrides *StateOverride Calls []TransactionArgs } -type mcBlockResult struct { - Number hexutil.Uint64 `json:"number"` - Hash common.Hash `json:"hash"` - Time hexutil.Uint64 `json:"timestamp"` - GasLimit hexutil.Uint64 `json:"gasLimit"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - FeeRecipient common.Address `json:"feeRecipient"` - BaseFee *hexutil.Big `json:"baseFeePerGas"` - PrevRandao common.Hash `json:"prevRandao"` - Calls []mcCallResult `json:"calls"` +type simBlockResult struct { + Number hexutil.Uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Time hexutil.Uint64 `json:"timestamp"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"feeRecipient"` + BaseFee *hexutil.Big `json:"baseFeePerGas"` + PrevRandao common.Hash `json:"prevRandao"` + Calls []simCallResult `json:"calls"` } -func mcBlockResultFromHeader(header *types.Header, callResults []mcCallResult) mcBlockResult { - return mcBlockResult{ +func mcBlockResultFromHeader(header *types.Header, callResults []simCallResult) simBlockResult { + return simBlockResult{ Number: hexutil.Uint64(header.Number.Uint64()), Hash: header.Hash(), Time: hexutil.Uint64(header.Time), @@ -77,7 +77,7 @@ func mcBlockResultFromHeader(header *types.Header, callResults []mcCallResult) m } } -type mcCallResult struct { +type simCallResult struct { ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` GasUsed hexutil.Uint64 `json:"gasUsed"` @@ -85,8 +85,8 @@ type mcCallResult struct { Error *callError `json:"error,omitempty"` } -func (r *mcCallResult) MarshalJSON() ([]byte, error) { - type callResultAlias mcCallResult +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{} @@ -94,20 +94,20 @@ func (r *mcCallResult) MarshalJSON() ([]byte, error) { return json.Marshal((*callResultAlias)(r)) } -type mcOpts struct { - BlockStateCalls []mcBlock +type simOpts struct { + BlockStateCalls []simBlock TraceTransfers bool Validation bool } -type multicall struct { +type simulator struct { blockNrOrHash rpc.BlockNumberOrHash b Backend hashes []common.Hash } -func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, error) { - state, base, err := mc.b.StateAndHeaderByNumberOrHash(ctx, mc.blockNrOrHash) +func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResult, error) { + state, base, err := sim.b.StateAndHeaderByNumberOrHash(ctx, sim.blockNrOrHash) if state == nil || err != nil { return nil, err } @@ -115,7 +115,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, // or, in case of unmetered gas, setup a context with a timeout. var ( cancel context.CancelFunc - timeout = mc.b.RPCEVMTimeout() + timeout = sim.b.RPCEVMTimeout() blocks = opts.BlockStateCalls ) if timeout > 0 { @@ -126,28 +126,28 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - headers, err := makeHeaders(mc.b.ChainConfig(), blocks, base) + headers, err := makeHeaders(sim.b.ChainConfig(), blocks, base) if err != nil { return nil, err } var ( - results = make([]mcBlockResult, len(blocks)) + results = make([]simBlockResult, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap - gp = new(core.GasPool).AddGas(mc.b.RPCGasCap()) - precompiles = mc.activePrecompiles(ctx, base) + gp = new(core.GasPool).AddGas(sim.b.RPCGasCap()) + precompiles = sim.activePrecompiles(ctx, base) numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 ) // Cache for the block hashes. - mc.hashes = make([]common.Hash, numHashes) + sim.hashes = make([]common.Hash, numHashes) for bi, block := range blocks { header := headers[bi] - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, mc.b), nil) + blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, sim.b), nil) if block.BlockOverrides != nil && block.BlockOverrides.BlobGasPrice != nil { blockContext.BlobBaseFee = block.BlockOverrides.BlobGasPrice.ToInt() } // Respond to BLOCKHASH requests. blockContext.GetHash = func(n uint64) common.Hash { - h, err := mc.getBlockHash(ctx, n, base, headers) + h, err := sim.getBlockHash(ctx, n, base, headers) if err != nil { log.Warn(err.Error()) return common.Hash{} @@ -161,10 +161,10 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, var ( gasUsed uint64 txes = make([]*types.Transaction, len(block.Calls)) - callResults = make([]mcCallResult, len(block.Calls)) + callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) tracer = newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) - config = mc.b.ChainConfig() + config = sim.b.ChainConfig() vmConfig = &vm.Config{ NoBaseFee: true, // Block hash will be repaired after execution. @@ -172,13 +172,15 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, } evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, state, config, *vmConfig) ) + // 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 { // TODO: Pre-estimate nonce and gas // TODO: Move gas fees sanitizing to beginning of func - if err := mc.sanitizeCall(&call, state, &gasUsed, blockContext); err != nil { + if err := sim.sanitizeCall(&call, state, &gasUsed, blockContext); err != nil { return nil, err } tx := call.ToTransaction() @@ -210,7 +212,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, result.Err = newRevertError(result.Revert()) } logs := tracer.Logs() - callRes := mcCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} + 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) { @@ -227,7 +229,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, parentHash common.Hash err error ) - parentHash, err = mc.getBlockHash(ctx, header.Number.Uint64()-1, base, headers) + parentHash, err = sim.getBlockHash(ctx, header.Number.Uint64()-1, base, headers) if err != nil { return nil, err } @@ -242,7 +244,7 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, return results, nil } -func (mc *multicall) sanitizeCall(call *TransactionArgs, state *state.StateDB, gasUsed *uint64, blockContext vm.BlockContext) error { +func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, gasUsed *uint64, blockContext vm.BlockContext) error { if call.Nonce == nil { nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) @@ -260,7 +262,7 @@ func (mc *multicall) sanitizeCall(call *TransactionArgs, state *state.StateDB, g call.Gas = (*hexutil.Uint64)(&remaining) } // TODO: check chainID and against current header for london fees - if err := call.validateAll(mc.b); err != nil { + if err := call.validateAll(sim.b); err != nil { return err } if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { @@ -274,7 +276,7 @@ func (mc *multicall) sanitizeCall(call *TransactionArgs, state *state.StateDB, g // part of the canonical chain, a simulated block or a phantom block. // Note getBlockHash assumes `n` is smaller than the last already simulated block // and smaller than the last block to be simulated. -func (mc *multicall) getBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { +func (sim *simulator) getBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { // getIndex returns the index of the hash in the hashes cache. // The cache potentially includes 255 blocks prior to the base. getIndex := func(n uint64) int { @@ -282,24 +284,24 @@ func (mc *multicall) getBlockHash(ctx context.Context, n uint64, base *types.Hea return int(n - first) } index := getIndex(n) - if h := mc.hashes[index]; h != (common.Hash{}) { + if h := sim.hashes[index]; h != (common.Hash{}) { return h, nil } - h, err := mc.computeBlockHash(ctx, n, base, headers) + h, err := sim.computeBlockHash(ctx, n, base, headers) if err != nil { return common.Hash{}, err } if h != (common.Hash{}) { - mc.hashes[index] = h + sim.hashes[index] = h } return h, nil } -func (mc *multicall) computeBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { +func (sim *simulator) computeBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { if n == base.Number.Uint64() { return base.Hash(), nil } else if n < base.Number.Uint64() { - h, err := mc.b.HeaderByNumber(ctx, rpc.BlockNumber(n)) + h, err := sim.b.HeaderByNumber(ctx, rpc.BlockNumber(n)) if err != nil { return common.Hash{}, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) } @@ -315,7 +317,7 @@ func (mc *multicall) computeBlockHash(ctx context.Context, n uint64, base *types return hash, nil } else if tmp.Number.Uint64() > n { // Phantom block. - lastNonPhantomHash, err := mc.getBlockHash(ctx, h.Number.Uint64(), base, headers) + lastNonPhantomHash, err := sim.getBlockHash(ctx, h.Number.Uint64(), base, headers) if err != nil { return common.Hash{}, err } @@ -331,10 +333,10 @@ func (mc *multicall) computeBlockHash(ctx context.Context, n uint64, base *types return common.Hash{}, errors.New("requested block is in future") } -func (mc *multicall) activePrecompiles(ctx context.Context, base *types.Header) vm.PrecompiledContracts { +func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) vm.PrecompiledContracts { var ( - blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, mc.b), nil) - rules = mc.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, sim.b), nil) + rules = sim.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) ) return vm.ActivePrecompiledContracts(rules).Copy() } @@ -342,7 +344,7 @@ func (mc *multicall) activePrecompiles(ctx context.Context, base *types.Header) // repairLogs updates the block hash in the logs present in a multicall // result object. This is needed as during execution when logs are collected // the block hash is not known. -func repairLogs(results []mcBlockResult, blockHash common.Hash) { +func repairLogs(results []simBlockResult, blockHash common.Hash) { for i := range results { for j := range results[i].Calls { for k := range results[i].Calls[j].Logs { @@ -352,7 +354,7 @@ func repairLogs(results []mcBlockResult, blockHash common.Hash) { } } -func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Header) ([]*types.Header, error) { +func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Header) ([]*types.Header, error) { res := make([]*types.Header, len(blocks)) var ( prevNumber = base.Number.Uint64() From 12d7dd0746ad4f576e8f67adb3131fe7f63d34f6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 17 Jan 2024 16:11:09 +0330 Subject: [PATCH 078/119] fix empty tx, receipts hash --- internal/ethapi/simulate.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index ba7bc34846c0..58f5d8340097 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -205,8 +205,7 @@ func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResu root = state.IntermediateRoot(config.IsEIP158(blockContext.BlockNumber)).Bytes() } gasUsed += result.UsedGas - receipt := core.MakeReceipt(evm, result, state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) - receipts[i] = receipt + receipts[i] = core.MakeReceipt(evm, result, state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) // If the result contains a revert reason, try to unpack it. if len(result.Revert()) > 0 { result.Err = newRevertError(result.Revert()) @@ -236,8 +235,13 @@ func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResu header.ParentHash = parentHash header.Root = state.IntermediateRoot(true) header.GasUsed = gasUsed - header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) - header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) + if len(txes) > 0 { + header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) + } + if len(receipts) > 0 { + header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) + header.Bloom = types.CreateBloom(types.Receipts(receipts)) + } results[bi] = mcBlockResultFromHeader(header, callResults) repairLogs(results, header.Hash()) } @@ -388,10 +392,12 @@ func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Head baseFee = eip1559.CalcBaseFee(config, header) } header = &types.Header{ - UncleHash: types.EmptyUncleHash, - Coinbase: base.Coinbase, - Difficulty: base.Difficulty, - GasLimit: base.GasLimit, + UncleHash: types.EmptyUncleHash, + ReceiptHash: types.EmptyReceiptsHash, + TxHash: types.EmptyTxsHash, + Coinbase: base.Coinbase, + Difficulty: base.Difficulty, + GasLimit: base.GasLimit, //MixDigest: header.MixDigest, BaseFee: baseFee, } From 214e2092949efa3b607ad7459da9a9a5a235ad27 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 17 Jan 2024 16:11:59 +0330 Subject: [PATCH 079/119] minor rename: --- internal/ethapi/simulate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 58f5d8340097..9e40bf3144c5 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -63,7 +63,7 @@ type simBlockResult struct { Calls []simCallResult `json:"calls"` } -func mcBlockResultFromHeader(header *types.Header, callResults []simCallResult) simBlockResult { +func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) simBlockResult { return simBlockResult{ Number: hexutil.Uint64(header.Number.Uint64()), Hash: header.Hash(), @@ -242,7 +242,7 @@ func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResu header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) header.Bloom = types.CreateBloom(types.Receipts(receipts)) } - results[bi] = mcBlockResultFromHeader(header, callResults) + results[bi] = simBlockResultFromHeader(header, callResults) repairLogs(results, header.Hash()) } return results, nil From af155c170298a804aaae8d12ee31a4a347382e40 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 17 Jan 2024 17:15:41 +0330 Subject: [PATCH 080/119] move block processing logic to func --- internal/ethapi/api.go | 8 +- internal/ethapi/simulate.go | 245 ++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 122 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d0cff254d494..db291971a2e9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1254,8 +1254,12 @@ func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrH n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &n } - mc := &simulator{b: s.b, blockNrOrHash: *blockNrOrHash} - return mc.execute(ctx, opts) + state, base, err := s.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + sim := &simulator{b: s.b, state: state, base: base, traceTransfers: opts.TraceTransfers, validate: opts.Validation} + return sim.execute(ctx, opts.BlockStateCalls) } // DoEstimateGas returns the lowest possible gas limit that allows the transaction to run diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 9e40bf3144c5..2043965b1243 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -77,6 +78,17 @@ func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) } } +// 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 (b *simBlockResult) repairLogs() { + for i := range b.Calls { + for j := range b.Calls[i].Logs { + b.Calls[i].Logs[j].BlockHash = b.Hash + } + } +} + type simCallResult struct { ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` @@ -101,22 +113,20 @@ type simOpts struct { } type simulator struct { - blockNrOrHash rpc.BlockNumberOrHash - b Backend - hashes []common.Hash + b Backend + hashes []common.Hash + state *state.StateDB + base *types.Header + traceTransfers bool + validate bool } -func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResult, error) { - state, base, err := sim.b.StateAndHeaderByNumberOrHash(ctx, sim.blockNrOrHash) - if state == nil || err != nil { - return nil, err - } +func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBlockResult, error) { // Setup context so it may be cancelled before the calls completed // or, in case of unmetered gas, setup a context with a timeout. var ( cancel context.CancelFunc timeout = sim.b.RPCEVMTimeout() - blocks = opts.BlockStateCalls ) if timeout > 0 { ctx, cancel = context.WithTimeout(ctx, timeout) @@ -126,7 +136,7 @@ func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResu // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - headers, err := makeHeaders(sim.b.ChainConfig(), blocks, base) + headers, err := makeHeaders(sim.b.ChainConfig(), blocks, sim.base) if err != nil { return nil, err } @@ -134,118 +144,126 @@ func (sim *simulator) execute(ctx context.Context, opts simOpts) ([]simBlockResu results = make([]simBlockResult, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap gp = new(core.GasPool).AddGas(sim.b.RPCGasCap()) - precompiles = sim.activePrecompiles(ctx, base) - numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 + precompiles = sim.activePrecompiles(ctx, sim.base) + numHashes = headers[len(headers)-1].Number.Uint64() - sim.base.Number.Uint64() + 256 ) // Cache for the block hashes. sim.hashes = make([]common.Hash, numHashes) for bi, block := range blocks { - header := headers[bi] - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, sim.b), nil) - if block.BlockOverrides != nil && block.BlockOverrides.BlobGasPrice != nil { - blockContext.BlobBaseFee = block.BlockOverrides.BlobGasPrice.ToInt() + result, err := sim.processBlock(ctx, &block, headers[bi], headers, gp, precompiles, timeout) + if err != nil { + return nil, err } - // Respond to BLOCKHASH requests. - blockContext.GetHash = func(n uint64) common.Hash { - h, err := sim.getBlockHash(ctx, n, base, headers) - if err != nil { - log.Warn(err.Error()) - return common.Hash{} - } - return h + results[bi] = *result + } + return results, nil +} + +func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { + blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, sim.b), nil) + if block.BlockOverrides != nil && block.BlockOverrides.BlobGasPrice != nil { + blockContext.BlobBaseFee = block.BlockOverrides.BlobGasPrice.ToInt() + } + // Respond to BLOCKHASH requests. + blockContext.GetHash = func(n uint64) common.Hash { + h, err := sim.getBlockHash(ctx, n, sim.base, headers) + if err != nil { + log.Warn(err.Error()) + return common.Hash{} } - // State overrides are applied prior to execution of a block - if err := block.StateOverrides.Apply(state, precompiles); err != nil { - return nil, err + return h + } + // State overrides are applied prior to execution of a block + if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil { + return nil, err + } + var ( + gasUsed uint64 + txes = make([]*types.Transaction, len(block.Calls)) + callResults = make([]simCallResult, len(block.Calls)) + receipts = make([]*types.Receipt, len(block.Calls)) + tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) + config = sim.b.ChainConfig() + vmConfig = &vm.Config{ + NoBaseFee: true, + // Block hash will be repaired after execution. + Tracer: tracer, } - var ( - gasUsed uint64 - txes = make([]*types.Transaction, len(block.Calls)) - callResults = make([]simCallResult, len(block.Calls)) - receipts = make([]*types.Receipt, len(block.Calls)) - tracer = newTracer(opts.TraceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) - config = sim.b.ChainConfig() - vmConfig = &vm.Config{ - NoBaseFee: true, - // Block hash will be repaired after execution. - Tracer: tracer, - } - evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, state, config, *vmConfig) - ) - // It is possible to override precompiles with EVM bytecode, or - // move them to another address. - if precompiles != nil { - evm.SetPrecompiles(precompiles) + evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, config, *vmConfig) + ) + // 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 { + // TODO: Pre-estimate nonce and gas + // TODO: Move gas fees sanitizing to beginning of func + if err := sim.sanitizeCall(&call, sim.state, &gasUsed, blockContext); err != nil { + return nil, err } - for i, call := range block.Calls { - // TODO: Pre-estimate nonce and gas - // TODO: Move gas fees sanitizing to beginning of func - if err := sim.sanitizeCall(&call, state, &gasUsed, blockContext); err != nil { - return nil, err - } - tx := call.ToTransaction() - txes[i] = tx + tx := call.ToTransaction() + txes[i] = tx - msg, err := call.ToMessage(gp.Gas(), header.BaseFee, !opts.Validation) - if err != nil { - return nil, err - } - tracer.reset(tx.Hash(), uint(i)) - evm.Reset(core.NewEVMTxContext(msg), state) - result, err := applyMessageWithEVM(ctx, evm, msg, state, timeout, gp) - if err != nil { - txErr := txValidationError(err) - return nil, txErr - } - // Update the state with pending changes. - var root []byte - if config.IsByzantium(blockContext.BlockNumber) { - state.Finalise(true) - } else { - root = state.IntermediateRoot(config.IsEIP158(blockContext.BlockNumber)).Bytes() - } - gasUsed += result.UsedGas - receipts[i] = core.MakeReceipt(evm, result, state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) - // If the result contains a revert reason, try to unpack it. - if len(result.Revert()) > 0 { - result.Err = newRevertError(result.Revert()) - } - 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) { - callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeReverted} - } else { - callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError} - } - } else { - callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) - } - callResults[i] = callRes - } - var ( - parentHash common.Hash - err error - ) - parentHash, err = sim.getBlockHash(ctx, header.Number.Uint64()-1, base, headers) + msg, err := call.ToMessage(gp.Gas(), header.BaseFee, !sim.validate) if err != nil { return nil, err } - header.ParentHash = parentHash - header.Root = state.IntermediateRoot(true) - header.GasUsed = gasUsed - if len(txes) > 0 { - header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) + tracer.reset(tx.Hash(), uint(i)) + evm.Reset(core.NewEVMTxContext(msg), sim.state) + result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, gp) + if err != nil { + txErr := txValidationError(err) + return nil, txErr + } + // Update the state with pending changes. + var root []byte + if config.IsByzantium(blockContext.BlockNumber) { + sim.state.Finalise(true) + } else { + root = sim.state.IntermediateRoot(config.IsEIP158(blockContext.BlockNumber)).Bytes() } - if len(receipts) > 0 { - header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) - header.Bloom = types.CreateBloom(types.Receipts(receipts)) + gasUsed += result.UsedGas + receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) + // If the result contains a revert reason, try to unpack it. + if len(result.Revert()) > 0 { + result.Err = newRevertError(result.Revert()) + } + 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) { + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeReverted} + } else { + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError} + } + } else { + callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) } - results[bi] = simBlockResultFromHeader(header, callResults) - repairLogs(results, header.Hash()) + callResults[i] = callRes } - return results, nil + var ( + parentHash common.Hash + err error + ) + parentHash, err = sim.getBlockHash(ctx, header.Number.Uint64()-1, sim.base, headers) + if err != nil { + return nil, err + } + header.ParentHash = parentHash + header.Root = sim.state.IntermediateRoot(true) + header.GasUsed = gasUsed + if len(txes) > 0 { + header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) + } + if len(receipts) > 0 { + header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) + header.Bloom = types.CreateBloom(types.Receipts(receipts)) + } + result := simBlockResultFromHeader(header, callResults) + result.repairLogs() + return &result, nil } func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, gasUsed *uint64, blockContext vm.BlockContext) error { @@ -345,19 +363,6 @@ func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) return vm.ActivePrecompiledContracts(rules).Copy() } -// repairLogs updates the block hash in the logs present in a multicall -// result object. This is needed as during execution when logs are collected -// the block hash is not known. -func repairLogs(results []simBlockResult, blockHash common.Hash) { - for i := range results { - for j := range results[i].Calls { - for k := range results[i].Calls[j].Logs { - results[i].Calls[j].Logs[k].BlockHash = blockHash - } - } - } -} - func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Header) ([]*types.Header, error) { res := make([]*types.Header, len(blocks)) var ( From 489ef435fb31467b66d05368fbd860ef7c66f98a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 19 Feb 2024 19:42:32 +0100 Subject: [PATCH 081/119] fix lint errs --- internal/ethapi/api_test.go | 4 ++-- internal/ethapi/simulate.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index ecfb189fea2c..b89f4d989d09 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1024,7 +1024,7 @@ func TestSimulateV1(t *testing.T) { coinbase = "0x000000000000000000000000000000000000ffff" genesis = &core.Genesis{ Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{ + Alloc: types.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)}, @@ -1354,7 +1354,7 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{}, - expectErr: &invalidBlockNumberError{message: fmt.Sprintf("block numbers must be in order: 11 <= 12")}, + expectErr: &invalidBlockNumberError{message: "block numbers must be in order: 11 <= 12"}, }, // Test on solidity storage example. Set value in one call, read in next. { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 2043965b1243..388236eb324f 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -330,7 +330,7 @@ func (sim *simulator) computeBlockHash(ctx context.Context, n uint64, base *type return h.Hash(), nil } h := base - for i, _ := range headers { + for i := range headers { tmp := headers[i] // BLOCKHASH will only allow numbers prior to current block // so no need to check that condition. From 0c0b13d22fa20425e058cf091fe96be3ea923b41 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 19 Feb 2024 19:44:54 +0100 Subject: [PATCH 082/119] rename to blobBaseFee --- internal/ethapi/api.go | 6 +++--- internal/ethapi/simulate.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index eea462b18845..13c8ddbe81bf 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1025,7 +1025,7 @@ type BlockOverrides struct { FeeRecipient *common.Address PrevRandao *common.Hash BaseFeePerGas *hexutil.Big - BlobGasPrice *hexutil.Big + BlobBaseFee *hexutil.Big } // Apply overrides the given header fields into the given block context. @@ -1054,8 +1054,8 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if o.BaseFeePerGas != nil { blockCtx.BaseFee = o.BaseFeePerGas.ToInt() } - if o.BlobGasPrice != nil { - blockCtx.BlobBaseFee = o.BlobGasPrice.ToInt() + if o.BlobBaseFee != nil { + blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt() } } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 388236eb324f..7b70d0a95065 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -161,8 +161,8 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, sim.b), nil) - if block.BlockOverrides != nil && block.BlockOverrides.BlobGasPrice != nil { - blockContext.BlobBaseFee = block.BlockOverrides.BlobGasPrice.ToInt() + if block.BlockOverrides != nil && block.BlockOverrides.BlobBaseFee != nil { + blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt() } // Respond to BLOCKHASH requests. blockContext.GetHash = func(n uint64) common.Hash { From 86ce9242a7d367068ed50937810859d0cf766833 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 21 Feb 2024 14:28:35 +0100 Subject: [PATCH 083/119] revert setDefaults changes, skip extra validations: --- internal/ethapi/simulate.go | 3 - internal/ethapi/transaction_args.go | 207 +++++++++++----------------- 2 files changed, 83 insertions(+), 127 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 7b70d0a95065..8c79fa27af52 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -284,9 +284,6 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, call.Gas = (*hexutil.Uint64)(&remaining) } // TODO: check chainID and against current header for london fees - if err := call.validateAll(sim.b); err != nil { - return err - } if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 64508810009d..60c5743919e2 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -95,112 +95,8 @@ func (args *TransactionArgs) data() []byte { return nil } -func (args *TransactionArgs) validateAll(b Backend) error { - if err := args.validate(); err != nil { - return err - } - return args.validateFees(b) -} - -func (args *TransactionArgs) validate() error { - if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { - return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) - } - if args.To == nil && len(args.data()) == 0 { - return errors.New(`contract creation without any data provided`) - } - return nil -} - -func (args *TransactionArgs) validateFees(b Backend) error { - var ( - head = b.CurrentHeader() - config = b.ChainConfig() - ) - // Sanity check the EIP-4844 fee parameters. - if args.BlobFeeCap != nil { - if args.BlobFeeCap.ToInt().Sign() == 0 { - return errors.New("maxFeePerBlobGas must be non-zero") - } - if !config.IsCancun(head.Number, head.Time) { - return errors.New("maxFeePerBlobGas is not valid before Cancun is active") - } - } - // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error. - if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { - return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } - // Sanity check the EIP-1559 fee parameters if present. - if (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) && !config.IsLondon(head.Number) { - return errors.New("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active") - } - if args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil { - if args.MaxFeePerGas.ToInt().Sign() == 0 { - return errors.New("maxFeePerGas must be non-zero") - } - if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { - return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) - } - } else if args.GasPrice != nil { - // Zero gas-price is not allowed after London fork - if args.GasPrice.ToInt().Sign() == 0 && config.IsLondon(head.Number) { - return errors.New("gasPrice must be non-zero after london fork") - } - } - return nil -} - -func (args *TransactionArgs) validateBlobs(b Backend) error { - head := b.CurrentHeader() - if !b.ChainConfig().IsCancun(head.Number, head.Time) && (args.BlobHashes != nil || args.Blobs != nil || args.Commitments != nil || args.Proofs != nil) { - return errors.New("blobs are not valid before Cancun is active") - } - if args.BlobHashes != nil { - if len(args.BlobHashes) == 0 { - return errors.New(`need at least 1 blob for a blob transaction`) - } - if len(args.BlobHashes) > maxBlobsPerTransaction { - return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobsPerTransaction) - } - if args.To == nil { - return errors.New(`missing "to" in blob transaction`) - } - } - // Some methods only accept blob hashes, not the blobs themselves. - if args.Blobs == nil { - if args.Commitments != nil || args.Proofs != nil { - return errors.New(`blob commitments and proofs provided without blobs`) - } - return nil - } - // Assume user provides either only blobs (w/o hashes), or - // blobs together with commitments and proofs. - if args.Commitments == nil && args.Proofs != nil { - return errors.New(`blob proofs provided while commitments were not`) - } else if args.Commitments != nil && args.Proofs == nil { - return errors.New(`blob commitments provided while proofs were not`) - } - - n := len(args.Blobs) - // len(blobs) == len(commitments) == len(proofs) == len(hashes) - if args.Commitments != nil && len(args.Commitments) != n { - return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n) - } - if args.Proofs != nil && len(args.Proofs) != n { - return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n) - } - if args.BlobHashes != nil && len(args.BlobHashes) != n { - return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n) - } - - return nil -} - // setDefaults fills in default values for unspecified tx fields. func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { - if err := args.validate(); err != nil { - return err - } if err := args.setBlobTxSidecar(ctx, b); err != nil { return err } @@ -218,6 +114,28 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { } args.Nonce = (*hexutil.Uint64)(&nonce) } + if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { + return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) + } + + // BlobTx fields + if args.BlobHashes != nil && len(args.BlobHashes) == 0 { + return errors.New(`need at least 1 blob for a blob transaction`) + } + if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobsPerTransaction { + return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobsPerTransaction) + } + + // create check + if args.To == nil { + if args.BlobHashes != nil { + return errors.New(`missing "to" in blob transaction`) + } + if len(args.data()) == 0 { + return errors.New(`contract creation without any data provided`) + } + } + // Estimate the gas usage if necessary. if args.Gas == nil { // These fields are immutable during the estimation, safe to @@ -259,33 +177,54 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { // setFeeDefaults fills in default fee values for unspecified tx fields. func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) error { - if err := args.validateFees(b); err != nil { + head := b.CurrentHeader() + // Sanity check the EIP-4844 fee parameters. + if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 { + return errors.New("maxFeePerBlobGas, if specified, must be non-zero") + } + if err := args.setCancunFeeDefaults(ctx, head, b); err != nil { return err } - head := b.CurrentHeader() - if b.ChainConfig().IsCancun(head.Number, head.Time) { - if err := args.setCancunFeeDefaults(ctx, head, b); err != nil { - return err - } + // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error. + if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { + return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } // If the tx has completely specified a fee mechanism, no default is needed. // This allows users who are not yet synced past London to get defaults for // other tx values. See https://github.com/ethereum/go-ethereum/pull/23274 // for more information. - if args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil { - return nil + eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil + // Sanity check the EIP-1559 fee parameters if present. + if args.GasPrice == nil && eip1559ParamsSet { + if args.MaxFeePerGas.ToInt().Sign() == 0 { + return errors.New("maxFeePerGas must be non-zero") + } + if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { + return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) + } + return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas } - // If gasPrice has been provided, no need to set any defaults. - if args.GasPrice != nil && args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas == nil { - return nil + + // Sanity check the non-EIP-1559 fee parameters. + isLondon := b.ChainConfig().IsLondon(head.Number) + if args.GasPrice != nil && !eip1559ParamsSet { + // Zero gas-price is not allowed after London fork + if args.GasPrice.ToInt().Sign() == 0 && isLondon { + return errors.New("gasPrice must be non-zero after london fork") + } + return nil // No need to set anything, user already set GasPrice } + // Now attempt to fill in default value depending on whether London is active or not. - if b.ChainConfig().IsLondon(head.Number) { + if isLondon { // London is active, set maxPriorityFeePerGas and maxFeePerGas. if err := args.setLondonFeeDefaults(ctx, head, b); err != nil { return err } } else { + if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil { + return errors.New("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active") + } // London not active, set gas price. price, err := b.SuggestGasTipCap(ctx) if err != nil { @@ -300,15 +239,19 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro func (args *TransactionArgs) setCancunFeeDefaults(ctx context.Context, head *types.Header, b Backend) error { // Set maxFeePerBlobGas if it is missing. if args.BlobHashes != nil && args.BlobFeeCap == nil { + var excessBlobGas uint64 + if head.ExcessBlobGas != nil { + excessBlobGas = *head.ExcessBlobGas + } // ExcessBlobGas must be set for a Cancun block. - blobBaseFee := eip4844.CalcBlobFee(*head.ExcessBlobGas) + blobBaseFee := eip4844.CalcBlobFee(excessBlobGas) // Set the max fee to be 2 times larger than the previous block's blob base fee. // The additional slack allows the tx to not become invalidated if the base // fee is rising. val := new(big.Int).Mul(blobBaseFee, big.NewInt(2)) args.BlobFeeCap = (*hexutil.Big)(val) } - return args.setLondonFeeDefaults(ctx, head, b) + return nil } // setLondonFeeDefaults fills in reasonable default fee values for unspecified fields. @@ -341,19 +284,36 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ // setBlobTxSidecar adds the blob tx func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) error { - if err := args.validateBlobs(b); err != nil { - return err - } // No blobs, we're done. if args.Blobs == nil { return nil } + // Passing blobs is not allowed in all contexts, only in specific methods. if !args.blobSidecarAllowed { return errors.New(`"blobs" is not supported for this RPC method`) } + n := len(args.Blobs) - // Either both commitments and proofs are available, or neither. + // Assume user provides either only blobs (w/o hashes), or + // blobs together with commitments and proofs. + if args.Commitments == nil && args.Proofs != nil { + return errors.New(`blob proofs provided while commitments were not`) + } else if args.Commitments != nil && args.Proofs == nil { + return errors.New(`blob commitments provided while proofs were not`) + } + + // len(blobs) == len(commitments) == len(proofs) == len(hashes) + if args.Commitments != nil && len(args.Commitments) != n { + return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n) + } + if args.Proofs != nil && len(args.Proofs) != n { + return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n) + } + if args.BlobHashes != nil && len(args.BlobHashes) != n { + return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n) + } + if args.Commitments == nil { // Generate commitment and proof. commitments := make([]kzg4844.Commitment, n) @@ -379,13 +339,12 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) er } } } - // Compute hashes from commitments. + hashes := make([]common.Hash, n) hasher := sha256.New() for i, c := range args.Commitments { hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c) } - // Generate/validate blob hashes. if args.BlobHashes != nil { for i, h := range hashes { if h != args.BlobHashes[i] { From 4a017ffc1ff8cd92ece93a9f272b6a703a11519c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 21 Feb 2024 14:30:59 +0100 Subject: [PATCH 084/119] rm obsolete test --- internal/ethapi/transaction_args_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 75267ffb8fde..e74b1ef8e160 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -207,20 +207,6 @@ func TestSetFeeDefaults(t *testing.T) { errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"), }, // EIP-4844 - { - "set maxFeePerBlobGas pre cancun", - "london", - &TransactionArgs{BlobFeeCap: fortytwo}, - nil, - errors.New("maxFeePerBlobGas is not valid before Cancun is active"), - }, - { - "set maxFeePerBlobGas pre london", - "legacy", - &TransactionArgs{BlobFeeCap: fortytwo}, - nil, - errors.New("maxFeePerBlobGas is not valid before Cancun is active"), - }, { "set gas price and maxFee for blob transaction", "cancun", From 9fa1556dff57d96aac4fa2da8e9e622435db0ad4 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 5 Mar 2024 16:41:23 +0100 Subject: [PATCH 085/119] fix makeHeaders using unpopulated parent --- internal/ethapi/simulate.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 8c79fa27af52..195216d71f16 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -393,7 +393,7 @@ func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Head if config.IsLondon(overrides.Number.ToInt()) { baseFee = eip1559.CalcBaseFee(config, header) } - header = &types.Header{ + header = overrides.MakeHeader(&types.Header{ UncleHash: types.EmptyUncleHash, ReceiptHash: types.EmptyReceiptsHash, TxHash: types.EmptyTxsHash, @@ -402,8 +402,8 @@ func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Head GasLimit: base.GasLimit, //MixDigest: header.MixDigest, BaseFee: baseFee, - } - res[bi] = overrides.MakeHeader(header) + }) + res[bi] = header } return res, nil } From 7177f222224f981353ba905c9ba7179fc631537a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 5 Mar 2024 17:36:38 +0100 Subject: [PATCH 086/119] check gas fee in validation mode --- internal/ethapi/simulate.go | 2 +- internal/web3ext/web3ext.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 195216d71f16..297d7d051395 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -185,7 +185,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) config = sim.b.ChainConfig() vmConfig = &vm.Config{ - NoBaseFee: true, + NoBaseFee: !sim.validate, // Block hash will be repaired after execution. Tracer: tracer, } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 76b71989e3e9..5da733ee5d54 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -620,8 +620,8 @@ web3._extend({ new web3._extend.Method({ name: 'simulateV1', call: 'eth_simulateV1', - params: 3, - inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter, null], + params: 2, + inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter], }), new web3._extend.Method({ name: 'getBlockReceipts', From 3daabda23fe0e0a88b3cdfc9a17f61f789888cf9 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 29 Mar 2024 13:36:45 +0100 Subject: [PATCH 087/119] add empty withdrawals obj --- internal/ethapi/simulate.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 297d7d051395..cb5e3529ab03 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -53,15 +53,16 @@ type simBlock struct { } type simBlockResult struct { - Number hexutil.Uint64 `json:"number"` - Hash common.Hash `json:"hash"` - Time hexutil.Uint64 `json:"timestamp"` - GasLimit hexutil.Uint64 `json:"gasLimit"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - FeeRecipient common.Address `json:"feeRecipient"` - BaseFee *hexutil.Big `json:"baseFeePerGas"` - PrevRandao common.Hash `json:"prevRandao"` - Calls []simCallResult `json:"calls"` + Number hexutil.Uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Time hexutil.Uint64 `json:"timestamp"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"feeRecipient"` + BaseFee *hexutil.Big `json:"baseFeePerGas"` + PrevRandao common.Hash `json:"prevRandao"` + Withdrawals types.Withdrawals `json:"withdrawals"` + Calls []simCallResult `json:"calls"` } func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) simBlockResult { @@ -74,7 +75,9 @@ func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) FeeRecipient: header.Coinbase, BaseFee: (*hexutil.Big)(header.BaseFee), PrevRandao: header.MixDigest, - Calls: callResults, + // Withdrawals will be always empty in the context of a simulated block. + Withdrawals: make(types.Withdrawals, 0), + Calls: callResults, } } From 81e7fe13ea24298696237ca34d9d2ca3da2950f4 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 5 Apr 2024 19:37:56 +0200 Subject: [PATCH 088/119] fix baseFee, add blobGasUsed and excessBlobGas to result --- internal/ethapi/simulate.go | 68 ++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index b4119d1a5407..e8ee10cc0a7b 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "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" @@ -53,20 +54,22 @@ type simBlock struct { } type simBlockResult struct { - Number hexutil.Uint64 `json:"number"` - Hash common.Hash `json:"hash"` - Time hexutil.Uint64 `json:"timestamp"` - GasLimit hexutil.Uint64 `json:"gasLimit"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - FeeRecipient common.Address `json:"feeRecipient"` - BaseFee *hexutil.Big `json:"baseFeePerGas"` - PrevRandao common.Hash `json:"prevRandao"` - Withdrawals types.Withdrawals `json:"withdrawals"` - Calls []simCallResult `json:"calls"` + Number hexutil.Uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Time hexutil.Uint64 `json:"timestamp"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"feeRecipient"` + BaseFee *hexutil.Big `json:"baseFeePerGas"` + PrevRandao common.Hash `json:"prevRandao"` + Withdrawals types.Withdrawals `json:"withdrawals"` + BlobGasUsed hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas hexutil.Uint64 `json:"excessBlobGas"` + Calls []simCallResult `json:"calls"` } func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) simBlockResult { - return simBlockResult{ + r := simBlockResult{ Number: hexutil.Uint64(header.Number.Uint64()), Hash: header.Hash(), Time: hexutil.Uint64(header.Time), @@ -79,6 +82,11 @@ func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) Withdrawals: make(types.Withdrawals, 0), Calls: callResults, } + if header.BlobGasUsed != nil && header.ExcessBlobGas != nil { + r.BlobGasUsed = hexutil.Uint64(*header.BlobGasUsed) + r.ExcessBlobGas = hexutil.Uint64(*header.ExcessBlobGas) + } + return r } // repairLogs updates the block hash in the logs present in the result of @@ -153,7 +161,7 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc // Cache for the block hashes. sim.hashes = make([]common.Hash, numHashes) for bi, block := range blocks { - result, err := sim.processBlock(ctx, &block, headers[bi], headers, gp, precompiles, timeout) + result, err := sim.processBlock(ctx, &block, headers[bi], headers, bi, gp, precompiles, timeout) if err != nil { return nil, err } @@ -162,7 +170,7 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc return results, nil } -func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { +func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header *types.Header, headers []*types.Header, blockIndex int, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, sim.b), nil) if block.BlockOverrides != nil && block.BlockOverrides.BlobBaseFee != nil { blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt() @@ -181,19 +189,23 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header return nil, err } var ( - gasUsed uint64 - txes = make([]*types.Transaction, len(block.Calls)) - callResults = make([]simCallResult, len(block.Calls)) - receipts = make([]*types.Receipt, len(block.Calls)) - tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) - config = sim.b.ChainConfig() - vmConfig = &vm.Config{ + gasUsed, blobGasUsed uint64 + txes = make([]*types.Transaction, len(block.Calls)) + callResults = make([]simCallResult, len(block.Calls)) + receipts = make([]*types.Receipt, len(block.Calls)) + tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) + config = sim.b.ChainConfig() + parent = sim.base + vmConfig = &vm.Config{ NoBaseFee: !sim.validate, // Block hash will be repaired after execution. Tracer: tracer.Hooks(), } evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, config, *vmConfig) ) + if blockIndex > 0 { + parent = headers[blockIndex-1] + } sim.state.SetLogger(tracer.Hooks()) // It is possible to override precompiles with EVM bytecode, or // move them to another address. @@ -229,6 +241,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header } gasUsed += result.UsedGas receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) + blobGasUsed += receipts[i].BlobGasUsed // If the result contains a revert reason, try to unpack it. if len(result.Revert()) > 0 { result.Err = newRevertError(result.Revert()) @@ -265,6 +278,21 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) header.Bloom = types.CreateBloom(types.Receipts(receipts)) } + if config.IsLondon(header.Number) { + // BaseFee depends on parent's gasUsed, hence can't be pre-computed. + // Phantom blocks are ignored. + header.BaseFee = eip1559.CalcBaseFee(config, parent) + } + if config.IsCancun(header.Number, header.Time) { + header.BlobGasUsed = &blobGasUsed + var excess uint64 + if config.IsCancun(parent.Number, parent.Time) { + excess = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) + } else { + excess = eip4844.CalcExcessBlobGas(0, 0) + } + header.ExcessBlobGas = &excess + } result := simBlockResultFromHeader(header, callResults) result.repairLogs() return &result, nil From 8698221faefd247b0dfde8974575854f9dc8f068 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 6 May 2024 16:06:16 +0200 Subject: [PATCH 089/119] default timestamp bump to 12 --- internal/ethapi/simulate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index e8ee10cc0a7b..44d2d05314b5 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -414,7 +414,7 @@ func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Head prevNumber = overrides.Number.ToInt().Uint64() if overrides.Time == nil { - t := prevTimestamp + 1 + t := prevTimestamp + 12 overrides.Time = (*hexutil.Uint64)(&t) } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", *time, prevTimestamp)} From 48ca188157809a32bc213d598b8cfc91eba6aa0a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 7 May 2024 20:02:22 +0200 Subject: [PATCH 090/119] generate empty blocks for gaps --- internal/ethapi/simulate.go | 73 ++++++++++++++++++++++++------- internal/ethapi/simulate_test.go | 74 ++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 internal/ethapi/simulate_test.go diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 44d2d05314b5..60680998ef50 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -34,7 +34,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" @@ -147,7 +146,13 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc // Make sure the context is cancelled when the call has completed // this makes sure resources are cleaned up. defer cancel() - headers, err := makeHeaders(sim.b.ChainConfig(), blocks, sim.base) + + var err error + blocks, err = sim.sanitizeBlockOrder(blocks) + if err != nil { + return nil, err + } + headers, err := sim.makeHeaders(blocks) if err != nil { return nil, err } @@ -392,27 +397,63 @@ func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) return vm.ActivePrecompiledContracts(rules).Copy() } -func makeHeaders(config *params.ChainConfig, blocks []simBlock, base *types.Header) ([]*types.Header, error) { +// sanitizeBlockOrder iterates the blocks checking that block numbers +// are strictly increasing. When necessary it will generate empty blocks. +// It modifies the block's override object. +func (sim *simulator) sanitizeBlockOrder(blocks []simBlock) ([]simBlock, error) { + var ( + res = make([]simBlock, 0, len(blocks)) + base = sim.base + prevNumber = base.Number + ) + 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))) + b := simBlock{BlockOverrides: &BlockOverrides{Number: (*hexutil.Big)(n)}} + res = append(res, b) + } + } + // Only append block after filling a potential gap. + prevNumber = block.BlockOverrides.Number.ToInt() + 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) { res := make([]*types.Header, len(blocks)) var ( - prevNumber = base.Number.Uint64() + config = sim.b.ChainConfig() + base = sim.base prevTimestamp = base.Time header = base ) for bi, block := range blocks { - overrides := new(BlockOverrides) - if block.BlockOverrides != nil { - overrides = block.BlockOverrides + if block.BlockOverrides == nil || block.BlockOverrides.Number == nil { + return nil, errors.New("empty block number") } - // Sanitize block number and timestamp - if overrides.Number == nil { - n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) - overrides.Number = (*hexutil.Big)(n) - } else if overrides.Number.ToInt().Uint64() <= prevNumber { - return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", overrides.Number.ToInt().Uint64(), prevNumber)} - } - prevNumber = overrides.Number.ToInt().Uint64() - + overrides := block.BlockOverrides if overrides.Time == nil { t := prevTimestamp + 12 overrides.Time = (*hexutil.Uint64)(&t) diff --git a/internal/ethapi/simulate_test.go b/internal/ethapi/simulate_test.go new file mode 100644 index 000000000000..34cbb3ba3824 --- /dev/null +++ b/internal/ethapi/simulate_test.go @@ -0,0 +1,74 @@ +package ethapi + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestSimulateSanitizeBlockOrder(t *testing.T) { + for i, tc := range []struct { + baseNumber int + blocks []simBlock + expectedLen int + err string + }{ + { + baseNumber: 10, + blocks: []simBlock{{}, {}, {}}, + expectedLen: 3, + }, + { + baseNumber: 10, + blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(13)}}, {}}, + expectedLen: 4, + }, + { + baseNumber: 10, + blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(11)}}, {BlockOverrides: &BlockOverrides{Number: newInt(14)}}, {}}, + expectedLen: 5, + }, + { + baseNumber: 10, + blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(13)}}, {BlockOverrides: &BlockOverrides{Number: newInt(12)}}}, + err: "block numbers must be in order: 12 <= 13", + }, + } { + sim := &simulator{base: &types.Header{Number: big.NewInt(int64(tc.baseNumber))}} + res, err := sim.sanitizeBlockOrder(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) != tc.expectedLen { + fmt.Printf("res: %v\n", res) + t.Errorf("testcase %d: mismatch number of blocks. Want %d, have %d", i, tc.expectedLen, 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) + } + want := tc.baseNumber + bi + 1 + have := b.BlockOverrides.Number.ToInt().Uint64() + if uint64(want) != have { + t.Errorf("testcase %d: block number mismatch. Want %d, have %d", i, want, have) + } + } + } +} + +func newInt(n int64) *hexutil.Big { + return (*hexutil.Big)(big.NewInt(n)) +} From 81e065cd20f02ef410920db4c01816f620500864 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 7 May 2024 21:23:58 +0200 Subject: [PATCH 091/119] fix block hashes --- internal/ethapi/api_test.go | 26 +++++- internal/ethapi/simulate.go | 159 +++++++++++++----------------------- 2 files changed, 81 insertions(+), 104 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 4019307eea83..34afce0e1c8e 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1312,7 +1312,7 @@ func TestSimulateV1(t *testing.T) { Number: "0xc", GasLimit: "0x47e7c4", GasUsed: "0xe891", - FeeRecipient: coinbase, + FeeRecipient: strings.ToLower(cac.String()), Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", @@ -1806,6 +1806,12 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + FeeRecipient: coinbase, + Calls: []callRes{}, + }, { Number: "0xc", GasLimit: "0x47e7c4", GasUsed: "0xf864", @@ -1827,6 +1833,24 @@ func TestSimulateV1(t *testing.T) { Logs: []log{}, Status: "0x1", }}, + }, { + Number: "0xd", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + FeeRecipient: coinbase, + Calls: []callRes{}, + }, { + Number: "0xe", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + FeeRecipient: coinbase, + Calls: []callRes{}, + }, { + Number: "0xf", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + FeeRecipient: coinbase, + Calls: []callRes{}, }, { Number: "0x10", GasLimit: "0x47e7c4", diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 60680998ef50..7b06162fd3dc 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -26,15 +26,13 @@ import ( "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/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" ) @@ -162,33 +160,28 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc gp = new(core.GasPool).AddGas(sim.b.RPCGasCap()) precompiles = sim.activePrecompiles(ctx, sim.base) numHashes = headers[len(headers)-1].Number.Uint64() - sim.base.Number.Uint64() + 256 + parent = sim.base ) // Cache for the block hashes. sim.hashes = make([]common.Hash, numHashes) for bi, block := range blocks { - result, err := sim.processBlock(ctx, &block, headers[bi], headers, bi, gp, precompiles, timeout) + result, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], gp, precompiles, timeout) if err != nil { return nil, err } results[bi] = *result + parent = headers[bi] } return results, nil } -func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header *types.Header, headers []*types.Header, blockIndex int, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { - blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, sim.b), nil) - if block.BlockOverrides != nil && block.BlockOverrides.BlobBaseFee != nil { +func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { + // Set this here for evm.GetHashFn to work. + header.ParentHash = parent.Hash() + blockContext := core.NewEVMBlockContext(header, sim.newSimulatedChainContext(ctx, headers), nil) + if block.BlockOverrides.BlobBaseFee != nil { blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt() } - // Respond to BLOCKHASH requests. - blockContext.GetHash = func(n uint64) common.Hash { - h, err := sim.getBlockHash(ctx, n, sim.base, headers) - if err != nil { - log.Warn(err.Error()) - return common.Hash{} - } - return h - } // State overrides are applied prior to execution of a block if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil { return nil, err @@ -200,7 +193,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header receipts = make([]*types.Receipt, len(block.Calls)) tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) config = sim.b.ChainConfig() - parent = sim.base vmConfig = &vm.Config{ NoBaseFee: !sim.validate, // Block hash will be repaired after execution. @@ -208,9 +200,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header } evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, config, *vmConfig) ) - if blockIndex > 0 { - parent = headers[blockIndex-1] - } sim.state.SetLogger(tracer.Hooks()) // It is possible to override precompiles with EVM bytecode, or // move them to another address. @@ -265,15 +254,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header } callResults[i] = callRes } - var ( - parentHash common.Hash - err error - ) - parentHash, err = sim.getBlockHash(ctx, header.Number.Uint64()-1, sim.base, headers) - if err != nil { - return nil, err - } - header.ParentHash = parentHash header.Root = sim.state.IntermediateRoot(true) header.GasUsed = gasUsed if len(txes) > 0 { @@ -285,7 +265,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header } if config.IsLondon(header.Number) { // BaseFee depends on parent's gasUsed, hence can't be pre-computed. - // Phantom blocks are ignored. header.BaseFee = eip1559.CalcBaseFee(config, parent) } if config.IsCancun(header.Number, header.Time) { @@ -328,67 +307,6 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, return nil } -// getBlockHash returns the hash for the block of the given number. Block can be -// part of the canonical chain, a simulated block or a phantom block. -// Note getBlockHash assumes `n` is smaller than the last already simulated block -// and smaller than the last block to be simulated. -func (sim *simulator) getBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { - // getIndex returns the index of the hash in the hashes cache. - // The cache potentially includes 255 blocks prior to the base. - getIndex := func(n uint64) int { - first := base.Number.Uint64() - 255 - return int(n - first) - } - index := getIndex(n) - if h := sim.hashes[index]; h != (common.Hash{}) { - return h, nil - } - h, err := sim.computeBlockHash(ctx, n, base, headers) - if err != nil { - return common.Hash{}, err - } - if h != (common.Hash{}) { - sim.hashes[index] = h - } - return h, nil -} - -func (sim *simulator) computeBlockHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header) (common.Hash, error) { - if n == base.Number.Uint64() { - return base.Hash(), nil - } else if n < base.Number.Uint64() { - h, err := sim.b.HeaderByNumber(ctx, rpc.BlockNumber(n)) - if err != nil { - return common.Hash{}, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) - } - return h.Hash(), nil - } - h := base - for i := range headers { - tmp := headers[i] - // BLOCKHASH will only allow numbers prior to current block - // so no need to check that condition. - if tmp.Number.Uint64() == n { - hash := tmp.Hash() - return hash, nil - } else if tmp.Number.Uint64() > n { - // Phantom block. - lastNonPhantomHash, err := sim.getBlockHash(ctx, h.Number.Uint64(), base, headers) - if err != nil { - return common.Hash{}, err - } - // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) - hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) - if err != nil { - return common.Hash{}, err - } - return crypto.Keccak256Hash(hashData), nil - } - h = tmp - } - return common.Hash{}, errors.New("requested block is in future") -} - func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) vm.PrecompiledContracts { var ( blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, sim.b), nil) @@ -442,8 +360,8 @@ func (sim *simulator) sanitizeBlockOrder(blocks []simBlock) ([]simBlock, error) // 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) { - res := make([]*types.Header, len(blocks)) var ( + res = make([]*types.Header, len(blocks)) config = sim.b.ChainConfig() base = sim.base prevTimestamp = base.Time @@ -462,21 +380,56 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { } prevTimestamp = uint64(*overrides.Time) - var baseFee *big.Int - if config.IsLondon(overrides.Number.ToInt()) { - baseFee = eip1559.CalcBaseFee(config, header) + var withdrawalsHash *common.Hash + if config.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + withdrawalsHash = &types.EmptyWithdrawalsHash + } + var parentBeaconRoot *common.Hash + if config.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: base.Coinbase, - Difficulty: base.Difficulty, - GasLimit: base.GasLimit, - //MixDigest: header.MixDigest, - BaseFee: baseFee, + 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") +} From 263cfd76e384bff3418c5a4f551e903ed3a4d897 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 22 May 2024 17:34:24 +0200 Subject: [PATCH 092/119] compute basefee prior to execution --- internal/ethapi/api_test.go | 32 ++++++++++++++++++++++++++++++++ internal/ethapi/simulate.go | 31 ++++++++++++++++++------------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 34afce0e1c8e..12edaf6dc898 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1693,6 +1693,38 @@ func TestSimulateV1(t *testing.T) { want: nil, expectErr: &invalidTxError{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, }, + // Successful validation + { + name: "validation-checks-success", + tag: latest, + blocks: []simBlock{{ + BlockOverrides: &BlockOverrides{ + BaseFeePerGas: (*hexutil.Big)(big.NewInt(1)), + }, + StateOverrides: &StateOverride{ + randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(10000000))}, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + MaxFeePerGas: (*hexutil.Big)(big.NewInt(2)), + }}, + }}, + validation: &validation, + want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x5208", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x5208", + Logs: []log{}, + Status: "0x1", + }}, + }}, + }, // Clear storage. { name: "clear-storage", diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 7b06162fd3dc..0850badc3e68 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -176,8 +176,25 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc } func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { - // Set this here for evm.GetHashFn to work. + // Set header fields that depend only on parent block. + config := sim.b.ChainConfig() + // Parent hash is needed for evm.GetHashFn to work. header.ParentHash = parent.Hash() + if config.IsLondon(header.Number) { + // Base fee could have been overridden. + if header.BaseFee == nil { + header.BaseFee = eip1559.CalcBaseFee(config, parent) + } + } + if config.IsCancun(header.Number, header.Time) { + var excess uint64 + if config.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() @@ -192,7 +209,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) - config = sim.b.ChainConfig() vmConfig = &vm.Config{ NoBaseFee: !sim.validate, // Block hash will be repaired after execution. @@ -263,19 +279,8 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) header.Bloom = types.CreateBloom(types.Receipts(receipts)) } - if config.IsLondon(header.Number) { - // BaseFee depends on parent's gasUsed, hence can't be pre-computed. - header.BaseFee = eip1559.CalcBaseFee(config, parent) - } if config.IsCancun(header.Number, header.Time) { header.BlobGasUsed = &blobGasUsed - var excess uint64 - if config.IsCancun(parent.Number, parent.Time) { - excess = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) - } else { - excess = eip4844.CalcExcessBlobGas(0, 0) - } - header.ExcessBlobGas = &excess } result := simBlockResultFromHeader(header, callResults) result.repairLogs() From e3827828fda9c36d8359911229c70cfdf82659fb Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 28 May 2024 21:45:43 +0200 Subject: [PATCH 093/119] return full header --- internal/ethapi/api.go | 2 +- internal/ethapi/api_test.go | 194 ++++++++++++++++++------------------ internal/ethapi/simulate.go | 86 +++++----------- 3 files changed, 123 insertions(+), 159 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2daca2a6c56f..0a3b31a3dc9e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1227,7 +1227,7 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]simBlockResult, error) { +func (s *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 { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 12edaf6dc898..b18c238e300e 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1108,11 +1108,11 @@ func TestSimulateV1(t *testing.T) { Number string //Hash string // Ignore timestamp - GasLimit string - GasUsed string - FeeRecipient string - BaseFee string - Calls []callRes + GasLimit string + GasUsed string + Miner string + BaseFee string + Calls []callRes } var testSuite = []struct { name string @@ -1146,10 +1146,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xf618", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xf618", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1199,10 +1199,10 @@ func TestSimulateV1(t *testing.T) { }, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xa410", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xa410", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1215,10 +1215,10 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0x5208", - FeeRecipient: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0x5208", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1253,10 +1253,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x47e7c4", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x47e7c4", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", Error: callErr{Message: "stack underflow (0 <=> 2)", Code: errCodeVMError}, @@ -1298,10 +1298,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xe891", - FeeRecipient: strings.ToLower(cac.String()), + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xe891", + Miner: strings.ToLower(cac.String()), Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", @@ -1309,10 +1309,10 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xe891", - FeeRecipient: strings.ToLower(cac.String()), + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xe891", + Miner: strings.ToLower(cac.String()), Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", @@ -1377,10 +1377,10 @@ func TestSimulateV1(t *testing.T) { }, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x10683", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x10683", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xaacc", @@ -1418,10 +1418,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x5508", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x5508", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", Logs: []log{{ @@ -1486,10 +1486,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x52f6", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x52f6", + Miner: coinbase, Calls: []callRes{{ // Caller is in this case the contract that invokes ecrecover. ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), @@ -1536,10 +1536,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xa58c", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xa58c", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0xec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5", GasUsed: "0x52dc", @@ -1584,10 +1584,10 @@ func TestSimulateV1(t *testing.T) { }}, includeTransfers: &includeTransfers, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xd984", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xd984", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd984", @@ -1650,10 +1650,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x1b83f", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x1b83f", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd166", @@ -1666,10 +1666,10 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xe6d9", - FeeRecipient: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xe6d9", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xe6d9", @@ -1713,10 +1713,10 @@ func TestSimulateV1(t *testing.T) { }}, validation: &validation, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x5208", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x5208", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1765,10 +1765,10 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xc542", - FeeRecipient: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xc542", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x0000000000000000000000000000000200000000000000000000000000000003", GasUsed: "0x62a1", @@ -1781,10 +1781,10 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0x62a1", - FeeRecipient: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0x62a1", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x0000000000000000000000000000000500000000000000000000000000000000", GasUsed: "0x62a1", @@ -1838,16 +1838,16 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - FeeRecipient: coinbase, - Calls: []callRes{}, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + Calls: []callRes{}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xf864", - FeeRecipient: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xf864", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x52cc", @@ -1866,28 +1866,28 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xd", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - FeeRecipient: coinbase, - Calls: []callRes{}, + Number: "0xd", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + Calls: []callRes{}, }, { - Number: "0xe", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - FeeRecipient: coinbase, - Calls: []callRes{}, + Number: "0xe", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + Calls: []callRes{}, }, { - Number: "0xf", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - FeeRecipient: coinbase, - Calls: []callRes{}, + Number: "0xf", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + Calls: []callRes{}, }, { - Number: "0x10", - GasLimit: "0x47e7c4", - GasUsed: "0xa598", - FeeRecipient: coinbase, + Number: "0x10", + GasLimit: "0x47e7c4", + GasUsed: "0xa598", + Miner: coinbase, Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x52cc", diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 0850badc3e68..c0811399eaae 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -50,53 +50,6 @@ type simBlock struct { Calls []TransactionArgs } -type simBlockResult struct { - Number hexutil.Uint64 `json:"number"` - Hash common.Hash `json:"hash"` - Time hexutil.Uint64 `json:"timestamp"` - GasLimit hexutil.Uint64 `json:"gasLimit"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - FeeRecipient common.Address `json:"feeRecipient"` - BaseFee *hexutil.Big `json:"baseFeePerGas"` - PrevRandao common.Hash `json:"prevRandao"` - Withdrawals types.Withdrawals `json:"withdrawals"` - BlobGasUsed hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas hexutil.Uint64 `json:"excessBlobGas"` - Calls []simCallResult `json:"calls"` -} - -func simBlockResultFromHeader(header *types.Header, callResults []simCallResult) simBlockResult { - r := simBlockResult{ - Number: hexutil.Uint64(header.Number.Uint64()), - Hash: header.Hash(), - Time: hexutil.Uint64(header.Time), - GasLimit: hexutil.Uint64(header.GasLimit), - GasUsed: hexutil.Uint64(header.GasUsed), - FeeRecipient: header.Coinbase, - BaseFee: (*hexutil.Big)(header.BaseFee), - PrevRandao: header.MixDigest, - // Withdrawals will be always empty in the context of a simulated block. - Withdrawals: make(types.Withdrawals, 0), - Calls: callResults, - } - if header.BlobGasUsed != nil && header.ExcessBlobGas != nil { - r.BlobGasUsed = hexutil.Uint64(*header.BlobGasUsed) - r.ExcessBlobGas = hexutil.Uint64(*header.ExcessBlobGas) - } - return r -} - -// 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 (b *simBlockResult) repairLogs() { - for i := range b.Calls { - for j := range b.Calls[i].Logs { - b.Calls[i].Logs[j].BlockHash = b.Hash - } - } -} - type simCallResult struct { ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` @@ -129,7 +82,7 @@ type simulator struct { validate bool } -func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBlockResult, error) { +func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[string]interface{}, error) { // Setup context so it may be cancelled before the calls completed // or, in case of unmetered gas, setup a context with a timeout. var ( @@ -155,7 +108,7 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc return nil, err } var ( - results = make([]simBlockResult, len(blocks)) + results = make([]map[string]interface{}, len(blocks)) // Each tx and all the series of txes shouldn't consume more gas than cap gp = new(core.GasPool).AddGas(sim.b.RPCGasCap()) precompiles = sim.activePrecompiles(ctx, sim.base) @@ -169,13 +122,13 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]simBloc if err != nil { return nil, err } - results[bi] = *result + results[bi] = result parent = headers[bi] } return results, nil } -func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (*simBlockResult, error) { +func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (map[string]interface{}, error) { // Set header fields that depend only on parent block. config := sim.b.ChainConfig() // Parent hash is needed for evm.GetHashFn to work. @@ -272,19 +225,30 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } header.Root = sim.state.IntermediateRoot(true) header.GasUsed = gasUsed - if len(txes) > 0 { - header.TxHash = types.DeriveSha(types.Transactions(txes), trie.NewStackTrie(nil)) - } - if len(receipts) > 0 { - header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) - header.Bloom = types.CreateBloom(types.Receipts(receipts)) - } if config.IsCancun(header.Number, header.Time) { header.BlobGasUsed = &blobGasUsed } - result := simBlockResultFromHeader(header, callResults) - result.repairLogs() - return &result, nil + var withdrawals types.Withdrawals + if config.IsShanghai(header.Number, header.Time) { + withdrawals = make([]*types.Withdrawal, 0) + } + b := types.NewBlockWithWithdrawals(header, txes, nil, receipts, withdrawals, trie.NewStackTrie(nil)) + res := RPCMarshalBlock(b, true, false, config) + repairLogs(callResults, res["hash"].(common.Hash)) + res["calls"] = callResults + + return res, 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, gasUsed *uint64, blockContext vm.BlockContext) error { From 9139685944e1a939ea313f9485e5aecdba45e156 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 28 May 2024 21:50:26 +0200 Subject: [PATCH 094/119] add fullTx option --- internal/ethapi/api.go | 2 +- internal/ethapi/simulate.go | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0a3b31a3dc9e..0ce46989fda4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1241,7 +1241,7 @@ func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrH if state == nil || err != nil { return nil, err } - sim := &simulator{b: s.b, state: state, base: base, traceTransfers: opts.TraceTransfers, validate: opts.Validation} + sim := &simulator{b: s.b, state: state, base: base, traceTransfers: opts.TraceTransfers, validate: opts.Validation, fullTx: opts.ReturnFullTransactions} return sim.execute(ctx, opts.BlockStateCalls) } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index c0811399eaae..79178e82229e 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -68,9 +68,10 @@ func (r *simCallResult) MarshalJSON() ([]byte, error) { } type simOpts struct { - BlockStateCalls []simBlock - TraceTransfers bool - Validation bool + BlockStateCalls []simBlock + TraceTransfers bool + Validation bool + ReturnFullTransactions bool } type simulator struct { @@ -80,6 +81,7 @@ type simulator struct { base *types.Header traceTransfers bool validate bool + fullTx bool } func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[string]interface{}, error) { @@ -233,7 +235,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, withdrawals = make([]*types.Withdrawal, 0) } b := types.NewBlockWithWithdrawals(header, txes, nil, receipts, withdrawals, trie.NewStackTrie(nil)) - res := RPCMarshalBlock(b, true, false, config) + res := RPCMarshalBlock(b, true, sim.fullTx, config) repairLogs(callResults, res["hash"].(common.Hash)) res["calls"] = callResults From 569a2d7f55401d387ed689745bbc784b4b32dac3 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 6 Jun 2024 19:01:19 +0200 Subject: [PATCH 095/119] move out base fee lowering to api code --- core/vm/evm.go | 11 ----------- eth/gasestimator/gasestimator.go | 10 +++++++++- internal/ethapi/api.go | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index c0e2ec440ead..13d43730c4c9 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -117,17 +117,6 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. 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{ Context: blockCtx, TxContext: txCtx, diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index f07f98956e3c..40f19299bcc4 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -211,8 +211,16 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil) 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 // a dangling goroutine until the outer estimation finishes, create an internal // context for the lifetime of this method call. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0ce46989fda4..fb4fac9af582 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1156,6 +1156,14 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s return nil, err } msg := args.ToMessage(header.BaseFee, skipChecks) + // 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) @@ -1606,6 +1614,14 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true} 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)) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(false).Hash(), err) From d7cd3a0b6fbaca00385e4df8b8f713e0f1395404 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 6 Jun 2024 19:01:35 +0200 Subject: [PATCH 096/119] missed one file --- eth/tracers/api.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 86ee254ef02b..cf9f470eefd2 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -935,6 +935,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc tx = args.ToTransaction(args.GasPrice == nil) 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 { traceConfig = &config.TraceConfig } From 399812ad372e86b145efe32300d6542ae8c7aa99 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 6 Jun 2024 19:03:35 +0200 Subject: [PATCH 097/119] set base fee to 0 in non-validating mode --- internal/ethapi/api_test.go | 89 +++++++++++++++++++++++++++++++++++++ internal/ethapi/simulate.go | 8 +++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index b18c238e300e..ffd8d9c63f14 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1902,6 +1902,95 @@ func TestSimulateV1(t *testing.T) { }}, }}, }, + { + name: "basefee-non-validation", + tag: latest, + blocks: []simBlock{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: { + Code: hex2Bytes("3a489060005260205260406000f3"), + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // 0 gas price + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // non-zero gas price + MaxPriorityFeePerGas: newInt(1), + MaxFeePerGas: newInt(2), + }, + }, + }, { + BlockOverrides: &BlockOverrides{ + BaseFeePerGas: (*hexutil.Big)(big.NewInt(1)), + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // 0 gas price + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // non-zero gas price + MaxPriorityFeePerGas: newInt(1), + MaxFeePerGas: newInt(2), + }, + }, + }, { + // Base fee should be 0 to zero even if it was set in previous block. + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + }}, + }}, + want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xa44e", + Miner: coinbase, + Calls: []callRes{{ + ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + GasUsed: "0x5227", + Logs: []log{}, + Status: "0x1", + }, { + ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", + GasUsed: "0x5227", + Logs: []log{}, + Status: "0x1", + }}, + }, { + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xa44e", + Miner: coinbase, + Calls: []callRes{{ + ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + GasUsed: "0x5227", + Logs: []log{}, + Status: "0x1", + }, { + ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001", + GasUsed: "0x5227", + Logs: []log{}, + Status: "0x1", + }}, + }, { + Number: "0xd", + GasLimit: "0x47e7c4", + GasUsed: "0x5227", + Miner: coinbase, + Calls: []callRes{{ + ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + GasUsed: "0x5227", + Logs: []log{}, + Status: "0x1", + }}, + }}, + }, } for _, tc := range testSuite { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 79178e82229e..f6c41a8654c2 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -136,9 +136,15 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, // Parent hash is needed for evm.GetHashFn to work. header.ParentHash = parent.Hash() if config.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 { - header.BaseFee = eip1559.CalcBaseFee(config, parent) + if sim.validate { + header.BaseFee = eip1559.CalcBaseFee(config, parent) + } else { + header.BaseFee = big.NewInt(0) + } } } if config.IsCancun(header.Number, header.Time) { From 1c154e996e46820961ba58c6f3a8bf357cb1a509 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 6 Jun 2024 19:15:17 +0200 Subject: [PATCH 098/119] check base fee in test block results --- internal/ethapi/api_test.go | 273 ++++++++++++++++++++++-------------- 1 file changed, 164 insertions(+), 109 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index ffd8d9c63f14..090915171d05 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1108,11 +1108,11 @@ func TestSimulateV1(t *testing.T) { Number string //Hash string // Ignore timestamp - GasLimit string - GasUsed string - Miner string - BaseFee string - Calls []callRes + GasLimit string + GasUsed string + Miner string + BaseFeePerGas string + Calls []callRes } var testSuite = []struct { name string @@ -1146,10 +1146,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xf618", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xf618", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1199,10 +1200,11 @@ func TestSimulateV1(t *testing.T) { }, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xa410", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xa410", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1215,10 +1217,11 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0x5208", - Miner: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0x5208", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1253,10 +1256,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x47e7c4", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x47e7c4", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", Error: callErr{Message: "stack underflow (0 <=> 2)", Code: errCodeVMError}, @@ -1298,10 +1302,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xe891", - Miner: strings.ToLower(cac.String()), + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xe891", + Miner: strings.ToLower(cac.String()), + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000b", GasUsed: "0xe891", @@ -1309,10 +1314,11 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xe891", - Miner: strings.ToLower(cac.String()), + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xe891", + Miner: strings.ToLower(cac.String()), + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x000000000000000000000000000000000000000000000000000000000000000c", GasUsed: "0xe891", @@ -1377,10 +1383,11 @@ func TestSimulateV1(t *testing.T) { }, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x10683", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x10683", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xaacc", @@ -1418,10 +1425,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x5508", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x5508", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", Logs: []log{{ @@ -1486,10 +1494,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x52f6", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x52f6", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ // Caller is in this case the contract that invokes ecrecover. ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), @@ -1536,10 +1545,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xa58c", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xa58c", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0xec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5", GasUsed: "0x52dc", @@ -1584,10 +1594,11 @@ func TestSimulateV1(t *testing.T) { }}, includeTransfers: &includeTransfers, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xd984", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xd984", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd984", @@ -1650,10 +1661,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x1b83f", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x1b83f", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xd166", @@ -1666,10 +1678,11 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xe6d9", - Miner: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xe6d9", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0xe6d9", @@ -1713,10 +1726,11 @@ func TestSimulateV1(t *testing.T) { }}, validation: &validation, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x5208", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x5208", + Miner: coinbase, + BaseFeePerGas: "0x1", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x5208", @@ -1765,10 +1779,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xc542", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xc542", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x0000000000000000000000000000000200000000000000000000000000000003", GasUsed: "0x62a1", @@ -1781,10 +1796,11 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0x62a1", - Miner: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0x62a1", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x0000000000000000000000000000000500000000000000000000000000000000", GasUsed: "0x62a1", @@ -1838,16 +1854,18 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - Miner: coinbase, - Calls: []callRes{}, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + BaseFeePerGas: "0x0", + Calls: []callRes{}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xf864", - Miner: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xf864", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x52cc", @@ -1866,28 +1884,32 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xd", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - Miner: coinbase, - Calls: []callRes{}, + Number: "0xd", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + BaseFeePerGas: "0x0", + Calls: []callRes{}, }, { - Number: "0xe", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - Miner: coinbase, - Calls: []callRes{}, + Number: "0xe", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + BaseFeePerGas: "0x0", + Calls: []callRes{}, }, { - Number: "0xf", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - Miner: coinbase, - Calls: []callRes{}, + Number: "0xf", + GasLimit: "0x47e7c4", + GasUsed: "0x0", + Miner: coinbase, + BaseFeePerGas: "0x0", + Calls: []callRes{}, }, { - Number: "0x10", - GasLimit: "0x47e7c4", - GasUsed: "0xa598", - Miner: coinbase, + Number: "0x10", + GasLimit: "0x47e7c4", + GasUsed: "0xa598", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", GasUsed: "0x52cc", @@ -1947,10 +1969,11 @@ func TestSimulateV1(t *testing.T) { }}, }}, want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0xa44e", - Miner: coinbase, + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xa44e", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", GasUsed: "0x5227", @@ -1963,10 +1986,11 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xc", - GasLimit: "0x47e7c4", - GasUsed: "0xa44e", - Miner: coinbase, + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xa44e", + Miner: coinbase, + BaseFeePerGas: "0x1", Calls: []callRes{{ ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", GasUsed: "0x5227", @@ -1979,10 +2003,11 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }, { - Number: "0xd", - GasLimit: "0x47e7c4", - GasUsed: "0x5227", - Miner: coinbase, + Number: "0xd", + GasLimit: "0x47e7c4", + GasUsed: "0x5227", + Miner: coinbase, + BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", GasUsed: "0x5227", @@ -1990,6 +2015,36 @@ func TestSimulateV1(t *testing.T) { Status: "0x1", }}, }}, + }, { + name: "basefee-validation-mode", + tag: latest, + blocks: []simBlock{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: { + Code: hex2Bytes("3a489060005260205260406000f3"), + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + MaxFeePerGas: newInt(233138868), + MaxPriorityFeePerGas: newInt(1), + }}, + }}, + validation: &validation, + want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x5227", + Miner: coinbase, + BaseFeePerGas: "0xde56ab3", + Calls: []callRes{{ + ReturnValue: "0x000000000000000000000000000000000000000000000000000000000de56ab4000000000000000000000000000000000000000000000000000000000de56ab3", + GasUsed: "0x5227", + Logs: []log{}, + Status: "0x1", + }}, + }}, }, } From ac690f0d4c09cbd00f4a6a017bc9a656dfbb4da5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 12 Jun 2024 13:11:26 +0200 Subject: [PATCH 099/119] fix chainId default --- internal/ethapi/simulate.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index a47cdec0cecf..00245fc24ad9 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -189,12 +189,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if err := sim.sanitizeCall(&call, sim.state, &gasUsed, blockContext); err != nil { return nil, err } - tx := call.ToTransaction(call.GasPrice == nil) - txes[i] = tx - if err := call.CallDefaults(gp.Gas(), header.BaseFee, config.ChainID); err != nil { return nil, err } + tx := call.ToTransaction(call.GasPrice == nil) + txes[i] = tx msg := call.ToMessage(header.BaseFee, !sim.validate) tracer.reset(tx.Hash(), uint(i)) evm.Reset(core.NewEVMTxContext(msg), sim.state) @@ -276,11 +275,6 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, remaining := blockContext.GasLimit - *gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } - // TODO: check chainID and against current header for london fees - if call.GasPrice == nil && call.MaxFeePerGas == nil && call.MaxPriorityFeePerGas == nil { - call.MaxFeePerGas = (*hexutil.Big)(big.NewInt(0)) - call.MaxPriorityFeePerGas = (*hexutil.Big)(big.NewInt(0)) - } return nil } From a9842e500c837400406366010f7d44bafb4fe8df Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 12 Jun 2024 13:29:40 +0200 Subject: [PATCH 100/119] add td --- internal/ethapi/simulate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 00245fc24ad9..2f3907f66fd5 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -241,6 +241,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil)) res := RPCMarshalBlock(b, true, sim.fullTx, config) + res["totalDifficulty"] = (*hexutil.Big)(sim.b.GetTd(ctx, sim.base.Hash())) repairLogs(callResults, res["hash"].(common.Hash)) res["calls"] = callResults From a6f80c750da586dcaacbfe93500d11b0311b9c70 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 12 Jun 2024 15:01:43 +0200 Subject: [PATCH 101/119] add data on reverts --- internal/ethapi/simulate.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 2f3907f66fd5..ca9e63b82f52 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -212,16 +212,14 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, gasUsed += result.UsedGas receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) blobGasUsed += receipts[i].BlobGasUsed - // If the result contains a revert reason, try to unpack it. - if len(result.Revert()) > 0 { - result.Err = newRevertError(result.Revert()) - } 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) { - callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeReverted} + // 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} } From 5db01067d3dd462946cb2ad6ec8559634c8f0573 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 12 Jun 2024 15:05:37 +0200 Subject: [PATCH 102/119] fix --- internal/ethapi/errors.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index 6b127494441b..ae38061234db 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -82,6 +82,7 @@ func (e *TxIndexingError) ErrorData() interface{} { return "transaction indexing type callError struct { Message string `json:"message"` Code int `json:"code"` + Data string `json:"data,omitempty"` } type invalidTxError struct { From 4c325ba5b365e44f1c33cfe7b3236bcb685ec2de Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 12 Jun 2024 15:24:10 +0200 Subject: [PATCH 103/119] skip EoA check in validation mode --- core/state_transition.go | 37 ++++++++++++++++------------- eth/tracers/api.go | 2 +- internal/ethapi/api.go | 6 ++--- internal/ethapi/api_test.go | 35 +++++++++++++++++++++++++++ internal/ethapi/simulate.go | 3 ++- internal/ethapi/transaction_args.go | 29 +++++++++++----------- 6 files changed, 77 insertions(+), 35 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 1a6a66a2fc14..2f42890cb414 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -142,27 +142,30 @@ type Message struct { BlobGasFeeCap *big.Int BlobHashes []common.Hash - // When SkipAccountChecks is true, the message nonce is not checked against the - // account nonce in state. It also disables checking that the sender is an EOA. + // When SkipNonceChecks is true, the message nonce is not checked against the + // account nonce in state. // 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. func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { msg := &Message{ - Nonce: tx.Nonce(), - GasLimit: tx.Gas(), - GasPrice: new(big.Int).Set(tx.GasPrice()), - GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), - GasTipCap: new(big.Int).Set(tx.GasTipCap()), - To: tx.To(), - Value: tx.Value(), - Data: tx.Data(), - AccessList: tx.AccessList(), - SkipAccountChecks: false, - BlobHashes: tx.BlobHashes(), - BlobGasFeeCap: tx.BlobGasFeeCap(), + Nonce: tx.Nonce(), + GasLimit: tx.Gas(), + GasPrice: new(big.Int).Set(tx.GasPrice()), + GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + GasTipCap: new(big.Int).Set(tx.GasTipCap()), + To: tx.To(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + SkipNonceChecks: false, + SkipFromEoACheck: false, + BlobHashes: tx.BlobHashes(), + BlobGasFeeCap: tx.BlobGasFeeCap(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -280,7 +283,7 @@ func (st *StateTransition) buyGas() error { func (st *StateTransition) preCheck() error { // Only check transactions that are not fake msg := st.msg - if !msg.SkipAccountChecks { + if !msg.SkipNonceChecks { // Make sure this transaction's nonce is correct. stNonce := st.state.GetNonce(msg.From) if msgNonce := msg.Nonce; stNonce < msgNonce { @@ -293,6 +296,8 @@ func (st *StateTransition) preCheck() error { return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, msg.From.Hex(), stNonce) } + } + if !msg.SkipFromEoACheck { // Make sure the sender is an EOA codeHash := st.state.GetCodeHash(msg.From) if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index ad5dba187bea..c897da2dc330 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -951,7 +951,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc return nil, err } var ( - msg = args.ToMessage(vmctx.BaseFee, true) + msg = args.ToMessage(vmctx.BaseFee, true, true) tx = args.ToTransaction(args.GasPrice == nil) traceConfig *TraceConfig ) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 12ebdd60a3bc..29169036d108 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1172,7 +1172,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil { return nil, err } - msg := args.ToMessage(header.BaseFee, skipChecks) + msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks) // Lower the basefee to 0 to avoid breaking EVM // invariants (basefee < feecap). if msg.GasPrice.Sign() == 0 { @@ -1299,7 +1299,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil { return 0, err } - call := args.ToMessage(header.BaseFee, true) + call := args.ToMessage(header.BaseFee, true, true) // Run the gas estimation and wrap any revertals into a custom return estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) @@ -1633,7 +1633,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH statedb := db.Copy() // Set the accesslist to the last al args.AccessList = &accessList - msg := args.ToMessage(header.BaseFee, true) + msg := args.ToMessage(header.BaseFee, true, true) // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 26faca97b15b..b3f99a3ef1ff 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1720,6 +1720,41 @@ func TestSimulateV1(t *testing.T) { want: nil, expectErr: &invalidTxError{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, }, + // Contract sends tx in validation mode. + { + name: "validation-checks-from-contract", + tag: latest, + blocks: []simBlock{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: OverrideAccount{ + Balance: newRPCBalance(big.NewInt(2098640803896784)), + Code: hex2Bytes("00"), + Nonce: newUint64(1), + }, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[2].addr, + To: &cac, + Nonce: newUint64(1), + MaxFeePerGas: newInt(233138868), + MaxPriorityFeePerGas: newInt(1), + }}, + }}, + validation: &validation, + want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0xd166", + Miner: coinbase, + BaseFeePerGas: "0xde56ab3", + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0xd166", + Logs: []log{}, + Status: "0x1", + }}, + }}, + }, // Successful validation { name: "validation-checks-success", diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index ca9e63b82f52..f2546f9693f7 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -194,7 +194,8 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } tx := call.ToTransaction(call.GasPrice == nil) txes[i] = tx - msg := call.ToMessage(header.BaseFee, !sim.validate) + // EoA check is always skipped, even in validation mode. + msg := call.ToMessage(header.BaseFee, !sim.validate, true) tracer.reset(tx.Hash(), uint(i)) evm.Reset(core.NewEVMTxContext(msg), sim.state) result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, gp) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 532ac92f96e8..37d8b1a05840 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -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 // live transaction. // Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called. -func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipChecks bool) *core.Message { +func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool) *core.Message { var ( gasPrice *big.Int gasFeeCap *big.Int @@ -452,19 +452,20 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipChecks bool) *core. accessList = *args.AccessList } return &core.Message{ - From: args.from(), - To: args.To, - Value: (*big.Int)(args.Value), - Nonce: uint64(*args.Nonce), - GasLimit: uint64(*args.Gas), - GasPrice: gasPrice, - GasFeeCap: gasFeeCap, - GasTipCap: gasTipCap, - Data: args.data(), - AccessList: accessList, - BlobGasFeeCap: (*big.Int)(args.BlobFeeCap), - BlobHashes: args.BlobHashes, - SkipAccountChecks: skipChecks, + From: args.from(), + To: args.To, + Value: (*big.Int)(args.Value), + Nonce: uint64(*args.Nonce), + GasLimit: uint64(*args.Gas), + GasPrice: gasPrice, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Data: args.data(), + AccessList: accessList, + BlobGasFeeCap: (*big.Int)(args.BlobFeeCap), + BlobHashes: args.BlobHashes, + SkipNonceChecks: skipNonceCheck, + SkipFromEoACheck: skipEoACheck, } } From 172ed025d156c5657ac7bf143c88aca7c2a3acac Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 15 Jul 2024 20:57:17 +0200 Subject: [PATCH 104/119] refactors --- internal/ethapi/api.go | 12 ++- .../{transfertracer.go => logtracer.go} | 15 ++- internal/ethapi/simulate.go | 94 ++++++++++--------- 3 files changed, 66 insertions(+), 55 deletions(-) rename internal/ethapi/{transfertracer.go => logtracer.go} (93%) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3db9658cc45d..458f054f234c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1265,7 +1265,17 @@ func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrH if state == nil || err != nil { return nil, err } - sim := &simulator{b: s.b, state: state, base: base, traceTransfers: opts.TraceTransfers, validate: opts.Validation, fullTx: opts.ReturnFullTransactions} + sim := &simulator{ + b: s.b, + state: state, + base: base, + chainConfig: s.b.ChainConfig(), + // Each tx and all the series of txes shouldn't consume more gas than cap + gp: new(core.GasPool).AddGas(s.b.RPCGasCap()), + traceTransfers: opts.TraceTransfers, + validate: opts.Validation, + fullTx: opts.ReturnFullTransactions, + } return sim.execute(ctx, opts.BlockStateCalls) } diff --git a/internal/ethapi/transfertracer.go b/internal/ethapi/logtracer.go similarity index 93% rename from internal/ethapi/transfertracer.go rename to internal/ethapi/logtracer.go index 04679d529f04..456aa937367f 100644 --- a/internal/ethapi/transfertracer.go +++ b/internal/ethapi/logtracer.go @@ -48,24 +48,23 @@ var ( // - Recipient address type tracer struct { // logs keeps logs for all open call frames. - // This lets us clear logs for failed logs. + // This lets us clear logs for failed calls. logs [][]*types.Log count int traceTransfers bool - // TODO: replace with tracers.Context once extended tracer PR is merged. - blockNumber uint64 - blockHash common.Hash - txHash common.Hash - txIdx uint + blockNumber uint64 + blockHash common.Hash + txHash common.Hash + txIdx uint } -func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIdx uint) *tracer { +func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIndex uint) *tracer { return &tracer{ traceTransfers: traceTransfers, blockNumber: blockNumber, blockHash: blockHash, txHash: txHash, - txIdx: txIdx, + txIdx: txIndex, } } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index f2546f9693f7..cbee558cfd13 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -33,6 +33,7 @@ import ( "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" ) @@ -50,6 +51,7 @@ type simBlock struct { Calls []TransactionArgs } +// simCallResult is the result of a simulated call. type simCallResult struct { ReturnValue hexutil.Bytes `json:"returnData"` Logs []*types.Log `json:"logs"` @@ -67,6 +69,7 @@ func (r *simCallResult) MarshalJSON() ([]byte, error) { return json.Marshal((*callResultAlias)(r)) } +// simOpts are the inputs to eth_simulateV1. type simOpts struct { BlockStateCalls []simBlock TraceTransfers bool @@ -74,19 +77,21 @@ type simOpts struct { 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 - hashes []common.Hash 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) { - // Setup context so it may be cancelled before the calls completed - // or, in case of unmetered gas, setup a context with a timeout. var ( cancel context.CancelFunc timeout = sim.b.RPCEVMTimeout() @@ -105,51 +110,51 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[str 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)) - // Each tx and all the series of txes shouldn't consume more gas than cap - gp = new(core.GasPool).AddGas(sim.b.RPCGasCap()) - precompiles = sim.activePrecompiles(ctx, sim.base) - numHashes = headers[len(headers)-1].Number.Uint64() - sim.base.Number.Uint64() + 256 - parent = sim.base + parent = sim.base + // Assume same total difficulty for all simulated blocks. + td = sim.b.GetTd(ctx, sim.base.Hash()) ) - // Cache for the block hashes. - sim.hashes = make([]common.Hash, numHashes) for bi, block := range blocks { - result, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], gp, precompiles, timeout) + result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout) if err != nil { return nil, err } - results[bi] = result + 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, gp *core.GasPool, precompiles vm.PrecompiledContracts, timeout time.Duration) (map[string]interface{}, error) { +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. - config := sim.b.ChainConfig() // Parent hash is needed for evm.GetHashFn to work. header.ParentHash = parent.Hash() - if config.IsLondon(header.Number) { + 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(config, parent) + header.BaseFee = eip1559.CalcBaseFee(sim.chainConfig, parent) } else { header.BaseFee = big.NewInt(0) } } } - if config.IsCancun(header.Number, header.Time) { + if sim.chainConfig.IsCancun(header.Number, header.Time) { var excess uint64 - if config.IsCancun(parent.Number, parent.Time) { + if sim.chainConfig.IsCancun(parent.Number, parent.Time) { excess = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) } else { excess = eip4844.CalcExcessBlobGas(0, 0) @@ -160,22 +165,23 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if block.BlockOverrides.BlobBaseFee != nil { blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt() } + precompiles := sim.activePrecompiles(ctx, sim.base) // State overrides are applied prior to execution of a block if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil { - return nil, err + 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)) - tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) - vmConfig = &vm.Config{ + // 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, - // Block hash will be repaired after execution. - Tracer: tracer.Hooks(), + Tracer: tracer.Hooks(), } - evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, config, *vmConfig) + 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 @@ -186,29 +192,26 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, for i, call := range block.Calls { // TODO: Pre-estimate nonce and gas // TODO: Move gas fees sanitizing to beginning of func - if err := sim.sanitizeCall(&call, sim.state, &gasUsed, blockContext); err != nil { - return nil, err - } - if err := call.CallDefaults(gp.Gas(), header.BaseFee, config.ChainID); err != nil { - return nil, err + if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil { + return nil, nil, err } tx := call.ToTransaction(call.GasPrice == nil) 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) - tracer.reset(tx.Hash(), uint(i)) evm.Reset(core.NewEVMTxContext(msg), sim.state) - result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, gp) + result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, sim.gp) if err != nil { txErr := txValidationError(err) - return nil, txErr + return nil, nil, txErr } // Update the state with pending changes. var root []byte - if config.IsByzantium(blockContext.BlockNumber) { + if sim.chainConfig.IsByzantium(blockContext.BlockNumber) { sim.state.Finalise(true) } else { - root = sim.state.IntermediateRoot(config.IsEIP158(blockContext.BlockNumber)).Bytes() + 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) @@ -231,20 +234,17 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } header.Root = sim.state.IntermediateRoot(true) header.GasUsed = gasUsed - if config.IsCancun(header.Number, header.Time) { + if sim.chainConfig.IsCancun(header.Number, header.Time) { header.BlobGasUsed = &blobGasUsed } var withdrawals types.Withdrawals - if config.IsShanghai(header.Number, header.Time) { + 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)) - res := RPCMarshalBlock(b, true, sim.fullTx, config) - res["totalDifficulty"] = (*hexutil.Big)(sim.b.GetTd(ctx, sim.base.Hash())) - repairLogs(callResults, res["hash"].(common.Hash)) - res["calls"] = callResults + repairLogs(callResults, b.Hash()) + return b, callResults, nil - return res, nil } // repairLogs updates the block hash in the logs present in the result of @@ -258,7 +258,7 @@ func repairLogs(calls []simCallResult, hash common.Hash) { } } -func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, gasUsed *uint64, blockContext vm.BlockContext) error { +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) @@ -275,13 +275,16 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, remaining := blockContext.GasLimit - *gasUsed call.Gas = (*hexutil.Uint64)(&remaining) } + if err := call.CallDefaults(sim.gp.Gas(), header.BaseFee, sim.chainConfig.ChainID); err != nil { + return err + } return nil } func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) vm.PrecompiledContracts { var ( blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, sim.b), nil) - rules = sim.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + rules = sim.chainConfig.Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) ) return vm.ActivePrecompiledContracts(rules).Copy() } @@ -333,7 +336,6 @@ func (sim *simulator) sanitizeBlockOrder(blocks []simBlock) ([]simBlock, error) func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { var ( res = make([]*types.Header, len(blocks)) - config = sim.b.ChainConfig() base = sim.base prevTimestamp = base.Time header = base @@ -352,11 +354,11 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { prevTimestamp = uint64(*overrides.Time) var withdrawalsHash *common.Hash - if config.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + if sim.chainConfig.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { withdrawalsHash = &types.EmptyWithdrawalsHash } var parentBeaconRoot *common.Hash - if config.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { parentBeaconRoot = &common.Hash{} } header = overrides.MakeHeader(&types.Header{ From ddba7dd3d8edccf69bd21326d40eca6b3ae052e6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 15 Jul 2024 21:08:14 +0200 Subject: [PATCH 105/119] rm stale todos --- internal/ethapi/simulate.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index cbee558cfd13..537de64ad3d7 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -190,8 +190,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, evm.SetPrecompiles(precompiles) } for i, call := range block.Calls { - // TODO: Pre-estimate nonce and gas - // TODO: Move gas fees sanitizing to beginning of func if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil { return nil, nil, err } From e3cd999230e07e9d0f7d1d9d9266c5205dae2c79 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 14 Aug 2024 13:09:50 +0200 Subject: [PATCH 106/119] default timestamp bump 1 sec --- internal/ethapi/simulate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 537de64ad3d7..509989999b0c 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -344,7 +344,7 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { } overrides := block.BlockOverrides if overrides.Time == nil { - t := prevTimestamp + 12 + t := prevTimestamp + 1 overrides.Time = (*hexutil.Uint64)(&t) } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", *time, prevTimestamp)} From 33400fb1529cfade2f9fe774fb5851bea9100984 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 16 Aug 2024 09:23:53 +0200 Subject: [PATCH 107/119] review comments, pt. 1 --- core/state_processor.go | 2 +- core/state_transition.go | 9 +++++---- core/vm/contracts.go | 3 ++- internal/ethapi/transaction_args.go | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index cc5d459252b4..75573d7e6d00 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -135,7 +135,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo } *usedGas += result.UsedGas - return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), err + return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), nil } // MakeReceipt generates the receipt object for a transaction given its execution result. diff --git a/core/state_transition.go b/core/state_transition.go index 2f42890cb414..823967b22044 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -146,8 +146,9 @@ type Message struct { // account nonce in state. // This field will be set to true for operations like RPC eth_call. SkipNonceChecks bool - // When SkipFromEoACheck is true, the message sender is not checked to be an EOA. - SkipFromEoACheck bool + + // When SkipFromEOACheck is true, the message sender is not checked to be an EOA. + SkipFromEOACheck bool } // TransactionToMessage converts a transaction into a Message. @@ -163,7 +164,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Data: tx.Data(), AccessList: tx.AccessList(), SkipNonceChecks: false, - SkipFromEoACheck: false, + SkipFromEOACheck: false, BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), } @@ -297,7 +298,7 @@ func (st *StateTransition) preCheck() error { msg.From.Hex(), stNonce) } } - if !msg.SkipFromEoACheck { + if !msg.SkipFromEOACheck { // Make sure the sender is an EOA codeHash := st.state.GetCodeHash(msg.From) if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 74347b8fdb34..a0c3cee70a7b 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -172,7 +172,8 @@ func init() { } } -// Copy returns a copy of the precompiled contracts. +// Copy returns a copy of the precompiled contracts. The precompiled +// contracts are shallow-copied. It should be safe as they are stateless/immutable. func (p PrecompiledContracts) Copy() PrecompiledContracts { c := make(PrecompiledContracts) for k, v := range p { diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 37d8b1a05840..92cb9c532758 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -465,7 +465,7 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA BlobGasFeeCap: (*big.Int)(args.BlobFeeCap), BlobHashes: args.BlobHashes, SkipNonceChecks: skipNonceCheck, - SkipFromEoACheck: skipEoACheck, + SkipFromEOACheck: skipEoACheck, } } From 5f0fafd8e6d5afc48f7caf84c6c8e188850444d5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 16 Aug 2024 16:41:31 +0200 Subject: [PATCH 108/119] fix precompile moveTo --- internal/ethapi/api.go | 17 +++++++ internal/ethapi/api_test.go | 92 +++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3a465d35cd21..14b7a824ef0c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -979,12 +979,23 @@ type OverrideAccount struct { // StateOverride is the collection of overridden accounts. 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. func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.PrecompiledContracts) error { if diff == nil { return nil } + // Tracks destinations of precompiles that were moved. + dirtyAddrs := make(map[common.Address]struct{}) 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 @@ -994,7 +1005,13 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi 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) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 7ce8c29795ce..202b573fc074 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -35,6 +35,7 @@ import ( "github.com/holiman/uint256" "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" @@ -3240,6 +3241,97 @@ func TestRPCGetBlockReceipts(t *testing.T) { } } +type precompileContract struct{} + +func (p *precompileContract) RequiredGas(input []byte) uint64 { return 0 } + +func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil } + +func TestStateOverrideMovePrecompile(t *testing.T) { + db := state.NewDatabase(rawdb.NewMemoryDatabase()) + statedb, err := state.New(common.Hash{}, db, nil) + if err != nil { + t.Fatalf("failed to create statedb: %v", err) + } + precompiles := map[common.Address]vm.PrecompiledContract{ + common.BytesToAddress([]byte{0x1}): &precompileContract{}, + common.BytesToAddress([]byte{0x2}): &precompileContract{}, + } + bytes2Addr := func(b []byte) *common.Address { + a := common.BytesToAddress(b) + return &a + } + var testSuite = []struct { + overrides StateOverride + expectedPrecompiles map[common.Address]struct{} + fail bool + }{ + { + overrides: StateOverride{ + common.BytesToAddress([]byte{0x1}): { + Code: hex2Bytes("0xff"), + MovePrecompileTo: bytes2Addr([]byte{0x2}), + }, + common.BytesToAddress([]byte{0x2}): { + Code: hex2Bytes("0x00"), + }, + }, + // 0x2 has already been touched by the moveTo. + fail: true, + }, { + overrides: StateOverride{ + common.BytesToAddress([]byte{0x1}): { + Code: hex2Bytes("0xff"), + MovePrecompileTo: bytes2Addr([]byte{0xff}), + }, + common.BytesToAddress([]byte{0x3}): { + Code: hex2Bytes("0x00"), + MovePrecompileTo: bytes2Addr([]byte{0xfe}), + }, + }, + // 0x3 is not a precompile. + fail: true, + }, { + overrides: StateOverride{ + common.BytesToAddress([]byte{0x1}): { + Code: hex2Bytes("0xff"), + MovePrecompileTo: bytes2Addr([]byte{0xff}), + }, + common.BytesToAddress([]byte{0x2}): { + Code: hex2Bytes("0x00"), + MovePrecompileTo: bytes2Addr([]byte{0xfe}), + }, + }, + expectedPrecompiles: map[common.Address]struct{}{common.BytesToAddress([]byte{0xfe}): {}, common.BytesToAddress([]byte{0xff}): {}}, + }, + } + + for i, tt := range testSuite { + cpy := maps.Clone(precompiles) + // Apply overrides + err := tt.overrides.Apply(statedb, cpy) + if tt.fail { + if err == nil { + t.Errorf("test %d: want error, have nothing", i) + } + continue + } + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + // Precompile keys + if len(cpy) != len(tt.expectedPrecompiles) { + t.Errorf("test %d: precompile mismatch, want %d, have %d", i, len(tt.expectedPrecompiles), len(cpy)) + } + for k, _ := range tt.expectedPrecompiles { + if _, ok := cpy[k]; !ok { + t.Errorf("test %d: precompile not found: %s", i, k.String()) + } + } + } +} + func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc string, file string) { data, err := json.MarshalIndent(result, "", " ") if err != nil { From 73ff7e51f6b984356c793ef8fbbc420ff0a299e0 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 19 Aug 2024 12:59:26 +0200 Subject: [PATCH 109/119] move timestamp sanitization --- internal/ethapi/api.go | 2 +- internal/ethapi/simulate.go | 50 ++++++++++++-------- internal/ethapi/simulate_test.go | 80 ++++++++++++++++++++++---------- 3 files changed, 86 insertions(+), 46 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 14b7a824ef0c..330ee5f29500 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1095,7 +1095,7 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { // MakeHeader returns a new header object with the overridden // fields. -// Note: MakeHeader ignores blobGasPrice if set. That's because +// 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 { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 509989999b0c..268cc246bb0c 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -42,6 +42,9 @@ 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. @@ -106,7 +109,7 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[str defer cancel() var err error - blocks, err = sim.sanitizeBlockOrder(blocks) + blocks, err = sim.sanitizeChain(blocks) if err != nil { return nil, err } @@ -287,14 +290,16 @@ func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) return vm.ActivePrecompiledContracts(rules).Copy() } -// sanitizeBlockOrder iterates the blocks checking that block numbers -// are strictly increasing. When necessary it will generate empty blocks. -// It modifies the block's override object. -func (sim *simulator) sanitizeBlockOrder(blocks []simBlock) ([]simBlock, error) { +// 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 + res = make([]simBlock, 0, len(blocks)) + base = sim.base + prevNumber = base.Number + prevTimestamp = base.Time ) for _, block := range blocks { if block.BlockOverrides == nil { @@ -317,12 +322,25 @@ func (sim *simulator) sanitizeBlockOrder(blocks []simBlock) ([]simBlock, error) // 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))) - b := simBlock{BlockOverrides: &BlockOverrides{Number: (*hexutil.Big)(n)}} + 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 @@ -333,23 +351,15 @@ func (sim *simulator) sanitizeBlockOrder(blocks []simBlock) ([]simBlock, error) // 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 - prevTimestamp = base.Time - header = base + 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 - if overrides.Time == nil { - t := prevTimestamp + 1 - overrides.Time = (*hexutil.Uint64)(&t) - } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { - return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", *time, prevTimestamp)} - } - prevTimestamp = uint64(*overrides.Time) var withdrawalsHash *common.Hash if sim.chainConfig.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { diff --git a/internal/ethapi/simulate_test.go b/internal/ethapi/simulate_test.go index 34cbb3ba3824..d80380a8e038 100644 --- a/internal/ethapi/simulate_test.go +++ b/internal/ethapi/simulate_test.go @@ -1,7 +1,6 @@ package ethapi import ( - "fmt" "math/big" "testing" @@ -10,35 +9,62 @@ import ( ) func TestSimulateSanitizeBlockOrder(t *testing.T) { + type result struct { + number uint64 + timestamp uint64 + } for i, tc := range []struct { - baseNumber int - blocks []simBlock - expectedLen int - err string + baseNumber int + baseTimestamp uint64 + blocks []simBlock + expected []result + err string }{ { - baseNumber: 10, - blocks: []simBlock{{}, {}, {}}, - expectedLen: 3, + 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, - blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(13)}}, {}}, - expectedLen: 4, + 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, - blocks: []simBlock{{BlockOverrides: &BlockOverrides{Number: newInt(11)}}, {BlockOverrides: &BlockOverrides{Number: newInt(14)}}, {}}, - expectedLen: 5, + 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, - 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(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))}} - res, err := sim.sanitizeBlockOrder(tc.blocks) + 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 @@ -49,9 +75,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) { if err == nil && tc.err != "" { t.Fatalf("testcase %d: expected err", i) } - if len(res) != tc.expectedLen { - fmt.Printf("res: %v\n", res) - t.Errorf("testcase %d: mismatch number of blocks. Want %d, have %d", i, tc.expectedLen, len(res)) + 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 { @@ -60,10 +85,15 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) { if b.BlockOverrides.Number == nil { t.Fatalf("testcase %d: block number not set", i) } - want := tc.baseNumber + bi + 1 + 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 uint64(want) != have { - t.Errorf("testcase %d: block number mismatch. Want %d, have %d", i, want, have) + if have != tc.expected[bi].number { + t.Errorf("testcase %d: block number mismatch. Want %d, have %d", i, tc.expected[bi].number, have) } } } From 5d8c058fd4237ee4b19e03774c024487026a85c5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 19 Aug 2024 13:09:32 +0200 Subject: [PATCH 110/119] Clean up sanitizeCall Co-authored-by: Gary Rong --- internal/ethapi/simulate.go | 11 ++++------- internal/ethapi/simulate_test.go | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 268cc246bb0c..9b053f69bdaa 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -264,18 +264,15 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) } - var gas uint64 - if call.Gas != nil { - gas = uint64(*call.Gas) - } - if *gasUsed+gas > blockContext.GasLimit { - return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)} - } // 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 } diff --git a/internal/ethapi/simulate_test.go b/internal/ethapi/simulate_test.go index d80380a8e038..e8a1f08cfd07 100644 --- a/internal/ethapi/simulate_test.go +++ b/internal/ethapi/simulate_test.go @@ -88,8 +88,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) { 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)) + 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 { From edf9e376ed44629df27e5495134ef64762d5a5fa Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 19 Aug 2024 18:01:31 +0200 Subject: [PATCH 111/119] Fix lint errors --- internal/ethapi/api.go | 10 +++++----- internal/ethapi/api_test.go | 2 +- internal/ethapi/simulate.go | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 330ee5f29500..391e6393be19 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1268,7 +1268,7 @@ func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockN // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { +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 { @@ -1278,17 +1278,17 @@ func (s *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrH n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &n } - state, base, err := s.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) + state, base, err := api.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash) if state == nil || err != nil { return nil, err } sim := &simulator{ - b: s.b, + b: api.b, state: state, base: base, - chainConfig: s.b.ChainConfig(), + chainConfig: api.b.ChainConfig(), // Each tx and all the series of txes shouldn't consume more gas than cap - gp: new(core.GasPool).AddGas(s.b.RPCGasCap()), + gp: new(core.GasPool).AddGas(api.b.RPCGasCap()), traceTransfers: opts.TraceTransfers, validate: opts.Validation, fullTx: opts.ReturnFullTransactions, diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 202b573fc074..53620bf29d2c 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -3324,7 +3324,7 @@ func TestStateOverrideMovePrecompile(t *testing.T) { if len(cpy) != len(tt.expectedPrecompiles) { t.Errorf("test %d: precompile mismatch, want %d, have %d", i, len(tt.expectedPrecompiles), len(cpy)) } - for k, _ := range tt.expectedPrecompiles { + for k := range tt.expectedPrecompiles { if _, ok := cpy[k]; !ok { t.Errorf("test %d: precompile not found: %s", i, k.String()) } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 9b053f69bdaa..7a0d6f2bc3eb 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -245,7 +245,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, 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 @@ -272,7 +271,6 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, 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 } From e7bac205a7addf0ce7772fc0be7e91f66f56d54e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 19 Aug 2024 18:04:30 +0200 Subject: [PATCH 112/119] add test comments --- internal/ethapi/api_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 53620bf29d2c..4e31aacfc637 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -2007,6 +2007,23 @@ func TestSimulateV1(t *testing.T) { blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: { + // Yul code: + // object "Test" { + // code { + // // Get the gas price from the transaction + // let gasPrice := gasprice() + // + // // Get the base fee from the block + // let baseFee := basefee() + // + // // Store gasPrice and baseFee in memory + // mstore(0x0, gasPrice) + // mstore(0x20, baseFee) + // + // // Return the data + // return(0x0, 0x40) + // } + // } Code: hex2Bytes("3a489060005260205260406000f3"), }, }, @@ -2098,6 +2115,23 @@ func TestSimulateV1(t *testing.T) { blocks: []simBlock{{ StateOverrides: &StateOverride{ randomAccounts[2].addr: { + // Yul code: + // object "Test" { + // code { + // // Get the gas price from the transaction + // let gasPrice := gasprice() + // + // // Get the base fee from the block + // let baseFee := basefee() + // + // // Store gasPrice and baseFee in memory + // mstore(0x0, gasPrice) + // mstore(0x20, baseFee) + // + // // Return the data + // return(0x0, 0x40) + // } + // } Code: hex2Bytes("3a489060005260205260406000f3"), }, }, From 9a963dc5d03cad205030e982f98715a6678d9c0c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 4 Sep 2024 11:25:14 +0200 Subject: [PATCH 113/119] add license to simulate_test --- internal/ethapi/simulate_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/internal/ethapi/simulate_test.go b/internal/ethapi/simulate_test.go index e8a1f08cfd07..37883924acc6 100644 --- a/internal/ethapi/simulate_test.go +++ b/internal/ethapi/simulate_test.go @@ -1,3 +1,19 @@ +// 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 . + package ethapi import ( From 7198fc1225c83adeb0c9a67d6fa001d16afa8494 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 4 Sep 2024 11:51:20 +0200 Subject: [PATCH 114/119] update test to use fixed address --- internal/ethapi/api_test.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 4e31aacfc637..8d17e6b20d3d 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1055,13 +1055,14 @@ func TestSimulateV1(t *testing.T) { t.Parallel() // Initialize test accounts var ( - accounts = newAccounts(3) - genBlocks = 10 - signer = types.HomesteadSigner{} - cac = common.HexToAddress("0x0000000000000000000000000000000000000cac") - bab = common.HexToAddress("0x0000000000000000000000000000000000000bab") - coinbase = "0x000000000000000000000000000000000000ffff" - genesis = &core.Genesis{ + accounts = newAccounts(3) + fixedAccount = newTestAccount() + genBlocks = 10 + signer = types.HomesteadSigner{} + cac = common.HexToAddress("0x0000000000000000000000000000000000000cac") + bab = common.HexToAddress("0x0000000000000000000000000000000000000bab") + coinbase = "0x000000000000000000000000000000000000ffff" + genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, @@ -1631,19 +1632,19 @@ func TestSimulateV1(t *testing.T) { From: &accounts[0].addr, To: &randomAccounts[0].addr, Value: (*hexutil.Big)(big.NewInt(50)), - Input: hex2Bytes(strings.TrimPrefix(randomAccounts[1].addr.String(), "0x")), + Input: hex2Bytes(strings.TrimPrefix(fixedAccount.addr.String(), "0x")), }}, }}, includeTransfers: &includeTransfers, want: []blockRes{{ Number: "0xb", GasLimit: "0x47e7c4", - GasUsed: "0xd984", + GasUsed: "0x77dc", Miner: coinbase, BaseFeePerGas: "0x0", Calls: []callRes{{ ReturnValue: "0x", - GasUsed: "0xd984", + GasUsed: "0x77dc", Logs: []log{{ Address: transferAddress, Topics: []common.Hash{ @@ -1658,7 +1659,7 @@ func TestSimulateV1(t *testing.T) { Topics: []common.Hash{ transferTopic, addressToHash(randomAccounts[0].addr), - addressToHash(randomAccounts[1].addr), + addressToHash(fixedAccount.addr), }, Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), BlockNumber: hexutil.Uint64(11), @@ -2530,6 +2531,14 @@ func newAccounts(n int) (accounts []account) { return accounts } +func newTestAccount() account { + // testKey is a private key to use for funding a tester account. + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + // testAddr is the Ethereum address of the tester account. + addr := crypto.PubkeyToAddress(key.PublicKey) + return account{key: key, addr: addr} +} + func newRPCBalance(balance *big.Int) *hexutil.Big { rpcBalance := (*hexutil.Big)(balance) return rpcBalance From 791719a0fe0a002d27aea01b5b615f000038ecbb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 4 Sep 2024 14:21:42 +0200 Subject: [PATCH 115/119] core/vm: use maps.Clone --- core/vm/contracts.go | 13 ++----------- eth/tracers/api.go | 1 + internal/ethapi/api.go | 3 ++- internal/ethapi/simulate.go | 3 ++- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index a0c3cee70a7b..104d2ba814b2 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -21,6 +21,7 @@ import ( "encoding/binary" "errors" "fmt" + "maps" "math/big" "github.com/consensys/gnark-crypto/ecc" @@ -172,16 +173,6 @@ func init() { } } -// Copy returns a copy of the precompiled contracts. The precompiled -// contracts are shallow-copied. It should be safe as they are stateless/immutable. -func (p PrecompiledContracts) Copy() PrecompiledContracts { - c := make(PrecompiledContracts) - for k, v := range p { - c[k] = v - } - return c -} - func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { switch { case rules.IsVerkle: @@ -203,7 +194,7 @@ func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { // ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration. func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { - return activePrecompiledContracts(rules).Copy() + return maps.Clone(activePrecompiledContracts(rules)) } // ActivePrecompiles returns the precompile addresses enabled with the current configuration. diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 8ae9d0907500..bfeedc3999f6 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -958,6 +958,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if config != 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 diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 391e6393be19..9df95ef070f3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -21,6 +21,7 @@ import ( "encoding/hex" "errors" "fmt" + "maps" "math/big" "strings" "time" @@ -1164,7 +1165,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S blockOverrides.Apply(&blockCtx) } rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time) - precompiles := vm.ActivePrecompiledContracts(rules).Copy() + precompiles := maps.Clone(vm.ActivePrecompiledContracts(rules)) if err := overrides.Apply(state, precompiles); err != nil { return nil, err } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 7a0d6f2bc3eb..8a73e606db3c 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "math/big" "time" @@ -282,7 +283,7 @@ func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, sim.b), nil) rules = sim.chainConfig.Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) ) - return vm.ActivePrecompiledContracts(rules).Copy() + return maps.Clone(vm.ActivePrecompiledContracts(rules)) } // sanitizeChain checks the chain integrity. Specifically it checks that From c216ef56c37ee66f6aa0e0ebb55742771251d785 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 4 Sep 2024 14:54:46 +0200 Subject: [PATCH 116/119] internal: refactored ToTransaction of transaction args --- eth/tracers/api.go | 2 +- internal/ethapi/api.go | 16 ++++++++-------- internal/ethapi/simulate.go | 2 +- internal/ethapi/transaction_args.go | 23 ++++++++++++++++++----- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index bfeedc3999f6..a8289512069b 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -970,7 +970,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc } var ( msg = args.ToMessage(vmctx.BaseFee, true, true) - tx = args.ToTransaction(args.GasPrice == nil) + tx = args.ToTransaction(types.LegacyTxType) traceConfig *TraceConfig ) // Lower the basefee to 0 to avoid breaking EVM diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9df95ef070f3..7cdc265a8b55 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -475,7 +475,7 @@ func (api *PersonalAccountAPI) signTransaction(ctx context.Context, args *Transa return nil, err } // Assemble the transaction and sign with the wallet - tx := args.ToTransaction(false) + tx := args.ToTransaction(types.LegacyTxType) return wallet.SignTxWithPassphrase(account, passwd, tx, api.b.ChainConfig().ChainID) } @@ -524,7 +524,7 @@ func (api *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transac return nil, errors.New("nonce not specified") } // Before actually signing the transaction, ensure the transaction fee is reasonable. - tx := args.ToTransaction(false) + tx := args.ToTransaction(types.LegacyTxType) if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil { return nil, err } @@ -1676,7 +1676,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) if err != nil { - return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(false).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) { return accessList, res.UsedGas, res.Err, nil @@ -1945,7 +1945,7 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction return common.Hash{}, err } // Assemble the transaction and sign with the wallet - tx := args.ToTransaction(false) + tx := args.ToTransaction(types.LegacyTxType) signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID) if err != nil { @@ -1965,7 +1965,7 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction return nil, err } // Assemble the transaction and obtain rlp - tx := args.ToTransaction(false) + tx := args.ToTransaction(types.LegacyTxType) data, err := tx.MarshalBinary() if err != nil { return nil, err @@ -2033,7 +2033,7 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction return nil, err } // Before actually sign the transaction, ensure the transaction fee is reasonable. - tx := args.ToTransaction(false) + tx := args.ToTransaction(types.LegacyTxType) if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil { return nil, err } @@ -2091,7 +2091,7 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, if err := sendArgs.setDefaults(ctx, api.b, false); err != nil { return common.Hash{}, err } - matchTx := sendArgs.ToTransaction(false) + matchTx := sendArgs.ToTransaction(types.LegacyTxType) // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. var price = matchTx.GasPrice() @@ -2121,7 +2121,7 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(false)) + signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType)) if err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 8a73e606db3c..a991a176a36e 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -197,7 +197,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil { return nil, nil, err } - tx := call.ToTransaction(call.GasPrice == nil) + tx := call.ToTransaction(types.DynamicFeeTxType) txes[i] = tx tracer.reset(tx.Hash(), uint(i)) // EoA check is always skipped, even in validation mode. diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 92cb9c532758..f9835a96dabf 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -471,10 +471,23 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA // ToTransaction converts the arguments to a transaction. // This assumes that setDefaults has been called. -func (args *TransactionArgs) ToTransaction(type2 bool) *types.Transaction { - var data types.TxData +func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction { + usedType := types.LegacyTxType 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{} if args.AccessList != nil { al = *args.AccessList @@ -500,7 +513,7 @@ func (args *TransactionArgs) ToTransaction(type2 bool) *types.Transaction { } } - case args.MaxFeePerGas != nil || type2: + case types.DynamicFeeTxType: al := types.AccessList{} if args.AccessList != nil { al = *args.AccessList @@ -517,7 +530,7 @@ func (args *TransactionArgs) ToTransaction(type2 bool) *types.Transaction { AccessList: al, } - case args.AccessList != nil: + case types.AccessListTxType: data = &types.AccessListTx{ To: args.To, ChainID: (*big.Int)(args.ChainID), From 62058060cb3ab7548ad04c132f3b653b2be14c64 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 5 Sep 2024 11:17:11 +0200 Subject: [PATCH 117/119] internal/ethapi: simplify --- internal/ethapi/simulate.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index a991a176a36e..996f600d77ef 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -169,7 +169,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if block.BlockOverrides.BlobBaseFee != nil { blockContext.BlobBaseFee = block.BlockOverrides.BlobBaseFee.ToInt() } - precompiles := sim.activePrecompiles(ctx, sim.base) + 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 @@ -278,10 +278,10 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, return nil } -func (sim *simulator) activePrecompiles(ctx context.Context, base *types.Header) vm.PrecompiledContracts { +func (sim *simulator) activePrecompiles(base *types.Header) vm.PrecompiledContracts { var ( - blockContext = core.NewEVMBlockContext(base, NewChainContext(ctx, sim.b), nil) - rules = sim.chainConfig.Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) + isMerge = (base.Difficulty.Sign() == 0) + rules = sim.chainConfig.Rules(base.Number, isMerge, base.Time) ) return maps.Clone(vm.ActivePrecompiledContracts(rules)) } From 098a448cd8a1b1111c45a0a1fc47d344760ebd3a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 5 Sep 2024 12:39:19 +0200 Subject: [PATCH 118/119] internal/ethapi: abort when context is cancelled --- internal/ethapi/simulate.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 996f600d77ef..bccf87f8efea 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -96,6 +96,9 @@ type simulator struct { // 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() @@ -194,6 +197,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, 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 } From f25fcd34e11061568c187c44dd02fb6e9c8546de Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 6 Sep 2024 09:43:55 +0200 Subject: [PATCH 119/119] internal/ethapi: fix merge-conflicts --- internal/ethapi/api_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 5648e06287c6..f2dd93e4ac0a 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -24,6 +24,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "math/big" "os" "path/filepath" @@ -33,10 +34,6 @@ import ( "testing" "time" - "github.com/holiman/uint256" - "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -58,6 +55,9 @@ import ( "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/triedb" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" ) func testTransactionMarshal(t *testing.T, tests []txData, config *params.ChainConfig) { @@ -3291,8 +3291,8 @@ func (p *precompileContract) RequiredGas(input []byte) uint64 { return 0 } func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil } func TestStateOverrideMovePrecompile(t *testing.T) { - db := state.NewDatabase(rawdb.NewMemoryDatabase()) - statedb, err := state.New(common.Hash{}, db, nil) + db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) + statedb, err := state.New(common.Hash{}, db) if err != nil { t.Fatalf("failed to create statedb: %v", err) }