paid-services 3.19.2 → 3.20.2

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,5 +1,17 @@
1
1
  # Versions
2
2
 
3
+ ## Version 3.20.2
4
+
5
+ - `manageGroupJoin`: Allow opening a pair channel when funds are below capacity
6
+
7
+ ## Version 3.20.1
8
+
9
+ - `manageTrades`: Fix seller connection when not peered
10
+
11
+ ## Version 3.20.0
12
+
13
+ - `manageGroupJoin`: Add support for 2 person groups
14
+
3
15
  ## Version 3.19.2
4
16
 
5
17
  - `manageSwap`: Add support for inbound peer constraint
@@ -11,7 +11,7 @@ const isOdd = n => !!(n % 2);
11
11
  const maxChannelSize = 21e14;
12
12
  const minChannelSize = 2e4;
13
13
  const maxGroupSize = 420;
14
- const minGroupSize = 3;
14
+ const minGroupSize = 2;
15
15
  const {round} = Math;
16
16
 
17
17
  /** Ask for new group details to create a group
@@ -150,7 +150,7 @@ module.exports = ({ask, lnd}, cbk) => {
150
150
  {
151
151
  return cbk(null, {
152
152
  capacity: askForCapacity,
153
- count: askForCount,
153
+ count: Number(askForCount),
154
154
  rate: askForFeeRate,
155
155
  });
156
156
  }],
@@ -105,7 +105,7 @@ module.exports = ({ask, lnd}, cbk) => {
105
105
  ({getAlias, getBalance, getDetails}, cbk) =>
106
106
  {
107
107
  // Check to make sure that there are on chain funds for this group
108
- if (getBalance.chain_balance < getDetails.capacity) {
108
+ if (getBalance.chain_balance < getDetails.funding) {
109
109
  return cbk([
110
110
  400,
111
111
  'InsufficientChainFundsAvailableToJoinGroup',
@@ -6,6 +6,7 @@ const tinysecp = require('tiny-secp256k1');
6
6
  const {Transaction} = require('bitcoinjs-lib');
7
7
 
8
8
  const {ceil} = Math;
9
+ const committed = (capacity, m) => m.length === 2 ? capacity / 2 : capacity;
9
10
  const dummyEcdsaSignature = Buffer.alloc(74);
10
11
  const dummyPublicKey = Buffer.alloc(33);
11
12
  const dummySchnorrSignature = Buffer.alloc(64);
@@ -24,7 +25,7 @@ const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
24
25
  capacity: <Channel Capacity Tokens Number>
25
26
  proposed: [{
26
27
  [change]: <Change Output Hex String>
27
- funding: <Funding Output Hex String>
28
+ [funding]: <Funding Output Hex String>
28
29
  utxos: [{
29
30
  [non_witness_utxo]: <Spending Transaction Hex String>
30
31
  transaction_id: <Transaction Id Hex String>
@@ -120,7 +121,7 @@ module.exports = ({capacity, proposed, rate}, cbk) => {
120
121
  },
121
122
  {
122
123
  script: member.change,
123
- tokens: funded - capacity - (vbytes * rate),
124
+ tokens: funded - committed(capacity, proposed) - (vbytes * rate),
124
125
  },
125
126
  ];
126
127
  });
@@ -21,7 +21,7 @@ const {serviceTypeRegisterSignedOpen} = require('./../../service_types');
21
21
  const findRecord = (records, type) => records.find(n => n.type === type);
22
22
  const {isArray} = Array;
23
23
  const makeGroupId = () => randomBytes(16).toString('hex');
24
- const minGroupCount = 3;
24
+ 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';
@@ -186,6 +186,11 @@ module.exports = ({capacity, count, identity, lnd, rate}) => {
186
186
  // Emit event that everyone has joined
187
187
  group.emitter.emit('joined', {ids: group.ids});
188
188
 
189
+ // Exit early when this is a pair group
190
+ if (count === minGroupCount) {
191
+ return res.success({});
192
+ }
193
+
189
194
  // Derive position in members list
190
195
  const {inbound, outbound} = partnersFromMembers({group, id: req.from});
191
196
 
@@ -16,6 +16,7 @@ const {signAndFundGroupChannel} = require('./funding');
16
16
 
17
17
  const {fromHex} = Transaction;
18
18
  const interval = 500;
19
+ const minGroupCount = 2;
19
20
  const times = 2 * 60 * 10;
20
21
 
21
22
  /** Assemble a channel group
@@ -68,6 +69,10 @@ const times = 2 * 60 * 10;
68
69
  {}
69
70
  */
70
71
  module.exports = ({capacity, count, ecp, identity, lnd, rate}) => {
72
+ if (count < minGroupCount) {
73
+ throw new Error('ExpectedHigherGroupCountToAssembleChannelGroup');
74
+ }
75
+
71
76
  const coordinator = coordinateGroup({capacity, count, identity, lnd, rate});
72
77
  const emitter = new EventEmitter();
73
78
  const pending = {};
@@ -85,6 +90,11 @@ module.exports = ({capacity, count, ecp, identity, lnd, rate}) => {
85
90
  coordinator.events.once('joined', async ({ids}) => {
86
91
  emitter.emit('filled', {ids});
87
92
 
93
+ // Exit early when this is a pair group
94
+ if (count === minGroupCount) {
95
+ return coordinator.connected();
96
+ }
97
+
88
98
  const {inbound, outbound} = coordinator.partners(identity);
89
99
 
90
100
  // Connect to the inbound and outbound partners
@@ -106,6 +116,7 @@ module.exports = ({capacity, count, ecp, identity, lnd, rate}) => {
106
116
  // Fund and propose the pending channel to the outbound partner
107
117
  const {change, funding, id, utxos} = await proposeGroupChannel({
108
118
  capacity,
119
+ count,
109
120
  lnd,
110
121
  rate,
111
122
  to: coordinator.partners(identity).outbound,
@@ -142,7 +153,7 @@ module.exports = ({capacity, count, ecp, identity, lnd, rate}) => {
142
153
  lnd,
143
154
  from: coordinator.partners(identity).inbound,
144
155
  id: fromHex(basePsbt.unsigned_transaction).getId(),
145
- to: coordinator.partners(identity).outbound,
156
+ to: coordinator.partners(identity).outbound || undefined,
146
157
  });
147
158
  });
148
159
 
@@ -11,7 +11,7 @@ const half = n => n / 2;
11
11
  from: <Look for Incoming Channel From Identity Public Key Hex String>
12
12
  id: <Channel Transaction id Hex String>
13
13
  lnd: <Authenticated LND API Object>
14
- to: <Look for Outgoing Channel To Identity Public Key Hex String>
14
+ [to]: <Look for Outgoing Channel To Identity Public Key Hex String>
15
15
  }
16
16
 
17
17
  @returns via cbk or Promise
@@ -37,10 +37,6 @@ module.exports = ({capacity, from, id, lnd, to}, cbk) => {
37
37
  return cbk([400, 'ExpectedAuthenticatedLndToConfirmChannel']);
38
38
  }
39
39
 
40
- if (!to) {
41
- return cbk([400, "ExpectedToPublicKeyToConfirmIncomingChannel"]);
42
- }
43
-
44
40
  return cbk();
45
41
  },
46
42
 
@@ -78,6 +74,11 @@ module.exports = ({capacity, from, id, lnd, to}, cbk) => {
78
74
 
79
75
  // Double check that there is also a twin outgoing channel
80
76
  outgoing: ['getPending', ({getPending}, cbk) => {
77
+ // Exit early when there is no outgoing channel
78
+ if (!to) {
79
+ return cbk();
80
+ }
81
+
81
82
  const pending = getPending.pending_channels.find(chan => {
82
83
  return !chan.is_partner_initiated && chan.transaction_id === id;
83
84
  });
@@ -1,14 +1,19 @@
1
1
  const asyncAuto = require('async/auto');
2
2
  const asyncEach = require('async/each');
3
3
  const {fundPsbtDisallowingInputs} = require('ln-sync');
4
+ const {getNetwork} = require('ln-sync');
4
5
  const {getUtxos} = require('ln-service');
5
6
  const {openChannels} = require('ln-service');
7
+ const {networks} = require('bitcoinjs-lib');
8
+ const {payments} = require('bitcoinjs-lib');
6
9
  const {returnResult} = require('asyncjs-util');
7
10
  const {unlockUtxo} = require('ln-service');
8
11
 
12
+ const dummyKeys = () => ([Buffer.alloc(33, 2), Buffer.alloc(33, 3)]);
9
13
  const fuzzSize = 1;
10
14
  const halfOf = n => n / 2;
11
15
  const isEven = n => !(n % 2);
16
+ const minGroupCount = 2;
12
17
  const nestedSegWitAddressFormat = 'np2wpkh';
13
18
  const nestedSegWitPath = "m/49'/";
14
19
 
@@ -18,7 +23,7 @@ const nestedSegWitPath = "m/49'/";
18
23
  capacity: <Channel Capacity Tokens Number>
19
24
  lnd: <Authenticated LND API Object>
20
25
  rate: <Fee Rate Number>
21
- to: <Peer Id Public Key Hex String>
26
+ [to]: <Peer Id Public Key Hex String>
22
27
  }
23
28
 
24
29
  @returns via cbk or Promise
@@ -45,7 +50,7 @@ const nestedSegWitPath = "m/49'/";
45
50
  }]
46
51
  }
47
52
  */
48
- module.exports = ({capacity, lnd, rate, to}, cbk) => {
53
+ module.exports = ({capacity, count, lnd, rate, to}, cbk) => {
49
54
  return new Promise((resolve, reject) => {
50
55
  return asyncAuto({
51
56
  // Check arguments
@@ -54,6 +59,10 @@ module.exports = ({capacity, lnd, rate, to}, cbk) => {
54
59
  return cbk([400, 'ExpectedCapacityToProposeGroupChannel']);
55
60
  }
56
61
 
62
+ if (!count) {
63
+ return cbk([400, 'ExpectedGroupCountToProposeGroupChannel']);
64
+ }
65
+
57
66
  if (!isEven(capacity)) {
58
67
  return cbk([400, 'ExpectedEventCapacityToProposeGroupChannel']);
59
68
  }
@@ -66,28 +75,59 @@ module.exports = ({capacity, lnd, rate, to}, cbk) => {
66
75
  return cbk([400, 'ExpectedChainFeeRateToProposeGroupChannel']);
67
76
  }
68
77
 
69
- if (!to) {
70
- return cbk([400, 'ExpectedChannelPartnerPublicKeyToProposeChannel']);
71
- }
72
-
73
78
  return cbk();
74
79
  },
75
80
 
76
81
  // Get inputs to figure out which cannot be used for a group funding
77
82
  getInputs: ['validate', ({}, cbk) => getUtxos({lnd}, cbk)],
78
83
 
84
+ // Get the bitcoinjs network name for dummy output derivation
85
+ getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
86
+
79
87
  // Propose the channel to get an address to fund
80
- propose: ['validate', ({}, cbk) => {
88
+ propose: ['getNetwork', ({getNetwork}, cbk) => {
89
+ const tokens = halfOf(capacity);
90
+
91
+ // Exit early when there is a shared proposal due to a pair group
92
+ if (!to) {
93
+ const {address} = payments.p2wsh({
94
+ network: networks[getNetwork.bitcoinjs],
95
+ redeem: payments.p2ms({
96
+ m: dummyKeys().length,
97
+ network: networks[getNetwork.bitcoinjs],
98
+ pubkeys: dummyKeys(),
99
+ }),
100
+ });
101
+
102
+ // Pretend we are opening a channel when there is no outbound target
103
+ return cbk(null, {pending: [{address, tokens}]});
104
+ }
105
+
106
+ // Propose a channel
81
107
  return openChannels({
82
108
  lnd,
83
109
  channels: [{
84
110
  capacity,
85
- give_tokens: halfOf(capacity),
111
+ give_tokens: tokens,
86
112
  partner_public_key: to,
87
113
  }],
88
114
  is_avoiding_broadcast: true,
89
115
  },
90
- cbk);
116
+ (err, res) => {
117
+ if (!!err) {
118
+ return cbk(err);
119
+ }
120
+
121
+ // Exit early with the regular pending when it's a normal group size
122
+ if (count > minGroupCount) {
123
+ return cbk(null, res);
124
+ }
125
+
126
+ // In a pair group size, remap the funding so that it's only half
127
+ const [{address, id}] = res.pending;
128
+
129
+ return cbk(null, {pending: [{address, id, tokens}]});
130
+ });
91
131
  }],
92
132
 
93
133
  // Fund the address to populate UTXOs that can be used
@@ -145,10 +185,13 @@ module.exports = ({capacity, lnd, rate, to}, cbk) => {
145
185
  // Find the change output
146
186
  const change = fund.outputs.find(n => n.is_change);
147
187
 
148
- // UTXOs have been selected and channel has been proposed to peer
188
+ // Find the funding output
189
+ const funding = fund.outputs.find(n => !n.is_change);
190
+
191
+ // UTXOs have been selected
149
192
  return cbk(null, {
150
193
  change: !!change ? change.output_script : undefined,
151
- funding: fund.outputs.find(n => !n.is_change).output_script,
194
+ funding: !!to ? funding.output_script : undefined,
152
195
  id: proposal.id,
153
196
  overflow: !!change ? change.tokens : undefined,
154
197
  utxos: fund.inputs.map(input => ({
@@ -40,7 +40,7 @@ const times = 500;
40
40
  /** Sign and fund group channel
41
41
 
42
42
  {
43
- id: <Pending Channel Id Hex String>
43
+ [id]: <Pending Channel Id Hex String>
44
44
  lnd: <Authenticated LND API Object>
45
45
  psbt: <Base Funding PSBT Hex String>
46
46
  utxos: [{
@@ -74,10 +74,6 @@ module.exports = ({id, lnd, psbt, utxos}, cbk) => {
74
74
 
75
75
  // Check arguments
76
76
  validate: cbk => {
77
- if (!id) {
78
- return cbk([400, 'ExpectedPendingChannelIdToSignAndFundGroupChan']);
79
- }
80
-
81
77
  if (!lnd) {
82
78
  return cbk([400, 'ExpectedAuthenticatedLndToSignAndFundGroupChan']);
83
79
  }
@@ -254,6 +250,11 @@ module.exports = ({id, lnd, psbt, utxos}, cbk) => {
254
250
 
255
251
  // Fund the pending channel with the finalized PSBT
256
252
  fundChannel: ['conflict', 'finalizePsbt', ({finalizePsbt}, cbk) => {
253
+ // Exit early when this is a pair channel and there is no proposal
254
+ if (!id) {
255
+ return cbk();
256
+ }
257
+
257
258
  return fundPendingChannels({
258
259
  lnd,
259
260
  channels: [id],
@@ -264,6 +265,11 @@ module.exports = ({id, lnd, psbt, utxos}, cbk) => {
264
265
 
265
266
  // Confirm that the outgoing pending channel is present
266
267
  confirmOutPending: ['ecp', 'fundChannel', ({ecp}, cbk) => {
268
+ // Exit early when this is a pair channel and there is no proposal
269
+ if (!id) {
270
+ return cbk();
271
+ }
272
+
267
273
  const tx = fromHex(decodePsbt({ecp, psbt}).unsigned_transaction);
268
274
 
269
275
  // Wait for the outgoing pending channel to be present
@@ -90,11 +90,16 @@ module.exports = ({capacity, coordinator, count, id, lnd, rate}, cbk) => {
90
90
 
91
91
  // Find partners in the group
92
92
  partners: ['validate', ({}, cbk) => {
93
- return findGroupPartners({coordinator, id, lnd}, cbk);
93
+ return findGroupPartners({coordinator, count, id, lnd}, cbk);
94
94
  }],
95
95
 
96
96
  // Peer with the group partners
97
97
  peer: ['partners', ({partners}, cbk) => {
98
+ // Exit early when there is no inbound partner to connect with
99
+ if (!partners.inbound) {
100
+ return cbk();
101
+ }
102
+
98
103
  // Let listeners know that peering will be happening
99
104
  emitter.emit('peering', {
100
105
  inbound: partners.inbound,
@@ -118,6 +123,7 @@ module.exports = ({capacity, coordinator, count, id, lnd, rate}, cbk) => {
118
123
  propose: ['connected', 'partners', ({partners}, cbk) => {
119
124
  return proposeGroupChannel({
120
125
  capacity,
126
+ count,
121
127
  lnd,
122
128
  rate,
123
129
  to: partners.outbound,
@@ -168,6 +174,11 @@ module.exports = ({capacity, coordinator, count, id, lnd, rate}, cbk) => {
168
174
  'transaction',
169
175
  asyncReflect(({ecp, partners, register, transaction}, cbk) =>
170
176
  {
177
+ // Exit early when there is no inbound partner
178
+ if (!partners.inbound) {
179
+ return cbk();
180
+ }
181
+
171
182
  // Make sure that there is an inbound channel
172
183
  return asyncRetry({interval, times}, cbk => {
173
184
  return confirmIncomingChannel({
@@ -1,3 +1,5 @@
1
+ const minGroupCount = 2;
2
+
1
3
  /** Derive inbound and outbound partners from members list
2
4
 
3
5
  {
@@ -14,6 +16,11 @@
14
16
  }
15
17
  */
16
18
  module.exports = ({group, id}) => {
19
+ // Exit early when the group only has a pair and there is only one partner
20
+ if (group.ids.length === minGroupCount) {
21
+ return {inbound: group.ids.find(n => n !== id)};
22
+ }
23
+
17
24
  const [first] = group.ids;
18
25
  const reversed = group.ids.slice().reverse();
19
26
 
@@ -1,15 +1,18 @@
1
1
  const {decodeBigSize} = require('bolt01');
2
2
 
3
3
  const findRecord = (records, type) => records.find(n => n.type === type);
4
+ const funding = (capacity, count) => count === 2 ? capacity / 2 : capacity;
4
5
  const {isArray} = Array;
5
6
  const isOdd = n => !!(n % 2);
6
7
  const maxCapacityTokens = BigInt(7e14);
7
8
  const maxMembersCount = BigInt(650);
8
9
  const maxFeeRate = BigInt(1e5);
10
+ const minMembersCount = BigInt(2);
9
11
  const typeCapacity = '1';
10
12
  const typeCount = '2';
11
13
  const typeRate = '3';
12
14
  const typeVersion = '0';
15
+ const version = '1';
13
16
 
14
17
  /** Decode group details
15
18
 
@@ -27,6 +30,7 @@ const typeVersion = '0';
27
30
  {
28
31
  capacity: <Channel Capacity Tokens Number>
29
32
  count: <Target Members Count Number>
33
+ funding: <Amount Of Funding Required Tokens Number>
30
34
  rate: <Chain Fee Rate Number>
31
35
  }
32
36
  */
@@ -41,8 +45,18 @@ module.exports = ({records}) => {
41
45
 
42
46
  const versionRecord = findRecord(records, typeVersion);
43
47
 
44
- if (!!versionRecord) {
45
- throw new Error('UnexpectedVersionOfGroupDetailsRecords');
48
+ if (!versionRecord) {
49
+ throw new Error('ExpectedVersionOfGroupDetailsRecords');
50
+ }
51
+
52
+ try {
53
+ decodeBigSize({encoded: versionRecord.value});
54
+ } catch (err) {
55
+ throw new Error('ExpectedValidVersionNumberInGroupRecords');
56
+ }
57
+
58
+ if (decodeBigSize({encoded: versionRecord.value}).decoded !== version) {
59
+ throw new Error('UnsupportedGroupVersion');
46
60
  }
47
61
 
48
62
  const capacityRecord = findRecord(records, typeCapacity);
@@ -77,6 +91,10 @@ module.exports = ({records}) => {
77
91
  throw new Error('UnexpectedValueForCountInGroupRecords');
78
92
  }
79
93
 
94
+ if (BigInt(count) < minMembersCount) {
95
+ throw new Error('ExpectedHigherMembersCountInGroupDetails');
96
+ }
97
+
80
98
  const rateRecord = findRecord(records, typeRate);
81
99
 
82
100
  try {
@@ -94,6 +112,7 @@ module.exports = ({records}) => {
94
112
  return {
95
113
  capacity: Number(capacity),
96
114
  count: Number(count),
115
+ funding: funding(Number(capacity), Number(count)),
97
116
  rate: Number(rate),
98
117
  };
99
118
  };
@@ -4,6 +4,8 @@ const encodeNumber = number => encodeBigSize({number}).encoded;
4
4
  const typeCapacity = '1';
5
5
  const typeCount = '2';
6
6
  const typeRate = '3';
7
+ const typeVersion = '0';
8
+ const version = '1';
7
9
 
8
10
  /** Encode group details records
9
11
 
@@ -27,6 +29,7 @@ module.exports = ({capacity, count, rate}) => {
27
29
  {type: typeCapacity, value: encodeNumber(capacity)},
28
30
  {type: typeCount, value: encodeNumber(count)},
29
31
  {type: typeRate, value: encodeNumber(rate)},
32
+ {type: typeVersion, value: encodeNumber(version)},
30
33
  ],
31
34
  };
32
35
  };
@@ -16,7 +16,5 @@ const typePartnersRecord = '1';
16
16
  }
17
17
  */
18
18
  module.exports = ({inbound, outbound}) => {
19
- const records = [{type: typePartnersRecord, value: inbound + outbound}];
20
-
21
- return {records};
19
+ return {records: [{type: typePartnersRecord, value: inbound + outbound}]};
22
20
  };
@@ -13,6 +13,7 @@ const defaultConnectPollTimes = 2 * 60 * 5;
13
13
  const defaultGroupPartnersIntervalMs = 500;
14
14
  const defaultGroupPartnersPollTimes = 2 * 60 * 60 * 24 * 3;
15
15
  const defaultRequestTimeoutMs = 1000 * 60;
16
+ const minGroupCount = 2;
16
17
  const missingGroupPartners = 'NoGroupPartnersFound';
17
18
  const typeGroupChannelId = '1';
18
19
 
@@ -30,7 +31,7 @@ const typeGroupChannelId = '1';
30
31
  outbound: <Outbound Peer Public Key Identity Hex String>
31
32
  }
32
33
  */
33
- module.exports = ({coordinator, id, lnd}, cbk) => {
34
+ module.exports = ({coordinator, count, id, lnd}, cbk) => {
34
35
  return new Promise((resolve, reject) => {
35
36
  return asyncAuto({
36
37
  // Check arguments
@@ -39,6 +40,10 @@ module.exports = ({coordinator, id, lnd}, cbk) => {
39
40
  return cbk([400, 'ExpectedCoordinatorToFindGroupPartners']);
40
41
  }
41
42
 
43
+ if (!count) {
44
+ return cbk([400, 'ExpectedGroupMemberCountToFindGroupPartners']);
45
+ }
46
+
42
47
  if (!id) {
43
48
  return cbk([400, 'ExpectedGroupIdToFindGroupPartners']);
44
49
  }
@@ -84,6 +89,11 @@ module.exports = ({coordinator, id, lnd}, cbk) => {
84
89
  return cbk(err);
85
90
  }
86
91
 
92
+ // Exit early when the group is a pair
93
+ if (count === minGroupCount) {
94
+ return cbk();
95
+ }
96
+
87
97
  // Exit with error when there are no group partners
88
98
  if (!res.records || !res.records.length) {
89
99
  return cbk([503, missingGroupPartners]);
@@ -97,6 +107,11 @@ module.exports = ({coordinator, id, lnd}, cbk) => {
97
107
 
98
108
  // Parse the group partners response
99
109
  partners: ['request', ({request}, cbk) => {
110
+ // Exit early when there are no records
111
+ if (!request) {
112
+ return cbk(null, {outbound: coordinator});
113
+ }
114
+
100
115
  try {
101
116
  return cbk(null, decodePartnersRecords({records: request}));
102
117
  } catch (err) {
@@ -106,6 +121,11 @@ module.exports = ({coordinator, id, lnd}, cbk) => {
106
121
 
107
122
  // Attempt to connect to the partners
108
123
  peer: ['partners', ({partners}, cbk) => {
124
+ // Exit early when there are no partners
125
+ if (!partners.inbound) {
126
+ return cbk();
127
+ }
128
+
109
129
  return asyncEach([partners.inbound, partners.outbound], (id, cbk) => {
110
130
  return asyncRetry({
111
131
  interval: defaultConnectIntervalMs,
@@ -24,6 +24,7 @@ const typeGroupChannelId = '1';
24
24
  {
25
25
  capacity: <Channel Capacity Tokens Number>
26
26
  count: <Target Members Count Number>
27
+ funding: <Amount Of Funding Required Tokens Number>
27
28
  rate: <Chain Fee Rate Number>
28
29
  }
29
30
  */
@@ -34,7 +34,7 @@ const typeGroupChannelId = '1';
34
34
  group: <Group Identifier Hex String>
35
35
  lnd: <Authenticated LND API Object>
36
36
  overflow: <Expected Minimum Change Amount Tokens Number>
37
- pending: <Pending Channel Id Hex String>
37
+ [pending]: <Pending Channel Id Hex String>
38
38
  utxos: [{
39
39
  [non_witness_utxo]: <Non Witness Transaction Hex String>
40
40
  transaction_id: <Transaction Id Hex String>
@@ -80,10 +80,6 @@ module.exports = (args, cbk) => {
80
80
  return cbk([400, 'ExpectedAuthenticatedLndToRegisterPendingOpen']);
81
81
  }
82
82
 
83
- if (!args.pending) {
84
- return cbk([400, 'ExpectedPendingChannelIdToRegisterPendingOpen']);
85
- }
86
-
87
83
  if (!isArray(args.utxos)) {
88
84
  return cbk([400, 'ExpectedArrayOfUtxosToRegisterPendingOpen']);
89
85
  }
@@ -157,6 +153,11 @@ module.exports = (args, cbk) => {
157
153
  return cbk();
158
154
  }
159
155
 
156
+ // Exit early if there is no pending id
157
+ if (!args.pending) {
158
+ return cbk();
159
+ }
160
+
160
161
  return cancelPendingChannel({id: args.pending, lnd: args.lnd}, err => {
161
162
  if (!!err) {
162
163
  return cbk([503, 'UnexpectedErrorCleaningPendingChannel', {err}]);
package/package.json CHANGED
@@ -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.19.2"
50
+ "version": "3.20.2"
51
51
  }
@@ -57,7 +57,7 @@ test(`Setup joint channel group`, async ({end, equal, strictSame}) => {
57
57
  lnd: remote.lnd,
58
58
  });
59
59
 
60
- // Send coins to target
60
+ // Send coins to remote
61
61
  await sendToChainAddress({lnd, tokens, address: remoteAddress.address});
62
62
 
63
63
  // Wait for funds to arrive
@@ -0,0 +1,145 @@
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 = 101;
24
+ const feeRate = 1;
25
+ const interval = 10;
26
+ const size = 2;
27
+ const tokens = 1e6;
28
+ const times = 2000;
29
+
30
+ // Make a joint transaction channel group
31
+ test(`Setup joint 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] = 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
+ // Wait for funds to arrive
55
+ await asyncRetry({interval, times}, async () => {
56
+ await generate({});
57
+
58
+ const {transactions} = await getChainTransactions({lnd});
59
+
60
+ if (!!transactions.filter(n => !n.is_confirmed).length) {
61
+ throw new Error('TransactionsAreUnconfirmed');
62
+ }
63
+ });
64
+
65
+ // Wait for UTXOs to be confirmed
66
+ await asyncRetry({interval, times}, async () => {
67
+ const targetUtxos = await getUtxos({lnd: target.lnd});
68
+
69
+ if (!targetUtxos.utxos.filter(n => !!n.confirmation_count).length) {
70
+ throw new Error('ExpectedConfirmedUtxoOnTarget');
71
+ }
72
+ });
73
+
74
+ // Connect control to target
75
+ await addPeer({lnd, public_key: target.id, socket: target.socket});
76
+
77
+ // Start Group Coordination
78
+
79
+ // Start the coordination
80
+ const assemble = assembleChannelGroup({
81
+ capacity,
82
+ ecp,
83
+ count: nodes.length,
84
+ identity: control.id,
85
+ lnd: control.lnd,
86
+ rate: feeRate,
87
+ });
88
+
89
+ const events = {};
90
+
91
+ assemble.events.once('broadcast', n => events.broadcast = n);
92
+ assemble.events.once('filled', n => events.filled = n);
93
+
94
+ // Target join the group
95
+ const joins = await asyncMap([target.lnd], async lnd => {
96
+ const group = await getGroupDetails({
97
+ lnd,
98
+ coordinator: control.id,
99
+ id: assemble.id,
100
+ });
101
+
102
+ const join = joinChannelGroup({
103
+ lnd,
104
+ capacity: group.capacity,
105
+ coordinator: control.id,
106
+ count: group.count,
107
+ id: assemble.id,
108
+ rate: group.rate,
109
+ });
110
+
111
+ const [tx] = await once(join, 'end');
112
+
113
+ return tx;
114
+ });
115
+
116
+ // Transaction ids of the open should be returned
117
+ const ids = joins.map(n => n.id);
118
+
119
+ // Finished, wait for the channels to activate
120
+ await generate({count});
121
+
122
+ const {getPendingChannels} = require('ln-service');
123
+
124
+ await asyncRetry({interval, times}, async () => {
125
+ await generate({});
126
+
127
+ const {channels} = await getChannels({lnd, is_active: true});
128
+
129
+ if (!channels.length) {
130
+ throw new Error('ExpectedChannelActivation');
131
+ }
132
+ });
133
+
134
+ strictSame(ids, [events.broadcast.id], 'Got tx ids');
135
+ strictSame(events.broadcast.id.length, 64, 'Got broadcast tx id');
136
+ strictSame(!!events.broadcast.transaction, true, 'Got broadcast tx');
137
+ strictSame(events.filled.ids.length, nodes.length, 'Got filled event');
138
+ } catch (err) {
139
+ strictSame(err, null, 'Expected no failure');
140
+ } finally {
141
+ await kill({});
142
+ }
143
+
144
+ return end();
145
+ });
@@ -66,6 +66,9 @@ module.exports = ({lnd, logger, nodes}, cbk) => {
66
66
  // Derive the self public key
67
67
  getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
68
68
 
69
+ // Get the actively connected peers
70
+ getPeered: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
71
+
69
72
  // Find node connect info to connect to
70
73
  getNodes: ['getIdentity', ({getIdentity}, cbk) => {
71
74
  return asyncMap(nodes, (connect, cbk) => {
@@ -144,7 +147,8 @@ module.exports = ({lnd, logger, nodes}, cbk) => {
144
147
  removePeer: [
145
148
  'getChannels',
146
149
  'getNodes',
147
- ({getChannels, getNodes}, cbk) =>
150
+ 'getPeered',
151
+ ({getChannels, getNodes, getPeered}, cbk) =>
148
152
  {
149
153
  const ids = getChannels.channels.map(n => n.partner_public_key);
150
154
 
@@ -154,6 +158,11 @@ module.exports = ({lnd, logger, nodes}, cbk) => {
154
158
  return cbk();
155
159
  }
156
160
 
161
+ // Exit early when already not peered
162
+ if (!getPeered.peers.map(n => n.public_key).includes(node.id)) {
163
+ return cbk();
164
+ }
165
+
157
166
  return removePeer({lnd, public_key: node.id}, (err, res) => {
158
167
  if (!!err) {
159
168
  return cbk(err);
@@ -181,7 +190,7 @@ module.exports = ({lnd, logger, nodes}, cbk) => {
181
190
  }],
182
191
 
183
192
  // Get the list of connected peers
184
- getPeers: ['removePeer', ({}, cbk) => getPeers({lnd}, cbk)],
193
+ getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
185
194
 
186
195
  // Try and connect to a node in order to do p2p messaging
187
196
  connect: [