mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-02 19:55:56 +01:00
refactor(cdk): defer BOLT12 invoice fetching to payment execution (#978)
* refactor(cdk): defer BOLT12 invoice fetching to payment execution Move BOLT12 invoice generation from quote creation to payment time, make request_lookup_id optional for offers, and simplify payment structures by removing unnecessary boxing and intermediate storage of invoices.
This commit is contained in:
@@ -307,13 +307,12 @@ impl MintPayment for Cln {
|
||||
let fee = max(relative_fee_reserve, absolute_fee_reserve);
|
||||
|
||||
Ok(PaymentQuoteResponse {
|
||||
request_lookup_id: PaymentIdentifier::PaymentHash(
|
||||
request_lookup_id: Some(PaymentIdentifier::PaymentHash(
|
||||
*bolt11_options.bolt11.payment_hash().as_ref(),
|
||||
),
|
||||
)),
|
||||
amount,
|
||||
fee: fee.into(),
|
||||
state: MeltQuoteState::Unpaid,
|
||||
options: None,
|
||||
unit: unit.clone(),
|
||||
})
|
||||
}
|
||||
@@ -341,52 +340,11 @@ impl MintPayment for Cln {
|
||||
let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
|
||||
let fee = max(relative_fee_reserve, absolute_fee_reserve);
|
||||
|
||||
let cln_response;
|
||||
{
|
||||
// Fetch invoice from offer
|
||||
let mut cln_client = self.cln_client().await?;
|
||||
|
||||
cln_response = cln_client
|
||||
.call_typed(&FetchinvoiceRequest {
|
||||
amount_msat: Some(CLN_Amount::from_msat(amount_msat)),
|
||||
payer_metadata: None,
|
||||
payer_note: None,
|
||||
quantity: None,
|
||||
recurrence_counter: None,
|
||||
recurrence_label: None,
|
||||
recurrence_start: None,
|
||||
timeout: None,
|
||||
offer: offer.to_string(),
|
||||
bip353: None,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Could not fetch invoice for offer: {:?}", err);
|
||||
Error::ClnRpc(err)
|
||||
})?;
|
||||
}
|
||||
|
||||
let decode_response = self.decode_string(cln_response.invoice.clone()).await?;
|
||||
|
||||
let options = payment::PaymentQuoteOptions::Bolt12 {
|
||||
invoice: Some(cln_response.invoice.into()),
|
||||
};
|
||||
|
||||
Ok(PaymentQuoteResponse {
|
||||
request_lookup_id: PaymentIdentifier::Bolt12PaymentHash(
|
||||
hex::decode(
|
||||
decode_response
|
||||
.invoice_payment_hash
|
||||
.ok_or(Error::UnknownInvoice)?,
|
||||
)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidHash)?,
|
||||
),
|
||||
request_lookup_id: None,
|
||||
amount,
|
||||
fee: fee.into(),
|
||||
state: MeltQuoteState::Unpaid,
|
||||
options: Some(options),
|
||||
unit: unit.clone(),
|
||||
})
|
||||
}
|
||||
@@ -403,7 +361,9 @@ impl MintPayment for Cln {
|
||||
let mut partial_amount: Option<u64> = None;
|
||||
let mut amount_msat: Option<u64> = None;
|
||||
|
||||
let invoice = match options {
|
||||
let mut cln_client = self.cln_client().await?;
|
||||
|
||||
let invoice = match &options {
|
||||
OutgoingPaymentOptions::Bolt11(bolt11_options) => {
|
||||
let payment_identifier =
|
||||
PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
|
||||
@@ -424,10 +384,42 @@ impl MintPayment for Cln {
|
||||
bolt11_options.bolt11.to_string()
|
||||
}
|
||||
OutgoingPaymentOptions::Bolt12(bolt12_options) => {
|
||||
let bolt12_invoice = bolt12_options.invoice.ok_or(Error::UnknownInvoice)?;
|
||||
let decode_response = self
|
||||
.decode_string(String::from_utf8(bolt12_invoice.clone()).map_err(Error::Utf8)?)
|
||||
.await?;
|
||||
let offer = &bolt12_options.offer;
|
||||
|
||||
let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
|
||||
amount.amount_msat().into()
|
||||
} else {
|
||||
// Fall back to offer amount
|
||||
let decode_response = self.decode_string(offer.to_string()).await?;
|
||||
|
||||
decode_response
|
||||
.offer_amount_msat
|
||||
.ok_or(Error::UnknownInvoiceAmount)?
|
||||
.msat()
|
||||
};
|
||||
|
||||
// Fetch invoice from offer
|
||||
|
||||
let cln_response = cln_client
|
||||
.call_typed(&FetchinvoiceRequest {
|
||||
amount_msat: Some(CLN_Amount::from_msat(amount_msat)),
|
||||
payer_metadata: None,
|
||||
payer_note: None,
|
||||
quantity: None,
|
||||
recurrence_counter: None,
|
||||
recurrence_label: None,
|
||||
recurrence_start: None,
|
||||
timeout: None,
|
||||
offer: offer.to_string(),
|
||||
bip353: None,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Could not fetch invoice for offer: {:?}", err);
|
||||
Error::ClnRpc(err)
|
||||
})?;
|
||||
|
||||
let decode_response = self.decode_string(cln_response.invoice.clone()).await?;
|
||||
|
||||
let payment_identifier = PaymentIdentifier::Bolt12PaymentHash(
|
||||
hex::decode(
|
||||
@@ -443,12 +435,11 @@ impl MintPayment for Cln {
|
||||
self.check_outgoing_unpaided(&payment_identifier).await?;
|
||||
|
||||
max_fee_msat = bolt12_options.max_fee_amount.map(|a| a.into());
|
||||
String::from_utf8(bolt12_invoice).map_err(Error::Utf8)?
|
||||
|
||||
cln_response.invoice
|
||||
}
|
||||
};
|
||||
|
||||
let mut cln_client = self.cln_client().await?;
|
||||
|
||||
let cln_response = cln_client
|
||||
.call_typed(&PayRequest {
|
||||
bolt11: invoice,
|
||||
@@ -475,8 +466,14 @@ impl MintPayment for Cln {
|
||||
PayStatus::FAILED => MeltQuoteState::Failed,
|
||||
};
|
||||
|
||||
let payment_identifier =
|
||||
PaymentIdentifier::PaymentHash(*pay_response.payment_hash.as_ref());
|
||||
let payment_identifier = match options {
|
||||
OutgoingPaymentOptions::Bolt11(_) => {
|
||||
PaymentIdentifier::PaymentHash(*pay_response.payment_hash.as_ref())
|
||||
}
|
||||
OutgoingPaymentOptions::Bolt12(_) => {
|
||||
PaymentIdentifier::Bolt12PaymentHash(*pay_response.payment_hash.as_ref())
|
||||
}
|
||||
};
|
||||
|
||||
MakePaymentResponse {
|
||||
payment_proof: Some(hex::encode(pay_response.payment_preimage.to_vec())),
|
||||
|
||||
@@ -246,7 +246,7 @@ pub struct MeltQuote {
|
||||
/// Payment preimage
|
||||
pub payment_preimage: Option<String>,
|
||||
/// Value used by ln backend to look up state of request
|
||||
pub request_lookup_id: PaymentIdentifier,
|
||||
pub request_lookup_id: Option<PaymentIdentifier>,
|
||||
/// Payment options
|
||||
///
|
||||
/// Used for amountless invoices and MPP payments
|
||||
@@ -270,7 +270,7 @@ impl MeltQuote {
|
||||
amount: Amount,
|
||||
fee_reserve: Amount,
|
||||
expiry: u64,
|
||||
request_lookup_id: PaymentIdentifier,
|
||||
request_lookup_id: Option<PaymentIdentifier>,
|
||||
options: Option<MeltOptions>,
|
||||
payment_method: PaymentMethod,
|
||||
) -> Self {
|
||||
@@ -433,8 +433,6 @@ pub enum MeltPaymentRequest {
|
||||
/// Offer
|
||||
#[serde(with = "offer_serde")]
|
||||
offer: Box<Offer>,
|
||||
/// Invoice
|
||||
invoice: Option<Vec<u8>>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -442,7 +440,7 @@ impl std::fmt::Display for MeltPaymentRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
|
||||
MeltPaymentRequest::Bolt12 { offer, invoice: _ } => write!(f, "{offer}"),
|
||||
MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +193,6 @@ pub struct Bolt12OutgoingPaymentOptions {
|
||||
pub max_fee_amount: Option<Amount>,
|
||||
/// Optional timeout in seconds
|
||||
pub timeout_secs: Option<u64>,
|
||||
/// Bolt12 Invoice
|
||||
pub invoice: Option<Vec<u8>>,
|
||||
/// Melt options
|
||||
pub melt_options: Option<MeltOptions>,
|
||||
}
|
||||
@@ -221,7 +219,7 @@ impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
|
||||
melt_options: melt_quote.options,
|
||||
},
|
||||
))),
|
||||
MeltPaymentRequest::Bolt12 { offer, invoice } => {
|
||||
MeltPaymentRequest::Bolt12 { offer } => {
|
||||
let melt_options = match melt_quote.options {
|
||||
None => None,
|
||||
Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
|
||||
@@ -233,7 +231,6 @@ impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
|
||||
max_fee_amount: Some(melt_quote.fee_reserve),
|
||||
timeout_secs: None,
|
||||
offer: *offer,
|
||||
invoice,
|
||||
melt_options,
|
||||
},
|
||||
)))
|
||||
@@ -343,7 +340,7 @@ pub struct MakePaymentResponse {
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PaymentQuoteResponse {
|
||||
/// Request look up id
|
||||
pub request_lookup_id: PaymentIdentifier,
|
||||
pub request_lookup_id: Option<PaymentIdentifier>,
|
||||
/// Amount
|
||||
pub amount: Amount,
|
||||
/// Fee required for melt
|
||||
@@ -352,18 +349,6 @@ pub struct PaymentQuoteResponse {
|
||||
pub unit: CurrencyUnit,
|
||||
/// Status
|
||||
pub state: MeltQuoteState,
|
||||
/// Payment Quote Options
|
||||
pub options: Option<PaymentQuoteOptions>,
|
||||
}
|
||||
|
||||
/// Payment quote options
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PaymentQuoteOptions {
|
||||
/// Bolt12 payment options
|
||||
Bolt12 {
|
||||
/// Bolt12 invoice
|
||||
invoice: Option<Vec<u8>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Ln backend settings
|
||||
|
||||
@@ -25,3 +25,4 @@ lightning-invoice.workspace = true
|
||||
lightning.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
reqwest.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -43,6 +43,7 @@ use tokio::time;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::instrument;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod error;
|
||||
|
||||
@@ -354,7 +355,7 @@ impl MintPayment for FakeWallet {
|
||||
};
|
||||
let payment_id =
|
||||
PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
|
||||
(amount_msat, payment_id)
|
||||
(amount_msat, Some(payment_id))
|
||||
}
|
||||
OutgoingPaymentOptions::Bolt12(bolt12_options) => {
|
||||
let offer = bolt12_options.offer;
|
||||
@@ -369,10 +370,7 @@ impl MintPayment for FakeWallet {
|
||||
_ => return Err(Error::UnknownInvoiceAmount.into()),
|
||||
}
|
||||
};
|
||||
(
|
||||
amount_msat,
|
||||
PaymentIdentifier::OfferId(offer.id().to_string()),
|
||||
)
|
||||
(amount_msat, None)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -390,7 +388,6 @@ impl MintPayment for FakeWallet {
|
||||
amount,
|
||||
fee: fee.into(),
|
||||
state: MeltQuoteState::Unpaid,
|
||||
options: None,
|
||||
unit: unit.clone(),
|
||||
})
|
||||
}
|
||||
@@ -471,7 +468,7 @@ impl MintPayment for FakeWallet {
|
||||
|
||||
Ok(MakePaymentResponse {
|
||||
payment_proof: Some("".to_string()),
|
||||
payment_lookup_id: PaymentIdentifier::OfferId(bolt12.id().to_string()),
|
||||
payment_lookup_id: PaymentIdentifier::CustomId(Uuid::new_v4().to_string()),
|
||||
status: MeltQuoteState::Paid,
|
||||
total_spent: total_spent + 1.into(),
|
||||
unit: unit.clone(),
|
||||
|
||||
@@ -204,13 +204,12 @@ impl MintPayment for LNbits {
|
||||
let fee = max(relative_fee_reserve, absolute_fee_reserve);
|
||||
|
||||
Ok(PaymentQuoteResponse {
|
||||
request_lookup_id: PaymentIdentifier::PaymentHash(
|
||||
request_lookup_id: Some(PaymentIdentifier::PaymentHash(
|
||||
*bolt11_options.bolt11.payment_hash().as_ref(),
|
||||
),
|
||||
)),
|
||||
amount,
|
||||
fee: fee.into(),
|
||||
state: MeltQuoteState::Unpaid,
|
||||
options: None,
|
||||
unit: unit.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -248,13 +248,12 @@ impl MintPayment for Lnd {
|
||||
let fee = max(relative_fee_reserve, absolute_fee_reserve);
|
||||
|
||||
Ok(PaymentQuoteResponse {
|
||||
request_lookup_id: PaymentIdentifier::PaymentHash(
|
||||
request_lookup_id: Some(PaymentIdentifier::PaymentHash(
|
||||
*bolt11_options.bolt11.payment_hash().as_ref(),
|
||||
),
|
||||
)),
|
||||
amount,
|
||||
fee: fee.into(),
|
||||
state: MeltQuoteState::Unpaid,
|
||||
options: None,
|
||||
unit: unit.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -229,7 +229,6 @@ impl MintPayment for PaymentProcessorClient {
|
||||
offer: opts.offer.to_string(),
|
||||
max_fee_amount: opts.max_fee_amount.map(Into::into),
|
||||
timeout_secs: opts.timeout_secs,
|
||||
invoice: opts.invoice,
|
||||
melt_options: opts.melt_options.map(Into::into),
|
||||
},
|
||||
)),
|
||||
|
||||
@@ -137,22 +137,11 @@ impl TryFrom<CreatePaymentResponse> for CreateIncomingPaymentResponse {
|
||||
impl From<cdk_common::payment::PaymentQuoteResponse> for PaymentQuoteResponse {
|
||||
fn from(value: cdk_common::payment::PaymentQuoteResponse) -> Self {
|
||||
Self {
|
||||
request_identifier: Some(value.request_lookup_id.into()),
|
||||
request_identifier: value.request_lookup_id.map(|i| i.into()),
|
||||
amount: value.amount.into(),
|
||||
fee: value.fee.into(),
|
||||
unit: value.unit.to_string(),
|
||||
state: QuoteState::from(value.state).into(),
|
||||
melt_options: value.options.map(|opt| match opt {
|
||||
cdk_common::payment::PaymentQuoteOptions::Bolt12 { invoice } => {
|
||||
PaymentQuoteOptions {
|
||||
melt_options: Some(payment_quote_options::MeltOptions::Bolt12(
|
||||
Bolt12Options {
|
||||
invoice: invoice.map(String::from_utf8).and_then(|r| r.ok()),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,26 +149,15 @@ impl From<cdk_common::payment::PaymentQuoteResponse> for PaymentQuoteResponse {
|
||||
impl From<PaymentQuoteResponse> for cdk_common::payment::PaymentQuoteResponse {
|
||||
fn from(value: PaymentQuoteResponse) -> Self {
|
||||
let state_val = value.state();
|
||||
let request_identifier = value
|
||||
.request_identifier
|
||||
.expect("request identifier required");
|
||||
let request_identifier = value.request_identifier;
|
||||
|
||||
Self {
|
||||
request_lookup_id: request_identifier
|
||||
.try_into()
|
||||
.expect("valid request identifier"),
|
||||
.map(|i| i.try_into().expect("valid request identifier")),
|
||||
amount: value.amount.into(),
|
||||
fee: value.fee.into(),
|
||||
unit: CurrencyUnit::from_str(&value.unit).unwrap_or_default(),
|
||||
state: state_val.into(),
|
||||
options: value.melt_options.map(|opt| match opt.melt_options {
|
||||
Some(payment_quote_options::MeltOptions::Bolt12(bolt12)) => {
|
||||
cdk_common::payment::PaymentQuoteOptions::Bolt12 {
|
||||
invoice: bolt12.invoice.as_deref().map(str::as_bytes).map(Vec::from),
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,23 +105,13 @@ enum QuoteState {
|
||||
ISSUED = 5;
|
||||
}
|
||||
|
||||
message Bolt12Options {
|
||||
optional string invoice = 1;
|
||||
}
|
||||
|
||||
message PaymentQuoteOptions {
|
||||
oneof melt_options {
|
||||
Bolt12Options bolt12 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message PaymentQuoteResponse {
|
||||
PaymentIdentifier request_identifier = 1;
|
||||
uint64 amount = 2;
|
||||
uint64 fee = 3;
|
||||
QuoteState state = 4;
|
||||
optional PaymentQuoteOptions melt_options = 5;
|
||||
string unit = 6;
|
||||
string unit = 5;
|
||||
}
|
||||
|
||||
message Bolt11OutgoingPaymentOptions {
|
||||
@@ -135,7 +125,6 @@ message Bolt12OutgoingPaymentOptions {
|
||||
string offer = 1;
|
||||
optional uint64 max_fee_amount = 2;
|
||||
optional uint64 timeout_secs = 3;
|
||||
optional bytes invoice = 4;
|
||||
optional MeltOptions melt_options = 5;
|
||||
}
|
||||
|
||||
|
||||
@@ -250,7 +250,6 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
offer: Offer::from_str(&request.request).unwrap(),
|
||||
max_fee_amount: None,
|
||||
timeout_secs: None,
|
||||
invoice: None,
|
||||
melt_options: request.options.map(Into::into),
|
||||
},
|
||||
))
|
||||
@@ -308,7 +307,6 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
offer,
|
||||
max_fee_amount: opts.max_fee_amount.map(Into::into),
|
||||
timeout_secs: opts.timeout_secs,
|
||||
invoice: opts.invoice,
|
||||
melt_options: opts.melt_options.map(Into::into),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/// Auto-generated by build.rs
|
||||
pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
||||
("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.sql"#)),
|
||||
("postgres", "2_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/postgres/2_remove_request_lookup_kind_constraints.sql"#)),
|
||||
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
|
||||
("sqlite", "20240612124932_init.sql", include_str!(r#"./migrations/sqlite/20240612124932_init.sql"#)),
|
||||
("sqlite", "20240618195700_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618195700_quote_state.sql"#)),
|
||||
@@ -25,4 +26,5 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
|
||||
("sqlite", "20250626120251_rename_blind_message_y_to_b.sql", include_str!(r#"./migrations/sqlite/20250626120251_rename_blind_message_y_to_b.sql"#)),
|
||||
("sqlite", "20250706101057_bolt12.sql", include_str!(r#"./migrations/sqlite/20250706101057_bolt12.sql"#)),
|
||||
("sqlite", "20250812132015_drop_melt_request.sql", include_str!(r#"./migrations/sqlite/20250812132015_drop_melt_request.sql"#)),
|
||||
("sqlite", "20250819200000_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/sqlite/20250819200000_remove_request_lookup_kind_constraints.sql"#)),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Set existing NULL or empty request_lookup_id_kind values to 'payment_hash' in melt_quote
|
||||
UPDATE melt_quote SET request_lookup_id_kind = 'payment_hash' WHERE request_lookup_id_kind IS NULL OR request_lookup_id_kind = '';
|
||||
|
||||
-- Remove NOT NULL constraint and default value from request_lookup_id_kind in melt_quote table
|
||||
ALTER TABLE melt_quote ALTER COLUMN request_lookup_id_kind DROP NOT NULL;
|
||||
ALTER TABLE melt_quote ALTER COLUMN request_lookup_id_kind DROP DEFAULT;
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
-- Set existing NULL or empty request_lookup_id_kind values to 'payment_hash' in melt_quote
|
||||
UPDATE melt_quote SET request_lookup_id_kind = 'payment_hash' WHERE request_lookup_id_kind IS NULL OR request_lookup_id_kind = '';
|
||||
|
||||
-- Remove NOT NULL constraint and default value from request_lookup_id_kind in melt_quote table
|
||||
CREATE TABLE melt_quote_temp (
|
||||
id TEXT PRIMARY KEY,
|
||||
unit TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
request TEXT NOT NULL,
|
||||
fee_reserve INTEGER NOT NULL,
|
||||
expiry INTEGER NOT NULL,
|
||||
state TEXT CHECK (
|
||||
state IN ('UNPAID', 'PENDING', 'PAID')
|
||||
) NOT NULL DEFAULT 'UNPAID',
|
||||
payment_preimage TEXT,
|
||||
request_lookup_id TEXT,
|
||||
created_time INTEGER NOT NULL DEFAULT 0,
|
||||
paid_time INTEGER,
|
||||
payment_method TEXT NOT NULL DEFAULT 'bolt11',
|
||||
options TEXT,
|
||||
request_lookup_id_kind TEXT
|
||||
);
|
||||
|
||||
INSERT INTO melt_quote_temp (id, unit, amount, request, fee_reserve, expiry, state, payment_preimage, request_lookup_id, created_time, paid_time, payment_method, options, request_lookup_id_kind)
|
||||
SELECT id, unit, amount, request, fee_reserve, expiry, state, payment_preimage, request_lookup_id, created_time, paid_time, payment_method, options, request_lookup_id_kind
|
||||
FROM melt_quote;
|
||||
|
||||
DROP TABLE melt_quote;
|
||||
ALTER TABLE melt_quote_temp RENAME TO melt_quote;
|
||||
|
||||
-- Recreate indexes for melt_quote
|
||||
CREATE INDEX IF NOT EXISTS melt_quote_state_index ON melt_quote(state);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS unique_request_lookup_id_melt ON melt_quote(request_lookup_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_melt_quote_request_lookup_id_and_kind ON melt_quote(request_lookup_id, request_lookup_id_kind);
|
||||
@@ -739,24 +739,6 @@ VALUES (:quote_id, :amount, :timestamp);
|
||||
|
||||
async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
|
||||
// First try to find and replace any expired UNPAID quotes with the same request_lookup_id
|
||||
let current_time = unix_time();
|
||||
let row_affected = query(
|
||||
r#"
|
||||
DELETE FROM melt_quote
|
||||
WHERE request_lookup_id = :request_lookup_id
|
||||
AND state = :state
|
||||
AND expiry < :current_time
|
||||
"#,
|
||||
)?
|
||||
.bind("request_lookup_id", quote.request_lookup_id.to_string())
|
||||
.bind("state", MeltQuoteState::Unpaid.to_string())
|
||||
.bind("current_time", current_time as i64)
|
||||
.execute(&self.inner)
|
||||
.await?;
|
||||
|
||||
if row_affected > 0 {
|
||||
tracing::info!("Received new melt quote for existing invoice with expired quote.");
|
||||
}
|
||||
|
||||
// Now insert the new quote
|
||||
query(
|
||||
@@ -783,14 +765,20 @@ VALUES (:quote_id, :amount, :timestamp);
|
||||
.bind("state", quote.state.to_string())
|
||||
.bind("expiry", quote.expiry as i64)
|
||||
.bind("payment_preimage", quote.payment_preimage)
|
||||
.bind("request_lookup_id", quote.request_lookup_id.to_string())
|
||||
.bind(
|
||||
"request_lookup_id",
|
||||
quote.request_lookup_id.as_ref().map(|id| id.to_string()),
|
||||
)
|
||||
.bind("created_time", quote.created_time as i64)
|
||||
.bind("paid_time", quote.paid_time.map(|t| t as i64))
|
||||
.bind(
|
||||
"options",
|
||||
quote.options.map(|o| serde_json::to_string(&o).ok()),
|
||||
)
|
||||
.bind("request_lookup_id_kind", quote.request_lookup_id.kind())
|
||||
.bind(
|
||||
"request_lookup_id_kind",
|
||||
quote.request_lookup_id.map(|id| id.kind()),
|
||||
)
|
||||
.bind("payment_method", quote.payment_method.to_string())
|
||||
.execute(&self.inner)
|
||||
.await?;
|
||||
@@ -1713,19 +1701,24 @@ fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
|
||||
let unit = column_as_string!(unit);
|
||||
let request = column_as_string!(request);
|
||||
|
||||
let mut request_lookup_id_kind = column_as_string!(request_lookup_id_kind);
|
||||
let request_lookup_id_kind = column_as_nullable_string!(request_lookup_id_kind);
|
||||
|
||||
let request_lookup_id = column_as_nullable_string!(&request_lookup_id).unwrap_or_else(|| {
|
||||
let request_lookup_id = column_as_nullable_string!(&request_lookup_id).or_else(|| {
|
||||
Bolt11Invoice::from_str(&request)
|
||||
.ok()
|
||||
.map(|invoice| invoice.payment_hash().to_string())
|
||||
.unwrap_or_else(|_| {
|
||||
request_lookup_id_kind = "custom".to_string();
|
||||
request.clone()
|
||||
})
|
||||
});
|
||||
|
||||
let request_lookup_id = PaymentIdentifier::new(&request_lookup_id_kind, &request_lookup_id)
|
||||
.map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?;
|
||||
let request_lookup_id = if let (Some(id_kind), Some(request_lookup_id)) =
|
||||
(request_lookup_id_kind, request_lookup_id)
|
||||
{
|
||||
Some(
|
||||
PaymentIdentifier::new(&id_kind, &request_lookup_id)
|
||||
.map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = match serde_json::from_str(&request) {
|
||||
Ok(req) => req,
|
||||
|
||||
@@ -9,7 +9,7 @@ use cdk_common::nut00::ProofsMethods;
|
||||
use cdk_common::nut05::MeltMethodOptions;
|
||||
use cdk_common::payment::{
|
||||
Bolt11OutgoingPaymentOptions, Bolt12OutgoingPaymentOptions, OutgoingPaymentOptions,
|
||||
PaymentQuoteOptions,
|
||||
PaymentIdentifier,
|
||||
};
|
||||
use cdk_common::{MeltOptions, MeltQuoteBolt12Request};
|
||||
use lightning::offers::offer::Offer;
|
||||
@@ -202,7 +202,7 @@ impl Mint {
|
||||
);
|
||||
|
||||
tracing::debug!(
|
||||
"New {} melt quote {} for {} {} with request id {}",
|
||||
"New {} melt quote {} for {} {} with request id {:?}",
|
||||
quote.payment_method,
|
||||
quote.id,
|
||||
amount_quote_unit,
|
||||
@@ -269,7 +269,6 @@ impl Mint {
|
||||
max_fee_amount: None,
|
||||
timeout_secs: None,
|
||||
melt_options: *options,
|
||||
invoice: None,
|
||||
};
|
||||
|
||||
let payment_quote = ln
|
||||
@@ -288,13 +287,8 @@ impl Mint {
|
||||
Error::UnsupportedUnit
|
||||
})?;
|
||||
|
||||
let invoice = payment_quote.options.and_then(|options| match options {
|
||||
PaymentQuoteOptions::Bolt12 { invoice } => invoice,
|
||||
});
|
||||
|
||||
let payment_request = MeltPaymentRequest::Bolt12 {
|
||||
offer: Box::new(offer),
|
||||
invoice,
|
||||
};
|
||||
|
||||
let quote = MeltQuote::new(
|
||||
@@ -309,7 +303,7 @@ impl Mint {
|
||||
);
|
||||
|
||||
tracing::debug!(
|
||||
"New {} melt quote {} for {} {} with request id {}",
|
||||
"New {} melt quote {} for {} {} with request id {:?}",
|
||||
quote.payment_method,
|
||||
quote.id,
|
||||
amount,
|
||||
@@ -382,7 +376,7 @@ impl Mint {
|
||||
.ok_or(Error::InvoiceAmountUndefined)?
|
||||
.amount_msat(),
|
||||
},
|
||||
MeltPaymentRequest::Bolt12 { offer, invoice: _ } => match offer.amount() {
|
||||
MeltPaymentRequest::Bolt12 { offer } => match offer.amount() {
|
||||
Some(amount) => {
|
||||
let (amount, currency) = match amount {
|
||||
lightning::offers::offer::Amount::Bitcoin { amount_msats } => {
|
||||
@@ -529,18 +523,15 @@ impl Mint {
|
||||
use std::sync::Arc;
|
||||
async fn check_payment_state(
|
||||
ln: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
|
||||
melt_quote: &MeltQuote,
|
||||
lookup_id: &PaymentIdentifier,
|
||||
) -> anyhow::Result<MakePaymentResponse> {
|
||||
match ln
|
||||
.check_outgoing_payment(&melt_quote.request_lookup_id)
|
||||
.await
|
||||
{
|
||||
match ln.check_outgoing_payment(lookup_id).await {
|
||||
Ok(response) => Ok(response),
|
||||
Err(check_err) => {
|
||||
// If we cannot check the status of the payment we keep the proofs stuck as pending.
|
||||
tracing::error!(
|
||||
"Could not check the status of payment for {},. Proofs stuck as pending",
|
||||
melt_quote.id
|
||||
lookup_id
|
||||
);
|
||||
tracing::error!("Checking payment error: {}", check_err);
|
||||
bail!("Could not check payment status")
|
||||
@@ -616,12 +607,13 @@ impl Mint {
|
||||
|| pay.status == MeltQuoteState::Failed =>
|
||||
{
|
||||
tracing::warn!("Got {} status when paying melt quote {} for {} {}. Checking with backend...", pay.status, quote.id, quote.amount, quote.unit);
|
||||
let check_response =
|
||||
if let Ok(ok) = check_payment_state(Arc::clone(ln), "e).await {
|
||||
ok
|
||||
} else {
|
||||
return Err(Error::Internal);
|
||||
};
|
||||
let check_response = if let Ok(ok) =
|
||||
check_payment_state(Arc::clone(ln), &pay.payment_lookup_id).await
|
||||
{
|
||||
ok
|
||||
} else {
|
||||
return Err(Error::Internal);
|
||||
};
|
||||
|
||||
if check_response.status == MeltQuoteState::Paid {
|
||||
tracing::warn!("Pay invoice returned {} but check returned {}. Proofs stuck as pending", pay.status.to_string(), check_response.status.to_string());
|
||||
@@ -644,8 +636,16 @@ impl Mint {
|
||||
|
||||
tracing::error!("Error returned attempting to pay: {} {}", quote.id, err);
|
||||
|
||||
let lookup_id = quote.request_lookup_id.as_ref().ok_or_else(|| {
|
||||
tracing::error!(
|
||||
"No payment id could not lookup payment for {} after error.",
|
||||
quote.id
|
||||
);
|
||||
Error::Internal
|
||||
})?;
|
||||
|
||||
let check_response =
|
||||
if let Ok(ok) = check_payment_state(Arc::clone(ln), "e).await {
|
||||
if let Ok(ok) = check_payment_state(Arc::clone(ln), lookup_id).await {
|
||||
ok
|
||||
} else {
|
||||
proof_writer.commit();
|
||||
@@ -690,21 +690,18 @@ impl Mint {
|
||||
let payment_lookup_id = pre.payment_lookup_id;
|
||||
let mut tx = self.localstore.begin_transaction().await?;
|
||||
|
||||
if payment_lookup_id != quote.request_lookup_id {
|
||||
if Some(payment_lookup_id.clone()).as_ref() != quote.request_lookup_id.as_ref() {
|
||||
tracing::info!(
|
||||
"Payment lookup id changed post payment from {} to {}",
|
||||
quote.request_lookup_id,
|
||||
"Payment lookup id changed post payment from {:?} to {}",
|
||||
"e.request_lookup_id,
|
||||
payment_lookup_id
|
||||
);
|
||||
|
||||
let mut melt_quote = quote;
|
||||
melt_quote.request_lookup_id = payment_lookup_id;
|
||||
melt_quote.request_lookup_id = Some(payment_lookup_id.clone());
|
||||
|
||||
if let Err(err) = tx
|
||||
.update_melt_quote_request_lookup_id(
|
||||
&melt_quote.id,
|
||||
&melt_quote.request_lookup_id,
|
||||
)
|
||||
.update_melt_quote_request_lookup_id(&melt_quote.id, &payment_lookup_id)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Could not update payment lookup id: {}", err);
|
||||
|
||||
@@ -40,39 +40,39 @@ impl Mint {
|
||||
}
|
||||
};
|
||||
|
||||
let pay_invoice_response = ln_backend
|
||||
.check_outgoing_payment(&pending_quote.request_lookup_id)
|
||||
.await?;
|
||||
if let Some(lookup_id) = pending_quote.request_lookup_id {
|
||||
let pay_invoice_response = ln_backend.check_outgoing_payment(&lookup_id).await?;
|
||||
|
||||
tracing::warn!(
|
||||
"There is no stored melt request for pending melt quote: {}",
|
||||
pending_quote.id
|
||||
);
|
||||
|
||||
let melt_quote_state = match pay_invoice_response.status {
|
||||
MeltQuoteState::Unpaid => MeltQuoteState::Unpaid,
|
||||
MeltQuoteState::Paid => MeltQuoteState::Paid,
|
||||
MeltQuoteState::Pending => MeltQuoteState::Pending,
|
||||
MeltQuoteState::Failed => MeltQuoteState::Unpaid,
|
||||
MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
|
||||
};
|
||||
|
||||
if let Err(err) = tx
|
||||
.update_melt_quote_state(
|
||||
&pending_quote.id,
|
||||
melt_quote_state,
|
||||
pay_invoice_response.payment_proof,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Could not update quote {} to state {}, current state {}, {}",
|
||||
pending_quote.id,
|
||||
melt_quote_state,
|
||||
pending_quote.state,
|
||||
err
|
||||
tracing::warn!(
|
||||
"There is no stored melt request for pending melt quote: {}",
|
||||
pending_quote.id
|
||||
);
|
||||
};
|
||||
|
||||
let melt_quote_state = match pay_invoice_response.status {
|
||||
MeltQuoteState::Unpaid => MeltQuoteState::Unpaid,
|
||||
MeltQuoteState::Paid => MeltQuoteState::Paid,
|
||||
MeltQuoteState::Pending => MeltQuoteState::Pending,
|
||||
MeltQuoteState::Failed => MeltQuoteState::Unpaid,
|
||||
MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
|
||||
};
|
||||
|
||||
if let Err(err) = tx
|
||||
.update_melt_quote_state(
|
||||
&pending_quote.id,
|
||||
melt_quote_state,
|
||||
pay_invoice_response.payment_proof,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Could not update quote {} to state {}, current state {}, {}",
|
||||
pending_quote.id,
|
||||
melt_quote_state,
|
||||
pending_quote.state,
|
||||
err
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Reference in New Issue
Block a user