@dashevo/dapi-client 1.0.0-pr.1902.1 → 1.0.0-pr.1935.A

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/docs/_sidebar.md CHANGED
@@ -7,13 +7,15 @@
7
7
  - [.broadcastTransaction()](usage/application/core/broadcastTransaction.md)
8
8
  - [.generateToAddress()](usage/application/core/generateToAddress.md)
9
9
  - [.getBestBlockHash()](usage/application/core/getBestBlockHash.md)
10
+ - [.getBestBlockHeight()](usage/application/core/getBestBlockHeight.md)
10
11
  - [.getBlockByHash()](usage/application/core/getBlockByHash.md)
11
12
  - [.getBlockByHeight()](usage/application/core/getBlockByHeight.md)
12
13
  - [.getBlockHash()](usage/application/core/getBlockHash.md)
13
- - [.getMnListDiff()](usage/application/core/getMnListDiff.md)
14
14
  - [.getStatus()](usage/application/core/getStatus.md)
15
15
  - [.getTransaction()](usage/application/core/getTransaction.md)
16
16
  - [.subscribeToTransactionsWithProofs()](usage/application/core/subscribeToTransactionsWithProofs.md)
17
+ - [.subscribeToBlockHeadersWithChainLocks()](usage/application/core/subscribeToBlockHeadersWithChainLocks.md)
18
+ - [.subscribeToMasternodeList()](usage/application/core/subscribeToMasternodeList.md)
17
19
  - Platform
18
20
  - [.broadcastStateTransition()](usage/application/platform/broadcastStateTransition.md)
19
21
  - [.getDataContract()](usage/application/platform/getDataContract.md)
@@ -0,0 +1,10 @@
1
+ **Usage**: `await client.core.getBestBlockHeight(options)`
2
+ **Description**: Allow to fetch the best (highest/latest block height) from the network
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **options** | DAPIClientOptions | no | |
9
+
10
+ Returns : {Promise<string>} - The best block height
@@ -0,0 +1,39 @@
1
+ **Usage**: `await client.core.subscribeToBlockHeadersWithChainLocks(options = { count: 0 })`\
2
+ **Description**: Returns a ClientReadableStream streaming of block headers and chainlocks.
3
+
4
+
5
+ Parameters:
6
+
7
+ | parameters | type | required | Description |
8
+ |----------------------------|------------------|----------------| ------------------------------------------------------------------------------------------------ |
9
+ | **options.fromBlockHash** | String | yes | Specifies block hash to start syncing from |
10
+ | **options.fromBlockHeight**| Number | yes | Specifies block height to start syncing from |
11
+ | **options.count** | Number | no (default: 0)| Number of blocks to sync, if set to 0 syncing is continuously sends new data as well |
12
+
13
+ Returns : Promise<EventEmitter>|!grpc.web.ClientReadableStream<!BlockHeadersWithChainLocksResponse>
14
+
15
+ Example :
16
+
17
+ ```js
18
+ const { BlockHeader, ChainLock } = require('@dashevo/dashcore-lib');
19
+
20
+ const stream = await client.subscribeToBlockHeadersWithChainLocks({ fromBlockHeight: 0 });
21
+
22
+ stream
23
+ .on('data', (response) => {
24
+ const rawHeaders = response.getBlockHeaders();
25
+ const rawChainLock = response.getChainLock();
26
+
27
+ if (headers.length > 0) {
28
+ const headers = rawHeaders.map((rawHeader) => new BlockHeader(rawHeader));
29
+ console.dir(headers);
30
+ }
31
+
32
+ if (rawChainLock) {
33
+ const chainLock = new ChainLock(rawChainLock);
34
+ }
35
+ })
36
+ .on('error', (err) => {
37
+ // do something with err
38
+ });
39
+ ```
@@ -0,0 +1,24 @@
1
+ **Usage**: `await client.core.subscribeToMasternodeList(options = {})`\
2
+ **Description**: Returns a ClientReadableStream streaming of masternode list diffs ([DIP-4](https://github.com/dashpay/dips/blob/master/dip-0004.md)). As a first message it returns a diff from the first block to the current tip and a diff for each new chainlocked block.
3
+
4
+ Returns : Promise<EventEmitter>|!grpc.web.ClientReadableStream<!MasternodeListResponse>
5
+
6
+ Example :
7
+
8
+ ```js
9
+ const { SimplifiedMNList, SimplifiedMNListDiff } = require('@dashevo/dashcore-lib');
10
+
11
+ const stream = await client.subscribeToMasternodeList();
12
+
13
+ const list = new SimplifiedMNList();
14
+
15
+ stream
16
+ .on('data', (response) => {
17
+ const diffBuffer = Buffer.from(response.getMasternodeListDiff_asU8());
18
+ const diff = new SimplifiedMNListDiff(diffBuffer);
19
+ list.applyDiff(diff);
20
+ })
21
+ .on('error', (err) => {
22
+ // do something with err
23
+ });
24
+ ```
@@ -19,24 +19,27 @@ Returns : Promise<EventEmitter>|!grpc.web.ClientReadableStream<!TransactionsWith
19
19
  Example :
20
20
 
21
21
  ```js
22
- const filter; // A BloomFilter object
22
+ const { BloomFilter, Transaction, MerkleBlock } = require('@dashevo/dashcore-lib');
23
+
24
+ const filter = BloomFilter.create(1, 0.001); // A BloomFilter object
23
25
  const stream = await client.subscribeToTransactionsWithProofs(filter, { fromBlockHeight: 0 });
24
26
 
25
27
  stream
26
28
  .on('data', (response) => {
27
- const merkleBlock = response.getRawMerkleBlock();
28
- const transactions = response.getRawTransactions();
29
+ const rawMerkleBlock = response.getRawMerkleBlock();
30
+ const rawTransactions = response.getRawTransactions();
29
31
 
30
32
  if (merkleBlock) {
31
- const merkleBlockHex = Buffer.from(merkleBlock).toString('hex');
33
+ const merkleBlock = new MerkleBlock(rawMerkleBlock);
34
+ console.dir(merkleBlock);
32
35
  }
33
36
 
34
- if (transactions) {
35
- transactions.getTransactionsList()
36
- .forEach((tx) => {
37
- // tx are probabilistic, so you will have to verify it's yours
38
- const tx = new Transaction(Buffer.from(tx));
39
- });
37
+ if (transactions.length > 0) {
38
+ // tx are probabilistic, so you will have to verify it's yours
39
+ const transactions = transactions.getTransactionsList()
40
+ .map((tx) => new Transaction(Buffer.from(tx)));
41
+
42
+ console.dir(transactions);
40
43
  }
41
44
  })
42
45
  .on('error', (err) => {
@@ -19,14 +19,15 @@ const validateNumber = (value, name, min = NaN, max = NaN) => {
19
19
  /**
20
20
  * @typedef {createBlockHeadersProviderFromOptions}
21
21
  * @param {DAPIClientOptions} options
22
+ * @param logger
22
23
  * @param {CoreMethodsFacade} coreMethods
23
24
  * @returns {BlockHeadersProvider}
24
25
  */
25
- function createBlockHeadersProviderFromOptions(options, coreMethods) {
26
+ function createBlockHeadersProviderFromOptions(options, coreMethods, logger) {
26
27
  let blockHeadersProvider;
27
28
  if (options.blockHeadersProvider) {
28
29
  if (options.blockHeadersProviderOptions) {
29
- throw new DAPIClientError("Can't use 'blockHeadersProviderOptions' with 'blockHeadersProvider' option");
30
+ throw new DAPIClientError('Can\'t use \'blockHeadersProviderOptions\' with \'blockHeadersProvider\' option');
30
31
  }
31
32
 
32
33
  blockHeadersProvider = options.blockHeadersProvider;
@@ -37,6 +38,7 @@ function createBlockHeadersProviderFromOptions(options, coreMethods) {
37
38
  coreMethods.subscribeToBlockHeadersWithChainLocks,
38
39
  {
39
40
  maxRetriesOnError: -1,
41
+ logger,
40
42
  },
41
43
  )({
42
44
  fromBlockHeight,
package/lib/DAPIClient.js CHANGED
@@ -70,12 +70,30 @@ class DAPIClient extends EventEmitter {
70
70
  * @private
71
71
  */
72
72
  initBlockHeadersProvider() {
73
- this.blockHeadersProvider = createBlockHeadersProviderFromOptions(this.options, this.core);
73
+ this.blockHeadersProvider = createBlockHeadersProviderFromOptions(
74
+ this.options,
75
+ this.core,
76
+ this.logger,
77
+ );
74
78
 
75
79
  this.blockHeadersProvider.on(BlockHeadersProvider.EVENTS.ERROR, (e) => {
76
80
  this.emit(EVENTS.ERROR, e);
77
81
  });
78
82
  }
83
+
84
+ /**
85
+ * Close all open connections
86
+ * @returns {Promise<void>}
87
+ */
88
+ async disconnect() {
89
+ // Stop block headers provider
90
+ await this.blockHeadersProvider.stop();
91
+
92
+ // Stop masternode list provider
93
+ if (this.dapiAddressProvider.smlProvider) {
94
+ await this.dapiAddressProvider.smlProvider.unsubscribe();
95
+ }
96
+ }
79
97
  }
80
98
 
81
99
  DAPIClient.EVENTS = EVENTS;
@@ -1,27 +1,33 @@
1
1
  const SimplifiedMNList = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNList');
2
2
  const SimplifiedMNListDiff = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListDiff');
3
3
 
4
+ const logger = require('../logger');
5
+
4
6
  class SimplifiedMasternodeListProvider {
5
7
  /**
6
- *
7
- * @param {JsonRpcTransport} jsonRpcTransport - JsonRpcTransport instance
8
+ * @param {Function} createStream - JsonRpcTransport instance
8
9
  * @param {object} [options] - Options
9
- * @param {number} [options.updateInterval]
10
10
  * @param {string} [options.network]
11
+ * @param {string} [options.loggerOptions]
11
12
  */
12
- constructor(jsonRpcTransport, options = {}) {
13
- this.jsonRpcTransport = jsonRpcTransport;
14
-
15
- this.options = {
16
- updateInterval: 60000,
17
- ...options,
18
- };
19
-
20
- this.simplifiedMNList = new SimplifiedMNList(undefined, this.options.network);
13
+ constructor(createStream, options = {}) {
14
+ this.createStream = createStream;
15
+ this.options = options;
16
+ this.logger = logger.getForId(
17
+ this.options.loggerOptions.identifier,
18
+ this.options.loggerOptions.level,
19
+ );
21
20
 
22
- this.lastUpdateDate = 0;
21
+ /**
22
+ * @type {ReconnectableStream}
23
+ */
24
+ this.stream = undefined;
25
+ this.removeStreamListeners = () => {};
23
26
 
24
- this.baseBlockHash = SimplifiedMasternodeListProvider.NULL_HASH;
27
+ /**
28
+ * @type {SimplifiedMNList}
29
+ */
30
+ this.simplifiedMNList = new SimplifiedMNList(undefined);
25
31
  }
26
32
 
27
33
  /**
@@ -29,75 +35,178 @@ class SimplifiedMasternodeListProvider {
29
35
  * @returns {Promise<SimplifiedMNList>}
30
36
  */
31
37
  async getSimplifiedMNList() {
32
- if (this.needsUpdate()) {
33
- await this.updateMasternodeList();
38
+ if (this.stream === undefined) {
39
+ await this.subscribeToMasternodeList();
34
40
  }
35
41
 
36
42
  return this.simplifiedMNList;
37
43
  }
38
44
 
39
45
  /**
40
- * Checks whether simplified masternode list needs update
46
+ * Subscribe to simplified masternodes list updates. No need to call it manually
41
47
  * @private
42
- * @returns {boolean}
48
+ * @returns {Promise<void>}
43
49
  */
44
- needsUpdate() {
45
- return Date.now() - this.options.updateInterval > this.lastUpdateDate;
46
- }
47
-
48
- /**
49
- * Updates simplified masternodes list. No need to call it manually
50
- * @private
51
- */
52
- async updateMasternodeList() {
53
- const diff = await this.getSimplifiedMNListDiff();
54
-
55
- try {
56
- this.simplifiedMNList.applyDiff(diff);
57
- } catch (e) {
58
- if (e.message === 'Cannot apply diff: previous blockHash needs to equal the new diff\'s baseBlockHash') {
59
- this.reset();
60
-
61
- await this.updateMasternodeList();
62
-
63
- return;
64
- }
65
-
66
- throw e;
50
+ async subscribeToMasternodeList() {
51
+ if (this.stream) {
52
+ this.logger.debug('Masternode list stream already started');
53
+ return Promise.resolve();
67
54
  }
68
55
 
69
- this.baseBlockHash = diff.blockHash;
56
+ this.logger.debug('Starting masternode list stream');
70
57
 
71
- this.lastUpdateDate = Date.now();
72
- }
58
+ this.stream = await this.createStream();
73
59
 
74
- /**
75
- * Fetches masternode diff from DAPI
76
- * @private
77
- * @returns {Promise<SimplifiedMNListDiff>}
78
- */
79
- async getSimplifiedMNListDiff() {
80
- const blockHash = await this.jsonRpcTransport.request('getBestBlockHash');
60
+ let diffCount = 0;
61
+ let resolved = false;
81
62
 
82
- const rawSimplifiedMNListDiff = await this.jsonRpcTransport.request(
83
- 'getMnListDiff',
84
- { baseBlockHash: this.baseBlockHash, blockHash },
85
- { addresses: [this.jsonRpcTransport.getLastUsedAddress()] },
86
- );
63
+ const rejectDiff = (error) => {
64
+ this.logger.silly('Stream is cancelled due to error. Retrying...', { error });
87
65
 
88
- return new SimplifiedMNListDiff(rawSimplifiedMNListDiff, this.options.network);
66
+ this.stream.cancel();
67
+ this.stream.retryOnError(error);
68
+ };
69
+
70
+ return new Promise((resolve, reject) => {
71
+ const errorHandler = (error) => {
72
+ this.stream = null;
73
+
74
+ this.logger.error(
75
+ `Masternode list sync failed: ${error.message}`,
76
+ { error, diffCount },
77
+ );
78
+
79
+ if (!resolved) {
80
+ reject(error);
81
+ resolved = true;
82
+ }
83
+ };
84
+
85
+ const dataHandler = (response) => {
86
+ diffCount += 1;
87
+
88
+ if (diffCount === 1) {
89
+ this.logger.silly(
90
+ 'Full masternode list diff received',
91
+ { diffCount },
92
+ );
93
+ } else {
94
+ this.logger.silly(
95
+ 'Received masternode list diff',
96
+ { diffCount },
97
+ );
98
+ }
99
+
100
+ let simplifiedMNListDiff;
101
+ let simplifiedMNListDiffBuffer;
102
+ try {
103
+ simplifiedMNListDiffBuffer = Buffer.from(response.getMasternodeListDiff_asU8());
104
+ simplifiedMNListDiff = new SimplifiedMNListDiff(
105
+ simplifiedMNListDiffBuffer,
106
+ this.options.network,
107
+ );
108
+ } catch (e) {
109
+ this.logger.warn(
110
+ `Can't parse masternode list diff: ${e.message}`,
111
+ {
112
+ diffCount,
113
+ network: this.options.network,
114
+ error: e,
115
+ simplifiedMNListDiff: simplifiedMNListDiffBuffer.toString('hex'),
116
+ },
117
+ );
118
+
119
+ rejectDiff(e);
120
+
121
+ return;
122
+ }
123
+
124
+ this.logger.silly(
125
+ 'Parsed masternode list diff successfully',
126
+ {
127
+ diffCount,
128
+ blockHash: simplifiedMNListDiff.blockHash,
129
+ },
130
+ );
131
+
132
+ try {
133
+ // Restart list when we receive a full diff
134
+ if (diffCount === 1) {
135
+ this.simplifiedMNList = new SimplifiedMNList(simplifiedMNListDiff);
136
+ } else {
137
+ this.simplifiedMNList.applyDiff(simplifiedMNListDiff);
138
+ }
139
+ } catch (e) {
140
+ this.logger.warn(
141
+ `Can't apply masternode list diff: ${e.message}`,
142
+ {
143
+ diffCount,
144
+ network: this.options.network,
145
+ blockHash: simplifiedMNListDiff.blockHash,
146
+ error: e,
147
+ simplifiedMNListDiff,
148
+ },
149
+ );
150
+
151
+ rejectDiff(e);
152
+ }
153
+
154
+ this.logger.silly(
155
+ 'Masternode list diff applied successfully',
156
+ {
157
+ diffCount,
158
+ blockHash: simplifiedMNListDiff.blockHash,
159
+ },
160
+ );
161
+
162
+ if (!resolved) {
163
+ resolve();
164
+ resolved = true;
165
+ }
166
+ };
167
+
168
+ const beforeReconnectHandler = () => {
169
+ diffCount = 0;
170
+
171
+ this.logger.debug(
172
+ 'Restarting masternode list stream',
173
+ { diffCount },
174
+ );
175
+ };
176
+
177
+ const endHandler = () => {
178
+ this.logger.warn(
179
+ 'Masternode list sync stopped',
180
+ { diffCount },
181
+ );
182
+
183
+ this.removeStreamListeners();
184
+ this.stream = null;
185
+ };
186
+
187
+ this.stream.on('data', dataHandler);
188
+ this.stream.on('beforeReconnect', beforeReconnectHandler);
189
+ this.stream.on('error', errorHandler);
190
+ this.stream.on('end', endHandler);
191
+
192
+ this.removeStreamListeners = () => {
193
+ this.stream.removeListener('data', dataHandler);
194
+ this.stream.removeListener('beforeReconnect', beforeReconnectHandler);
195
+ this.stream.removeListener('error', errorHandler);
196
+ this.stream.removeListener('end', endHandler);
197
+ };
198
+ });
89
199
  }
90
200
 
91
201
  /**
92
- * Reset simplifiedMNList
93
- * @private
202
+ * Unsubscribe from masternode list updates
94
203
  */
95
- reset() {
96
- this.simplifiedMNList = new SimplifiedMNList(undefined, this.options.network);
97
-
98
- this.lastUpdateDate = 0;
99
-
100
- this.baseBlockHash = SimplifiedMasternodeListProvider.NULL_HASH;
204
+ unsubscribe() {
205
+ if (this.stream) {
206
+ this.removeStreamListeners();
207
+ this.stream.cancel();
208
+ this.stream = null;
209
+ }
101
210
  }
102
211
  }
103
212
 
@@ -0,0 +1,49 @@
1
+ const {
2
+ v0: {
3
+ MasternodeListRequest,
4
+ CorePromiseClient,
5
+ },
6
+ } = require('@dashevo/dapi-grpc');
7
+
8
+ const GrpcTransport = require('../transport/GrpcTransport/GrpcTransport');
9
+ const createGrpcTransportError = require('../transport/GrpcTransport/createGrpcTransportError');
10
+ const ReconnectableStream = require('../transport/ReconnectableStream');
11
+
12
+ /**
13
+ * Creates continues masternode list stream
14
+ *
15
+ * @param {createDAPIAddressProviderFromOptions} createDAPIAddressProviderFromOptions
16
+ * @param {ListDAPIAddressProvider} listDAPIAddressProvider
17
+ * @param {Object} options
18
+ * @return {function(...[*]): Promise<ReconnectableStream>}
19
+ */
20
+ function createMasternodeListStreamFactory(
21
+ createDAPIAddressProviderFromOptions,
22
+ listDAPIAddressProvider,
23
+ options,
24
+ ) {
25
+ const grpcTransport = new GrpcTransport(
26
+ createDAPIAddressProviderFromOptions,
27
+ listDAPIAddressProvider,
28
+ createGrpcTransportError,
29
+ options,
30
+ );
31
+
32
+ return ReconnectableStream
33
+ .create(
34
+ () => grpcTransport.request(
35
+ CorePromiseClient,
36
+ 'subscribeToMasternodeList',
37
+ new MasternodeListRequest(),
38
+ {
39
+ timeout: undefined,
40
+ autoReconnectInterval: 0,
41
+ },
42
+ ),
43
+ {
44
+ maxRetriesOnError: -1,
45
+ },
46
+ );
47
+ }
48
+
49
+ module.exports = createMasternodeListStreamFactory;
@@ -6,10 +6,7 @@ const ListDAPIAddressProvider = require('./ListDAPIAddressProvider');
6
6
 
7
7
  const SimplifiedMasternodeListProvider = require('../SimplifiedMasternodeListProvider/SimplifiedMasternodeListProvider');
8
8
  const SimplifiedMasternodeListDAPIAddressProvider = require('./SimplifiedMasternodeListDAPIAddressProvider');
9
-
10
- const JsonRpcTransport = require('../transport/JsonRpcTransport/JsonRpcTransport');
11
- const requestJsonRpc = require('../transport/JsonRpcTransport/requestJsonRpc');
12
- const createJsonTransportError = require('../transport/JsonRpcTransport/createJsonTransportError');
9
+ const createMasternodeListStreamFactory = require('../SimplifiedMasternodeListProvider/createMasternodeListStreamFactory');
13
10
 
14
11
  const DAPIClientError = require('../errors/DAPIClientError');
15
12
 
@@ -82,17 +79,15 @@ function createDAPIAddressProviderFromOptions(options) {
82
79
  options,
83
80
  );
84
81
 
85
- const jsonRpcTransport = new JsonRpcTransport(
82
+ const createStream = createMasternodeListStreamFactory(
86
83
  createDAPIAddressProviderFromOptions,
87
- requestJsonRpc,
88
84
  listDAPIAddressProvider,
89
- createJsonTransportError,
90
85
  options,
91
86
  );
92
87
 
93
88
  const smlProvider = new SimplifiedMasternodeListProvider(
94
- jsonRpcTransport,
95
- { network: options.network },
89
+ createStream,
90
+ options,
96
91
  );
97
92
 
98
93
  return new SimplifiedMasternodeListDAPIAddressProvider(
@@ -1,15 +1,15 @@
1
1
  const broadcastTransactionFactory = require('./broadcastTransactionFactory');
2
- const generateToAddressFactory = require('./generateToAddressFactory');
3
2
  const getBestBlockHashFactory = require('./getBestBlockHashFactory');
3
+ const getBestBlockHeightFactory = require('./getBestBlockHeightFactory');
4
4
  const getBlockByHashFactory = require('./getBlockByHashFactory');
5
5
  const getBlockByHeightFactory = require('./getBlockByHeightFactory');
6
6
  const getBlockHashFactory = require('./getBlockHashFactory');
7
- const getMnListDiffFactory = require('./getMnListDiffFactory');
8
7
  const getBlockchainStatusFactory = require('./getBlockchainStatusFactory');
9
8
  const getMasternodeStatusFactory = require('./getMasternodeStatusFactory');
10
9
  const getTransactionFactory = require('./getTransaction/getTransactionFactory');
11
10
  const subscribeToTransactionsWithProofsFactory = require('./subscribeToTransactionsWithProofsFactory');
12
11
  const subscribeToBlockHeadersWithChainLocksFactory = require('./subscribeToBlockHeadersWithChainLocksFactory');
12
+ const subscribeToToMasternodeListFactory = require('./subscribeToMasternodeListFactory');
13
13
 
14
14
  class CoreMethodsFacade {
15
15
  /**
@@ -18,12 +18,11 @@ class CoreMethodsFacade {
18
18
  */
19
19
  constructor(jsonRpcTransport, grpcTransport) {
20
20
  this.broadcastTransaction = broadcastTransactionFactory(grpcTransport);
21
- this.generateToAddress = generateToAddressFactory(jsonRpcTransport);
22
21
  this.getBestBlockHash = getBestBlockHashFactory(jsonRpcTransport);
22
+ this.getBestBlockHeight = getBestBlockHeightFactory(grpcTransport);
23
23
  this.getBlockByHash = getBlockByHashFactory(grpcTransport);
24
24
  this.getBlockByHeight = getBlockByHeightFactory(grpcTransport);
25
25
  this.getBlockHash = getBlockHashFactory(jsonRpcTransport);
26
- this.getMnListDiff = getMnListDiffFactory(jsonRpcTransport);
27
26
  this.getBlockchainStatus = getBlockchainStatusFactory(grpcTransport);
28
27
  this.getMasternodeStatus = getMasternodeStatusFactory(grpcTransport);
29
28
  this.getTransaction = getTransactionFactory(grpcTransport);
@@ -33,6 +32,9 @@ class CoreMethodsFacade {
33
32
  this.subscribeToBlockHeadersWithChainLocks = subscribeToBlockHeadersWithChainLocksFactory(
34
33
  grpcTransport,
35
34
  );
35
+ this.subscribeToMasternodeList = subscribeToToMasternodeListFactory(
36
+ grpcTransport,
37
+ );
36
38
  }
37
39
  }
38
40
 
@@ -0,0 +1,34 @@
1
+ const {
2
+ v0: {
3
+ GetBestBlockHeightRequest,
4
+ CorePromiseClient,
5
+ },
6
+ } = require('@dashevo/dapi-grpc');
7
+
8
+ /**
9
+ *
10
+ * @param {GrpcTransport} grpcTransport
11
+ * @returns {getBestBlockHeight}
12
+ */
13
+ function getBestBlockHeightFactory(grpcTransport) {
14
+ /**
15
+ * Returns block height of chain tip
16
+ * @typedef {getBestBlockHeight}
17
+ * @param {DAPIClientOptions} [options]
18
+ * @returns {Promise<string>}
19
+ */
20
+ async function getBestBlockHeight(options = {}) {
21
+ const response = await grpcTransport.request(
22
+ CorePromiseClient,
23
+ 'getBestBlockHeight',
24
+ new GetBestBlockHeightRequest(),
25
+ options,
26
+ );
27
+
28
+ return response.getHeight();
29
+ }
30
+
31
+ return getBestBlockHeight;
32
+ }
33
+
34
+ module.exports = getBestBlockHeightFactory;
@@ -0,0 +1,46 @@
1
+ const {
2
+ v0: {
3
+ MasternodeListRequest,
4
+ CorePromiseClient,
5
+ },
6
+ } = require('@dashevo/dapi-grpc');
7
+
8
+ /**
9
+ * @param {GrpcTransport} grpcTransport
10
+ * @returns {subscribeToMasternodeList}
11
+ */
12
+ function subscribeToMasternodeListFactory(grpcTransport) {
13
+ /**
14
+ * @typedef {subscribeToMasternodeList}
15
+ * @param {DAPIClientOptions & subscribeToMasternodeListOptions} [options]
16
+ * @returns {
17
+ * EventEmitter|!grpc.web.ClientReadableStream<!MasternodeListResponse>
18
+ * }
19
+ */
20
+ async function subscribeToMasternodeList(options = { }) {
21
+ // eslint-disable-next-line no-param-reassign
22
+ options = {
23
+ // Override global timeout option
24
+ // and timeout for this method by default
25
+ timeout: undefined,
26
+ ...options,
27
+ };
28
+
29
+ const request = new MasternodeListRequest();
30
+
31
+ return grpcTransport.request(
32
+ CorePromiseClient,
33
+ 'subscribeToMasternodeList',
34
+ request,
35
+ options,
36
+ );
37
+ }
38
+
39
+ return subscribeToMasternodeList;
40
+ }
41
+
42
+ /**
43
+ * @typedef {object} subscribeToMasternodeListOptions
44
+ */
45
+
46
+ module.exports = subscribeToMasternodeListFactory;
@@ -29,7 +29,7 @@ function subscribeToTransactionsWithProofsFactory(grpcTransport) {
29
29
  * EventEmitter|!grpc.web.ClientReadableStream<!TransactionsWithProofsResponse>
30
30
  * }
31
31
  */
32
- async function subscribeToTransactionsWithProofs(bloomFilter, options = { }) {
32
+ async function subscribeToTransactionsWithProofs(bloomFilter, options = {}) {
33
33
  // eslint-disable-next-line no-param-reassign
34
34
  options = {
35
35
  count: 0,
@@ -12,8 +12,7 @@ const wait = require('../utils/wait');
12
12
  * delay in MS to perform retry after an error
13
13
  */
14
14
  const defaultOptions = {
15
- // TODO: manage timeout according to the Nginx setting of the node
16
- autoReconnectInterval: 50000,
15
+ autoReconnectInterval: 600000,
17
16
  maxRetriesOnError: 10,
18
17
  retryOnErrorDelay: 1000,
19
18
  };
@@ -57,6 +56,8 @@ class ReconnectableStream extends EventEmitter {
57
56
 
58
57
  const opts = { ...defaultOptions, ...options };
59
58
 
59
+ this.logger = opts.logger || { debug: () => {} };
60
+
60
61
  /**
61
62
  * Auto-reconnect interval in millisecond
62
63
  * It is needed to automatically reconnect to another DAPI node
@@ -109,6 +110,8 @@ class ReconnectableStream extends EventEmitter {
109
110
  }
110
111
 
111
112
  async connect(...args) {
113
+ // eslint-disable-next-line no-unused-expressions
114
+ this.logger.debug('[ReconnectableStream] Connecting to stream');
112
115
  // Memorize current stream args (which can be altered by beforeReconnect logic)
113
116
  this.args = args;
114
117
 
@@ -127,6 +130,8 @@ class ReconnectableStream extends EventEmitter {
127
130
  if (this.reconnectTimeout) {
128
131
  throw new Error('Auto reconnect timeout is already running.');
129
132
  }
133
+ // eslint-disable-next-line no-unused-expressions
134
+ this.logger.debug('[ReconnectableStream] Setting reconnect timeout');
130
135
  this.reconnectTimeout = this.setTimeout(
131
136
  this.reconnect,
132
137
  this.autoReconnectInterval,
@@ -134,6 +139,8 @@ class ReconnectableStream extends EventEmitter {
134
139
  }
135
140
 
136
141
  reconnect() {
142
+ // eslint-disable-next-line no-unused-expressions
143
+ this.logger.debug('[ReconnectableStream] Try reconnecting to stream');
137
144
  if (this.reconnectTimeout) {
138
145
  this.reconnectTimeout = null;
139
146
  this.stream.cancel();
@@ -146,6 +153,8 @@ class ReconnectableStream extends EventEmitter {
146
153
  this.emit(EVENTS.BEFORE_RECONNECT, updateArgs);
147
154
  this.connect(...newArgs)
148
155
  .catch((connectError) => this.emit(EVENTS.ERROR, connectError));
156
+ // eslint-disable-next-line no-unused-expressions
157
+ this.logger.debug('[ReconnectableStream] Reconnected to stream');
149
158
  }
150
159
  }
151
160
 
@@ -186,9 +195,13 @@ class ReconnectableStream extends EventEmitter {
186
195
  * @private
187
196
  */
188
197
  endHandler() {
189
- this.stopAutoReconnect();
190
- this.stream = null;
191
- this.emit(EVENTS.END);
198
+ // eslint-disable-next-line no-unused-expressions
199
+ this.logger.debug('[ReconnectableStream] End handler, stream exists:', !!this.stream);
200
+ if (this.stream) {
201
+ this.stopAutoReconnect();
202
+ this.stream = null;
203
+ this.emit(EVENTS.END);
204
+ }
192
205
  }
193
206
 
194
207
  /**
@@ -197,9 +210,21 @@ class ReconnectableStream extends EventEmitter {
197
210
  * @param e
198
211
  */
199
212
  errorHandler(e) {
213
+ // eslint-disable-next-line no-unused-expressions
214
+ this.logger.debug(`[ReconnectableStream] Error in stream, code ${e.code}, e:`, e);
215
+
200
216
  // In case of cancellation nothing has to happen.
217
+ // Do not retry UNKNOWN error code - HACH for grpc-web that ignores following error that happens
218
+ // in a while after stream cancellation
219
+ // Error message:
220
+ // "Response closed without grpc-status (Headers only) {
221
+ // [Error: Response closed without grpc-status (Headers only)]"
201
222
  // TODO: do we need to propagate GrpcErrorCodes.CANCELLED further?
202
- if (e.code === GrpcErrorCodes.CANCELLED) {
223
+ if (e.code === GrpcErrorCodes.CANCELLED
224
+ || (e.code === GrpcErrorCodes.UNKNOWN && this.stream === null)
225
+ ) {
226
+ // e.code
227
+ this.logger.debug(`[ReconnectableStream] Returning from error handler without restart, error code ${e.code}, e:`);
203
228
  return;
204
229
  }
205
230
 
@@ -212,6 +237,8 @@ class ReconnectableStream extends EventEmitter {
212
237
  * @param e
213
238
  */
214
239
  retryOnError(e) {
240
+ // eslint-disable-next-line no-unused-expressions
241
+ this.logger.debug('[ReconnectableStream] Error handler', e);
215
242
  // Stop reconnect timeout if there is one
216
243
  this.stopAutoReconnect();
217
244
 
@@ -252,9 +279,13 @@ class ReconnectableStream extends EventEmitter {
252
279
  * @private
253
280
  */
254
281
  stopAutoReconnect() {
282
+ // eslint-disable-next-line no-unused-expressions
283
+ this.logger.debug('[ReconnectableStream] Stopping auto reconnect');
255
284
  if (this.reconnectTimeout) {
256
285
  this.clearTimeout(this.reconnectTimeout);
257
286
  this.reconnectTimeout = null;
287
+ // eslint-disable-next-line no-unused-expressions
288
+ this.logger.debug('[ReconnectableStream] Stoped auto reconnect');
258
289
  }
259
290
  }
260
291
 
@@ -265,7 +296,18 @@ class ReconnectableStream extends EventEmitter {
265
296
  * @returns {*}
266
297
  */
267
298
  cancel() {
299
+ // eslint-disable-next-line no-unused-expressions
300
+ this.logger.debug('[ReconnectableStream] Canceling streams');
268
301
  this.stopAutoReconnect();
302
+ // Hack for browsers to properly unsubscribe from ERROR event.
303
+ // (It will continue propagating despite of calling cancel)
304
+ // Ref to unsubscribe from ERROR event
305
+ const { stream } = this;
306
+ setTimeout(() => {
307
+ stream.removeListener(EVENTS.ERROR, this.errorHandler);
308
+ // endHandler
309
+ this.stream = null;
310
+ }, 1000);
269
311
  return this.stream.cancel();
270
312
  }
271
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashevo/dapi-client",
3
- "version": "1.0.0-pr.1902.1",
3
+ "version": "1.0.0-pr.1935.A",
4
4
  "description": "Client library used to access Dash DAPI endpoints",
5
5
  "main": "lib/index.js",
6
6
  "contributors": [
@@ -26,11 +26,11 @@
26
26
  }
27
27
  ],
28
28
  "dependencies": {
29
- "@dashevo/dapi-grpc": "1.0.0-pr.1902.1",
30
- "@dashevo/dash-spv": "1.0.0-pr.1902.1",
31
- "@dashevo/dashcore-lib": "~0.21.1",
32
- "@dashevo/grpc-common": "1.0.0-pr.1902.1",
33
- "@dashevo/wasm-dpp": "1.0.0-pr.1902.1",
29
+ "@dashevo/dapi-grpc": "1.0.0-pr.1935.A",
30
+ "@dashevo/dash-spv": "1.0.0-pr.1935.A",
31
+ "@dashevo/dashcore-lib": "~0.21.2",
32
+ "@dashevo/grpc-common": "1.0.0-pr.1935.A",
33
+ "@dashevo/wasm-dpp": "1.0.0-pr.1935.A",
34
34
  "bs58": "^4.0.1",
35
35
  "cbor": "^8.0.0",
36
36
  "google-protobuf": "^3.12.2",
@@ -57,7 +57,7 @@
57
57
  "eslint-plugin-import": "^2.29.0",
58
58
  "eslint-plugin-jsdoc": "^46.9.0",
59
59
  "events": "^3.3.0",
60
- "karma": "^6.4.1",
60
+ "karma": "^6.4.3",
61
61
  "karma-chai": "^0.1.0",
62
62
  "karma-chrome-launcher": "^3.1.0",
63
63
  "karma-firefox-launcher": "^2.1.2",
@@ -92,7 +92,7 @@
92
92
  "test:integration": "mocha './test/integration/**/*.spec.js'",
93
93
  "test:node": "NODE_ENV=test mocha",
94
94
  "test:browsers": "karma start ./karma.conf.js --single-run",
95
- "test:coverage": "NODE_ENV=test nyc --check-coverage --stmts=98 --branch=98 --funcs=98 --lines=94.99 yarn run mocha 'test/unit/**/*.spec.js' 'test/integration/**/*.spec.js'",
95
+ "test:coverage": "NODE_ENV=test nyc --check-coverage --stmts=98 --branch=98 --funcs=98 --lines=90 yarn run mocha 'test/unit/**/*.spec.js' 'test/integration/**/*.spec.js'",
96
96
  "prepublishOnly": "yarn run build:web"
97
97
  },
98
98
  "ultra": {
@@ -1,13 +0,0 @@
1
- **Usage**: `await client.core.generateToAddress(blockMumber, address, options)`
2
- **Description**: Allow to broadcast a valid **signed** transaction to the network.
3
- **Notes**: Will only works on regtest.
4
-
5
- Parameters:
6
-
7
- | parameters | type | required | Description |
8
- |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
9
- | **blocksNumber** | Number | yes | A number of block to see generated on the regtest network |
10
- | **address** | String | yes | The address that will receive the newly generated Dash |
11
- | **options** | DAPIClientOptions | no | |
12
-
13
- Returns : {Promise<string[]>} - a set of generated blockhashes.
@@ -1,12 +0,0 @@
1
- **Usage**: `await client.core.getMnListDiff(baseBlockHash, blockHash, options)`
2
- **Description**: Allow to fetch a specific block hash from its height
3
-
4
- Parameters:
5
-
6
- | parameters | type | required | Description |
7
- |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
- | **baseBlockHash** | String | yes | hash or height of start block |
9
- | **blockHash** | String | yes | hash or height of end block |
10
- | **options** | DAPIClientOptions | no | |
11
-
12
- Returns : {Promise<object>} - The Masternode List Diff of the specified period
@@ -1,25 +0,0 @@
1
- /**
2
- * @param {JsonRpcTransport} jsonRpcTransport
3
- * @returns {generateToAddress}
4
- */
5
- function generateToAddressFactory(jsonRpcTransport) {
6
- /**
7
- * ONLY FOR TESTING PURPOSES WITH REGTEST. WILL NOT WORK ON TESTNET/LIVENET.
8
- * @typedef {generateToAddress}
9
- * @param {number} blocksNumber - Number of blocks to generate
10
- * @param {string} address - The address that will receive the newly generated Dash
11
- * @param {DAPIClientOptions} [options]
12
- * @returns {Promise<string[]>} - block hashes
13
- */
14
- function generateToAddress(blocksNumber, address, options = {}) {
15
- return jsonRpcTransport.request(
16
- 'generateToAddress',
17
- { blocksNumber, address },
18
- options,
19
- );
20
- }
21
-
22
- return generateToAddress;
23
- }
24
-
25
- module.exports = generateToAddressFactory;
@@ -1,21 +0,0 @@
1
- /**
2
- * @param {JsonRpcTransport} jsonRpcTransport
3
- * @returns {getMnListDiff}
4
- */
5
- function getMnListDiffFactory(jsonRpcTransport) {
6
- /**
7
- * Get deterministic masternodelist diff
8
- * @typedef {getMnListDiff}
9
- * @param {string} baseBlockHash - hash or height of start block
10
- * @param {string} blockHash - hash or height of end block
11
- * @param {DAPIClientOptions} [options]
12
- * @returns {Promise<object>}
13
- */
14
- function getMnListDiff(baseBlockHash, blockHash, options = {}) {
15
- return jsonRpcTransport.request('getMnListDiff', { baseBlockHash, blockHash }, options);
16
- }
17
-
18
- return getMnListDiff;
19
- }
20
-
21
- module.exports = getMnListDiffFactory;