ln-service
Version:
Interaction helper for your Lightning Network daemon
187 lines (147 loc) • 6.89 kB
JavaScript
const {equal} = require('node:assert').strict;
const test = require('node:test');
const asyncRetry = require('async/retry');
const {spawnLightningCluster} = require('ln-docker-daemons');
const {addPeer} = require('./../../');
const {closeChannel} = require('./../../');
const {getChainBalance} = require('./../../');
const {getWalletInfo} = require('./../../');
const {openChannel} = require('./../../');
const {removePeer} = require('./../../');
const {subscribeToChannels} = require('./../../');
const channelCapacityTokens = 1e6;
const defaultFee = 1e3;
const description = 'description';
const giveTokens = 1e5;
const interval = 100;
const size = 2;
const times = 200;
// Subscribing to channels should trigger channel events
test('Subscribe to channels', async () => {
const activeChanged = [];
const channelAdding = [];
const channelClosed = [];
const channelOpened = [];
const {kill, nodes} = await spawnLightningCluster({size});
const errors = [];
const [control, target] = nodes;
const {generate, lnd} = control;
const {socket} = target;
const {features} = await getWalletInfo({lnd});
const sub = subscribeToChannels({lnd});
sub.on('channel_active_changed', update => activeChanged.push(update));
sub.on('channel_closed', update => channelClosed.push(update));
sub.on('channel_opened', update => channelOpened.push(update));
sub.on('channel_opening', update => channelAdding.push(update));
sub.on('error', err => errors.push(err));
await asyncRetry({times: 150}, async () => {
if (!(await getChainBalance({lnd})).chain_balance) {
await generate({});
throw new Error('ExpectedChainBalanceToOpenChannel');
}
});
const channelOpen = await asyncRetry({interval, times}, async () => {
await addPeer({lnd, public_key: target.id, socket: target.socket});
// Create a channel from the control to the target node
return await openChannel({
description,
lnd,
socket,
chain_fee_tokens_per_vbyte: defaultFee,
give_tokens: giveTokens,
local_tokens: channelCapacityTokens,
partner_public_key: target.id,
});
});
// Wait for the channel to confirm
await asyncRetry({interval, times}, async () => {
// Generate to confirm the tx
await generate({});
if (!channelOpened.length) {
throw new Error('ExpectedChannelOpened');
}
return;
});
const pendingEvent = channelAdding.pop();
equal(pendingEvent.transaction_id, channelOpen.transaction_id, 'Got txid');
equal(pendingEvent.transaction_vout, channelOpen.transaction_vout, 'Vout');
const openEvent = channelOpened.pop();
if (!!openEvent.local_given) {
equal(openEvent.local_given, giveTokens, 'Push tokens are reflected');
equal(openEvent.remote_given, Number(), 'Push tokens are reflected');
}
if (!!openEvent.remote_given) {
equal(openEvent.local_given, Number(), 'Push tokens are reflected');
equal(openEvent.remote_given, giveTokens, 'Push tokens are reflected');
}
equal(openEvent.commit_transaction_fee, 2810, 'Channel commit tx fee');
equal(openEvent.commit_transaction_weight, 1116, 'Commit tx weight');
equal(openEvent.local_balance, 896530, 'Channel local balance returned');
// LND 0.16.4 and below do not support channel descriptions
if (!!openEvent.description) {
equal(openEvent.description, description, 'Got channel open description');
}
equal(openEvent.capacity, channelCapacityTokens, 'Channel open capacity');
equal(!!openEvent.id, true, 'Channel id is returned');
equal(openEvent.is_active, true, 'Channel is active');
equal(openEvent.is_closing, false, 'Channel is not inactive');
equal(openEvent.is_opening, false, 'Channel is no longer opening');
equal(openEvent.is_partner_initiated, false, 'Channel was locally made');
equal(openEvent.is_private, false, 'Channel is not private by default');
equal(openEvent.local_reserve, 10000, 'Reserve tokens are reflected');
equal(openEvent.partner_public_key, target.id, 'Peer pk');
equal(openEvent.pending_payments.length, [].length, 'No pending payments');
equal(openEvent.received, 0, 'Not received anything yet');
equal(openEvent.remote_balance, giveTokens, 'Gift tokens is remote balance');
equal(openEvent.remote_reserve, 10000, 'Reserve tokens are reflected');
equal(openEvent.sent, 0, 'No tokens sent yet');
equal(openEvent.transaction_id, channelOpen.transaction_id, 'Funding tx id');
equal(openEvent.transaction_vout, channelOpen.transaction_vout, 'Fund vout');
equal(openEvent.type, 'anchor', 'Got channel type');
equal(openEvent.unsettled_balance, 0, 'No unsettled balance');
// Wait for the channel close to confirm
await asyncRetry({interval, times}, async () => {
try {
// Close the channel
const channelClose = await closeChannel({
lnd,
tokens_per_vbyte: defaultFee,
transaction_id: channelOpen.transaction_id,
transaction_vout: channelOpen.transaction_vout,
});
} catch (err) {}
// Generate to confirm the close
await generate({});
if (!channelClosed.length) {
throw new Error('ExpectedChannelClosed');
}
return;
});
const closeEvent = channelClosed.pop();
const final = closeEvent.final_local_balance;
equal([897190, 846655, 863366, 875633].includes(final), true, 'Close final');
equal(closeEvent.capacity, channelCapacityTokens, 'Channel close capacity');
equal(!!closeEvent.close_confirm_height, true, 'Close confirm height');
equal(!!closeEvent.close_transaction_id, true, 'Tx id');
equal(closeEvent.final_time_locked_balance, 0, 'Close final locked balance');
equal(!!closeEvent.id, true, 'Close channel id');
equal(closeEvent.is_breach_close, false, 'Not breach close');
equal(closeEvent.is_cooperative_close, true, 'Cooperative close');
equal(closeEvent.is_funding_cancel, false, 'Not funding cancel');
equal(closeEvent.is_local_force_close, false, 'Not local force close');
equal(closeEvent.is_remote_force_close, false, 'Not remote force close');
equal(closeEvent.partner_public_key, target.id, 'Pk');
equal(closeEvent.transaction_id, channelOpen.transaction_id, 'Chan tx id');
equal(closeEvent.transaction_vout, channelOpen.transaction_vout, 'Tx vout');
equal(errors.length, [].length, 'No errors encountered');
const [activated, deactivated] = activeChanged;
equal(activated.is_active, true, 'Channel was activated');
equal(activated.transaction_id, channelOpen.transaction_id, 'Chan tx id');
equal(activated.transaction_vout, channelOpen.transaction_vout, 'Chan vout');
equal(deactivated.is_active, false, 'Channel was de-activated');
equal(deactivated.transaction_id, channelOpen.transaction_id, 'Chan tx id');
equal(deactivated.transaction_vout, channelOpen.transaction_vout, 'Tx vout');
sub.removeAllListeners();
await kill({});
return;
});