FIX: better locks (rel #3)
This commit is contained in:
parent
98ba940342
commit
b7073abb0e
47
class/Lock.js
Normal file
47
class/Lock.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
export class Lock {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Redis} redis
|
||||||
|
* @param {String} lock_key
|
||||||
|
*/
|
||||||
|
constructor(redis, lock_key) {
|
||||||
|
this._redis = redis;
|
||||||
|
this._lock_key = lock_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to obtain lock in single-threaded Redis.
|
||||||
|
* Returns TRUE if success.
|
||||||
|
*
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async obtainLock() {
|
||||||
|
if (await this._redis.get(this._lock_key)) {
|
||||||
|
// someone already has the lock
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trying to set the lock:
|
||||||
|
let buffer = crypto.randomBytes(10);
|
||||||
|
const randomValue = buffer.toString('hex');
|
||||||
|
await this._redis.set(this._lock_key, randomValue);
|
||||||
|
|
||||||
|
// checking if it was set:
|
||||||
|
let value = await this._redis.get(this._lock_key);
|
||||||
|
if (value !== randomValue) {
|
||||||
|
// someone else managed to obtain this lock
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// success - got lock
|
||||||
|
await this._redis.expire(this._lock_key, 2 * 60);
|
||||||
|
// lock expires in 2 mins just for any case
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async releaseLock() {
|
||||||
|
await this._redis.del(this._lock_key);
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
export * from './User';
|
export * from './User';
|
||||||
|
export * from './Lock';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { User } from '../class/User';
|
import { User, Lock } from '../class/';
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
let express = require('express');
|
let express = require('express');
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
@ -113,12 +113,20 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
if (!req.body.invoice) return errorBadArguments(res);
|
if (!req.body.invoice) return errorBadArguments(res);
|
||||||
let freeAmount = false;
|
let freeAmount = false;
|
||||||
if (req.body.amount) freeAmount = parseInt(req.body.amount);
|
if (req.body.amount) freeAmount = parseInt(req.body.amount);
|
||||||
const lock_key = 'invoice_paying_for_' + u.getUserId();
|
|
||||||
|
// obtaining a lock
|
||||||
|
let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId());
|
||||||
|
if (!(await lock.obtainLock())) {
|
||||||
|
return errorTryAgainLater(res);
|
||||||
|
}
|
||||||
|
|
||||||
let userBalance = await u.getBalance();
|
let userBalance = await u.getBalance();
|
||||||
|
|
||||||
lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) {
|
lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) {
|
||||||
if (err) return errorNotAValidInvoice(res);
|
if (err) {
|
||||||
|
await lock.releaseLock();
|
||||||
|
return errorNotAValidInvoice(res);
|
||||||
|
}
|
||||||
|
|
||||||
if (+info.num_satoshis === 0) {
|
if (+info.num_satoshis === 0) {
|
||||||
// 'tip' invoices
|
// 'tip' invoices
|
||||||
@ -132,13 +140,10 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
// this is internal invoice
|
// this is internal invoice
|
||||||
// now, receiver add balance
|
// now, receiver add balance
|
||||||
let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);
|
let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);
|
||||||
if (!userid_payee) return errorGeneralServerError(res);
|
if (!userid_payee) {
|
||||||
|
await lock.releaseLock();
|
||||||
if (await redis.get(lock_key)) {
|
return errorGeneralServerError(res);
|
||||||
return errorTryAgainLater(res);
|
|
||||||
}
|
}
|
||||||
await redis.set(lock_key, 1);
|
|
||||||
await redis.expire(lock_key, 2 * 60);
|
|
||||||
|
|
||||||
let UserPayee = new User(redis);
|
let UserPayee = new User(redis);
|
||||||
UserPayee._userid = userid_payee; // hacky, fixme
|
UserPayee._userid = userid_payee; // hacky, fixme
|
||||||
@ -159,10 +164,12 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
|
|
||||||
await UserPayee.setPaymentHashPaid(info.payment_hash);
|
await UserPayee.setPaymentHashPaid(info.payment_hash);
|
||||||
|
|
||||||
await redis.del(lock_key, 1);
|
await lock.releaseLock();
|
||||||
return res.send(info);
|
return res.send(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// else - regular lightning network payment:
|
||||||
|
|
||||||
var call = lightning.sendPayment();
|
var call = lightning.sendPayment();
|
||||||
call.on('data', function(payment) {
|
call.on('data', function(payment) {
|
||||||
// payment callback
|
// payment callback
|
||||||
@ -172,32 +179,30 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
payment.pay_req = req.body.invoice;
|
payment.pay_req = req.body.invoice;
|
||||||
payment.decoded = info;
|
payment.decoded = info;
|
||||||
u.savePaidLndInvoice(payment);
|
u.savePaidLndInvoice(payment);
|
||||||
redis.del(lock_key);
|
lock.releaseLock();
|
||||||
res.send(payment);
|
res.send(payment);
|
||||||
} else {
|
} else {
|
||||||
// payment failed
|
// payment failed
|
||||||
redis.del(lock_key);
|
lock.releaseLock();
|
||||||
return errorLnd(res);
|
return errorLnd(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!info.num_satoshis && !info.num_satoshis) {
|
if (!info.num_satoshis && !info.num_satoshis) {
|
||||||
// tip invoice, but someone forgot to specify amount
|
// tip invoice, but someone forgot to specify amount
|
||||||
|
await lock.releaseLock();
|
||||||
return errorBadArguments(res);
|
return errorBadArguments(res);
|
||||||
}
|
}
|
||||||
let inv = { payment_request: req.body.invoice, amt: info.num_satoshis }; // amt is used only for 'tip' invoices
|
let inv = { payment_request: req.body.invoice, amt: info.num_satoshis }; // amt is used only for 'tip' invoices
|
||||||
try {
|
try {
|
||||||
if (await redis.get(lock_key)) {
|
|
||||||
return errorTryAgainLater(res);
|
|
||||||
}
|
|
||||||
await redis.set(lock_key, 1);
|
|
||||||
await redis.expire(lock_key, 2 * 60);
|
|
||||||
logger.log('/payinvoice', [req.id, 'before write', JSON.stringify(inv)]);
|
logger.log('/payinvoice', [req.id, 'before write', JSON.stringify(inv)]);
|
||||||
call.write(inv);
|
call.write(inv);
|
||||||
} catch (Err) {
|
} catch (Err) {
|
||||||
logger.log('/payinvoice', [req.id, 'exception', JSON.stringify(Err)]);
|
logger.log('/payinvoice', [req.id, 'exception', JSON.stringify(Err)]);
|
||||||
|
await lock.releaseLock();
|
||||||
return errorLnd(res);
|
return errorLnd(res);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
await lock.releaseLock();
|
||||||
return errorNotEnougBalance(res);
|
return errorNotEnougBalance(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user