diff --git a/core/chain_makers.go b/core/chain_makers.go index 58985347bb..6c3f60a209 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -117,7 +117,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti b.SetCoinbase(common.Address{}) } b.statedb.SetTxContext(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) + receipt, _, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) if err != nil { panic(err) } diff --git a/core/state_processor.go b/core/state_processor.go index 7166ed8bd8..780c9583f1 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -167,20 +167,90 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo return receipt, err } +func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, *ExecutionResult, error) { + // Create a new context to be used in the EVM environment. + txContext := NewEVMTxContext(msg) + evm.Reset(txContext, statedb) + + // Apply the transaction to the current state (included in the env). + result, err := ApplyMessage(evm, msg, gp) + if err != nil { + return nil, nil, err + } + + // Update the state with pending changes. + var root []byte + if config.IsByzantium(blockNumber) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() + } + *usedGas += result.UsedGas + + // 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} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = result.UsedGas + + if tx.Type() == types.BlobTxType { + receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) + receipt.BlobGasPrice = evm.Context.BlobBaseFee + } + + // If the transaction created a contract, store the creation address in the receipt. + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) + } + + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.BlockHash = blockHash + receipt.BlockNumber = blockNumber + receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, result, err +} + // ApplyTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, error) { msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee) if err != nil { - return nil, err + return nil, nil, err } + + // Create a new context to be used in the EVM environment + blockContext := NewEVMBlockContext(header, bc, author) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) + return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) +} + +// This is a modified version of ApplyTransaction that allows the sender to be +// different from the signer. This is used for the "signed by other" feature. +func ApplyTransactionSignedByOther(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, originalSender common.Address) (*types.Receipt, *ExecutionResult, error) { + msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee) + if err != nil { + return nil, nil, err + } + + // Change the sender to the original sender and set the nonce accordingly + msg.From = originalSender + nonce := statedb.GetNonce(originalSender) + msg.Nonce = nonce + // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author) txContext := NewEVMTxContext(msg) vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) - return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) + return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root diff --git a/eth/backend.go b/eth/backend.go index e616b5f2f1..0e5c119489 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -318,7 +318,7 @@ func makeExtraData(extra []byte) []byte { // APIs return the collection of RPC services the ethereum package offers. // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { - apis := ethapi.GetAPIs(s.APIBackend) + apis := ethapi.GetAPIs(s.APIBackend, s.blockchain) // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8866a5b870..df944de24c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -24,6 +24,7 @@ import ( "math/big" "strings" "time" + "crypto/rand" "github.com/davecgh/go-spew/spew" "github.com/holiman/uint256" @@ -51,6 +52,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" + + "golang.org/x/crypto/sha3" ) // estimateGasErrorRatio is the amount of overestimation eth_estimateGas is @@ -2173,3 +2176,773 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { } return nil } + +// SMG additions for ninja +// many changes are from https://github.com/0x2mev/mevexec +// 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. +// TODO nick evaluate and maybe get rid of CreateAccessList, StateOverrides, MixDigest +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"` +} + +// CallBundleArgs represents the arguments for a call. +type CallBundleSignedByOther struct { + Txs []hexutil.Bytes `json:"txs"` + TxsSignedByOther []hexutil.Bytes `json:"txsSignedByOther"` + OriginalSenders []common.Address `json:"originalSenders"` + 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 + } + log.Debug("Decoded tx", "tx", tx) + 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 + 1 + 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) + } + header := &types.Header{ + ParentHash: parent.Hash(), + Number: blockNumber, + GasLimit: gasLimit, + Time: timestamp, + Difficulty: difficulty, + Coinbase: coinbase, + BaseFee: baseFee, + } + + // 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(), blockNumber, header.Time) + var totalGasUsed uint64 + gasFees := new(big.Int) + for i, tx := range txs { + coinbaseBalanceBeforeTx := state.GetBalance(coinbase) + state.SetTxContext(tx.Hash(), i) + + // for now i do not want to use the access list stuff + // 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 + } + // i want to cut all that access list stuff. i do not think i need it + // // 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 +} + +// This is a modified version of the function CallBundle +// It is modified to allow for transactions to be signed by different addresses than the one that is sending it. +func (s *SearcherAPI) CallBundleSignedByOther(ctx context.Context, args CallBundleSignedByOther) (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 txsSignedByOther types.Transactions + var txs types.Transactions + + for _, encodedTx := range args.TxsSignedByOther { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(encodedTx); err != nil { + return nil, err + } + txsSignedByOther = append(txsSignedByOther, tx) + } + + 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 txsSignedByOther { + 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.ApplyTransactionSignedByOther(s.b.ChainConfig(), s.chain, &coinbase, gp, state, header, tx, &header.GasUsed, vmconfig, args.OriginalSenders[i]) + 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) + } + + 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() + + // 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 := txArgs.ToMessage(header.BaseFee) + + // 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) + } + + // TODO nick - why are no logs being returned? - we never use this anyways. callBundle already returns gas costs + // 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/backend.go b/internal/ethapi/backend.go index 2a45ba0921..bb49a9fb2e 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -99,7 +99,7 @@ type Backend interface { ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) } -func GetAPIs(apiBackend Backend) []rpc.API { +func GetAPIs(apiBackend Backend, chain *core.BlockChain) []rpc.API { nonceLock := new(AddrLocker) return []rpc.API{ { @@ -111,6 +111,9 @@ func GetAPIs(apiBackend Backend) []rpc.API { }, { Namespace: "eth", Service: NewTransactionAPI(apiBackend, nonceLock), + }, { + Namespace: "eth", + Service: NewSearcherAPI(apiBackend, chain), }, { Namespace: "txpool", Service: NewTxPoolAPI(apiBackend), diff --git a/miner/worker.go b/miner/worker.go index 5dc3e2056b..9a6297b5ff 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -265,7 +265,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* snap = env.state.Snapshot() gp = env.gasPool.Gas() ) - receipt, err := core.ApplyTransaction(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{}) + receipt, _, err := core.ApplyTransaction(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{}) if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp)