aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-03-04 20:49:17 -0800
committerAdam Soltys <[email protected]>2024-03-04 20:49:17 -0800
commit5112b3f44d3eb1967f65d0f214a78bf9022806eb (patch)
tree7dc3a585284ec7f199ffd414da36aa2104f480ce
parentf1389aa4a1e104e1380da3342f79b0cde6dd5c75 (diff)
downloadcoinos-server-5112b3f44d3eb1967f65d0f214a78bf9022806eb.tar.gz
coinos-server-5112b3f44d3eb1967f65d0f214a78bf9022806eb.zip
payment throttling
-rw-r--r--lib/payments.js160
1 files changed, 85 insertions, 75 deletions
diff --git a/lib/payments.js b/lib/payments.js
index 5958bc14..cb5d4111 100644
--- a/lib/payments.js
+++ b/lib/payments.js
@@ -440,112 +440,122 @@ let getMaxFee = (n) =>
);
let checking = {};
+let inflight = {};
export let sendLightning = async ({ user, pr, amount, maxfee, memo }) => {
- let p;
- let hash = pr;
+ try {
+ if (inflight[user.id]) fail("payment in flight");
+ inflight[user.id] = true;
- if (typeof amount !== "undefined") {
- amount = parseInt(amount);
- if (amount < 0 || amount > SATS || isNaN(amount)) fail("Invalid amount");
- }
+ let p;
+ let hash = pr;
- let total = amount;
- let { amount_msat, payment_hash } = await ln.decode(pr);
- if (amount_msat) total = Math.round(amount_msat / 1000);
+ if (typeof amount !== "undefined") {
+ amount = parseInt(amount);
+ if (amount < 0 || amount > SATS || isNaN(amount)) fail("Invalid amount");
+ }
- maxfee = parseInt(maxfee) || getMaxFee(total);
+ let total = amount;
+ let { amount_msat, payment_hash } = await ln.decode(pr);
+ if (amount_msat) total = Math.round(amount_msat / 1000);
- if (maxfee < 0) fail("Max fee cannot be negative");
+ maxfee = parseInt(maxfee) || getMaxFee(total);
- let invoice = await getInvoice(hash);
+ if (maxfee < 0) fail("Max fee cannot be negative");
- if (invoice) {
- if (invoice.uid === user.id) fail("Cannot send to self");
- hash = pr;
- } else {
- let r;
+ let invoice = await getInvoice(hash);
- let { pays } = await ln.listpays(pr);
- if (pays.find((p) => p.status === "complete"))
- fail("Invoice has already been paid");
+ if (invoice) {
+ if (invoice.uid === user.id) fail("Cannot send to self");
+ hash = pr;
+ } else {
+ let r;
- if (pays.find((p) => p.status === "pending"))
- fail("Payment is already underway");
+ let { pays } = await ln.listpays(pr);
+ if (pays.find((p) => p.status === "complete"))
+ fail("Invoice has already been paid");
- p = await debit({
- hash: pr,
- amount: total,
- fee: maxfee,
- memo,
- user,
- type: types.lightning,
- });
+ if (pays.find((p) => p.status === "pending"))
+ fail("Payment is already underway");
- let check = async () => {
- if (checking(pr)) return;
- checking[pr] = true;
+ p = await debit({
+ hash: pr,
+ amount: total,
+ fee: maxfee,
+ memo,
+ user,
+ type: types.lightning,
+ });
- try {
- let { pays } = await ln.listpays(pr);
+ let check = async () => {
+ if (checking[pr]) return;
+ checking[pr] = true;
- let recordExists = !!(await g(`payment:${pr}`));
- let paymentFailed =
- !pays.length || pays.every((p) => p.status === "failed");
+ try {
+ let { pays } = await ln.listpays(pr);
- if (recordExists && paymentFailed) {
- await db.del(`payment:${pr}`);
- await db.del(`payment:${p.id}`);
+ let recordExists = !!(await g(`payment:${pr}`));
+ let paymentFailed =
+ !pays.length || pays.every((p) => p.status === "failed");
- let ourfee = p.ourfee || 0;
- let credit = Math.round(total * config.fee) - ourfee;
- await db.incrBy(`balance:${p.uid}`, total + maxfee + ourfee);
+ if (recordExists && paymentFailed) {
+ await db.del(`payment:${pr}`);
+ await db.del(`payment:${p.id}`);
- await db.incrBy(`credit:${types.lightning}:${p.uid}`, credit);
- await db.lRem(`${p.uid}:payments`, 1, p.id);
+ let ourfee = p.ourfee || 0;
+ let credit = Math.round(total * config.fee) - ourfee;
+ await db.incrBy(`balance:${p.uid}`, total + maxfee + ourfee);
- clearInterval(interval);
+ await db.incrBy(`credit:${types.lightning}:${p.uid}`, credit);
+ await db.lRem(`${p.uid}:payments`, 1, p.id);
+
+ clearInterval(interval);
+ }
+ } catch (e) {
+ err("Failed to reverse payment", r);
}
- } catch (e) {
- err("Failed to reverse payment", r);
- }
checking[pr] = false;
- };
+ };
- let interval = setInterval(check, 5000);
+ let interval = setInterval(check, 5000);
- try {
- l("paying lightning invoice", pr.substr(-8), total, amount, maxfee);
+ try {
+ l("paying lightning invoice", pr.substr(-8), total, amount, maxfee);
- r = await ln.pay({
- bolt11: pr.replace(/\s/g, "").toLowerCase(),
- amount_msat: amount_msat ? undefined : amount * 1000,
- maxfee: maxfee * 1000,
- retry_for: 5,
- });
+ r = await ln.pay({
+ bolt11: pr.replace(/\s/g, "").toLowerCase(),
+ amount_msat: amount_msat ? undefined : amount * 1000,
+ maxfee: maxfee * 1000,
+ retry_for: 5,
+ });
- if (!(r.status === "complete" && r.payment_preimage))
- fail("Payment did not complete");
+ if (!(r.status === "complete" && r.payment_preimage))
+ fail("Payment did not complete");
- l("payment completed", p.id, r.payment_preimage);
+ l("payment completed", p.id, r.payment_preimage);
- p.amount = -total;
- p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000);
- p.ref = r.payment_preimage;
+ p.amount = -total;
+ p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000);
+ p.ref = r.payment_preimage;
- await s(`payment:${p.id}`, p);
+ await s(`payment:${p.id}`, p);
- l("refunding fee", maxfee, p.fee, maxfee - p.fee, p.ref);
- await db.incrBy(`balance:${p.uid}`, maxfee - p.fee);
- } catch (e) {
- await check();
- throw e;
+ l("refunding fee", maxfee, p.fee, maxfee - p.fee, p.ref);
+ await db.incrBy(`balance:${p.uid}`, maxfee - p.fee);
+ } catch (e) {
+ await check();
+ throw e;
+ }
+
+ clearInterval(interval);
}
- clearInterval(interval);
+ delete inflight[user.id];
+ return p;
+ } catch (e) {
+ delete inflight[user.id];
+ throw e;
}
-
- return p;
};
export let getNode = (type) => {