From 1ae4c03116ce3a650af88f78a4b5d19581238c71 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 9 Mar 2020 10:59:04 -0700 Subject: fee estimation --- bitcoinPayments.js | 42 ++++++++++++++----------------------- estimateBitcoinFee.js | 32 ++++++++++++++++++++++++++++ estimateLiquidFee.js | 35 +++++++++++++++++++++++++++++++ liquidPayments.js | 18 ++++++---------- payments.js | 6 ++++-- sendCoins.js | 55 +++++++++++++++++++++++++++--------------------- sendLiquid.js | 58 ++++++++++++++++++++++++++++++--------------------- 7 files changed, 158 insertions(+), 88 deletions(-) create mode 100644 estimateBitcoinFee.js create mode 100644 estimateLiquidFee.js diff --git a/bitcoinPayments.js b/bitcoinPayments.js index 64a50e58..5b916265 100644 --- a/bitcoinPayments.js +++ b/bitcoinPayments.js @@ -5,6 +5,7 @@ const Sequelize = require("sequelize"); const bitcoin = require("bitcoinjs-lib"); const l = require("pino")(); +const toSats = n => parseInt((n * 100000000).toFixed()) const zmqRawBlock = zmq.socket("sub"); zmqRawBlock.connect(config.bitcoin.zmqrawblock); @@ -17,26 +18,19 @@ zmqRawTx.subscribe("rawtx"); let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; module.exports = (app, bc, db, addresses, payments, emit) => { - zmqRawTx.on("message", (topic, message, sequence) => { - message = message.toString("hex"); - - let tx = bitcoin.Transaction.fromHex(message); - let hash = reverse(tx.getHash()).toString("hex"); + zmqRawTx.on("message", async (topic, message, sequence) => { + const hex = message.toString("hex"); + const tx = await bc.decodeRawTransaction(hex); + let hash = tx.txid; if (payments.includes(hash)) return; Promise.all( - tx.outs.map(async o => { - - let address; - try { - address = bitcoin.address.fromOutputScript( - o.script, - NETWORK - ); - } catch (e) { - return; - } + tx.vout.map(async o => { + if (!(o.scriptPubKey && o.scriptPubKey.addresses)) return; + + const value = toSats(o.value); + const address = o.scriptPubKey.addresses[0]; if (Object.keys(addresses).includes(address)) { payments.push(hash); @@ -64,22 +58,18 @@ module.exports = (app, bc, db, addresses, payments, emit) => { let confirmed = false; - if (user.friend) { - user.balance += o.value; - confirmed = true; - } else { - user.pending += o.value; - } - user.address = await bc.getNewAddress("", "bech32"); - addresses[user.address] = user.username; + user.pending += value; + await user.save(); emit(user.username, "user", user); + + addresses[user.address] = user.username; const payment = await db.Payment.create({ user_id: user.id, hash, - amount: o.value, + amount: value, currency: user.currency, rate: app.get("rates")[user.currency], received: true, @@ -126,7 +116,7 @@ module.exports = (app, bc, db, addresses, payments, emit) => { let user = await p.getUser(); user.balance += p.amount; - user.pending -= p.amount; + user.pending -= Math.min(user.pending, p.amount); emit(user.username, "user", user); await user.save(); diff --git a/estimateBitcoinFee.js b/estimateBitcoinFee.js new file mode 100644 index 00000000..215a180d --- /dev/null +++ b/estimateBitcoinFee.js @@ -0,0 +1,32 @@ +const config = require("./config"); + +const l = require("pino")(); +const SATS = 100000000; + +module.exports = (app, bc, db, emit) => async (req, res) => { + let { user } = req; + let { address, amount, confTarget, feeRate, mode } = req.body; + let tx, fee; + + try { + amount = parseInt(amount); + + tx = await bc.createRawTransaction([], { + [address]: (amount / SATS).toFixed(8) + }); + + let params = { + subtractFeeFromOutputs: amount === user.balance ? [0] : [] + } + + if (feeRate) params["feeRate"] = (parseInt(feeRate) / SATS * 1000).toFixed(8); + if (confTarget) params["conf_target"] = parseInt(confTarget); + if (mode) params["estimate_mode"] = mode; + + tx = await bc.fundRawTransaction(tx, params); + res.send({ tx }); + } catch (e) { + l.error(e); + return res.status(500).send(e.message); + } +}; diff --git a/estimateLiquidFee.js b/estimateLiquidFee.js new file mode 100644 index 00000000..fc1655b6 --- /dev/null +++ b/estimateLiquidFee.js @@ -0,0 +1,35 @@ +const config = require("./config"); +const BitcoinCore = require("bitcoin-core"); + +const l = require("pino")(); +const SATS = 100000000; + +const bc = new BitcoinCore(config.liquid); + +module.exports = (app, db, emit) => async (req, res) => { + let { user } = req; + let { address, amount, confTarget, feeRate, mode } = req.body; + let tx, fee; + + try { + amount = parseInt(amount); + + tx = await bc.createRawTransaction([], { + [address]: (amount / SATS).toFixed(8) + }); + + let params = { + subtractFeeFromOutputs: amount === user.balance ? [0] : [] + } + + if (feeRate) params["feeRate"] = (parseInt(feeRate) / SATS * 1000).toFixed(8); + if (confTarget) params["conf_target"] = parseInt(confTarget); + if (mode) params["estimate_mode"] = mode; + + tx = await bc.fundRawTransaction(tx, params); + res.send({ tx }); + } catch (e) { + l.error(e); + return res.status(500).send(e.message); + } +}; diff --git a/liquidPayments.js b/liquidPayments.js index 8ed5507a..3c2a12f4 100644 --- a/liquidPayments.js +++ b/liquidPayments.js @@ -17,7 +17,7 @@ zmqRawTx.subscribe("rawtx"); let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; const bc = new BitcoinCore(config.liquid); -const SATS = 100000000; +const toSats = n => parseInt((n * 100000000).toFixed()) module.exports = (app, db, addresses, payments, emit) => { zmqRawTx.on("message", async (topic, message, sequence) => { @@ -30,8 +30,7 @@ module.exports = (app, db, addresses, payments, emit) => { Promise.all( tx.vout.map(async o => { if (!(o.scriptPubKey && o.scriptPubKey.addresses)) return; - let { value } = o; - value *= SATS + const value = toSats(o.value); const address = o.scriptPubKey.addresses[0]; if (Object.keys(addresses).includes(address)) { @@ -58,20 +57,15 @@ module.exports = (app, db, addresses, payments, emit) => { let confirmed = 0; - if (user.friend) { - user.balance += value; - confirmed = 1; - } else { - user.pending += value; - } - + user.pending += value; user.confidential = await bc.getNewAddress(); user.liquid = (await bc.getAddressInfo(user.confidential)).unconfidential; - addresses[user.liquid] = user.username; await user.save(); emit(user.username, "user", user); + addresses[user.liquid] = user.username; + const payment = await db.Payment.create({ user_id: user.id, hash: blinded.txid, @@ -126,7 +120,7 @@ module.exports = (app, db, addresses, payments, emit) => { let user = await p.getUser(); user.balance += p.amount; - user.pending -= p.amount; + user.pending -= Math.min(user.pending, p.amount); l.info("liquid confirmed", user.username, p.amount); emit(user.username, "user", user); diff --git a/payments.js b/payments.js index 10c3b541..c2d9d306 100644 --- a/payments.js +++ b/payments.js @@ -24,8 +24,10 @@ module.exports = async (app, auth, addresses, bc, db, emit, seen, payments) => { require("./sendPayment")(app, db, emit, seen, lnaraw, lnb) ); app.post("/payUser", auth, require("./payUser")(app, db, lna, lnb)); - app.post("/sendCoins", auth, require("./sendCoins")(app, bc, db, emit)); - app.post("/sendLiquid", auth, require("./sendLiquid")(app, db, emit)); + app.post("/sendCoins", auth, require("./sendCoins")(addresses, app, bc, db, emit)); + app.post("/estimateBitcoinFee", auth, require("./estimateBitcoinFee")(app, bc, db, emit)); + app.post("/sendLiquid", auth, require("./sendLiquid")(addresses, app, db, emit)); + app.post("/estimateLiquidFee", auth, require("./estimateLiquidFee")(app, db, emit)); app.post("/addInvoice", auth, require("./addInvoice")(app, db, lnb)); app.get("/payments", auth, async (req, res) => { const payments = await db.Payment.findAll({ diff --git a/sendCoins.js b/sendCoins.js index 00e2fa99..6d871ca4 100644 --- a/sendCoins.js +++ b/sendCoins.js @@ -3,31 +3,40 @@ const config = require("./config"); const reverse = require("buffer-reverse"); const l = require("pino")(); -const SATS = 100000000; +const toSats = n => parseInt((n * 100000000).toFixed()) -module.exports = (app, bc, db, emit) => async (req, res) => { +module.exports = (addresses, app, bc, db, emit) => async (req, res) => { let { user } = req; - let { address, amount } = req.body; - let rawtx, hex, fee, total; + let { address, tx } = req.body; + let { hex } = tx; - try { - amount = parseInt(amount); + let fee = toSats(tx.fee); - rawtx = await bc.createRawTransaction([], { - [address]: (amount / SATS).toFixed(8) - }); + const isChange = async address => + !((await bc.getAddressInfo(address)).ismine) || + !Object.keys(addresses).includes(address) || + address === user.address; - rawtx = await bc.fundRawTransaction(rawtx, { - subtractFeeFromOutputs: amount === user.balance ? [0] : [] - }); + tx = await bc.decodeRawTransaction(hex); - ({ hex, fee } = rawtx); - fee = parseInt(fee * SATS); - total = amount + fee; - } catch (e) { - l.error(e); - return res.status(500).send(e.message); - } + let total = 0; + let change = 0; + + for (let i = 0; i < tx.vout.length; i++) { + let o = tx.vout[i]; + total += toSats(o.value); + + if (o.scriptPubKey.addresses) { + if ((await isChange(o.scriptPubKey.addresses[0]))) { + change += toSats(o.value); + } + } + } + + l.info(total); + total = (total - change) + fee; + let amount = total - fee; + l.info(fee, total, amount, change); try { await db.transaction(async transaction => { @@ -56,10 +65,8 @@ module.exports = (app, bc, db, emit) => async (req, res) => { if (config.bitcoin.walletpass) await bc.walletPassphrase(config.bitcoin.walletpass, 300); - rawtx = (await bc.signRawTransactionWithWallet(hex)).hex; - let txid = await bc.sendRawTransaction(rawtx); - - let tx = bitcoin.Transaction.fromHex(rawtx); + hex = (await bc.signRawTransactionWithWallet(hex)).hex; + let txid = await bc.sendRawTransaction(hex); await db.transaction(async transaction => { await user.save({ transaction }); @@ -76,7 +83,7 @@ module.exports = (app, bc, db, emit) => async (req, res) => { address, confirmed: true, received: false, - asset: 'BTC', + asset: "BTC" }, { transaction } ); diff --git a/sendLiquid.js b/sendLiquid.js index 855a2617..a86395a2 100644 --- a/sendLiquid.js +++ b/sendLiquid.js @@ -4,35 +4,43 @@ const reverse = require("buffer-reverse"); const BitcoinCore = require("bitcoin-core"); const l = require("pino")(); -const SATS = 100000000; +const toSats = n => parseInt((n * 100000000).toFixed()) const bc = new BitcoinCore(config.liquid); -module.exports = (app, db, emit) => async (req, res) => { - let { address, amount } = req.body; - let rawtx, hex, fee, total; +module.exports = (addresses, app, db, emit) => async (req, res) => { let { user } = req; + let { + address, + tx: { hex } + } = req.body; + + const isChange = async address => + !((await bc.getAddressInfo(address)).ismine) || + !Object.keys(addresses).includes(address) || + address === user.liquid; + + const unblinded = await bc.unblindRawTransaction(hex); + tx = await bc.decodeRawTransaction(unblinded.hex); + + let total = 0; + let change = 0; + let fee = 0; + + for (let i = 0; i < tx.vout.length; i++) { + let o = tx.vout[i]; + total += toSats(o.value); + if (o.scriptPubKey.type === 'fee') fee = toSats(o.value); + + if (o.scriptPubKey.addresses) { + if ((await isChange(o.scriptPubKey.addresses[0]))) { + change += toSats(o.value); + } + } + } - try { - amount = parseInt(amount); - - rawtx = await bc.createRawTransaction([], { - [address]: (amount / SATS).toFixed(8) - }); - - rawtx = await bc.fundRawTransaction(rawtx, { - subtractFeeFromOutputs: amount === user.balance ? [0] : [] - }); - - hex = await bc.blindRawTransaction(rawtx.hex); - - ({ fee } = rawtx); - fee = parseInt(fee * SATS); - total = amount + fee; - } catch (e) { - l.error("funding failed", e); - return res.status(500).send(e.message); - } + total = total - change; + let amount = total - fee; try { await db.transaction(async transaction => { @@ -61,12 +69,14 @@ module.exports = (app, db, emit) => async (req, res) => { if (config.liquid.walletpass) await bc.walletPassphrase(config.liquid.walletpass, 300); + hex = await bc.blindRawTransaction(hex); rawtx = (await bc.signRawTransactionWithWallet(hex)).hex; let txid = await bc.sendRawTransaction(rawtx); await db.transaction(async transaction => { await user.save({ transaction }); emit(user.username, "user", user); + l.info("total", total); const payment = await db.Payment.create( { -- cgit v1.2.3