Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Allow payments to same lightning node where the invoice was not issued by lndhub.io #547

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
05299d7
[FEATURE] allow lightning payment to own node which is not from anoth…
thespielplatz Apr 6, 2023
8ceb553
[REFACTOR] make code more readable
thespielplatz Apr 6, 2023
3008846
[TASK] log error from lnd payment error occurs
thespielplatz Apr 6, 2023
d9cddff
[TASK] lnd grpc add allow_self_payment flag and set it according to c…
thespielplatz Apr 7, 2023
6977eb7
[TASK] Update docs according to self payment feature
thespielplatz Apr 7, 2023
afa59c2
[TASK] fix audit report
thespielplatz Jun 2, 2023
bef0ceb
[TASK] describe self payment feature in readme
thespielplatz Jun 2, 2023
108695a
[TASK] add support dashboard with hash to config and prepare api route
thespielplatz Jun 2, 2023
44f9d44
[TASK] add frontend account creation mode
thespielplatz Jun 2, 2023
d059b04
[TASK] complete account creation mode & describe in readme
thespielplatz Jun 2, 2023
b5f4664
[FEATURE] generate safety onchain address, after account get's an onc…
thespielplatz Jun 2, 2023
126726e
[TASK] add thunderhub issue to readme
thespielplatz Jun 2, 2023
a66fdc8
[TASK] HTML Clean up for Release v1.5.0
thespielplatz Jun 5, 2023
c2ddc49
[TASK] add basic config infos to support dashboard
thespielplatz Jun 7, 2023
267c0a8
[TASK] Dashboard: show config only on auth and shrare Node URL from w…
thespielplatz Jun 7, 2023
ebfb1e0
[TASK] Dashboard add number of accounts and number of sats
thespielplatz Jun 7, 2023
679f8d9
[TASK] Refactor
thespielplatz Jun 7, 2023
547d36c
[BUG] repair normal website
thespielplatz Jun 7, 2023
bd95579
[TASK] Refactor
thespielplatz Jun 7, 2023
dbe120f
[TASK] Add Account page with transactions
thespielplatz Jun 7, 2023
24b14e6
[TASK] Show User Names instead of userIds & show BTC value
thespielplatz Jun 7, 2023
b0ae237
Refactor
thespielplatz Jun 7, 2023
530e5ad
[TASK] UI Update Support Dashboard
thespielplatz Jun 8, 2023
652f5f9
[TASK] UI Update Support Dashboard
thespielplatz Jun 8, 2023
17b900a
[TASK] Feature toggle in config for accounts, add to readme
thespielplatz Jun 8, 2023
87f8d63
[TASK] Typo fix
thespielplatz Jun 8, 2023
85fbbbd
[TASK] Typo fix 2
thespielplatz Jun 8, 2023
651107a
[TASK] account statement, change to incoming and outgoing for more cl…
thespielplatz Jun 13, 2023
09d9842
[TASK] redesign support sidebar, to 'not overlap' on regular screens
thespielplatz Jun 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@

admin.macaroon
tls.cert
doc/devnotes.md
build/
logs/

# dependencies
/node_modules
node_modules/

# misc
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.idea/

# Docker Dev
docker-compose.yml

npm-debug.log*
yarn-debug.log*
yarn-error.log*

# OSX# OSX
#
# OSX
.DS_Store
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
Satoshi Engineering - Extensions
======

### [Feature] Allow self payment
Reading the code it seems that LndHub was never supposed to run on a node with other applications on it, which leads to the problem:

If you want to pay an invoice issued by the node, but not issued "via" LndHub it will be denied. To allow this add this to your config:
```
allowLightningPaymentToNode: true // it defaults to false
```

### [Feature] Support Dashboard

![](doc/img/support_dashboard.png)

If the Support Dashboard is not turned an, all routes (api and web) are turned off. To turn it on choose a sha265 password. To Create it

```javascript
const { createHash } = require('crypto')
const password = 'gobrrr'
const passwordSHA256 = createHash('sha256').update(password).digest('hex')
console.info(passwordSHA256)
```

```
supportDashboardPasswordHash: 'e42703b94ce32a831ea363a8924dc0239ca54160a8f3fb2755bdbceb07238a8a'
// it defaults to '' which means it's turned completly off
```

### [Feature] Account Creation Mode

By default everyone could open account on our node, what we can't have as a company (KYC), so we added an account creation mode.

In config you can set the mode, when it starts & can be edited by the support dashboard
```
accountCreationMode: 'on', // 'on', 'off', 'once' ... defaults to 'on'
```

### [Feature] Generate Safety On Chain Address

In combination with Thunderhub we noticed, that an onchain address assigned to a lndhub account
had the same addess we got from Thunderhub's "Create On Chain Address" functiontionality. Thunderhub
somehow is caching address, which is normally ok, but in this case not :( The issue is addressed [here](https://github.com/apotdevin/thunderhub/issues/534).

This feature creates an additional address from LND after an onchain address is created and assigned to the user.

```
generateSafetyOnChainAddress: true, // defaults to false
```

### [Feature] Check account balance, bc1 address

To make the support dashboard even more powerfull, it is possible to show a list of accounts and
a detailed view of the accounts.

If it's turned off, no account data is published to the dashboard & and the detail route is turned off!

```
supportDashboardShowAccounts: true, // defaults to false
```

![](doc/img/account_list.png)
![](doc/img/account_detail.png)

LndHub
======

Expand Down
19 changes: 19 additions & 0 deletions class/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ export class User {
return new Promise(function (resolve, reject) {
self._lightning.newAddress({ type: 0 }, async function (err, response) {
if (err) return reject('LND failure when trying to generate new address');

// Due Buggy other systems, check Feature description in readme
if (config.generateSafetyOnChainAddress) {
await self.generateSafetyAddress()
}

const addressAlreadyExists = await self.getAddress();
if (addressAlreadyExists) {
// one last final check, for a case of really long race condition
Expand All @@ -132,6 +138,19 @@ export class User {
});
}

async generateSafetyAddress() {
let self = this;

return new Promise(function (resolve, reject) {
self._lightning.newAddress({ type: 0 }, async function (err, response) {
if (err) return reject('LND failure when trying to generate safety address');

if (config.bitcoind) self._bitcoindrpc.request('importaddress', [response.address, response.address, false]);
resolve();
});
});
}

async watchAddress(address) {
if (!address) return;
if (config.bitcoind) return this._bitcoindrpc.request('importaddress', [address, address, false]);
Expand Down
13 changes: 13 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ let config = {
rateLimit: 200,
forwardReserveFee: 0.01, // default 0.01
intraHubFee: 0.003, // default 0.003
allowLightningPaymentToNode: false,
supportDashboardPasswordHash: '',
supportDashboardShowAccounts: false,
accountCreationMode: 'on', // 'on', 'off', 'once' ... defaults to 'on'
generateSafetyOnChainAddress: false,
bitcoind: {
rpc: 'http://login:[email protected]:8332/wallet/wallet.dat',
},
Expand All @@ -25,4 +30,12 @@ if (process.env.CONFIG) {
config = JSON.parse(process.env.CONFIG);
}

// Config checks
if (!(config.supportDashboardPasswordHash)) config.supportDashboardPasswordHash = ''
if (typeof config.supportDashboardPasswordHash !== 'string') config.supportDashboardPasswordHash = ''
if (!(config.supportDashboardShowAccounts)) config.supportDashboardShowAccounts = false

if (!(config.accountCreationMode)) config.accountCreationMode = 'on'
if (!(config.generateSafetyOnChainAddress)) config.generateSafetyOnChainAddress = false

module.exports = config;
115 changes: 77 additions & 38 deletions controllers/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const config = require('../config');
let express = require('express');
let router = express.Router();
let logger = require('../utils/logger');
const shared = require('../utils/shared');

const MIN_BTC_BLOCK = 670000;
if (process.env.NODE_ENV !== 'prod') {
console.log('using config', JSON.stringify(config));
Expand All @@ -17,6 +19,8 @@ redis.monitor(function (err, monitor) {
});
});

shared.redis = redis

/****** START SET FEES FROM CONFIG AT STARTUP ******/
/** GLOBALS */
global.forwardFee = config.forwardReserveFee || 0.01;
Expand Down Expand Up @@ -149,9 +153,14 @@ router.post('/create', postLimiter, async function (req, res) {

if (config.sunset) return errorSunset(res);

if (config.accountCreationMode === 'off') return errorAccountCreationOff(res)

let u = new User(redis, bitcoinclient, lightning);
await u.create();
await u.saveMetadata({ partnerid: req.body.partnerid, accounttype: req.body.accounttype, created_at: new Date().toISOString() });

if (config.accountCreationMode === 'once') config.accountCreationMode = 'off'

res.send({ login: u.getLogin(), password: u.getPassword() });
});

Expand Down Expand Up @@ -254,49 +263,58 @@ router.post('/payinvoice', postLimiter, async function (req, res) {
// this is internal invoice
// now, receiver add balance
let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);

// receiver is not a lndhub account
if (!userid_payee) {
await lock.releaseLock();
return errorGeneralServerError(res);
}
// Check if Is payment to node allowed?
if (!config.allowLightningPaymentToNode || false) {
await lock.releaseLock();
return errorPaymentToNodeNotAllowed(res);
}

if (await u.getPaymentHashPaid(info.payment_hash)) {
// this internal invoice was paid, no sense paying it again
await lock.releaseLock();
return errorLnd(res);
}
// Continues at // else - regular lightning network payment:

let UserPayee = new User(redis, bitcoinclient, lightning);
UserPayee._userid = userid_payee; // hacky, fixme
await UserPayee.clearBalanceCache();

// sender spent his balance:
await u.clearBalanceCache();
await u.savePaidLndInvoice({
timestamp: parseInt(+new Date() / 1000),
type: 'paid_invoice',
value: +info.num_satoshis + Math.floor(info.num_satoshis * internalFee),
fee: Math.floor(info.num_satoshis * internalFee),
memo: decodeURIComponent(info.description),
pay_req: req.body.invoice,
});

const invoice = new Invo(redis, bitcoinclient, lightning);
invoice.setInvoice(req.body.invoice);
await invoice.markAsPaidInDatabase();

// now, faking LND callback about invoice paid:
const preimage = await invoice.getPreimage();
if (preimage) {
subscribeInvoicesCallCallback({
state: 'SETTLED',
memo: info.description,
r_preimage: Buffer.from(preimage, 'hex'),
r_hash: Buffer.from(info.payment_hash, 'hex'),
amt_paid_sat: +info.num_satoshis,
// receiver is a lndhub account
} else {
if (await u.getPaymentHashPaid(info.payment_hash)) {
// this internal invoice was paid, no sense paying it again
await lock.releaseLock();
return errorLnd(res);
}

let UserPayee = new User(redis, bitcoinclient, lightning);
UserPayee._userid = userid_payee; // hacky, fixme
await UserPayee.clearBalanceCache();

// sender spent his balance:
await u.clearBalanceCache();
await u.savePaidLndInvoice({
timestamp: parseInt(+new Date() / 1000),
type: 'paid_invoice',
value: +info.num_satoshis + Math.floor(info.num_satoshis * internalFee),
fee: Math.floor(info.num_satoshis * internalFee),
memo: decodeURIComponent(info.description),
pay_req: req.body.invoice,
});

const invoice = new Invo(redis, bitcoinclient, lightning);
invoice.setInvoice(req.body.invoice);
await invoice.markAsPaidInDatabase();

// now, faking LND callback about invoice paid:
const preimage = await invoice.getPreimage();
if (preimage) {
subscribeInvoicesCallCallback({
state: 'SETTLED',
memo: info.description,
r_preimage: Buffer.from(preimage, 'hex'),
r_hash: Buffer.from(info.payment_hash, 'hex'),
amt_paid_sat: +info.num_satoshis,
});
}
await lock.releaseLock();
return res.send(info);
}
await lock.releaseLock();
return res.send(info);
}

// else - regular lightning network payment:
Expand All @@ -305,6 +323,9 @@ router.post('/payinvoice', postLimiter, async function (req, res) {
call.on('data', async function (payment) {
// payment callback
await u.unlockFunds(req.body.invoice);

if (payment && payment.payment_error) logger.error('/payinvoice', payment);

if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
let PaymentShallow = new Paym(false, false, false);
payment = PaymentShallow.processSendPaymentResponse(payment);
Expand All @@ -329,6 +350,7 @@ router.post('/payinvoice', postLimiter, async function (req, res) {
payment_request: req.body.invoice,
amt: info.num_satoshis, // amt is used only for 'tip' invoices
fee_limit: { fixed: Math.floor(info.num_satoshis * forwardFee) + 1 },
allow_self_payment: (config.allowLightningPaymentToNode || false),
};
try {
await u.lockFunds(req.body.invoice, info);
Expand Down Expand Up @@ -398,6 +420,7 @@ router.get('/balance', postLimiter, async function (req, res) {
res.send({ BTC: { AvailableBalance: balance } });
} catch (Error) {
logger.log('', [req.id, 'error getting balance:', Error, 'userid:', u.getUserId()]);
logger.error(Error)
return errorGeneralServerError(res);
}
});
Expand Down Expand Up @@ -618,3 +641,19 @@ function errorSunsetAddInvoice(res) {
message: 'This LNDHub instance is scheduled to shut down. Withdraw any remaining funds',
});
}

function errorPaymentToNodeNotAllowed(res) {
return res.send({
error: true,
code: 12,
message: 'This LNDHub instance does not allow self payments other then issued by this LNDHub',
});
}

function errorAccountCreationOff(res) {
return res.send({
error: true,
code: 22,
message: 'This LNDHub instance has turned off it\'s account creation.',
});
}
Loading