UNPKG

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