Skip to main content

hopr_protocol_hopr/codec/
encoder.rs

1use bytes::{BufMut, BytesMut};
2use hopr_api::{
3    chain::*,
4    types::{
5        crypto::{crypto_traits::Randomizable, prelude::*},
6        internal::prelude::*,
7        primitive::prelude::*,
8    },
9};
10use hopr_crypto_packet::prelude::*;
11use hopr_protocol_pix::EntryShareGenerator;
12
13use crate::{HoprCodecConfig, OutgoingPacket, PacketEncoder, SurbStore, errors::HoprProtocolError};
14
15/// Maximum number of acknowledgements that can be packed into a single HOPR packet.
16///
17/// Currently, the [`HoprPacket::PAYLOAD_SIZE`] minus 16-bit acknowledgement batch size counter
18/// divided by [`Acknowledgement::SIZE`].
19pub const MAX_ACKNOWLEDGEMENTS_BATCH_SIZE: usize =
20    (HoprPacket::PAYLOAD_SIZE - size_of::<u16>()) / Acknowledgement::SIZE;
21
22/// Default [encoder](PacketEncoder) implementation for HOPR packets.
23pub struct HoprEncoder<Chain, G, S, T> {
24    chain_api: Chain,
25    surb_store: S,
26    ticket_factory: T,
27    chain_key: ChainKeypair,
28    channels_dst: Hash,
29    ssa_generator: G,
30    cfg: HoprCodecConfig,
31}
32
33impl<Chain, G, S, T> HoprEncoder<Chain, G, S, T> {
34    /// Creates a new instance of the encoder.
35    pub fn new(
36        chain_key: ChainKeypair,
37        chain_api: Chain,
38        surb_store: S,
39        ticket_factory: T,
40        channels_dst: Hash,
41        ssa_generator: G,
42        cfg: HoprCodecConfig,
43    ) -> Self {
44        Self {
45            chain_api,
46            surb_store,
47            ticket_factory,
48            chain_key,
49            channels_dst,
50            ssa_generator,
51            cfg,
52        }
53    }
54}
55
56impl<Chain, G, S, T> HoprEncoder<Chain, G, S, T>
57where
58    Chain: ChainKeyOperations + ChainReadChannelOperations + ChainReadTicketOperations + ChainValues + Sync,
59    G: EntryShareGenerator<HoprPixSpec>,
60    S: SurbStore,
61    T: hopr_api::tickets::TicketFactory + Sync,
62{
63    fn encode_packet_internal<D: AsRef<[u8]> + Send + 'static, Sig: Into<PacketSignals> + Send + 'static>(
64        &self,
65        next_peer: OffchainPublicKey,
66        data: D,
67        num_hops: usize,
68        signals: Sig,
69        routing: PacketRouting<ValidatedPath>,
70        pseudonym: HoprPseudonym,
71    ) -> Result<OutgoingPacket, HoprProtocolError> {
72        let next_peer = self
73            .chain_api
74            .packet_key_to_chain_key(&next_peer)
75            .map_err(HoprProtocolError::resolver)?
76            .ok_or(HoprProtocolError::KeyNotFound)?;
77
78        // Decide whether to create a multi-hop or a zero-hop ticket
79        let next_ticket = if num_hops > 1 {
80            let channel = self
81                .chain_api
82                .channel_by_parties(self.chain_key.as_ref(), &next_peer)
83                .map_err(HoprProtocolError::resolver)?
84                .ok_or_else(|| HoprProtocolError::ChannelNotFound(*self.chain_key.as_ref(), next_peer))?;
85
86            let (outgoing_ticket_win_prob, outgoing_ticket_price) = self
87                .chain_api
88                .outgoing_ticket_values(self.cfg.outgoing_win_prob, self.cfg.outgoing_ticket_price)
89                .map_err(HoprProtocolError::resolver)?;
90
91            self.ticket_factory
92                .new_multihop_ticket(
93                    &channel,
94                    (num_hops as u8).try_into().expect("cannot fail due to num_hops > 1"),
95                    outgoing_ticket_win_prob,
96                    outgoing_ticket_price,
97                )
98                .map_err(HoprProtocolError::ticket_factory)?
99        } else {
100            TicketBuilder::zero_hop().counterparty(next_peer)
101        };
102
103        // Construct the outgoing packet
104        let (packet, openers) = HoprPacket::into_outgoing(
105            data.as_ref(),
106            &pseudonym,
107            routing,
108            &self.chain_key,
109            next_ticket,
110            self.chain_api.key_id_mapper_ref(),
111            &self.channels_dst,
112            &self.ssa_generator,
113            signals,
114        )?;
115
116        // Store the reply openers under the given SenderId
117        // This is a no-op for reply packets
118        let mut minted_surbs = Vec::with_capacity(openers.len());
119        openers.into_iter().for_each(|(surb_id, opener)| {
120            minted_surbs.push(surb_id);
121            self.surb_store
122                .insert_reply_opener(HoprSenderId::from_pseudonym_and_id(&pseudonym, surb_id), opener);
123        });
124
125        let out = packet.try_as_outgoing().ok_or(HoprProtocolError::InvalidState(
126            "cannot send out packet that is not outgoing",
127        ))?;
128
129        let mut transport_payload = BytesMut::with_capacity(HoprPacket::SIZE);
130        transport_payload.put_slice(out.packet.as_ref());
131        transport_payload.put_slice(&out.ticket.into_encoded());
132
133        Ok(OutgoingPacket {
134            next_hop: out.next_hop,
135            ack_challenge: out.ack_challenge,
136            encrypted_pix_share: out.encrypted_pix_share,
137            data: transport_payload.freeze(),
138            minted_surbs,
139        })
140    }
141}
142
143impl<Chain, G, S, T> PacketEncoder for HoprEncoder<Chain, G, S, T>
144where
145    Chain: ChainKeyOperations + ChainReadChannelOperations + ChainReadTicketOperations + ChainValues + Send + Sync,
146    G: EntryShareGenerator<HoprPixSpec>,
147    S: SurbStore + Send + Sync,
148    T: hopr_api::tickets::TicketFactory + Send + Sync,
149{
150    type Error = HoprProtocolError;
151
152    #[tracing::instrument(skip_all, level = "trace")]
153    fn encode_packet<D: AsRef<[u8]> + Send + 'static, Sig: Into<PacketSignals> + Send + 'static>(
154        &self,
155        data: D,
156        routing: ResolvedTransportRouting<HoprSurb>,
157        signals: Sig,
158        generation: Option<u8>,
159    ) -> Result<OutgoingPacket, Self::Error> {
160        // Get necessary packet routing values
161        let (next_peer, num_hops, pseudonym, routing) = match routing {
162            ResolvedTransportRouting::Forward {
163                pseudonym,
164                forward_path,
165                return_paths,
166            } => (
167                forward_path[0],
168                forward_path.num_hops(),
169                pseudonym,
170                PacketRouting::ForwardPath {
171                    forward_path,
172                    return_paths,
173                    // Stamp the SURB-batch generation captured when these return paths were resolved,
174                    // so the replying side drops SURBs left over from a superseded return path. It is
175                    // captured with the plan (not read here) so a concurrent re-plan/bump cannot
176                    // label this already-chosen batch with a newer generation; falling back to the
177                    // store's current value only when a caller did not supply one.
178                    generation: generation.unwrap_or_else(|| self.surb_store.current_generation(&pseudonym)),
179                },
180            ),
181            ResolvedTransportRouting::Return(sender_id, surb) => {
182                let next = self
183                    .chain_api
184                    .key_id_mapper_ref()
185                    .map_id_to_public(&surb.first_relayer)
186                    .ok_or(HoprProtocolError::KeyNotFound)?;
187
188                (
189                    next,
190                    surb.additional_data_receiver.proof_of_relay_values().chain_length() as usize,
191                    sender_id.pseudonym(),
192                    PacketRouting::Surb(sender_id, surb),
193                )
194            }
195        };
196
197        tracing::trace!(len = data.as_ref().len(), "encoding packet");
198        self.encode_packet_internal(next_peer, data, num_hops, signals, routing, pseudonym)
199    }
200
201    #[tracing::instrument(skip_all, level = "trace", fields(destination = destination.to_peerid_str()))]
202    fn encode_acknowledgements(
203        &self,
204        acks: &[VerifiedAcknowledgement],
205        destination: &OffchainPublicKey,
206    ) -> Result<OutgoingPacket, Self::Error> {
207        tracing::trace!(num_acks = acks.len(), "encoding acknowledgements");
208
209        let mut all_acks = Vec::<u8>::with_capacity(size_of::<u16>() + acks.len() * Acknowledgement::SIZE);
210        all_acks.extend((acks.len() as u16).to_be_bytes());
211        acks.iter().for_each(|ack| all_acks.extend(ack.leak().as_ref()));
212
213        self.encode_packet_internal(
214            *destination,
215            all_acks,
216            0,
217            None,
218            PacketRouting::NoAck(*destination),
219            HoprPseudonym::random(),
220        )
221    }
222}