Skip to content

Commit

Permalink
add tag support to rebalance in and out
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbosworth committed Dec 18, 2020
1 parent 379bf77 commit 202c55c
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Versions

## Version 7.13.0

- `rebalance`: Add support for specifying a tag group to rebalance `--in` or `--out`

## Version 7.12.0

- `remove-peer`: Fix error running command
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
"test": "tap -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/fiat/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/wallets/*.js"
},
"version": "7.12.0"
"version": "7.13.0"
}
67 changes: 67 additions & 0 deletions peers/find_tag_match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const {shuffle} = require('./../arrays');

/** Find a node for a tag query
{
channels: [{
partner_public_key: <Peer Public Key Hex String>
}]
query: <Query String>
tags: [{
[alias]: <Tag Alias String>
id: <Tag Id Hex String>
[nodes]: [<Public Key Hex String>]
}]
}
@returns
{
[match]: <Matching Node Public Key Hex String>
[matches]: [{
[alias]: <Tag Alias String>
id: <Tag Id Hex String>
[nodes]: [<Public Key Hex String>]
}]
}
*/
module.exports = ({channels, tags, query}) => {
const peerKeys = channels.map(n => n.partner_public_key);

// Find tags that match on id or on alias, and also have relevant nodes
const matches = tags.filter(tag => {
const nodes = (tag.nodes || []).filter(n => peerKeys.includes(n));

if (!nodes.length) {
return false;
}

const alias = tag.alias || String();

const isAliasMatch = alias.toLowerCase().includes(query);
const isIdMatch = tag.id.startsWith(query);

return isAliasMatch || isIdMatch;
});

const [tagMatch, ...otherTagMatches] = matches;

// Exit early when there are no matches at all
if (!tagMatch) {
return {};
}

// Exit early when there is ambiguity around the matching
if (!!otherTagMatches.length) {
return {matches};
}

// Get the array of nodes in the tag match
const array = tagMatch.nodes.filter(n => peerKeys.includes(n));

// Shuffle the results
const {shuffled} = shuffle({array});

const [match] = shuffled;

return {match};
};
3 changes: 2 additions & 1 deletion peers/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const findTagMatch = require('./find_tag_match');
const rejectInboundChannels = require('./reject_inbound_channels');

module.exports = {rejectInboundChannels};
module.exports = {findTagMatch, rejectInboundChannels};
57 changes: 46 additions & 11 deletions swaps/rebalance.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ const {payViaRoutes} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const {routeFromChannels} = require('ln-service');

const {findTagMatch} = require('./../peers');
const {parseAmount} = require('./../display');
const {probeDestination} = require('./../network');
const {shuffle} = require('./../arrays');
const {sortBy} = require('./../arrays');

const {ceil} = Math;
Expand Down Expand Up @@ -78,7 +80,7 @@ const uniq = arr => Array.from(new Set(arr));
[node]: <Node Name String>
[out_channels]: [<Exclusively Rebalance Through Channel Ids String>]
[out_inbound]: <Outbound Target Inbound Liquidity Tokens Number>
out_through: <Pay Out Through Peer String>
[out_through]: <Pay Out Through Peer String>
[timeout_minutes]: <Deadline To Stop Rebalance Minutes Number>
}
Expand Down Expand Up @@ -160,21 +162,24 @@ module.exports = (args, cbk) => {
// Lnd by itself
lnd: ['validate', ({}, cbk) => cbk(null, args.lnd)],

// Get global avoids
getAvoids: ['validate', ({}, cbk) => {
// Get the set of tags
getTags: ['validate', ({}, cbk) => {
const defaultTags = {avoids: [], tags: []};

return args.fs.getFile(tagFilePath(), (err, res) => {
if (!!err || !res) {
return cbk(null, []);
return cbk(null, defaultTags);
}

try {
const {tags} = parse(res.toString());

// Find global avoids in tags
const avoids = tags.filter(n => !!n.is_avoided).map(n => n.nodes);

return cbk(null, flatten(avoids));
return cbk(null, {tags, avoids: flatten(avoids)});
} catch (err) {
return cbk(null, []);
return cbk(null, defaultTags);
}
});
}],
Expand All @@ -187,13 +192,13 @@ module.exports = (args, cbk) => {

// Figure out which public keys and channels to avoid
ignore: [
'getAvoids',
'getInitialLiquidity',
'getPublicKey',
'getTags',
'lnd',
({getAvoids, getInitialLiquidity, getPublicKey, lnd}, cbk) =>
({getInitialLiquidity, getPublicKey, getTags, lnd}, cbk) =>
{
const avoids = [].concat(args.avoid).concat(getAvoids)
const avoids = [].concat(args.avoid).concat(getTags.avoids)
.filter(n => n !== getPublicKey.public_key);

return asyncMap(avoids, (id, cbk) => {
Expand Down Expand Up @@ -267,9 +272,24 @@ module.exports = (args, cbk) => {
// Find inbound peer key if a name is specified
findInKey: [
'getInitialLiquidity',
'getTags',
'lnd',
({getInitialLiquidity, lnd}, cbk) =>
({getInitialLiquidity, getTags, lnd}, cbk) =>
{
const {match, matches} = findTagMatch({
channels: getInitialLiquidity.channels.filter(n => n.is_active),
tags: getTags.tags,
query: args.in_through,
});

if (!!matches) {
return cbk([400, 'MultipleTagMatchesFoundForInPeer', {matches}]);
}

if (match) {
return cbk(null, match);
}

return findKey({
lnd,
channels: getInitialLiquidity.channels,
Expand All @@ -287,9 +307,24 @@ module.exports = (args, cbk) => {
// Find outbound peer key if a name is specified
findOutKey: [
'getInitialLiquidity',
'getTags',
'lnd',
({getInitialLiquidity, lnd}, cbk) =>
({getInitialLiquidity, getTags, lnd}, cbk) =>
{
const {match, matches} = findTagMatch({
channels: getInitialLiquidity.channels.filter(n => n.is_active),
tags: getTags.tags,
query: args.out_through,
});

if (!!matches) {
return cbk([400, 'MultipleTagMatchesFoundForOutPeer', {matches}]);
}

if (match) {
return cbk(null, match);
}

return findKey({
lnd,
channels: getInitialLiquidity.channels,
Expand Down
83 changes: 83 additions & 0 deletions test/peers/test_find_tag_match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const {test} = require('tap');

const {findTagMatch} = require('./../../peers');

const makeArgs = overrides => {
const args = {
channels: [{partner_public_key: Buffer.alloc(33, 3).toString('hex')}],
query: '0000',
tags: [{
id: Buffer.alloc(32).toString('hex'),
nodes: [Buffer.alloc(33, 3).toString('hex')],
}],
};

Object.keys(overrides).forEach(k => args[k] = overrides[k]);

return args;
};

const tests = [
{
args: makeArgs({}),
description: 'A tagged node is found',
expected: {match: Buffer.alloc(33, 3).toString('hex')},
},
{
args: makeArgs({
tags: [{id: Buffer.alloc(32).toString('hex')}],
}),
description: 'No tagged node is found',
expected: {},
},
{
args: makeArgs({
channels: [
{partner_public_key: Buffer.alloc(33, 2).toString('hex')},
{partner_public_key: Buffer.alloc(33, 3).toString('hex')},
],
query: 'alias',
tags: [
{
alias: 'alias1',
id: Buffer.alloc(32).toString('hex'),
nodes: [Buffer.alloc(33, 2).toString('hex')],
},
{
alias: 'alias2',
id: Buffer.alloc(32, 1).toString('hex'),
nodes: [Buffer.alloc(33, 3).toString('hex')],
},
],
}),
description: 'Multiple tags are found',
expected: {
matches: [
{
alias: 'alias1',
id: Buffer.alloc(32).toString('hex'),
nodes: [Buffer.alloc(33, 2).toString('hex')],
},
{
alias: 'alias2',
id: Buffer.alloc(32, 1).toString('hex'),
nodes: [Buffer.alloc(33, 3).toString('hex')],
},
],
},
},
];

tests.forEach(({args, description, error, expected}) => {
return test(description, ({deepIs, end, equal, throws}) => {
if (!!error) {
throws(() => findTagMatch(args), new Error(error), 'Got error');
} else {
const res = findTagMatch(args);

deepIs(res, expected, 'Got expected rule violation');
}

return end();
});
});

0 comments on commit 202c55c

Please sign in to comment.