From 0cdc49c4bec5b96b6a190e3c7cf5625d76e0ed08 Mon Sep 17 00:00:00 2001 From: Jaco Date: Sun, 12 Mar 2023 19:21:50 +0200 Subject: [PATCH] Adjust max batch size calc (w/ proofSize & older) (#8920) * Adjust max batch size calc (w/ proofSize & older) * Cleanups * Adjust fill ratio * Adjust, only check refTime * Comments * Clarify ratio further * Extract getKnown * Split size calc into function --- packages/react-hooks/src/types.ts | 12 ++- packages/react-hooks/src/useTxBatch.ts | 143 ++++++++++++++++++++++--- packages/react-hooks/src/useWeight.ts | 11 +- 3 files changed, 142 insertions(+), 24 deletions(-) diff --git a/packages/react-hooks/src/types.ts b/packages/react-hooks/src/types.ts index de5dbdd6af..0333845257 100644 --- a/packages/react-hooks/src/types.ts +++ b/packages/react-hooks/src/types.ts @@ -7,7 +7,7 @@ import type { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/ap import type { DisplayedJudgement } from '@polkadot/react-components/types'; import type { AccountId, Balance, BlockNumber, Call, Exposure, Hash, RewardDestination, SessionIndex, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces'; import type { PalletPreimageRequestStatus } from '@polkadot/types/lookup'; -import type { IExtrinsic, Registry } from '@polkadot/types/types'; +import type { ICompact, IExtrinsic, INumber, Registry } from '@polkadot/types/types'; import type { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; import type { BN } from '@polkadot/util'; import type { HexString } from '@polkadot/util/types'; @@ -196,3 +196,13 @@ export interface PreimageBytes { export interface Preimage extends PreimageBytes, PreimageStatus { // just the interfaces above } + +export interface V2WeightConstruct { + refTime: BN | ICompact; + proofSize?: BN | ICompact; +} + +export interface WeightResult { + v1Weight: BN; + v2Weight: V2WeightConstruct; +} diff --git a/packages/react-hooks/src/useTxBatch.ts b/packages/react-hooks/src/useTxBatch.ts index ec3f1b43b0..4f455e31c8 100644 --- a/packages/react-hooks/src/useTxBatch.ts +++ b/packages/react-hooks/src/useTxBatch.ts @@ -4,17 +4,135 @@ import type { ApiPromise } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { Weight } from '@polkadot/types/interfaces'; -import type { BatchOptions, BatchType } from './types.js'; +import type { BN } from '@polkadot/util'; +import type { BatchOptions, BatchType, WeightResult } from './types.js'; import { useEffect, useMemo, useState } from 'react'; -import { isFunction, nextTick } from '@polkadot/util'; +import { BN_HUNDRED, BN_ZERO, bnMax, bnMin, bnToBn, isCompact, isFunction, nextTick } from '@polkadot/util'; import { createNamedHook } from './createNamedHook.js'; import { useAccounts } from './useAccounts.js'; import { useApi } from './useApi.js'; import { convertWeight } from './useWeight.js'; +interface BNWeight { + proofSize: BN; + refTime: BN; +} + +type WeightSimple = WeightResult['v2Weight'] | BNWeight; + +interface Known { + baseExtrinsic: BNWeight | null; + maxBlock: BNWeight; + maxExtrinsic: BNWeight | null; +} + +// converts a weight construct to only contain BN values +function bnWeight (a: WeightSimple): BNWeight { + return { + proofSize: a.proofSize + ? bnToBn( + isCompact(a.proofSize) + ? a.proofSize.unwrap() + : a.proofSize + ) + : BN_ZERO, + refTime: bnToBn( + isCompact(a.refTime) + ? a.refTime.unwrap() + : a.refTime + ) + }; +} + +// subtract 2 BN-only weight values +function weightSub (_a: WeightSimple, _b: WeightSimple): BNWeight { + const a = bnWeight(_a); + const b = bnWeight(_b); + + return { + proofSize: bnMax(BN_ZERO, a.proofSize.sub(b.proofSize)), + refTime: bnMax(BN_ZERO, a.refTime.sub(b.refTime)) + }; +} + +// divide 2 BN-only weight values +function weightDiv (_a: WeightSimple, _b: WeightSimple): number { + const a = bnWeight(_a); + const b = bnWeight(_b); + const r = { + proofSize: b.proofSize.isZero() + ? BN_ZERO + : bnMax(BN_ZERO, a.proofSize.mul(BN_HUNDRED).div(b.proofSize)), + refTime: b.refTime.isZero() + ? BN_ZERO + : bnMax(BN_ZERO, a.refTime.mul(BN_HUNDRED).div(b.refTime)) + }; + + return ( + r.proofSize.isZero() + ? r.refTime.toNumber() + : bnMin(r.proofSize, r.refTime).toNumber() + ) / 100; +} + +function getKnown (api: ApiPromise): Known { + return { + baseExtrinsic: api.consts.system.blockWeights + ? bnWeight( + convertWeight( + api.consts.system.blockWeights.perClass.normal.baseExtrinsic + ).v2Weight + ) + : null, + maxBlock: bnWeight( + convertWeight( + api.consts.system.blockWeights + ? api.consts.system.blockWeights.maxBlock + : api.consts.system.maximumBlockWeight as Weight + ).v2Weight + ), + maxExtrinsic: api.consts.system.blockWeights && api.consts.system.blockWeights.perClass.normal.maxExtrinsic.isSome + ? bnWeight( + convertWeight( + api.consts.system.blockWeights.perClass.normal.maxExtrinsic.unwrap() + ).v2Weight + ) + : null + }; +} + +function getBatchSize ({ v1Weight, v2Weight }: WeightResult, { baseExtrinsic, maxBlock, maxExtrinsic }: Known): number { + let div = 0; + + // for newer chains we will try and calculate the size based on the supplied constants + // (At least maxExtrinsic is Option<...>, hence also having the fallback ratio) + if (baseExtrinsic && maxExtrinsic) { + // 65 div 75 below is around 86% of space, use same safety ratio here + // (Since we also have a max total limit for normal, this ensure faster + // throughput when the chain is busy at the expense of having less txs + // per batch - it does _eventually_ go through without the ratio) + div = Math.floor( + 0.85 * weightDiv( + weightSub(maxExtrinsic, baseExtrinsic), + weightSub(v2Weight, baseExtrinsic) + ) + ); + } + + // If we don't have a size calculation above, we create the extrinsic with a fallback + // of up to 65% of the block weight (applied here as 64 for a safety margin) + // (This is based on the Kusama/Polkadot 75% allowance for all extrinsics) + return div || Math.floor( + maxBlock.refTime + .muln(64) + .div(v1Weight) + .toNumber() / 100 + ); +} + function createBatches (api: ApiPromise, txs: SubmittableExtrinsic<'promise'>[], batchSize: number, type: BatchType = 'default'): SubmittableExtrinsic<'promise'>[] { if (batchSize === 1 || !isFunction(api.tx.utility?.batch)) { return txs; @@ -44,7 +162,12 @@ function createBatches (api: ApiPromise, txs: SubmittableExtrinsic<'promise'>[], function useTxBatchImpl (txs?: SubmittableExtrinsic<'promise'>[] | null | false, options?: BatchOptions): SubmittableExtrinsic<'promise'>[] | null { const { api } = useApi(); const { allAccounts } = useAccounts(); - const [batchSize, setBatchSize] = useState(() => Math.floor(options?.max || 64)); + const [batchSize, setBatchSize] = useState(() => Math.floor(options?.max || 4)); + + const known = useMemo( + () => getKnown(api), + [api] + ); useEffect((): void => { txs && txs.length && allAccounts[0] && txs[0].hasPaymentInfo && @@ -52,27 +175,17 @@ function useTxBatchImpl (txs?: SubmittableExtrinsic<'promise'>[] | null | false, try { const paymentInfo = await txs[0].paymentInfo(allAccounts[0]); const weight = convertWeight(paymentInfo.weight); - const maxBlock = convertWeight( - api.consts.system.blockWeights - ? api.consts.system.blockWeights.maxBlock - : api.consts.system.maximumBlockWeight as Weight - ); setBatchSize((prev) => weight.v1Weight.isZero() ? prev - : Math.floor( - maxBlock.v1Weight - .muln(64) // 65% of the block weight on a single extrinsic (64 for safety) - .div(weight.v1Weight) - .toNumber() / 100 - ) + : getBatchSize(weight, known) ); } catch (error) { console.error(error); } }); - }, [allAccounts, api, options, txs]); + }, [allAccounts, api, known, options, txs]); return useMemo( () => txs && txs.length diff --git a/packages/react-hooks/src/useWeight.ts b/packages/react-hooks/src/useWeight.ts index 1e7528668e..3d811b9327 100644 --- a/packages/react-hooks/src/useWeight.ts +++ b/packages/react-hooks/src/useWeight.ts @@ -4,6 +4,7 @@ import type { Call } from '@polkadot/types/interfaces'; import type { ICompact, INumber } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; +import type { V2WeightConstruct, WeightResult } from './types'; import { useEffect, useState } from 'react'; @@ -20,15 +21,9 @@ interface V2Weight { proofSize: ICompact; } -interface V2WeightConstruct { - refTime: BN | ICompact; -} - -interface Result { +interface Result extends WeightResult { encodedCallLength: number; isWeightV2: boolean; - v1Weight: BN; - v2Weight: V2WeightConstruct; weight: BN | V2WeightConstruct; } @@ -43,7 +38,7 @@ const EMPTY_STATE: Partial = { }; // return both v1 & v2 weight structures (would depend on actual use) -export function convertWeight (weight: V1Weight | V2Weight): { v1Weight: BN, v2Weight: V2WeightConstruct } { +export function convertWeight (weight: V1Weight | V2Weight): WeightResult { if ((weight as V2Weight).proofSize) { // V2 weight const refTime = (weight as V2Weight).refTime.toBn();