Fix a few possible concurrency bugs
This commit is contained in:
@@ -469,6 +469,14 @@ impl Billing {
|
||||
pub async fn ensure_bolt11_for_invoice(&self, invoice: &Invoice) -> Result<Bolt11> {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
|
||||
// A resolved (paid or voided) invoice must never mint a new payable
|
||||
// bolt11; return the existing one if present, otherwise refuse.
|
||||
if invoice.paid_at.is_some() || invoice.voided_at.is_some() {
|
||||
return query::get_bolt11_for_invoice(&invoice.id)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("invoice is not open"));
|
||||
}
|
||||
|
||||
if let Some(existing) = query::get_bolt11_for_invoice(&invoice.id).await?
|
||||
&& (existing.settled_at.is_none() || now < existing.expires_at)
|
||||
{
|
||||
@@ -487,6 +495,11 @@ impl Billing {
|
||||
|
||||
pub async fn reconcile_bolt11_for_invoice(&self, invoice: &Invoice) -> Result<Option<Bolt11>> {
|
||||
if let Some(bolt11) = query::get_bolt11_for_invoice(&invoice.id).await? {
|
||||
// Don't settle an invoice that is already resolved
|
||||
if invoice.paid_at.is_some() || invoice.voided_at.is_some() {
|
||||
return Ok(Some(bolt11));
|
||||
}
|
||||
|
||||
return self.reconcile_bolt11(&bolt11).await;
|
||||
};
|
||||
|
||||
|
||||
+20
-10
@@ -393,7 +393,8 @@ pub async fn settle_invoice_via_nwc(
|
||||
with_tx(async |tx| {
|
||||
clear_tenant_nwc_error_tx(tx, tenant_pubkey).await?;
|
||||
mark_bolt11_settled_tx(tx, bolt11_id).await?;
|
||||
mark_invoice_paid_tx(tx, invoice_id, "nwc").await
|
||||
mark_invoice_paid_tx(tx, invoice_id, "nwc").await?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -402,7 +403,8 @@ pub async fn settle_invoice_via_nwc(
|
||||
pub async fn settle_invoice_out_of_band(bolt11_id: &str, invoice_id: &str) -> Result<()> {
|
||||
with_tx(async |tx| {
|
||||
mark_bolt11_settled_tx(tx, bolt11_id).await?;
|
||||
mark_invoice_paid_tx(tx, invoice_id, "oob").await
|
||||
mark_invoice_paid_tx(tx, invoice_id, "oob").await?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -417,7 +419,8 @@ pub async fn settle_invoice_via_stripe(
|
||||
with_tx(async |tx| {
|
||||
insert_intent_tx(tx, intent_id, invoice_id).await?;
|
||||
clear_tenant_stripe_error_tx(tx, tenant_pubkey).await?;
|
||||
mark_invoice_paid_tx(tx, invoice_id, "stripe").await
|
||||
mark_invoice_paid_tx(tx, invoice_id, "stripe").await?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -592,10 +595,11 @@ async fn set_relay_status_tx(
|
||||
insert_activity_tx(tx, activity_type, &relay.id, snapshot).await
|
||||
}
|
||||
|
||||
/// Stamp a bolt11 as settled but don't overwrite an existing settled_at.
|
||||
async fn mark_bolt11_settled_tx(tx: &mut Transaction<'_, Sqlite>, bolt11_id: &str) -> Result<()> {
|
||||
let settled_at = chrono::Utc::now().timestamp();
|
||||
|
||||
sqlx::query("UPDATE bolt11 SET settled_at = ? WHERE id = ?")
|
||||
sqlx::query("UPDATE bolt11 SET settled_at = ? WHERE id = ? AND settled_at IS NULL")
|
||||
.bind(settled_at)
|
||||
.bind(bolt11_id)
|
||||
.execute(&mut **tx)
|
||||
@@ -603,6 +607,9 @@ async fn mark_bolt11_settled_tx(tx: &mut Transaction<'_, Sqlite>, bolt11_id: &st
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark an invoice paid, but only while it is still open — a late Lightning
|
||||
/// payment never flips a voided/forgiven invoice to paid, and a Stripe-paid
|
||||
/// invoice never has its provenance overwritten by a later bolt11.
|
||||
async fn mark_invoice_paid_tx(
|
||||
tx: &mut Transaction<'_, Sqlite>,
|
||||
invoice_id: &str,
|
||||
@@ -610,12 +617,15 @@ async fn mark_invoice_paid_tx(
|
||||
) -> Result<()> {
|
||||
let paid_at = chrono::Utc::now().timestamp();
|
||||
|
||||
sqlx::query("UPDATE invoice SET method = ?, paid_at = ? WHERE id = ?")
|
||||
.bind(method)
|
||||
.bind(paid_at)
|
||||
.bind(invoice_id)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"UPDATE invoice SET method = ?, paid_at = ?
|
||||
WHERE id = ? AND paid_at IS NULL AND voided_at IS NULL",
|
||||
)
|
||||
.bind(method)
|
||||
.bind(paid_at)
|
||||
.bind(invoice_id)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import Login from "@/views/Login"
|
||||
import { createRelayForActiveTenant } from "@/lib/hooks"
|
||||
import { account } from "@/lib/state"
|
||||
import FlotillaLogo from "@/assets/flotilla-logo.svg"
|
||||
import ChachiLogo from "@/assets/chachi-logo.svg"
|
||||
import NostordLogo from "@/assets/nostord-logo.svg"
|
||||
|
||||
export default function Home() {
|
||||
|
||||
Reference in New Issue
Block a user