From dac53e22e0b53ccd29b80d1e8e411dfc84ee09b0 Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Thu, 7 May 2026 09:21:47 +0200 Subject: [PATCH] feat: bring priority to body detail --- .papi/descriptors/package.json | 2 +- .papi/whitelist.ts | 1 + src/pages/Explorer/Detail/BlockBody.tsx | 2 +- src/pages/Explorer/Detail/Extrinsic.tsx | 101 ++++-- .../Explorer/Detail/SignedExtensions.tsx | 14 +- .../Extrinsics/Analyzer/ExtrinsicDecoder.tsx | 19 +- src/pages/Extrinsics/Analyzer/Priority.tsx | 324 ++++++++++-------- 7 files changed, 276 insertions(+), 187 deletions(-) diff --git a/.papi/descriptors/package.json b/.papi/descriptors/package.json index 3462628..04c04af 100644 --- a/.papi/descriptors/package.json +++ b/.papi/descriptors/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-autogenerated.11438231719699111413", + "version": "0.1.0-autogenerated.6048354284411761388", "name": "@polkadot-api/descriptors", "files": [ "dist" diff --git a/.papi/whitelist.ts b/.papi/whitelist.ts index 6ef714c..7c85182 100644 --- a/.papi/whitelist.ts +++ b/.papi/whitelist.ts @@ -10,4 +10,5 @@ export const whitelist: Polkadot_peopleWhitelistEntry[] = [ "tx.Balances.transfer_keep_alive", "query.Session.Validators", "query.Session.CurrentIndex", + "api.TransactionPaymentApi.query_info", ] diff --git a/src/pages/Explorer/Detail/BlockBody.tsx b/src/pages/Explorer/Detail/BlockBody.tsx index 61181f2..621387a 100644 --- a/src/pages/Explorer/Detail/BlockBody.tsx +++ b/src/pages/Explorer/Detail/BlockBody.tsx @@ -131,7 +131,7 @@ export const BlockBody: FC = () => { )} -
    +
      {extrinsics.map((extrinsic, idx) => ( -
      +
    1. +
      - + @@ -57,21 +64,38 @@ export const Extrinsic: FC<{
      {expanded ? ( -
      -
      - Extrinsic Hash: {shortStr(extrinsic.hash, 6)}{" "} - +
      +
      + {extrinsic.type === "signed" ? ( + + + + ) : null} + + +
      + + {shortStr(extrinsic.hash, 6)} + + +
      +
      + + + +
      - {extrinsic.type === "signed" && ( -
      - + +
      +
      - )} -
      - -
      -
      + + +
        {events.map((evt, i) => ( ))}
      -
      +
      + {"extra" in extrinsic && ( -
      - -
      + + + )}
      ) : null} @@ -141,12 +166,16 @@ export const senderToAddress = ( const Sender: React.FC<{ sender: Enum<{ Id: SS58String }> | SS58String | HexString -}> = ({ sender }) => { + compact?: boolean +}> = ({ sender, compact }) => { const value = senderToAddress(sender) return ( value && ( -
      - Signer: +
      {value.startsWith("0x") ? ( ) : ( @@ -156,3 +185,27 @@ const Sender: React.FC<{ ) ) } + +const CompactSection: FC<{ title: string; children: ReactNode }> = ({ + title, + children, +}) => ( +
      +
      + {title} +
      + {children} +
      +) + +const CompactBlock: FC<{ label: string; children: ReactNode }> = ({ + label, + children, +}) => ( +
      +
      + {label} +
      +
      {children}
      +
      +) diff --git a/src/pages/Explorer/Detail/SignedExtensions.tsx b/src/pages/Explorer/Detail/SignedExtensions.tsx index b574de2..3bfe6b9 100644 --- a/src/pages/Explorer/Detail/SignedExtensions.tsx +++ b/src/pages/Explorer/Detail/SignedExtensions.tsx @@ -47,7 +47,7 @@ const SignedExtension: FC<{ id: string; value: unknown }> = ({ id, value }) => { } const inlineJson = JSON.stringify(value, jsonSerialize) - return !inlineJson || inlineJson.length < 40 ? ( + return !inlineJson || inlineJson.length < 30 ? ( ) : ( @@ -77,10 +77,14 @@ const ExpandableLine: FC< return (
    2. - setExpanded((e) => !e)} /> + setExpanded((e) => !e)} + /> {id} {inlineContent ? ( -
      +
      - {inlineContent}
      ) : null} @@ -94,13 +98,13 @@ const InlineLine: FC<{ id: string; inlineContent?: ReactNode }> = ({ id, inlineContent, }) => ( -
    3. +
    4. {id}
      {inlineContent ? ( -
      +
      - {inlineContent}
      ) : null} diff --git a/src/pages/Extrinsics/Analyzer/ExtrinsicDecoder.tsx b/src/pages/Extrinsics/Analyzer/ExtrinsicDecoder.tsx index 0c84a00..f818534 100644 --- a/src/pages/Extrinsics/Analyzer/ExtrinsicDecoder.tsx +++ b/src/pages/Extrinsics/Analyzer/ExtrinsicDecoder.tsx @@ -14,7 +14,7 @@ import { fromHex, toHex } from "polkadot-api/utils" import { FC, ReactNode, useMemo } from "react" import { map, merge, switchMap } from "rxjs" import { senderToAddress } from "../../Explorer/Detail/Extrinsic" -import { AnalyzePriority, analyzePriority$ } from "./Priority" +import { AnalyzePriority } from "./Priority" import { selectedBlock$, selectedBlockHex$ } from "./selectedBlock" const extDecoder$ = selectedBlock$.pipeState( @@ -72,6 +72,10 @@ export const ExtrinsicDecoder: FC<{ const decoded = decodeResult.value const signerAddress = decoded.type === "signed" ? senderToAddress(decoded.address) : null + const txPayment = + "extra" in decoded + ? (decoded.extra.ChargeAssetTxPayment ?? decoded.extra.ChargeTxPayment) + : null return ( @@ -109,6 +113,11 @@ export const ExtrinsicDecoder: FC<{
      + + {decoded.type === "signed" ? ( @@ -121,12 +130,8 @@ export const ExtrinsicDecoder: FC<{ ) : null} - + -
    5. ) @@ -166,7 +171,7 @@ const CallData: FC<{ call: TxCallData; callData: Uint8Array }> = ({ ) -export const extrinsicDecoder$ = merge(extDecoder$, analyzePriority$) +export const extrinsicDecoder$ = extDecoder$ const SectionCard: FC<{ children: ReactNode diff --git a/src/pages/Extrinsics/Analyzer/Priority.tsx b/src/pages/Extrinsics/Analyzer/Priority.tsx index 7dc08ab..f3750b7 100644 --- a/src/pages/Extrinsics/Analyzer/Priority.tsx +++ b/src/pages/Extrinsics/Analyzer/Priority.tsx @@ -1,177 +1,107 @@ +import { TokenAmount } from "@/components/TokenAmount" +import { BlockContext } from "@/pages/Explorer/Detail/blockContext" import { client$ } from "@/state/chains/chain.state" import { polkadot_people } from "@polkadot-api/descriptors" -import { state, useStateObservable } from "@react-rxjs/core" import { Binary, HexString } from "polkadot-api" -import { FC, ReactNode, useEffect, useState } from "react" -import { combineLatest, firstValueFrom, switchMap } from "rxjs" -import { selectedBlockHex$ } from "./selectedBlock" -import { TokenAmount } from "@/components/TokenAmount" - -const maxBlockSize$ = state( - combineLatest([selectedBlockHex$, client$]).pipe( - switchMap(([selectedBlock, client]) => - Promise.all([ - client.getTypedApi(polkadot_people).constants.System.BlockLength({ - at: selectedBlock, - }), - client.getTypedApi(polkadot_people).constants.System.BlockWeights({ - at: selectedBlock, - }), - ]).then( - ([length, weights]) => ({ - length: length.max, - maxWeight: weights.max_block, - }), - () => null, - ), - ), - ), -) +import { FC, ReactNode, useContext, useEffect, useState } from "react" export const AnalyzePriority: FC<{ txPayment?: { tip?: bigint } extrinsic: HexString }> = ({ txPayment, extrinsic }) => { - const blockHex = useStateObservable(selectedBlockHex$) - const maxBlockSize = useStateObservable(maxBlockSize$) - const [queryInfo, setQueryInfo] = useState<{ - weight: Weight - class: { type: "Normal" | "Operational" | "Mandatory" } - partial_fee: bigint - } | null>(null) - const length = (extrinsic.length - 2) / 8 - const tip = txPayment?.tip ?? 0n - - useEffect(() => { - let cancelled = false - ;(async () => { - setQueryInfo(null) - const client = await firstValueFrom(client$) - const result = await client - .getUnsafeApi() - .apis.TransactionPaymentApi.query_info( - Binary.fromOpaque(extrinsic), - length, - { - at: blockHex, - }, - ) - if (!cancelled) { - setQueryInfo(result as any) - } - })() - return () => { - cancelled = true - } - }, [extrinsic, blockHex, length]) - - if (!queryInfo) return null - - const priority = (() => { - if (!maxBlockSize) return null - const maxLength = - maxBlockSize.length[ - queryInfo.class.type.toLocaleLowerCase() as keyof typeof maxBlockSize.length - ] - - const maxTxPerBlockWeight = divWeight( - maxBlockSize.maxWeight, - queryInfo.weight, - ) - const maxTxPerBlockLength = BigInt(Math.floor(maxLength / length)) - const maxTxPerBlock = - maxTxPerBlockWeight < maxTxPerBlockLength - ? maxTxPerBlockWeight - : maxTxPerBlockLength - const priority = (tip + 1n) * maxTxPerBlock - const limitingFactor = - maxTxPerBlockWeight < maxTxPerBlockLength ? "Weight" : "Length" - - return { - priority, - maxTxPerBlockLength, - maxTxPerBlockWeight, - maxTxPerBlock, - limitingFactor, - } - })() + const blockHex = useContext(BlockContext)?.hash + const details = usePriorityAnalysis({ + txPayment, + extrinsic, + blockHex, + }) return (
      - {priority ? ( -
      -
      -
      - Computed Priority -
      -
      - {priority.priority.toLocaleString()} -
      -
      -
      - ) : null} - -
      -
      +
      +
      - Inputs + Computed Priority
      -
      - {tip}} /> - {queryInfo.partial_fee}} - /> - - - -
      -
      - -
      -
      - Capacity -
      -
      - - - +
      + {details?.priority.toLocaleString() ?? "…"}
      - {!priority ? ( + {details ? ( +
      +
      +
      + Inputs +
      +
      + {details.tip}} + /> + {details.queryInfo.partial_fee} + } + /> + + + +
      +
      + +
      +
      + Capacity +
      +
      + + + +
      +
      +
      + ) : (
      Waiting for block limits to finish the priority calculation.
      - ) : null} + )}
      ) } -export const analyzePriority$ = maxBlockSize$ +export const PriorityValue: FC<{ + txPayment?: { tip?: bigint } + extrinsic: HexString +}> = ({ txPayment, extrinsic }) => { + const blockHex = useContext(BlockContext)?.hash + const details = usePriorityAnalysis({ + txPayment, + extrinsic, + blockHex, + }) -type Weight = { ref_time: bigint; proof_size: bigint } -const divWeight = (a: Weight, b: Weight) => { - const ref_time = b.ref_time === 0n ? 1n : a.ref_time / b.ref_time - const proof_size = b.proof_size === 0n ? 1n : a.proof_size / b.proof_size - return ref_time < proof_size ? ref_time : proof_size + return details ? ( + + {details.priority.toLocaleString()} + + ) : ( + + ) } const DetailRow: FC<{ label: string; value: ReactNode }> = ({ @@ -183,3 +113,99 @@ const DetailRow: FC<{ label: string; value: ReactNode }> = ({
      {value}
      ) + +const getPriority = async ( + extrinsic: HexString, + txPayment: { tip?: bigint } | undefined, + at?: HexString, +) => { + const client = await client$.getValue() + const typedApi = client.getTypedApi(polkadot_people) + + const extLength = (extrinsic.length - 2) / 8 + const tip = txPayment?.tip ?? 0n + + try { + const [length, weights, queryInfo] = await Promise.all([ + typedApi.constants.System.BlockLength({ + at, + }), + typedApi.constants.System.BlockWeights({ + at, + }), + typedApi.apis.TransactionPaymentApi.query_info( + Binary.fromOpaque(extrinsic), + extLength, + { + at, + }, + ), + ]) + + const maxLength = + length.max[ + queryInfo.class.type.toLocaleLowerCase() as keyof typeof length.max + ] + + const maxTxPerBlockWeight = divWeight(weights.max_block, queryInfo.weight) + const maxTxPerBlockLength = BigInt(Math.floor(maxLength / extLength)) + const maxTxPerBlock = + maxTxPerBlockWeight < maxTxPerBlockLength + ? maxTxPerBlockWeight + : maxTxPerBlockLength + const priority = (tip + 1n) * maxTxPerBlock + const limitingFactor = + maxTxPerBlockWeight < maxTxPerBlockLength ? "Weight" : "Length" + + return { + priority, + length: extLength, + queryInfo, + tip, + maxTxPerBlockLength, + maxTxPerBlockWeight, + maxTxPerBlock, + limitingFactor, + } + } catch (ex) { + console.error(ex) + if (at) { + // Might be the block was already unpinned and no archive support, try with latest block + return getPriority(extrinsic, txPayment) + } + return null + } +} +type PriorityDetails = Awaited> + +const usePriorityAnalysis = ({ + txPayment, + extrinsic, + blockHex, +}: { + txPayment?: { tip?: bigint } + extrinsic: HexString + blockHex?: string +}) => { + const [result, setResult] = useState(null) + + useEffect(() => { + let cancelled = false + getPriority(extrinsic, txPayment, blockHex).then((r) => { + if (cancelled) return + setResult(r) + }) + return () => { + cancelled = true + } + }, [blockHex, extrinsic, txPayment]) + + return result +} + +type Weight = { ref_time: bigint; proof_size: bigint } +const divWeight = (a: Weight, b: Weight) => { + const ref_time = b.ref_time === 0n ? 1n : a.ref_time / b.ref_time + const proof_size = b.proof_size === 0n ? 1n : a.proof_size / b.proof_size + return ref_time < proof_size ? ref_time : proof_size +}