1use allocative::Allocative;
5use linera_base::{
6 data_types::{ArithmeticError, BlockHeight, Cursor},
7 ensure,
8 identifiers::ChainId,
9};
10#[cfg(with_testing)]
11use linera_views::context::MemoryContext;
12use linera_views::{
13 context::Context,
14 queue_view::QueueView,
15 register_view::RegisterView,
16 views::{ClonableView, View},
17 ViewError,
18};
19use thiserror::Error;
20
21use crate::{data_types::MessageBundle, ChainError};
22
23#[cfg(test)]
24#[path = "unit_tests/inbox_tests.rs"]
25mod inbox_tests;
26
27#[cfg(with_metrics)]
28pub(crate) mod metrics {
29 use linera_base::prometheus_util::{exponential_bucket_interval, register_histogram_vec};
30 use prometheus::HistogramVec;
31
32 linera_base::declare_metrics! {
33 pub static INBOX_SIZE: HistogramVec =
34 register_histogram_vec(
35 "inbox_size",
36 "Inbox size",
37 &[],
38 exponential_bucket_interval(1.0, 2_000_000.0),
39 );
40
41 pub static REMOVED_BUNDLES: HistogramVec =
42 register_histogram_vec(
43 "removed_bundles",
44 "Number of bundles removed by anticipation",
45 &[],
46 exponential_bucket_interval(1.0, 10_000.0),
47 );
48 }
49}
50
51#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject))]
64#[derive(Allocative, Debug, ClonableView, View)]
65#[allocative(bound = "C")]
66pub struct InboxStateView<C>
67where
68 C: Clone + Context,
69{
70 pub next_cursor_to_add: RegisterView<C, Cursor>,
72 pub next_cursor_to_remove: RegisterView<C, Cursor>,
74 pub added_bundles: QueueView<C, MessageBundle>,
76 pub removed_bundles: QueueView<C, MessageBundle>,
79 pub restored_cursor: RegisterView<C, Cursor>,
86}
87
88#[derive(Error, Debug)]
89pub(crate) enum InboxError {
90 #[error(transparent)]
91 ViewError(#[from] ViewError),
92 #[error(transparent)]
93 ArithmeticError(#[from] ArithmeticError),
94 #[error("Cannot reconcile {bundle:?} with {previous_bundle:?}")]
95 UnexpectedBundle {
96 bundle: MessageBundle,
97 previous_bundle: MessageBundle,
98 },
99 #[error("{bundle:?} is out of order. Block and height should be at least: {next_cursor:?}")]
100 IncorrectOrder {
101 bundle: MessageBundle,
102 next_cursor: Cursor,
103 },
104 #[error(
105 "{bundle:?} cannot be skipped: it must be received before the next \
106 messages from the same origin"
107 )]
108 UnskippableBundle { bundle: MessageBundle },
109}
110
111impl From<(ChainId, ChainId, InboxError)> for ChainError {
112 fn from(value: (ChainId, ChainId, InboxError)) -> Self {
113 let (chain_id, origin, error) = value;
114 match error {
115 InboxError::ViewError(e) => ChainError::ViewError(e),
116 InboxError::ArithmeticError(e) => ChainError::ArithmeticError(e),
117 InboxError::UnexpectedBundle {
118 bundle,
119 previous_bundle,
120 } => ChainError::UnexpectedMessage {
121 chain_id,
122 origin,
123 bundle: Box::new(bundle),
124 previous_bundle: Box::new(previous_bundle),
125 },
126 InboxError::IncorrectOrder {
127 bundle,
128 next_cursor,
129 } => ChainError::IncorrectMessageOrder {
130 chain_id,
131 origin,
132 bundle: Box::new(bundle),
133 next_height: next_cursor.height,
134 next_index: next_cursor.index,
135 },
136 InboxError::UnskippableBundle { bundle } => ChainError::CannotSkipMessage {
137 chain_id,
138 origin,
139 bundle: Box::new(bundle),
140 },
141 }
142 }
143}
144
145impl<C> InboxStateView<C>
146where
147 C: Context + Clone + 'static,
148{
149 pub fn next_block_height_to_receive(&self) -> Result<BlockHeight, ChainError> {
152 let cursor = self.next_cursor_to_add.get();
153 if cursor.index == 0 {
154 Ok(cursor.height)
155 } else {
156 Ok(cursor.height.try_add_one()?)
157 }
158 }
159
160 pub fn observe_size_metric(&self) {
162 #[cfg(with_metrics)]
163 metrics::INBOX_SIZE
164 .with_label_values(&[])
165 .observe(self.added_bundles.count() as f64);
166 }
167
168 pub async fn restore_from_checkpoint(&mut self, cursor: Cursor) -> Result<(), ChainError> {
185 ensure!(
186 *self.restored_cursor.get() <= cursor,
187 ChainError::InternalError(format!(
188 "cannot restore inbox at cursor {cursor:?}: already bootstrapped \
189 from a later checkpoint at cursor {previous:?}",
190 previous = *self.restored_cursor.get(),
191 ))
192 );
193 self.restored_cursor.set(cursor);
194 if *self.next_cursor_to_add.get() < cursor {
195 self.next_cursor_to_add.set(cursor);
196 }
197 self.next_cursor_to_remove.set(cursor);
198 while let Some(front) = self.added_bundles.front().await? {
199 if front.cursor() >= cursor {
200 break;
201 }
202 self.added_bundles.delete_front();
203 }
204 self.removed_bundles.clear();
205 Ok(())
206 }
207
208 pub(crate) async fn remove_bundle(
212 &mut self,
213 bundle: &MessageBundle,
214 ) -> Result<bool, InboxError> {
215 let cursor = bundle.cursor();
217 if cursor < *self.restored_cursor.get() {
218 return Ok(true);
222 }
223 ensure!(
224 cursor >= *self.next_cursor_to_remove.get(),
225 InboxError::IncorrectOrder {
226 bundle: bundle.clone(),
227 next_cursor: *self.next_cursor_to_remove.get(),
228 }
229 );
230 while let Some(previous_bundle) = self.added_bundles.front().await? {
232 if previous_bundle.cursor() >= cursor {
233 break;
234 }
235 ensure!(
236 previous_bundle.is_skippable(),
237 InboxError::UnskippableBundle {
238 bundle: previous_bundle
239 }
240 );
241 self.added_bundles.delete_front();
242 tracing::trace!("Skipping previously received bundle {:?}", previous_bundle);
243 }
244 let already_known = match self.added_bundles.front().await? {
246 Some(previous_bundle) => {
247 ensure!(
253 bundle == &previous_bundle,
254 InboxError::UnexpectedBundle {
255 previous_bundle,
256 bundle: bundle.clone(),
257 }
258 );
259 self.added_bundles.delete_front();
260 tracing::trace!("Consuming bundle {:?}", bundle);
261 true
262 }
263 None => {
264 tracing::trace!("Marking bundle as expected: {:?}", bundle);
265 self.removed_bundles.push_back(bundle.clone());
266 #[cfg(with_metrics)]
267 metrics::REMOVED_BUNDLES
268 .with_label_values(&[])
269 .observe(self.removed_bundles.count() as f64);
270 false
271 }
272 };
273 self.next_cursor_to_remove.set(cursor.try_add_one()?);
274 Ok(already_known)
275 }
276
277 pub(crate) async fn add_bundle(&mut self, bundle: MessageBundle) -> Result<bool, InboxError> {
282 let cursor = bundle.cursor();
284 if cursor < *self.restored_cursor.get() {
285 return Ok(false);
288 }
289 ensure!(
290 cursor >= *self.next_cursor_to_add.get(),
291 InboxError::IncorrectOrder {
292 bundle: bundle.clone(),
293 next_cursor: *self.next_cursor_to_add.get(),
294 }
295 );
296 let newly_added = match self.removed_bundles.front().await? {
298 Some(previous_bundle) => {
299 if previous_bundle.cursor() == cursor {
300 ensure!(
303 bundle == previous_bundle,
304 InboxError::UnexpectedBundle {
305 previous_bundle,
306 bundle,
307 }
308 );
309 self.removed_bundles.delete_front();
310 #[cfg(with_metrics)]
311 metrics::REMOVED_BUNDLES
312 .with_label_values(&[])
313 .observe(self.removed_bundles.count() as f64);
314 } else {
315 ensure!(
318 cursor < previous_bundle.cursor() && bundle.is_skippable(),
319 InboxError::UnexpectedBundle {
320 previous_bundle,
321 bundle,
322 }
323 );
324 }
325 false
326 }
327 None => {
328 self.added_bundles.push_back(bundle);
330 true
331 }
332 };
333 self.next_cursor_to_add.set(cursor.try_add_one()?);
334 Ok(newly_added)
335 }
336}
337
338#[cfg(with_testing)]
339impl InboxStateView<MemoryContext<()>>
340where
341 MemoryContext<()>: Context + Clone + Send + Sync + 'static,
342{
343 pub async fn new() -> Self {
344 let context = MemoryContext::new_for_testing(());
345 Self::load(context)
346 .await
347 .expect("Loading from memory should work")
348 }
349}