UNPKG

17.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Init = void 0;
4const rxjs_1 = require("rxjs");
5const types_1 = require("@polkadot/types");
6const types_known_1 = require("@polkadot/types-known");
7const util_1 = require("@polkadot/util");
8const util_crypto_1 = require("@polkadot/util-crypto");
9const Decorate_js_1 = require("./Decorate.js");
10const KEEPALIVE_INTERVAL = 10000;
11const WITH_VERSION_SHORTCUT = false;
12const l = (0, util_1.logger)('api/init');
13function textToString(t) {
14 return t.toString();
15}
16class Init extends Decorate_js_1.Decorate {
17 __internal__atLast = null;
18 __internal__healthTimer = null;
19 __internal__registries = [];
20 __internal__updateSub = null;
21 __internal__waitingRegistries = {};
22 constructor(options, type, decorateMethod) {
23 super(options, type, decorateMethod);
24 // all injected types added to the registry for overrides
25 this.registry.setKnownTypes(options);
26 // We only register the types (global) if this is not a cloned instance.
27 // Do right up-front, so we get in the user types before we are actually
28 // doing anything on-chain, this ensures we have the overrides in-place
29 if (!options.source) {
30 this.registerTypes(options.types);
31 }
32 else {
33 this.__internal__registries = options.source.__internal__registries;
34 }
35 this._rpc = this._decorateRpc(this._rpcCore, this._decorateMethod);
36 this._rx.rpc = this._decorateRpc(this._rpcCore, this._rxDecorateMethod);
37 if (this.supportMulti) {
38 this._queryMulti = this._decorateMulti(this._decorateMethod);
39 this._rx.queryMulti = this._decorateMulti(this._rxDecorateMethod);
40 }
41 this._rx.signer = options.signer;
42 this._rpcCore.setRegistrySwap((blockHash) => this.getBlockRegistry(blockHash));
43 this._rpcCore.setResolveBlockHash((blockNumber) => (0, rxjs_1.firstValueFrom)(this._rpcCore.chain.getBlockHash(blockNumber)));
44 if (this.hasSubscriptions) {
45 this._rpcCore.provider.on('disconnected', () => this.__internal__onProviderDisconnect());
46 this._rpcCore.provider.on('error', (e) => this.__internal__onProviderError(e));
47 this._rpcCore.provider.on('connected', () => this.__internal__onProviderConnect());
48 }
49 else if (!this._options.noInitWarn) {
50 l.warn('Api will be available in a limited mode since the provider does not support subscriptions');
51 }
52 // If the provider was instantiated earlier, and has already emitted a
53 // 'connected' event, then the `on('connected')` won't fire anymore. To
54 // cater for this case, we call manually `this._onProviderConnect`.
55 if (this._rpcCore.provider.isConnected) {
56 this.__internal__onProviderConnect().catch(util_1.noop);
57 }
58 }
59 /**
60 * @description Decorates a registry based on the runtime version
61 */
62 _initRegistry(registry, chain, version, metadata, chainProps) {
63 registry.clearCache();
64 registry.setChainProperties(chainProps || this.registry.getChainProperties());
65 registry.setKnownTypes(this._options);
66 registry.register((0, types_known_1.getSpecTypes)(registry, chain, version.specName, version.specVersion));
67 registry.setHasher((0, types_known_1.getSpecHasher)(registry, chain, version.specName));
68 // for bundled types, pull through the aliases defined
69 if (registry.knownTypes.typesBundle) {
70 registry.knownTypes.typesAlias = (0, types_known_1.getSpecAlias)(registry, chain, version.specName);
71 }
72 registry.setMetadata(metadata, undefined, (0, util_1.objectSpread)({}, (0, types_known_1.getSpecExtensions)(registry, chain, version.specName), this._options.signedExtensions), this._options.noInitWarn);
73 }
74 /**
75 * @description Returns the default versioned registry
76 */
77 _getDefaultRegistry() {
78 return (0, util_1.assertReturn)(this.__internal__registries.find(({ isDefault }) => isDefault), 'Initialization error, cannot find the default registry');
79 }
80 /**
81 * @description Returns a decorated API instance at a specific point in time
82 */
83 async at(blockHash, knownVersion) {
84 const u8aHash = (0, util_1.u8aToU8a)(blockHash);
85 const u8aHex = (0, util_1.u8aToHex)(u8aHash);
86 const registry = await this.getBlockRegistry(u8aHash, knownVersion);
87 if (!this.__internal__atLast || this.__internal__atLast[0] !== u8aHex) {
88 // always create a new decoration - since we are pointing to a specific hash, this
89 // means that all queries needs to use that hash (not a previous one already existing)
90 this.__internal__atLast = [u8aHex, this._createDecorated(registry, true, null, u8aHash).decoratedApi];
91 }
92 return this.__internal__atLast[1];
93 }
94 async _createBlockRegistry(blockHash, header, version) {
95 const registry = new types_1.TypeRegistry(blockHash);
96 const metadata = new types_1.Metadata(registry, await (0, rxjs_1.firstValueFrom)(this._rpcCore.state.getMetadata.raw(header.parentHash)));
97 const runtimeChain = this._runtimeChain;
98 if (!runtimeChain) {
99 throw new Error('Invalid initializion order, runtimeChain is not available');
100 }
101 this._initRegistry(registry, runtimeChain, version, metadata);
102 // add our new registry
103 const result = { counter: 0, lastBlockHash: blockHash, metadata, registry, runtimeVersion: version };
104 this.__internal__registries.push(result);
105 return result;
106 }
107 _cacheBlockRegistryProgress(key, creator) {
108 // look for waiting resolves
109 let waiting = this.__internal__waitingRegistries[key];
110 if ((0, util_1.isUndefined)(waiting)) {
111 // nothing waiting, construct new
112 waiting = this.__internal__waitingRegistries[key] = new Promise((resolve, reject) => {
113 creator()
114 .then((registry) => {
115 delete this.__internal__waitingRegistries[key];
116 resolve(registry);
117 })
118 .catch((error) => {
119 delete this.__internal__waitingRegistries[key];
120 reject(error);
121 });
122 });
123 }
124 return waiting;
125 }
126 _getBlockRegistryViaVersion(blockHash, version) {
127 if (version) {
128 // check for pre-existing registries. We also check specName, e.g. it
129 // could be changed like in Westmint with upgrade from shell -> westmint
130 const existingViaVersion = this.__internal__registries.find(({ runtimeVersion: { specName, specVersion } }) => specName.eq(version.specName) &&
131 specVersion.eq(version.specVersion));
132 if (existingViaVersion) {
133 existingViaVersion.counter++;
134 existingViaVersion.lastBlockHash = blockHash;
135 return existingViaVersion;
136 }
137 }
138 return null;
139 }
140 async _getBlockRegistryViaHash(blockHash) {
141 // ensure we have everything required
142 if (!this._genesisHash || !this._runtimeVersion) {
143 throw new Error('Cannot retrieve data on an uninitialized chain');
144 }
145 // We have to assume that on the RPC layer the calls used here does not call back into
146 // the registry swap, so getHeader & getRuntimeVersion should not be historic
147 const header = this.registry.createType('HeaderPartial', this._genesisHash.eq(blockHash)
148 ? { number: util_1.BN_ZERO, parentHash: this._genesisHash }
149 : await (0, rxjs_1.firstValueFrom)(this._rpcCore.chain.getHeader.raw(blockHash)));
150 if (header.parentHash.isEmpty) {
151 throw new Error('Unable to retrieve header and parent from supplied hash');
152 }
153 // get the runtime version, either on-chain or via an known upgrade history
154 const [firstVersion, lastVersion] = (0, types_known_1.getUpgradeVersion)(this._genesisHash, header.number);
155 const version = this.registry.createType('RuntimeVersionPartial', WITH_VERSION_SHORTCUT && (firstVersion && (lastVersion ||
156 firstVersion.specVersion.eq(this._runtimeVersion.specVersion)))
157 ? { apis: firstVersion.apis, specName: this._runtimeVersion.specName, specVersion: firstVersion.specVersion }
158 : await (0, rxjs_1.firstValueFrom)(this._rpcCore.state.getRuntimeVersion.raw(header.parentHash)));
159 return (
160 // try to find via version
161 this._getBlockRegistryViaVersion(blockHash, version) ||
162 // return new or in-flight result
163 await this._cacheBlockRegistryProgress(version.toHex(), () => this._createBlockRegistry(blockHash, header, version)));
164 }
165 /**
166 * @description Sets up a registry based on the block hash defined
167 */
168 async getBlockRegistry(blockHash, knownVersion) {
169 return (
170 // try to find via blockHash
171 this.__internal__registries.find(({ lastBlockHash }) => lastBlockHash && (0, util_1.u8aEq)(lastBlockHash, blockHash)) ||
172 // try to find via version
173 this._getBlockRegistryViaVersion(blockHash, knownVersion) ||
174 // return new or in-flight result
175 await this._cacheBlockRegistryProgress((0, util_1.u8aToHex)(blockHash), () => this._getBlockRegistryViaHash(blockHash)));
176 }
177 async _loadMeta() {
178 // on re-connection to the same chain, we don't want to re-do everything from chain again
179 if (this._isReady) {
180 return true;
181 }
182 this._unsubscribeUpdates();
183 // only load from on-chain if we are not a clone (default path), alternatively
184 // just use the values from the source instance provided
185 [this._genesisHash, this._runtimeMetadata] = this._options.source?._isReady
186 ? await this._metaFromSource(this._options.source)
187 : await this._metaFromChain(this._options.metadata);
188 return this._initFromMeta(this._runtimeMetadata);
189 }
190 // eslint-disable-next-line @typescript-eslint/require-await
191 async _metaFromSource(source) {
192 this._extrinsicType = source.extrinsicVersion;
193 this._runtimeChain = source.runtimeChain;
194 this._runtimeVersion = source.runtimeVersion;
195 // manually build a list of all available methods in this RPC, we are
196 // going to filter on it to align the cloned RPC without making a call
197 const sections = Object.keys(source.rpc);
198 const rpcs = [];
199 for (let s = 0, scount = sections.length; s < scount; s++) {
200 const section = sections[s];
201 const methods = Object.keys(source.rpc[section]);
202 for (let m = 0, mcount = methods.length; m < mcount; m++) {
203 rpcs.push(`${section}_${methods[m]}`);
204 }
205 }
206 this._filterRpc(rpcs, (0, types_known_1.getSpecRpc)(this.registry, source.runtimeChain, source.runtimeVersion.specName));
207 return [source.genesisHash, source.runtimeMetadata];
208 }
209 // subscribe to metadata updates, inject the types on changes
210 _subscribeUpdates() {
211 if (this.__internal__updateSub || !this.hasSubscriptions) {
212 return;
213 }
214 this.__internal__updateSub = this._rpcCore.state.subscribeRuntimeVersion().pipe((0, rxjs_1.switchMap)((version) =>
215 // only retrieve the metadata when the on-chain version has been changed
216 this._runtimeVersion?.specVersion.eq(version.specVersion)
217 ? (0, rxjs_1.of)(false)
218 : this._rpcCore.state.getMetadata().pipe((0, rxjs_1.map)((metadata) => {
219 l.log(`Runtime version updated to spec=${version.specVersion.toString()}, tx=${version.transactionVersion.toString()}`);
220 this._runtimeMetadata = metadata;
221 this._runtimeVersion = version;
222 this._rx.runtimeVersion = version;
223 // update the default registry version
224 const thisRegistry = this._getDefaultRegistry();
225 const runtimeChain = this._runtimeChain;
226 if (!runtimeChain) {
227 throw new Error('Invalid initializion order, runtimeChain is not available');
228 }
229 // setup the data as per the current versions
230 thisRegistry.metadata = metadata;
231 thisRegistry.runtimeVersion = version;
232 this._initRegistry(this.registry, runtimeChain, version, metadata);
233 this._injectMetadata(thisRegistry, true);
234 return true;
235 })))).subscribe();
236 }
237 async _metaFromChain(optMetadata) {
238 const [genesisHash, runtimeVersion, chain, chainProps, rpcMethods, chainMetadata] = await Promise.all([
239 (0, rxjs_1.firstValueFrom)(this._rpcCore.chain.getBlockHash(0)),
240 (0, rxjs_1.firstValueFrom)(this._rpcCore.state.getRuntimeVersion()),
241 (0, rxjs_1.firstValueFrom)(this._rpcCore.system.chain()),
242 (0, rxjs_1.firstValueFrom)(this._rpcCore.system.properties()),
243 (0, rxjs_1.firstValueFrom)(this._rpcCore.rpc.methods()),
244 optMetadata
245 ? Promise.resolve(null)
246 : (0, rxjs_1.firstValueFrom)(this._rpcCore.state.getMetadata())
247 ]);
248 // set our chain version & genesisHash as returned
249 this._runtimeChain = chain;
250 this._runtimeVersion = runtimeVersion;
251 this._rx.runtimeVersion = runtimeVersion;
252 // retrieve metadata, either from chain or as pass-in via options
253 const metadataKey = `${genesisHash.toHex() || '0x'}-${runtimeVersion.specVersion.toString()}`;
254 const metadata = chainMetadata || (optMetadata?.[metadataKey]
255 ? new types_1.Metadata(this.registry, optMetadata[metadataKey])
256 : await (0, rxjs_1.firstValueFrom)(this._rpcCore.state.getMetadata()));
257 // initializes the registry & RPC
258 this._initRegistry(this.registry, chain, runtimeVersion, metadata, chainProps);
259 this._filterRpc(rpcMethods.methods.map(textToString), (0, types_known_1.getSpecRpc)(this.registry, chain, runtimeVersion.specName));
260 this._subscribeUpdates();
261 // setup the initial registry, when we have none
262 if (!this.__internal__registries.length) {
263 this.__internal__registries.push({ counter: 0, isDefault: true, metadata, registry: this.registry, runtimeVersion });
264 }
265 // get unique types & validate
266 metadata.getUniqTypes(this._options.throwOnUnknown || false);
267 return [genesisHash, metadata];
268 }
269 _initFromMeta(metadata) {
270 const runtimeVersion = this._runtimeVersion;
271 if (!runtimeVersion) {
272 throw new Error('Invalid initializion order, runtimeVersion is not available');
273 }
274 this._extrinsicType = metadata.asLatest.extrinsic.version.toNumber();
275 this._rx.extrinsicType = this._extrinsicType;
276 this._rx.genesisHash = this._genesisHash;
277 this._rx.runtimeVersion = runtimeVersion;
278 // inject metadata and adjust the types as detected
279 this._injectMetadata(this._getDefaultRegistry(), true);
280 // derive is last, since it uses the decorated rx
281 this._rx.derive = this._decorateDeriveRx(this._rxDecorateMethod);
282 this._derive = this._decorateDerive(this._decorateMethod);
283 return true;
284 }
285 _subscribeHealth() {
286 this._unsubscribeHealth();
287 // Only enable the health keepalive on WS, not needed on HTTP
288 this.__internal__healthTimer = this.hasSubscriptions
289 ? setInterval(() => {
290 (0, rxjs_1.firstValueFrom)(this._rpcCore.system.health.raw()).catch(util_1.noop);
291 }, KEEPALIVE_INTERVAL)
292 : null;
293 }
294 _unsubscribeHealth() {
295 if (this.__internal__healthTimer) {
296 clearInterval(this.__internal__healthTimer);
297 this.__internal__healthTimer = null;
298 }
299 }
300 _unsubscribeUpdates() {
301 if (this.__internal__updateSub) {
302 this.__internal__updateSub.unsubscribe();
303 this.__internal__updateSub = null;
304 }
305 }
306 _unsubscribe() {
307 this._unsubscribeHealth();
308 this._unsubscribeUpdates();
309 }
310 async __internal__onProviderConnect() {
311 this._isConnected.next(true);
312 this.emit('connected');
313 try {
314 const cryptoReady = this._options.initWasm === false
315 ? true
316 : await (0, util_crypto_1.cryptoWaitReady)();
317 const hasMeta = await this._loadMeta();
318 this._subscribeHealth();
319 if (hasMeta && !this._isReady && cryptoReady) {
320 this._isReady = true;
321 this.emit('ready', this);
322 }
323 }
324 catch (_error) {
325 const error = new Error(`FATAL: Unable to initialize the API: ${_error.message}`);
326 l.error(error);
327 this.emit('error', error);
328 }
329 }
330 __internal__onProviderDisconnect() {
331 this._isConnected.next(false);
332 this._unsubscribe();
333 this.emit('disconnected');
334 }
335 __internal__onProviderError(error) {
336 this.emit('error', error);
337 }
338}
339exports.Init = Init;