Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 53 additions & 34 deletions crates/fiber-lib/src/fiber/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,15 +913,15 @@ where
// Not a hold invoice.
if is_mpp {
// Use the default expiry for mpp
now_timestamp_as_millis_u64() + DEFAULT_HOLD_TLC_TIMEOUT
Some(now_timestamp_as_millis_u64() + DEFAULT_HOLD_TLC_TIMEOUT)
} else {
// Use 0 to indicate to not create HoldTLC for single path payment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment should be updated also since we changed 0 to None.

// with preimage.
0
None
}
} else {
// A hold invoice. Ensure the expiry is large enough for manual settlement via RPC.
match invoice.expiry_time() {
Some(match invoice.expiry_time() {
Some(invoice_expiry) => u64::try_from(
invoice_expiry
.as_millis()
Expand All @@ -930,14 +930,13 @@ where
)
.unwrap_or(u64::MAX),
None => tlc.expiry,
}
})
};
state
.pending_notify_settle_tlcs
.push(PendingNotifySettleTlc {
payment_hash: tlc.payment_hash,
tlc_id: tlc.id(),
is_mpp,
hold_expire_at,
});

Expand Down Expand Up @@ -2726,46 +2725,52 @@ where

self.store.insert_channel_actor_state(state.clone());

let channel_id = state.get_id();
let mut immediate_tlc_sets = HashMap::<Hash256, Vec<(Hash256, u64)>>::new();
let mut hold_tlc_sets = HashSet::new();
// try to settle down tlc set
for PendingNotifySettleTlc {
payment_hash,
tlc_id,
is_mpp,
hold_expire_at,
} in pending_notify_tlcs
{
let channel_id = state.get_id();
for pending_notify_tlc in pending_notify_tlcs {
// Hold the tlc
let expiry_duration =
Duration::from_millis(hold_expire_at.saturating_sub(now_timestamp_as_millis_u64()));
if !expiry_duration.is_zero() {
if pending_notify_tlc.should_hold() {
let expiry_duration =
pending_notify_tlc.hold_expiry_duration(now_timestamp_as_millis_u64());
self.store.insert_payment_hold_tlc(
payment_hash,
pending_notify_tlc.payment_hash,
HoldTlc {
channel_id,
tlc_id,
hold_expire_at,
tlc_id: pending_notify_tlc.tlc_id,
hold_expire_at: pending_notify_tlc.hold_expire_at.unwrap_or_default(),
},
);
// set timeout for hold tlc
self.network.send_after(expiry_duration, move || {
let timeout_command = move || {
NetworkActorMessage::new_command(NetworkActorCommand::TimeoutHoldTlc(
payment_hash,
pending_notify_tlc.payment_hash,
channel_id,
tlc_id,
pending_notify_tlc.tlc_id,
))
});
};
// set timeout for hold tlc
self.network.send_after(expiry_duration, timeout_command);
hold_tlc_sets.insert(pending_notify_tlc.payment_hash);
} else {
immediate_tlc_sets
.entry(pending_notify_tlc.payment_hash)
.or_default()
.push((channel_id, pending_notify_tlc.tlc_id));
}
}

for (payment_hash, tlc_ids) in immediate_tlc_sets {
self.network
.send_message(NetworkActorMessage::new_command(
NetworkActorCommand::SettleTlcSet(
payment_hash,
if !is_mpp {
Some((state.get_id(), tlc_id))
} else {
None
},
),
NetworkActorCommand::SettleTlcSet(payment_hash, tlc_ids),
))
.expect(ASSUME_NETWORK_ACTOR_ALIVE);
}
for payment_hash in hold_tlc_sets {
self.network
.send_message(NetworkActorMessage::new_command(
NetworkActorCommand::SettleHoldTlcSet(payment_hash),
))
.expect(ASSUME_NETWORK_ACTOR_ALIVE);
}
Expand Down Expand Up @@ -3563,8 +3568,22 @@ type ScheduledChannelUpdateHandle =
pub struct PendingNotifySettleTlc {
pub payment_hash: Hash256,
pub tlc_id: u64,
pub is_mpp: bool,
pub hold_expire_at: u64,
/// The expire time if the TLC should be held.
pub hold_expire_at: Option<u64>,
}

impl PendingNotifySettleTlc {
fn should_hold(&self) -> bool {
self.hold_expire_at.is_some()
}

fn hold_expiry_duration(&self, now_millis_since_unix_epoch: u64) -> Duration {
Duration::from_millis(
self.hold_expire_at
.unwrap_or_default()
.saturating_sub(now_millis_since_unix_epoch),
)
}
}

#[serde_as]
Expand Down
2 changes: 2 additions & 0 deletions crates/fiber-lib/src/fiber/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod fee;
mod in_flight_ckb_tx_actor;
mod key;
mod path;
mod settle_tlc_set_command;

pub use config::FiberConfig;
pub use in_flight_ckb_tx_actor::{
Expand All @@ -28,6 +29,7 @@ pub use network::{
NetworkServiceEvent,
};
pub use payment::PaymentCustomRecords;
pub use settle_tlc_set_command::SettleTlcSetCommand;

pub(crate) const ASSUME_NETWORK_ACTOR_ALIVE: &str = "network actor must be alive";

Expand Down
175 changes: 61 additions & 114 deletions crates/fiber-lib/src/fiber/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ use crate::fiber::serde_utils::EntityHex;
use crate::fiber::types::{
FiberChannelMessage, PeeledPaymentOnionPacket, TlcErrPacket, TxAbort, TxSignatures,
};
use crate::fiber::SettleTlcSetCommand;
use crate::invoice::{
CkbInvoice, CkbInvoiceStatus, InvoiceError, InvoiceStore, PreimageStore, SettleInvoiceError,
};
Expand Down Expand Up @@ -282,8 +283,10 @@ pub enum NetworkActorCommand {
CheckChannels,
// Timeout a hold tlc
TimeoutHoldTlc(Hash256, Hash256, u64),
// Settle tlc set, including MPP and normal tlc set
SettleTlcSet(Hash256, Option<(Hash256, u64)>),
// Settle tlc set by given a list of `(channel_id, tlc_id)`
SettleTlcSet(Hash256, Vec<(Hash256, u64)>),
// Settle hold tlc set saved for a payment hash
SettleHoldTlcSet(Hash256),
// Check peer send us Init message in an expected time, otherwise disconnect with the peer.
CheckPeerInit(PeerId, SessionId),
// For internal use and debugging only. Most of the messages requires some
Expand Down Expand Up @@ -1525,113 +1528,19 @@ where
}
}
}
NetworkActorCommand::SettleTlcSet(payment_hash, tlc_info) => {
let tlc_ids = if let Some((channel_id, tlc_id)) = tlc_info {
vec![(channel_id, tlc_id)]
} else {
self.store
.get_payment_hold_tlcs(payment_hash)
.iter()
.map(|hold_tlc| (hold_tlc.channel_id, hold_tlc.tlc_id))
.collect()
};
let tlcs: Vec<_> = tlc_ids
.into_iter()
.filter_map(|(channel_id, tlc_id)| {
let state = self.store.get_channel_actor_state(&channel_id)?;
let tlc_id = TLCId::Received(tlc_id);
state
.get_received_tlc(tlc_id)
.map(|tlc| (channel_id, tlc.clone()))
})
NetworkActorCommand::SettleHoldTlcSet(payment_hash) => {
let channel_tlc_ids = self
.store
.get_payment_hold_tlcs(payment_hash)
.iter()
.map(|hold_tlc| (hold_tlc.channel_id, hold_tlc.tlc_id))
.collect();

let not_mpp = tlc_info.is_some();
let mut tlc_fail = None;

// check if all tlcs have the same total amount
if tlcs.len() > 1
&& !tlcs
.windows(2)
.all(|w| w[0].1.total_amount == w[1].1.total_amount)
{
error!("TLCs have inconsistent total_amount: {:?}", tlcs);
tlc_fail = Some(TlcErr::new(TlcErrorCode::IncorrectOrUnknownPaymentDetails));
}
let Some(invoice) = self.store.get_invoice(&payment_hash) else {
error!(
"Try to settle mpp tlc set, but invoice not found for payment hash {:?}",
payment_hash
);
return Ok(());
};

let fulfilled = is_invoice_fulfilled(&invoice, tlcs.iter().map(|(_, tlc)| tlc));
if not_mpp {
if self.store.get_invoice_status(&payment_hash) != Some(CkbInvoiceStatus::Open)
|| !fulfilled
{
tlc_fail =
Some(TlcErr::new(TlcErrorCode::IncorrectOrUnknownPaymentDetails));
}
} else if !fulfilled {
return Ok(());
}

// if we have enough tlcs to fulfill the invoice, update invoice status to Received
// for hold invoice we may don't have preimages yet, so just update status here
self.store
.update_invoice_status(&payment_hash, CkbInvoiceStatus::Received)
.expect("update invoice status failed");

let Some(preimage) = self.store.get_preimage(&payment_hash) else {
return Ok(());
};

// remove tlcs
for (channel_id, tlc) in tlcs {
let (send, _recv) = oneshot::channel();
let rpc_reply = RpcReplyPort::from(send);
let remove_reason = match tlc_fail.clone() {
Some(tlc_fail) => RemoveTlcReason::RemoveTlcFail(TlcErrPacket::new(
tlc_fail,
&tlc.shared_secret,
)),
None => RemoveTlcReason::RemoveTlcFulfill(RemoveTlcFulfill {
payment_preimage: preimage,
}),
};

match state
.send_command_to_channel(
channel_id,
ChannelCommand::RemoveTlc(
RemoveTlcCommand {
id: tlc.id(),
reason: remove_reason,
},
rpc_reply,
),
)
.await
{
Ok(_) => {
self.store.remove_payment_hold_tlc(
&payment_hash,
&channel_id,
tlc.id(),
);
}
Err(err) => {
error!(
"Failed to remove tlc {:?} for channel {:?}: {}",
tlc.id(),
channel_id,
err
);
}
}
}
self.settle_tlc_set(state, payment_hash, channel_tlc_ids, true)
.await;
}
NetworkActorCommand::SettleTlcSet(payment_hash, channel_tlc_ids) => {
self.settle_tlc_set(state, payment_hash, channel_tlc_ids, false)
.await;
}
NetworkActorCommand::TimeoutHoldTlc(payment_hash, channel_id, tlc_id) => {
debug!(
Expand Down Expand Up @@ -2116,6 +2025,48 @@ where
Ok(())
}

async fn settle_tlc_set(
&self,
state: &mut NetworkActorState<S, C>,
payment_hash: Hash256,
channel_tlc_ids: Vec<(Hash256, u64)>,
is_hold_tlc_set: bool,
) {
let settle_command = SettleTlcSetCommand::new(payment_hash, channel_tlc_ids, &self.store);
for tlc_settlement in settle_command.run() {
let (send, _recv) = oneshot::channel();
let rpc_reply = RpcReplyPort::from(send);
match state
.send_command_to_channel(
tlc_settlement.channel_id(),
ChannelCommand::RemoveTlc(
tlc_settlement.remove_tlc_command().clone(),
rpc_reply,
),
)
.await
{
Ok(_) => {
if is_hold_tlc_set {
self.store.remove_payment_hold_tlc(
&payment_hash,
&tlc_settlement.channel_id(),
tlc_settlement.tlc_id(),
);
}
}
Err(err) => {
error!(
"Failed to remove tlc {:?} for channel {:?}: {}",
tlc_settlement.tlc_id(),
tlc_settlement.channel_id(),
err
);
}
}
}
}

/// Async version of check_channel_shutdown that runs in spawned task.
/// Checks if the channel funding cell has been spent (indicating remote force close).
async fn check_channel_shutdown(
Expand Down Expand Up @@ -2329,13 +2280,9 @@ where
}

self.store.insert_preimage(payment_hash, payment_preimage);
let _ = myself.send_message(NetworkActorMessage::new_notification(
NetworkServiceEvent::PreimageCreated(payment_hash, payment_preimage),
));

// We will send network actor a message to settle the invoice immediately if possible.
let _ = myself.send_message(NetworkActorMessage::new_command(
NetworkActorCommand::SettleTlcSet(payment_hash, None),
NetworkActorCommand::SettleHoldTlcSet(payment_hash),
));

Ok(())
Expand Down Expand Up @@ -4225,7 +4172,7 @@ where
if !already_timeout {
myself
.send_message(NetworkActorMessage::new_command(
NetworkActorCommand::SettleTlcSet(payment_hash, None),
NetworkActorCommand::SettleHoldTlcSet(payment_hash),
))
.expect(ASSUME_NETWORK_MYSELF_ALIVE);
}
Expand Down
Loading
Loading