Introduce LSPSDateTime wrapper

This wrapper is more ergonomic to use in the local context and will be
used as a serialization wrapper in following commits.
This commit is contained in:
Elias Rohrer 2025-01-09 11:09:46 +01:00
parent 8526b9fca3
commit 690fcb1545
No known key found for this signature in database
GPG key ID: 36153082BDF676FD
7 changed files with 83 additions and 50 deletions

View file

@ -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};
@ -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 {
/// 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

View file

@ -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;
@ -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";
@ -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.
@ -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,
@ -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,
@ -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.

View file

@ -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;
@ -73,7 +75,7 @@ impl OutboundRequestState {
struct OutboundLSPS1Config {
order: LSPS1OrderParams,
created_at: chrono::DateTime<Utc>,
created_at: LSPSDateTime,
payment: LSPS1PaymentInfo,
}
@ -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 {
@ -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();
@ -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,
});

View file

@ -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;
@ -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.
@ -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.
@ -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;
@ -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;
@ -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,
@ -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;
@ -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,
@ -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;
@ -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,
@ -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;
@ -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,

View file

@ -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)> {
@ -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,
@ -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,

View file

@ -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],
@ -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"))]
{

View file

@ -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;
@ -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(
@ -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,