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