hopr_chain_connector/connector/
tickets.rs1use blokli_client::api::{BlokliQueryClient, BlokliTransactionClient};
2use futures::{FutureExt, TryFutureExt, future::BoxFuture};
3use hopr_api::chain::{ChainReceipt, ChainValues, TicketRedeemError};
4use hopr_chain_types::prelude::*;
5use hopr_crypto_types::prelude::*;
6use hopr_internal_types::prelude::*;
7
8use crate::{backend::Backend, connector::HoprBlockchainConnector, errors::ConnectorError};
9
10impl<B, C, P> HoprBlockchainConnector<C, B, P, P::TxRequest>
11where
12 B: Backend + Send + Sync + 'static,
13 C: BlokliTransactionClient + BlokliQueryClient + Send + Sync + 'static,
14 P: PayloadGenerator + Send + Sync + 'static,
15 P::TxRequest: Send + Sync + 'static,
16{
17 async fn prepare_ticket_redeem_payload(&self, ticket: AcknowledgedTicket) -> Result<P::TxRequest, ConnectorError> {
18 self.check_connection_state()?;
19
20 let channel_id = ticket.verified_ticket().channel_id;
21 if generate_channel_id(ticket.ticket.verified_issuer(), self.chain_key.public().as_ref()) != channel_id {
22 tracing::error!(%channel_id, "redeemed ticket is not ours");
23 return Err(ConnectorError::InvalidTicket);
24 }
25
26 let channel = self
28 .client
29 .query_channels(blokli_client::api::ChannelSelector {
30 filter: blokli_client::api::ChannelFilter::ChannelId(ticket.verified_ticket().channel_id.into()),
31 status: None,
32 })
33 .await?
34 .first()
35 .cloned()
36 .ok_or_else(|| {
37 tracing::error!(%channel_id, "trying to redeem a ticket on a channel that does not exist");
38 ConnectorError::ChannelDoesNotExist(channel_id)
39 })?;
40
41 if channel.status == blokli_client::api::types::ChannelStatus::Closed {
42 tracing::error!(%channel_id, "trying to redeem a ticket on a closed channel");
43 return Err(ConnectorError::ChannelClosed(channel_id));
44 }
45
46 if channel.epoch as u32 != ticket.verified_ticket().channel_epoch {
47 tracing::error!(
48 channel_epoch = channel.epoch,
49 ticket_epoch = ticket.verified_ticket().channel_epoch,
50 "invalid redeemed ticket epoch"
51 );
52 return Err(ConnectorError::InvalidTicket);
53 }
54
55 let channel_index: u64 = channel
56 .ticket_index
57 .0
58 .parse()
59 .map_err(|e| ConnectorError::TypeConversion(format!("unparseable channel index at redemption: {e}")))?;
60
61 if channel_index > ticket.verified_ticket().index {
62 tracing::error!(
63 channel_index,
64 ticket_index = ticket.verified_ticket().index,
65 "invalid redeemed ticket index"
66 );
67 return Err(ConnectorError::InvalidTicket);
68 }
69
70 let channel_dst = self.domain_separators().await?.channel;
72 let chain_key = self.chain_key.clone();
73 let ticket =
74 hopr_async_runtime::prelude::spawn_blocking(move || ticket.into_redeemable(&chain_key, &channel_dst))
75 .await
76 .map_err(|e| ConnectorError::OtherError(e.into()))??;
77
78 Ok(self.payload_generator.redeem_ticket(ticket.clone())?)
79 }
80}
81
82#[async_trait::async_trait]
83impl<B, C, P> hopr_api::chain::ChainWriteTicketOperations for HoprBlockchainConnector<C, B, P, P::TxRequest>
84where
85 B: Backend + Send + Sync + 'static,
86 C: BlokliTransactionClient + BlokliQueryClient + Send + Sync + 'static,
87 P: PayloadGenerator + Send + Sync + 'static,
88 P::TxRequest: Send + Sync + 'static,
89{
90 type Error = ConnectorError;
91
92 async fn redeem_ticket<'a>(
93 &'a self,
94 ticket: AcknowledgedTicket,
95 ) -> Result<
96 BoxFuture<'a, Result<(VerifiedTicket, ChainReceipt), TicketRedeemError<Self::Error>>>,
97 TicketRedeemError<Self::Error>,
98 > {
99 match self.prepare_ticket_redeem_payload(ticket.clone()).await {
100 Ok(tx_req) => {
101 let ticket_clone = ticket.clone();
102 Ok(self
103 .send_tx(tx_req)
104 .await
105 .map_err(|e| TicketRedeemError::ProcessingError(ticket.ticket.clone(), e))?
106 .map_err(move |tx_tracking_error|
107 if let Some(reject_error) = tx_tracking_error.as_transaction_rejection_error() {
109 TicketRedeemError::Rejected(ticket.ticket.clone(), format!("on-chain rejection: {reject_error:?}"))
110 } else {
111 TicketRedeemError::ProcessingError(ticket.ticket.clone(), tx_tracking_error)
112 })
113 .and_then(move |receipt| futures::future::ok((ticket_clone.ticket, receipt)))
114 .boxed())
115 }
116 Err(e @ ConnectorError::InvalidTicket)
117 | Err(e @ ConnectorError::ChannelDoesNotExist(_))
118 | Err(e @ ConnectorError::ChannelClosed(_)) => {
119 Err(TicketRedeemError::Rejected(ticket.ticket, e.to_string()))
120 }
121 Err(e) => Err(TicketRedeemError::ProcessingError(ticket.ticket, e)),
122 }
123 }
124}