UNPKG

probing

Version:

Lightning Network probing utilities

360 lines (339 loc) 9.81 kB
const {once} = require('events'); const {promisify} = require('util'); const strictSame = require('node:assert').strict.deepStrictEqual; const test = require('node:test'); const {throws} = require('node:assert').strict; const {getChanInfoResponse} = require('./../fixtures'); const {getInfoResponse} = require('./../fixtures'); const {subscribeToMultiPathProbe} = require('./../../liquidity'); const delay = promisify(setTimeout); const getInfoRes = () => JSON.parse(JSON.stringify(getInfoResponse)); let i = 0; const nextTick = promisify(process.nextTick); const makeLndDefault = overrides => { const lndDefault = { deletePayment: ({}, cbk) => cbk(), getChanInfo: ({channel}, cbk) => cbk(null, { capacity: '1', chan_point: '1:1', channel_id: 1, node1_policy: { disabled: false, fee_base_msat: '1', fee_rate_milli_msat: '1', last_update: 1, max_htlc_msat: (21e8).toString(), min_htlc: '1', time_lock_delta: 1, }, node1_pub: 'a', node2_policy: { disabled: false, fee_base_msat: '2', fee_rate_milli_msat: '2', last_update: 2, max_htlc_msat: (21e8).toString(), min_htlc: '2', time_lock_delta: 2, }, node2_pub: '00', }), getInfo: ({}, cbk) => cbk(null, getInfoRes()), listChannels: ({}, cbk) => cbk(null, {channels: []}), queryRoutes: ({}, cbk) => cbk(null, { routes: [], success_prob: 1, }), }; Object.keys(overrides).forEach(key => lndDefault[key] = overrides[key]); return lndDefault; }; const makeLnd = overrides => { const lnd = { default: makeLndDefault({}), router: { buildRoute: ({}, cbk) => cbk('err'), sendToRouteV2: ({}, cbk) => cbk(null, { failure: {code: 'UNKNOWN_PAYMENT_HASH'}, }), }, }; Object.keys(overrides).forEach(key => lnd[key] = overrides[key]); return lnd; }; const makeArgs = overrides => { const args = { cltv_delta: 1, destination: Buffer.alloc(33).toString('hex'), evaluation_delay_ms: 1, lnd: makeLnd({}), probes: [], public_key: 'a', }; Object.keys(overrides).forEach(key => args[key] = overrides[key]); return args; }; const tests = [ { args: makeArgs({cltv_delta: undefined}), description: 'CLTV delta is required', error: [400, 'ExpectedFinalCltvDeltaToSubscribeToMultiPathProbe'], }, { args: makeArgs({destination: undefined}), description: 'A destination or a request is required', error: [400, 'ExpectedDestinationOrRequestToSubscribeToMultiPathProbe'], }, { args: makeArgs({lnd: undefined}), description: 'LND is required', error: [400, 'ExpectedLndApiObjectToSubscribeToMultiPathProbe'], }, { args: makeArgs({ lnd: makeLnd({ default: { deletePayment: ({}, cbk) => cbk(), getInfo: ({}, cbk) => cbk('err'), }, }), }), description: 'An error is passed back', expected: { events: [{ data: [503, 'GetWalletInfoErr', {err: 'err'}], event: 'error', }], }, }, { args: makeArgs({}), description: 'A failure is passed back', expected: {events: [{data: {}, event: 'failure'}]}, }, { args: makeArgs({ path_timeout_ms: 1, probe_timeout_ms: 1, lnd: makeLnd({ default: { deletePayment: ({}, cbk) => cbk(), getChanInfo: ({channel}, cbk) => cbk(null, { capacity: '1', chan_point: '1:1', channel_id: 1, node1_policy: { disabled: false, fee_base_msat: '1', fee_rate_milli_msat: '1', last_update: 1, max_htlc_msat: (21e8).toString(), min_htlc: '1', time_lock_delta: 1, }, node1_pub: 'a', node2_policy: { disabled: false, fee_base_msat: '2', fee_rate_milli_msat: '2', last_update: 2, max_htlc_msat: (21e8).toString(), min_htlc: '2', time_lock_delta: 2, }, node2_pub: '00', }), getInfo: ({}, cbk) => cbk(null, getInfoRes()), listChannels: ({}, cbk) => cbk(null, { channels: [{ active: true, alias_scids: [], capacity: 1, chan_id: '1', channel_point: '00:1', close_address: 'cooperative_close_address', commit_fee: '1', commit_weight: '1', commitment_type: 'LEGACY', fee_per_kw: '1', initiator: true, local_balance: '1', local_chan_reserve_sat: '1', local_constraints: { chan_reserve_sat: '1', csv_delay: 1, dust_limit_sat: '1', max_accepted_htlcs: 1, max_pending_amt_msat: '1', min_htlc_msat: '1', }, num_updates: 1, pending_htlcs: [{ amount: '1', expiration_height: 1, hash_lock: Buffer.alloc(32), incoming: true, }], private: true, remote_balance: 1, remote_chan_reserve_sat: '1', remote_constraints: { chan_reserve_sat: '1', csv_delay: 1, dust_limit_sat: '1', max_accepted_htlcs: 1, max_pending_amt_msat: '1', min_htlc_msat: '1', }, remote_pubkey: '00', thaw_height: 0, total_satoshis_received: 1, total_satoshis_sent: 1, unsettled_balance: 1, }], }), queryRoutes: (args, cbk) => { if (args.ignored_pairs.length) { return cbk(null, {routes: [], success_prob: 0}); } return cbk(null, { routes: [{ hops: [{ amt_to_forward_msat: '1', chan_capacity: '1', chan_id: '1', custom_records: {}, expiry: 1, fee_msat: '1', pub_key: '00', }], total_amt: 1, total_amt_msat: '1', total_fees: '1', total_fees_msat: '1', total_time_lock: 1, }], success_prob: 1, }); }, }, router: { buildRoute: ({}, cbk) => cbk('err'), sendToRouteV2: (args, cbk) => { return cbk(null, { failure: { chan_id: '1', code: 'UNKNOWN_PAYMENT_HASH', failure_source_index: 1, }, preimage: Buffer.alloc(Number()), }); }, }, }), }), description: 'A success is found', expected: { events: [ { data: { route: { confidence: 1000000, fee: 0, fee_mtokens: '1', hops: [{ channel: '0x0x1', channel_capacity: 1, fee: 0, fee_mtokens: '1', forward: 0, forward_mtokens: '1', public_key: '00', timeout: 1, }], messages: [], mtokens: '1', safe_fee: 1, safe_tokens: 1, timeout: 1, tokens: 0, payment: undefined, total_mtokens: undefined, }, }, event: 'probing' }, { data: { route: { confidence: 1000000, fee: 0, fee_mtokens: '1', hops: [{ channel: '0x0x1', channel_capacity: 1, fee: 0, fee_mtokens: '1', forward: 0, forward_mtokens: '1', public_key: '00', timeout: 1 }], messages: [], mtokens: '1', safe_fee: 1, safe_tokens: 1, timeout: 1, tokens: 0, payment: undefined, total_mtokens: undefined, } }, event: 'routing_success', }, { data: {tokens: 50000}, event: 'evaluating', }, { data: { paths: [{ channels: ['0x0x1'], fee: 0, fee_mtokens: '0', liquidity: 50001, relays: ['00'], }], }, event: 'success', }, ], }, }, ]; tests.forEach(({args, description, error, expected}) => { return test(description, async () => { if (!!error) { throws(() => subscribeToMultiPathProbe(args), error, 'Got error'); } else { const events = []; const sub = subscribeToMultiPathProbe(args); [ 'error', 'evaluating', 'failure', 'probing', 'routing_failure', 'routing_success', 'success', ] .forEach(event => sub.on(event, data => events.push({data, event}))); await delay(100); // Make sure that no listener to error doesn't cause an issue const sub2 = subscribeToMultiPathProbe(args); await nextTick(); strictSame(events, expected.events, 'Got expected events'); } return; }); });