Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue 3306] Update signing flow with metadata #3937

Open
wants to merge 2 commits into
base: subwallet-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 24 additions & 68 deletions packages/extension-base/src/koni/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import { CommonOptimalPath } from '@subwallet/extension-base/types/service-base'
import { SwapPair, SwapQuoteResponse, SwapRequest, SwapRequestResult, SwapSubmitParams, ValidateSwapProcessParams } from '@subwallet/extension-base/types/swap';
import { _analyzeAddress, BN_ZERO, combineAllAccountProxy, createTransactionFromRLP, isSameAddress, MODULE_SUPPORT, reformatAddress, signatureToHex, Transaction as QrTransaction, transformAccounts, transformAddresses, uniqueStringArray } from '@subwallet/extension-base/utils';
import { parseContractInput, parseEvmRlp } from '@subwallet/extension-base/utils/eth/parseTransaction';
import { metadataExpand } from '@subwallet/extension-chains';
import { MetadataDef } from '@subwallet/extension-inject/types';
import { getKeypairTypeByAddress, isAddress, isSubstrateAddress, isTonAddress } from '@subwallet/keyring';
import { EthereumKeypairTypes, SubstrateKeypairTypes, TonKeypairTypes } from '@subwallet/keyring/types';
Expand All @@ -71,12 +70,13 @@ import { combineLatest, Subject } from 'rxjs';
import { TransactionConfig } from 'web3-core';

import { SubmittableExtrinsic } from '@polkadot/api/types';
import { Metadata, TypeRegistry } from '@polkadot/types';
import { ChainProperties } from '@polkadot/types/interfaces';
import { TypeRegistry } from '@polkadot/types';
import { AnyJson, Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util';
import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto';

import { getSuitableMetadata, MetadataSource, MetadataWithSource, setupDappRegistry, setupDatabaseRegistry } from '../utils';

export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON {
return (value as SignerPayloadJSON).genesisHash !== undefined;
}
Expand Down Expand Up @@ -2516,7 +2516,7 @@ export default class KoniExtension {
}
}

/// Signing substrate request
// Signing substrate request
private async signingApprovePasswordV2 ({ id }: RequestSigningApprovePasswordV2): Promise<boolean> {
const queued = this.#koniState.getSignRequest(id);

Expand All @@ -2527,7 +2527,7 @@ export default class KoniExtension {

// unlike queued.account.address the following
// address is encoded with the default prefix
// which what is used for password caching mapping
// which is used for password caching mapping
const { address } = pair;

if (!pair) {
Expand All @@ -2542,77 +2542,33 @@ export default class KoniExtension {

const { payload } = request;

let registry: Registry;
let registry: Registry = new TypeRegistry();

if (isJsonPayload(payload)) {
const [, chainInfo] = this.#koniState.findNetworkKeyByGenesisHash(payload.genesisHash);
let metadata: MetadataDef | MetadataItem | undefined;

/**
* Get the metadata for the genesisHash
* @todo: need to handle case metadata store in db
*/
metadata = this.#koniState.knownMetadata.find((meta: MetadataDef) =>
meta.genesisHash === payload.genesisHash);
const [apiMetadata, dbMetadata, dappMetadata] = await Promise.all([
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to get registry

{ metadata: this.#koniState.getSubstrateApi(chainInfo?.slug ?? '').metadata, source: MetadataSource.API },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This metadata not updated after connect with chain

{ metadata: await this.#koniState.chainService.getMetadataByHash(payload.genesisHash), source: MetadataSource.DB },
{ metadata: this.#koniState.knownMetadata.find((meta: MetadataDef) => meta.genesisHash === payload.genesisHash), source: MetadataSource.DAPP }
]);

if (metadata) {
// we have metadata, expand it and extract the info/registry
const expanded = metadataExpand(metadata, false);
const allMetadata: MetadataWithSource[] = [
{ metadata: apiMetadata.metadata, source: apiMetadata.source },
{ metadata: dbMetadata.metadata, source: dbMetadata.source },
{ metadata: dappMetadata.metadata, source: dappMetadata.source }
].filter((item) => item.metadata);

registry = expanded.registry;
registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
if (allMetadata.length === 0) {
registry.setSignedExtensions(payload.signedExtensions);
} else {
metadata = await this.#koniState.chainService.getMetadataByHash(payload.genesisHash);

if (metadata) {
registry = new TypeRegistry();

const _metadata = new Metadata(registry, metadata.hexValue);
const { metadata, source } = getSuitableMetadata(allMetadata, parseInt(payload.specVersion));

registry.register(metadata.types);
registry.setChainProperties(registry.createType('ChainProperties', {
ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
tokenDecimals: chainInfo?.substrateInfo?.decimals,
tokenSymbol: chainInfo?.substrateInfo?.symbol
}) as unknown as ChainProperties);
registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);
} else {
// we have no metadata, create a new registry
registry = new TypeRegistry();
registry.setSignedExtensions(payload.signedExtensions);
}
registry = source === MetadataSource.API && chainInfo
? this.#koniState.getSubstrateApi(chainInfo.slug).api.registry as unknown as TypeRegistry
: source === MetadataSource.DB && chainInfo
? setupDatabaseRegistry(metadata as MetadataItem, chainInfo, payload)
: setupDappRegistry(metadata as MetadataDef, payload);
}

if (!metadata) {
/*
* Some networks must have metadata to signing,
* so if the chain not active (cannot use metadata from api), it must be rejected
* */
if (
chainInfo &&
(_API_OPTIONS_CHAIN_GROUP.avail.includes(chainInfo.slug) || _API_OPTIONS_CHAIN_GROUP.goldberg.includes(chainInfo.slug)) // The special case for chains that need metadata to signing
) {
// For case the chain does not have any provider
if (!Object.keys(chainInfo.providers).length) {
reject(new Error('{{chain}} network does not have any provider to connect, please update metadata from dApp'.replaceAll('{{chain}}', chainInfo.name)));

return false;
}

const isChainActive = this.#koniState.getChainStateByKey(chainInfo.slug).active;

if (!isChainActive) {
reject(new Error('Please activate {{chain}} network before signing'.replaceAll('{{chain}}', chainInfo.name)));

return false;
}

registry = this.#koniState.getSubstrateApi(chainInfo.slug).api.registry as unknown as TypeRegistry;
}
}
} else {
// for non-payload, just create a registry to use
registry = new TypeRegistry();
}

const result = request.sign(registry as unknown as TypeRegistry, pair);
Expand Down
69 changes: 69 additions & 0 deletions packages/extension-base/src/koni/background/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2019-2022 @subwallet/extension-koni authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { _ChainInfo } from '@subwallet/chain-list/types';
import { MetadataItem } from '@subwallet/extension-base/background/KoniTypes';
import { metadataExpand } from '@subwallet/extension-chains/bundle';
import { MetadataDef } from '@subwallet/extension-inject/types';

import { Metadata, TypeRegistry } from '@polkadot/types';
import { ChainProperties } from '@polkadot/types/interfaces';
import { SignerPayloadJSON } from '@polkadot/types/types';

export enum MetadataSource {
API = 'api',
DB = 'db',
DAPP = 'dapp'
}

export interface MetadataWithSource{
metadata: MetadataDef | MetadataItem | undefined;
source: MetadataSource;
}

export function getSuitableMetadata (metadatas: MetadataWithSource[], payloadSpecVersion: number) {
const sortedMetadatas = metadatas
.filter((meta): meta is MetadataWithSource => meta.metadata !== undefined)
.map((meta) => ({
meta: meta.metadata,
source: meta.source,
distance: Math.abs(Number(meta.metadata?.specVersion) - payloadSpecVersion),
isHigher: Number(meta.metadata?.specVersion) >= payloadSpecVersion
}))
.sort((a, b) => {
if (a.distance !== b.distance) {
return a.distance - b.distance;
}

return Number(b.meta?.specVersion) - Number(a.meta?.specVersion);
});

return {
metadata: sortedMetadatas[0].meta,
source: sortedMetadatas[0].source
};
}

export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _ChainInfo, payload: SignerPayloadJSON) {
const registry = new TypeRegistry();
const _metadata = new Metadata(registry, metadata.hexValue);

registry.register(metadata.types);
registry.setChainProperties(registry.createType('ChainProperties', {
ss58Format: chainInfo?.substrateInfo?.addressPrefix ?? 42,
tokenDecimals: chainInfo?.substrateInfo?.decimals,
tokenSymbol: chainInfo?.substrateInfo?.symbol
}) as unknown as ChainProperties);
registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);

return registry;
}

export function setupDappRegistry (metadata: MetadataDef, payload: SignerPayloadJSON) {
const expanded = metadataExpand(metadata, false);
const registry = expanded.registry;

registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);

return registry;
}
2 changes: 2 additions & 0 deletions packages/extension-base/src/services/chain-service/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import type { ApiInterfaceRx } from '@polkadot/api/types';

import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _CrowdloanFund } from '@subwallet/chain-list/types';
import { MetadataItem } from '@subwallet/extension-base/background/KoniTypes';
import { AccountState, TxByMsgResponse } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/types';
import { _CHAIN_VALIDATION_ERROR } from '@subwallet/extension-base/services/chain-service/handler/types';
import { TonWalletContract } from '@subwallet/keyring/types';
Expand Down Expand Up @@ -94,6 +95,7 @@ export interface _SubstrateApiState {
}

export interface _SubstrateApi extends _SubstrateApiState, _ChainBaseApi, _SubstrateApiAdapter {
metadata?: MetadataItem;
api: ApiPromise;
isReady: Promise<_SubstrateApi>;
connect: (_callbackUpdateMetadata?: (substrateApi: _SubstrateApi) => void) => void;
Expand Down
31 changes: 23 additions & 8 deletions packages/extension-base/src/utils/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { ChainService } from '@subwallet/extension-base/services/chain-service';
import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';

import { ApiPromise } from '@polkadot/api';
import { getSpecExtensions, getSpecTypes } from '@polkadot/types-known';
import { u8aToHex } from '@polkadot/util';
import { HexString } from '@polkadot/util/types';
Expand All @@ -25,6 +26,22 @@ export const getShortMetadata = (blob: HexString, extraInfo: ExtraInfo, metadata
return u8aToHex(_merkleizeMetadata.getProofForExtrinsicPayload(blob));
};

const getMetadataV15 = async (api: ApiPromise): Promise<HexString | undefined> => {
try {
if (api.call.metadata.metadataAtVersion) {
const metadataV15 = await api.call.metadata.metadataAtVersion(15);

if (!metadataV15.isEmpty) {
return metadataV15.unwrap().toHex();
}
}
} catch (err) {
console.error('Error fetching metadata V15:', err);
}

return undefined;
};

export const cacheMetadata = (
chain: string,
substrateApi: _SubstrateApi,
Expand All @@ -42,23 +59,21 @@ export const cacheMetadata = (
return;
}

const systemChain = await api.rpc.system.chain();
const systemChain = api.runtimeChain;
// const _metadata: Option<OpaqueMetadata> = await api.call.metadata.metadataAtVersion(15);
// const metadataHex = _metadata.isSome ? _metadata.unwrap().toHex().slice(2) : ''; // Need unwrap to create metadata object
let hexV15: HexString | undefined;

const metadataV15 = await api.call.metadata.metadataAtVersion(15);

if (!metadataV15.isEmpty) {
hexV15 = metadataV15.unwrap().toHex();
}
const [metadataHex, hexV15] = await Promise.all([
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a new store to metadataV15 and add new function to save.

Promise.resolve(api.runtimeMetadata.toHex()),
getMetadataV15(api)
]);

chainService?.upsertMetadata(chain, {
chain: chain,
genesisHash: genesisHash,
specName: specName,
specVersion: currentSpecVersion,
hexValue: api.runtimeMetadata.toHex(),
hexValue: metadataHex,
types: getSpecTypes(api.registry, systemChain, api.runtimeVersion.specName, api.runtimeVersion.specVersion) as unknown as Record<string, string>,
userExtensions: getSpecExtensions(api.registry, systemChain, api.runtimeVersion.specName),
hexV15
Expand Down
Loading