monero_interface/
provides_blockchain.rs

1use core::{ops::RangeInclusive, future::Future};
2use alloc::{borrow::ToOwned as _, format, vec::Vec};
3
4use monero_oxide::block::Block;
5
6use crate::{InterfaceError, ProvidesBlockchainMeta};
7
8/// Provides the blockchain from an untrusted interface.
9///
10/// This provides some of its methods yet (`contiguous_blocks` || `block_by_number`) MUST be
11/// overriden, and the batch  method SHOULD be overriden.
12pub trait ProvidesUnvalidatedBlockchain: Sync + ProvidesBlockchainMeta {
13  /// Get a contiguous range of blocks.
14  ///
15  /// No validation is applied to the received blocks other than that they deserialize and have the
16  /// expected length.
17  // This accepts a `RangeInclusive`, not a `impl RangeBounds`, to ensure the range is finite
18  fn contiguous_blocks(
19    &self,
20    range: RangeInclusive<usize>,
21  ) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>> {
22    async move {
23      // If a caller requests an exorbitant amount of blocks, this may trigger an OOM kill
24      // In order to maintain correctness, we have to attempt to service this request though
25      let mut blocks =
26        Vec::with_capacity(range.end().saturating_sub(*range.start()).saturating_add(1));
27      for number in range {
28        blocks.push(self.block_by_number(number).await?);
29      }
30      Ok(blocks)
31    }
32  }
33
34  /* TODO
35  /// Subscribe to blocks.
36  fn subscribe(start: usize) ->
37    impl Iterator<Item = Future<Output = Result<Block, InterfaceError>>> {}
38  */
39
40  /// Get a block by its hash.
41  ///
42  /// No validation is applied to the received block other than that it deserializes.
43  fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>>;
44
45  /// Get a block by its number.
46  ///
47  /// The number of a block is its index on the blockchain, so the genesis block would have
48  /// `number = 0`.
49  ///
50  /// No validation is applied to the received blocks other than that it deserializes.
51  fn block_by_number(
52    &self,
53    number: usize,
54  ) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
55    async move {
56      let mut blocks = self.contiguous_blocks(number ..= number).await?;
57      if blocks.len() != 1 {
58        Err(InterfaceError::InternalError(format!(
59          "`{}` returned {} blocks, expected {}",
60          "ProvidesUnvalidatedBlockchain::contiguous_blocks",
61          blocks.len(),
62          1,
63        )))?;
64      }
65      Ok(blocks.pop().expect("verified we had a block"))
66    }
67  }
68
69  /// Get the hash of a block by its number.
70  ///
71  /// The number of a block is its index on the blockchain, so the genesis block would have
72  /// `number = 0`.
73  fn block_hash(
74    &self,
75    number: usize,
76  ) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>>;
77}
78
79/// Provides blocks which have been sanity-checked.
80pub trait ProvidesBlockchain: ProvidesBlockchainMeta {
81  /// Get a contiguous range of blocks.
82  ///
83  /// The blocks will be validated to build upon each other, as expected, and have the expected
84  /// numbers.
85  fn contiguous_blocks(
86    &self,
87    range: RangeInclusive<usize>,
88  ) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>>;
89
90  /// Get a block by its hash.
91  ///
92  /// The block will be validated to be the requested block.
93  fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>>;
94
95  /// Get a block by its number.
96  ///
97  /// The number of a block is its index on the blockchain, so the genesis block would have
98  /// `number = 0`.
99  ///
100  /// The block will be validated to be a block with the requested number.
101  fn block_by_number(
102    &self,
103    number: usize,
104  ) -> impl Send + Future<Output = Result<Block, InterfaceError>>;
105
106  /// Get the hash of a block by its number.
107  ///
108  /// The number of a block is its index on the blockchain, so the genesis block would have
109  /// `number = 0`.
110  fn block_hash(
111    &self,
112    number: usize,
113  ) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>>;
114}
115
116#[expect(single_use_lifetimes)] // False positive, this can't be removed
117pub(crate) fn sanity_check_contiguous_blocks<'block>(
118  range: RangeInclusive<usize>,
119  blocks: impl Iterator<Item = &'block Block>,
120) -> Result<(), InterfaceError> {
121  let mut parent = None;
122  for (number, block) in range.zip(blocks) {
123    if block.number() != number {
124      Err(InterfaceError::InvalidInterface(format!(
125        "requested block #{number}, received #{}",
126        block.number()
127      )))?;
128    }
129
130    let block_hash = block.hash();
131    if let Some(parent) = parent.or((number == 0).then_some([0; 32])) {
132      if parent != block.header.previous {
133        Err(InterfaceError::InvalidInterface(
134          "
135            interface returned a block which doesn't build on the prior block \
136            when requesting a contiguous series
137          "
138          .to_owned(),
139        ))?;
140      }
141    }
142    parent = Some(block_hash);
143  }
144  Ok(())
145}
146
147pub(crate) fn sanity_check_block_by_hash(
148  hash: &[u8; 32],
149  block: &Block,
150) -> Result<(), InterfaceError> {
151  let actual_hash = block.hash();
152  if &actual_hash != hash {
153    Err(InterfaceError::InvalidInterface(format!(
154      "requested block {}, received {}",
155      hex::encode(hash),
156      hex::encode(actual_hash)
157    )))?;
158  }
159
160  Ok(())
161}
162
163pub(crate) fn sanity_check_block_by_number(
164  number: usize,
165  block: &Block,
166) -> Result<(), InterfaceError> {
167  if block.number() != number {
168    Err(InterfaceError::InvalidInterface(format!(
169      "requested block #{number}, received #{}",
170      block.number()
171    )))?;
172  }
173  Ok(())
174}
175
176impl<P: ProvidesUnvalidatedBlockchain> ProvidesBlockchain for P {
177  fn contiguous_blocks(
178    &self,
179    range: RangeInclusive<usize>,
180  ) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>> {
181    async move {
182      let blocks =
183        <P as ProvidesUnvalidatedBlockchain>::contiguous_blocks(self, range.clone()).await?;
184      let expected_blocks =
185        range.end().saturating_sub(*range.start()).checked_add(1).ok_or_else(|| {
186          InterfaceError::InternalError(
187            "amount of blocks requested wasn't representable in a `usize`".to_owned(),
188          )
189        })?;
190      if blocks.len() != expected_blocks {
191        Err(InterfaceError::InternalError(format!(
192          "`{}` returned {} blocks, expected {}",
193          "ProvidesUnvalidatedBlockchain::contiguous_blocks",
194          blocks.len(),
195          expected_blocks,
196        )))?;
197      }
198      sanity_check_contiguous_blocks(range, blocks.iter())?;
199      Ok(blocks)
200    }
201  }
202
203  fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
204    async move {
205      let block = <P as ProvidesUnvalidatedBlockchain>::block(self, hash).await?;
206      sanity_check_block_by_hash(&hash, &block)?;
207      Ok(block)
208    }
209  }
210
211  fn block_by_number(
212    &self,
213    number: usize,
214  ) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
215    async move {
216      let block = <P as ProvidesUnvalidatedBlockchain>::block_by_number(self, number).await?;
217      sanity_check_block_by_number(number, &block)?;
218      Ok(block)
219    }
220  }
221
222  fn block_hash(
223    &self,
224    number: usize,
225  ) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>> {
226    <P as ProvidesUnvalidatedBlockchain>::block_hash(self, number)
227  }
228}