From 7f31580a6812422acc0a8293e22040b58908cbd9 Mon Sep 17 00:00:00 2001 From: vacwmX Date: Thu, 23 Nov 2023 10:18:41 +0100 Subject: [PATCH 1/4] fix: fixing start_any_period type for offer recurrence --- doc/schemas/decode.schema.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/schemas/decode.schema.json b/doc/schemas/decode.schema.json index 92b0ac047643..12be9fa7cf1a 100644 --- a/doc/schemas/decode.schema.json +++ b/doc/schemas/decode.schema.json @@ -185,7 +185,7 @@ "description": "period starts at this UNIX timestamp" }, "start_any_period": { - "type": "u64", + "type": "boolean", "description": "you can start at any period (only if `basetime` present)" }, "limit": { @@ -480,7 +480,7 @@ "description": "period starts at this UNIX timestamp" }, "start_any_period": { - "type": "u64", + "type": "boolean", "description": "you can start at any period (only if `basetime` present)" }, "limit": { @@ -844,7 +844,7 @@ "description": "period starts at this UNIX timestamp" }, "start_any_period": { - "type": "u64", + "type": "boolean", "description": "you can start at any period (only if `basetime` present)" }, "limit": { From c85f2fcda207c6759efa22e49a7f69194fd499ed Mon Sep 17 00:00:00 2001 From: vacwmX Date: Fri, 24 Nov 2023 17:29:06 +0100 Subject: [PATCH 2/4] feat: adding offer request method to grpc --- cln-grpc/proto/node.proto | 41 +++++++++++++ cln-grpc/src/convert.rs | 117 ++++++++++++++++++++++++++++++++++++++ cln-grpc/src/server.rs | 31 ++++++++++ cln-rpc/src/model.rs | 77 +++++++++++++++++++++++++ 4 files changed, 266 insertions(+) diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index f26f85620235..31b3626c36af 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -56,6 +56,7 @@ service Node { rpc ListForwards(ListforwardsRequest) returns (ListforwardsResponse) {} rpc ListPays(ListpaysRequest) returns (ListpaysResponse) {} rpc ListHtlcs(ListhtlcsRequest) returns (ListhtlcsResponse) {} + rpc Offer(OfferRequest) returns (OfferResponse) {} rpc Ping(PingRequest) returns (PingResponse) {} rpc SendCustomMsg(SendcustommsgRequest) returns (SendcustommsgResponse) {} rpc SetChannel(SetchannelRequest) returns (SetchannelResponse) {} @@ -1661,6 +1662,46 @@ message ListhtlcsHtlcs { HtlcState state = 7; } +message OfferRequest { + AmountOrAny amount = 1; + string description = 2; + optional string issuer = 3; + optional string label = 4; + optional uint64 absolute_expiry = 5; + optional Offer_recurrence recurrence = 6; + optional Offer_recurrenceBase recurrence_base = 7; + optional Offer_recurrencePaywindow recurrence_paywindow = 8; + optional uint64 recurrence_limit = 9; + optional bool single_use = 10; +} + +message Offer_recurrence { + uint32 time_unit = 1; + uint32 period = 2; + optional string time_unit_name = 3; +} + +message Offer_recurrenceBase { + uint64 basetime = 1; + bool start_any_period = 2; +} + +message Offer_recurrencePaywindow { + uint32 seconds_before = 1; + uint32 seconds_after = 2; + optional bool proportional_amount = 3; +} + +message OfferResponse { + bytes offer_id = 1; + bool active = 2; + bool single_use = 3; + string bolt12 = 4; + bool used = 5; + bool created = 6; + optional string label = 7; +} + message PingRequest { bytes id = 1; optional uint32 len = 2; diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 39978386d97a..b3c0382d1b12 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -1533,6 +1533,21 @@ impl From for pb::ListhtlcsResponse { } } +#[allow(unused_variables)] +impl From for pb::OfferResponse { + fn from(c: responses::OfferResponse) -> Self { + Self { + offer_id: c.offer_id, // Rule #2 for type hex + active: c.active, // Rule #2 for type boolean + single_use: c.single_use, // Rule #2 for type boolean + bolt12: c.bolt12, // Rule #2 for type string + used: c.used, // Rule #2 for type boolean + created: c.created, // Rule #2 for type boolean + label: c.label, // Rule #2 for type string? + } + } +} + #[allow(unused_variables)] impl From for pb::PingResponse { fn from(c: responses::PingResponse) -> Self { @@ -2245,6 +2260,57 @@ impl From for pb::ListhtlcsRequest { } } +#[allow(unused_variables)] +impl From for pb::OfferRecurrence { + fn from(c: requests::OfferRecurrence) -> Self { + Self { + period: c.period, // Rule #2 for type u32 + time_unit: c.time_unit, // Rule #2 for type u32 + time_unit_name: c.time_unit_name, // Rule #2 for type string? + } + } +} + + +#[allow(unused_variables)] +impl From for pb::OfferRecurrenceBase { + fn from(c: requests::OfferRecurrenceBase) -> Self { + Self { + basetime: c.basetime, // Rule #2 for type u64 + start_any_period: c.start_any_period, // Rule #2 for type bool + } + } +} + +#[allow(unused_variables)] +impl From for pb::OfferRecurrencePaywindow { + fn from(c: requests::OfferRecurrencePaywindow) -> Self { + Self { + seconds_before: c.seconds_before, // Rule #2 for type u32 + seconds_after: c.seconds_after, // Rule #2 for type u32 + proportional_amount: c.proportional_amount, // Rule #2 for type bool? + } + } +} + +#[allow(unused_variables)] +impl From for pb::OfferRequest { + fn from(c: requests::OfferRequest) -> Self { + Self { + amount: Some(c.amount.into()), // Rule #2 for type msat_or_any + description: c.description, // Rule #2 for type string + issuer: c.issuer, // Rule #2 for type string? + label: c.label, // Rule #2 for type string? + absolute_expiry: c.absolute_expiry, // Rule #2 for type u64? + recurrence: c.recurrence.map(|v| v.into()), + recurrence_base: c.recurrence_base.map(|v| v.into()), + recurrence_paywindow: c.recurrence_paywindow.map(|v| v.into()), + recurrence_limit: c.recurrence_limit, // Rule #2 for type u64? + single_use: c.single_use // Rule #2 for type bool? + } + } +} + #[allow(unused_variables)] impl From for pb::PingRequest { fn from(c: requests::PingRequest) -> Self { @@ -2932,6 +2998,57 @@ impl From for requests::ListhtlcsRequest { } } +#[allow(unused_variables)] +impl From for requests::OfferRecurrence { + fn from(c: pb::OfferRecurrence) -> Self { + Self { + period: c.period, // Rule #1 for type u32 + time_unit: c.time_unit, // Rule #1 for type u32 + time_unit_name: c.time_unit_name, // Rule #1 for type string? + } + } +} + + +#[allow(unused_variables)] +impl From for requests::OfferRecurrenceBase { + fn from(c: pb::OfferRecurrenceBase) -> Self { + Self { + basetime: c.basetime, // Rule #1 for type u64 + start_any_period: c.start_any_period, // Rule #1 for type bool + } + } +} + +#[allow(unused_variables)] +impl From for requests::OfferRecurrencePaywindow { + fn from(c: pb::OfferRecurrencePaywindow) -> Self { + Self { + seconds_before: c.seconds_before, // Rule #1 for type u32 + seconds_after: c.seconds_after, // Rule #1 for type u32 + proportional_amount: c.proportional_amount, // Rule #1 for type bool? + } + } +} + +#[allow(unused_variables)] +impl From for requests::OfferRequest { + fn from(c: pb::OfferRequest) -> Self { + Self { + amount: c.amount.unwrap().into(), // Rule #1 for type msat_or_any + description: c.description, // Rule #1 for type string + issuer: c.issuer, // Rule #1 for type string? + label: c.label, // Rule #1 for type string? + absolute_expiry: c.absolute_expiry, // Rule #1 for type u64? + recurrence: c.recurrence.map(|v| v.into()), + recurrence_base: c.recurrence_base.map(|v| v.into()), + recurrence_paywindow: c.recurrence_paywindow.map(|v| v.into()), + recurrence_limit: c.recurrence_limit, // Rule #1 for type u64? + single_use: c.single_use // Rule #1 for type bool? + } + } +} + #[allow(unused_variables)] impl From for requests::PingRequest { fn from(c: pb::PingRequest) -> Self { diff --git a/cln-grpc/src/server.rs b/cln-grpc/src/server.rs index a3dc92a26611..0e0d9bda2ce4 100644 --- a/cln-grpc/src/server.rs +++ b/cln-grpc/src/server.rs @@ -1562,6 +1562,37 @@ async fn list_htlcs( } +async fn offer( + &self, + request: tonic::Request, +) -> Result, tonic::Status> { + let req = request.into_inner(); + let req: requests::OfferRequest = req.into(); + debug!("Client asked for offer"); + trace!("offer request: {:?}", req); + let mut rpc = ClnRpc::new(&self.rpc_path) + .await + .map_err(|e| Status::new(Code::Internal, e.to_string()))?; + let result = rpc.call(Request::Offer(req)) + .await + .map_err(|e| Status::new( + Code::Unknown, + format!("Error calling method Offer: {:?}", e)))?; + match result { + Response::Offer(r) => { + trace!("offer response: {:?}", r); + Ok(tonic::Response::new(r.into())) + }, + r => Err(Status::new( + Code::Internal, + format!( + "Unexpected result {:?} to method call Offer", + r + ) + )), + } +} + async fn ping( &self, request: tonic::Request, diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index c99a1fce7cb5..1ec7cd6f60df 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -62,6 +62,7 @@ pub enum Request { ListForwards(requests::ListforwardsRequest), ListPays(requests::ListpaysRequest), ListHtlcs(requests::ListhtlcsRequest), + Offer(requests::OfferRequest), Ping(requests::PingRequest), SendCustomMsg(requests::SendcustommsgRequest), SetChannel(requests::SetchannelRequest), @@ -125,6 +126,7 @@ pub enum Response { ListForwards(responses::ListforwardsResponse), ListPays(responses::ListpaysResponse), ListHtlcs(responses::ListhtlcsResponse), + Offer(responses::OfferResponse), Ping(responses::PingResponse), SendCustomMsg(responses::SendcustommsgResponse), SetChannel(responses::SetchannelResponse), @@ -1415,6 +1417,58 @@ pub mod requests { type Response = super::responses::ListhtlcsResponse; } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OfferRecurrence { + pub time_unit: u32, + pub period: u32, + pub time_unit_name: Option, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OfferRecurrenceBase { + pub basetime: u64, + pub start_any_period: bool, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OfferRecurrencePaywindow { + pub seconds_before: u32, + pub seconds_after: u32, + pub proportional_amount: Option, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OfferRequest { + pub amount: AmountOrAny, + pub description: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub issuer: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub label: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub absolute_expiry: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence_base: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence_paywindow: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence_limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub single_use: Option, + } + + impl From for Request { + fn from(r: OfferRequest) -> Self { + Request::Offer(r) + } + } + + impl IntoRequest for OfferRequest { + type Response = super::responses::OfferResponse; + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PingRequest { pub id: PublicKey, @@ -4771,6 +4825,29 @@ pub mod responses { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OfferResponse { + pub offer_id: Vec, + pub active: bool, + pub single_use: bool, + pub bolt12: String, + pub used: bool, + pub created: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub label: Option, + } + + impl TryFrom for OfferResponse { + type Error = super::TryFromResponseError; + + fn try_from(response: Response) -> Result { + match response { + Response::Offer(response) => Ok(response), + _ => Err(TryFromResponseError) + } + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PingResponse { pub totlen: u16, From ace05670e4dc718f463ac9c90dfb9a0fb32e2737 Mon Sep 17 00:00:00 2001 From: vacwmX Date: Fri, 24 Nov 2023 17:45:44 +0100 Subject: [PATCH 3/4] feat: adding offer command to pyln-testing and msggen --- contrib/msggen/msggen/utils/utils.py | 2 +- contrib/pyln-testing/pyln/testing/grpc2py.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/contrib/msggen/msggen/utils/utils.py b/contrib/msggen/msggen/utils/utils.py index a4af51be733c..6a01a16b36cf 100644 --- a/contrib/msggen/msggen/utils/utils.py +++ b/contrib/msggen/msggen/utils/utils.py @@ -86,7 +86,7 @@ def load_jsonrpc_service(schema_dir: str): "ListHtlcs", # "multifundchannel", # "multiwithdraw", - # "offer", + "Offer", # "openchannel_abort", # "openchannel_bump", # "openchannel_init", diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py index b94cd5d254ec..aab579fad87a 100644 --- a/contrib/pyln-testing/pyln/testing/grpc2py.py +++ b/contrib/pyln-testing/pyln/testing/grpc2py.py @@ -1311,6 +1311,17 @@ def listhtlcs2py(m): }) +def offer2py(m): + return remove_default({ + "offer_id": m.offer_id, # PrimitiveField in generate_composite + "active": m.active, # PrimitiveField in generate_composite + "single_use": m.single_use, # PrimitiveField in generate_composite + "bolt12": m.bolt12, # PrimitiveField in generate_composite + "used": m.used, # PrimitiveField in generate_composite + "created": c.created, # PrimitiveField in generate_composite + "label": c.label, # PrimitiveField in generate_composite # PrimitiveField in generate_composite + }) + def ping2py(m): return remove_default({ "totlen": m.totlen, # PrimitiveField in generate_composite From 199fed85f442330031266e5a719166bd3c2b5e89 Mon Sep 17 00:00:00 2001 From: vacwmX Date: Fri, 24 Nov 2023 17:46:24 +0100 Subject: [PATCH 4/4] doc: adding documentation for cln offer command --- doc/schemas/offer.request.json | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 doc/schemas/offer.request.json diff --git a/doc/schemas/offer.request.json b/doc/schemas/offer.request.json new file mode 100644 index 000000000000..f9300ea2d5f9 --- /dev/null +++ b/doc/schemas/offer.request.json @@ -0,0 +1,101 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "amount", + "description" + ], + "properties": { + "amount": { + "type": "msat_or_any", + "description": "either the string \"any\" (e.g. for donations) or a positive value in millisatoshi precision" + }, + "description": { + "type": "string", + "description": "a short description of purpose of the offer" + }, + "issuer": { + "type": "string", + "description": "who is issuing this offer" + }, + "label": { + "type": "string", + "description": "an internal-use name for the offer" + }, + "quantity_max": { + "type": "u64", + "description": "specifies the number of items up (and including) this maximum" + }, + "absolute_expiry": { + "type": "u64", + "description": "the time the offer is valid until, in seconds since the first day of 1970 UTC" + }, + "recurrence": { + "type": "object", + "required": [ + "time_unit", + "period" + ], + "properties": { + "time_unit": { + "type": "u32", + "description": "a positive number referring to a time unit - 0 (seconds), 1 (days), 2 (months), 3 (years)" + }, + "period": { + "type": "u32", + "description": "the positive number which the time unit refers to" + }, + "time_unit_name": { + "type": "string", + "description": "the name of the specified time unit - either \"seconds\", \"minutes\", \"hours\", \"days\", \"weeks\" or \"years\"" + } + } + }, + "recurrence_base": { + "type": "object", + "required": [ + "basetime", + "start_any_period" + ], + "properties": { + "basetime": { + "type": "u64", + "description": "time in seconds since the first day of 1970 UTC" + }, + "start_any_period": { + "type": "boolean", + "description": "whether or not the invoice should start at any period" + } + } + }, + "recurrence_paywindow": { + "type": "object", + "required": [ + "seconds_before", + "seconds_after" + ], + "properties": { + "seconds_before": { + "type": "u32", + "description": "number of seconds before the start of a period in which an invoice and payment is valid" + }, + "seconds_after": { + "type": "u32", + "description": "number of seconds after the start of a period in which an invoice and payment is valid" + }, + "proportional_amount": { + "type": "boolean", + "description": "indicates whether the amount of the invoice will be scaled by the time remaining in the period" + } + } + }, + "recurrence_limit": { + "type": "u64", + "description": "the maximum recurrence period which exists" + }, + "single_use": { + "type": "boolean", + "description": "indicates that the offer is only valid once" + } + } +}