@dedot/api 0.9.5-next.20891389.0 → 0.9.5-next.889fe7cd.3
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.
|
@@ -22,6 +22,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
22
22
|
#recovering;
|
|
23
23
|
#blockUsage;
|
|
24
24
|
#cache;
|
|
25
|
+
#operationQueue;
|
|
25
26
|
constructor(client, options) {
|
|
26
27
|
super(client, { prefix: 'chainHead', supportedVersions: ['unstable', 'v1'], ...options });
|
|
27
28
|
this.#handlers = {};
|
|
@@ -32,6 +33,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
32
33
|
this.#retryQueue = new utils_1.AsyncQueue();
|
|
33
34
|
this.#blockUsage = new BlockUsage_js_1.BlockUsage();
|
|
34
35
|
this.#cache = new Map();
|
|
36
|
+
this.#operationQueue = new utils_1.ThrottleQueue(this.#__unsafe__isSmoldot() ? 30 : 250);
|
|
35
37
|
}
|
|
36
38
|
async runtimeVersion() {
|
|
37
39
|
await this.#ensureFollowed();
|
|
@@ -80,11 +82,18 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
80
82
|
const defer = (0, utils_1.deferred)();
|
|
81
83
|
try {
|
|
82
84
|
this.#unsub && this.#unsub().catch(utils_1.noop); // ensure unfollowed
|
|
85
|
+
let signals = 0;
|
|
83
86
|
this.#unsub = await this.send('follow', true, (event, subscription) => {
|
|
84
87
|
this.#followResponseQueue
|
|
85
88
|
.enqueue(async () => {
|
|
86
89
|
await this.#onFollowEvent(event, subscription);
|
|
87
|
-
if (
|
|
90
|
+
if (signals >= 2)
|
|
91
|
+
return;
|
|
92
|
+
signals += 1;
|
|
93
|
+
// Sometime smoldot send a `stop` event right after `initialized`
|
|
94
|
+
// So requests sending between `initialized` and `stop` will be on pruned hashes -> throwing out errors
|
|
95
|
+
// Here we make sure to receive at least the first 2 signals to resolve
|
|
96
|
+
if (signals >= 2) {
|
|
88
97
|
defer.resolve();
|
|
89
98
|
}
|
|
90
99
|
})
|
|
@@ -229,7 +238,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
229
238
|
.then(() => {
|
|
230
239
|
// 3. Resolve the recovering promise
|
|
231
240
|
// This means to continue all pending requests while the chainHead started recovering mode at step 1.
|
|
232
|
-
this.#recovering
|
|
241
|
+
this.#recovering?.resolve();
|
|
233
242
|
// 4. Recover stale operations
|
|
234
243
|
// 4.1. Operations that's going on & waiting to receiving its operationId
|
|
235
244
|
// will eventually get an `limitedReached` error for using a stale followSubscriptionId
|
|
@@ -245,7 +254,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
245
254
|
.catch((e) => {
|
|
246
255
|
console.error(e);
|
|
247
256
|
// TODO we should retry a few attempts
|
|
248
|
-
this.#recovering
|
|
257
|
+
this.#recovering?.reject(new error_js_1.ChainHeadError('Cannot recover from stop event!'));
|
|
249
258
|
Object.values(this.#handlers).forEach(({ defer, operationId }) => {
|
|
250
259
|
defer.reject(new error_js_1.ChainHeadError('Cannot recover from stop event!'));
|
|
251
260
|
delete this.#handlers[operationId];
|
|
@@ -393,6 +402,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
393
402
|
this.#retryQueue.clear();
|
|
394
403
|
this.#blockUsage.clear();
|
|
395
404
|
this.#cache.clear();
|
|
405
|
+
this.#operationQueue.cancel();
|
|
396
406
|
}
|
|
397
407
|
async #ensureFollowed() {
|
|
398
408
|
if (this.#recovering) {
|
|
@@ -422,17 +432,15 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
422
432
|
async #retryOperation(strategy, retry) {
|
|
423
433
|
try {
|
|
424
434
|
return await new Promise((resolve, reject) => {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
}); // retry again in the next tick
|
|
435
|
+
if (strategy === error_js_1.RetryStrategy.NOW) {
|
|
436
|
+
retry().then(resolve).catch(reject);
|
|
437
|
+
}
|
|
438
|
+
else if (strategy === error_js_1.RetryStrategy.QUEUED) {
|
|
439
|
+
this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
throw new Error('Invalid retry strategy');
|
|
443
|
+
}
|
|
436
444
|
});
|
|
437
445
|
}
|
|
438
446
|
catch (e) {
|
|
@@ -480,7 +488,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
480
488
|
const resp = await this.send('body', this.#subscriptionId, hash);
|
|
481
489
|
return this.#awaitOperation(resp, hash);
|
|
482
490
|
};
|
|
483
|
-
const resp = await this.#performOperationWithRetry(operation, atHash);
|
|
491
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, atHash));
|
|
484
492
|
this.#cache.set(cacheKey, resp);
|
|
485
493
|
return resp;
|
|
486
494
|
}
|
|
@@ -509,7 +517,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
509
517
|
const resp = await this.send('call', this.#subscriptionId, hash, func, params);
|
|
510
518
|
return this.#awaitOperation(resp, hash);
|
|
511
519
|
};
|
|
512
|
-
const resp = await this.#performOperationWithRetry(operation, atHash);
|
|
520
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, atHash));
|
|
513
521
|
this.#cache.set(cacheKey, resp);
|
|
514
522
|
return resp;
|
|
515
523
|
}
|
|
@@ -552,9 +560,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
552
560
|
}
|
|
553
561
|
this.#blockUsage.use(hash);
|
|
554
562
|
let results = [];
|
|
555
|
-
|
|
556
|
-
const isSmoldot = typeof this.client.provider['chain'] === 'function';
|
|
557
|
-
if (isSmoldot) {
|
|
563
|
+
if (this.#__unsafe__isSmoldot()) {
|
|
558
564
|
const fetchItem = async (item) => {
|
|
559
565
|
const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
|
|
560
566
|
if (newDiscardedItems.length > 0) {
|
|
@@ -587,7 +593,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
587
593
|
}
|
|
588
594
|
async #getStorage(items, childTrie, at) {
|
|
589
595
|
const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
|
|
590
|
-
return this.#performOperationWithRetry(operation, at);
|
|
596
|
+
return this.#operationQueue.add(() => this.#performOperationWithRetry(operation, at));
|
|
591
597
|
}
|
|
592
598
|
async #getStorageOperation(items, childTrie, at) {
|
|
593
599
|
await this.#ensureFollowed();
|
|
@@ -624,5 +630,9 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
624
630
|
return;
|
|
625
631
|
await this.send('unpin', this.#subscriptionId, hashes);
|
|
626
632
|
}
|
|
633
|
+
#__unsafe__isSmoldot() {
|
|
634
|
+
// @ts-ignore a trick internally to check whether a provider is using smoldot connection
|
|
635
|
+
return typeof this.client.provider['chain'] === 'function';
|
|
636
|
+
}
|
|
627
637
|
}
|
|
628
638
|
exports.ChainHead = ChainHead;
|
|
@@ -83,7 +83,13 @@ class NewStorageQuery extends BaseStorageQuery_js_1.BaseStorageQuery {
|
|
|
83
83
|
await pull(best);
|
|
84
84
|
// Subscribe to best block events
|
|
85
85
|
const unsub = this.client.on('bestBlock', (block) => {
|
|
86
|
-
|
|
86
|
+
// Here we're handling each pull one by one,
|
|
87
|
+
// If the queue get too long, it might take a long time for us to get the fresh & latest data
|
|
88
|
+
// This is a precaution in such case, if the queue size >= 3 we skip all the pending pull and jump to the latest pull
|
|
89
|
+
if (pullQueue.size >= 3)
|
|
90
|
+
pullQueue.clear();
|
|
91
|
+
// TODO timing out for a pull to prevent it took too long to fetch
|
|
92
|
+
pullQueue.enqueue(() => pull(block)).catch(utils_1.noop);
|
|
87
93
|
});
|
|
88
94
|
return async () => {
|
|
89
95
|
unsub();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $Header } from '@dedot/codecs';
|
|
2
|
-
import { assert, AsyncQueue, deferred, ensurePresence, noop, waitFor } from '@dedot/utils';
|
|
2
|
+
import { assert, AsyncQueue, deferred, ensurePresence, noop, ThrottleQueue, waitFor, } from '@dedot/utils';
|
|
3
3
|
import { JsonRpcGroup } from '../JsonRpcGroup.js';
|
|
4
4
|
import { BlockUsage } from './BlockUsage.js';
|
|
5
5
|
import { ChainHeadBlockNotPinnedError, ChainHeadBlockPrunedError, ChainHeadError, ChainHeadLimitReachedError, ChainHeadOperationError, ChainHeadOperationInaccessibleError, ChainHeadStopError, RetryStrategy, } from './error.js';
|
|
@@ -19,6 +19,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
19
19
|
#recovering;
|
|
20
20
|
#blockUsage;
|
|
21
21
|
#cache;
|
|
22
|
+
#operationQueue;
|
|
22
23
|
constructor(client, options) {
|
|
23
24
|
super(client, { prefix: 'chainHead', supportedVersions: ['unstable', 'v1'], ...options });
|
|
24
25
|
this.#handlers = {};
|
|
@@ -29,6 +30,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
29
30
|
this.#retryQueue = new AsyncQueue();
|
|
30
31
|
this.#blockUsage = new BlockUsage();
|
|
31
32
|
this.#cache = new Map();
|
|
33
|
+
this.#operationQueue = new ThrottleQueue(this.#__unsafe__isSmoldot() ? 30 : 250);
|
|
32
34
|
}
|
|
33
35
|
async runtimeVersion() {
|
|
34
36
|
await this.#ensureFollowed();
|
|
@@ -77,11 +79,18 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
77
79
|
const defer = deferred();
|
|
78
80
|
try {
|
|
79
81
|
this.#unsub && this.#unsub().catch(noop); // ensure unfollowed
|
|
82
|
+
let signals = 0;
|
|
80
83
|
this.#unsub = await this.send('follow', true, (event, subscription) => {
|
|
81
84
|
this.#followResponseQueue
|
|
82
85
|
.enqueue(async () => {
|
|
83
86
|
await this.#onFollowEvent(event, subscription);
|
|
84
|
-
if (
|
|
87
|
+
if (signals >= 2)
|
|
88
|
+
return;
|
|
89
|
+
signals += 1;
|
|
90
|
+
// Sometime smoldot send a `stop` event right after `initialized`
|
|
91
|
+
// So requests sending between `initialized` and `stop` will be on pruned hashes -> throwing out errors
|
|
92
|
+
// Here we make sure to receive at least the first 2 signals to resolve
|
|
93
|
+
if (signals >= 2) {
|
|
85
94
|
defer.resolve();
|
|
86
95
|
}
|
|
87
96
|
})
|
|
@@ -226,7 +235,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
226
235
|
.then(() => {
|
|
227
236
|
// 3. Resolve the recovering promise
|
|
228
237
|
// This means to continue all pending requests while the chainHead started recovering mode at step 1.
|
|
229
|
-
this.#recovering
|
|
238
|
+
this.#recovering?.resolve();
|
|
230
239
|
// 4. Recover stale operations
|
|
231
240
|
// 4.1. Operations that's going on & waiting to receiving its operationId
|
|
232
241
|
// will eventually get an `limitedReached` error for using a stale followSubscriptionId
|
|
@@ -242,7 +251,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
242
251
|
.catch((e) => {
|
|
243
252
|
console.error(e);
|
|
244
253
|
// TODO we should retry a few attempts
|
|
245
|
-
this.#recovering
|
|
254
|
+
this.#recovering?.reject(new ChainHeadError('Cannot recover from stop event!'));
|
|
246
255
|
Object.values(this.#handlers).forEach(({ defer, operationId }) => {
|
|
247
256
|
defer.reject(new ChainHeadError('Cannot recover from stop event!'));
|
|
248
257
|
delete this.#handlers[operationId];
|
|
@@ -390,6 +399,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
390
399
|
this.#retryQueue.clear();
|
|
391
400
|
this.#blockUsage.clear();
|
|
392
401
|
this.#cache.clear();
|
|
402
|
+
this.#operationQueue.cancel();
|
|
393
403
|
}
|
|
394
404
|
async #ensureFollowed() {
|
|
395
405
|
if (this.#recovering) {
|
|
@@ -419,17 +429,15 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
419
429
|
async #retryOperation(strategy, retry) {
|
|
420
430
|
try {
|
|
421
431
|
return await new Promise((resolve, reject) => {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
}); // retry again in the next tick
|
|
432
|
+
if (strategy === RetryStrategy.NOW) {
|
|
433
|
+
retry().then(resolve).catch(reject);
|
|
434
|
+
}
|
|
435
|
+
else if (strategy === RetryStrategy.QUEUED) {
|
|
436
|
+
this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
throw new Error('Invalid retry strategy');
|
|
440
|
+
}
|
|
433
441
|
});
|
|
434
442
|
}
|
|
435
443
|
catch (e) {
|
|
@@ -477,7 +485,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
477
485
|
const resp = await this.send('body', this.#subscriptionId, hash);
|
|
478
486
|
return this.#awaitOperation(resp, hash);
|
|
479
487
|
};
|
|
480
|
-
const resp = await this.#performOperationWithRetry(operation, atHash);
|
|
488
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, atHash));
|
|
481
489
|
this.#cache.set(cacheKey, resp);
|
|
482
490
|
return resp;
|
|
483
491
|
}
|
|
@@ -506,7 +514,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
506
514
|
const resp = await this.send('call', this.#subscriptionId, hash, func, params);
|
|
507
515
|
return this.#awaitOperation(resp, hash);
|
|
508
516
|
};
|
|
509
|
-
const resp = await this.#performOperationWithRetry(operation, atHash);
|
|
517
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, atHash));
|
|
510
518
|
this.#cache.set(cacheKey, resp);
|
|
511
519
|
return resp;
|
|
512
520
|
}
|
|
@@ -549,9 +557,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
549
557
|
}
|
|
550
558
|
this.#blockUsage.use(hash);
|
|
551
559
|
let results = [];
|
|
552
|
-
|
|
553
|
-
const isSmoldot = typeof this.client.provider['chain'] === 'function';
|
|
554
|
-
if (isSmoldot) {
|
|
560
|
+
if (this.#__unsafe__isSmoldot()) {
|
|
555
561
|
const fetchItem = async (item) => {
|
|
556
562
|
const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
|
|
557
563
|
if (newDiscardedItems.length > 0) {
|
|
@@ -584,7 +590,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
584
590
|
}
|
|
585
591
|
async #getStorage(items, childTrie, at) {
|
|
586
592
|
const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
|
|
587
|
-
return this.#performOperationWithRetry(operation, at);
|
|
593
|
+
return this.#operationQueue.add(() => this.#performOperationWithRetry(operation, at));
|
|
588
594
|
}
|
|
589
595
|
async #getStorageOperation(items, childTrie, at) {
|
|
590
596
|
await this.#ensureFollowed();
|
|
@@ -621,4 +627,8 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
621
627
|
return;
|
|
622
628
|
await this.send('unpin', this.#subscriptionId, hashes);
|
|
623
629
|
}
|
|
630
|
+
#__unsafe__isSmoldot() {
|
|
631
|
+
// @ts-ignore a trick internally to check whether a provider is using smoldot connection
|
|
632
|
+
return typeof this.client.provider['chain'] === 'function';
|
|
633
|
+
}
|
|
624
634
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dedot/api",
|
|
3
|
-
"version": "0.9.5-next.
|
|
3
|
+
"version": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
4
4
|
"description": "A delightful JavaScript/TypeScript client for Polkadot & Substrate",
|
|
5
5
|
"author": "Thang X. Vu <thang@dedot.dev>",
|
|
6
6
|
"homepage": "https://dedot.dev",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"sideEffects": false,
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@dedot/codecs": "0.9.5-next.
|
|
17
|
-
"@dedot/providers": "0.9.5-next.
|
|
18
|
-
"@dedot/runtime-specs": "0.9.5-next.
|
|
19
|
-
"@dedot/shape": "0.9.5-next.
|
|
20
|
-
"@dedot/storage": "0.9.5-next.
|
|
21
|
-
"@dedot/types": "0.9.5-next.
|
|
22
|
-
"@dedot/utils": "0.9.5-next.
|
|
16
|
+
"@dedot/codecs": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
17
|
+
"@dedot/providers": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
18
|
+
"@dedot/runtime-specs": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
19
|
+
"@dedot/shape": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
20
|
+
"@dedot/storage": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
21
|
+
"@dedot/types": "0.9.5-next.889fe7cd.3+889fe7c",
|
|
22
|
+
"@dedot/utils": "0.9.5-next.889fe7cd.3+889fe7c"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=18"
|
|
49
49
|
},
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "889fe7cd88f69202d4634cfe9fd25900b41a8935",
|
|
52
52
|
"module": "./index.js",
|
|
53
53
|
"types": "./index.d.ts"
|
|
54
54
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AsyncQueue } from '@dedot/utils';
|
|
1
|
+
import { AsyncQueue, noop } from '@dedot/utils';
|
|
2
2
|
import { BaseStorageQuery } from './BaseStorageQuery.js';
|
|
3
3
|
/**
|
|
4
4
|
* @name NewStorageQuery
|
|
@@ -80,7 +80,13 @@ export class NewStorageQuery extends BaseStorageQuery {
|
|
|
80
80
|
await pull(best);
|
|
81
81
|
// Subscribe to best block events
|
|
82
82
|
const unsub = this.client.on('bestBlock', (block) => {
|
|
83
|
-
|
|
83
|
+
// Here we're handling each pull one by one,
|
|
84
|
+
// If the queue get too long, it might take a long time for us to get the fresh & latest data
|
|
85
|
+
// This is a precaution in such case, if the queue size >= 3 we skip all the pending pull and jump to the latest pull
|
|
86
|
+
if (pullQueue.size >= 3)
|
|
87
|
+
pullQueue.clear();
|
|
88
|
+
// TODO timing out for a pull to prevent it took too long to fetch
|
|
89
|
+
pullQueue.enqueue(() => pull(block)).catch(noop);
|
|
84
90
|
});
|
|
85
91
|
return async () => {
|
|
86
92
|
unsub();
|