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 [`VerifiedAcknowledgement`] as an outgoing packet to be sent to the given
76    /// [`peer`](OffchainPublicKey).
77    async fn encode_acknowledgement(
78        &self,
79        ack: VerifiedAcknowledgement,
80        peer: &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/// Performs necessary processing of unacknowledged tickets in the HOPR packet processing pipeline.
100#[async_trait::async_trait]
101#[auto_impl::auto_impl(&, Box, Arc)]
102pub trait UnacknowledgedTicketProcessor {
103    type Error: std::error::Error + Send + Sync + 'static;
104
105    /// Inserts a verified unacknowledged ticket from a delivered packet into the internal storage.
106    ///
107    /// The [`ticket`](UnacknowledgedTicket) corresponds to the given [`challenge`](HalfKeyChallenge)
108    /// and awaits to be [acknowledged](UnacknowledgedTicketProcessor::acknowledge_ticket)
109    /// once an [`Acknowledgement`] is received from the `next_hop`.
110    async fn insert_unacknowledged_ticket(
111        &self,
112        next_hop: &OffchainPublicKey,
113        challenge: HalfKeyChallenge,
114        ticket: UnacknowledgedTicket,
115    ) -> Result<(), Self::Error>;
116
117    /// Finds and acknowledges previously inserted ticket, using an [`Acknowledgement`] from the
118    /// upstream [`peer`](OffchainPublicKey).
119    ///
120    /// This function must verify the given acknowledgement and find if it contains a solution
121    /// to a challenge of a previously [inserted ticket](UnacknowledgedTicketProcessor::insert_unacknowledged_ticket).
122    ///
123    /// On success, the [resolution](ResolvedAcknowledgement) contains a decision whether the corresponding previously
124    /// stored ticket was found, and whether it is winning (and thus also redeemable) or losing.
125    ///
126    /// Returns `Ok(None)` if no [`Acknowledgement`] from `peer` was
127    /// expected.
128    ///
129    /// Returns an error if acknowledgement was expected from `peer`, but a ticket with the challenge
130    /// corresponding to the [`Acknowledgement`] was not found.
131    async fn acknowledge_ticket(
132        &self,
133        peer: OffchainPublicKey,
134        ack: Acknowledgement,
135    ) -> Result<Option<ResolvedAcknowledgement>, Self::Error>;
136}
137
138/// Allows tracking ticket indices of outgoing channels and
139/// unrealized balances of incoming channels.
140#[async_trait::async_trait]
141#[auto_impl::auto_impl(&, Box, Arc)]
142pub trait TicketTracker {
143    type Error: std::error::Error + Send + Sync + 'static;
144
145    /// Gets the next ticket index for an outgoing ticket for the given channel.
146    async fn next_outgoing_ticket_index(&self, channel_id: &ChannelId, epoch: u32) -> Result<u64, Self::Error>;
147
148    /// Retrieves the unrealized balance of the given channel.
149    ///
150    /// This allows guarding from situations where the ticket issuer issues more tickets
151    /// than there's balance in the given channel.
152    async fn incoming_channel_unrealized_balance(
153        &self,
154        channel_id: &ChannelId,
155        epoch: u32,
156    ) -> Result<HoprBalance, Self::Error>;
157
158    /// Convenience function that allows creating multi-hop tickets.
159    async fn create_multihop_ticket(
160        &self,
161        channel: &ChannelEntry,
162        current_path_pos: u8,
163        winning_prob: WinningProbability,
164        ticket_price: HoprBalance,
165    ) -> Result<TicketBuilder, TicketCreationError<Self::Error>> {
166        // The next ticket is worth: price * remaining hop count / winning probability
167        let amount = HoprBalance::from(
168            ticket_price
169                .amount()
170                .mul(U256::from(current_path_pos - 1))
171                .div_f64(winning_prob.into())
172                .expect("winning probability is always less than or equal to 1"),
173        );
174
175        if channel.balance.lt(&amount) {
176            return Err(TicketCreationError::OutOfFunds(*channel.get_id(), amount));
177        }
178
179        let ticket_builder = TicketBuilder::default()
180            .counterparty(channel.destination)
181            .balance(amount)
182            .index(
183                self.next_outgoing_ticket_index(channel.get_id(), channel.channel_epoch)
184                    .await
185                    .map_err(TicketCreationError::Other)?,
186            )
187            .win_prob(winning_prob)
188            .channel_epoch(channel.channel_epoch);
189
190        Ok(ticket_builder)
191    }
192}