paid-services 3.2.0 → 3.3.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/capacity/accept_capacity_change.js +284 -0
  3. package/capacity/ask_for_change_details.js +505 -0
  4. package/capacity/ask_for_decrease.js +185 -0
  5. package/capacity/assemble_replacement_tx.js +171 -0
  6. package/capacity/capacity_change_requests.js +76 -0
  7. package/capacity/change_channel_capacity.js +183 -0
  8. package/capacity/encode_change_request.js +55 -0
  9. package/capacity/encode_sign_capacity_response.js +32 -0
  10. package/capacity/fee_for_replacement.js +108 -0
  11. package/capacity/finalize_capacity_replacement.js +67 -0
  12. package/capacity/get_capacity_change_requests.js +98 -0
  13. package/capacity/get_capacity_replacement.js +371 -0
  14. package/capacity/get_replacement_signature.js +232 -0
  15. package/capacity/index.js +4 -0
  16. package/capacity/initiate_capacity_change.js +506 -0
  17. package/capacity/interim_replacement_psbt.js +57 -0
  18. package/capacity/is_open_witness_script.js +40 -0
  19. package/capacity/parse_accept_request.js +58 -0
  20. package/capacity/parse_accept_response.js +52 -0
  21. package/capacity/parse_capacity_change_request.js +101 -0
  22. package/capacity/parse_sign_capacity_request.js +59 -0
  23. package/capacity/propose_capacity_change.js +361 -0
  24. package/capacity/service_types.json +6 -0
  25. package/capacity/sign_capacity_replacement.js +119 -0
  26. package/capacity/witness_script_from_close_txs.js +50 -0
  27. package/index.js +2 -0
  28. package/p2p/encode_peer_response.js +0 -1
  29. package/p2p/make_peer_request.js +3 -3
  30. package/package.json +6 -4
  31. package/test/capacity/test_assemble_replacement_tx.js +46 -0
  32. package/test/capacity/test_capacity_change_requests.js +54 -0
  33. package/test/capacity/test_encode_change_request.js +49 -0
  34. package/test/capacity/test_encode_sign_capacity_response.js +47 -0
  35. package/test/capacity/test_fee_for_replacement.js +40 -0
  36. package/test/capacity/test_finalize_capacity_replacement.js +45 -0
  37. package/test/capacity/test_interim_replacement_psbt.js +44 -0
  38. package/test/capacity/test_is_open_witness_script.js +40 -0
  39. package/test/capacity/test_parse_accept_request.js +42 -0
  40. package/test/capacity/test_parse_capacity_change_request.js +51 -0
  41. package/test/capacity/test_parse_sign_capacity_request.js +49 -0
  42. package/test/capacity/test_witness_script_from_close_txs.js +53 -0
  43. package/test/integration/test_accept_capacity_change.js +152 -0
  44. package/test/integration/test_change_channel_capacity.js +196 -0
  45. package/test/integration/test_decrease_channel_capacity.js +206 -0
  46. package/test/integration/test_increase_channel_capacity.js +197 -0
  47. package/test/integration/test_sign_capacity_replacement.js +142 -0
  48. package/test/p2p/test_encode_peer_request.js +53 -0
  49. package/test/p2p/test_encode_peer_response.js +38 -0
  50. package/test/p2p/test_encode_response_code.js +48 -0
  51. package/trades/network_from_network_record.js +6 -0
  52. package/trades/network_record_from_request.js +5 -0
@@ -0,0 +1,197 @@
1
+ const {address} = require('bitcoinjs-lib');
2
+ const asyncAuto = require('async/auto');
3
+ const asyncMap = require('async/map');
4
+ const asyncRetry = require('async/retry');
5
+ const {broadcastChainTransaction} = require('ln-service');
6
+ const {closeChannel} = require('ln-service');
7
+ const {createChainAddress} = require('ln-service');
8
+ const {getChainTransactions} = require('ln-service');
9
+ const {getChannel} = require('ln-service');
10
+ const {getChannels} = require('ln-service');
11
+ const {getNetwork} = require('ln-sync');
12
+ const {networks} = require('bitcoinjs-lib');
13
+ const {openChannel} = require('ln-service');
14
+ const {spawnLightningCluster} = require('ln-docker-daemons');
15
+ const {test} = require('@alexbosworth/tap');
16
+ const {Transaction} = require('bitcoinjs-lib');
17
+
18
+ const {changeChannelCapacity} = require('./../../capacity');
19
+ const {getCapacityChangeRequests} = require('./../../capacity');
20
+
21
+ const bufferAsHex = buffer => buffer.toString('hex');
22
+ const capacity = 1e6;
23
+ const {fromHex} = Transaction;
24
+ const id = Buffer.alloc(32).toString('hex');
25
+ const interval = 10;
26
+ const logger = {error: () => {}, info: () => {}};
27
+ const maturity = 100;
28
+ const size = 2;
29
+ const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
30
+ const times = 2000;
31
+ const {toOutputScript} = address;
32
+ const weightAsVBytes = n => Math.ceil(n / 4);
33
+
34
+ // A capacity replacement proposal should be counter signed and accepted
35
+ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
36
+ const {kill, nodes} = await spawnLightningCluster({size});
37
+
38
+ const [control, target] = nodes;
39
+
40
+ const {generate, lnd} = control;
41
+
42
+ const network = networks[(await getNetwork({lnd})).bitcoinjs];
43
+
44
+ // Make some funds
45
+ await generate({count: maturity});
46
+
47
+ try {
48
+ // Open up a new channel
49
+ const channelOpen = await openChannel({
50
+ lnd,
51
+ local_tokens: capacity,
52
+ partner_public_key: target.id,
53
+ partner_socket: target.socket,
54
+ });
55
+
56
+ // Wait for the channel to be active
57
+ const channel = await asyncRetry({interval, times}, async () => {
58
+ const [channel] = (await getChannels({lnd, is_active: true})).channels;
59
+
60
+ if (!channel) {
61
+ await generate({});
62
+
63
+ throw new Error('ExpectedChannelActivation');
64
+ }
65
+
66
+ const {policies} = await getChannel({lnd, id: channel.id});
67
+
68
+ const noPolicy = policies.find(n => !n.cltv_delta);
69
+
70
+ if (!!channel && !noPolicy) {
71
+ return channel;
72
+ }
73
+
74
+ await generate({});
75
+
76
+ throw new Error('ExpectedChannelActivation');
77
+ });
78
+
79
+ await asyncAuto({
80
+ // Propose the channel
81
+ propose: async () => {
82
+ await changeChannelCapacity({
83
+ lnd,
84
+ logger,
85
+ ask: (args, cbk) => {
86
+ if (args.name === 'amount') {
87
+ return cbk({amount: '200000'});
88
+ }
89
+
90
+ if (args.name === 'direction') {
91
+ return cbk({direction: 'increase'});
92
+ }
93
+
94
+ if (args.name === 'internal') {
95
+ return cbk({internal: true});
96
+ }
97
+
98
+ if (args.name === 'ok') {
99
+ return cbk({ok: true});
100
+ }
101
+
102
+ if (args.name === 'proceed') {
103
+ return cbk({proceed: true});
104
+ }
105
+
106
+ if (args.name === 'rate') {
107
+ return cbk({rate: args.default});
108
+ }
109
+
110
+ if (args.name === 'query') {
111
+ return cbk({query: target.id});
112
+ }
113
+
114
+ throw new Error('UnexpectedQueryNameForProposingSide');
115
+ },
116
+ });
117
+ },
118
+
119
+ // Wait to see the inbound proposal
120
+ waitForProposal: async () => {
121
+ await asyncRetry({interval, times}, async () => {
122
+ const {requests} = await getCapacityChangeRequests({lnd: target.lnd});
123
+
124
+ if (!requests.length) {
125
+ throw new Error('FailedToFindChangeRequest');
126
+ }
127
+ });
128
+ },
129
+
130
+ // Accept the channel
131
+ accept: ['waitForProposal', async ({}) => {
132
+ await changeChannelCapacity({
133
+ logger,
134
+ ask: (args, cbk) => {
135
+ if (args.name === 'accept') {
136
+ return cbk({accept: true});
137
+ }
138
+
139
+ if (args.name === 'ok') {
140
+ return cbk({ok: true});
141
+ }
142
+
143
+ throw new Error('UnexpectedQueryNameForAcceptingSide');
144
+ },
145
+ lnd: target.lnd,
146
+ });
147
+ }],
148
+
149
+ // Generate to confirm the change
150
+ generate: ['waitForProposal', async ({}) => {
151
+ // Wait for the new channel to be active
152
+ const recreated = await asyncRetry({interval: 3000, times}, async () => {
153
+ const [channel] = (await getChannels({lnd, is_active: true})).channels.filter(n => n.capacity > capacity);
154
+
155
+ await generate({});
156
+
157
+ if (!channel) {
158
+ throw new Error('ExpectedChannelActivation');
159
+ }
160
+
161
+ {
162
+ const {policies} = await getChannel({lnd, id: channel.id});
163
+
164
+ const noPolicy = policies.find(n => !n.cltv_delta);
165
+
166
+ if (!!noPolicy) {
167
+ throw new Error('ExpectedChannelPolicy');
168
+ }
169
+ };
170
+
171
+ {
172
+ const {policies} = await getChannel({
173
+ lnd: target.lnd,
174
+ id: channel.id,
175
+ });
176
+
177
+ const noPolicy = policies.find(n => !n.cltv_delta);
178
+
179
+ if (!!noPolicy) {
180
+ throw new Error('ExpectedChannelPolicy');
181
+ }
182
+ };
183
+
184
+ return;
185
+ });
186
+
187
+ return;
188
+ }],
189
+ });
190
+ } catch (err) {
191
+ strictSame(err, null, 'Expected no failure');
192
+ } finally {
193
+ await kill({});
194
+ }
195
+
196
+ return end();
197
+ });
@@ -0,0 +1,142 @@
1
+ const {address} = require('bitcoinjs-lib');
2
+ const asyncMap = require('async/map');
3
+ const asyncRetry = require('async/retry');
4
+ const {broadcastChainTransaction} = require('ln-service');
5
+ const {closeChannel} = require('ln-service');
6
+ const {createChainAddress} = require('ln-service');
7
+ const {getChainTransactions} = require('ln-service');
8
+ const {getChannels} = require('ln-service');
9
+ const {networks} = require('bitcoinjs-lib');
10
+ const {openChannel} = require('ln-service');
11
+ const {spawnLightningCluster} = require('ln-docker-daemons');
12
+ const {test} = require('@alexbosworth/tap');
13
+ const {Transaction} = require('bitcoinjs-lib');
14
+
15
+ const finalize = require('./../../capacity/finalize_capacity_replacement');
16
+ const method = require('./../../capacity/sign_capacity_replacement');
17
+
18
+ const bufferAsHex = buffer => buffer.toString('hex');
19
+ const capacity = 1e6;
20
+ const finalizeCapacityReplacement = finalize;
21
+ const {fromHex} = Transaction;
22
+ const interval = 10;
23
+ const maturity = 100;
24
+ const network = networks.regtest;
25
+ const sequence = 0;
26
+ const signCapacityReplacement = method;
27
+ const size = 2;
28
+ const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
29
+ const times = 2000;
30
+ const {toOutputScript} = address;
31
+ const weightAsVBytes = n => Math.ceil(n / 4);
32
+
33
+ // Signing a capacity replacement transaction should result in a valid sig
34
+ test(`Sign capacity replacement`, async ({end, equal, strictSame}) => {
35
+ const {kill, nodes} = await spawnLightningCluster({size});
36
+
37
+ const [{generate, lnd}, target] = nodes;
38
+
39
+ await generate({count: maturity});
40
+
41
+ try {
42
+ // Open up a new channel
43
+ const channelOpen = await openChannel({
44
+ lnd,
45
+ local_tokens: capacity,
46
+ partner_public_key: target.id,
47
+ partner_socket: target.socket,
48
+ });
49
+
50
+ // Wait for the channel to be active
51
+ const channel = await asyncRetry({interval, times}, async () => {
52
+ const [channel] = (await getChannels({lnd, is_active: true})).channels;
53
+
54
+ if (!!channel) {
55
+ return channel;
56
+ }
57
+
58
+ await generate({});
59
+
60
+ throw new Error('ExpectedChannelActivation');
61
+ });
62
+
63
+ // Find the open tx
64
+ const tx = (await getChainTransactions({lnd})).transactions.find(tx => {
65
+ return tx.id === channel.transaction_id;
66
+ });
67
+
68
+ // Get the open tx output
69
+ const output = fromHex(tx.transaction).outs[channel.transaction_vout];
70
+
71
+ // Close the channel
72
+ const channelClose = await closeChannel({
73
+ lnd,
74
+ id: channel.id,
75
+ is_force_close: true,
76
+ });
77
+
78
+ // Find the open tx
79
+ const endTx = (await getChainTransactions({lnd})).transactions.find(tx => {
80
+ return tx.id === channelClose.transaction_id;
81
+ });
82
+
83
+ // Get the witness spending the channel funds
84
+ const [{witness}] = fromHex(endTx.transaction).ins;
85
+
86
+ const replacement = new Transaction();
87
+
88
+ const openTxHash = fromHex(tx.transaction).getHash();
89
+
90
+ replacement.addInput(openTxHash, channel.transaction_vout, sequence);
91
+
92
+ const spendIdx = replacement.ins.findIndex(n => n.hash.equals(openTxHash));
93
+
94
+ const fee = sumOf([
95
+ channel.commit_transaction_fee,
96
+ weightAsVBytes(channel.commit_transaction_weight),
97
+ ]);
98
+
99
+ replacement.addOutput(
100
+ toOutputScript((await createChainAddress({lnd})).address, network),
101
+ channel.capacity - fee,
102
+ );
103
+
104
+ // Gather the replacement signatures
105
+ const [local, remote] = await asyncMap([lnd, target.lnd], async lnd => {
106
+ return await signCapacityReplacement({
107
+ lnd,
108
+ capacity: channel.capacity,
109
+ output: bufferAsHex(output.script),
110
+ script: bufferAsHex(witness.slice().pop()),
111
+ transaction: replacement.toHex(),
112
+ vin: replacement.ins.findIndex(n => n.hash.equals(openTxHash)),
113
+ });
114
+ });
115
+
116
+ // Add the signature to the replacement transaction
117
+ const {transaction} = finalizeCapacityReplacement({
118
+ local_public_key: local.key,
119
+ local_signature: local.signature,
120
+ funding_spend_vin: spendIdx,
121
+ remote_signature: remote.signature,
122
+ transaction: replacement.toHex(),
123
+ witness_script: bufferAsHex(witness.slice().pop()),
124
+ });
125
+
126
+ await broadcastChainTransaction({transaction, lnd});
127
+
128
+ await generate({count: maturity});
129
+
130
+ const done = (await getChainTransactions({lnd})).transactions.find(tx => {
131
+ return tx.id === fromHex(transaction).getId();
132
+ });
133
+
134
+ strictSame(done.is_confirmed, true, 'Signed replacement is confirmed');
135
+ } catch (err) {
136
+ strictSame(err, null, 'Expected no failure');
137
+ } finally {
138
+ await kill({});
139
+ }
140
+
141
+ return end();
142
+ });
@@ -0,0 +1,53 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const method = require('./../../p2p/encode_peer_request');
4
+
5
+ const makeArgs = overrides => {
6
+ const args = {id: '00', type: '0'};
7
+
8
+ Object.keys(overrides).forEach(k => args[k] = overrides[k]);
9
+
10
+ return args;
11
+ };
12
+
13
+ const tests = [
14
+ {
15
+ args: makeArgs({id: undefined}),
16
+ description: 'A request id is required',
17
+ error: 'ExpectedRequestIdHexStringToEncodePeerRequest',
18
+ },
19
+ {
20
+ args: makeArgs({records: '00'}),
21
+ description: 'Records array is required',
22
+ error: 'ExpectedRecordsArrayToEncodePeerRequest',
23
+ },
24
+ {
25
+ args: makeArgs({type: undefined}),
26
+ description: 'A type is required',
27
+ error: 'ExpectedRequestTypeToEncodePeerRequest',
28
+ },
29
+ {
30
+ args: makeArgs({}),
31
+ description: 'Peer request is encoded',
32
+ expected: {message: '626f73ff010100030100'},
33
+ },
34
+ {
35
+ args: makeArgs({records: [{type: '0', value: '00'}]}),
36
+ description: 'Peer request with records is encoded',
37
+ expected: {message: '626f73ff0101000301000503000100'},
38
+ },
39
+ ];
40
+
41
+ tests.forEach(({args, description, error, expected}) => {
42
+ return test(description, async ({end, strictSame, throws}) => {
43
+ if (!!error) {
44
+ throws(() => method(args), new Error(error), 'Got error');
45
+ } else {
46
+ const res = method(args);
47
+
48
+ strictSame(res, expected, 'Got expected result');
49
+ }
50
+
51
+ return end();
52
+ });
53
+ });
@@ -0,0 +1,38 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const method = require('./../../p2p/encode_peer_response');
4
+
5
+ const makeArgs = overrides => {
6
+ const args = {id: '00'};
7
+
8
+ Object.keys(overrides).forEach(k => args[k] = overrides[k]);
9
+
10
+ return args;
11
+ };
12
+
13
+ const tests = [
14
+ {
15
+ args: makeArgs({}),
16
+ description: 'Peer response is encoded',
17
+ expected: {message: '626f73ff01010002030001c8'},
18
+ },
19
+ {
20
+ args: makeArgs({records: [{type: '0', value: '00'}]}),
21
+ description: 'Peer response with records is encoded',
22
+ expected: {message: '626f73ff01010002030001c80503000100'},
23
+ },
24
+ ];
25
+
26
+ tests.forEach(({args, description, error, expected}) => {
27
+ return test(description, async ({end, strictSame, throws}) => {
28
+ if (!!error) {
29
+ throws(() => method(args), new Error(error), 'Got error');
30
+ } else {
31
+ const res = method(args);
32
+
33
+ strictSame(res, expected, 'Got expected result');
34
+ }
35
+
36
+ return end();
37
+ });
38
+ });
@@ -0,0 +1,48 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const method = require('./../../p2p/encode_response_code');
4
+
5
+ const makeArgs = overrides => {
6
+ const args = {};
7
+
8
+ Object.keys(overrides).forEach(k => args[k] = overrides[k]);
9
+
10
+ return args;
11
+ };
12
+
13
+ const tests = [
14
+ {
15
+ args: makeArgs({failure: '00'}),
16
+ description: 'A failure array is expected',
17
+ error: 'ExpectedFailureArrayToEncodeResponseCode',
18
+ },
19
+ {
20
+ args: makeArgs({failure: []}),
21
+ description: 'A failure code is expected',
22
+ error: 'ExpectedErrorCodeToEncodeResponseCode',
23
+ },
24
+ {
25
+ args: makeArgs({}),
26
+ description: 'Encode response code',
27
+ expected: {encoded: '0001c8'},
28
+ },
29
+ {
30
+ args: makeArgs({failure: [400, 'Failure']}),
31
+ description: 'Encode failure code',
32
+ expected: {encoded: '0003fd019001074661696c757265'},
33
+ },
34
+ ];
35
+
36
+ tests.forEach(({args, description, error, expected}) => {
37
+ return test(description, async ({end, strictSame, throws}) => {
38
+ if (!!error) {
39
+ throws(() => method(args), new Error(error), 'Got error');
40
+ } else {
41
+ const res = method(args);
42
+
43
+ strictSame(res, expected, 'Got expected result');
44
+ }
45
+
46
+ return end();
47
+ });
48
+ });
@@ -1,4 +1,6 @@
1
1
  const defaultNetwork = 'bitcoin';
2
+ const regtestNetwork = 'regtest';
3
+ const regtestNetworkType = '02';
2
4
  const testnetNetwork = 'testnet';
3
5
  const testnetNetworkType = '01';
4
6
 
@@ -18,6 +20,10 @@ module.exports = ({value}) => {
18
20
  return defaultNetwork;
19
21
  }
20
22
 
23
+ if (value === regtestNetworkType) {
24
+ return regtestNetwork;
25
+ }
26
+
21
27
  if (value === testnetNetworkType) {
22
28
  return testnetNetwork;
23
29
  }
@@ -1,4 +1,5 @@
1
1
  const defaultNetwork = 'lnbc';
2
+ const regtestNetwork = 'lnbcrt';
2
3
  const testnetNetwork = 'lntb';
3
4
  const testnetNetworkType = '01';
4
5
 
@@ -22,6 +23,10 @@ module.exports = ({request}) => {
22
23
  return {};
23
24
  }
24
25
 
26
+ if (request.startsWith(regtestNetwork)) {
27
+ return {};
28
+ }
29
+
25
30
  if (request.startsWith(testnetNetwork)) {
26
31
  return {value: testnetNetworkType};
27
32
  }