@dedot/api 0.18.8 → 1.0.0
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/chaintypes/substrate/consts.d.ts +2 -2
- package/chaintypes/substrate/consts.js +1 -0
- package/chaintypes/substrate/errors.d.ts +909 -909
- package/chaintypes/substrate/errors.js +1 -0
- package/chaintypes/substrate/events.d.ts +664 -664
- package/chaintypes/substrate/events.js +1 -0
- package/chaintypes/substrate/index.d.ts +13 -16
- package/chaintypes/substrate/index.js +1 -0
- package/chaintypes/substrate/json-rpc.d.ts +2 -2
- package/chaintypes/substrate/json-rpc.js +1 -0
- package/chaintypes/substrate/query.d.ts +509 -527
- package/chaintypes/substrate/query.js +1 -0
- package/chaintypes/substrate/runtime.d.ts +123 -123
- package/chaintypes/substrate/runtime.js +1 -0
- package/chaintypes/substrate/tx.d.ts +1311 -1311
- package/chaintypes/substrate/tx.js +1 -0
- package/chaintypes/substrate/types.js +1 -0
- package/chaintypes/substrate/view-functions.d.ts +2 -2
- package/chaintypes/substrate/view-functions.js +1 -0
- package/cjs/chaintypes/substrate/consts.js +1 -0
- package/cjs/chaintypes/substrate/errors.js +1 -0
- package/cjs/chaintypes/substrate/events.js +1 -0
- package/cjs/chaintypes/substrate/index.js +1 -0
- package/cjs/chaintypes/substrate/json-rpc.js +1 -0
- package/cjs/chaintypes/substrate/query.js +1 -0
- package/cjs/chaintypes/substrate/runtime.js +1 -0
- package/cjs/chaintypes/substrate/tx.js +1 -0
- package/cjs/chaintypes/substrate/types.js +1 -0
- package/cjs/chaintypes/substrate/view-functions.js +1 -0
- package/cjs/client/BaseSubstrateClient.js +45 -2
- package/cjs/client/DedotClient.js +356 -224
- package/cjs/client/LegacyClient.js +99 -23
- package/cjs/client/V2Client.js +316 -0
- package/cjs/client/explorer/LegacyBlockExplorer.js +263 -0
- package/cjs/client/explorer/V2BlockExplorer.js +74 -0
- package/cjs/client/explorer/index.js +18 -0
- package/cjs/client/utils.js +57 -0
- package/cjs/executor/EventExecutor.js +1 -0
- package/cjs/executor/RuntimeApiExecutor.js +49 -2
- package/cjs/executor/StorageQueryExecutor.js +114 -2
- package/cjs/executor/ViewFunctionExecutor.js +21 -2
- package/cjs/executor/v2/StorageQueryExecutorV2.js +7 -1
- package/cjs/executor/validation-helpers.js +181 -0
- package/cjs/extrinsic/submittable/BaseSubmittableExtrinsic.js +86 -2
- package/cjs/extrinsic/submittable/SubmittableExtrinsic.js +38 -16
- package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +12 -2
- package/cjs/json-rpc/group/ChainHead/ChainHead.js +226 -21
- package/cjs/storage/QueryableStorage.js +1 -0
- package/client/BaseSubstrateClient.d.ts +34 -18
- package/client/BaseSubstrateClient.js +47 -4
- package/client/DedotClient.d.ts +346 -49
- package/client/DedotClient.js +356 -224
- package/client/LegacyClient.d.ts +22 -17
- package/client/LegacyClient.js +101 -25
- package/client/V2Client.d.ts +75 -0
- package/client/V2Client.js +312 -0
- package/client/explorer/LegacyBlockExplorer.d.ts +68 -0
- package/client/explorer/LegacyBlockExplorer.js +259 -0
- package/client/explorer/V2BlockExplorer.d.ts +40 -0
- package/client/explorer/V2BlockExplorer.js +70 -0
- package/client/explorer/index.d.ts +2 -0
- package/client/explorer/index.js +2 -0
- package/client/utils.d.ts +18 -0
- package/client/utils.js +52 -0
- package/executor/ConstantExecutor.d.ts +1 -2
- package/executor/ErrorExecutor.d.ts +2 -2
- package/executor/EventExecutor.d.ts +2 -2
- package/executor/EventExecutor.js +1 -0
- package/executor/Executor.d.ts +4 -5
- package/executor/RuntimeApiExecutor.d.ts +2 -2
- package/executor/RuntimeApiExecutor.js +27 -3
- package/executor/StorageQueryExecutor.d.ts +3 -2
- package/executor/StorageQueryExecutor.js +92 -3
- package/executor/TxExecutor.d.ts +2 -2
- package/executor/ViewFunctionExecutor.d.ts +2 -2
- package/executor/ViewFunctionExecutor.js +22 -3
- package/executor/v2/RuntimeApiExecutorV2.d.ts +2 -3
- package/executor/v2/StorageQueryExecutorV2.d.ts +3 -3
- package/executor/v2/StorageQueryExecutorV2.js +8 -2
- package/executor/v2/TxExecutorV2.d.ts +5 -5
- package/executor/v2/ViewFunctionExecutorV2.d.ts +2 -3
- package/executor/validation-helpers.d.ts +36 -0
- package/executor/validation-helpers.js +174 -0
- package/extrinsic/extensions/SignedExtension.d.ts +1 -1
- package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +7 -3
- package/extrinsic/submittable/BaseSubmittableExtrinsic.js +64 -3
- package/extrinsic/submittable/SubmittableExtrinsic.d.ts +4 -1
- package/extrinsic/submittable/SubmittableExtrinsic.js +39 -17
- package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +6 -3
- package/extrinsic/submittable/SubmittableExtrinsicV2.js +13 -3
- package/json-rpc/JsonRpcClient.d.ts +6 -6
- package/json-rpc/group/Archive.d.ts +1 -1
- package/json-rpc/group/ChainHead/ChainHead.d.ts +18 -2
- package/json-rpc/group/ChainHead/ChainHead.js +226 -21
- package/json-rpc/group/ChainSpec.d.ts +3 -3
- package/json-rpc/group/Transaction.d.ts +1 -1
- package/json-rpc/group/TransactionWatch.d.ts +1 -1
- package/package.json +9 -9
- package/proxychain.d.ts +3 -4
- package/storage/NewStorageQuery.d.ts +3 -3
- package/storage/QueryableStorage.js +1 -0
- package/types.d.ts +94 -6
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ChainHead = void 0;
|
|
4
4
|
const codecs_1 = require("@dedot/codecs");
|
|
5
|
+
const providers_1 = require("@dedot/providers");
|
|
5
6
|
const utils_1 = require("@dedot/utils");
|
|
7
|
+
const utils_js_1 = require("../../../client/utils.js");
|
|
6
8
|
const JsonRpcGroup_js_1 = require("../JsonRpcGroup.js");
|
|
7
9
|
const BlockUsage_js_1 = require("./BlockUsage.js");
|
|
8
10
|
const error_js_1 = require("./error.js");
|
|
9
|
-
const MIN_FINALIZED_QUEUE_SIZE =
|
|
11
|
+
const MIN_FINALIZED_QUEUE_SIZE = 10; // finalized queue size
|
|
10
12
|
const CHAINHEAD_CACHE_CAPACITY = 256;
|
|
11
13
|
const CHAINHEAD_CACHE_TTL = 30_000; // 30 seconds
|
|
12
14
|
class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
@@ -14,11 +16,15 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
14
16
|
#subscriptionId;
|
|
15
17
|
#handlers;
|
|
16
18
|
#pendingOperations;
|
|
19
|
+
#pendingRequests = new Map();
|
|
20
|
+
#requestIdCounter = 0;
|
|
17
21
|
#finalizedQueue;
|
|
18
22
|
#pinnedBlocks;
|
|
19
23
|
#bestHash;
|
|
24
|
+
#lastBestBlockNumber; // Track last emitted best block number for gap detection
|
|
20
25
|
#finalizedHash; // best finalized hash
|
|
21
26
|
#finalizedRuntime;
|
|
27
|
+
#hasher; // detected hasher from block header
|
|
22
28
|
#followResponseQueue;
|
|
23
29
|
#retryQueue;
|
|
24
30
|
#recovering;
|
|
@@ -63,6 +69,34 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
63
69
|
this.#archive = archive;
|
|
64
70
|
return this;
|
|
65
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Override send() to track pending requests for retry on network disconnection
|
|
74
|
+
*/
|
|
75
|
+
async send(method, ...params) {
|
|
76
|
+
const requestId = `req_${++this.#requestIdCounter}`;
|
|
77
|
+
const defer = (0, utils_1.deferred)();
|
|
78
|
+
this.#pendingRequests.set(requestId, {
|
|
79
|
+
method,
|
|
80
|
+
params,
|
|
81
|
+
defer,
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
const result = await super.send(method, ...params);
|
|
85
|
+
this.#pendingRequests.delete(requestId);
|
|
86
|
+
defer.resolve(result);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error instanceof providers_1.NetworkDisconnectedError) {
|
|
91
|
+
// Keep in pending for retry on re-follow - don't delete
|
|
92
|
+
// Return the deferred promise that will be resolved on retry
|
|
93
|
+
return defer.promise;
|
|
94
|
+
}
|
|
95
|
+
// Other errors: clean up and throw
|
|
96
|
+
this.#pendingRequests.delete(requestId);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
66
100
|
async runtimeVersion() {
|
|
67
101
|
await this.#ensureFollowed();
|
|
68
102
|
return this.#finalizedRuntime;
|
|
@@ -85,9 +119,20 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
85
119
|
async finalizedBlock() {
|
|
86
120
|
return this.findBlock(await this.finalizedHash());
|
|
87
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns the detected hasher for the chain
|
|
124
|
+
* Returns undefined if detection failed (client should use default)
|
|
125
|
+
*/
|
|
126
|
+
async hasher() {
|
|
127
|
+
await this.#ensureFollowed();
|
|
128
|
+
return this.#hasher;
|
|
129
|
+
}
|
|
88
130
|
findBlock(hash) {
|
|
89
131
|
return this.#pinnedBlocks[hash];
|
|
90
132
|
}
|
|
133
|
+
#findBlocksByNumber(blockNumber) {
|
|
134
|
+
return Object.values(this.#pinnedBlocks).filter((block) => block.number === blockNumber);
|
|
135
|
+
}
|
|
91
136
|
isPinned(hash) {
|
|
92
137
|
return !!this.findBlock(hash);
|
|
93
138
|
}
|
|
@@ -102,8 +147,10 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
102
147
|
/**
|
|
103
148
|
* chainHead_follow
|
|
104
149
|
*/
|
|
105
|
-
async follow() {
|
|
106
|
-
|
|
150
|
+
async follow(force) {
|
|
151
|
+
if (!force) {
|
|
152
|
+
(0, utils_1.assert)(!this.#subscriptionId, 'Already followed chain head. Please unfollow first.');
|
|
153
|
+
}
|
|
107
154
|
return this.#doFollow();
|
|
108
155
|
}
|
|
109
156
|
async #doFollow() {
|
|
@@ -132,7 +179,41 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
132
179
|
catch (e) {
|
|
133
180
|
defer.reject(e);
|
|
134
181
|
}
|
|
135
|
-
|
|
182
|
+
// Wait for follow to complete, then retry pending requests
|
|
183
|
+
await defer.promise;
|
|
184
|
+
// Retry pending requests if any (from previous disconnection)
|
|
185
|
+
if (this.#pendingRequests.size > 0) {
|
|
186
|
+
this.#retryPendingRequests();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Retry pending requests that were queued during network disconnection
|
|
191
|
+
*/
|
|
192
|
+
#retryPendingRequests() {
|
|
193
|
+
const pending = Array.from(this.#pendingRequests.entries());
|
|
194
|
+
for (const [requestId, request] of pending) {
|
|
195
|
+
const { method, params, defer } = request;
|
|
196
|
+
// Update subscription ID with the new one (first param for all ChainHead RPC methods)
|
|
197
|
+
const updatedParams = [...params];
|
|
198
|
+
if (updatedParams.length > 0) {
|
|
199
|
+
updatedParams[0] = this.#subscriptionId;
|
|
200
|
+
}
|
|
201
|
+
// Retry the request with new subscriptionId
|
|
202
|
+
super
|
|
203
|
+
.send(method, ...updatedParams)
|
|
204
|
+
.then((result) => {
|
|
205
|
+
defer.resolve(result);
|
|
206
|
+
this.#pendingRequests.delete(requestId);
|
|
207
|
+
})
|
|
208
|
+
.catch((error) => {
|
|
209
|
+
if (!(error instanceof providers_1.NetworkDisconnectedError)) {
|
|
210
|
+
// Real error, reject and clean up
|
|
211
|
+
defer.reject(error);
|
|
212
|
+
this.#pendingRequests.delete(requestId);
|
|
213
|
+
}
|
|
214
|
+
// If NetworkDisconnectedError again, keep in pending for next retry
|
|
215
|
+
});
|
|
216
|
+
}
|
|
136
217
|
}
|
|
137
218
|
#onFollowEvent = async (result, subscription) => {
|
|
138
219
|
switch (result.event) {
|
|
@@ -140,6 +221,9 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
140
221
|
const { finalizedBlockHashes = [], finalizedBlockHash, finalizedBlockRuntime } = result;
|
|
141
222
|
if (finalizedBlockHash)
|
|
142
223
|
finalizedBlockHashes.push(finalizedBlockHash);
|
|
224
|
+
const lastFinalizedHash = this.#finalizedHash;
|
|
225
|
+
const prevPinnedBlocks = this.#pinnedBlocks;
|
|
226
|
+
// Initialize subscription state
|
|
143
227
|
this.#subscriptionId = subscription.subscriptionId;
|
|
144
228
|
this.#finalizedQueue = finalizedBlockHashes;
|
|
145
229
|
if (finalizedBlockHashes.length > MIN_FINALIZED_QUEUE_SIZE) {
|
|
@@ -147,35 +231,37 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
147
231
|
}
|
|
148
232
|
this.#finalizedRuntime = this.#extractRuntime(finalizedBlockRuntime);
|
|
149
233
|
(0, utils_1.assert)(this.#finalizedRuntime, 'Invalid finalized runtime');
|
|
150
|
-
|
|
151
|
-
this.#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
});
|
|
234
|
+
// Build initial pinned blocks
|
|
235
|
+
this.#finalizedHash = finalizedBlockHashes.at(-1);
|
|
236
|
+
if (!this.#bestHash) {
|
|
237
|
+
this.#bestHash = this.#finalizedHash;
|
|
238
|
+
}
|
|
239
|
+
this.#pinnedBlocks = this.#buildInitialPinnedBlocks(finalizedBlockHashes, this.#finalizedRuntime);
|
|
240
|
+
await this.#updateBlockNumbersAndParents(finalizedBlockHashes, prevPinnedBlocks);
|
|
241
|
+
// Handle first initialization or reconnection
|
|
242
|
+
if (!lastFinalizedHash) {
|
|
243
|
+
this.emit('finalizedBlock', await this.finalizedBlock());
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
await this.#handleMissingBlocks(finalizedBlockHashes, lastFinalizedHash, prevPinnedBlocks);
|
|
247
|
+
}
|
|
166
248
|
break;
|
|
167
249
|
}
|
|
168
250
|
case 'newBlock': {
|
|
169
251
|
const { blockHash: hash, parentBlockHash: parent, newRuntime } = result;
|
|
170
252
|
const runtime = this.#extractRuntime(newRuntime);
|
|
171
253
|
const parentBlock = this.findBlock(parent);
|
|
172
|
-
|
|
254
|
+
if (!parentBlock) {
|
|
255
|
+
console.warn(`Parent block not found for new block ${hash}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
173
258
|
this.#pinnedBlocks[hash] = {
|
|
174
259
|
hash,
|
|
175
260
|
parent,
|
|
176
261
|
// if the runtime is not provided, we'll find it from the parent block
|
|
177
262
|
runtime: runtime || this.#findRuntimeAt(parent),
|
|
178
263
|
number: parentBlock.number + 1,
|
|
264
|
+
runtimeUpgraded: !!runtime,
|
|
179
265
|
};
|
|
180
266
|
this.emit('newBlock', this.#pinnedBlocks[hash]);
|
|
181
267
|
break;
|
|
@@ -188,8 +274,13 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
188
274
|
return;
|
|
189
275
|
if (currentBestBlock.hash === newBestBlock.hash)
|
|
190
276
|
return;
|
|
277
|
+
// Detect and fill gaps before updating best hash
|
|
278
|
+
if (this.#lastBestBlockNumber !== undefined) {
|
|
279
|
+
this.#fillBestBlockGaps(this.#lastBestBlockNumber, newBestBlock.number);
|
|
280
|
+
}
|
|
191
281
|
const bestChainChanged = !this.#onTheSameChain(currentBestBlock, newBestBlock);
|
|
192
282
|
this.#bestHash = bestBlockHash;
|
|
283
|
+
this.#lastBestBlockNumber = newBestBlock.number; // Update tracking
|
|
193
284
|
this.emit('bestBlock', newBestBlock, bestChainChanged);
|
|
194
285
|
if (bestChainChanged)
|
|
195
286
|
this.emit('bestChainChanged', newBestBlock);
|
|
@@ -365,6 +456,100 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
365
456
|
return;
|
|
366
457
|
return this.#findRuntimeAt(block.parent);
|
|
367
458
|
}
|
|
459
|
+
#hasRuntimeUpgrade(header) {
|
|
460
|
+
return header.digest.logs.some((log) => log.type === 'RuntimeEnvironmentUpdated');
|
|
461
|
+
}
|
|
462
|
+
#buildInitialPinnedBlocks(finalizedBlockHashes, runtime) {
|
|
463
|
+
return finalizedBlockHashes.reduce((o, hash, idx, arr) => {
|
|
464
|
+
o[hash] = { hash, parent: arr[idx - 1], number: idx };
|
|
465
|
+
// assign finalized runtime to the current finalized block
|
|
466
|
+
if (idx === finalizedBlockHashes.length - 1) {
|
|
467
|
+
o[hash]['runtime'] = runtime;
|
|
468
|
+
}
|
|
469
|
+
return o;
|
|
470
|
+
}, {});
|
|
471
|
+
}
|
|
472
|
+
async #fetchInitializationHeaders(finalizedBlockHashes, prevPinnedBlocks) {
|
|
473
|
+
const fetchStartingHeader = async () => {
|
|
474
|
+
const startingBlockHash = finalizedBlockHashes[0];
|
|
475
|
+
const existing = prevPinnedBlocks[startingBlockHash];
|
|
476
|
+
if (existing) {
|
|
477
|
+
return {
|
|
478
|
+
number: existing.number,
|
|
479
|
+
parent: existing.parent,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
const header = codecs_1.$Header.tryDecode(await this.#getHeader(startingBlockHash));
|
|
484
|
+
return {
|
|
485
|
+
number: header.number,
|
|
486
|
+
parent: header.parentHash,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
const [startingBlock, finalizedHeader] = await Promise.all([
|
|
491
|
+
fetchStartingHeader(),
|
|
492
|
+
codecs_1.$Header.tryDecode(await this.#getHeader(this.#finalizedHash)),
|
|
493
|
+
]);
|
|
494
|
+
return { startingHeader: startingBlock, finalizedHeader };
|
|
495
|
+
}
|
|
496
|
+
async #updateBlockNumbersAndParents(finalizedBlockHashes, prevPinnedBlocks) {
|
|
497
|
+
// Fetch headers and update block metadata
|
|
498
|
+
const { startingHeader, finalizedHeader } = await this.#fetchInitializationHeaders(finalizedBlockHashes, prevPinnedBlocks);
|
|
499
|
+
// Detect hasher using the finalized header we just fetched
|
|
500
|
+
this.#hasher = (0, utils_js_1.detectHasherFromHeader)(finalizedHeader, this.#finalizedHash);
|
|
501
|
+
Object.values(this.#pinnedBlocks).forEach((b, idx) => {
|
|
502
|
+
b.number += startingHeader.number;
|
|
503
|
+
if (idx === 0) {
|
|
504
|
+
b.parent = startingHeader.parent;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
this.#pinnedBlocks[this.#finalizedHash].runtimeUpgraded = this.#hasRuntimeUpgrade(finalizedHeader);
|
|
508
|
+
}
|
|
509
|
+
async #fetchAndApplyRuntimeUpgrades(missingHashes, prevPinnedBlocks) {
|
|
510
|
+
const blocksNeedingHeaders = [];
|
|
511
|
+
for (const hash of missingHashes) {
|
|
512
|
+
const prevBlock = prevPinnedBlocks[hash];
|
|
513
|
+
const currentBlock = this.#pinnedBlocks[hash];
|
|
514
|
+
if (prevBlock?.runtimeUpgraded !== undefined) {
|
|
515
|
+
// Reuse existing runtime info from prevPinnedBlocks
|
|
516
|
+
currentBlock.runtimeUpgraded = prevBlock.runtimeUpgraded;
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
// Need to fetch header for this block
|
|
520
|
+
blocksNeedingHeaders.push(hash);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (blocksNeedingHeaders.length === 0)
|
|
524
|
+
return;
|
|
525
|
+
const headers = await Promise.all(blocksNeedingHeaders.map(async (hash) => ({
|
|
526
|
+
hash,
|
|
527
|
+
header: codecs_1.$Header.tryDecode(await this.#getHeader(hash)),
|
|
528
|
+
})));
|
|
529
|
+
// Set runtimeUpgraded flag based on digest check
|
|
530
|
+
for (const { hash, header } of headers) {
|
|
531
|
+
const block = this.#pinnedBlocks[hash];
|
|
532
|
+
if (block) {
|
|
533
|
+
block.runtimeUpgraded = this.#hasRuntimeUpgrade(header);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async #handleMissingBlocks(finalizedBlockHashes, lastFinalizedHash, prevPinnedBlocks) {
|
|
538
|
+
const lastFinalizedIndex = finalizedBlockHashes.indexOf(lastFinalizedHash);
|
|
539
|
+
if (lastFinalizedIndex !== -1 && lastFinalizedIndex < finalizedBlockHashes.length - 1) {
|
|
540
|
+
// Get missing block hashes that occurred during disconnection
|
|
541
|
+
const missingHashes = finalizedBlockHashes.slice(lastFinalizedIndex + 1);
|
|
542
|
+
// Identify blocks needing header fetch vs those with cached runtime info
|
|
543
|
+
await this.#fetchAndApplyRuntimeUpgrades(missingHashes, prevPinnedBlocks);
|
|
544
|
+
// Emit all missing finalized blocks with proper runtime info
|
|
545
|
+
for (const hash of missingHashes) {
|
|
546
|
+
const block = this.#pinnedBlocks[hash];
|
|
547
|
+
if (block) {
|
|
548
|
+
this.emit('finalizedBlock', block);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
368
553
|
#onTheSameChain(b1, b2) {
|
|
369
554
|
if (!b1 || !b2)
|
|
370
555
|
return false;
|
|
@@ -378,6 +563,23 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
378
563
|
return this.#onTheSameChain(this.findBlock(b1.parent), b2);
|
|
379
564
|
}
|
|
380
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Detect and emit missing best blocks from pinned blocks
|
|
568
|
+
* Called when bestBlockChanged event detects a gap
|
|
569
|
+
*/
|
|
570
|
+
#fillBestBlockGaps(lastNumber, currentNumber) {
|
|
571
|
+
// No gap if current is next sequential block
|
|
572
|
+
if (currentNumber <= lastNumber + 1) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
// Emit missing blocks from pinned blocks
|
|
576
|
+
for (let num = lastNumber + 1; num < currentNumber; num++) {
|
|
577
|
+
const blocks = this.#findBlocksByNumber(num);
|
|
578
|
+
for (let idx = 0; idx < blocks.length; idx++) {
|
|
579
|
+
this.emit('bestBlock', blocks[idx], idx >= 1);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
381
583
|
#ensurePinnedHash(hash) {
|
|
382
584
|
if (hash) {
|
|
383
585
|
if (this.isPinned(hash)) {
|
|
@@ -463,8 +665,10 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
463
665
|
this.#unsub = undefined;
|
|
464
666
|
this.#handlers = {};
|
|
465
667
|
this.#pendingOperations = {};
|
|
668
|
+
this.#pendingRequests.clear();
|
|
466
669
|
this.#pinnedBlocks = {};
|
|
467
670
|
this.#bestHash = undefined;
|
|
671
|
+
this.#lastBestBlockNumber = undefined;
|
|
468
672
|
this.#finalizedHash = undefined;
|
|
469
673
|
this.#finalizedRuntime = undefined;
|
|
470
674
|
this.#followResponseQueue.clear();
|
|
@@ -472,6 +676,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
472
676
|
this.#blockUsage.clear();
|
|
473
677
|
this.clearCache();
|
|
474
678
|
this.#operationQueue.cancel();
|
|
679
|
+
this.#hasher = undefined;
|
|
475
680
|
}
|
|
476
681
|
async #ensureFollowed() {
|
|
477
682
|
if (this.#recovering) {
|
|
@@ -68,6 +68,7 @@ class QueryableStorage {
|
|
|
68
68
|
const keyId = keyTypeIds[index];
|
|
69
69
|
const hasher = utils_1.HASHERS[hashers[index]];
|
|
70
70
|
const $keyCodec = this.registry.findCodec(keyId);
|
|
71
|
+
$keyCodec.assert?.(input);
|
|
71
72
|
return hasher($keyCodec.tryEncode(input));
|
|
72
73
|
});
|
|
73
74
|
return (0, utils_2.u8aToHex)((0, utils_2.concatU8a)(this.prefixKeyAsU8a, ...keyParts));
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { BlockHash, Hash, Metadata, PortableRegistry, RuntimeVersion } from '@dedot/codecs';
|
|
2
|
-
import type
|
|
1
|
+
import { BlockHash, Extrinsic, Hash, Metadata, PortableRegistry, RuntimeVersion } from '@dedot/codecs';
|
|
2
|
+
import { type JsonRpcProvider } from '@dedot/providers';
|
|
3
3
|
import { type IStorage } from '@dedot/storage';
|
|
4
|
-
import { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion,
|
|
5
|
-
import { Deferred, LRUCache } from '@dedot/utils';
|
|
4
|
+
import { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion, TxUnsub, Unsub } from '@dedot/types';
|
|
5
|
+
import { Deferred, HexString, LRUCache } from '@dedot/utils';
|
|
6
6
|
import type { SubstrateApi } from '../chaintypes/index.js';
|
|
7
7
|
import { JsonRpcClient } from '../json-rpc/index.js';
|
|
8
8
|
import { BaseStorageQuery } from '../storage/index.js';
|
|
9
|
-
import type { ApiEvent, ApiOptions, ISubstrateClient, ISubstrateClientAt, JsonRpcClientOptions, MetadataKey, SubstrateRuntimeVersion } from '../types.js';
|
|
9
|
+
import type { ApiEvent, ApiOptions, BlockExplorer, EventHandlerFn, IChainSpec, ISubstrateClient, ISubstrateClientAt, JsonRpcClientOptions, MetadataKey, SubstrateRuntimeVersion } from '../types.js';
|
|
10
10
|
export declare function ensurePresence<T>(value: T): NonNullable<T>;
|
|
11
11
|
/**
|
|
12
12
|
* @name BaseSubstrateClient
|
|
13
13
|
* @description Base & shared abstraction for Substrate API Clients
|
|
14
14
|
*/
|
|
15
|
-
export declare abstract class BaseSubstrateClient<
|
|
15
|
+
export declare abstract class BaseSubstrateClient<ChainApi extends GenericSubstrateApi = SubstrateApi, Events extends string = ApiEvent> extends JsonRpcClient<ChainApi, Events> implements ISubstrateClient<ChainApi, Events> {
|
|
16
|
+
#private;
|
|
16
17
|
rpcVersion: RpcVersion;
|
|
17
18
|
protected _options: ApiOptions;
|
|
18
|
-
protected _registry?: PortableRegistry<ChainApi[
|
|
19
|
+
protected _registry?: PortableRegistry<ChainApi['types']>;
|
|
19
20
|
protected _metadata?: Metadata;
|
|
20
21
|
protected _genesisHash?: Hash;
|
|
21
22
|
protected _runtimeVersion?: SubstrateRuntimeVersion;
|
|
@@ -30,7 +31,7 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
|
|
|
30
31
|
* Safely set metadata to cache with fallback cleanup if storage limit is exceeded
|
|
31
32
|
*/
|
|
32
33
|
protected safeSetMetadataToCache(key: string, value: string): Promise<void>;
|
|
33
|
-
protected setMetadata(metadata: Metadata): void
|
|
34
|
+
protected setMetadata(metadata: Metadata): Promise<void>;
|
|
34
35
|
protected getMetadataKey(runtime?: SubstrateRuntimeVersion): MetadataKey;
|
|
35
36
|
get currentMetadataKey(): string;
|
|
36
37
|
protected shouldPreloadMetadata(): Promise<boolean>;
|
|
@@ -42,6 +43,17 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
|
|
|
42
43
|
*/
|
|
43
44
|
protected findMetadataInCache(specVersion: number): [Metadata, PortableRegistry] | undefined;
|
|
44
45
|
protected fetchMetadata(hash?: BlockHash, runtime?: SubstrateRuntimeVersion): Promise<Metadata>;
|
|
46
|
+
/**
|
|
47
|
+
* Set up staling detection with timeout reset logic.
|
|
48
|
+
* Returns a callback function that should be called on each activity (block/event).
|
|
49
|
+
*
|
|
50
|
+
* @returns A function to call on each activity to reset the timeout
|
|
51
|
+
*/
|
|
52
|
+
protected getStalingDetectionFn(): (() => void) | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Clean up staling detection timer
|
|
55
|
+
*/
|
|
56
|
+
protected cleanupStalingDetection(): void;
|
|
45
57
|
protected cleanUp(): void;
|
|
46
58
|
/**
|
|
47
59
|
* @description Clear local cache and API at-block cache
|
|
@@ -69,19 +81,20 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
|
|
|
69
81
|
disconnect(): Promise<void>;
|
|
70
82
|
get options(): ApiOptions;
|
|
71
83
|
get metadata(): Metadata;
|
|
72
|
-
get registry(): PortableRegistry<ChainApi[
|
|
84
|
+
get registry(): PortableRegistry<ChainApi['types']>;
|
|
73
85
|
get genesisHash(): Hash;
|
|
74
86
|
get runtimeVersion(): SubstrateRuntimeVersion;
|
|
75
87
|
getRuntimeVersion(): Promise<SubstrateRuntimeVersion>;
|
|
76
|
-
get
|
|
77
|
-
get
|
|
78
|
-
get
|
|
79
|
-
get
|
|
80
|
-
get
|
|
81
|
-
get
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
get block(): BlockExplorer;
|
|
89
|
+
get consts(): ChainApi['consts'];
|
|
90
|
+
get errors(): ChainApi['errors'];
|
|
91
|
+
get events(): ChainApi['events'];
|
|
92
|
+
get query(): ChainApi['query'];
|
|
93
|
+
get view(): ChainApi['view'];
|
|
94
|
+
get call(): ChainApi['call'];
|
|
95
|
+
protected callAt(hash?: BlockHash): ChainApi['call'];
|
|
96
|
+
get tx(): ChainApi['tx'];
|
|
97
|
+
at<ChainApiAt extends GenericSubstrateApi = ChainApi>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
|
|
85
98
|
setSigner(signer?: InjectedSigner): void;
|
|
86
99
|
protected internalQueryMulti(queries: {
|
|
87
100
|
fn: GenericStorageQuery;
|
|
@@ -119,4 +132,7 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
|
|
|
119
132
|
[K in keyof Fns]: QueryFnResult<Fns[K]>;
|
|
120
133
|
}>): Promise<Unsub>;
|
|
121
134
|
protected getStorageQuery(): BaseStorageQuery;
|
|
135
|
+
sendTx(tx: HexString | Extrinsic, callback?: Callback): TxUnsub;
|
|
136
|
+
get chainSpec(): IChainSpec;
|
|
137
|
+
on<Event extends Events = Events>(event: Event, handler: EventHandlerFn<Event>): () => void;
|
|
122
138
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { $Metadata, PortableRegistry } from '@dedot/codecs';
|
|
1
|
+
import { $Metadata, PortableRegistry, } from '@dedot/codecs';
|
|
2
|
+
import { WsProvider } from '@dedot/providers';
|
|
2
3
|
import { LocalStorage } from '@dedot/storage';
|
|
3
|
-
import { calcRuntimeApiHash, deferred, ensurePresence as _ensurePresence,
|
|
4
|
+
import { calcRuntimeApiHash, deferred, ensurePresence as _ensurePresence, LRUCache, noop, u8aToHex, } from '@dedot/utils';
|
|
4
5
|
import { ConstantExecutor, ErrorExecutor, EventExecutor } from '../executor/index.js';
|
|
5
6
|
import { isJsonRpcProvider, JsonRpcClient } from '../json-rpc/index.js';
|
|
6
7
|
import { newProxyChain } from '../proxychain.js';
|
|
@@ -27,6 +28,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
27
28
|
_localCache;
|
|
28
29
|
_runtimeUpgrading;
|
|
29
30
|
_apiAtCache;
|
|
31
|
+
#stalingWatchdogTimer;
|
|
30
32
|
constructor(rpcVersion, options) {
|
|
31
33
|
super(options);
|
|
32
34
|
this.rpcVersion = rpcVersion;
|
|
@@ -94,7 +96,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
94
96
|
if (!metadata) {
|
|
95
97
|
throw new Error('Cannot load metadata');
|
|
96
98
|
}
|
|
97
|
-
this.setMetadata(metadata);
|
|
99
|
+
await this.setMetadata(metadata);
|
|
98
100
|
}
|
|
99
101
|
/**
|
|
100
102
|
* Safely set metadata to cache with fallback cleanup if storage limit is exceeded
|
|
@@ -126,7 +128,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
|
-
setMetadata(metadata) {
|
|
131
|
+
async setMetadata(metadata) {
|
|
130
132
|
this._metadata = metadata;
|
|
131
133
|
this._registry = new PortableRegistry(metadata.latest, this.options.hasher);
|
|
132
134
|
}
|
|
@@ -199,6 +201,34 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
199
201
|
return await this.rpc.state_getMetadata();
|
|
200
202
|
}
|
|
201
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Set up staling detection with timeout reset logic.
|
|
206
|
+
* Returns a callback function that should be called on each activity (block/event).
|
|
207
|
+
*
|
|
208
|
+
* @returns A function to call on each activity to reset the timeout
|
|
209
|
+
*/
|
|
210
|
+
getStalingDetectionFn() {
|
|
211
|
+
const timeout = this._options.stalingDetectionTimeout ?? 30_000; // default to 30 seconds
|
|
212
|
+
if (timeout === 0 || !(this.provider instanceof WsProvider)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
return () => {
|
|
216
|
+
this.cleanupStalingDetection();
|
|
217
|
+
this.#stalingWatchdogTimer = setTimeout(() => {
|
|
218
|
+
console.warn(`No new blocks received for over ${timeout}ms, triggering reconnection with endpoint switch...`);
|
|
219
|
+
this.provider.disconnect(true).catch(noop);
|
|
220
|
+
}, timeout);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Clean up staling detection timer
|
|
225
|
+
*/
|
|
226
|
+
cleanupStalingDetection() {
|
|
227
|
+
if (this.#stalingWatchdogTimer) {
|
|
228
|
+
clearTimeout(this.#stalingWatchdogTimer);
|
|
229
|
+
this.#stalingWatchdogTimer = undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
202
232
|
cleanUp() {
|
|
203
233
|
this._registry = undefined;
|
|
204
234
|
this._metadata = undefined;
|
|
@@ -206,6 +236,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
206
236
|
this._runtimeVersion = undefined;
|
|
207
237
|
this._localCache = undefined;
|
|
208
238
|
this._apiAtCache.clear();
|
|
239
|
+
this.cleanupStalingDetection();
|
|
209
240
|
}
|
|
210
241
|
/**
|
|
211
242
|
* @description Clear local cache and API at-block cache
|
|
@@ -306,6 +337,9 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
306
337
|
await this.ensureRuntimeUpgraded();
|
|
307
338
|
return this.runtimeVersion;
|
|
308
339
|
}
|
|
340
|
+
get block() {
|
|
341
|
+
throw new Error('Unimplemented!');
|
|
342
|
+
}
|
|
309
343
|
get consts() {
|
|
310
344
|
return newProxyChain({ executor: new ConstantExecutor(this) });
|
|
311
345
|
}
|
|
@@ -369,4 +403,13 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
369
403
|
getStorageQuery() {
|
|
370
404
|
throw new Error('Unimplemented!');
|
|
371
405
|
}
|
|
406
|
+
sendTx(tx, callback) {
|
|
407
|
+
throw new Error('Unimplemented!');
|
|
408
|
+
}
|
|
409
|
+
get chainSpec() {
|
|
410
|
+
throw new Error('Unimplemented!');
|
|
411
|
+
}
|
|
412
|
+
on(event, handler) {
|
|
413
|
+
return super.on(event, handler);
|
|
414
|
+
}
|
|
372
415
|
}
|