diff --git a/.msggen.json b/.msggen.json index 4b19ed69ca93..324d88477959 100644 --- a/.msggen.json +++ b/.msggen.json @@ -697,6 +697,35 @@ "Feerates.perkw": 3, "Feerates.warning_missing_feerates": 1 }, + "FetchinvoiceChanges": { + "FetchInvoice.changes.amount_msat": 5, + "FetchInvoice.changes.description": 2, + "FetchInvoice.changes.description_appended": 1, + "FetchInvoice.changes.vendor": 4, + "FetchInvoice.changes.vendor_removed": 3 + }, + "FetchinvoiceNext_period": { + "FetchInvoice.next_period.counter": 1, + "FetchInvoice.next_period.endtime": 3, + "FetchInvoice.next_period.paywindow_end": 5, + "FetchInvoice.next_period.paywindow_start": 4, + "FetchInvoice.next_period.starttime": 2 + }, + "FetchinvoiceRequest": { + "FetchInvoice.amount_msat": 2, + "FetchInvoice.offer": 1, + "FetchInvoice.payer_note": 8, + "FetchInvoice.quantity": 3, + "FetchInvoice.recurrence_counter": 4, + "FetchInvoice.recurrence_label": 6, + "FetchInvoice.recurrence_start": 5, + "FetchInvoice.timeout": 7 + }, + "FetchinvoiceResponse": { + "FetchInvoice.changes": 2, + "FetchInvoice.invoice": 1, + "FetchInvoice.next_period": 3 + }, "FundchannelRequest": { "FundChannel.amount": 1, "FundChannel.announce": 3, @@ -2853,6 +2882,94 @@ "added": "pre-v0.10.1", "deprecated": false }, + "FetchInvoice": { + "added": "pre-v0.10.1", + "deprecated": null + }, + "FetchInvoice.amount_msat": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.changes": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.changes.amount_msat": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.changes.description": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.changes.description_appended": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.changes.vendor": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.changes.vendor_removed": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.invoice": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.next_period": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.next_period.counter": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.next_period.endtime": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.next_period.paywindow_end": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.next_period.paywindow_start": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.next_period.starttime": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.offer": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.payer_note": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.quantity": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.recurrence_counter": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.recurrence_label": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.recurrence_start": { + "added": "pre-v0.10.1", + "deprecated": false + }, + "FetchInvoice.timeout": { + "added": "pre-v0.10.1", + "deprecated": false + }, "FundChannel": { "added": "pre-v0.10.1", "deprecated": null diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 7185bec60c8f..5b6d19529029 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -52,6 +52,7 @@ service Node { rpc Decode(DecodeRequest) returns (DecodeResponse) {} rpc Disconnect(DisconnectRequest) returns (DisconnectResponse) {} rpc Feerates(FeeratesRequest) returns (FeeratesResponse) {} + rpc FetchInvoice(FetchinvoiceRequest) returns (FetchinvoiceResponse) {} rpc FundChannel(FundchannelRequest) returns (FundchannelResponse) {} rpc GetRoute(GetrouteRequest) returns (GetrouteResponse) {} rpc ListForwards(ListforwardsRequest) returns (ListforwardsResponse) {} @@ -1566,6 +1567,39 @@ message FeeratesOnchain_fee_estimates { uint64 htlc_success_satoshis = 5; } +message FetchinvoiceRequest { + string offer = 1; + optional Amount amount_msat = 2; + optional uint64 quantity = 3; + optional uint64 recurrence_counter = 4; + optional double recurrence_start = 5; + optional string recurrence_label = 6; + optional double timeout = 7; + optional string payer_note = 8; +} + +message FetchinvoiceResponse { + string invoice = 1; + FetchinvoiceChanges changes = 2; + optional FetchinvoiceNext_period next_period = 3; +} + +message FetchinvoiceChanges { + optional string description_appended = 1; + optional string description = 2; + optional string vendor_removed = 3; + optional string vendor = 4; + optional Amount amount_msat = 5; +} + +message FetchinvoiceNext_period { + uint64 counter = 1; + uint64 starttime = 2; + uint64 endtime = 3; + uint64 paywindow_start = 4; + uint64 paywindow_end = 5; +} + message FundchannelRequest { bytes id = 9; AmountOrAll amount = 1; diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 0e8fcc40ab90..4451bfe3db5e 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -1482,6 +1482,43 @@ impl From for pb::FeeratesResponse { } } +#[allow(unused_variables)] +impl From for pb::FetchinvoiceChanges { + fn from(c: responses::FetchinvoiceChanges) -> Self { + Self { + description_appended: c.description_appended, // Rule #2 for type string? + description: c.description, // Rule #2 for type string? + vendor_removed: c.vendor_removed, // Rule #2 for type string? + vendor: c.vendor, // Rule #2 for type string? + amount_msat: c.amount_msat.map(|f| f.into()), // Rule #2 for type msat? + } + } +} + +#[allow(unused_variables)] +impl From for pb::FetchinvoiceNextPeriod { + fn from(c: responses::FetchinvoiceNext_period) -> Self { + Self { + counter: c.counter, // Rule #2 for type u64 + starttime: c.starttime, // Rule #2 for type u64 + endtime: c.endtime, // Rule #2 for type u64 + paywindow_start: c.paywindow_start, // Rule #2 for type u64 + paywindow_end: c.paywindow_end, // Rule #2 for type u64 + } + } +} + +#[allow(unused_variables)] +impl From for pb::FetchinvoiceResponse { + fn from(c: responses::FetchinvoiceResponse) -> Self { + Self { + invoice: c.invoice, // Rule #2 for type string + changes: Some(c.changes.into()), + next_period: c.next_period.map(|v| v.into()), + } + } +} + #[allow(unused_variables)] impl From for pb::FundchannelResponse { fn from(c: responses::FundchannelResponse) -> Self { @@ -2270,6 +2307,22 @@ impl From for pb::FeeratesRequest { } } +#[allow(unused_variables)] +impl From for pb::FetchinvoiceRequest { + fn from(c: requests::FetchinvoiceRequest) -> Self { + Self { + offer: c.offer, // Rule #2 for type string + amount_msat: c.amount_msat.map(|f| f.into()), // Rule #2 for type msat? + quantity: c.quantity, // Rule #2 for type u64? + recurrence_counter: c.recurrence_counter, // Rule #2 for type u64? + recurrence_start: c.recurrence_start, // Rule #2 for type number? + recurrence_label: c.recurrence_label, // Rule #2 for type string? + timeout: c.timeout, // Rule #2 for type number? + payer_note: c.payer_note, // Rule #2 for type string? + } + } +} + #[allow(unused_variables)] impl From for pb::FundchannelRequest { fn from(c: requests::FundchannelRequest) -> Self { @@ -2983,6 +3036,22 @@ impl From for requests::FeeratesRequest { } } +#[allow(unused_variables)] +impl From for requests::FetchinvoiceRequest { + fn from(c: pb::FetchinvoiceRequest) -> Self { + Self { + offer: c.offer, // Rule #1 for type string + amount_msat: c.amount_msat.map(|a| a.into()), // Rule #1 for type msat? + quantity: c.quantity, // Rule #1 for type u64? + recurrence_counter: c.recurrence_counter, // Rule #1 for type u64? + recurrence_start: c.recurrence_start, // Rule #1 for type number? + recurrence_label: c.recurrence_label, // Rule #1 for type string? + timeout: c.timeout, // Rule #1 for type number? + payer_note: c.payer_note, // Rule #1 for type string? + } + } +} + #[allow(unused_variables)] impl From for requests::FundchannelRequest { fn from(c: pb::FundchannelRequest) -> Self { diff --git a/cln-grpc/src/server.rs b/cln-grpc/src/server.rs index c7d4e0cd0195..236b174a8e7e 100644 --- a/cln-grpc/src/server.rs +++ b/cln-grpc/src/server.rs @@ -1434,6 +1434,38 @@ async fn feerates( } +async fn fetch_invoice( + &self, + request: tonic::Request, +) -> Result, tonic::Status> { + let req = request.into_inner(); + let req: requests::FetchinvoiceRequest = req.into(); + debug!("Client asked for fetch_invoice"); + trace!("fetch_invoice 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::FetchInvoice(req)) + .await + .map_err(|e| Status::new( + Code::Unknown, + format!("Error calling method FetchInvoice: {:?}", e)))?; + match result { + Response::FetchInvoice(r) => { + trace!("fetch_invoice response: {:?}", r); + Ok(tonic::Response::new(r.into())) + }, + r => Err(Status::new( + Code::Internal, + format!( + "Unexpected result {:?} to method call FetchInvoice", + r + ) + )), + } + +} + async fn fund_channel( &self, request: tonic::Request, diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index b81080a88a4f..859e6037536a 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -58,6 +58,7 @@ pub enum Request { Decode(requests::DecodeRequest), Disconnect(requests::DisconnectRequest), Feerates(requests::FeeratesRequest), + FetchInvoice(requests::FetchinvoiceRequest), FundChannel(requests::FundchannelRequest), GetRoute(requests::GetrouteRequest), ListForwards(requests::ListforwardsRequest), @@ -123,6 +124,7 @@ pub enum Response { Decode(responses::DecodeResponse), Disconnect(responses::DisconnectResponse), Feerates(responses::FeeratesResponse), + FetchInvoice(responses::FetchinvoiceResponse), FundChannel(responses::FundchannelResponse), GetRoute(responses::GetrouteResponse), ListForwards(responses::ListforwardsResponse), @@ -1280,6 +1282,35 @@ pub mod requests { type Response = super::responses::FeeratesResponse; } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FetchinvoiceRequest { + pub offer: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub amount_msat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub quantity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence_counter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence_start: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub recurrence_label: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub payer_note: Option, + } + + impl From for Request { + fn from(r: FetchinvoiceRequest) -> Self { + Request::FetchInvoice(r) + } + } + + impl IntoRequest for FetchinvoiceRequest { + type Response = super::responses::FetchinvoiceResponse; + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FundchannelRequest { pub id: PublicKey, @@ -4666,6 +4697,48 @@ pub mod responses { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FetchinvoiceChanges { + #[serde(skip_serializing_if = "Option::is_none")] + pub description_appended: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub vendor_removed: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub vendor: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub amount_msat: Option, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FetchinvoiceNext_period { + pub counter: u64, + pub starttime: u64, + pub endtime: u64, + pub paywindow_start: u64, + pub paywindow_end: u64, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct FetchinvoiceResponse { + pub invoice: String, + pub changes: FetchinvoiceChanges, + #[serde(skip_serializing_if = "Option::is_none")] + pub next_period: Option, + } + + impl TryFrom for FetchinvoiceResponse { + type Error = super::TryFromResponseError; + + fn try_from(response: Response) -> Result { + match response { + Response::FetchInvoice(response) => Ok(response), + _ => Err(TryFromResponseError) + } + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FundchannelResponse { pub tx: String, diff --git a/contrib/msggen/msggen/utils/utils.py b/contrib/msggen/msggen/utils/utils.py index ab52b1e715b6..c73af7a8bcba 100644 --- a/contrib/msggen/msggen/utils/utils.py +++ b/contrib/msggen/msggen/utils/utils.py @@ -73,7 +73,7 @@ def load_jsonrpc_service(schema_dir: str): # "disableoffer", "Disconnect", "Feerates", - # "fetchinvoice", + "FetchInvoice", # "fundchannel_cancel", # "fundchannel_complete", "FundChannel", diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py index c519c35101e6..147de6a301cc 100644 --- a/contrib/pyln-testing/pyln/testing/grpc2py.py +++ b/contrib/pyln-testing/pyln/testing/grpc2py.py @@ -1268,6 +1268,32 @@ def feerates2py(m): }) +def fetchinvoice_changes2py(m): + return remove_default({ + "description_appended": m.description_appended, # PrimitiveField in generate_composite + "description": m.description, # PrimitiveField in generate_composite + "vendor_removed": m.vendor_removed, # PrimitiveField in generate_composite + "vendor": m.vendor, # PrimitiveField in generate_composite + "amount_msat": amount2msat(m.amount_msat), # PrimitiveField in generate_composite + }) + + +def fetchinvoice_next_period2py(m): + return remove_default({ + "counter": m.counter, # PrimitiveField in generate_composite + "starttime": m.starttime, # PrimitiveField in generate_composite + "endtime": m.endtime, # PrimitiveField in generate_composite + "paywindow_start": m.paywindow_start, # PrimitiveField in generate_composite + "paywindow_end": m.paywindow_end, # PrimitiveField in generate_composite + }) + + +def fetchinvoice2py(m): + return remove_default({ + "invoice": m.invoice, # PrimitiveField in generate_composite + }) + + def fundchannel2py(m): return remove_default({ "tx": hexlify(m.tx), # PrimitiveField in generate_composite diff --git a/doc/schemas/fetchinvoice.request.json b/doc/schemas/fetchinvoice.request.json new file mode 100644 index 000000000000..d3f293d4ab61 --- /dev/null +++ b/doc/schemas/fetchinvoice.request.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "offer" + ], + "additionalProperties": false, + "properties": { + "offer": { + "type": "string", + "description": "" + }, + "amount_msat": { + "type": "msat", + "description": "amount_msat is required if the offer does not specify an amount at all, otherwise it is optional (but presumably if you set it to less than the offer, you will get an error from the issuer)." + }, + "quantity": { + "type": "u64", + "description": "quantity is is required if the offer specifies quantity_max, otherwise it is not allowed." + }, + "recurrence_counter": { + "type": "u64", + "description": "recurrence_counter is required if the offer specifies recurrence, otherwise it is not allowed. recurrence_counter should first be set to 0, and incremented for each successive invoice in a given series." + }, + "recurrence_start": { + "type": "number", + "description": "recurrence_start is required if the offer specifies recurrence_base with start_any_period set, otherwise it is not allowed. It indicates what period number to start at." + }, + "recurrence_label": { + "type": "string", + "description": "recurrence_label is required if recurrence_counter is set, and otherwise is not allowed. It must be the same as prior fetchinvoice calls for the same recurrence, as it is used to link them together." + }, + "timeout": { + "type": "number", + "description": "timeout is an optional timeout; if we don't get a reply before this we fail (default, 60 seconds)." + }, + "payer_note": { + "type": "string", + "description": "payer_note is an optional payer note to ask the issuer to include in the fetched invoice." + } + } +}