From d01159ce9eee956dca32fd69525c0edd5aae9477 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 15 Jan 2021 15:31:46 -0800 Subject: add more transactions and locks --- lib/account.js | 7 +- lib/send.js | 1 - routes/assets.js | 45 +++++++----- routes/bitcoin/broadcast.js | 56 +++++++++------ routes/bitcoin/receive.js | 9 ++- routes/bitcoin/send.js | 43 +++++------ routes/lightning/receive.js | 4 +- routes/liquid/broadcast.js | 57 +++++++++------ routes/liquid/receive.js | 170 +++++++++++++++++++++++++------------------- routes/liquid/send.js | 22 +++--- routes/lnurl.js | 8 +-- routes/send.js | 29 ++++---- routes/users.js | 73 ++++++++++++++----- 13 files changed, 311 insertions(+), 213 deletions(-) diff --git a/lib/account.js b/lib/account.js index 5a8c3924..ac09a8e8 100644 --- a/lib/account.js +++ b/lib/account.js @@ -33,9 +33,10 @@ module.exports = async (asset, user, transaction) => { asset }, order: [["id", "ASC"]], - limit: 1 + limit: 1, + lock: transaction.LOCK.UPDATE, + transaction }, - { transaction } ); if (existing) { @@ -46,5 +47,5 @@ module.exports = async (asset, user, transaction) => { params = { ...params, ...{ domain, ticker, precision, name } }; params.balance = 0; params.pending = 0; - return db.Account.create(params); + return db.Account.create(params, { transaction }); }; diff --git a/lib/send.js b/lib/send.js index 4e00f54a..a1052041 100644 --- a/lib/send.js +++ b/lib/send.js @@ -1,7 +1,6 @@ const bolt11 = require("bolt11"); send = async (amount, memo, hash, user) => { - return res.status(500).send("Internal payments are temporarily disabled"); let payreq = bolt11.decode(hash); if (!amount) amount = payreq.satoshis; diff --git a/routes/assets.js b/routes/assets.js index ea3e82ff..7d2cdfcf 100644 --- a/routes/assets.js +++ b/routes/assets.js @@ -29,15 +29,17 @@ app.get( Object.keys(assets).map(a => { assets[a].registered = true; if (!assets[a].asset) assets[a].asset = assets[a].asset_id; - if (( - assets[a].ticker === 'BTC' && assets[a].asset !== config.liquid.btcasset - ) || ( - assets[a].ticker === 'EUR' && assets[a].asset !== config.liquid.eurasset - ) || ( - assets[a].ticker === 'CAD' && assets[a].asset !== config.liquid.cadasset - ) || ( - assets[a].ticker === 'USDt' && assets[a].asset !== config.liquid.usdtasset - )) delete assets[a]; + if ( + (assets[a].ticker === "BTC" && + assets[a].asset !== config.liquid.btcasset) || + (assets[a].ticker === "EUR" && + assets[a].asset !== config.liquid.eurasset) || + (assets[a].ticker === "CAD" && + assets[a].asset !== config.liquid.cadasset) || + (assets[a].ticker === "USDt" && + assets[a].asset !== config.liquid.usdtasset) + ) + delete assets[a]; }); accounts.map(({ asset, name, domain, ticker, precision }) => { @@ -156,8 +158,11 @@ app.post( ); } - account.balance -= Math.round(funded.fee * SATS); - await account.save({ transaction }); + await account.decrement( + { balance: Math.round(funded.fee * SATS) }, + { transaction } + ); + await account.reload({ transaction }); emit(user.username, "account", account); account = await db.Account.create( @@ -294,7 +299,11 @@ app.post( try { await db.transaction(async transaction => { - let { account } = user; + let account = await getAccount( + config.liquid.btcasset, + user, + transaction + ); if (amount < 0) throw new Error("Amount to load cannot be negative"); if (account.asset !== config.liquid.btcasset) @@ -305,8 +314,8 @@ app.post( let fee = 0; - account.balance -= amount; - await account.save({ transaction }); + await account.decrement({ balance: amount }, { transaction }); + await account.reload({ transaction }); let a2; let acc = { @@ -321,8 +330,8 @@ app.post( }); if (a2) { - a2.balance += amount; - await a2.save({ transaction }); + await a2.increment({ balance: amount }, { transaction }); + await a2.reload({ transaction }); } else { let name = asset.substr(0, 6); let domain; @@ -339,7 +348,9 @@ app.post( asset }, order: [["id", "ASC"]], - limit: 1 + limit: 1, + lock: transaction.LOCK.UPDATE, + transaction }); if (existing) { diff --git a/routes/bitcoin/broadcast.js b/routes/bitcoin/broadcast.js index 77b487de..a32e9067 100644 --- a/routes/bitcoin/broadcast.js +++ b/routes/bitcoin/broadcast.js @@ -4,29 +4,43 @@ module.exports = ah(async (req, res) => { let hash = await bc.sendRawTransaction(req.body.tx); if (user) { - let { account } = user; - let payment = await db.Payment.create({ - ...req.body.payment, - hash, - amount: -req.body.payment.amount, - tip: 0, - account_id: account.id, - user_id: user.id, - rate: app.get("rates")[user.currency], - currency: user.currency, - confirmed: true, - received: false, - network: "bitcoin", - }); + await db.transaction(async transaction => { + let account = await db.Account.findOne({ + where: { + id: user.account_id + }, + lock: transaction.LOCK.UPDATE, + transaction + }); + let payment = await db.Payment.create( + { + ...req.body.payment, + hash, + amount: -req.body.payment.amount, + tip: 0, + account_id: account.id, + user_id: user.id, + rate: app.get("rates")[user.currency], + currency: user.currency, + confirmed: true, + received: false, + network: "bitcoin" + }, + { transaction } + ); - account.balance += payment.amount - payment.fee; - await account.save(); + await account.increment( + { balance: payment.amount - payment.fee }, + { transaction } + ); + await account.reload({ transaction }); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - res.send(payment); + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); + emit(user.username, "payment", payment); + emit(user.username, "account", payment.account); + res.send(payment); + }); } else { res.send(result); } diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js index e0e3828b..2a6a7954 100644 --- a/routes/bitcoin/receive.js +++ b/routes/bitcoin/receive.js @@ -159,10 +159,13 @@ setInterval(async () => { let total = p.amount + p.tip; p.confirmed = 1; - p.account.balance += total; - p.account.pending -= Math.min(p.account.pending, total); - await p.account.save({ transaction }); + await p.account.increment({ balance: total }, { transaction }); + await p.account.decrement( + { pending: Math.min(p.account.pending, total) }, + { transaction } + ); + await p.account.reload({ transaction }); await p.save({ transaction }); p = p.get({ plain: true }); diff --git a/routes/bitcoin/send.js b/routes/bitcoin/send.js index 2089adfa..f1814b08 100644 --- a/routes/bitcoin/send.js +++ b/routes/bitcoin/send.js @@ -31,15 +31,23 @@ module.exports = ah(async (req, res) => { total = total - change + fee; let amount = total - fee; - let { account } = user; let params; try { await db.transaction(async transaction => { + let account = await db.Account.findOne({ + where: { + id: user.account_id + }, + lock: transaction.LOCK.UPDATE, + transaction + }); + if (account.asset !== btc) { account = await db.Account.findOne({ where: { user_id: user.id, asset: btc, + pubkey: null }, lock: transaction.LOCK.UPDATE, transaction @@ -51,8 +59,8 @@ module.exports = ah(async (req, res) => { throw new Error("Insufficient funds"); } - account.balance -= total; - await account.save({ transaction }); + await account.decrement({ balance: total }, { transaction }); + await account.reload({ transaction }); params = { amount: -amount, @@ -67,29 +75,24 @@ module.exports = ah(async (req, res) => { received: false, network: "bitcoin" }; - }); - } catch (e) { - l.error("problem creating bitcoin payment", user.username, e.message); - return res.status(500).send(e.message); - } - try { - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); + if (config.bitcoin.walletpass) + await bc.walletPassphrase(config.bitcoin.walletpass, 300); - hex = (await bc.signRawTransactionWithWallet(hex)).hex; - params.hash = await bc.sendRawTransaction(hex); + hex = (await bc.signRawTransactionWithWallet(hex)).hex; + params.hash = await bc.sendRawTransaction(hex); - let payment = await db.Payment.create(params); + let payment = await db.Payment.create(params, { transaction }); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - res.send(payment); + emit(user.username, "payment", payment); + res.send(payment); - payments.push(params.hash); - l.info("sent bitcoin", user.username, total); + payments.push(params.hash); + l.info("sent bitcoin", user.username, total); + }); } catch (e) { l.error("error sending bitcoin", e.message); return res.status(500).send(e.message); diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js index 5b0d1d77..0c8d6557 100644 --- a/routes/lightning/receive.js +++ b/routes/lightning/receive.js @@ -55,9 +55,9 @@ const handlePayment = async (msg) => { let total = amount + tip; invoice.received += total; - account.balance += total; - await account.save({ transaction }); + await account.increment({ balance: total }, { transaction }); + await account.reload({ transaction }); await invoice.save({ transaction }); await payment.save({ transaction }); payments.push(msg.payment_request); diff --git a/routes/liquid/broadcast.js b/routes/liquid/broadcast.js index a39349a1..1db166c5 100644 --- a/routes/liquid/broadcast.js +++ b/routes/liquid/broadcast.js @@ -4,29 +4,44 @@ module.exports = ah(async (req, res) => { let hash = await lq.sendRawTransaction(req.body.tx); if (user) { - let { account } = user; - let payment = await db.Payment.create({ - ...req.body.payment, - hash, - amount: -req.body.payment.amount, - tip: 0, - account_id: account.id, - user_id: user.id, - rate: app.get("rates")[user.currency], - currency: user.currency, - confirmed: true, - received: false, - network: "liquid", - }); + await db.transaction(async transaction => { + let account = await db.Account.findOne({ + where: { + id: user.account_id + }, + lock: transaction.LOCK.UPDATE, + transaction + }); + + let payment = await db.Payment.create( + { + ...req.body.payment, + hash, + amount: -req.body.payment.amount, + tip: 0, + account_id: account.id, + user_id: user.id, + rate: app.get("rates")[user.currency], + currency: user.currency, + confirmed: true, + received: false, + network: "liquid" + }, + { transaction } + ); - account.balance += payment.amount - payment.fee; - await account.save(); + await account.increment( + { balance: payment.amount - payment.fee }, + { transaction } + ); + await account.reload({ transaction }); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - res.send(payment); + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); + emit(user.username, "payment", payment); + emit(user.username, "account", payment.account); + res.send(payment); + }); } else { res.send(result); } diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js index 2976bf57..ce1c6d4b 100644 --- a/routes/liquid/receive.js +++ b/routes/liquid/receive.js @@ -12,9 +12,11 @@ const network = config.liquid.network === "mainnet" ? "liquid" : config.liquid.network ]; -const getAccount = async params => { +const getAccount = async (params, transaction) => { let account = await db.Account.findOne({ - where: params + where: params, + lock: transaction.LOCK.UPDATE, + transaction }); if (account) { @@ -39,7 +41,9 @@ const getAccount = async params => { pubkey }, order: [["id", "ASC"]], - limit: 1 + limit: 1, + lock: transaction.LOCK.UPDATE, + transaction }); if (existing) { @@ -51,7 +55,7 @@ const getAccount = async params => { params = { ...params, ...{ domain, ticker, precision, name } }; params.balance = 0; params.network = "liquid"; - return db.Account.create(params); + return db.Account.create(params, { transaction }); }; const zmqRawBlock = zmq.socket("sub"); @@ -82,78 +86,91 @@ zmqRawTx.on("message", async (topic, message, sequence) => { Object.keys(addresses).includes(address) && !change.includes(address) ) { - let user = await getUser(addresses[address]); + await db.transaction(async transaction => { + let user = await getUser(addresses[address], transaction); + + let invoice = await db.Invoice.findOne({ + where: { + unconfidential: address, + user_id: user.id, + network: "liquid" + }, + order: [["id", "DESC"]] + }); - let invoice = await db.Invoice.findOne({ - where: { - unconfidential: address, - user_id: user.id, - network: "liquid" - }, - order: [["id", "DESC"]], - include: { - model: db.Account, - as: "account" - } - }); + if (!invoice) return; - if (!invoice) return; - - let confirmed = 0; - - let { account } = invoice; - - if ( - account.asset === asset && - (!account.pubkey || account.network === "liquid") - ) { - account.pending += value; - await account.save(); - } else { - account = await getAccount({ - seed: account.seed, - path: account.path, - user_id: user.id, - asset, - pubkey: account.pubkey, - pending: value, - index: 0 + let confirmed = 0; + + let account = await db.Account.findOne({ + where: { + id: invoice.account_id + }, + lock: transaction.LOCK.UPDATE, + transaction }); - } - - if (config.liquid.walletpass) - await lq.walletPassphrase(config.liquid.walletpass, 300); - - await user.save(); - - const currency = invoice ? invoice.currency : user.currency; - const rate = invoice ? invoice.rate : app.get("rates")[user.currency]; - const tip = invoice ? invoice.tip : 0; - const memo = invoice ? invoice.memo : ""; - - let payment = await db.Payment.create({ - account_id: account.id, - user_id: user.id, - hash: blinded.txid, - amount: value - tip, - currency, - memo, - rate, - received: true, - tip, - confirmed, - address, - network: "liquid" - }); - payments.push(blinded.txid); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); + console.log(account); + if ( + account.asset === asset && + (!account.pubkey || account.network === "liquid") + ) { + await account.increment({ pending: value }, { transaction }); + await account.reload({ transaction }); + } else { + account = await getAccount( + { + seed: account.seed, + path: account.path, + user_id: user.id, + asset, + pubkey: account.pubkey, + pending: value, + index: 0 + }, + transaction + ); + } - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - l.info("liquid detected", address, user.username, asset, value); - notify(user, `${value} SAT payment detected`); + if (config.liquid.walletpass) + await lq.walletPassphrase(config.liquid.walletpass, 300); + + await user.save({ transaction }); + + const currency = invoice ? invoice.currency : user.currency; + const rate = invoice + ? invoice.rate + : app.get("rates")[user.currency]; + const tip = invoice ? invoice.tip : 0; + const memo = invoice ? invoice.memo : ""; + + let payment = await db.Payment.create( + { + account_id: account.id, + user_id: user.id, + hash: blinded.txid, + amount: value - tip, + currency, + memo, + rate, + received: true, + tip, + confirmed, + address, + network: "liquid" + }, + { transaction } + ); + + payments.push(blinded.txid); + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); + + emit(user.username, "payment", payment); + emit(user.username, "account", payment.account); + l.info("liquid detected", address, user.username, asset, value); + notify(user, `${value} SAT payment detected`); + }); } } catch (e) { l.error("Problem processing transaction", e.message, e.stack); @@ -279,10 +296,15 @@ setInterval(async () => { if (p) { let total = p.amount + p.tip; p.confirmed = 1; - p.account.balance += total; - p.account.pending -= Math.min(p.account.pending, total); - await p.account.save({ transaction }); + + await p.account.increment({ balance: total }, { transaction }); + await p.account.decrement( + { pending: Math.min(p.account.pending, total) }, + { transaction } + ); + await p.account.reload({ transaction }); + await p.save({ transaction }); p = p.get({ plain: true }); diff --git a/routes/liquid/send.js b/routes/liquid/send.js index a04bc007..04b90814 100644 --- a/routes/liquid/send.js +++ b/routes/liquid/send.js @@ -54,13 +54,16 @@ module.exports = ah(async (req, res) => { where: { asset: nonbtc[0], user_id: null - } + }, + lock: transaction.LOCK.UPDATE, + transaction }); if (faucet) { covered = faucet.balance; if (covered > fee) covered = fee; - faucet.balance -= covered; + await faucet.decrement({ balance: covered }, { transaction }); + await faucet.reload({ transaction }); await faucet.save({ transaction }); } @@ -88,12 +91,14 @@ module.exports = ah(async (req, res) => { balance: account.balance }); throw new Error( - `Insufficient funds, need ${total} ${account.ticker === 'BTC' ? 'SAT' : account.ticker}, have ${account.balance}` + `Insufficient funds, need ${total} ${ + account.ticker === "BTC" ? "SAT" : account.ticker + }, have ${account.balance}` ); } - account.balance -= total; - await account.save({ transaction }); + await account.decrement({ balance: total }, { transaction }); + await account.reload({ transaction }); let payment = { amount: -amount, @@ -113,14 +118,7 @@ module.exports = ah(async (req, res) => { payments.push(payment); } } - }); - } catch (e) { - l.error("problem creating liquid payment", user.username, e.message); - return res.status(500).send(e.message); - } - try { - await db.transaction(async transaction => { if (config.liquid.walletpass) await lq.walletPassphrase(config.liquid.walletpass, 300); diff --git a/routes/lnurl.js b/routes/lnurl.js index f47cf6f4..f6b066ff 100644 --- a/routes/lnurl.js +++ b/routes/lnurl.js @@ -395,8 +395,8 @@ lnurlServer.bindToHook( if (p) { l.info("found payment", pr); payment.fee = p.fee; - account.balance -= p.fee; - await account.save(); + await account.decrement({ balance: p.fee }, { transaction }); + await account.reload({ transaction }); await payment.save(); payment = payment.get({ plain: true }); payment.account = account.get({ plain: true }); @@ -410,8 +410,8 @@ lnurlServer.bindToHook( } }, 1000); - account.balance -= amount; - await account.save({ transaction }); + await account.decrement({ balance: amount }, { transaction }); + await account.reload({ transaction }); let p = payment.get({ plain: true }); p.account = account.get({ plain: true }); diff --git a/routes/send.js b/routes/send.js index 8a183183..8e5e527b 100644 --- a/routes/send.js +++ b/routes/send.js @@ -2,7 +2,6 @@ const axios = require("axios"); const uuidv4 = require("uuid/v4"); module.exports = ah(async (req, res, next) => { - return res.status(500).send("Internal payments are temporarily disabled"); let { amount, asset, memo, username } = req.body; let { user } = req; amount = parseInt(amount); @@ -84,12 +83,11 @@ module.exports = ah(async (req, res, next) => { if (recipient.account.asset === asset && !recipient.account.pubkey) ({ account: a2 } = recipient); else { - a2 = await db.Account.findOne( - { - where: acc - }, - { transaction } - ); + a2 = await db.Account.findOne({ + where: acc, + lock: transaction.LOCK.UPDATE, + transaction + }); } if (a2) { @@ -106,16 +104,15 @@ module.exports = ah(async (req, res, next) => { if (assets[asset]) { ({ domain, ticker, precision, name } = assets[asset]); } else { - const existing = await db.Account.findOne( - { - where: { - asset - }, - order: [["id", "ASC"]], - limit: 1 + const existing = await db.Account.findOne({ + where: { + asset }, - { transaction } - ); + order: [["id", "ASC"]], + limit: 1, + lock: transaction.LOCK.UPDATE, + transaction + }); if (existing) { ({ domain, ticker, precision, name } = existing); diff --git a/routes/users.js b/routes/users.js index c9d60585..41fdb154 100644 --- a/routes/users.js +++ b/routes/users.js @@ -508,14 +508,14 @@ app.post( } = req.body; try { - let account = await db.Account.findOne({ - where: { id, user_id: user.id } - }); - - if (!account || (account.pubkey && !pubkey)) throw new Error("Problem updating account"); + await db.transaction(async transaction => { + let account = await db.Account.findOne({ + where: { id, user_id: user.id }, + lock: transaction.LOCK.UPDATE, + transaction + }); - await db.Account.update( - { + let params = { name, ticker, precision, @@ -526,18 +526,19 @@ app.post( hide, index, privkey - }, - { - where: { id, user_id: user.id } - } - ); + }; - account = await db.Account.findOne({ - where: { id } - }); + if (!account || (account.pubkey && !pubkey)) delete params.pubkey; - emit(user.username, "account", account); - res.end(); + await db.Account.update(params, { + where: { id, user_id: user.id }, + transaction + }); + + await account.reload({ transaction }); + emit(user.username, "account", account); + res.end(); + }); } catch (e) { l.error(e.message); return res.status(500).send("There was a problem updating the account"); @@ -676,8 +677,8 @@ app.post( { transaction } ); - account.balance -= source.amount; - await account.save(); + await account.decrement({ balance: source.amount }, { transaction }); + await account.reload({ transaction }); payment = payment.get({ plain: true }); payment.account = account.get({ plain: true }); @@ -711,3 +712,37 @@ app.post( res.send(await bcrypt.compare(password, user.password)); }) ); + +app.get( + "/isInternal", + auth, + ah(async function(req, res) { + let { user } = req; + let { address } = req.query; + if (!address) throw new Error("Address not provided"); + + let invoice = await db.Invoice.findOne({ + where: { address }, + include: { + attributes: ["username"], + model: db.User, + as: "user" + } + }); + + if (invoice) { + let info; + try { + info = await bc.getAddressInfo(address); + } catch (e) { + info = await lq.getAddressInfo(address); + } + if (info.ismine) { + emit(user.username, "to", invoice.user); + return res.send(true); + } + } + + res.send(false); + }) +); -- cgit v1.2.3