diff --git a/core/types/bundle.go b/core/types/bundle.go index ec1b201d74..006cbf77d3 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 diff --git a/core/types/transaction.go b/core/types/transaction.go index b91f8b10b8..75baa9ee3b 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -546,6 +546,12 @@ func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) { } } +func (s Transactions) Remove(idx int) Transactions { + copy(s[idx:], s[idx+1:]) + s[len(s)-1] = nil + return s[:len(s)-1] +} + // TxDifference returns a new set which is the difference between a and b. func TxDifference(a, b Transactions) Transactions { keep := make(Transactions, 0, len(a)) diff --git a/internal/ethapi/api_bundle.go b/internal/ethapi/api_bundle.go index b88ca258ba..ea8ff1518a 100644 --- a/internal/ethapi/api_bundle.go +++ b/internal/ethapi/api_bundle.go @@ -110,6 +110,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 3d17abf27c..8a152f8d2c 100644 --- a/miner/worker_builder.go +++ b/miner/worker_builder.go @@ -413,15 +413,28 @@ func (w *worker) simulateBundle( ethSentToSystem = new(big.Int) ) + currentState := state.Copy() + for i, tx := range bundle.Txs { state.SetTxContext(tx.Hash(), i+currentTxCount) sysBalanceBefore := state.GetBalance(consensus.SystemAddress) + prevState := currentState.Copy() + prevGasPool := new(core.GasPool).AddGas(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 = prevState + gasPool = prevGasPool + bundle.Txs = bundle.Txs.Remove(i) + continue + } + if prune { if errors.Is(err, core.ErrGasLimitReached) && !pruneGasExceed { log.Warn("bundle gas limit exceed", "hash", bundle.Hash().String()) @@ -435,6 +448,15 @@ 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()) + state = prevState + gasPool = prevGasPool + bundle.Txs = bundle.Txs.Remove(i) + continue + } + err = errNonRevertingTxInBundleFailed log.Warn("fail to simulate bundle", "hash", bundle.Hash().String(), "err", err) @@ -474,6 +496,13 @@ func (w *worker) simulateBundle( } } + // prune bundle when all txs are dropped + if len(bundle.Txs) == 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 mempool, we accept the bundle without checking gas price bundleGasPrice := big.NewInt(0)