feat: bring priority to body detail
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.1.0-autogenerated.11438231719699111413",
|
||||
"version": "0.1.0-autogenerated.6048354284411761388",
|
||||
"name": "@polkadot-api/descriptors",
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -10,4 +10,5 @@ export const whitelist: Polkadot_peopleWhitelistEntry[] = [
|
||||
"tx.Balances.transfer_keep_alive",
|
||||
"query.Session.Validators",
|
||||
"query.Session.CurrentIndex",
|
||||
"api.TransactionPaymentApi.query_info",
|
||||
]
|
||||
|
||||
@@ -131,7 +131,7 @@ export const BlockBody: FC = () => {
|
||||
)}
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="tx" className="py-2">
|
||||
<ol>
|
||||
<ol className="space-y-2">
|
||||
{extrinsics.map((extrinsic, idx) => (
|
||||
<Extrinsic
|
||||
key={idx}
|
||||
|
||||
@@ -11,8 +11,9 @@ import { DecodedExtrinsic } from "@polkadot-api/tx-utils"
|
||||
import { Edit, FileSearch } from "lucide-react"
|
||||
import { Enum, HexString, SS58String } from "polkadot-api"
|
||||
import { toHex } from "polkadot-api/utils"
|
||||
import { FC, useEffect, useRef, useState } from "react"
|
||||
import { FC, ReactNode, useEffect, useRef, useState } from "react"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { PriorityValue } from "../../Extrinsics/Analyzer/Priority"
|
||||
import { SignedExtensions } from "./SignedExtensions"
|
||||
|
||||
export type ApplyExtrinsicEvent = SystemEvent & {
|
||||
@@ -35,20 +36,26 @@ export const Extrinsic: FC<{
|
||||
events.includes(highlightedEvent as ApplyExtrinsicEvent))
|
||||
),
|
||||
)
|
||||
const rawExtrinsic = toHex(extrinsic.raw)
|
||||
const txPayment =
|
||||
"extra" in extrinsic
|
||||
? (extrinsic.extra.ChargeAssetTxPayment ??
|
||||
extrinsic.extra.ChargeTxPayment)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<li className="p-2 border rounded mb-2 bg-card text-card-foreground">
|
||||
<div className="flex justify-between items-center">
|
||||
<li className="overflow-hidden rounded-lg border border-foreground/10 bg-card text-card-foreground">
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-2">
|
||||
<button
|
||||
onClick={() => setExpanded((e) => !e)}
|
||||
className="flex gap-1 items-center"
|
||||
className="flex min-w-0 items-center gap-1 text-left"
|
||||
>
|
||||
<ExpandBtn expanded={expanded} />
|
||||
{extrinsic.idx}. {extrinsic.call.type}.{extrinsic.call.value.type}
|
||||
</button>
|
||||
<div className="flex gap-2 items-center">
|
||||
<CopyBinary value={extrinsic.callData} />
|
||||
<Link to={`/extrinsics/analyzer#extrinsic=${toHex(extrinsic.raw)}`}>
|
||||
<Link to={`/extrinsics/analyzer#extrinsic=${rawExtrinsic}`}>
|
||||
<FileSearch size={15} />
|
||||
</Link>
|
||||
<Link to={"/extrinsics#data=" + toHex(extrinsic.callData)}>
|
||||
@@ -57,21 +64,38 @@ export const Extrinsic: FC<{
|
||||
</div>
|
||||
</div>
|
||||
{expanded ? (
|
||||
<div className="overflow-hidden">
|
||||
<div className="flex gap-2 items-center py-2">
|
||||
Extrinsic Hash: {shortStr(extrinsic.hash, 6)}{" "}
|
||||
<CopyText text={extrinsic.hash} binary />
|
||||
<div className="space-y-2 border-t border-foreground/10 px-2 py-2">
|
||||
<div className="grid gap-2 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)_auto]">
|
||||
{extrinsic.type === "signed" ? (
|
||||
<CompactBlock label="Signer">
|
||||
<Sender compact sender={extrinsic.address} />
|
||||
</CompactBlock>
|
||||
) : null}
|
||||
|
||||
<CompactBlock label="Extrinsic Hash">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate font-mono text-sm">
|
||||
{shortStr(extrinsic.hash, 6)}
|
||||
</span>
|
||||
<CopyText text={extrinsic.hash} binary />
|
||||
</div>
|
||||
</CompactBlock>
|
||||
|
||||
<CompactBlock label="Priority">
|
||||
<PriorityValue
|
||||
extrinsic={rawExtrinsic}
|
||||
txPayment={txPayment ?? {}}
|
||||
/>
|
||||
</CompactBlock>
|
||||
</div>
|
||||
|
||||
{extrinsic.type === "signed" && (
|
||||
<div>
|
||||
<Sender sender={extrinsic.address} />
|
||||
<CompactSection title="Call Payload">
|
||||
<div className="max-h-[80vh] overflow-auto">
|
||||
<JsonDisplay src={extrinsic.call.value.value} />
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-auto max-h-[80vh] p-2">
|
||||
<JsonDisplay src={extrinsic.call.value.value} />
|
||||
</div>
|
||||
<div className="p-2 overflow-auto max-h-[80vh] border-t">
|
||||
</CompactSection>
|
||||
|
||||
<CompactSection title={`Events (${events.length})`}>
|
||||
<ol className="flex flex-col gap-1">
|
||||
{events.map((evt, i) => (
|
||||
<EventDisplay
|
||||
@@ -82,11 +106,12 @@ export const Extrinsic: FC<{
|
||||
/>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</CompactSection>
|
||||
|
||||
{"extra" in extrinsic && (
|
||||
<div className="p-2 overflow-auto max-h-[80vh] border-t">
|
||||
<SignedExtensions extra={extrinsic.extra} />
|
||||
</div>
|
||||
<CompactSection title="Signed Extensions">
|
||||
<SignedExtensions extra={extrinsic.extra} title={false} />
|
||||
</CompactSection>
|
||||
)}
|
||||
</div>
|
||||
) : 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 && (
|
||||
<div className="flex gap-2 items-center py-2">
|
||||
Signer:
|
||||
<div
|
||||
className={
|
||||
compact ? "flex items-center gap-2" : "flex items-center gap-2 py-2"
|
||||
}
|
||||
>
|
||||
{value.startsWith("0x") ? (
|
||||
<EthAccountDisplay value={value} />
|
||||
) : (
|
||||
@@ -156,3 +185,27 @@ const Sender: React.FC<{
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const CompactSection: FC<{ title: string; children: ReactNode }> = ({
|
||||
title,
|
||||
children,
|
||||
}) => (
|
||||
<div className="rounded-lg border border-foreground/10 bg-foreground/3 px-2 py-2">
|
||||
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
{title}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const CompactBlock: FC<{ label: string; children: ReactNode }> = ({
|
||||
label,
|
||||
children,
|
||||
}) => (
|
||||
<div className="rounded-lg border border-foreground/10 bg-foreground/3 px-2 py-2">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
{label}
|
||||
</div>
|
||||
<div className="mt-1">{children}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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 ? (
|
||||
<InlineLine id={id} inlineContent={inlineJson} />
|
||||
) : (
|
||||
<ExpandableLine id={id} inlineContent={null}>
|
||||
@@ -77,10 +77,14 @@ const ExpandableLine: FC<
|
||||
return (
|
||||
<li className="space-y-2 rounded-lg border border-foreground/10 bg-foreground/5 px-3 py-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
<ExpandBtn expanded={expanded} onClick={() => setExpanded((e) => !e)} />
|
||||
<ExpandBtn
|
||||
className="shrink-0"
|
||||
expanded={expanded}
|
||||
onClick={() => setExpanded((e) => !e)}
|
||||
/>
|
||||
{id}
|
||||
{inlineContent ? (
|
||||
<div className="max-w-full whitespace-nowrap font-mono text-sm">
|
||||
<div className="max-w-full whitespace-nowrap font-mono text-sm overflow-hidden text-ellipsis">
|
||||
- {inlineContent}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -94,13 +98,13 @@ const InlineLine: FC<{ id: string; inlineContent?: ReactNode }> = ({
|
||||
id,
|
||||
inlineContent,
|
||||
}) => (
|
||||
<li className="flex flex-wrap items-center gap-2 rounded-lg border border-foreground/10 bg-foreground/5 px-3 py-2">
|
||||
<li className="flex items-center gap-2 rounded-lg border border-foreground/10 bg-foreground/5 px-3 py-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Dot size={16} />
|
||||
{id}
|
||||
</div>
|
||||
{inlineContent ? (
|
||||
<div className="max-w-full whitespace-nowrap font-mono text-sm">
|
||||
<div className="max-w-full whitespace-nowrap font-mono text-sm overflow-hidden text-ellipsis">
|
||||
- {inlineContent}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -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 (
|
||||
<BlockContext value={block}>
|
||||
@@ -109,6 +113,11 @@ export const ExtrinsicDecoder: FC<{
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<CallData
|
||||
call={decoded.call as TxCallData}
|
||||
callData={decoded.callData}
|
||||
/>
|
||||
|
||||
{decoded.type === "signed" ? (
|
||||
<SectionCard title="Signed Extensions">
|
||||
<SignedExtensions extra={decoded.extra} title={false} />
|
||||
@@ -121,12 +130,8 @@ export const ExtrinsicDecoder: FC<{
|
||||
</SectionCard>
|
||||
) : null}
|
||||
<SectionCard title="Priority Analysis">
|
||||
<AnalyzePriority extrinsic={extrinsic} />
|
||||
<AnalyzePriority extrinsic={extrinsic} txPayment={txPayment} />
|
||||
</SectionCard>
|
||||
<CallData
|
||||
call={decoded.call as TxCallData}
|
||||
callData={decoded.callData}
|
||||
/>
|
||||
</div>
|
||||
</BlockContext>
|
||||
)
|
||||
@@ -166,7 +171,7 @@ const CallData: FC<{ call: TxCallData; callData: Uint8Array }> = ({
|
||||
</SectionCard>
|
||||
)
|
||||
|
||||
export const extrinsicDecoder$ = merge(extDecoder$, analyzePriority$)
|
||||
export const extrinsicDecoder$ = extDecoder$
|
||||
|
||||
const SectionCard: FC<{
|
||||
children: ReactNode
|
||||
|
||||
@@ -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 (
|
||||
<div className="space-y-4">
|
||||
{priority ? (
|
||||
<div className="flex flex-col gap-2 border-b border-foreground/10 pb-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
Computed Priority
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
|
||||
{priority.priority.toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(18rem,0.9fr)]">
|
||||
<div className="rounded-lg border border-foreground/10 bg-foreground/5 p-3">
|
||||
<div className="flex flex-col gap-2 border-b border-foreground/10 pb-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
Inputs
|
||||
Computed Priority
|
||||
</div>
|
||||
<div className="mt-3 space-y-2">
|
||||
<DetailRow label="Tip" value={<TokenAmount>{tip}</TokenAmount>} />
|
||||
<DetailRow
|
||||
label="Partial Fee"
|
||||
value={<TokenAmount>{queryInfo.partial_fee}</TokenAmount>}
|
||||
/>
|
||||
<DetailRow label="Class" value={queryInfo.class.type} />
|
||||
<DetailRow label="Encoded Length" value={length.toLocaleString()} />
|
||||
<DetailRow
|
||||
label="Weight"
|
||||
value={`${(Number(queryInfo.weight.proof_size) / 1024).toLocaleString(undefined, { maximumSignificantDigits: 3 })} KB / ${(Number(queryInfo.weight.ref_time) / 1_000_000).toLocaleString(undefined, { maximumSignificantDigits: 3 })} ms`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-foreground/10 bg-foreground/5 p-3">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
Capacity
|
||||
</div>
|
||||
<div className="mt-3 space-y-2">
|
||||
<DetailRow
|
||||
label="Tx/Block by Length"
|
||||
value={
|
||||
priority ? priority.maxTxPerBlockLength.toLocaleString() : "N/A"
|
||||
}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Tx/Block by Weight"
|
||||
value={
|
||||
priority ? priority.maxTxPerBlockWeight.toLocaleString() : "N/A"
|
||||
}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Limited By"
|
||||
value={priority ? priority.limitingFactor : "N/A"}
|
||||
/>
|
||||
<div className="mt-1 font-mono text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
|
||||
{details?.priority.toLocaleString() ?? "…"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!priority ? (
|
||||
{details ? (
|
||||
<div className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(18rem,0.9fr)]">
|
||||
<div className="rounded-lg border border-foreground/10 bg-foreground/5 p-3">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
Inputs
|
||||
</div>
|
||||
<div className="mt-3 space-y-2">
|
||||
<DetailRow
|
||||
label="Tip"
|
||||
value={<TokenAmount>{details.tip}</TokenAmount>}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Fee"
|
||||
value={
|
||||
<TokenAmount>{details.queryInfo.partial_fee}</TokenAmount>
|
||||
}
|
||||
/>
|
||||
<DetailRow label="Class" value={details.queryInfo.class.type} />
|
||||
<DetailRow
|
||||
label="Encoded Length"
|
||||
value={length.toLocaleString()}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Weight"
|
||||
value={`${(Number(details.queryInfo.weight.proof_size) / 1024).toLocaleString(undefined, { maximumSignificantDigits: 3 })} KB / ${(Number(details.queryInfo.weight.ref_time) / 1_000_000).toLocaleString(undefined, { maximumSignificantDigits: 3 })} Mref`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-foreground/10 bg-foreground/5 p-3">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-foreground/50">
|
||||
Capacity
|
||||
</div>
|
||||
<div className="mt-3 space-y-2">
|
||||
<DetailRow
|
||||
label="Tx/Block by Length"
|
||||
value={details.maxTxPerBlockLength.toLocaleString()}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Tx/Block by Weight"
|
||||
value={details.maxTxPerBlockWeight.toLocaleString()}
|
||||
/>
|
||||
<DetailRow label="Limited By" value={details.limitingFactor} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-lg border border-foreground/10 bg-background/60 p-3 text-xs text-muted-foreground">
|
||||
Waiting for block limits to finish the priority calculation.
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 ? (
|
||||
<span className="font-mono text-sm text-foreground">
|
||||
{details.priority.toLocaleString()}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">…</span>
|
||||
)
|
||||
}
|
||||
|
||||
const DetailRow: FC<{ label: string; value: ReactNode }> = ({
|
||||
@@ -183,3 +113,99 @@ const DetailRow: FC<{ label: string; value: ReactNode }> = ({
|
||||
<div className="text-right font-mono text-sm text-foreground">{value}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
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<ReturnType<typeof getPriority>>
|
||||
|
||||
const usePriorityAnalysis = ({
|
||||
txPayment,
|
||||
extrinsic,
|
||||
blockHex,
|
||||
}: {
|
||||
txPayment?: { tip?: bigint }
|
||||
extrinsic: HexString
|
||||
blockHex?: string
|
||||
}) => {
|
||||
const [result, setResult] = useState<PriorityDetails | null>(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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user