1use core::{ops::RangeInclusive, future::Future};
2use alloc::{format, vec::Vec, string::ToString};
3
4use monero_oxide::{
5 transaction::{Pruned, Transaction},
6 block::Block,
7};
8
9use crate::{
10 InterfaceError, TransactionsError, PrunedTransactionWithPrunableHash, ProvidesTransactions,
11 ProvidesOutputs,
12};
13
14#[derive(Clone, PartialEq, Eq, Debug)]
19pub struct ScannableBlock {
20 pub block: Block,
22 pub transactions: Vec<Transaction<Pruned>>,
24 pub output_index_for_first_ringct_output: Option<u64>,
36}
37
38pub trait ExpandToScannableBlock: ProvidesTransactions + ProvidesOutputs {
40 fn expand_to_scannable_block(
45 &self,
46 block: Block,
47 ) -> impl Send + Future<Output = Result<ScannableBlock, TransactionsError>> {
48 async move {
49 let transactions = self.pruned_transactions(&block.transactions).await?;
50
51 let mut output_index_for_first_ringct_output = None;
91 let miner_tx_hash = block.miner_transaction().hash();
92 let miner_tx = Transaction::<Pruned>::from(block.miner_transaction().clone());
93 for (hash, tx) in core::iter::once((&miner_tx_hash, &miner_tx))
94 .chain(block.transactions.iter().zip(&transactions))
95 {
96 if (!matches!(tx, Transaction::V2 { .. })) || tx.prefix().outputs.is_empty() {
98 continue;
99 }
100
101 let index =
102 *ProvidesOutputs::output_indexes(self, *hash).await?.first().ok_or_else(|| {
103 InterfaceError::InvalidInterface(
104 "requested output indexes for a TX with outputs and got none".to_string(),
105 )
106 })?;
107 output_index_for_first_ringct_output = Some(index);
108 break;
109 }
110
111 Ok(ScannableBlock { block, transactions, output_index_for_first_ringct_output })
112 }
113 }
114}
115
116impl<P: ProvidesTransactions + ProvidesOutputs> ExpandToScannableBlock for P {}
117
118#[derive(Clone, PartialEq, Eq, Debug)]
120pub struct UnvalidatedScannableBlock {
121 pub block: Block,
123 pub transactions: Vec<PrunedTransactionWithPrunableHash>,
125 pub output_index_for_first_ringct_output: Option<u64>,
129}
130
131pub trait ProvidesUnvalidatedScannableBlocks: Sync {
137 fn contiguous_scannable_blocks(
142 &self,
143 range: RangeInclusive<usize>,
144 ) -> impl Send + Future<Output = Result<Vec<UnvalidatedScannableBlock>, InterfaceError>> {
145 async move {
146 let mut blocks =
149 Vec::with_capacity(range.end().saturating_sub(*range.start()).saturating_add(1));
150 for number in range {
151 blocks.push(self.scannable_block_by_number(number).await?);
152 }
153 Ok(blocks)
154 }
155 }
156
157 fn scannable_block(
161 &self,
162 hash: [u8; 32],
163 ) -> impl Send + Future<Output = Result<UnvalidatedScannableBlock, InterfaceError>>;
164
165 fn scannable_block_by_number(
169 &self,
170 number: usize,
171 ) -> impl Send + Future<Output = Result<UnvalidatedScannableBlock, InterfaceError>> {
172 async move {
173 let mut blocks = self.contiguous_scannable_blocks(number ..= number).await?;
174 if blocks.len() != 1 {
175 Err(InterfaceError::InternalError(format!(
176 "`{}` returned {} blocks, expected {}",
177 "ProvidesUnvalidatedScannableBlocks::contiguous_scannable_blocks",
178 blocks.len(),
179 1,
180 )))?;
181 }
182 Ok(blocks.pop().expect("verified we had a scannable block"))
183 }
184 }
185}
186
187pub trait ProvidesScannableBlocks: Sync {
189 fn contiguous_scannable_blocks(
194 &self,
195 range: RangeInclusive<usize>,
196 ) -> impl Send + Future<Output = Result<Vec<ScannableBlock>, InterfaceError>>;
197
198 fn scannable_block(
203 &self,
204 hash: [u8; 32],
205 ) -> impl Send + Future<Output = Result<ScannableBlock, InterfaceError>>;
206
207 fn scannable_block_by_number(
215 &self,
216 number: usize,
217 ) -> impl Send + Future<Output = Result<ScannableBlock, InterfaceError>>;
218}
219
220async fn validate_scannable_block<P: ProvidesTransactions + ProvidesUnvalidatedScannableBlocks>(
221 interface: &P,
222 block: UnvalidatedScannableBlock,
223) -> Result<ScannableBlock, InterfaceError> {
224 let UnvalidatedScannableBlock { block, transactions, output_index_for_first_ringct_output } =
225 block;
226 let transactions = match crate::provides_transactions::validate_pruned_transactions(
227 interface,
228 transactions,
229 &block.transactions,
230 )
231 .await
232 {
233 Ok(transactions) => transactions,
234 Err(e) => Err(match e {
235 TransactionsError::InterfaceError(e) => e,
236 TransactionsError::TransactionNotFound => InterfaceError::InvalidInterface(
237 "interface sent us a scannable block it doesn't have the transactions for".to_string(),
238 ),
239 TransactionsError::PrunedTransaction => InterfaceError::InvalidInterface(
240 "interface sent us pruned transaction when validating a scannable block".to_string(),
242 ),
243 })?,
244 };
245 Ok(ScannableBlock { block, transactions, output_index_for_first_ringct_output })
246}
247
248impl<P: ProvidesTransactions + ProvidesUnvalidatedScannableBlocks> ProvidesScannableBlocks for P {
249 fn contiguous_scannable_blocks(
250 &self,
251 range: RangeInclusive<usize>,
252 ) -> impl Send + Future<Output = Result<Vec<ScannableBlock>, InterfaceError>> {
253 async move {
254 let blocks =
255 <P as ProvidesUnvalidatedScannableBlocks>::contiguous_scannable_blocks(self, range.clone())
256 .await?;
257 let expected_blocks =
258 range.end().saturating_sub(*range.start()).checked_add(1).ok_or_else(|| {
259 InterfaceError::InternalError(
260 "amount of blocks requested wasn't representable in a `usize`".to_string(),
261 )
262 })?;
263 if blocks.len() != expected_blocks {
264 Err(InterfaceError::InternalError(format!(
265 "`{}` returned {} blocks, expected {}",
266 "ProvidesUnvalidatedScannableBlocks::contiguous_scannable_blocks",
267 blocks.len(),
268 expected_blocks,
269 )))?;
270 }
271 crate::provides_blockchain::sanity_check_contiguous_blocks(
272 range,
273 blocks.iter().map(|scannable_block| &scannable_block.block),
274 )?;
275
276 let mut res = Vec::with_capacity(blocks.len());
277 for block in blocks {
278 res.push(validate_scannable_block(self, block).await?);
279 }
280 Ok(res)
281 }
282 }
283
284 fn scannable_block(
285 &self,
286 hash: [u8; 32],
287 ) -> impl Send + Future<Output = Result<ScannableBlock, InterfaceError>> {
288 async move {
289 let block = <P as ProvidesUnvalidatedScannableBlocks>::scannable_block(self, hash).await?;
290 crate::provides_blockchain::sanity_check_block_by_hash(&hash, &block.block)?;
291 validate_scannable_block(self, block).await
292 }
293 }
294
295 fn scannable_block_by_number(
296 &self,
297 number: usize,
298 ) -> impl Send + Future<Output = Result<ScannableBlock, InterfaceError>> {
299 async move {
300 let block =
301 <P as ProvidesUnvalidatedScannableBlocks>::scannable_block_by_number(self, number).await?;
302 crate::provides_blockchain::sanity_check_block_by_number(number, &block.block)?;
303 validate_scannable_block(self, block).await
304 }
305 }
306}