48 Commits

Author SHA1 Message Date
Overtorment
fc0d2a4c86 RE: v1.2.0 2020-05-04 14:11:56 +01:00
Overtorment
b125d68fff Merge branch 'master' of github.com:BlueWallet/LndHub 2020-05-04 14:10:22 +01:00
Overtorment
e390a06aae ADD: process-unpaid-invoices; REF: stuck payments script; REF: website; REF: important channels script; DOC: some docs 2020-05-04 14:09:37 +01:00
snyk-bot
c8ef1e59e2 fix: upgrade ioredis from 4.16.1 to 4.16.2
Snyk has created this PR to upgrade ioredis from 4.16.1 to 4.16.2.

See this package in NPM:
https://www.npmjs.com/package/ioredis

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-05-04 11:45:14 +01:00
snyk-bot
3ad92da8a6 fix: upgrade ioredis from 4.16.1 to 4.16.2
Snyk has created this PR to upgrade ioredis from 4.16.1 to 4.16.2.

See this package in NPM:
https://www.npmjs.com/package/ioredis

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-05-04 11:45:14 +01:00
snyk-bot
61956f2a46 fix: upgrade prettier from 2.0.3 to 2.0.4
Snyk has created this PR to upgrade prettier from 2.0.3 to 2.0.4.

See this package in NPM:
https://www.npmjs.com/package/prettier

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-04-29 16:15:15 +01:00
snyk-bot
93899facd6 fix: upgrade prettier from 2.0.3 to 2.0.4
Snyk has created this PR to upgrade prettier from 2.0.3 to 2.0.4.

See this package in NPM:
https://www.npmjs.com/package/prettier

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-04-29 16:15:15 +01:00
snyk-bot
6723094ed6 fix: upgrade prettier from 2.0.2 to 2.0.3
Snyk has created this PR to upgrade prettier from 2.0.2 to 2.0.3.

See this package in NPM:
https://www.npmjs.com/package/prettier

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-04-28 16:59:39 +01:00
snyk-bot
ca1f5427cb fix: upgrade prettier from 2.0.2 to 2.0.3
Snyk has created this PR to upgrade prettier from 2.0.2 to 2.0.3.

See this package in NPM:
https://www.npmjs.com/package/prettier

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-04-28 16:59:39 +01:00
Overtorment
d4e8d9b476 Merge pull request #61 from bitarmy/master
Minimal changes
2020-04-02 10:46:53 +01:00
Agustin Kassis
3a2ee779b6 Revert config change 2020-04-01 23:08:02 -03:00
Agustin Kassis
ea1035e414 Lookup invoice on lnd if not already cached as paid on redis 2020-04-01 22:58:24 -03:00
Agustin Kassis
7afb56398a Updated npm packages 2020-04-01 20:52:22 -03:00
Agustin Kassis
a4cd81a106 Removed duplicated balance api 2020-04-01 20:51:14 -03:00
Agustin Kassis
7a94225d87 Error typos 2020-04-01 20:46:48 -03:00
Agustin Kassis
9be413a549 added tls.cert debug 2020-04-01 20:43:36 -03:00
Agustin Kassis
305559174f Removed checkinvoicepaid 2020-04-01 20:43:13 -03:00
Agustin Kassis
bfdd319070 Height check only on mainnet 2020-04-01 20:32:22 -03:00
Agustin Kassis
a6b08363a7 Merge branch 'bluewallet-lndhub'
# Conflicts:
#	controllers/api.js
2020-03-30 21:24:32 -03:00
Overtorment
9f45c31618 REL: ver bump 2020-03-27 21:09:55 +00:00
Overtorment
d7e91f51ec REF: get user invoices 2020-03-27 21:03:38 +00:00
Overtorment
b58e7dac88 stricted rate limiting for /getuserinvoices 2020-03-27 20:09:50 +00:00
Overtorment
7b5c6bc7e0 REF: more logging 2020-03-27 19:51:06 +00:00
Agustin Kassis
f9b2cca0bd Check individual invoice's payment 2020-03-23 13:44:44 +00:00
Agustin Kassis
376a3402bd Merge branch 'bluewallet-lndhub' 2020-03-20 20:32:07 -03:00
Agustin Kassis
38a3dea8c2 Check individual invoice's payment 2020-03-20 19:56:22 -03:00
snyk-bot
cdcd1fdd82 fix: upgrade babel-eslint from 10.0.3 to 10.1.0
Snyk has created this PR to upgrade babel-eslint from 10.0.3 to 10.1.0.

See this package in NPM:
https://www.npmjs.com/package/babel-eslint

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-03-19 10:34:11 +00:00
dependabot[bot]
f4105292a3 Build(deps): Bump acorn from 6.3.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-16 11:57:49 +00:00
Agustin Kassis
6074dc6c2c Merge branch 'bluewallet-lndhub'
# Conflicts:
#	package.json
2020-03-15 00:19:51 -03:00
snyk-bot
76421f2850 fix: upgrade ioredis from 4.15.1 to 4.16.0
Snyk has created this PR to upgrade ioredis from 4.15.1 to 4.16.0.

See this package in NPM:
https://www.npmjs.com/package/ioredis

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-03-13 11:42:52 +00:00
Overtorment
f01c533899 REF: minor 2020-03-11 16:01:35 +00:00
Overtorment
6b67839414 REF 2020-03-02 12:34:43 +00:00
Overtorment
dfe55a4f9f Merge branch 'master' of github.com:BlueWallet/LndHub 2020-01-25 15:41:55 +00:00
Overtorment
14d42a7aed REF 2020-01-25 15:41:49 +00:00
snyk-bot
a4b24ec00a fix: upgrade mustache from 3.1.0 to 3.2.1
Snyk has created this PR to upgrade mustache from 3.1.0 to 3.2.1.

See this package in NPM:
https://www.npmjs.com/package/mustache

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-01-25 15:00:02 +00:00
snyk-bot
0a0bf5caa3 fix: upgrade ioredis from 4.14.1 to 4.15.1
Snyk has created this PR to upgrade ioredis from 4.14.1 to 4.15.1.

See this package in NPM:
https://www.npmjs.com/package/ioredis

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-01-25 14:59:49 +00:00
snyk-bot
2f1c89bb8f fix: upgrade prettier from 1.18.2 to 1.19.1
Snyk has created this PR to upgrade prettier from 1.18.2 to 1.19.1.

See this package in NPM:
https://www.npmjs.com/package/prettier

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-01-25 14:58:36 +00:00
snyk-bot
243902faad fix: upgrade eslint-plugin-prettier from 3.1.1 to 3.1.2
Snyk has created this PR to upgrade eslint-plugin-prettier from 3.1.1 to 3.1.2.

See this package in NPM:
https://www.npmjs.com/package/eslint-plugin-prettier

See this project in Snyk:
https://app.snyk.io/org/bluewallet/project/29c066bc-abce-44d9-b68e-064466e610e7?utm_source=github&utm_medium=upgrade-pr
2020-01-25 14:57:47 +00:00
Overtorment
03ddddbf6a Merge branch 'master' of github.com:BlueWallet/LndHub 2020-01-24 21:20:03 +00:00
Overtorment
8e06bef3b9 REF: better error handling 2020-01-24 21:19:54 +00:00
Overtorment
b55e0f0327 FIX: fees 2020-01-24 09:44:13 +00:00
Overtorment
ec9a71f4e9 FIX: account for msats in /payinvoice 2020-01-24 00:19:27 +00:00
Overtorment
9d8466b595 FIX: invoice description decode 2020-01-23 23:09:50 +00:00
Overtorment
0f6a4d8cba FIX: default invoice expiry is 24h 2020-01-19 23:43:45 +00:00
Overtorment
b56064a7ba error logging 2020-01-18 13:39:55 +00:00
Agustin Kassis
6507de9770 Updated npm packages to latest version
Remove blockcount check for regtest
FIX
FIX: change default ln invoice expiry hour -> day
REF
FIX: added some caching; skip checking old invoices
Update README.md
2020-01-14 13:25:51 -03:00
Agustin Kassis
8f28fd2865 Updated npm packages to latest version 2019-11-26 18:12:00 -03:00
Agustin Kassis
013cadc5c8 Remove blockcount check for regtest 2019-11-26 18:05:12 -03:00
16 changed files with 1068 additions and 562 deletions

View File

@@ -3,5 +3,5 @@ const config = require('./config');
let jayson = require('jayson/promise'); let jayson = require('jayson/promise');
let url = require('url'); let url = require('url');
let rpc = url.parse(config.bitcoind.rpc); let rpc = url.parse(config.bitcoind.rpc);
rpc.timeout = 5000; rpc.timeout = 15000;
module.exports = jayson.client.http(rpc); module.exports = jayson.client.http(rpc);

86
class/Invo.js Normal file
View File

@@ -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<array>}
*/
async listInvoices() {
return new Promise((resolve, reject) => {
this._lightning.listInvoices(
{
num_max_invoices: 9000111,
},
function(err, response) {
if (err) return reject(err);
resolve(response);
},
);
});
}
}

View File

@@ -38,7 +38,6 @@ export class Paym {
var request = { var request = {
pub_key: this._decoded.destination, pub_key: this._decoded.destination,
amt: this._decoded.num_satoshis, amt: this._decoded.num_satoshis,
num_routes: 1,
final_cltv_delta: 144, final_cltv_delta: 144,
fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * 0.01) + 1 }, fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * 0.01) + 1 },
}; };
@@ -57,9 +56,11 @@ export class Paym {
let request = { let request = {
payment_hash_string: this._decoded.payment_hash, payment_hash_string: this._decoded.payment_hash,
routes: routes, route: routes[0],
}; };
console.log('sendToRouteSync:', { request });
let that = this; let that = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
that._lightning.sendToRouteSync(request, function(err, response) { that._lightning.sendToRouteSync(request, function(err, response) {

View File

@@ -212,6 +212,8 @@ export class User {
/** /**
* Doent belong here, FIXME * Doent belong here, FIXME
* @see Invo._setIsPaymentHashPaidInDatabase
* @see Invo.markAsPaidInDatabase
*/ */
async setPaymentHashPaid(payment_hash) { async setPaymentHashPaid(payment_hash) {
return await this._redis.set('ispaid_' + payment_hash, 1); return await this._redis.set('ispaid_' + payment_hash, 1);
@@ -229,13 +231,29 @@ export class User {
/** /**
* Doent belong here, FIXME * Doent belong here, FIXME
* @see Invo._getIsPaymentHashMarkedPaidInDatabase
* @see Invo.getIsMarkedAsPaidInDatabase
*/ */
async getPaymentHashPaid(payment_hash) { async getPaymentHashPaid(payment_hash) {
return await this._redis.get('ispaid_' + payment_hash); return await this._redis.get('ispaid_' + payment_hash);
} }
async getUserInvoices() { async syncInvoicePaid(payment_hash) {
const invoice = await this.lookupInvoice(payment_hash);
const ispaid = invoice.settled; // TODO: start using `state` instead as its future proof, and this one might get deprecated
if (ispaid) {
// so invoice was paid after all
await this.setPaymentHashPaid(payment_hash);
await this.clearBalanceCache();
}
return ispaid;
}
async getUserInvoices(limit) {
let range = await this._redis.lrange('userinvoices_for_' + this._userid, 0, -1); let range = await this._redis.lrange('userinvoices_for_' + this._userid, 0, -1);
if (limit && !isNaN(parseInt(limit))) {
range = range.slice(parseInt(limit) * -1);
}
let result = []; let result = [];
for (let invoice of range) { for (let invoice of range) {
invoice = JSON.parse(invoice); invoice = JSON.parse(invoice);
@@ -243,7 +261,11 @@ export class User {
invoice.description = ''; invoice.description = '';
for (let tag of decoded.tags) { for (let tag of decoded.tags) {
if (tag.tagName === 'description') { if (tag.tagName === 'description') {
try {
invoice.description += decodeURIComponent(tag.data); invoice.description += decodeURIComponent(tag.data);
} catch (_) {
invoice.description += tag.data;
}
} }
if (tag.tagName === 'payment_hash') { if (tag.tagName === 'payment_hash') {
invoice.payment_hash = tag.data; invoice.payment_hash = tag.data;
@@ -254,20 +276,14 @@ export class User {
if (!invoice.ispaid) { if (!invoice.ispaid) {
if (decoded && decoded.timestamp > +new Date() / 1000 - 3600 * 24 * 5) { if (decoded && decoded.timestamp > +new Date() / 1000 - 3600 * 24 * 5) {
// if invoice is not too old we query lnd to find out if its paid // if invoice is not too old we query lnd to find out if its paid
let lookup_info = await this.lookupInvoice(invoice.payment_hash); invoice.ispaid = await this.syncInvoicePaid(invoice.payment_hash);
invoice.ispaid = lookup_info.settled; // TODO: start using `state` instead as its future proof, and this one might get deprecated
if (invoice.ispaid) {
// so invoice was paid after all
await this.setPaymentHashPaid(invoice.payment_hash);
await this.clearBalanceCache();
}
} }
} else { } else {
_invoice_ispaid_cache[invoice.payment_hash] = true; _invoice_ispaid_cache[invoice.payment_hash] = true;
} }
invoice.amt = decoded.satoshis; invoice.amt = decoded.satoshis;
invoice.expire_time = 3600; invoice.expire_time = 3600 * 24;
// ^^^default; will keep for now. if we want to un-hardcode it - it should be among tags (`expire_time`) // ^^^default; will keep for now. if we want to un-hardcode it - it should be among tags (`expire_time`)
invoice.timestamp = decoded.timestamp; invoice.timestamp = decoded.timestamp;
invoice.type = 'user_invoice'; invoice.type = 'user_invoice';
@@ -313,6 +329,13 @@ export class User {
if (invoice.payment_route) { if (invoice.payment_route) {
invoice.fee = +invoice.payment_route.total_fees; invoice.fee = +invoice.payment_route.total_fees;
invoice.value = +invoice.payment_route.total_fees + +invoice.payment_route.total_amt; 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
}
} else { } else {
invoice.fee = 0; invoice.fee = 0;
} }

View File

@@ -1,3 +1,4 @@
export * from './User'; export * from './User';
export * from './Lock'; export * from './Lock';
export * from './Paym'; export * from './Paym';
export * from './Invo';

View File

@@ -20,12 +20,12 @@ let identity_pubkey = false;
bitcoinclient.request('getblockchaininfo', false, function(err, info) { bitcoinclient.request('getblockchaininfo', false, function(err, info) {
if (info && info.result && info.result.blocks) { if (info && info.result && info.result.blocks) {
if (info.result.blocks < 550000) { if (info.result.chain === 'mainnet' && info.result.blocks < 550000) {
console.error('bitcoind is not caught up'); console.error('bitcoind is not caught up');
process.exit(1); process.exit(1);
} }
} else { } else {
console.error('bitcoind failure'); console.error('bitcoind failure:', err, info);
process.exit(2); process.exit(2);
} }
}); });
@@ -33,9 +33,11 @@ bitcoinclient.request('getblockchaininfo', false, function(err, info) {
lightning.getInfo({}, function(err, info) { lightning.getInfo({}, function(err, info) {
if (err) { if (err) {
console.error('lnd failure'); console.error('lnd failure');
console.dir(err);
process.exit(3); process.exit(3);
} }
if (info) { if (info) {
console.info(info);
if (!info.synced_to_chain) { if (!info.synced_to_chain) {
console.error('lnd not synced'); console.error('lnd not synced');
process.exit(4); process.exit(4);
@@ -56,7 +58,7 @@ redis.info(function(err, info) {
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const postLimiter = rateLimit({ const postLimiter = rateLimit({
windowMs: 30 * 60 * 1000, windowMs: 30 * 60 * 1000,
max: 50, max: 100,
}); });
router.post('/create', postLimiter, async function(req, res) { router.post('/create', postLimiter, async function(req, res) {
@@ -128,10 +130,17 @@ router.post('/payinvoice', async function(req, res) {
// obtaining a lock // obtaining a lock
let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId()); let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId());
if (!(await lock.obtainLock())) { if (!(await lock.obtainLock())) {
return errorTryAgainLater(res); return errorGeneralServerError(res);
} }
let userBalance = await u.getCalculatedBalance(); let userBalance;
try {
userBalance = await u.getCalculatedBalance();
} catch (Error) {
logger.log('', [req.id, 'error running getCalculatedBalance():', Error.message]);
lock.releaseLock();
return errorTryAgainLater(res);
}
lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) { lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) {
if (err) { if (err) {
@@ -206,7 +215,7 @@ router.post('/payinvoice', async function(req, res) {
return errorPaymentFailed(res); return errorPaymentFailed(res);
} }
}); });
if (!info.num_satoshis && !info.num_satoshis) { if (!info.num_satoshis) {
// tip invoice, but someone forgot to specify amount // tip invoice, but someone forgot to specify amount
await lock.releaseLock(); await lock.releaseLock();
return errorBadArguments(res); return errorBadArguments(res);
@@ -248,18 +257,40 @@ router.get('/getbtc', async function(req, res) {
res.send([{ address }]); res.send([{ address }]);
}); });
router.get('/checkpayment/:payment_hash', async function(req, res) {
logger.log('/checkpayment', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
await u.loadByAuthorization(req.headers.authorization);
if (!u.getUserId()) {
return errorBadAuth(res);
}
let paid = true;
if (!(await u.getPaymentHashPaid(req.params.payment_hash))) { // Not found on cache
paid = await u.syncInvoicePaid(req.params.payment_hash);
}
res.send({paid: paid});
});
router.get('/balance', postLimiter, async function(req, res) { router.get('/balance', postLimiter, async function(req, res) {
try {
logger.log('/balance', [req.id]); logger.log('/balance', [req.id]);
let u = new User(redis, bitcoinclient, lightning); let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) { if (!(await u.loadByAuthorization(req.headers.authorization))) {
return errorBadAuth(res); return errorBadAuth(res);
} }
logger.log('/balance', [req.id, 'userid: ' + u.getUserId()]);
if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further
await u.accountForPosibleTxids(); await u.accountForPosibleTxids();
let balance = await u.getBalance(); let balance = await u.getBalance();
if (balance < 0) balance = 0; if (balance < 0) balance = 0;
res.send({ BTC: { AvailableBalance: balance } }); res.send({ BTC: { AvailableBalance: balance } });
} catch (Error) {
logger.log('', [req.id, 'error getting balance:', Error.message, 'userid:', u.getUserId()]);
return errorGeneralServerError(res);
}
}); });
router.get('/getinfo', postLimiter, async function(req, res) { router.get('/getinfo', postLimiter, async function(req, res) {
@@ -281,6 +312,7 @@ router.get('/gettxs', async function(req, res) {
if (!(await u.loadByAuthorization(req.headers.authorization))) { if (!(await u.loadByAuthorization(req.headers.authorization))) {
return errorBadAuth(res); return errorBadAuth(res);
} }
logger.log('/gettxs', [req.id, 'userid: ' + u.getUserId()]);
if (!(await u.getAddress())) await u.generateAddress(); // onchain addr needed further if (!(await u.getAddress())) await u.generateAddress(); // onchain addr needed further
try { try {
@@ -298,27 +330,24 @@ router.get('/gettxs', async function(req, res) {
} }
res.send(txs); res.send(txs);
} catch (Err) { } catch (Err) {
logger.log('', [req.id, 'error:', Err]); logger.log('', [req.id, 'error gettxs:', Err.message, 'userid:', u.getUserId()]);
res.send([]); res.send([]);
} }
}); });
router.get('/getuserinvoices', async function(req, res) { router.get('/getuserinvoices', postLimiter, async function(req, res) {
logger.log('/getuserinvoices', [req.id]); logger.log('/getuserinvoices', [req.id]);
let u = new User(redis, bitcoinclient, lightning); let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) { if (!(await u.loadByAuthorization(req.headers.authorization))) {
return errorBadAuth(res); return errorBadAuth(res);
} }
logger.log('/getuserinvoices', [req.id, 'userid: ' + u.getUserId()]);
try { try {
let invoices = await u.getUserInvoices(); let invoices = await u.getUserInvoices(req.query.limit);
if (req.query.limit && !isNaN(parseInt(req.query.limit))) {
res.send(invoices.slice(parseInt(req.query.limit) * -1));
} else {
res.send(invoices); res.send(invoices);
}
} catch (Err) { } catch (Err) {
logger.log('', [req.id, 'error:', Err]); logger.log('', [req.id, 'error getting user invoices:', Err.message, 'userid:', u.getUserId()]);
res.send([]); res.send([]);
} }
}); });
@@ -329,6 +358,7 @@ router.get('/getpending', async function(req, res) {
if (!(await u.loadByAuthorization(req.headers.authorization))) { if (!(await u.loadByAuthorization(req.headers.authorization))) {
return errorBadAuth(res); return errorBadAuth(res);
} }
logger.log('/getpending', [req.id, 'userid: ' + u.getUserId()]);
if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further
await u.accountForPosibleTxids(); await u.accountForPosibleTxids();
@@ -408,7 +438,7 @@ function errorGeneralServerError(res) {
return res.send({ return res.send({
error: true, error: true,
code: 6, code: 6,
message: 'Server fault', message: 'Something went wrong. Please try again later',
}); });
} }

View File

@@ -25,7 +25,7 @@ function updateLightning() {
lightningListChannels = response; lightningListChannels = response;
let channels = []; let channels = [];
for (let channel of lightningListChannels.channels) { for (let channel of lightningListChannels.channels) {
let divider = 524287; let divider = 5242870;
let ascii_length1 = channel.local_balance / divider; let ascii_length1 = channel.local_balance / divider;
let ascii_length2 = channel.remote_balance / divider; let ascii_length2 = channel.remote_balance / divider;
channel.ascii = '['; channel.ascii = '[';
@@ -70,6 +70,12 @@ const pubkey2name = {
'02a0bc43557fae6af7be8e3a29fdebda819e439bea9c0f8eb8ed6a0201f3471ca9': 'LightningPeachHub', '02a0bc43557fae6af7be8e3a29fdebda819e439bea9c0f8eb8ed6a0201f3471ca9': 'LightningPeachHub',
'02d4531a2f2e6e5a9033d37d548cff4834a3898e74c3abe1985b493c42ebbd707d': 'coinfinity.co', '02d4531a2f2e6e5a9033d37d548cff4834a3898e74c3abe1985b493c42ebbd707d': 'coinfinity.co',
'02d23fa6794d8fd056c757f3c8f4877782138dafffedc831fc570cab572620dc61': 'paywithmoon.com', '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) { router.get('/', function(req, res) {

18
doc/recover.md Normal file
View File

@@ -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

View File

@@ -19,7 +19,7 @@ app.enable('trust proxy');
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ const limiter = rateLimit({
windowMs: 15 * 60 * 1000, windowMs: 15 * 60 * 1000,
max: 100, max: 200,
}); });
app.use(limiter); app.use(limiter);

1206
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "LndHub", "name": "LndHub",
"version": "1.1.3", "version": "1.2.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -14,25 +14,25 @@
"dependencies": { "dependencies": {
"babel": "^6.23.0", "babel": "^6.23.0",
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.1.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0", "babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"babel-register": "^6.26.0", "babel-register": "^6.26.0",
"bignumber.js": "^8.0.1", "bignumber.js": "^9.0.0",
"bolt11": "https://github.com/bitcoinjs/bolt11", "bolt11": "^1.2.6",
"eslint": "^5.9.0", "eslint": "^6.8.0",
"eslint-config-prettier": "^3.3.0", "eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.0.0", "eslint-plugin-prettier": "^3.1.2",
"express": "^4.16.4", "express": "^4.16.4",
"express-rate-limit": "^3.4.0", "express-rate-limit": "^5.0.0",
"grpc": "^1.17.0-pre1", "grpc": "^1.17.0-pre1",
"ioredis": "^4.2.0", "ioredis": "^4.16.2",
"jayson": "^2.1.0", "jayson": "^3.1.2",
"morgan": "^1.9.1", "morgan": "^1.9.1",
"mustache": "^3.0.1", "mustache": "^4.0.1",
"node-uuid": "^1.4.8", "node-uuid": "^1.4.8",
"prettier": "^1.15.3", "prettier": "^2.0.4",
"request": "^2.88.0", "request": "^2.88.0",
"request-promise": "^4.2.2", "request-promise": "^4.2.2",
"winston": "^3.1.0" "winston": "^3.1.0"

View File

@@ -1,15 +1,54 @@
let important_channels = { const important_channels = {
'02d23fa6794d8fd056c757f3c8f4877782138dafffedc831fc570cab572620dc61': 'paywithmoon.com', '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f': {
'03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f': 'ACINQ', name: 'ACINQ',
'03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': 'OpenNode', uri: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f@34.239.230.56:9735',
'0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': 'coingate.com', },
'0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3': 'bitrefill thor', '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': {
'025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5': 'paywithmoon.com', name: 'OpenNode',
'02c91d6aa51aa940608b497b6beebcb1aec05be3c47704b682b3889424679ca490': 'lnbig 21', uri: '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@18.221.23.28:9735',
'0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': 'ln1.satoshilabs.com', wumbo: 1,
'026c7d28784791a4b31a64eb34d9ab01552055b795919165e6ae886de637632efb': 'LivingRoomOfSatoshi', },
'02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774': 'ln.pizza', '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'); let lightning = require('../lightning');
lightning.listChannels({}, function(err, response) { lightning.listChannels({}, function(err, response) {
@@ -20,12 +59,12 @@ lightning.listChannels({}, function(err, response) {
} }
let lightningListChannels = response; let lightningListChannels = response;
for (let channel of lightningListChannels.channels) { for (let channel of lightningListChannels.channels) {
if (0 && channel.capacity < 5000000) { if (channel.capacity < 0.05 / 100000000) {
console.log( console.log(
'lncli closechannel', 'lncli closechannel',
channel.channel_point.replace(':', ' '), channel.channel_point.replace(':', ' '),
(!channel.active && '--force') || '', (!channel.active && '--force') || '',
'#', '; sleep 10 #',
'low capacity channel', 'low capacity channel',
channel.capacity / 100000000, channel.capacity / 100000000,
'btc', 'btc',
@@ -33,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; let atLeastOneChannelIsSufficientCapacity = false;
for (let channel of lightningListChannels.channels) { for (let channel of lightningListChannels.channels) {
if (channel.remote_pubkey === important && channel.local_balance >= 4000000 && channel.active) { if (channel.remote_pubkey === important && channel.local_balance >= 4000000 && channel.active) {
@@ -42,7 +101,19 @@ lightning.listChannels({}, function(err, response) {
} }
if (!atLeastOneChannelIsSufficientCapacity) { if (!atLeastOneChannelIsSufficientCapacity) {
console.log('lncli openchannel --node_key ', important, '--local_amt 16777215', '#', important_channels[important]); console.log(
'lncli disconnect',
important,
'; sleep 3;',
'lncli openchannel --node_key',
important,
'--connect',
important_channels[important].uri.split('@')[1],
'--local_amt',
important_channels[important].wumbo ? '167772150' : '16777215',
'#',
important_channels[important].name,
);
} }
} }

View File

@@ -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/'; import { User, Lock, Paym } from '../class/';
const config = require('../config'); const config = require('../config');
const fs = require('fs');
var Redis = require('ioredis'); var Redis = require('ioredis');
var redis = new Redis(config.redis); var redis = new Redis(config.redis);
@@ -15,6 +21,7 @@ let lightning = require('../lightning');
let tempPaym = new Paym(redis, bitcoinclient, lightning); let tempPaym = new Paym(redis, bitcoinclient, lightning);
let listPayments = await tempPaym.listPayments(); let listPayments = await tempPaym.listPayments();
console.log('done', 'got', listPayments['payments'].length, 'payments'); console.log('done', 'got', listPayments['payments'].length, 'payments');
fs.writeFileSync('listPayments.json', JSON.stringify(listPayments['payments'], null, 2));
for (let key of keys) { for (let key of keys) {
const userid = key.replace('locked_payments_for_', ''); const userid = key.replace('locked_payments_for_', '');
@@ -34,6 +41,7 @@ let lightning = require('../lightning');
if (daysPassed < 2) { if (daysPassed < 2) {
// if (!await payment.isExpired()) { // if (!await payment.isExpired()) {
let sendResult; let sendResult;
console.log('attempting to pay to route');
try { try {
sendResult = await payment.attemptPayToRoute(); sendResult = await payment.attemptPayToRoute();
} catch (_) { } catch (_) {

View File

@@ -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();
})();

View File

@@ -62,9 +62,18 @@
<pre class="line"><span class="dyer-white">num_active_channels:</span></pre> <pre class="line"><span class="dyer-white">num_active_channels:</span></pre>
<pre class="line">{{num_active_channels}}</pre> <pre class="line">{{num_active_channels}}</pre>
<pre class="line"> </pre> <pre class="line"> </pre>
<pre class="line"><span class="dyer-white">num_pending_channels:</span></pre>
<pre class="line">{{num_pending_channels}}</pre>
<pre class="line"> </pre>
<pre class="line"><span class="dyer-white">num_peers:</span></pre> <pre class="line"><span class="dyer-white">num_peers:</span></pre>
<pre class="line">{{num_peers}}</pre> <pre class="line">{{num_peers}}</pre>
<pre class="line"> </pre> <pre class="line"> </pre>
<pre class="line"><span class="dyer-white">block_height:</span></pre>
<pre class="line">{{block_height}}</pre>
<pre class="line"> </pre>
<pre class="line"><span class="dyer-white">synced_to_chain:</span></pre>
<pre class="line">{{synced_to_chain}}</pre>
<pre class="line"> </pre>
<pre class="line"><span class="dyer-white">version:</span></pre> <pre class="line"><span class="dyer-white">version:</span></pre>
<pre class="line">{{version}}</pre> <pre class="line">{{version}}</pre>
<pre class="line"> </pre> <pre class="line"> </pre>

View File

@@ -37,10 +37,6 @@ if (!fs.existsSync('logs')) {
fs.mkdirSync('logs'); fs.mkdirSync('logs');
} }
/**
* @param {string} label group label
* @param {string} message log message
*/
function log(label, message) { function log(label, message) {
logger.log({ logger.log({
level: 'info', level: 'info',