diff --git a/test/src/main.rs b/test/src/main.rs index 0d6984186f7..030617b27c3 100644 --- a/test/src/main.rs +++ b/test/src/main.rs @@ -429,6 +429,7 @@ fn all_specs() -> Vec> { Box::new(TxPoolOrphanNormal), Box::new(TxPoolOrphanReverse), Box::new(TxPoolOrphanUnordered), + Box::new(TxPoolOrphanDoubleSpend), Box::new(OrphanTxRejected), Box::new(GetRawTxPool), Box::new(PoolReconcile), diff --git a/test/src/specs/tx_pool/orphan_tx.rs b/test/src/specs/tx_pool/orphan_tx.rs index 4afb48626f1..90398822dde 100644 --- a/test/src/specs/tx_pool/orphan_tx.rs +++ b/test/src/specs/tx_pool/orphan_tx.rs @@ -302,3 +302,49 @@ impl Spec for TxPoolOrphanUnordered { ); } } + +pub struct TxPoolOrphanDoubleSpend; +impl Spec for TxPoolOrphanDoubleSpend { + fn run(&self, nodes: &mut Vec) { + let node0 = &nodes[0]; + node0.mine_until_out_bootstrap_period(); + let parent = node0.new_transaction_with_capacity(capacity_bytes!(800)); + + let script = node0.always_success_script(); + let new_output1 = CellOutputBuilder::default() + .capacity(capacity_bytes!(200).pack()) + .lock(script.clone()) + .build(); + let new_output2 = new_output1.clone(); + let new_output3 = new_output1.clone(); + + let tx1 = parent + .as_advanced_builder() + .set_inputs(vec![CellInput::new(OutPoint::new(parent.hash(), 0), 0)]) + .set_outputs(vec![new_output1, new_output2, new_output3]) + .set_outputs_data(vec![Default::default(); 3]) + .build(); + + let tx11 = + node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(100), 0, 0); + let tx12 = + node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(120), 0, 0); + + let mut net = Net::new( + "orphan_tx_test", + node0.consensus(), + vec![SupportProtocols::RelayV3], + ); + net.connect(node0); + + assert!( + run_replay_tx(&net, node0, tx11, 1, 0), + "tx11 in orphan pool" + ); + + assert!( + run_replay_tx(&net, node0, tx12, 1, 0), + "tx12 is not in orphan pool" + ); + } +} diff --git a/tx-pool/src/component/orphan.rs b/tx-pool/src/component/orphan.rs index 7459e944450..ebd99cd1568 100644 --- a/tx-pool/src/component/orphan.rs +++ b/tx-pool/src/component/orphan.rs @@ -69,6 +69,7 @@ impl OrphanPool { pub fn remove_orphan_tx(&mut self, id: &ProposalShortId) -> Option { self.entries.remove(id).map(|entry| { + debug!("remove orphan tx {}", entry.tx.hash()); for out_point in entry.tx.input_pts_iter() { self.by_out_point.remove(&out_point); } @@ -122,6 +123,15 @@ impl OrphanPool { if self.entries.contains_key(&tx.proposal_short_id()) { return; } + + // double spend checking + if tx + .input_pts_iter() + .any(|out_point| self.by_out_point.contains_key(&out_point)) + { + return; + } + debug!("add_orphan_tx {}", tx.hash()); self.entries.insert( diff --git a/tx-pool/src/pool.rs b/tx-pool/src/pool.rs index cb7d207aaa2..20e3028e323 100644 --- a/tx-pool/src/pool.rs +++ b/tx-pool/src/pool.rs @@ -203,6 +203,7 @@ impl TxPool { ) { for tx in txs { let tx_hash = tx.hash(); + debug!("try remove_committed_tx {}", tx_hash); self.remove_committed_tx(tx, callbacks); self.committed_txs_hash_cache diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 80ff1262c62..61d303b0990 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -475,7 +475,7 @@ impl TxPoolService { let orphan = self.orphan.read().await; let ids = orphan.find_by_previous(tx); ids.iter() - .map(|id| orphan.get(id).cloned().unwrap()) + .filter_map(|id| orphan.get(id).cloned()) .collect::>() } @@ -483,6 +483,9 @@ impl TxPoolService { self.orphan.write().await.remove_orphan_tx(id); } + /// Remove all orphans which are resolved by the given transaction + /// the process is like a breath first search, if there is a cycle in `orphan_queue`, + /// `_process_tx` will return `Reject` since we have checked duplicated tx pub(crate) async fn process_orphan_tx(&self, tx: &TransactionView) { let mut orphan_queue: VecDeque = VecDeque::new(); orphan_queue.push_back(tx.clone());