Skip to content

Commit

Permalink
Separate LSPS2 schema's
Browse files Browse the repository at this point in the history
Separate the lsps2-schema's from the message file
  • Loading branch information
ErikDeSmedt committed Oct 27, 2023
1 parent 32f76b5 commit 612413c
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 200 deletions.
20 changes: 9 additions & 11 deletions libs/gl-client/src/lsps/lsps0/common_schemas.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use serde::{Serialize, Deserialize};
use serde::de::Error as SeError;
use serde::ser::Error as DeError;
use serde::{Deserialize, Serialize};

use time::format_description::FormatItem;
use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime};


// Implements all the common schema's defined in LSPS0 common schema's

// Initially I used serde_as for the parsing and serialization of this type.
Expand Down Expand Up @@ -84,20 +83,19 @@ impl SatAmount {
self.0
}

pub fn new(value : u64) -> Self {
SatAmount(value)
}
pub fn new(value: u64) -> Self {
SatAmount(value)
}
}

impl MsatAmount {
pub fn msat_value(&self) -> u64 {
self.0
}


pub fn new(value : u64) -> Self {
MsatAmount(value)
}
pub fn new(value: u64) -> Self {
MsatAmount(value)
}
}

impl Serialize for SatAmount {
Expand Down Expand Up @@ -148,8 +146,8 @@ impl<'de> Deserialize<'de> for MsatAmount {

#[cfg(test)]
mod test {
use super::*;
use super::*;

#[test]
fn parsing_amount_sats() {
// Pick a number which exceeds 2^32 to ensure internal representation exceeds 32 bits
Expand Down
2 changes: 1 addition & 1 deletion libs/gl-client/src/lsps/lsps0/schema.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct ProtocolList {
Expand Down
5 changes: 2 additions & 3 deletions libs/gl-client/src/lsps/lsps1/schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::lsps::lsps0::common_schemas::{IsoDatetime, SatAmount};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub type OnchainFeeRate = u64;

Expand Down Expand Up @@ -55,7 +55,6 @@ pub struct Lsps1GetOrderResponse {
payment: Payment,
}


#[derive(Debug, Serialize, Deserialize)]
enum OrderState {
#[serde(rename = "CREATED")]
Expand Down
1 change: 1 addition & 0 deletions libs/gl-client/src/lsps/lsps2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod schema;
184 changes: 184 additions & 0 deletions libs/gl-client/src/lsps/lsps2/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use serde::de::Error as DeError;
use serde::{Deserialize, Serialize};

use crate::lsps::error::map_json_rpc_error_code_to_str;
use crate::lsps::json_rpc::MapErrorCode;
use crate::lsps::lsps0::common_schemas::{IsoDatetime, MsatAmount};

const MAX_PROMISE_LEN_BYTES: usize = 512;
#[derive(Debug)]
struct Promise {
promise: String,
}

impl Promise {
fn new(promise: String) -> Result<Self, String> {
if promise.len() <= MAX_PROMISE_LEN_BYTES {
Ok(Promise { promise })
} else {
Err(String::from("Promise exceeds maximum length"))
}
}
}

impl Serialize for Promise {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.promise)
}
}

// We only accept promises with a max length of 512 to be compliant to the spec
// Note, that serde-json still forces us to parse the entire string fully.
// However, the client will not story the overly large json-file
impl<'de> Deserialize<'de> for Promise {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = String::deserialize(deserializer)?;
let promise = Promise::new(str_repr.clone());
format!("{:?}", &promise);
return Promise::new(str_repr.clone())
.map_err(|_| D::Error::custom("promise exceeds max length"));
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2GetVersionsResponse {
versions: Vec<u64>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2GetInfoRequest {
version: i64,
token: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2GetInfoResponse {
opening_fee_params_menu: Vec<OpeningFeeParamsMenuItem>,
min_payment_size_msat: String,
max_payment_size_msat: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2GetInfoError {}

impl MapErrorCode for Lsps2GetInfoError {
fn get_code_str(code: i64) -> &'static str {
match code {
1 => "unsupported_version",
2 => "unrecognized_or_stale_token",
_ => map_json_rpc_error_code_to_str(code),
}
}
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OpeningFeeParamsMenuItem {
min_fee_msat: MsatAmount,
proportional: u64,
_valid_until: IsoDatetime,
min_lifetime: u64,
max_client_to_self_delay: u64,
promise: Promise,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2BuyRequest {
version: String,
opening_fee_params: OpeningFeeParamsMenuItem,
payment_size_msat: MsatAmount,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2BuyResponse {
jit_channel_scid: String,
lsp_cltv_expiry_delta: u64,
#[serde(default)]
client_trusts_lsp: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps2BuyError {}

impl MapErrorCode for Lsps2BuyError {
fn get_code_str(code: i64) -> &'static str {
match code {
1 => "unsupported_version",
2 => "invalid_opening_fee_params",
3 => "payment_size_too_small",
4 => "payment_size_too_large",
_ => map_json_rpc_error_code_to_str(code),
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn parsing_error_when_opening_fee_menu_has_extra_fields() {
// LSPS2 mentions
// Clients MUST fail and abort the flow if a opening_fee_params object has unrecognized fields.
let fee_menu_item = serde_json::json!(
{
"min_fee_msat": "546000",
"proportional": 1200,
"valid_until": "2023-02-23T08:47:30.511Z",
"min_lifetime": 1008,
"max_client_to_self_delay": 2016,
"promise": "abcdefghijklmnopqrstuvwxyz",
"extra_field" : "This shouldn't be their"
}
);

let parsed_opening_fee_menu_item: Result<OpeningFeeParamsMenuItem, _> =
serde_json::from_value(fee_menu_item);
assert!(
parsed_opening_fee_menu_item.is_err_and(|x| format!("{}", x).contains("extra_field"))
)
}

#[test]
fn parse_valid_promise() {
let promise_json = "\"abcdefghijklmnopqrstuvwxyz\"";
let promise = serde_json::from_str::<Promise>(promise_json).expect("Can parse promise");
assert_eq!(promise.promise, "abcdefghijklmnopqrstuvwxyz");
}

#[test]
fn parse_too_long_promise_fails() {
// Each a char correspond to 1 byte
// We refuse to parse the promise if it is too long
// LSPS2 requires us to ignore Promise that are too long
// so the client cannot be burdened with unneeded storage requirements
let a_513_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"";
let a_512_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"";

serde_json::from_str::<Promise>(a_512_chars)
.expect("Should fail because 512 bytes is the max length");
serde_json::from_str::<Promise>(a_513_chars).expect_err("Should fail to parse promise ");
}

#[test]
fn client_trust_lsp_defaults_to_false() {
let data = serde_json::json!({
"jit_channel_scid" : "#scid#",
"lsp_cltv_expiry_delta" : 144
});

let buy_response =
serde_json::from_value::<Lsps2BuyResponse>(data).expect("The response can be parsed");

assert!(
!buy_response.client_trusts_lsp,
"If the field is absent it assumed the client should not trust the LSP"
)
}
}
Loading

0 comments on commit 612413c

Please sign in to comment.