@dedot/api 0.18.5 → 0.18.7
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/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +53 -33
- package/cjs/json-rpc/group/ChainHead/ChainHead.js +41 -11
- package/extrinsic/submittable/SubmittableExtrinsicV2.js +53 -33
- package/json-rpc/group/ChainHead/ChainHead.d.ts +2 -1
- package/json-rpc/group/ChainHead/ChainHead.js +40 -10
- package/package.json +9 -9
|
@@ -63,23 +63,30 @@ class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic_js_1.BaseSubmittab
|
|
|
63
63
|
};
|
|
64
64
|
let txFound;
|
|
65
65
|
let isSearching = false;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const searchQueue = new utils_1.AsyncQueue();
|
|
67
|
+
const finalizedQueue = new utils_1.AsyncQueue();
|
|
68
|
+
const trackedHashes = [];
|
|
69
69
|
const cancelPendingSearch = () => {
|
|
70
70
|
searchQueue.clear();
|
|
71
71
|
};
|
|
72
72
|
const cancelBodySearch = () => {
|
|
73
73
|
searchQueue.cancel();
|
|
74
74
|
};
|
|
75
|
-
const startSearching = (block) => {
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const startSearching = async (block) => {
|
|
76
|
+
const hash = block.hash;
|
|
77
|
+
if (!trackedHashes.includes(hash)) {
|
|
78
|
+
api.chainHead.holdBlock(hash);
|
|
79
|
+
trackedHashes.push(hash);
|
|
80
|
+
}
|
|
81
|
+
return searchQueue
|
|
82
|
+
.enqueue(async () => {
|
|
83
|
+
const found = await checkTxIsOnChain(hash);
|
|
78
84
|
if (found) {
|
|
79
85
|
cancelPendingSearch();
|
|
80
86
|
}
|
|
81
87
|
return found;
|
|
82
|
-
})
|
|
88
|
+
})
|
|
89
|
+
.catch(() => undefined);
|
|
83
90
|
};
|
|
84
91
|
const checkBestBlockIncluded = async (block, bestChainChanged) => {
|
|
85
92
|
if (bestChainChanged) {
|
|
@@ -142,43 +149,56 @@ class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic_js_1.BaseSubmittab
|
|
|
142
149
|
}
|
|
143
150
|
};
|
|
144
151
|
const checkFinalizedBlockIncluded = async (block) => {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
status: { type: 'Finalized', value: { blockHash, blockNumber, txIndex } },
|
|
150
|
-
txHash,
|
|
151
|
-
events,
|
|
152
|
-
txIndex,
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// Revalidate the tx, just in-case it becomes invalid along the way
|
|
157
|
-
// Context: https://github.com/paritytech/json-rpc-interface-spec/pull/107#issuecomment-1906008814
|
|
158
|
-
const validation = await validateTx(block.hash);
|
|
159
|
-
if (validation.isOk)
|
|
160
|
-
return;
|
|
161
|
-
callback(new SubmittableResult_js_1.SubmittableResult({
|
|
162
|
-
status: {
|
|
163
|
-
type: 'Invalid',
|
|
164
|
-
value: { error: `Invalid Tx: ${validation.err.type} - ${validation.err.value.type}` },
|
|
165
|
-
},
|
|
166
|
-
txHash,
|
|
167
|
-
}));
|
|
152
|
+
const hash = block.hash;
|
|
153
|
+
if (!trackedHashes.includes(hash)) {
|
|
154
|
+
api.chainHead.holdBlock(hash);
|
|
155
|
+
trackedHashes.push(hash);
|
|
168
156
|
}
|
|
169
|
-
|
|
157
|
+
finalizedQueue
|
|
158
|
+
.enqueue(async () => {
|
|
159
|
+
const inBlock = await checkTxIsOnChain(hash);
|
|
160
|
+
if (inBlock) {
|
|
161
|
+
const { index: txIndex, events, blockHash, blockNumber } = inBlock;
|
|
162
|
+
callback(new SubmittableResult_js_1.SubmittableResult({
|
|
163
|
+
status: { type: 'Finalized', value: { blockHash, blockNumber, txIndex } },
|
|
164
|
+
txHash,
|
|
165
|
+
events,
|
|
166
|
+
txIndex,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Revalidate the tx, just in-case it becomes invalid along the way
|
|
171
|
+
// Context: https://github.com/paritytech/json-rpc-interface-spec/pull/107#issuecomment-1906008814
|
|
172
|
+
const validation = await validateTx(hash);
|
|
173
|
+
if (validation.isOk)
|
|
174
|
+
return;
|
|
175
|
+
callback(new SubmittableResult_js_1.SubmittableResult({
|
|
176
|
+
status: {
|
|
177
|
+
type: 'Invalid',
|
|
178
|
+
value: { error: `Invalid Tx: ${validation.err.type} - ${validation.err.value.type}` },
|
|
179
|
+
},
|
|
180
|
+
txHash,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
txUnsub().catch(utils_1.noop);
|
|
184
|
+
})
|
|
185
|
+
.catch(utils_1.noop);
|
|
170
186
|
};
|
|
187
|
+
const stopBestBlockTrackingFn = api.on('bestBlock', checkBestBlockIncluded);
|
|
188
|
+
const stopFinalizedBlockTrackingFn = api.on('finalizedBlock', checkFinalizedBlockIncluded);
|
|
171
189
|
stopBroadcastFn = await api.txBroadcaster.broadcastTx(txHex);
|
|
172
190
|
callback(new SubmittableResult_js_1.SubmittableResult({
|
|
173
191
|
status: { type: 'Broadcasting' },
|
|
174
192
|
txHash,
|
|
175
193
|
}));
|
|
176
|
-
const stopBestBlockTrackingFn = api.on('bestBlock', checkBestBlockIncluded);
|
|
177
|
-
const stopFinalizedBlockTrackingFn = api.on('finalizedBlock', checkFinalizedBlockIncluded);
|
|
178
194
|
const stopTracking = () => {
|
|
179
195
|
stopBestBlockTrackingFn();
|
|
180
196
|
stopFinalizedBlockTrackingFn();
|
|
181
197
|
cancelBodySearch();
|
|
198
|
+
finalizedQueue.cancel();
|
|
199
|
+
trackedHashes.forEach((h) => {
|
|
200
|
+
api.chainHead.releaseBlock(h);
|
|
201
|
+
});
|
|
182
202
|
};
|
|
183
203
|
txUnsub = async () => {
|
|
184
204
|
stopTracking();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ChainHead =
|
|
3
|
+
exports.ChainHead = void 0;
|
|
4
4
|
const codecs_1 = require("@dedot/codecs");
|
|
5
5
|
const utils_1 = require("@dedot/utils");
|
|
6
6
|
const JsonRpcGroup_js_1 = require("../JsonRpcGroup.js");
|
|
7
7
|
const BlockUsage_js_1 = require("./BlockUsage.js");
|
|
8
8
|
const error_js_1 = require("./error.js");
|
|
9
|
-
|
|
9
|
+
const MIN_FINALIZED_QUEUE_SIZE = 16; // finalized queue size
|
|
10
10
|
const CHAINHEAD_CACHE_CAPACITY = 256;
|
|
11
11
|
const CHAINHEAD_CACHE_TTL = 30_000; // 30 seconds
|
|
12
12
|
class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
@@ -25,6 +25,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
25
25
|
#blockUsage;
|
|
26
26
|
#cache;
|
|
27
27
|
#operationQueue;
|
|
28
|
+
#minQueueSize;
|
|
28
29
|
/**
|
|
29
30
|
* Archive instance used as fallback when ChainHead blocks are not pinned.
|
|
30
31
|
*
|
|
@@ -48,6 +49,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
48
49
|
this.#cache = new utils_1.LRUCache(CHAINHEAD_CACHE_CAPACITY, CHAINHEAD_CACHE_TTL);
|
|
49
50
|
// This helps us to not accidentally putting too much stress on the JSON-RPC server, especially smoldot/light-client
|
|
50
51
|
this.#operationQueue = new utils_1.ThrottleQueue(this.#__unsafe__isSmoldot() ? 25 : 250);
|
|
52
|
+
this.#minQueueSize = MIN_FINALIZED_QUEUE_SIZE;
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Attach an Archive instance as fallback for operations that fail due to unpinned blocks.
|
|
@@ -140,6 +142,9 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
140
142
|
finalizedBlockHashes.push(finalizedBlockHash);
|
|
141
143
|
this.#subscriptionId = subscription.subscriptionId;
|
|
142
144
|
this.#finalizedQueue = finalizedBlockHashes;
|
|
145
|
+
if (finalizedBlockHashes.length > MIN_FINALIZED_QUEUE_SIZE) {
|
|
146
|
+
this.#minQueueSize = finalizedBlockHashes.length;
|
|
147
|
+
}
|
|
143
148
|
this.#finalizedRuntime = this.#extractRuntime(finalizedBlockRuntime);
|
|
144
149
|
(0, utils_1.assert)(this.#finalizedRuntime, 'Invalid finalized runtime');
|
|
145
150
|
this.#bestHash = this.#finalizedHash = finalizedBlockHashes.at(-1);
|
|
@@ -224,9 +229,9 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
224
229
|
const pinnedHashes = Object.keys(this.#pinnedBlocks);
|
|
225
230
|
const hashesToUnpin = new Set(prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)));
|
|
226
231
|
// Unpin the oldest finalized pinned blocks to maintain the queue size
|
|
227
|
-
if (this.#finalizedQueue.length >
|
|
232
|
+
if (this.#finalizedQueue.length > this.#minQueueSize) {
|
|
228
233
|
const finalizedQueue = this.#finalizedQueue.slice();
|
|
229
|
-
const numOfItemsToUnpin = finalizedQueue.length -
|
|
234
|
+
const numOfItemsToUnpin = finalizedQueue.length - this.#minQueueSize;
|
|
230
235
|
const queuedHashesToUnpin = finalizedQueue.splice(0, numOfItemsToUnpin);
|
|
231
236
|
queuedHashesToUnpin.forEach((hash) => {
|
|
232
237
|
// if the block is being used, we'll keep it pinned
|
|
@@ -553,9 +558,15 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
553
558
|
const resp = await this.send('body', this.#subscriptionId, hash);
|
|
554
559
|
return this.#awaitOperation(resp, hash);
|
|
555
560
|
};
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
561
|
+
try {
|
|
562
|
+
this.#blockUsage.use(atHash);
|
|
563
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(bodyOperation, atHash));
|
|
564
|
+
this.#cache.set(cacheKey, resp);
|
|
565
|
+
return resp;
|
|
566
|
+
}
|
|
567
|
+
finally {
|
|
568
|
+
this.#blockUsage.release(atHash);
|
|
569
|
+
}
|
|
559
570
|
};
|
|
560
571
|
const fallback = async (archive, hash) => {
|
|
561
572
|
const result = await archive.body(hash);
|
|
@@ -593,9 +604,15 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
593
604
|
const resp = await this.send('call', this.#subscriptionId, hash, func, params);
|
|
594
605
|
return this.#awaitOperation(resp, hash);
|
|
595
606
|
};
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
607
|
+
try {
|
|
608
|
+
this.#blockUsage.use(atHash);
|
|
609
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(callOperation, atHash));
|
|
610
|
+
this.#cache.set(cacheKey, resp);
|
|
611
|
+
return resp;
|
|
612
|
+
}
|
|
613
|
+
finally {
|
|
614
|
+
this.#blockUsage.release(atHash);
|
|
615
|
+
}
|
|
599
616
|
};
|
|
600
617
|
const fallback = (archive, hash) => archive.call(func, params, hash);
|
|
601
618
|
try {
|
|
@@ -687,7 +704,13 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
687
704
|
}
|
|
688
705
|
async #getStorage(items, childTrie, at) {
|
|
689
706
|
const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
|
|
690
|
-
|
|
707
|
+
try {
|
|
708
|
+
this.#blockUsage.use(at);
|
|
709
|
+
return await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, at));
|
|
710
|
+
}
|
|
711
|
+
finally {
|
|
712
|
+
this.#blockUsage.release(at);
|
|
713
|
+
}
|
|
691
714
|
}
|
|
692
715
|
async #getStorageOperation(items, childTrie, at) {
|
|
693
716
|
await this.#ensureFollowed();
|
|
@@ -742,5 +765,12 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
742
765
|
this.#cache.clear();
|
|
743
766
|
this.#archive?.clearCache();
|
|
744
767
|
}
|
|
768
|
+
holdBlock(hash) {
|
|
769
|
+
this.#ensurePinnedHash(hash);
|
|
770
|
+
this.#blockUsage.use(hash);
|
|
771
|
+
}
|
|
772
|
+
releaseBlock(hash) {
|
|
773
|
+
this.#blockUsage.release(hash);
|
|
774
|
+
}
|
|
745
775
|
}
|
|
746
776
|
exports.ChainHead = ChainHead;
|
|
@@ -60,23 +60,30 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
60
60
|
};
|
|
61
61
|
let txFound;
|
|
62
62
|
let isSearching = false;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
const searchQueue = new AsyncQueue();
|
|
64
|
+
const finalizedQueue = new AsyncQueue();
|
|
65
|
+
const trackedHashes = [];
|
|
66
66
|
const cancelPendingSearch = () => {
|
|
67
67
|
searchQueue.clear();
|
|
68
68
|
};
|
|
69
69
|
const cancelBodySearch = () => {
|
|
70
70
|
searchQueue.cancel();
|
|
71
71
|
};
|
|
72
|
-
const startSearching = (block) => {
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const startSearching = async (block) => {
|
|
73
|
+
const hash = block.hash;
|
|
74
|
+
if (!trackedHashes.includes(hash)) {
|
|
75
|
+
api.chainHead.holdBlock(hash);
|
|
76
|
+
trackedHashes.push(hash);
|
|
77
|
+
}
|
|
78
|
+
return searchQueue
|
|
79
|
+
.enqueue(async () => {
|
|
80
|
+
const found = await checkTxIsOnChain(hash);
|
|
75
81
|
if (found) {
|
|
76
82
|
cancelPendingSearch();
|
|
77
83
|
}
|
|
78
84
|
return found;
|
|
79
|
-
})
|
|
85
|
+
})
|
|
86
|
+
.catch(() => undefined);
|
|
80
87
|
};
|
|
81
88
|
const checkBestBlockIncluded = async (block, bestChainChanged) => {
|
|
82
89
|
if (bestChainChanged) {
|
|
@@ -139,43 +146,56 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
139
146
|
}
|
|
140
147
|
};
|
|
141
148
|
const checkFinalizedBlockIncluded = async (block) => {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
status: { type: 'Finalized', value: { blockHash, blockNumber, txIndex } },
|
|
147
|
-
txHash,
|
|
148
|
-
events,
|
|
149
|
-
txIndex,
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
// Revalidate the tx, just in-case it becomes invalid along the way
|
|
154
|
-
// Context: https://github.com/paritytech/json-rpc-interface-spec/pull/107#issuecomment-1906008814
|
|
155
|
-
const validation = await validateTx(block.hash);
|
|
156
|
-
if (validation.isOk)
|
|
157
|
-
return;
|
|
158
|
-
callback(new SubmittableResult({
|
|
159
|
-
status: {
|
|
160
|
-
type: 'Invalid',
|
|
161
|
-
value: { error: `Invalid Tx: ${validation.err.type} - ${validation.err.value.type}` },
|
|
162
|
-
},
|
|
163
|
-
txHash,
|
|
164
|
-
}));
|
|
149
|
+
const hash = block.hash;
|
|
150
|
+
if (!trackedHashes.includes(hash)) {
|
|
151
|
+
api.chainHead.holdBlock(hash);
|
|
152
|
+
trackedHashes.push(hash);
|
|
165
153
|
}
|
|
166
|
-
|
|
154
|
+
finalizedQueue
|
|
155
|
+
.enqueue(async () => {
|
|
156
|
+
const inBlock = await checkTxIsOnChain(hash);
|
|
157
|
+
if (inBlock) {
|
|
158
|
+
const { index: txIndex, events, blockHash, blockNumber } = inBlock;
|
|
159
|
+
callback(new SubmittableResult({
|
|
160
|
+
status: { type: 'Finalized', value: { blockHash, blockNumber, txIndex } },
|
|
161
|
+
txHash,
|
|
162
|
+
events,
|
|
163
|
+
txIndex,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Revalidate the tx, just in-case it becomes invalid along the way
|
|
168
|
+
// Context: https://github.com/paritytech/json-rpc-interface-spec/pull/107#issuecomment-1906008814
|
|
169
|
+
const validation = await validateTx(hash);
|
|
170
|
+
if (validation.isOk)
|
|
171
|
+
return;
|
|
172
|
+
callback(new SubmittableResult({
|
|
173
|
+
status: {
|
|
174
|
+
type: 'Invalid',
|
|
175
|
+
value: { error: `Invalid Tx: ${validation.err.type} - ${validation.err.value.type}` },
|
|
176
|
+
},
|
|
177
|
+
txHash,
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
txUnsub().catch(noop);
|
|
181
|
+
})
|
|
182
|
+
.catch(noop);
|
|
167
183
|
};
|
|
184
|
+
const stopBestBlockTrackingFn = api.on('bestBlock', checkBestBlockIncluded);
|
|
185
|
+
const stopFinalizedBlockTrackingFn = api.on('finalizedBlock', checkFinalizedBlockIncluded);
|
|
168
186
|
stopBroadcastFn = await api.txBroadcaster.broadcastTx(txHex);
|
|
169
187
|
callback(new SubmittableResult({
|
|
170
188
|
status: { type: 'Broadcasting' },
|
|
171
189
|
txHash,
|
|
172
190
|
}));
|
|
173
|
-
const stopBestBlockTrackingFn = api.on('bestBlock', checkBestBlockIncluded);
|
|
174
|
-
const stopFinalizedBlockTrackingFn = api.on('finalizedBlock', checkFinalizedBlockIncluded);
|
|
175
191
|
const stopTracking = () => {
|
|
176
192
|
stopBestBlockTrackingFn();
|
|
177
193
|
stopFinalizedBlockTrackingFn();
|
|
178
194
|
cancelBodySearch();
|
|
195
|
+
finalizedQueue.cancel();
|
|
196
|
+
trackedHashes.forEach((h) => {
|
|
197
|
+
api.chainHead.releaseBlock(h);
|
|
198
|
+
});
|
|
179
199
|
};
|
|
180
200
|
txUnsub = async () => {
|
|
181
201
|
stopTracking();
|
|
@@ -17,7 +17,6 @@ export type PinnedBlock = {
|
|
|
17
17
|
runtime?: ChainHeadRuntimeVersion;
|
|
18
18
|
};
|
|
19
19
|
export type ChainHeadEvent = 'newBlock' | 'bestBlock' | 'finalizedBlock' | 'bestChainChanged';
|
|
20
|
-
export declare const MIN_FINALIZED_QUEUE_SIZE = 10;
|
|
21
20
|
export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
|
|
22
21
|
#private;
|
|
23
22
|
constructor(client: IJsonRpcClient, options?: Partial<JsonRpcGroupOptions>);
|
|
@@ -88,4 +87,6 @@ export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
|
|
|
88
87
|
* ```
|
|
89
88
|
*/
|
|
90
89
|
clearCache(): void;
|
|
90
|
+
holdBlock(hash: BlockHash): void;
|
|
91
|
+
releaseBlock(hash: BlockHash): void;
|
|
91
92
|
}
|
|
@@ -3,7 +3,7 @@ import { assert, AsyncQueue, deferred, ensurePresence, noop, ThrottleQueue, wait
|
|
|
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';
|
|
6
|
-
|
|
6
|
+
const MIN_FINALIZED_QUEUE_SIZE = 16; // finalized queue size
|
|
7
7
|
const CHAINHEAD_CACHE_CAPACITY = 256;
|
|
8
8
|
const CHAINHEAD_CACHE_TTL = 30_000; // 30 seconds
|
|
9
9
|
export class ChainHead extends JsonRpcGroup {
|
|
@@ -22,6 +22,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
22
22
|
#blockUsage;
|
|
23
23
|
#cache;
|
|
24
24
|
#operationQueue;
|
|
25
|
+
#minQueueSize;
|
|
25
26
|
/**
|
|
26
27
|
* Archive instance used as fallback when ChainHead blocks are not pinned.
|
|
27
28
|
*
|
|
@@ -45,6 +46,7 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
45
46
|
this.#cache = new LRUCache(CHAINHEAD_CACHE_CAPACITY, CHAINHEAD_CACHE_TTL);
|
|
46
47
|
// This helps us to not accidentally putting too much stress on the JSON-RPC server, especially smoldot/light-client
|
|
47
48
|
this.#operationQueue = new ThrottleQueue(this.#__unsafe__isSmoldot() ? 25 : 250);
|
|
49
|
+
this.#minQueueSize = MIN_FINALIZED_QUEUE_SIZE;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Attach an Archive instance as fallback for operations that fail due to unpinned blocks.
|
|
@@ -137,6 +139,9 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
137
139
|
finalizedBlockHashes.push(finalizedBlockHash);
|
|
138
140
|
this.#subscriptionId = subscription.subscriptionId;
|
|
139
141
|
this.#finalizedQueue = finalizedBlockHashes;
|
|
142
|
+
if (finalizedBlockHashes.length > MIN_FINALIZED_QUEUE_SIZE) {
|
|
143
|
+
this.#minQueueSize = finalizedBlockHashes.length;
|
|
144
|
+
}
|
|
140
145
|
this.#finalizedRuntime = this.#extractRuntime(finalizedBlockRuntime);
|
|
141
146
|
assert(this.#finalizedRuntime, 'Invalid finalized runtime');
|
|
142
147
|
this.#bestHash = this.#finalizedHash = finalizedBlockHashes.at(-1);
|
|
@@ -221,9 +226,9 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
221
226
|
const pinnedHashes = Object.keys(this.#pinnedBlocks);
|
|
222
227
|
const hashesToUnpin = new Set(prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)));
|
|
223
228
|
// Unpin the oldest finalized pinned blocks to maintain the queue size
|
|
224
|
-
if (this.#finalizedQueue.length >
|
|
229
|
+
if (this.#finalizedQueue.length > this.#minQueueSize) {
|
|
225
230
|
const finalizedQueue = this.#finalizedQueue.slice();
|
|
226
|
-
const numOfItemsToUnpin = finalizedQueue.length -
|
|
231
|
+
const numOfItemsToUnpin = finalizedQueue.length - this.#minQueueSize;
|
|
227
232
|
const queuedHashesToUnpin = finalizedQueue.splice(0, numOfItemsToUnpin);
|
|
228
233
|
queuedHashesToUnpin.forEach((hash) => {
|
|
229
234
|
// if the block is being used, we'll keep it pinned
|
|
@@ -550,9 +555,15 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
550
555
|
const resp = await this.send('body', this.#subscriptionId, hash);
|
|
551
556
|
return this.#awaitOperation(resp, hash);
|
|
552
557
|
};
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
558
|
+
try {
|
|
559
|
+
this.#blockUsage.use(atHash);
|
|
560
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(bodyOperation, atHash));
|
|
561
|
+
this.#cache.set(cacheKey, resp);
|
|
562
|
+
return resp;
|
|
563
|
+
}
|
|
564
|
+
finally {
|
|
565
|
+
this.#blockUsage.release(atHash);
|
|
566
|
+
}
|
|
556
567
|
};
|
|
557
568
|
const fallback = async (archive, hash) => {
|
|
558
569
|
const result = await archive.body(hash);
|
|
@@ -590,9 +601,15 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
590
601
|
const resp = await this.send('call', this.#subscriptionId, hash, func, params);
|
|
591
602
|
return this.#awaitOperation(resp, hash);
|
|
592
603
|
};
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
604
|
+
try {
|
|
605
|
+
this.#blockUsage.use(atHash);
|
|
606
|
+
const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(callOperation, atHash));
|
|
607
|
+
this.#cache.set(cacheKey, resp);
|
|
608
|
+
return resp;
|
|
609
|
+
}
|
|
610
|
+
finally {
|
|
611
|
+
this.#blockUsage.release(atHash);
|
|
612
|
+
}
|
|
596
613
|
};
|
|
597
614
|
const fallback = (archive, hash) => archive.call(func, params, hash);
|
|
598
615
|
try {
|
|
@@ -684,7 +701,13 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
684
701
|
}
|
|
685
702
|
async #getStorage(items, childTrie, at) {
|
|
686
703
|
const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
|
|
687
|
-
|
|
704
|
+
try {
|
|
705
|
+
this.#blockUsage.use(at);
|
|
706
|
+
return await this.#operationQueue.add(() => this.#performOperationWithRetry(operation, at));
|
|
707
|
+
}
|
|
708
|
+
finally {
|
|
709
|
+
this.#blockUsage.release(at);
|
|
710
|
+
}
|
|
688
711
|
}
|
|
689
712
|
async #getStorageOperation(items, childTrie, at) {
|
|
690
713
|
await this.#ensureFollowed();
|
|
@@ -739,4 +762,11 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
739
762
|
this.#cache.clear();
|
|
740
763
|
this.#archive?.clearCache();
|
|
741
764
|
}
|
|
765
|
+
holdBlock(hash) {
|
|
766
|
+
this.#ensurePinnedHash(hash);
|
|
767
|
+
this.#blockUsage.use(hash);
|
|
768
|
+
}
|
|
769
|
+
releaseBlock(hash) {
|
|
770
|
+
this.#blockUsage.release(hash);
|
|
771
|
+
}
|
|
742
772
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dedot/api",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.7",
|
|
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.18.
|
|
17
|
-
"@dedot/providers": "0.18.
|
|
18
|
-
"@dedot/runtime-specs": "0.18.
|
|
19
|
-
"@dedot/shape": "0.18.
|
|
20
|
-
"@dedot/storage": "0.18.
|
|
21
|
-
"@dedot/types": "0.18.
|
|
22
|
-
"@dedot/utils": "0.18.
|
|
16
|
+
"@dedot/codecs": "0.18.7",
|
|
17
|
+
"@dedot/providers": "0.18.7",
|
|
18
|
+
"@dedot/runtime-specs": "0.18.7",
|
|
19
|
+
"@dedot/shape": "0.18.7",
|
|
20
|
+
"@dedot/storage": "0.18.7",
|
|
21
|
+
"@dedot/types": "0.18.7",
|
|
22
|
+
"@dedot/utils": "0.18.7"
|
|
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": "07f47f75512087407e03f301fc18c2059b3f613a",
|
|
52
52
|
"module": "./index.js",
|
|
53
53
|
"types": "./index.d.ts"
|
|
54
54
|
}
|