paid-services 4.0.4 → 4.1.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 +5 -1
- 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 +5 -5
- package/test/integration/test_group_noninteractive.js +1 -0
- package/test/integration/test_group_sort.js +202 -0
package/CHANGELOG.md
CHANGED
|
@@ -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
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"bolt07": "1.8.2",
|
|
16
16
|
"ecpair": "2.1.0",
|
|
17
17
|
"goldengate": "11.4.0",
|
|
18
|
-
"invoices": "2.2.
|
|
19
|
-
"ln-service": "54.
|
|
20
|
-
"ln-sync": "4.0.
|
|
18
|
+
"invoices": "2.2.2",
|
|
19
|
+
"ln-service": "54.3.1",
|
|
20
|
+
"ln-sync": "4.0.5",
|
|
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": "3.1.
|
|
28
|
+
"ln-docker-daemons": "3.1.5",
|
|
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.1.0"
|
|
51
51
|
}
|
|
@@ -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
|
+
});
|