ln-service
Version:
Interaction helper for your Lightning Network daemon
204 lines (164 loc) • 6.74 kB
JavaScript
const {strictEqual} = require('node:assert').strict;
const test = require('node:test');
const asyncRetry = require('async/retry');
const {getPortPromise: getPort} = require('portfinder');
const {setupChannel} = require('ln-docker-daemons');
const {spawnLightningDocker} = require('ln-docker-daemons');
const {addPeer} = require('./../../');
const {authenticatedLndGrpc} = require('./../../');
const {createChainAddress} = require('./../../');
const {getBackups} = require('./../../');
const {getIdentity} = require('./../../');
const {getPendingChannels} = require('./../../');
const {getUtxos} = require('./../../');
const {recoverFundsFromChannels} = require('./../../');
const confirmationCount = 20;
const generateAddress = '2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF';
const giftTokens = 1e5;
const interval = 10;
const makeAddress = ({lnd}) => createChainAddress({lnd});
const maturity = 100;
const seed = 'about rabbit ozone hope jaguar quit scare twenty punch crisp consider clutch ring frost universe okay execute shrug drink notice abandon wine denial retreat';
const times = 3000;
// Using a channels backup should recover funds
test(`Recover funds from channels`, async () => {
const control = await asyncRetry({interval, times}, async () => {
return await spawnLightningDocker({
seed,
chain_p2p_port: await getPort({port: 8000, stopPort: 9000}),
chain_rpc_port: await getPort({port: 9001, stopPort: 10000}),
chain_zmq_block_port: await getPort({port: 10001, stopPort: 11000}),
chain_zmq_tx_port: await getPort({port: 11001, stopPort: 12000}),
generate_address: generateAddress,
lightning_p2p_port: await getPort({port: 12001, stopPort: 13000}),
lightning_rpc_port: await getPort({port: 13001, stopPort: 14000}),
lightning_tower_port: await getPort({port: 14001, stopPort: 15000}),
});
});
const target = await asyncRetry({interval, times}, async () => {
return await spawnLightningDocker({
chain_p2p_port: await getPort({port: 8000, stopPort: 9000}),
chain_rpc_port: await getPort({port: 9001, stopPort: 10000}),
chain_zmq_block_port: await getPort({port: 10001, stopPort: 11000}),
chain_zmq_tx_port: await getPort({port: 11001, stopPort: 12000}),
generate_address: generateAddress,
lightning_p2p_port: await getPort({port: 12001, stopPort: 13000}),
lightning_rpc_port: await getPort({port: 13001, stopPort: 14000}),
lightning_tower_port: await getPort({port: 14001, stopPort: 15000}),
});
});
await control.add_chain_peer({socket: target.chain_socket});
await target.add_chain_peer({socket: control.chain_socket});
const controlLnd = authenticatedLndGrpc({
cert: control.cert,
macaroon: control.macaroon,
socket: control.socket,
});
const id = (await getIdentity({lnd: controlLnd.lnd})).public_key;
const targetLnd = authenticatedLndGrpc({
cert: target.cert,
macaroon: target.macaroon,
socket: target.socket,
});
const targetId = (await getIdentity({lnd: targetLnd.lnd})).public_key;
const channelOpen = await setupChannel({
generate: ({address, count}) => {
return new Promise(async (resolve, reject) => {
const {lnd} = targetLnd;
await target.generate({
count,
address: (await makeAddress({lnd})).address,
});
if (!count || count < maturity) {
return resolve();
}
await asyncRetry({interval, times}, async () => {
const [utxo] = (await getUtxos({lnd})).utxos;
if (!utxo) {
throw new Error('ExpectedUtxoInUtxos');
}
});
return resolve();
});
},
give_tokens: giftTokens,
lnd: targetLnd.lnd,
to: {id, socket: control.ln_socket},
});
const {backup} = await getBackups({lnd: controlLnd.lnd});
await control.kill({});
const clone = await asyncRetry({interval, times}, async () => {
return await spawnLightningDocker({
seed,
chain_p2p_port: await getPort({port: 8000, stopPort: 9000}),
chain_rpc_port: await getPort({port: 9001, stopPort: 10000}),
chain_zmq_block_port: await getPort({port: 10001, stopPort: 11000}),
chain_zmq_tx_port: await getPort({port: 11001, stopPort: 12000}),
generate_address: generateAddress,
lightning_p2p_port: await getPort({port: 12001, stopPort: 13000}),
lightning_rpc_port: await getPort({port: 13001, stopPort: 14000}),
lightning_tower_port: await getPort({port: 14001, stopPort: 15000}),
});
});
await clone.add_chain_peer({socket: target.chain_socket});
await target.add_chain_peer({socket: clone.chain_socket});
const cloneLnd = authenticatedLndGrpc({
cert: clone.cert,
macaroon: clone.macaroon,
socket: clone.socket,
});
await addPeer({
lnd: cloneLnd.lnd,
public_key: targetId,
socket: target.ln_socket,
});
await asyncRetry({interval, times}, async () => {
await recoverFundsFromChannels({backup, lnd: cloneLnd.lnd});
});
await clone.generate({
address: generateAddress,
count: confirmationCount,
});
await target.generate({
address: generateAddress,
count: confirmationCount,
});
// Target should force close the channel
{
const {lnd} = targetLnd;
await asyncRetry({interval, times}, async () => {
await addPeer({
lnd: cloneLnd.lnd,
public_key: targetId,
socket: target.ln_socket,
});
const [chan] = (await getPendingChannels({lnd})).pending_channels;
if (!chan) {
throw new Error('ExpectedChannelClosing');
}
});
}
// Make sure that the clone is getting the recovered funds
{
const {lnd} = cloneLnd;
await asyncRetry({interval, times}, async () => {
const [chan] = (await getPendingChannels({lnd})).pending_channels;
await target.generate({address: generateAddress});
if (!chan.local_balance) {
throw new Error('ExpectedChannelClosingBalance');
}
});
const [chan] = (await getPendingChannels({lnd})).pending_channels;
strictEqual(!!chan.close_transaction_id, true, 'Close transaction id');
strictEqual(chan.is_active, false, 'Chan no longer active');
strictEqual(chan.is_closing, true, 'Channel is closing');
strictEqual(chan.is_opening, false, 'Channel closing');
strictEqual(chan.local_balance, giftTokens, 'Funds are being restored');
strictEqual(chan.partner_public_key, targetId, 'Peer key');
strictEqual(chan.transaction_id, channelOpen.transaction_id, 'Chan tx id');
strictEqual(chan.transaction_vout, channelOpen.transaction_vout, 'Vout');
}
await clone.kill({});
await target.kill({});
return;
});