paid-services 3.15.0 → 3.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Versions
2
2
 
3
- ## Version 3.15.0
3
+ ## Version 3.15.3
4
4
 
5
5
  - `manageSwap`: Add method to execute submarine swaps on testnet
6
6
 
package/package.json CHANGED
@@ -10,12 +10,13 @@
10
10
  "@alexbosworth/fiat": "1.0.2",
11
11
  "async": "3.2.3",
12
12
  "asyncjs-util": "1.2.9",
13
+ "bech32": "2.0.0",
13
14
  "bolt01": "1.2.4",
14
15
  "bolt07": "1.8.1",
15
16
  "ecpair": "2.0.1",
16
17
  "goldengate": "11.2.1",
17
18
  "invoices": "2.0.6",
18
- "ln-service": "53.11.0",
19
+ "ln-service": "53.14.1",
19
20
  "ln-sync": "3.12.0",
20
21
  "psbt": "2.0.1",
21
22
  "p2tr": "1.3.1",
@@ -24,7 +25,7 @@
24
25
  "description": "Lightning Paid Services library",
25
26
  "devDependencies": {
26
27
  "@alexbosworth/tap": "15.0.11",
27
- "ln-docker-daemons": "2.2.7",
28
+ "ln-docker-daemons": "2.2.9",
28
29
  "mock-lnd": "1.4.1",
29
30
  "secp256k1": "4.0.3"
30
31
  },
@@ -46,5 +47,5 @@
46
47
  "integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
47
48
  "test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/actions/*.js test/balanced/*.js test/capacity/*.js test/client/*.js test/config/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/server/*.js test/services/*.js test/trades/*.js"
48
49
  },
49
- "version": "3.15.0"
50
+ "version": "3.15.3"
50
51
  }
@@ -5,6 +5,7 @@ const asyncAuto = require('async/auto');
5
5
  const asyncMap = require('async/map');
6
6
  const asyncRace = require('async/race');
7
7
  const asyncReflect = require('async/reflect');
8
+ const {bech32m} = require('bech32');
8
9
  const {broadcastChainTransaction} = require('ln-service');
9
10
  const {confirmationFee} = require('goldengate');
10
11
  const {controlBlock} = require('p2tr');
@@ -19,12 +20,14 @@ const {getNetwork} = require('ln-sync');
19
20
  const {getPayment} = require('ln-service');
20
21
  const {getPublicKey} = require('ln-service');
21
22
  const {hashForTree} = require('p2tr');
23
+ const {networks} = require('bitcoinjs-lib');
22
24
  const {parsePaymentRequest} = require('ln-service');
23
25
  const {pay} = require('ln-service');
24
26
  const {payViaPaymentDetails} = require('ln-service');
25
27
  const {pointAdd} = require('tiny-secp256k1');
26
28
  const {privateAdd} = require('tiny-secp256k1');
27
29
  const {returnResult} = require('asyncjs-util');
30
+ const {script} = require('bitcoinjs-lib');
28
31
  const {signTransaction} = require('ln-service');
29
32
  const {subscribeToBlocks} = require('ln-service');
30
33
  const {subscribeToPastPayment} = require('ln-service');
@@ -42,12 +45,14 @@ const {typePayMetadata} = require('./swap_field_types');
42
45
 
43
46
  const bufferAsHex = buffer => buffer.toString('hex');
44
47
  const {ceil} = Math;
48
+ const decompileOutputScript = hex => script.decompile(Buffer.from(hex, 'hex'));
45
49
  const defaultConfsCount = 1;
46
50
  const defaultDepositSettleTimeoutMs = 1000 * 60 * 10;
47
51
  const defaultMaxFeeMultiplier = 1000;
48
52
  const defaultMaxPreimagePushFee = 10;
49
53
  const defaultMinSweepBlocks = 20;
50
54
  const defaultWaitForChainFundingMs = 1000 * 60 * 60 * 3;
55
+ const encodeAddress = (prefix, data) => bech32m.encode(prefix, data);
51
56
  const family = 805;
52
57
  const {floor} = Math;
53
58
  const {from} = Buffer;
@@ -69,6 +74,7 @@ const sweepInputIndex = 0;
69
74
  const times = n => Array(n).fill(null).map((_, i) => i);
70
75
  const tokensForPushPreimage = 1;
71
76
  const uniqBy = (a,b) => a.filter((e,i) => a.findIndex(n => n[b] == e[b]) == i);
77
+ const v1AddressWords = key => [].concat(1).concat(bech32m.toWords(key));
72
78
 
73
79
  /** Complete the off to on swap
74
80
 
@@ -393,9 +399,13 @@ module.exports = (args, cbk) => {
393
399
  'swap',
394
400
  ({deadline, getNetwork, requestDetails, startHeight, swap}, cbk) =>
395
401
  {
402
+ const [, key] = decompileOutputScript(swap.output_script);
396
403
  const outputScript = swap.output_script;
404
+ const prefix = networks[getNetwork.bitcoinjs].bech32;
397
405
 
398
- args.emitter.emit('update', {waiting_for_chain_funding: outputScript});
406
+ const address = encodeAddress(prefix, v1AddressWords(key));
407
+
408
+ args.emitter.emit('update', {waiting_for_chain_funding: address});
399
409
 
400
410
  if (!!args.request) {
401
411
  return findDeposit({
@@ -418,8 +418,7 @@ module.exports = (args, cbk) => {
418
418
  });
419
419
 
420
420
  return cbk(null, tx.toHex());
421
- },
422
- cbk);
421
+ });
423
422
  },
424
423
  cbk);
425
424
  }],
@@ -681,7 +680,7 @@ module.exports = (args, cbk) => {
681
680
  transaction,
682
681
  lnd: args.lnd,
683
682
  },
684
- err => {});
683
+ () => {});
685
684
  });
686
685
 
687
686
  subTimeout.on('error', err => done(err));
@@ -718,7 +717,7 @@ module.exports = (args, cbk) => {
718
717
  return cbk();
719
718
  }
720
719
 
721
- args.emitter.emit('update', {receiving_funds: findSecret.secret});
720
+ args.emitter.emit('update', {receiving_funds: getSettlement.tokens});
722
721
 
723
722
  return settleHodlInvoice({
724
723
  lnd: args.lnd,
@@ -1,5 +1,6 @@
1
1
  const asyncAuto = require('async/auto');
2
- const {getNetwork} = require('ln-sync')
2
+ const {getMasterPublicKeys} = require('ln-service');
3
+ const {getNetwork} = require('ln-sync');
3
4
  const {returnResult} = require('asyncjs-util');
4
5
 
5
6
  const recoverResponseToSwapOut = require('./recover_response_to_swap_out');
@@ -7,6 +8,7 @@ const requestSwapOut = require('./request_swap_out');
7
8
  const respondToSwapOutRequest = require('./respond_to_swap_out_request');
8
9
 
9
10
  const allowedNetwork = 'btctestnet';
11
+ const bip86Path = `m/86'/0'/0'`;
10
12
  const recoverRespondAction = 'recover-response';
11
13
  const requestAction = 'request';
12
14
  const respondAction = 'respond';
@@ -46,13 +48,25 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
46
48
  return cbk();
47
49
  },
48
50
 
51
+ // Look for a p2tr master public key to detect p2tr support
52
+ getMasterPublicKeys: ['validate', ({}, cbk) => {
53
+ return getMasterPublicKeys({lnd}, cbk);
54
+ }],
55
+
49
56
  // Get the network to make sure this is on testnet
50
57
  getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
51
58
 
59
+ // Determine if taproot is supported or not
60
+ hasTr: ['getMasterPublicKeys', ({getMasterPublicKeys}, cbk) => {
61
+ const {keys} = getMasterPublicKeys;
62
+
63
+ return cbk(null, !!keys.find(n => n.derivation_path === bip86Path));
64
+ }],
65
+
52
66
  // Select a swap action
53
- selectAction: ['getNetwork', ({getNetwork}, cbk) => {
67
+ selectAction: ['getNetwork', 'hasTr', ({getNetwork, hasTr}, cbk) => {
54
68
  // Check to make sure the network is supported
55
- if (getNetwork.network !== allowedNetwork) {
69
+ if (!hasTr && getNetwork.network !== allowedNetwork) {
56
70
  return cbk([501, 'CurrentlyUnsupportedChainForSwap']);
57
71
  }
58
72
 
@@ -79,30 +93,52 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
79
93
  }],
80
94
 
81
95
  // Make swap request
82
- makeRequest: ['selectAction', ({selectAction}, cbk) => {
96
+ makeRequest: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
83
97
  if (selectAction !== requestAction) {
84
98
  return cbk();
85
99
  }
86
100
 
87
- return requestSwapOut({ask, lnd, logger, request}, cbk);
101
+ return requestSwapOut({
102
+ ask,
103
+ lnd,
104
+ logger,
105
+ request: !hasTr ? request : undefined,
106
+ },
107
+ cbk);
88
108
  }],
89
109
 
90
110
  // Respond to swap request
91
- makeResponse: ['selectAction', ({selectAction}, cbk) => {
111
+ makeResponse: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
92
112
  if (selectAction !== respondAction) {
93
113
  return cbk();
94
114
  }
95
115
 
96
- return respondToSwapOutRequest({ask, lnd, logger, request}, cbk);
116
+ return respondToSwapOutRequest({
117
+ ask,
118
+ lnd,
119
+ logger,
120
+ request: !hasTr ? request : undefined,
121
+ },
122
+ cbk);
97
123
  }],
98
124
 
99
125
  // Recover responding to swap request
100
- recoverResponse: ['selectAction', ({selectAction}, cbk) => {
126
+ recoverResponse: [
127
+ 'hasTr',
128
+ 'selectAction',
129
+ ({hasTr, selectAction}, cbk) =>
130
+ {
101
131
  if (selectAction !== recoverRespondAction) {
102
132
  return cbk();
103
133
  }
104
134
 
105
- return recoverResponseToSwapOut({ask, lnd, logger, request}, cbk);
135
+ return recoverResponseToSwapOut({
136
+ ask,
137
+ lnd,
138
+ logger,
139
+ request: !hasTr ? request : undefined,
140
+ },
141
+ cbk);
106
142
  }],
107
143
 
108
144
  // Final result
@@ -11,7 +11,7 @@ const completeOnToOffSwap = require('./complete_on_to_off_swap');
11
11
  ask: <Ask Function>
12
12
  lnd: <Authenticated LND API Object>
13
13
  logger: <Winston Logger Object>
14
- request: <Request Function>
14
+ [request]: <Request Function>
15
15
  }
16
16
 
17
17
  @returns via cbk or Promise
@@ -33,10 +33,6 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
33
33
  return cbk([400, 'ExpectedWinstonLoggerToRecoverResponseToSwapOut']);
34
34
  }
35
35
 
36
- if (!request) {
37
- return cbk([400, 'ExpectedRequestFunctionToRecoverResponseToSwap']);
38
- }
39
-
40
36
  return cbk();
41
37
  },
42
38
 
@@ -22,27 +22,29 @@ const rateDenominator = 1e6;
22
22
 
23
23
  {
24
24
  ask: <Ask Function>
25
+ [is_avoiding_broadcast]: <Avoid Broadcasting Bool>
26
+ [is_uncooperative]: <Avoid Cooperative Resolution Bool>
25
27
  lnd: <Authenticated LND API Object>
26
28
  logger: <Winston Logger Object>
27
- request: <Request Function>
29
+ [request]: <Request Function>
28
30
  }
29
31
 
30
32
  @returns via cbk or Promise
31
33
  */
32
- module.exports = ({ask, lnd, logger, request}, cbk) => {
34
+ module.exports = (args, cbk) => {
33
35
  return new Promise((resolve, reject) => {
34
36
  return asyncAuto({
35
37
  // Check arguments
36
38
  validate: cbk => {
37
- if (!ask) {
39
+ if (!args.ask) {
38
40
  return cbk([400, 'ExpectedAskFunctionToRequestSwapOut']);
39
41
  }
40
42
 
41
- if (!lnd) {
43
+ if (!args.lnd) {
42
44
  return cbk([400, 'ExpectedAuthenticatedLndToRequestSwapOut']);
43
45
  }
44
46
 
45
- if (!logger) {
47
+ if (!args.logger) {
46
48
  return cbk([400, 'ExpectedWinstonLoggerToRequestSwapOut']);
47
49
  }
48
50
 
@@ -51,7 +53,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
51
53
 
52
54
  // Ask for amount
53
55
  askForTokens: ['validate', ({}, cbk) => {
54
- return ask({
56
+ return args.ask({
55
57
  default: defaultSwapAmount,
56
58
  message: 'Amount to swap?',
57
59
  name: 'tokens',
@@ -71,11 +73,11 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
71
73
  }],
72
74
 
73
75
  // Get the network name
74
- getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
76
+ getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
75
77
 
76
78
  // Ask for routing fee rate
77
79
  askForRate: ['askForTokens', ({}, cbk) => {
78
- return ask({
80
+ return args.ask({
79
81
  default: defaultFeeRate,
80
82
  message: 'Max routing fee rate for swap funds in parts per million?',
81
83
  name: 'rate',
@@ -97,8 +99,8 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
97
99
  // Make a swap request
98
100
  makeRequest: ['askForTokens', ({askForTokens}, cbk) => {
99
101
  return makeRequest({
100
- lnd,
101
- is_external_solo_key: true,
102
+ is_external_solo_key: !!args.request,
103
+ lnd: args.lnd,
102
104
  tokens: askForTokens,
103
105
  },
104
106
  cbk);
@@ -111,9 +113,9 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
111
113
  'makeRequest',
112
114
  ({getNetwork, makeRequest}, cbk) =>
113
115
  {
114
- logger.info({swap_request: makeRequest.request});
116
+ args.logger.info({swap_request: makeRequest.request});
115
117
 
116
- return ask({
118
+ return args.ask({
117
119
  message: 'Response to swap request?',
118
120
  name: 'response',
119
121
  validate: response => {
@@ -152,7 +154,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
152
154
  const fee = tokens - askForTokens;
153
155
  const pricing = `Execution cost ${deposit}, plus liquidity fee ${fee}`;
154
156
 
155
- return ask({
157
+ return args.ask({
156
158
  default: true,
157
159
  message: `Start swap ${timeout}? ${pricing}?`,
158
160
  name: 'ok',
@@ -176,15 +178,17 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
176
178
 
177
179
  const emitter = new EventEmitter();
178
180
 
179
- emitter.on('update', update => logger.info(update));
181
+ emitter.on('update', update => args.logger.info(update));
180
182
 
181
183
  return completeOffToOnSwap({
182
184
  emitter,
183
- lnd,
184
- request,
185
+ is_avoiding_broadcast: args.is_avoiding_broadcast,
186
+ is_uncooperative: args.is_uncooperative,
187
+ lnd: args.lnd,
185
188
  max_fee_deposit: defaultMaxFeeForDeposit,
186
189
  max_fee_funding: askForTokens * askForRate / rateDenominator,
187
190
  recovery: makeRequest.recovery,
191
+ request: args.request,
188
192
  response: getResponse,
189
193
  },
190
194
  cbk);
@@ -23,7 +23,7 @@ const rateDenominator = 1e6;
23
23
  ask: <Ask Function>
24
24
  lnd: <Authenticated LND API Object>
25
25
  logger: <Winston Logger Object>
26
- request: <Request Function>
26
+ [request]: <Request Function>
27
27
  }
28
28
 
29
29
  @returns via cbk or Promise
@@ -45,10 +45,6 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
45
45
  return cbk([400, 'ExpectedLoggerToRespondToSwapOutRequest']);
46
46
  }
47
47
 
48
- if (!request) {
49
- return cbk([400, 'ExpectedRequestFunctionToRespondToSwapOutReq']);
50
- }
51
-
52
48
  return cbk();
53
49
  },
54
50
 
@@ -115,7 +111,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
115
111
  lnd,
116
112
  delta: defaultCltvDelta,
117
113
  deposit: ceil(getRate.tokens_per_vbyte * estimatedVirtualSize),
118
- is_external_solo_key: true,
114
+ is_external_solo_key: !!request,
119
115
  price: floor(tokens * askForRate / rateDenominator),
120
116
  request: askForRequest,
121
117
  },
@@ -0,0 +1,151 @@
1
+ const asyncRetry = require('async/retry');
2
+ const {createChainAddress} = require('ln-service');
3
+ const {createInvoice} = require('ln-service');
4
+ const {getChannelBalance} = require('ln-service');
5
+ const {getChannels} = require('ln-service');
6
+ const {getMasterPublicKeys} = require('ln-service');
7
+ const {openChannel} = require('ln-service');
8
+ const {pay} = require('ln-service');
9
+ const {sendToChainAddress} = require('ln-service');
10
+ const {spawnLightningCluster} = require('ln-docker-daemons');
11
+ const {test} = require('@alexbosworth/tap');
12
+
13
+ const requestSwapOut = require('./../../swaps/request_swap_out');
14
+ const respondToSwapOut = require('./../../swaps/respond_to_swap_out_request');
15
+
16
+ const capacity = 1e6;
17
+ const count = 50;
18
+ const interval = 100;
19
+ const maturity = 100;
20
+ const size = 2;
21
+ const taprootDerivationPath = `m/86'/0'/0'`;
22
+ const times = 3000;
23
+ const tokens = 1e5;
24
+
25
+ // Start an offchain swap
26
+ test(`Start offchain swap`, async ({end, equal, strictSame}) => {
27
+ const {kill, nodes} = await spawnLightningCluster({size});
28
+
29
+ const [{generate, id, lnd}, target] = nodes;
30
+
31
+ const {keys} = await getMasterPublicKeys({lnd});
32
+
33
+ // Exit early when taproot is not supported
34
+ if (!keys.find(n => n.derivation_path === taprootDerivationPath)) {
35
+ await kill({});
36
+
37
+ return end();
38
+ }
39
+
40
+ try {
41
+ await generate({count: maturity});
42
+
43
+ // Setup a channel between the nodes
44
+ {
45
+ // Make a channel
46
+ await asyncRetry({interval, times}, async () => {
47
+ await openChannel({
48
+ lnd,
49
+ local_tokens: capacity,
50
+ partner_public_key: target.id,
51
+ partner_socket: target.socket,
52
+ });
53
+ });
54
+
55
+ await asyncRetry({interval, times}, async () => {
56
+ const {channels} = await getChannels({lnd, is_actve: true});
57
+
58
+ if (!!channels.length) {
59
+ return channels;
60
+ }
61
+
62
+ await generate({});
63
+
64
+ await sendToChainAddress({
65
+ lnd,
66
+ address: (await createChainAddress({lnd: target.lnd})).address,
67
+ tokens: capacity,
68
+ });
69
+
70
+ const balance = await getChannelBalance({lnd});
71
+
72
+ if (!balance.channel_balance) {
73
+ throw new Error('WaitingForChannelBalance');
74
+ }
75
+
76
+ throw new Error('WaitingForChannelOpen');
77
+ });
78
+
79
+ await target.generate({count: maturity});
80
+ }
81
+
82
+ // Collect request messages
83
+ const messages = [];
84
+
85
+ // Make a swap out request
86
+ await requestSwapOut({
87
+ lnd,
88
+ ask: async (args, cbk) => {
89
+ if (args.name === 'tokens') {
90
+ return cbk({tokens: '10000'});
91
+ }
92
+
93
+ if (args.name === 'rate') {
94
+ return cbk({rate: '10'});
95
+ }
96
+
97
+ const swapRequest = messages.find(n => !!n.swap_request);
98
+
99
+ if (args.name === 'response') {
100
+ return await respondToSwapOut({
101
+ ask: (args, cbk) => {
102
+ if (args.name === 'rate') {
103
+ return cbk({rate: '100'});
104
+ }
105
+
106
+ if (args.name === 'req') {
107
+ return cbk({req: swapRequest.swap_request});
108
+ }
109
+
110
+ throw new Error('UnrecognizedQueryForResponse');
111
+ },
112
+ lnd: target.lnd,
113
+ logger: {
114
+ info: async message => {
115
+ if (!!message.response) {
116
+ return cbk({response: message.response});
117
+ }
118
+
119
+ // Transaction is funded, generate funding into a block
120
+ if (!!message.funding_transaction_id) {
121
+ return await target.generate({count});
122
+ }
123
+ },
124
+ },
125
+ });
126
+ }
127
+
128
+ if (args.name === 'ok') {
129
+ return cbk({ok: true});
130
+ }
131
+
132
+ throw new Error('UnrecognizedQueryForRequest');
133
+ },
134
+ logger: {
135
+ info: async message => {
136
+ if (message.broadcasting_tx_to_resolve_swap) {
137
+ await generate({count});
138
+ }
139
+
140
+ return messages.push(message);
141
+ },
142
+ },
143
+ });
144
+ } catch (err) {
145
+ strictSame(err, null, 'Expected no failure');
146
+ }
147
+
148
+ await kill({});
149
+
150
+ return end();
151
+ });
@@ -0,0 +1,152 @@
1
+ const asyncRetry = require('async/retry');
2
+ const {createChainAddress} = require('ln-service');
3
+ const {createInvoice} = require('ln-service');
4
+ const {getChannelBalance} = require('ln-service');
5
+ const {getChannels} = require('ln-service');
6
+ const {getMasterPublicKeys} = require('ln-service');
7
+ const {openChannel} = require('ln-service');
8
+ const {pay} = require('ln-service');
9
+ const {sendToChainAddress} = require('ln-service');
10
+ const {spawnLightningCluster} = require('ln-docker-daemons');
11
+ const {test} = require('@alexbosworth/tap');
12
+
13
+ const requestSwapOut = require('./../../swaps/request_swap_out');
14
+ const respondToSwapOut = require('./../../swaps/respond_to_swap_out_request');
15
+
16
+ const capacity = 1e6;
17
+ const count = 50;
18
+ const interval = 100;
19
+ const maturity = 100;
20
+ const size = 2;
21
+ const taprootDerivationPath = `m/86'/0'/0'`;
22
+ const times = 3000;
23
+ const tokens = 1e5;
24
+
25
+ // Start an offchain swap but use the claim path to sweep
26
+ test(`Swap with claim path`, async ({end, equal, strictSame}) => {
27
+ const {kill, nodes} = await spawnLightningCluster({size});
28
+
29
+ const [{generate, id, lnd}, target] = nodes;
30
+
31
+ const {keys} = await getMasterPublicKeys({lnd});
32
+
33
+ // Exit early when taproot is not supported
34
+ if (!keys.find(n => n.derivation_path === taprootDerivationPath)) {
35
+ await kill({});
36
+
37
+ return end();
38
+ }
39
+
40
+ try {
41
+ await generate({count: maturity});
42
+
43
+ // Setup a channel between the nodes
44
+ {
45
+ // Make a channel
46
+ await asyncRetry({interval, times}, async () => {
47
+ await openChannel({
48
+ lnd,
49
+ local_tokens: capacity,
50
+ partner_public_key: target.id,
51
+ partner_socket: target.socket,
52
+ });
53
+ });
54
+
55
+ await asyncRetry({interval, times}, async () => {
56
+ const {channels} = await getChannels({lnd, is_actve: true});
57
+
58
+ if (!!channels.length) {
59
+ return channels;
60
+ }
61
+
62
+ await generate({});
63
+
64
+ await sendToChainAddress({
65
+ lnd,
66
+ address: (await createChainAddress({lnd: target.lnd})).address,
67
+ tokens: capacity,
68
+ });
69
+
70
+ const balance = await getChannelBalance({lnd});
71
+
72
+ if (!balance.channel_balance) {
73
+ throw new Error('WaitingForChannelBalance');
74
+ }
75
+
76
+ throw new Error('WaitingForChannelOpen');
77
+ });
78
+
79
+ await target.generate({count: maturity});
80
+ }
81
+
82
+ // Collect request messages
83
+ const messages = [];
84
+
85
+ // Make a swap out request
86
+ await requestSwapOut({
87
+ lnd,
88
+ ask: async (args, cbk) => {
89
+ if (args.name === 'tokens') {
90
+ return cbk({tokens: '10000'});
91
+ }
92
+
93
+ if (args.name === 'rate') {
94
+ return cbk({rate: '10'});
95
+ }
96
+
97
+ const swapRequest = messages.find(n => !!n.swap_request);
98
+
99
+ if (args.name === 'response') {
100
+ return await respondToSwapOut({
101
+ ask: (args, cbk) => {
102
+ if (args.name === 'rate') {
103
+ return cbk({rate: '100'});
104
+ }
105
+
106
+ if (args.name === 'req') {
107
+ return cbk({req: swapRequest.swap_request});
108
+ }
109
+
110
+ throw new Error('UnrecognizedQueryForResponse');
111
+ },
112
+ lnd: target.lnd,
113
+ logger: {
114
+ info: async message => {
115
+ if (!!message.response) {
116
+ return cbk({response: message.response});
117
+ }
118
+
119
+ // Transaction is funded, generate funding into a block
120
+ if (!!message.funding_transaction_id) {
121
+ return await target.generate({count});
122
+ }
123
+ },
124
+ },
125
+ });
126
+ }
127
+
128
+ if (args.name === 'ok') {
129
+ return cbk({ok: true});
130
+ }
131
+
132
+ throw new Error('UnrecognizedQueryForRequest');
133
+ },
134
+ is_uncooperative: true,
135
+ logger: {
136
+ info: async message => {
137
+ if (message.broadcasting_tx_to_resolve_swap) {
138
+ await generate({count});
139
+ }
140
+
141
+ return messages.push(message);
142
+ },
143
+ },
144
+ });
145
+ } catch (err) {
146
+ strictSame(err, null, 'Expected no failure');
147
+ }
148
+
149
+ await kill({});
150
+
151
+ return end();
152
+ });
@@ -0,0 +1,164 @@
1
+ const asyncRetry = require('async/retry');
2
+ const {createChainAddress} = require('ln-service');
3
+ const {createInvoice} = require('ln-service');
4
+ const {getChannelBalance} = require('ln-service');
5
+ const {getChannels} = require('ln-service');
6
+ const {getMasterPublicKeys} = require('ln-service');
7
+ const {openChannel} = require('ln-service');
8
+ const {pay} = require('ln-service');
9
+ const {sendToChainAddress} = require('ln-service');
10
+ const {spawnLightningCluster} = require('ln-docker-daemons');
11
+ const {test} = require('@alexbosworth/tap');
12
+
13
+ const requestSwapOut = require('./../../swaps/request_swap_out');
14
+ const respondToSwapOut = require('./../../swaps/respond_to_swap_out_request');
15
+
16
+ const capacity = 1e6;
17
+ const count = 50;
18
+ const interval = 100;
19
+ const maturity = 100;
20
+ const size = 2;
21
+ const taprootDerivationPath = `m/86'/0'/0'`;
22
+ const times = 3000;
23
+ const tokens = 1e5;
24
+
25
+ // Start an offchain swap but do not cooperate and eventually force a refund
26
+ test(`Timeout a swap`, async ({end, equal, strictSame}) => {
27
+ const {kill, nodes} = await spawnLightningCluster({size});
28
+
29
+ const [{generate, id, lnd}, target] = nodes;
30
+
31
+ const {keys} = await getMasterPublicKeys({lnd});
32
+
33
+ // Exit early when taproot is not supported
34
+ if (!keys.find(n => n.derivation_path === taprootDerivationPath)) {
35
+ await kill({});
36
+
37
+ return end();
38
+ }
39
+
40
+ try {
41
+ await generate({count: maturity});
42
+
43
+ // Setup a channel between the nodes
44
+ {
45
+ // Make a channel
46
+ await asyncRetry({interval, times}, async () => {
47
+ await openChannel({
48
+ lnd,
49
+ local_tokens: capacity,
50
+ partner_public_key: target.id,
51
+ partner_socket: target.socket,
52
+ });
53
+ });
54
+
55
+ await asyncRetry({interval, times}, async () => {
56
+ const {channels} = await getChannels({lnd, is_actve: true});
57
+
58
+ if (!!channels.length) {
59
+ return channels;
60
+ }
61
+
62
+ await generate({});
63
+
64
+ await sendToChainAddress({
65
+ lnd,
66
+ address: (await createChainAddress({lnd: target.lnd})).address,
67
+ tokens: capacity,
68
+ });
69
+
70
+ const balance = await getChannelBalance({lnd});
71
+
72
+ if (!balance.channel_balance) {
73
+ throw new Error('WaitingForChannelBalance');
74
+ }
75
+
76
+ throw new Error('WaitingForChannelOpen');
77
+ });
78
+
79
+ await target.generate({count: maturity});
80
+ }
81
+
82
+ // Collect request messages
83
+ const messages = [];
84
+
85
+ // Make a swap out request
86
+ await requestSwapOut({
87
+ lnd,
88
+ ask: async (args, cbk) => {
89
+ if (args.name === 'tokens') {
90
+ return cbk({tokens: '10000'});
91
+ }
92
+
93
+ if (args.name === 'rate') {
94
+ return cbk({rate: '10'});
95
+ }
96
+
97
+ const swapRequest = messages.find(n => !!n.swap_request);
98
+
99
+ if (args.name === 'response') {
100
+ try {
101
+ return await respondToSwapOut({
102
+ ask: (args, cbk) => {
103
+ if (args.name === 'rate') {
104
+ return cbk({rate: '100'});
105
+ }
106
+
107
+ if (args.name === 'req') {
108
+ return cbk({req: swapRequest.swap_request});
109
+ }
110
+
111
+ throw new Error('UnrecognizedQueryForResponse');
112
+ },
113
+ lnd: target.lnd,
114
+ logger: {
115
+ info: async message => {
116
+ // Push chain forward to timeout
117
+ if (!!message.blocks_until_timeout) {
118
+ return await target.generate({});
119
+ }
120
+
121
+ if (!!message.response) {
122
+ return cbk({response: message.response});
123
+ }
124
+
125
+ // Transaction is funded, generate funding into a block
126
+ if (!!message.funding_transaction_id) {
127
+ return await target.generate({count});
128
+ }
129
+ },
130
+ },
131
+ });
132
+ } catch (err) {
133
+ strictSame(err, [503, 'SwapFailedViaTimeout']);
134
+
135
+ return;
136
+ }
137
+ }
138
+
139
+ if (args.name === 'ok') {
140
+ return cbk({ok: true});
141
+ }
142
+
143
+ throw new Error('UnrecognizedQueryForRequest');
144
+ },
145
+ is_avoiding_broadcast: true,
146
+ is_uncooperative: true,
147
+ logger: {
148
+ info: async message => {
149
+ if (message.broadcasting_tx_to_resolve_swap) {
150
+ await generate({count});
151
+ }
152
+
153
+ return messages.push(message);
154
+ },
155
+ },
156
+ });
157
+ } catch (err) {
158
+ strictSame(err, null, 'Expected no failure');
159
+ }
160
+
161
+ await kill({});
162
+
163
+ return end();
164
+ });