1use core::future::Future;
2use alloc::{format, vec, vec::Vec, string::String};
3
4use monero_oxide::transaction::{Pruned, Transaction};
5
6use crate::InterfaceError;
7
8#[derive(Clone, PartialEq, Eq, Debug)]
10pub struct PrunedTransactionWithPrunableHash {
11 transaction: Transaction<Pruned>,
12 prunable_hash: Option<[u8; 32]>,
13}
14
15impl PrunedTransactionWithPrunableHash {
16 pub fn new(
20 transaction: Transaction<Pruned>,
21 mut prunable_hash: Option<[u8; 32]>,
22 ) -> Option<Self> {
23 match &transaction {
24 Transaction::V1 { .. } => {
25 if prunable_hash.is_some() {
26 None?
27 }
28 }
29 Transaction::V2 { proofs, .. } => {
30 if prunable_hash.is_none() {
31 None?;
32 }
33 if proofs.is_none() {
34 prunable_hash = Some([0; 32]);
35 }
36 }
37 }
38 Some(Self { transaction, prunable_hash })
39 }
40
41 pub fn verify_as_possible(self, hash: [u8; 32]) -> Result<Transaction<Pruned>, [u8; 32]> {
48 if let Some(prunable_hash) = self.prunable_hash {
49 let actual_hash = self
50 .transaction
51 .hash_with_prunable_hash(prunable_hash)
52 .expect("couldn't hash with prunable hash despite prior ensuring presence was as expected");
53 if actual_hash != hash {
54 Err(actual_hash)?;
55 }
56 }
57 Ok(self.transaction)
58 }
59}
60
61impl AsRef<Transaction<Pruned>> for PrunedTransactionWithPrunableHash {
62 fn as_ref(&self) -> &Transaction<Pruned> {
63 &self.transaction
64 }
65}
66
67#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
69pub enum TransactionsError {
70 #[error("interface error ({0})")]
72 InterfaceError(InterfaceError),
73 #[error("transaction wasn't found")]
75 TransactionNotFound,
76 #[error("transaction was unexpectedly pruned")]
78 PrunedTransaction,
79}
80
81impl From<InterfaceError> for TransactionsError {
82 fn from(err: InterfaceError) -> Self {
83 Self::InterfaceError(err)
84 }
85}
86
87#[rustfmt::skip]
93pub trait ProvidesUnvalidatedTransactions: Sync {
94 fn transactions(
98 &self,
99 hashes: &[[u8; 32]],
100 ) -> impl Send + Future<Output = Result<Vec<Transaction>, TransactionsError>> {
101 async move {
102 let mut txs = Vec::with_capacity(hashes.len());
103 for hash in hashes {
104 txs.push(self.transaction(*hash).await?);
105 }
106 Ok(txs)
107 }
108 }
109
110 fn pruned_transactions(
114 &self,
115 hashes: &[[u8; 32]],
116 ) -> impl Send + Future<Output = Result<Vec<PrunedTransactionWithPrunableHash>, TransactionsError>>
117 {
118 async move {
119 let mut txs = Vec::with_capacity(hashes.len());
120 for hash in hashes {
121 txs.push(self.pruned_transaction(*hash).await?);
122 }
123 Ok(txs)
124 }
125 }
126
127 fn transaction(
129 &self,
130 hash: [u8; 32],
131 ) -> impl Send + Future<Output = Result<Transaction, TransactionsError>> {
132 async move {
133 let mut txs = self.transactions(&[hash]).await?;
134 if txs.len() != 1 {
135 Err(InterfaceError::InternalError(format!(
136 "`{}` returned {} transactions, expected {}",
137 "ProvidesUnvalidatedTransactions::transactions",
138 txs.len(),
139 1,
140 )))?;
141 }
142 Ok(txs.pop().expect("verified we had a transaction"))
143 }
144 }
145
146 fn pruned_transaction(
148 &self,
149 hash: [u8; 32],
150 ) -> impl Send + Future<Output = Result<PrunedTransactionWithPrunableHash, TransactionsError>> {
151 async move {
152 let mut txs = self.pruned_transactions(&[hash]).await?;
153 if txs.len() != 1 {
154 Err(InterfaceError::InternalError(format!(
155 "`{}` returned {} transactions, expected {}",
156 "ProvidesUnvalidatedTransactions::pruned_transactions",
157 txs.len(),
158 1,
159 )))?;
160 }
161 Ok(txs.pop().expect("verified we had a pruned transaction"))
162 }
163 }
164}
165
166pub trait ProvidesTransactions: Sync {
168 fn transactions(
173 &self,
174 hashes: &[[u8; 32]],
175 ) -> impl Send + Future<Output = Result<Vec<Transaction>, TransactionsError>>;
176
177 fn pruned_transactions(
183 &self,
184 hashes: &[[u8; 32]],
185 ) -> impl Send + Future<Output = Result<Vec<Transaction<Pruned>>, TransactionsError>>;
186
187 fn transaction(
191 &self,
192 hash: [u8; 32],
193 ) -> impl Send + Future<Output = Result<Transaction, TransactionsError>>;
194
195 fn pruned_transaction(
200 &self,
201 hash: [u8; 32],
202 ) -> impl Send + Future<Output = Result<Transaction<Pruned>, TransactionsError>>;
203}
204
205pub(crate) async fn validate_pruned_transactions<P: ProvidesTransactions>(
206 interface: &P,
207 unvalidated: Vec<PrunedTransactionWithPrunableHash>,
208 hashes: &[[u8; 32]],
209) -> Result<Vec<Transaction<Pruned>>, TransactionsError> {
210 if unvalidated.len() != hashes.len() {
211 Err(InterfaceError::InternalError(format!(
212 "`{}` returned {} transactions, expected {}",
213 "ProvidesUnvalidatedTransactions::pruned_transactions",
214 unvalidated.len(),
215 hashes.len(),
216 )))?;
217 }
218
219 let mut txs = Vec::with_capacity(unvalidated.len());
220 let mut v1_indexes = vec![];
221 let mut v1_hashes = vec![];
222 for (tx, expected_hash) in unvalidated.into_iter().zip(hashes) {
223 match tx.verify_as_possible(*expected_hash) {
224 Ok(tx) => {
225 if matches!(tx, Transaction::V1 { .. }) {
226 v1_indexes.push(txs.len());
227 v1_hashes.push(*expected_hash);
228 }
229 txs.push(tx)
230 }
231 Err(hash) => Err(InterfaceError::InvalidInterface(format!(
232 "interface returned TX {} when {} was requested",
233 hex::encode(hash),
234 hex::encode(expected_hash)
235 )))?,
236 }
237 }
238
239 if !v1_indexes.is_empty() {
240 let full_txs = <P as ProvidesTransactions>::transactions(interface, &v1_hashes).await?;
241 for ((pruned_tx, hash), tx) in
242 v1_indexes.into_iter().map(|i| &txs[i]).zip(v1_hashes).zip(full_txs)
243 {
244 if &Transaction::<Pruned>::from(tx) != pruned_tx {
245 Err(InterfaceError::InvalidInterface(format!(
246 "interface returned pruned V1 TX which didn't match TX {}",
247 hex::encode(hash)
248 )))?;
249 }
250 }
251 }
252
253 Ok(txs)
254}
255
256impl<P: ProvidesUnvalidatedTransactions> ProvidesTransactions for P {
257 fn transactions(
258 &self,
259 hashes: &[[u8; 32]],
260 ) -> impl Send + Future<Output = Result<Vec<Transaction>, TransactionsError>> {
261 async move {
262 let txs = <P as ProvidesUnvalidatedTransactions>::transactions(self, hashes).await?;
263 if txs.len() != hashes.len() {
264 Err(InterfaceError::InternalError(format!(
265 "`{}` returned {} transactions, expected {}",
266 "ProvidesUnvalidatedTransactions::transactions",
267 txs.len(),
268 hashes.len(),
269 )))?;
270 }
271
272 for (tx, expected_hash) in txs.iter().zip(hashes) {
273 let hash = tx.hash();
274 if &hash != expected_hash {
275 Err(InterfaceError::InvalidInterface(format!(
276 "interface returned TX {} when {} was requested",
277 hex::encode(hash),
278 hex::encode(expected_hash)
279 )))?;
280 }
281 }
282 Ok(txs)
283 }
284 }
285
286 fn pruned_transactions(
287 &self,
288 hashes: &[[u8; 32]],
289 ) -> impl Send + Future<Output = Result<Vec<Transaction<Pruned>>, TransactionsError>> {
290 async move {
291 let unvalidated =
292 <P as ProvidesUnvalidatedTransactions>::pruned_transactions(self, hashes).await?;
293 validate_pruned_transactions(self, unvalidated, hashes).await
294 }
295 }
296
297 fn transaction(
298 &self,
299 hash: [u8; 32],
300 ) -> impl Send + Future<Output = Result<Transaction, TransactionsError>> {
301 async move {
302 let tx = <P as ProvidesUnvalidatedTransactions>::transaction(self, hash).await?;
303 let actual_hash = tx.hash();
304 if actual_hash != hash {
305 Err(InterfaceError::InvalidInterface(format!(
306 "interface returned TX {} when {} was requested",
307 hex::encode(actual_hash),
308 hex::encode(hash)
309 )))?;
310 }
311 Ok(tx)
312 }
313 }
314
315 fn pruned_transaction(
316 &self,
317 hash: [u8; 32],
318 ) -> impl Send + Future<Output = Result<Transaction<Pruned>, TransactionsError>> {
319 async move {
320 let unvalidated =
321 <P as ProvidesUnvalidatedTransactions>::pruned_transaction(self, hash).await?;
322 Ok(validate_pruned_transactions(self, vec![unvalidated], &[hash]).await?.swap_remove(0))
323 }
324 }
325}
326
327#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
329pub enum PublishTransactionError {
330 #[error("interface error ({0})")]
332 InterfaceError(InterfaceError),
333 #[error("transaction was rejected ({0})")]
335 TransactionRejected(String),
336}
337
338impl From<InterfaceError> for PublishTransactionError {
339 fn from(err: InterfaceError) -> Self {
340 Self::InterfaceError(err)
341 }
342}
343
344pub trait PublishTransaction: Sync {
346 fn publish_transaction(
348 &self,
349 transaction: &Transaction,
350 ) -> impl Send + Future<Output = Result<(), PublishTransactionError>>;
351}