@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.
@@ -63,23 +63,30 @@ class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic_js_1.BaseSubmittab
63
63
  };
64
64
  let txFound;
65
65
  let isSearching = false;
66
- let searchQueue = new utils_1.AsyncQueue();
67
- // TODO 1. move the searching logic into a different utility
68
- // 2. properly cancel the work by actually cancel the on-going operations
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
- return searchQueue.enqueue(async () => {
77
- const found = await checkTxIsOnChain(block.hash);
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 inBlock = await checkTxIsOnChain(block.hash);
146
- if (inBlock) {
147
- const { index: txIndex, events, blockHash, blockNumber } = inBlock;
148
- callback(new SubmittableResult_js_1.SubmittableResult({
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
- txUnsub().catch(utils_1.noop);
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 = exports.MIN_FINALIZED_QUEUE_SIZE = void 0;
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
- exports.MIN_FINALIZED_QUEUE_SIZE = 10; // finalized queue size
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 > exports.MIN_FINALIZED_QUEUE_SIZE) {
232
+ if (this.#finalizedQueue.length > this.#minQueueSize) {
228
233
  const finalizedQueue = this.#finalizedQueue.slice();
229
- const numOfItemsToUnpin = finalizedQueue.length - exports.MIN_FINALIZED_QUEUE_SIZE;
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
- const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(bodyOperation, atHash));
557
- this.#cache.set(cacheKey, resp);
558
- return resp;
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
- const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(callOperation, atHash));
597
- this.#cache.set(cacheKey, resp);
598
- return resp;
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
- return this.#operationQueue.add(() => this.#performOperationWithRetry(operation, at));
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
- let searchQueue = new AsyncQueue();
64
- // TODO 1. move the searching logic into a different utility
65
- // 2. properly cancel the work by actually cancel the on-going operations
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
- return searchQueue.enqueue(async () => {
74
- const found = await checkTxIsOnChain(block.hash);
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 inBlock = await checkTxIsOnChain(block.hash);
143
- if (inBlock) {
144
- const { index: txIndex, events, blockHash, blockNumber } = inBlock;
145
- callback(new SubmittableResult({
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
- txUnsub().catch(noop);
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
- export const MIN_FINALIZED_QUEUE_SIZE = 10; // finalized queue size
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 > MIN_FINALIZED_QUEUE_SIZE) {
229
+ if (this.#finalizedQueue.length > this.#minQueueSize) {
225
230
  const finalizedQueue = this.#finalizedQueue.slice();
226
- const numOfItemsToUnpin = finalizedQueue.length - MIN_FINALIZED_QUEUE_SIZE;
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
- const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(bodyOperation, atHash));
554
- this.#cache.set(cacheKey, resp);
555
- return resp;
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
- const resp = await this.#operationQueue.add(() => this.#performOperationWithRetry(callOperation, atHash));
594
- this.#cache.set(cacheKey, resp);
595
- return resp;
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
- return this.#operationQueue.add(() => this.#performOperationWithRetry(operation, at));
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.5",
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.5",
17
- "@dedot/providers": "0.18.5",
18
- "@dedot/runtime-specs": "0.18.5",
19
- "@dedot/shape": "0.18.5",
20
- "@dedot/storage": "0.18.5",
21
- "@dedot/types": "0.18.5",
22
- "@dedot/utils": "0.18.5"
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": "54b0dcfddfc775009eacd1858441b1b2b12b9cae",
51
+ "gitHead": "07f47f75512087407e03f301fc18c2059b3f613a",
52
52
  "module": "./index.js",
53
53
  "types": "./index.d.ts"
54
54
  }