UNPKG

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