@dashevo/dapi-client 0.21.6 → 0.22.0-dev.10

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 (33) hide show
  1. package/README.md +3 -3
  2. package/docs/_sidebar.md +20 -14
  3. package/docs/getting-started/quickstart.md +4 -2
  4. package/docs/usage/application/DAPIClient.md +29 -0
  5. package/docs/usage/application/core/broadcastTransaction.md +15 -0
  6. package/docs/usage/application/core/generateToAddress.md +13 -0
  7. package/docs/usage/application/core/getBestBlockHash.md +10 -0
  8. package/docs/usage/application/core/getBlockByHash.md +11 -0
  9. package/docs/usage/application/core/getBlockByHeight.md +11 -0
  10. package/docs/usage/application/core/getBlockHash.md +11 -0
  11. package/docs/usage/application/core/getMnListDiff.md +12 -0
  12. package/docs/usage/application/core/getStatus.md +29 -0
  13. package/docs/usage/application/core/getTransaction.md +11 -0
  14. package/docs/usage/application/platform/broadcastStateTransition.md +11 -0
  15. package/docs/usage/utils/subscribeToTransactionsWithProofs.md +1 -1
  16. package/lib/BlockHeadersProvider/BlockHeadersProvider.js +121 -0
  17. package/lib/BlockHeadersProvider/BlockHeadersReader.js +229 -0
  18. package/lib/BlockHeadersProvider/createBlockHeadersProviderFromOptions.js +77 -0
  19. package/lib/BlockHeadersProvider/interfaces/BlockHeadersReaderInterface.js +25 -0
  20. package/lib/DAPIClient.js +36 -1
  21. package/lib/methods/core/CoreMethodsFacade.js +4 -0
  22. package/lib/methods/core/subscribeToBlockHeadersWithChainLocksFactory.js +69 -0
  23. package/lib/methods/platform/getIdentitiesByPublicKeyHashes/GetIdentitiesByPublicKeyHashesResponse.js +10 -3
  24. package/lib/methods/platform/getIdentityIdsByPublicKeyHashes/GetIdentityIdsByPublicKeyHashesResponse.js +10 -3
  25. package/lib/methods/platform/response/Proof.js +6 -31
  26. package/lib/test/fixtures/getHeadersFixture.js +215 -0
  27. package/lib/test/fixtures/getProofFixture.js +2 -14
  28. package/package.json +6 -5
  29. package/docs/usage/application/applyStateTransition.md +0 -11
  30. package/docs/usage/payment/getBestBlockHash.md +0 -10
  31. package/docs/usage/payment/getBlockHash.md +0 -11
  32. package/docs/usage/payment/getUTXO.md +0 -15
  33. package/docs/usage/utils/getMnListDiff.md +0 -12
package/README.md CHANGED
@@ -12,7 +12,7 @@ Platform via the decentralized API ([DAPI](https://github.com/dashevo/dapi))
12
12
  hosted on Dash masternodes.
13
13
 
14
14
  - `DAPI-Client` provides automatic server (masternode) discovery using either a default seed node or a user-supplied one
15
- - `DAPI-Client` maps to DAPI's [RPC](https://github.com/dashevo/dapi/tree/master/lib/rpcServer/commands) and [gRPC](https://github.com/dashevo/dapi/tree/master/lib/grpcServer/handlers) endpoints
15
+ - `DAPI-Client` maps to DAPI's [RPC](https://github.com/dashevo/platform/tree/master/packages/dapi/lib/rpcServer/commands) and [gRPC](https://github.com/dashevo/platform/tree/master/packages/dapi/lib/grpcServer/handlers) endpoints
16
16
 
17
17
  ## Table of Contents
18
18
  - [Install](#install)
@@ -101,12 +101,12 @@ client.core.getBestBlockHash(options).then((r) => {
101
101
 
102
102
  ## Documentation
103
103
 
104
- More extensive documentation available at https://dashevo.github.io/js-dapi-client/.
104
+ More extensive documentation available at https://dashevo.github.io/platform/DAPI-Client/.
105
105
 
106
106
 
107
107
  ## Contributing
108
108
 
109
- Feel free to dive in! [Open an issue](https://github.com/dashevo/js-dapi-client/issues/new/choose) or submit PRs.
109
+ Feel free to dive in! [Open an issue](https://github.com/dashevo/platform/issues/new/choose) or submit PRs.
110
110
 
111
111
  ## License
112
112
 
package/docs/_sidebar.md CHANGED
@@ -1,18 +1,24 @@
1
1
  - Getting started
2
2
  - [Quick start](getting-started/quickstart.md)
3
3
  - Usage
4
- - Application
5
- - [`.applyStateTransition()`](usage/application/applyStateTransition.md)
6
- - [`.getDataContract()`](usage/application/getDataContract.md)
7
- - [`.getDocuments()`](usage/application/getDocuments.md)
8
- - [`.getIdentity()`](usage/application/getIdentity.md)
9
- - [`.getIdentityByFirstPublicKey()`](usage/application/getIdentityByFirstPublicKey.md)
10
- - [`.getIdentityIdByFirstPublicKey()`](usage/application/getIdentityIdByFirstPublicKey.md)
11
- - Payment
12
- - [`.getBestBlockHash()`](usage/payment/getBestBlockHash.md)
13
- - [`.getBlockHash()`](usage/payment/getBlockHash.md)
14
- - [`.getUTXO()`](usage/payment/getUTXO.md)
15
- - Utilities
16
- - [`.getMnListDiff()`](usage/utils/getMnListDiff.md)
17
- - [`.subscribeToTransactionsWithProofs()`](usage/utils/subscribeToTransactionsWithProofs.md)
4
+ - DAPIClient
5
+ - [new DAPIClient()](usage/application/DAPIClient.md)
6
+ - Core
7
+ - [.broadcastTransaction()](usage/application/core/broadcastTransaction.md)
8
+ - [.generateToAddress()](usage/application/core/generateToAddress.md)
9
+ - [.getBestBlockHash()](usage/application/core/getBestBlockHash.md)
10
+ - [.getBlockByHash()](usage/application/core/getBlockByHash.md)
11
+ - [.getBlockByHeight()](usage/application/core/getBlockByHeight.md)
12
+ - [.getBlockHash()](usage/application/core/getBlockHash.md)
13
+ - [.getMnListDiff()](usage/application/core/getMnListDiff.md)
14
+ - [.getStatus()](usage/application/core/getStatus.md)
15
+ - [.getTransaction()](usage/application/core/getTransaction.md)
16
+ - [.subscribeToTransactionsWithProofs()](usage/application/core/subscribeToTransactionsWithProofs.md)
17
+ - Platform
18
+ - [.broadcastStateTransition()](usage/application/platform/broadcastStateTransition.md)
19
+ - [.getDataContract()](usage/application/platform/getDataContract.md)
20
+ - [.getDocuments()](usage/application/platform/getDocuments.md)
21
+ - [.getIdentityByFirstPublicKey()](usage/application/platform/getIdentityByFirstPublicKey.md)
22
+ - [.getIdentity()](usage/application/platform/getIdentity.md)
23
+ - [.getIdentityIdByFirstPublicKey()](usage/application/platform/getIdentityIdByFirstPublicKey.md)
18
24
  - [License](https://github.com/dashevo/dapi-client/blob/master/LICENSE)
@@ -1,6 +1,6 @@
1
1
  # Quick start
2
2
 
3
- ### ES5/ES6 via NPM
3
+ ## ES5/ES6 via NPM
4
4
 
5
5
  In order to use this library in Node, you will need to add it to your project as a dependency.
6
6
 
@@ -10,7 +10,7 @@ Having [NodeJS](https://nodejs.org/) installed, just type in your terminal :
10
10
  npm install @dashevo/dapi-client
11
11
  ```
12
12
 
13
- ### CDN Standalone
13
+ ## CDN Standalone
14
14
 
15
15
  For browser usage, you can also directly rely on unpkg :
16
16
 
@@ -18,6 +18,8 @@ For browser usage, you can also directly rely on unpkg :
18
18
  <script src="https://unpkg.com/@dashevo/dapi-client"></script>
19
19
  ```
20
20
 
21
+ You can see an [example usage here](https://github.com/dashevo/js-dapi-client/blob/master/examples/web/web.usage.html)
22
+
21
23
  ## Initialization
22
24
 
23
25
  ```js
@@ -0,0 +1,29 @@
1
+ **Usage**: `new DAPIClient(options)`
2
+ **Description**: This method creates a new DAPIClient instance.
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required[def value] | Description |
7
+ |-------------------------------------------|---------------------|-----------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
8
+ | **options** | Object | | |
9
+ | **options.dapiAddressProvider** | DAPIAddressProvider | no[ListDAPIAddressProvider] | Allow to override the default dapiAddressProvider (do not allow seeds or dapiAddresses params) |
10
+ | **options.seeds** | string[] | no[seeds] | Allow to override default seeds (to connect to specific node) |
11
+ | **options.network** | string|Network | no[=evonet] | Allow to setup the network to be used (livenet, testnet, evonet,..) |
12
+ | **options.timeout** | number | no[=2000] | Used to specify the timeout time in milliseconds. |
13
+ | **options.retries** | number | no[=3] | Used to specify the number of retries before aborting and erroring a request. |
14
+ | **options.baseBanTime** | number | no[=6000] | |
15
+
16
+ Returns : DAPIClient instance.
17
+
18
+ ```js
19
+ const DAPIClient = require('@dashevo/dapi-client');
20
+ const client = new DAPIClient({
21
+ timeout: 5000,
22
+ retries: 3,
23
+ network: 'livenet'
24
+ });
25
+ ```
26
+
27
+ **Notes**:
28
+ - Accessing the SimplifiedMasternodeListDAPIAddressProvider (or its overwrote instance), can be accessed via `client.dapiAddressProvider`.
29
+
@@ -0,0 +1,15 @@
1
+ **Usage**: `await client.core.broadcastTransaction(transaction)`
2
+ **Description**: Allow to broadcast a valid **signed** transaction to the network.
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **transaction** | Buffer | yes | A valid Buffer representation of a transaction |
9
+ | **options** | Object | | |
10
+ | **options.allowHighFees** | Boolean | no[=false] | As safety measure, "absurd" fees are rejected when considered to high. This allow to overwrite that comportement |
11
+ | **options.bypassLimits** | Boolean | no[=false] | Allow to bypass default transaction policy rules limitation |
12
+
13
+ Returns : transactionId (string).
14
+
15
+ N.B : The TransactionID provided is subject to [transaction malleability](https://dashcore.readme.io/docs/core-guide-transactions-transaction-malleability), and is not a source of truth (the transaction might be included in a block with a different txid).
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,10 @@
1
+ **Usage**: `await client.core.getBestBlockHash(options)`
2
+ **Description**: Allow to fetch the best (highest/latest block hash) 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 hash
@@ -0,0 +1,11 @@
1
+ **Usage**: `await client.core.getBlockByHash(hash, options)`
2
+ **Description**: Allow to fetch a specific block by its hash
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **hash** | String | yes | A valid block hash |
9
+ | **options** | DAPIClientOptions | no | |
10
+
11
+ Returns : {Promise<null|Buffer>} - The specified bufferized block
@@ -0,0 +1,11 @@
1
+ **Usage**: `await client.core.getBlockByHeight(height, options)`
2
+ **Description**: Allow to fetch a specific block by its height
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **height** | Number | yes | A valid block height |
9
+ | **options** | DAPIClientOptions | no | |
10
+
11
+ Returns : {Promise<null|Buffer>} - The specified bufferized block
@@ -0,0 +1,11 @@
1
+ **Usage**: `await client.core.getBlockHash(height, options)`
2
+ **Description**: Allow to fetch a specific block hash from its height
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **height** | Number | yes | A valid block height |
9
+ | **options** | DAPIClientOptions | no | |
10
+
11
+ Returns : {Promise<null|string>} - the corresponding block hash
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,29 @@
1
+ **Usage**: `await client.core.getStatus(options)`
2
+ **Description**: Allow to fetch a specific block hash from its height
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **options** | DAPIClientOptions | no | |
9
+
10
+ Returns : {Promise<object>} - Status object
11
+
12
+ ```js
13
+ const status = await client.core.getStatus()
14
+ /**
15
+ {
16
+ coreVersion: 150000,
17
+ protocolVersion: 70216,
18
+ blocks: 10630,
19
+ timeOffset: 0,
20
+ connections: 58,
21
+ proxy: '',
22
+ difficulty: 0.001745769130443678,
23
+ testnet: false,
24
+ relayFee: 0.00001,
25
+ errors: '',
26
+ network: 'testnet'
27
+ }
28
+ **/
29
+ ```
@@ -0,0 +1,11 @@
1
+ **Usage**: `await client.core.getTransaction(id, options)`
2
+ **Description**: Allow to fetch a transaction by ID
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |---------------------------|---------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **id** | string | yes | A valid transaction id to fetch |
9
+ | **options** | DAPIClientOptions | no | |
10
+
11
+ Returns : {Promise<null|Buffer>} - The bufferized transaction
@@ -0,0 +1,11 @@
1
+ **Usage**: `async client.platform.broadcastStateTransition(stateTransition, options)`
2
+ **Description**: Send State Transition to machine
3
+
4
+ Parameters:
5
+
6
+ | parameters | type | required | Description |
7
+ |------------------------|-------------------|----------------| ------------------------------------------------------------------------------------------------ |
8
+ | **stateTransition** | Buffer | yes | A valid bufferized state transition |
9
+ | **options** | DAPIClientOptions | no | A valid state transition |
10
+
11
+ Returns : Promise<!BroadcastStateTransitionResponse>
@@ -1,4 +1,4 @@
1
- **Usage**: `async client.core.subscribeToTransactionsWithProofs(bloomFilter, options = { count: 0 })`\
1
+ **Usage**: `await client.core.subscribeToTransactionsWithProofs(bloomFilter, options = { count: 0 })`\
2
2
  **Description**: For any provided bloomfilter, it will return a ClientReadableStream streaming the transaction matching the filter.
3
3
 
4
4
 
@@ -0,0 +1,121 @@
1
+ const EventEmitter = require('events');
2
+ const { SpvChain } = require('@dashevo/dash-spv');
3
+
4
+ const BlockHeadersReader = require('./BlockHeadersReader');
5
+
6
+ /**
7
+ * @typedef {BlockHeadersProviderOptions} BlockHeadersProviderOptions
8
+ * @property {string} [network=testnet]
9
+ * @property {number} [maxParallelStreams=5] max parallel streams to read historical block headers
10
+ * @property {number} [targetBatchSize=100000] a target batch size per stream
11
+ * @property {number} [maxRetries=10] max amount of retries per stream connection
12
+ * @property {number} [autoStart=false] auto start fetching verifying block headers
13
+ */
14
+ const defaultOptions = {
15
+ network: 'testnet',
16
+ maxParallelStreams: 5,
17
+ targetBatchSize: 100000,
18
+ fromBlockHeight: 1,
19
+ maxRetries: 10,
20
+ autoStart: false,
21
+ };
22
+
23
+ const EVENTS = {
24
+ ERROR: 'error',
25
+ };
26
+
27
+ class BlockHeadersProvider extends EventEmitter {
28
+ /**
29
+ * @param {BlockHeadersProviderOptions} options
30
+ */
31
+ constructor(options = {}) {
32
+ super();
33
+ this.options = {
34
+ ...defaultOptions,
35
+ ...options,
36
+ };
37
+
38
+ this.spvChain = new SpvChain(this.options.network);
39
+ this.started = false;
40
+ }
41
+
42
+ /**
43
+ * @param {CoreMethodsFacade} coreMethods
44
+ */
45
+ setCoreMethods(coreMethods) {
46
+ this.coreMethods = coreMethods;
47
+ }
48
+
49
+ /**
50
+ * @param {BlockHeadersReader} blockHeadersReader
51
+ */
52
+ setBlockHeadersReader(blockHeadersReader) {
53
+ this.blockHeadersReader = blockHeadersReader;
54
+ }
55
+
56
+ /**
57
+ *
58
+ * @param spvChain
59
+ */
60
+ setSpvChain(spvChain) {
61
+ this.spvChain = spvChain;
62
+ }
63
+
64
+ async start() {
65
+ if (!this.coreMethods) {
66
+ throw new Error('Core methods have not been provided. Please use "setCoreMethods"');
67
+ }
68
+
69
+ if (this.started) {
70
+ throw new Error('BlockHeaderProvider has already been started');
71
+ }
72
+
73
+ const { chain: { blocksCount: bestBlockHeight } } = await this.coreMethods.getStatus();
74
+
75
+ if (!this.blockHeadersReader) {
76
+ this.blockHeadersReader = new BlockHeadersReader(
77
+ {
78
+ coreMethods: this.coreMethods,
79
+ maxParallelStreams: this.options.maxParallelStreams,
80
+ targetBatchSize: this.options.targetBatchSize,
81
+ maxRetries: this.options.maxRetries,
82
+ },
83
+ );
84
+ }
85
+
86
+ this.blockHeadersReader.on(BlockHeadersReader.EVENTS.ERROR, (e) => {
87
+ this.emit(EVENTS.ERROR, e);
88
+ });
89
+
90
+ this.blockHeadersReader.on(BlockHeadersReader.EVENTS.HISTORICAL_DATA_OBTAINED, () => {
91
+ this.blockHeadersReader.subscribeToNew(bestBlockHeight)
92
+ .catch((e) => {
93
+ this.emit(EVENTS.ERROR, e);
94
+ });
95
+ });
96
+
97
+ this.blockHeadersReader.on(BlockHeadersReader.EVENTS.BLOCK_HEADERS, (headers, reject) => {
98
+ try {
99
+ this.spvChain.addHeaders(headers.map((header) => Buffer.from(header)));
100
+ } catch (e) {
101
+ if (e.message === 'Some headers are invalid') {
102
+ reject(e);
103
+ } else {
104
+ this.emit(EVENTS.ERROR, e);
105
+ }
106
+ }
107
+ });
108
+
109
+ await this.blockHeadersReader.readHistorical(
110
+ this.options.fromBlockHeight,
111
+ bestBlockHeight - 1,
112
+ );
113
+
114
+ this.started = true;
115
+ }
116
+ }
117
+
118
+ BlockHeadersProvider.EVENTS = EVENTS;
119
+ BlockHeadersProvider.defaultOptions = defaultOptions;
120
+
121
+ module.exports = BlockHeadersProvider;
@@ -0,0 +1,229 @@
1
+ const { EventEmitter } = require('events');
2
+
3
+ const EVENTS = {
4
+ BLOCK_HEADERS: 'BLOCK_HEADERS',
5
+ HISTORICAL_DATA_OBTAINED: 'HISTORICAL_DATA_OBTAINED',
6
+ ERROR: 'error',
7
+ };
8
+
9
+ const COMMANDS = {
10
+ HANDLE_FINISHED_STREAM: 'HANDLE_FINISHED_STREAM',
11
+ HANDLE_STREAM_RETRY: 'HANDLE_STREAM_RETRY',
12
+ HANDLE_STREAM_ERROR: 'HANDLE_STREAM_ERROR',
13
+ };
14
+
15
+ /**
16
+ * @typedef BlockHeadersReaderOptions
17
+ * @property {CoreMethodsFacade} [coreMethods]
18
+ * @property {number} [maxParallelStreams]
19
+ * @property {number} [targetBatchSize]
20
+ * @property {number} [maxRetries]
21
+ */
22
+
23
+ class BlockHeadersReader extends EventEmitter {
24
+ /**
25
+ * @param {BlockHeadersReaderOptions} options
26
+ */
27
+ constructor(options = {}) {
28
+ super();
29
+ this.coreMethods = options.coreMethods;
30
+ this.maxParallelStreams = options.maxParallelStreams;
31
+ this.targetBatchSize = options.targetBatchSize;
32
+ this.maxRetries = options.maxRetries;
33
+
34
+ /**
35
+ * Holds references to the historical streams
36
+ *
37
+ * @type {*[]}
38
+ */
39
+ this.historicalStreams = [];
40
+ }
41
+
42
+ /**
43
+ * Reads historical block heights using multiple streams
44
+ *
45
+ * @param {number} fromBlockHeight
46
+ * @param {number} toBlockHeight
47
+ * @returns {Promise<void>}
48
+ */
49
+ async readHistorical(fromBlockHeight, toBlockHeight) {
50
+ if (this.historicalStreams.length) {
51
+ throw new Error('Historical streams are already running');
52
+ }
53
+
54
+ const totalAmount = toBlockHeight - fromBlockHeight + 1;
55
+ if (totalAmount === 0) {
56
+ return;
57
+ }
58
+
59
+ if (totalAmount < 0) {
60
+ throw new Error(`Invalid total amount of headers to read: ${totalAmount}`);
61
+ }
62
+
63
+ // Resubscribe to the stream in case of error, and replace the stream in the array
64
+ this.on(COMMANDS.HANDLE_STREAM_RETRY, (oldStream, newStream) => {
65
+ const index = this.historicalStreams.indexOf(oldStream);
66
+ this.historicalStreams[index] = newStream;
67
+ });
68
+
69
+ // Remove stream from the array in case of error
70
+ this.on(COMMANDS.HANDLE_STREAM_ERROR, (stream, e) => {
71
+ const index = this.historicalStreams.indexOf(stream);
72
+ this.historicalStreams.splice(index, 1);
73
+ this.emit(EVENTS.ERROR, e);
74
+ });
75
+
76
+ // Remove finished stream from the array and emit HISTORICAL_DATA_OBTAINED event
77
+ this.on(COMMANDS.HANDLE_FINISHED_STREAM, (stream) => {
78
+ const index = this.historicalStreams.indexOf(stream);
79
+ this.historicalStreams.splice(index, 1);
80
+ if (this.historicalStreams.length === 0) {
81
+ this.emit(EVENTS.HISTORICAL_DATA_OBTAINED);
82
+ }
83
+ });
84
+
85
+ const numStreams = Math.min(
86
+ Math.max(Math.round(totalAmount / this.targetBatchSize), 1),
87
+ this.maxParallelStreams,
88
+ );
89
+
90
+ const actualBatchSize = Math.ceil(totalAmount / numStreams);
91
+ for (let batchIndex = 0; batchIndex < numStreams; batchIndex += 1) {
92
+ const startingHeight = (batchIndex * actualBatchSize) + 1;
93
+ const count = Math.min(actualBatchSize, toBlockHeight - startingHeight + 1);
94
+
95
+ const subscribeWithRetries = this.subscribeToHistoricalBatch(this.maxRetries);
96
+
97
+ // eslint-disable-next-line no-await-in-loop
98
+ const stream = await subscribeWithRetries(startingHeight, count);
99
+ this.historicalStreams.push(stream);
100
+ }
101
+ }
102
+
103
+ stopReadingHistorical() {
104
+ this.removeAllListeners(COMMANDS.HANDLE_STREAM_RETRY);
105
+ this.removeAllListeners(COMMANDS.HANDLE_STREAM_ERROR);
106
+ this.removeAllListeners(COMMANDS.HANDLE_FINISHED_STREAM);
107
+ this.historicalStreams.forEach((stream) => stream.destroy());
108
+ this.historicalStreams = [];
109
+ }
110
+
111
+ /**
112
+ * Subscribes to continuously arriving block headers
113
+ *
114
+ * @param {number} fromBlockHeight
115
+ * @returns {Promise<Stream>}
116
+ */
117
+ async subscribeToNew(fromBlockHeight) {
118
+ const stream = await this.coreMethods.subscribeToBlockHeadersWithChainLocks({
119
+ fromBlockHeight,
120
+ });
121
+
122
+ stream.on('data', (data) => {
123
+ const blockHeaders = data.getBlockHeaders();
124
+
125
+ if (blockHeaders) {
126
+ /**
127
+ * Kills stream in case of deliberate rejection from the outside
128
+ *
129
+ * @param e
130
+ */
131
+ const rejectHeaders = (e) => {
132
+ stream.destroy(e);
133
+ };
134
+
135
+ this.emit(EVENTS.BLOCK_HEADERS, blockHeaders.getHeadersList(), rejectHeaders);
136
+ }
137
+ });
138
+
139
+ stream.on('error', (e) => {
140
+ this.emit(EVENTS.ERROR, e);
141
+ });
142
+
143
+ return stream;
144
+ }
145
+
146
+ /**
147
+ * A HOF that returns a function to subscribe to historical block headers and chain locks
148
+ * and handles retry logic
149
+ *
150
+ * @private
151
+ * @param {number} [maxRetries=0] - maximum amount of retries
152
+ * @returns {function(*, *): Promise<Stream>}
153
+ */
154
+ subscribeToHistoricalBatch(maxRetries = 0) {
155
+ let currentRetries = 0;
156
+
157
+ /**
158
+ * Subscribes to the stream of historical data and handles retry logic
159
+ *
160
+ * @param {number} fromBlockHeight
161
+ * @param {number} count
162
+ * @returns {Promise<Stream>}
163
+ */
164
+ const subscribeWithRetries = async (fromBlockHeight, count) => {
165
+ let headersObtained = 0;
166
+
167
+ const stream = await this.coreMethods.subscribeToBlockHeadersWithChainLocks({
168
+ fromBlockHeight,
169
+ count,
170
+ });
171
+
172
+ stream.on('data', (data) => {
173
+ const blockHeaders = data.getBlockHeaders();
174
+
175
+ if (blockHeaders) {
176
+ const headersList = blockHeaders.getHeadersList();
177
+
178
+ let rejected = false;
179
+
180
+ /**
181
+ * Kills stream in case of deliberate rejection from the outside
182
+ *
183
+ * @param e
184
+ */
185
+ const rejectHeaders = (e) => {
186
+ rejected = true;
187
+ stream.destroy(e);
188
+ };
189
+
190
+ this.emit(EVENTS.BLOCK_HEADERS, headersList, rejectHeaders);
191
+
192
+ if (!rejected) {
193
+ headersObtained += headersList.length;
194
+ }
195
+ }
196
+ });
197
+
198
+ stream.on('error', (streamError) => {
199
+ if (currentRetries < maxRetries) {
200
+ const newFromBlockHeight = fromBlockHeight + headersObtained;
201
+ const newCount = count - headersObtained;
202
+
203
+ subscribeWithRetries(newFromBlockHeight, newCount)
204
+ .then((newStream) => {
205
+ currentRetries += 1;
206
+ this.emit(COMMANDS.HANDLE_STREAM_RETRY, stream, newStream);
207
+ }).catch((e) => {
208
+ this.emit(COMMANDS.HANDLE_STREAM_ERROR, stream, e);
209
+ });
210
+ } else {
211
+ this.emit(COMMANDS.HANDLE_STREAM_ERROR, stream, streamError);
212
+ }
213
+ });
214
+
215
+ stream.on('end', () => {
216
+ this.emit(COMMANDS.HANDLE_FINISHED_STREAM, stream);
217
+ });
218
+
219
+ return stream;
220
+ };
221
+
222
+ return subscribeWithRetries;
223
+ }
224
+ }
225
+
226
+ BlockHeadersReader.EVENTS = EVENTS;
227
+ BlockHeadersReader.COMMANDS = COMMANDS;
228
+
229
+ module.exports = BlockHeadersReader;