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