Skip to content
43 changes: 42 additions & 1 deletion lightning-liquidity/src/lsps0/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ use lightning::util::ser::WithoutLength;

use bitcoin::secp256k1::PublicKey;

use core::fmt;
use core::fmt::{self, Display};
use core::str::FromStr;

#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};

use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize};
Expand Down Expand Up @@ -186,6 +189,44 @@ impl wire::Type for RawLSPSMessage {
#[serde(transparent)]
pub struct LSPSRequestId(pub String);

/// An object representing datetimes as described in bLIP-50 / LSPS0.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct LSPSDateTime(chrono::DateTime<chrono::Utc>);

impl LSPSDateTime {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, thanks for doing this.

/// Returns the LSPSDateTime as RFC3339 formatted string.
pub fn to_rfc3339(&self) -> String {
self.0.to_rfc3339()
}

/// Returns if the given time is in the past.
#[cfg(feature = "std")]
pub fn is_past(&self) -> bool {
let now_seconds_since_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock to be ahead of the unix epoch")
.as_secs();
let datetime_seconds_since_epoch =
self.0.timestamp().try_into().expect("expiration to be ahead of unix epoch");
now_seconds_since_epoch > datetime_seconds_since_epoch
}
}

impl FromStr for LSPSDateTime {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let datetime = chrono::DateTime::parse_from_rfc3339(s).map_err(|_| ())?;
Ok(Self(datetime.into()))
}
}

impl Display for LSPSDateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_rfc3339())
}
}

/// An error returned in response to an JSON-RPC request.
///
/// Please refer to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#error_object) for
Expand Down
16 changes: 7 additions & 9 deletions lightning-liquidity/src/lsps1/msgs.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Message, request, and other primitive types used to implement bLIP-51 / LSPS1.

use crate::lsps0::ser::{
string_amount, u32_fee_rate, unchecked_address, unchecked_address_option, LSPSMessage,
LSPSRequestId, LSPSResponseError,
string_amount, u32_fee_rate, unchecked_address, unchecked_address_option, LSPSDateTime,
LSPSMessage, LSPSRequestId, LSPSResponseError,
};

use crate::prelude::String;
Expand All @@ -13,8 +13,6 @@ use lightning_invoice::Bolt11Invoice;

use serde::{Deserialize, Serialize};

use chrono::Utc;

use core::convert::TryFrom;

pub(crate) const LSPS1_GET_INFO_METHOD_NAME: &str = "lsps1.get_info";
Expand Down Expand Up @@ -127,7 +125,7 @@ pub struct LSPS1CreateOrderResponse {
#[serde(flatten)]
pub order: LSPS1OrderParams,
/// The datetime when the order was created
pub created_at: chrono::DateTime<Utc>,
pub created_at: LSPSDateTime,
/// The current state of the order.
pub order_state: LSPS1OrderState,
/// Contains details about how to pay for the order.
Expand Down Expand Up @@ -163,7 +161,7 @@ pub struct LSPS1Bolt11PaymentInfo {
/// Indicates the current state of the payment.
pub state: LSPS1PaymentState,
/// The datetime when the payment option expires.
pub expires_at: chrono::DateTime<Utc>,
pub expires_at: LSPSDateTime,
/// The total fee the LSP will charge to open this channel in satoshi.
#[serde(with = "string_amount")]
pub fee_total_sat: u64,
Expand All @@ -180,7 +178,7 @@ pub struct LSPS1OnchainPaymentInfo {
/// Indicates the current state of the payment.
pub state: LSPS1PaymentState,
/// The datetime when the payment option expires.
pub expires_at: chrono::DateTime<Utc>,
pub expires_at: LSPSDateTime,
/// The total fee the LSP will charge to open this channel in satoshi.
#[serde(with = "string_amount")]
pub fee_total_sat: u64,
Expand Down Expand Up @@ -237,11 +235,11 @@ pub struct LSPS1OnchainPayment {
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LSPS1ChannelInfo {
/// The datetime when the funding transaction has been published.
pub funded_at: chrono::DateTime<Utc>,
pub funded_at: LSPSDateTime,
/// The outpoint of the funding transaction.
pub funding_outpoint: OutPoint,
/// The earliest datetime when the channel may be closed by the LSP.
pub expires_at: chrono::DateTime<Utc>,
pub expires_at: LSPSDateTime,
}

/// A request made to an LSP to retrieve information about an previously made order.
Expand Down
12 changes: 7 additions & 5 deletions lightning-liquidity/src/lsps1/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use super::msgs::{
use crate::message_queue::MessageQueue;

use crate::events::EventQueue;
use crate::lsps0::ser::{LSPSProtocolMessageHandler, LSPSRequestId, LSPSResponseError};
use crate::lsps0::ser::{
LSPSDateTime, LSPSProtocolMessageHandler, LSPSRequestId, LSPSResponseError,
};
use crate::prelude::{new_hash_map, HashMap, String};
use crate::sync::{Arc, Mutex, RwLock};
use crate::utils;
Expand Down Expand Up @@ -73,7 +75,7 @@ impl OutboundRequestState {

struct OutboundLSPS1Config {
order: LSPS1OrderParams,
created_at: chrono::DateTime<Utc>,
created_at: LSPSDateTime,
payment: LSPS1PaymentInfo,
}

Expand All @@ -84,7 +86,7 @@ struct OutboundCRChannel {

impl OutboundCRChannel {
fn new(
order: LSPS1OrderParams, created_at: chrono::DateTime<Utc>, order_id: LSPS1OrderId,
order: LSPS1OrderParams, created_at: LSPSDateTime, order_id: LSPS1OrderId,
payment: LSPS1PaymentInfo,
) -> Self {
Self {
Expand Down Expand Up @@ -237,7 +239,7 @@ where
/// [`LSPS1ServiceEvent::RequestForPaymentDetails`]: crate::lsps1::event::LSPS1ServiceEvent::RequestForPaymentDetails
pub fn send_payment_details(
&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
payment: LSPS1PaymentInfo, created_at: chrono::DateTime<Utc>,
payment: LSPS1PaymentInfo, created_at: LSPSDateTime,
) -> Result<(), APIError> {
let (result, response) = {
let outer_state_lock = self.per_peer_state.read().unwrap();
Expand Down Expand Up @@ -380,7 +382,7 @@ where
order_id,
order: config.order.clone(),
order_state,
created_at: config.created_at,
created_at: config.created_at.clone(),
payment: config.payment.clone(),
channel,
});
Expand Down
30 changes: 16 additions & 14 deletions lightning-liquidity/src/lsps2/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ use core::convert::TryFrom;
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::{Hash, HashEngine};
use chrono::Utc;
use serde::{Deserialize, Serialize};

use lightning::util::scid_utils;

use crate::lsps0::ser::{
string_amount, string_amount_option, LSPSMessage, LSPSRequestId, LSPSResponseError,
string_amount, string_amount_option, LSPSDateTime, LSPSMessage, LSPSRequestId,
LSPSResponseError,
};
use crate::prelude::{String, Vec};
use crate::utils;
Expand Down Expand Up @@ -42,7 +42,7 @@ pub struct LSPS2RawOpeningFeeParams {
/// A fee proportional to the size of the initial payment.
pub proportional: u32,
/// An [`ISO8601`](https://www.iso.org/iso-8601-date-and-time-format.html) formatted date for which these params are valid.
pub valid_until: chrono::DateTime<Utc>,
pub valid_until: LSPSDateTime,
/// The number of blocks after confirmation that the LSP promises it will keep the channel alive without closing.
pub min_lifetime: u32,
/// The maximum number of blocks that the client is allowed to set its `to_self_delay` parameter.
Expand Down Expand Up @@ -93,7 +93,7 @@ pub struct LSPS2OpeningFeeParams {
/// A fee proportional to the size of the initial payment.
pub proportional: u32,
/// An [`ISO8601`](https://www.iso.org/iso-8601-date-and-time-format.html) formatted date for which these params are valid.
pub valid_until: chrono::DateTime<Utc>,
pub valid_until: LSPSDateTime,
/// The number of blocks after confirmation that the LSP promises it will keep the channel alive without closing.
pub min_lifetime: u32,
/// The maximum number of blocks that the client is allowed to set its `to_self_delay` parameter.
Expand Down Expand Up @@ -214,15 +214,17 @@ impl From<LSPS2Message> for LSPSMessage {
#[cfg(test)]
mod tests {
use super::*;

use crate::alloc::string::ToString;
use crate::lsps2::utils::is_valid_opening_fee_params;

use core::str::FromStr;

#[test]
fn into_opening_fee_params_produces_valid_promise() {
let min_fee_msat = 100;
let proportional = 21;
let valid_until: chrono::DateTime<Utc> =
chrono::DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap().into();
let valid_until = LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap();
let min_lifetime = 144;
let max_client_to_self_delay = 128;
let min_payment_size_msat = 1;
Expand Down Expand Up @@ -257,7 +259,7 @@ mod tests {
fn changing_single_field_produced_invalid_params() {
let min_fee_msat = 100;
let proportional = 21;
let valid_until = chrono::DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap();
let valid_until = LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap();
let min_lifetime = 144;
let max_client_to_self_delay = 128;
let min_payment_size_msat = 1;
Expand All @@ -266,7 +268,7 @@ mod tests {
let raw = LSPS2RawOpeningFeeParams {
min_fee_msat,
proportional,
valid_until: valid_until.into(),
valid_until,
min_lifetime,
max_client_to_self_delay,
min_payment_size_msat,
Expand All @@ -284,7 +286,7 @@ mod tests {
fn wrong_secret_produced_invalid_params() {
let min_fee_msat = 100;
let proportional = 21;
let valid_until = chrono::DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap();
let valid_until = LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap();
let min_lifetime = 144;
let max_client_to_self_delay = 128;
let min_payment_size_msat = 1;
Expand All @@ -293,7 +295,7 @@ mod tests {
let raw = LSPS2RawOpeningFeeParams {
min_fee_msat,
proportional,
valid_until: valid_until.into(),
valid_until,
min_lifetime,
max_client_to_self_delay,
min_payment_size_msat,
Expand All @@ -313,7 +315,7 @@ mod tests {
fn expired_params_produces_invalid_params() {
let min_fee_msat = 100;
let proportional = 21;
let valid_until = chrono::DateTime::parse_from_rfc3339("2023-05-20T08:30:45Z").unwrap();
let valid_until = LSPSDateTime::from_str("2023-05-20T08:30:45Z").unwrap();
let min_lifetime = 144;
let max_client_to_self_delay = 128;
let min_payment_size_msat = 1;
Expand All @@ -322,7 +324,7 @@ mod tests {
let raw = LSPS2RawOpeningFeeParams {
min_fee_msat,
proportional,
valid_until: valid_until.into(),
valid_until,
min_lifetime,
max_client_to_self_delay,
min_payment_size_msat,
Expand All @@ -339,7 +341,7 @@ mod tests {
fn buy_request_serialization() {
let min_fee_msat = 100;
let proportional = 21;
let valid_until = chrono::DateTime::parse_from_rfc3339("2023-05-20T08:30:45Z").unwrap();
let valid_until = LSPSDateTime::from_str("2023-05-20T08:30:45Z").unwrap();
let min_lifetime = 144;
let max_client_to_self_delay = 128;
let min_payment_size_msat = 1;
Expand All @@ -348,7 +350,7 @@ mod tests {
let raw = LSPS2RawOpeningFeeParams {
min_fee_msat,
proportional,
valid_until: valid_until.into(),
valid_until,
min_lifetime,
max_client_to_self_delay,
min_payment_size_msat,
Expand Down
12 changes: 7 additions & 5 deletions lightning-liquidity/src/lsps2/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1419,12 +1419,14 @@ fn calculate_amount_to_forward_per_htlc(

#[cfg(test)]
mod tests {

use super::*;
use chrono::TimeZone;
use chrono::Utc;

use crate::lsps0::ser::LSPSDateTime;

use proptest::prelude::*;

use core::str::FromStr;

const MAX_VALUE_MSAT: u64 = 21_000_000_0000_0000_000;

fn arb_forward_amounts() -> impl Strategy<Value = (u64, u64, u64, u64)> {
Expand Down Expand Up @@ -1518,7 +1520,7 @@ mod tests {
let opening_fee_params = LSPS2OpeningFeeParams {
min_fee_msat: 10_000_000,
proportional: 10_000,
valid_until: Utc.timestamp_opt(3000, 0).unwrap(),
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
min_lifetime: 4032,
max_client_to_self_delay: 2016,
min_payment_size_msat: 10_000_000,
Expand Down Expand Up @@ -1710,7 +1712,7 @@ mod tests {
let opening_fee_params = LSPS2OpeningFeeParams {
min_fee_msat: 10_000_000,
proportional: 10_000,
valid_until: Utc.timestamp_opt(3000, 0).unwrap(),
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
min_lifetime: 4032,
max_client_to_self_delay: 2016,
min_payment_size_msat: 10_000_000,
Expand Down
14 changes: 1 addition & 13 deletions lightning-liquidity/src/lsps2/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::{Hash, HashEngine};

#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};

/// Determines if the given parameters are valid given the secret used to generate the promise.
pub fn is_valid_opening_fee_params(
fee_params: &LSPS2OpeningFeeParams, promise_secret: &[u8; 32],
Expand All @@ -35,16 +32,7 @@ pub fn is_valid_opening_fee_params(
pub fn is_expired_opening_fee_params(fee_params: &LSPS2OpeningFeeParams) -> bool {
#[cfg(feature = "std")]
{
let seconds_since_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock to be ahead of the unix epoch")
.as_secs();
let valid_until_seconds_since_epoch = fee_params
.valid_until
.timestamp()
.try_into()
.expect("expiration to be ahead of unix epoch");
seconds_since_epoch > valid_until_seconds_since_epoch
fee_params.valid_until.is_past()
}
#[cfg(not(feature = "std"))]
{
Expand Down
6 changes: 3 additions & 3 deletions lightning-liquidity/tests/lsps2_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod common;
use common::{create_service_and_client_nodes, get_lsps_message, Node};

use lightning_liquidity::events::LiquidityEvent;
use lightning_liquidity::lsps0::ser::LSPSDateTime;
use lightning_liquidity::lsps2::client::LSPS2ClientConfig;
use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
use lightning_liquidity::lsps2::msgs::LSPS2RawOpeningFeeParams;
Expand All @@ -24,8 +25,7 @@ use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{PublicKey, Secp256k1};
use bitcoin::Network;

use chrono::DateTime;

use std::str::FromStr;
use std::time::Duration;

fn create_jit_invoice(
Expand Down Expand Up @@ -128,7 +128,7 @@ fn invoice_generation_flow() {
let raw_opening_params = LSPS2RawOpeningFeeParams {
min_fee_msat: 100,
proportional: 21,
valid_until: DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap().into(),
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
min_lifetime: 144,
max_client_to_self_delay: 128,
min_payment_size_msat: 1,
Expand Down
Loading