update dependencies

This commit is contained in:
Alex Bosworth 2019-09-01 11:19:04 -07:00
parent 004357e846
commit 9927706f2b
No known key found for this signature in database
GPG key ID: E80D2F3F311FD87E
20 changed files with 882 additions and 789 deletions

View file

@ -1,6 +1,6 @@
# Versions
## Version 3.4.0
## Version 3.4.1
- `peers`: add --sort to sort returned peers by an attribute

3
arrays/index.js Normal file
View file

@ -0,0 +1,3 @@
const sortBy = require('./sort_by');
module.exports = {sortBy};

44
arrays/sort_by.js Normal file
View file

@ -0,0 +1,44 @@
const {isArray} = Array;
const equalTo = 0;
const greaterThan = 1;
const lessThan = -1;
/** Sort array by attribute, lowest to highest
{
array: [<Array Element Object>]
attribute: <Attribute String>
}
@throws
<Error>
@returns
{
sorted: [<Sorted Element Object>]
}
*/
module.exports = ({array, attribute}) => {
if (!isArray(array)) {
throw new Error('ExpectedArrayToSortByAttribute');
}
if (!attribute) {
throw new Error('ExpectedAttributeToSortArrayBy');
}
const sorted = array.slice().sort((a, b) => {
if (a[attribute] > b[attribute]) {
return greaterThan;
}
if (b[attribute] > a[attribute]) {
return lessThan;
}
return equalTo;
});
return {sorted};
};

View file

@ -44,7 +44,11 @@ module.exports = ({encrypted, secret}) => {
const ciphertext = Hex.parse(trim(hex, hex.length - zeroIndex(hex)));
const clear = decrypt({ciphertext}, key, {iv, padding, mode: mode.CFB});
try {
const clear = decrypt({ciphertext}, key, {iv, padding, mode: mode.CFB});
return {payload: clear.toString(enc.Utf8)};
return {payload: clear.toString(enc.Utf8)};
} catch (err) {
throw new Error('FailedToDecryptCipherTextWithSecretKey');
}
};

View file

@ -59,11 +59,15 @@ module.exports = ({node}, cbk) => {
const [chains, nets] = defaults;
const all = chains.map(chain => nets.map(network => ({chain, network})));
const all = chains.map(chain => {
return nets.map(network => ({chain, network}))
});
// Find the default macaroon
return asyncDetectSeries(flatten(all), ({chain, network}, cbk) => {
const macPath = [].concat(pathToMac).concat([chain, network, macName]);
const macPath = []
.concat(pathToMac)
.concat([chain, network, macName]);
return readFile(join(...[path].concat(macPath)), (_, macaroon) => {
return cbk(null, macaroon);
@ -76,11 +80,13 @@ module.exports = ({node}, cbk) => {
const {chain, network} = macaroon;
const macPath = [].concat(pathToMac).concat([chain, network, macName]);
const macPath = []
.concat(pathToMac)
.concat([chain, network, macName]);
return readFile(join(...[path].concat(macPath)), (err, macaroon) => {
if (!!err) {
return cbk([503, 'FailedToGetMacaroonData', err]);
return cbk([503, 'FailedToGetMacaroonData', {err}]);
}
return cbk(null, macaroon.toString(base64));

View file

@ -5,6 +5,7 @@ const {getNode} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const {authenticatedLnd} = require('./../lnd');
const {sortBy} = require('./../arrays');
const defaultSort = 'public_key';
const sumOf = arr => arr.reduce((sum, n) => sum + n);
@ -52,10 +53,6 @@ module.exports = (args, cbk) => {
const maxOutbound = args.outbound_liquidity_below;
const peerKeys = getChannels.channels.map(n => n.partner_public_key);
const sortBy = (arr, attr) => arr.slice().sort((a,b) => {
return (a[attr] > b[attr]) ? 1 : ((b[attr] > a[attr]) ? -1 : 0);
});
const peers = await asyncMap(uniq(peerKeys), async publicKey => {
const channels = getChannels.channels.filter(channel => {
return channel.partner_public_key === publicKey;
@ -75,7 +72,8 @@ module.exports = (args, cbk) => {
};
});
return sortBy(peers, args.sort_by || defaultSort)
return sortBy({array: peers, attribute: args.sort_by || defaultSort})
.sorted
.filter(n => !maxInbound || n.inbound_liquidity < maxInbound)
.filter(n => !maxOutbound || n.outbound_liquidity < maxOutbound)
.map(n => ({

View file

@ -10,12 +10,16 @@ const {authenticatedLnd} = require('./../lnd');
const executeProbe = require('./execute_probe');
const {findMaxRoutable} = require('./../routing');
const {getInboundPath} = require('./../routing');
const {sortBy} = require('./../arrays');
const cltvBuffer = 3;
const defaultCltvDelta = 144;
const defaultTokens = 10;
const {floor} = Math;
const {isArray} = Array;
const maxCltvDelta = 144 * 30;
const {now} = Date;
const reserveRatio = 0.01;
/** Determine if a destination can be paid by probing it
@ -111,18 +115,22 @@ module.exports = (args, cbk) => {
const withPeer = channels.filter(n => n.partner_public_key == outPeer);
if (!withPeer.length) {
return cbk([404, 'NoActiveChannelWithChosenPeer']);
return cbk([404, 'NoActiveChannelWithOutgoingPeer']);
}
const withBalance = withPeer
.filter(n => tokens < n.local_balance - (n.local_reserve || 0));
const withBalance = withPeer.filter(n => {
const reserve = n.local_reserve || floor(n.capacity * reserveRatio);
return n.local_balance - tokens > reserve + n.commit_transaction_fee;
});
if (!withBalance.length) {
return cbk([404, 'NoChannelWithSufficientBalance']);
}
const [channel] = withBalance
.sort((a, b) => a.local_balance < b.local_balance ? -1 : 1);
const attribute = 'local_balance';
const [channel] = sortBy({attribute, array: withBalance}).sorted;
return cbk(null, channel.id);
}],
@ -169,7 +177,7 @@ module.exports = (args, cbk) => {
({getHeight, getInboundPath, getLnd, outgoingChannelId, to}, cbk) =>
{
return executeProbe({
cltv_delta: to.cltv_delta || defaultCltvDelta,
cltv_delta: (to.cltv_delta || defaultCltvDelta) + cltvBuffer,
destination: to.destination,
is_strict_hints: !!getInboundPath,
lnd: getLnd.lnd,
@ -213,6 +221,8 @@ module.exports = (args, cbk) => {
return cbk([400, 'MaxFeeTooLow', {required_fee: probe.route.fee}]);
}
args.logger.info({paying: probe.route.hops.map(({channel}) => channel)});
return payViaRoutes({
id: to.id,
lnd: getLnd.lnd,

View file

@ -1,17 +1,18 @@
const asyncAuto = require('async/auto');
const {authenticatedLndGrpc} = require('ln-service');
const {createInvoice} = require('ln-service');
const {getChannel} = require('ln-service');
const {getChannels} = require('ln-service');
const {getWalletInfo} = require('ln-service');
const {payViaRoutes} = require('ln-service');
const {routeFromChannels} = require('ln-service');
const {pay} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const {lndCredentials} = require('./../lnd');
const {authenticatedLnd} = require('./../lnd');
const {channelForGift} = require('./../routing');
const {giftRoute} = require('./../routing');
const {sortBy} = require('./../arrays');
const {floor} = Math;
const {isArray} = Array;
const minFeeRate = 0;
const minReceivableMtokens = BigInt(1000);
const mtokPerTok = BigInt(1000);
@ -33,7 +34,7 @@ const reserveRatio = 0.01;
module.exports = ({node, to, tokens}, cbk) => {
return asyncAuto({
// Credentials
credentials: cbk => lndCredentials({node}, cbk),
getLnd: cbk => authenticatedLnd({node}, cbk),
// Check arguments
validate: cbk => {
@ -49,51 +50,37 @@ module.exports = ({node, to, tokens}, cbk) => {
},
// Lnd
lnd: ['credentials', 'validate', ({credentials}, cbk) => {
const {cert, macaroon, socket} = credentials;
return cbk(null, authenticatedLndGrpc({cert, macaroon, socket}).lnd);
}],
lnd: ['getLnd', ({getLnd}, cbk) => cbk(null, getLnd.lnd)],
// Get channels
getChannels: ['lnd', ({lnd}, cbk) => getChannels({lnd}, cbk)],
// Peer channel
peerChannel: ['getChannels', ({getChannels}, cbk) => {
const {channels} = getChannels;
try {
const {channels} = getChannels;
const withPeer = channels.filter(n => n.partner_public_key === to);
return cbk(null, channelForGift({channels, to, tokens}).id);
} catch (err) {
const {message} = err;
if (!withPeer.length) {
return cbk([400, 'ExpectedDirectChannelWithPeerToGiftTo']);
switch (message) {
case 'NoActiveChannelWithSpecifiedPeer':
return cbk([400, 'SendingGiftRequiresActiveChannelWithPeer']);
case 'NoActiveChannelWithSufficientLocalBalance':
return cbk([400, 'SendingGiftRequiresChannelWithSufficientBalance']);
case 'NoActiveChannelWithSufficientRemoteBalance':
return cbk([400, 'SendingGiftRequiresChannelWithSomeRemoteBalance']);
case 'NoDirectChannelWithSpecifiedPeer':
return cbk([400, 'SendingGiftRequiresDirectChannelWithPeer']);
default:
return cbk([500, 'UnexpectedErrorDeterminingChannelForGift', {err}]);
}
}
const active = withPeer.filter(n => !!n.is_active);
if (!active.length) {
return cbk([400, 'ExpectedActiveChannelWithPeerToGiftTo']);
}
const hasTokens = active.filter(n => n.local_balance > tokens);
if (!hasTokens.length) {
return cbk([400, 'ExpectedChannelWithAvailableFundsToGift']);
}
const hasRemoteBalance = hasTokens
.filter(n => n.remote_balance > floor(n.capacity * reserveRatio));
if (!hasRemoteBalance.length) {
return cbk([400, 'ExpectedChannelWithSufficientRemoteReserveBalance']);
}
hasRemoteBalance.sort((a, b) => {
return a.local_balance > b.local_balance ? -1 : 1;
});
const [channel] = hasRemoteBalance;
return cbk(null, channel.id);
}],
// Get channel policy info
@ -118,66 +105,60 @@ module.exports = ({node, to, tokens}, cbk) => {
'getWallet',
({createInvoice, getChannel, getWallet}, cbk) =>
{
const channel = getChannel;
const destination = getWallet.public_key;
const height = getWallet.current_block_height;
const invoice = createInvoice;
const mtokens = minReceivableMtokens.toString();
const mtokensToGive = BigInt(tokens) * mtokPerTok;
const peerPolicy = channel.policies.find(n => n.public_key === to);
peerPolicy.base_fee_mtokens = mtokensToGive.toString();
peerPolicy.fee_rate = minFeeRate;
const channels = [channel, channel];
try {
const {route} = routeFromChannels({
channels,
destination,
height,
mtokens,
const {route} = giftRoute({
tokens,
channel: getChannel,
destination: getWallet.public_key,
height: getWallet.current_block_height,
});
return cbk(null, route);
} catch (err) {
return cbk([500, 'FailedToConstructGiftRoute', err]);
const {message} = err;
switch (message) {
case 'GiftAmountTooLowToSend':
case 'OwnPolicyTooLowToCompleteForward':
case 'PeerPolicyTooLowToCompleteForward':
return cbk([400, 'AmountTooLowToCompleteGiftSend']);
default:
return cbk([500, 'FailedToConstructGiftRoute', {err}]);
}
}
}],
// Pay
// Send the gift
pay: [
'createInvoice',
'lnd',
'route',
({createInvoice, lnd, route}, cbk) =>
{
const path = {id: createInvoice.id, routes: [route]};
const {id} = createInvoice;
return pay({lnd, path}, (err, res) => {
if (!err) {
return cbk(null, {fee: res.fee});
return payViaRoutes({id, lnd, routes: [route]}, (err, res) => {
if (!!err) {
const [errCode, errMessage] = err;
console.log("ERR", err);
switch (errMessage) {
case 'RejectedUnacceptableFee':
return cbk([400, 'GiftTokensAmountTooLowToSend']);
default:
return cbk([503, 'UnexpectedErrorSendingGiftTokens', {err}]);
}
}
if (!isArray(err)) {
return cbk([503, 'UnexpectedErrorSendingTokens', err]);
}
const [errCode, errMessage] = err;
switch (errMessage) {
case 'RejectedUnacceptableFee':
return cbk([400, 'GiftTokensAmountTooLowToSend']);
default:
return cbk([503, 'UnexpectedErrorSendingGiftTokens', errMessage]);
}
return cbk(null, {gave_tokens: res.fee});
});
}],
// Done paying
paid: ['pay', ({pay}, cbk) => cbk(null, {gave_tokens: pay.fee})],
paid: ['pay', ({pay}, cbk) => cbk(null, {gave_tokens: pay.gave_tokens})],
},
returnResult({of: 'paid'}, cbk));
};

802
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,15 +11,15 @@
"async": "3.1.0",
"asyncjs-util": "1.1.2",
"bitcoin-ops": "1.4.1",
"bitcoinjs-lib": "5.1.4",
"bitcoinjs-lib": "5.1.5",
"bolt03": "1.2.1",
"bolt07": "1.4.3",
"caporal": "1.3.0",
"crypto-js": "3.1.9-1",
"goldengate": "2.0.0",
"goldengate": "2.0.1",
"html2unicode": "1.1.3",
"ln-accounting": "3.0.2",
"ln-service": "43.2.0",
"ln-accounting": "3.0.3",
"ln-service": "44.0.2",
"moment": "2.24.0",
"request": "2.88.0",
"stats-lite": "2.2.0",
@ -46,7 +46,7 @@
"url": "https://github.com/alexbosworth/balanceofsatoshis.git"
},
"scripts": {
"test": "tap test/balances/*.js test/fiat/*.js test/network/*.js test/responses/*.js test/routing/*.js"
"test": "tap test/arrays/*.js test/balances/*.js test/encryption/*.js test/fiat/*.js test/network/*.js test/responses/*.js test/routing/*.js"
},
"version": "3.4.0"
"version": "3.4.1"
}

View file

@ -0,0 +1,77 @@
const {sortBy} = require('./../arrays');
const {isArray} = Array;
const reserveValue = n => n.local_reserve || Math.floor(n.capacity * 0.01);
/** Channel with sufficient balance for a circular gift route
{
channels: [{
capacity: <Channel Capacity Tokens Number>
is_active: <Channel is Active Bool>
local_balance: <Local Balance Tokens Number>
[local_reserve]: <Local Reserve Tokens Number>
partner_public_key: <Peer Public Key Hex String>
remote_balance: <Remote Balance Tokens Number>
}]
to: <Channel With Peer Public Key Hex String>
tokens: <Tokens To Send Number>
}
@throws
<Error>
@returns
{
id: <Standard Format Channel Id String>
}
*/
module.exports = ({channels, to, tokens}) => {
if (!isArray(channels)) {
throw new Error('ExpectedArrayOfChannelsToFindChannelWithBalance');
}
if (channels.find(n => !n) !== undefined) {
throw new Error('ExpectedChannelsInArrayOfChannels');
}
if (!to) {
throw new Error('ExpectedToPublicKeyToFindChannelWithBalance');
}
if (!tokens) {
throw new Error('ExpectedTokensToFindChannelWithSufficientBalance');
}
const withPeer = channels.filter(n => n.partner_public_key === to);
if (!withPeer.length) {
throw new Error('NoDirectChannelWithSpecifiedPeer');
}
const active = withPeer.filter(n => !!n.is_active);
if (!active.length) {
throw new Error('NoActiveChannelWithSpecifiedPeer');
}
const hasTokens = active.filter(channel => {
return channel.local_balance - tokens > reserveValue(channel);
});
if (!hasTokens.length) {
throw new Error('NoActiveChannelWithSufficientLocalBalance');
}
const array = hasTokens.filter(channel => {
return channel.remote_balance + tokens > reserveValue(channel)
});
if (!array.length) {
throw new Error('NoActiveChannelWithSufficientRemoteBalance');
}
const [{id}] = sortBy({array, attribute: 'local_balance'}).sorted;
return {id};
};

121
routing/gift_route.js Normal file
View file

@ -0,0 +1,121 @@
const {routeFromChannels} = require('ln-service');
const defaultBaseFeeMtokens = '1000';
const defaultFeeRate = 1;
const defaultMin = '1000';
const feeRateDivisor = BigInt(1e6);
const {isArray} = Array;
const minFeeRate = 0;
const mtokPerTok = BigInt(1e3);
/** Get a gift route
{
channel: [{
id: <Channel Id String>
policies: [{
base_fee_mtokens: <Base Fee Millitokens String>
cltv_delta: <CLTV Delta Number>
fee_rate: <Fee Rate Number>
min_htlc_mtokens: <Minimum HTLC Tokens Number>
public_key: <Forwarding Public Key Hex String>
}]
}]
destination: <Destination Public Key Hex String>
height: <Current Best Tip Block Height Number>
tokens: <Tokens to Gift Number>
}
@throws
<Error>
@returns
{
route: {
fee: <Total Fee Tokens To Pay Number>
fee_mtokens: <Total Fee Millitokens To Pay String>
hops: [{
channel: <Standard Format Channel Id String>
channel_capacity: <Channel Capacity Tokens Number>
fee: <Fee Number>
fee_mtokens: <Fee Millitokens String>
forward: <Forward Tokens Number>
forward_mtokens: <Forward Millitokens String>
[public_key]: <Public Key Hex String>
timeout: <Timeout Block Height Number>
}]
mtokens: <Total Fee-Inclusive Millitokens String>
timeout: <Timeout Block Height Number>
tokens: <Total Fee-Inclusive Tokens Number>
}
}
*/
module.exports = ({channel, destination, height, tokens}) => {
if (!channel) {
throw new Error('ExpectedChannelToCalculateGiftRoute');
}
if (!destination) {
throw new Error('ExpectedDestinationToCalculateGiftRoute');
}
if (!height) {
throw new Error('ExpectedHeightToCalculateGiftRoute');
}
if (!tokens) {
throw new Error('ExpectedTokensToCalculateGiftRoute');
}
const {policies} = channel;
if (!isArray(policies) || !!policies.find(n => !n.public_key)) {
throw new Error('ExpectedChannelPoliciesToCalculateGiftRoute');
}
const policy = policies.find(n => n.public_key === destination);
if (!policy) {
throw new Error('ExpectedDestinationPolicyToCalculateGiftRoute');
}
const peerPolicy = policies.find(n => n.public_key !== destination);
if (!peerPolicy) {
throw new Error('ExpectedPeerPolicyToCalculateGiftRoute');
}
const minReceivableMtokens = BigInt(policy.min_htlc_mtokens || defaultMin);
const minSendableMtokens = BigInt(peerPolicy.min_htlc_mtokens || defaultMin);
const mtokens = minReceivableMtokens.toString();
const mtokensToGive = BigInt(tokens) * mtokPerTok;
if (minSendableMtokens > minReceivableMtokens) {
throw new Error('PeerPolicyTooLowToCompleteForward');
}
const baseFee = BigInt(peerPolicy.base_fee_mtokens || defaultBaseFeeMtokens);
const feeRate = BigInt(peerPolicy.fee_rate || defaultFeeRate);
const standardFee = (BigInt(mtokens) * feeRate / feeRateDivisor) + baseFee;
// Make sure that the real fee is going to be lower than the gift
if (standardFee > mtokensToGive) {
throw new Error('GiftAmountTooLowToSend');
}
peerPolicy.base_fee_mtokens = mtokensToGive.toString();
peerPolicy.fee_rate = minFeeRate;
const channels = [channel, channel];
const {route} = routeFromChannels({
channels,
destination,
height,
mtokens,
});
return {route};
};

View file

@ -1,4 +1,6 @@
const channelForGift = require('./channel_for_gift');
const findMaxRoutable = require('./find_max_routable');
const getInboundPath = require('./get_inbound_path');
const giftRoute = require('./gift_route');
module.exports = {findMaxRoutable, getInboundPath};
module.exports = {channelForGift, findMaxRoutable, getInboundPath, giftRoute};

View file

@ -66,7 +66,7 @@ module.exports = ({channels, cltv, lnd, tokens}, cbk) => {
route: ['getHeight', ({getHeight}, cbk) => {
const {route} = routeFromChannels({
channels,
cltv,
cltv_delta: cltv,
height: getHeight.current_block_height,
mtokens: mtokensFromTokens(tokens),
});

View file

@ -122,6 +122,24 @@ module.exports = (args, cbk) => {
return authenticatedLnd({node: args.node}, cbk);
}],
// Create a sweep address
createAddress: ['getLnd', 'recover', ({getLnd, recover}, cbk) => {
if (!!recover && recover.sweep_address) {
return cbk(null, {address: recover.sweep_address});
}
if (!!args.out_address) {
return cbk(null, {address: args.out_address});
}
return createChainAddress({
format: 'p2wpkh',
is_unused: true,
lnd: getLnd.lnd,
},
cbk);
}],
// Get channels
getChannels: ['getLnd', ({getLnd}, cbk) => {
return getChannels({lnd: getLnd.lnd, is_active: true}, cbk);
@ -132,6 +150,11 @@ module.exports = (args, cbk) => {
return getWalletInfo({lnd: getLnd.lnd}, cbk);
}],
// Get the current block height
getHeight: ['getWalletInfo', ({getWalletInfo}, cbk) => {
return cbk(null, getWalletInfo.current_block_height);
}],
// Figure out which channel to use when swapping
channel: ['getChannels', 'getLnd', ({getChannels, getLnd}, cbk) => {
if (!!args.recovery) {
@ -855,29 +878,6 @@ module.exports = (args, cbk) => {
return cbk();
}],
// Get the current block height
getHeight: ['getWalletInfo', ({getWalletInfo}, cbk) => {
return cbk(null, getWalletInfo.current_block_height);
}],
// Create a sweep address
createAddress: ['getLnd', 'recover', ({getLnd, recover}, cbk) => {
if (!!recover && recover.sweep_address) {
return cbk(null, {address: recover.sweep_address});
}
if (!!args.out_address) {
return cbk(null, {address: args.out_address});
}
return createChainAddress({
format: 'p2wpkh',
is_unused: true,
lnd: getLnd.lnd,
},
cbk);
}],
// Claim details
claim: [
'findDeposit',

View file

@ -0,0 +1,45 @@
const {test} = require('tap');
const {sortBy} = require('./../../arrays');
const tests = [
{
args: {},
description: 'An array is required',
error: 'ExpectedArrayToSortByAttribute',
},
{
args: {array: []},
description: 'An attribute to sort by is required',
error: 'ExpectedAttributeToSortArrayBy',
},
{
args: {array: [{foo: 1}, {foo: 2}, {foo: 3}], attribute: 'foo'},
description: 'Array is sorted when reversed',
expected: {sorted: [{foo: 1}, {foo: 2}, {foo: 3}]},
},
{
args: {array: [{foo: 1}, {foo: 3}, {foo: 2}], attribute: 'foo'},
description: 'Array is sorted when jumbled',
expected: {sorted: [{foo: 1}, {foo: 2}, {foo: 3}]},
},
{
args: {array: [{foo: 3}, {foo: 3}, {foo: 2}], attribute: 'foo'},
description: 'Array is sorted when equals exist',
expected: {sorted: [{foo: 2}, {foo: 3}, {foo: 3}]},
},
];
tests.forEach(({args, description, error, expected}) => {
return test(description, ({deepIs, end, throws}) => {
if (!!error) {
throws(() => sortBy(args), new Error(error), 'Got expected error');
} else {
const {sorted} = sortBy(args);
deepIs(sorted, expected.sorted, 'Array is sorted as expected');
}
return end();
});
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,53 @@
const {test} = require('tap');
const {decryptPayload} = require('./../../encryption');
const {encrypted} = require('./fixtures');
const {secret} = require('./fixtures');
const {parse} = JSON;
const tests = [
{
args: {},
description: 'An encrypted payload is required',
error: 'ExpectedEncryptedPayloadToDecrypt',
},
{
args: {encrypted},
description: 'A secret key is required',
error: 'ExpectedDecryptionSecretKeyToDecrypt',
},
{
args: {encrypted, secret: 'ff'},
description: 'A valid secret key is required',
error: 'FailedToDecryptCipherTextWithSecretKey',
},
{
args: {
secret,
encrypted: Buffer.from(encrypted, 'base64').toString('hex'),
},
description: 'Paylaod is decrypted',
expected: {
pair: 'BTCUSD',
price: 4004.14,
timestamp: '2019-01-10T00:00:11.000Z',
},
},
];
tests.forEach(({args, description, error, expected}) => {
return test(description, async ({end, equal, throws}) => {
if (!!error) {
throws(() => decryptPayload(args), new Error(error), 'Got error');
} else {
const [{pair, price, timestamp}] = parse(decryptPayload(args).payload);
equal(pair, expected.pair, 'Got expected pair');
equal(price, expected.price, 'Got expectd price');
equal(timestamp, expected.timestamp, 'Got expected timestamp');
}
return end();
});
});

View file

@ -0,0 +1,117 @@
const {test} = require('tap');
const {channelForGift} = require('./../../routing');
const tests = [
{
args: {},
description: 'An array of channels is required',
error: 'ExpectedArrayOfChannelsToFindChannelWithBalance',
},
{
args: {channels: [null]},
description: 'An array of channels objects is required',
error: 'ExpectedChannelsInArrayOfChannels',
},
{
args: {channels: []},
description: 'A to public key is required',
error: 'ExpectedToPublicKeyToFindChannelWithBalance',
},
{
args: {channels: [], to: 'bob'},
description: 'Tokens are required',
error: 'ExpectedTokensToFindChannelWithSufficientBalance',
},
{
args: {channels: [], to: 'bob', tokens: 1},
description: 'A direct channel is required',
error: 'NoDirectChannelWithSpecifiedPeer',
},
{
args: {channels: [{partner_public_key: 'bob'}], to: 'bob', tokens: 1},
description: 'An active channel is required',
error: 'NoActiveChannelWithSpecifiedPeer',
},
{
args: {
channels: [{is_active: true, partner_public_key: 'bob'}],
to: 'bob',
tokens: 1,
},
description: 'A channel with enough balance is required',
error: 'NoActiveChannelWithSufficientLocalBalance',
},
{
args: {
channels: [{
capacity: 20000,
is_active: true,
local_balance: 10000,
partner_public_key: 'bob',
}],
to: 'bob',
tokens: 500,
},
description: 'A channel with enough remote balance is required',
error: 'NoActiveChannelWithSufficientRemoteBalance',
},
{
args: {
channels: [{
capacity: 20000,
id: 'id',
is_active: true,
local_balance: 10000,
partner_public_key: 'bob',
remote_balance: 10000,
}],
to: 'bob',
tokens: 500,
},
description: 'A balanced channel is returned',
expected: {id: 'id'},
},
{
args: {
channels: [
{
capacity: 20000,
id: 'id1',
is_active: true,
local_balance: 200,
local_reserve: 100,
partner_public_key: 'bob',
remote_balance: 19800,
},
{
capacity: 20000,
id: 'id2',
is_active: true,
local_balance: 10000,
local_reserve: 100,
partner_public_key: 'bob',
remote_balance: 10000,
},
],
to: 'bob',
tokens: 500,
},
description: 'A channel with enough remote balance is required',
expected: {id: 'id2'},
},
];
tests.forEach(({args, description, error, expected}) => {
return test(description, ({end, equal, throws}) => {
if (!!error) {
throws(() => channelForGift(args), new Error(error), 'Got error');
} else {
const {id} = channelForGift(args);
equal(id, expected.id, 'Channel id is returned as expected');
}
return end();
});
});

View file

@ -0,0 +1,146 @@
const {test} = require('tap');
const {giftRoute} = require('./../../routing');
const tests = [
{
args: {},
description: 'A channel is required',
error: 'ExpectedChannelToCalculateGiftRoute',
},
{
args: {channel: {}},
description: 'A destination is required',
error: 'ExpectedDestinationToCalculateGiftRoute',
},
{
args: {channel: {}, destination: 'b'},
description: 'The current chain tip height is required',
error: 'ExpectedHeightToCalculateGiftRoute',
},
{
args: {channel: {}, destination: 'b', height: 1},
description: 'Tokens to gift are required',
error: 'ExpectedTokensToCalculateGiftRoute',
},
{
args: {channel: {}, destination: 'b', height: 1, tokens: 1},
description: 'Channel policies array is required',
error: 'ExpectedChannelPoliciesToCalculateGiftRoute',
},
{
args: {channel: {policies: [{}]}, destination: 'b', height: 1, tokens: 1},
description: 'Channel policies need public keys',
error: 'ExpectedChannelPoliciesToCalculateGiftRoute',
},
{
args: {
channel: {policies: [{public_key: 'c'}]},
destination: 'b',
height: 1,
tokens: 1,
},
description: 'Channel policies require a destination policy',
error: 'ExpectedDestinationPolicyToCalculateGiftRoute',
},
{
args: {
channel: {policies: [{public_key: 'b'}]},
destination: 'b',
height: 1,
tokens: 1,
},
description: 'Channel policies require the peer policy',
error: 'ExpectedPeerPolicyToCalculateGiftRoute',
},
{
args: {
channel: {
id: '1x1x1',
policies: [
{cltv_delta: 1, public_key: 'b'},
{cltv_delta: 1, min_htlc_mtokens: '100000', public_key: 'c'},
],
},
destination: 'b',
height: 1,
tokens: 1,
},
description: 'The peer min htlc policy has to be high enough to forward',
error: 'PeerPolicyTooLowToCompleteForward',
},
{
args: {
channel: {
id: '1x1x1',
policies: [
{cltv_delta: 1, public_key: 'b'},
{base_fee_mtokens: '1001', cltv_delta: 1, public_key: 'c'},
],
},
destination: 'b',
height: 1,
tokens: 1,
},
description: 'The real fee must be lower than the gift fee',
error: 'GiftAmountTooLowToSend',
},
{
args: {
channel: {
id: '1x1x1',
policies: [
{cltv_delta: 1, public_key: 'b'},
{cltv_delta: 1, public_key: 'c'},
],
},
destination: 'b',
height: 1,
tokens: 1,
},
description: 'Route is created',
expected: {
route: {
fee: 1,
fee_mtokens: '1000',
hops: [
{
channel: '1x1x1',
channel_capacity: undefined,
fee: 1,
fee_mtokens: '1000',
forward: 1,
forward_mtokens: '1000',
public_key: 'c',
timeout: 41,
},
{
channel: '1x1x1',
channel_capacity: undefined,
fee: 0,
fee_mtokens: '0',
forward: 1,
forward_mtokens: '1000',
public_key: 'b',
timeout: 41,
},
],
mtokens: '2000',
timeout: 42,
tokens: 2,
},
},
},
];
tests.forEach(({args, description, error, expected}) => {
return test(description, async ({deepIs, end, throws}) => {
if (!!error) {
throws(() => giftRoute(args), new Error(error), 'Got expected error');
} else {
deepIs(giftRoute(args).route, expected.route, 'Got expected route');
}
return end();
});
});