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}