lightning
Version:
Lightning Network client library
430 lines (417 loc) • 11.3 kB
JavaScript
const {deepStrictEqual} = require('node:assert').strict;
const EventEmitter = require('node:events');
const {rejects} = require('node:assert').strict;
const {strictEqual} = require('node:assert').strict;
const test = require('node:test');
const {payViaRoutes} = require('./../../../');
const preimage = Buffer.alloc(32);
const deletePayment = ({}, cbk) => cbk();
const getInfo = ({}, cbk) => {
return cbk(null, {
alias: '',
best_header_timestamp: Math.round(Date.now() / 1000),
block_hash: Buffer.alloc(32).toString('hex'),
block_height: 100,
chains: [],
color: '#000000',
features: {},
identity_pubkey: Buffer.alloc(33).toString('hex'),
num_active_channels: 1,
num_peers: 1,
num_pending_channels: 1,
synced_to_chain: true,
uris: [],
version: 'version',
});
};
const tests = [
{
args: {id: 'id'},
description: 'A hex ID is expected',
error: [400, 'ExpectedStandardHexPaymentHashId'],
},
{
args: {},
description: 'LND is expected',
error: [400, 'ExpectedLndForToPayViaSpecifiedRoutes'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
},
description: 'An array of routes is expected',
error: [400, 'ExpectedArrayOfRoutesToPayViaRoutes'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
routes: [],
},
description: 'A non-empty array of routes is expected',
error: [400, 'ExpectedArrayOfRoutesToPayViaRoutes'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
routes: [null],
},
description: 'An array of non-empty routes is expected',
error: [400, 'ExpectedArrayOfRoutesToAttemptPayingOver'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
routes: [{}],
},
description: 'Routes are expected to have hops',
error: [400, 'ExpectedArrayOfHopsForPayViaRoute'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: Buffer.alloc(33).toString('hex'),
timeout: 100,
}],
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'A discrete resolution is expected',
error: [503, 'FailedToReceiveDiscreteFailureOrSuccess'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk('err')},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: Buffer.alloc(33).toString('hex'),
timeout: 100,
}],
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'Errors are passed back',
error: [
503,
'UnexpectedErrorWhenPayingViaRoute',
{failures: [[503, 'UnexpectedErrorWhenPayingViaRoute', {err: 'err'}]]},
],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
timeout: 100,
}],
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'Public keys in hops are expected',
error: [400, 'ExpectedPublicKeyInPayViaRouteHops'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {})},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [...Array(21)].map(() => ({
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: Buffer.alloc(33).toString('hex'),
timeout: 100,
})),
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'A route with fewer than max hops is expected',
error: [400, 'ExpectedRouteWithFewerThanMaxHops'],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {
sendToRouteV2: ({}, cbk) => {
return cbk(null, {
failure: {
code: 'UNKNOWN_PAYMENT_HASH',
failure_source_index: 1,
},
});
},
},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: Buffer.alloc(33).toString('hex'),
timeout: 100,
}],
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'Get error paying via routes',
error: [
404,
'UnknownPaymentHash',
{
failures: [[
404,
'UnknownPaymentHash',
{
channel: undefined,
height: undefined,
index: 1,
mtokens: undefined,
policy: null,
timeout_height: undefined,
update: undefined,
},
]],
},
],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {
sendToRouteV2: ({}, cbk) => {
return cbk(null, {
failure: {
channel_update: {
base_fee: 1000,
chain_hash: Buffer.alloc(32),
chan_id: '7654321',
channel_flags: 0,
extra_opaque_data: Buffer.alloc(0),
fee_rate: 1,
htlc_maximum_msat: 10000,
htlc_minimum_msat: 1000,
message_flags: 1,
signature: Buffer.alloc(64),
time_lock_delta: 40,
timestamp: 1231006505,
},
code: 'FEE_INSUFFICIENT',
failure_source_index: 1,
htlc_msat: '1',
},
});
},
},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [
{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: '00',
timeout: 100,
},
{
channel: '0x116x52145',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: '01',
timeout: 100,
},
],
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'Get error paying via routes',
error: [
503,
'FeeInsufficient',
{
failures: [[
503,
'FeeInsufficient',
{
channel: '0x116x52145',
height: undefined,
index: 1,
mtokens: '1',
policy: {
base_fee_mtokens: '1000',
cltv_delta: 40,
fee_rate: 1,
is_disabled: false,
max_htlc_mtokens: 10000,
min_htlc_mtokens: 1000,
public_key: '00',
updated_at: '2009-01-03T18:15:05.000Z',
},
timeout_height: undefined,
update: {
chain: '0000000000000000000000000000000000000000000000000000000000000000',
channel_flags: 0,
extra_opaque_data: '',
message_flags: 1,
signature: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
},
},
]],
},
],
},
{
args: {
lnd: {
default: {deletePayment, getInfo},
router: {sendToRouteV2: ({}, cbk) => cbk(null, {preimage})},
},
routes: [{
fee: 1,
fee_mtokens: '1000',
hops: [{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: '01',
timeout: 100,
}],
mtokens: '1000',
timeout: 100,
tokens: 1,
}],
},
description: 'Pay via routes',
expected: {
failures: [],
fee: 1,
fee_mtokens: '1000',
hops: [{
channel: '1x1x1',
channel_capacity: 1,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
timeout: 100,
}],
mtokens: '1000',
secret: preimage.toString('hex'),
tokens: 1,
},
},
];
tests.forEach(({args, description, error, expected}) => {
return test(description, async () => {
if (!!error) {
try {
await payViaRoutes(args);
} catch (gotErr) {
if (gotErr.length === 2) {
deepStrictEqual(gotErr, error, 'Got expected error')
} else {
const [errCode, errMessage, {failures}] = gotErr;
const [expectedCode, expectedMessage, expectedDetails] = error;
strictEqual(errCode, expectedCode, 'Error code received');
strictEqual(errMessage, expectedMessage, 'Error message received');
deepStrictEqual(failures, expectedDetails.failures, 'Full fails');
}
return;
}
}
const paid = await payViaRoutes(args);
deepStrictEqual(paid.failures, expected.failures, 'Failures are returned');
strictEqual(paid.fee, expected.fee, 'Fee is returned');
strictEqual(paid.fee_mtokens, expected.fee_mtokens, 'Fee mtokens');
deepStrictEqual(paid.hops, expected.hops, 'Hops are returned');
strictEqual(paid.id.length, preimage.toString('hex').length, 'Got hash');
strictEqual(paid.is_confirmed, true, 'Payment is confirmed');
strictEqual(paid.is_outgoing, true, 'Transaction is an outgoing one');
strictEqual(paid.mtokens, expected.mtokens, 'Mtokens are returned');
strictEqual(paid.secret, expected.secret, 'Payment results in delivery');
strictEqual(paid.tokens, expected.tokens, 'Tokens are returned');
return;
});
});