paid-services 4.0.5 → 4.2.0
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 +8 -0
- package/groups/assemble/assemble_unsigned_psbt.js +1 -0
- package/groups/assemble/coordinate_group.js +13 -1
- package/groups/assemble_channel_group.js +11 -2
- package/groups/create_group_channel.js +20 -1
- package/groups/members/partners_from_members.js +6 -3
- package/package.json +6 -6
- package/swaps/complete_off_to_on_swap.js +3 -2
- package/swaps/complete_on_to_off_swap.js +9 -2
- package/swaps/manage_swap.js +5 -1
- package/swaps/output_script_for_address.js +55 -0
- package/swaps/request_swap_out.js +7 -1
- package/test/integration/test_group_noninteractive.js +1 -0
- package/test/integration/test_group_sort.js +202 -0
- package/test/integration/test_request_swap_out.js +1 -1
- package/test/integration/test_swap_with_claim_path.js +1 -1
- package/test/integration/test_swap_with_refund_path.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 4.2.0
|
|
4
|
+
|
|
5
|
+
- `manageSwap`: Add support for P2TR output addresses
|
|
6
|
+
|
|
7
|
+
## Version 4.1.0
|
|
8
|
+
|
|
9
|
+
- `createGroupChannel`: Add `members` to restrict and order group membership
|
|
10
|
+
|
|
3
11
|
## Version 4.0.5
|
|
4
12
|
|
|
5
13
|
- `manageTrades`: Fix connection to seller failing
|
|
@@ -25,6 +25,7 @@ const minGroupCount = 2;
|
|
|
25
25
|
const now = () => new Date().toISOString();
|
|
26
26
|
const staleDate = () => new Date(Date.now() - (1000 * 60 * 10)).toISOString();
|
|
27
27
|
const typeGroupId = '1';
|
|
28
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
28
29
|
|
|
29
30
|
/** Coordinate channel group
|
|
30
31
|
|
|
@@ -33,6 +34,7 @@ const typeGroupId = '1';
|
|
|
33
34
|
count: <Group Members Count Number>
|
|
34
35
|
identity: <Coordinator Identity Public Key Hex String>
|
|
35
36
|
lnd: <Authenticated LND API Object>
|
|
37
|
+
[members]: [<Member Node Id Public Key Hex String>]
|
|
36
38
|
rate: <Chain Fee Rate Number>
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -87,7 +89,7 @@ const typeGroupId = '1';
|
|
|
87
89
|
// All members have submitted their partial signatures
|
|
88
90
|
@event 'signed'
|
|
89
91
|
*/
|
|
90
|
-
module.exports = ({capacity, count, identity, lnd, rate}) => {
|
|
92
|
+
module.exports = ({capacity, count, identity, lnd, members, rate}) => {
|
|
91
93
|
if (count < minGroupCount) {
|
|
92
94
|
throw new Error('ExpectedHigherGroupMembersCountToCoordinateGroup');
|
|
93
95
|
}
|
|
@@ -100,8 +102,13 @@ module.exports = ({capacity, count, identity, lnd, rate}) => {
|
|
|
100
102
|
throw new Error('ExpectedAuthenticatedLndToCoordinateGroup');
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
if (!!members && uniq(members).length !== count) {
|
|
106
|
+
throw new Error('ExpectedCompleteSetOfAllowedMembers');
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
// Instantiate the group with self as a member
|
|
104
110
|
const group = {
|
|
111
|
+
allowed: members,
|
|
105
112
|
connected: [],
|
|
106
113
|
emitter: new EventEmitter(),
|
|
107
114
|
members: [{id: identity}],
|
|
@@ -152,6 +159,11 @@ module.exports = ({capacity, count, identity, lnd, rate}) => {
|
|
|
152
159
|
return;
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
// Exit early when group member is not allowed
|
|
163
|
+
if (!!group.allowed && !group.allowed.includes(req.from)) {
|
|
164
|
+
return res.failure([403, 'AccessDeniedToGroup']);
|
|
165
|
+
}
|
|
166
|
+
|
|
155
167
|
// Emit event that someone is joining
|
|
156
168
|
if (!group.members.find(n => n.id === req.from)) {
|
|
157
169
|
group.emitter.emit('joining', {id: req.from});
|
|
@@ -27,6 +27,7 @@ const times = 2 * 60 * 10;
|
|
|
27
27
|
ecp: <ECPair Library Object>
|
|
28
28
|
identity: <Coordinator Identity Public Key Hex String>
|
|
29
29
|
lnd: <Authenticated LND API Object>
|
|
30
|
+
[members]: [<Member Identity Public Key Hex String>]
|
|
30
31
|
rate: <Chain Fee Tokens Per VByte Number>
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -68,12 +69,20 @@ const times = 2 * 60 * 10;
|
|
|
68
69
|
@event 'signed'
|
|
69
70
|
{}
|
|
70
71
|
*/
|
|
71
|
-
module.exports = ({capacity, count, ecp, identity, lnd, rate}) => {
|
|
72
|
+
module.exports = ({capacity, count, ecp, identity, lnd, members, rate}) => {
|
|
72
73
|
if (count < minGroupCount) {
|
|
73
74
|
throw new Error('ExpectedHigherGroupCountToAssembleChannelGroup');
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
const coordinator = coordinateGroup({
|
|
77
|
+
const coordinator = coordinateGroup({
|
|
78
|
+
capacity,
|
|
79
|
+
count,
|
|
80
|
+
identity,
|
|
81
|
+
lnd,
|
|
82
|
+
members,
|
|
83
|
+
rate,
|
|
84
|
+
});
|
|
85
|
+
|
|
77
86
|
const emitter = new EventEmitter();
|
|
78
87
|
const pending = {};
|
|
79
88
|
|
|
@@ -10,7 +10,10 @@ const tinysecp = require('tiny-secp256k1');
|
|
|
10
10
|
const assembleChannelGroup = require('./assemble_channel_group');
|
|
11
11
|
|
|
12
12
|
const halfOf = n => n / 2;
|
|
13
|
+
const {isArray} = Array;
|
|
14
|
+
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
13
15
|
const isOdd = n => !!(n % 2);
|
|
16
|
+
const isValidMembersCount = (n, count) => !n.length || n.length === count - 1;
|
|
14
17
|
const join = arr => arr.join(', ');
|
|
15
18
|
const maxGroupSize = 420;
|
|
16
19
|
const minChannelSize = 2e4;
|
|
@@ -18,13 +21,14 @@ const minGroupSize = 2;
|
|
|
18
21
|
const niceName = ({alias, id}) => `${alias} ${id}`.trim();
|
|
19
22
|
const signPsbtEndpoint = '/walletrpc.WalletKit/SignPsbt';
|
|
20
23
|
|
|
21
|
-
/**
|
|
24
|
+
/** Create a channel group
|
|
22
25
|
|
|
23
26
|
{
|
|
24
27
|
capacity: <Channel Capacity Tokens Number>
|
|
25
28
|
count: <Group Member Count Number>
|
|
26
29
|
lnd: <Authenticated LND API Object>
|
|
27
30
|
logger: <Winston Logger Object>
|
|
31
|
+
[members]: [<Member Identity Public Key Hex String>]
|
|
28
32
|
rate: <Opening Chain Fee Tokens Per VByte Rate Number>
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -69,6 +73,18 @@ module.exports = (args, cbk) => {
|
|
|
69
73
|
return cbk([400, 'ExpectedWinstonLoggerToCreateChannelGroup']);
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
if (!isArray(args.members)) {
|
|
77
|
+
return cbk([400, 'ExpectedArrayOfGroupMembersToCreateChannelGroup']);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isValidMembersCount(args.members, args.count)) {
|
|
81
|
+
return cbk([400, 'ExpectedCompleteSetOfAllowedGroupMembers']);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!!args.members.filter(n => !isPublicKey(n)).length) {
|
|
85
|
+
return cbk([400, 'ExpectedNodeIdentityPublicKeysForChannelGroup']);
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
if (!args.rate) {
|
|
73
89
|
return cbk([400, 'ExpectedOpeningFeeRateToCreateChannelGroup']);
|
|
74
90
|
}
|
|
@@ -121,12 +137,15 @@ module.exports = (args, cbk) => {
|
|
|
121
137
|
'getIdentity',
|
|
122
138
|
({ecp, getIdentity}, cbk) =>
|
|
123
139
|
{
|
|
140
|
+
const members = [getIdentity.public_key].concat(args.members);
|
|
141
|
+
|
|
124
142
|
const coordinate = assembleChannelGroup({
|
|
125
143
|
ecp,
|
|
126
144
|
capacity: args.capacity,
|
|
127
145
|
count: args.count,
|
|
128
146
|
identity: getIdentity.public_key,
|
|
129
147
|
lnd: args.lnd,
|
|
148
|
+
members: !!args.members.length ? members : undefined,
|
|
130
149
|
rate: args.rate,
|
|
131
150
|
});
|
|
132
151
|
|
|
@@ -4,6 +4,7 @@ const minGroupCount = 2;
|
|
|
4
4
|
|
|
5
5
|
{
|
|
6
6
|
group: {
|
|
7
|
+
allowed: [<Allowed Public Key Id Hex String>]
|
|
7
8
|
ids: [<Public Key Id Hex String>]
|
|
8
9
|
}
|
|
9
10
|
id: <Identity Public Key Hex String>
|
|
@@ -21,11 +22,13 @@ module.exports = ({group, id}) => {
|
|
|
21
22
|
return {inbound: group.ids.find(n => n !== id)};
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
25
|
+
const ids = group.allowed || group.ids;
|
|
26
|
+
|
|
27
|
+
const [first] = ids;
|
|
28
|
+
const reversed = ids.slice().reverse();
|
|
26
29
|
|
|
27
30
|
const [last] = reversed;
|
|
28
|
-
const [, next] =
|
|
31
|
+
const [, next] = ids.slice(ids.indexOf(id));
|
|
29
32
|
|
|
30
33
|
const [, previous] = reversed.slice(reversed.indexOf(id));
|
|
31
34
|
|
package/package.json
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"bolt01": "1.2.5",
|
|
15
15
|
"bolt07": "1.8.2",
|
|
16
16
|
"ecpair": "2.1.0",
|
|
17
|
-
"goldengate": "
|
|
18
|
-
"invoices": "2.2.
|
|
19
|
-
"ln-service": "54.
|
|
20
|
-
"ln-sync": "4.0
|
|
17
|
+
"goldengate": "12.0.0",
|
|
18
|
+
"invoices": "2.2.2",
|
|
19
|
+
"ln-service": "54.8.0",
|
|
20
|
+
"ln-sync": "4.1.0",
|
|
21
21
|
"psbt": "2.7.1",
|
|
22
22
|
"p2tr": "1.3.2",
|
|
23
23
|
"tiny-secp256k1": "2.2.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": "
|
|
28
|
+
"ln-docker-daemons": "4.0.2",
|
|
29
29
|
"mock-lnd": "1.4.4",
|
|
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/swaps/*.js test/services/*.js test/trades/*.js"
|
|
49
49
|
},
|
|
50
|
-
"version": "4.0
|
|
50
|
+
"version": "4.2.0"
|
|
51
51
|
}
|
|
@@ -65,6 +65,7 @@ const encodeAddress = (prefix, data) => bech32m.encode(prefix, data);
|
|
|
65
65
|
const externalKeyAsOutputScript = key => `5120${key}`;
|
|
66
66
|
const family = 805;
|
|
67
67
|
const {floor} = Math;
|
|
68
|
+
const format = 'p2tr';
|
|
68
69
|
const {from} = Buffer;
|
|
69
70
|
const {fromHex} = Transaction;
|
|
70
71
|
const fuzzBlocks = 100;
|
|
@@ -79,7 +80,7 @@ const {min} = Math;
|
|
|
79
80
|
const minTokens = 10000;
|
|
80
81
|
const minBlockMs = 1000 * 60;
|
|
81
82
|
const pollInterval = {btcregtest: 100};
|
|
82
|
-
const ppmRate = (fee, total) => fee * 1e6 / total
|
|
83
|
+
const ppmRate = (fee, total) => fee * 1e6 / total;
|
|
83
84
|
const preimageByteLength = 32;
|
|
84
85
|
const pubKeyAsInternalKey = key => Buffer.from(key).slice(1).toString('hex');
|
|
85
86
|
const pushSecret = asyncTimeout(releaseSwapOutSecret, 1000 * 30);
|
|
@@ -155,7 +156,7 @@ module.exports = (args, cbk) => {
|
|
|
155
156
|
return cbk(null, {address: args.sweep_address});
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
return createChainAddress({lnd: args.lnd}, cbk);
|
|
159
|
+
return createChainAddress({format, lnd: args.lnd}, cbk);
|
|
159
160
|
}],
|
|
160
161
|
|
|
161
162
|
// Get the current chain fee rate for the sweep fee rate calculation
|
|
@@ -42,9 +42,11 @@ const {typePayMetadata} = require('./swap_field_types');
|
|
|
42
42
|
const bufferAsHex = buffer => buffer.toString('hex');
|
|
43
43
|
const defaultMaxFundingConfirmationDelay = 12;
|
|
44
44
|
const defaultMinDelta = 60;
|
|
45
|
+
const estimatedSize = 150;
|
|
45
46
|
const family = 805;
|
|
46
47
|
const flatten = arr => [].concat(...arr);
|
|
47
48
|
const {floor} = Math;
|
|
49
|
+
const format = 'p2tr';
|
|
48
50
|
const {from} = Buffer;
|
|
49
51
|
const {fromHex} = Transaction;
|
|
50
52
|
const fuzzBlocks = 100;
|
|
@@ -126,7 +128,7 @@ module.exports = (args, cbk) => {
|
|
|
126
128
|
|
|
127
129
|
// Get an address to sweep out to
|
|
128
130
|
getSweepAddress: ['validate', ({}, cbk) => {
|
|
129
|
-
return createChainAddress({lnd: args.lnd}, cbk);
|
|
131
|
+
return createChainAddress({format, lnd: args.lnd}, cbk);
|
|
130
132
|
}],
|
|
131
133
|
|
|
132
134
|
// Get the encryption key to decode the recovery secrets
|
|
@@ -370,6 +372,11 @@ module.exports = (args, cbk) => {
|
|
|
370
372
|
});
|
|
371
373
|
|
|
372
374
|
const refunds = uniqBy(feeRates, 'rate').map(({rate, height}) => {
|
|
375
|
+
// Exit early when there is nothing to recover
|
|
376
|
+
if (rate * estimatedSize > recoveryDetails.tokens) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
373
380
|
return taprootRefundTransaction({
|
|
374
381
|
ecp,
|
|
375
382
|
block_height: height,
|
|
@@ -388,7 +395,7 @@ module.exports = (args, cbk) => {
|
|
|
388
395
|
});
|
|
389
396
|
});
|
|
390
397
|
|
|
391
|
-
return cbk(null, refunds.map(n => n.transaction));
|
|
398
|
+
return cbk(null, refunds.filter(n => !!n).map(n => n.transaction));
|
|
392
399
|
}],
|
|
393
400
|
|
|
394
401
|
// Sign the refund transactions
|
package/swaps/manage_swap.js
CHANGED
|
@@ -13,6 +13,7 @@ const {returnResult} = require('asyncjs-util');
|
|
|
13
13
|
const decodeOffToOnRequest = require('./decode_off_to_on_request');
|
|
14
14
|
const decodeOffToOnRestart = require('./decode_off_to_on_restart');
|
|
15
15
|
const {decodeTrade} = require('./../trades');
|
|
16
|
+
const outputScriptForAddress = require('./output_script_for_address');
|
|
16
17
|
const recoverResponseToSwapOut = require('./recover_response_to_swap_out');
|
|
17
18
|
const requestSwapOut = require('./request_swap_out');
|
|
18
19
|
const respondToSwapOutRequest = require('./respond_to_swap_out_request');
|
|
@@ -310,7 +311,10 @@ module.exports = (args, cbk) => {
|
|
|
310
311
|
}
|
|
311
312
|
|
|
312
313
|
try {
|
|
313
|
-
return !!
|
|
314
|
+
return !!outputScriptForAddress({
|
|
315
|
+
address: input,
|
|
316
|
+
network: getNetwork.bitcoinjs,
|
|
317
|
+
});
|
|
314
318
|
} catch (err) {
|
|
315
319
|
return 'Unsupported on-chain address format';
|
|
316
320
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const {address} = require('bitcoinjs-lib');
|
|
2
|
+
const {networks} = require('bitcoinjs-lib');
|
|
3
|
+
|
|
4
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
5
|
+
const byteLengthForP2tr = 32;
|
|
6
|
+
const {fromBech32} = address;
|
|
7
|
+
const prefixForP2tr = '5120';
|
|
8
|
+
const toOutputScript = (a, n) => address.toOutputScript(a, n).toString('hex');
|
|
9
|
+
const versionTaproot = 1;
|
|
10
|
+
|
|
11
|
+
/** Get the output script for an address
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
address: <Address String>
|
|
15
|
+
network: <BitcoinJs Network Name String>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@throws
|
|
19
|
+
<Error>
|
|
20
|
+
|
|
21
|
+
@returns
|
|
22
|
+
{
|
|
23
|
+
script: <Output Script Hex String>
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
module.exports = ({address, network}) => {
|
|
27
|
+
try {
|
|
28
|
+
return {
|
|
29
|
+
script: bufferAsHex(toOutputScript(address, networks[network])),
|
|
30
|
+
};
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// Exit early when this is not a bech32 address
|
|
33
|
+
try {
|
|
34
|
+
fromBech32(address);
|
|
35
|
+
} catch (_) {
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const {data, prefix, version} = fromBech32(address);
|
|
40
|
+
|
|
41
|
+
if (prefix !== networks[network].bech32) {
|
|
42
|
+
throw new Error('InvalidNetworkToConvertAddressToOutputScript');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (version !== versionTaproot) {
|
|
46
|
+
throw new Error('UnexpectedVersionOfAddressToConverToOutputScript');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (data.length !== byteLengthForP2tr) {
|
|
50
|
+
throw new Error('UnexpectedByteLengthForPayToTaprootAddress');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {script: `${prefixForP2tr}${bufferAsHex(data)}`};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -31,6 +31,7 @@ const defaultFeeRate = 5000;
|
|
|
31
31
|
const defaultMaxAmount = 21e14;
|
|
32
32
|
const defaultMaxFeeForDeposit = 1337;
|
|
33
33
|
const defaultSwapAmount = 2500000;
|
|
34
|
+
const feeAsPpm = (fee, total) => Math.ceil(fee * 1e6 / total);
|
|
34
35
|
const findRecord = (r, type) => (r.find(n => n.type === type) || {}).value;
|
|
35
36
|
const {floor} = Math;
|
|
36
37
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
@@ -421,14 +422,19 @@ module.exports = (args, cbk) => {
|
|
|
421
422
|
|
|
422
423
|
const deposit = mtokensAsTokens(response.deposit_mtokens);
|
|
423
424
|
|
|
425
|
+
const combinedFee = tokens - askForTokens + deposit;
|
|
424
426
|
const serviceFee = tokensAsBigUnit(deposit);
|
|
425
427
|
const totalFee = tokensAsBigUnit(tokens - askForTokens + deposit);
|
|
426
428
|
|
|
429
|
+
const ppmTotal = feeAsPpm(combinedFee, askForTokens);
|
|
430
|
+
|
|
431
|
+
const feeInfo = `total fee is ${totalFee}, est PPM ${ppmTotal}`
|
|
432
|
+
|
|
427
433
|
return cbk(null, {
|
|
428
434
|
deposit,
|
|
429
435
|
fee: tokens - askForTokens,
|
|
430
436
|
incoming_peer: response.incoming_peer,
|
|
431
|
-
pricing: `Execution cost ${serviceFee},
|
|
437
|
+
pricing: `Execution cost ${serviceFee}, ${feeInfo}`,
|
|
432
438
|
rate: rateForFee(askForTokens, deposit + (tokens - askForTokens)),
|
|
433
439
|
timeout: response.timeout,
|
|
434
440
|
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const {once} = require('events');
|
|
2
|
+
|
|
3
|
+
const {addPeer} = require('ln-service');
|
|
4
|
+
const asyncMap = require('async/map');
|
|
5
|
+
const asyncRetry = require('async/retry');
|
|
6
|
+
const {createChainAddress} = require('ln-service');
|
|
7
|
+
const {getChainTransactions} = require('ln-service');
|
|
8
|
+
const {getChannels} = require('ln-service');
|
|
9
|
+
const {getNetwork} = require('ln-sync');
|
|
10
|
+
const {getUtxos} = require('ln-service');
|
|
11
|
+
const {networks} = require('bitcoinjs-lib');
|
|
12
|
+
const {sendToChainAddress} = require('ln-service');
|
|
13
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
14
|
+
const {test} = require('@alexbosworth/tap');
|
|
15
|
+
const tinysecp = require('tiny-secp256k1');
|
|
16
|
+
|
|
17
|
+
const assembleChannelGroup = require('./../../groups/assemble_channel_group');
|
|
18
|
+
const {confirmIncomingChannel} = require('./../../groups/funding');
|
|
19
|
+
const {getGroupDetails} = require('./../../groups/p2p');
|
|
20
|
+
const joinChannelGroup = require('./../../groups/join_channel_group');
|
|
21
|
+
|
|
22
|
+
const capacity = 1e5;
|
|
23
|
+
const count = 102;
|
|
24
|
+
const feeRate = 1;
|
|
25
|
+
const interval = 10;
|
|
26
|
+
const size = 4;
|
|
27
|
+
const tokens = 1e6;
|
|
28
|
+
const times = 2000;
|
|
29
|
+
|
|
30
|
+
// Make a joint transaction channel group with a sort defined
|
|
31
|
+
test(`Setup sorted channel group`, async ({end, equal, strictSame}) => {
|
|
32
|
+
const ecp = (await import('ecpair')).ECPairFactory(tinysecp);
|
|
33
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
34
|
+
|
|
35
|
+
const [control, target, remote, extra] = nodes;
|
|
36
|
+
|
|
37
|
+
const {generate, lnd} = control;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Setup the cluster of nodes to have funds and be connected
|
|
41
|
+
|
|
42
|
+
// Make some funds for control
|
|
43
|
+
await generate({count});
|
|
44
|
+
|
|
45
|
+
// Get the bitcoinjs network
|
|
46
|
+
const network = networks[(await getNetwork({lnd})).bitcoinjs];
|
|
47
|
+
|
|
48
|
+
// Create a target chain address
|
|
49
|
+
const targetAddress = await createChainAddress({lnd: target.lnd});
|
|
50
|
+
|
|
51
|
+
// Send coins to target
|
|
52
|
+
await sendToChainAddress({lnd, tokens, address: targetAddress.address});
|
|
53
|
+
|
|
54
|
+
// Create a remote chain address
|
|
55
|
+
const remoteAddress = await createChainAddress({lnd: remote.lnd});
|
|
56
|
+
|
|
57
|
+
// Create an address on the extra node
|
|
58
|
+
const extraAddress = await createChainAddress({lnd: extra.lnd});
|
|
59
|
+
|
|
60
|
+
// Send coins to remote
|
|
61
|
+
await sendToChainAddress({lnd, tokens, address: remoteAddress.address});
|
|
62
|
+
|
|
63
|
+
// Send coins to extra
|
|
64
|
+
await sendToChainAddress({lnd, tokens, address: extraAddress.address});
|
|
65
|
+
|
|
66
|
+
// Wait for funds to arrive
|
|
67
|
+
await asyncRetry({interval, times}, async () => {
|
|
68
|
+
await generate({});
|
|
69
|
+
|
|
70
|
+
const {transactions} = await getChainTransactions({lnd});
|
|
71
|
+
|
|
72
|
+
if (!!transactions.filter(n => !n.is_confirmed).length) {
|
|
73
|
+
throw new Error('TransactionsAreUnconfirmed');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Wait for UTXOs to be confirmed
|
|
78
|
+
await asyncRetry({interval, times}, async () => {
|
|
79
|
+
const remoteUtxos = await getUtxos({lnd: remote.lnd});
|
|
80
|
+
const targetUtxos = await getUtxos({lnd: target.lnd});
|
|
81
|
+
|
|
82
|
+
if (!targetUtxos.utxos.filter(n => !!n.confirmation_count).length) {
|
|
83
|
+
throw new Error('ExpectedConfirmedUtxoOnTarget');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!remoteUtxos.utxos.filter(n => !!n.confirmation_count).length) {
|
|
87
|
+
throw new Error('ExpectedConfirmUtxoOnRemote');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Connect control to target
|
|
92
|
+
await addPeer({lnd, public_key: target.id, socket: target.socket});
|
|
93
|
+
|
|
94
|
+
// Connect target to remote
|
|
95
|
+
await addPeer({
|
|
96
|
+
lnd: target.lnd,
|
|
97
|
+
public_key: remote.id,
|
|
98
|
+
socket: remote.socket,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Connect remote to control
|
|
102
|
+
await addPeer({
|
|
103
|
+
lnd: remote.lnd,
|
|
104
|
+
public_key: control.id,
|
|
105
|
+
socket: control.socket,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Connect extra to the other nodes
|
|
109
|
+
await asyncMap([control, target, remote], async ({id, socket}) => {
|
|
110
|
+
await addPeer({socket, lnd: extra.lnd, public_key: id});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Start Group Coordination
|
|
114
|
+
|
|
115
|
+
// Start the coordination
|
|
116
|
+
const assemble = assembleChannelGroup({
|
|
117
|
+
capacity,
|
|
118
|
+
ecp,
|
|
119
|
+
count: nodes.length,
|
|
120
|
+
identity: control.id,
|
|
121
|
+
lnd: control.lnd,
|
|
122
|
+
members: [control.id, extra.id, remote.id, target.id],
|
|
123
|
+
rate: feeRate,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const events = {};
|
|
127
|
+
|
|
128
|
+
assemble.events.once('broadcast', n => events.broadcast = n);
|
|
129
|
+
assemble.events.once('filled', n => events.filled = n);
|
|
130
|
+
|
|
131
|
+
// Target, remote, and extra join the group
|
|
132
|
+
const joins = await asyncMap([target, remote, extra], async node => {
|
|
133
|
+
const group = await getGroupDetails({
|
|
134
|
+
coordinator: control.id,
|
|
135
|
+
id: assemble.id,
|
|
136
|
+
lnd: node.lnd,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const join = joinChannelGroup({
|
|
140
|
+
capacity: group.capacity,
|
|
141
|
+
coordinator: control.id,
|
|
142
|
+
count: group.count,
|
|
143
|
+
id: assemble.id,
|
|
144
|
+
lnd: node.lnd,
|
|
145
|
+
rate: group.rate,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const [{inbound, outbound}] = await once(join, 'peering');
|
|
149
|
+
|
|
150
|
+
switch (node.id) {
|
|
151
|
+
case (extra.id):
|
|
152
|
+
strictSame(inbound, control.id, 'Extra inbound is control');
|
|
153
|
+
strictSame(outbound, remote.id, 'Extra outbound is remote');
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case (remote.id):
|
|
157
|
+
strictSame(inbound, extra.id, 'Remote inbound is extra');
|
|
158
|
+
strictSame(outbound, target.id, 'Remote outbound is target');
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case (target.id):
|
|
162
|
+
strictSame(inbound, remote.id, 'Target inbound is remote');
|
|
163
|
+
strictSame(outbound, control.id, 'Target outbound is control');
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
default:
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
strictSame(!!inbound, true, 'Received inbound peer');
|
|
171
|
+
strictSame(!!outbound, true, 'Received outbound peer');
|
|
172
|
+
|
|
173
|
+
const [tx] = await once(join, 'end');
|
|
174
|
+
|
|
175
|
+
return tx;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Transaction ids of the open should be returned
|
|
179
|
+
const ids = joins.map(n => n.id);
|
|
180
|
+
|
|
181
|
+
// Finished, wait for the channels to activate
|
|
182
|
+
await generate({count});
|
|
183
|
+
|
|
184
|
+
await asyncRetry({interval, times}, async () => {
|
|
185
|
+
const {channels} = await getChannels({lnd, is_active: true});
|
|
186
|
+
|
|
187
|
+
if (!channels.length) {
|
|
188
|
+
throw new Error('ExpectedChannelActivation');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
strictSame(events.broadcast.id.length, 64, 'Got broadcast tx id');
|
|
193
|
+
strictSame(!!events.broadcast.transaction, true, 'Got broadcast tx');
|
|
194
|
+
strictSame(events.filled.ids.length, nodes.length, 'Got filled event');
|
|
195
|
+
} catch (err) {
|
|
196
|
+
strictSame(err, null, 'Expected no failure');
|
|
197
|
+
} finally {
|
|
198
|
+
await kill({});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return end();
|
|
202
|
+
});
|
|
@@ -53,7 +53,7 @@ test(`Start offchain swap`, async ({end, equal, strictSame}) => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
await asyncRetry({interval, times}, async () => {
|
|
56
|
-
const {channels} = await getChannels({lnd,
|
|
56
|
+
const {channels} = await getChannels({lnd, is_active: true});
|
|
57
57
|
|
|
58
58
|
if (!!channels.length) {
|
|
59
59
|
return channels;
|
|
@@ -53,7 +53,7 @@ test(`Swap with claim path`, async ({end, equal, strictSame}) => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
await asyncRetry({interval, times}, async () => {
|
|
56
|
-
const {channels} = await getChannels({lnd,
|
|
56
|
+
const {channels} = await getChannels({lnd, is_active: true});
|
|
57
57
|
|
|
58
58
|
if (!!channels.length) {
|
|
59
59
|
return channels;
|
|
@@ -58,7 +58,7 @@ test(`Timeout a swap`, async ({end, equal, strictSame}) => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
await asyncRetry({interval, times}, async () => {
|
|
61
|
-
const {channels} = await getChannels({lnd,
|
|
61
|
+
const {channels} = await getChannels({lnd, is_active: true});
|
|
62
62
|
|
|
63
63
|
if (!!channels.length) {
|
|
64
64
|
return channels;
|