Skip to content
This repository has been archived by the owner on Oct 25, 2024. It is now read-only.

Add support for additional block building algorithm #76

Merged
merged 36 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
571a2a3
Add initial implementation for builder bucketized merging algorithm
Wazzymandias Jun 13, 2023
75de38c
Simplify logic and update buckets to initialize from top of heap rath…
Wazzymandias Jun 13, 2023
96af7eb
Add logic to commit transactions when heap is empty
Wazzymandias Jun 13, 2023
44cce7a
Fix erroneous integer division
Wazzymandias Jun 13, 2023
631a995
Refactor function signatures
Wazzymandias Jun 13, 2023
d3a6c14
Revert algo type since lots of tight coupling to it
Wazzymandias Jun 13, 2023
9f3eb5a
Update unit tests, pass in builder algorithm type to greedy builder
Wazzymandias Jun 14, 2023
a54912e
Fix linter error
Wazzymandias Jun 14, 2023
5aec42b
Add comment
Wazzymandias Jun 14, 2023
a489dc7
Move profit function to TxWithMinerFee pointer receiver, refactor sor…
Wazzymandias Jun 15, 2023
32a38d8
Add logic for enforcing profit on bundles and sbundles
Wazzymandias Jun 15, 2023
d184abc
Fix unit tests
Wazzymandias Jun 15, 2023
9c67c26
Split greedy buckets builder from greedy builder
Wazzymandias Jun 15, 2023
b226b8c
Add greedy bucket worker
Wazzymandias Jun 15, 2023
e422c3b
Update tests to support separate greedy buckets builder, add retry logic
Wazzymandias Jun 16, 2023
76206d7
Rename function for retry and push
Wazzymandias Jun 16, 2023
b71bc56
Fix README, update comments
Wazzymandias Jun 16, 2023
13be2e9
Make new multi worker explicit in supported algorithm types, update S…
Wazzymandias Jun 17, 2023
359517d
Address PR feedback
Wazzymandias Jun 21, 2023
1c5300a
Fix unit test
Wazzymandias Jun 21, 2023
02e1ebe
Reduce retry count to 1, update signature formatting
Wazzymandias Jun 23, 2023
56880a3
Add else statement with panic clause for unsupported order type in al…
Wazzymandias Jun 23, 2023
5f311af
Update function signature
Wazzymandias Jun 23, 2023
e20aab1
Update unit test
Wazzymandias Jun 23, 2023
9a463fb
Update greedy buckets algorithm to use gas used for transaction on re…
Wazzymandias Jun 24, 2023
d2a2a86
Address PR feedback
Wazzymandias Jun 26, 2023
5cea23a
Merge remote-tracking branch 'origin/main' into build-300/improve-blo…
Wazzymandias Jun 26, 2023
59ee5cd
Remove print statements used for debugging
Wazzymandias Jun 26, 2023
c9cad3d
Add support for test builder algorithm for parallelized algo testing,…
Wazzymandias Jun 28, 2023
19feb4a
Remove tx profit validation for the scope of this PR due to performan…
Wazzymandias Jun 28, 2023
647309f
Update unit test
Wazzymandias Jun 28, 2023
0df6b29
Update method signatures to algoConf
Wazzymandias Jun 28, 2023
4b9e2c3
Update references of validation conf to algo conf
Wazzymandias Jun 28, 2023
8eccf03
Merge remote-tracking branch 'origin/main' into build-300/improve-blo…
Wazzymandias Jun 28, 2023
76aec25
Remove test only algorithm from PR
Wazzymandias Jun 28, 2023
adaaf14
Move closures to outside function, add low profit error and update gr…
Wazzymandias Jun 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ Miner is responsible for block creation. Request from the `builder` is routed to
`proposerTxCommit`. We do it in a way so all fees received by the block builder are sent to the fee recipient.
* Transaction insertion is done in `fillTransactionsAlgoWorker` \ `fillTransactions`. Depending on the algorithm selected.
Algo worker (greedy) inserts bundles whenever they belong in the block by effective gas price but default method inserts bundles on top of the block.
(see `--miner.algo`)
(see `--miner.algotype`)
* Worker is also responsible for simulating bundles. Bundles are simulated in parallel and results are cached for the particular parent block.
* `algo_greedy.go` implements logic of the block building. Bundles and transactions are sorted in the order of effective gas price then
we try to insert everything into to block until gas limit is reached. Failing bundles are reverted during the insertion but txs are not.
Expand Down
54 changes: 52 additions & 2 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,28 @@ func (t *TxWithMinerFee) SBundle() *SimSBundle {
return t.order.AsSBundle()
}

func (t *TxWithMinerFee) Price() *big.Int {
return new(big.Int).Set(t.minerFee)
}

func (t *TxWithMinerFee) Profit(baseFee *big.Int, gasUsed uint64) *big.Int {
if tx := t.Tx(); tx != nil {
profit := new(big.Int).Sub(tx.GasPrice(), baseFee)
if gasUsed != 0 {
profit.Mul(profit, new(big.Int).SetUint64(gasUsed))
} else {
profit.Mul(profit, new(big.Int).SetUint64(tx.Gas()))
}
return profit
} else if bundle := t.Bundle(); bundle != nil {
return bundle.TotalEth
} else if sbundle := t.SBundle(); sbundle != nil {
return sbundle.Profit
} else {
panic("profit called on unsupported order type")
}
}

// NewTxWithMinerFee creates a wrapped transaction, calculating the effective
// miner gasTipCap if a base fee is provided.
// Returns error in case of a negative effective miner gasTipCap.
Expand All @@ -536,7 +558,7 @@ func NewTxWithMinerFee(tx *Transaction, baseFee *big.Int) (*TxWithMinerFee, erro
}

// NewBundleWithMinerFee creates a wrapped bundle.
func NewBundleWithMinerFee(bundle *SimulatedBundle, baseFee *big.Int) (*TxWithMinerFee, error) {
func NewBundleWithMinerFee(bundle *SimulatedBundle, _ *big.Int) (*TxWithMinerFee, error) {
minerFee := bundle.MevGasPrice
return &TxWithMinerFee{
order: _BundleOrder{bundle},
Expand All @@ -545,7 +567,7 @@ func NewBundleWithMinerFee(bundle *SimulatedBundle, baseFee *big.Int) (*TxWithMi
}

// NewSBundleWithMinerFee creates a wrapped bundle.
func NewSBundleWithMinerFee(sbundle *SimSBundle, baseFee *big.Int) (*TxWithMinerFee, error) {
func NewSBundleWithMinerFee(sbundle *SimSBundle, _ *big.Int) (*TxWithMinerFee, error) {
minerFee := sbundle.MevGasPrice
return &TxWithMinerFee{
order: _SBundleOrder{sbundle},
Expand Down Expand Up @@ -683,6 +705,34 @@ func (t *TransactionsByPriceAndNonce) Shift() {
heap.Pop(&t.heads)
}

// ShiftAndPushByAccountForTx attempts to update the transaction list associated with a given account address
// based on the input transaction account. If the associated account exists and has additional transactions,
// the top of the transaction list is popped and pushed to the heap.
// Note that this operation should only be performed when the head transaction on the heap is different from the
// input transaction. This operation is useful in scenarios where the current best head transaction for an account
// was already popped from the heap and we want to process the next one from the same account.
func (t *TransactionsByPriceAndNonce) ShiftAndPushByAccountForTx(tx *Transaction) {
if tx == nil {
return
}

acc, _ := Sender(t.signer, tx)
if txs, exists := t.txs[acc]; exists && len(txs) > 0 {
if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil {
t.txs[acc] = txs[1:]
heap.Push(&t.heads, wrapped)
}
}
}

func (t *TransactionsByPriceAndNonce) Push(tx *TxWithMinerFee) {
if tx == nil {
return
}

heap.Push(&t.heads, tx)
}

// Pop removes the best transaction, *not* replacing it with the next one from
// the same account. This should be used when a transaction cannot be executed
// and hence all subsequent ones should be discarded from the same account.
Expand Down
49 changes: 41 additions & 8 deletions miner/algo_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,31 +161,31 @@ func (envDiff *environmentDiff) commitTx(tx *types.Transaction, chData chainData
// Pop the current out-of-gas transaction without shifting in the next from the account
from, _ := types.Sender(signer, tx)
log.Trace("Gas limit exceeded for current block", "sender", from)
return nil, popTx, err
return receipt, popTx, err

case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift
from, _ := types.Sender(signer, tx)
log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
return nil, shiftTx, err
return receipt, shiftTx, err

case errors.Is(err, core.ErrNonceTooHigh):
// Reorg notification data race between the transaction pool and miner, skip account =
from, _ := types.Sender(signer, tx)
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
return nil, popTx, err
return receipt, popTx, err

case errors.Is(err, core.ErrTxTypeNotSupported):
// Pop the unsupported transaction without shifting in the next from the account
from, _ := types.Sender(signer, tx)
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
return nil, popTx, err
return receipt, popTx, err

default:
// Strange error, discard the transaction and get the next in line (note, the
// nonce-too-high clause will prevent us from executing in vain).
log.Trace("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
return nil, shiftTx, err
return receipt, shiftTx, err
}
}

Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -196,7 +196,7 @@ func (envDiff *environmentDiff) commitTx(tx *types.Transaction, chData chainData
}

// Commit Bundle to env diff
func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chData chainData, interrupt *int32) error {
func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chData chainData, interrupt *int32, enforceProfit bool) error {
coinbase := envDiff.baseEnvironment.coinbase
tmpEnvDiff := envDiff.copy()

Expand Down Expand Up @@ -262,14 +262,31 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
bundleSimEffGP := new(big.Int).Set(bundle.MevGasPrice)

// allow >-1% divergence
bundleActualEffGP.Mul(bundleActualEffGP, big.NewInt(100))
bundleActualEffGP.Mul(bundleActualEffGP, common.Big100)
Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
bundleSimEffGP.Mul(bundleSimEffGP, big.NewInt(99))

if bundleSimEffGP.Cmp(bundleActualEffGP) == 1 {
log.Trace("Bundle underpays after inclusion", "bundle", bundle.OriginalBundle.Hash)
return errors.New("bundle underpays")
}

if enforceProfit {
// if profit is enforced between simulation and actual commit, only allow >-1% divergence
simulatedBundleProfit := new(big.Int).Set(bundle.TotalEth)
actualBundleGasFees := new(big.Int).Mul(bundleActualEffGP, big.NewInt(int64(gasUsed)))
actualBundleProfit := new(big.Int).Add(coinbaseBalanceDelta, actualBundleGasFees)
Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved

// We want to make simulated profit smaller to allow for some leeway in cases where the actual profit is
// lower due to transaction ordering
simulatedBundleProfit.Mul(simulatedBundleProfit, big.NewInt(99))
actualBundleProfit.Mul(actualBundleProfit, common.Big100)

if simulatedBundleProfit.Cmp(actualBundleProfit) > 0 {
Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
log.Trace("Lower bundle profit found after inclusion", "bundle", bundle.OriginalBundle.Hash)
return errors.New("bundle profit too low")
}
}

*envDiff = *tmpEnvDiff
return nil
}
Expand Down Expand Up @@ -393,7 +410,7 @@ func (envDiff *environmentDiff) commitPayoutTx(amount *big.Int, sender, receiver
return receipt, nil
}

func (envDiff *environmentDiff) commitSBundle(b *types.SimSBundle, chData chainData, interrupt *int32, key *ecdsa.PrivateKey) error {
func (envDiff *environmentDiff) commitSBundle(b *types.SimSBundle, chData chainData, interrupt *int32, key *ecdsa.PrivateKey, enforceProfit bool) error {
if key == nil {
return errors.New("no private key provided")
}
Expand Down Expand Up @@ -428,6 +445,22 @@ func (envDiff *environmentDiff) commitSBundle(b *types.SimSBundle, chData chainD
return fmt.Errorf("incorrect EGP: got %d, expected %d", gotEGP, simEGP)
}

if enforceProfit {
Ruteri marked this conversation as resolved.
Show resolved Hide resolved
Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
// if profit is enforced between simulation and actual commit, only allow >-1% divergence
simulatedProfit := new(big.Int).Set(b.Profit)
actualProfit := new(big.Int).Set(coinbaseDelta)

Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
// We want to make simulated profit smaller to allow for some leeway in cases where the actual profit is
// lower due to transaction ordering
simulatedProfit.Mul(simulatedProfit, big.NewInt(99))
actualProfit.Mul(actualProfit, common.Big100)

if simulatedProfit.Cmp(actualProfit) > 0 {
log.Trace("Lower sbundle profit found after inclusion", "sbundle", b.Bundle.Hash())
return errors.New("sbundle profit too low")
}
}

*envDiff = *tmpEnvDiff
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions miner/algo_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ func TestBundleCommit(t *testing.T) {
t.Fatal("Failed to simulate bundle", err)
}

err = envDiff.commitBundle(&simBundle, chData, nil)
err = envDiff.commitBundle(&simBundle, chData, nil, false)
if err != nil {
t.Fatal("Failed to commit bundle", err)
}
Expand Down Expand Up @@ -408,7 +408,7 @@ func TestErrorBundleCommit(t *testing.T) {
newProfitBefore := new(big.Int).Set(envDiff.newProfit)
balanceBefore := envDiff.state.GetBalance(signers.addresses[2])

err = envDiff.commitBundle(&simBundle, chData, nil)
err = envDiff.commitBundle(&simBundle, chData, nil, false)
if err == nil {
t.Fatal("Committed failed bundle", err)
}
Expand Down
21 changes: 13 additions & 8 deletions miner/algo_greedy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ type greedyBuilder struct {
interrupt *int32
}

func newGreedyBuilder(chain *core.BlockChain, chainConfig *params.ChainConfig, blacklist map[common.Address]struct{}, env *environment, key *ecdsa.PrivateKey, interrupt *int32) *greedyBuilder {
func newGreedyBuilder(
chain *core.BlockChain, chainConfig *params.ChainConfig,
blacklist map[common.Address]struct{}, env *environment, key *ecdsa.PrivateKey, interrupt *int32,
) *greedyBuilder {
return &greedyBuilder{
inputEnvironment: env,
chainData: chainData{chainConfig, chain, blacklist},
Expand All @@ -31,10 +34,13 @@ func newGreedyBuilder(chain *core.BlockChain, chainConfig *params.ChainConfig, b
}
}

func (b *greedyBuilder) mergeOrdersIntoEnvDiff(envDiff *environmentDiff, orders *types.TransactionsByPriceAndNonce) ([]types.SimulatedBundle, []types.UsedSBundle) {
usedBundles := []types.SimulatedBundle{}
usedSbundles := []types.UsedSBundle{}

func (b *greedyBuilder) mergeOrdersIntoEnvDiff(
Wazzymandias marked this conversation as resolved.
Show resolved Hide resolved
envDiff *environmentDiff, orders *types.TransactionsByPriceAndNonce) ([]types.SimulatedBundle, []types.UsedSBundle,
) {
var (
usedBundles []types.SimulatedBundle
usedSbundles []types.UsedSBundle
)
for {
order := orders.Peek()
if order == nil {
Expand All @@ -60,7 +66,7 @@ func (b *greedyBuilder) mergeOrdersIntoEnvDiff(envDiff *environmentDiff, orders
}
} else if bundle := order.Bundle(); bundle != nil {
//log.Debug("buildBlock considering bundle", "egp", bundle.MevGasPrice.String(), "hash", bundle.OriginalBundle.Hash)
err := envDiff.commitBundle(bundle, b.chainData, b.interrupt)
err := envDiff.commitBundle(bundle, b.chainData, b.interrupt, false)
orders.Pop()
if err != nil {
log.Trace("Could not apply bundle", "bundle", bundle.OriginalBundle.Hash, "err", err)
Expand All @@ -73,7 +79,7 @@ func (b *greedyBuilder) mergeOrdersIntoEnvDiff(envDiff *environmentDiff, orders
usedEntry := types.UsedSBundle{
Bundle: sbundle.Bundle,
}
err := envDiff.commitSBundle(sbundle, b.chainData, b.interrupt, b.builderKey)
err := envDiff.commitSBundle(sbundle, b.chainData, b.interrupt, b.builderKey, false)
orders.Pop()
if err != nil {
log.Trace("Could not apply sbundle", "bundle", sbundle.Bundle.Hash(), "err", err)
Expand All @@ -87,7 +93,6 @@ func (b *greedyBuilder) mergeOrdersIntoEnvDiff(envDiff *environmentDiff, orders
usedSbundles = append(usedSbundles, usedEntry)
}
}

return usedBundles, usedSbundles
}

Expand Down
Loading