@dashevo/dash-spv 0.25.0-dev.2 → 0.25.0-dev.21
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/.mocharc.yml +4 -0
- package/.ultra.cache.json +1 -1
- package/lib/spvchain.js +115 -192
- package/lib/test/.eslintrc +9 -0
- package/lib/test/bootstrap.js +19 -0
- package/lib/x11.js +27 -0
- package/package.json +6 -4
- package/test/index.js +21 -11
- package/test/spvchain.js +50 -4
package/.mocharc.yml
ADDED
package/.ultra.cache.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"files":{".eslintignore":"5772aab4a649e8c99c5b74bd685792cd6a69f9ac",".eslintrc.json":"41aa1d3c1477f203b588dcb5c0015454edd94481",".gitignore":"095d365a56f423d4fab2345c95c137e7c6ea45c0","LICENSE":"95bf3c7cb642fb61c1bbbb319fd3c83e47a5af96","README.md":"3659fd1bfb44b5e6916679ae586ccbff18683e59","config/config.js":"267c3536b5fe54c88080a56241c34d7393a54ac2","index.js":"4413accedb71ffb73ea7ce4ad45a2035c2ce92ca","lib/blockstore.js":"1ed82a8c1cfb4bc3aa96f0669a27c94d2e76b164","lib/consensus.js":"73201c7fec9c62f5f87dba7b2d0d54c08fa4fa6f","lib/errors/SPVError.js":"08b7af972c146bcdf29ffaa06eb90ca2c5660914","lib/merkleproofs.js":"dca09952c352109795af64f9490209a96e483e2f","lib/spvchain.js":"
|
|
1
|
+
{"files":{".eslintignore":"5772aab4a649e8c99c5b74bd685792cd6a69f9ac",".eslintrc.json":"41aa1d3c1477f203b588dcb5c0015454edd94481",".gitignore":"095d365a56f423d4fab2345c95c137e7c6ea45c0",".mocharc.yml":"4b2c6eac5413c431a990afeec68f34c8398891ad","LICENSE":"95bf3c7cb642fb61c1bbbb319fd3c83e47a5af96","README.md":"3659fd1bfb44b5e6916679ae586ccbff18683e59","config/config.js":"267c3536b5fe54c88080a56241c34d7393a54ac2","index.js":"4413accedb71ffb73ea7ce4ad45a2035c2ce92ca","lib/blockstore.js":"1ed82a8c1cfb4bc3aa96f0669a27c94d2e76b164","lib/consensus.js":"73201c7fec9c62f5f87dba7b2d0d54c08fa4fa6f","lib/errors/SPVError.js":"08b7af972c146bcdf29ffaa06eb90ca2c5660914","lib/merkleproofs.js":"dca09952c352109795af64f9490209a96e483e2f","lib/spvchain.js":"167c1fecd83df5ae597c7af87471ec972c6b782e","lib/test/.eslintrc":"4c2b11fe8177c0c2fd0667acb599fc44c4c9741a","lib/test/bootstrap.js":"53142f9ec29cd1e75714de8a0be0080cf3bbda04","lib/utils.js":"c2119b88a67698053e783f09c038a04ae208f232","lib/x11.js":"d85e555bad579a8b3e2e92447331933ce9b0c4b1","package.json":"64e06defc8e405821aa6f4e7667085f1cd6a6471","test/data/headers.js":"eb9cae85ee11027338fbf1f69e373d22388286d6","test/data/merkleproofs.js":"49ea4c29fb1f0cf6378ed7ee46b0a1db3bb555c1","test/data/rawHeaders.js":"03fed6ca2f8c2d1b63cfa13743431a2d551c7ad9","test/index.js":"4b67c838fadf0baac0207b13ddd99b0bc8d228c0","test/spvchain.js":"58a7716c91830e0a23bd27ab857cbba70b8bee93"},"deps":{}}
|
package/lib/spvchain.js
CHANGED
|
@@ -1,26 +1,50 @@
|
|
|
1
|
+
const X11 = require('./x11');
|
|
2
|
+
|
|
1
3
|
const config = require('../config/config');
|
|
2
4
|
const Consensus = require('./consensus');
|
|
3
5
|
const utils = require('./utils');
|
|
4
6
|
const SPVError = require('./errors/SPVError');
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const CHAIN_TYPE_NETWORKS = {
|
|
9
|
+
testnet: 'testnet',
|
|
10
|
+
devnet: 'devnet',
|
|
11
|
+
local: 'regtest',
|
|
12
|
+
regtest: 'regtest',
|
|
13
|
+
lowdiff: 'regtest',
|
|
14
|
+
livenet: 'mainnet',
|
|
15
|
+
mainnet: 'mainnet',
|
|
16
|
+
};
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
const createGenesis = (chainType) => {
|
|
19
|
+
switch (chainType) {
|
|
20
|
+
case 'testnet': return config.getTestnetGenesis();
|
|
21
|
+
case 'devnet': return config.getDevnetGenesis();
|
|
22
|
+
case 'local':
|
|
23
|
+
case 'regtest': return config.getRegtestGenesis();
|
|
24
|
+
case 'livenet':
|
|
25
|
+
case 'mainnet': return config.getLivenetGenesis();
|
|
26
|
+
case 'lowdiff': return config.getLowDiffGenesis();
|
|
27
|
+
default: throw new SPVError(`Unsupported chain type ${chainType}`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
13
30
|
|
|
14
|
-
|
|
31
|
+
const SpvChain = class {
|
|
32
|
+
constructor(chainType, confirms = 100) {
|
|
33
|
+
this.confirmsBeforeFinal = confirms;
|
|
34
|
+
this.network = CHAIN_TYPE_NETWORKS[chainType];
|
|
35
|
+
if (!this.network) {
|
|
36
|
+
throw new SPVError(`Unsupported chain type "${chainType}"`);
|
|
37
|
+
}
|
|
38
|
+
this.genesis = createGenesis(chainType);
|
|
15
39
|
|
|
16
|
-
this.
|
|
40
|
+
this.reset();
|
|
41
|
+
}
|
|
17
42
|
|
|
18
|
-
|
|
19
|
-
|
|
43
|
+
static async wasmX11Ready() {
|
|
44
|
+
return X11.load();
|
|
20
45
|
}
|
|
21
46
|
|
|
22
|
-
reset(
|
|
23
|
-
this.root = null;
|
|
47
|
+
reset() {
|
|
24
48
|
this.allBranches = [[]];
|
|
25
49
|
this.orphanChunks = [];
|
|
26
50
|
this.prunedHeaders = [];
|
|
@@ -28,71 +52,52 @@ const SpvChain = class {
|
|
|
28
52
|
this.hashesByHeight = new Map();
|
|
29
53
|
this.heightByHash = new Map();
|
|
30
54
|
this.orphansHashes = new Set();
|
|
31
|
-
|
|
55
|
+
// Height of the first header in the chain.
|
|
56
|
+
// 0 - if we are synchronizing from genesis
|
|
57
|
+
// > 0 if part of the chain is pruned and we are synchronizing from checkpoint where
|
|
58
|
+
this.startBlockHeight = null;
|
|
59
|
+
|
|
60
|
+
// Head of pending first header.
|
|
61
|
+
// Used in situations where we don't know root in advance
|
|
62
|
+
this.pendingStartBlockHeight = null;
|
|
32
63
|
}
|
|
33
64
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
break;
|
|
61
|
-
case 'livenet':
|
|
62
|
-
this.network = 'mainnet';
|
|
63
|
-
if (startBlock) {
|
|
64
|
-
this.root = startBlock;
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
this.root = config.getLivenetGenesis();
|
|
68
|
-
break;
|
|
69
|
-
case 'mainnet':
|
|
70
|
-
this.network = 'mainnet';
|
|
71
|
-
if (startBlock) {
|
|
72
|
-
this.root = startBlock;
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
this.root = config.getLivenetGenesis();
|
|
76
|
-
break;
|
|
77
|
-
case 'lowdiff':
|
|
78
|
-
this.network = 'regtest';
|
|
79
|
-
if (startBlock) {
|
|
80
|
-
this.root = startBlock;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
this.root = config.getLowDiffGenesis();
|
|
84
|
-
break;
|
|
85
|
-
default:
|
|
86
|
-
if (startBlock) {
|
|
87
|
-
this.root = startBlock;
|
|
88
|
-
this.network = 'mainnet';
|
|
89
|
-
} else {
|
|
90
|
-
throw new SPVError('Unhandled chaintype or startBlock not provided');
|
|
91
|
-
}
|
|
92
|
-
break;
|
|
65
|
+
/**
|
|
66
|
+
* Initializes chain with a start block and height
|
|
67
|
+
* - When no arguments provided, chain is initialized from genesis block
|
|
68
|
+
* - If startBlock is provided, height must be provided as well
|
|
69
|
+
* @param {BlockHeader} [startBlock]
|
|
70
|
+
* @param {number} [height]
|
|
71
|
+
*/
|
|
72
|
+
initialize(startBlock, height) {
|
|
73
|
+
if (!X11.ready()) {
|
|
74
|
+
throw new SPVError('X11 wasm not ready, call wasmX11Ready() first');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (this.startBlockHeight !== null) {
|
|
78
|
+
throw new SPVError('Chain already initialized');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (startBlock && typeof height === 'number') {
|
|
82
|
+
// eslint-disable-next-line
|
|
83
|
+
startBlock = utils.normalizeHeader(startBlock);
|
|
84
|
+
} else if (!startBlock && typeof height !== 'number') {
|
|
85
|
+
// eslint-disable-next-line
|
|
86
|
+
startBlock = this.genesis;
|
|
87
|
+
// eslint-disable-next-line
|
|
88
|
+
height = 0;
|
|
89
|
+
} else {
|
|
90
|
+
throw new SPVError('Initialization error, please provide both startBlock and height');
|
|
93
91
|
}
|
|
94
|
-
|
|
95
|
-
this.
|
|
92
|
+
|
|
93
|
+
this.startBlockHeight = height;
|
|
94
|
+
this.hashesByHeight = new Map([[height, startBlock.hash]]);
|
|
95
|
+
this.heightByHash = new Map([[startBlock.hash, height]]);
|
|
96
|
+
this.setAllBranches(startBlock);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
initialized() {
|
|
100
|
+
return this.startBlockHeight !== null || this.pendingStartBlockHeight !== null;
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
validate() {
|
|
@@ -146,30 +151,15 @@ const SpvChain = class {
|
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
/** @private */
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const node = stack.pop();
|
|
153
|
-
if (node.hash === utils.getCorrectedHash(newHeader.prevHash)) {
|
|
154
|
-
return node;
|
|
155
|
-
}
|
|
156
|
-
// node.children.forEach((c) => { stack.push(c); });
|
|
154
|
+
setAllBranches(node, branch = []) {
|
|
155
|
+
if (!node) {
|
|
156
|
+
throw new SPVError('Root node for a branch is not defined');
|
|
157
157
|
}
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
158
|
|
|
161
|
-
/** @private */
|
|
162
|
-
setAllBranches(node = this.root, branch = []) {
|
|
163
159
|
this.allBranches = [];
|
|
164
160
|
branch.push(node);
|
|
165
161
|
|
|
166
|
-
// node.children.forEach((c) => {
|
|
167
|
-
// this.setAllBranches(c, Array.from(branch));
|
|
168
|
-
// });
|
|
169
|
-
|
|
170
|
-
// if (node.children.length === 0) {
|
|
171
162
|
this.allBranches.push(branch);
|
|
172
|
-
// }
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
/** @private */
|
|
@@ -200,17 +190,6 @@ const SpvChain = class {
|
|
|
200
190
|
return this.heightByHash.has(hash) || this.orphansHashes.has(hash);
|
|
201
191
|
}
|
|
202
192
|
|
|
203
|
-
/** @private */
|
|
204
|
-
// orphanReconnect() {
|
|
205
|
-
// for (let i = 0; i < this.orphanBlocks.length; i += 1) {
|
|
206
|
-
// const connectionTip = this.findConnection(this.orphanBlocks[i]);
|
|
207
|
-
// if (connectionTip) {
|
|
208
|
-
// connectionTip.children.push(this.orphanBlocks[i]);
|
|
209
|
-
// this.orphanBlocks.splice(i, 1);
|
|
210
|
-
// }
|
|
211
|
-
// }
|
|
212
|
-
// }
|
|
213
|
-
|
|
214
193
|
/** @private */
|
|
215
194
|
orphanChunksReconnect() {
|
|
216
195
|
// TODO: consider optimizing with map of { [chunkHeadHash]: chunkIndex }
|
|
@@ -233,28 +212,11 @@ const SpvChain = class {
|
|
|
233
212
|
}
|
|
234
213
|
}
|
|
235
214
|
|
|
236
|
-
/** @private */
|
|
237
|
-
// TODO: remove
|
|
238
|
-
// getOrphans() {
|
|
239
|
-
// return this.orphanBlocks;
|
|
240
|
-
// }
|
|
241
|
-
|
|
242
215
|
/** @private */
|
|
243
216
|
getOrphanChunks() {
|
|
244
217
|
return this.orphanChunks;
|
|
245
218
|
}
|
|
246
219
|
|
|
247
|
-
/** @private */
|
|
248
|
-
// processValidHeader(header) {
|
|
249
|
-
// const connection = this.findConnection(header);
|
|
250
|
-
// if (connection) {
|
|
251
|
-
// // connection.children.push(header);
|
|
252
|
-
// this.orphanReconnect();
|
|
253
|
-
// } else {
|
|
254
|
-
// this.orphanBlocks.push(header);
|
|
255
|
-
// }
|
|
256
|
-
// }
|
|
257
|
-
|
|
258
220
|
/** @private
|
|
259
221
|
* validates a dashcore.BlockHeader object
|
|
260
222
|
*
|
|
@@ -281,16 +243,9 @@ const SpvChain = class {
|
|
|
281
243
|
if (utils.getCorrectedHash(header.prevHash) !== previousHeader.hash) {
|
|
282
244
|
return false;
|
|
283
245
|
}
|
|
284
|
-
|
|
285
|
-
// header.children = [];
|
|
286
|
-
// }
|
|
287
|
-
// if (!previousHeader.children) {
|
|
288
|
-
// previousHeader.children = [];
|
|
289
|
-
// }
|
|
290
|
-
// previousHeader.children.push(header);
|
|
246
|
+
|
|
291
247
|
return true;
|
|
292
248
|
}
|
|
293
|
-
/* eslint-enable no-param-reassign */
|
|
294
249
|
|
|
295
250
|
/**
|
|
296
251
|
* Returns the longest chain of headers
|
|
@@ -337,44 +292,6 @@ const SpvChain = class {
|
|
|
337
292
|
return this.getLongestChain().filter((h) => h.hash === hash)[0];
|
|
338
293
|
}
|
|
339
294
|
|
|
340
|
-
/**
|
|
341
|
-
* Gets specified amount of headers in the confirmed chain
|
|
342
|
-
* @param n
|
|
343
|
-
* @return Object[]
|
|
344
|
-
*/
|
|
345
|
-
getLastHeaders(n) {
|
|
346
|
-
const longestChain = this.getLongestChain();
|
|
347
|
-
let headers = longestChain.slice(-n);
|
|
348
|
-
|
|
349
|
-
if (headers.length < n) {
|
|
350
|
-
const remaining = n - headers.length;
|
|
351
|
-
headers = [...this.prunedHeaders.slice(-remaining), ...headers];
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return headers;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* adds a valid header to the tip of the longest spv chain.
|
|
359
|
-
* If it cannot be connected to the tip it gets temporarily
|
|
360
|
-
* added to an orphan array for possible later reconnection
|
|
361
|
-
*
|
|
362
|
-
* @param {Object[]|string[]|buffer[]} header
|
|
363
|
-
* @return {boolean}
|
|
364
|
-
*/
|
|
365
|
-
// addHeader(header) {
|
|
366
|
-
// const headerNormalised = utils.normalizeHeader(header);
|
|
367
|
-
//
|
|
368
|
-
// if (this.isValid(headerNormalised, this.getLongestChain())) {
|
|
369
|
-
// // headerNormalised.children = [];
|
|
370
|
-
// this.processValidHeader(headerNormalised);
|
|
371
|
-
// this.setAllBranches();
|
|
372
|
-
// this.checkPruneBlocks();
|
|
373
|
-
// return true;
|
|
374
|
-
// }
|
|
375
|
-
// return false;
|
|
376
|
-
// }
|
|
377
|
-
|
|
378
295
|
/**
|
|
379
296
|
* adds an array of valid headers to the longest spv chain.
|
|
380
297
|
* If they cannot be connected to last tip they get temporarily
|
|
@@ -385,39 +302,45 @@ const SpvChain = class {
|
|
|
385
302
|
* @return {BlockHeader[]}
|
|
386
303
|
*/
|
|
387
304
|
addHeaders(headers, batchHeadHeight = 0) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const tip = this.getTipHeader();
|
|
392
|
-
// Handle 1 block intersection of batches
|
|
393
|
-
if (tip && tip.hash === normalizedHeaders[0].hash) {
|
|
394
|
-
normalizedHeaders.splice(0, 1);
|
|
395
|
-
// Patch head height value after splice
|
|
396
|
-
headHeight += 1;
|
|
305
|
+
if (!X11.ready()) {
|
|
306
|
+
throw new SPVError('X11 wasm not ready, call wasmX11Ready() first');
|
|
397
307
|
}
|
|
398
308
|
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
return [];
|
|
309
|
+
if (!this.initialized()) {
|
|
310
|
+
throw new SPVError('Chain not initialized, either call initialize() or set pendingStartBlockHeight');
|
|
402
311
|
}
|
|
403
312
|
|
|
404
|
-
const
|
|
405
|
-
const connectsToTip = tip && SpvChain.isParentChild(firstHeader, tip);
|
|
313
|
+
const normalizedHeaders = headers.map((h) => utils.normalizeHeader(h));
|
|
406
314
|
|
|
407
|
-
|
|
408
|
-
// Reorg detection
|
|
409
|
-
// Get prev header hash
|
|
410
|
-
const prevHash = utils.getCorrectedHash(firstHeader.prevHash);
|
|
411
|
-
const prevHeaderHeight = this.heightByHash.get(prevHash);
|
|
315
|
+
let isOrphan = false;
|
|
412
316
|
|
|
413
|
-
//
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
317
|
+
// Chain is initialized, root block, and it's height are known
|
|
318
|
+
if (this.pendingStartBlockHeight === null) {
|
|
319
|
+
const tip = this.getTipHeader();
|
|
320
|
+
// Handle 1 block intersection of batches
|
|
321
|
+
if (tip.hash === normalizedHeaders[0].hash) {
|
|
322
|
+
normalizedHeaders.splice(0, 1);
|
|
323
|
+
}
|
|
418
324
|
|
|
419
|
-
|
|
420
|
-
|
|
325
|
+
if (normalizedHeaders.length === 0) {
|
|
326
|
+
// The batch already in the chain, do nothing
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const firstHeader = normalizedHeaders[0];
|
|
331
|
+
|
|
332
|
+
isOrphan = !SpvChain.isParentChild(firstHeader, tip);
|
|
333
|
+
} else if (batchHeadHeight === this.pendingStartBlockHeight) {
|
|
334
|
+
// Header at pendingStartBlockHeight is found, initialize chain
|
|
335
|
+
this.startBlockHeight = this.pendingStartBlockHeight;
|
|
336
|
+
this.pendingStartBlockHeight = null;
|
|
337
|
+
isOrphan = false;
|
|
338
|
+
} else if (batchHeadHeight > this.pendingStartBlockHeight) {
|
|
339
|
+
// Orphan chunk has arrived
|
|
340
|
+
isOrphan = true;
|
|
341
|
+
} else {
|
|
342
|
+
throw new SPVError(`Batch at invalid height arrived: ${batchHeadHeight}, expected > ${this.pendingStartBlockHeight}`);
|
|
343
|
+
}
|
|
421
344
|
|
|
422
345
|
const allValid = normalizedHeaders.reduce(
|
|
423
346
|
(acc, header, index, array) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const sinon = require('sinon');
|
|
2
|
+
|
|
3
|
+
beforeEach(function beforeEach() {
|
|
4
|
+
if (!this.sinon) {
|
|
5
|
+
this.sinon = sinon.createSandbox();
|
|
6
|
+
} else {
|
|
7
|
+
this.sinon.restore();
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
before(function before() {
|
|
12
|
+
if (!this.sinon) {
|
|
13
|
+
this.sinon = sinon.createSandbox();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(function afterEach() {
|
|
18
|
+
this.sinon.restore();
|
|
19
|
+
});
|
package/lib/x11.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { configure: configureDashcore } = require('@dashevo/dashcore-lib');
|
|
2
|
+
const X11 = require('wasm-x11-hash');
|
|
3
|
+
|
|
4
|
+
let x11Promise = null;
|
|
5
|
+
let x11Ready = false;
|
|
6
|
+
|
|
7
|
+
const load = async () => {
|
|
8
|
+
if (!x11Promise) {
|
|
9
|
+
x11Promise = X11().then(
|
|
10
|
+
(x11Hash) => {
|
|
11
|
+
configureDashcore({
|
|
12
|
+
x11hash: x11Hash,
|
|
13
|
+
});
|
|
14
|
+
x11Ready = true;
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return x11Promise;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ready = () => x11Ready;
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
ready,
|
|
26
|
+
load,
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashevo/dash-spv",
|
|
3
|
-
"version": "0.25.0-dev.
|
|
3
|
+
"version": "0.25.0-dev.21",
|
|
4
4
|
"description": "Repository containing SPV functions used by @dashevo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@dashevo/dark-gravity-wave": "^1.1.1",
|
|
16
16
|
"@dashevo/dash-util": "^2.0.3",
|
|
17
|
-
"@dashevo/dashcore-lib": "~0.20.
|
|
17
|
+
"@dashevo/dashcore-lib": "~0.20.9",
|
|
18
18
|
"levelup": "^4.4.0",
|
|
19
|
-
"memdown": "^5.1.0"
|
|
19
|
+
"memdown": "^5.1.0",
|
|
20
|
+
"wasm-x11-hash": "~0.0.2"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"chai": "^4.3.4",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"eslint-config-airbnb-base": "^14.2.1",
|
|
25
26
|
"eslint-plugin-import": "^2.24.2",
|
|
26
27
|
"mocha": "^9.1.2",
|
|
27
|
-
"should": "^13.2.3"
|
|
28
|
+
"should": "^13.2.3",
|
|
29
|
+
"sinon": "^11.1.2"
|
|
28
30
|
}
|
|
29
31
|
}
|
package/test/index.js
CHANGED
|
@@ -16,8 +16,10 @@ let merkleBlock = null;
|
|
|
16
16
|
let merkleBlock2 = null;
|
|
17
17
|
|
|
18
18
|
describe('SPV-DASH (forks & re-orgs) deserialized headers', () => {
|
|
19
|
-
before(() => {
|
|
19
|
+
before(async () => {
|
|
20
20
|
chain = new Blockchain('testnet');
|
|
21
|
+
await Blockchain.wasmX11Ready();
|
|
22
|
+
chain.initialize();
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
it('should get 26 testnet headers', () => {
|
|
@@ -83,8 +85,9 @@ describe('SPV-DASH (forks & re-orgs) deserialized headers', () => {
|
|
|
83
85
|
});
|
|
84
86
|
|
|
85
87
|
describe('SPV-DASH (forks & re-orgs) serialized raw headers for mainnet', () => {
|
|
86
|
-
before(() => {
|
|
87
|
-
chain = new Blockchain('mainnet', 10000
|
|
88
|
+
before(async () => {
|
|
89
|
+
chain = new Blockchain('mainnet', 10000);
|
|
90
|
+
chain.initialize(utils.normalizeHeader(mainnet[0]), 1);
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
it('should get 2000 mainnet headers', () => {
|
|
@@ -127,8 +130,9 @@ describe('SPV-DASH (forks & re-orgs) serialized raw headers for mainnet', () =>
|
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
describe('SPV-DASH (addHeaders) add many headers for testnet', () => {
|
|
130
|
-
before(() => {
|
|
131
|
-
chain = new Blockchain('testnet', 10000
|
|
133
|
+
before(async () => {
|
|
134
|
+
chain = new Blockchain('testnet', 10000);
|
|
135
|
+
chain.initialize(utils.normalizeHeader(testnet[0]), 1);
|
|
132
136
|
});
|
|
133
137
|
|
|
134
138
|
it('should add the 1st 250 testnet headers', () => {
|
|
@@ -146,7 +150,8 @@ describe('SPV-DASH (addHeaders) add many headers for testnet', () => {
|
|
|
146
150
|
});
|
|
147
151
|
|
|
148
152
|
it('should add the 1st 250 testnet2 headers', () => {
|
|
149
|
-
chain = new Blockchain('testnet', 10000
|
|
153
|
+
chain = new Blockchain('testnet', 10000);
|
|
154
|
+
chain.initialize(utils.normalizeHeader(testnet2[0]), 1);
|
|
150
155
|
chain.addHeaders(testnet2.slice(1, 250));
|
|
151
156
|
chain.getOrphanChunks().length.should.equal(0);
|
|
152
157
|
chain.getAllBranches().length.should.equal(1);
|
|
@@ -161,7 +166,8 @@ describe('SPV-DASH (addHeaders) add many headers for testnet', () => {
|
|
|
161
166
|
});
|
|
162
167
|
|
|
163
168
|
it('should add the 1st 250 testnet3 headers', () => {
|
|
164
|
-
chain = new Blockchain('testnet', 10000
|
|
169
|
+
chain = new Blockchain('testnet', 10000);
|
|
170
|
+
chain.initialize(utils.normalizeHeader(testnet3[0]), 1);
|
|
165
171
|
chain.addHeaders(testnet3.slice(1, 250));
|
|
166
172
|
chain.getOrphanChunks().length.should.equal(0);
|
|
167
173
|
chain.getAllBranches().length.should.equal(1);
|
|
@@ -199,7 +205,8 @@ describe('SPV-DASH (addHeaders) add many headers for testnet', () => {
|
|
|
199
205
|
|
|
200
206
|
describe('SPV-DASH (addHeaders) add testnet headers out of order', () => {
|
|
201
207
|
before(() => {
|
|
202
|
-
chain = new Blockchain('testnet', 10000
|
|
208
|
+
chain = new Blockchain('testnet', 10000);
|
|
209
|
+
chain.initialize(utils.normalizeHeader(testnet[0]), 1);
|
|
203
210
|
});
|
|
204
211
|
|
|
205
212
|
it('should add the 1st 100 testnet headers', () => {
|
|
@@ -242,7 +249,8 @@ describe('SPV-DASH (addHeaders) add testnet headers out of order', () => {
|
|
|
242
249
|
|
|
243
250
|
describe('SPV-DASH (addHeaders) add many headers for mainnet', () => {
|
|
244
251
|
before(() => {
|
|
245
|
-
chain = new Blockchain('mainnet', 10000
|
|
252
|
+
chain = new Blockchain('mainnet', 10000);
|
|
253
|
+
chain.initialize(utils.normalizeHeader(mainnet[0]), 1);
|
|
246
254
|
});
|
|
247
255
|
|
|
248
256
|
it('should add the 1st 500 mainnet headers', () => {
|
|
@@ -290,7 +298,8 @@ describe('SPV-DASH (addHeaders) add many headers for mainnet', () => {
|
|
|
290
298
|
|
|
291
299
|
describe('SPV-DASH (addHeaders) add mainnet headers out of order', () => {
|
|
292
300
|
before(() => {
|
|
293
|
-
chain = new Blockchain('mainnet', 10000
|
|
301
|
+
chain = new Blockchain('mainnet', 10000);
|
|
302
|
+
chain.initialize(utils.normalizeHeader(mainnet[0]), 1);
|
|
294
303
|
});
|
|
295
304
|
|
|
296
305
|
it('should add the 1st 100 mainnet headers', () => {
|
|
@@ -377,7 +386,8 @@ describe('MerkleProofs', () => {
|
|
|
377
386
|
|
|
378
387
|
describe('Transaction validation', () => {
|
|
379
388
|
before(() => {
|
|
380
|
-
chain = new Blockchain('testnet', 10000
|
|
389
|
+
chain = new Blockchain('testnet', 10000);
|
|
390
|
+
chain.initialize(utils.normalizeHeader(testnet[0]), 1);
|
|
381
391
|
chain.addHeaders(testnet.slice(1, 500));
|
|
382
392
|
});
|
|
383
393
|
|
package/test/spvchain.js
CHANGED
|
@@ -1,19 +1,65 @@
|
|
|
1
1
|
const { expect } = require('chai');
|
|
2
|
+
const X11 = require('../lib/x11');
|
|
2
3
|
const { SpvChain } = require('../index');
|
|
3
|
-
|
|
4
4
|
const { testnet } = require('./data/rawHeaders');
|
|
5
5
|
|
|
6
6
|
describe('SPVChain', () => {
|
|
7
7
|
let spvChain;
|
|
8
8
|
|
|
9
|
-
beforeEach(() => {
|
|
9
|
+
beforeEach(async () => {
|
|
10
10
|
spvChain = new SpvChain('testnet', 100);
|
|
11
|
+
await SpvChain.wasmX11Ready();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('#initialize', () => {
|
|
15
|
+
it('should throw an error if wasmX11 is not initialized', function it() {
|
|
16
|
+
this.sinon.stub(X11, 'ready').returns(false);
|
|
17
|
+
expect(() => spvChain.initialize())
|
|
18
|
+
.to.throw('X11 wasm not ready, call wasmX11Ready() first');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should throw an error if chain is already initialized', async () => {
|
|
22
|
+
spvChain.initialize(testnet[0], 10000);
|
|
23
|
+
expect(() => spvChain.initialize())
|
|
24
|
+
.to.throw('Chain already initialized');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should initialize chain with genesis header', async () => {
|
|
28
|
+
spvChain.initialize();
|
|
29
|
+
expect(spvChain.startBlockHeight).to.be.equal(0);
|
|
30
|
+
expect(spvChain.getLongestChain())
|
|
31
|
+
.to.be.deep.equal([spvChain.genesis]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should not allow initializing from arbitrary block without height', () => {
|
|
35
|
+
expect(() => spvChain.initialize(testnet[0]))
|
|
36
|
+
.to.throw('Initialization error, please provide both startBlock and height');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should initialize from arbitrary block', () => {
|
|
40
|
+
spvChain.initialize(testnet[0], 10000);
|
|
41
|
+
expect(spvChain.startBlockHeight).to.be.equal(10000);
|
|
42
|
+
expect(spvChain.getLongestChain()[0].toBuffer().toString('hex'))
|
|
43
|
+
.to.be.equal(testnet[0]);
|
|
44
|
+
});
|
|
11
45
|
});
|
|
12
46
|
|
|
13
47
|
describe('#addHeaders', () => {
|
|
14
|
-
it('should
|
|
48
|
+
it('should throw an error if wasmX11 is not initialized', function it() {
|
|
49
|
+
this.sinon.stub(X11, 'ready').returns(false);
|
|
50
|
+
expect(() => spvChain.addHeaders(testnet.slice(400)))
|
|
51
|
+
.to.throw('X11 wasm not ready, call wasmX11Ready() first');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should throw an error if chain is not initialized', async () => {
|
|
55
|
+
expect(() => spvChain.addHeaders(testnet.slice(400)))
|
|
56
|
+
.to.throw('Chain not initialized, either call initialize() or set pendingStartBlockHeight');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should assemble headers chain if headers arriving out of order', async () => {
|
|
15
60
|
expect(true).to.equal(true);
|
|
16
|
-
|
|
61
|
+
|
|
62
|
+
spvChain.initialize(testnet[0], 10000);
|
|
17
63
|
|
|
18
64
|
spvChain.addHeaders(testnet.slice(400), 10400);
|
|
19
65
|
expect(spvChain.getOrphanChunks()).to.have.length(1);
|