ln-service
Version:
Interaction helper for your Lightning Network daemon
225 lines (170 loc) • 6.8 kB
JavaScript
const {deepEqual} = require('node:assert').strict;
const {equal} = require('node:assert').strict;
const test = require('node:test');
const asyncRetry = require('async/retry');
const {setupChannel} = require('ln-docker-daemons');
const {spawnLightningCluster} = require('ln-docker-daemons');
const {addPeer} = require('./../../');
const {createInvoice} = require('./../../');
const {decodePaymentRequest} = require('./../../');
const {getChannel} = require('./../../');
const {getChannels} = require('./../../');
const {getHeight} = require('./../../');
const {getInvoice} = require('./../../');
const {getRouteToDestination} = require('./../../');
const {openChannel} = require('./../../');
const {payViaRoutes} = require('./../../');
const {routeFromChannels} = require('./../../');
const {subscribeToForwardRequests} = require('./../../');
const waitForRoute = require('./../macros/wait_for_route');
const channelCapacityTokens = 1e6;
const confirmationCount = 6;
const defaultFee = 1e3;
const defaultVout = 0;
const interval = 10;
const intermediateRecord = {type: '65536', value: '5678'};
const mtokPadding = '000';
const regtestChain = '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206';
const reserveRatio = 0.99;
const size = 3;
const start = new Date().toISOString();
const times = 3000;
const tlvType = '67676';
const tlvValue = '010203';
const tokens = 100;
const txIdHexLength = 32 * 2;
// Paying via routes should successfully pay via routes
test(`Pay via routes`, async () => {
const {kill, nodes} = await spawnLightningCluster({size});
const [{generate, lnd}, target, remote] = nodes;
await generate({count: 400});
const remoteLnd = remote.lnd;
const targetPubKey = target.id;
await addPeer({lnd, public_key: target.id, socket: target.socket});
const channel = await setupChannel({generate, lnd, to: target});
await addPeer({
lnd: target.lnd,
public_key: remote.id,
socket: remote.socket,
});
const targetToRemoteChan = await setupChannel({
generate: target.generate,
is_private: true,
lnd: target.lnd,
to: remote,
});
const [remoteChannel] = (await getChannels({lnd: remoteLnd})).channels;
await target.generate({count: confirmationCount});
await asyncRetry({interval, times}, async () => {
const {request} = await createInvoice({
tokens,
is_including_private_channels: true,
lnd: remote.lnd,
});
const {routes} = await decodePaymentRequest({lnd, request});
if (!!routes.length) {
return;
}
throw new Error('ExpectedRoutesOnInvoice');
});
const invoice = await createInvoice({
tokens,
is_including_private_channels: true,
lnd: remote.lnd,
});
const {id} = invoice;
const {request} = invoice;
const decodedRequest = await decodePaymentRequest({lnd, request});
await waitForRoute({
lnd,
destination: decodedRequest.destination,
routes: decodedRequest.routes,
tokens: invoice.tokens,
});
const {route} = await getRouteToDestination({
lnd,
cltv_delta: decodedRequest.cltv_delta,
destination: decodedRequest.destination,
payment: invoice.payment,
routes: decodedRequest.routes,
tokens: invoice.tokens,
total_mtokens: !!invoice.payment ? invoice.mtokens : undefined,
});
const {destination} = decodedRequest;
// Pay invoice, but with an invalid id
try {
await payViaRoutes({lnd, routes: [route]});
} catch (err) {
const [code, message, {failures}] = err;
equal(code, 404, 'Invoice is unknown');
equal(message, 'UnknownPaymentHash', 'Payment hash not recognized');
const [[failCode, failMessage, failDetails]] = failures;
equal(failCode, 404, 'No known entity');
equal(failMessage, 'UnknownPaymentHash', 'This is an unknown hash');
}
const toRemote = await getChannel({
lnd: target.lnd,
id: targetToRemoteChan.id,
});
toRemote.policies.forEach(n => n.base_fee_mtokens = '0');
const insufficientFeeRoute = routeFromChannels({
channels: [await getChannel({lnd, id: channel.id}), toRemote],
cltv_delta: 40,
destination: decodedRequest.destination,
height: (await getHeight({lnd})).current_block_height,
mtokens: '100000',
});
try {
await payViaRoutes({id, lnd, routes: [insufficientFeeRoute.route]});
} catch (err) {
const [lowFeeErrCode, lowFeeErrMessage, {failures}] = err;
const [[,, details]] = failures;
let flags = !details.policy.is_disabled ? 0 : 1;
if (target.id > remote.id) {
flags = !flags ? 1 : 0;
}
if (details.index !== undefined) {
equal(details.index, 1, 'Failure index returned');
}
equal(lowFeeErrCode, 503, 'Low fee returns low fee error code');
equal(details.channel, toRemote.id, 'Fwd channel id returned');
equal(details.policy.base_fee_mtokens, '1000', 'Base fee');
equal(details.policy.cltv_delta, 40, 'CLTV delta returned');
equal(details.policy.fee_rate, 1, 'Fee rate returned');
equal(details.policy.is_disabled, false, 'Channel is enabled');
equal(details.policy.min_htlc_mtokens, '1000', 'Min HTLC tokens');
equal(!!details.policy.updated_at, true, 'Updated at returned');
equal(details.update.chain, regtestChain, 'Chain is returned');
equal(details.update.channel_flags, flags, 'Channel flags');
equal(details.update.extra_opaque_data, '', 'Opaque data');
equal(details.update.message_flags, 1, 'Message flags');
equal(details.update.signature.length, 128, 'Signature length');
equal(lowFeeErrMessage, 'FeeInsufficient', 'Low fee returns low fee msg');
}
const [hopToTarget] = route.hops;
hopToTarget.messages = [intermediateRecord];
route.messages = [{type: tlvType, value: tlvValue}];
const sub = subscribeToForwardRequests({lnd: target.lnd});
const forwardMessages = [];
sub.on('forward_request', request => {
request.messages.forEach(message => forwardMessages.push(message));
return request.accept();
});
const payment = await payViaRoutes({id, lnd, routes: [route]});
deepEqual(forwardMessages, [intermediateRecord], 'Got intermediate record');
equal(payment.confirmed_at > start, true, 'Paid has confirm date');
const paidInvoice = await getInvoice({id, lnd: remote.lnd});
if (!!paidInvoice.payments.length) {
const [payment] = paidInvoice.payments;
const messages = payment.messages.filter(n => n.type !== '106823');
if (!!messages.length) {
const [message] = messages;
equal(message.type, tlvType, 'Got message type');
equal(message.value, tlvValue, 'Got message value');
}
}
equal(paidInvoice.secret, invoice.secret, 'Paying invoice got secret');
equal(paidInvoice.is_confirmed, true, 'Private invoice is paid');
await kill({});
return;
});