@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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # DAPI Client
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@dashevo/dapi-client)](https://www.npmjs.com/package/@dashevo/dapi-client)
4
- [![Build Status](https://github.com/dashevo/platform/actions/workflows/release.yml/badge.svg)](https://github.com/dashevo/platform/actions/workflows/release.yml)
5
- [![Release Date](https://img.shields.io/github/release-date/dashevo/platform)](https://github.com/dashevo/platform/releases/latest)
4
+ [![Build Status](https://github.com/dashpay/platform/actions/workflows/release.yml/badge.svg)](https://github.com/dashpay/platform/actions/workflows/release.yml)
5
+ [![Release Date](https://img.shields.io/github/release-date/dashpay/platform)](https://github.com/dashpay/platform/releases/latest)
6
6
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen)](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/dashevo/platform/tree/master/packages/dapi/lib/rpcServer/commands) and [gRPC](https://github.com/dashevo/platform/tree/master/packages/dapi/lib/grpcServer/handlers) endpoints
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
- httpPort: 3000,
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:3000:3010',
75
- '127.0.0.2:3000:3010',
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/dashevo/platform/issues/new/choose) or submit PRs.
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 { SpvChain } = require('@dashevo/dash-spv');
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: 5,
17
- targetBatchSize: 100000,
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
- * @param {CoreMethodsFacade} coreMethods
44
- */
45
- setCoreMethods(coreMethods) {
46
- this.coreMethods = coreMethods;
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
- 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');
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.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
- });
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
- this.options.fromBlockHeight,
111
- bestBlockHeight - 1,
157
+ fromBlockHeight,
158
+ toBlockHeight,
112
159
  );
113
160
 
114
- this.started = true;
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.defaultOptions = defaultOptions;
255
+ BlockHeadersProvider.STATES = STATES;
256
+ BlockHeadersProvider.defaultOptions = { ...defaultOptions };
120
257
 
121
258
  module.exports = BlockHeadersProvider;