cuprate_consensus/transactions/
contextual_data.rs
1use 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 outputs = IndexMap::new();
135
136 for tx_v_data in txs_verification_data {
137 insert_ring_member_ids(&tx_v_data.tx.prefix().inputs, &mut outputs)
138 .map_err(ConsensusError::Transaction)?;
139 }
140
141 let BlockchainResponse::Outputs(outputs) = database
142 .ready()
143 .await?
144 .call(BlockchainReadRequest::Outputs {
145 outputs,
146 get_txid: false,
147 })
148 .await?
149 else {
150 unreachable!();
151 };
152
153 Ok(outputs)
154}
155
156pub async fn batch_get_ring_member_info<D: Database>(
161 txs_verification_data: impl Iterator<Item = &TransactionVerificationData> + Clone,
162 hf: HardFork,
163 mut database: D,
164 cache: Option<&OutputCache>,
165) -> Result<Vec<TxRingMembersInfo>, ExtendedConsensusError> {
166 let mut outputs = IndexMap::new();
167
168 for tx_v_data in txs_verification_data.clone() {
169 insert_ring_member_ids(&tx_v_data.tx.prefix().inputs, &mut outputs)
170 .map_err(ConsensusError::Transaction)?;
171 }
172
173 let outputs = if let Some(cache) = cache {
174 Cow::Borrowed(cache)
175 } else {
176 let BlockchainResponse::Outputs(outputs) = database
177 .ready()
178 .await?
179 .call(BlockchainReadRequest::Outputs {
180 outputs,
181 get_txid: false,
182 })
183 .await?
184 else {
185 unreachable!();
186 };
187
188 Cow::Owned(outputs)
189 };
190
191 Ok(txs_verification_data
192 .map(move |tx_v_data| {
193 let numb_outputs = |amt| outputs.number_outs_with_amount(amt);
194
195 let ring_members_for_tx = get_ring_members_for_inputs(
196 |amt, idx| outputs.get_output(amt, idx).copied(),
197 &tx_v_data.tx.prefix().inputs,
198 )
199 .map_err(ConsensusError::Transaction)?;
200
201 let decoy_info = if hf == HardFork::V1 {
202 None
203 } else {
204 Some(
206 DecoyInfo::new(&tx_v_data.tx.prefix().inputs, numb_outputs, hf)
207 .map_err(ConsensusError::Transaction)?,
208 )
209 };
210
211 new_ring_member_info(ring_members_for_tx, decoy_info, tx_v_data.version)
212 .map_err(ConsensusError::Transaction)
213 })
214 .collect::<Result<_, _>>()?)
215}
216
217#[instrument(level = "debug", skip_all)]
223pub async fn batch_get_decoy_info<'a, 'b, D: Database>(
224 txs_verification_data: impl Iterator<Item = &'a TransactionVerificationData> + Clone,
225 hf: HardFork,
226 mut database: D,
227 cache: Option<&'b OutputCache>,
228) -> Result<
229 impl Iterator<Item = Result<DecoyInfo, ConsensusError>> + sealed::Captures<(&'a (), &'b ())>,
230 ExtendedConsensusError,
231> {
232 assert_ne!(hf, HardFork::V1);
234
235 let unique_input_amounts = txs_verification_data
237 .clone()
238 .flat_map(|tx_info| {
239 tx_info.tx.prefix().inputs.iter().map(|input| match input {
240 Input::ToKey { amount, .. } => amount.unwrap_or(0),
241 Input::Gen(_) => 0,
242 })
243 })
244 .collect::<HashSet<_>>();
245
246 tracing::debug!(
247 "Getting the amount of outputs with certain amounts for {} amounts",
248 unique_input_amounts.len()
249 );
250
251 let outputs_with_amount = if let Some(cache) = cache {
252 unique_input_amounts
253 .into_iter()
254 .map(|amount| (amount, cache.number_outs_with_amount(amount)))
255 .collect()
256 } else {
257 let BlockchainResponse::NumberOutputsWithAmount(outputs_with_amount) = database
258 .ready()
259 .await?
260 .call(BlockchainReadRequest::NumberOutputsWithAmount(
261 unique_input_amounts.into_iter().collect(),
262 ))
263 .await?
264 else {
265 unreachable!();
266 };
267
268 outputs_with_amount
269 };
270
271 Ok(txs_verification_data.map(move |tx_v_data| {
272 DecoyInfo::new(
273 &tx_v_data.tx.prefix().inputs,
274 |amt| outputs_with_amount.get(&amt).copied().unwrap_or(0),
275 hf,
276 )
277 .map_err(ConsensusError::Transaction)
278 }))
279}
280
281mod sealed {
282 pub trait Captures<U> {}
286 impl<T: ?Sized, U> Captures<U> for T {}
287}