1use bitcoin::Txid;
7use quick_cache::sync::Cache as QuickCache;
8
9use crate::types::{TxNode, TxOutput};
10
11const DEFAULT_TX_CAPACITY: usize = 20_000;
17
18const DEFAULT_PREVOUT_CAPACITY: usize = 100_000;
20
21pub struct Cache {
32 transactions: QuickCache<Txid, TxNode>,
33 prevouts: QuickCache<(Txid, u32), TxOutput>,
34}
35
36impl Cache {
37 pub fn new() -> Self {
39 Self::with_capacity(DEFAULT_TX_CAPACITY, DEFAULT_PREVOUT_CAPACITY)
40 }
41
42 pub fn with_capacity(tx_cap: usize, prevout_cap: usize) -> Self {
44 assert!(tx_cap > 0, "tx capacity must be > 0");
45 assert!(prevout_cap > 0, "prevout capacity must be > 0");
46
47 Self {
48 transactions: QuickCache::new(tx_cap),
49 prevouts: QuickCache::new(prevout_cap),
50 }
51 }
52
53 pub async fn get_tx(&self, txid: &Txid) -> Option<TxNode> {
55 self.transactions.get(txid)
56 }
57
58 pub async fn insert_tx(&self, txid: Txid, node: TxNode) {
60 self.transactions.insert(txid, node);
61 }
62
63 pub async fn get_prevout(&self, txid: &Txid, vout: u32) -> Option<TxOutput> {
65 self.prevouts.get(&(*txid, vout))
66 }
67
68 pub async fn insert_prevout(&self, txid: Txid, vout: u32, info: TxOutput) {
70 self.prevouts.insert((txid, vout), info);
71 }
72}
73
74impl Default for Cache {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::test_util::{make_output, make_tx_node, txid_from_byte};
84
85 #[tokio::test]
86 async fn cache_returns_none_for_unknown_txid() {
87 let cache = Cache::new();
88 assert!(cache.get_tx(&txid_from_byte(1)).await.is_none());
89 }
90
91 #[tokio::test]
92 async fn cache_returns_inserted_tx() {
93 let cache = Cache::new();
94 let txid = txid_from_byte(1);
95 let node = make_tx_node(vec![], vec![make_output(1000)], 100);
96 cache.insert_tx(txid, node.clone()).await;
97
98 let cached = cache.get_tx(&txid).await.expect("should be cached");
99 assert_eq!(cached.txid, node.txid);
100 }
101
102 #[tokio::test]
103 async fn cache_evicts_lru_entry() {
104 let cache = Cache::with_capacity(2, 1);
107 let txid_a = txid_from_byte(1);
108 let txid_b = txid_from_byte(2);
109 let txid_c = txid_from_byte(3);
110
111 let node = make_tx_node(vec![], vec![make_output(1000)], 100);
112 cache.insert_tx(txid_a, node.clone()).await;
113 cache.insert_tx(txid_b, node.clone()).await;
114 cache.insert_tx(txid_c, node.clone()).await;
115
116 assert!(
117 cache.get_tx(&txid_a).await.is_none() || cache.get_tx(&txid_b).await.is_none(),
118 "one of the two older entries should be evicted"
119 );
120 assert!(cache.get_tx(&txid_c).await.is_some());
121 }
122
123 #[tokio::test]
124 async fn prevout_cache_hit_and_miss() {
125 let cache = Cache::new();
126 let txid = txid_from_byte(1);
127
128 assert!(cache.get_prevout(&txid, 0).await.is_none());
129
130 let info = make_output(5000);
131 cache.insert_prevout(txid, 0, info.clone()).await;
132
133 let cached = cache.get_prevout(&txid, 0).await.expect("should be cached");
134 assert_eq!(cached.value, info.value);
135
136 assert!(cache.get_prevout(&txid, 1).await.is_none());
138 }
139}