Skip to main content

hop_relay/
lib.rs

1#![forbid(unsafe_code)]
2//! Relay-side validation and ordered bundle application.
3
4use core::fmt;
5
6use hop_actions::Action;
7use hop_channels::{Channel, ChannelError};
8
9#[cfg(feature = "net")]
10pub mod net;
11
12/// Successful bundle application metadata.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct BundleReceipt {
15    /// Channel height before the first action.
16    pub start_height: u64,
17    /// Channel height after the final action.
18    pub end_height: u64,
19    /// Number of committed actions.
20    pub committed: usize,
21}
22
23/// A bundle failure with an explicit committed-prefix count.
24#[derive(Debug)]
25pub struct BundleError {
26    /// Zero-based action index that failed.
27    pub action_index: usize,
28    /// Number of actions committed before the failure.
29    pub committed: usize,
30    /// Underlying channel failure.
31    pub source: ChannelError,
32}
33
34impl fmt::Display for BundleError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(
37            f,
38            "bundle action {} failed after {} commits: {}",
39            self.action_index, self.committed, self.source
40        )
41    }
42}
43
44impl std::error::Error for BundleError {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        Some(&self.source)
47    }
48}
49
50/// Validates a complete bundle, then applies it in the supplied order.
51///
52/// Polynomial actions over one channel commute, so no speculative scheduler is
53/// needed. Validation is completed before the first mutation. Storage failures
54/// can still produce a committed prefix, which is reported by [`BundleError`].
55pub fn apply_bundle(
56    channel: &mut Channel,
57    actions: &[Action],
58) -> Result<BundleReceipt, BundleError> {
59    for (index, action) in actions.iter().enumerate() {
60        if let Err(source) = action
61            .validate_for_dimension(channel.dimension())
62            .map_err(ChannelError::from)
63        {
64            return Err(BundleError {
65                action_index: index,
66                committed: 0,
67                source,
68            });
69        }
70    }
71
72    let start_height = channel.height();
73    for (index, action) in actions.iter().enumerate() {
74        if let Err(source) = channel.apply(action) {
75            return Err(BundleError {
76                action_index: index,
77                committed: index,
78                source,
79            });
80        }
81    }
82    Ok(BundleReceipt {
83        start_height,
84        end_height: channel.height(),
85        committed: actions.len(),
86    })
87}