Skip to content

Commit

Permalink
mevexec: callbundle
Browse files Browse the repository at this point in the history
  • Loading branch information
Code0x2 committed Sep 6, 2022
1 parent d408cb6 commit 8369bb6
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 11 deletions.
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
b.SetCoinbase(common.Address{})
}
b.statedb.Prepare(tx.Hash(), len(b.txs))
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
if err != nil {
panic(err)
}
Expand Down
13 changes: 7 additions & 6 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.Prepare(tx.Hash(), i)
receipt, err := applyTransaction(msg, p.config, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
receipt, _, err := applyTransaction(msg, p.config, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
Expand All @@ -92,15 +92,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return receipts, allLogs, *usedGas, nil
}

func applyTransaction(msg types.Message, config *params.ChainConfig, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
func applyTransaction(msg types.Message, config *params.ChainConfig, author *common.Address, 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, err
return nil, nil, err
}

// Update the state with pending changes.
Expand Down Expand Up @@ -134,17 +134,18 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, author *com
receipt.BlockHash = blockHash
receipt.BlockNumber = blockNumber
receipt.TransactionIndex = uint(statedb.TxIndex())
return receipt, err
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) {
// mevexec: additionally returns the full result, which can be should at a later stage where unneccessary
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 := tx.AsMessage(types.MakeSigner(config, header.Number), 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)
Expand Down
2 changes: 1 addition & 1 deletion eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,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())...)
Expand Down
303 changes: 303 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package ethapi

import (
"context"
"encoding/hex"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -46,6 +47,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/sha3"
)

// EthereumAPI provides an API to access Ethereum related information.
Expand Down Expand Up @@ -1449,6 +1451,74 @@ 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); 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))

// 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); 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, Debug: true, NoBaseFee: true}
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config)
if err != nil {
return nil, 0, nil, err
}
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
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
Expand Down Expand Up @@ -2064,3 +2134,236 @@ func toHexSlice(b [][]byte) []string {
}
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 *big.Int `json:"difficulty"`
BaseFee *big.Int `json:"baseFee"`
SimulationLogs bool `json:"simulationLogs"`
CreateAccessList bool `json:"createAccessList"`
StateOverrides *StateOverride `json:"stateOverrides"`
}

// 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 + 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
}
gasLimit := parent.GasLimit
if args.GasLimit != nil {
gasLimit = *args.GasLimit
}
var baseFee *big.Int
if args.BaseFee != nil {
baseFee = args.BaseFee
} else if s.b.ChainConfig().IsLondon(big.NewInt(args.BlockNumber.Int64())) {
baseFee = misc.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)
var totalGasUsed uint64
gasFees := new(big.Int)
for i, tx := range txs {
coinbaseBalanceBeforeTx := state.GetBalance(coinbase)
state.Prepare(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(big.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBeforeTx)
jsonResult["coinbaseDiff"] = coinbaseDiffTx.String()
jsonResult["gasFees"] = gasFeesTx.String()
jsonResult["ethSentToCoinbase"] = new(big.Int).Sub(coinbaseDiffTx, gasFeesTx).String()
jsonResult["gasPrice"] = new(big.Int).Div(coinbaseDiffTx, big.NewInt(int64(receipt.GasUsed))).String()
jsonResult["gasUsed"] = receipt.GasUsed
results = append(results, jsonResult)
}

ret := map[string]interface{}{}
ret["results"] = results
coinbaseDiff := new(big.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBefore)
ret["coinbaseDiff"] = coinbaseDiff.String()
ret["gasFees"] = gasFees.String()
ret["ethSentToCoinbase"] = new(big.Int).Sub(coinbaseDiff, gasFees).String()
ret["bundleGasPrice"] = new(big.Int).Div(coinbaseDiff, big.NewInt(int64(totalGasUsed))).String()
ret["totalGasUsed"] = totalGasUsed
ret["stateBlockNumber"] = parent.Number.Int64()

ret["bundleHash"] = "0x" + common.Bytes2Hex(bundleHash.Sum(nil))
return ret, nil
}
Loading

0 comments on commit 8369bb6

Please sign in to comment.