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 CHANGED
@@ -1,6 +1,10 @@
1
1
  # Versions
2
2
 
3
- ## Version 4.0.4
3
+ ## Version 4.1.0
4
+
5
+ - `createGroupChannel`: Add `members` to restrict and order group membership
6
+
7
+ ## Version 4.0.5
4
8
 
5
9
  - `manageTrades`: Fix connection to seller failing
6
10
 
@@ -142,6 +142,7 @@ module.exports = ({capacity, proposed, rate}, cbk) => {
142
142
  .map(utxo => ({
143
143
  id: utxo.transaction_id,
144
144
  non_witness_utxo: utxo.non_witness_utxo,
145
+ sequence: Number(),
145
146
  vout: utxo.transaction_vout,
146
147
  witness_utxo: utxo.witness_utxo,
147
148
  }))
@@ -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({capacity, count, identity, lnd, rate});
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
- /** Join a channel group
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 [first] = group.ids;
25
- const reversed = group.ids.slice().reverse();
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] = group.ids.slice(group.ids.indexOf(id));
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.0",
19
- "ln-service": "54.2.5",
20
- "ln-sync": "4.0.4",
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.4",
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.4"
50
+ "version": "4.1.0"
51
51
  }
@@ -82,6 +82,7 @@ test(`Setup joint channel group`, async ({end, equal, strictSame}) => {
82
82
  count: nodes.length,
83
83
  lnd: control.lnd,
84
84
  logger: {info: line => createLog.push(line)},
85
+ members: [],
85
86
  rate: feeRate,
86
87
  });
87
88
  },
@@ -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
+ });