@dashevo/dapi-client 1.0.0-pr.1875.1 → 1.0.0-pr.1883.2
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 +2 -1
- package/docs/usage/application/core/subscribeToBlockHeadersWithChainLocks.md +39 -0
- package/docs/usage/application/core/subscribeToMasternodeList.md +24 -0
- package/docs/usage/{utils → application/core}/subscribeToTransactionsWithProofs.md +13 -10
- package/lib/BlockHeadersProvider/createBlockHeadersProviderFromOptions.js +4 -2
- package/lib/DAPIClient.js +19 -1
- package/lib/SimplifiedMasternodeListProvider/SimplifiedMasternodeListProvider.js +174 -65
- package/lib/SimplifiedMasternodeListProvider/createMasternodeListStreamFactory.js +49 -0
- package/lib/dapiAddressProvider/createDAPIAddressProviderFromOptions.js +4 -9
- package/lib/methods/core/CoreMethodsFacade.js +4 -4
- package/lib/methods/core/subscribeToMasternodeListFactory.js +46 -0
- package/lib/methods/core/subscribeToTransactionsWithProofsFactory.js +1 -1
- package/lib/transport/ReconnectableStream.js +48 -6
- package/package.json +6 -6
- package/docs/usage/application/core/generateToAddress.md +0 -13
- package/docs/usage/application/core/getMnListDiff.md +0 -12
- package/lib/methods/core/generateToAddressFactory.js +0 -25
- package/lib/methods/core/getMnListDiffFactory.js +0 -21
package/docs/_sidebar.md
CHANGED
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
- [.getBlockByHash()](usage/application/core/getBlockByHash.md)
|
|
12
12
|
- [.getBlockByHeight()](usage/application/core/getBlockByHeight.md)
|
|
13
13
|
- [.getBlockHash()](usage/application/core/getBlockHash.md)
|
|
14
|
-
- [.getMnListDiff()](usage/application/core/getMnListDiff.md)
|
|
15
14
|
- [.getStatus()](usage/application/core/getStatus.md)
|
|
16
15
|
- [.getTransaction()](usage/application/core/getTransaction.md)
|
|
17
16
|
- [.subscribeToTransactionsWithProofs()](usage/application/core/subscribeToTransactionsWithProofs.md)
|
|
17
|
+
- [.subscribeToBlockHeadersWithChainLocks()](usage/application/core/subscribeToBlockHeadersWithChainLocks.md)
|
|
18
|
+
- [.subscribeToMasternodeList()](usage/application/core/subscribeToMasternodeList.md)
|
|
18
19
|
- Platform
|
|
19
20
|
- [.broadcastStateTransition()](usage/application/platform/broadcastStateTransition.md)
|
|
20
21
|
- [.getDataContract()](usage/application/platform/getDataContract.md)
|
|
@@ -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
|
|
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
|
|
28
|
-
const
|
|
29
|
+
const rawMerkleBlock = response.getRawMerkleBlock();
|
|
30
|
+
const rawTransactions = response.getRawTransactions();
|
|
29
31
|
|
|
30
32
|
if (merkleBlock) {
|
|
31
|
-
const
|
|
33
|
+
const merkleBlock = new MerkleBlock(rawMerkleBlock);
|
|
34
|
+
console.dir(merkleBlock);
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
if (transactions) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @type {ReconnectableStream}
|
|
23
|
+
*/
|
|
24
|
+
this.stream = undefined;
|
|
25
|
+
this.removeStreamListeners = () => {};
|
|
23
26
|
|
|
24
|
-
|
|
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.
|
|
33
|
-
await this.
|
|
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
|
-
*
|
|
46
|
+
* Subscribe to simplified masternodes list updates. No need to call it manually
|
|
41
47
|
* @private
|
|
42
|
-
* @returns {
|
|
48
|
+
* @returns {Promise<void>}
|
|
43
49
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
56
|
+
this.logger.debug('Starting masternode list stream');
|
|
70
57
|
|
|
71
|
-
this.
|
|
72
|
-
}
|
|
58
|
+
this.stream = await this.createStream();
|
|
73
59
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
83
|
-
'
|
|
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
|
-
|
|
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
|
-
*
|
|
93
|
-
* @private
|
|
202
|
+
* Unsubscribe from masternode list updates
|
|
94
203
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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
|
-
|
|
95
|
-
|
|
89
|
+
createStream,
|
|
90
|
+
options,
|
|
96
91
|
);
|
|
97
92
|
|
|
98
93
|
return new SimplifiedMasternodeListDAPIAddressProvider(
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
const broadcastTransactionFactory = require('./broadcastTransactionFactory');
|
|
2
|
-
const generateToAddressFactory = require('./generateToAddressFactory');
|
|
3
2
|
const getBestBlockHashFactory = require('./getBestBlockHashFactory');
|
|
4
3
|
const getBestBlockHeightFactory = require('./getBestBlockHeightFactory');
|
|
5
4
|
const getBlockByHashFactory = require('./getBlockByHashFactory');
|
|
6
5
|
const getBlockByHeightFactory = require('./getBlockByHeightFactory');
|
|
7
6
|
const getBlockHashFactory = require('./getBlockHashFactory');
|
|
8
|
-
const getMnListDiffFactory = require('./getMnListDiffFactory');
|
|
9
7
|
const getBlockchainStatusFactory = require('./getBlockchainStatusFactory');
|
|
10
8
|
const getMasternodeStatusFactory = require('./getMasternodeStatusFactory');
|
|
11
9
|
const getTransactionFactory = require('./getTransaction/getTransactionFactory');
|
|
12
10
|
const subscribeToTransactionsWithProofsFactory = require('./subscribeToTransactionsWithProofsFactory');
|
|
13
11
|
const subscribeToBlockHeadersWithChainLocksFactory = require('./subscribeToBlockHeadersWithChainLocksFactory');
|
|
12
|
+
const subscribeToToMasternodeListFactory = require('./subscribeToMasternodeListFactory');
|
|
14
13
|
|
|
15
14
|
class CoreMethodsFacade {
|
|
16
15
|
/**
|
|
@@ -19,13 +18,11 @@ class CoreMethodsFacade {
|
|
|
19
18
|
*/
|
|
20
19
|
constructor(jsonRpcTransport, grpcTransport) {
|
|
21
20
|
this.broadcastTransaction = broadcastTransactionFactory(grpcTransport);
|
|
22
|
-
this.generateToAddress = generateToAddressFactory(jsonRpcTransport);
|
|
23
21
|
this.getBestBlockHash = getBestBlockHashFactory(jsonRpcTransport);
|
|
24
22
|
this.getBestBlockHeight = getBestBlockHeightFactory(grpcTransport);
|
|
25
23
|
this.getBlockByHash = getBlockByHashFactory(grpcTransport);
|
|
26
24
|
this.getBlockByHeight = getBlockByHeightFactory(grpcTransport);
|
|
27
25
|
this.getBlockHash = getBlockHashFactory(jsonRpcTransport);
|
|
28
|
-
this.getMnListDiff = getMnListDiffFactory(jsonRpcTransport);
|
|
29
26
|
this.getBlockchainStatus = getBlockchainStatusFactory(grpcTransport);
|
|
30
27
|
this.getMasternodeStatus = getMasternodeStatusFactory(grpcTransport);
|
|
31
28
|
this.getTransaction = getTransactionFactory(grpcTransport);
|
|
@@ -35,6 +32,9 @@ class CoreMethodsFacade {
|
|
|
35
32
|
this.subscribeToBlockHeadersWithChainLocks = subscribeToBlockHeadersWithChainLocksFactory(
|
|
36
33
|
grpcTransport,
|
|
37
34
|
);
|
|
35
|
+
this.subscribeToMasternodeList = subscribeToToMasternodeListFactory(
|
|
36
|
+
grpcTransport,
|
|
37
|
+
);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
190
|
-
this.stream
|
|
191
|
-
this.
|
|
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.
|
|
3
|
+
"version": "1.0.0-pr.1883.2",
|
|
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.
|
|
30
|
-
"@dashevo/dash-spv": "1.0.0-pr.
|
|
29
|
+
"@dashevo/dapi-grpc": "1.0.0-pr.1883.2",
|
|
30
|
+
"@dashevo/dash-spv": "1.0.0-pr.1883.2",
|
|
31
31
|
"@dashevo/dashcore-lib": "~0.21.1",
|
|
32
|
-
"@dashevo/grpc-common": "1.0.0-pr.
|
|
33
|
-
"@dashevo/wasm-dpp": "1.0.0-pr.
|
|
32
|
+
"@dashevo/grpc-common": "1.0.0-pr.1883.2",
|
|
33
|
+
"@dashevo/wasm-dpp": "1.0.0-pr.1883.2",
|
|
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.
|
|
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",
|
|
@@ -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;
|