mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
merge changes
This commit is contained in:
parent
d7eecf3de4
commit
6959baccf7
8 changed files with 289 additions and 110 deletions
14
bos
14
bos
|
|
@ -835,28 +835,28 @@ prog
|
|||
.command('inbound-channel-rules', 'Enforce rules for inbound channels')
|
||||
.help('Rules should be written evaluating to TRUE to accept a channel')
|
||||
.help('Example rule: --rule "CAPACITY > 100000"')
|
||||
.help('Avoid requiring Blockchain confirmations using --trust-funding-from')
|
||||
.help('For formulas: CAPACITIES are the sizes of the peer public channels')
|
||||
.help('For formulas: CAPACITY is the size of the requested channel open')
|
||||
.help('For formulas: CHANNEL_AGES are the block ages of public channels')
|
||||
.help('For formulas: FEE_RATES are the outbound fee rates for the peer')
|
||||
.help('For formulas: IS_TRUSTED_FUNDING is allowing acceptance of trusted funding channel')
|
||||
.help('For formulas: LOCAL_BALANCE is the gifted amount from the peer')
|
||||
.help('For formulas: PUBLIC_KEY is the public key of the requesting peer')
|
||||
.option('--coop-close-address', 'Request using a cooperative close address')
|
||||
.option('--node <node_name>', 'Saved node to reject inbound channels on')
|
||||
.option('--reason <message>', 'Message to return when rejecting a request')
|
||||
.option('--rule <formula>', 'Freeform rule for inbound channel', REPEATABLE)
|
||||
.option('--trust-funding-from <trust_funding_from_pubkey>', 'Allow trusted funding from pubkey', REPEATABLE)
|
||||
.option('--trust-funding-from <key>', 'Trust channel funds from', REPEATABLE)
|
||||
.action((args, options, logger) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
return await peers.interceptInboundChannels({
|
||||
logger,
|
||||
address: options.coopCloseAddress || undefined,
|
||||
keys: collect(options.trustFundingFrom),
|
||||
lnd: (await lndForNode(logger, options.node)).lnd,
|
||||
reason: options.reason,
|
||||
rules: flatten([options.rule].filter(n => !!n)),
|
||||
trust: collect(options.trustFundingFrom),
|
||||
});
|
||||
} catch (err) {
|
||||
return logger.error({err}) && reject();
|
||||
|
|
@ -1125,9 +1125,11 @@ prog
|
|||
.command('open', 'Open channels, optionally using an external wallet')
|
||||
.help('When creating channels from an external wallet do not self-broadcast')
|
||||
.help('Skip external option by specifying --internal-fund-at-fee-rate')
|
||||
.help('For trusted funding specify --type as private-trusted/public-trusted')
|
||||
.help('Trusted channel funding is not supported in LND 0.15.0 and below')
|
||||
.argument('<peer_public_keys...>', 'With nodes with public keys')
|
||||
.option('--amount <channel_capacity>', 'Capacities to open', REPEATABLE)
|
||||
.option('--avoid-broadcast <avoid_trusted_funding_broadcast>', 'Avoid broadcasting trusted funding channels')
|
||||
.option('--avoid-broadcast', 'Avoid broadcasting channel open transaction')
|
||||
.option('--coop-close-address <addr>', 'Coop-close address', REPEATABLE)
|
||||
.option('--external-funding', 'Use external funds for the channel open')
|
||||
.option('--give <give_amount>', 'Amount to gift to peer', REPEATABLE)
|
||||
|
|
@ -1135,19 +1137,19 @@ prog
|
|||
.option('--node <node_name>', 'Saved node to open channels')
|
||||
.option('--opening-node <node_name>', 'Open with saved node', REPEATABLE)
|
||||
.option('--set-fee-rate <ppm>', 'Set forward fee rate to peer', REPEATABLE)
|
||||
.option('--type <type>', 'Type of channel (private/private-trusted/public/public-trusted)', REPEATABLE)
|
||||
.option('--type <type>', 'Type of channel (private/public)', REPEATABLE)
|
||||
.action((args, options, logger) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
return peers.openChannels({
|
||||
logger,
|
||||
ask: await commands.interrogate({}),
|
||||
avoid_broadcast: options.avoidBroadcast || undefined,
|
||||
capacities: collect(options.amount),
|
||||
cooperative_close_addresses: collect(options.coopCloseAddress),
|
||||
fs: {getFile: readFile},
|
||||
gives: collect(options.give),
|
||||
internal_fund_fee_rate: options.internalFundAtFeeRate || undefined,
|
||||
is_avoiding_broadcast: options.avoidBroadcast || undefined,
|
||||
is_external: options.externalFunding,
|
||||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
opening_nodes: collect(options.openingNode),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
const defaultChannelCapacity = 5e6;
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
const isTrusted = type => ['private-trusted', 'public-trusted'].includes(type);
|
||||
const privateTypes = ['private', 'private-trusted'];
|
||||
const trustedFundingTypes = ['private-trusted', 'public-trusted'];
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
|
||||
/** Derive channel to open details from channel argument list
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ module.exports = args => {
|
|||
cooperative_close_address: args.addresses[i] || undefined,
|
||||
give_tokens: !!args.gives[i] ? Number(args.gives[i]) : undefined,
|
||||
is_private: !!args.types[i] && privateTypes.includes(args.types[i]),
|
||||
is_trusted_funding: !!args.types[i] && trustedFundingTypes.includes(args.types[i]),
|
||||
is_trusted_funding: !!args.types[i] && isTrusted(args.types[i]),
|
||||
node: args.saved[i] || undefined,
|
||||
partner_public_key: key,
|
||||
rate: args.rates[i] || undefined,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ module.exports = (args, cbk) => {
|
|||
|
||||
// Get the capacities fees for the node to use in rule parsing
|
||||
getNodeFees: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no rules to evaluate
|
||||
if (!args.rules.length) {
|
||||
return cbk(null, {});
|
||||
}
|
||||
|
||||
return getNode({
|
||||
lnd: args.lnd,
|
||||
public_key: args.partner_public_key,
|
||||
|
|
|
|||
68
peers/get_channel_outpoints.js
Normal file
68
peers/get_channel_outpoints.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getPendingChannels} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const chanAsOutpoint = n => `${n.transaction_id}:${n.transaction_vout}`;
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
|
||||
/** Get channel outpoints to check for open channel publish safety
|
||||
|
||||
{
|
||||
lnd: <Authenticated LND API Object>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
channels: [{
|
||||
transaction_id: <Transaction Id Hex String>
|
||||
transaction_vout: <Transaction Output Index Number>
|
||||
}]
|
||||
}
|
||||
*/
|
||||
module.exports = ({lnd}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedAuthenticatedLndToGetChannelOutpoints']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get regular channel outpoints
|
||||
getChannels: ['validate', ({}, cbk) => getChannels({lnd}, cbk)],
|
||||
|
||||
// Get pending channel outpoints
|
||||
getPending: ['validate', ({}, cbk) => getPendingChannels({lnd}, cbk)],
|
||||
|
||||
// Assemble channel outpoints
|
||||
outpoints: [
|
||||
'getChannels',
|
||||
'getPending',
|
||||
({getChannels, getPending}, cbk) =>
|
||||
{
|
||||
const channelOutpoints = getChannels.channels.map(channel => ({
|
||||
transaction_id: channel.transaction_id,
|
||||
transaction_vout: channel.transaction_vout,
|
||||
}));
|
||||
|
||||
const pendingOutpoints = getPending.pending_channels.map(channel => ({
|
||||
transaction_id: channel.transaction_id,
|
||||
transaction_vout: channel.transaction_vout,
|
||||
}));
|
||||
|
||||
const channels = [].concat(channelOutpoints).concat(pendingOutpoints);
|
||||
|
||||
if (uniq(channels.map(chanAsOutpoint)).length !== channels.length) {
|
||||
return cbk([503, 'UnexpectedDuplicateOutpointWithPendingChannels']);
|
||||
}
|
||||
|
||||
return cbk(null, {channels});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'outpoints'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
@ -21,9 +21,10 @@ const {toOutputScript} = address;
|
|||
logger: <Winston Logger Object>
|
||||
[reason]: <Reason Error Message String>
|
||||
rules: [<Rule for Inbound Channel String>]
|
||||
trust: [<Trust Funding From Node With Identity Public Key Hex String>]
|
||||
}
|
||||
*/
|
||||
module.exports = ({address, keys, lnd, logger, reason, rules}, cbk) => {
|
||||
module.exports = ({address, lnd, logger, reason, rules, trust}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
|
|
@ -44,12 +45,8 @@ module.exports = ({address, keys, lnd, logger, reason, rules}, cbk) => {
|
|||
return cbk([400, 'ExpectedArrayOfRejectRulesToRejectChannels']);
|
||||
}
|
||||
|
||||
if (!isArray(keys)) {
|
||||
return cbk([400, 'ExpectedArrayOfPublicKeysToInterceptChannels']);
|
||||
}
|
||||
|
||||
if (!!keys.filter(n => !isPublicKey(n)).length) {
|
||||
return cbk([400, 'ExpectedValidPublicKeysToInterceptChannels']);
|
||||
if (!!trust.filter(n => !isPublicKey(n)).length) {
|
||||
return cbk([400, 'ExpectedValidTrustPublicKeysToInterceptChannels']);
|
||||
}
|
||||
|
||||
if (!!rules.length) {
|
||||
|
|
@ -69,6 +66,10 @@ module.exports = ({address, keys, lnd, logger, reason, rules}, cbk) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (!isArray(trust)) {
|
||||
return cbk([400, 'ExpectedArrayOfTrustedKeysToInterceptChannels']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
|
|
@ -114,16 +115,28 @@ module.exports = ({address, keys, lnd, logger, reason, rules}, cbk) => {
|
|||
logger.info({
|
||||
enforcing_inbound_channel_rules: rules,
|
||||
requesting_cooperative_close_address: address,
|
||||
trusted_funding_pubkeys: !!keys.length ? keys.filter(n => !!n) : undefined,
|
||||
do_not_require_conf_funds_from: !!trust.length ? trust : undefined,
|
||||
});
|
||||
|
||||
sub.on('channel_request', request => {
|
||||
const peerId = request.partner_public_key;
|
||||
|
||||
// Exit early when requester is not trusted for trusted funding
|
||||
if (!!request.is_trusted_funding && !trust.includes(peerId)) {
|
||||
logger.info({
|
||||
rejected: peerId,
|
||||
reason: {trusted_funding_not_configured_for_peer: true},
|
||||
});
|
||||
|
||||
return request.reject({reason: 'TrustedFundingAccessDenied'});
|
||||
}
|
||||
|
||||
return detectOpenRuleViolation({
|
||||
lnd,
|
||||
rules,
|
||||
capacity: request.capacity,
|
||||
local_balance: request.local_balance,
|
||||
partner_public_key: request.partner_public_key,
|
||||
partner_public_key: peerId,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
|
|
@ -136,7 +149,7 @@ module.exports = ({address, keys, lnd, logger, reason, rules}, cbk) => {
|
|||
// Exit early when a channel open rule violation rejects a channel
|
||||
if (!!res.rule) {
|
||||
logger.info({
|
||||
rejected: request.partner_public_key,
|
||||
rejected: peerId,
|
||||
capacity: request.capacity,
|
||||
rule: res.rule,
|
||||
});
|
||||
|
|
@ -144,32 +157,11 @@ module.exports = ({address, keys, lnd, logger, reason, rules}, cbk) => {
|
|||
return request.reject({reason});
|
||||
}
|
||||
|
||||
// Exit early if no allow list is specified and trusted funding is true
|
||||
if (!keys.length && !!request.is_trusted_funding) {
|
||||
logger.info({
|
||||
rejected: request.partner_public_key,
|
||||
reason: {
|
||||
is_trusted_funding: true
|
||||
}
|
||||
});
|
||||
|
||||
return request.reject({reason: 'PublicKeyNotWhitelistedForTrustedFunding'});
|
||||
}
|
||||
|
||||
// Exit early if requester pubkey is not allowed for trusted funding
|
||||
if (!!keys.length && !!request.is_trusted_funding && !keys.includes(request.partner_public_key)) {
|
||||
logger.info({
|
||||
rejected: request.partner_public_key,
|
||||
reason: {
|
||||
is_trusted_funding: true
|
||||
}
|
||||
});
|
||||
|
||||
return request.reject({reason: 'PublicKeyNotWhitelistedForTrustedFunding'});
|
||||
}
|
||||
|
||||
// Accept the channel open request
|
||||
return request.accept({cooperative_close_address: address, is_trusted_funding: request.is_trusted_funding});
|
||||
return request.accept({
|
||||
cooperative_close_address: address,
|
||||
is_trusted_funding: request.is_trusted_funding,
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -15,12 +15,11 @@ const asyncRetry = require('async/retry');
|
|||
const {broadcastChainTransaction} = require('ln-service');
|
||||
const {cancelPendingChannel} = require('ln-service');
|
||||
const {fundPendingChannels} = require('ln-service');
|
||||
const {getFundedTransaction} = require('ln-sync');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getFundedTransaction} = require('ln-sync');
|
||||
const {getNetwork} = require('ln-sync');
|
||||
const {getNode} = require('ln-service');
|
||||
const {getPeers} = require('ln-service');
|
||||
const {getPendingChannels} = require('ln-service');
|
||||
const {getPsbtFromTransaction} = require('goldengate');
|
||||
const {getWalletVersion} = require('ln-service');
|
||||
const {openChannels} = require('ln-service');
|
||||
|
|
@ -34,6 +33,7 @@ const adjustFees = require('./../routing/adjust_fees');
|
|||
const {authenticatedLnd} = require('./../lnd');
|
||||
const channelsFromArguments = require('./channels_from_arguments');
|
||||
const {getAddressUtxo} = require('./../chain');
|
||||
const getChannelOutpoints = require('./get_channel_outpoints');
|
||||
const {parseAmount} = require('./../display');
|
||||
|
||||
const bech32AsData = bech32 => address.fromBech32(bech32).data;
|
||||
|
|
@ -54,7 +54,6 @@ const per = (a, b) => (a / b).toFixed(2);
|
|||
const relockIntervalMs = 1000 * 20;
|
||||
const times = 10;
|
||||
const tokAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
||||
const trustedFundingTypes = ['private-trusted', 'public-trusted'];
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
const utxoPollingIntervalMs = 1000 * 30;
|
||||
const utxoPollingTimes = 20;
|
||||
|
|
@ -63,13 +62,13 @@ const utxoPollingTimes = 20;
|
|||
|
||||
{
|
||||
ask: <Ask For Input Function>
|
||||
[avoid_broadcast]: <Avoid Broadcast Bool>
|
||||
capacities: [<New Channel Capacity Tokens String>]
|
||||
cooperative_close_addresses: [<Cooperative Close Address>]
|
||||
fs: {
|
||||
getFile: <Read File Contents Function> (path, cbk) => {}
|
||||
}
|
||||
gives: [<New Channel Give Tokens Number>]
|
||||
[is_avoiding_broadcast]: <Avoid Funding Transaction Broadcast Bool>
|
||||
[is_external]: <Use External Funds to Open Channels Bool>
|
||||
lnd: <Authenticated LND API Object>
|
||||
logger: <Winston Logger Object>
|
||||
|
|
@ -166,7 +165,7 @@ module.exports = (args, cbk) => {
|
|||
}
|
||||
|
||||
if (args.types.findIndex(n => !knownTypes.includes(n)) !== notFound) {
|
||||
return cbk([400, 'UnknownChannelType']);
|
||||
return cbk([400, 'UnknownChannelType', {channel_types: knownTypes}]);
|
||||
}
|
||||
|
||||
if (!!args.types.length && args.types.length !== publicKeysLength) {
|
||||
|
|
@ -189,11 +188,6 @@ module.exports = (args, cbk) => {
|
|||
return cbk(null, capacities);
|
||||
}],
|
||||
|
||||
// Get the wallet version to make sure the node supports internal funding
|
||||
getWalletVersion: ['validate', ({}, cbk) => {
|
||||
return getWalletVersion({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get LNDs associated with nodes specified for opening
|
||||
getLnds: ['validate', ({}, cbk) => {
|
||||
// Exit early when there are no opening nodes specified
|
||||
|
|
@ -244,6 +238,11 @@ module.exports = (args, cbk) => {
|
|||
cbk);
|
||||
}],
|
||||
|
||||
// Get the wallet version to make sure the node supports internal funding
|
||||
getWalletVersion: ['validate', ({}, cbk) => {
|
||||
return getWalletVersion({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get the networks of the opening nodes
|
||||
getOpeningNetworks: ['getLnds', ({getLnds}, cbk) => {
|
||||
if (!getLnds) {
|
||||
|
|
@ -720,51 +719,13 @@ module.exports = (args, cbk) => {
|
|||
cbk);
|
||||
})],
|
||||
|
||||
// Get list of trusted channels
|
||||
getTrustedChannels: [
|
||||
'fundChannels',
|
||||
'fundingPsbt',
|
||||
'getFunding',
|
||||
'openChannels',
|
||||
'outputs',
|
||||
({getFunding, openChannels}, cbk) => {
|
||||
// Exit early if no trusted channels are being opened
|
||||
const trustedChannelsLength = (args.types.filter(n => (n === 'public-trusted' || n === 'private-trusted'))).length;
|
||||
if (!trustedChannelsLength) {
|
||||
return cbk(null, []);
|
||||
}
|
||||
|
||||
return asyncRetry({interval, times: pendingCheckTimes}, cbk => {
|
||||
return asyncMap(openChannels, ({lnd, node, pending}, cbk) => {
|
||||
return getChannels({lnd}, cbk);
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk([400, 'FailedToGetTrustedChannels']);
|
||||
}
|
||||
|
||||
const txId = fromHex(getFunding.value.transaction).getId();
|
||||
const trustedChannels = res[0].channels.filter(n => !!n.is_trusted_funding && n.transaction_id === txId);
|
||||
|
||||
if (!trustedChannels.length || trustedChannels.length !== trustedChannelsLength) {
|
||||
return cbk([400, 'FailedToFindTrustedChannelsList']);
|
||||
}
|
||||
|
||||
return cbk(null, trustedChannels);
|
||||
},
|
||||
cbk)
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Broadcast the funding transaction when opening on multiple nodes
|
||||
broadcastChainTransaction: [
|
||||
'fundChannels',
|
||||
'fundingPsbt',
|
||||
'getFunding',
|
||||
'getTrustedChannels',
|
||||
'openChannels',
|
||||
({fundChannels, fundingPsbt, getFunding, getTrustedChannels, openChannels}, cbk) =>
|
||||
({fundChannels, fundingPsbt, getFunding, openChannels}, cbk) =>
|
||||
{
|
||||
const fundingError = getFunding.error || fundingPsbt.error;
|
||||
const error = fundChannels.error || fundingError;
|
||||
|
|
@ -773,7 +734,7 @@ module.exports = (args, cbk) => {
|
|||
if (!!error || !!fundingError) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
|
||||
const toOpen = flatten(openChannels.map(n => n.pending));
|
||||
const txId = fromHex(getFunding.value.transaction).getId();
|
||||
|
||||
|
|
@ -782,34 +743,36 @@ module.exports = (args, cbk) => {
|
|||
// Make sure that pending channels are showing up: got commitment tx
|
||||
return asyncRetry({interval, times: pendingCheckTimes}, cbk => {
|
||||
return asyncMap(openChannels, ({lnd, node, pending}, cbk) => {
|
||||
return getPendingChannels({lnd}, cbk);
|
||||
return getChannelOutpoints({lnd}, cbk);
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
// Consolidate all pending channels from all nodes
|
||||
const pending = flatten(res.map(n => n.pending_channels));
|
||||
|
||||
// Only consider pending channels related to this funding tx
|
||||
// Consolidate all channels from all nodes
|
||||
const pending = flatten(res.map(n => n.channels));
|
||||
|
||||
// Only consider channels related to this funding tx
|
||||
const opening = pending.filter(n => n.transaction_id === txId);
|
||||
|
||||
// Every channel to open should be reflected in a pending channel
|
||||
if ((opening.length + getTrustedChannels.length) !== toOpen.length) {
|
||||
// Every channel to open should be reflected in a channel
|
||||
if (opening.length !== toOpen.length) {
|
||||
return cbk([503, 'FailedToFindPendingChannelOpen']);
|
||||
}
|
||||
|
||||
// Exit early if avoiding broadcast
|
||||
if (!!args.avoid_broadcast) {
|
||||
args.logger.info({
|
||||
args.logger.info({
|
||||
raw_transaction_to_broadcast: getFunding.value.transaction,
|
||||
is_avoiding_broadcast: true,
|
||||
});
|
||||
|
||||
|
||||
// Exit early when avoiding broadcast
|
||||
if (!!args.is_avoiding_broadcast) {
|
||||
args.logger.info({is_avoiding_broadcast: true});
|
||||
|
||||
return cbk();
|
||||
}
|
||||
|
||||
args.logger.info({broadcasting: getFunding.value.transaction});
|
||||
args.logger.info({broadcasting: getFunding.value.id});
|
||||
|
||||
return broadcastChainTransaction({
|
||||
lnd: args.lnd,
|
||||
|
|
@ -947,11 +910,10 @@ module.exports = (args, cbk) => {
|
|||
'setFeeRates',
|
||||
({getFunding, fundingPsbt}, cbk) =>
|
||||
{
|
||||
if (!!args.avoid_broadcast) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return cbk(null, {transaction_id: getFunding.value.id});
|
||||
return cbk(null, {
|
||||
transaction: getFunding.value.transaction,
|
||||
transaction_id: getFunding.value.id,
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'completed'}, cbk));
|
||||
|
|
|
|||
145
test/integration/test_trusted_channels.js
Normal file
145
test/integration/test_trusted_channels.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
const {addPeer} = require('ln-service');
|
||||
const asyncAuto = require('async/auto');
|
||||
const asyncEach = require('async/each');
|
||||
const asyncRetry = require('async/retry');
|
||||
const {createChainAddress} = require('ln-service');
|
||||
const {fundPsbt} = require('ln-service');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getPendingChannels} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {openChannel} = require('ln-service');
|
||||
const {signPsbt} = require('ln-service');
|
||||
const {spawnLightningCluster} = require('ln-docker-daemons');
|
||||
const {test} = require('@alexbosworth/tap');
|
||||
const {Transaction} = require('bitcoinjs-lib');
|
||||
|
||||
const {interceptInboundChannels} = require('./../../peers');
|
||||
const {openChannels} = require('./../../peers');
|
||||
|
||||
const count = 100;
|
||||
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
const interval = 200;
|
||||
const log = () => {};
|
||||
const size = 2;
|
||||
const times = 1000;
|
||||
|
||||
// Opening trusted channels should open channels with specified nodes
|
||||
test(`Open channels`, async ({end, equal, strictSame}) => {
|
||||
const {kill, nodes} = await spawnLightningCluster({
|
||||
size,
|
||||
lnd_configuration: [
|
||||
'--maxpendingchannels=10',
|
||||
'--protocol.option-scid-alias',
|
||||
'--protocol.zero-conf',
|
||||
],
|
||||
});
|
||||
|
||||
const [{generate, id, lnd}, target] = nodes;
|
||||
|
||||
try {
|
||||
await generate({count});
|
||||
|
||||
await asyncRetry({interval, times}, async () => {
|
||||
await addPeer({lnd, public_key: target.id, socket: target.socket});
|
||||
|
||||
await asyncEach(nodes, async ({lnd}) => {
|
||||
const chain = await getWalletInfo({lnd});
|
||||
|
||||
if (!chain.is_synced_to_chain || !chain.is_synced_to_graph) {
|
||||
throw new Error('WaitingForSync');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const {address} = await asyncRetry({interval, times}, async () => {
|
||||
return await createChainAddress({lnd});
|
||||
});
|
||||
|
||||
// Propose a trusted channel and accept it
|
||||
await asyncAuto({
|
||||
// Intercept the trusted open to accept it
|
||||
intercept: async () => {
|
||||
try {
|
||||
return await interceptInboundChannels({
|
||||
lnd: target.lnd,
|
||||
logger: {error: () => {}, info: () => {}},
|
||||
rules: [],
|
||||
trust: [id],
|
||||
});
|
||||
} catch (err) {
|
||||
// Interception will terminate after the channel is open
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
// Propose the trusted channel to target
|
||||
propose: async () => {
|
||||
return asyncRetry({interval, times}, async () => {
|
||||
await addPeer({lnd, public_key: target.id, socket: target.socket});
|
||||
|
||||
await openChannels({
|
||||
lnd,
|
||||
ask: async (args, cbk) => {
|
||||
if (args.name === 'internal') {
|
||||
return cbk({internal: false});
|
||||
}
|
||||
|
||||
if (args.name === 'fund') {
|
||||
const address = args.message.split(' ')[9];
|
||||
const amount = args.message.split(' ')[7];
|
||||
|
||||
// Provide funding
|
||||
const {psbt} = await fundPsbt({
|
||||
lnd,
|
||||
outputs: [{address, tokens: amount * 1e8}],
|
||||
});
|
||||
|
||||
const signed = await signPsbt({lnd, psbt});
|
||||
|
||||
return cbk({fund: signed.psbt});
|
||||
}
|
||||
|
||||
throw new Error('UnrecognizedParameter');
|
||||
},
|
||||
capacities: [],
|
||||
cooperative_close_addresses: [],
|
||||
fs: {getFile: () => {}},
|
||||
gives: [],
|
||||
logger: {info: log, error: log},
|
||||
opening_nodes: [],
|
||||
public_keys: [target.id],
|
||||
request: () => {},
|
||||
set_fee_rates: [],
|
||||
types: ['private-trusted'],
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Generate blocks until the channel confirms
|
||||
generate: async () => {
|
||||
return await asyncRetry({interval, times}, async () => {
|
||||
await generate({});
|
||||
|
||||
const {channels} = await getChannels({lnd});
|
||||
|
||||
if (!channels.length) {
|
||||
throw new Error('Expected Channels');
|
||||
}
|
||||
|
||||
const [channel] = channels;
|
||||
|
||||
equal(channel.is_trusted_funding, true, 'Trusted channel opened');
|
||||
});
|
||||
},
|
||||
|
||||
// Stop the target node
|
||||
finish: ['generate', 'propose', async ({}) => {
|
||||
await target.kill({});
|
||||
}],
|
||||
});
|
||||
} catch (err) {
|
||||
equal(err, null, 'Expected no error');
|
||||
}
|
||||
|
||||
await kill({});
|
||||
});
|
||||
|
|
@ -29,6 +29,7 @@ const tests = [
|
|||
cooperative_close_address: 'address',
|
||||
give_tokens: 1,
|
||||
is_private: true,
|
||||
is_trusted_funding: false,
|
||||
node: undefined,
|
||||
partner_public_key: Buffer.alloc(33, 3).toString('hex'),
|
||||
rate: undefined,
|
||||
|
|
@ -51,6 +52,7 @@ const tests = [
|
|||
cooperative_close_address: undefined,
|
||||
give_tokens: undefined,
|
||||
is_private: false,
|
||||
is_trusted_funding: false,
|
||||
node: undefined,
|
||||
partner_public_key: Buffer.alloc(33, 3).toString('hex'),
|
||||
rate: undefined,
|
||||
|
|
@ -77,6 +79,7 @@ const tests = [
|
|||
cooperative_close_address: 'coopCloseAddressNodeA',
|
||||
give_tokens: 3,
|
||||
is_private: true,
|
||||
is_trusted_funding: false,
|
||||
node: 'savedA',
|
||||
partner_public_key: 'remoteNodeA',
|
||||
rate: '1',
|
||||
|
|
@ -89,6 +92,7 @@ const tests = [
|
|||
cooperative_close_address: 'coopCloseAddressNodeB',
|
||||
give_tokens: 4,
|
||||
is_private: false,
|
||||
is_trusted_funding: false,
|
||||
node: 'savedB',
|
||||
partner_public_key: 'remoteNodeB',
|
||||
rate: '2',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue