cuprate_consensus/transactions/
contextual_data.rs1use std::{borrow::Cow, collections::HashSet};
15
16use indexmap::IndexMap;
17use monero_serai::transaction::{Input, Timelock};
18use tower::ServiceExt;
19use tracing::instrument;
20
21use cuprate_consensus_rules::{
22 transactions::{
23 get_absolute_offsets, insert_ring_member_ids, DecoyInfo, Rings, TransactionError,
24 TxRingMembersInfo,
25 },
26 ConsensusError, HardFork, TxVersion,
27};
28
29use cuprate_types::{
30 blockchain::{BlockchainReadRequest, BlockchainResponse},
31 output_cache::OutputCache,
32 OutputOnChain,
33};
34
35use crate::{transactions::TransactionVerificationData, Database, ExtendedConsensusError};
36
37fn get_ring_members_for_inputs(
41 get_outputs: impl Fn(u64, u64) -> Option<OutputOnChain>,
42 inputs: &[Input],
43) -> Result<Vec<Vec<OutputOnChain>>, TransactionError> {
44 inputs
45 .iter()
46 .map(|inp| match inp {
47 Input::ToKey {
48 amount,
49 key_offsets,
50 ..
51 } => {
52 let offsets = get_absolute_offsets(key_offsets)?;
53 Ok(offsets
54 .iter()
55 .map(|offset| {
56 get_outputs(amount.unwrap_or(0), *offset)
57 .ok_or(TransactionError::RingMemberNotFoundOrInvalid)
58 })
59 .collect::<Result<_, TransactionError>>()?)
60 }
61 Input::Gen(_) => Err(TransactionError::IncorrectInputType),
62 })
63 .collect::<Result<_, TransactionError>>()
64}
65
66pub fn new_ring_member_info(
70 used_outs: Vec<Vec<OutputOnChain>>,
71 decoy_info: Option<DecoyInfo>,
72 tx_version: TxVersion,
73) -> Result<TxRingMembersInfo, TransactionError> {
74 Ok(TxRingMembersInfo {
75 youngest_used_out_height: used_outs
76 .iter()
77 .map(|inp_outs| {
78 inp_outs
79 .iter()
80 .map(|out| out.height)
82 .max()
83 .expect("Input must have ring members")
84 })
85 .max()
86 .expect("Tx must have inputs"),
87 time_locked_outs: used_outs
88 .iter()
89 .flat_map(|inp_outs| {
90 inp_outs
91 .iter()
92 .filter_map(|out| match out.time_lock {
93 Timelock::None => None,
94 lock => Some(lock),
95 })
96 .collect::<Vec<_>>()
97 })
98 .collect(),
99 rings: new_rings(used_outs, tx_version),
100 decoy_info,
101 })
102}
103
104fn new_rings(outputs: Vec<Vec<OutputOnChain>>, tx_version: TxVersion) -> Rings {
106 match tx_version {
107 TxVersion::RingSignatures => Rings::Legacy(
108 outputs
109 .into_iter()
110 .map(|inp_outs| inp_outs.into_iter().map(|out| out.key).collect::<Vec<_>>())
111 .collect::<Vec<_>>(),
112 ),
113 TxVersion::RingCT => Rings::RingCT(
114 outputs
115 .into_iter()
116 .map(|inp_outs| {
117 inp_outs
118 .into_iter()
119 .map(|out| [out.key, out.commitment])
120 .collect::<_>()
121 })
122 .collect::<_>(),
123 ),
124 }
125}
126
127pub async fn get_output_cache<D: Database>(
131 txs_verification_data: impl Iterator<Item = &TransactionVerificationData>,
132 mut database: D,
133) -> Result<OutputCache, ExtendedConsensusError> {
134 let mut output_ids = IndexMap::new();
135
136 for tx_v_data in txs_verification_data {
137 insert_ring_member_ids(&tx_v_data.tx.prefix().inputs, &mut output_ids)
138 .map_err(ConsensusError::Transaction)?;
139 }
140
141 let BlockchainResponse::Outputs(outputs) = database
142 .ready()
143 .await?
144 .call(BlockchainReadRequest::Outputs(output_ids))
145 .await?
146 else {
147 unreachable!();
148 };
149
150 Ok(outputs)
151}
152
153pub async fn batch_get_ring_member_info<D: Database>(
158 txs_verification_data: impl Iterator<Item = &TransactionVerificationData> + Clone,
159 hf: HardFork,
160 mut database: D,
161 cache: Option<&OutputCache>,
162) -> Result<Vec<TxRingMembersInfo>, ExtendedConsensusError> {
163 let mut output_ids = IndexMap::new();
164
165 for tx_v_data in txs_verification_data.clone() {
166 insert_ring_member_ids(&tx_v_data.tx.prefix().inputs, &mut output_ids)
167 .map_err(ConsensusError::Transaction)?;
168 }
169
170 let outputs = if let Some(cache) = cache {
171 Cow::Borrowed(cache)
172 } else {
173 let BlockchainResponse::Outputs(outputs) = database
174 .ready()
175 .await?
176 .call(BlockchainReadRequest::Outputs(output_ids))
177 .await?
178 else {
179 unreachable!();
180 };
181
182 Cow::Owned(outputs)
183 };
184
185 Ok(txs_verification_data
186 .map(move |tx_v_data| {
187 let numb_outputs = |amt| outputs.number_outs_with_amount(amt);
188
189 let ring_members_for_tx = get_ring_members_for_inputs(
190 |amt, idx| outputs.get_output(amt, idx).copied(),
191 &tx_v_data.tx.prefix().inputs,
192 )
193 .map_err(ConsensusError::Transaction)?;
194
195 let decoy_info = if hf == HardFork::V1 {
196 None
197 } else {
198 Some(
200 DecoyInfo::new(&tx_v_data.tx.prefix().inputs, numb_outputs, hf)
201 .map_err(ConsensusError::Transaction)?,
202 )
203 };
204
205 new_ring_member_info(ring_members_for_tx, decoy_info, tx_v_data.version)
206 .map_err(ConsensusError::Transaction)
207 })
208 .collect::<Result<_, _>>()?)
209}
210
211#[instrument(level = "debug", skip_all)]
217pub async fn batch_get_decoy_info<'a, 'b, D: Database>(
218 txs_verification_data: impl Iterator<Item = &'a TransactionVerificationData> + Clone,
219 hf: HardFork,
220 mut database: D,
221 cache: Option<&'b OutputCache>,
222) -> Result<
223 impl Iterator<Item = Result<DecoyInfo, ConsensusError>> + sealed::Captures<(&'a (), &'b ())>,
224 ExtendedConsensusError,
225> {
226 assert_ne!(hf, HardFork::V1);
228
229 let unique_input_amounts = txs_verification_data
231 .clone()
232 .flat_map(|tx_info| {
233 tx_info.tx.prefix().inputs.iter().map(|input| match input {
234 Input::ToKey { amount, .. } => amount.unwrap_or(0),
235 Input::Gen(_) => 0,
236 })
237 })
238 .collect::<HashSet<_>>();
239
240 tracing::debug!(
241 "Getting the amount of outputs with certain amounts for {} amounts",
242 unique_input_amounts.len()
243 );
244
245 let outputs_with_amount = if let Some(cache) = cache {
246 unique_input_amounts
247 .into_iter()
248 .map(|amount| (amount, cache.number_outs_with_amount(amount)))
249 .collect()
250 } else {
251 let BlockchainResponse::NumberOutputsWithAmount(outputs_with_amount) = database
252 .ready()
253 .await?
254 .call(BlockchainReadRequest::NumberOutputsWithAmount(
255 unique_input_amounts.into_iter().collect(),
256 ))
257 .await?
258 else {
259 unreachable!();
260 };
261
262 outputs_with_amount
263 };
264
265 Ok(txs_verification_data.map(move |tx_v_data| {
266 DecoyInfo::new(
267 &tx_v_data.tx.prefix().inputs,
268 |amt| outputs_with_amount.get(&amt).copied().unwrap_or(0),
269 hf,
270 )
271 .map_err(ConsensusError::Transaction)
272 }))
273}
274
275mod sealed {
276 pub trait Captures<U> {}
280 impl<T: ?Sized, U> Captures<U> for T {}
281}