diff --git a/core/txpool/bundlepool/bundlepool.go b/core/txpool/bundlepool/bundlepool.go index a1b0e1a838..0bbaee7536 100644 --- a/core/txpool/bundlepool/bundlepool.go +++ b/core/txpool/bundlepool/bundlepool.go @@ -361,6 +361,12 @@ func (p *BundlePool) reset(newHead *types.Header) { p.mu.Lock() defer p.mu.Unlock() + if len(p.bundles) == 0 { + bundleGauge.Update(int64(len(p.bundles))) + slotsGauge.Update(int64(p.slots)) + return + } + // Prune outdated bundles and invalid bundles block := p.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) txSet := mapset.NewSet[common.Hash]() @@ -370,14 +376,24 @@ func (p *BundlePool) reset(newHead *types.Header) { txSet.Add(tx.Hash()) } } + p.bundleHeap = make(BundleHeap, 0) for hash, bundle := range p.bundles { if (bundle.MaxTimestamp != 0 && newHead.Time > bundle.MaxTimestamp) || (bundle.MaxBlockNumber != 0 && newHead.Number.Cmp(new(big.Int).SetUint64(bundle.MaxBlockNumber)) > 0) { p.slots -= numSlots(p.bundles[hash]) delete(p.bundles, hash) - } else if txSet.Contains(bundle.Txs[0].Hash()) { - p.slots -= numSlots(p.bundles[hash]) - delete(p.bundles, hash) + continue + } else { + for _, tx := range bundle.Txs { + if txSet.Contains(tx.Hash()) && !containsHash(bundle.DroppingTxHashes, tx.Hash()) { + p.slots -= numSlots(p.bundles[hash]) + delete(p.bundles, hash) + break + } + } + } + if p.bundles[hash] != nil { + p.bundleHeap.Push(bundle) } } bundleGauge.Update(int64(len(p.bundles))) @@ -447,6 +463,15 @@ func numSlots(bundle *types.Bundle) uint64 { return (bundle.Size() + bundleSlotSize - 1) / bundleSlotSize } +func containsHash(arr []common.Hash, match common.Hash) bool { + for _, elem := range arr { + if elem == match { + return true + } + } + return false +} + // ===================================================================================================================== type BundleHeap []*types.Bundle diff --git a/core/types/bundle.go b/core/types/bundle.go index 5eb0922b9c..513a2bbf49 100644 --- a/core/types/bundle.go +++ b/core/types/bundle.go @@ -23,6 +23,7 @@ type SendBundleArgs struct { MinTimestamp *uint64 `json:"minTimestamp"` MaxTimestamp *uint64 `json:"maxTimestamp"` RevertingTxHashes []common.Hash `json:"revertingTxHashes"` + DroppingTxHashes []common.Hash `json:"droppingTxHashes"` } type Bundle struct { @@ -31,6 +32,7 @@ type Bundle struct { MinTimestamp uint64 MaxTimestamp uint64 RevertingTxHashes []common.Hash + DroppingTxHashes []common.Hash Price *big.Int // for bundle compare and prune @@ -41,6 +43,7 @@ type Bundle struct { type SimulatedBundle struct { OriginalBundle *Bundle + SucceedTxs Transactions BundleGasFees *big.Int BundleGasPrice *big.Int diff --git a/internal/ethapi/api_bundle.go b/internal/ethapi/api_bundle.go index d291dc2083..3e2e14c7fe 100644 --- a/internal/ethapi/api_bundle.go +++ b/internal/ethapi/api_bundle.go @@ -116,6 +116,7 @@ func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args types.SendBund MinTimestamp: minTimestamp, MaxTimestamp: maxTimestamp, RevertingTxHashes: args.RevertingTxHashes, + DroppingTxHashes: args.DroppingTxHashes, } // If the maxBlockNumber and maxTimestamp are not set, set max ddl of bundle as types.MaxBundleAliveBlock diff --git a/miner/worker_builder.go b/miner/worker_builder.go index 61a5672c2f..2a42139a92 100644 --- a/miner/worker_builder.go +++ b/miner/worker_builder.go @@ -331,9 +331,9 @@ func (w *worker) mergeBundles( log.Info("included bundle", "gasUsed", simulatedBundle.BundleGasUsed, "gasPrice", simulatedBundle.BundleGasPrice, - "txcount", len(simulatedBundle.OriginalBundle.Txs)) + "txcount", len(simulatedBundle.SucceedTxs)) - includedTxs = append(includedTxs, bundle.OriginalBundle.Txs...) + includedTxs = append(includedTxs, simulatedBundle.SucceedTxs...) mergedBundle.BundleGasFees.Add(mergedBundle.BundleGasFees, simulatedBundle.BundleGasFees) mergedBundle.BundleGasUsed += simulatedBundle.BundleGasUsed @@ -366,13 +366,24 @@ func (w *worker) simulateBundle( bundleGasFees = new(big.Int) ) - for i, tx := range bundle.Txs { - state.SetTxContext(tx.Hash(), i+currentTxCount) + succeedTxs := types.Transactions{} + succeedTxCount := 0 + for _, tx := range bundle.Txs { + state.SetTxContext(tx.Hash(), succeedTxCount+currentTxCount) + + snap := state.Snapshot() + gp := gasPool.Gas() receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &w.coinbase, gasPool, state, env.header, tx, &tempGasUsed, *w.chain.GetVMConfig()) if err != nil { log.Warn("fail to simulate bundle", "hash", bundle.Hash().String(), "err", err) + if containsHash(bundle.DroppingTxHashes, tx.Hash()) { + log.Warn("drop tx in bundle", "hash", tx.Hash().String()) + state.RevertToSnapshot(snap) + gasPool.SetGas(gp) + continue + } if prune { if errors.Is(err, core.ErrGasLimitReached) && !pruneGasExceed { @@ -387,6 +398,12 @@ func (w *worker) simulateBundle( } if receipt.Status == types.ReceiptStatusFailed && !containsHash(bundle.RevertingTxHashes, receipt.TxHash) { + // for unRevertible tx but itself can be dropped, we drop it and revert the state and gas pool + if containsHash(bundle.DroppingTxHashes, receipt.TxHash) { + log.Warn("drop tx in bundle", "hash", receipt.TxHash.String()) + gasPool.SetGas(gp) + continue + } err = errNonRevertingTxInBundleFailed log.Warn("fail to simulate bundle", "hash", bundle.Hash().String(), "err", err) @@ -411,7 +428,17 @@ func (w *worker) simulateBundle( txGasFees := new(big.Int).Mul(txGasUsed, effectiveTip) bundleGasFees.Add(bundleGasFees, txGasFees) } + succeedTxs = append(succeedTxs, tx) + succeedTxCount++ } + + // prune bundle when all txs are dropped + if len(succeedTxs) == 0 { + log.Warn("prune bundle", "hash", bundle.Hash().String(), "err", "empty bundle") + w.eth.TxPool().PruneBundle(bundle.Hash()) + return nil, errors.New("empty bundle") + } + // if all txs in the bundle are from txpool, we accept the bundle without checking gas price bundleGasPrice := big.NewInt(0) if bundleGasUsed != 0 { @@ -432,6 +459,7 @@ func (w *worker) simulateBundle( return &types.SimulatedBundle{ OriginalBundle: bundle, + SucceedTxs: succeedTxs, BundleGasFees: bundleGasFees, BundleGasPrice: bundleGasPrice, BundleGasUsed: bundleGasUsed,