cuprate_types/
output_cache.rs1use indexmap::{IndexMap, IndexSet};
2use monero_oxide::{
3 ed25519::CompressedPoint,
4 transaction::{Pruned, Transaction},
5};
6
7use cuprate_helper::{cast::u64_to_usize, crypto::compute_zero_commitment};
8
9use crate::{OutputOnChain, VerifiedBlockInformation};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct OutputCache {
14 cached_outputs: IndexMap<u64, IndexMap<u64, OutputOnChain>>,
16 number_of_outputs: IndexMap<u64, u64>,
18 wanted_outputs: IndexMap<u64, IndexSet<u64>>,
20}
21
22impl OutputCache {
23 pub const fn new(
25 cached_outputs: IndexMap<u64, IndexMap<u64, OutputOnChain>>,
26 number_of_outputs: IndexMap<u64, u64>,
27 wanted_outputs: IndexMap<u64, IndexSet<u64>>,
28 ) -> Self {
29 Self {
30 cached_outputs,
31 number_of_outputs,
32 wanted_outputs,
33 }
34 }
35
36 pub const fn cached_outputs(&self) -> &IndexMap<u64, IndexMap<u64, OutputOnChain>> {
43 &self.cached_outputs
44 }
45
46 pub fn number_outs_with_amount(&self, amount: u64) -> usize {
54 u64_to_usize(
55 self.number_of_outputs
56 .get(&amount)
57 .copied()
58 .unwrap_or_default(),
59 )
60 }
61
62 pub fn get_output(&self, amount: u64, index: u64) -> Option<&OutputOnChain> {
64 self.cached_outputs
65 .get(&amount)
66 .and_then(|map| map.get(&index))
67 }
68
69 fn add_tx<const MINER_TX: bool>(&mut self, height: usize, tx: &Transaction<Pruned>) {
71 for (i, out) in tx.prefix().outputs.iter().enumerate() {
72 let amount = if MINER_TX && tx.version() == 2 {
73 0
74 } else {
75 out.amount.unwrap_or_default()
76 };
77
78 let Some(outputs_with_amount) = self.number_of_outputs.get_mut(&amount) else {
79 continue;
80 };
81
82 let amount_index_of_out = *outputs_with_amount;
83 *outputs_with_amount += 1;
84
85 if let Some(set) = self.wanted_outputs.get_mut(&amount) {
86 if set.swap_remove(&amount_index_of_out) {
87 self.cached_outputs.entry(amount).or_default().insert(
88 amount_index_of_out,
89 OutputOnChain {
90 height,
91 time_lock: tx.prefix().additional_timelock,
92 key: out.key,
93 commitment: get_output_commitment(tx, i),
94 txid: None,
95 },
96 );
97 }
98 }
99 }
100 }
101
102 pub fn add_block_to_cache(&mut self, block: &VerifiedBlockInformation) {
109 self.add_tx::<true>(
110 block.height,
111 &block
112 .block
113 .miner_transaction()
114 .clone()
115 .pruned_with_prunable()
116 .0,
117 );
118
119 for tx in &block.txs {
120 self.add_tx::<false>(block.height, &tx.tx);
121 }
122 }
123}
124
125fn get_output_commitment(tx: &Transaction<Pruned>, i: usize) -> CompressedPoint {
127 match tx {
128 Transaction::V1 { prefix, .. } => {
129 compute_zero_commitment(prefix.outputs[i].amount.unwrap_or_default())
130 }
131 Transaction::V2 { prefix, proofs } => {
132 let Some(proofs) = proofs else {
133 return compute_zero_commitment(prefix.outputs[i].amount.unwrap_or_default());
134 };
135
136 proofs.base.commitments[i]
137 }
138 }
139}