monero_interface/
provides_blockchain.rs

1use core::{ops::RangeInclusive, future::Future};
2use alloc::{format, vec::Vec, string::ToString};
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
116pub(crate) fn sanity_check_contiguous_blocks<'a>(
117  range: RangeInclusive<usize>,
118  blocks: impl Iterator<Item = &'a Block>,
119) -> Result<(), InterfaceError> {
120  let mut parent = None;
121  for (number, block) in range.zip(blocks) {
122    if block.number() != number {
123      Err(InterfaceError::InvalidInterface(format!(
124        "requested block #{number}, received #{}",
125        block.number()
126      )))?;
127    }
128
129    let block_hash = block.hash();
130    if let Some(parent) = parent.or((number == 0).then_some([0; 32])) {
131      if parent != block.header.previous {
132        Err(InterfaceError::InvalidInterface(
133          "
134            interface returned a block which doesn't build on the prior block \
135            when requesting a contiguous series
136          "
137          .to_string(),
138        ))?;
139      }
140    }
141    parent = Some(block_hash);
142  }
143  Ok(())
144}
145
146pub(crate) fn sanity_check_block_by_hash(
147  hash: &[u8; 32],
148  block: &Block,
149) -> Result<(), InterfaceError> {
150  let actual_hash = block.hash();
151  if &actual_hash != hash {
152    Err(InterfaceError::InvalidInterface(format!(
153      "requested block {}, received {}",
154      hex::encode(hash),
155      hex::encode(actual_hash)
156    )))?;
157  }
158
159  Ok(())
160}
161
162pub(crate) fn sanity_check_block_by_number(
163  number: usize,
164  block: &Block,
165) -> Result<(), InterfaceError> {
166  if block.number() != number {
167    Err(InterfaceError::InvalidInterface(format!(
168      "requested block #{number}, received #{}",
169      block.number()
170    )))?;
171  }
172  Ok(())
173}
174
175impl<P: ProvidesUnvalidatedBlockchain> ProvidesBlockchain for P {
176  fn contiguous_blocks(
177    &self,
178    range: RangeInclusive<usize>,
179  ) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>> {
180    async move {
181      let blocks =
182        <P as ProvidesUnvalidatedBlockchain>::contiguous_blocks(self, range.clone()).await?;
183      let expected_blocks =
184        range.end().saturating_sub(*range.start()).checked_add(1).ok_or_else(|| {
185          InterfaceError::InternalError(
186            "amount of blocks requested wasn't representable in a `usize`".to_string(),
187          )
188        })?;
189      if blocks.len() != expected_blocks {
190        Err(InterfaceError::InternalError(format!(
191          "`{}` returned {} blocks, expected {}",
192          "ProvidesUnvalidatedBlockchain::contiguous_blocks",
193          blocks.len(),
194          expected_blocks,
195        )))?;
196      }
197      sanity_check_contiguous_blocks(range, blocks.iter())?;
198      Ok(blocks)
199    }
200  }
201
202  fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
203    async move {
204      let block = <P as ProvidesUnvalidatedBlockchain>::block(self, hash).await?;
205      sanity_check_block_by_hash(&hash, &block)?;
206      Ok(block)
207    }
208  }
209
210  fn block_by_number(
211    &self,
212    number: usize,
213  ) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
214    async move {
215      let block = <P as ProvidesUnvalidatedBlockchain>::block_by_number(self, number).await?;
216      sanity_check_block_by_number(number, &block)?;
217      Ok(block)
218    }
219  }
220
221  fn block_hash(
222    &self,
223    number: usize,
224  ) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>> {
225    <P as ProvidesUnvalidatedBlockchain>::block_hash(self, number)
226  }
227}