From 5719736b63ba6944fec36f98745bffe82be979bd Mon Sep 17 00:00:00 2001 From: michael1011 Date: Sat, 2 Nov 2024 17:26:03 +0100 Subject: [PATCH] feat: nicer invoice network check error (#704) --- boltzr/src/currencies.rs | 4 ++ boltzr/src/grpc/service.rs | 9 ++- boltzr/src/lightning/invoice.rs | 60 +++++++++++++++++-- boltzr/src/swap/filters.rs | 3 + .../sidecar/DecodedInvoice.spec.ts | 20 ++----- 5 files changed, 75 insertions(+), 21 deletions(-) diff --git a/boltzr/src/currencies.rs b/boltzr/src/currencies.rs index 59a1f20c..182f1107 100644 --- a/boltzr/src/currencies.rs +++ b/boltzr/src/currencies.rs @@ -11,6 +11,8 @@ use tracing::{debug, warn}; #[derive(Clone)] pub struct Currency { + pub network: wallet::Network, + pub wallet: Arc, pub chain: Option>>, @@ -36,6 +38,7 @@ pub async fn connect_nodes( curs.insert( currency.symbol.clone(), Currency { + network, wallet: Arc::new(wallet::Bitcoin::new(network)), chain: match currency.chain { Some(config) => { @@ -78,6 +81,7 @@ pub async fn connect_nodes( curs.insert( crate::chain::elements_client::SYMBOL.to_string(), Currency { + network, cln: None, chain: Some(Arc::new(Box::new(chain))), wallet: Arc::new(wallet::Elements::new(network)), diff --git a/boltzr/src/grpc/service.rs b/boltzr/src/grpc/service.rs index 24077623..4acf5435 100644 --- a/boltzr/src/grpc/service.rs +++ b/boltzr/src/grpc/service.rs @@ -418,7 +418,14 @@ where ) -> Result, Status> { extract_parent_context(&request); - match crate::lightning::invoice::decode(&request.into_inner().invoice_or_offer) { + let network = match self.manager.get_currency("BTC") { + Some(cur) => cur.network, + None => { + return Err(Status::new(Code::Internal, "BTC currency not configured")); + } + }; + + match crate::lightning::invoice::decode(network, &request.into_inner().invoice_or_offer) { Ok(dec) => Ok(Response::new(match dec { Invoice::Bolt11(invoice) => DecodeInvoiceOrOfferResponse { is_expired: invoice.is_expired(), diff --git a/boltzr/src/lightning/invoice.rs b/boltzr/src/lightning/invoice.rs index 70d7735a..50ff217e 100644 --- a/boltzr/src/lightning/invoice.rs +++ b/boltzr/src/lightning/invoice.rs @@ -1,4 +1,6 @@ +use crate::wallet; use bech32::FromBase32; +use bitcoin::constants::ChainHash; use std::error::Error; use std::fmt::{Display, Formatter}; use std::str::FromStr; @@ -7,6 +9,7 @@ const BECH32_BOLT12_INVOICE_HRP: &str = "lni"; #[derive(Debug, PartialEq)] pub enum InvoiceError { + InvalidNetwork, InvalidInvariant, DecodeError(String), } @@ -14,6 +17,7 @@ pub enum InvoiceError { impl Display for InvoiceError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + InvoiceError::InvalidNetwork => write!(f, "invalid network"), InvoiceError::InvalidInvariant => write!(f, "invalid invariant"), InvoiceError::DecodeError(data) => write!(f, "could not parse invoice: {}", data), } @@ -29,7 +33,36 @@ pub enum Invoice { Bolt12(Box), } -pub fn decode(invoice_or_offer: &str) -> Result { +impl Invoice { + fn is_for_network(&self, network: wallet::Network) -> bool { + let chain_hash = Self::network_to_chain_hash(network); + + match self { + Invoice::Bolt11(invoice) => invoice.network().chain_hash() == chain_hash, + Invoice::Offer(offer) => offer.supports_chain(chain_hash), + Invoice::Bolt12(invoice) => invoice.chain() == chain_hash, + } + } + + fn network_to_chain_hash(network: wallet::Network) -> ChainHash { + match network { + wallet::Network::Mainnet => ChainHash::BITCOIN, + wallet::Network::Testnet => ChainHash::TESTNET, + wallet::Network::Regtest => ChainHash::REGTEST, + } + } +} + +pub fn decode(network: wallet::Network, invoice_or_offer: &str) -> Result { + let invoice = parse(invoice_or_offer)?; + if !invoice.is_for_network(network) { + return Err(InvoiceError::InvalidNetwork); + } + + Ok(invoice) +} + +fn parse(invoice_or_offer: &str) -> Result { if let Ok(invoice) = decode_bolt11(invoice_or_offer) { return Ok(invoice); } @@ -77,6 +110,7 @@ mod test { use crate::lightning::invoice::{ decode, decode_bolt11, decode_bolt12_invoice, decode_bolt12_offer, Invoice, InvoiceError, }; + use crate::wallet; use bech32::FromBase32; use std::str::FromStr; @@ -87,24 +121,40 @@ mod test { #[test] fn test_decode() { assert_eq!( - decode(BOLT12_OFFER).unwrap(), + decode(wallet::Network::Regtest, BOLT12_OFFER).unwrap(), decode_bolt12_offer(BOLT12_OFFER).unwrap() ); assert_eq!( - decode(BOLT12_INVOICE).unwrap(), + decode(wallet::Network::Regtest, BOLT12_INVOICE).unwrap(), decode_bolt12_invoice(BOLT12_INVOICE).unwrap() ); assert_eq!( - decode(BOLT11_INVOICE).unwrap(), + decode(wallet::Network::Regtest, BOLT11_INVOICE).unwrap(), decode_bolt11(BOLT11_INVOICE).unwrap() ); assert_eq!( - decode("invalid").err().unwrap(), + decode(wallet::Network::Regtest, "invalid").err().unwrap(), InvoiceError::InvalidInvariant ); } + #[test] + fn test_decode_invalid_network() { + assert_eq!( + decode(wallet::Network::Regtest, "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqqsespexwyy4tcadvgg89l9aljus6709kx235hhqrk6n8dey98uyuftzdqrvfp0pxcmv8l8txqssq8cm7hrd8ja0ucz0cexwg22l305307pxp3qyq7heen7fpurds7kqhm0h8pk5hghv84wz53rh3yfgmahp7ddlyv4gyqrx55lhnfh5ld0pf8gatyf9x4m50mg3zex4hkq4ehysrs7ptnh4j4mwc5qqrt9rlu78elnkly3qw4g0my8mtcz08jhsdk0a7vz2mx52hu6qxw7q86nhggm335n8ef39e5vrm9gd4xqqy8tm2ptwy2xw8scz3jl9djen0t6").err().unwrap(), + InvoiceError::InvalidNetwork, + ); + assert_eq!( + decode(wallet::Network::Regtest, "lni1qqgzm8segjwvfcwp2a22ah30heyn6q3qgdyhl4lcy62hzz855v8annkr46a8n9eqsn5satgpagesjqqqqqqppnqrjvugf2h366csswt7tml9ep4u7tvv4rf0wq8d4xwmjg20cfcjky6qxcjz7zd3kc07wkvppqq03hawx6096lesyl3jvus54lzlfzluzvrzqgpa0nn8ujrcxmpavp0klwwrdfw3wc02u9fz80zgj3hmwru6m7ge2sgqxdffl0xn0f767zjw36kgj2dthglk3z9jdt0vptnwfq8puzh80t9tka3gqqxk28leu0nl8d7fzqa2slkg0khsy7090qmvlmucy4kdg40e5qvauq048ws3hrrfx0jnztngc8k2sm2vqqgwhk5zkug5vu0ps9r972m9nx7h55pqgdyhl4lcy62hzz855v8annkr46a8n9eqsn5satgpagesjqqqqqq9yqc0gfq9sggrhyqf72htl64lc7kr5g8zwuq4elrkr96zvtllvathlnxfvl86vjs6plgpngpexwyy4tcadvgg89l9aljus6709kx235hhqrk6n8dey98uyuftzdqz79jnp0yxdj3kqcajj9w0ept5m002j3lqkjszg2hc3qakcaafc6wsyqhfll8m4yx5l3nemd2taaslyuzvnjkqaw895yzllt0wpv5wg5z3msqy24sutfy7nvkg6rwqsxwgj60e3sa9vqa9lw8ffgnu8wwfdd2rxatgynm5xgy2zrs9l8wezyu3raenejlu453ln8xztjydnk8c7x0s4dgweq0gvmkq8hqgnt2w0jljkzq7x5nhcdju9zlg2xqgh3n8uz4t04syrm27dhlyqrxd3qeyetuvfkjhklsn64r2tc4ze3eswywvnr43g39mk0ayy9ldz6zwndlmgws0e5wfmfr2mzm0cqa4xwlyz7eqmfekykzka3fz3n5y9tyj5x33xalqn7hpl5k4y0u8fm64sfrdfzxtst4e9yxcrrexg5cfxmadc2jc2q36dqvl5896zj2le55ffyhva6jgm57wqh6nwua0fzhapjvy2r9zdn35qcfsj33tnl393ajzhp6q7mx3ck2wd3k5jkg82gru4v6ls9l9wkhtf5r24lj9pnk3c99zk7a5xncc870pcr6zm2uszk4d5avp97et68uvlv4ev73pcqqqq05qqqqqvsqjqqqqqqqqqqqraqqqqqqqqq0gfqqqqzjqgeexfx72vqcp2xq2sgzv7n8srl73msjm7zcp8sn6xx9fcgq7w3jk0fgehqct0s7cdm4u0j4qxr6zgzhqxqsqqzczzqneu4urdnl0nqjke4z4lxspnhsp75a6zxuvdye72vfwdrq7e2rdfncypw3kr7xnqf8xynne80kkz2sczhzm8gwq49r8e92h4jfpkzlwxcfaessde00f5k5c4z69pjqm0nq6rh70ru8kc4gm9tfrhj4zr7t7egms").err().unwrap(), + InvoiceError::InvalidNetwork, + ); + assert_eq!( + decode(wallet::Network::Regtest, "lntb1101010n1pnjvjnzsp5ad9y769h0y6vge68cegs8ft62mh858228accp0zq0pqgjzw7756spp5snujw7nydyxu44sf6c79n0tzmd2g79nkfl5uwc53zrf9satrcplqdp6tddjyar90p6z7urvv95kug3vyfwzysn0d3685gr5v4ehgmn9w3wzygjat5xqyjw5qcqp2rzjqgjmhs48xsve8n2e94ascxzmhrrrt8xpm5fn096ud4qn2nj8qwlkgtzq7vqqppcqqqqqqqw0qqqqrncqqc9qxpqysgqfc9a554d2mg6ljxyg8axfwunejtfhglrw9m67e90pec6520jjukxpjnx8m8484fenusg2697w42ec4ypk7wk86z62rms5uym6l5vh0gq7t50m6").err().unwrap(), + InvoiceError::InvalidNetwork, + ); + } + #[test] fn test_decode_bolt12_offer() { let res = decode_bolt12_offer(BOLT12_OFFER).unwrap(); diff --git a/boltzr/src/swap/filters.rs b/boltzr/src/swap/filters.rs index c6936ff3..2fb0e447 100644 --- a/boltzr/src/swap/filters.rs +++ b/boltzr/src/swap/filters.rs @@ -272,6 +272,7 @@ mod test { ( String::from("BTC"), Currency { + network: Network::Regtest, wallet: Arc::new(Bitcoin::new(Network::Regtest)), chain: Some(Arc::new(Box::new( crate::chain::chain_client::test::get_client(), @@ -282,6 +283,7 @@ mod test { ( String::from("LTC"), Currency { + network: Network::Regtest, wallet: Arc::new(Bitcoin::new(Network::Regtest)), chain: None, cln: None, @@ -290,6 +292,7 @@ mod test { ( String::from("L-BTC"), Currency { + network: Network::Regtest, wallet: Arc::new(Elements::new(Network::Regtest)), chain: Some(Arc::new(Box::new( crate::chain::elements_client::test::get_client().0, diff --git a/test/integration/sidecar/DecodedInvoice.spec.ts b/test/integration/sidecar/DecodedInvoice.spec.ts index 2e731ff8..2083b858 100644 --- a/test/integration/sidecar/DecodedInvoice.spec.ts +++ b/test/integration/sidecar/DecodedInvoice.spec.ts @@ -108,28 +108,18 @@ describe('DecodedInvoice', () => { test('should decode routing info', async () => { const dec = await sidecar.decodeInvoiceOrOffer( - 'lnbc1p023g0zpp5rrr09tcxfymsyxgywe0vpeqzt8ppc7dzlme9e0wa3qqch0fpt8tsdqqxqrrss9qy9qsqsp56xpafe94rfkt5qtc00lua7pwem9znvvq4en9sr2t24kmdq4ll2mqrzjqt3xwz3vyes6nm4p8d70mnwh74f0tydeaesw2eut02l80dle29hevz905gqqjdsqqqqqqqlgqqqqqeqqjqrzjqfsktpgyjffp7jkg40vmmqygzg6yd5fx7eyv5d0xp7ypwlwpf88tyzx0ccqq8msqqqqqqqlgqqqqqeqqjqtk44jdc0f78c6cg8jd02889jud0phxea7nxtj7sue7ft44daf9nye99ekujxxgkgw82t0kxfwetxp9vs5rt54lkfd35vjle0sexhv2qqpv7aq4', + 'lnbcrt10n1pnjvncnpp5hguramzhrx50q5up3e92h2kkxctmeem5xtt2hh75cf66lm336nqsdqqcqzzsxqyz5vqrzjq03gty986tnezs8azr9x92wp3s76l789n9qy0tn3ttsgwdce7ya2cqqqjsqqqqgqqqqqqqlgqqqqqqgq2qsp5za8lwlcdpvw5xcnvjgx05rmt2kl37e4qdegs607nusq7zce7vr2s9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq8hexd68jdvz0m4mqyy23evuuenygppqe9j4tgnvty6ea83mall68tar53zx9tllmk0jfg69f3nvxru34w8d76ksr8un3ua4ks9utuwgqudggxx', ); expect(dec.routingHints).toEqual([ [ { feeBaseMsat: 1000, - cltvExpiryDelta: 144, - chanId: '625896994266021900', - feeProportionalMillionths: 100, + cltvExpiryDelta: 80, + chanId: '162727720976384', + feeProportionalMillionths: 1, nodeId: - '02e2670a2c2661a9eea13b7cfdcdd7f552f591b9ee60e5678b7abe77b7f9516f96', - }, - ], - [ - { - feeBaseMsat: 1000, - cltvExpiryDelta: 144, - chanId: '634943775850758100', - feeProportionalMillionths: 100, - nodeId: - '026165850492521f4ac8abd9bd8088123446d126f648ca35e60f88177dc149ceb2', + '03e28590a7d2e79140fd10ca62a9c18c3daff8e5994047ae715ae0873719f13aac', }, ], ]);