From ab35c21d3db2125aad54a2cc9d253792ffdfe310 Mon Sep 17 00:00:00 2001 From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com> Date: Fri, 7 Oct 2022 14:42:30 +0200 Subject: [PATCH] Improvements from greedy improve algo (#41) * Backport improvements to the builder from incremental improvements * Make linter happy --- builder/builder.go | 5 +- builder/local_relay_test.go | 3 +- builder/relay.go | 2 - builder/resubmit_utils.go | 3 +- builder/resubmit_utils_test.go | 3 +- builder/service.go | 3 +- cmd/utils/flags.go | 9 +- core/types/transaction.go | 13 ++ eth/block-validation/api_test.go | 7 +- eth/tracers/logger/account_touch_tracer.go | 5 +- miner/algo_common.go | 8 +- miner/algo_common_test.go | 39 ++++- miner/algo_greedy.go | 31 ++-- miner/algo_greedy_test.go | 6 +- miner/bundle_cache.go | 3 +- miner/bundle_cache_test.go | 3 +- miner/miner.go | 14 +- miner/multi_worker.go | 6 +- miner/worker.go | 49 +++---- miner/worker_test.go | 160 ++++++++++++++++++--- 20 files changed, 276 insertions(+), 96 deletions(-) diff --git a/builder/builder.go b/builder/builder.go index a20897477a..7f9758ceb0 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -3,13 +3,14 @@ package builder import ( "context" "errors" - blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation" - "golang.org/x/time/rate" "math/big" _ "os" "sync" "time" + blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation" + "golang.org/x/time/rate" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go index 9ba2bcc96e..53db30392a 100644 --- a/builder/local_relay_test.go +++ b/builder/local_relay_test.go @@ -4,13 +4,14 @@ import ( "bytes" "encoding/json" "fmt" - "golang.org/x/time/rate" "math/big" "net/http" "net/http/httptest" "testing" "time" + "golang.org/x/time/rate" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/beacon" diff --git a/builder/relay.go b/builder/relay.go index 4642707d41..f5414ae280 100644 --- a/builder/relay.go +++ b/builder/relay.go @@ -29,8 +29,6 @@ func (r *testRelay) GetValidatorForSlot(nextSlot uint64) (ValidatorData, error) r.requestedSlot = nextSlot return r.validator, nil } -func (r *testRelay) handleRegisterValidator(w http.ResponseWriter, req *http.Request) { -} type RemoteRelay struct { endpoint string diff --git a/builder/resubmit_utils.go b/builder/resubmit_utils.go index 749bbc2e65..4d95dab93a 100644 --- a/builder/resubmit_utils.go +++ b/builder/resubmit_utils.go @@ -2,9 +2,10 @@ package builder import ( "context" + "time" + "github.com/ethereum/go-ethereum/log" "golang.org/x/time/rate" - "time" ) // runResubmitLoop checks for update signal and calls submit respecting provided rate limiter and context diff --git a/builder/resubmit_utils_test.go b/builder/resubmit_utils_test.go index d451097fd5..f612c5a292 100644 --- a/builder/resubmit_utils_test.go +++ b/builder/resubmit_utils_test.go @@ -2,12 +2,13 @@ package builder import ( "context" - "golang.org/x/time/rate" "math/rand" "sort" "sync" "testing" "time" + + "golang.org/x/time/rate" ) type submission struct { diff --git a/builder/service.go b/builder/service.go index c53fada894..08f8aa20e2 100644 --- a/builder/service.go +++ b/builder/service.go @@ -3,10 +3,11 @@ package builder import ( "errors" "fmt" - blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation" "net/http" "os" + blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 39a6c701b6..26d3a4a473 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1641,14 +1641,11 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { cfg.GasPrice = flags.GlobalBig(ctx, MinerGasPriceFlag.Name) } if ctx.IsSet(MinerAlgoTypeFlag.Name) { - switch ctx.String(MinerAlgoTypeFlag.Name) { - case "greedy": - cfg.AlgoType = miner.ALGO_GREEDY - case "mev-geth": - cfg.AlgoType = miner.ALGO_MEV_GETH - default: + algoType, err := miner.AlgoTypeFlagToEnum(ctx.String(MinerAlgoTypeFlag.Name)) + if err != nil { Fatalf("Invalid algo in --miner.algotype: %s", ctx.String(MinerAlgoTypeFlag.Name)) } + cfg.AlgoType = algoType } if ctx.IsSet(MinerRecommitIntervalFlag.Name) { cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name) diff --git a/core/types/transaction.go b/core/types/transaction.go index 82ea61f740..8c553b570f 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -615,6 +615,19 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa } } +func (t *TransactionsByPriceAndNonce) DeepCopy() *TransactionsByPriceAndNonce { + newT := &TransactionsByPriceAndNonce{ + txs: make(map[common.Address]Transactions), + heads: append(TxByPriceAndTime{}, t.heads...), + signer: t.signer, + baseFee: new(big.Int).Set(t.baseFee), + } + for k, v := range t.txs { + newT.txs[k] = v + } + return newT +} + // Peek returns the next transaction by price. func (t *TransactionsByPriceAndNonce) Peek() *TxWithMinerFee { if len(t.heads) == 0 { diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go index fa356bc240..9169abc775 100644 --- a/eth/block-validation/api_test.go +++ b/eth/block-validation/api_test.go @@ -41,9 +41,6 @@ var ( testValidatorKey, _ = crypto.HexToECDSA("28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0") testValidatorAddr = crypto.PubkeyToAddress(testValidatorKey.PublicKey) - testMinerKey, _ = crypto.HexToECDSA("28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0") - testMinerAddr = crypto.PubkeyToAddress(testValidatorKey.PublicKey) - testBalance = big.NewInt(2e18) ) @@ -57,7 +54,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) { api := NewBlockValidationAPI(ethservice, nil) parent := preMergeBlocks[len(preMergeBlocks)-1] - api.eth.APIBackend.Miner().SetEtherbase(testMinerAddr) + api.eth.APIBackend.Miner().SetEtherbase(testValidatorAddr) // This EVM code generates a log when the contract is created. logCode := common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") @@ -142,7 +139,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) { invalidPayload.LogsBloom = boostTypes.Bloom{} copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32]) blockRequest.ExecutionPayload = invalidPayload - copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x272872d14b2a8a0454e747ed472d82d8d5ce342cfafd65fa7b77aa6de1c061d4")[:32]) + copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x2ff468dee2e05f1f58744d5496f3ab22fdc23c8141f86f907b4b0f2c8e22afc4")[:32]) require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value") } diff --git a/eth/tracers/logger/account_touch_tracer.go b/eth/tracers/logger/account_touch_tracer.go index 10d2fb32af..4c24779637 100644 --- a/eth/tracers/logger/account_touch_tracer.go +++ b/eth/tracers/logger/account_touch_tracer.go @@ -17,10 +17,11 @@ package logger import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" "math/big" "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" ) type AccountTouchTracer struct { diff --git a/miner/algo_common.go b/miner/algo_common.go index ef16c86617..9c39993612 100644 --- a/miner/algo_common.go +++ b/miner/algo_common.go @@ -26,6 +26,12 @@ var emptyCodeHash = common.HexToHash("c5d2460186f7233c927e7db2dcc703c0e500b653ca var errInterrupt = errors.New("miner worker interrupted") +type chainData struct { + chainConfig *params.ChainConfig + chain *core.BlockChain + blacklist map[common.Address]struct{} +} + type environmentDiff struct { baseEnvironment *environment header *types.Header @@ -51,7 +57,7 @@ func (e *environmentDiff) copy() *environmentDiff { gasPool := new(core.GasPool).AddGas(e.gasPool.Gas()) return &environmentDiff{ - baseEnvironment: e.baseEnvironment, + baseEnvironment: e.baseEnvironment.copy(), header: types.CopyHeader(e.header), gasPool: gasPool, state: e.state.Copy(), diff --git a/miner/algo_common_test.go b/miner/algo_common_test.go index 42277613fa..b94198608f 100644 --- a/miner/algo_common_test.go +++ b/miner/algo_common_test.go @@ -4,10 +4,11 @@ import ( "crypto/ecdsa" "errors" "fmt" - "github.com/stretchr/testify/require" "math/big" "testing" + "github.com/stretchr/testify/require" + mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -366,10 +367,8 @@ func TestCommitTxOverGasLimit(t *testing.T) { t.Fatal("Env diff gas pool is not drained") } - receipt, i, err = envDiff.commitTx(tx2, chData) - if err == nil { - t.Fatal("committed tx over gas limit") - } + _, _, err = envDiff.commitTx(tx2, chData) + require.Error(t, err, "committed tx over gas limit") } func TestErrorBundleCommit(t *testing.T) { @@ -497,6 +496,36 @@ func TestBlacklist(t *testing.T) { } } +func TestGetSealingWorkAlgos(t *testing.T) { + t.Cleanup(func() { + testConfig.AlgoType = ALGO_MEV_GETH + }) + + for _, algoType := range []AlgoType{ALGO_MEV_GETH, ALGO_GREEDY} { + local := new(params.ChainConfig) + *local = *ethashChainConfig + local.TerminalTotalDifficulty = big.NewInt(0) + testConfig.AlgoType = algoType + testGetSealingWork(t, local, ethash.NewFaker(), true) + } +} + +func TestGetSealingWorkAlgosWithProfit(t *testing.T) { + t.Cleanup(func() { + testConfig.AlgoType = ALGO_MEV_GETH + testConfig.BuilderTxSigningKey = nil + }) + + for _, algoType := range []AlgoType{ALGO_GREEDY} { + var err error + testConfig.BuilderTxSigningKey, err = crypto.GenerateKey() + require.NoError(t, err) + testConfig.AlgoType = algoType + t.Logf("running for %d", algoType) + testBundles(t) + } +} + func TestPayoutTxUtils(t *testing.T) { availableFunds := big.NewInt(50000000000000000) // 0.05 eth diff --git a/miner/algo_greedy.go b/miner/algo_greedy.go index d1fd114625..51b76a76cc 100644 --- a/miner/algo_greedy.go +++ b/miner/algo_greedy.go @@ -8,16 +8,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -type chainData struct { - chainConfig *params.ChainConfig - chain *core.BlockChain - blacklist map[common.Address]struct{} -} - -type IBuilder interface { - buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle) -} - // / To use it: // / 1. Copy relevant data from the worker // / 2. Call buildBlock @@ -37,14 +27,8 @@ func newGreedyBuilder(chain *core.BlockChain, chainConfig *params.ChainConfig, b } } -func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle) { - - env := b.inputEnvironment.copy() - - orders := types.NewTransactionsByPriceAndNonce(env.signer, transactions, simBundles, env.header.BaseFee) - envDiff := newEnvironmentDiff(env) - - usedBundles := make([]types.SimulatedBundle, 0) +func (b *greedyBuilder) mergeOrdersIntoEnvDiff(envDiff *environmentDiff, orders *types.TransactionsByPriceAndNonce) []types.SimulatedBundle { + usedBundles := []types.SimulatedBundle{} for { order := orders.Peek() @@ -65,7 +49,7 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti log.Trace("could not apply tx", "hash", order.Tx.Hash(), "err", err) continue } - effGapPrice, err := order.Tx.EffectiveGasTip(env.header.BaseFee) + effGapPrice, err := order.Tx.EffectiveGasTip(envDiff.baseEnvironment.header.BaseFee) if err == nil { log.Trace("Included tx", "EGP", effGapPrice.String(), "gasUsed", receipt.GasUsed) } @@ -84,6 +68,13 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti } } + return usedBundles +} + +func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle) { + orders := types.NewTransactionsByPriceAndNonce(b.inputEnvironment.signer, transactions, simBundles, b.inputEnvironment.header.BaseFee) + envDiff := newEnvironmentDiff(b.inputEnvironment.copy()) + usedBundles := b.mergeOrdersIntoEnvDiff(envDiff, orders) envDiff.applyToBaseEnv() - return env, usedBundles + return envDiff.baseEnvironment, usedBundles } diff --git a/miner/algo_greedy_test.go b/miner/algo_greedy_test.go index b38704bc18..8f7f99d7ba 100644 --- a/miner/algo_greedy_test.go +++ b/miner/algo_greedy_test.go @@ -2,11 +2,12 @@ package miner import ( "fmt" + "math/big" + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "math/big" - "testing" ) func TestBuildBlockGasLimit(t *testing.T) { @@ -65,5 +66,4 @@ func TestTxWithMinerFeeHeap(t *testing.T) { orders.Pop() } } - } diff --git a/miner/bundle_cache.go b/miner/bundle_cache.go index 42a810bd21..2d6bf18537 100644 --- a/miner/bundle_cache.go +++ b/miner/bundle_cache.go @@ -1,9 +1,10 @@ package miner import ( + "sync" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "sync" ) const ( diff --git a/miner/bundle_cache_test.go b/miner/bundle_cache_test.go index 71fcf376f6..77de101af6 100644 --- a/miner/bundle_cache_test.go +++ b/miner/bundle_cache_test.go @@ -1,9 +1,10 @@ package miner import ( + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "testing" ) func TestBundleCacheEntry(t *testing.T) { diff --git a/miner/miner.go b/miner/miner.go index 4bf5ed86f4..7c5e466541 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -19,8 +19,8 @@ package miner import ( "crypto/ecdsa" + "errors" "fmt" - "github.com/ethereum/go-ethereum/crypto" "math/big" "os" "strings" @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -54,6 +55,17 @@ const ( ALGO_GREEDY ) +func AlgoTypeFlagToEnum(algoString string) (AlgoType, error) { + switch algoString { + case "mev-geth": + return ALGO_MEV_GETH, nil + case "greedy": + return ALGO_GREEDY, nil + default: + return ALGO_MEV_GETH, errors.New("algo not recognized") + } +} + // Config is the configuration parameters of mining. type Config struct { Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards diff --git a/miner/multi_worker.go b/miner/multi_worker.go index 41ef0ea813..ca18ef3c83 100644 --- a/miner/multi_worker.go +++ b/miner/multi_worker.go @@ -94,7 +94,7 @@ type resChPair struct { func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) { resChans := []resChPair{} - for _, worker := range append(w.workers) { + for _, worker := range w.workers { resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook) if err != nil { log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles) @@ -141,7 +141,7 @@ func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, } func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker { - if config.AlgoType == ALGO_GREEDY { + if config.AlgoType != ALGO_MEV_GETH { return newMultiWorkerGreedy(config, chainConfig, engine, eth, mux, isLocalBlock, init) } else { return newMultiWorkerMevGeth(config, chainConfig, engine, eth, mux, isLocalBlock, init) @@ -154,7 +154,7 @@ func newMultiWorkerGreedy(config *Config, chainConfig *params.ChainConfig, engin greedyWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{ isFlashbots: true, queue: queue, - algoType: ALGO_GREEDY, + algoType: config.AlgoType, maxMergedBundles: config.MaxMergedBundles, bundleCache: NewBundleCache(), }) diff --git a/miner/worker.go b/miner/worker.go index 72de14397d..79d8110526 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -79,8 +79,6 @@ const ( // staleThreshold is the maximum depth of the acceptable stale block. staleThreshold = 7 - - paymentTxGas = 26000 ) var ( @@ -294,6 +292,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus } else { builderCoinbase = crypto.PubkeyToAddress(config.BuilderTxSigningKey.PublicKey) } + log.Info("new worker", "builderCoinbase", builderCoinbase.String()) exitCh := make(chan struct{}) taskCh := make(chan *task) @@ -1301,9 +1300,6 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) (er localTxs[account] = txs } } - if env.gasPool == nil { - env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit) - } var blockBundles []types.SimulatedBundle if w.flashbots.isFlashbots { @@ -1351,25 +1347,9 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) // Split the pending transactions into locals and remotes // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) - if env.gasPool == nil { - env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit) - } - - var bundlesToConsider []types.SimulatedBundle - if w.flashbots.isFlashbots { - bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time) - if err != nil { - log.Error("Failed to fetch pending bundles", "err", err) - return err, nil - } - - simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */ - if err != nil { - log.Error("Failed to simulate flashbots bundles", "err", err) - return err, nil - } - - bundlesToConsider = simBundles + bundlesToConsider, err := w.getSimulatedBundles(env) + if err != nil { + return err, nil } builder := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt) @@ -1379,6 +1359,27 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) return nil, blockBundles } +func (w *worker) getSimulatedBundles(env *environment) ([]types.SimulatedBundle, error) { + if !w.flashbots.isFlashbots { + return nil, nil + } + + bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time) + if err != nil { + log.Error("Failed to fetch pending bundles", "err", err) + return nil, err + } + + // TODO: consider interrupt + simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */ + if err != nil { + log.Error("Failed to simulate flashbots bundles", "err", err) + return nil, err + } + + return simBundles, nil +} + // generateWork generates a sealing block based on the given parameters. func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, error) { work, err := w.prepareWork(params) diff --git a/miner/worker_test.go b/miner/worker_test.go index a0a13948ca..c67296f05f 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -18,6 +18,7 @@ package miner import ( "crypto/rand" + "crypto/ecdsa" "errors" "math/big" "sync/atomic" @@ -39,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" ) const ( @@ -61,6 +63,13 @@ var ( testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) testBankFunds = big.NewInt(1000000000000000000) + testAddress1Key, _ = crypto.GenerateKey() + testAddress1 = crypto.PubkeyToAddress(testAddress1Key.PublicKey) + testAddress2Key, _ = crypto.GenerateKey() + testAddress2 = crypto.PubkeyToAddress(testAddress2Key.PublicKey) + testAddress3Key, _ = crypto.GenerateKey() + testAddress3 = crypto.PubkeyToAddress(testAddress3Key.PublicKey) + testUserKey, _ = crypto.GenerateKey() testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) @@ -72,6 +81,8 @@ var ( Recommit: time.Second, GasCeil: params.GenesisGasLimit, } + + defaultGenesisAlloc = core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}} ) func init() { @@ -119,7 +130,7 @@ type testWorkerBackend struct { func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { var gspec = &core.Genesis{ Config: chainConfig, - Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, + Alloc: alloc, } switch e := engine.(type) { case *clique.Clique: @@ -189,24 +200,24 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block { return blocks[0] } -func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { +func (b *testWorkerBackend) newRandomTx(creation bool, to common.Address, amt int64, key *ecdsa.PrivateKey, additionalGasLimit uint64, gasPrice *big.Int) *types.Transaction { var tx *types.Transaction - gasPrice := big.NewInt(10 * params.InitialBaseFee) if creation { - tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey) + tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(crypto.PubkeyToAddress(key.PublicKey)), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, key) } else { - tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey) + tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(crypto.PubkeyToAddress(key.PublicKey)), to, big.NewInt(amt), params.TxGas+additionalGasLimit, gasPrice, nil), types.HomesteadSigner{}, key) } return tx } -func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { - backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) +func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, alloc core.GenesisAlloc, blocks int) (*worker, *testWorkerBackend) { + backend := newTestWorkerBackend(t, chainConfig, engine, db, alloc, blocks) backend.txPool.AddLocals(pendingTxs) w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, &flashbotsData{ - isFlashbots: false, + isFlashbots: testConfig.AlgoType != ALGO_MEV_GETH, queue: nil, bundleCache: NewBundleCache(), + algoType: testConfig.AlgoType, }) w.setEtherbase(testBankAddress) return w, backend @@ -250,8 +261,8 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { w.start() for i := 0; i < 5; i++ { - b.txPool.AddLocal(b.newRandomTx(true)) - b.txPool.AddLocal(b.newRandomTx(false)) + b.txPool.AddLocal(b.newRandomTx(true, testUserAddress, 0, testBankKey, 0, big.NewInt(10*params.InitialBaseFee))) + b.txPool.AddLocal(b.newRandomTx(false, testUserAddress, 1000, testBankKey, 0, big.NewInt(10*params.InitialBaseFee))) w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) @@ -277,7 +288,7 @@ func TestEmptyWorkClique(t *testing.T) { func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0) defer w.close() var ( @@ -323,7 +334,7 @@ func TestStreamUncleBlock(t *testing.T) { ethash := ethash.NewFaker() defer ethash.Close() - w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1) + w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 1) defer w.close() var taskCh = make(chan struct{}, 3) @@ -381,7 +392,7 @@ func TestRegenerateMiningBlockClique(t *testing.T) { func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0) defer w.close() var taskCh = make(chan struct{}, 3) @@ -441,7 +452,7 @@ func TestAdjustIntervalClique(t *testing.T) { func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0) defer w.close() w.skipSealHook = func(task *task) bool { @@ -541,7 +552,7 @@ func TestGetSealingWorkPostMerge(t *testing.T) { func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { defer engine.Close() - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0) defer w.close() w.setExtra([]byte{0x01, 0x02}) @@ -665,7 +676,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co } func TestSimulateBundles(t *testing.T) { - w, _ := newTestWorker(t, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0) defer w.close() env, err := w.prepareWork(&generateParams{gasLimit: 30000000}) @@ -687,6 +698,7 @@ func TestSimulateBundles(t *testing.T) { bundle3 := types.MevBundle{Txs: types.Transactions{signTx(0)}, Hash: common.HexToHash("0x03")} simBundles, err := w.simulateBundles(env, []types.MevBundle{bundle1, bundle2, bundle3}, nil) + require.NoError(t, err) if len(simBundles) != 2 { t.Fatalf("Incorrect amount of sim bundles") @@ -700,6 +712,7 @@ func TestSimulateBundles(t *testing.T) { // simulate 2 times to check cache simBundles, err = w.simulateBundles(env, []types.MevBundle{bundle1, bundle2, bundle3}, nil) + require.NoError(t, err) if len(simBundles) != 2 { t.Fatalf("Incorrect amount of sim bundles(cache)") @@ -711,3 +724,118 @@ func TestSimulateBundles(t *testing.T) { } } } + +func testBundles(t *testing.T) { + db := rawdb.NewMemoryDatabase() + chainConfig := params.AllEthashProtocolChanges + engine := ethash.NewFaker() + + chainConfig.LondonBlock = big.NewInt(0) + + genesisAlloc := core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}} + + nExtraKeys := 5 + extraKeys := make([]*ecdsa.PrivateKey, nExtraKeys) + for i := 0; i < nExtraKeys; i++ { + pk, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(pk.PublicKey) + extraKeys[i] = pk + genesisAlloc[address] = core.GenesisAccount{Balance: testBankFunds} + } + + nSearchers := 5 + searcherPrivateKeys := make([]*ecdsa.PrivateKey, nSearchers) + for i := 0; i < nSearchers; i++ { + pk, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(pk.PublicKey) + searcherPrivateKeys[i] = pk + genesisAlloc[address] = core.GenesisAccount{Balance: testBankFunds} + } + + for _, address := range []common.Address{testAddress1, testAddress2, testAddress3} { + genesisAlloc[address] = core.GenesisAccount{Balance: testBankFunds} + } + + w, b := newTestWorker(t, chainConfig, engine, db, genesisAlloc, 0) + w.setEtherbase(crypto.PubkeyToAddress(testConfig.BuilderTxSigningKey.PublicKey)) + defer w.close() + + // This test chain imports the mined blocks. + db2 := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(db2) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) + defer chain.Stop() + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + rand.Seed(10) + + for i := 0; i < 2; i++ { + commonTxs := []*types.Transaction{ + b.newRandomTx(false, testBankAddress, 1e15, testAddress1Key, 0, big.NewInt(100*params.InitialBaseFee)), + b.newRandomTx(false, testBankAddress, 1e15, testAddress2Key, 0, big.NewInt(110*params.InitialBaseFee)), + b.newRandomTx(false, testBankAddress, 1e15, testAddress3Key, 0, big.NewInt(120*params.InitialBaseFee)), + } + + searcherTxs := make([]*types.Transaction, len(searcherPrivateKeys)*2) + for i, pk := range searcherPrivateKeys { + searcherTxs[2*i] = b.newRandomTx(false, testBankAddress, 1, pk, 0, big.NewInt(150*params.InitialBaseFee)) + searcherTxs[2*i+1] = b.newRandomTx(false, testBankAddress, 1+1, pk, 0, big.NewInt(150*params.InitialBaseFee)) + } + + nBundles := 2 * len(searcherPrivateKeys) + // two bundles per searcher, i and i+1 + bundles := make([]*types.MevBundle, nBundles) + for i := 0; i < nBundles; i++ { + bundles[i] = new(types.MevBundle) + bundles[i].Txs = append(bundles[i].Txs, searcherTxs[i]) + } + + // common transactions in 10% of the bundles, randomly + for i := 0; i < nBundles/10; i++ { + randomCommonIndex := rand.Intn(len(commonTxs)) + randomBundleIndex := rand.Intn(nBundles) + bundles[randomBundleIndex].Txs = append(bundles[randomBundleIndex].Txs, commonTxs[randomCommonIndex]) + } + + // additional lower profit transactions in 10% of the bundles, randomly + for _, extraKey := range extraKeys { + tx := b.newRandomTx(false, testBankAddress, 1, extraKey, 0, big.NewInt(20*params.InitialBaseFee)) + randomBundleIndex := rand.Intn(nBundles) + bundles[randomBundleIndex].Txs = append(bundles[randomBundleIndex].Txs, tx) + } + + blockNumber := big.NewInt(0).Add(chain.CurrentBlock().Number(), big.NewInt(1)) + for _, bundle := range bundles { + err := b.txPool.AddMevBundle(bundle.Txs, blockNumber, 0, 0, nil) + require.NoError(t, err) + } + + blockCh, errCh, err := w.getSealingBlock(chain.CurrentBlock().Hash(), chain.CurrentHeader().Time+12, testUserAddress, 0, common.Hash{}, false, false, nil) + require.NoError(t, err) + select { + case block := <-blockCh: + state, err := chain.State() + require.NoError(t, err) + balancePre := state.GetBalance(testUserAddress) + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + state, err = chain.StateAt(block.Root()) + require.NoError(t, err) + balancePost := state.GetBalance(testUserAddress) + t.Log("Balances", balancePre, balancePost) + case err := <-errCh: + require.NoError(t, err) + case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. + t.Fatalf("timeout") + } + } +}