mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
avoid invalid local channels when specifying inbound peer
This commit is contained in:
parent
91fb7b6502
commit
774f601d01
13 changed files with 1146 additions and 929 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
package-lock.json binary
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Versions
|
||||
|
||||
## Version 5.5.0
|
||||
## Version 5.5.1
|
||||
|
||||
- `peers`: Add `fee-days` option to show fees earned via a peer
|
||||
- `peers`: Add `first_connected` attribute to peers list
|
||||
|
|
|
|||
44
bos
44
bos
|
|
@ -215,18 +215,22 @@ prog
|
|||
.option('--no-color', 'Mute all colors')
|
||||
.option('--node <node_name>', 'Node to get chain fees view from')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return getChainFees({
|
||||
blocks: options.blocks,
|
||||
node: options.node,
|
||||
},
|
||||
returnObject({
|
||||
logger,
|
||||
reject,
|
||||
resolve,
|
||||
file: options.file,
|
||||
write: writeFile,
|
||||
}));
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
return getChainFees({
|
||||
blocks: options.blocks,
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
},
|
||||
returnObject({
|
||||
logger,
|
||||
reject,
|
||||
resolve,
|
||||
file: options.file,
|
||||
write: writeFile,
|
||||
}));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
|
@ -258,12 +262,16 @@ prog
|
|||
.option('--no-color', 'Mute all colors')
|
||||
.option('--node <node_name>', 'Get channel closes from saved node')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return getChannelCloses({
|
||||
limit: options.limit,
|
||||
node: options.node,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
return getChannelCloses({
|
||||
limit: options.limit,
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ const start = 2;
|
|||
|
||||
{
|
||||
[blocks]: <Block Count Number>
|
||||
[node]: <Node Name String>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
}
|
||||
|
||||
@returns via cbk
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
current_block_hash: <Chain Tip Best Block Hash Hex String>
|
||||
fee_by_block_target: {
|
||||
|
|
@ -30,63 +30,64 @@ const start = 2;
|
|||
}
|
||||
}
|
||||
*/
|
||||
module.exports = ({blocks, node}, cbk) => {
|
||||
return asyncAuto({
|
||||
// Authenticated lnd
|
||||
getLnd: cbk => authenticatedLnd({node}, cbk),
|
||||
module.exports = ({blocks, lnd}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedLndToGetChainFees']);
|
||||
}
|
||||
|
||||
// Get wallet info
|
||||
getInfo: ['getLnd', ({getLnd}, cbk) => {
|
||||
return getWalletInfo({lnd: getLnd.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get the fees
|
||||
getFees: ['getLnd', ({getLnd}, cbk) => {
|
||||
const blockCount = blocks || defaultBlockCount;
|
||||
|
||||
return asyncTimesSeries(blockCount - iteration, (i, cbk) => {
|
||||
return getChainFeeRate({
|
||||
confirmation_target: start + i,
|
||||
lnd: getLnd.lnd,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
// Exit with error when there is an invalid fee rate response
|
||||
if (!res.tokens_per_vbyte || res.tokens_per_vbyte < minFeeRate) {
|
||||
return cbk([503, 'UnexpectedChainFeeRateInGetFeesResponse']);
|
||||
}
|
||||
|
||||
return cbk(null, {rate: res.tokens_per_vbyte, target: start + i});
|
||||
});
|
||||
return cbk();
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Collapse chain fees into steps
|
||||
chainFees: ['getFees', 'getInfo', ({getFees, getInfo}, cbk) => {
|
||||
let cursor = {};
|
||||
const feeByBlockTarget = {};
|
||||
// Get the fees
|
||||
getFees: ['validate', ({}, cbk) => {
|
||||
const blockCount = blocks || defaultBlockCount;
|
||||
|
||||
getFees
|
||||
.filter(fee => {
|
||||
const isNewFee = cursor.rate !== fee.rate;
|
||||
return asyncTimesSeries(blockCount - iteration, (i, cbk) => {
|
||||
return getChainFeeRate({
|
||||
lnd,
|
||||
confirmation_target: start + i,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
cursor = isNewFee ? fee : cursor;
|
||||
return cbk(null, {rate: res.tokens_per_vbyte, target: start + i});
|
||||
});
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
return isNewFee;
|
||||
})
|
||||
.forEach(fee => {
|
||||
return feeByBlockTarget[fee.target+''] = ceil(fee.rate * bytesPerKb);
|
||||
// Get wallet info
|
||||
getInfo: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Collapse chain fees into steps
|
||||
chainFees: ['getFees', 'getInfo', ({getFees, getInfo}, cbk) => {
|
||||
let cursor = {};
|
||||
const feeByBlockTarget = {};
|
||||
|
||||
getFees
|
||||
.filter(fee => {
|
||||
const isNewFee = cursor.rate !== fee.rate;
|
||||
|
||||
cursor = isNewFee ? fee : cursor;
|
||||
|
||||
return isNewFee;
|
||||
})
|
||||
.forEach(({target, rate}) => {
|
||||
return feeByBlockTarget[target+''] = ceil(rate * bytesPerKb);
|
||||
});
|
||||
|
||||
return cbk(null, {
|
||||
current_block_hash: getInfo.current_block_hash,
|
||||
fee_by_block_target: feeByBlockTarget,
|
||||
});
|
||||
|
||||
return cbk(null, {
|
||||
current_block_hash: getInfo.current_block_hash,
|
||||
fee_by_block_target: feeByBlockTarget,
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({of :'chainFees'}, cbk));
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of :'chainFees'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const asyncAuto = require('async/auto');
|
|||
const asyncMapSeries = require('async/mapSeries');
|
||||
const {authenticatedLndGrpc} = require('ln-service');
|
||||
const {getClosedChannels} = require('ln-service');
|
||||
const {getNode} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
const {take} = require('lodash');
|
||||
|
|
@ -16,7 +17,7 @@ const defaultLimit = 20;
|
|||
|
||||
{
|
||||
[limit]: <Limit Number>
|
||||
[node]: <Node Name String>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
}
|
||||
|
||||
@returns via cbk
|
||||
|
|
@ -39,72 +40,89 @@ const defaultLimit = 20;
|
|||
}]
|
||||
}
|
||||
*/
|
||||
module.exports = (args, cbk) => {
|
||||
return asyncAuto({
|
||||
// Get lnd
|
||||
getLnd: cbk => authenticatedLnd({node: args.node}, cbk),
|
||||
|
||||
// Lnd
|
||||
lnd: ['getLnd', ({getLnd}, cbk) => cbk(null, getLnd.lnd)],
|
||||
|
||||
// Get closed channels
|
||||
getClosed: ['lnd', ({lnd}, cbk) => getClosedChannels({lnd}, cbk)],
|
||||
|
||||
// Get the current height
|
||||
getHeight: ['lnd', ({lnd}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Get the network
|
||||
getNetwork: ['lnd', ({lnd}, cbk) => getNetwork({lnd}, cbk)],
|
||||
|
||||
// Get spends
|
||||
getSpends: [
|
||||
'getClosed',
|
||||
'getHeight',
|
||||
'getNetwork',
|
||||
({getClosed, getHeight, getNetwork}, cbk) =>
|
||||
{
|
||||
const closedChannels = getClosed.channels
|
||||
.reverse()
|
||||
.filter(channel => !channel.is_funding_cancel);
|
||||
|
||||
const limit = args.limit || defaultLimit;
|
||||
|
||||
return asyncMapSeries(take(closedChannels, limit), (channel, cbk) => {
|
||||
return getChannelResolution({
|
||||
close_transaction_id: channel.close_transaction_id,
|
||||
is_cooperative_close: channel.is_cooperative_close,
|
||||
network: getNetwork.network,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
const currentHeight = getHeight.current_block_height;
|
||||
|
||||
return cbk(null, {
|
||||
blocks_since_close: currentHeight - channel.close_confirm_height,
|
||||
capacity: channel.capacity,
|
||||
close_transaction_id: channel.close_transaction_id,
|
||||
is_breach_close: channel.is_breach_close || undefined,
|
||||
is_cooperative_close: channel.is_cooperative_close || undefined,
|
||||
is_local_force_close: channel.is_local_force_close || undefined,
|
||||
is_remote_force_close: channel.is_remote_force_close || undefined,
|
||||
output_resolutions: res.resolutions || undefined,
|
||||
partner_public_key: channel.partner_public_key,
|
||||
transaction_id: channel.transaction_id,
|
||||
transaction_vout: channel.transaction_vout,
|
||||
});
|
||||
});
|
||||
},
|
||||
(err, closes) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
module.exports = ({limit, lnd}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedLndToGetChannelCloses']);
|
||||
}
|
||||
|
||||
return cbk(null, {closes: closes.reverse()});
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({of :'getSpends'}, cbk));
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get closed channels
|
||||
getClosed: ['validate', ({}, cbk) => getClosedChannels({lnd}, cbk)],
|
||||
|
||||
// Get the current height
|
||||
getHeight: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Get the network
|
||||
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
||||
|
||||
// Get spends
|
||||
getSpends: [
|
||||
'getClosed',
|
||||
'getHeight',
|
||||
'getNetwork',
|
||||
({getClosed, getHeight, getNetwork}, cbk) =>
|
||||
{
|
||||
const closedChannels = getClosed.channels
|
||||
.reverse()
|
||||
.filter(channel => !channel.is_funding_cancel);
|
||||
|
||||
const num = limit || defaultLimit;
|
||||
|
||||
return asyncMapSeries(take(closedChannels, num), (channel, cbk) => {
|
||||
return getChannelResolution({
|
||||
close_transaction_id: channel.close_transaction_id,
|
||||
is_cooperative_close: channel.is_cooperative_close,
|
||||
network: getNetwork.network,
|
||||
},
|
||||
async (err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
let alias;
|
||||
const currentHeight = getHeight.current_block_height;
|
||||
const isRemoteForceClose = channel.is_remote_force_close;
|
||||
|
||||
try {
|
||||
const node = await getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: channel.partner_public_key,
|
||||
});
|
||||
|
||||
alias = node.alias;
|
||||
} catch (err) {}
|
||||
|
||||
return cbk(null, {
|
||||
alias: alias || undefined,
|
||||
blocks_since_close: currentHeight - channel.close_confirm_height,
|
||||
capacity: channel.capacity,
|
||||
close_transaction_id: channel.close_transaction_id,
|
||||
is_breach_close: channel.is_breach_close || undefined,
|
||||
is_cooperative_close: channel.is_cooperative_close || undefined,
|
||||
is_local_force_close: channel.is_local_force_close || undefined,
|
||||
is_remote_force_close: isRemoteForceClose || undefined,
|
||||
output_resolutions: res.resolutions || undefined,
|
||||
partner_public_key: channel.partner_public_key,
|
||||
transaction_id: channel.transaction_id,
|
||||
transaction_vout: channel.transaction_vout,
|
||||
});
|
||||
});
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Channel closes
|
||||
closes: ['getSpends', ({getSpends}, cbk) => {
|
||||
return cbk(null, {closes: getSpends.slice().reverse()});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of :'closes'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ module.exports = (args, cbk) => {
|
|||
});
|
||||
|
||||
if (!withBalance.length) {
|
||||
return cbk([404, 'NoChannelWithSufficientBalance']);
|
||||
return cbk([404, 'NoOutboundPeerWithSufficientBalance']);
|
||||
}
|
||||
|
||||
const attribute = 'local_balance';
|
||||
|
|
|
|||
1064
package-lock.json
generated
1064
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,7 @@
|
|||
"ini": "1.3.5",
|
||||
"inquirer": "7.0.0",
|
||||
"ln-accounting": "3.1.6",
|
||||
"ln-service": "47.5.0",
|
||||
"ln-service": "47.5.1",
|
||||
"moment": "2.24.0",
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"request": "2.88.0",
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
},
|
||||
"description": "Lightning balance CLI",
|
||||
"devDependencies": {
|
||||
"tap": "14.10.1"
|
||||
"tap": "14.10.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
|
|
@ -61,5 +61,5 @@
|
|||
"scripts": {
|
||||
"test": "tap test/arrays/*.js test/balances/*.js test/chain/*.js test/encryption/*.js test/fiat/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/responses/*.js test/routing/*.js test/swaps/*.js test/telegram/*.js"
|
||||
},
|
||||
"version": "5.5.0"
|
||||
"version": "5.5.1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getNode} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const tokensAsMtokens = tokens => BigInt(tokens) * BigInt(1e3);
|
||||
|
|
@ -49,13 +51,24 @@ module.exports = ({destination, lnd, through, tokens}, cbk) => {
|
|||
return cbk();
|
||||
},
|
||||
|
||||
// Get channels to validate the inbound channel exists
|
||||
getChannels: ['validate', ({}, cbk) => getChannels({lnd}, cbk)],
|
||||
|
||||
// Get local node info to check if this is a local inbound channel
|
||||
getInfo: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Get node
|
||||
getNode: ['validate', ({}, cbk) => {
|
||||
return getNode({lnd, public_key: destination}, cbk);
|
||||
}],
|
||||
|
||||
// Connecting path
|
||||
path: ['getNode', ({getNode}, cbk) => {
|
||||
path: [
|
||||
'getChannels',
|
||||
'getInfo',
|
||||
'getNode',
|
||||
({getChannels, getInfo, getNode}, cbk) =>
|
||||
{
|
||||
const connectingChannels = getNode.channels
|
||||
.filter(chan => !!chan.policies.find(n => n.public_key === through));
|
||||
|
||||
|
|
@ -63,6 +76,8 @@ module.exports = ({destination, lnd, through, tokens}, cbk) => {
|
|||
return cbk([400, 'NoConnectingChannelToPayIn']);
|
||||
}
|
||||
|
||||
const publicKey = getInfo.public_key;
|
||||
|
||||
const [channel] = connectingChannels.filter(chan => {
|
||||
const policy = chan.policies.find(n => n.public_key === through);
|
||||
|
||||
|
|
@ -74,6 +89,17 @@ module.exports = ({destination, lnd, through, tokens}, cbk) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
const isLocal = chan.policies.find(n => n.public_key === publicKey);
|
||||
|
||||
const localChannel = getChannels.channels.find(({id}) => {
|
||||
return id === chan.id;
|
||||
});
|
||||
|
||||
// Exit early when this is a local channel but doesn't exist
|
||||
if (!!isLocal && !localChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return BigInt(policy.max_htlc_mtokens) > tokensAsMtokens(tokens);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const mtokensPerToken = BigInt(1e3);
|
|||
const notFoundIndex = -1;
|
||||
const rateDivisor = 1e6;
|
||||
const sample = a => !!a.length ? a[Math.floor(Math.random()*a.length)] : null;
|
||||
const tokAsBigTok = tokens => (tokens / 1e8).toFixed(8);
|
||||
const topOf = arr => arr.slice(0, Math.ceil(arr.length / 2));
|
||||
|
||||
/** Rebalance funds between peers
|
||||
|
|
@ -40,329 +41,344 @@ const topOf = arr => arr.slice(0, Math.ceil(arr.length / 2));
|
|||
[node]: <Node Name String>
|
||||
[out_through]: <Out through peer with Public Key Hex String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
*/
|
||||
module.exports = (args, cbk) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!args.logger) {
|
||||
return cbk([400, 'ExpectedLoggerToRebalance'])
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!args.logger) {
|
||||
return cbk([400, 'ExpectedLoggerToRebalance'])
|
||||
}
|
||||
|
||||
if (!!args.in_through && args.in_through === args.out_through) {
|
||||
return cbk([400, 'ExpectedInPeerNotEqualToOutPeer']);
|
||||
}
|
||||
if (!!args.in_through && args.in_through === args.out_through) {
|
||||
return cbk([400, 'ExpectedInPeerNotEqualToOutPeer']);
|
||||
}
|
||||
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedLndToExecuteRebalance']);
|
||||
}
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedLndToExecuteRebalance']);
|
||||
}
|
||||
|
||||
if (args.max_fee === 0) {
|
||||
return cbk([400, 'ExpectedNonZeroMaxFeeForRebalance']);
|
||||
}
|
||||
if (args.max_fee === 0) {
|
||||
return cbk([400, 'ExpectedNonZeroMaxFeeForRebalance']);
|
||||
}
|
||||
|
||||
if (args.max_fee_rate === 0) {
|
||||
return cbk([400, 'ExpectedNonZeroMaxFeeRateForRebalance']);
|
||||
}
|
||||
if (args.max_fee_rate === 0) {
|
||||
return cbk([400, 'ExpectedNonZeroMaxFeeRateForRebalance']);
|
||||
}
|
||||
|
||||
if (!!args.out_through && args.in_through === args.out_through) {
|
||||
return cbk([400, 'ExpectedOutPeerNotEqualToInPeer']);
|
||||
}
|
||||
if (!!args.out_through && args.in_through === args.out_through) {
|
||||
return cbk([400, 'ExpectedOutPeerNotEqualToInPeer']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Lnd by itself
|
||||
lnd: ['validate', ({}, cbk) => cbk(null, args.lnd)],
|
||||
|
||||
// Get initial liquidity
|
||||
getInitialLiquidity: ['lnd', ({lnd}, cbk) => getChannels({lnd}, cbk)],
|
||||
|
||||
// Get public key
|
||||
getPublicKey: ['lnd', ({lnd}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Get fee rates
|
||||
getFees: ['getPublicKey', 'lnd', ({getPublicKey, lnd}, cbk) => {
|
||||
return getNode({lnd, public_key: getPublicKey.public_key}, cbk);
|
||||
}],
|
||||
|
||||
// Get outbound node details
|
||||
getOutbound: [
|
||||
'getInitialLiquidity',
|
||||
'lnd',
|
||||
({getInitialLiquidity, lnd}, cbk) =>
|
||||
{
|
||||
const ignore = args.avoid || [];
|
||||
|
||||
const active = getInitialLiquidity.channels
|
||||
.filter(n => !!n.is_active)
|
||||
.filter(n => ignore.indexOf(n.partner_public_key) === notFoundIndex);
|
||||
|
||||
const channels = active
|
||||
.map(channel => {
|
||||
const remote = active
|
||||
.filter(n => n.partner_public_key === channel.partner_public_key)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
return {remote, partner_public_key: channel.partner_public_key};
|
||||
})
|
||||
.filter(n => n.remote < minRemoteBalance);
|
||||
|
||||
if (!args.out_through && !channels.length) {
|
||||
return cbk([400, 'NoOutboundChannelNeedsARebalance']);
|
||||
}
|
||||
|
||||
const {sorted} = sortBy({array: channels, attribute: 'remote'});
|
||||
|
||||
const key = args.out_through || sample(sorted).partner_public_key;
|
||||
|
||||
return getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: key,
|
||||
return cbk();
|
||||
},
|
||||
(err, res) => {
|
||||
return cbk(null, {
|
||||
alias: !!res && !!res.alias ? `${res.alias} ${key}` : key,
|
||||
|
||||
// Lnd by itself
|
||||
lnd: ['validate', ({}, cbk) => cbk(null, args.lnd)],
|
||||
|
||||
// Get initial liquidity
|
||||
getInitialLiquidity: ['lnd', ({lnd}, cbk) => getChannels({lnd}, cbk)],
|
||||
|
||||
// Get public key
|
||||
getPublicKey: ['lnd', ({lnd}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Get fee rates
|
||||
getFees: ['getPublicKey', 'lnd', ({getPublicKey, lnd}, cbk) => {
|
||||
return getNode({lnd, public_key: getPublicKey.public_key}, cbk);
|
||||
}],
|
||||
|
||||
// Get outbound node details
|
||||
getOutbound: [
|
||||
'getInitialLiquidity',
|
||||
'lnd',
|
||||
({getInitialLiquidity, lnd}, cbk) =>
|
||||
{
|
||||
const ignore = args.avoid || [];
|
||||
|
||||
const active = getInitialLiquidity.channels
|
||||
.filter(n => !!n.is_active)
|
||||
.filter(n => ignore.indexOf(n.partner_public_key) === notFoundIndex);
|
||||
|
||||
const channels = active
|
||||
.map(channel => {
|
||||
const remote = active
|
||||
.filter(n => n.partner_public_key === channel.partner_public_key)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
return {remote, partner_public_key: channel.partner_public_key};
|
||||
})
|
||||
.filter(n => n.remote < minRemoteBalance);
|
||||
|
||||
if (!args.out_through && !channels.length) {
|
||||
return cbk([400, 'NoOutboundChannelNeedsARebalance']);
|
||||
}
|
||||
|
||||
const {sorted} = sortBy({array: channels, attribute: 'remote'});
|
||||
|
||||
const key = args.out_through || sample(sorted).partner_public_key;
|
||||
|
||||
return getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: key,
|
||||
},
|
||||
(err, res) => {
|
||||
return cbk(null, {
|
||||
alias: !!res && !!res.alias ? `${res.alias} ${key}` : key,
|
||||
public_key: key,
|
||||
});
|
||||
});
|
||||
});
|
||||
}],
|
||||
}],
|
||||
|
||||
// Get inbound node details
|
||||
getInbound: [
|
||||
'getFees',
|
||||
'getInitialLiquidity',
|
||||
'getOutbound',
|
||||
'lnd',
|
||||
({getFees, getInitialLiquidity, getOutbound, lnd}, cbk) =>
|
||||
{
|
||||
const ignore = args.avoid || [];
|
||||
// Get inbound node details
|
||||
getInbound: [
|
||||
'getFees',
|
||||
'getInitialLiquidity',
|
||||
'getOutbound',
|
||||
'lnd',
|
||||
({getFees, getInitialLiquidity, getOutbound, lnd}, cbk) =>
|
||||
{
|
||||
const hasInThrough = !!args.in_through;
|
||||
const ignore = args.avoid || [];
|
||||
|
||||
const activeChannels = getInitialLiquidity.channels
|
||||
.filter(n => !!n.is_active)
|
||||
.filter(n => n.partner_public_key !== getOutbound.public_key)
|
||||
.filter(n => ignore.indexOf(n.partner_public_key) === notFoundIndex);
|
||||
const activeChannels = getInitialLiquidity.channels
|
||||
.filter(n => !!n.is_active)
|
||||
.filter(n => n.partner_public_key !== getOutbound.public_key)
|
||||
.filter(n => ignore.indexOf(n.partner_public_key) === notFoundIndex);
|
||||
|
||||
const channels = activeChannels
|
||||
.filter(n => !!args.in_through || n.remote_balance > minInboundBalance)
|
||||
.map(channel => {
|
||||
const remote = activeChannels
|
||||
.filter(n => n.partner_public_key === channel.partner_public_key)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
const channels = activeChannels
|
||||
.filter(n => hasInThrough || n.remote_balance > minInboundBalance)
|
||||
.map(channel => {
|
||||
const remote = activeChannels
|
||||
.filter(n => n.partner_public_key === channel.partner_public_key)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
return {
|
||||
remote,
|
||||
id: channel.id,
|
||||
partner_public_key: channel.partner_public_key,
|
||||
};
|
||||
return {
|
||||
remote,
|
||||
id: channel.id,
|
||||
partner_public_key: channel.partner_public_key,
|
||||
};
|
||||
});
|
||||
|
||||
if (!channels.length) {
|
||||
return cbk([400, 'NoInboundChannelIsAvailableToReceiveRebalance']);
|
||||
}
|
||||
|
||||
const array = channels.filter(chan => {
|
||||
const peerKey = chan.partner_public_key;
|
||||
|
||||
const policies = getFees.channels.map(({policies}) => {
|
||||
return policies.find(n => n.public_key === peerKey);
|
||||
});
|
||||
|
||||
const feeRates = policies.filter(n => !!n).map(n => n.fee_rate);
|
||||
|
||||
const feeRate = max(...feeRates);
|
||||
|
||||
return feeRate < (args.max_fee_rate || defaultMaxFeeRate);
|
||||
});
|
||||
|
||||
if (!channels.length) {
|
||||
return cbk([400, 'NoInboundChannelIsAvailableToReceiveRebalance']);
|
||||
}
|
||||
if (!array.length) {
|
||||
return cbk([400, 'NoLowFeeInboundChannelToReceiveRebalance']);
|
||||
}
|
||||
|
||||
const array = channels.filter(chan => {
|
||||
const policies = getFees.channels.map(({policies}) => {
|
||||
return policies.find(n => n.public_key === chan.partner_public_key);
|
||||
});
|
||||
const {sorted} = sortBy({array, attribute: 'remote'});
|
||||
|
||||
const feeRate = max(...policies.filter(n => !!n).map(n => n.fee_rate));
|
||||
const suggestedInbound = sample(topOf(sorted.slice().reverse()));
|
||||
|
||||
return feeRate < (args.max_fee_rate || defaultMaxFeeRate);
|
||||
});
|
||||
const key = args.in_through || suggestedInbound.partner_public_key;
|
||||
|
||||
if (!array.length) {
|
||||
return cbk([400, 'NoLowFeeInboundChannelToReceiveRebalance']);
|
||||
}
|
||||
|
||||
const {sorted} = sortBy({array, attribute: 'remote'});
|
||||
|
||||
const suggestedInbound = sample(topOf(sorted.slice().reverse()));
|
||||
|
||||
const key = args.in_through || suggestedInbound.partner_public_key;
|
||||
|
||||
return getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: key,
|
||||
},
|
||||
(err, res) => {
|
||||
return cbk(null, {
|
||||
alias: !!res && !!res.alias ? `${res.alias} ${key}` : key,
|
||||
return getNode({
|
||||
lnd,
|
||||
is_omitting_channels: true,
|
||||
public_key: key,
|
||||
},
|
||||
(err, res) => {
|
||||
return cbk(null, {
|
||||
alias: !!res && !!res.alias ? `${res.alias} ${key}` : key,
|
||||
public_key: key,
|
||||
});
|
||||
});
|
||||
});
|
||||
}],
|
||||
}],
|
||||
|
||||
// Find a route to the destination
|
||||
findRoute: [
|
||||
'getInbound',
|
||||
'getOutbound',
|
||||
'getPublicKey',
|
||||
({getInbound, getOutbound, getPublicKey}, cbk) =>
|
||||
{
|
||||
const avoid = (args.avoid || []).map(n => ({from_public_key: n}));
|
||||
// Find a route to the destination
|
||||
findRoute: [
|
||||
'getInbound',
|
||||
'getOutbound',
|
||||
'getPublicKey',
|
||||
({getInbound, getOutbound, getPublicKey}, cbk) =>
|
||||
{
|
||||
const avoid = (args.avoid || []).map(n => ({from_public_key: n}));
|
||||
|
||||
return probeDestination({
|
||||
destination: getPublicKey.public_key,
|
||||
find_max: 5e6,
|
||||
ignore: [{from_public_key: getPublicKey.public_key}].concat(avoid),
|
||||
in_through: getInbound.public_key,
|
||||
logger: args.logger,
|
||||
lnd: args.lnd,
|
||||
max_fee: Math.floor(5e6 * 0.0025),
|
||||
node: args.node,
|
||||
out_through: getOutbound.public_key,
|
||||
tokens: 10000,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
return probeDestination({
|
||||
destination: getPublicKey.public_key,
|
||||
find_max: 5e6,
|
||||
ignore: [{from_public_key: getPublicKey.public_key}].concat(avoid),
|
||||
in_through: getInbound.public_key,
|
||||
logger: args.logger,
|
||||
lnd: args.lnd,
|
||||
max_fee: Math.floor(5e6 * 0.0025),
|
||||
node: args.node,
|
||||
out_through: getOutbound.public_key,
|
||||
tokens: 10000,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get channels for the rebalance route
|
||||
channels: [
|
||||
'findRoute',
|
||||
'getInitialLiquidity',
|
||||
'getPublicKey',
|
||||
'lnd',
|
||||
({findRoute, getPublicKey, lnd}, cbk) =>
|
||||
{
|
||||
if (!findRoute.success) {
|
||||
return cbk([400, 'FailedToFindPathBetweenPeers']);
|
||||
}
|
||||
// Get channels for the rebalance route
|
||||
channels: [
|
||||
'findRoute',
|
||||
'getInitialLiquidity',
|
||||
'getPublicKey',
|
||||
'lnd',
|
||||
({findRoute, getPublicKey, lnd}, cbk) =>
|
||||
{
|
||||
if (!findRoute.success) {
|
||||
return cbk([400, 'FailedToFindPathBetweenPeers']);
|
||||
}
|
||||
|
||||
let from = getPublicKey.public_key;
|
||||
let from = getPublicKey.public_key;
|
||||
|
||||
return asyncMapSeries(findRoute.success, (id, cbk) => {
|
||||
return getChannel({id, lnd}, (err, channel) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
return asyncMapSeries(findRoute.success, (id, cbk) => {
|
||||
return getChannel({id, lnd}, (err, channel) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
const {capacity} = channel;
|
||||
const {policies} = channel;
|
||||
|
||||
const to = policies.find(n => n.public_key !== from).public_key;
|
||||
|
||||
// The next hop from will be this hop's to
|
||||
from = to;
|
||||
|
||||
return cbk(null, {capacity, id, policies, destination: to});
|
||||
});
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Create local invoice
|
||||
invoice: ['channels', 'findRoute', 'lnd', ({findRoute, lnd}, cbk) => {
|
||||
return createInvoice({
|
||||
lnd,
|
||||
cltv_delta: cltvDelta,
|
||||
description: 'Rebalance',
|
||||
tokens: min(maxRebalanceTokens, findRoute.route_maximum),
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get the current height
|
||||
getHeight: ['channels', 'lnd', ({lnd}, cbk) => {
|
||||
return getWalletInfo({lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Calculate route for rebalance
|
||||
routes: [
|
||||
'channels',
|
||||
'getHeight',
|
||||
'getPublicKey',
|
||||
'invoice',
|
||||
({channels, getHeight, getPublicKey, invoice}, cbk) =>
|
||||
{
|
||||
try {
|
||||
const {route} = routeFromChannels({
|
||||
channels,
|
||||
cltv_delta: cltvDelta,
|
||||
destination: getPublicKey.public_key,
|
||||
height: getHeight.current_block_height,
|
||||
mtokens: (BigInt(invoice.tokens) * mtokensPerToken).toString(),
|
||||
});
|
||||
|
||||
const maxFee = args.max_fee || defaultMaxFee;
|
||||
const maxFeeRate = args.max_fee_rate || defaultMaxFeeRate;
|
||||
|
||||
// Exit early when a max fee is specified and exceeded
|
||||
if (!!maxFee && route.fee > maxFee) {
|
||||
return cbk([
|
||||
400,
|
||||
'RebalanceFeeTooHigh',
|
||||
{needed_max_fee: route.fee},
|
||||
]);
|
||||
}
|
||||
|
||||
const {capacity} = channel;
|
||||
const {policies} = channel;
|
||||
const feeRate = ceil(route.fee / route.tokens * rateDivisor);
|
||||
|
||||
const to = policies.find(n => n.public_key !== from).public_key;
|
||||
// Exit early when the max fee rate is specified and exceeded
|
||||
if (!!maxFeeRate && feeRate > maxFeeRate) {
|
||||
return cbk([
|
||||
400,
|
||||
'RebalanceFeeTooHigh',
|
||||
{needed_max_fee_rate: feeRate},
|
||||
]);
|
||||
}
|
||||
|
||||
// The next hop from will be this hop's to
|
||||
from = to;
|
||||
return cbk(null, [route]);
|
||||
} catch (err) {
|
||||
return cbk([500, 'FailedToConstructRebalanceRoute', {err}]);
|
||||
}
|
||||
}],
|
||||
|
||||
return cbk(null, {capacity, id, policies, destination: to});
|
||||
});
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
// Execute the rebalance
|
||||
pay: ['invoice', 'lnd', 'routes', ({invoice, lnd, routes}, cbk) => {
|
||||
return payViaRoutes({lnd, routes, id: invoice.id}, cbk);
|
||||
}],
|
||||
|
||||
// Create local invoice
|
||||
invoice: ['channels', 'findRoute', 'lnd', ({findRoute, lnd}, cbk) => {
|
||||
return createInvoice({
|
||||
lnd,
|
||||
cltv_delta: cltvDelta,
|
||||
description: 'Rebalance',
|
||||
tokens: min(maxRebalanceTokens, findRoute.route_maximum),
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
// Final rebalancing
|
||||
rebalance: [
|
||||
'getInbound',
|
||||
'getInitialLiquidity',
|
||||
'getOutbound',
|
||||
'pay',
|
||||
({getInbound, getInitialLiquidity, getOutbound, pay}, cbk) =>
|
||||
{
|
||||
const inPeerInbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getInbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
// Get the current height
|
||||
getHeight: ['channels', 'lnd', ({lnd}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
const inPeerOutbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getInbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.local_balance, minTokens);
|
||||
|
||||
// Calculate route for rebalance
|
||||
routes: [
|
||||
'channels',
|
||||
'getHeight',
|
||||
'getPublicKey',
|
||||
'invoice',
|
||||
({channels, getHeight, getPublicKey, invoice}, cbk) =>
|
||||
{
|
||||
try {
|
||||
const {route} = routeFromChannels({
|
||||
channels,
|
||||
cltv_delta: cltvDelta,
|
||||
destination: getPublicKey.public_key,
|
||||
height: getHeight.current_block_height,
|
||||
mtokens: (BigInt(invoice.tokens) * mtokensPerToken).toString(),
|
||||
const outPeerInbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getOutbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
const outPeerOutbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getOutbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.local_balance, minTokens);
|
||||
|
||||
args.logger.info({
|
||||
rebalance: [
|
||||
{
|
||||
increased_inbound_on: getOutbound.alias,
|
||||
liquidity_inbound: tokAsBigTok(outPeerInbound + pay.tokens),
|
||||
liquidity_outbound: tokAsBigTok(outPeerOutbound - pay.tokens),
|
||||
},
|
||||
{
|
||||
decreased_inbound_on: getInbound.alias,
|
||||
liquidity_inbound: tokAsBigTok(inPeerInbound - pay.tokens),
|
||||
liquidity_outbound: tokAsBigTok(inPeerOutbound + pay.tokens),
|
||||
},
|
||||
{
|
||||
rebalanced: tokAsBigTok(pay.tokens),
|
||||
rebalance_fees_spent: tokAsBigTok(pay.fee),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const maxFee = args.max_fee || defaultMaxFee;
|
||||
const maxFeeRate = args.max_fee_rate || defaultMaxFeeRate;
|
||||
|
||||
// Exit early when a max fee is specified and exceeded
|
||||
if (!!maxFee && route.fee > maxFee) {
|
||||
return cbk([
|
||||
400,
|
||||
'RebalanceFeeTooHigh',
|
||||
{needed_max_fee: route.fee},
|
||||
]);
|
||||
}
|
||||
|
||||
const feeRate = ceil(route.fee / route.tokens * rateDivisor);
|
||||
|
||||
// Exit early when the max fee rate is specified and exceeded
|
||||
if (!!maxFeeRate && feeRate > maxFeeRate) {
|
||||
return cbk([
|
||||
400,
|
||||
'RebalanceFeeTooHigh',
|
||||
{needed_max_fee_rate: feeRate},
|
||||
]);
|
||||
}
|
||||
|
||||
return cbk(null, [route]);
|
||||
} catch (err) {
|
||||
return cbk([500, 'FailedToConstructRebalanceRoute', {err}]);
|
||||
}
|
||||
}],
|
||||
|
||||
// Execute the rebalance
|
||||
pay: ['invoice', 'lnd', 'routes', ({invoice, lnd, routes}, cbk) => {
|
||||
return payViaRoutes({lnd, routes, id: invoice.id}, cbk);
|
||||
}],
|
||||
|
||||
// Final rebalancing
|
||||
rebalance: [
|
||||
'getInbound',
|
||||
'getInitialLiquidity',
|
||||
'getOutbound',
|
||||
'pay',
|
||||
({getInbound, getInitialLiquidity, getOutbound, pay}, cbk) =>
|
||||
{
|
||||
const inPeerInbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getInbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
const inPeerOutbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getInbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.local_balance, minTokens);
|
||||
|
||||
const outPeerInbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getOutbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.remote_balance, minTokens);
|
||||
|
||||
const outPeerOutbound = getInitialLiquidity.channels
|
||||
.filter(n => n.partner_public_key === getOutbound.public_key)
|
||||
.filter(n => !!n.is_active)
|
||||
.reduce((sum, n) => sum + n.local_balance, minTokens);
|
||||
|
||||
return cbk(null, {
|
||||
rebalanced_liquidity: [
|
||||
{
|
||||
spent_out: getOutbound.alias,
|
||||
liquidity_inbound: outPeerInbound + pay.tokens,
|
||||
liquidity_outbound: outPeerOutbound - pay.tokens,
|
||||
},
|
||||
{
|
||||
received_in: getInbound.alias,
|
||||
liquidity_inbound: inPeerInbound - pay.tokens,
|
||||
liquidity_outbound: inPeerOutbound + pay.tokens,
|
||||
},
|
||||
],
|
||||
rebalanced: pay.tokens,
|
||||
rebalance_fee_paid: pay.fee,
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({of: 'rebalance'}, cbk));
|
||||
return cbk();
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -349,10 +349,12 @@ module.exports = (args, cbk) => {
|
|||
|
||||
const swapDelayMin = !args.is_fast ? slowDelayMinutes : fastDelayMinutes;
|
||||
|
||||
const fundAt = moment().add(swapDelayMin, 'minutes');
|
||||
|
||||
return createSwapOut({
|
||||
network,
|
||||
service,
|
||||
fund_at: moment().add(swapDelayMin, 'minutes').toISOString(),
|
||||
fund_at: fundAt.toISOString(),
|
||||
tokens: args.tokens,
|
||||
},
|
||||
cbk);
|
||||
|
|
@ -664,6 +666,8 @@ module.exports = (args, cbk) => {
|
|||
return cbk();
|
||||
}
|
||||
|
||||
args.logger.info({funding_swap: decodeFundingRequest.id});
|
||||
|
||||
return payViaRoutes({
|
||||
id: decodeFundingRequest.id,
|
||||
lnd: args.lnd,
|
||||
|
|
@ -696,7 +700,14 @@ module.exports = (args, cbk) => {
|
|||
return cbk();
|
||||
}
|
||||
|
||||
args.logger.info({paying_execution_request: decodeExecutionRequest.id});
|
||||
const swapDelayMin = !args.is_fast ? slowDelayMinutes : fastDelayMinutes;
|
||||
|
||||
const fundAt = moment().add(swapDelayMin, 'minutes');
|
||||
|
||||
args.logger.info({
|
||||
paying_execution_request: decodeExecutionRequest.id,
|
||||
estimated_swap_start_time: fundAt.calendar(),
|
||||
});
|
||||
|
||||
const sub = subscribeToPayViaRequest({
|
||||
lnd: args.lnd,
|
||||
|
|
|
|||
57
test/chain/test_get_chain_fees.js
Normal file
57
test/chain/test_get_chain_fees.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const {getChainFees} = require('./../../chain');
|
||||
const {getInfoResponse} = require('./../network/fixtures');
|
||||
|
||||
const getInfoRes = () => JSON.parse(JSON.stringify(getInfoResponse));
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {},
|
||||
description: 'LND is required',
|
||||
error: [400, 'ExpectedLndToGetChainFees'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
lnd: {
|
||||
default: {getInfo: ({}, cbk) => cbk(null, getInfoRes())},
|
||||
wallet: {estimateFee: (args, cbk) => cbk('err')},
|
||||
},
|
||||
},
|
||||
description: 'Errors from get chain fee are passed back',
|
||||
error: [503, 'UnexpectedErrorGettingFeeFromLnd', {err: 'err'}],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
lnd: {
|
||||
default: {getInfo: ({}, cbk) => cbk(null, getInfoRes())},
|
||||
wallet: {
|
||||
estimateFee: (args, cbk) => {
|
||||
return cbk(null, {
|
||||
sat_per_kw: 1 + Math.round(1 / args.conf_target),
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description: 'Fees are mapped to levels',
|
||||
expected: {
|
||||
current_block_hash: '00',
|
||||
fee_by_block_target: {'2': 8, '3': 4},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, async ({deepIs, end, rejects}) => {
|
||||
if (!!error) {
|
||||
rejects(getChainFees(args), error, 'Got expected error');
|
||||
} else {
|
||||
const fees = await getChainFees(args);
|
||||
|
||||
deepIs(fees, expected, 'Got expected fees rundown');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const {getInboundPath} = require('./../../routing');
|
||||
const {getInfoResponse} = require('./../network/fixtures');
|
||||
|
||||
const getInfoRes = () => JSON.parse(JSON.stringify(getInfoResponse));
|
||||
|
||||
const makeLnd = ({channels}) => {
|
||||
return {
|
||||
|
|
@ -8,6 +11,7 @@ const makeLnd = ({channels}) => {
|
|||
getChanInfo: (args, cbk) => {
|
||||
return cbk(null, channels.find(n => n.channel_id === args.chan_id));
|
||||
},
|
||||
getInfo: ({}, cbk) => cbk(null, getInfoRes()),
|
||||
getNodeInfo: ({}, cbk) => {
|
||||
return cbk(null, {
|
||||
channels: channels || [],
|
||||
|
|
@ -24,6 +28,7 @@ const makeLnd = ({channels}) => {
|
|||
total_capacity: '0',
|
||||
});
|
||||
},
|
||||
listChannels: ({}, cbk) => cbk(null, {channels: []}),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue