paid-services 6.1.5 → 6.1.7
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/groups/assemble/coordinate_group.js +11 -5
- package/groups/assemble_channel_group.js +15 -1
- package/groups/estimate_fee_rate.js +38 -0
- package/groups/fanout/assemble_fanout_group.js +185 -0
- package/groups/fanout/confirm_fanout_join.js +156 -0
- package/groups/fanout/coordinate_fanout.js +348 -0
- package/groups/fanout/create_group_fanout.js +323 -0
- package/groups/fanout/get_fanout_details.js +84 -0
- package/groups/fanout/get_fanout_funding.js +183 -0
- package/groups/fanout/index.js +4 -0
- package/groups/fanout/join_fanout.js +168 -0
- package/groups/fanout/join_group_fanout.js +207 -0
- package/groups/funding/sign_and_fund_group_channel.js +8 -228
- package/groups/index.js +9 -1
- package/groups/messages/decode_fanout_details.js +106 -0
- package/groups/messages/decode_fanout_proposal.js +84 -0
- package/groups/messages/decode_group_details.js +2 -2
- package/groups/messages/decode_pending_proposal.js +3 -103
- package/groups/messages/decode_signed_funding.js +18 -2
- package/groups/messages/decode_utxos_record.js +135 -0
- package/groups/messages/encode_fanout_details.js +35 -0
- package/groups/messages/encode_fanout_proposal.js +87 -0
- package/groups/messages/encode_group_details.js +1 -1
- package/groups/messages/index.js +8 -0
- package/groups/p2p/get_group_details.js +5 -2
- package/groups/p2p/index.js +4 -0
- package/groups/p2p/register_fanout_proposal.js +235 -0
- package/groups/p2p/register_fanout_signed.js +118 -0
- package/groups/p2p/register_group_connected.js +4 -0
- package/groups/p2p/register_pending_open.js +11 -0
- package/groups/transaction_fee_rate.js +29 -0
- package/index.js +4 -0
- package/package.json +6 -6
- package/service_types.json +7 -1
- package/test/integration/test_fanout.js +220 -0
- package/groups/assemble/assemble_unsigned_psbt.js +0 -171
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
2
|
const {randomBytes} = require('crypto');
|
|
3
3
|
|
|
4
|
-
const assembleUnsignedPsbt = require('
|
|
4
|
+
const {assembleUnsignedPsbt} = require('ln-sync');
|
|
5
|
+
|
|
5
6
|
const {decodeGroupDetails} = require('./../messages');
|
|
6
7
|
const {decodePendingProposal} = require('./../messages');
|
|
7
8
|
const {decodeSignedFunding} = require('./../messages');
|
|
@@ -32,6 +33,7 @@ const uniq = arr => Array.from(new Set(arr));
|
|
|
32
33
|
{
|
|
33
34
|
capacity: <Channel Capacity Tokens Number>
|
|
34
35
|
count: <Group Members Count Number>
|
|
36
|
+
ecp: <ECPair Library Object>
|
|
35
37
|
identity: <Coordinator Identity Public Key Hex String>
|
|
36
38
|
lnd: <Authenticated LND API Object>
|
|
37
39
|
[members]: [<Member Node Id Public Key Hex String>]
|
|
@@ -95,11 +97,15 @@ const uniq = arr => Array.from(new Set(arr));
|
|
|
95
97
|
// All members have submitted their partial signatures
|
|
96
98
|
@event 'signed'
|
|
97
99
|
*/
|
|
98
|
-
module.exports = ({capacity, count, identity, lnd, members, rate}) => {
|
|
100
|
+
module.exports = ({capacity, count, ecp, identity, lnd, members, rate}) => {
|
|
99
101
|
if (count < minGroupCount) {
|
|
100
102
|
throw new Error('ExpectedHigherGroupMembersCountToCoordinateGroup');
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
if (!ecp) {
|
|
106
|
+
throw new Error('ExpectedEcpLibraryToCoordinateChannelGroup');
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
if (!identity) {
|
|
104
110
|
throw new Error('ExpectedSelfIdentityPublicKeyToCoordinateGroup');
|
|
105
111
|
}
|
|
@@ -307,7 +313,7 @@ module.exports = ({capacity, count, identity, lnd, members, rate}) => {
|
|
|
307
313
|
|
|
308
314
|
group.proposed.push({
|
|
309
315
|
change: proposal.change,
|
|
310
|
-
funding: proposal.funding,
|
|
316
|
+
funding: [proposal.funding],
|
|
311
317
|
id: req.from,
|
|
312
318
|
utxos: proposal.utxos,
|
|
313
319
|
});
|
|
@@ -371,12 +377,12 @@ module.exports = ({capacity, count, identity, lnd, members, rate}) => {
|
|
|
371
377
|
}
|
|
372
378
|
|
|
373
379
|
try {
|
|
374
|
-
decodeSignedFunding({records: req.records});
|
|
380
|
+
decodeSignedFunding({ecp, records: req.records});
|
|
375
381
|
} catch (err) {
|
|
376
382
|
return res.failure([400, err.message]);
|
|
377
383
|
}
|
|
378
384
|
|
|
379
|
-
const signed = decodeSignedFunding({records: req.records});
|
|
385
|
+
const signed = decodeSignedFunding({ecp, records: req.records});
|
|
380
386
|
|
|
381
387
|
// Register the signed funding
|
|
382
388
|
if (!group.signed.find(n => n.id === req.from)) {
|
|
@@ -13,6 +13,7 @@ const {coordinateGroup} = require('./assemble');
|
|
|
13
13
|
const {peerWithPartners} = require('./p2p');
|
|
14
14
|
const {proposeGroupChannel} = require('./funding');
|
|
15
15
|
const {signAndFundGroupChannel} = require('./funding');
|
|
16
|
+
const transactionFeeRate = require('./transaction_fee_rate');
|
|
16
17
|
|
|
17
18
|
const {fromHex} = Transaction;
|
|
18
19
|
const interval = 500;
|
|
@@ -82,6 +83,7 @@ module.exports = ({capacity, count, ecp, identity, lnd, members, rate}) => {
|
|
|
82
83
|
const coordinator = coordinateGroup({
|
|
83
84
|
capacity,
|
|
84
85
|
count,
|
|
86
|
+
ecp,
|
|
85
87
|
identity,
|
|
86
88
|
lnd,
|
|
87
89
|
members,
|
|
@@ -144,7 +146,12 @@ module.exports = ({capacity, count, ecp, identity, lnd, members, rate}) => {
|
|
|
144
146
|
pending.utxos = utxos;
|
|
145
147
|
|
|
146
148
|
// Register as proposed
|
|
147
|
-
return coordinator.proposed({
|
|
149
|
+
return coordinator.proposed({
|
|
150
|
+
change,
|
|
151
|
+
utxos,
|
|
152
|
+
funding: !!funding ? [funding] : undefined,
|
|
153
|
+
id: identity,
|
|
154
|
+
});
|
|
148
155
|
} catch (err) {
|
|
149
156
|
return errored(err);
|
|
150
157
|
}
|
|
@@ -203,6 +210,13 @@ module.exports = ({capacity, count, ecp, identity, lnd, members, rate}) => {
|
|
|
203
210
|
// Pull out the raw transaction from the PSBT
|
|
204
211
|
const {transaction} = extractTransaction({ecp, psbt: finalized.psbt});
|
|
205
212
|
|
|
213
|
+
const {inputs} = decodePsbt({ecp, psbt: combined.psbt});
|
|
214
|
+
|
|
215
|
+
// Make sure the final transaction fee rate is not too low
|
|
216
|
+
if (transactionFeeRate({inputs, transaction}).rate < rate) {
|
|
217
|
+
throw [503, 'UnexpectedLowFeeRateForChannelGroupTransaction'];
|
|
218
|
+
}
|
|
219
|
+
|
|
206
220
|
emitter.emit('broadcasting', ({transaction}));
|
|
207
221
|
|
|
208
222
|
const {id} = await broadcastChainTransaction({lnd, transaction});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
2
|
+
|
|
3
|
+
const {fromHex} = Transaction;
|
|
4
|
+
const isUtxoP2wpkh = utxo => utxo.witness_utxo.script_pub.startsWith('0014');
|
|
5
|
+
const sumTokens = arr => arr.reduce((sum, n) => sum + n.tokens, 0);
|
|
6
|
+
const dummyP2wpkh = [Buffer.alloc(33), Buffer.alloc(72)];
|
|
7
|
+
const dummyP2tr = [Buffer.alloc(64)];
|
|
8
|
+
|
|
9
|
+
/** Assuming large ECDSA signatures, determine a chain fee rate for a PSBT
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
inputs: [{
|
|
13
|
+
witness_utxo: {
|
|
14
|
+
script_pub: <P2TR or P2WPKH Output Script Hex String>
|
|
15
|
+
tokens: <Input Value Tokens Number>
|
|
16
|
+
}
|
|
17
|
+
}]
|
|
18
|
+
outputs: [{
|
|
19
|
+
tokens: <Output Value Tokens Number>
|
|
20
|
+
}]
|
|
21
|
+
unsigned: <Unsigned Transaction Hex String>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@returns
|
|
25
|
+
{
|
|
26
|
+
rate: <Estimated Chain Fee Tokens Per VByte Number>
|
|
27
|
+
}
|
|
28
|
+
*/
|
|
29
|
+
module.exports = ({inputs, outputs, unsigned}) => {
|
|
30
|
+
const fee = sumTokens(inputs.map(n => n.witness_utxo)) - sumTokens(outputs);
|
|
31
|
+
const tx = fromHex(unsigned);
|
|
32
|
+
|
|
33
|
+
inputs.forEach((input, index) => {
|
|
34
|
+
return tx.setWitness(index, isUtxoP2wpkh(input) ? dummyP2wpkh : dummyP2tr);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {rate: fee / tx.virtualSize()};
|
|
38
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
|
|
3
|
+
const {broadcastChainTransaction} = require('ln-service');
|
|
4
|
+
const {combinePsbts} = require('psbt');
|
|
5
|
+
const {decodePsbt} = require('psbt');
|
|
6
|
+
const {extractTransaction} = require('psbt');
|
|
7
|
+
const {finalizePsbt} = require('psbt');
|
|
8
|
+
const {signAndFundPsbt} = require('ln-sync');
|
|
9
|
+
|
|
10
|
+
const coordinateFanout = require('./coordinate_fanout');
|
|
11
|
+
const getFanoutFunding = require('./get_fanout_funding');
|
|
12
|
+
const transactionFeeRate = require('./../transaction_fee_rate');
|
|
13
|
+
|
|
14
|
+
/** Assemble a fanout group
|
|
15
|
+
|
|
16
|
+
This group type must have at least 3 participants
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
capacity: <Output Size Tokens Number>
|
|
20
|
+
count: <Fanout Members Count Number>
|
|
21
|
+
ecp: <ECPair Library Object>
|
|
22
|
+
identity: <Coordinator Identity Public Key Hex String>
|
|
23
|
+
inputs: [<Utxo Outpoint String>]
|
|
24
|
+
lnd: <Authenticated LND API Object>
|
|
25
|
+
[members]: [<Member Identity Public Key Hex String>]
|
|
26
|
+
outputs: <Output Count Number>
|
|
27
|
+
rate: <Chain Fee Tokens Per VByte Number>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@returns
|
|
31
|
+
{
|
|
32
|
+
events: <Fanout Status Notifications EventEmitter Object>
|
|
33
|
+
id: <Group Identifier Hex Encoded String>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The fanout transaction was successfully published to relays
|
|
37
|
+
@event 'broadcast'
|
|
38
|
+
{
|
|
39
|
+
id: <Transaction Id Hex String>
|
|
40
|
+
transaction: <Transaction Hex String>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The fanout transaction is being published to relays
|
|
44
|
+
@event 'broadcasting'
|
|
45
|
+
{
|
|
46
|
+
transaction: <Transaction Hex String>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A member is peered with the coordinator
|
|
50
|
+
@event 'connected'
|
|
51
|
+
{}
|
|
52
|
+
|
|
53
|
+
// All members are peered with the coordinator
|
|
54
|
+
@event 'filled'
|
|
55
|
+
{
|
|
56
|
+
ids: [<Group Member Identity Public Key Hex String>]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A member sent a presence update
|
|
60
|
+
@event 'present'
|
|
61
|
+
{
|
|
62
|
+
id: <Present Member Public Id Hex String>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Members have proposed the entire unsigned fanout to the coordinator
|
|
66
|
+
@event 'proposed'
|
|
67
|
+
{}
|
|
68
|
+
|
|
69
|
+
// Members have all signed the fanout transaction
|
|
70
|
+
@event 'signed'
|
|
71
|
+
{}
|
|
72
|
+
*/
|
|
73
|
+
module.exports = args => {
|
|
74
|
+
const coordinator = coordinateFanout({
|
|
75
|
+
capacity: args.capacity,
|
|
76
|
+
count: args.count,
|
|
77
|
+
ecp: args.ecp,
|
|
78
|
+
identity: args.identity,
|
|
79
|
+
lnd: args.lnd,
|
|
80
|
+
members: args.members,
|
|
81
|
+
rate: args.rate,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const emitter = new EventEmitter();
|
|
85
|
+
const pending = {};
|
|
86
|
+
|
|
87
|
+
const errored = err => {
|
|
88
|
+
coordinator.events.removeAllListeners();
|
|
89
|
+
|
|
90
|
+
return emitter.emit('error', err);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// An error was encountered
|
|
94
|
+
coordinator.events.once('error', errored);
|
|
95
|
+
|
|
96
|
+
// Group members have registered themselves
|
|
97
|
+
coordinator.events.once('joined', ({ids}) => emitter.emit('filled', {ids}));
|
|
98
|
+
|
|
99
|
+
// Group members have connected to the coordinator
|
|
100
|
+
coordinator.events.once('connected', async () => {
|
|
101
|
+
emitter.emit('connected', {});
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Get the self funding details for the fanout
|
|
105
|
+
const {change, funding, utxos} = await getFanoutFunding({
|
|
106
|
+
capacity: args.capacity,
|
|
107
|
+
inputs: args.inputs,
|
|
108
|
+
lnd: args.lnd,
|
|
109
|
+
outputs: args.outputs,
|
|
110
|
+
rate: args.rate,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
pending.utxos = utxos;
|
|
114
|
+
|
|
115
|
+
// Register self as proposed
|
|
116
|
+
return coordinator.proposed({change, funding, utxos, id: args.identity});
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return errored(err);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Group members have proposed fanout to the coordinator
|
|
123
|
+
coordinator.events.once('funded', async () => {
|
|
124
|
+
emitter.emit('proposed', {unsigned: coordinator.unsigned()});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Sign the unsigned funding transaction
|
|
128
|
+
const signed = await signAndFundPsbt({
|
|
129
|
+
lnd: args.lnd,
|
|
130
|
+
psbt: coordinator.unsigned(),
|
|
131
|
+
utxos: pending.utxos,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Register the signature with the coordinator
|
|
135
|
+
return coordinator.sign({id: args.identity, signed: signed.psbt});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return errored(err);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Relay presence notifications
|
|
142
|
+
coordinator.events.on('present', ({id}) => emitter.emit('present', {id}));
|
|
143
|
+
|
|
144
|
+
// Group members have submitted their partial signatures
|
|
145
|
+
coordinator.events.once('signed', async () => {
|
|
146
|
+
// Collect all the partially signed PSBTs to be combined into a single PSBT
|
|
147
|
+
const psbts = coordinator.signed().map(n => n.signed);
|
|
148
|
+
|
|
149
|
+
emitter.emit('signed', {psbts});
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// Merge all the partial signed PSBTs into a single PSBT with all sigs
|
|
153
|
+
const combined = combinePsbts({psbts, ecp: args.ecp});
|
|
154
|
+
|
|
155
|
+
// Finalize the PSBT to convert partial signatures to final signatures
|
|
156
|
+
const finalized = finalizePsbt({ecp: args.ecp, psbt: combined.psbt});
|
|
157
|
+
|
|
158
|
+
// Pull out the raw transaction from the PSBT
|
|
159
|
+
const {transaction} = extractTransaction({
|
|
160
|
+
ecp: args.ecp,
|
|
161
|
+
psbt: finalized.psbt,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const {inputs} = decodePsbt({ecp: args.ecp, psbt: combined.psbt});
|
|
165
|
+
|
|
166
|
+
// Make sure the final transaction fee rate is not too low
|
|
167
|
+
if (transactionFeeRate({inputs, transaction}).rate < args.rate) {
|
|
168
|
+
throw [503, 'UnexpectedLowFeeRateForFanoutTransaction'];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
emitter.emit('broadcasting', ({transaction}));
|
|
172
|
+
|
|
173
|
+
const {id} = await broadcastChainTransaction({
|
|
174
|
+
transaction,
|
|
175
|
+
lnd: args.lnd,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return emitter.emit('broadcast', {id, transaction});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
return errored(err);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return {events: emitter, id: coordinator.id};
|
|
185
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncRetry = require('async/retry');
|
|
3
|
+
const asyncTimeout = require('async/timeout');
|
|
4
|
+
const {connectPeer} = require('ln-sync');
|
|
5
|
+
const {getChainBalance} = require('ln-service');
|
|
6
|
+
const {getNodeAlias} = require('ln-sync');
|
|
7
|
+
const {returnResult} = require('asyncjs-util');
|
|
8
|
+
|
|
9
|
+
const getFanoutDetails = require('./get_fanout_details');
|
|
10
|
+
|
|
11
|
+
const coordinatorFromJoinCode = n => n.slice(0, 66);
|
|
12
|
+
const defaultTimeoutMs = 1000 * 60;
|
|
13
|
+
const groupIdFromJoinCode = n => n.slice(66);
|
|
14
|
+
const interval = 500;
|
|
15
|
+
const isCode = n => !!n && n.length === 98;
|
|
16
|
+
const isNumber = n => !isNaN(n);
|
|
17
|
+
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
18
|
+
const join = arr => arr.join(' ');
|
|
19
|
+
const niceName = n => `${n.alias} ${n.id}`.trim();
|
|
20
|
+
const times = 2 * 60;
|
|
21
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
22
|
+
|
|
23
|
+
/** Ask to confirm joining a group
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
code: <Group Invite Code String>
|
|
27
|
+
count: <Output Count Number>
|
|
28
|
+
lnd: <Authenticated LND API Object>
|
|
29
|
+
logger: <Winston Logger Object>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@returns via cbk or Promise
|
|
33
|
+
{
|
|
34
|
+
capacity: <Fanout Output Capacity Tokens Number>
|
|
35
|
+
coordinator: <Group Coordinator Identity Public Key Hex String>
|
|
36
|
+
count: <Group Members Count>
|
|
37
|
+
id: <Group Id Hex String>
|
|
38
|
+
rate: <Chain Fee Tokens Per VByte Number>
|
|
39
|
+
}
|
|
40
|
+
*/
|
|
41
|
+
module.exports = ({code, count, lnd, logger}, cbk) => {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
return asyncAuto({
|
|
44
|
+
// Check arguments
|
|
45
|
+
validate: cbk => {
|
|
46
|
+
if (!isCode(code)) {
|
|
47
|
+
return cbk([400, 'ExpectedFanoutGroupInviteCodeToGetJoinDetails']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!count || !isNumber(count)) {
|
|
51
|
+
return cbk([400, 'ExpectedOutputCountToJoinGroupFanout']);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!lnd) {
|
|
55
|
+
return cbk([400, 'ExpectedAuthenticatedLndToGetJoinGroupDetails']);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!logger) {
|
|
59
|
+
return cbk([400, 'ExpectedWinstonLoggerObjectToGetJoinDetails']);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return cbk();
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Get the wallet balance to make sure there are enough funds to join
|
|
66
|
+
getBalance: ['validate', ({}, cbk) => getChainBalance({lnd}, cbk)],
|
|
67
|
+
|
|
68
|
+
// Parse the group join code
|
|
69
|
+
group: ['validate', ({}, cbk) => {
|
|
70
|
+
const coordinator = coordinatorFromJoinCode(code);
|
|
71
|
+
|
|
72
|
+
if (!isPublicKey(coordinator)) {
|
|
73
|
+
return cbk([400, 'ExpectedValidGroupJoinCodeToRequestGroupDetails']);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const id = groupIdFromJoinCode(code);
|
|
77
|
+
|
|
78
|
+
return cbk(null, {coordinator, id});
|
|
79
|
+
}],
|
|
80
|
+
|
|
81
|
+
// Connect to the coordinator
|
|
82
|
+
connect: ['group', ({group}, cbk) => {
|
|
83
|
+
return asyncRetry({interval, times}, cbk => {
|
|
84
|
+
logger.info({connecting_to: group.coordinator});
|
|
85
|
+
|
|
86
|
+
return asyncTimeout(connectPeer, defaultTimeoutMs)({
|
|
87
|
+
lnd,
|
|
88
|
+
id: group.coordinator,
|
|
89
|
+
},
|
|
90
|
+
cbk);
|
|
91
|
+
},
|
|
92
|
+
cbk);
|
|
93
|
+
}],
|
|
94
|
+
|
|
95
|
+
// Get the coordinator node alias to log it out
|
|
96
|
+
getAlias: ['group', ({group}, cbk) => {
|
|
97
|
+
return getNodeAlias({lnd, id: group.coordinator}, cbk);
|
|
98
|
+
}],
|
|
99
|
+
|
|
100
|
+
// Get the group details from the coordinator once connected
|
|
101
|
+
getDetails: ['connect', 'group', ({group}, cbk) => {
|
|
102
|
+
return asyncRetry({interval, times}, cbk => {
|
|
103
|
+
logger.info({requesting_group_details_from: group.coordinator});
|
|
104
|
+
|
|
105
|
+
return getFanoutDetails({
|
|
106
|
+
lnd,
|
|
107
|
+
coordinator: group.coordinator,
|
|
108
|
+
id: group.id,
|
|
109
|
+
},
|
|
110
|
+
cbk);
|
|
111
|
+
},
|
|
112
|
+
cbk);
|
|
113
|
+
}],
|
|
114
|
+
|
|
115
|
+
// Log the details of the group being joined
|
|
116
|
+
log: [
|
|
117
|
+
'getAlias',
|
|
118
|
+
'getBalance',
|
|
119
|
+
'getDetails',
|
|
120
|
+
({getAlias, getBalance, getDetails}, cbk) =>
|
|
121
|
+
{
|
|
122
|
+
const coordinatedBy = `coordinated by ${niceName(getAlias)}`;
|
|
123
|
+
const members = `${getDetails.count} member fanout group`;
|
|
124
|
+
const size = `with ${tokensAsBigUnit(getDetails.capacity)} outputs`;
|
|
125
|
+
const rate = `and paying ${getDetails.rate}/vbyte chain fee`;
|
|
126
|
+
|
|
127
|
+
logger.info({joining: join([members, coordinatedBy, size, rate])});
|
|
128
|
+
|
|
129
|
+
const requiredFunding = getDetails.funding * count;
|
|
130
|
+
|
|
131
|
+
// Check to make sure that there are on chain funds for this group
|
|
132
|
+
if (getBalance.chain_balance < requiredFunding) {
|
|
133
|
+
return cbk([
|
|
134
|
+
400,
|
|
135
|
+
'InsufficientChainFundsAvailableToJoinGroup',
|
|
136
|
+
{chain_balance: getBalance.chain_balance},
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return cbk();
|
|
141
|
+
}],
|
|
142
|
+
|
|
143
|
+
// Go ahead with the group join
|
|
144
|
+
join: ['getDetails', 'group', ({getDetails, group}, cbk) => {
|
|
145
|
+
return cbk(null, {
|
|
146
|
+
capacity: getDetails.capacity,
|
|
147
|
+
coordinator: group.coordinator,
|
|
148
|
+
count: getDetails.count,
|
|
149
|
+
id: group.id,
|
|
150
|
+
rate: getDetails.rate,
|
|
151
|
+
});
|
|
152
|
+
}],
|
|
153
|
+
},
|
|
154
|
+
returnResult({reject, resolve, of: 'join'}, cbk));
|
|
155
|
+
});
|
|
156
|
+
};
|