Skip to content

Commit

Permalink
Change pricing formula to ignore gas from txs in the txpool
Browse files Browse the repository at this point in the history
Fixes #39
  • Loading branch information
jparyani committed May 17, 2021
1 parent f933b03 commit 688206c
Showing 1 changed file with 79 additions and 35 deletions.
114 changes: 79 additions & 35 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ func (w *worker) taskLoop() {
// Interrupt previous sealing operation
interrupt()
stopCh, prev = make(chan struct{}), sealHash
log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", prevProfit, "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash)
log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", ethIntToFloat(prevProfit), "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash)
if w.skipSealHook != nil && w.skipSealHook(task) {
continue
}
Expand Down Expand Up @@ -1147,9 +1147,16 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64)
log.Error("Failed to fetch pending transactions", "err", err)
return
}
maxBundle, bundlePrice, ethToCoinbase, gasUsed := w.findMostProfitableBundle(bundles, w.coinbase, parent, header)
log.Info("Flashbots bundle", "ethToCoinbase", ethToCoinbase, "gasUsed", gasUsed, "bundlePrice", bundlePrice, "bundleLength", len(maxBundle))
if w.commitBundle(maxBundle, w.coinbase, interrupt) {
bundle, err := w.findMostProfitableBundle(bundles, w.coinbase, parent, header, pending)
if err != nil {
log.Error("Failed to generate flashbots bundle", "err", err)
return
}
log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.totalEth), "gasUsed", bundle.totalGasUsed, "bundleScore", bundle.mevGasPrice, "bundleLength", len(bundle.txs))
if len(bundle.txs) == 0 {
return
}
if w.commitBundle(bundle.txs, w.coinbase, interrupt) {
return
}
}
Expand Down Expand Up @@ -1187,7 +1194,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
w.unconfirmed.Shift(block.NumberU64() - 1)
log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
"uncles", len(uncles), "txs", w.current.tcount,
"gas", block.GasUsed(), "fees", totalFees(block, receipts),
"gas", block.GasUsed(), "fees", totalFees(block, receipts), "profit", ethIntToFloat(w.current.profit),
"elapsed", common.PrettyDuration(time.Since(start)),
"isFlashbots", w.flashbots.isFlashbots)

Expand All @@ -1201,64 +1208,94 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
return nil
}

func (w *worker) findMostProfitableBundle(bundles []types.Transactions, coinbase common.Address, parent *types.Block, header *types.Header) (types.Transactions, *big.Int, *big.Int, uint64) {
maxBundlePrice := new(big.Int)
maxTotalEth := new(big.Int)
var maxTotalGasUsed uint64
maxBundle := types.Transactions{}
type simulatedBundle struct {
txs types.Transactions
mevGasPrice *big.Int
totalEth *big.Int
totalGasUsed uint64
}

func (w *worker) findMostProfitableBundle(bundles []types.Transactions, coinbase common.Address, parent *types.Block, header *types.Header, pendingTxs map[common.Address]types.Transactions) (simulatedBundle, error) {
maxBundle := simulatedBundle{mevGasPrice: new(big.Int)}

for _, bundle := range bundles {
state, err := w.chain.StateAt(parent.Root())
if err != nil {
return simulatedBundle{}, err
}
gasPool := new(core.GasPool).AddGas(header.GasLimit)
if len(bundle) == 0 {
continue
}
totalEth, totalGasUsed, err := w.computeBundleGas(bundle, parent, header)
simmed, err := w.computeBundleGas(bundle, parent, header, state, gasPool, pendingTxs)

if err != nil {
log.Debug("Error computing gas for a bundle", "error", err)
continue
}

mevGasPrice := new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed))
if mevGasPrice.Cmp(maxBundlePrice) > 0 {
maxBundle = bundle
maxBundlePrice = mevGasPrice
maxTotalEth = totalEth
maxTotalGasUsed = totalGasUsed
if simmed.mevGasPrice.Cmp(maxBundle.mevGasPrice) > 0 {
maxBundle = simmed
}
}

return maxBundle, maxBundlePrice, maxTotalEth, maxTotalGasUsed
return maxBundle, nil
}

// Compute the adjusted gas price for a whole bundle
// Done by calculating all gas spent, adding transfers to the coinbase, and then dividing by gas used
func (w *worker) computeBundleGas(bundle types.Transactions, parent *types.Block, header *types.Header) (*big.Int, uint64, error) {
env, err := w.generateEnv(parent, header)
if err != nil {
return nil, 0, err
}

func (w *worker) computeBundleGas(bundle types.Transactions, parent *types.Block, header *types.Header, state *state.StateDB, gasPool *core.GasPool, pendingTxs map[common.Address]types.Transactions) (simulatedBundle, error) {
var totalGasUsed uint64 = 0
var tempGasUsed uint64
gasFees := new(big.Int)

coinbaseBalanceBefore := env.state.GetBalance(w.coinbase)
totalEth := new(big.Int)

for _, tx := range bundle {
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &w.coinbase, env.gasPool, env.state, env.header, tx, &tempGasUsed, *w.chain.GetVMConfig())
coinbaseBalanceBefore := state.GetBalance(w.coinbase)

receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &w.coinbase, gasPool, state, header, tx, &tempGasUsed, *w.chain.GetVMConfig())
if err != nil {
return nil, 0, err
return simulatedBundle{}, err
}
if receipt.Status == types.ReceiptStatusFailed {
return nil, 0, errors.New("revert")
return simulatedBundle{}, errors.New("revert")
}

totalGasUsed += receipt.GasUsed
gasFees.Add(gasFees, new(big.Int).Mul(big.NewInt(int64(totalGasUsed)), tx.GasPrice()))

from, err := types.Sender(w.current.signer, tx)
if err != nil {
return simulatedBundle{}, err
}

txInPendingPool := false
// check if tx is in pending pool
if accountTxs, ok := pendingTxs[from]; ok {
txNonce := tx.Nonce()

for _, accountTx := range accountTxs {
if accountTx.Nonce() == txNonce {
txInPendingPool = true
break
}
}
}

coinbaseBalanceAfter := state.GetBalance(w.coinbase)
totalEth = totalEth.Add(totalEth, coinbaseBalanceAfter.Sub(coinbaseBalanceAfter, coinbaseBalanceBefore))

if txInPendingPool {
// If tx is in pending pool, ignore the gas fees
gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
totalEth.Sub(totalEth, gasUsed.Mul(gasUsed, tx.GasPrice()))
}
}
coinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
coinbaseDiff := new(big.Int).Sub(new(big.Int).Sub(coinbaseBalanceAfter, gasFees), coinbaseBalanceBefore)

return coinbaseDiff, totalGasUsed, nil
return simulatedBundle{
txs: bundle,
mevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
totalEth: totalEth,
totalGasUsed: totalGasUsed,
}, nil
}

// copyReceipts makes a deep copy of the given receipts.
Expand All @@ -1279,11 +1316,18 @@ func (w *worker) postSideBlock(event core.ChainSideEvent) {
}
}

func ethIntToFloat(eth *big.Int) *big.Float {
if eth == nil {
return big.NewFloat(0)
}
return new(big.Float).Quo(new(big.Float).SetInt(eth), new(big.Float).SetInt(big.NewInt(params.Ether)))
}

// totalFees computes total consumed fees in ETH. Block transactions and receipts have to have the same order.
func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
feesWei := new(big.Int)
for i, tx := range block.Transactions() {
feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice()))
}
return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
return ethIntToFloat(feesWei)
}

0 comments on commit 688206c

Please sign in to comment.