|
1 | | -use std::collections::HashMap; |
| 1 | +use std::collections::{HashMap, HashSet}; |
2 | 2 |
|
3 | | -use bitcoin::{Transaction, Wtxid}; |
| 3 | +use bitcoin::{Transaction, Txid, Wtxid}; |
4 | 4 | use tokio::sync::oneshot; |
5 | 5 |
|
| 6 | +use crate::Package; |
| 7 | + |
6 | 8 | #[derive(Debug)] |
7 | 9 | pub(crate) struct BroadcastQueue { |
8 | | - pending: HashMap<Wtxid, oneshot::Sender<Wtxid>>, |
9 | | - data: HashMap<Wtxid, Transaction>, |
| 10 | + // There are the transactions that a peer should receive first. In the case of 1p1c, these are |
| 11 | + // the `Wtxid` of the child transaction in the package. |
| 12 | + advertise: HashSet<Wtxid>, |
| 13 | + // Notify the user when: |
| 14 | + // 1. a singleton transaction was broadcast |
| 15 | + // 2. the final transaction in a package was broadcast |
| 16 | + callbacks: HashMap<Wtxid, (oneshot::Sender<Wtxid>, Wtxid)>, |
| 17 | + // These transactions will be fetched by the usual `Wtxid`. |
| 18 | + witness_data: HashMap<Wtxid, Transaction>, |
| 19 | + // These transactions represent missing inputs to a previously broadcast transaction. Because |
| 20 | + // the inputs use the legacy `Txid` in the outpoint, these transactions are indexed by `Txid`. |
| 21 | + legacy_data: HashMap<Txid, Transaction>, |
10 | 22 | } |
11 | 23 |
|
12 | 24 | impl BroadcastQueue { |
13 | 25 | pub(crate) fn new() -> Self { |
14 | 26 | Self { |
15 | | - pending: HashMap::new(), |
16 | | - data: HashMap::new(), |
| 27 | + advertise: HashSet::new(), |
| 28 | + callbacks: HashMap::new(), |
| 29 | + witness_data: HashMap::new(), |
| 30 | + legacy_data: HashMap::new(), |
17 | 31 | } |
18 | 32 | } |
19 | 33 |
|
20 | | - pub(crate) fn add_to_queue(&mut self, tx: Transaction, oneshot: oneshot::Sender<Wtxid>) { |
21 | | - let wtxid = tx.compute_wtxid(); |
22 | | - self.pending.insert(wtxid, oneshot); |
23 | | - self.data.insert(wtxid, tx); |
| 34 | + pub(crate) fn add_to_queue(&mut self, package: Package, oneshot: oneshot::Sender<Wtxid>) { |
| 35 | + let advertise_wtxid = package.advertise_package(); |
| 36 | + self.advertise.insert(advertise_wtxid); |
| 37 | + let parent = package.parent(); |
| 38 | + let parent_txid = parent.compute_txid(); |
| 39 | + let parent_wtxid = parent.compute_wtxid(); |
| 40 | + match package.child() { |
| 41 | + Some(child) => { |
| 42 | + let child_wtxid = child.compute_wtxid(); |
| 43 | + // Only confirm once the parent is confirmed to have been requested. |
| 44 | + self.callbacks.insert(parent_wtxid, (oneshot, child_wtxid)); |
| 45 | + self.witness_data.insert(child_wtxid, child); |
| 46 | + // The only way a peer can feasibly request this transaction is by `Txid`, as it is |
| 47 | + // never advertised explicitly. |
| 48 | + self.legacy_data.insert(parent_txid, parent); |
| 49 | + } |
| 50 | + None => { |
| 51 | + self.callbacks.insert(parent_wtxid, (oneshot, parent_wtxid)); |
| 52 | + self.witness_data.insert(parent_wtxid, parent); |
| 53 | + } |
| 54 | + } |
24 | 55 | } |
25 | 56 |
|
26 | | - pub(crate) fn fetch_tx(&self, wtxid: Wtxid) -> Option<Transaction> { |
27 | | - self.data.get(&wtxid).cloned() |
| 57 | + pub(crate) fn fetch_tx(&self, id: impl Into<TxIdentifier>) -> Option<Transaction> { |
| 58 | + let id = id.into(); |
| 59 | + match id { |
| 60 | + TxIdentifier::Legacy(txid) => self.legacy_data.get(&txid).cloned(), |
| 61 | + TxIdentifier::Witness(wtxid) => self.witness_data.get(&wtxid).cloned(), |
| 62 | + } |
28 | 63 | } |
29 | 64 |
|
30 | | - pub(crate) fn successful(&mut self, wtxid: Wtxid) { |
31 | | - if let Some(pending) = self.pending.remove(&wtxid) { |
32 | | - let _ = pending.send(wtxid); |
| 65 | + pub(crate) fn sent_transaction_payload(&mut self, wtxid: Wtxid) { |
| 66 | + if let Some((callback, child)) = self.callbacks.remove(&wtxid) { |
| 67 | + self.advertise.remove(&child); |
| 68 | + let _ = callback.send(child); |
33 | 69 | } |
34 | 70 | } |
35 | 71 |
|
36 | 72 | pub(crate) fn pending_wtxid(&self) -> Vec<Wtxid> { |
37 | | - self.pending.keys().copied().collect() |
| 73 | + self.advertise.iter().copied().collect() |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, std::hash::Hash)] |
| 78 | +pub(crate) enum TxIdentifier { |
| 79 | + Legacy(Txid), |
| 80 | + Witness(Wtxid), |
| 81 | +} |
| 82 | + |
| 83 | +impl From<Txid> for TxIdentifier { |
| 84 | + fn from(value: Txid) -> Self { |
| 85 | + Self::Legacy(value) |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +impl From<Wtxid> for TxIdentifier { |
| 90 | + fn from(value: Wtxid) -> Self { |
| 91 | + Self::Witness(value) |
38 | 92 | } |
39 | 93 | } |
40 | 94 |
|
@@ -65,15 +119,15 @@ mod tests { |
65 | 119 | let transaction_2: Transaction = tx_data.transactions[1].clone().0; |
66 | 120 | let mut queue = BroadcastQueue::new(); |
67 | 121 | let (tx, _) = tokio::sync::oneshot::channel(); |
68 | | - queue.add_to_queue(transaction_1.clone(), tx); |
| 122 | + queue.add_to_queue(transaction_1.clone().into(), tx); |
69 | 123 | let (tx, _) = tokio::sync::oneshot::channel(); |
70 | | - queue.add_to_queue(transaction_2.clone(), tx); |
| 124 | + queue.add_to_queue(transaction_2.clone().into(), tx); |
71 | 125 | assert_eq!(queue.pending_wtxid().len(), 2); |
72 | | - queue.successful(transaction_1.compute_wtxid()); |
| 126 | + queue.sent_transaction_payload(transaction_1.compute_wtxid()); |
73 | 127 | assert_eq!(queue.pending_wtxid().len(), 1); |
74 | 128 | assert!(queue.fetch_tx(transaction_1.compute_wtxid()).is_some()); |
75 | 129 | assert!(queue.fetch_tx(transaction_2.compute_wtxid()).is_some()); |
76 | | - queue.successful(transaction_2.compute_wtxid()); |
| 130 | + queue.sent_transaction_payload(transaction_2.compute_wtxid()); |
77 | 131 | assert_eq!(queue.pending_wtxid().len(), 0); |
78 | 132 | } |
79 | 133 | } |
0 commit comments