cennznet / api.js

CENNZnet JS API for browsers, React Native and Node.js
Apache License 2.0
10 stars 10 forks source link

Type error when querying data at block 705492 #167

Closed AlexZhenWang closed 4 years ago

AlexZhenWang commented 4 years ago

When I am using a workaround to query historical data, I got an error when querying data at block 705492. It works for all blocks before 705492, but get an error at the block 705492.

SDK version: @cennznet/api 1.2.1 Environment: node 12.18.3 Error message: queryInfo(extrinsic: Bytes, at?: BlockHash): RuntimeDispatchInfo:: 2: Unable to query dispatch info.: Error decoding field Call :: CennzxSpot.0 How to reproduce:

import { ApiOptions } from '@cennznet/api/types';
import { WsProvider, ApiRx } from '@cennznet/api';
import { take } from 'rxjs/operators';

export const customizedTypes = {
  Phase: {
    _enum: {
      ApplyExtrinsic: 'u32',
      Finalization: 'Null',
      Initialization: 'Null',
    },
  },
};

export async function createApi(opt?: Partial<ApiOptions>): Promise<ApiRx> {
  const provider = new WsProvider('wss://node-6691907305808760832.jm.onfinality.io/ws?apikey=5907c66b-320e-4d9c-86b3-207a63c94ad6');
  const api = await ApiRx.create({
    provider,
    types: customizedTypes,
    ...opt,
  }).toPromise();
  return api;
}

async function main() {
  // this apiForPrepare is for querying data before creating the API instance with metadata of block 705492
  const apiForPrepare = await createApi();
  const genesisHash = apiForPrepare.genesisHash.toString();

  const blockNumber = 705492;

  const blockHash = await apiForPrepare.rpc.chain
    .getBlockHash(blockNumber)
    .pipe(take(1))
    .toPromise();

  const metadata = await apiForPrepare.rpc.state
    .getMetadata(blockHash)
    .toPromise();

  /* This is a tricky workaround: 
   * api instance only accept customized metadata if        
   * the spec verions is the latest spec version.
   * So we use latestSpecVersion to let api accept our metadata.
   */
  const latestSpecVersion = ((await apiForPrepare.rpc.state
    .getRuntimeVersion()
    .pipe(take(1))
    .toPromise()) as any).specVersion.toString();

  const api = await createApi({
    metadata: {
      [`${genesisHash}-${latestSpecVersion}`]: metadata.toHex(),
    },
  });

  const block = await api.rpc.chain.getBlock(blockHash).toPromise()
  // The query blow will throw error
  await api.rpc.payment.queryInfo(block.block.extrinsics[2].toHex()).toPromise();
  // Error message: `queryInfo(extrinsic: Bytes, at?: BlockHash): RuntimeDispatchInfo:: 2: Unable to query dispatch info.: Error decoding field Call :: CennzxSpot.0`
}

main();
AlexZhenWang commented 4 years ago

Another strange thing here is if I don't use the API instance created with historical metadata like showing above

const api = await createApi({
      metadata: {
        [`${genesisHash}-${latestSpecVersion}`]: metadata.toHex(),
      },
    });

but use the API instance with current latest block metadata like

const apiForPrepare = await createApi();

the api.rpc.payment.queryInfo won't throw that error, but I think I should use historical metadata.

aliXsed commented 4 years ago

It turns out we have a CodeUpdated system event at block #705466. If we set the metadata of any block number smaller than 705466, we can successfully decode all the events of the block 705492 plus the payment info. This suggests there is a gap (here 27 blocks) between the place where the metadata had been updated and where it should be used. I will share more details as I learn more about this situation.

AlexZhenWang commented 4 years ago

Thank you @alexsednz 👍

aliXsed commented 4 years ago

If the tricky workaround in @AlexZhenWang's code above is replaced with the right spec version for the same block that we have obtained the metadata from, then this issue is solved. However, it would then cause #158 to re-emerge. So I'm tending to say instead of fixing this issue we should probably be working on #158 and assume the suggested workaround there is problematic. I'm saying that because the spec version is an important tag in the metadata. It's not acceptable to use the latest spec version for any historical block.

AlexZhenWang commented 4 years ago

Thank you for checking @alexsednz!

But as I have known, if we use the spec version for the same block that we have obtained the metadata from, the metadata for that specific block is actually not used by the API instance. so it's same as if we don't pass any metadata as I mentioned above, which feels like maybe the error is just muted but not solved because the data from the chain is not even correctly decoded(check #158).

The reason I said the metadata for that specific block is actually not used by API instance if we don't use latest spec version, is that as I have seen, when the API instance initializes, it will check the spec version, and if that version is not the latest version, it will fetch metadata of latest version and replace the metadata you passed in. I found this when solving #158 on the CENNZnet Scan project. This is also why I use that tricky workaround.

Also, if we don't use my tricky workaround, we need to find another way to solve #158 as well...

AlexZhenWang commented 4 years ago

Em.. just thinking. Maybe can try and test: Find the place where API instance checks spec version. Hardcode to use metadata for block 705492 after the check(So the spec version check won't impact us. By this way, we can use metadata for block 705492 without changing spec version).

If this doesn't work, means the issue is just muted and maybe is not related to which spec version that we pass in. If this works, can add a flag to @cennznet/api to skip the spec version check. So we can pass in the metadata with the current spec version.

aliXsed commented 4 years ago

I would prefer to address the root cause. I'm now focused on a function in the API which is called metaFromChain. I think the issue of not loading the correct metadata when the spec version is correctly specified should be there.

aliXsed commented 4 years ago

I believe the root cause is in the following code in the version of the Polkadot that we are using:

async metaFromChain(optMetadata) {
    const {
      typesChain,
      typesSpec
    } = this._options;
    const [genesisHash, runtimeVersion, chain, chainProps] = await Promise.all([this._rpcCore.chain.getBlockHash(0).toPromise(), this._rpcCore.state.getRuntimeVersion().toPromise(), this._rpcCore.system.chain().toPromise(), this._rpcCore.system.properties().toPromise()]); // based on the node spec & chain, inject specific type overrides

    this.registerTypes((0, _known.getChainTypes)(chain, runtimeVersion, typesChain, typesSpec)); // filter the RPC methods (this does an rpc-methods call)

    await this.filterRpc(); // retrieve metadata, either from chain  or as pass-in via options

    const metadataKey = "".concat(genesisHash, "-").concat(runtimeVersion.specVersion);
    const metadata = metadataKey in optMetadata ? new _types.Metadata(this.registry, optMetadata[metadataKey]) : await this._rpcCore.state.getMetadata().toPromise(); // set our chain version & genesisHash as returned

    this._genesisHash = genesisHash;
    this._runtimeVersion = runtimeVersion; // set the global ss58Format as detected by the chain

    (0, _utilCrypto.setSS58Format)(chainProps.ss58Format.unwrapOr(new _types.u32(this.registry, _defaults.default.prefix)).toNumber()); // get unique types & validate

    metadata.getUniqTypes(false);
    return metadata;
  }

metadataKey is made out of the latest runtime version, irrespective of the metadata (optMetadata) that you provide for the API. Then if it doesn't find metadataKey in the provided metadata, it will discard them and fetch the latest metadata from the chain instead.

aliXsed commented 4 years ago

Finally here is the underlying issue https://github.com/cennznet/cennznet/issues/264 which is already fixed and tested by using the following docker tag cennznet/cennznet:develop-6b1402

aliXsed commented 4 years ago

@AlexZhenWang Please remember to pass in the blockhash to api.rpc.payment.queryInfo(extrinsic, blockhash) in your code.

AlexZhenWang commented 4 years ago

Thanks, @alexsednz!

AlexZhenWang commented 4 years ago

Hi, I can successfully query payment info at block 705492 now. But when using image cennznet/cennznet:develop-6b1402, the node stopped syncing at block 1,749,753. image (15)