diff --git a/miner/verify_bundles.go b/miner/verify_bundles.go index 07eb476786..69a4aa513d 100644 --- a/miner/verify_bundles.go +++ b/miner/verify_bundles.go @@ -72,18 +72,43 @@ func (e *ErrBundleTxWrongPlace) Error() string { return fmt.Sprintf("tx from included bundle is in wrong place tx_hash=%s, bundle_hash=%s, tx_bundle_index=%d, tx_block_index=%d, expected_block_index=%d", e.TxHash.Hex(), e.BundleHash.Hex(), e.TxIndex, e.BlockIndex, e.ExpectedBlockIndex) } +// ErrPrivateTxFromFailedBundle is returned when a private tx is included in the block, but the bundle it belongs to was not included +type ErrPrivateTxFromFailedBundle struct { + BundleHash common.Hash + TxHash common.Hash + // Index of the tx in the bundle + TxIndex int +} + +func NewErrPrivateTxFromFailedBundle(bundleHash, txHash common.Hash, txIndex int) *ErrPrivateTxFromFailedBundle { + return &ErrPrivateTxFromFailedBundle{ + BundleHash: bundleHash, + TxHash: txHash, + TxIndex: txIndex, + } +} + +func (e *ErrPrivateTxFromFailedBundle) Error() string { + return fmt.Sprintf("private tx from failed bundle included in the block tx_hash=%s, bundle_hash=%s, tx_bundle_index=%d", e.TxHash.Hex(), e.BundleHash.Hex(), e.TxIndex) +} + // VerifyBundlesAtomicity checks that all txs from the included bundles are included in the block correctly // 1. We check that all non-reverted txs from the bundle are included in the block in correct order (with possible gaps) and are not reverted // 2. Reverted txs are allowed to be not included in the block // NOTE: we only verify bundles that were committed in the block but not all bundles that we tried to include -func VerifyBundlesAtomicity(env *environment, commitedBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle) error { +func VerifyBundlesAtomicity(env *environment, committedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle, mempoolTxHashes map[common.Hash]struct{}) error { // bundleHash -> tx includedBundles := make(map[common.Hash][]bundleTxData) - extractBundleTxDataFromBundles(commitedBundles, includedBundles) - extractBundleTxDataFromSbundles(usedSbundles, includedBundles) + extractBundleTxDataFromBundles(committedBundles, includedBundles) + extractBundleTxDataFromSbundles(usedSbundles, includedBundles, true) includedTxDataByHash := extractIncludedTxDataFromEnv(env) - return checkBundlesAtomicity(includedBundles, includedTxDataByHash) + allUsedBundles := make(map[common.Hash][]bundleTxData) + extractBundleTxDataFromBundles(allBundles, allUsedBundles) + extractBundleTxDataFromSbundles(usedSbundles, allUsedBundles, false) + privateTxDataFromFailedBundles := extractPrivateTxsFromFailedBundles(includedBundles, allUsedBundles, mempoolTxHashes) + + return checkBundlesAtomicity(includedBundles, includedTxDataByHash, privateTxDataFromFailedBundles) } type bundleTxData struct { @@ -97,11 +122,22 @@ type includedTxData struct { reverted bool } +type privateTxData struct { + bundleHash common.Hash + index int +} + // checkBundlesAtomicity checks that all txs from the included bundles are included in the block correctly // 1. We check that all non-reverted txs from the bundle are included in the block and are not reverted // 2. Reverted txs are allowed to be not included in the block // 3. All txs from the bundle must be in the right order, gaps between txs are allowed -func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, includedTxDataByHash map[common.Hash]includedTxData) error { +func checkBundlesAtomicity( + includedBundles map[common.Hash][]bundleTxData, + includedTxDataByHash map[common.Hash]includedTxData, + privateTxsFromFailedBundles map[common.Hash]privateTxData, +) error { + txsFromSuccessfulBundles := make(map[common.Hash]struct{}) + for bundleHash, b := range includedBundles { var ( firstTxBlockIdx int @@ -109,6 +145,8 @@ func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, inclu ) // 1. locate the first included tx of the bundle for bundleIdx, tx := range b { + txsFromSuccessfulBundles[tx.hash] = struct{}{} + txInclusion, ok := includedTxDataByHash[tx.hash] if !ok { // tx not found, maybe it was reverting @@ -131,6 +169,8 @@ func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, inclu currentBlockTx := firstTxBlockIdx + 1 // locate other txs in the bundle for idx, tx := range b[firstTxBundleIdx+1:] { + txsFromSuccessfulBundles[tx.hash] = struct{}{} + bundleIdx := firstTxBundleIdx + 1 + idx // see if tx is on its place txInclusion, ok := includedTxDataByHash[tx.hash] @@ -156,6 +196,16 @@ func checkBundlesAtomicity(includedBundles map[common.Hash][]bundleTxData, inclu currentBlockTx = txInclusion.index + 1 } } + + for hash, priv := range privateTxsFromFailedBundles { + if _, ok := txsFromSuccessfulBundles[hash]; ok { + continue + } + if _, ok := includedTxDataByHash[hash]; ok { + return NewErrPrivateTxFromFailedBundle(priv.bundleHash, hash, priv.index) + } + } + return nil } @@ -187,9 +237,9 @@ func getShareBundleTxData(bundle *types.SBundle) []bundleTxData { return res } -func extractBundleTxDataFromSbundles(bundles []types.UsedSBundle, result map[common.Hash][]bundleTxData) { +func extractBundleTxDataFromSbundles(bundles []types.UsedSBundle, result map[common.Hash][]bundleTxData, onlyIncluded bool) { for _, b := range bundles { - if !b.Success { + if onlyIncluded && !b.Success { continue } result[b.Bundle.Hash()] = getShareBundleTxData(b.Bundle) @@ -209,3 +259,27 @@ func extractIncludedTxDataFromEnv(env *environment) map[common.Hash]includedTxDa } return res } + +func extractPrivateTxsFromFailedBundles( + includedBundles, allBundles map[common.Hash][]bundleTxData, mempoolTxHashes map[common.Hash]struct{}, +) map[common.Hash]privateTxData { + // we don't handle overlapping bundles here, they are handled in checkBundlesAtomicity + res := make(map[common.Hash]privateTxData) + + for bundleHash, b := range allBundles { + if _, bundleIncluded := includedBundles[bundleHash]; bundleIncluded { + continue + } + + for i, tx := range b { + if _, mempool := mempoolTxHashes[tx.hash]; mempool { + continue + } + res[tx.hash] = privateTxData{ + bundleHash: bundleHash, + index: i, + } + } + } + return res +} diff --git a/miner/verify_bundles_test.go b/miner/verify_bundles_test.go index 44fd5723c5..18b8949b92 100644 --- a/miner/verify_bundles_test.go +++ b/miner/verify_bundles_test.go @@ -16,6 +16,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { includedBundles map[common.Hash][]bundleTxData // includedTxDataByHash is a map of tx hash to tx data that were included in the block includedTxDataByHash map[common.Hash]includedTxData + // privateTxData is a map of tx hash to private tx data of private txs from failed bundles + privateTxData map[common.Hash]privateTxData // expectedErr is the expected error returned by verifyBundles expectedErr error }{ @@ -35,7 +37,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: false}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false}, }, - expectedErr: nil, + privateTxData: nil, + expectedErr: nil, }, { name: "Simple bundle included with gaps between txs", @@ -56,7 +59,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xc5"): {hash: common.HexToHash("0xc5"), index: 6, reverted: false}, common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 7, reverted: false}, }, - expectedErr: nil, + privateTxData: nil, + expectedErr: nil, }, { name: "Simple bundle included with revertible tx, tx included and reverted", @@ -74,7 +78,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: true}, common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 4, reverted: false}, }, - expectedErr: nil, + privateTxData: nil, + expectedErr: nil, }, { name: "Simple bundle included with revertible tx, tx not included", @@ -89,7 +94,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 0, reverted: false}, common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 1, reverted: false}, }, - expectedErr: nil, + privateTxData: nil, + expectedErr: nil, }, { name: "Simple bundle included with all revertible tx, last of them is included as success", @@ -105,7 +111,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb13"): {hash: common.HexToHash("0xb13"), index: 1, reverted: false}, common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 2, reverted: true}, }, - expectedErr: nil, + privateTxData: nil, + expectedErr: nil, }, { name: "Simple bundle included with all revertible tx, none of the txs are included", @@ -120,7 +127,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 0, reverted: true}, common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 1, reverted: true}, }, - expectedErr: nil, + privateTxData: nil, + expectedErr: nil, }, { name: "Two bundles included, both backrun one tx that is allowed to revert", @@ -140,6 +148,27 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 2, reverted: true}, common.HexToHash("0xb22"): {hash: common.HexToHash("0xb22"), index: 3, reverted: false}, }, + privateTxData: nil, + expectedErr: nil, + }, + { + name: "Private tx from overlapping included bundle included", + includedBundles: map[common.Hash][]bundleTxData{ + common.HexToHash("0xb1"): { + {hash: common.HexToHash("0xb11"), canRevert: true}, + {hash: common.HexToHash("0xb12"), canRevert: false}, + }, + }, + includedTxDataByHash: map[common.Hash]includedTxData{ + common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 0, reverted: false}, + common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 1, reverted: true}, + common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 2, reverted: false}, + common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: false}, + common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false}, + }, + privateTxData: map[common.Hash]privateTxData{ + common.HexToHash("0xb11"): {bundleHash: common.HexToHash("0xb2"), index: 2}, + }, expectedErr: nil, }, // Error cases @@ -158,7 +187,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: false}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false}, }, - expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0), + privateTxData: nil, + expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0), }, { name: "Simple bundle included but with reverted txs (second tx reverted)", @@ -175,7 +205,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 3, reverted: true}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false}, }, - expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1), + privateTxData: nil, + expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1), }, { name: "Simple bundle included with gaps between txs (second tx reverted)", @@ -192,7 +223,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false}, common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 4, reverted: true}, }, - expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1), + privateTxData: nil, + expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1), }, { name: "Simple bundle included but with incorrect order", @@ -209,7 +241,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 3, reverted: false}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 4, reverted: false}, }, - expectedErr: NewErrBundleTxWrongPlace(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1, 2, 4), + privateTxData: nil, + expectedErr: NewErrBundleTxWrongPlace(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1, 2, 4), }, { name: "Simple bundle included but first tx missing", @@ -225,7 +258,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb12"): {hash: common.HexToHash("0xb12"), index: 2, reverted: false}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false}, }, - expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0), + privateTxData: nil, + expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 0), }, { name: "Simple bundle included but second tx missing", @@ -241,7 +275,8 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 2, reverted: false}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false}, }, - expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1), + privateTxData: nil, + expectedErr: NewErrBundleTxNotFound(common.HexToHash("0xb1"), common.HexToHash("0xb12"), 1), }, { name: "Bundle with multiple reverting txs in the front has failing tx", @@ -259,13 +294,28 @@ func TestVerifyBundlesAtomicity(t *testing.T) { common.HexToHash("0xb14"): {hash: common.HexToHash("0xb11"), index: 2, reverted: true}, common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 3, reverted: false}, }, - expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb14"), 3), + privateTxData: nil, + expectedErr: NewErrBundleTxReverted(common.HexToHash("0xb1"), common.HexToHash("0xb14"), 3), + }, + { + name: "Private tx from failed bundles was included in a block", + includedBundles: nil, + includedTxDataByHash: map[common.Hash]includedTxData{ + common.HexToHash("0xc1"): {hash: common.HexToHash("0xc1"), index: 0, reverted: false}, + common.HexToHash("0xc2"): {hash: common.HexToHash("0xc2"), index: 1, reverted: true}, + common.HexToHash("0xb11"): {hash: common.HexToHash("0xb11"), index: 2, reverted: false}, + common.HexToHash("0xc3"): {hash: common.HexToHash("0xc3"), index: 2, reverted: false}, + }, + privateTxData: map[common.Hash]privateTxData{ + common.HexToHash("0xb11"): {bundleHash: common.HexToHash("0xb1"), index: 2}, + }, + expectedErr: NewErrPrivateTxFromFailedBundle(common.HexToHash("0xb1"), common.HexToHash("0xb11"), 2), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := checkBundlesAtomicity(test.includedBundles, test.includedTxDataByHash) + err := checkBundlesAtomicity(test.includedBundles, test.includedTxDataByHash, test.privateTxData) if test.expectedErr == nil { require.NoError(t, err) } else { @@ -319,7 +369,7 @@ func TestExtractBundleDataFromUsedBundles(t *testing.T) { result := make(map[common.Hash][]bundleTxData) extractBundleTxDataFromBundles([]types.SimulatedBundle{bundle}, result) - extractBundleTxDataFromSbundles([]types.UsedSBundle{{Bundle: sbundle, Success: true}}, result) + extractBundleTxDataFromSbundles([]types.UsedSBundle{{Bundle: sbundle, Success: true}}, result, true) require.Equal(t, expectedResult, result) } @@ -346,3 +396,38 @@ func TestExtractIncludedTxDataFromEnv(t *testing.T) { result := extractIncludedTxDataFromEnv(env) require.Equal(t, expectedResult, result) } + +func TestExtractPrivateTxData(t *testing.T) { + includedBundles := map[common.Hash][]bundleTxData{ + common.HexToHash("0xb1"): { + {hash: common.HexToHash("0xb11"), canRevert: true}, + }, + common.HexToHash("0xb2"): { + {hash: common.HexToHash("0xb21"), canRevert: true}, + }, + } + allUsedBundles := map[common.Hash][]bundleTxData{ + common.HexToHash("0xb1"): { + {hash: common.HexToHash("0xb11"), canRevert: true}, + }, + common.HexToHash("0xb2"): { + {hash: common.HexToHash("0xb21"), canRevert: true}, + }, + common.HexToHash("0xb3"): { + {hash: common.HexToHash("0xb31"), canRevert: true}, + {hash: common.HexToHash("0xb32"), canRevert: true}, + }, + } + mempoolTxHashes := map[common.Hash]struct{}{ + common.HexToHash("0xb11"): {}, + common.HexToHash("0xb31"): {}, + } + + expectedResult := map[common.Hash]privateTxData{ + common.HexToHash("0xb32"): {bundleHash: common.HexToHash("0xb3"), index: 1}, + } + + result := extractPrivateTxsFromFailedBundles(includedBundles, allUsedBundles, mempoolTxHashes) + + require.Equal(t, expectedResult, result) +} diff --git a/miner/worker.go b/miner/worker.go index 24eae2cc85..a58860a7f5 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1303,32 +1303,39 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { return env, nil } -func (w *worker) fillTransactionsSelectAlgo(interrupt *int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, []types.UsedSBundle, error) { +func (w *worker) fillTransactionsSelectAlgo(interrupt *int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, []types.UsedSBundle, map[common.Hash]struct{}, error) { var ( - blockBundles []types.SimulatedBundle - allBundles []types.SimulatedBundle - usedSbundles []types.UsedSBundle - err error + blockBundles []types.SimulatedBundle + allBundles []types.SimulatedBundle + usedSbundles []types.UsedSBundle + mempoolTxHashes map[common.Hash]struct{} + err error ) switch w.flashbots.algoType { case ALGO_GREEDY, ALGO_GREEDY_BUCKETS: - blockBundles, allBundles, usedSbundles, err = w.fillTransactionsAlgoWorker(interrupt, env) + blockBundles, allBundles, usedSbundles, mempoolTxHashes, err = w.fillTransactionsAlgoWorker(interrupt, env) case ALGO_MEV_GETH: - blockBundles, allBundles, err = w.fillTransactions(interrupt, env) + blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env) default: - blockBundles, allBundles, err = w.fillTransactions(interrupt, env) + blockBundles, allBundles, mempoolTxHashes, err = w.fillTransactions(interrupt, env) } - return blockBundles, allBundles, usedSbundles, err + return blockBundles, allBundles, usedSbundles, mempoolTxHashes, err } // fillTransactions retrieves the pending transactions from the txpool and fills them // into the given sealing block. The transaction selection and ordering strategy can // be customized with the plugin in the future. // Returns error if any, otherwise the bundles that made it into the block and all bundles that passed simulation -func (w *worker) fillTransactions(interrupt *int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, error) { +func (w *worker) fillTransactions(interrupt *int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, map[common.Hash]struct{}, error) { // Split the pending transactions into locals and remotes // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) + mempoolTxHashes := make(map[common.Hash]struct{}, len(pending)) + for _, txs := range pending { + for _, tx := range txs { + mempoolTxHashes[tx.Hash()] = struct{}{} + } + } localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending for _, account := range w.eth.TxPool().Locals() { if txs := remoteTxs[account]; len(txs) > 0 { @@ -1354,14 +1361,14 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) ([]types.S bundleTxs, resultingBundle, mergedBundles, numBundles, allBundles, err = w.generateFlashbotsBundle(env, bundles, pending) if err != nil { log.Error("Failed to generate flashbots bundle", "err", err) - return nil, nil, err + return nil, nil, nil, err } log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(resultingBundle.TotalEth), "gasUsed", resultingBundle.TotalGasUsed, "bundleScore", resultingBundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles) if len(bundleTxs) == 0 { - return nil, nil, errors.New("no bundles to apply") + return nil, nil, nil, errors.New("no bundles to apply") } if err := w.commitBundle(env, bundleTxs, interrupt); err != nil { - return nil, nil, err + return nil, nil, nil, err } blockBundles = mergedBundles env.profit.Add(env.profit, resultingBundle.EthSentToCoinbase) @@ -1370,29 +1377,35 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) ([]types.S if len(localTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, nil, nil, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { - return nil, nil, err + return nil, nil, nil, err } } if len(remoteTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, nil, nil, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { - return nil, nil, err + return nil, nil, nil, err } } - return blockBundles, allBundles, nil + return blockBundles, allBundles, mempoolTxHashes, nil } // fillTransactionsAlgoWorker retrieves the pending transactions and bundles from the txpool and fills them // into the given sealing block. // Returns error if any, otherwise the bundles that made it into the block and all bundles that passed simulation -func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, []types.UsedSBundle, error) { +func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) ([]types.SimulatedBundle, []types.SimulatedBundle, []types.UsedSBundle, map[common.Hash]struct{}, error) { // Split the pending transactions into locals and remotes // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) + mempoolTxHashes := make(map[common.Hash]struct{}, len(pending)) + for _, txs := range pending { + for _, tx := range txs { + mempoolTxHashes[tx.Hash()] = struct{}{} + } + } bundlesToConsider, sbundlesToConsider, err := w.getSimulatedBundles(env) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } var ( @@ -1405,7 +1418,7 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) case ALGO_GREEDY_BUCKETS: priceCutoffPercent := w.config.PriceCutoffPercent if !(priceCutoffPercent >= 0 && priceCutoffPercent <= 100) { - return nil, nil, nil, errors.New("invalid price cutoff percent - must be between 0 and 100") + return nil, nil, nil, nil, errors.New("invalid price cutoff percent - must be between 0 and 100") } algoConf := &algorithmConfig{ @@ -1443,7 +1456,7 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) } *env = *newEnv - return blockBundles, bundlesToConsider, usedSbundle, err + return blockBundles, bundlesToConsider, usedSbundle, mempoolTxHashes, err } func (w *worker) getSimulatedBundles(env *environment) ([]types.SimulatedBundle, []*types.SimSBundle, error) { @@ -1533,13 +1546,13 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e orderCloseTime := time.Now() - blockBundles, allBundles, usedSbundles, err := w.fillTransactionsSelectAlgo(nil, work) + blockBundles, allBundles, usedSbundles, mempoolTxHashes, err := w.fillTransactionsSelectAlgo(nil, work) if err != nil { return nil, nil, err } - err = VerifyBundlesAtomicity(work, blockBundles, usedSbundles) + err = VerifyBundlesAtomicity(work, blockBundles, allBundles, usedSbundles, mempoolTxHashes) if err != nil { return nil, nil, err } @@ -1629,7 +1642,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) { } // Fill pending transactions from the txpool - _, _, _, err = w.fillTransactionsSelectAlgo(interrupt, work) + _, _, _, _, err = w.fillTransactionsSelectAlgo(interrupt, work) switch { case err == nil: // The entire block is filled, decrease resubmit interval in case