@dashevo/dapi-client 0.21.8 → 0.22.0-dev.12
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/README.md +3 -3
- package/docs/_sidebar.md +20 -14
- package/docs/getting-started/quickstart.md +4 -2
- package/docs/usage/application/DAPIClient.md +29 -0
- package/docs/usage/application/core/broadcastTransaction.md +15 -0
- package/docs/usage/application/core/generateToAddress.md +13 -0
- package/docs/usage/application/core/getBestBlockHash.md +10 -0
- package/docs/usage/application/core/getBlockByHash.md +11 -0
- package/docs/usage/application/core/getBlockByHeight.md +11 -0
- package/docs/usage/application/core/getBlockHash.md +11 -0
- package/docs/usage/application/core/getMnListDiff.md +12 -0
- package/docs/usage/application/core/getStatus.md +29 -0
- package/docs/usage/application/core/getTransaction.md +11 -0
- package/docs/usage/application/platform/broadcastStateTransition.md +11 -0
- package/docs/usage/utils/subscribeToTransactionsWithProofs.md +1 -1
- package/lib/BlockHeadersProvider/BlockHeadersProvider.js +121 -0
- package/lib/BlockHeadersProvider/BlockHeadersReader.js +229 -0
- package/lib/BlockHeadersProvider/createBlockHeadersProviderFromOptions.js +77 -0
- package/lib/BlockHeadersProvider/interfaces/BlockHeadersReaderInterface.js +25 -0
- package/lib/DAPIClient.js +36 -1
- package/lib/methods/core/CoreMethodsFacade.js +4 -0
- package/lib/methods/core/subscribeToBlockHeadersWithChainLocksFactory.js +69 -0
- package/lib/methods/platform/getIdentitiesByPublicKeyHashes/GetIdentitiesByPublicKeyHashesResponse.js +10 -3
- package/lib/methods/platform/getIdentityIdsByPublicKeyHashes/GetIdentityIdsByPublicKeyHashesResponse.js +10 -3
- package/lib/methods/platform/response/Proof.js +6 -31
- package/lib/test/fixtures/getHeadersFixture.js +215 -0
- package/lib/test/fixtures/getProofFixture.js +2 -14
- package/package.json +5 -4
- package/docs/usage/application/applyStateTransition.md +0 -11
- package/docs/usage/payment/getBestBlockHash.md +0 -10
- package/docs/usage/payment/getBlockHash.md +0 -11
- package/docs/usage/payment/getUTXO.md +0 -15
- 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/
|
|
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/
|
|
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/
|
|
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
|
-
-
|
|
5
|
-
- [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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**: `
|
|
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;
|