Skip to content

Commit

Permalink
Merge pull request #3937 from Koniverse/koni/dev/issue-3306-v2
Browse files Browse the repository at this point in the history
[Issue 3306] Update signing flow with metadata
  • Loading branch information
saltict authored Jan 10, 2025
2 parents 1f327e4 + f1943f0 commit 42a5e2d
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 114 deletions.
11 changes: 11 additions & 0 deletions packages/extension-base/src/background/KoniTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,17 @@ export interface MetadataItem {
types: Record<string, Record<string, string> | string>;
userExtensions?: ExtDef;
hexV15?: HexString;
tokenInfo?: {
ss58Format: number;
tokenDecimals: number;
tokenSymbol: string;
};
}

export interface MetadataV15Item {
genesisHash: string;
specVersion: string;
hexV15?: HexString;
}

export interface CrowdloanItem {
Expand Down
92 changes: 22 additions & 70 deletions packages/extension-base/src/koni/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,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, toBNString, 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 @@ -73,12 +72,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 { getSuitableRegistry, RegistrySource, setupApiRegistry, setupDappRegistry, setupDatabaseRegistry } from '../utils';

export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON {
return (value as SignerPayloadJSON).genesisHash !== undefined;
}
Expand Down Expand Up @@ -2523,7 +2523,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 @@ -2534,7 +2534,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 @@ -2549,77 +2549,29 @@ 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);

if (metadata) {
// we have metadata, expand it and extract the info/registry
const expanded = metadataExpand(metadata, false);

registry = expanded.registry;
registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
const allRegistry: RegistrySource[] = [
setupApiRegistry(chainInfo, this.#koniState),
setupDatabaseRegistry(
await this.#koniState.chainService.getMetadataByHash(payload.genesisHash) as MetadataItem,
chainInfo,
payload
),
setupDappRegistry(
this.#koniState.knownMetadata.find((meta: MetadataDef) => meta.genesisHash === payload.genesisHash) as MetadataDef,
payload
)
].filter((item): item is RegistrySource => item !== null && item.registry !== undefined);

if (allRegistry.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);

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);
}
}

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;
}
registry = getSuitableRegistry(allRegistry, payload);
}
} 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
94 changes: 94 additions & 0 deletions packages/extension-base/src/koni/background/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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 { Registry, SignerPayloadJSON } from '@polkadot/types/types';

import KoniState from './handlers/State';

export interface RegistrySource{
registry: Registry,
specVersion: string | number,
}

export function getSuitableRegistry (registries: RegistrySource[], payload: SignerPayloadJSON) {
const payloadSpecVersion = parseInt(payload.specVersion);
const sortedRegistries = registries
.filter((registrySource): registrySource is RegistrySource => registrySource.registry !== undefined)
.map((registrySource) => {
const specVersion = Number(registrySource.specVersion);
const distance = Math.abs(specVersion - payloadSpecVersion);
const isHigher = specVersion >= payloadSpecVersion;

return {
registry: registrySource.registry,
specVersion,
distance,
isHigher
};
})
.sort((a, b) => {
if (a.distance !== b.distance) {
return a.distance - b.distance;
}

return b.specVersion - a.specVersion;
});

return sortedRegistries[0].registry;
}

export function setupApiRegistry (chainInfo: _ChainInfo | undefined, koniState: KoniState): RegistrySource | null {
if (!chainInfo) {
return null;
}

const api = koniState.getSubstrateApi(chainInfo.slug).api;
const apiSpecVersion = api?.runtimeVersion.specVersion.toString();
const registry = api?.registry as unknown as TypeRegistry;

return {
registry,
specVersion: apiSpecVersion
};
}

export function setupDatabaseRegistry (metadata: MetadataItem, chainInfo: _ChainInfo | undefined, payload: SignerPayloadJSON): RegistrySource | null {
if (!metadata || !metadata.genesisHash || !chainInfo) {
return null;
}

const registry = new TypeRegistry();
const _metadata = new Metadata(registry, metadata.hexValue);

registry.register(metadata.types);
registry.setChainProperties(registry.createType('ChainProperties', metadata.tokenInfo) as unknown as ChainProperties);
registry.setMetadata(_metadata, payload.signedExtensions, metadata.userExtensions);

return {
registry,
specVersion: metadata.specVersion
};
}

export function setupDappRegistry (metadata: MetadataDef, payload: SignerPayloadJSON): RegistrySource | null {
if (!metadata || !metadata.genesisHash) {
return null;
}

const expanded = metadataExpand(metadata, false);
const registry = expanded.registry;

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

return {
registry,
specVersion: metadata.specVersion
};
}
36 changes: 23 additions & 13 deletions packages/extension-base/src/services/chain-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { AssetLogoMap, AssetRefMap, ChainAssetMap, ChainInfoMap, ChainLogoMap, MultiChainAssetMap } from '@subwallet/chain-list';
import { _AssetRef, _AssetRefPath, _AssetType, _ChainAsset, _ChainInfo, _ChainStatus, _EvmInfo, _MultiChainAsset, _SubstrateChainType, _SubstrateInfo, _TonInfo } from '@subwallet/chain-list/types';
import { AssetSetting, ValidateNetworkResponse } from '@subwallet/extension-base/background/KoniTypes';
import { AssetSetting, MetadataItem, ValidateNetworkResponse } from '@subwallet/extension-base/background/KoniTypes';
import { _DEFAULT_ACTIVE_CHAINS, _ZK_ASSET_PREFIX, LATEST_CHAIN_DATA_FETCHING_INTERVAL } from '@subwallet/extension-base/services/chain-service/constants';
import { EvmChainHandler } from '@subwallet/extension-base/services/chain-service/handler/EvmChainHandler';
import { MantaPrivateHandler } from '@subwallet/extension-base/services/chain-service/handler/manta/MantaPrivateHandler';
Expand All @@ -13,7 +13,7 @@ import { _CHAIN_VALIDATION_ERROR } from '@subwallet/extension-base/services/chai
import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _CUSTOM_PREFIX, _DataMap, _EvmApi, _NetworkUpsertParams, _NFT_CONTRACT_STANDARDS, _SMART_CONTRACT_STANDARDS, _SmartContractTokenInfo, _SubstrateApi, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse } from '@subwallet/extension-base/services/chain-service/types';
import { _isAssetAutoEnable, _isAssetCanPayTxFee, _isAssetFungibleToken, _isChainEnabled, _isCustomAsset, _isCustomChain, _isCustomProvider, _isEqualContractAddress, _isEqualSmartContractAsset, _isLocalToken, _isMantaZkAsset, _isPureEvmChain, _isPureSubstrateChain, _parseAssetRefKey, randomizeProvider, updateLatestChainInfo } from '@subwallet/extension-base/services/chain-service/utils';
import { EventService } from '@subwallet/extension-base/services/event-service';
import { IChain, IMetadataItem } from '@subwallet/extension-base/services/storage-service/databases';
import { IChain, IMetadataItem, IMetadataV15Item } from '@subwallet/extension-base/services/storage-service/databases';
import DatabaseService from '@subwallet/extension-base/services/storage-service/DatabaseService';
import AssetSettingStore from '@subwallet/extension-base/stores/AssetSetting';
import { addLazy, calculateMetadataHash, fetchStaticData, filterAssetsByChainAndType, getShortMetadata, MODULE_SUPPORT } from '@subwallet/extension-base/utils';
Expand Down Expand Up @@ -2070,46 +2070,56 @@ export class ChainService {
return this.dbService.stores.metadata.upsertMetadata(chain, metadata);
}

getMetadataV15 (chain: string) {
return this.dbService.stores.metadataV15.getMetadata(chain);
}

upsertMetadataV15 (chain: string, metadata: IMetadataV15Item) {
return this.dbService.stores.metadataV15.upsertMetadata(chain, metadata);
}

getMetadataByHash (hash: string) {
return this.dbService.stores.metadata.getMetadataByGenesisHash(hash);
}

getExtraInfo (chain: string): Omit<ExtraInfo, 'specVersion' | 'specName'> {
const chainInfo = this.getChainInfoByKey(chain);
getExtraInfo (metadata: MetadataItem): Omit<ExtraInfo, 'specVersion' | 'specName'> {
const tokenInfo = metadata.tokenInfo;

return {
decimals: chainInfo.substrateInfo?.decimals ?? 0,
tokenSymbol: chainInfo.substrateInfo?.symbol ?? 'Unit',
base58Prefix: chainInfo.substrateInfo?.addressPrefix ?? 42
decimals: tokenInfo?.tokenDecimals ?? 0,
tokenSymbol: tokenInfo?.tokenSymbol ?? 'Unit',
base58Prefix: tokenInfo?.ss58Format ?? 42
};
}

async calculateMetadataHash (chain: string): Promise<string | undefined> {
const metadata = await this.getMetadata(chain);
const metadataV15 = await this.getMetadataV15(chain);

if (!metadata || !metadata.hexV15) {
if (!metadata || !metadataV15 || !metadataV15.hexV15) {
return undefined;
}

const extraInfo = this.getExtraInfo(chain);
const extraInfo = this.getExtraInfo(metadata);
const specVersion = parseInt(metadata.specVersion);
const specName = metadata.specName;
const hexV15 = metadata.hexV15;
const hexV15 = metadataV15.hexV15;

return calculateMetadataHash({ ...extraInfo, specVersion, specName }, hexV15);
}

async shortenMetadata (chain: string, txBlob: string): Promise<string | undefined> {
const metadata = await this.getMetadata(chain);
const metadataV15 = await this.getMetadataV15(chain);

if (!metadata || !metadata.hexV15) {
if (!metadata || !metadataV15 || !metadataV15.hexV15) {
return undefined;
}

const extraInfo = this.getExtraInfo(chain);
const extraInfo = this.getExtraInfo(metadata);
const specVersion = parseInt(metadata.specVersion);
const specName = metadata.specName;
const hexV15 = metadata.hexV15;
const hexV15 = metadataV15.hexV15;

return getShortMetadata(txBlob as HexString, { ...extraInfo, specVersion, specName }, hexV15);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export default <Record<string, typeof BaseMigrationJob>>{
'1.2.28-02': MigrateTransactionHistoryBySymbol,
'1.2.69-01': MigrateRemoveGenesisHash,
'1.2.13-01': ReloadMetadata,
'1.2.14-01': ClearMetadataDatabase,
'1.2.32-01': MigratePairData,
'1.3.6-01': MigrateTransactionHistoryBridge
'1.3.6-01': MigrateTransactionHistoryBridge,
'1.3.10-01': ClearMetadataDatabase
// [`${EVERYTIME}-1.1.42-02`]: MigrateTransactionHistoryBySymbol
// [`${EVERYTIME}-1`]: AutoEnableChainsTokens
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { APIItemState, ChainStakingMetadata, CrowdloanItem, MantaPayConfig, NftC
import { EventService } from '@subwallet/extension-base/services/event-service';
import { _NotificationInfo } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
import KoniDatabase, { IBalance, ICampaign, IChain, ICrowdloanItem, INft } from '@subwallet/extension-base/services/storage-service/databases';
import { AssetStore, BalanceStore, ChainStore, CrowdloanStore, MetadataStore, MigrationStore, NftCollectionStore, NftStore, PriceStore, StakingStore, TransactionStore } from '@subwallet/extension-base/services/storage-service/db-stores';
import { AssetStore, BalanceStore, ChainStore, CrowdloanStore, MetadataStore, MetadataV15Store, MigrationStore, NftCollectionStore, NftStore, PriceStore, StakingStore, TransactionStore } from '@subwallet/extension-base/services/storage-service/db-stores';
import BaseStore from '@subwallet/extension-base/services/storage-service/db-stores/BaseStore';
import CampaignStore from '@subwallet/extension-base/services/storage-service/db-stores/Campaign';
import ChainStakingMetadataStore from '@subwallet/extension-base/services/storage-service/db-stores/ChainStakingMetadata';
Expand Down Expand Up @@ -55,6 +55,8 @@ export default class DatabaseService {
migration: new MigrationStore(this._db.migrations),

metadata: new MetadataStore(this._db.metadata),
metadataV15: new MetadataV15Store(this._db.metadataV15),

chain: new ChainStore(this._db.chain),
asset: new AssetStore(this._db.asset),

Expand Down
Loading

1 comment on commit 42a5e2d

@saltict
Copy link
Author

Choose a reason for hiding this comment

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

Please sign in to comment.