@dashevo/dapi-client 0.24.0-dev.6 → 0.25.0-dev.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/README.md +7 -8
- package/lib/BlockHeadersProvider/BlockHeadersProvider.js +187 -50
- package/lib/BlockHeadersProvider/BlockHeadersReader.js +170 -88
- package/lib/BlockHeadersProvider/createBlockHeadersProviderFromOptions.js +31 -16
- package/lib/DAPIClient.js +0 -6
- package/lib/dapiAddressProvider/DAPIAddress.js +14 -40
- package/lib/dapiAddressProvider/ListDAPIAddressProvider.js +3 -4
- package/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js +8 -1
- package/lib/index.js +5 -0
- package/lib/methods/platform/response/Metadata.js +21 -1
- package/lib/methods/platform/response/Proof.js +16 -5
- package/lib/test/bootstrap.js +8 -0
- package/lib/test/fixtures/getMNListDiffsFixture.js +85 -1066
- package/lib/test/fixtures/getMetadataFixture.js +9 -1
- package/lib/test/fixtures/getProofFixture.js +3 -2
- package/lib/test/mocks/BlockHeadersWithChainLocksStreamMock.js +54 -0
- package/lib/test/mocks/mockHeadersChain.js +55 -0
- package/lib/transport/GrpcTransport/GrpcTransport.js +5 -10
- package/lib/transport/GrpcTransport/createGrpcTransportError.js +11 -14
- package/lib/transport/JsonRpcTransport/JsonRpcTransport.js +1 -1
- package/lib/transport/JsonRpcTransport/requestJsonRpc.js +22 -25
- package/lib/transport/ReconnectableStream.js +284 -0
- package/package.json +15 -12
- package/polyfills/fetch-polyfill.js +10 -0
- /package/lib/{test/utils → utils}/wait.js +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# DAPI Client
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@dashevo/dapi-client)
|
|
4
|
-
[](https://github.com/dashpay/platform/actions/workflows/release.yml)
|
|
5
|
+
[](https://github.com/dashpay/platform/releases/latest)
|
|
6
6
|
[](https://github.com/RichardLitt/standard-readme)
|
|
7
7
|
|
|
8
8
|
Client library used to access Dash DAPI endpoints
|
|
@@ -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/
|
|
15
|
+
- `DAPI-Client` maps to DAPI's [RPC](https://github.com/dashpay/platform/tree/master/packages/dapi/lib/rpcServer/commands) and [gRPC](https://github.com/dashpay/platform/tree/master/packages/dapi/lib/grpcServer/handlers) endpoints
|
|
16
16
|
|
|
17
17
|
## Table of Contents
|
|
18
18
|
- [Install](#install)
|
|
@@ -50,8 +50,7 @@ const DAPIClient = require('@dashevo/dapi-client');
|
|
|
50
50
|
var client = new DAPIClient({
|
|
51
51
|
seeds: [{
|
|
52
52
|
host: 'seed-1.evonet.networks.dash.org',
|
|
53
|
-
|
|
54
|
-
grpcPort: 3010,
|
|
53
|
+
port: 443,
|
|
55
54
|
}],
|
|
56
55
|
});
|
|
57
56
|
|
|
@@ -71,8 +70,8 @@ const DAPIClient = require('@dashevo/dapi-client');
|
|
|
71
70
|
|
|
72
71
|
var client = new DAPIClient({
|
|
73
72
|
dapiAddresses: [
|
|
74
|
-
'127.0.0.1:
|
|
75
|
-
'127.0.0.2:
|
|
73
|
+
'127.0.0.1:443',
|
|
74
|
+
'127.0.0.2:443',
|
|
76
75
|
],
|
|
77
76
|
});
|
|
78
77
|
|
|
@@ -106,7 +105,7 @@ More extensive documentation available at https://dashpay.github.io/platform/DAP
|
|
|
106
105
|
|
|
107
106
|
## Contributing
|
|
108
107
|
|
|
109
|
-
Feel free to dive in! [Open an issue](https://github.com/
|
|
108
|
+
Feel free to dive in! [Open an issue](https://github.com/dashpay/platform/issues/new/choose) or submit PRs.
|
|
110
109
|
|
|
111
110
|
## License
|
|
112
111
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
|
-
const {
|
|
2
|
+
const { configure: configureDashcore } = require('@dashevo/dashcore-lib');
|
|
3
|
+
const X11 = require('wasm-x11-hash');
|
|
4
|
+
const { SpvChain, SPVError } = require('@dashevo/dash-spv');
|
|
3
5
|
|
|
4
6
|
const BlockHeadersReader = require('./BlockHeadersReader');
|
|
5
7
|
|
|
@@ -9,41 +11,53 @@ const BlockHeadersReader = require('./BlockHeadersReader');
|
|
|
9
11
|
* @property {number} [maxParallelStreams=5] max parallel streams to read historical block headers
|
|
10
12
|
* @property {number} [targetBatchSize=100000] a target batch size per stream
|
|
11
13
|
* @property {number} [maxRetries=10] max amount of retries per stream connection
|
|
12
|
-
* @property {number} [autoStart=false] auto start fetching verifying block headers
|
|
13
14
|
*/
|
|
14
15
|
const defaultOptions = {
|
|
15
16
|
network: 'testnet',
|
|
16
|
-
maxParallelStreams:
|
|
17
|
-
targetBatchSize:
|
|
17
|
+
maxParallelStreams: 10,
|
|
18
|
+
targetBatchSize: 50000,
|
|
18
19
|
fromBlockHeight: 1,
|
|
19
20
|
maxRetries: 10,
|
|
20
|
-
autoStart: false,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const EVENTS = {
|
|
24
24
|
ERROR: 'error',
|
|
25
|
+
HISTORICAL_SYNC_STARTED: 'HISTORICAL_SYNC_STARTED',
|
|
26
|
+
CONTINUOUS_SYNC_STARTED: 'CONTINUOUS_SYNC_STARTED',
|
|
27
|
+
CHAIN_UPDATED: 'CHAIN_UPDATED',
|
|
28
|
+
HISTORICAL_DATA_OBTAINED: 'HISTORICAL_DATA_OBTAINED',
|
|
29
|
+
STOPPED: 'STOPPED',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const STATES = {
|
|
33
|
+
IDLE: 'IDLE',
|
|
34
|
+
HISTORICAL_SYNC: 'HISTORICAL_SYNC',
|
|
35
|
+
CONTINUOUS_SYNC: 'CONTINUOUS_SYNC',
|
|
25
36
|
};
|
|
26
37
|
|
|
27
38
|
class BlockHeadersProvider extends EventEmitter {
|
|
28
39
|
/**
|
|
29
40
|
* @param {BlockHeadersProviderOptions} options
|
|
41
|
+
* @param {Function} [createHistoricalSyncStream]
|
|
42
|
+
* @param {Function} [createContinuousSyncStream]
|
|
30
43
|
*/
|
|
31
|
-
constructor(options = {}) {
|
|
44
|
+
constructor(options = {}, createHistoricalSyncStream, createContinuousSyncStream) {
|
|
32
45
|
super();
|
|
33
46
|
this.options = {
|
|
34
47
|
...defaultOptions,
|
|
35
48
|
...options,
|
|
36
49
|
};
|
|
37
50
|
|
|
38
|
-
this.spvChain = new SpvChain(this.options.network);
|
|
39
|
-
this.started = false;
|
|
40
|
-
}
|
|
51
|
+
this.spvChain = new SpvChain(this.options.network, 100);
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.
|
|
53
|
+
this.state = STATES.IDLE;
|
|
54
|
+
this.wasmX11Ready = false;
|
|
55
|
+
|
|
56
|
+
this.errorHandler = this.errorHandler.bind(this);
|
|
57
|
+
this.headersHandler = this.headersHandler.bind(this);
|
|
58
|
+
this.historicalDataObtainedHandler = this.historicalDataObtainedHandler.bind(this);
|
|
59
|
+
this.createHistoricalSyncStream = createHistoricalSyncStream;
|
|
60
|
+
this.createContinuousSyncStream = createContinuousSyncStream;
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
/**
|
|
@@ -55,23 +69,24 @@ class BlockHeadersProvider extends EventEmitter {
|
|
|
55
69
|
|
|
56
70
|
/**
|
|
57
71
|
*
|
|
58
|
-
* @param spvChain
|
|
72
|
+
* @param {SpvChain} spvChain
|
|
59
73
|
*/
|
|
60
74
|
setSpvChain(spvChain) {
|
|
61
75
|
this.spvChain = spvChain;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
async init() {
|
|
82
|
+
if (!this.wasmX11Ready) {
|
|
83
|
+
const x11Hash = await X11();
|
|
84
|
+
configureDashcore({
|
|
85
|
+
x11hash: x11Hash,
|
|
86
|
+
});
|
|
87
|
+
this.wasmX11Ready = true;
|
|
71
88
|
}
|
|
72
89
|
|
|
73
|
-
const { chain: { blocksCount: bestBlockHeight } } = await this.coreMethods.getStatus();
|
|
74
|
-
|
|
75
90
|
if (!this.blockHeadersReader) {
|
|
76
91
|
this.blockHeadersReader = new BlockHeadersReader(
|
|
77
92
|
{
|
|
@@ -80,42 +95,164 @@ class BlockHeadersProvider extends EventEmitter {
|
|
|
80
95
|
targetBatchSize: this.options.targetBatchSize,
|
|
81
96
|
maxRetries: this.options.maxRetries,
|
|
82
97
|
},
|
|
98
|
+
this.createHistoricalSyncStream,
|
|
99
|
+
this.createContinuousSyncStream,
|
|
83
100
|
);
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
this.blockHeadersReader.on(BlockHeadersReader.EVENTS.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
this.blockHeadersReader.on(BlockHeadersReader.EVENTS.BLOCK_HEADERS, this.headersHandler);
|
|
104
|
+
this.blockHeadersReader.on(BlockHeadersReader.EVENTS.ERROR, this.errorHandler);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
destroyReader() {
|
|
108
|
+
this.removeReaderListeners();
|
|
109
|
+
this.blockHeadersReader = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Initializes SPV chain with a list of headers and a known lastSyncedHeaderHeight
|
|
114
|
+
*
|
|
115
|
+
* @param headers
|
|
116
|
+
* @param lastSyncedHeaderHeight
|
|
117
|
+
*/
|
|
118
|
+
initializeWith(headers, lastSyncedHeaderHeight) {
|
|
119
|
+
const firstHeaderHeight = lastSyncedHeaderHeight - headers.length + 1;
|
|
120
|
+
this.spvChain.reset(firstHeaderHeight);
|
|
121
|
+
this.spvChain.addHeaders(headers, firstHeaderHeight);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Checks whether spv chain has header at specified height and flushes chains if not
|
|
126
|
+
*
|
|
127
|
+
* @private
|
|
128
|
+
* @param height
|
|
129
|
+
*/
|
|
130
|
+
ensureChainRoot(height) {
|
|
131
|
+
// Flush spv chain in case header at specified height was not found
|
|
132
|
+
if (!this.spvChain.hashesByHeight.has(height - 1)) {
|
|
133
|
+
this.spvChain.reset(height);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Reads historical block headers
|
|
139
|
+
*
|
|
140
|
+
* @param fromBlockHeight
|
|
141
|
+
* @param toBlockHeight
|
|
142
|
+
* @returns {Promise<void>}
|
|
143
|
+
*/
|
|
144
|
+
async readHistorical(fromBlockHeight, toBlockHeight) {
|
|
145
|
+
if (this.state !== STATES.IDLE) {
|
|
146
|
+
throw new Error(`BlockHeaderProvider can not read historical data while being in ${this.state} state.`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await this.init();
|
|
150
|
+
|
|
151
|
+
this.ensureChainRoot(fromBlockHeight);
|
|
152
|
+
|
|
153
|
+
this.blockHeadersReader
|
|
154
|
+
.once(BlockHeadersReader.EVENTS.HISTORICAL_DATA_OBTAINED, this.historicalDataObtainedHandler);
|
|
108
155
|
|
|
109
156
|
await this.blockHeadersReader.readHistorical(
|
|
110
|
-
|
|
111
|
-
|
|
157
|
+
fromBlockHeight,
|
|
158
|
+
toBlockHeight,
|
|
112
159
|
);
|
|
113
160
|
|
|
114
|
-
this.
|
|
161
|
+
this.state = STATES.HISTORICAL_SYNC;
|
|
162
|
+
this.emit(EVENTS.HISTORICAL_SYNC_STARTED);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async startContinuousSync(fromBlockHeight) {
|
|
166
|
+
if (this.state !== STATES.IDLE) {
|
|
167
|
+
throw new Error(`BlockHeaderProvider can not sync continuous data while being in ${this.state} state.`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await this.init();
|
|
171
|
+
this.ensureChainRoot(fromBlockHeight);
|
|
172
|
+
await this.blockHeadersReader.subscribeToNew(fromBlockHeight);
|
|
173
|
+
this.state = STATES.CONTINUOUS_SYNC;
|
|
174
|
+
this.emit(EVENTS.CONTINUOUS_SYNC_STARTED);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async stop() {
|
|
178
|
+
if (this.state === STATES.IDLE) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.state === STATES.HISTORICAL_SYNC) {
|
|
183
|
+
this.blockHeadersReader.stopReadingHistorical();
|
|
184
|
+
} else if (this.state === STATES.CONTINUOUS_SYNC) {
|
|
185
|
+
this.blockHeadersReader.unsubscribeFromNew();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.destroyReader();
|
|
189
|
+
|
|
190
|
+
this.state = STATES.IDLE;
|
|
191
|
+
|
|
192
|
+
this.emit(EVENTS.STOPPED);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
errorHandler(e) {
|
|
196
|
+
this.destroyReader();
|
|
197
|
+
this.emit(EVENTS.ERROR, e);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @private
|
|
202
|
+
* @param headersData
|
|
203
|
+
* @param reject
|
|
204
|
+
*/
|
|
205
|
+
headersHandler(headersData, reject) {
|
|
206
|
+
const { headers, headHeight } = headersData;
|
|
207
|
+
|
|
208
|
+
// TODO: simulate headers rejection and debug
|
|
209
|
+
// BlockHeadersReader might not correctly set startBlockHeight
|
|
210
|
+
// which results in orphan chunks in SPV
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const headersAdded = this.spvChain.addHeaders(headers, headHeight);
|
|
214
|
+
|
|
215
|
+
if (headersAdded.length) {
|
|
216
|
+
// Calculate amount of removed headers in order to properly adjust head height
|
|
217
|
+
// TODO(spv): move this logic to SpvChain?
|
|
218
|
+
const difference = headers.length - headersAdded.length;
|
|
219
|
+
this.emit(EVENTS.CHAIN_UPDATED, headersAdded, headHeight + difference);
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
if (e instanceof SPVError) {
|
|
223
|
+
reject(e);
|
|
224
|
+
} else {
|
|
225
|
+
this.errorHandler(e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
historicalDataObtainedHandler() {
|
|
231
|
+
try {
|
|
232
|
+
this.spvChain.validate();
|
|
233
|
+
this.emit(EVENTS.HISTORICAL_DATA_OBTAINED);
|
|
234
|
+
this.removeReaderListeners();
|
|
235
|
+
} catch (e) {
|
|
236
|
+
this.errorHandler(e);
|
|
237
|
+
} finally {
|
|
238
|
+
this.state = STATES.IDLE;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
removeReaderListeners() {
|
|
243
|
+
this.blockHeadersReader.removeListener(BlockHeadersReader.EVENTS.ERROR, this.errorHandler);
|
|
244
|
+
this.blockHeadersReader
|
|
245
|
+
.removeListener(BlockHeadersReader.EVENTS.BLOCK_HEADERS, this.headersHandler);
|
|
246
|
+
this.blockHeadersReader
|
|
247
|
+
.removeListener(
|
|
248
|
+
BlockHeadersReader.EVENTS.HISTORICAL_DATA_OBTAINED,
|
|
249
|
+
this.historicalDataObtainedHandler,
|
|
250
|
+
);
|
|
115
251
|
}
|
|
116
252
|
}
|
|
117
253
|
|
|
118
254
|
BlockHeadersProvider.EVENTS = EVENTS;
|
|
119
|
-
BlockHeadersProvider.
|
|
255
|
+
BlockHeadersProvider.STATES = STATES;
|
|
256
|
+
BlockHeadersProvider.defaultOptions = { ...defaultOptions };
|
|
120
257
|
|
|
121
258
|
module.exports = BlockHeadersProvider;
|