1use crate::gen::fiber as molecule_fiber;
4use crate::Hash256;
5use crate::{EntityHex, Pubkey, SliceHex};
6use ckb_types::prelude::{Pack, Unpack};
7use molecule::prelude::{Builder, Byte, Entity};
8use num_enum::{IntoPrimitive, TryFromPrimitive};
9use serde::{Deserialize, Serialize};
10use serde_with::serde_as;
11use std::collections::HashMap;
12use strum::{AsRefStr, EnumString};
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
19pub enum PaymentStatus {
20 Created,
22 Inflight,
26 Success,
29 Failed,
32}
33
34impl PaymentStatus {
35 pub fn is_final(&self) -> bool {
36 matches!(self, PaymentStatus::Success | PaymentStatus::Failed)
37 }
38}
39
40pub const USER_CUSTOM_RECORDS_MAX_INDEX: u32 = 65535;
42
43#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
46pub struct PaymentCustomRecords {
47 pub data: HashMap<u32, Vec<u8>>,
49}
50
51impl From<PaymentCustomRecords> for molecule_fiber::CustomRecords {
52 fn from(custom_records: PaymentCustomRecords) -> Self {
53 molecule_fiber::CustomRecords::new_builder()
54 .data(
55 custom_records
56 .data
57 .into_iter()
58 .map(|(key, val)| {
59 molecule_fiber::CustomRecordDataPairBuilder::default()
60 .key(key.pack())
61 .value(val.pack())
62 .build()
63 })
64 .collect(),
65 )
66 .build()
67 }
68}
69
70impl From<molecule_fiber::CustomRecords> for PaymentCustomRecords {
71 fn from(custom_records: molecule_fiber::CustomRecords) -> Self {
72 PaymentCustomRecords {
73 data: custom_records
74 .data()
75 .into_iter()
76 .map(|pair| (pair.key().unpack(), pair.value().unpack()))
77 .collect(),
78 }
79 }
80}
81
82#[derive(Eq, PartialEq, Debug)]
84pub struct BasicMppPaymentData {
85 pub payment_secret: crate::Hash256,
86 pub total_amount: u128,
87}
88
89impl BasicMppPaymentData {
90 pub const CUSTOM_RECORD_KEY: u32 = USER_CUSTOM_RECORDS_MAX_INDEX + 1;
93
94 pub fn new(payment_secret: crate::Hash256, total_amount: u128) -> Self {
95 Self {
96 payment_secret,
97 total_amount,
98 }
99 }
100
101 fn to_vec(&self) -> Vec<u8> {
102 let mut vec = Vec::new();
103 vec.extend_from_slice(self.payment_secret.as_ref());
104 vec.extend_from_slice(&self.total_amount.to_le_bytes());
105 vec
106 }
107
108 pub fn write(&self, custom_records: &mut PaymentCustomRecords) {
109 custom_records
110 .data
111 .insert(Self::CUSTOM_RECORD_KEY, self.to_vec());
112 }
113
114 pub fn read(custom_records: &PaymentCustomRecords) -> Option<Self> {
115 custom_records
116 .data
117 .get(&Self::CUSTOM_RECORD_KEY)
118 .and_then(|data| {
119 if data.len() != 32 + 16 {
120 return None;
121 }
122 let secret: [u8; 32] = data[..32].try_into().unwrap();
123 let payment_secret = crate::Hash256::from(secret);
124 let total_amount = u128::from_le_bytes(data[32..].try_into().unwrap());
125 Some(Self::new(payment_secret, total_amount))
126 })
127 }
128}
129
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
138pub enum AttemptStatus {
139 Created,
141 Inflight,
143 Retrying,
145 Success,
147 Failed,
149}
150
151impl AttemptStatus {
152 pub fn is_final(&self) -> bool {
153 matches!(self, AttemptStatus::Success | AttemptStatus::Failed)
154 }
155}
156
157use crate::protocol::ChannelUpdate;
158use ckb_types::packed::OutPoint;
159use std::fmt::Display;
160
161#[serde_as]
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164pub enum TlcErrData {
165 ChannelFailed {
166 #[serde_as(as = "EntityHex")]
167 channel_outpoint: OutPoint,
168 channel_update: Option<ChannelUpdate>,
169 node_id: Pubkey,
170 },
171 NodeFailed {
172 node_id: Pubkey,
173 },
174 TrampolineFailed {
175 node_id: Pubkey,
176 #[serde_as(as = "SliceHex")]
177 inner_error_packet: Vec<u8>,
178 },
179}
180
181#[serde_as]
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub struct TlcErr {
185 pub error_code: TlcErrorCode,
186 pub extra_data: Option<TlcErrData>,
187}
188
189impl Display for TlcErr {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "{}", self.error_code_as_str())
192 }
193}
194
195impl TlcErr {
196 pub fn new(error_code: TlcErrorCode) -> Self {
197 TlcErr {
198 error_code,
199 extra_data: None,
200 }
201 }
202
203 pub fn new_node_fail(error_code: TlcErrorCode, node_id: Pubkey) -> Self {
204 TlcErr {
205 error_code,
206 extra_data: Some(TlcErrData::NodeFailed { node_id }),
207 }
208 }
209
210 pub fn new_channel_fail(
211 error_code: TlcErrorCode,
212 node_id: Pubkey,
213 channel_outpoint: OutPoint,
214 channel_update: Option<ChannelUpdate>,
215 ) -> Self {
216 TlcErr {
217 error_code,
218 extra_data: Some(TlcErrData::ChannelFailed {
219 node_id,
220 channel_outpoint,
221 channel_update,
222 }),
223 }
224 }
225
226 pub fn error_node_id(&self) -> Option<Pubkey> {
227 match &self.extra_data {
228 Some(TlcErrData::NodeFailed { node_id }) => Some(*node_id),
229 Some(TlcErrData::ChannelFailed { node_id, .. }) => Some(*node_id),
230 Some(TlcErrData::TrampolineFailed { node_id, .. }) => Some(*node_id),
231 _ => None,
232 }
233 }
234
235 pub fn error_channel_outpoint(&self) -> Option<OutPoint> {
236 match &self.extra_data {
237 Some(TlcErrData::ChannelFailed {
238 channel_outpoint, ..
239 }) => Some(channel_outpoint.clone()),
240 _ => None,
241 }
242 }
243
244 pub fn error_code(&self) -> TlcErrorCode {
245 self.error_code
246 }
247
248 pub fn error_code_as_str(&self) -> String {
249 let error_code: TlcErrorCode = self.error_code;
250 error_code.as_ref().to_string()
251 }
252
253 pub fn error_code_as_u16(&self) -> u16 {
254 self.error_code.into()
255 }
256
257 pub fn set_extra_data(&mut self, extra_data: TlcErrData) {
258 self.extra_data = Some(extra_data);
259 }
260
261 pub fn serialize(&self) -> Vec<u8> {
262 molecule_fiber::TlcErr::try_from(self.clone())
263 .expect("TlcErr serialization should not fail for valid TlcErr")
264 .as_slice()
265 .to_vec()
266 }
267
268 pub fn deserialize(data: &[u8]) -> Option<Self> {
269 molecule_fiber::TlcErr::from_slice(data)
270 .ok()
271 .and_then(|e| TlcErr::try_from(e).ok())
272 }
273}
274
275impl TryFrom<TlcErrData> for molecule_fiber::TlcErrData {
276 type Error = anyhow::Error;
277
278 fn try_from(tlc_err_data: TlcErrData) -> Result<Self, Self::Error> {
279 match tlc_err_data {
280 TlcErrData::ChannelFailed {
281 channel_outpoint,
282 channel_update,
283 node_id,
284 } => Ok(molecule_fiber::ChannelFailed::new_builder()
285 .channel_outpoint(channel_outpoint)
286 .channel_update(
287 molecule_fiber::ChannelUpdateOpt::new_builder()
288 .set(channel_update.map(|x| x.into()))
289 .build(),
290 )
291 .node_id(node_id.into())
292 .build()
293 .into()),
294 TlcErrData::NodeFailed { node_id } => Ok(molecule_fiber::NodeFailed::new_builder()
295 .node_id(node_id.into())
296 .build()
297 .into()),
298 TlcErrData::TrampolineFailed {
299 node_id,
300 inner_error_packet,
301 } => Ok(molecule_fiber::TrampolineFailed::new_builder()
302 .node_id(node_id.into())
303 .inner_error_packet(inner_error_packet.pack())
304 .build()
305 .into()),
306 }
307 }
308}
309
310impl TryFrom<molecule_fiber::TlcErrData> for TlcErrData {
311 type Error = anyhow::Error;
312
313 fn try_from(tlc_err_data: molecule_fiber::TlcErrData) -> Result<Self, Self::Error> {
314 match tlc_err_data.to_enum() {
315 molecule_fiber::TlcErrDataUnion::ChannelFailed(channel_failed) => {
316 Ok(TlcErrData::ChannelFailed {
317 channel_outpoint: channel_failed.channel_outpoint(),
318 channel_update: channel_failed
319 .channel_update()
320 .to_opt()
321 .map(|x| x.try_into())
322 .transpose()?,
323 node_id: channel_failed.node_id().try_into()?,
324 })
325 }
326 molecule_fiber::TlcErrDataUnion::NodeFailed(node_failed) => {
327 Ok(TlcErrData::NodeFailed {
328 node_id: node_failed.node_id().try_into()?,
329 })
330 }
331 molecule_fiber::TlcErrDataUnion::TrampolineFailed(trampoline_failed) => {
332 Ok(TlcErrData::TrampolineFailed {
333 node_id: trampoline_failed.node_id().try_into()?,
334 inner_error_packet: trampoline_failed.inner_error_packet().unpack(),
335 })
336 }
337 }
338 }
339}
340
341impl TryFrom<TlcErr> for molecule_fiber::TlcErr {
342 type Error = anyhow::Error;
343
344 fn try_from(tlc_err: TlcErr) -> Result<Self, Self::Error> {
345 Ok(molecule_fiber::TlcErr::new_builder()
346 .error_code(tlc_err.error_code_as_u16().into())
347 .extra_data(
348 molecule_fiber::TlcErrDataOpt::new_builder()
349 .set(tlc_err.extra_data.map(|data| data.try_into()).transpose()?)
350 .build(),
351 )
352 .build())
353 }
354}
355
356impl TryFrom<molecule_fiber::TlcErr> for TlcErr {
357 type Error = anyhow::Error;
358
359 fn try_from(tlc_err: molecule_fiber::TlcErr) -> Result<Self, Self::Error> {
360 let code: u16 = tlc_err.error_code().into();
361 let error_code = TlcErrorCode::try_from(code)
362 .map_err(|_| anyhow::anyhow!("Invalid TLC error code: {}", code))?;
363 let extra_data = tlc_err
364 .extra_data()
365 .to_opt()
366 .map(|data| data.try_into())
367 .transpose()?;
368 Ok(TlcErr {
369 error_code,
370 extra_data,
371 })
372 }
373}
374
375use crate::invoice::HashAlgorithm;
376
377#[serde_as]
379#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
380pub struct CurrentPaymentHopData {
381 pub amount: u128,
382 pub expiry: u64,
383 pub payment_preimage: Option<crate::Hash256>,
384 pub hash_algorithm: HashAlgorithm,
385 pub funding_tx_hash: crate::Hash256,
386 pub custom_records: Option<PaymentCustomRecords>,
387}
388
389impl From<PaymentHopData> for CurrentPaymentHopData {
390 fn from(hop: PaymentHopData) -> Self {
391 CurrentPaymentHopData {
392 amount: hop.amount,
393 expiry: hop.expiry,
394 payment_preimage: hop.payment_preimage,
395 hash_algorithm: hop.hash_algorithm,
396 funding_tx_hash: hop.funding_tx_hash,
397 custom_records: hop.custom_records,
398 }
399 }
400}
401
402impl From<CurrentPaymentHopData> for PaymentHopData {
403 fn from(hop: CurrentPaymentHopData) -> Self {
404 PaymentHopData {
405 amount: hop.amount,
406 expiry: hop.expiry,
407 payment_preimage: hop.payment_preimage,
408 hash_algorithm: hop.hash_algorithm,
409 funding_tx_hash: hop.funding_tx_hash,
410 custom_records: hop.custom_records,
411 next_hop: None,
412 }
413 }
414}
415
416impl CurrentPaymentHopData {
417 pub fn trampoline_onion(&self) -> Option<Vec<u8>> {
419 self.custom_records
420 .as_ref()
421 .and_then(TrampolineOnionData::read)
422 }
423
424 pub fn set_trampoline_onion(&mut self, data: Vec<u8>) {
426 let cr = self.custom_records.get_or_insert_with(Default::default);
427 TrampolineOnionData::write(data, cr);
428 }
429}
430
431use crate::U128Hex;
432
433#[serde_as]
435#[derive(Clone, Debug, Serialize, Deserialize)]
436pub struct SessionRouteNode {
437 pub pubkey: Pubkey,
439 #[serde_as(as = "U128Hex")]
441 pub amount: u128,
442 #[serde_as(as = "EntityHex")]
444 pub channel_outpoint: OutPoint,
445}
446
447use crate::U64Hex;
448
449#[serde_as]
453#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
454pub struct RouterHop {
455 pub target: Pubkey,
457 #[serde_as(as = "EntityHex")]
459 pub channel_outpoint: OutPoint,
460 #[serde_as(as = "U128Hex")]
463 pub amount_received: u128,
464 #[serde_as(as = "U64Hex")]
468 pub incoming_tlc_expiry: u64,
469}
470
471#[serde_as]
474#[derive(Clone, Debug, Serialize, Deserialize)]
475pub struct HopHint {
476 pub pubkey: Pubkey,
478 #[serde_as(as = "EntityHex")]
480 pub channel_outpoint: OutPoint,
481 pub fee_rate: u64,
483 pub tlc_expiry_delta: u64,
485}
486
487#[derive(Clone, Debug, Serialize, Deserialize, Default)]
494pub struct SessionRoute {
495 pub nodes: Vec<SessionRouteNode>,
497}
498
499impl SessionRoute {
500 pub fn new(source: Pubkey, target: Pubkey, payment_hops: &[PaymentHopData]) -> Self {
506 let nodes = std::iter::once(source)
507 .chain(
508 payment_hops
509 .iter()
510 .map(|hop| hop.next_hop.unwrap_or(target)),
511 )
512 .zip(payment_hops)
513 .map(|(pubkey, hop)| SessionRouteNode {
514 pubkey,
515 channel_outpoint: OutPoint::new(
516 if hop.funding_tx_hash != Hash256::default() {
517 hop.funding_tx_hash.into()
518 } else {
519 Hash256::default().into()
520 },
521 0,
522 ),
523 amount: hop.amount,
524 })
525 .collect();
526 Self { nodes }
527 }
528
529 pub fn receiver_amount(&self) -> u128 {
530 self.nodes.last().map_or(0, |s| s.amount)
531 }
532
533 pub fn fee(&self) -> u128 {
534 let first_amount = self.nodes.first().map_or(0, |s| s.amount);
535 let last_amount = self.receiver_amount();
536 debug_assert!(first_amount >= last_amount);
537 first_amount - last_amount
538 }
539
540 pub fn channel_outpoints(&self) -> impl Iterator<Item = (Pubkey, &OutPoint, u128)> {
541 self.nodes
542 .iter()
543 .map(|x| (x.pubkey, &x.channel_outpoint, x.amount))
544 }
545}
546
547#[serde_as]
549#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
550pub struct PaymentHopData {
551 pub amount: u128,
553 pub expiry: u64,
554 pub payment_preimage: Option<Hash256>,
555 pub hash_algorithm: HashAlgorithm,
556 pub funding_tx_hash: Hash256,
557 pub next_hop: Option<Pubkey>,
558 pub custom_records: Option<PaymentCustomRecords>,
559}
560
561use crate::onion::TrampolineOnionData;
563
564impl PaymentHopData {
565 pub fn next_hop(&self) -> Option<Pubkey> {
566 self.next_hop
567 }
568
569 pub fn trampoline_onion(&self) -> Option<Vec<u8>> {
571 self.custom_records
572 .as_ref()
573 .and_then(TrampolineOnionData::read)
574 }
575
576 pub fn set_trampoline_onion(&mut self, data: Vec<u8>) {
578 let cr = self.custom_records.get_or_insert_with(Default::default);
579 TrampolineOnionData::write(data, cr);
580 }
581
582 pub fn serialize(&self) -> Vec<u8> {
583 molecule_fiber::PaymentHopData::from(self.clone())
584 .as_bytes()
585 .to_vec()
586 }
587
588 pub fn deserialize(data: &[u8]) -> Option<Self> {
589 molecule_fiber::PaymentHopData::from_slice(data)
590 .ok()
591 .map(|x| x.into())
592 }
593}
594
595impl From<PaymentHopData> for molecule_fiber::PaymentHopData {
596 fn from(payment_hop_data: PaymentHopData) -> Self {
597 molecule_fiber::PaymentHopData::new_builder()
598 .amount(payment_hop_data.amount.pack())
599 .expiry(payment_hop_data.expiry.pack())
600 .payment_preimage(
601 molecule_fiber::PaymentPreimageOpt::new_builder()
602 .set(payment_hop_data.payment_preimage.map(|x| x.into()))
603 .build(),
604 )
605 .hash_algorithm(Byte::new(payment_hop_data.hash_algorithm as u8))
606 .funding_tx_hash(payment_hop_data.funding_tx_hash.into())
607 .next_hop(
608 molecule_fiber::PubkeyOpt::new_builder()
609 .set(payment_hop_data.next_hop.map(|x| x.into()))
610 .build(),
611 )
612 .custom_records(
613 molecule_fiber::CustomRecordsOpt::new_builder()
614 .set(payment_hop_data.custom_records.map(|x| x.into()))
615 .build(),
616 )
617 .build()
618 }
619}
620
621impl From<molecule_fiber::PaymentHopData> for PaymentHopData {
622 fn from(payment_hop_data: molecule_fiber::PaymentHopData) -> Self {
623 let custom_records: Option<PaymentCustomRecords> =
624 payment_hop_data.custom_records().to_opt().map(|x| x.into());
625
626 PaymentHopData {
627 amount: payment_hop_data.amount().unpack(),
628 expiry: payment_hop_data.expiry().unpack(),
629 payment_preimage: payment_hop_data
630 .payment_preimage()
631 .to_opt()
632 .map(|x| x.into()),
633 hash_algorithm: payment_hop_data
634 .hash_algorithm()
635 .try_into()
636 .unwrap_or_default(),
637 funding_tx_hash: payment_hop_data.funding_tx_hash().into(),
638 next_hop: payment_hop_data
639 .next_hop()
640 .to_opt()
641 .map(|x| x.try_into())
642 .and_then(Result::ok),
643 custom_records,
644 }
645 }
646}
647
648#[derive(Clone, Serialize, Deserialize)]
650pub struct Attempt {
651 pub id: u64,
652 pub try_limit: u32,
653 pub tried_times: u32,
654 pub hash: Hash256,
655 pub status: AttemptStatus,
656 pub payment_hash: Hash256,
657 pub route: SessionRoute,
658 pub route_hops: Vec<PaymentHopData>,
659 pub session_key: [u8; 32],
660 pub preimage: Option<Hash256>,
661 pub created_at: u64,
662 pub last_updated_at: u64,
663 pub last_error: Option<String>,
664}
665
666impl std::fmt::Debug for Attempt {
667 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
668 f.debug_struct("Attempt")
669 .field("id", &self.id)
670 .field("try_limit", &self.try_limit)
671 .field("tried_times", &self.tried_times)
672 .field("hash", &self.hash)
673 .field("status", &self.status)
674 .field("payment_hash", &self.payment_hash)
675 .field("route", &self.route)
676 .field("route_hops", &self.route_hops)
677 .field("session_key", &"[REDACTED]")
678 .field("preimage", &self.preimage.as_ref().map(|_| "[REDACTED]"))
679 .field("created_at", &self.created_at)
680 .field("last_updated_at", &self.last_updated_at)
681 .field("last_error", &self.last_error)
682 .finish()
683 }
684}
685
686impl Attempt {
687 pub fn set_inflight_status(&mut self) {
688 self.status = AttemptStatus::Inflight;
689 self.last_error = None;
690 }
691
692 pub fn set_success_status(&mut self) {
693 self.status = AttemptStatus::Success;
694 self.last_error = None;
695 }
696
697 pub fn set_failed_status(&mut self, error: &str, retryable: bool) {
698 self.last_error = Some(error.to_string());
699 self.last_updated_at = crate::now_timestamp_as_millis_u64();
700 if !retryable || self.tried_times > self.try_limit {
701 self.status = AttemptStatus::Failed;
702 } else {
703 self.status = AttemptStatus::Retrying;
704 self.tried_times += 1;
706 }
707 }
708
709 pub fn update_route(&mut self, new_route_hops: Vec<PaymentHopData>) {
710 self.route_hops = new_route_hops;
711 let sender = self.route.nodes[0].pubkey;
712 let receiver = self.route.nodes.last().unwrap().pubkey;
713 self.route = SessionRoute::new(sender, receiver, &self.route_hops);
714 }
715
716 pub fn is_success(&self) -> bool {
717 self.status == AttemptStatus::Success
718 }
719
720 pub fn is_inflight(&self) -> bool {
721 self.status == AttemptStatus::Inflight
722 }
723
724 pub fn is_failed(&self) -> bool {
725 self.status == AttemptStatus::Failed
726 }
727
728 pub fn is_active(&self) -> bool {
729 self.status != AttemptStatus::Failed
730 }
731
732 pub fn is_retrying(&self) -> bool {
733 self.status == AttemptStatus::Retrying
734 }
735
736 pub fn first_hop_channel_outpoint_eq(&self, out_point: &OutPoint) -> bool {
737 self.first_hop_channel_outpoint()
738 .map(|x| x.eq(out_point))
739 .unwrap_or_default()
740 }
741
742 pub fn first_hop_channel_outpoint(&self) -> Option<&OutPoint> {
743 self.route.nodes.first().map(|x| &x.channel_outpoint)
744 }
745
746 pub fn channel_outpoints(&self) -> impl Iterator<Item = (Pubkey, &OutPoint, u128)> {
747 self.route.channel_outpoints()
748 }
749
750 pub fn hops_public_keys(&self) -> Vec<Pubkey> {
751 self.route.nodes.iter().skip(1).map(|x| x.pubkey).collect()
753 }
754}
755
756use crate::channel::PrevTlcInfo;
757
758#[derive(Clone, Debug, Serialize, Deserialize, Default)]
760pub struct TrampolineContext {
761 pub remaining_trampoline_onion: Vec<u8>,
766 pub previous_tlcs: Vec<PrevTlcInfo>,
769 pub hash_algorithm: HashAlgorithm,
771 #[serde(default)]
777 pub max_outgoing_tlc_expiry: Option<u64>,
778}
779
780const BADONION: u16 = 0x8000;
782const PERM: u16 = 0x4000;
784const NODE: u16 = 0x2000;
786const UPDATE: u16 = 0x1000;
788
789#[repr(u16)]
791#[derive(
792 Debug,
793 Copy,
794 Clone,
795 Serialize,
796 Deserialize,
797 PartialEq,
798 Eq,
799 AsRefStr,
800 EnumString,
801 TryFromPrimitive,
802 IntoPrimitive,
803)]
804pub enum TlcErrorCode {
805 TemporaryNodeFailure = NODE | 2,
806 PermanentNodeFailure = PERM | NODE | 2,
807 RequiredNodeFeatureMissing = PERM | NODE | 3,
808 InvalidOnionVersion = BADONION | PERM | 4,
809 InvalidOnionHmac = BADONION | PERM | 5,
810 InvalidOnionKey = BADONION | PERM | 6,
811 TemporaryChannelFailure = UPDATE | 7,
812 PermanentChannelFailure = PERM | 8,
813 RequiredChannelFeatureMissing = PERM | 9,
814 UnknownNextPeer = PERM | 10,
815 AmountBelowMinimum = UPDATE | 11,
816 FeeInsufficient = UPDATE | 12,
817 IncorrectTlcExpiry = UPDATE | 13,
818 ExpiryTooSoon = PERM | 14,
819 IncorrectOrUnknownPaymentDetails = PERM | 15,
820 InvoiceExpired = PERM | 16,
821 InvoiceCancelled = PERM | 17,
822 FinalIncorrectExpiryDelta = 18,
823 FinalIncorrectTlcAmount = 19,
824 ChannelDisabled = UPDATE | 20,
825 ExpiryTooFar = PERM | 21,
826 InvalidOnionPayload = PERM | 22,
827 HoldTlcTimeout = PERM | 23,
828 InvalidOnionError = BADONION | PERM | 25,
829 IncorrectTlcDirection = PERM | 26,
830}
831
832impl TlcErrorCode {
833 pub fn is_node(&self) -> bool {
834 *self as u16 & NODE != 0
835 }
836
837 pub fn is_bad_onion(&self) -> bool {
838 *self as u16 & BADONION != 0
839 }
840
841 pub fn is_perm(&self) -> bool {
842 *self as u16 & PERM != 0
843 }
844
845 pub fn is_update(&self) -> bool {
846 *self as u16 & UPDATE != 0
847 }
848}
849
850use ckb_types::packed::Script;
851
852#[serde_as]
854#[derive(Clone, Debug, Serialize, Deserialize)]
855pub struct SendPaymentData {
856 pub target_pubkey: Pubkey,
857 pub amount: u128,
858 pub payment_hash: Hash256,
859 pub invoice: Option<String>,
860 pub final_tlc_expiry_delta: u64,
861 pub tlc_expiry_limit: u64,
862 pub timeout: Option<u64>,
863 pub max_fee_amount: Option<u128>,
864 pub max_parts: Option<u64>,
866 pub keysend: bool,
867 #[serde_as(as = "Option<EntityHex>")]
868 pub udt_type_script: Option<Script>,
869 pub preimage: Option<Hash256>,
870 pub custom_records: Option<PaymentCustomRecords>,
871 pub allow_self_payment: bool,
872 pub hop_hints: Vec<HopHint>,
873 pub router: Vec<RouterHop>,
874 pub allow_mpp: bool,
876 pub dry_run: bool,
877 pub trampoline_hops: Option<Vec<Pubkey>>,
882 #[serde(default)]
883 pub trampoline_context: Option<TrampolineContext>,
884}
885
886#[derive(Clone, Debug, Serialize, Deserialize)]
888pub struct PaymentSession {
889 pub request: SendPaymentData,
890 pub last_error: Option<String>,
891 #[serde(default)]
892 pub last_error_code: Option<TlcErrorCode>,
893 pub try_limit: u32,
896 pub status: PaymentStatus,
897 pub created_at: u64,
898 pub last_updated_at: u64,
899 #[serde(skip)]
901 pub cached_attempts: Vec<Attempt>,
902}
903
904pub const DEFAULT_MAX_PARTS: u64 = 12;
906
907pub const DEFAULT_PAYMENT_MPP_ATTEMPT_TRY_LIMIT: u32 = 3;
909
910impl SendPaymentData {
911 pub fn max_parts(&self) -> usize {
913 self.max_parts.unwrap_or(DEFAULT_MAX_PARTS) as usize
914 }
915
916 pub fn allow_mpp(&self) -> bool {
918 self.allow_mpp && self.max_parts() > 1 && !self.keysend
920 }
921
922 pub fn use_trampoline_routing(&self) -> bool {
924 self.trampoline_hops
925 .as_ref()
926 .is_some_and(|hops| !hops.is_empty())
927 }
928}
929
930impl PaymentSession {
931 pub fn update_with_attempt(&mut self, attempt: Attempt) {
932 if let Some(a) = self.cached_attempts.iter_mut().find(|a| a.id == attempt.id) {
933 *a = attempt;
934 }
935 self.status = self.calc_payment_status();
936 }
937
938 pub fn retry_times(&self) -> u32 {
939 self.attempts().map(|a| a.tried_times).sum()
940 }
941
942 pub fn allow_mpp(&self) -> bool {
943 self.request.allow_mpp()
944 }
945
946 pub fn payment_hash(&self) -> crate::Hash256 {
947 self.request.payment_hash
948 }
949
950 pub fn is_payment_with_router(&self) -> bool {
951 !self.request.router.is_empty()
952 }
953
954 pub fn is_dry_run(&self) -> bool {
955 self.request.dry_run
956 }
957
958 pub fn attempts(&self) -> std::slice::Iter<'_, Attempt> {
959 self.cached_attempts.iter()
960 }
961
962 #[cfg(test)]
963 pub fn all_attempts_with_status(&self) -> Vec<(u64, AttemptStatus, Option<String>, u32, u128)> {
964 self.cached_attempts
965 .iter()
966 .map(|a| {
967 (
968 a.id,
969 a.status,
970 a.last_error.clone(),
971 a.tried_times,
972 a.route.receiver_amount(),
973 )
974 })
975 .collect()
976 }
977
978 pub fn attempts_count(&self) -> usize {
979 self.cached_attempts.len()
980 }
981
982 pub fn max_parts(&self) -> usize {
983 if self.allow_mpp() && !self.request.use_trampoline_routing() {
984 self.request.max_parts()
985 } else {
986 1
987 }
988 }
989
990 pub fn active_attempts(&self) -> Vec<&Attempt> {
991 self.attempts().filter(|a| a.is_active()).collect()
992 }
993
994 pub fn fee_paid(&self) -> u128 {
995 if self.request.use_trampoline_routing() {
996 let total_sent: u128 = self
997 .active_attempts()
998 .iter()
999 .map(|a| a.route.nodes.first().map_or(0, |n| n.amount))
1000 .sum();
1001 let total_received = self.request.amount;
1002 total_sent.saturating_sub(total_received)
1003 } else {
1004 self.active_attempts().iter().map(|a| a.route.fee()).sum()
1005 }
1006 }
1007
1008 pub fn remain_fee_amount(&self) -> Option<u128> {
1009 let max_fee_amount = self.request.max_fee_amount?;
1010 let remain_fee = max_fee_amount.saturating_sub(self.fee_paid());
1011 Some(remain_fee)
1012 }
1013
1014 pub fn remain_amount(&self) -> u128 {
1015 let sent_amount = self
1016 .active_attempts()
1017 .iter()
1018 .map(|a| a.route.receiver_amount())
1019 .sum::<u128>();
1020 self.request.amount.saturating_sub(sent_amount)
1021 }
1022
1023 pub fn new_attempt(
1024 &self,
1025 attempt_id: u64,
1026 source: Pubkey,
1027 target: Pubkey,
1028 route_hops: Vec<PaymentHopData>,
1029 ) -> Attempt {
1030 let now = crate::now_timestamp_as_millis_u64();
1031 let payment_hash = self.payment_hash();
1032 let hash = payment_hash;
1033 let try_limit = if self.allow_mpp() {
1034 DEFAULT_PAYMENT_MPP_ATTEMPT_TRY_LIMIT
1035 } else {
1036 self.try_limit
1037 };
1038
1039 let route = SessionRoute::new(source, target, &route_hops);
1040
1041 Attempt {
1042 id: attempt_id,
1043 hash,
1044 try_limit,
1045 tried_times: 1,
1046 payment_hash,
1047 route,
1048 route_hops,
1049 session_key: [0; 32],
1050 preimage: None,
1051 created_at: now,
1052 last_updated_at: now,
1053 last_error: None,
1054 status: AttemptStatus::Created,
1055 }
1056 }
1057
1058 pub fn append_attempt(&mut self, attempt: Attempt) {
1059 self.cached_attempts.push(attempt);
1060 }
1061
1062 pub fn allow_more_attempts(&self) -> bool {
1063 if self.status.is_final() {
1064 return false;
1065 }
1066
1067 if self.remain_amount() == 0 {
1068 return false;
1069 }
1070
1071 if self.retry_times() >= self.try_limit {
1072 return false;
1073 }
1074
1075 if self.active_attempts().len() >= self.max_parts() {
1076 return false;
1077 }
1078
1079 true
1080 }
1081
1082 pub fn calc_payment_status(&self) -> PaymentStatus {
1083 if self.cached_attempts.is_empty() || self.status.is_final() {
1084 return self.status;
1085 }
1086
1087 if self.attempts().any(|a| a.is_inflight()) {
1088 return PaymentStatus::Inflight;
1089 }
1090
1091 if self.attempts().all(|a| a.is_failed()) && !self.allow_more_attempts() {
1092 return PaymentStatus::Failed;
1093 }
1094
1095 if self.success_attempts_amount_is_enough() {
1096 return PaymentStatus::Success;
1097 }
1098
1099 PaymentStatus::Created
1100 }
1101
1102 pub fn set_inflight_status(&mut self) {
1103 self.status = PaymentStatus::Inflight;
1104 self.last_updated_at = crate::now_timestamp_as_millis_u64();
1105 }
1106
1107 pub fn set_success_status(&mut self) {
1108 self.status = PaymentStatus::Success;
1109 self.last_updated_at = crate::now_timestamp_as_millis_u64();
1110 self.last_error = None;
1111 self.last_error_code = None;
1112 }
1113
1114 pub fn set_failed_status(&mut self, error: &str) {
1115 self.status = PaymentStatus::Failed;
1116 self.last_updated_at = crate::now_timestamp_as_millis_u64();
1117 self.last_error = Some(error.to_string());
1118 }
1119
1120 fn success_attempts_amount_is_enough(&self) -> bool {
1121 let success_amount: u128 = self
1122 .cached_attempts
1123 .iter()
1124 .filter_map(|a| {
1125 if a.is_success() {
1126 Some(a.route.receiver_amount())
1127 } else {
1128 None
1129 }
1130 })
1131 .sum();
1132 success_amount >= self.request.amount
1133 }
1134}
1135
1136#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
1139pub struct TimedResult {
1140 pub fail_time: u64,
1141 pub fail_amount: u128,
1142 pub success_time: u64,
1143 pub success_amount: u128,
1144}
1145
1146#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1150pub enum Direction {
1151 Forward,
1152 Backward,
1153}