Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

LSPS1: Fix and test serialization #126

Merged
merged 12 commits into from
Apr 26, 2024
24 changes: 21 additions & 3 deletions src/lsps0/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ use crate::lsps1::msgs::{
use crate::lsps2::msgs::{
LSPS2Message, LSPS2Request, LSPS2Response, LSPS2_BUY_METHOD_NAME, LSPS2_GET_INFO_METHOD_NAME,
};
use crate::prelude::{HashMap, String, ToString, Vec};
use crate::prelude::{HashMap, String, ToString};

use lightning::impl_writeable_msg;
use lightning::ln::msgs::LightningError;
use lightning::ln::wire;
use lightning::util::ser::WithoutLength;

use bitcoin::secp256k1::PublicKey;

Expand All @@ -42,6 +42,8 @@ pub(crate) const JSONRPC_ERROR_FIELD_KEY: &str = "error";
pub(crate) const JSONRPC_INVALID_MESSAGE_ERROR_CODE: i32 = -32700;
pub(crate) const JSONRPC_INVALID_MESSAGE_ERROR_MESSAGE: &str = "parse error";

pub(crate) const _LSPS0_CLIENT_REJECTED_ERROR_CODE: i32 = 001;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum LSPSMethod {
LSPS0ListProtocols,
Expand Down Expand Up @@ -160,7 +162,23 @@ pub struct RawLSPSMessage {
pub payload: String,
}

impl_writeable_msg!(RawLSPSMessage, { payload }, {});
// We encode `RawLSPSMessage`'s payload without a length prefix as LSPS0 expects it to be the
// remainder of the object.
impl lightning::util::ser::Writeable for RawLSPSMessage {
fn write<W: lightning::util::ser::Writer>(
&self, w: &mut W,
) -> Result<(), lightning::io::Error> {
WithoutLength(&self.payload).write(w)?;
Ok(())
}
}

impl lightning::util::ser::Readable for RawLSPSMessage {
fn read<R: lightning::io::Read>(r: &mut R) -> Result<Self, lightning::ln::msgs::DecodeError> {
let payload_without_length = WithoutLength::read(r)?;
Ok(Self { payload: payload_without_length.0 })
}
}

impl wire::Type for RawLSPSMessage {
fn type_id(&self) -> u16 {
Expand Down
1 change: 0 additions & 1 deletion src/lsps1/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ where
self.pending_events.enqueue(Event::LSPS1Client(LSPS1ClientEvent::GetInfoResponse {
user_channel_id,
counterparty_node_id: *counterparty_node_id,
website: result.website,
options_supported: result.options,
}))
},
Expand Down
6 changes: 2 additions & 4 deletions src/lsps1/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

//! Contains LSPS1 event types

use super::msgs::{ChannelInfo, OptionsSupported, OrderId, OrderParams, OrderPayment};
use super::msgs::{ChannelInfo, OptionsSupported, OrderId, OrderParams, PaymentInfo};

use crate::lsps0::ser::RequestId;
use crate::prelude::String;
Expand All @@ -35,8 +35,6 @@ pub enum LSPS1ClientEvent {
user_channel_id: u128,
/// The node id of the LSP that provided this response.
counterparty_node_id: PublicKey,
/// The website of the LSP.
website: String,
/// All options supported by the LSP.
options_supported: OptionsSupported,
},
Expand All @@ -62,7 +60,7 @@ pub enum LSPS1ClientEvent {
/// The order created by client and approved by LSP.
order: OrderParams,
/// The details regarding payment of the order
payment: OrderPayment,
payment: PaymentInfo,
/// The details regarding state of the channel ordered.
channel: Option<ChannelInfo>,
},
Expand Down
139 changes: 119 additions & 20 deletions src/lsps1/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::lsps0::ser::{
use crate::prelude::{String, Vec};

use bitcoin::address::{Address, NetworkUnchecked};
use bitcoin::OutPoint;

use lightning_invoice::Bolt11Invoice;

Expand All @@ -21,9 +22,7 @@ pub(crate) const LSPS1_CREATE_ORDER_METHOD_NAME: &str = "lsps1.create_order";
pub(crate) const LSPS1_GET_ORDER_METHOD_NAME: &str = "lsps1.get_order";

pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_PARAMS_ERROR_CODE: i32 = -32602;
pub(crate) const LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE: i32 = 1000;
pub(crate) const LSPS1_CREATE_ORDER_REQUEST_CLIENT_REJECTED_ERROR_CODE: i32 = 1001;
pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_TOKEN_ERROR_CODE: i32 = 2;
pub(crate) const LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE: i32 = 100;

/// The identifier of an order.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)]
Expand All @@ -40,8 +39,10 @@ pub struct GetInfoRequest {}
/// An object representing the supported protocol options.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct OptionsSupported {
/// The minimum number of block confirmations before the LSP accepts a channel as confirmed.
pub min_channel_confirmations: u8,
/// The smallest number of confirmations needed for the LSP to accept a channel as confirmed.
pub min_required_channel_confirmations: u8,
/// The smallest number of blocks in which the LSP can confirm the funding transaction.
pub min_funding_confirms_within_blocks: u8,
/// The minimum number of block confirmations before the LSP accepts an on-chain payment as confirmed.
pub min_onchain_payment_confirmations: Option<u8>,
/// Indicates if the LSP supports zero reserve.
Expand Down Expand Up @@ -72,11 +73,9 @@ pub struct OptionsSupported {
pub max_channel_balance_sat: u64,
}

/// A response to an [`GetInfoRequest`].
/// A response to a [`GetInfoRequest`].
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct GetInfoResponse {
/// The website of the LSP.
pub website: String,
/// All options supported by the LSP.
pub options: OptionsSupported,
}
Expand All @@ -88,6 +87,7 @@ pub struct GetInfoResponse {
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct CreateOrderRequest {
/// The order made.
#[serde(flatten)]
pub order: OrderParams,
}

Expand All @@ -103,8 +103,10 @@ pub struct OrderParams {
/// the channel.
#[serde(with = "string_amount")]
pub client_balance_sat: u64,
/// The number of blocks the client wants to wait maximally for the channel to be confirmed.
pub confirms_within_blocks: u32,
/// The number of confirmations the funding tx must have before the LSP sends `channel_ready`.
pub required_channel_confirmations: u8,
/// The maximum number of blocks the client wants to wait until the funding transaction is confirmed.
pub funding_confirms_within_blocks: u8,
/// Indicates how long the channel is leased for in block time.
pub channel_expiry_blocks: u32,
/// May contain arbitrary associated data like a coupon code or a authentication token.
Expand All @@ -121,6 +123,7 @@ pub struct CreateOrderResponse {
/// The id of the channel order.
pub order_id: OrderId,
/// The parameters of channel order.
#[serde(flatten)]
pub order: OrderParams,
/// The datetime when the order was created
pub created_at: chrono::DateTime<Utc>,
Expand All @@ -129,13 +132,14 @@ pub struct CreateOrderResponse {
/// The current state of the order.
pub order_state: OrderState,
/// Contains details about how to pay for the order.
pub payment: OrderPayment,
pub payment: PaymentInfo,
/// Contains information about the channel state.
pub channel: Option<ChannelInfo>,
}

/// An object representing the state of an order.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OrderState {
/// The order has been created.
Created,
Expand All @@ -147,7 +151,7 @@ pub enum OrderState {

/// Details regarding how to pay for an order.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct OrderPayment {
pub struct PaymentInfo {
/// Indicates the current state of the payment.
pub state: PaymentState,
/// The total fee the LSP will charge to open this channel in satoshi.
Expand All @@ -168,11 +172,12 @@ pub struct OrderPayment {
/// confirmed without a confirmation.
pub min_fee_for_0conf: u8,
/// Details regarding a detected on-chain payment.
pub onchain_payment: OnchainPayment,
pub onchain_payment: Option<OnchainPayment>,
}

/// The state of an [`OrderPayment`].
/// The state of an [`PaymentInfo`].
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentState {
/// A payment is expected.
ExpectPayment,
Expand Down Expand Up @@ -200,11 +205,11 @@ pub struct OnchainPayment {
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct ChannelInfo {
/// The datetime when the funding transaction has been published.
pub funded_at: String,
pub funded_at: chrono::DateTime<Utc>,
/// The outpoint of the funding transaction.
pub funding_outpoint: String,
pub funding_outpoint: OutPoint,
/// The earliest datetime when the channel may be closed by the LSP.
pub expires_at: String,
pub expires_at: chrono::DateTime<Utc>,
}

/// A request made to an LSP to retrieve information about an previously made order.
Expand Down Expand Up @@ -279,7 +284,8 @@ mod tests {

#[test]
fn options_supported_serialization() {
let min_channel_confirmations = 6;
let min_required_channel_confirmations = 0;
let min_funding_confirms_within_blocks = 6;
let min_onchain_payment_confirmations = Some(6);
let supports_zero_channel_reserve = true;
let min_onchain_payment_size_sat = Some(100_000);
Expand All @@ -292,7 +298,8 @@ mod tests {
let max_channel_balance_sat = 100_000_000;

let options_supported = OptionsSupported {
min_channel_confirmations,
min_required_channel_confirmations,
min_funding_confirms_within_blocks,
min_onchain_payment_confirmations,
supports_zero_channel_reserve,
min_onchain_payment_size_sat,
Expand All @@ -305,8 +312,100 @@ mod tests {
max_channel_balance_sat,
};

let json_str = r#"{"max_channel_balance_sat":"100000000","max_channel_expiry_blocks":144,"max_initial_client_balance_sat":"100000000","max_initial_lsp_balance_sat":"100000000","min_channel_balance_sat":"100000","min_channel_confirmations":6,"min_initial_client_balance_sat":"10000000","min_initial_lsp_balance_sat":"100000","min_onchain_payment_confirmations":6,"min_onchain_payment_size_sat":"100000","supports_zero_channel_reserve":true}"#;
let json_str = r#"{"max_channel_balance_sat":"100000000","max_channel_expiry_blocks":144,"max_initial_client_balance_sat":"100000000","max_initial_lsp_balance_sat":"100000000","min_channel_balance_sat":"100000","min_funding_confirms_within_blocks":6,"min_initial_client_balance_sat":"10000000","min_initial_lsp_balance_sat":"100000","min_onchain_payment_confirmations":6,"min_onchain_payment_size_sat":"100000","min_required_channel_confirmations":0,"supports_zero_channel_reserve":true}"#;

assert_eq!(json_str, serde_json::json!(options_supported).to_string());
assert_eq!(options_supported, serde_json::from_str(json_str).unwrap());
}

#[test]
fn parse_spec_test_vectors() {
// Here, we simply assert that we're able to parse all examples given in LSPS1.
let json_str = r#"{}"#;
let _get_info_request: GetInfoRequest = serde_json::from_str(json_str).unwrap();

let json_str = r#"{
"options": {
"min_required_channel_confirmations": 0,
"min_funding_confirms_within_blocks" : 6,
"min_onchain_payment_confirmations": null,
"supports_zero_channel_reserve": true,
"min_onchain_payment_size_sat": null,
"max_channel_expiry_blocks": 20160,
"min_initial_client_balance_sat": "20000",
"max_initial_client_balance_sat": "100000000",
"min_initial_lsp_balance_sat": "0",
"max_initial_lsp_balance_sat": "100000000",
"min_channel_balance_sat": "50000",
"max_channel_balance_sat": "100000000"
}
}"#;
let _get_info_response: GetInfoResponse = serde_json::from_str(json_str).unwrap();

let json_str = r#"{
"lsp_balance_sat": "5000000",
"client_balance_sat": "2000000",
"required_channel_confirmations" : 0,
"funding_confirms_within_blocks": 6,
"channel_expiry_blocks": 144,
"token": "",
"refund_onchain_address": "bc1qvmsy0f3yyes6z9jvddk8xqwznndmdwapvrc0xrmhd3vqj5rhdrrq6hz49h",
"announce_channel": true
}"#;
let _create_order_request: CreateOrderRequest = serde_json::from_str(json_str).unwrap();

let json_str = r#"{
"order_id": "bb4b5d0a-8334-49d8-9463-90a6d413af7c",
"lsp_balance_sat": "5000000",
"client_balance_sat": "2000000",
"required_channel_confirmations" : 0,
"funding_confirms_within_blocks": 1,
"channel_expiry_blocks": 12,
"token": "",
"created_at": "2012-04-23T18:25:43.511Z",
"expires_at": "2015-01-25T19:29:44.612Z",
"announce_channel": true,
"order_state": "CREATED",
"payment": {
"state": "EXPECT_PAYMENT",
"fee_total_sat": "8888",
"order_total_sat": "2008888",
"bolt11_invoice": "lnbc252u1p3aht9ysp580g4633gd2x9lc5al0wd8wx0mpn9748jeyz46kqjrpxn52uhfpjqpp5qgf67tcqmuqehzgjm8mzya90h73deafvr4m5705l5u5l4r05l8cqdpud3h8ymm4w3jhytnpwpczqmt0de6xsmre2pkxzm3qydmkzdjrdev9s7zhgfaqxqyjw5qcqpjrzjqt6xptnd85lpqnu2lefq4cx070v5cdwzh2xlvmdgnu7gqp4zvkus5zapryqqx9qqqyqqqqqqqqqqqcsq9q9qyysgqen77vu8xqjelum24hgjpgfdgfgx4q0nehhalcmuggt32japhjuksq9jv6eksjfnppm4hrzsgyxt8y8xacxut9qv3fpyetz8t7tsymygq8yzn05",
"onchain_address": "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr",
"min_onchain_payment_confirmations": 0,
"min_fee_for_0conf": 253,
"onchain_payment": null
},
"channel": null
}"#;
let _create_order_response: CreateOrderResponse = serde_json::from_str(json_str).unwrap();

let json_str = r#"{
"order_id": "bb4b5d0a-8334-49d8-9463-90a6d413af7c"
}"#;
let _get_order_request: GetOrderRequest = serde_json::from_str(json_str).unwrap();

let json_str = r#"{
"state": "EXPECT_PAYMENT",
"fee_total_sat": "8888",
"order_total_sat": "2008888",
"bolt11_invoice": "lnbc252u1p3aht9ysp580g4633gd2x9lc5al0wd8wx0mpn9748jeyz46kqjrpxn52uhfpjqpp5qgf67tcqmuqehzgjm8mzya90h73deafvr4m5705l5u5l4r05l8cqdpud3h8ymm4w3jhytnpwpczqmt0de6xsmre2pkxzm3qydmkzdjrdev9s7zhgfaqxqyjw5qcqpjrzjqt6xptnd85lpqnu2lefq4cx070v5cdwzh2xlvmdgnu7gqp4zvkus5zapryqqx9qqqyqqqqqqqqqqqcsq9q9qyysgqen77vu8xqjelum24hgjpgfdgfgx4q0nehhalcmuggt32japhjuksq9jv6eksjfnppm4hrzsgyxt8y8xacxut9qv3fpyetz8t7tsymygq8yzn05",
"onchain_address": "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr",
"min_onchain_payment_confirmations": 1,
"min_fee_for_0conf": 253,
"onchain_payment": {
"outpoint": "0301e0480b374b32851a9462db29dc19fe830a7f7d7a88b81612b9d42099c0ae:1",
"sat": "1200",
"confirmed": false
}
}"#;
let _payment: PaymentInfo = serde_json::from_str(json_str).unwrap();

let json_str = r#"{
"funded_at": "2012-04-23T18:25:43.511Z",
"funding_outpoint": "0301e0480b374b32851a9462db29dc19fe830a7f7d7a88b81612b9d42099c0ae:0",
"expires_at": "2012-04-23T18:25:43.511Z"
}"#;
let _channel: ChannelInfo = serde_json::from_str(json_str).unwrap();
}
}
13 changes: 5 additions & 8 deletions src/lsps1/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
use super::event::LSPS1ServiceEvent;
use super::msgs::{
ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoResponse, GetOrderRequest,
LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, OrderId, OrderParams,
OrderPayment, OrderState, LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE,
LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, OrderId, OrderParams, OrderState,
PaymentInfo, LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE,
};
use super::utils::is_valid;
use crate::message_queue::MessageQueue;
Expand Down Expand Up @@ -43,8 +43,6 @@ pub struct LSPS1ServiceConfig {
pub token: Option<String>,
/// The options supported by the LSP.
pub options_supported: Option<OptionsSupported>,
/// The LSP's website.
pub website: Option<String>,
}

struct ChannelStateError(String);
Expand Down Expand Up @@ -77,7 +75,7 @@ struct OutboundLSPS1Config {
order: OrderParams,
created_at: chrono::DateTime<Utc>,
expires_at: chrono::DateTime<Utc>,
payment: OrderPayment,
payment: PaymentInfo,
}

struct OutboundCRChannel {
Expand All @@ -88,7 +86,7 @@ struct OutboundCRChannel {
impl OutboundCRChannel {
fn new(
order: OrderParams, created_at: chrono::DateTime<Utc>, expires_at: chrono::DateTime<Utc>,
order_id: OrderId, payment: OrderPayment,
order_id: OrderId, payment: PaymentInfo,
) -> Self {
Self {
state: OutboundRequestState::OrderCreated { order_id },
Expand Down Expand Up @@ -171,7 +169,6 @@ where
&self, request_id: RequestId, counterparty_node_id: &PublicKey,
) -> Result<(), LightningError> {
let response = LSPS1Response::GetInfo(GetInfoResponse {
website: self.config.website.clone().unwrap().to_string(),
options: self
.config
.options_supported
Expand Down Expand Up @@ -241,7 +238,7 @@ where
///
/// [`LSPS1ServiceEvent::RequestForPaymentDetails`]: crate::lsps1::event::LSPS1ServiceEvent::RequestForPaymentDetails
pub fn send_payment_details(
&self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: OrderPayment,
&self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: PaymentInfo,
created_at: chrono::DateTime<Utc>, expires_at: chrono::DateTime<Utc>,
) -> Result<(), APIError> {
let (result, response) = {
Expand Down
Loading