paid-services 3.15.1 → 3.15.4
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 +1 -1
- package/package.json +3 -3
- package/swaps/complete_on_to_off_swap.js +2 -3
- package/swaps/manage_swap.js +47 -9
- package/swaps/recover_response_to_swap_out.js +1 -5
- package/swaps/request_swap_out.js +22 -16
- package/swaps/respond_to_swap_out_request.js +2 -6
- package/test/integration/test_request_swap_out.js +152 -0
- package/test/integration/test_swap_with_claim_path.js +152 -0
- package/test/integration/test_swap_with_refund_path.js +164 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"ecpair": "2.0.1",
|
|
17
17
|
"goldengate": "11.2.1",
|
|
18
18
|
"invoices": "2.0.6",
|
|
19
|
-
"ln-service": "53.
|
|
19
|
+
"ln-service": "53.15.0",
|
|
20
20
|
"ln-sync": "3.12.0",
|
|
21
21
|
"psbt": "2.0.1",
|
|
22
22
|
"p2tr": "1.3.1",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"description": "Lightning Paid Services library",
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@alexbosworth/tap": "15.0.11",
|
|
28
|
-
"ln-docker-daemons": "2.2.
|
|
28
|
+
"ln-docker-daemons": "2.2.9",
|
|
29
29
|
"mock-lnd": "1.4.1",
|
|
30
30
|
"secp256k1": "4.0.3"
|
|
31
31
|
},
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
|
|
48
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"
|
|
49
49
|
},
|
|
50
|
-
"version": "3.15.
|
|
50
|
+
"version": "3.15.4"
|
|
51
51
|
}
|
|
@@ -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
|
-
|
|
683
|
+
() => {});
|
|
685
684
|
});
|
|
686
685
|
|
|
687
686
|
subTimeout.on('error', err => done(err));
|
package/swaps/manage_swap.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {
|
|
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,8 @@ 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'`;
|
|
12
|
+
const minConfirmations = {btc: 2, btcregtest: 3, btctestnet: 1};
|
|
10
13
|
const recoverRespondAction = 'recover-response';
|
|
11
14
|
const requestAction = 'request';
|
|
12
15
|
const respondAction = 'respond';
|
|
@@ -46,13 +49,25 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
46
49
|
return cbk();
|
|
47
50
|
},
|
|
48
51
|
|
|
52
|
+
// Look for a p2tr master public key to detect p2tr support
|
|
53
|
+
getMasterPublicKeys: ['validate', ({}, cbk) => {
|
|
54
|
+
return getMasterPublicKeys({lnd}, cbk);
|
|
55
|
+
}],
|
|
56
|
+
|
|
49
57
|
// Get the network to make sure this is on testnet
|
|
50
58
|
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
|
51
59
|
|
|
60
|
+
// Determine if taproot is supported or not
|
|
61
|
+
hasTr: ['getMasterPublicKeys', ({getMasterPublicKeys}, cbk) => {
|
|
62
|
+
const {keys} = getMasterPublicKeys;
|
|
63
|
+
|
|
64
|
+
return cbk(null, !!keys.find(n => n.derivation_path === bip86Path));
|
|
65
|
+
}],
|
|
66
|
+
|
|
52
67
|
// Select a swap action
|
|
53
|
-
selectAction: ['getNetwork', ({getNetwork}, cbk) => {
|
|
68
|
+
selectAction: ['getNetwork', 'hasTr', ({getNetwork, hasTr}, cbk) => {
|
|
54
69
|
// Check to make sure the network is supported
|
|
55
|
-
if (getNetwork.network !== allowedNetwork) {
|
|
70
|
+
if (!hasTr && getNetwork.network !== allowedNetwork) {
|
|
56
71
|
return cbk([501, 'CurrentlyUnsupportedChainForSwap']);
|
|
57
72
|
}
|
|
58
73
|
|
|
@@ -79,30 +94,53 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
79
94
|
}],
|
|
80
95
|
|
|
81
96
|
// Make swap request
|
|
82
|
-
makeRequest: ['selectAction', ({selectAction}, cbk) => {
|
|
97
|
+
makeRequest: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
|
|
83
98
|
if (selectAction !== requestAction) {
|
|
84
99
|
return cbk();
|
|
85
100
|
}
|
|
86
101
|
|
|
87
|
-
return requestSwapOut({
|
|
102
|
+
return requestSwapOut({
|
|
103
|
+
ask,
|
|
104
|
+
lnd,
|
|
105
|
+
logger,
|
|
106
|
+
min_confirmations: minConfirmations[getNetwork.network],
|
|
107
|
+
request: !hasTr ? request : undefined,
|
|
108
|
+
},
|
|
109
|
+
cbk);
|
|
88
110
|
}],
|
|
89
111
|
|
|
90
112
|
// Respond to swap request
|
|
91
|
-
makeResponse: ['selectAction', ({selectAction}, cbk) => {
|
|
113
|
+
makeResponse: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
|
|
92
114
|
if (selectAction !== respondAction) {
|
|
93
115
|
return cbk();
|
|
94
116
|
}
|
|
95
117
|
|
|
96
|
-
return respondToSwapOutRequest({
|
|
118
|
+
return respondToSwapOutRequest({
|
|
119
|
+
ask,
|
|
120
|
+
lnd,
|
|
121
|
+
logger,
|
|
122
|
+
request: !hasTr ? request : undefined,
|
|
123
|
+
},
|
|
124
|
+
cbk);
|
|
97
125
|
}],
|
|
98
126
|
|
|
99
127
|
// Recover responding to swap request
|
|
100
|
-
recoverResponse: [
|
|
128
|
+
recoverResponse: [
|
|
129
|
+
'hasTr',
|
|
130
|
+
'selectAction',
|
|
131
|
+
({hasTr, selectAction}, cbk) =>
|
|
132
|
+
{
|
|
101
133
|
if (selectAction !== recoverRespondAction) {
|
|
102
134
|
return cbk();
|
|
103
135
|
}
|
|
104
136
|
|
|
105
|
-
return recoverResponseToSwapOut({
|
|
137
|
+
return recoverResponseToSwapOut({
|
|
138
|
+
ask,
|
|
139
|
+
lnd,
|
|
140
|
+
logger,
|
|
141
|
+
request: !hasTr ? request : undefined,
|
|
142
|
+
},
|
|
143
|
+
cbk);
|
|
106
144
|
}],
|
|
107
145
|
|
|
108
146
|
// 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,30 @@ 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
|
-
|
|
29
|
+
[min_confirmations]: <Minimum Confirmations to Wait Number>
|
|
30
|
+
[request]: <Request Function>
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
@returns via cbk or Promise
|
|
31
34
|
*/
|
|
32
|
-
module.exports = (
|
|
35
|
+
module.exports = (args, cbk) => {
|
|
33
36
|
return new Promise((resolve, reject) => {
|
|
34
37
|
return asyncAuto({
|
|
35
38
|
// Check arguments
|
|
36
39
|
validate: cbk => {
|
|
37
|
-
if (!ask) {
|
|
40
|
+
if (!args.ask) {
|
|
38
41
|
return cbk([400, 'ExpectedAskFunctionToRequestSwapOut']);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
if (!lnd) {
|
|
44
|
+
if (!args.lnd) {
|
|
42
45
|
return cbk([400, 'ExpectedAuthenticatedLndToRequestSwapOut']);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
if (!logger) {
|
|
48
|
+
if (!args.logger) {
|
|
46
49
|
return cbk([400, 'ExpectedWinstonLoggerToRequestSwapOut']);
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -51,7 +54,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
51
54
|
|
|
52
55
|
// Ask for amount
|
|
53
56
|
askForTokens: ['validate', ({}, cbk) => {
|
|
54
|
-
return ask({
|
|
57
|
+
return args.ask({
|
|
55
58
|
default: defaultSwapAmount,
|
|
56
59
|
message: 'Amount to swap?',
|
|
57
60
|
name: 'tokens',
|
|
@@ -71,11 +74,11 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
71
74
|
}],
|
|
72
75
|
|
|
73
76
|
// Get the network name
|
|
74
|
-
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
|
77
|
+
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
|
|
75
78
|
|
|
76
79
|
// Ask for routing fee rate
|
|
77
80
|
askForRate: ['askForTokens', ({}, cbk) => {
|
|
78
|
-
return ask({
|
|
81
|
+
return args.ask({
|
|
79
82
|
default: defaultFeeRate,
|
|
80
83
|
message: 'Max routing fee rate for swap funds in parts per million?',
|
|
81
84
|
name: 'rate',
|
|
@@ -97,8 +100,8 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
97
100
|
// Make a swap request
|
|
98
101
|
makeRequest: ['askForTokens', ({askForTokens}, cbk) => {
|
|
99
102
|
return makeRequest({
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
is_external_solo_key: !!args.request,
|
|
104
|
+
lnd: args.lnd,
|
|
102
105
|
tokens: askForTokens,
|
|
103
106
|
},
|
|
104
107
|
cbk);
|
|
@@ -111,9 +114,9 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
111
114
|
'makeRequest',
|
|
112
115
|
({getNetwork, makeRequest}, cbk) =>
|
|
113
116
|
{
|
|
114
|
-
logger.info({swap_request: makeRequest.request});
|
|
117
|
+
args.logger.info({swap_request: makeRequest.request});
|
|
115
118
|
|
|
116
|
-
return ask({
|
|
119
|
+
return args.ask({
|
|
117
120
|
message: 'Response to swap request?',
|
|
118
121
|
name: 'response',
|
|
119
122
|
validate: response => {
|
|
@@ -152,7 +155,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
152
155
|
const fee = tokens - askForTokens;
|
|
153
156
|
const pricing = `Execution cost ${deposit}, plus liquidity fee ${fee}`;
|
|
154
157
|
|
|
155
|
-
return ask({
|
|
158
|
+
return args.ask({
|
|
156
159
|
default: true,
|
|
157
160
|
message: `Start swap ${timeout}? ${pricing}?`,
|
|
158
161
|
name: 'ok',
|
|
@@ -176,15 +179,18 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
176
179
|
|
|
177
180
|
const emitter = new EventEmitter();
|
|
178
181
|
|
|
179
|
-
emitter.on('update', update => logger.info(update));
|
|
182
|
+
emitter.on('update', update => args.logger.info(update));
|
|
180
183
|
|
|
181
184
|
return completeOffToOnSwap({
|
|
182
185
|
emitter,
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
is_avoiding_broadcast: args.is_avoiding_broadcast,
|
|
187
|
+
is_uncooperative: args.is_uncooperative,
|
|
188
|
+
lnd: args.lnd,
|
|
185
189
|
max_fee_deposit: defaultMaxFeeForDeposit,
|
|
186
190
|
max_fee_funding: askForTokens * askForRate / rateDenominator,
|
|
191
|
+
min_confirmations: args.min_confirmations,
|
|
187
192
|
recovery: makeRequest.recovery,
|
|
193
|
+
request: args.request,
|
|
188
194
|
response: getResponse,
|
|
189
195
|
},
|
|
190
196
|
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:
|
|
114
|
+
is_external_solo_key: !!request,
|
|
119
115
|
price: floor(tokens * askForRate / rateDenominator),
|
|
120
116
|
request: askForRequest,
|
|
121
117
|
},
|
|
@@ -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
|
|
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
|
+
min_confirmations: 3,
|
|
144
|
+
});
|
|
145
|
+
} catch (err) {
|
|
146
|
+
strictSame(err, null, 'Expected no failure');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await kill({});
|
|
150
|
+
|
|
151
|
+
return end();
|
|
152
|
+
});
|
|
@@ -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
|
+
});
|