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 './Lock';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { User } from '../class/User';
|
||||
import { User, Lock } from '../class/';
|
||||
const config = require('../config');
|
||||
let express = require('express');
|
||||
let router = express.Router();
|
||||
@ -113,12 +113,20 @@ router.post('/payinvoice', async function(req, res) {
|
||||
if (!req.body.invoice) return errorBadArguments(res);
|
||||
let freeAmount = false;
|
||||
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();
|
||||
|
||||
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) {
|
||||
// 'tip' invoices
|
||||
@ -132,13 +140,10 @@ router.post('/payinvoice', async function(req, res) {
|
||||
// this is internal invoice
|
||||
// now, receiver add balance
|
||||
let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);
|
||||
if (!userid_payee) return errorGeneralServerError(res);
|
||||
|
||||
if (await redis.get(lock_key)) {
|
||||
return errorTryAgainLater(res);
|
||||
if (!userid_payee) {
|
||||
await lock.releaseLock();
|
||||
return errorGeneralServerError(res);
|
||||
}
|
||||
await redis.set(lock_key, 1);
|
||||
await redis.expire(lock_key, 2 * 60);
|
||||
|
||||
let UserPayee = new User(redis);
|
||||
UserPayee._userid = userid_payee; // hacky, fixme
|
||||
@ -159,10 +164,12 @@ router.post('/payinvoice', async function(req, res) {
|
||||
|
||||
await UserPayee.setPaymentHashPaid(info.payment_hash);
|
||||
|
||||
await redis.del(lock_key, 1);
|
||||
await lock.releaseLock();
|
||||
return res.send(info);
|
||||
}
|
||||
|
||||
// else - regular lightning network payment:
|
||||
|
||||
var call = lightning.sendPayment();
|
||||
call.on('data', function(payment) {
|
||||
// payment callback
|
||||
@ -172,32 +179,30 @@ router.post('/payinvoice', async function(req, res) {
|
||||
payment.pay_req = req.body.invoice;
|
||||
payment.decoded = info;
|
||||
u.savePaidLndInvoice(payment);
|
||||
redis.del(lock_key);
|
||||
lock.releaseLock();
|
||||
res.send(payment);
|
||||
} else {
|
||||
// payment failed
|
||||
redis.del(lock_key);
|
||||
lock.releaseLock();
|
||||
return errorLnd(res);
|
||||
}
|
||||
});
|
||||
if (!info.num_satoshis && !info.num_satoshis) {
|
||||
// tip invoice, but someone forgot to specify amount
|
||||
await lock.releaseLock();
|
||||
return errorBadArguments(res);
|
||||
}
|
||||
let inv = { payment_request: req.body.invoice, amt: info.num_satoshis }; // amt is used only for 'tip' invoices
|
||||
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)]);
|
||||
call.write(inv);
|
||||
} catch (Err) {
|
||||
logger.log('/payinvoice', [req.id, 'exception', JSON.stringify(Err)]);
|
||||
await lock.releaseLock();
|
||||
return errorLnd(res);
|
||||
}
|
||||
} else {
|
||||
await lock.releaseLock();
|
||||
return errorNotEnougBalance(res);
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user