diff --git a/e2e-tests/endpoints/kusama/blocks/10819306.json b/e2e-tests/endpoints/kusama/blocks/10819306.json index 7df8be600..a82098f68 100644 --- a/e2e-tests/endpoints/kusama/blocks/10819306.json +++ b/e2e-tests/endpoints/kusama/blocks/10819306.json @@ -2027,7 +2027,7 @@ "info": { "weight": "1177134000", "class": "Normal", - "partialFee": "51999544" + "partialFee": "52002823" }, "era": { "mortalEra": [ @@ -2173,7 +2173,7 @@ "info": { "weight": "2623797000", "class": "Normal", - "partialFee": "51999582" + "partialFee": "52002823" }, "era": { "mortalEra": [ @@ -2379,7 +2379,7 @@ "info": { "weight": "1659355000", "class": "Normal", - "partialFee": "51999557" + "partialFee": "52002823" }, "era": { "mortalEra": [ @@ -2545,7 +2545,7 @@ "info": { "weight": "1177134000", "class": "Normal", - "partialFee": "51999544" + "partialFee": "52002823" }, "era": { "mortalEra": [ @@ -2691,7 +2691,7 @@ "info": { "weight": "1177134000", "class": "Normal", - "partialFee": "51999544" + "partialFee": "52002823" }, "era": { "mortalEra": [ diff --git a/e2e-tests/endpoints/kusama/blocks/2350438.json b/e2e-tests/endpoints/kusama/blocks/2350438.json index cd6ac707b..82293b86c 100644 --- a/e2e-tests/endpoints/kusama/blocks/2350438.json +++ b/e2e-tests/endpoints/kusama/blocks/2350438.json @@ -146,7 +146,9 @@ "tip": "0", "hash": "0x3f5a8e3b5dae4e28252d98e8464f29b50b08a49a36cf71ac009c9a791c1ffa82", "info": { - "error": "Fee calculation not supported for 1062#kusama" + "weight": "195000000", + "class": "Normal", + "partialFee": "1000000000" }, "era": { "mortalEra": [ diff --git a/e2e-tests/endpoints/kusama/blocks/6566865.json b/e2e-tests/endpoints/kusama/blocks/6566865.json index f7d8c3354..96568db3d 100644 --- a/e2e-tests/endpoints/kusama/blocks/6566865.json +++ b/e2e-tests/endpoints/kusama/blocks/6566865.json @@ -447,7 +447,7 @@ "info": { "weight": "423471000", "class": "Normal", - "partialFee": "4033332350" + "partialFee": "4033332622" }, "era": { "mortalEra": [ @@ -782,7 +782,7 @@ "info": { "weight": "423471000", "class": "Normal", - "partialFee": "4033332350" + "partialFee": "4033332622" }, "era": { "mortalEra": [ diff --git a/e2e-tests/endpoints/polkadot/blocks/4392619.json b/e2e-tests/endpoints/polkadot/blocks/4392619.json index dabec626d..5bdfbef1e 100644 --- a/e2e-tests/endpoints/polkadot/blocks/4392619.json +++ b/e2e-tests/endpoints/polkadot/blocks/4392619.json @@ -242,7 +242,7 @@ "info": { "weight": "181791000", "class": "Normal", - "partialFee": "201000014" + "partialFee": "201000024" }, "era": { "mortalEra": [ diff --git a/src/services/blocks/BlocksService.spec.ts b/src/services/blocks/BlocksService.spec.ts index 17fbf636e..bc05ce3c1 100644 --- a/src/services/blocks/BlocksService.spec.ts +++ b/src/services/blocks/BlocksService.spec.ts @@ -16,12 +16,10 @@ import { ApiPromise } from '@polkadot/api'; import { ApiDecoration } from '@polkadot/api/types'; -import { AugmentedConst } from '@polkadot/api/types'; import { PromiseRpcResult } from '@polkadot/api-base/types/rpc'; -import { GenericExtrinsic, u128, Vec } from '@polkadot/types'; +import { GenericExtrinsic } from '@polkadot/types'; import { GenericCall } from '@polkadot/types/generic'; import { BlockHash, Hash, SignedBlock } from '@polkadot/types/interfaces'; -import { FrameSupportWeightsWeightToFeeCoefficient } from '@polkadot/types/lookup'; import { BadRequest } from 'http-errors'; import LRU from 'lru-cache'; @@ -30,17 +28,13 @@ import { createCall } from '../../test-helpers/createCall'; import { kusamaRegistry, polkadotRegistry, - polkadotRegistryV9190, } from '../../test-helpers/registries'; -import { TypeFactory } from '../../test-helpers/typeFactory'; -import { ExtBaseWeightValue, PerClassValue } from '../../types/chains-config'; -import { IBlock, IExtrinsic } from '../../types/responses/'; +import { IBlock } from '../../types/responses/'; import { blockHash20000, blockHash100000, blockHash789629, defaultMockApi, - mockBlock789629, mockForkedBlock789629, } from '../test-helpers/mock'; import block789629 from '../test-helpers/mock/data/block789629.json'; @@ -114,13 +108,6 @@ type GetBlock = PromiseRpcResult< (hash?: string | BlockHash | Uint8Array | undefined) => Promise >; -/** - * Interface for the reponse in `fetchBlock` test suite - */ -interface ResponseObj { - extrinsics: IExtrinsic[]; -} - // LRU cache used to cache blocks // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const cache = new LRU({ max: 2 }) as LRU; @@ -212,181 +199,6 @@ describe('BlocksService', () => { expect(block.finalized).toEqual(undefined); }); - - it('Return an error with a null calcFee when perByte is undefined', async () => { - mockHistoricApi.consts.transactionPayment.transactionByteFee = - undefined as unknown as u128 & AugmentedConst<'promise'>; - - const configuredBlocksService = new BlocksService(mockApi, 0, new LRU()); - - // fetchBlock options - const options = { - eventDocs: true, - extrinsicDocs: true, - checkFinalized: false, - queryFinalizedHead: false, - omitFinalizedTag: false, - }; - - const response = sanitizeNumbers( - await configuredBlocksService.fetchBlock( - blockHash789629, - mockHistoricApi, - options - ) - ); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const responseObj: ResponseObj = JSON.parse(JSON.stringify(response)); - - // Revert mockApi back to its original setting that was changed above. - mockHistoricApi.consts.transactionPayment.transactionByteFee = - polkadotRegistry.createType('Balance', 1000000) as u128 & - AugmentedConst<'promise'>; - - expect(responseObj.extrinsics[3].info).toEqual({ - error: 'Fee calculation not supported for 16#polkadot', - }); - }); - }); - - describe('createCalcFee & calc_fee', () => { - it('calculates partialFee for proxy.proxy in polkadot block 789629', async () => { - // Reset LRU cache - cache.reset(); - // tx hash: 0x6d6c0e955650e689b14fb472daf14d2bdced258c748ded1d6cb0da3bfcc5854f - const { calcFee } = await blocksService['createCalcFee']( - mockApi, - mockHistoricApi, - '0xParentHash' as unknown as Hash, - mockBlock789629 - ); - - expect(calcFee?.calc_fee(BigInt(399480000), 534, BigInt(125000000))).toBe( - '544000000' - ); - }); - - it('calculates partialFee for utility.batch in polkadot block 789629', async () => { - // Reset LRU cache - cache.reset(); - // tx hash: 0xc96b4d442014fae60c932ea50cba30bf7dea3233f59d1fe98c6f6f85bfd51045 - const { calcFee } = await blocksService['createCalcFee']( - mockApi, - mockHistoricApi, - '0xParentHash' as unknown as Hash, - mockBlock789629 - ); - - expect( - calcFee?.calc_fee(BigInt(941325000000), 1247, BigInt(125000000)) - ).toBe('1257000075'); - }); - }); - - describe('getPerByte', () => { - it('Correctly handles LengthToFee', () => { - // Reset LRU cache - cache.reset(); - - /** - * Setup a mockApiClone specific to v9190 - */ - const mockHistoricApiClone = { - ...mockHistoricApi, - registry: polkadotRegistryV9190, - } as unknown as ApiDecoration<'promise'>; - - /** - * Typecast here because the typefactory just needs the registry so this is safe. - * It's a shortcut so that constructing a augmented api is not necessary. - */ - const polkadotV9190TypeFactory = new TypeFactory( - mockHistoricApiClone as ApiPromise - ); - - /** - * Create a Vec type - * The `coeffInteger` value here should be different from - * `transactionByteFee` set in mockHistoricApi in order to properly - * distinquish values. - */ - const lengthToFeeStruct = polkadotRegistryV9190.createType( - 'FrameSupportWeightsWeightToFeeCoefficient', - { coeffInteger: 12345678 } - ); - const lengthToFee = polkadotV9190TypeFactory.vecOf([lengthToFeeStruct]); - - /** - * Remove TransactionByteFee from our clone and replace it with LengthToFee - */ - (mockHistoricApiClone.consts.transactionPayment[ - 'transactionByteFee' - ] as unknown) = undefined; - mockHistoricApiClone.consts.transactionPayment['lengthToFee'] = - lengthToFee as unknown as Vec & - AugmentedConst<'promise'>; - - const result = blocksService['getPerByte'](mockHistoricApiClone); - - expect(sanitizeNumbers(result)).toEqual('12345678'); - }); - }); - - describe('BlocksService.getWeight', () => { - it('Should return correct `extrinsicBaseWeight`', () => { - // Reset LRU cache - cache.reset(); - - const weightValue = blocksService['getWeight'](mockHistoricApi); - - expect( - (weightValue as unknown as ExtBaseWeightValue).extrinsicBaseWeight - ).toBe(BigInt(125000000)); - }); - - it('Should return correct `blockWeights`', () => { - // Reset LRU cache - cache.reset(); - - /** - * This is the mockApi adjusted to mock a runtime that uses - * consts.system.blockWeights for its weight nomination. - */ - const mockHistoricApiAdjusted = { - consts: { - system: { - blockWeights: { - perClass: { - normal: { - baseExtrinsic: polkadotRegistry.createType('u64', 125000000), - }, - operational: { - baseExtrinsic: polkadotRegistry.createType('u64', 125000000), - }, - mandatory: { - baseExtrinsic: polkadotRegistry.createType('u64', 125000000), - }, - }, - }, - }, - }, - } as unknown as ApiDecoration<'promise'>; - - const weightValue = blocksService['getWeight'](mockHistoricApiAdjusted); - - expect( - (weightValue as unknown as PerClassValue).perClass.normal.baseExtrinsic - ).toBe(BigInt(125000000)); - expect( - (weightValue as unknown as PerClassValue).perClass.operational - .baseExtrinsic - ).toBe(BigInt(125000000)); - expect( - (weightValue as unknown as PerClassValue).perClass.mandatory - .baseExtrinsic - ).toBe(BigInt(125000000)); - }); }); describe('BlocksService.parseGenericCall', () => { diff --git a/src/services/blocks/BlocksService.ts b/src/services/blocks/BlocksService.ts index b88b7d7c2..2a8e1a3a1 100644 --- a/src/services/blocks/BlocksService.ts +++ b/src/services/blocks/BlocksService.ts @@ -17,35 +17,24 @@ import { ApiPromise } from '@polkadot/api'; import { ApiDecoration } from '@polkadot/api/types'; import { extractAuthor } from '@polkadot/api-derive/type/util'; -import { Compact, GenericCall, Struct, u128, Vec } from '@polkadot/types'; +import { Compact, GenericCall, Struct, Vec } from '@polkadot/types'; import { AccountId32, - Balance, Block, BlockHash, BlockNumber, DispatchInfo, EventRecord, - Hash, Header, } from '@polkadot/types/interfaces'; import { AnyJson, Codec, Registry } from '@polkadot/types/types'; -import { AbstractInt } from '@polkadot/types-codec'; import { u8aToHex } from '@polkadot/util'; import { blake2AsU8a } from '@polkadot/util-crypto'; -import { CalcFee } from '@substrate/calc'; import { BadRequest, InternalServerError } from 'http-errors'; import LRU from 'lru-cache'; -import { - IPerClass, - isExtBaseWeightValue, - isPerClassValue, - WeightValue, -} from '../../types/chains-config'; import { IBlock, - ICalcFee, IExtrinsic, IExtrinsicIndex, ISanitizedCall, @@ -115,8 +104,15 @@ export class BlocksService extends AbstractService { return isBlockCached; } - const [{ block }, validators, events, finalizedHead] = await Promise.all([ + const [ + { block }, + { specName, specVersion }, + validators, + events, + finalizedHead, + ] = await Promise.all([ api.rpc.chain.getBlock(hash), + api.rpc.state.getRuntimeVersion(hash), this.fetchValidators(historicApi), this.fetchEvents(historicApi), queryFinalizedHead @@ -181,26 +177,6 @@ export class BlocksService extends AbstractService { }; } - let calcFee, specName, specVersion, weights; - if (this.minCalcFeeRuntime === null) { - // Don't bother with trying to create calcFee for a runtime where fee calcs are not supported - specVersion = -1; - specName = 'ERROR'; - calcFee = undefined; - } else { - // This runtime supports fee calc - const createCalcFee = await this.createCalcFee( - api, - historicApi, - parentHash, - block - ); - calcFee = createCalcFee.calcFee; - specName = createCalcFee.specName; - specVersion = createCalcFee.specVersion; - weights = createCalcFee.weights; - } - for (let idx = 0; idx < block.extrinsics.length; ++idx) { if (!extrinsics[idx].paysFee || !block.extrinsics[idx].isSigned) { continue; @@ -213,9 +189,9 @@ export class BlocksService extends AbstractService { continue; } - if (calcFee === null || calcFee === undefined) { + if (this.minCalcFeeRuntime > specVersion.toNumber()) { extrinsics[idx].info = { - error: `Fee calculation not supported for ${specVersion}#${specName}`, + error: `Fee calculation not supported for ${specVersion.toString()}#${specName.toString()}`, }; continue; } @@ -259,45 +235,20 @@ export class BlocksService extends AbstractService { continue; } - // The Dispatch class used to key into `blockWeights.perClass` - // We set default to be normal. - let weightInfoClass: keyof IPerClass = 'normal'; - if (weightInfo.class.isMandatory) { - weightInfoClass = 'mandatory'; - } else if (weightInfo.class.isOperational) { - weightInfoClass = 'operational'; - } - - /** - * `extrinsicBaseWeight` changed from using system.extrinsicBaseWeight => system.blockWeights.perClass[weightInfoClass].baseExtrinsic - * in polkadot v0.8.27 due to this pr: https://github.com/paritytech/substrate/pull/6629 . - * https://github.com/paritytech/substrate-api-sidecar/issues/393 . - * https://github.com/polkadot-js/api/issues/2365 - */ - // This makes the compiler happy for below type guards - let extrinsicBaseWeight; - if (isExtBaseWeightValue(weights)) { - extrinsicBaseWeight = weights.extrinsicBaseWeight; - } else if (isPerClassValue(weights)) { - extrinsicBaseWeight = weights.perClass[weightInfoClass]?.baseExtrinsic; - } + if (!api.rpc.payment || !api.rpc.payment.queryInfo) { + extrinsics[idx].info = { + error: 'Rpc method payment::queryInfo is not available', + }; - if (!extrinsicBaseWeight) { - throw new InternalServerError('Could not find extrinsicBaseWeight'); + continue; } - const len = block.extrinsics[idx].encodedLength; - const weight = weightInfo.weight; - - const partialFee = calcFee.calc_fee( - BigInt(weight.toString()), - len, - extrinsicBaseWeight - ); + const { class: dispatchClass, partialFee } = + await api.rpc.payment.queryInfo(block.extrinsics[idx].toHex(), hash); extrinsics[idx].info = api.createType('RuntimeDispatchInfo', { - weight, - class: weightInfo.class, + weight: weightInfo.weight, + class: dispatchClass, partialFee: partialFee, }); } @@ -484,181 +435,6 @@ export class BlocksService extends AbstractService { }; } - /** - * Create calcFee from params or return `null` if calcFee cannot be created. - * - * @param api ApiPromise - * @param historicApi ApiDecoration to use for runtime specific querying - * @param parentHash Hash of the parent block - * @param block Block which the extrinsic is from - */ - private async createCalcFee( - api: ApiPromise, - historicApi: ApiDecoration<'promise'>, - parentHash: Hash, - block: Block - ): Promise { - const parentParentHash: Hash = await this.getParentParentHash( - api, - parentHash, - block - ); - - const version = await api.rpc.state.getRuntimeVersion(parentParentHash); - - const specName = version.specName.toString(); - const specVersion = version.specVersion.toNumber(); - - if (this.minCalcFeeRuntime && specVersion < this.minCalcFeeRuntime) { - return { - specVersion, - specName, - }; - } - - /** - * This will remain using the original api.query.*.*.at to retrieve the multiplier - * of the `parentHash` block. - */ - const multiplier = - await api.query.transactionPayment?.nextFeeMultiplier?.at(parentHash); - - const perByte = this.getPerByte(historicApi); - - const extrinsicBaseWeightExists = - historicApi.consts.system.extrinsicBaseWeight || - historicApi.consts.system.blockWeights.perClass.normal.baseExtrinsic; - const { weightToFee } = historicApi.consts.transactionPayment; - - if (!perByte || !extrinsicBaseWeightExists || !multiplier || !weightToFee) { - // This particular runtime version is not supported with fee calcs or - // does not have the necessay materials to build calcFee - return { - specVersion, - specName, - }; - } - - const coefficients = weightToFee.map((c) => { - return { - // Anything that could overflow Number.MAX_SAFE_INTEGER needs to be serialized - // to BigInt or string. - coeffInteger: c.coeffInteger.toString(10), - coeffFrac: c.coeffFrac.toNumber(), - degree: c.degree.toNumber(), - negative: c.negative, - }; - }); - - const weights = this.getWeight(historicApi); - - const calcFee = CalcFee.from_params( - coefficients, - multiplier.toString(10), - perByte.toString(10), - specName, - specVersion - ); - - return { - calcFee, - specName, - specVersion, - weights, - }; - } - - /** - * Retrieve the PerByte integer used to calculate fees. - * TransactionByteFee has been replaced with LengthToFee via runtime 9190. - * https://github.com/paritytech/polkadot/pull/5028 - * - * @param historicApi ApiDecoration to use for runtime specific querying - */ - private getPerByte( - historicApi: ApiDecoration<'promise'> - ): Balance | u128 | null { - const { transactionPayment } = historicApi.consts; - - if (transactionPayment?.transactionByteFee) { - return transactionPayment?.transactionByteFee as Balance; - } - - if (transactionPayment?.lengthToFee) { - return transactionPayment?.lengthToFee.toArray()[0].coeffInteger; - } - - return null; - } - - /** - * Get a formatted weight constant for the runtime corresponding to the given block hash. - * - * @param api ApiPromise - * @param blockHash Hash of a block in the runtime to get the extrinsic base weight(s) for - */ - private getWeight(historicApi: ApiDecoration<'promise'>): WeightValue { - const { - consts: { system }, - } = historicApi; - - let weightValue; - if (system.blockWeights?.perClass) { - const { normal, operational, mandatory } = system.blockWeights.perClass; - - const perClass = { - normal: { - baseExtrinsic: normal.baseExtrinsic.toBigInt(), - }, - operational: { - baseExtrinsic: operational.baseExtrinsic.toBigInt(), - }, - mandatory: { - baseExtrinsic: mandatory.baseExtrinsic.toBigInt(), - }, - }; - - weightValue = { perClass }; - } else if (system.extrinsicBaseWeight) { - weightValue = { - extrinsicBaseWeight: ( - system.extrinsicBaseWeight as unknown as AbstractInt - ).toBigInt(), - }; - } else { - throw new InternalServerError( - 'Could not find a extrinsic base weight in metadata' - ); - } - - return weightValue; - } - - /** - * The block where the runtime is deployed falsely proclaims it would - * be already using the new runtime. This workaround therefore uses the - * parent of the parent in order to determine the correct runtime under which - * this block was produced. - * - * @param api ApiPromise to use for rpc call - * @param parentHash Used to identify the runtime in a block - * @param block Used to make sure we dont - */ - private async getParentParentHash( - api: ApiPromise, - parentHash: Hash, - block: Block - ): Promise { - let parentParentHash: Hash; - if (block.header.number.toNumber() > 1) { - parentParentHash = (await api.rpc.chain.getHeader(parentHash)).parentHash; - } else { - parentParentHash = parentHash; - } - - return parentParentHash; - } - /** * Fetch events for the specified block. * diff --git a/src/services/test-helpers/responses/blocks/blocks789629.json b/src/services/test-helpers/responses/blocks/blocks789629.json index b7622706a..8431c63cb 100644 --- a/src/services/test-helpers/responses/blocks/blocks789629.json +++ b/src/services/test-helpers/responses/blocks/blocks789629.json @@ -448,7 +448,7 @@ "info": { "weight": "941325000000", "class": "Normal", - "partialFee": "1257000075" + "partialFee": "149000000" }, "era": { "mortalEra": [ @@ -1192,7 +1192,7 @@ "info": { "weight": "399480000", "class": "Normal", - "partialFee": "544000000" + "partialFee": "149000000" }, "era": { "mortalEra": [ diff --git a/src/types/chains-config/MetadataConsts.ts b/src/types/chains-config/MetadataConsts.ts deleted file mode 100644 index 42721ddf1..000000000 --- a/src/types/chains-config/MetadataConsts.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017-2022 Parity Technologies (UK) Ltd. -// This file is part of Substrate API Sidecar. -// -// Substrate API Sidecar is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -/** - * Given an extrinsic that is going to be sanitized. It is given one of the - * weight types for calculating fees. - */ -export type WeightValue = ExtBaseWeightValue | PerClassValue; - -/** - * Polkadot runtime versions before v27 - * Kusama runtime versions before v2027 - */ -export interface ExtBaseWeightValue { - extrinsicBaseWeight?: BigInt; -} - -export function isExtBaseWeightValue( - thing: unknown -): thing is ExtBaseWeightValue { - return typeof (thing as ExtBaseWeightValue)?.extrinsicBaseWeight === 'bigint'; -} - -/** - * Polkadot runtime versions after v26 - * Kusama runtime versions after v2026 - */ -export interface PerClassValue { - perClass: IPerClass; -} - -export function isPerClassValue(thing: unknown): thing is PerClassValue { - return ( - typeof (thing as PerClassValue)?.perClass?.normal.baseExtrinsic === - 'bigint' && - typeof (thing as PerClassValue)?.perClass?.operational.baseExtrinsic === - 'bigint' && - typeof (thing as PerClassValue)?.perClass?.mandatory.baseExtrinsic === - 'bigint' - ); -} - -export interface IPerClass { - normal: IWeightPerClass; - mandatory: IWeightPerClass; - operational: IWeightPerClass; -} - -export interface IWeightPerClass { - baseExtrinsic: BigInt; -} diff --git a/src/types/chains-config/index.ts b/src/types/chains-config/index.ts index 8f89936d6..ed118adb1 100644 --- a/src/types/chains-config/index.ts +++ b/src/types/chains-config/index.ts @@ -15,4 +15,3 @@ // along with this program. If not, see . export * from './ControllerConfig'; -export * from './MetadataConsts'; diff --git a/src/types/responses/Block.ts b/src/types/responses/Block.ts index b55b5c31d..47bc6d92c 100644 --- a/src/types/responses/Block.ts +++ b/src/types/responses/Block.ts @@ -18,9 +18,7 @@ import { Compact } from '@polkadot/types'; import { BlockHash, BlockNumber, Hash } from '@polkadot/types/interfaces'; import { AccountId } from '@polkadot/types/interfaces/runtime'; import { Codec } from '@polkadot/types/types'; -import { CalcFee } from '@substrate/calc'; -import { WeightValue } from '../chains-config'; import { IExtrinsic, ISanitizedEvent } from '.'; export interface IBlock { @@ -46,10 +44,3 @@ interface ILog { index: number; value: Codec; } - -export interface ICalcFee { - calcFee?: null | CalcFee; - specName: string; - specVersion: number; - weights?: WeightValue; -}