@dedot/api 0.0.1-alpha.32

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.
Files changed (202) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1 -0
  3. package/chaintypes/index.d.ts +1 -0
  4. package/chaintypes/index.js +1 -0
  5. package/chaintypes/substrate/consts.d.ts +2132 -0
  6. package/chaintypes/substrate/consts.js +2 -0
  7. package/chaintypes/substrate/errors.d.ts +3490 -0
  8. package/chaintypes/substrate/errors.js +2 -0
  9. package/chaintypes/substrate/events.d.ts +5187 -0
  10. package/chaintypes/substrate/events.js +2 -0
  11. package/chaintypes/substrate/index.d.ts +22 -0
  12. package/chaintypes/substrate/index.js +2 -0
  13. package/chaintypes/substrate/json-rpc.d.ts +3 -0
  14. package/chaintypes/substrate/json-rpc.js +2 -0
  15. package/chaintypes/substrate/query.d.ts +3829 -0
  16. package/chaintypes/substrate/query.js +2 -0
  17. package/chaintypes/substrate/runtime.d.ts +850 -0
  18. package/chaintypes/substrate/runtime.js +2 -0
  19. package/chaintypes/substrate/tx.d.ts +14269 -0
  20. package/chaintypes/substrate/tx.js +2 -0
  21. package/chaintypes/substrate/types.d.ts +30457 -0
  22. package/chaintypes/substrate/types.js +2 -0
  23. package/cjs/chaintypes/index.js +17 -0
  24. package/cjs/chaintypes/substrate/consts.js +3 -0
  25. package/cjs/chaintypes/substrate/errors.js +3 -0
  26. package/cjs/chaintypes/substrate/events.js +3 -0
  27. package/cjs/chaintypes/substrate/index.js +18 -0
  28. package/cjs/chaintypes/substrate/json-rpc.js +3 -0
  29. package/cjs/chaintypes/substrate/query.js +3 -0
  30. package/cjs/chaintypes/substrate/runtime.js +3 -0
  31. package/cjs/chaintypes/substrate/tx.js +3 -0
  32. package/cjs/chaintypes/substrate/types.js +3 -0
  33. package/cjs/client/BaseSubstrateClient.js +257 -0
  34. package/cjs/client/Dedot.js +250 -0
  35. package/cjs/client/DedotClient.js +186 -0
  36. package/cjs/client/index.js +18 -0
  37. package/cjs/executor/ConstantExecutor.js +20 -0
  38. package/cjs/executor/ErrorExecutor.js +46 -0
  39. package/cjs/executor/EventExecutor.js +52 -0
  40. package/cjs/executor/Executor.js +42 -0
  41. package/cjs/executor/RuntimeApiExecutor.js +121 -0
  42. package/cjs/executor/StorageQueryExecutor.js +115 -0
  43. package/cjs/executor/TxExecutor.js +55 -0
  44. package/cjs/executor/index.js +26 -0
  45. package/cjs/executor/v2/RuntimeApiExecutorV2.js +21 -0
  46. package/cjs/executor/v2/StorageQueryExecutorV2.js +65 -0
  47. package/cjs/executor/v2/TxExecutorV2.js +20 -0
  48. package/cjs/extrinsic/extensions/ExtraSignedExtension.js +87 -0
  49. package/cjs/extrinsic/extensions/SignedExtension.js +41 -0
  50. package/cjs/extrinsic/extensions/index.js +19 -0
  51. package/cjs/extrinsic/extensions/known/ChargeAssetTxPayment.js +43 -0
  52. package/cjs/extrinsic/extensions/known/ChargeTransactionPayment.js +16 -0
  53. package/cjs/extrinsic/extensions/known/CheckGenesis.js +19 -0
  54. package/cjs/extrinsic/extensions/known/CheckMortality.js +90 -0
  55. package/cjs/extrinsic/extensions/known/CheckNonZeroSender.js +10 -0
  56. package/cjs/extrinsic/extensions/known/CheckNonce.js +33 -0
  57. package/cjs/extrinsic/extensions/known/CheckSpecVersion.js +19 -0
  58. package/cjs/extrinsic/extensions/known/CheckTxVersion.js +19 -0
  59. package/cjs/extrinsic/extensions/known/CheckWeight.js +10 -0
  60. package/cjs/extrinsic/extensions/known/PrevalidateAttests.js +11 -0
  61. package/cjs/extrinsic/extensions/known/index.js +25 -0
  62. package/cjs/extrinsic/index.js +18 -0
  63. package/cjs/extrinsic/submittable/BaseSubmittableExtrinsic.js +70 -0
  64. package/cjs/extrinsic/submittable/SubmittableExtrinsic.js +47 -0
  65. package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +204 -0
  66. package/cjs/extrinsic/submittable/SubmittableResult.js +33 -0
  67. package/cjs/extrinsic/submittable/errors.js +16 -0
  68. package/cjs/extrinsic/submittable/fakeSigner.js +13 -0
  69. package/cjs/extrinsic/submittable/index.js +20 -0
  70. package/cjs/extrinsic/submittable/utils.js +20 -0
  71. package/cjs/index.js +21 -0
  72. package/cjs/json-rpc/JsonRpcClient.js +165 -0
  73. package/cjs/json-rpc/group/ChainHead/BlockUsage.js +24 -0
  74. package/cjs/json-rpc/group/ChainHead/ChainHead.js +603 -0
  75. package/cjs/json-rpc/group/ChainHead/error.js +59 -0
  76. package/cjs/json-rpc/group/ChainHead/index.js +18 -0
  77. package/cjs/json-rpc/group/ChainSpec.js +19 -0
  78. package/cjs/json-rpc/group/JsonRpcGroup.js +89 -0
  79. package/cjs/json-rpc/group/Transaction.js +25 -0
  80. package/cjs/json-rpc/group/TransactionWatch.js +17 -0
  81. package/cjs/json-rpc/group/index.js +21 -0
  82. package/cjs/json-rpc/index.js +20 -0
  83. package/cjs/json-rpc/scaledResponses.js +15 -0
  84. package/cjs/json-rpc/subscriptionsInfo.js +20 -0
  85. package/cjs/package.json +1 -0
  86. package/cjs/proxychain.js +27 -0
  87. package/cjs/storage/QueryableStorage.js +161 -0
  88. package/cjs/storage/index.js +17 -0
  89. package/cjs/types.js +2 -0
  90. package/client/BaseSubstrateClient.d.ts +65 -0
  91. package/client/BaseSubstrateClient.js +252 -0
  92. package/client/Dedot.d.ts +131 -0
  93. package/client/Dedot.js +246 -0
  94. package/client/DedotClient.d.ts +61 -0
  95. package/client/DedotClient.js +182 -0
  96. package/client/index.d.ts +2 -0
  97. package/client/index.js +2 -0
  98. package/executor/ConstantExecutor.d.ts +10 -0
  99. package/executor/ConstantExecutor.js +16 -0
  100. package/executor/ErrorExecutor.d.ts +10 -0
  101. package/executor/ErrorExecutor.js +42 -0
  102. package/executor/EventExecutor.d.ts +10 -0
  103. package/executor/EventExecutor.js +48 -0
  104. package/executor/Executor.d.ts +149 -0
  105. package/executor/Executor.js +38 -0
  106. package/executor/RuntimeApiExecutor.d.ts +27 -0
  107. package/executor/RuntimeApiExecutor.js +117 -0
  108. package/executor/StorageQueryExecutor.d.ts +14 -0
  109. package/executor/StorageQueryExecutor.js +111 -0
  110. package/executor/TxExecutor.d.ts +10 -0
  111. package/executor/TxExecutor.js +51 -0
  112. package/executor/index.d.ts +10 -0
  113. package/executor/index.js +10 -0
  114. package/executor/v2/RuntimeApiExecutorV2.d.ts +14 -0
  115. package/executor/v2/RuntimeApiExecutorV2.js +17 -0
  116. package/executor/v2/StorageQueryExecutorV2.d.ts +17 -0
  117. package/executor/v2/StorageQueryExecutorV2.js +61 -0
  118. package/executor/v2/TxExecutorV2.d.ts +11 -0
  119. package/executor/v2/TxExecutorV2.js +16 -0
  120. package/extrinsic/extensions/ExtraSignedExtension.d.ts +14 -0
  121. package/extrinsic/extensions/ExtraSignedExtension.js +60 -0
  122. package/extrinsic/extensions/SignedExtension.d.ts +40 -0
  123. package/extrinsic/extensions/SignedExtension.js +37 -0
  124. package/extrinsic/extensions/index.d.ts +3 -0
  125. package/extrinsic/extensions/index.js +3 -0
  126. package/extrinsic/extensions/known/ChargeAssetTxPayment.d.ts +14 -0
  127. package/extrinsic/extensions/known/ChargeAssetTxPayment.js +39 -0
  128. package/extrinsic/extensions/known/ChargeTransactionPayment.d.ts +6 -0
  129. package/extrinsic/extensions/known/ChargeTransactionPayment.js +12 -0
  130. package/extrinsic/extensions/known/CheckGenesis.d.ts +10 -0
  131. package/extrinsic/extensions/known/CheckGenesis.js +15 -0
  132. package/extrinsic/extensions/known/CheckMortality.d.ts +15 -0
  133. package/extrinsic/extensions/known/CheckMortality.js +86 -0
  134. package/extrinsic/extensions/known/CheckNonZeroSender.d.ts +6 -0
  135. package/extrinsic/extensions/known/CheckNonZeroSender.js +6 -0
  136. package/extrinsic/extensions/known/CheckNonce.d.ts +10 -0
  137. package/extrinsic/extensions/known/CheckNonce.js +29 -0
  138. package/extrinsic/extensions/known/CheckSpecVersion.d.ts +9 -0
  139. package/extrinsic/extensions/known/CheckSpecVersion.js +15 -0
  140. package/extrinsic/extensions/known/CheckTxVersion.d.ts +9 -0
  141. package/extrinsic/extensions/known/CheckTxVersion.js +15 -0
  142. package/extrinsic/extensions/known/CheckWeight.d.ts +6 -0
  143. package/extrinsic/extensions/known/CheckWeight.js +6 -0
  144. package/extrinsic/extensions/known/PrevalidateAttests.d.ts +7 -0
  145. package/extrinsic/extensions/known/PrevalidateAttests.js +7 -0
  146. package/extrinsic/extensions/known/index.d.ts +3 -0
  147. package/extrinsic/extensions/known/index.js +22 -0
  148. package/extrinsic/index.d.ts +2 -0
  149. package/extrinsic/index.js +2 -0
  150. package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +17 -0
  151. package/extrinsic/submittable/BaseSubmittableExtrinsic.js +66 -0
  152. package/extrinsic/submittable/SubmittableExtrinsic.d.ts +12 -0
  153. package/extrinsic/submittable/SubmittableExtrinsic.js +43 -0
  154. package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +15 -0
  155. package/extrinsic/submittable/SubmittableExtrinsicV2.js +200 -0
  156. package/extrinsic/submittable/SubmittableResult.d.ts +19 -0
  157. package/extrinsic/submittable/SubmittableResult.js +29 -0
  158. package/extrinsic/submittable/errors.d.ts +11 -0
  159. package/extrinsic/submittable/errors.js +12 -0
  160. package/extrinsic/submittable/fakeSigner.d.ts +2 -0
  161. package/extrinsic/submittable/fakeSigner.js +10 -0
  162. package/extrinsic/submittable/index.d.ts +4 -0
  163. package/extrinsic/submittable/index.js +4 -0
  164. package/extrinsic/submittable/utils.d.ts +10 -0
  165. package/extrinsic/submittable/utils.js +15 -0
  166. package/index.d.ts +6 -0
  167. package/index.js +5 -0
  168. package/json-rpc/JsonRpcClient.d.ts +50 -0
  169. package/json-rpc/JsonRpcClient.js +160 -0
  170. package/json-rpc/group/ChainHead/BlockUsage.d.ts +11 -0
  171. package/json-rpc/group/ChainHead/BlockUsage.js +20 -0
  172. package/json-rpc/group/ChainHead/ChainHead.d.ts +70 -0
  173. package/json-rpc/group/ChainHead/ChainHead.js +599 -0
  174. package/json-rpc/group/ChainHead/error.d.ts +47 -0
  175. package/json-rpc/group/ChainHead/error.js +48 -0
  176. package/json-rpc/group/ChainHead/index.d.ts +2 -0
  177. package/json-rpc/group/ChainHead/index.js +2 -0
  178. package/json-rpc/group/ChainSpec.d.ts +10 -0
  179. package/json-rpc/group/ChainSpec.js +15 -0
  180. package/json-rpc/group/JsonRpcGroup.d.ts +74 -0
  181. package/json-rpc/group/JsonRpcGroup.js +85 -0
  182. package/json-rpc/group/Transaction.d.ts +11 -0
  183. package/json-rpc/group/Transaction.js +21 -0
  184. package/json-rpc/group/TransactionWatch.d.ts +10 -0
  185. package/json-rpc/group/TransactionWatch.js +13 -0
  186. package/json-rpc/group/index.d.ts +5 -0
  187. package/json-rpc/group/index.js +5 -0
  188. package/json-rpc/index.d.ts +4 -0
  189. package/json-rpc/index.js +4 -0
  190. package/json-rpc/scaledResponses.d.ts +2 -0
  191. package/json-rpc/scaledResponses.js +12 -0
  192. package/json-rpc/subscriptionsInfo.d.ts +2 -0
  193. package/json-rpc/subscriptionsInfo.js +17 -0
  194. package/package.json +54 -0
  195. package/proxychain.d.ts +14 -0
  196. package/proxychain.js +23 -0
  197. package/storage/QueryableStorage.d.ts +35 -0
  198. package/storage/QueryableStorage.js +157 -0
  199. package/storage/index.d.ts +1 -0
  200. package/storage/index.js +1 -0
  201. package/types.d.ts +120 -0
  202. package/types.js +1 -0
@@ -0,0 +1,599 @@
1
+ import { $Header } from '@dedot/codecs';
2
+ import { assert, AsyncQueue, deferred, ensurePresence, noop, waitFor } from '@dedot/utils';
3
+ import { JsonRpcGroup } from '../JsonRpcGroup.js';
4
+ import { BlockUsage } from './BlockUsage.js';
5
+ import { ChainHeadBlockNotPinnedError, ChainHeadBlockPrunedError, ChainHeadError, ChainHeadLimitReachedError, ChainHeadOperationError, ChainHeadOperationInaccessibleError, ChainHeadStopError, RetryStrategy, } from './error.js';
6
+ export const MIN_FINALIZED_QUEUE_SIZE = 10; // finalized queue size
7
+ export class ChainHead extends JsonRpcGroup {
8
+ #unsub;
9
+ #subscriptionId;
10
+ #handlers;
11
+ #pendingOperations;
12
+ #finalizedQueue;
13
+ #pinnedBlocks;
14
+ #bestHash;
15
+ #finalizedHash; // best finalized hash
16
+ #finalizedRuntime;
17
+ #followResponseQueue;
18
+ #retryQueue;
19
+ #recovering;
20
+ #blockUsage;
21
+ #cache;
22
+ constructor(client, options) {
23
+ super(client, { prefix: 'chainHead', supportedVersions: ['unstable', 'v1'], ...options });
24
+ this.#handlers = {};
25
+ this.#pendingOperations = {};
26
+ this.#pinnedBlocks = {};
27
+ this.#finalizedQueue = [];
28
+ this.#followResponseQueue = new AsyncQueue();
29
+ this.#retryQueue = new AsyncQueue();
30
+ this.#blockUsage = new BlockUsage();
31
+ this.#cache = new Map();
32
+ }
33
+ async runtimeVersion() {
34
+ await this.#ensureFollowed();
35
+ return this.#finalizedRuntime;
36
+ }
37
+ async bestRuntimeVersion() {
38
+ await this.#ensureFollowed();
39
+ return this.#findRuntimeAt(this.#bestHash) || this.runtimeVersion();
40
+ }
41
+ async finalizedHash() {
42
+ await this.#ensureFollowed();
43
+ return this.#finalizedHash;
44
+ }
45
+ async bestHash() {
46
+ await this.#ensureFollowed();
47
+ return this.#bestHash;
48
+ }
49
+ async bestBlock() {
50
+ return this.findBlock(await this.bestHash());
51
+ }
52
+ async finalizedBlock() {
53
+ return this.findBlock(await this.finalizedHash());
54
+ }
55
+ findBlock(hash) {
56
+ return this.#pinnedBlocks[hash];
57
+ }
58
+ isPinned(hash) {
59
+ return !!this.findBlock(hash);
60
+ }
61
+ /**
62
+ * chainHead_unfollow
63
+ */
64
+ async unfollow() {
65
+ await this.#ensureFollowed();
66
+ this.#unsub && (await this.#unsub());
67
+ this.#cleanUp();
68
+ }
69
+ /**
70
+ * chainHead_follow
71
+ */
72
+ async follow() {
73
+ assert(!this.#subscriptionId, 'Already followed chain head. Please unfollow first.');
74
+ return this.#doFollow();
75
+ }
76
+ async #doFollow() {
77
+ const defer = deferred();
78
+ try {
79
+ this.#unsub && this.#unsub().catch(noop); // ensure unfollowed
80
+ this.#unsub = await this.send('follow', true, (event, subscription) => {
81
+ this.#followResponseQueue
82
+ .enqueue(async () => {
83
+ await this.#onFollowEvent(event, subscription);
84
+ if (event.event == 'initialized') {
85
+ defer.resolve();
86
+ }
87
+ })
88
+ .catch(console.error); // print this out for logging purpose
89
+ });
90
+ }
91
+ catch (e) {
92
+ defer.reject(e);
93
+ }
94
+ return defer.promise;
95
+ }
96
+ #onFollowEvent = async (result, subscription) => {
97
+ switch (result.event) {
98
+ case 'initialized': {
99
+ const { finalizedBlockHashes = [], finalizedBlockHash, finalizedBlockRuntime } = result;
100
+ if (finalizedBlockHash)
101
+ finalizedBlockHashes.push(finalizedBlockHash);
102
+ this.#subscriptionId = subscription.subscriptionId;
103
+ this.#finalizedQueue = finalizedBlockHashes;
104
+ this.#finalizedRuntime = this.#extractRuntime(finalizedBlockRuntime);
105
+ assert(this.#finalizedRuntime, 'Invalid finalized runtime');
106
+ this.#bestHash = this.#finalizedHash = finalizedBlockHashes.at(-1);
107
+ this.#pinnedBlocks = finalizedBlockHashes.reduce((o, hash, idx, arr) => {
108
+ o[hash] = { hash, parent: arr[idx - 1], number: idx };
109
+ // assign finalized runtime to the current finalized block
110
+ if (idx === finalizedBlockHashes.length - 1) {
111
+ o[hash]['runtime'] = this.#finalizedRuntime;
112
+ }
113
+ return o;
114
+ }, {});
115
+ const header = $Header.tryDecode(await this.#getHeader(finalizedBlockHashes[0]));
116
+ Object.values(this.#pinnedBlocks).forEach((b, idx) => {
117
+ b.number += header.number;
118
+ if (idx === 0) {
119
+ b.parent = header.parentHash;
120
+ }
121
+ });
122
+ break;
123
+ }
124
+ case 'newBlock': {
125
+ const { blockHash: hash, parentBlockHash: parent, newRuntime } = result;
126
+ const runtime = this.#extractRuntime(newRuntime);
127
+ const parentBlock = this.findBlock(parent);
128
+ assert(parentBlock, `Parent block not found for new block ${hash}`);
129
+ this.#pinnedBlocks[hash] = {
130
+ hash,
131
+ parent,
132
+ // if the runtime is not provided, we'll find it from the parent block
133
+ runtime: runtime || this.#findRuntimeAt(parent),
134
+ number: parentBlock.number + 1,
135
+ };
136
+ this.emit('newBlock', this.#pinnedBlocks[hash]);
137
+ break;
138
+ }
139
+ case 'bestBlockChanged': {
140
+ const { bestBlockHash } = result;
141
+ const currentBestBlock = this.findBlock(this.#bestHash);
142
+ const newBestBlock = this.findBlock(bestBlockHash);
143
+ if (!newBestBlock)
144
+ return;
145
+ if (currentBestBlock.hash === newBestBlock.hash)
146
+ return;
147
+ const bestChainChanged = !this.#onTheSameChain(currentBestBlock, newBestBlock);
148
+ this.#bestHash = bestBlockHash;
149
+ this.emit('bestBlock', newBestBlock, bestChainChanged);
150
+ if (bestChainChanged)
151
+ this.emit('bestChainChanged', newBestBlock);
152
+ break;
153
+ }
154
+ case 'finalized': {
155
+ const { finalizedBlockHashes, prunedBlockHashes } = result;
156
+ this.#finalizedHash = finalizedBlockHashes.at(-1);
157
+ const finalizedRuntime = this.#findRuntimeAt(this.#finalizedHash);
158
+ if (finalizedRuntime) {
159
+ this.#finalizedRuntime = finalizedRuntime;
160
+ }
161
+ // push new finalized hashes into the queue, we'll adjust the size later if needed
162
+ finalizedBlockHashes.forEach((hash) => {
163
+ if (this.#finalizedQueue.includes(hash))
164
+ return;
165
+ this.#finalizedQueue.push(hash);
166
+ });
167
+ this.emit('finalizedBlock', this.findBlock(this.#finalizedHash));
168
+ // TODO should we find all descendants of the pruned blocks and unpin them as well?
169
+ // that's probably a premature optimization
170
+ const finalizedBlockHeights = finalizedBlockHashes.map((hash) => this.findBlock(hash).number);
171
+ const pinnedHashes = Object.keys(this.#pinnedBlocks);
172
+ const hashesToUnpin = new Set([
173
+ ...prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)),
174
+ // Since we have the current finalized blocks,
175
+ // we can mark all the other blocks at the same height as pruned and unpin all together with the reported pruned blocks
176
+ ...Object.values(this.#pinnedBlocks)
177
+ .filter((b) => finalizedBlockHeights.includes(b.number))
178
+ .filter((b) => !finalizedBlockHashes.includes(b.hash))
179
+ .map((b) => b.hash),
180
+ ]);
181
+ // TODO account for operations that haven't received its operationId yet
182
+ Object.values(this.#handlers).forEach(({ defer, hash, operationId }) => {
183
+ if (hashesToUnpin.has(hash)) {
184
+ defer.reject(new ChainHeadBlockPrunedError());
185
+ this.stopOperation(operationId).catch(noop);
186
+ delete this.#handlers[operationId];
187
+ }
188
+ });
189
+ // Unpin the oldest finalized pinned blocks to maintain the queue size
190
+ if (this.#finalizedQueue.length > MIN_FINALIZED_QUEUE_SIZE) {
191
+ const finalizedQueue = this.#finalizedQueue.slice();
192
+ const numOfItemsToUnpin = finalizedQueue.length - MIN_FINALIZED_QUEUE_SIZE;
193
+ const queuedHashesToUnpin = finalizedQueue.splice(0, numOfItemsToUnpin);
194
+ queuedHashesToUnpin.forEach((hash) => {
195
+ // if the block is being used, we'll keep it pinned
196
+ // and recheck it later in the next finalized event
197
+ if (this.#blockUsage.usage(hash) > 0) {
198
+ finalizedQueue.unshift(hash);
199
+ }
200
+ else {
201
+ hashesToUnpin.add(hash);
202
+ }
203
+ });
204
+ this.#finalizedQueue = finalizedQueue;
205
+ }
206
+ hashesToUnpin.forEach((hash) => {
207
+ if (!this.isPinned(hash))
208
+ return;
209
+ delete this.#pinnedBlocks[hash];
210
+ // clear cache
211
+ Array.from(this.#cache.keys())
212
+ .filter((key) => key.startsWith(`${hash}::`))
213
+ .forEach((key) => this.#cache.delete(key));
214
+ });
215
+ this.unpin([...hashesToUnpin]).catch(noop);
216
+ break;
217
+ }
218
+ case 'stop': {
219
+ // 1. First thing, set up the #recovering promise
220
+ // So any requests/operations coming to the chainHead should be put on waiting
221
+ // for the #recovering promise to resolve
222
+ this.#recovering = deferred();
223
+ // 2. Attempt to re-follow the chainHead
224
+ this.#doFollow()
225
+ .then(() => {
226
+ // 3. Resolve the recovering promise
227
+ // This means to continue all pending requests while the chainHead started recovering mode at step 1.
228
+ this.#recovering.resolve();
229
+ // 4. Recover stale operations
230
+ // 4.1. Operations that's going on & waiting to receiving its operationId
231
+ // will eventually get an `limitedReached` error for using a stale followSubscriptionId
232
+ // these operation will automatically be recovered via the #retryQueue
233
+ // after the chainHead is re-followed & the #recovering promise is resolved
234
+ // 4.2. Operations that's already received an operationId, is waiting for its response
235
+ // will not receive any data, we'll throw a ChainHeadStopError to trigger retrying via the #retryQueue
236
+ Object.values(this.#handlers).forEach(({ defer, operationId }) => {
237
+ defer.reject(new ChainHeadStopError('ChainHead subscription stopped!'));
238
+ delete this.#handlers[operationId];
239
+ });
240
+ })
241
+ .catch((e) => {
242
+ console.error(e);
243
+ // TODO we should retry a few attempts
244
+ this.#recovering.reject(new ChainHeadError('Cannot recover from stop event!'));
245
+ Object.values(this.#handlers).forEach(({ defer, operationId }) => {
246
+ defer.reject(new ChainHeadError('Cannot recover from stop event!'));
247
+ delete this.#handlers[operationId];
248
+ });
249
+ })
250
+ .finally(() => {
251
+ // cleaning up
252
+ waitFor().then(() => {
253
+ this.#recovering = undefined;
254
+ });
255
+ });
256
+ break;
257
+ }
258
+ case 'operationBodyDone': {
259
+ this.#handleOperationResponse(result, ({ defer }) => {
260
+ defer.resolve(result.value);
261
+ });
262
+ break;
263
+ }
264
+ case 'operationCallDone': {
265
+ this.#handleOperationResponse(result, ({ defer }) => {
266
+ defer.resolve(result.output);
267
+ });
268
+ break;
269
+ }
270
+ case 'operationStorageItems': {
271
+ this.#handleOperationResponse(result, (handler) => {
272
+ if (!handler.storageResults)
273
+ handler.storageResults = [];
274
+ handler.storageResults.push(...result.items);
275
+ }, false);
276
+ break;
277
+ }
278
+ case 'operationStorageDone': {
279
+ this.#handleOperationResponse(result, ({ defer, storageResults }) => {
280
+ defer.resolve(storageResults || []);
281
+ });
282
+ break;
283
+ }
284
+ case 'operationError': {
285
+ this.#handleOperationResponse(result, ({ defer }) => {
286
+ defer.reject(new ChainHeadOperationError(result.error));
287
+ });
288
+ break;
289
+ }
290
+ case 'operationInaccessible': {
291
+ this.#handleOperationResponse(result, ({ defer }) => {
292
+ defer.reject(new ChainHeadOperationInaccessibleError('Operation Inaccessible'));
293
+ });
294
+ break;
295
+ }
296
+ case 'operationWaitingForContinue': {
297
+ this.continue(result.operationId).catch(noop);
298
+ break;
299
+ }
300
+ }
301
+ };
302
+ #findRuntimeAt(at) {
303
+ const block = this.findBlock(at);
304
+ if (!block)
305
+ return undefined;
306
+ const runtime = block.runtime;
307
+ if (runtime)
308
+ return runtime;
309
+ if (this.#finalizedQueue.includes(at))
310
+ return;
311
+ return this.#findRuntimeAt(block.parent);
312
+ }
313
+ #onTheSameChain(b1, b2) {
314
+ if (!b1 || !b2)
315
+ return false;
316
+ if (b1.number === b2.number) {
317
+ return b1.hash === b2.hash;
318
+ }
319
+ else if (b1.number < b2.number) {
320
+ return this.#onTheSameChain(b1, this.findBlock(b2.parent));
321
+ }
322
+ else {
323
+ return this.#onTheSameChain(this.findBlock(b1.parent), b2);
324
+ }
325
+ }
326
+ #ensurePinnedHash(hash) {
327
+ if (hash) {
328
+ if (this.isPinned(hash)) {
329
+ return hash;
330
+ }
331
+ else {
332
+ throw new ChainHeadBlockNotPinnedError(`Block hash ${hash} is not pinned`);
333
+ }
334
+ }
335
+ return ensurePresence(this.#bestHash || this.#finalizedHash);
336
+ }
337
+ #getOperationHandler(result) {
338
+ const handler = this.#handlers[result.operationId];
339
+ if (handler)
340
+ return handler;
341
+ // Register pending operations
342
+ if (!this.#pendingOperations[result.operationId]) {
343
+ this.#pendingOperations[result.operationId] = [];
344
+ }
345
+ this.#pendingOperations[result.operationId].push(result);
346
+ }
347
+ #handleOperationResponse(result, handle, cleanUp = true) {
348
+ const handler = this.#getOperationHandler(result);
349
+ if (!handler)
350
+ return;
351
+ handle(handler);
352
+ if (cleanUp) {
353
+ this.#cleanUpOperation(result.operationId);
354
+ }
355
+ }
356
+ #extractRuntime(runtimeEvent) {
357
+ if (!runtimeEvent)
358
+ return;
359
+ if (runtimeEvent.type == 'valid') {
360
+ return runtimeEvent.spec;
361
+ }
362
+ // If the runtime is invalid,
363
+ // we safely return an undefined runtime for now
364
+ console.error(runtimeEvent.error);
365
+ }
366
+ #cleanUp() {
367
+ this.off('newBlock');
368
+ this.off('bestBlock');
369
+ this.off('finalizedBlock');
370
+ this.#subscriptionId = undefined;
371
+ this.#unsub = undefined;
372
+ this.#handlers = {};
373
+ this.#pendingOperations = {};
374
+ this.#pinnedBlocks = {};
375
+ this.#bestHash = undefined;
376
+ this.#finalizedHash = undefined;
377
+ this.#finalizedRuntime = undefined;
378
+ this.#followResponseQueue.clear();
379
+ this.#retryQueue.clear();
380
+ this.#blockUsage.clear();
381
+ this.#cache.clear();
382
+ }
383
+ async #ensureFollowed() {
384
+ if (this.#recovering) {
385
+ await this.#recovering.promise;
386
+ }
387
+ assert(this.#subscriptionId, 'Please call the .follow() method before invoking any other methods in this group.');
388
+ }
389
+ #cleanUpOperation(operationId) {
390
+ delete this.#handlers[operationId];
391
+ this.stopOperation(operationId).catch(noop);
392
+ }
393
+ async #performOperationWithRetry(operation, hash) {
394
+ try {
395
+ this.#blockUsage.use(hash);
396
+ return await operation();
397
+ }
398
+ catch (e) {
399
+ if (e instanceof ChainHeadError && e.retryStrategy) {
400
+ return this.#retryOperation(e.retryStrategy, operation);
401
+ }
402
+ throw e;
403
+ }
404
+ finally {
405
+ this.#blockUsage.release(hash);
406
+ }
407
+ }
408
+ async #retryOperation(strategy, retry) {
409
+ try {
410
+ return await new Promise((resolve, reject) => {
411
+ setTimeout(() => {
412
+ if (strategy === RetryStrategy.NOW) {
413
+ retry().then(resolve).catch(reject);
414
+ }
415
+ else if (strategy === RetryStrategy.QUEUED) {
416
+ this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
417
+ }
418
+ else {
419
+ throw new Error('Invalid retry strategy');
420
+ }
421
+ }); // retry again in the next tick
422
+ });
423
+ }
424
+ catch (e) {
425
+ // retry again until success, TODO we might need to limit the number of retries
426
+ if (e instanceof ChainHeadError && e.retryStrategy) {
427
+ return this.#retryOperation(e.retryStrategy, retry);
428
+ }
429
+ throw e;
430
+ }
431
+ }
432
+ #awaitOperation(resp, hash) {
433
+ if (resp.result === 'limitReached') {
434
+ throw new ChainHeadLimitReachedError('Limit reached');
435
+ }
436
+ const defer = deferred();
437
+ this.#handlers[resp.operationId] = {
438
+ operationId: resp.operationId,
439
+ defer,
440
+ hash,
441
+ };
442
+ // Resolve pending operations
443
+ if (this.#pendingOperations[resp.operationId]) {
444
+ this.#pendingOperations[resp.operationId].forEach((one) => {
445
+ this.#onFollowEvent(one);
446
+ });
447
+ delete this.#pendingOperations[resp.operationId];
448
+ }
449
+ return defer.promise;
450
+ }
451
+ /**
452
+ * chainHead_body
453
+ */
454
+ async body(at) {
455
+ await this.#ensureFollowed();
456
+ const shouldRetryOnPrunedBlock = !at;
457
+ try {
458
+ const atHash = this.#ensurePinnedHash(at);
459
+ const cacheKey = `${atHash}::body`;
460
+ if (this.#cache.has(cacheKey)) {
461
+ return this.#cache.get(cacheKey);
462
+ }
463
+ const operation = async () => {
464
+ await this.#ensureFollowed();
465
+ const hash = this.#ensurePinnedHash(atHash);
466
+ const resp = await this.send('body', this.#subscriptionId, hash);
467
+ return this.#awaitOperation(resp, hash);
468
+ };
469
+ const resp = await this.#performOperationWithRetry(operation, atHash);
470
+ this.#cache.set(cacheKey, resp);
471
+ return resp;
472
+ }
473
+ catch (e) {
474
+ if (e instanceof ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
475
+ return this.body();
476
+ }
477
+ throw e;
478
+ }
479
+ }
480
+ /**
481
+ * chainHead_call
482
+ */
483
+ async call(func, params = '0x', at) {
484
+ await this.#ensureFollowed();
485
+ const shouldRetryOnPrunedBlock = !at;
486
+ try {
487
+ const atHash = this.#ensurePinnedHash(at);
488
+ const cacheKey = `${atHash}::call::${func}::${params}`;
489
+ if (this.#cache.has(cacheKey)) {
490
+ return this.#cache.get(cacheKey);
491
+ }
492
+ const operation = async () => {
493
+ await this.#ensureFollowed();
494
+ const hash = this.#ensurePinnedHash(atHash);
495
+ const resp = await this.send('call', this.#subscriptionId, hash, func, params);
496
+ return this.#awaitOperation(resp, hash);
497
+ };
498
+ const resp = await this.#performOperationWithRetry(operation, atHash);
499
+ this.#cache.set(cacheKey, resp);
500
+ return resp;
501
+ }
502
+ catch (e) {
503
+ if (e instanceof ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
504
+ return this.call(func, params);
505
+ }
506
+ throw e;
507
+ }
508
+ }
509
+ /**
510
+ * chainHead_header
511
+ */
512
+ async header(at) {
513
+ await this.#ensureFollowed();
514
+ const hash = this.#ensurePinnedHash(at);
515
+ const cacheKey = `${hash}::header`;
516
+ if (this.#cache.has(cacheKey)) {
517
+ return this.#cache.get(cacheKey);
518
+ }
519
+ const resp = await this.#getHeader(hash);
520
+ this.#cache.set(cacheKey, resp);
521
+ return resp;
522
+ }
523
+ async #getHeader(at) {
524
+ return await this.send('header', this.#subscriptionId, at);
525
+ }
526
+ /**
527
+ * chainHead_storage
528
+ */
529
+ async storage(items, childTrie, at) {
530
+ await this.#ensureFollowed();
531
+ const shouldRetryOnPrunedBlock = !at;
532
+ const hash = this.#ensurePinnedHash(at);
533
+ try {
534
+ // JSON.stringify(items) might get big, we probably should do a twox hashing in such case
535
+ const cacheKey = `${hash}::storage::${JSON.stringify(items)}::${childTrie ?? null}`;
536
+ if (this.#cache.has(cacheKey)) {
537
+ return this.#cache.get(cacheKey);
538
+ }
539
+ this.#blockUsage.use(hash);
540
+ const results = [];
541
+ let queryItems = items;
542
+ while (queryItems.length > 0) {
543
+ const [newBatch, newDiscardedItems] = await this.#getStorage(queryItems, childTrie ?? null, hash);
544
+ results.push(...newBatch);
545
+ queryItems = newDiscardedItems;
546
+ }
547
+ this.#cache.set(cacheKey, results);
548
+ return results;
549
+ }
550
+ catch (e) {
551
+ if (e instanceof ChainHeadBlockPrunedError && shouldRetryOnPrunedBlock) {
552
+ return this.storage(items, childTrie);
553
+ }
554
+ throw e;
555
+ }
556
+ finally {
557
+ this.#blockUsage.release(hash);
558
+ }
559
+ }
560
+ async #getStorage(items, childTrie, at) {
561
+ const operation = () => this.#getStorageOperation(items, childTrie, this.#ensurePinnedHash(at));
562
+ return this.#performOperationWithRetry(operation, at);
563
+ }
564
+ async #getStorageOperation(items, childTrie, at) {
565
+ await this.#ensureFollowed();
566
+ const resp = await this.send('storage', this.#subscriptionId, at, items, childTrie);
567
+ let discardedItems = [];
568
+ if (resp.result === 'started' && resp.discardedItems && resp.discardedItems > 0) {
569
+ discardedItems = items.slice(items.length - resp.discardedItems);
570
+ }
571
+ return [await this.#awaitOperation(resp, at), discardedItems];
572
+ }
573
+ /**
574
+ * chainHead_stopOperation
575
+ * @protected
576
+ */
577
+ async stopOperation(operationId) {
578
+ await this.#ensureFollowed();
579
+ await this.send('stopOperation', this.#subscriptionId, operationId);
580
+ }
581
+ /**
582
+ * chainHead_continue
583
+ * @protected
584
+ */
585
+ async continue(operationId) {
586
+ await this.#ensureFollowed();
587
+ await this.send('continue', this.#subscriptionId, operationId);
588
+ }
589
+ /**
590
+ * chainHead_unpin
591
+ * @protected
592
+ */
593
+ async unpin(hashes) {
594
+ await this.#ensureFollowed();
595
+ if (Array.isArray(hashes) && hashes.length === 0)
596
+ return;
597
+ await this.send('unpin', this.#subscriptionId, hashes);
598
+ }
599
+ }
@@ -0,0 +1,47 @@
1
+ import { DedotError } from '@dedot/utils';
2
+ export declare enum RetryStrategy {
3
+ NOW = "NOW",// Retry immediately
4
+ QUEUED = "QUEUED"
5
+ }
6
+ export declare class ChainHeadError extends DedotError {
7
+ retryStrategy?: RetryStrategy | undefined;
8
+ }
9
+ /**
10
+ * Operation Inaccessible Error
11
+ *
12
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_follow.html#operationinaccessible
13
+ */
14
+ export declare class ChainHeadOperationInaccessibleError extends ChainHeadError {
15
+ retryStrategy: RetryStrategy;
16
+ }
17
+ /**
18
+ * Stop Error
19
+ *
20
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_follow.html#stop
21
+ */
22
+ export declare class ChainHeadStopError extends ChainHeadError {
23
+ retryStrategy: RetryStrategy;
24
+ }
25
+ /**
26
+ * Operation Error
27
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_follow.html#operationerror
28
+ */
29
+ export declare class ChainHeadOperationError extends ChainHeadError {
30
+ }
31
+ /**
32
+ * Limit Reached Error
33
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_storage.html#limitreached
34
+ */
35
+ export declare class ChainHeadLimitReachedError extends ChainHeadError {
36
+ retryStrategy: RetryStrategy;
37
+ }
38
+ /**
39
+ * Invalid Runtime Error
40
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_storage.html#limitreached
41
+ */
42
+ export declare class ChainHeadInvalidRuntimeError extends ChainHeadError {
43
+ }
44
+ export declare class ChainHeadBlockNotPinnedError extends ChainHeadError {
45
+ }
46
+ export declare class ChainHeadBlockPrunedError extends ChainHeadError {
47
+ }
@@ -0,0 +1,48 @@
1
+ import { DedotError } from '@dedot/utils';
2
+ export var RetryStrategy;
3
+ (function (RetryStrategy) {
4
+ RetryStrategy["NOW"] = "NOW";
5
+ RetryStrategy["QUEUED"] = "QUEUED";
6
+ })(RetryStrategy || (RetryStrategy = {}));
7
+ export class ChainHeadError extends DedotError {
8
+ retryStrategy;
9
+ }
10
+ /**
11
+ * Operation Inaccessible Error
12
+ *
13
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_follow.html#operationinaccessible
14
+ */
15
+ export class ChainHeadOperationInaccessibleError extends ChainHeadError {
16
+ retryStrategy = RetryStrategy.NOW;
17
+ }
18
+ /**
19
+ * Stop Error
20
+ *
21
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_follow.html#stop
22
+ */
23
+ export class ChainHeadStopError extends ChainHeadError {
24
+ retryStrategy = RetryStrategy.QUEUED;
25
+ }
26
+ /**
27
+ * Operation Error
28
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_follow.html#operationerror
29
+ */
30
+ export class ChainHeadOperationError extends ChainHeadError {
31
+ }
32
+ /**
33
+ * Limit Reached Error
34
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_storage.html#limitreached
35
+ */
36
+ export class ChainHeadLimitReachedError extends ChainHeadError {
37
+ retryStrategy = RetryStrategy.QUEUED;
38
+ }
39
+ /**
40
+ * Invalid Runtime Error
41
+ * Ref: https://paritytech.github.io/json-rpc-interface-spec/api/chainHead_v1_storage.html#limitreached
42
+ */
43
+ export class ChainHeadInvalidRuntimeError extends ChainHeadError {
44
+ }
45
+ export class ChainHeadBlockNotPinnedError extends ChainHeadError {
46
+ }
47
+ export class ChainHeadBlockPrunedError extends ChainHeadError {
48
+ }
@@ -0,0 +1,2 @@
1
+ export * from './error.js';
2
+ export * from './ChainHead.js';