Skip to content

Commit

Permalink
Merge pull request #317 from chenyukang/yukang-add-expiry-check
Browse files Browse the repository at this point in the history
Add expiry check for rpc and HTLC forwarding
  • Loading branch information
quake authored Nov 25, 2024
2 parents 4ea7577 + 40980df commit 89c2483
Show file tree
Hide file tree
Showing 45 changed files with 815 additions and 233 deletions.
5 changes: 3 additions & 2 deletions src/cch/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::fiber::hash_algorithm::HashAlgorithm;
use crate::fiber::types::{Hash256, RemoveTlcFulfill, RemoveTlcReason};
use crate::fiber::{NetworkActorCommand, NetworkActorMessage};
use crate::invoice::Currency;
use crate::now_timestamp;
use crate::now_timestamp_as_millis_u64;

use super::error::CchDbError;
use super::{CchConfig, CchError, CchOrderStatus, CchOrdersDb, ReceiveBTCOrder, SendBTCOrder};
Expand Down Expand Up @@ -592,7 +592,8 @@ impl CchActor {
payment_hash: Some(
Hash256::from_str(&order.payment_hash).expect("parse Hash256"),
),
expiry: now_timestamp() + self.config.ckb_final_tlc_expiry_delta,
expiry: now_timestamp_as_millis_u64()
+ self.config.ckb_final_tlc_expiry_delta,
hash_algorithm: HashAlgorithm::Sha256,
onion_packet: vec![],
previous_tlc: None,
Expand Down
39 changes: 37 additions & 2 deletions src/fiber/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
types::{ChannelUpdate, TlcErr, TlcErrPacket, TlcErrorCode},
},
invoice::{CkbInvoice, CkbInvoiceStatus, InvoiceStore},
now_timestamp_as_millis_u64,
};
use ckb_hash::{blake2b_256, new_blake2b};
use ckb_sdk::{Since, SinceType};
Expand Down Expand Up @@ -64,6 +65,7 @@ use crate::{

use super::{
config::DEFAULT_MIN_SHUTDOWN_FEE,
config::{MAX_PAYMENT_TLC_EXPIRY_LIMIT, MIN_TLC_EXPIRY_DELTA},
fee::calculate_shutdown_tx_fee,
hash_algorithm::HashAlgorithm,
key::blake2b_hash_with_salt,
Expand Down Expand Up @@ -649,6 +651,8 @@ where
let error_code = match error {
ProcessingChannelError::PeelingOnionPacketError(_) => TlcErrorCode::InvalidOnionPayload,
ProcessingChannelError::TlcForwardFeeIsTooLow => TlcErrorCode::FeeInsufficient,
ProcessingChannelError::TlcExpirySoon => TlcErrorCode::ExpiryTooSoon,
ProcessingChannelError::TlcExpiryTooFar => TlcErrorCode::ExpiryTooFar,
ProcessingChannelError::FinalInvoiceInvalid(status) => match status {
CkbInvoiceStatus::Expired => TlcErrorCode::InvoiceExpired,
CkbInvoiceStatus::Cancelled => TlcErrorCode::InvoiceCancelled,
Expand All @@ -659,7 +663,7 @@ where
TlcErrorCode::IncorrectOrUnknownPaymentDetails
}
ProcessingChannelError::FinalIncorrectHTLCAmount => {
TlcErrorCode::FinalIncorrectHtlcAmount
TlcErrorCode::FinalIncorrectTlcAmount
}
ProcessingChannelError::TlcAmountIsTooLow => TlcErrorCode::AmountBelowMinimum,
ProcessingChannelError::TlcNumberExceedLimit
Expand Down Expand Up @@ -810,6 +814,7 @@ where
add_tlc: AddTlc,
) -> Result<(), ProcessingChannelError> {
state.check_for_tlc_update(Some(add_tlc.amount))?;
state.check_tlc_expiry(add_tlc.expiry)?;

// check the onion_packet is valid or not, if not, we should return an error.
// If there is a next hop, we should send the AddTlc message to the next hop.
Expand Down Expand Up @@ -1093,6 +1098,7 @@ where
) -> Result<u64, ProcessingChannelError> {
debug!("handle add tlc command : {:?}", &command);
state.check_for_tlc_update(Some(command.amount))?;
state.check_tlc_expiry(command.expiry)?;
let tlc = state.create_outbounding_tlc(command);
state.insert_tlc(tlc.clone())?;

Expand Down Expand Up @@ -1239,6 +1245,12 @@ where
}

if let Some(delta) = tlc_expiry_delta {
if delta < MIN_TLC_EXPIRY_DELTA {
return Err(ProcessingChannelError::InvalidParameter(format!(
"TLC expiry delta is too small, expect larger than {}",
MIN_TLC_EXPIRY_DELTA
)));
}
updated |= state.update_our_tlc_expiry_delta(delta);
}

Expand Down Expand Up @@ -2187,6 +2199,10 @@ pub enum ProcessingChannelError {
TlcValueInflightExceedLimit,
#[error("The tlc amount below minimal")]
TlcAmountIsTooLow,
#[error("The tlc expiry soon")]
TlcExpirySoon,
#[error("The tlc expiry too far")]
TlcExpiryTooFar,
}

bitflags! {
Expand Down Expand Up @@ -4084,7 +4100,26 @@ impl ChannelActorState {
return fee <= remote_available_max_fee;
}

pub fn check_for_tlc_update(&self, add_tlc_amount: Option<u128>) -> ProcessingChannelResult {
fn check_tlc_expiry(&self, expiry: u64) -> ProcessingChannelResult {
let current_time = now_timestamp_as_millis_u64();
if current_time >= expiry {
debug!(
"TLC expiry {} is already passed, current time: {}",
expiry, current_time
);
return Err(ProcessingChannelError::TlcExpirySoon);
}
if expiry >= current_time + MAX_PAYMENT_TLC_EXPIRY_LIMIT {
debug!(
"TLC expiry {} is too far in the future, current time: {}",
expiry, current_time
);
return Err(ProcessingChannelError::TlcExpiryTooFar);
}
Ok(())
}

fn check_for_tlc_update(&self, add_tlc_amount: Option<u128>) -> ProcessingChannelResult {
match self.state {
ChannelState::ChannelReady() => {}
ChannelState::ShuttingDown(_) if add_tlc_amount.is_none() => {}
Expand Down
6 changes: 6 additions & 0 deletions src/fiber/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ pub const DEFAULT_OPEN_CHANNEL_AUTO_ACCEPT_MIN_CKB_FUNDING_AMOUNT: u64 = 100 * C
/// The expiry delta to forward a tlc, in milliseconds, default to 1 day.
pub const DEFAULT_TLC_EXPIRY_DELTA: u64 = 24 * 60 * 60 * 1000;

/// The minimal expiry delta to forward a tlc, in milliseconds. 15 minutes.
pub const MIN_TLC_EXPIRY_DELTA: u64 = 15 * 60 * 1000; // 15 minutes

/// The maximum expiry delta for a payment, in milliseconds. 2 days
pub const MAX_PAYMENT_TLC_EXPIRY_LIMIT: u64 = 2 * 24 * 60 * 60 * 1000; // 2 days

/// The minimal value of a tlc. 0 means no minimal value.
pub const DEFAULT_TLC_MIN_VALUE: u128 = 0;

Expand Down
66 changes: 39 additions & 27 deletions src/fiber/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::fiber::path::NodeHeapElement;
use crate::fiber::serde_utils::EntityHex;
use crate::fiber::types::PaymentHopData;
use crate::invoice::CkbInvoice;
use crate::now_timestamp;
use crate::now_timestamp_as_millis_u64;
use ckb_jsonrpc_types::JsonBytes;
use ckb_types::packed::{OutPoint, Script};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -135,11 +135,11 @@ pub struct ChannelUpdateInfo {
/// Whether the channel can be currently used for payments (in this one direction).
pub enabled: bool,
/// The difference in htlc expiry values that you must have when routing through this channel (in milliseconds).
pub htlc_expiry_delta: u64,
pub tlc_expiry_delta: u64,
/// The minimum value, which must be relayed to the next hop via the channel
pub htlc_minimum_value: u128,
pub tlc_minimum_value: u128,
/// The maximum value which may be relayed to the next hop via the channel.
pub htlc_maximum_value: u128,
pub tlc_maximum_value: u128,
pub fee_rate: u64,
/// Most recent update for the channel received from the network
/// Mostly redundant with the data we store in fields explicitly.
Expand Down Expand Up @@ -430,9 +430,9 @@ where
.expect("Duration since unix epoch")
.as_millis() as u64,
enabled: !disabled,
htlc_expiry_delta: update.tlc_expiry_delta,
htlc_minimum_value: update.tlc_minimum_value,
htlc_maximum_value: update.tlc_maximum_value,
tlc_expiry_delta: update.tlc_expiry_delta,
tlc_minimum_value: update.tlc_minimum_value,
tlc_maximum_value: update.tlc_maximum_value,
fee_rate: update.tlc_fee_proportional_millionths as u64,
last_update_message: update.clone(),
});
Expand Down Expand Up @@ -546,6 +546,7 @@ where
let preimage = payment_data.preimage;
let payment_hash = payment_data.payment_hash;
let udt_type_script = payment_data.udt_type_script;
let final_tlc_expiry_delta = payment_data.final_tlc_expiry_delta;
let invoice = payment_data
.invoice
.map(|x| x.parse::<CkbInvoice>().expect("parse CKB invoice"));
Expand All @@ -562,7 +563,7 @@ where
let allow_self_payment = payment_data.allow_self_payment;
if source == target && !allow_self_payment {
return Err(PathFindError::PathFind(
"source and target are the same and allow_self_payment is not enable".to_string(),
"allow_self_payment is not enable, can not pay to self".to_string(),
));
}

Expand All @@ -572,13 +573,17 @@ where
amount,
payment_data.max_fee_amount,
udt_type_script,
final_tlc_expiry_delta,
payment_data.tlc_expiry_limit,
allow_self_payment,
)?;
assert!(!route.is_empty());

let mut current_amount = amount;
let mut current_expiry = 0;
let mut hops_data = vec![];
let current_time = now_timestamp_as_millis_u64();

for i in (0..route.len()).rev() {
let is_last = i == route.len() - 1;
let (next_hop, next_channel_outpoint) = if is_last {
Expand All @@ -590,7 +595,7 @@ where
)
};
let (fee, expiry) = if is_last {
(0, 0)
(0, current_time + final_tlc_expiry_delta)
} else {
let channel_info = self
.get_channel(&route[i].channel_outpoint)
Expand All @@ -600,7 +605,7 @@ where
.expect("channel_update is none");
let fee_rate = channel_update.fee_rate;
let fee = calculate_tlc_forward_fee(current_amount, fee_rate as u128);
let expiry = channel_update.htlc_expiry_delta;
let expiry = channel_update.tlc_expiry_delta;
(fee, expiry)
};

Expand All @@ -615,8 +620,8 @@ where
channel_outpoint: next_channel_outpoint,
preimage: if is_last { preimage } else { None },
});
current_amount += fee;
current_expiry += expiry;
current_amount += fee;
}
// Add the first hop as the instruction for the current node, so the logic for send HTLC can be reused.
hops_data.push(PaymentHopData {
Expand Down Expand Up @@ -652,6 +657,8 @@ where
amount: u128,
max_fee_amount: Option<u128>,
udt_type_script: Option<Script>,
fianl_tlc_expiry_delta: u64,
tlc_expiry_limit: u64,
allow_self: bool,
) -> Result<Vec<PathEdge>, PathFindError> {
let started_time = std::time::Instant::now();
Expand All @@ -678,7 +685,7 @@ where

if source == target && !allow_self {
return Err(PathFindError::PathFind(
"source and target are the same".to_string(),
"allow_self_payment is not enable, can not pay self".to_string(),
));
}

Expand All @@ -704,7 +711,7 @@ where
fee_charged: 0,
probability: 1.0,
next_hop: None,
incoming_htlc_expiry: 0,
incoming_tlc_expiry: fianl_tlc_expiry_delta,
});

while let Some(cur_hop) = nodes_heap.pop() {
Expand Down Expand Up @@ -741,22 +748,27 @@ where
}
}
// check to make sure the current hop can send the amount
// if `htlc_maximum_value` equals 0, it means there is no limit
// if `tlc_maximum_value` equals 0, it means there is no limit
if amount_to_send > channel_info.capacity()
|| (channel_update.htlc_maximum_value != 0
&& amount_to_send > channel_update.htlc_maximum_value)
|| (channel_update.tlc_maximum_value != 0
&& amount_to_send > channel_update.tlc_maximum_value)
{
continue;
}
if amount_to_send < channel_update.htlc_minimum_value {
if amount_to_send < channel_update.tlc_minimum_value {
continue;
}

let expiry_delta = if from == source {
0
} else {
channel_update.tlc_expiry_delta
};

let incoming_htlc_expiry = cur_hop.incoming_tlc_expiry + expiry_delta;
if incoming_htlc_expiry > tlc_expiry_limit {
continue;
}
let incoming_htlc_expiry = cur_hop.incoming_htlc_expiry
+ if from == source {
0
} else {
channel_update.htlc_expiry_delta
};

let probability = cur_hop.probability
* self.history.eval_probability(
Expand All @@ -770,7 +782,7 @@ where
continue;
}
let agg_weight =
self.edge_weight(amount_to_send, fee, channel_update.htlc_expiry_delta);
self.edge_weight(amount_to_send, fee, channel_update.tlc_expiry_delta);
let weight = cur_hop.weight + agg_weight;
let distance = self.calculate_distance_based_probability(probability, weight);

Expand All @@ -784,7 +796,7 @@ where
weight,
distance,
amount_received: amount_to_send,
incoming_htlc_expiry,
incoming_tlc_expiry: incoming_htlc_expiry,
fee_charged: fee,
probability,
next_hop: Some((cur_hop.node_id, channel_info.out_point())),
Expand Down Expand Up @@ -942,7 +954,7 @@ pub struct PaymentSession {

impl PaymentSession {
pub fn new(request: SendPaymentData, try_limit: u32) -> Self {
let now = now_timestamp();
let now = now_timestamp_as_millis_u64();
Self {
request,
retried_times: 0,
Expand All @@ -963,7 +975,7 @@ impl PaymentSession {

fn set_status(&mut self, status: PaymentSessionStatus) {
self.status = status;
self.last_updated_at = now_timestamp();
self.last_updated_at = now_timestamp_as_millis_u64();
}

pub fn set_inflight_status(&mut self, channel_outpoint: OutPoint, tlc_id: u64) {
Expand Down
4 changes: 3 additions & 1 deletion src/fiber/graph_syncer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use tracing::{debug, error};

use anyhow::anyhow;

use crate::now_timestamp_as_millis_u64;

use super::{
network::GraphSyncerExitStatus, NetworkActorCommand, NetworkActorEvent, NetworkActorMessage,
ASSUME_NETWORK_ACTOR_ALIVE,
Expand Down Expand Up @@ -56,7 +58,7 @@ impl GraphSyncer {
ending_height: u64,
starting_time: u64,
) -> Self {
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
let now = now_timestamp_as_millis_u64();
Self {
network,
peer_id,
Expand Down
Loading

0 comments on commit 89c2483

Please sign in to comment.