1
1
2
2
import type { Observable , Subscription } from 'https://esm.sh/rxjs@7.8.1' ;
3
- import type { Text } from 'https://deno.land/x/polkadot/types/mod.ts' ;
3
+ import type { Bytes , Text , u32 , Vec } from 'https://deno.land/x/polkadot/types/mod.ts' ;
4
4
import type { ExtDef } from 'https://deno.land/x/polkadot/types/extrinsic/signedExtensions/types.ts' ;
5
- import type { ChainProperties , Hash , HeaderPartial , RuntimeVersion , RuntimeVersionPartial } from 'https://deno.land/x/polkadot/types/interfaces/index.ts' ;
5
+ import type { BlockHash , ChainProperties , Hash , HeaderPartial , RuntimeVersion , RuntimeVersionApi , RuntimeVersionPartial } from 'https://deno.land/x/polkadot/types/interfaces/index.ts' ;
6
6
import type { Registry } from 'https://deno.land/x/polkadot/types/types/index.ts' ;
7
7
import type { BN } from 'https://deno.land/x/polkadot/util/mod.ts' ;
8
8
import type { HexString } from 'https://deno.land/x/polkadot/util/types.ts' ;
@@ -14,12 +14,13 @@ import { firstValueFrom, map, of, switchMap } from 'https://esm.sh/rxjs@7.8.1';
14
14
import { Metadata , TypeRegistry } from 'https://deno.land/x/polkadot/types/mod.ts' ;
15
15
import { getSpecAlias , getSpecExtensions , getSpecHasher , getSpecRpc , getSpecTypes , getUpgradeVersion } from 'https://deno.land/x/polkadot/types-known/mod.ts' ;
16
16
import { assertReturn , BN_ZERO , isUndefined , logger , noop , objectSpread , u8aEq , u8aToHex , u8aToU8a } from 'https://deno.land/x/polkadot/util/mod.ts' ;
17
- import { cryptoWaitReady } from 'https://deno.land/x/polkadot/util-crypto/mod.ts' ;
17
+ import { blake2AsHex , cryptoWaitReady } from 'https://deno.land/x/polkadot/util-crypto/mod.ts' ;
18
18
19
19
import { Decorate } from './Decorate.ts' ;
20
20
21
21
const KEEPALIVE_INTERVAL = 10000 ;
22
22
const WITH_VERSION_SHORTCUT = false ;
23
+ const SUPPORTED_METADATA_VERSIONS = [ 15 , 14 ] ;
23
24
24
25
const l = logger ( 'api/init' ) ;
25
26
@@ -123,9 +124,7 @@ export abstract class Init<ApiType extends ApiTypes> extends Decorate<ApiType> {
123
124
124
125
private async _createBlockRegistry ( blockHash : Uint8Array , header : HeaderPartial , version : RuntimeVersionPartial ) : Promise < VersionedRegistry < ApiType > > {
125
126
const registry = new TypeRegistry ( blockHash ) ;
126
- const metadata = new Metadata ( registry ,
127
- await firstValueFrom ( this . _rpcCore . state . getMetadata . raw < HexString > ( header . parentHash ) )
128
- ) ;
127
+ const metadata = await this . _retrieveMetadata ( version . apis , header . parentHash , registry ) ;
129
128
const runtimeChain = this . _runtimeChain ;
130
129
131
130
if ( ! runtimeChain ) {
@@ -328,15 +327,12 @@ export abstract class Init<ApiType extends ApiTypes> extends Decorate<ApiType> {
328
327
}
329
328
330
329
private async _metaFromChain ( optMetadata ?: Record < string , HexString > ) : Promise < [ Hash , Metadata ] > {
331
- const [ genesisHash , runtimeVersion , chain , chainProps , rpcMethods , chainMetadata ] = await Promise . all ( [
330
+ const [ genesisHash , runtimeVersion , chain , chainProps , rpcMethods ] = await Promise . all ( [
332
331
firstValueFrom ( this . _rpcCore . chain . getBlockHash ( 0 ) ) ,
333
332
firstValueFrom ( this . _rpcCore . state . getRuntimeVersion ( ) ) ,
334
333
firstValueFrom ( this . _rpcCore . system . chain ( ) ) ,
335
334
firstValueFrom ( this . _rpcCore . system . properties ( ) ) ,
336
- firstValueFrom ( this . _rpcCore . rpc . methods ( ) ) ,
337
- optMetadata
338
- ? Promise . resolve ( null )
339
- : firstValueFrom ( this . _rpcCore . state . getMetadata ( ) )
335
+ firstValueFrom ( this . _rpcCore . rpc . methods ( ) )
340
336
] ) ;
341
337
342
338
// set our chain version & genesisHash as returned
@@ -346,11 +342,9 @@ export abstract class Init<ApiType extends ApiTypes> extends Decorate<ApiType> {
346
342
347
343
// retrieve metadata, either from chain or as pass-in via options
348
344
const metadataKey = `${ genesisHash . toHex ( ) || '0x' } -${ runtimeVersion . specVersion . toString ( ) } ` ;
349
- const metadata = chainMetadata || (
350
- optMetadata ?. [ metadataKey ]
351
- ? new Metadata ( this . registry , optMetadata [ metadataKey ] )
352
- : await firstValueFrom ( this . _rpcCore . state . getMetadata ( ) )
353
- ) ;
345
+ const metadata = optMetadata ?. [ metadataKey ]
346
+ ? new Metadata ( this . registry , optMetadata [ metadataKey ] )
347
+ : await this . _retrieveMetadata ( runtimeVersion . apis ) ;
354
348
355
349
// initializes the registry & RPC
356
350
this . _initRegistry ( this . registry , chain , runtimeVersion , metadata , chainProps ) ;
@@ -390,6 +384,72 @@ export abstract class Init<ApiType extends ApiTypes> extends Decorate<ApiType> {
390
384
return true ;
391
385
}
392
386
387
+ /**
388
+ * @internal
389
+ *
390
+ * Tries to use runtime api calls to retrieve metadata. This ensures the api initializes with the latest metadata.
391
+ * If the runtime call is not there it will use the rpc method.
392
+ */
393
+ private async _retrieveMetadata ( apis : Vec < RuntimeVersionApi > , at ?: BlockHash | string | Uint8Array , registry ?: TypeRegistry ) : Promise < Metadata > {
394
+ let metadataVersion : u32 | null = null ;
395
+ const metadataApi = apis . find ( ( [ a ] ) => a . eq ( blake2AsHex ( 'Metadata' , 64 ) ) ) ;
396
+ const typeRegistry = registry || this . registry ;
397
+
398
+ // This chain does not have support for the metadataApi, or does not have the required version.
399
+ if ( ! metadataApi || metadataApi [ 1 ] . toNumber ( ) < 2 ) {
400
+ l . warn ( 'MetadataApi not available, rpc::state::get_metadata will be used.' ) ;
401
+
402
+ return at
403
+ ? new Metadata ( typeRegistry , await firstValueFrom ( this . _rpcCore . state . getMetadata . raw < HexString > ( at ) ) )
404
+ : await firstValueFrom ( this . _rpcCore . state . getMetadata ( ) ) ;
405
+ }
406
+
407
+ try {
408
+ const metadataVersionsAsBytes = at
409
+ ? await firstValueFrom ( this . _rpcCore . state . call . raw ( 'Metadata_metadata_versions' , '0x' , at ) )
410
+ : await firstValueFrom ( this . _rpcCore . state . call ( 'Metadata_metadata_versions' , '0x' ) ) ;
411
+ const versions = typeRegistry . createType ( 'Vec<u32>' , metadataVersionsAsBytes ) ;
412
+
413
+ metadataVersion = versions . reduce ( ( largest , current ) => current . gt ( largest ) ? current : largest ) ;
414
+ } catch ( e ) {
415
+ l . debug ( ( e as Error ) . message ) ;
416
+ l . warn ( 'error with state_call::Metadata_metadata_versions, rpc::state::get_metadata will be used' ) ;
417
+ }
418
+
419
+ // When the metadata version does not align with the latest supported versions we ensure not to call the metadata runtime call.
420
+ // I noticed on some previous runtimes that have support for `Metadata_metadata_at_version` that very irregular versions were being returned.
421
+ // This was evident with runtime 1000000 - it return a very large number. This ensures we always stick within what is supported.
422
+ if ( metadataVersion && ! SUPPORTED_METADATA_VERSIONS . includes ( metadataVersion . toNumber ( ) ) ) {
423
+ metadataVersion = null ;
424
+ }
425
+
426
+ if ( metadataVersion ) {
427
+ try {
428
+ const metadataBytes = at
429
+ ? await firstValueFrom ( this . _rpcCore . state . call . raw < Bytes > ( 'Metadata_metadata_at_version' , u8aToHex ( metadataVersion . toU8a ( ) ) , at ) )
430
+ : await firstValueFrom ( this . _rpcCore . state . call ( 'Metadata_metadata_at_version' , u8aToHex ( metadataVersion . toU8a ( ) ) ) ) ;
431
+ // When the metadata is called with `at` it is required to use `.raw`. Therefore since the length prefix is not present the
432
+ // need to create a `Raw` type is necessary before creating the `OpaqueMetadata` type or else there will be a magic number
433
+ // mismatch
434
+ const rawMeta = at
435
+ ? typeRegistry . createType ( 'Raw' , metadataBytes ) . toU8a ( )
436
+ : metadataBytes ;
437
+ const opaqueMetadata = typeRegistry . createType ( 'Option<OpaqueMetadata>' , rawMeta ) . unwrapOr ( null ) ;
438
+
439
+ if ( opaqueMetadata ) {
440
+ return new Metadata ( typeRegistry , opaqueMetadata . toHex ( ) ) ;
441
+ }
442
+ } catch ( e ) {
443
+ l . debug ( ( e as Error ) . message ) ;
444
+ l . warn ( 'error with state_call::Metadata_metadata_at_version, rpc::state::get_metadata will be used' ) ;
445
+ }
446
+ }
447
+
448
+ return at
449
+ ? new Metadata ( typeRegistry , await firstValueFrom ( this . _rpcCore . state . getMetadata . raw < HexString > ( at ) ) )
450
+ : await firstValueFrom ( this . _rpcCore . state . getMetadata ( ) ) ;
451
+ }
452
+
393
453
private _subscribeHealth ( ) : void {
394
454
this . _unsubscribeHealth ( ) ;
395
455
0 commit comments