Skip to content

Commit

Permalink
Merge pull request #77 from blocknative/feature/eth_callBundle-rearra…
Browse files Browse the repository at this point in the history
…ngement

Feature | eth_callBundle rearrangement
  • Loading branch information
ayazabbas authored Jan 5, 2023
2 parents 899691c + 0431620 commit 4e4d5ef
Showing 1 changed file with 152 additions and 2 deletions.
154 changes: 152 additions & 2 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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 @@ -2223,6 +2224,155 @@ type CallBundleArgs struct {
// a past block.
// The sender is responsible for signing the transactions and using the correct
// nonce and ensuring validity
func (s *BundleAPI) CallBundle(ctx context.Context, args BNMultiSimArgs) (map[string]interface{}, error) {
return s.BNMultiSim(ctx, args)
func (s *BundleAPI) 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
}
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)

receipt, result, err := core.ApplyTransactionWithResult(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)
}
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
}

0 comments on commit 4e4d5ef

Please sign in to comment.