From e390a06aae008785878a49f428cedb207678af0c Mon Sep 17 00:00:00 2001 From: Overtorment Date: Mon, 4 May 2020 14:09:37 +0100 Subject: [PATCH] ADD: process-unpaid-invoices; REF: stuck payments script; REF: website; REF: important channels script; DOC: some docs --- class/Invo.js | 86 ++++++++++++++++++++++++++ class/Paym.js | 5 +- class/User.js | 9 ++- class/index.js | 1 + controllers/website.js | 6 ++ doc/recover.md | 18 ++++++ scripts/important-channels.js | 99 +++++++++++++++++++++++------- scripts/process-locked-payments.js | 8 +++ scripts/process-unpaid-invoices.js | 35 +++++++++++ templates/index.html | 9 +++ 10 files changed, 252 insertions(+), 24 deletions(-) create mode 100644 class/Invo.js create mode 100644 doc/recover.md create mode 100644 scripts/process-unpaid-invoices.js diff --git a/class/Invo.js b/class/Invo.js new file mode 100644 index 0000000..81468a5 --- /dev/null +++ b/class/Invo.js @@ -0,0 +1,86 @@ +var lightningPayReq = require('bolt11'); + +export class Invo { + constructor(redis, bitcoindrpc, lightning) { + this._redis = redis; + this._bitcoindrpc = bitcoindrpc; + this._lightning = lightning; + this._decoded = false; + this._bolt11 = false; + this._isPaid = null; + } + + setInvoice(bolt11) { + this._bolt11 = bolt11; + } + + async getIsMarkedAsPaidInDatabase() { + if (!this._bolt11) throw new Error('bolt11 is not provided'); + const decoded = lightningPayReq.decode(this._bolt11); + let paymentHash = false; + for (const tag of decoded.tags) { + if (tag.tagName === 'payment_hash') { + paymentHash = tag.data; + } + } + if (!paymentHash) throw new Error('Could not find payment hash in invoice tags'); + return await this._getIsPaymentHashMarkedPaidInDatabase(paymentHash); + } + + async markAsPaidInDatabase() { + if (!this._bolt11) throw new Error('bolt11 is not provided'); + const decoded = lightningPayReq.decode(this._bolt11); + let paymentHash = false; + for (const tag of decoded.tags) { + if (tag.tagName === 'payment_hash') { + paymentHash = tag.data; + } + } + if (!paymentHash) throw new Error('Could not find payment hash in invoice tags'); + return await this._setIsPaymentHashPaidInDatabase(paymentHash, true); + } + + async markAsUnpaidInDatabase() { + if (!this._bolt11) throw new Error('bolt11 is not provided'); + const decoded = lightningPayReq.decode(this._bolt11); + let paymentHash = false; + for (const tag of decoded.tags) { + if (tag.tagName === 'payment_hash') { + paymentHash = tag.data; + } + } + if (!paymentHash) throw new Error('Could not find payment hash in invoice tags'); + return await this._setIsPaymentHashPaidInDatabase(paymentHash, false); + } + + async _setIsPaymentHashPaidInDatabase(paymentHash, isPaid) { + if (isPaid) { + return await this._redis.set('ispaid_' + paymentHash, 1); + } else { + return await this._redis.del('ispaid_' + paymentHash); + } + } + + async _getIsPaymentHashMarkedPaidInDatabase(paymentHash) { + return await this._redis.get('ispaid_' + paymentHash); + } + + /** + * Queries LND ofr all user invoices + * + * @return {Promise} + */ + async listInvoices() { + return new Promise((resolve, reject) => { + this._lightning.listInvoices( + { + num_max_invoices: 9000111, + }, + function(err, response) { + if (err) return reject(err); + resolve(response); + }, + ); + }); + } +} diff --git a/class/Paym.js b/class/Paym.js index 1bb0fda..4d8b663 100644 --- a/class/Paym.js +++ b/class/Paym.js @@ -38,7 +38,6 @@ export class Paym { var request = { pub_key: this._decoded.destination, amt: this._decoded.num_satoshis, - num_routes: 1, final_cltv_delta: 144, fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * 0.01) + 1 }, }; @@ -57,9 +56,11 @@ export class Paym { let request = { payment_hash_string: this._decoded.payment_hash, - routes: routes, + route: routes[0], }; + console.log('sendToRouteSync:', { request }); + let that = this; return new Promise(function(resolve, reject) { that._lightning.sendToRouteSync(request, function(err, response) { diff --git a/class/User.js b/class/User.js index c203b22..19539cd 100644 --- a/class/User.js +++ b/class/User.js @@ -212,6 +212,8 @@ export class User { /** * Doent belong here, FIXME + * @see Invo._setIsPaymentHashPaidInDatabase + * @see Invo.markAsPaidInDatabase */ async setPaymentHashPaid(payment_hash) { return await this._redis.set('ispaid_' + payment_hash, 1); @@ -229,6 +231,8 @@ export class User { /** * Doent belong here, FIXME + * @see Invo._getIsPaymentHashMarkedPaidInDatabase + * @see Invo.getIsMarkedAsPaidInDatabase */ async getPaymentHashPaid(payment_hash) { return await this._redis.get('ispaid_' + payment_hash); @@ -322,7 +326,10 @@ export class User { invoice.value = +invoice.payment_route.total_fees + +invoice.payment_route.total_amt; if (invoice.payment_route.total_amt_msat && invoice.payment_route.total_amt_msat / 1000 !== +invoice.payment_route.total_amt) { // okay, we have to account for MSAT - invoice.value = +invoice.payment_route.total_fees + Math.max(parseInt(invoice.payment_route.total_amt_msat / 1000), +invoice.payment_route.total_amt) + 1; // extra sat to cover for msats, as external layer (clients) dont have that resolution + invoice.value = + +invoice.payment_route.total_fees + + Math.max(parseInt(invoice.payment_route.total_amt_msat / 1000), +invoice.payment_route.total_amt) + + 1; // extra sat to cover for msats, as external layer (clients) dont have that resolution } } else { invoice.fee = 0; diff --git a/class/index.js b/class/index.js index 16370be..6fda5a0 100644 --- a/class/index.js +++ b/class/index.js @@ -1,3 +1,4 @@ export * from './User'; export * from './Lock'; export * from './Paym'; +export * from './Invo'; diff --git a/controllers/website.js b/controllers/website.js index 5da942e..6cbb2d4 100644 --- a/controllers/website.js +++ b/controllers/website.js @@ -70,6 +70,12 @@ const pubkey2name = { '02a0bc43557fae6af7be8e3a29fdebda819e439bea9c0f8eb8ed6a0201f3471ca9': 'LightningPeachHub', '02d4531a2f2e6e5a9033d37d548cff4834a3898e74c3abe1985b493c42ebbd707d': 'coinfinity.co', '02d23fa6794d8fd056c757f3c8f4877782138dafffedc831fc570cab572620dc61': 'paywithmoon.com', + '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5': 'paywithmoon.com', + '02004c625d622245606a1ea2c1c69cfb4516b703b47945a3647713c05fe4aaeb1c': 'walletofsatoshi', + '0331f80652fb840239df8dc99205792bba2e559a05469915804c08420230e23c7c': 'LightningPowerUsers.com', + '033d8656219478701227199cbd6f670335c8d408a92ae88b962c49d4dc0e83e025': 'bfx-lnd0', + '03021c5f5f57322740e4ee6936452add19dc7ea7ccf90635f95119ab82a62ae268': 'lnd1.bluewallet.io', + '037cc5f9f1da20ac0d60e83989729a204a33cc2d8e80438969fadf35c1c5f1233b': 'lnd2.bluewallet.io', }; router.get('/', function(req, res) { diff --git a/doc/recover.md b/doc/recover.md new file mode 100644 index 0000000..ae23df2 --- /dev/null +++ b/doc/recover.md @@ -0,0 +1,18 @@ + +recover user's wallet +===================== + +* find user's id + f0db84e6fd5dee530314fbb90cec24839f4620914e7cd0c7 +* issue new credentials via tests/integration/LightningCustodianWallet.test.js + lndhub://3d7c028419356d017199:66666666666666666666 + (this is user:password) +* lookup redis record `user_{login}_{password_hash} = {userid}` : + ``` + > keys user_3d7c028419356d017199* + 1) "user_3d7c028419356d017199_505018e35414147406fcacdae63babbfca9b1abfcb6d091a4cca9a7611183284" + ``` + +* save to this record old user's id: + `> set user_3d7c028419356d017199_505018e35414147406fcacdae63babbfca9b1abfcb6d091a4cca9a7611183284 f0db84e6fd5dee530314fbb90cec24839f4620914e7cd0c7` + done! issued credentials should point to old user diff --git a/scripts/important-channels.js b/scripts/important-channels.js index ced5297..6e8c3a3 100644 --- a/scripts/important-channels.js +++ b/scripts/important-channels.js @@ -1,20 +1,52 @@ const important_channels = { - '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f': 'ACINQ', - '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': 'OpenNode', - '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': 'coingate.com', - '0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3': 'bitrefill thor', - '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': 'bitrefill 2', - '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5': 'paywithmoon.com', - '0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': 'ln1.satoshilabs.com', - '026c7d28784791a4b31a64eb34d9ab01552055b795919165e6ae886de637632efb': 'LivingRoomOfSatoshi', - '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774': 'ln.pizza aka fold', -}; - -const wumbo = { - '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': true, // opennode - '0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3': true, // bitrefill - '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': true, // bitrefill 2 - '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774': true, // fold + '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f': { + name: 'ACINQ', + uri: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f@34.239.230.56:9735', + }, + '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': { + name: 'OpenNode', + uri: '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@18.221.23.28:9735', + wumbo: 1, + }, + '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': { + name: 'coingate.com', + uri: '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735', + }, + '0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3': { + name: 'bitrefill thor', + uri: '0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3@52.30.63.2:9735', + wumbo: 1, + }, + '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': { + name: 'bitrefill 2', + uri: '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f@52.50.244.44:9735', + wumbo: 1, + }, + '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5': { + name: 'paywithmoon.com', + uri: '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5@52.86.210.65:9735', + }, + '0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': { + name: 'ln1.satoshilabs.com', + uri: '0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4@157.230.28.160:9735', + }, + '02004c625d622245606a1ea2c1c69cfb4516b703b47945a3647713c05fe4aaeb1c': { + name: 'LivingRoomOfSatoshi', + uri: '02004c625d622245606a1ea2c1c69cfb4516b703b47945a3647713c05fe4aaeb1c@172.81.178.151:9735', + }, + '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774': { + name: 'ln.pizza aka fold', + uri: '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774@35.238.153.25:9735', + wumbo: 1, + }, + '0331f80652fb840239df8dc99205792bba2e559a05469915804c08420230e23c7c': { + name: 'LightningPowerUsers.com', + uri: '0331f80652fb840239df8dc99205792bba2e559a05469915804c08420230e23c7c@34.200.181.109:9735', + }, + '033d8656219478701227199cbd6f670335c8d408a92ae88b962c49d4dc0e83e025': { + name: 'bfx-lnd0', + uri: '033d8656219478701227199cbd6f670335c8d408a92ae88b962c49d4dc0e83e025@34.65.85.39:9735', + }, }; let lightning = require('../lightning'); @@ -27,12 +59,12 @@ lightning.listChannels({}, function(err, response) { } let lightningListChannels = response; for (let channel of lightningListChannels.channels) { - if (0 && channel.capacity < 5000000) { + if (channel.capacity < 0.05 / 100000000) { console.log( 'lncli closechannel', channel.channel_point.replace(':', ' '), (!channel.active && '--force') || '', - '#', + '; sleep 10 #', 'low capacity channel', channel.capacity / 100000000, 'btc', @@ -40,7 +72,27 @@ lightning.listChannels({}, function(err, response) { } } - for (let important of Object.keys(important_channels)) { + console.log('# reconnect important channels that are inactive:\n'); + + for (const important of Object.keys(important_channels)) { + for (let channel of lightningListChannels.channels) { + if (channel.remote_pubkey === important && !channel.active) { + console.log( + 'lncli disconnect', + channel.remote_pubkey, + '; sleep 5;', + 'lncli connect', + important_channels[channel.remote_pubkey].uri, + '#', + important_channels[channel.remote_pubkey].name, + ); + } + } + } + + console.log('\n# open important channels:\n'); + + for (const important of Object.keys(important_channels)) { let atLeastOneChannelIsSufficientCapacity = false; for (let channel of lightningListChannels.channels) { if (channel.remote_pubkey === important && channel.local_balance >= 4000000 && channel.active) { @@ -50,12 +102,17 @@ lightning.listChannels({}, function(err, response) { if (!atLeastOneChannelIsSufficientCapacity) { console.log( + 'lncli disconnect', + important, + '; sleep 3;', 'lncli openchannel --node_key', important, + '--connect', + important_channels[important].uri.split('@')[1], '--local_amt', - wumbo[important] ? '167772150' : '16777215', + important_channels[important].wumbo ? '167772150' : '16777215', '#', - important_channels[important], + important_channels[important].name, ); } } diff --git a/scripts/process-locked-payments.js b/scripts/process-locked-payments.js index 75de03c..beb1969 100644 --- a/scripts/process-locked-payments.js +++ b/scripts/process-locked-payments.js @@ -1,6 +1,12 @@ +/** + * This script gets all locked payments from our database and cross-checks them with actual + * sentout payments from LND. If locked payment is in there we moe locked payment to array of real payments for the user + * (it is effectively spent coins by user), if not - we attempt to pay it again (if it is not too old). + */ import { User, Lock, Paym } from '../class/'; const config = require('../config'); +const fs = require('fs'); var Redis = require('ioredis'); var redis = new Redis(config.redis); @@ -15,6 +21,7 @@ let lightning = require('../lightning'); let tempPaym = new Paym(redis, bitcoinclient, lightning); let listPayments = await tempPaym.listPayments(); console.log('done', 'got', listPayments['payments'].length, 'payments'); + fs.writeFileSync('listPayments.json', JSON.stringify(listPayments['payments'], null, 2)); for (let key of keys) { const userid = key.replace('locked_payments_for_', ''); @@ -34,6 +41,7 @@ let lightning = require('../lightning'); if (daysPassed < 2) { // if (!await payment.isExpired()) { let sendResult; + console.log('attempting to pay to route'); try { sendResult = await payment.attemptPayToRoute(); } catch (_) { diff --git a/scripts/process-unpaid-invoices.js b/scripts/process-unpaid-invoices.js new file mode 100644 index 0000000..8364557 --- /dev/null +++ b/scripts/process-unpaid-invoices.js @@ -0,0 +1,35 @@ +/** + * This script goes through all user invoices in LND and if it is settled - marks it + * so in our database. Does this only for invoices younger than week. * + */ +import { Invo } from '../class/'; +const config = require('../config'); + +const fs = require('fs'); +const Redis = require('ioredis'); +const redis = new Redis(config.redis); + +let bitcoinclient = require('../bitcoin'); +let lightning = require('../lightning'); + +(async () => { + console.log('fetching listinvoices...'); + let tempInv = new Invo(redis, bitcoinclient, lightning); + + let listinvoices = await tempInv.listInvoices(); + console.log('done', 'got', listinvoices['invoices'].length, 'invoices'); + fs.writeFileSync('listInvoices.json', JSON.stringify(listinvoices['invoices'], null, 2)); + + let markedInvoices = 0; + for (const invoice of listinvoices['invoices']) { + if (invoice.state === 'SETTLED' && +invoice.creation_date >= +new Date() / 1000 - 3600 * 24 * 7) { + tempInv.setInvoice(invoice.payment_request); + await tempInv.markAsPaidInDatabase(); + markedInvoices++; + process.stdout.write(markedInvoices + '\r'); + } + } + + console.log('done, marked', markedInvoices, 'invoices'); + process.exit(); +})(); diff --git a/templates/index.html b/templates/index.html index 456dfa5..4e69b52 100644 --- a/templates/index.html +++ b/templates/index.html @@ -62,9 +62,18 @@
num_active_channels:
{{num_active_channels}}
 
+
num_pending_channels:
+
{{num_pending_channels}}
+
 
num_peers:
{{num_peers}}
 
+
block_height:
+
{{block_height}}
+
 
+
synced_to_chain:
+
{{synced_to_chain}}
+
 
version:
{{version}}