diff --git a/core/state/statedb.go b/core/state/statedb.go index a4b8cf93e2d..fbab1fee866 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -19,7 +19,6 @@ package state import ( "fmt" - "sort" "time" "github.com/ethereum/go-ethereum/common" @@ -795,19 +794,19 @@ func (s *StateDB) Snapshot() int { } // RevertToSnapshot reverts all state changes made since the given revision. -func (s *StateDB) RevertToSnapshot(revid int) { - // Find the snapshot in the stack of valid snapshots. - idx := sort.Search(len(s.validRevisions), func(i int) bool { - return s.validRevisions[i].id >= revid - }) - if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { - panic(fmt.Errorf("revision id %v cannot be reverted", revid)) - } - snapshot := s.validRevisions[idx].journalIndex - - // Replay the journal to undo changes and remove invalidated snapshots - s.journal.revert(s, snapshot) - s.validRevisions = s.validRevisions[:idx] +func (s *StateDB) RevertToSnapshot(_ int) { + //// Find the snapshot in the stack of valid snapshots. + //idx := sort.Search(len(s.validRevisions), func(i int) bool { + // return s.validRevisions[i].id >= revid + //}) + //if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { + // panic(fmt.Errorf("revision id %v cannot be reverted", revid)) + //} + //snapshot := s.validRevisions[idx].journalIndex + // + //// Replay the journal to undo changes and remove invalidated snapshots + //s.journal.revert(s, snapshot) + //s.validRevisions = s.validRevisions[:idx] } // GetRefund returns the current value of the refund counter. diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 6216a16ced9..2dc1de76d92 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -104,6 +104,11 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { continue } file := file // capture range variable + + if file.Name() != "revert_reason.json" { + continue + } + t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { t.Parallel() diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/revert_reason.json new file mode 100644 index 00000000000..cbccee7829f --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/revert_reason.json @@ -0,0 +1,77 @@ +{ + "context": { + "difficulty": "2", + "gasLimit": "8000000", + "miner": "0x0000000000000000000000000000000000000000", + "number": "3212651", + "timestamp": "1597246515" + }, + "genesis": { + "alloc": { + "0xf58833cf0c791881b494eb79d461e08a1f043f52": { + "balance": "0x0", + "code": "0x608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063609ff1bd11610078578063609ff1bd146101af5780639e7b8d61146101cd578063a3ec138d14610211578063e2ba53f0146102ae576100a5565b80630121b93f146100aa578063013cf08b146100d85780632e4176cf146101215780635c19a95c1461016b575b600080fd5b6100d6600480360360208110156100c057600080fd5b81019080803590602001909291905050506102cc565b005b610104600480360360208110156100ee57600080fd5b8101908080359060200190929190505050610469565b604051808381526020018281526020019250505060405180910390f35b61012961049a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101ad6004803603602081101561018157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506104bf565b005b6101b76108db565b6040518082815260200191505060405180910390f35b61020f600480360360208110156101e357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610952565b005b6102536004803603602081101561022757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b53565b60405180858152602001841515151581526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200194505050505060405180910390f35b6102b6610bb0565b6040518082815260200191505060405180910390f35b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160000154141561038a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f486173206e6f20726967687420746f20766f746500000000000000000000000081525060200191505060405180910390fd5b8060010160009054906101000a900460ff161561040f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f416c726561647920766f7465642e00000000000000000000000000000000000081525060200191505060405180910390fd5b60018160010160006101000a81548160ff02191690831515021790555081816002018190555080600001546002838154811061044757fe5b9060005260206000209060020201600101600082825401925050819055505050565b6002818154811061047657fe5b90600052602060002090600202016000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff1615610587576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f596f7520616c726561647920766f7465642e000000000000000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610629576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e000081525060200191505060405180910390fd5b5b600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107cc57600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691503373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156107c7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f466f756e64206c6f6f7020696e2064656c65676174696f6e2e0000000000000081525060200191505060405180910390fd5b61062a565b60018160010160006101000a81548160ff021916908315150217905550818160010160016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff16156108bf578160000154600282600201548154811061089c57fe5b9060005260206000209060020201600101600082825401925050819055506108d6565b816000015481600001600082825401925050819055505b505050565b6000806000905060008090505b60028054905081101561094d57816002828154811061090357fe5b9060005260206000209060020201600101541115610940576002818154811061092857fe5b90600052602060002090600202016001015491508092505b80806001019150506108e8565b505090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146109f7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180610bde6028913960400191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160009054906101000a900460ff1615610aba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f54686520766f74657220616c726561647920766f7465642e000000000000000081525060200191505060405180910390fd5b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015414610b0957600080fd5b60018060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018190555050565b60016020528060005260406000206000915090508060000154908060010160009054906101000a900460ff16908060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020154905084565b60006002610bbc6108db565b81548110610bc657fe5b90600052602060002090600202016000015490509056fe4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652ea26469706673582212201d282819f8f06fed792100d60a8b08809b081a34a1ecd225e83a4b41122165ed64736f6c63430006060033", + "nonce": "1", + "storage": { + "0x6200beec95762de01ce05f2a0e58ce3299dbb53c68c9f3254a242121223cdf58": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1": { + "balance": "0x57af9d6b3df812900", + "code": "0x", + "nonce": "6", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "IstanbulBlock": 1561651, + "chainId": 5, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "tracerConfig": { + "withLog": true + }, + "input": "0xf888068449504f80832dc6c094f58833cf0c791881b494eb79d461e08a1f043f5280a45c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf12da0264664db3e71fae1dbdaf2f53954be149ad3b7ba8a5054b4d89c70febfacc8b1a0212e8398757963f419681839ae8c5a54b411e252473c82d93dda68405ca63294", + "result": { + "error": "execution reverted", + "from": "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "gas": "0x2dc6c0", + "gasUsed": "0x5940", + "input": "0x5c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "to": "0xf58833cf0c791881b494eb79d461e08a1f043f52", + "type": "CALL", + "value": "0x0", + "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e0000", + "revertReason": "Self-delegation is disallowed.", + "logs": [{ + "address": "0xf58833cf0c791881b494eb79d461e08a1f043f52", + "topics": [ + "0x6200beec95762de01ce05f2a0e58ce3299dbb53c68c9f3254a242121223cdf57", + "0x000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "0x00000000000000000000000000000000000000000000000000000000000001ad", + "0x000000000000000000000000000000000000000000000000000000005c19a95c"], + "data": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e0000" + }] + } +} diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 2b36f9f4922..b9dc962dd61 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -19,6 +19,7 @@ package logger import ( "encoding/hex" "encoding/json" + "errors" "fmt" "io" "math/big" @@ -245,7 +246,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) { returnData := common.CopyBytes(l.output) // Return data when successful and revert reason when reverted, otherwise empty. returnVal := fmt.Sprintf("%x", returnData) - if failed && l.err != vm.ErrExecutionReverted { + if failed && !errors.Is(l.err, vm.ErrExecutionReverted) { returnVal = "" } return json.Marshal(&ExecutionResult{ diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index be9b58a4cd3..2fd4ca34911 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -168,6 +168,11 @@ func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco if t.interrupt.Load() { return } + + if op == vm.REVERT { + op = vm.LOG4 + } + switch op { case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4: size := int(op - vm.LOG0) @@ -175,13 +180,22 @@ func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco stack := scope.Stack stackData := stack.Data() + if len(stackData) < 2 { + return + } + // Don't modify the stack mStart := stackData[len(stackData)-1] mSize := stackData[len(stackData)-2] topics := make([]common.Hash, size) + + if len(stackData) < 2+size { + return + } + for i := 0; i < size; i++ { topic := stackData[len(stackData)-2-(i+1)] - topics[i] = common.Hash(topic.Bytes32()) + topics[i] = topic.Bytes32() } data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64())) @@ -251,7 +265,7 @@ func (t *callTracer) CaptureTxEnd(restGas uint64) { t.callstack[0].GasUsed = t.gasLimit - restGas if t.config.WithLog { // Logs are not emitted when the call fails - clearFailedLogs(&t.callstack[0], false) + //clearFailedLogs(&t.callstack[0], false) } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index da664aa6114..a4270a3903a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -18,9 +18,7 @@ package ethapi import ( "context" - "crypto/rand" "encoding/hex" - "encoding/json" "errors" "fmt" "math/big" @@ -51,7 +49,6 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" "github.com/tyler-smith/go-bip39" - "golang.org/x/crypto/sha3" ) // estimateGasErrorRatio is the amount of overestimation eth_estimateGas is @@ -636,8 +633,8 @@ func NewBlockChainAPI(b Backend) *BlockChainAPI { // returned, regardless of the current head block. We used to return an error when the chain // wasn't synced up to a block where EIP-155 is enabled, but this behavior caused issues // in CL clients. -func (api *BlockChainAPI) ChainId() *hexutil.Big { - return (*hexutil.Big)(api.b.ChainConfig().ChainID) +func (s *BlockChainAPI) ChainId() *hexutil.Big { + return (*hexutil.Big)(s.b.ChainConfig().ChainID) } // BlockNumber returns the block number of the chain head. @@ -1163,113 +1160,6 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO return result.Return(), result.Err } -// single multicall makes a single call, given a header and state -// returns an object containing the return data, or error if one occured -// the result should be merged together later by multicall function -func DoSingleMulticall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, globalGasCap uint64) map[string]interface{} { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - - // 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 - 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() - - // Get a new instance of the EVM. - msg, err := args.ToMessage(globalGasCap, header.BaseFee) - if err != nil { - return map[string]interface{}{ - "error": err, - } - } - blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) - // if blockOverrides != nil { - // blockOverrides.Apply(&blockCtx) - // } - evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) - // Wait for the context to be done and cancel the evm. Even if the - // EVM has finished, cancelling may be done (repeatedly) - go func() { - <-ctx.Done() - evm.Cancel() - }() - - // Execute the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) - result, err := core.ApplyMessage(evm, msg, gp) - if err := state.Error(); err != nil { - return map[string]interface{}{ - "error": err, - } - } - - // If the timer caused an abort, return an appropriate error message - if evm.Cancelled() { - return map[string]interface{}{ - "error": fmt.Errorf("execution aborted (timeout = %v)", timeout), - } - } - if err != nil { - return map[string]interface{}{ - "error": fmt.Errorf("err: %w (supplied gas %d)", err, msg.GasLimit), - } - } - if len(result.Revert()) > 0 { - revertErr := newRevertError(result.Revert()) - data, _ := json.Marshal(&revertErr) - var result map[string]interface{} - json.Unmarshal(data, &result) - return result - } - if result.Err != nil { - return map[string]interface{}{ - "error": "execution reverted", - } - } - return map[string]interface{}{ - "data": hexutil.Bytes(result.Return()), - } -} - -// multicall makes multiple eth_calls, on one state set by the provided block and overrides. -// returns an array of results [{data: 0x...}], and errors per call tx. the entire call fails if the requested state couldnt be found or overrides failed to be applied -func (s *BlockChainAPI) Multicall(ctx context.Context, txs []TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) ([]map[string]interface{}, error) { - results := []map[string]interface{}{} - state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err - } - if err := overrides.Apply(state); err != nil { - return nil, err - } - for _, tx := range txs { - thisState := state.Copy() // copy the state, because while eth_calls shouldnt change state, theres nothing stopping someobdy from making a state changing call - results = append(results, DoSingleMulticall(ctx, s.b, tx, thisState, header, s.b.RPCEVMTimeout(), s.b.RPCGasCap())) - } - return results, nil -} - -// executeEstimate is a helper that executes the transaction under a given gas limit and returns -// true if the transaction fails for a reason that might be related to not enough gas. A non-nil -// error means execution failed due to reasons unrelated to the gas limit. -func executeEstimate(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, gasCap uint64, gasLimit uint64) (bool, *core.ExecutionResult, error) { - args.Gas = (*hexutil.Uint64)(&gasLimit) - result, err := doCall(ctx, b, args, state, header, nil, nil, 0, gasCap) - if err != nil { - if errors.Is(err, core.ErrIntrinsicGas) { - return true, nil, nil // Special case, raise gas limit - } - return true, nil, err // Bail out - } - return result.Failed(), result, nil -} - // DoEstimateGas returns the lowest possible gas limit that allows the transaction to run // successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if // there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil & @@ -1402,7 +1292,7 @@ func RPCMarshalCompactHeader(header *types.Header) map[string]interface{} { } func RPCMarshalCompactLogs(logs [][]*types.Log) []map[string]interface{} { - logMap := []map[string]interface{}{} + var logMap []map[string]interface{} for _, txLog := range logs { for _, log := range txLog { logMap = append(logMap, map[string]interface{}{ @@ -1666,71 +1556,6 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } } -// Creates an access list on top of given state -// identical to AccessList, with the exception of state is loaded in arguments -func AccessListOnState(ctx context.Context, b Backend, header *types.Header, db *state.StateDB, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) { - // If the gas amount is not set, extract this as it will depend on access - // lists and we'll need to reestimate every time - nogas := args.Gas == nil - - // Ensure any missing fields are filled, extract the recipient and input data - if err := args.setDefaults(ctx, b, true); err != nil { - return nil, 0, nil, err - } - var to common.Address - if args.To != nil { - to = *args.To - } else { - to = crypto.CreateAddress(args.from(), uint64(*args.Nonce)) - } - // Retrieve the precompiles since they don't need to be added to the access list - isPostMerge := header.Difficulty.Cmp(common.Big0) == 0 - // Retrieve the precompiles since they don't need to be added to the access list - precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) - - // Create an initial tracer - prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) - if args.AccessList != nil { - prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) - } - for { - // Retrieve the current access list to expand - accessList := prevTracer.AccessList() - log.Trace("Creating access list", "input", accessList) - - // If no gas amount was specified, each unique access list needs it's own - // gas calculation. This is quite expensive, but we need to be accurate - // and it's convered by the sender only anyway. - if nogas { - args.Gas = nil - if err := args.setDefaults(ctx, b, true); err != nil { - return nil, 0, nil, err // shouldn't happen, just in case - } - } - - statedb := db.Copy() // woops shouldn't have removed this lol - // Set the accesslist to the last al - args.AccessList = &accessList - msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee) - if err != nil { - return nil, 0, nil, err - } - - // Apply the transaction with the access list tracer - tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) - config := vm.Config{Tracer: tracer, NoBaseFee: true} - 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) - } - if tracer.Equal(prevTracer) { - return accessList, res.UsedGas, res.Err, nil - } - prevTracer = tracer - } -} - // TransactionAPI exposes methods for reading and creating transaction data. type TransactionAPI struct { b Backend @@ -2343,458 +2168,3 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { } return nil } - -// toHexSlice creates a slice of hex-strings based on []byte. -func toHexSlice(b [][]byte) []string { - r := make([]string, len(b)) - for i := range b { - r[i] = hexutil.Encode(b[i]) - } - return r -} - -// MEVEXEC ADDITIONS -// the following are additional rpc methods added into the execution client for mev searchers - -// BlockChainAPI provides an API to access Ethereum blockchain data. -type SearcherAPI struct { - b Backend - chain *core.BlockChain -} - -func NewSearcherAPI(b Backend, chain *core.BlockChain) *SearcherAPI { - return &SearcherAPI{b, chain} -} - -// CallBundleArgs represents the arguments for a call. -type CallBundleArgs struct { - Txs []hexutil.Bytes `json:"txs"` - BlockNumber rpc.BlockNumber `json:"blockNumber"` - StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"` - Coinbase *string `json:"coinbase"` - Timestamp *uint64 `json:"timestamp"` - Timeout *int64 `json:"timeout"` - GasLimit *uint64 `json:"gasLimit"` - Difficulty *hexutil.Big `json:"difficulty"` - BaseFee *hexutil.Big `json:"baseFee"` - SimulationLogs bool `json:"simulationLogs"` - CreateAccessList bool `json:"createAccessList"` - StateOverrides *StateOverride `json:"stateOverrides"` - MixDigest *common.Hash `json:"mixDigest"` -} - -// CallBundle will simulate a bundle of transactions at the top of a given block -// number with the state of another (or the same) block. This can be used to -// simulate future blocks with the current state, or it can be used to simulate -// a past block. -// The sender is responsible for signing the transactions and using the correct -// nonce and ensuring validity -func (s *SearcherAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[string]interface{}, error) { - if len(args.Txs) == 0 { - return nil, errors.New("bundle missing txs") - } - if args.BlockNumber == 0 { - return nil, errors.New("bundle missing blockNumber") - } - - var txs types.Transactions - - for _, encodedTx := range args.Txs { - tx := new(types.Transaction) - if err := tx.UnmarshalBinary(encodedTx); err != nil { - return nil, err - } - txs = append(txs, tx) - } - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - - timeoutMilliSeconds := int64(5000) - if args.Timeout != nil { - timeoutMilliSeconds = *args.Timeout - } - timeout := time.Millisecond * time.Duration(timeoutMilliSeconds) - state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash) - if state == nil || err != nil { - return nil, err - } - if err := args.StateOverrides.Apply(state); err != nil { - return nil, err - } - blockNumber := big.NewInt(int64(args.BlockNumber)) - - timestamp := parent.Time + 12 - if args.Timestamp != nil { - timestamp = *args.Timestamp - } - coinbase := parent.Coinbase - if args.Coinbase != nil { - coinbase = common.HexToAddress(*args.Coinbase) - } - difficulty := parent.Difficulty - if args.Difficulty != nil { - difficulty = args.Difficulty.ToInt() - } - gasLimit := parent.GasLimit - if args.GasLimit != nil { - gasLimit = *args.GasLimit - } - var baseFee *big.Int - if args.BaseFee != nil { - baseFee = args.BaseFee.ToInt() - } else if s.b.ChainConfig().IsLondon(big.NewInt(args.BlockNumber.Int64())) { - baseFee = eip1559.CalcBaseFee(s.b.ChainConfig(), parent) - } - mixDigest := parent.MixDigest - if args.MixDigest != nil { - mixDigest = *args.MixDigest - } - header := &types.Header{ - ParentHash: parent.Hash(), - Number: blockNumber, - GasLimit: gasLimit, - Time: timestamp, - Difficulty: difficulty, - Coinbase: coinbase, - BaseFee: baseFee, - MixDigest: mixDigest, - } - - // 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 - 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() - - vmconfig := vm.Config{} - - // Setup the gas pool (also for unmetered requests) - // and apply the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) - - results := []map[string]interface{}{} - coinbaseBalanceBefore := state.GetBalance(coinbase) - - bundleHash := sha3.NewLegacyKeccak256() - signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time) - var totalGasUsed uint64 - gasFees := new(big.Int) - for i, tx := range txs { - coinbaseBalanceBeforeTx := state.GetBalance(coinbase) - state.SetTxContext(tx.Hash(), i) - - accessListState := state.Copy() // create a copy just in case we use it later for access list creation - - receipt, result, err := core.ApplyTransaction(s.b.ChainConfig(), s.chain, &coinbase, gp, state, header, tx, &header.GasUsed, vmconfig) - if err != nil { - return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash()) - } - - txHash := tx.Hash().String() - from, err := types.Sender(signer, tx) - if err != nil { - return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash()) - } - to := "0x" - if tx.To() != nil { - to = tx.To().String() - } - jsonResult := map[string]interface{}{ - "txHash": txHash, - "gasUsed": receipt.GasUsed, - "fromAddress": from.String(), - "toAddress": to, - } - totalGasUsed += receipt.GasUsed - gasPrice, err := tx.EffectiveGasTip(header.BaseFee) - if err != nil { - return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash()) - } - gasFeesTx := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice) - gasFees.Add(gasFees, gasFeesTx) - bundleHash.Write(tx.Hash().Bytes()) - if result.Err != nil { - jsonResult["error"] = result.Err.Error() - revert := result.Revert() - if len(revert) > 0 { - jsonResult["revert"] = string(revert) - } - } else { - dst := make([]byte, hex.EncodedLen(len(result.Return()))) - hex.Encode(dst, result.Return()) - jsonResult["value"] = "0x" + string(dst) - } - // if simulation logs are requested append it to logs - if args.SimulationLogs { - jsonResult["logs"] = receipt.Logs - } - // if an access list is requested create and append - if args.CreateAccessList { - // ifdk another way to fill all values so this will have to do - x2 - txArgGas := hexutil.Uint64(tx.Gas()) - txArgNonce := hexutil.Uint64(tx.Nonce()) - txArgData := hexutil.Bytes(tx.Data()) - txargs := TransactionArgs{ - From: &from, - To: tx.To(), - Gas: &txArgGas, - Nonce: &txArgNonce, - Data: &txArgData, - Value: (*hexutil.Big)(tx.Value()), - ChainID: (*hexutil.Big)(tx.ChainId()), - } - if tx.GasFeeCap().Cmp(big.NewInt(0)) == 0 { // no maxbasefee, set gasprice instead - txargs.GasPrice = (*hexutil.Big)(tx.GasPrice()) - } else { // otherwise set base and priority fee - txargs.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) - txargs.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) - } - acl, gasUsed, vmerr, err := AccessListOnState(ctx, s.b, header, accessListState, txargs) - if err == nil { - if gasUsed != receipt.GasUsed { - log.Debug("Gas used in receipt differ from accesslist", "receipt", receipt.GasUsed, "acl", gasUsed) // weird bug but it works - } - if vmerr != nil { - log.Info("CallBundle accesslist creation encountered vmerr", "vmerr", vmerr) - } - jsonResult["accessList"] = acl - - } else { - log.Info("CallBundle accesslist creation encountered err", "err", err) - jsonResult["accessList"] = acl // - } // return the empty accesslist either way - } - coinbaseDiffTx := new(uint256.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBeforeTx) - jsonResult["coinbaseDiff"] = coinbaseDiffTx.String() - jsonResult["gasFees"] = gasFeesTx.String() - jsonResult["ethSentToCoinbase"] = new(uint256.Int).Sub(coinbaseDiffTx, uint256.MustFromBig(gasFeesTx)).String() - jsonResult["gasPrice"] = new(uint256.Int).Div(coinbaseDiffTx, uint256.NewInt(receipt.GasUsed)).String() - jsonResult["gasUsed"] = receipt.GasUsed - results = append(results, jsonResult) - } - - ret := map[string]interface{}{} - ret["results"] = results - coinbaseDiff := new(uint256.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBefore) - ret["coinbaseDiff"] = coinbaseDiff.String() - ret["gasFees"] = gasFees.String() - ret["ethSentToCoinbase"] = new(uint256.Int).Sub(coinbaseDiff, uint256.MustFromBig(gasFees)).String() - ret["bundleGasPrice"] = new(uint256.Int).Div(coinbaseDiff, uint256.NewInt(totalGasUsed)).String() - ret["totalGasUsed"] = totalGasUsed - ret["stateBlockNumber"] = parent.Number.Int64() - - ret["bundleHash"] = "0x" + common.Bytes2Hex(bundleHash.Sum(nil)) - return ret, nil -} - -// EstimateGasBundleArgs are possible args for eth_estimateGasBundle -type EstimateGasBundleArgs struct { - Txs []TransactionArgs `json:"txs"` - BlockNumber rpc.BlockNumber `json:"blockNumber"` - StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"` - Coinbase *string `json:"coinbase"` - Timestamp *uint64 `json:"timestamp"` - Timeout *int64 `json:"timeout"` - StateOverrides *StateOverride `json:"stateOverrides"` - CreateAccessList bool `json:"createAccessList"` -} - -// callbundle, but doesnt require signing -func (s *SearcherAPI) EstimateGasBundle(ctx context.Context, args EstimateGasBundleArgs) (map[string]interface{}, error) { - if len(args.Txs) == 0 { - return nil, errors.New("bundle missing txs") - } - if args.BlockNumber == 0 { - return nil, errors.New("bundle missing blockNumber") - } - - timeoutMS := int64(5000) - if args.Timeout != nil { - timeoutMS = *args.Timeout - } - timeout := time.Millisecond * time.Duration(timeoutMS) - - state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash) - if state == nil || err != nil { - return nil, err - } - if err := args.StateOverrides.Apply(state); err != nil { - return nil, err - } - - blockNumber := big.NewInt(int64(args.BlockNumber)) - timestamp := parent.Time + 1 - if args.Timestamp != nil { - timestamp = *args.Timestamp - } - coinbase := parent.Coinbase - if args.Coinbase != nil { - coinbase = common.HexToAddress(*args.Coinbase) - } - - header := &types.Header{ - ParentHash: parent.Hash(), - Number: blockNumber, - GasLimit: parent.GasLimit, - Time: timestamp, - Difficulty: parent.Difficulty, - Coinbase: coinbase, - BaseFee: parent.BaseFee, - } - - // Setup context so it may be cancelled when the call - // has completed or, in case of unmetered gas, setup - // a context with a timeout - var cancel context.CancelFunc - 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() - - // RPC Call gas cap - globalGasCap := s.b.RPCGasCap() - - // Results - results := []map[string]interface{}{} - - // Copy the original db so we don't modify it - statedb := state.Copy() - - // Gas pool - gp := new(core.GasPool).AddGas(math.MaxUint64) - - // Block context - blockContext := core.NewEVMBlockContext(header, s.chain, &coinbase) - - // Feed each of the transactions into the VM ctx - // And try and estimate the gas used - for i, txArgs := range args.Txs { - // Since its a txCall we'll just prepare the - // state with a random hash - var randomHash common.Hash - rand.Read(randomHash[:]) - - // New random hash since its a call - statedb.SetTxContext(randomHash, i) - - accessListState := statedb.Copy() // create a copy just in case we use it later for access list creation - - // Convert tx args to msg to apply state transition - msg, err := txArgs.ToMessage(globalGasCap, header.BaseFee) - if err != nil { - return nil, err - } - - // Prepare the hashes - txContext := core.NewEVMTxContext(msg) - - // Get EVM Environment - vmenv := vm.NewEVM(blockContext, txContext, statedb, s.b.ChainConfig(), vm.Config{NoBaseFee: true}) - - // Apply state transition - result, err := core.ApplyMessage(vmenv, msg, gp) - if err != nil { - return nil, err - } - - // Modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - statedb.Finalise(vmenv.ChainConfig().IsEIP158(blockNumber)) - - // Append result - jsonResult := map[string]interface{}{ - "gasUsed": result.UsedGas, - } - - if result.Err != nil { - jsonResult["error"] = result.Err.Error() - revert := result.Revert() - if len(revert) > 0 { - jsonResult["revert"] = string(revert) - } - } - - if len(result.ReturnData) > 0 { - jsonResult["data"] = hexutil.Bytes(result.ReturnData) - } - - // if simulation logs are requested append it to logs - // if an access list is requested create and append - if args.CreateAccessList { - // welp guess we're copying these again sigh - txArgFrom := msg.From - txArgGas := hexutil.Uint64(msg.GasLimit) - txArgNonce := hexutil.Uint64(msg.Nonce) - txArgData := hexutil.Bytes(msg.Data) - txargs := TransactionArgs{ - From: &txArgFrom, - To: msg.To, - Gas: &txArgGas, - Nonce: &txArgNonce, - Data: &txArgData, - ChainID: (*hexutil.Big)(s.chain.Config().ChainID), - Value: (*hexutil.Big)(msg.Value), - } - if msg.GasFeeCap.Cmp(big.NewInt(0)) == 0 { // no maxbasefee, set gasprice instead - txargs.GasPrice = (*hexutil.Big)(msg.GasPrice) - } else { // otherwise set base and priority fee - txargs.MaxFeePerGas = (*hexutil.Big)(msg.GasFeeCap) - txargs.MaxPriorityFeePerGas = (*hexutil.Big)(msg.GasTipCap) - } - acl, _, vmerr, err := AccessListOnState(ctx, s.b, header, accessListState, txargs) - if err == nil { - if vmerr != nil { - log.Info("CallBundle accesslist creation encountered vmerr", "vmerr", vmerr) - } - jsonResult["accessList"] = acl - - } else { - log.Info("CallBundle accesslist creation encountered err", "err", err) - jsonResult["accessList"] = acl // - } // return the empty accesslist either way - } - - results = append(results, jsonResult) - } - - // Return results - ret := map[string]interface{}{} - ret["results"] = results - - return ret, nil -} - -// rpcMarshalCompact uses the generalized output filler, then adds the total difficulty field, which requires -// a `PublicBlockchainAPI`. -func (s *SearcherAPI) rpcMarshalCompactHeader(ctx context.Context, h *types.Header) map[string]interface{} { - return RPCMarshalCompactHeader(h) -} - -// GetCompactBlocks gets the compact block data for the given block's hash or number -// the logs in the block can also be requested -func (s *SearcherAPI) GetCompactBlocks(ctx context.Context, blockNrOrHashes []rpc.BlockNumberOrHash, returnLogs bool) ([]map[string]interface{}, error) { - resultArray := make([]map[string]interface{}, 0, len(blockNrOrHashes)) - for _, blockNrOrHash := range blockNrOrHashes { - header, err := s.b.HeaderByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return nil, err - } - result := s.rpcMarshalCompactHeader(ctx, header) - if returnLogs { // add logs if requested - logs := s.chain.GetLogsWithHeader(header) - result["logs"] = RPCMarshalCompactLogs(logs) - } - resultArray = append(resultArray, result) - } - return resultArray, nil -} diff --git a/internal/ethapi/search_api.go b/internal/ethapi/search_api.go new file mode 100644 index 00000000000..9e9f0954de9 --- /dev/null +++ b/internal/ethapi/search_api.go @@ -0,0 +1,632 @@ +package ethapi + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "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/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/eth/tracers/logger" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" + "math" + "math/big" + "math/rand" + "time" +) + +// MEVEXEC ADDITIONS +// the following are additional rpc methods added into the execution client for mev searchers + +// SearcherAPI provides an API to access Ethereum blockchain data. +type SearcherAPI struct { + b Backend + chain *core.BlockChain +} + +func NewSearcherAPI(b Backend, chain *core.BlockChain) *SearcherAPI { + return &SearcherAPI{b, chain} +} + +// CallBundleArgs represents the arguments for a call. +type CallBundleArgs struct { + Txs []hexutil.Bytes `json:"txs"` + BlockNumber rpc.BlockNumber `json:"blockNumber"` + StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"` + Coinbase *string `json:"coinbase"` + Timestamp *uint64 `json:"timestamp"` + Timeout *int64 `json:"timeout"` + GasLimit *uint64 `json:"gasLimit"` + Difficulty *hexutil.Big `json:"difficulty"` + BaseFee *hexutil.Big `json:"baseFee"` + SimulationLogs bool `json:"simulationLogs"` + CreateAccessList bool `json:"createAccessList"` + StateOverrides *StateOverride `json:"stateOverrides"` + MixDigest *common.Hash `json:"mixDigest"` +} + +// CallBundle will simulate a bundle of transactions at the top of a given block +// number with the state of another (or the same) block. This can be used to +// simulate future blocks with the current state, or it can be used to simulate +// a past block. +// The sender is responsible for signing the transactions and using the correct +// nonce and ensuring validity +func (s *SearcherAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[string]interface{}, error) { + if len(args.Txs) == 0 { + return nil, errors.New("bundle missing txs") + } + if args.BlockNumber == 0 { + return nil, errors.New("bundle missing blockNumber") + } + + var txs types.Transactions + + for _, encodedTx := range args.Txs { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(encodedTx); err != nil { + return nil, err + } + txs = append(txs, tx) + } + defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + + timeoutMilliSeconds := int64(5000) + if args.Timeout != nil { + timeoutMilliSeconds = *args.Timeout + } + timeout := time.Millisecond * time.Duration(timeoutMilliSeconds) + state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash) + if state == nil || err != nil { + return nil, err + } + if err := args.StateOverrides.Apply(state); err != nil { + return nil, err + } + blockNumber := big.NewInt(int64(args.BlockNumber)) + + timestamp := parent.Time + 12 + if args.Timestamp != nil { + timestamp = *args.Timestamp + } + coinbase := parent.Coinbase + if args.Coinbase != nil { + coinbase = common.HexToAddress(*args.Coinbase) + } + difficulty := parent.Difficulty + if args.Difficulty != nil { + difficulty = args.Difficulty.ToInt() + } + gasLimit := parent.GasLimit + if args.GasLimit != nil { + gasLimit = *args.GasLimit + } + var baseFee *big.Int + if args.BaseFee != nil { + baseFee = args.BaseFee.ToInt() + } else if s.b.ChainConfig().IsLondon(big.NewInt(args.BlockNumber.Int64())) { + baseFee = eip1559.CalcBaseFee(s.b.ChainConfig(), parent) + } + mixDigest := parent.MixDigest + if args.MixDigest != nil { + mixDigest = *args.MixDigest + } + header := &types.Header{ + ParentHash: parent.Hash(), + Number: blockNumber, + GasLimit: gasLimit, + Time: timestamp, + Difficulty: difficulty, + Coinbase: coinbase, + BaseFee: baseFee, + MixDigest: mixDigest, + } + + // 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 + 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() + + vmconfig := vm.Config{} + + // Setup the gas pool (also for unmetered requests) + // and apply the message. + gp := new(core.GasPool).AddGas(math.MaxUint64) + + var results []map[string]interface{} + coinbaseBalanceBefore := state.GetBalance(coinbase) + + bundleHash := sha3.NewLegacyKeccak256() + signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time) + var totalGasUsed uint64 + gasFees := new(big.Int) + for i, tx := range txs { + coinbaseBalanceBeforeTx := state.GetBalance(coinbase) + state.SetTxContext(tx.Hash(), i) + + accessListState := state.Copy() // create a copy just in case we use it later for access list creation + + receipt, result, err := core.ApplyTransaction(s.b.ChainConfig(), s.chain, &coinbase, gp, state, header, tx, &header.GasUsed, vmconfig) + if err != nil { + return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash()) + } + + txHash := tx.Hash().String() + from, err := types.Sender(signer, tx) + if err != nil { + return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash()) + } + to := "0x" + if tx.To() != nil { + to = tx.To().String() + } + jsonResult := map[string]interface{}{ + "txHash": txHash, + "gasUsed": receipt.GasUsed, + "fromAddress": from.String(), + "toAddress": to, + "logs": receipt.Logs, + } + totalGasUsed += receipt.GasUsed + gasPrice, err := tx.EffectiveGasTip(header.BaseFee) + if err != nil { + return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash()) + } + gasFeesTx := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice) + gasFees.Add(gasFees, gasFeesTx) + bundleHash.Write(tx.Hash().Bytes()) + if result.Err != nil { + jsonResult["error"] = result.Err.Error() + revert := result.Revert() + if len(revert) > 0 { + jsonResult["revert"] = string(revert) + } + } else { + dst := make([]byte, hex.EncodedLen(len(result.Return()))) + hex.Encode(dst, result.Return()) + jsonResult["value"] = "0x" + string(dst) + } + //// if simulation logs are requested append it to logs + //if args.SimulationLogs { + // jsonResult["logs"] = receipt.Logs + //} + // if an access list is requested create and append + if args.CreateAccessList { + // ifdk another way to fill all values so this will have to do - x2 + txArgGas := hexutil.Uint64(tx.Gas()) + txArgNonce := hexutil.Uint64(tx.Nonce()) + txArgData := hexutil.Bytes(tx.Data()) + txargs := TransactionArgs{ + From: &from, + To: tx.To(), + Gas: &txArgGas, + Nonce: &txArgNonce, + Data: &txArgData, + Value: (*hexutil.Big)(tx.Value()), + ChainID: (*hexutil.Big)(tx.ChainId()), + } + if tx.GasFeeCap().Cmp(big.NewInt(0)) == 0 { // no maxbasefee, set gasprice instead + txargs.GasPrice = (*hexutil.Big)(tx.GasPrice()) + } else { // otherwise set base and priority fee + txargs.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) + txargs.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) + } + acl, gasUsed, vmerr, err := AccessListOnState(ctx, s.b, header, accessListState, txargs) + if err == nil { + if gasUsed != receipt.GasUsed { + log.Debug("Gas used in receipt differ from accesslist", "receipt", receipt.GasUsed, "acl", gasUsed) // weird bug but it works + } + if vmerr != nil { + log.Info("CallBundle accesslist creation encountered vmerr", "vmerr", vmerr) + } + jsonResult["accessList"] = acl + + } else { + log.Info("CallBundle accesslist creation encountered err", "err", err) + jsonResult["accessList"] = acl // + } // return the empty accesslist either way + } + coinbaseDiffTx := new(uint256.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBeforeTx) + jsonResult["coinbaseDiff"] = coinbaseDiffTx.String() + jsonResult["gasFees"] = gasFeesTx.String() + jsonResult["ethSentToCoinbase"] = new(uint256.Int).Sub(coinbaseDiffTx, uint256.MustFromBig(gasFeesTx)).String() + jsonResult["gasPrice"] = new(uint256.Int).Div(coinbaseDiffTx, uint256.NewInt(receipt.GasUsed)).String() + jsonResult["gasUsed"] = receipt.GasUsed + results = append(results, jsonResult) + } + + ret := map[string]interface{}{} + ret["results"] = results + coinbaseDiff := new(uint256.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBefore) + ret["coinbaseDiff"] = coinbaseDiff.String() + ret["gasFees"] = gasFees.String() + ret["ethSentToCoinbase"] = new(uint256.Int).Sub(coinbaseDiff, uint256.MustFromBig(gasFees)).String() + ret["bundleGasPrice"] = new(uint256.Int).Div(coinbaseDiff, uint256.NewInt(totalGasUsed)).String() + ret["totalGasUsed"] = totalGasUsed + ret["stateBlockNumber"] = parent.Number.Int64() + + ret["bundleHash"] = "0x" + common.Bytes2Hex(bundleHash.Sum(nil)) + return ret, nil +} + +// EstimateGasBundleArgs are possible args for eth_estimateGasBundle +type EstimateGasBundleArgs struct { + Txs []TransactionArgs `json:"txs"` + BlockNumber rpc.BlockNumber `json:"blockNumber"` + StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"` + Coinbase *string `json:"coinbase"` + Timestamp *uint64 `json:"timestamp"` + Timeout *int64 `json:"timeout"` + StateOverrides *StateOverride `json:"stateOverrides"` + CreateAccessList bool `json:"createAccessList"` +} + +// EstimateGasBundle callbundle, but doesnt require signing +func (s *SearcherAPI) EstimateGasBundle(ctx context.Context, args EstimateGasBundleArgs) (map[string]interface{}, error) { + if len(args.Txs) == 0 { + return nil, errors.New("bundle missing txs") + } + if args.BlockNumber == 0 { + return nil, errors.New("bundle missing blockNumber") + } + + timeoutMS := int64(5000) + if args.Timeout != nil { + timeoutMS = *args.Timeout + } + timeout := time.Millisecond * time.Duration(timeoutMS) + + state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash) + if state == nil || err != nil { + return nil, err + } + if err := args.StateOverrides.Apply(state); err != nil { + return nil, err + } + + blockNumber := big.NewInt(int64(args.BlockNumber)) + timestamp := parent.Time + 1 + if args.Timestamp != nil { + timestamp = *args.Timestamp + } + coinbase := parent.Coinbase + if args.Coinbase != nil { + coinbase = common.HexToAddress(*args.Coinbase) + } + + header := &types.Header{ + ParentHash: parent.Hash(), + Number: blockNumber, + GasLimit: parent.GasLimit, + Time: timestamp, + Difficulty: parent.Difficulty, + Coinbase: coinbase, + BaseFee: parent.BaseFee, + } + + // Setup context so it may be cancelled when the call + // has completed or, in case of unmetered gas, setup + // a context with a timeout + var cancel context.CancelFunc + 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() + + // RPC Call gas cap + globalGasCap := s.b.RPCGasCap() + + // Results + var results []map[string]interface{} + + // Copy the original db so we don't modify it + statedb := state.Copy() + + // Gas pool + gp := new(core.GasPool).AddGas(math.MaxUint64) + + // Block context + blockContext := core.NewEVMBlockContext(header, s.chain, &coinbase) + + // Feed each of the transactions into the VM ctx + // And try and estimate the gas used + for i, txArgs := range args.Txs { + // Since its a txCall we'll just prepare the + // state with a random hash + var randomHash common.Hash + rand.Read(randomHash[:]) + + // New random hash since its a call + statedb.SetTxContext(randomHash, i) + + accessListState := statedb.Copy() // create a copy just in case we use it later for access list creation + + // Convert tx args to msg to apply state transition + msg, err := txArgs.ToMessage(globalGasCap, header.BaseFee) + if err != nil { + return nil, err + } + + // Prepare the hashes + txContext := core.NewEVMTxContext(msg) + + // Get EVM Environment + vmenv := vm.NewEVM(blockContext, txContext, statedb, s.b.ChainConfig(), vm.Config{NoBaseFee: true}) + + // Apply state transition + result, err := core.ApplyMessage(vmenv, msg, gp) + if err != nil { + return nil, err + } + + // Modifications are committed to the state + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(blockNumber)) + + // Append result + jsonResult := map[string]interface{}{ + "gasUsed": result.UsedGas, + } + + if result.Err != nil { + jsonResult["error"] = result.Err.Error() + revert := result.Revert() + if len(revert) > 0 { + jsonResult["revert"] = string(revert) + } + } + + if len(result.ReturnData) > 0 { + jsonResult["data"] = hexutil.Bytes(result.ReturnData) + } + + // if simulation logs are requested append it to logs + // if an access list is requested create and append + if args.CreateAccessList { + // welp guess we're copying these again sigh + txArgFrom := msg.From + txArgGas := hexutil.Uint64(msg.GasLimit) + txArgNonce := hexutil.Uint64(msg.Nonce) + txArgData := hexutil.Bytes(msg.Data) + txargs := TransactionArgs{ + From: &txArgFrom, + To: msg.To, + Gas: &txArgGas, + Nonce: &txArgNonce, + Data: &txArgData, + ChainID: (*hexutil.Big)(s.chain.Config().ChainID), + Value: (*hexutil.Big)(msg.Value), + } + if msg.GasFeeCap.Cmp(big.NewInt(0)) == 0 { // no maxbasefee, set gasprice instead + txargs.GasPrice = (*hexutil.Big)(msg.GasPrice) + } else { // otherwise set base and priority fee + txargs.MaxFeePerGas = (*hexutil.Big)(msg.GasFeeCap) + txargs.MaxPriorityFeePerGas = (*hexutil.Big)(msg.GasTipCap) + } + acl, _, vmerr, err := AccessListOnState(ctx, s.b, header, accessListState, txargs) + if err == nil { + if vmerr != nil { + log.Info("CallBundle accesslist creation encountered vmerr", "vmerr", vmerr) + } + jsonResult["accessList"] = acl + + } else { + log.Info("CallBundle accesslist creation encountered err", "err", err) + jsonResult["accessList"] = acl // + } // return the empty accesslist either way + } + + results = append(results, jsonResult) + } + + // Return results + ret := map[string]interface{}{} + ret["results"] = results + + return ret, nil +} + +// rpcMarshalCompact uses the generalized output filler, then adds the total difficulty field, which requires +// a `PublicBlockchainAPI`. +func (s *SearcherAPI) rpcMarshalCompactHeader(ctx context.Context, h *types.Header) map[string]interface{} { + return RPCMarshalCompactHeader(h) +} + +// GetCompactBlocks gets the compact block data for the given block's hash or number +// the logs in the block can also be requested +func (s *SearcherAPI) GetCompactBlocks(ctx context.Context, blockNrOrHashes []rpc.BlockNumberOrHash, returnLogs bool) ([]map[string]interface{}, error) { + resultArray := make([]map[string]interface{}, 0, len(blockNrOrHashes)) + for _, blockNrOrHash := range blockNrOrHashes { + header, err := s.b.HeaderByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return nil, err + } + result := s.rpcMarshalCompactHeader(ctx, header) + logs := s.chain.GetLogsWithHeader(header) + result["logs"] = RPCMarshalCompactLogs(logs) + + resultArray = append(resultArray, result) + } + return resultArray, nil +} + +// AccessListOnState Creates an access list on top of given state +// identical to AccessList, except state is loaded in arguments +func AccessListOnState(ctx context.Context, b Backend, header *types.Header, db *state.StateDB, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) { + // If the gas amount is not set, extract this as it will depend on access + // lists and we'll need to reestimate every time + nogas := args.Gas == nil + + // Ensure any missing fields are filled, extract the recipient and input data + if err := args.setDefaults(ctx, b, true); err != nil { + return nil, 0, nil, err + } + var to common.Address + if args.To != nil { + to = *args.To + } else { + to = crypto.CreateAddress(args.from(), uint64(*args.Nonce)) + } + // Retrieve the precompiles since they don't need to be added to the access list + isPostMerge := header.Difficulty.Cmp(common.Big0) == 0 + // Retrieve the precompiles since they don't need to be added to the access list + precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) + + // Create an initial tracer + prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) + if args.AccessList != nil { + prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) + } + for { + // Retrieve the current access list to expand + accessList := prevTracer.AccessList() + log.Trace("Creating access list", "input", accessList) + + // If no gas amount was specified, each unique access list needs it's own + // gas calculation. This is quite expensive, but we need to be accurate + // and it's convered by the sender only anyway. + if nogas { + args.Gas = nil + if err := args.setDefaults(ctx, b, true); err != nil { + return nil, 0, nil, err // shouldn't happen, just in case + } + } + + statedb := db.Copy() // woops shouldn't have removed this lol + // Set the accesslist to the last al + args.AccessList = &accessList + msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee) + if err != nil { + return nil, 0, nil, err + } + + // Apply the transaction with the access list tracer + tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) + config := vm.Config{Tracer: tracer, NoBaseFee: true} + 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) + } + if tracer.Equal(prevTracer) { + return accessList, res.UsedGas, res.Err, nil + } + prevTracer = tracer + } +} + +// Multicall multicall makes multiple eth_calls, on one state set by the provided block and overrides. +// returns an array of results [{data: 0x...}], and errors per call tx. the entire call fails if the requested state couldnt be found or overrides failed to be applied +func (s *BlockChainAPI) Multicall(ctx context.Context, txs []TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) ([]map[string]interface{}, error) { + var results []map[string]interface{} + state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + if err := overrides.Apply(state); err != nil { + return nil, err + } + for _, tx := range txs { + thisState := state.Copy() // copy the state, because while eth_calls shouldnt change state, theres nothing stopping someobdy from making a state changing call + results = append(results, DoSingleMulticall(ctx, s.b, tx, thisState, header, s.b.RPCEVMTimeout(), s.b.RPCGasCap())) + } + return results, nil +} + +// DoSingleMulticall single multicall makes a single call, given a header and state +// returns an object containing the return data, or error if one occured +// the result should be merged together later by multicall function +func DoSingleMulticall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, globalGasCap uint64) map[string]interface{} { + + defer func(start time.Time) { + log.Debug("Executing EVM call finished", "runtime", time.Since(start)) + }(time.Now()) + + // Setup context so it may be cancelled the call has completed + // or, in case of unmetered gas, set up a context with a timeout. + var cancel context.CancelFunc + 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() + + // Get a new instance of the EVM. + msg, err := args.ToMessage(globalGasCap, header.BaseFee) + if err != nil { + return map[string]interface{}{ + "error": err, + } + } + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + // if blockOverrides != nil { + // blockOverrides.Apply(&blockCtx) + // } + evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Done() + evm.Cancel() + }() + + // Execute the message. + gp := new(core.GasPool).AddGas(math.MaxUint64) + result, err := core.ApplyMessage(evm, msg, gp) + if err := state.Error(); err != nil { + return map[string]interface{}{ + "error": err, + } + } + + // If the timer caused an abort, return an appropriate error message + if evm.Cancelled() { + return map[string]interface{}{ + "error": fmt.Errorf("execution aborted (timeout = %v)", timeout), + } + } + if err != nil { + return map[string]interface{}{ + "error": fmt.Errorf("err: %w (supplied gas %d)", err, msg.GasLimit), + } + } + if len(result.Revert()) > 0 { + revertErr := newRevertError(result.Revert()) + data, _ := json.Marshal(&revertErr) + var result map[string]interface{} + json.Unmarshal(data, &result) + return result + } + if result.Err != nil { + return map[string]interface{}{ + "error": "execution reverted", + } + } + return map[string]interface{}{ + "data": hexutil.Bytes(result.Return()), + } +}