mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
add filters and dryrun to advertise
This commit is contained in:
parent
53cd8e8157
commit
e75dee4220
11 changed files with 850 additions and 2139 deletions
|
|
@ -1,5 +1,11 @@
|
|||
# Versions
|
||||
|
||||
## 11.11.0
|
||||
|
||||
- `advertise`: Add `--dryrun` to skip actually sending ads
|
||||
- `advertise`: Add `--filter` to target nodes with specified capacities, channel counts
|
||||
- `swap-in`: Fix command to execute when there is a liquidity lookup needed
|
||||
|
||||
## 11.10.0
|
||||
|
||||
- `find`: Add estimated disk usage to channels with a peer
|
||||
|
|
|
|||
34
bos
34
bos
|
|
@ -83,7 +83,7 @@ prog
|
|||
month: options.month,
|
||||
node: options.node,
|
||||
rate_provider: options.rateProvider,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
year: options.year,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve, table}));
|
||||
|
|
@ -95,6 +95,11 @@ prog
|
|||
|
||||
// Advertise to other nodes on the network
|
||||
.command('advertise', 'Broadcast advertisement')
|
||||
.help('use --filter conditions to limit broadcast scope: capacity > 1*m')
|
||||
.help('--filter variables: CAPACITY/CHANNELS_COUNT/K/M')
|
||||
.help('Default filter scope: channels_count > 9')
|
||||
.option('--dryrun', 'Avoid actually sending advertisements')
|
||||
.option('--filter <expression>', 'Require node match condition', REPEATABLE)
|
||||
.option('--message <message>', 'Custom advertisement message')
|
||||
.option('--node <node_name>', 'Advertise via saved node')
|
||||
.action((args, options, logger) => {
|
||||
|
|
@ -102,6 +107,8 @@ prog
|
|||
try {
|
||||
return await services.advertise({
|
||||
logger,
|
||||
filters: flatten([options.filter].filter(n => !!n)),
|
||||
is_dry_run: !!options.dryrun,
|
||||
lnd: (await lndForNode(logger, options.node)).lnd,
|
||||
message: options.message,
|
||||
});
|
||||
|
|
@ -131,7 +138,7 @@ prog
|
|||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
mirrors: flatten([options.mirror].filter(n => !!n)),
|
||||
node: options.node,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
urls: flatten([options.url].filter(n => !!n)),
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
|
|
@ -303,7 +310,7 @@ prog
|
|||
days: options.days,
|
||||
is_monochrome: !!options.noColor,
|
||||
lnds: (await lnd.getLnds({logger, nodes: options.node})).lnds,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
},
|
||||
responses.returnChart({logger, reject, resolve, data: 'data'}));
|
||||
} catch (err) {
|
||||
|
|
@ -427,7 +434,7 @@ prog
|
|||
return chain.getChannelCloses({
|
||||
limit: options.limit,
|
||||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
} catch (err) {
|
||||
|
|
@ -783,7 +790,7 @@ prog
|
|||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
max_fee_rate: options.maxFeeRate || undefined,
|
||||
min_node_score: options.minScore || undefined,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
with: options.with,
|
||||
},
|
||||
responses.returnNumber({logger, reject, resolve, number: 'balance'}));
|
||||
|
|
@ -871,7 +878,7 @@ prog
|
|||
is_private: options.private,
|
||||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
peer: options.with || undefined,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
tokens: options.amount,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
|
|
@ -976,7 +983,7 @@ prog
|
|||
is_external: options.externalFunding,
|
||||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
public_keys: args.peerPublicKeys,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
set_fee_rates: flatten([options.setFeeRate]).filter(n => !!n),
|
||||
types: flatten([options.type].filter(n => !!n)),
|
||||
},
|
||||
|
|
@ -1139,7 +1146,7 @@ prog
|
|||
return new Promise((resolve, reject) => {
|
||||
return fiat.getPrices({
|
||||
from: options.from,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
symbols: args.symbols.map(n => n.toUpperCase()),
|
||||
},
|
||||
responses.returnObject({
|
||||
|
|
@ -1277,7 +1284,7 @@ prog
|
|||
return chain.recoverP2pk({
|
||||
id: args.id,
|
||||
lnd: (await lndForNode(logger, options.node)).lnd,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
vout: args.vout,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
|
|
@ -1328,7 +1335,7 @@ prog
|
|||
outbound_liquidity_below: options.outboundBelow,
|
||||
outpoints: flatten([options.outpoint].filter(n => !!n)),
|
||||
public_key: args.publicKey,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
} catch (err) {
|
||||
|
|
@ -1346,7 +1353,7 @@ prog
|
|||
return wallets.getReport({
|
||||
fs: {getFile: readFile},
|
||||
node: options.node,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
style: !!options.styled ? 'styled' : undefined,
|
||||
},
|
||||
responses.returnOutput({logger, reject, resolve}));
|
||||
|
|
@ -1391,7 +1398,7 @@ prog
|
|||
message: options.message,
|
||||
quiz_answers: flatten([options.quiz].filter(n => !!n)),
|
||||
out_through: options.out,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
} catch (err) {
|
||||
|
|
@ -1501,6 +1508,7 @@ prog
|
|||
fetch,
|
||||
logger,
|
||||
api_key: options.apiKey || undefined,
|
||||
fs: {getFile: readFile},
|
||||
in_through: options.in || undefined,
|
||||
is_refund_test: options.testRefund,
|
||||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
|
|
@ -1508,7 +1516,7 @@ prog
|
|||
node: options.node,
|
||||
recovery: options.recovery,
|
||||
refund_address: options.refundAddress,
|
||||
request: commands.fetchRequest({fetch}),
|
||||
request: commands.simpleRequest,
|
||||
socket: options.serviceSocket || undefined,
|
||||
tokens: args.amount,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ module.exports = ({id, interval, network, request, retries}, cbk) => {
|
|||
return cbk([503, 'FailedToGetRawTransaction', {err}]);
|
||||
}
|
||||
|
||||
if (!!r && r.statusCode === 404) {
|
||||
return cbk([404, 'TransactionNotFound']);
|
||||
}
|
||||
|
||||
if (!isHex(transaction)) {
|
||||
return cbk([503, 'ExpectedTransactionInResponse']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const fetchRequest = require('./fetch_request');
|
|||
const {marketPairs} = require('./constants');
|
||||
const {peerSortOptions} = require('./constants');
|
||||
const {rateProviders} = require('./constants');
|
||||
const simpleRequest = require('./simple_request');
|
||||
const {swapTypes} = require('./constants');
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -13,5 +14,6 @@ module.exports = {
|
|||
marketPairs,
|
||||
peerSortOptions,
|
||||
rateProviders,
|
||||
simpleRequest,
|
||||
swapTypes,
|
||||
};
|
||||
|
|
|
|||
79
commands/simple_request.js
Normal file
79
commands/simple_request.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
const https = require('https');
|
||||
|
||||
const asQueryString = qs => !!qs ? `?${qs}` : '';
|
||||
const httpsProtocol = 'https:';
|
||||
const {keys} = Object;
|
||||
const {parse} = JSON;
|
||||
|
||||
/** Simplified version of request method
|
||||
|
||||
{
|
||||
[json]: <Interpret Result as JSON Bool>
|
||||
[method]: <HTTPS Method String>
|
||||
[timeout]: <Timeout MS Number>
|
||||
url: <HTTPS URL String>
|
||||
}
|
||||
|
||||
@returns
|
||||
<Response Object>
|
||||
<Response Body>
|
||||
*/
|
||||
module.exports = (args, cbk) => {
|
||||
if (!args.url) {
|
||||
return cbk([400, 'ExpectedUrlToExecuteSimpleRequest']);
|
||||
}
|
||||
|
||||
const url = new URL(args.url);
|
||||
|
||||
if (url.protocol !== httpsProtocol) {
|
||||
return cbk([400, 'ExpectedHttpsProtocolToExecuteSimpleRequest']);
|
||||
}
|
||||
|
||||
let error;
|
||||
const qs = url.searchParams;
|
||||
|
||||
// Include passed query string arguments
|
||||
if (!!args.qs) {
|
||||
keys(args.qs).forEach(key => qs.set(key, args.qs[key]));
|
||||
}
|
||||
|
||||
const req = https.request({
|
||||
hostname: url.hostname,
|
||||
method: args.method,
|
||||
path: url.pathname + asQueryString(qs.toString()),
|
||||
port: url.port,
|
||||
timeout: args.timeout,
|
||||
},
|
||||
res => {
|
||||
const body = [];
|
||||
|
||||
// Collect response chunks
|
||||
res.on('data', data => body.push(data));
|
||||
|
||||
// Response is finished
|
||||
res.on('end', () => {
|
||||
if (!!error) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
const combined = Buffer.concat(body).toString();
|
||||
|
||||
if (!args.json) {
|
||||
return cbk(null, res, combined);
|
||||
}
|
||||
|
||||
try {
|
||||
return cbk(null, res, parse(combined));
|
||||
} catch (err) {
|
||||
return cbk(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', err => error = err);
|
||||
req.on('timeout', () => req.abort());
|
||||
|
||||
req.end();
|
||||
|
||||
return;
|
||||
};
|
||||
2790
package-lock.json
generated
2790
package-lock.json
generated
File diff suppressed because it is too large
Load diff
23
package.json
23
package.json
|
|
@ -18,9 +18,9 @@
|
|||
"asyncjs-util": "1.2.7",
|
||||
"bip66": "1.1.5",
|
||||
"bitcoin-ops": "1.4.1",
|
||||
"bitcoinjs-lib": "5.2.0",
|
||||
"bitcoinjs-lib": "6.0.0",
|
||||
"bolt01": "1.2.3",
|
||||
"bolt03": "1.2.11",
|
||||
"bolt03": "1.2.12",
|
||||
"bolt07": "1.7.4",
|
||||
"caporal": "1.4.0",
|
||||
"cbor": "8.1.0",
|
||||
|
|
@ -28,19 +28,19 @@
|
|||
"colorette": "2.0.16",
|
||||
"crypto-js": "4.1.1",
|
||||
"csv-parse": "4.16.3",
|
||||
"goldengate": "10.4.1",
|
||||
"goldengate": "10.4.2",
|
||||
"hot-formula-parser": "4.0.0",
|
||||
"import-lazy": "4.0.0",
|
||||
"ini": "2.0.0",
|
||||
"inquirer": "8.2.0",
|
||||
"invoices": "2.0.1",
|
||||
"ln-accounting": "5.0.4",
|
||||
"ln-service": "52.15.0",
|
||||
"ln-sync": "3.0.0",
|
||||
"ln-telegram": "3.4.0",
|
||||
"invoices": "2.0.2",
|
||||
"ln-accounting": "5.0.5",
|
||||
"ln-service": "52.16.0",
|
||||
"ln-sync": "3.0.1",
|
||||
"ln-telegram": "3.4.2",
|
||||
"moment": "2.29.1",
|
||||
"paid-services": "3.1.1",
|
||||
"probing": "2.0.0",
|
||||
"paid-services": "3.1.2",
|
||||
"probing": "2.0.1",
|
||||
"psbt": "1.1.10",
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"sanitize-filename": "1.6.3",
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
"description": "Lightning balance CLI",
|
||||
"devDependencies": {
|
||||
"@alexbosworth/tap": "15.0.10",
|
||||
"ecpair": "1.0.1",
|
||||
"mock-lnd": "1.4.1",
|
||||
"secp256k1": "4.0.2"
|
||||
},
|
||||
|
|
@ -80,5 +81,5 @@
|
|||
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
||||
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -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/tags/*.js test/wallets/*.js"
|
||||
},
|
||||
"version": "11.10.0"
|
||||
"version": "11.11.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ const {payViaRoutes} = require('ln-service');
|
|||
const {returnResult} = require('asyncjs-util');
|
||||
const {subscribeToProbeForRoute} = require('ln-service');
|
||||
|
||||
const {isMatchingFilters} = require('./../display');
|
||||
const {shuffle} = require('./../arrays');
|
||||
|
||||
const {ceil} = Math;
|
||||
const cltvDelay = 144;
|
||||
const createSecret = () => randomBytes(32).toString('hex');
|
||||
const defaultFilter = ['channels_count > 9'];
|
||||
const defaultMsg = (alias, key) => `Check out my node! ${alias} ${key}`;
|
||||
const filterLimit = 10;
|
||||
const hashOf = n => createHash('sha256').update(n).digest().toString('hex');
|
||||
|
|
@ -25,6 +27,7 @@ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|||
const invoiceDescription = n => `👀 ${n}`;
|
||||
const invoiceExpiration = () => new Date(Date.now() + 1000 * 60 * 60 * 24 * 5);
|
||||
const invoiceTokens = 1;
|
||||
const {isArray} = Array;
|
||||
const keySendValueType = '5482373484';
|
||||
const maxFeeTokens = 10;
|
||||
const messageWithReply = (msg, req) => `${msg} (Mark seen: ${req})`;
|
||||
|
|
@ -34,12 +37,15 @@ const pathTimeoutMs = 1000 * 45;
|
|||
const payTimeoutMs = 1000 * 60;
|
||||
const probeTimeoutMs = 1000 * 60 * 2;
|
||||
const sendTokens = 10;
|
||||
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
||||
const textMessageType = '34349334';
|
||||
const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
||||
|
||||
/** Advertise to nodes that accept KeySend
|
||||
|
||||
{
|
||||
filters: [<Node Condition Filter String>]
|
||||
[is_dry_run]: <Avoid Sending Advertisements Bool>
|
||||
lnd: <Authenticated LND API Object>
|
||||
logger: <Winston Logger Object>
|
||||
[message]: <Message To Send String>
|
||||
|
|
@ -50,6 +56,10 @@ module.exports = (args, cbk) => {
|
|||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!isArray(args.filters)) {
|
||||
return cbk([400, 'ExpectedArrayOfFiltersToAdvertise']);
|
||||
}
|
||||
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedAuthenticatedLndToAdvertise']);
|
||||
}
|
||||
|
|
@ -95,19 +105,41 @@ module.exports = (args, cbk) => {
|
|||
const {shuffled} = shuffle({array: getGraph.nodes});
|
||||
|
||||
// Only consider 3rd party nodes that have known features
|
||||
const nodes = shuffled
|
||||
const filteredNodes = shuffled
|
||||
.filter(n => n.public_key !== getIdentity.public_key)
|
||||
.filter(n => !!n.features.length)
|
||||
.filter(node => {
|
||||
.map(node => {
|
||||
const channels = getGraph.channels.filter(channel => {
|
||||
const {policies} = channel;
|
||||
|
||||
return policies.find(n => n.public_key === node.public_key);
|
||||
});
|
||||
|
||||
return channels.length >= minChannelCount;
|
||||
const filters = args.filters.length ? args.filters : defaultFilter;
|
||||
|
||||
const variables = {
|
||||
capacity: sumOf(channels.map(n => n.capacity)),
|
||||
channels_count: channels.length,
|
||||
};
|
||||
|
||||
const isMatching = isMatchingFilters({filters, variables});
|
||||
|
||||
// Exit early when there is a failure with a filter
|
||||
if (isMatching.failure) {
|
||||
return {failure: isMatching.failure};
|
||||
}
|
||||
|
||||
return !!isMatching.is_matching ? {node} : {};
|
||||
});
|
||||
|
||||
const failure = filteredNodes.find(n => !!n.failure);
|
||||
|
||||
if (!!filteredNodes.find(n => !!n.failure)) {
|
||||
return cbk([400, 'ExpectedValidFiltersForNodes', {failure}]);
|
||||
}
|
||||
|
||||
const nodes = filteredNodes.map(n => n.node).filter(n => !!n);
|
||||
|
||||
args.logger.info({potential_nodes: nodes.length});
|
||||
|
||||
return asyncFilterLimit(nodes, filterLimit, (node, cbk) => {
|
||||
|
|
@ -196,6 +228,11 @@ module.exports = (args, cbk) => {
|
|||
{type: textMessageType, value: utf8AsHex(finalMessage)},
|
||||
];
|
||||
|
||||
// Exit early when this is a dry run
|
||||
if (!!args.is_dry_run) {
|
||||
return args.logger.info({skipping_due_to_dry_run: node.alias});
|
||||
}
|
||||
|
||||
// Send the payment
|
||||
const paid = await payViaRoutes({
|
||||
id: hashOf(hexAsBuffer(secret)),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ const bufferAsHex = buffer => buffer.toString('hex');
|
|||
const componentsSeparator = ' ';
|
||||
const decBase = 10;
|
||||
const defaultMaxFeeMtokens = '9000';
|
||||
const derivePubKey = n => ECPair.fromPrivateKey(n).publicKey.toString('hex');
|
||||
const encodeSig = (sig, hash) => Buffer.concat(Buffer.from(sig, 'hex'), hash);
|
||||
const {fromBech32} = address;
|
||||
const {fromHex} = Transaction;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ module.exports = (args, cbk) => {
|
|||
return cbk([400, 'ExpectedFetchFunctionToReceiveOnChain']);
|
||||
}
|
||||
|
||||
if (!args.fs) {
|
||||
return cbk([400, 'ExpectedFileSystemMethodsToReceiveOnChain']);
|
||||
}
|
||||
|
||||
if (!args.logger) {
|
||||
return cbk([400, 'ExpectedLoggerToReceiveOnChain']);
|
||||
}
|
||||
|
|
@ -97,6 +101,7 @@ module.exports = (args, cbk) => {
|
|||
|
||||
return getLiquidity({
|
||||
above: args.tokens,
|
||||
fs: args.fs,
|
||||
is_top: true,
|
||||
lnd: args.lnd,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const EventEmitter = require('events');
|
||||
|
||||
const {ECPair} = require('bitcoinjs-lib');
|
||||
const {ECPair} = require('ecpair');
|
||||
const {payments} = require('bitcoinjs-lib');
|
||||
const {test} = require('@alexbosworth/tap');
|
||||
const {Transaction} = require('bitcoinjs-lib');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue