Skip to content

Commit

Permalink
refactor: clearer invoice parsing errors (#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 authored Jan 4, 2025
1 parent 9161e6a commit 6db48fc
Showing 1 changed file with 44 additions and 24 deletions.
68 changes: 44 additions & 24 deletions boltzr/src/lightning/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ use std::error::Error;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

type DecodeFunction = fn(&str) -> Result<Invoice, InvoiceError>;

const BECH32_BOLT12_INVOICE_HRP: &str = "lni";

const DECODE_FUNCS: &[DecodeFunction] =
&[decode_bolt11, decode_bolt12_offer, decode_bolt12_invoice];

#[derive(Debug, PartialEq)]
pub enum InvoiceError {
InvalidNetwork,
InvalidInvariant,
DecodeError(String),
}

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),
InvoiceError::DecodeError(data) => write!(f, "invalid invoice: {}", data),
}
}
}
Expand Down Expand Up @@ -71,45 +74,53 @@ pub fn decode(network: wallet::Network, invoice_or_offer: &str) -> Result<Invoic
}

fn parse(invoice_or_offer: &str) -> Result<Invoice, InvoiceError> {
if let Ok(invoice) = decode_bolt11(invoice_or_offer) {
return Ok(invoice);
}

if let Ok(invoice) = decode_bolt12_offer(invoice_or_offer) {
return Ok(invoice);
}

if let Ok(invoice) = decode_bolt12_invoice(invoice_or_offer) {
return Ok(invoice);
let mut first_error: Option<InvoiceError> = None;

for func in DECODE_FUNCS {
match func(invoice_or_offer) {
Ok(res) => return Ok(res),
Err(err) => {
if first_error.is_none() {
first_error.replace(err);
}
}
}
}

Err(InvoiceError::InvalidInvariant)
Err(first_error.unwrap_or(InvoiceError::DecodeError("could not decode".to_string())))
}

fn decode_bolt12_offer(offer: &str) -> anyhow::Result<Invoice> {
fn decode_bolt12_offer(offer: &str) -> Result<Invoice, InvoiceError> {
match lightning::offers::offer::Offer::from_str(offer) {
Ok(offer) => Ok(Invoice::Offer(Box::new(offer))),
Err(err) => Err(InvoiceError::DecodeError(format!("{:?}", err)).into()),
Err(err) => Err(InvoiceError::DecodeError(format!("{:?}", err))),
}
}

fn decode_bolt12_invoice(invoice: &str) -> anyhow::Result<Invoice> {
let (hrp, data) = bech32::decode_without_checksum(invoice)?;
fn decode_bolt12_invoice(invoice: &str) -> Result<Invoice, InvoiceError> {
let (hrp, data) = match bech32::decode_without_checksum(invoice) {
Ok(dec) => dec,
Err(err) => return Err(InvoiceError::DecodeError(format!("{:?}", err))),
};
if hrp != BECH32_BOLT12_INVOICE_HRP {
return Err(InvoiceError::DecodeError("invalid HRP".to_string()).into());
return Err(InvoiceError::DecodeError("invalid HRP".to_string()));
}

let data = Vec::<u8>::from_base32(&data)?;
let data = match Vec::<u8>::from_base32(&data) {
Ok(dec) => dec,
Err(err) => return Err(InvoiceError::DecodeError(format!("{:?}", err))),
};

match lightning::offers::invoice::Bolt12Invoice::try_from(data) {
Ok(invoice) => Ok(Invoice::Bolt12(Box::new(invoice))),
Err(err) => Err(InvoiceError::DecodeError(format!("{:?}", err)).into()),
Err(err) => Err(InvoiceError::DecodeError(format!("{:?}", err))),
}
}

fn decode_bolt11(invoice: &str) -> anyhow::Result<Invoice> {
fn decode_bolt11(invoice: &str) -> Result<Invoice, InvoiceError> {
match lightning_invoice::Bolt11Invoice::from_str(invoice) {
Ok(invoice) => Ok(Invoice::Bolt11(Box::new(invoice))),
Err(err) => Err(InvoiceError::DecodeError(format!("{:?}", err)).into()),
Err(err) => Err(InvoiceError::DecodeError(format!("{:?}", err))),
}
}

Expand Down Expand Up @@ -144,7 +155,16 @@ mod test {

assert_eq!(
decode(wallet::Network::Regtest, "invalid").err().unwrap(),
InvoiceError::InvalidInvariant
InvoiceError::DecodeError("ParseError(Bech32Error(MissingSeparator))".to_string())
);
}

#[test]
fn test_decode_amp_invoice() {
let invoice = "lnbc10n1pnhs4wjpp5w09tfff6a4l4dqvy40rr284l3uxccadgnzkep4fh2v4cs9yracmsdqqcqzzsxq9z0rgqsp52vckh4k525a2vlkdy77w7c5940t9lfj34ythh8s9ckmdx3q2zfpq9q8pqqqsgqv07zpwlccsq23ry8ptzqjy5vwcatgg0y6490y5udkzcpgjhmr449hqpdxapjnsu3vucnav040sqg5a9rg5cxp5y4e50mhh8keluhf2spwyry73";
assert_eq!(
decode(wallet::Network::Mainnet, invoice).unwrap_err(),
InvoiceError::DecodeError("SemanticError(InvalidFeatures)".to_string())
);
}

Expand Down

0 comments on commit 6db48fc

Please sign in to comment.