diff --git a/.msggen.json b/.msggen.json index 9e553c569..60de060a5 100644 --- a/.msggen.json +++ b/.msggen.json @@ -774,6 +774,7 @@ }, "WithdrawRequest": { "Withdraw.destination": 1, + "Withdraw.feerate": 5, "Withdraw.minconf": 3, "Withdraw.satoshi": 2, "Withdraw.utxos[]": 4 diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index fc26237dd..a2023b1e6 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -904,6 +904,7 @@ message NewaddrResponse { message WithdrawRequest { bytes destination = 1; optional Amount satoshi = 2; + optional Feerate feerate = 5; optional uint32 minconf = 3; repeated Utxo utxos = 4; } diff --git a/cln-grpc/proto/primitives.proto b/cln-grpc/proto/primitives.proto index bbcde1c7d..b440936d3 100644 --- a/cln-grpc/proto/primitives.proto +++ b/cln-grpc/proto/primitives.proto @@ -29,4 +29,14 @@ message ChannelStateChangeCause {} message Utxo { bytes txid = 1; uint32 outnum = 2; +} + +message Feerate { + oneof style { + bool slow = 1; + bool normal = 2; + bool urgent = 3; + uint32 perkb = 4; + uint32 perkw = 5; + } } \ No newline at end of file diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 58b78b3f9..907cac989 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -1052,6 +1052,7 @@ impl From<&pb::WithdrawRequest> for requests::WithdrawRequest { Self { destination: hex::encode(&c.destination), // Rule #1 for type pubkey satoshi: c.satoshi.as_ref().map(|a| a.into()), // Rule #1 for type msat? + feerate: c.feerate.as_ref().map(|a| a.into()), // Rule #1 for type feerate? minconf: c.minconf.map(|v| v as u16), // Rule #1 for type u16? utxos: c.utxos.iter().map(|s| s.into()).collect(), } diff --git a/cln-grpc/src/pb.rs b/cln-grpc/src/pb.rs index ae8e8bc4b..12ae1d117 100644 --- a/cln-grpc/src/pb.rs +++ b/cln-grpc/src/pb.rs @@ -31,3 +31,17 @@ impl From<&Utxo> for JUtxo { } } } + +impl From<&Feerate> for cln_rpc::primitives::Feerate { + fn from(f: &Feerate) -> cln_rpc::primitives::Feerate { + use cln_rpc::primitives::Feerate as JFeerate; + use feerate::Style; + match f.style.clone().unwrap() { + Style::Slow(_) => JFeerate::Slow, + Style::Normal(_) => JFeerate::Normal, + Style::Urgent(_) => JFeerate::Urgent, + Style::Perkw(i) => JFeerate::PerKw(i), + Style::Perkb(i) => JFeerate::PerKb(i), + } + } +} diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index ac81bc0da..620b30e5e 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -464,6 +464,8 @@ pub mod requests { pub destination: String, #[serde(alias = "satoshi", skip_serializing_if = "Option::is_none")] pub satoshi: Option, + #[serde(alias = "feerate", skip_serializing_if = "Option::is_none")] + pub feerate: Option, #[serde(alias = "minconf", skip_serializing_if = "Option::is_none")] pub minconf: Option, #[serde(alias = "utxos")] diff --git a/cln-rpc/src/primitives.rs b/cln-rpc/src/primitives.rs index 8f7daacf7..355e42142 100644 --- a/cln-rpc/src/primitives.rs +++ b/cln-rpc/src/primitives.rs @@ -218,6 +218,77 @@ impl From for String { } } +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Feerate { + Slow, + Normal, + Urgent, + PerKb(u32), + PerKw(u32), +} + +impl TryFrom<&str> for Feerate { + type Error = Error; + fn try_from(s: &str) -> Result { + let number: u32 = s + .chars() + .map(|c| c.to_digit(10)) + .take_while(|opt| opt.is_some()) + .fold(0, |acc, digit| acc * 10 + (digit.unwrap() as u32)); + + let s = s.to_lowercase(); + if s.ends_with("perkw") { + Ok(Feerate::PerKw(number)) + } else if s.ends_with("perkb") { + Ok(Feerate::PerKb(number)) + } else if s == "slow" { + Ok(Feerate::Slow) + } else if s == "normal" { + Ok(Feerate::Normal) + } else if s == "urgent" { + Ok(Feerate::Urgent) + } else { + Err(anyhow!("Unable to parse feerate from string: {}", s)) + } + } +} + +impl From<&Feerate> for String { + fn from(f: &Feerate) -> String { + match f { + Feerate::Slow => "slow".to_string(), + Feerate::Normal => "normal".to_string(), + Feerate::Urgent => "urgent".to_string(), + Feerate::PerKb(v) => format!("{}perkb", v), + Feerate::PerKw(v) => format!("{}perkw", v), + } + } +} + +impl<'de> Deserialize<'de> for Feerate { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + let res: Feerate = s + .as_str() + .try_into() + .map_err(|e| serde::de::Error::custom(format!("{}", e)))?; + Ok(res) + } +} + +impl Serialize for Feerate { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s: String = self.into(); + serializer.serialize_str(&s) + } +} + #[cfg(test)] mod test { use super::*; @@ -283,4 +354,22 @@ mod test { r#"{"all":"all","not_all":"31337msat","any":"any","not_any":"42msat"}"# ); } + + #[test] + fn test_parse_feerate() { + let tests = vec![ + ("slow", Feerate::Slow), + ("normal", Feerate::Normal), + ("urgent", Feerate::Urgent), + ("12345perkb", Feerate::PerKb(12345)), + ("54321perkw", Feerate::PerKw(54321)), + ]; + + for (input, output) in tests.into_iter() { + let parsed: Feerate = input.try_into().unwrap(); + assert_eq!(parsed, output); + let serialized: String = (&parsed).into(); + assert_eq!(serialized, input); + } + } } diff --git a/contrib/msggen/msggen/grpc.py b/contrib/msggen/msggen/grpc.py index ad1f28181..0e093d19f 100644 --- a/contrib/msggen/msggen/grpc.py +++ b/contrib/msggen/msggen/grpc.py @@ -23,6 +23,7 @@ typemap = { 'f32': 'float', 'integer': 'sint64', "utxo": "Utxo", + "feerate": "Feerate", } @@ -394,6 +395,7 @@ class GrpcUnconverterGenerator(GrpcConverterGenerator): 'pubkey?': f'c.{name}.clone().map(|v| hex::encode(v))', 'msat': f'c.{name}.as_ref().unwrap().into()', 'msat?': f'c.{name}.as_ref().map(|a| a.into())', + 'feerate?': f'c.{name}.as_ref().map(|a| a.into())', }.get( typ, f'c.{name}.clone()' # default to just assignment diff --git a/contrib/msggen/msggen/model.py b/contrib/msggen/msggen/model.py index a08388859..532a8ec91 100644 --- a/contrib/msggen/msggen/model.py +++ b/contrib/msggen/msggen/model.py @@ -230,6 +230,7 @@ class PrimitiveField(Field): "integer", "u16", "number", + "feerate", "utxo", # A string representing the tuple (txid, outnum) ] diff --git a/contrib/msggen/msggen/rust.py b/contrib/msggen/msggen/rust.py index bb7a64064..ea5aaf3cc 100644 --- a/contrib/msggen/msggen/rust.py +++ b/contrib/msggen/msggen/rust.py @@ -40,6 +40,7 @@ typemap = { 'txid': 'String', 'float': 'f32', 'utxo': 'Utxo', + 'feerate': 'Feerate', } header = f"""#![allow(non_camel_case_types)]