hopr_protocol_hopr/
traits.rs

1use std::ops::Mul;
2
3use hopr_crypto_packet::prelude::*;
4use hopr_crypto_types::prelude::*;
5use hopr_internal_types::prelude::*;
6use hopr_network_types::prelude::*;
7use hopr_primitive_types::prelude::*;
8
9use crate::TicketCreationError;
10pub use crate::{
11    errors::IncomingPacketError,
12    types::{FoundSurb, IncomingPacket, OutgoingPacket, ResolvedAcknowledgement},
13};
14
15/// A trait defining the operations required to store and retrieve SURBs (Single Use Reply Blocks) and their reply
16/// openers.
17///
18/// The sending side stores the reply openers, whereas the SURBs are stored by the replying side
19/// of the communication.
20#[async_trait::async_trait]
21#[auto_impl::auto_impl(&, Box, Arc)]
22pub trait SurbStore {
23    /// Tries to find SURB using the given [`matcher`](SurbMatcher).
24    ///
25    /// This is used by the replying side when it is about to send a reply packet back
26    /// to the sender.
27    async fn find_surb(&self, matcher: SurbMatcher) -> Option<FoundSurb>;
28
29    /// Stores the `surbs` and associates them with the given [`pseudonym`](HoprPseudonym).
30    ///
31    /// This is used by the replying side when it receives packets containing SURBs from the sender
32    /// with the given `pseudonym`.
33    ///
34    /// Returns the total number of SURBs available for that `pseudonym`, including the newly inserted
35    /// ones.
36    async fn insert_surbs(&self, pseudonym: HoprPseudonym, surbs: Vec<(HoprSurbId, HoprSurb)>) -> usize;
37
38    /// Stores the given [`opener`](ReplyOpener) for the given [`sender_id`](HoprSenderId).
39    ///
40    /// This is done by the sending side, when it creates a packet containing a SURB to be delivered
41    /// to the replying side.
42    ///
43    /// The operation should happen reasonably fast, as it is called from the packet processing code.
44    fn insert_reply_opener(&self, sender_id: HoprSenderId, opener: ReplyOpener);
45
46    /// Tries to find a [`ReplyOpener`] given the [`sender_id`](HoprSenderId).
47    ///
48    /// This is done by the sending side of the original packet when the reply to that
49    /// packet is received and needs to be decrypted.
50    ///
51    /// The operation should happen reasonably fast, as it is called from the packet processing code.
52    fn find_reply_opener(&self, sender_id: &HoprSenderId) -> Option<ReplyOpener>;
53}
54
55/// Trait defining encoder for [outgoing HOPR packets](OutgoingPacket).
56///
57/// These operations are done directly by the packet processing pipeline before
58/// the outgoing packet is handled to the underlying p2p transport.
59#[async_trait::async_trait]
60#[auto_impl::auto_impl(&, Box, Arc)]
61pub trait PacketEncoder {
62    type Error: std::error::Error + Send + Sync + 'static;
63
64    /// Encodes the given `data` and [`signals`](PacketSignals) for sending.
65    ///
66    /// The `data` MUST be already correctly sized for HOPR packets, otherwise the operation
67    /// must fail.
68    async fn encode_packet<T: AsRef<[u8]> + Send + 'static, S: Into<PacketSignals> + Send + 'static>(
69        &self,
70        data: T,
71        routing: ResolvedTransportRouting,
72        signals: S,
73    ) -> Result<OutgoingPacket, Self::Error>;
74
75    /// Encodes the given vector of [`VerifiedAcknowledgements`](VerifiedAcknowledgement) as an outgoing packet to be
76    /// sent to the given [`destination`](OffchainPublicKey).
77    async fn encode_acknowledgements(
78        &self,
79        acks: &[VerifiedAcknowledgement],
80        destination: &OffchainPublicKey,
81    ) -> Result<OutgoingPacket, Self::Error>;
82}
83
84/// Trait defining decoder HOPR packets.
85///
86/// This operation is done directly by the packet processing pipeline after
87/// the underlying p2p transport hands over incoming data packets.
88#[async_trait::async_trait]
89#[auto_impl::auto_impl(&, Box, Arc)]
90pub trait PacketDecoder {
91    type Error: std::error::Error + Send + Sync + 'static;
92
93    /// Decodes the `data` received from the given [`sender`](PeerId)
94    /// returns the corresponding [`IncomingPacket`] if the decoding into a HOPR packet was successful.
95    async fn decode(&self, sender: PeerId, data: Box<[u8]>)
96    -> Result<IncomingPacket, IncomingPacketError<Self::Error>>;
97}
98
99/// Defines errors returned by [`UnacknowledgedTicketProcessor::acknowledge_ticket`].
100#[derive(Debug, thiserror::Error)]
101pub enum TicketAcknowledgementError<E> {
102    /// An acknowledgement from a peer was not expected.
103    #[error("acknowledgement from the peer was not expected")]
104    UnexpectedAcknowledgement,
105    /// An error occurred while processing the acknowledgement.
106    #[error(transparent)]
107    Inner(E),
108}
109
110impl<E> TicketAcknowledgementError<E> {
111    pub fn inner<F: Into<E>>(e: F) -> Self {
112        Self::Inner(e.into())
113    }
114}
115
116/// Performs necessary processing of unacknowledged tickets in the HOPR packet processing pipeline.
117#[async_trait::async_trait]
118#[auto_impl::auto_impl(&, Box, Arc)]
119pub trait UnacknowledgedTicketProcessor {
120    type Error: std::error::Error + Send + Sync + 'static;
121
122    /// Inserts a verified unacknowledged ticket from a delivered packet into the internal storage.
123    ///
124    /// The [`ticket`](UnacknowledgedTicket) corresponds to the given [`challenge`](HalfKeyChallenge)
125    /// and awaits to be [acknowledged](UnacknowledgedTicketProcessor::acknowledge_tickets)
126    /// once an [`Acknowledgement`] is received from the `next_hop`.
127    async fn insert_unacknowledged_ticket(
128        &self,
129        next_hop: &OffchainPublicKey,
130        challenge: HalfKeyChallenge,
131        ticket: UnacknowledgedTicket,
132    ) -> Result<(), Self::Error>;
133
134    /// Finds and acknowledges previously inserted tickets, using incoming [`Acknowledgements`](Acknowledgement) from
135    /// the upstream [`peer`](OffchainPublicKey).
136    ///
137    /// Function should first check if any acknowledgements are expected from the given `peer`.
138    ///
139    /// Furthermore, the function must verify each given acknowledgement and find if it evaluates to any solutions
140    /// to challenges of previously [inserted tickets](UnacknowledgedTicketProcessor::insert_unacknowledged_ticket).
141    ///
142    /// On success, the [resolutions](ResolvedAcknowledgement) contain decisions whether the previously
143    /// stored ticket with a matching challenge was found, and whether it is winning (and thus also redeemable) or
144    /// losing.
145    /// Challenges for which tickets were not found are skipped.
146    ///
147    /// Must return [`TicketAcknowledgementError::UnexpectedAcknowledgement`] if no `Acknowledgements` from the given
148    /// `peer` was expected.
149    async fn acknowledge_tickets(
150        &self,
151        peer: OffchainPublicKey,
152        acks: Vec<Acknowledgement>,
153    ) -> Result<Vec<ResolvedAcknowledgement>, TicketAcknowledgementError<Self::Error>>;
154}
155
156/// Allows tracking ticket indices of outgoing channels and
157/// unrealized balances of incoming channels.
158#[async_trait::async_trait]
159#[auto_impl::auto_impl(&, Box, Arc)]
160pub trait TicketTracker {
161    type Error: std::error::Error + Send + Sync + 'static;
162
163    /// Gets the next ticket index for an outgoing ticket for the given channel.
164    async fn next_outgoing_ticket_index(&self, channel_id: &ChannelId, epoch: u32) -> Result<u64, Self::Error>;
165
166    /// Retrieves the unrealized balance of the given channel.
167    ///
168    /// This allows guarding from situations where the ticket issuer issues more tickets
169    /// than there's balance in the given channel.
170    async fn incoming_channel_unrealized_balance(
171        &self,
172        channel_id: &ChannelId,
173        epoch: u32,
174    ) -> Result<HoprBalance, Self::Error>;
175
176    /// Convenience function that allows creating multi-hop tickets.
177    async fn create_multihop_ticket(
178        &self,
179        channel: &ChannelEntry,
180        current_path_pos: u8,
181        winning_prob: WinningProbability,
182        ticket_price: HoprBalance,
183    ) -> Result<TicketBuilder, TicketCreationError<Self::Error>> {
184        // The next ticket is worth: price * remaining hop count / winning probability
185        let amount = HoprBalance::from(
186            ticket_price
187                .amount()
188                .mul(U256::from(current_path_pos - 1))
189                .div_f64(winning_prob.into())
190                .expect("winning probability is always less than or equal to 1"),
191        );
192
193        if channel.balance.lt(&amount) {
194            return Err(TicketCreationError::OutOfFunds(*channel.get_id(), amount));
195        }
196
197        let ticket_builder = TicketBuilder::default()
198            .counterparty(channel.destination)
199            .balance(amount)
200            .index(
201                self.next_outgoing_ticket_index(channel.get_id(), channel.channel_epoch)
202                    .await
203                    .map_err(TicketCreationError::Other)?,
204            )
205            .win_prob(winning_prob)
206            .channel_epoch(channel.channel_epoch);
207
208        Ok(ticket_builder)
209    }
210}