FIX: better locks (rel #3)

This commit is contained in:
Overtorment 2019-01-07 16:40:32 +00:00
parent 98ba940342
commit b7073abb0e
3 changed files with 70 additions and 17 deletions

47
class/Lock.js Normal file
View 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);
}
}

View File

@ -1 +1,2 @@
export * from './User';
export * from './Lock';

View File

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