ln-sync
Version:
LN metadata persistence methods
121 lines (103 loc) • 3.8 kB
JavaScript
const asyncAuto = require('async/auto');
const asyncEach = require('async/each');
const asyncUntil = require('async/until');
const {getChainTransactions} = require('ln-service');
const {getHeight} = require('ln-service');
const {lockUtxo} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const asOutpoint = n => `${n.transaction_id}:${n.transaction_vout}`;
const blockHeightBuffer = 144;
const currentDate = () => new Date().toISOString();
const {isArray} = Array;
const {max} = Math;
/** Keep input locks alive while related transaction is not yet confirmed
{
id: <Transaction Id Hex String>
inputs: [{
[lock_expires_at]: <UTXO Lock Expires At ISO 8601 Date String>
transaction_id: <Unspent Transaction Id Hex String>
transaction_vout: <Unspent Transaction Output Index Number>
}]
interval: <Relock Interval Milliseconds Number>
lnd: <Authenticated LND API Object>
}
@returns via cbk or Promise
*/
module.exports = ({id, inputs, interval, lnd}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!id) {
return cbk([400, 'ExpectedTransactionIdToMaintainUtxoLocks']);
}
if (!isArray(inputs)) {
return cbk([400, 'ExpectedArrayOfInputsToMaintainUtxoLocks']);
}
if (!interval) {
return cbk([400, 'ExpectedRelockIntervalToMaintainUtxoLocks']);
}
if (!lnd) {
return cbk([400, 'ExpectedAuthenticatedLndToMaintainUtxoLocks']);
}
return cbk();
},
// Get the starting height to avoid pulling too many chain transactions
getHeight: ['validate', ({}, cbk) => getHeight({lnd}, cbk)],
// Wait for the transaction to become confirmed
unlockWhenConfirmed: ['getHeight', ({getHeight}, cbk) => {
const after = getHeight.current_block_height - blockHeightBuffer;
const locks = {};
return asyncUntil(
// Stop when the transaction is confirmed
cbk => {
return getChainTransactions({after, lnd}, (err, res) => {
if (!!err) {
return cbk();
}
const tx = res.transactions.find(n => n.id === id);
return cbk(null, !!tx && tx.is_confirmed);
});
},
// Lock all the inputs to extend their lock timers
cbk => {
return asyncEach(inputs, (input, cbk) => {
const lock = locks[asOutpoint(input)] || {
lock_expires_at: input.lock_expires_at,
};
const expiry = new Date(lock.lock_expires_at || currentDate());
// Wait until the lock expires
return setTimeout(() => {
return lockUtxo({
lnd,
id: lock.lock_id,
transaction_id: input.transaction_id,
transaction_vout: input.transaction_vout,
},
(err, res) => {
// Exit early and ignore errors on lock attempt
if (!!err) {
return cbk();
}
// Update the outpoint lock
locks[asOutpoint(input)] = {
lock_expires_at: res.expires_at,
lock_id: res.id,
};
return cbk();
});
},
max(expiry - new Date(), Number()));
},
() => {
return setTimeout(cbk, interval);
});
},
// All input transactions were confirmed
cbk
)
}],
},
returnResult({reject, resolve}, cbk));
});
};