feat: show digest information in block detail (#152)
* feat: decode digest from header * separate into summary + detail * break down each digest into its own file
This commit is contained in:
@@ -1,15 +1,7 @@
|
||||
import { blockInfoState$, BlockState, finalized$ } from "@/state/block.state"
|
||||
import { client$ } from "@/state/chains/chain.state"
|
||||
import { Polkadot_people } from "@polkadot-api/descriptors"
|
||||
import {
|
||||
_void,
|
||||
Bytes,
|
||||
HexString,
|
||||
Struct,
|
||||
u32,
|
||||
u64,
|
||||
Variant,
|
||||
} from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "@polkadot-api/substrate-bindings"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import { BlockHeader, PolkadotClient } from "polkadot-api"
|
||||
import { AddressIdentity } from "polkahub"
|
||||
@@ -24,6 +16,8 @@ import {
|
||||
switchMap,
|
||||
take,
|
||||
} from "rxjs"
|
||||
import { decodeAuraPreRuntime } from "./digests/aura"
|
||||
import { decodeBabePreRuntime } from "./digests/babe"
|
||||
|
||||
const validatorCache: WeakMap<
|
||||
PolkadotClient,
|
||||
@@ -108,24 +102,6 @@ export const BlockAuthor: FC<{ hash: HexString; header: BlockHeader }> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const DigestWithVRF = Struct({
|
||||
authority_index: u32,
|
||||
slot: u64,
|
||||
vrf_signature: Struct({
|
||||
pre_output: Bytes(32),
|
||||
proof: Bytes(64),
|
||||
}),
|
||||
})
|
||||
const BabePreDigest = Variant({
|
||||
Uknown: _void,
|
||||
Primary: DigestWithVRF,
|
||||
SecondaryPlain: Struct({
|
||||
authority_index: u32,
|
||||
slot: u64,
|
||||
}),
|
||||
SecondaryVRF: DigestWithVRF,
|
||||
})
|
||||
|
||||
const getAuthorityIdx = (header: BlockHeader) => {
|
||||
const preRuntime = header.digests.find(
|
||||
(d) => d.type === "preRuntime" && ["BABE", "aura"].includes(d.value.engine),
|
||||
@@ -138,12 +114,12 @@ const getAuthorityIdx = (header: BlockHeader) => {
|
||||
|
||||
switch (preRuntime?.engine) {
|
||||
case "BABE": {
|
||||
const babe = BabePreDigest.dec(preRuntime.payload)
|
||||
if (!babe.value) return null
|
||||
const babe = decodeBabePreRuntime(preRuntime.payload)
|
||||
if (!babe?.value) return null
|
||||
return BigInt(babe.value.authority_index)
|
||||
}
|
||||
case "aura": {
|
||||
return u64.dec(preRuntime.payload)
|
||||
return decodeAuraPreRuntime(preRuntime.payload)?.slot ?? null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,16 @@ import {
|
||||
blocksByHeight$,
|
||||
BlockState,
|
||||
} from "@/state/block.state"
|
||||
import { shortStr } from "@/utils"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import { combineKeys } from "@react-rxjs/utils"
|
||||
import { BlockHeader, HexString } from "polkadot-api"
|
||||
import { FC, ReactNode, useContext } from "react"
|
||||
import { filter, map, startWith, switchMap, take } from "rxjs"
|
||||
import { AddBlockToWorkspace } from "../ExplorerWorkspaceEntry"
|
||||
import { BlockAuthor } from "./BlockAuthor"
|
||||
import { BlockStatusIcon, statusText } from "./BlockState"
|
||||
import { DigestDetails } from "./DigestDetails"
|
||||
|
||||
export const BlockInfoView: FC = () => {
|
||||
const block = useContext(BlockContext)
|
||||
@@ -46,20 +49,34 @@ export const BlockInfoView: FC = () => {
|
||||
</div>
|
||||
|
||||
{block.header && (
|
||||
<div className="grid gap-4 grid-cols-1 md:grid-cols-2">
|
||||
<DetailTile label="Roots">
|
||||
<HashPreview title="state" value={block.header.stateRoot} />
|
||||
<HashPreview title="extrinsic" value={block.header.extrinsicRoot} />
|
||||
</DetailTile>
|
||||
<DetailTile label="Block author">
|
||||
<BlockAuthor hash={block.hash} header={block.header} />
|
||||
</DetailTile>
|
||||
</div>
|
||||
<HeaderDetails hash={block.hash as HexString} header={block.header} />
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const HeaderDetails: FC<{ hash: HexString; header: BlockHeader }> = ({
|
||||
hash,
|
||||
header,
|
||||
}) => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<DetailTile label="Header commitments">
|
||||
<div className="space-y-3">
|
||||
<HashPreview title="Storage root" value={header.stateRoot} />
|
||||
<HashPreview title="Extrinsics root" value={header.extrinsicRoot} />
|
||||
</div>
|
||||
</DetailTile>
|
||||
<DetailTile label="Block author">
|
||||
<BlockAuthor hash={hash} header={header} />
|
||||
</DetailTile>
|
||||
<DetailTile label="Digest details" className="md:col-span-2">
|
||||
<DigestDetails header={header} />
|
||||
</DetailTile>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const StatusChip: FC<{ state: BlockState }> = ({ state }) => (
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-foreground/20 bg-foreground/2 px-4 py-2 text-sm font-medium">
|
||||
<BlockStatusIcon size={20} state={state} />
|
||||
@@ -71,17 +88,23 @@ const HashPreview: FC<{ title: string; value: string }> = ({
|
||||
title,
|
||||
value,
|
||||
}) => (
|
||||
<div className="font-mono text-sm">
|
||||
{title}: {value.slice(0, 18)}…
|
||||
<CopyText className="align-middle" text={value} binary />
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="w-28 shrink-0 text-xs text-foreground/55">{title}</span>
|
||||
<span className="min-w-0 truncate font-mono text-sm text-foreground">
|
||||
{shortStr(value, 10)}
|
||||
</span>
|
||||
<CopyText className="shrink-0 text-foreground/65" text={value} binary />
|
||||
</div>
|
||||
)
|
||||
|
||||
const DetailTile: FC<{ label: string; children: ReactNode }> = ({
|
||||
label,
|
||||
children,
|
||||
}) => (
|
||||
<div className="rounded-xl border border-foreground/10 bg-foreground/2 p-4">
|
||||
const DetailTile: FC<{
|
||||
label: string
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}> = ({ label, children, className = "" }) => (
|
||||
<div
|
||||
className={`rounded-lg border border-foreground/10 bg-foreground/2 p-4 ${className}`}
|
||||
>
|
||||
<p className="text-xs uppercase tracking-widest text-foreground/60">
|
||||
{label}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { ExpandBtn } from "@/components/Expand"
|
||||
import { BlockHeader, HexString } from "polkadot-api"
|
||||
import { toHex } from "polkadot-api/utils"
|
||||
import { FC, ReactNode, useState } from "react"
|
||||
import { auraPreDigest } from "./digests/aura"
|
||||
import { babePreDigest } from "./digests/babe"
|
||||
import { beefyConsensus } from "./digests/beef"
|
||||
import { cmlsPreDigest } from "./digests/cmls"
|
||||
import { HexDisplay } from "./digests/components"
|
||||
import { fronConsensus } from "./digests/fron"
|
||||
import { ismpConsensus } from "./digests/ismp"
|
||||
import { istmConsensus } from "./digests/istm"
|
||||
import { randPreDigest } from "./digests/rand"
|
||||
import { rsprConsensus } from "./digests/rspr"
|
||||
|
||||
export const DigestDetails: FC<{ header: BlockHeader }> = ({ header }) => {
|
||||
if (!header.digests.length) return <span className="text-slate-400">N/A</span>
|
||||
|
||||
return (
|
||||
<ol className="divide-y divide-foreground/10">
|
||||
{header.digests.map((digest, idx) => (
|
||||
<DigestRow key={idx} digest={digest} />
|
||||
))}
|
||||
</ol>
|
||||
)
|
||||
}
|
||||
|
||||
const DigestRow: FC<{ digest: HeaderDigest }> = ({ digest }) => {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const view = getDigestView(digest)
|
||||
const canExpand = view.details != null
|
||||
|
||||
const rawValue =
|
||||
digest.type === "other"
|
||||
? toHex(digest.value)
|
||||
: (digest.value?.payload ?? null)
|
||||
const engine = digest.type === "other" ? null : (digest.value?.engine ?? null)
|
||||
|
||||
return (
|
||||
<li className="py-2.5 last:pb-0 space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 sm:grid sm:grid-cols-[2rem_6rem_3rem_minmax(0,1fr)_auto] sm:gap-2">
|
||||
{canExpand ? (
|
||||
<div>
|
||||
<button
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-foreground/55 hover:bg-foreground/5 hover:text-foreground"
|
||||
onClick={() => setExpanded((value) => !value)}
|
||||
title={expanded ? "Hide details" : "Show details"}
|
||||
type="button"
|
||||
>
|
||||
<ExpandBtn expanded={expanded} />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<DigestTypeLabel type={digest.type} />
|
||||
</div>
|
||||
<span className="font-mono text-sm text-foreground/75">
|
||||
{engine ?? "-"}
|
||||
</span>
|
||||
{view.summary ? (
|
||||
<div className="flex items-baseline gap-2 text-sm text-foreground">
|
||||
<span className="shrink-0 text-foreground/55">
|
||||
{view.summary.label}
|
||||
</span>
|
||||
{view.summary.value}
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div className="text-sm hidden sm:block">
|
||||
{rawValue ? <HexDisplay value={rawValue} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{canExpand && expanded ? (
|
||||
<div className="rounded-md bg-foreground/5 px-3 py-2 sm:ml-48">
|
||||
{view.details}
|
||||
</div>
|
||||
) : null}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
type DigestView = {
|
||||
summary: {
|
||||
label: string
|
||||
value: ReactNode
|
||||
} | null
|
||||
details: ReactNode | null
|
||||
}
|
||||
|
||||
const getDigestView = (digest: HeaderDigest): DigestView => {
|
||||
if (digest.type === "other" || digest.type === "runtimeUpdated")
|
||||
return {
|
||||
summary: null,
|
||||
details: null,
|
||||
}
|
||||
|
||||
const decoder = digestDecoders[digest.type][digest.value.engine]
|
||||
return (
|
||||
decoder?.(digest.value.payload) ?? {
|
||||
summary: null,
|
||||
details: null,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const digestDecoders: Record<
|
||||
string,
|
||||
Record<string, (payload: HexString) => DigestView | null>
|
||||
> = {
|
||||
preRuntime: {
|
||||
BABE: babePreDigest,
|
||||
aura: auraPreDigest,
|
||||
CMLS: cmlsPreDigest,
|
||||
rand: randPreDigest,
|
||||
},
|
||||
consensus: {
|
||||
BEEF: beefyConsensus,
|
||||
RPSR: rsprConsensus,
|
||||
fron: fronConsensus,
|
||||
ISMP: ismpConsensus,
|
||||
ISTM: istmConsensus,
|
||||
},
|
||||
seal: {
|
||||
// Generally, seals are just the raw signature
|
||||
},
|
||||
}
|
||||
|
||||
const formatDigestType = (type: string) =>
|
||||
type === "preRuntime"
|
||||
? "pre-runtime"
|
||||
: type === "runtimeUpdated"
|
||||
? "runtime updated"
|
||||
: type
|
||||
const DigestTypeLabel: FC<{ type: string }> = ({ type }) => (
|
||||
<span className="w-fit whitespace-nowrap rounded-md border border-foreground/15 bg-foreground/5 px-2 py-0.5 text-xs font-medium text-foreground/75">
|
||||
{formatDigestType(type)}
|
||||
</span>
|
||||
)
|
||||
|
||||
type HeaderDigest = BlockHeader["digests"][number]
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Struct, u64 } from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn } from "./components"
|
||||
|
||||
export const auraPreDigest = (payload: HexString) => {
|
||||
const decoded = decodeAuraPreRuntime(payload)
|
||||
return decoded
|
||||
? {
|
||||
summary: { label: "Slot", value: decoded.slot.toString() },
|
||||
details: null,
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
const AuraDigest = Struct({
|
||||
slot: u64,
|
||||
})
|
||||
export const decodeAuraPreRuntime = createDecodeDigestFn(AuraDigest)
|
||||
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
_void,
|
||||
Bytes,
|
||||
Struct,
|
||||
u32,
|
||||
u64,
|
||||
Variant,
|
||||
} from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { toHex } from "polkadot-api/utils"
|
||||
import { createDecodeDigestFn, FieldGrid, HexDisplay } from "./components"
|
||||
|
||||
export const babePreDigest = (payload: HexString) => {
|
||||
const decoded = decodeBabePreRuntime(payload)
|
||||
if (!decoded) return null
|
||||
|
||||
const digestValue =
|
||||
decoded.value && typeof decoded.value == "object"
|
||||
? decoded.value
|
||||
: {
|
||||
authority_index: null,
|
||||
slot: null,
|
||||
}
|
||||
const vrf =
|
||||
decoded.value &&
|
||||
typeof decoded.value == "object" &&
|
||||
"vrf_signature" in decoded.value
|
||||
? decoded.value.vrf_signature
|
||||
: null
|
||||
|
||||
return {
|
||||
summary:
|
||||
digestValue.slot != null
|
||||
? { label: "Slot", value: digestValue.slot.toString() }
|
||||
: { label: "Kind", value: decoded.type },
|
||||
details: (
|
||||
<FieldGrid
|
||||
fields={[
|
||||
{ label: "Kind", value: decoded.type },
|
||||
digestValue.authority_index != null
|
||||
? {
|
||||
label: "Authority",
|
||||
value: `#${digestValue.authority_index}`,
|
||||
}
|
||||
: null,
|
||||
vrf
|
||||
? {
|
||||
label: "VRF output",
|
||||
value: <HexDisplay value={toHex(vrf.pre_output)} />,
|
||||
}
|
||||
: null,
|
||||
vrf
|
||||
? {
|
||||
label: "VRF proof",
|
||||
value: <HexDisplay value={toHex(vrf.proof)} />,
|
||||
}
|
||||
: null,
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const DigestWithVRF = Struct({
|
||||
authority_index: u32,
|
||||
slot: u64,
|
||||
vrf_signature: Struct({
|
||||
pre_output: Bytes(32),
|
||||
proof: Bytes(64),
|
||||
}),
|
||||
})
|
||||
const BabePreDigest = Variant({
|
||||
Unknown: _void,
|
||||
Primary: DigestWithVRF,
|
||||
SecondaryPlain: Struct({
|
||||
authority_index: u32,
|
||||
slot: u64,
|
||||
}),
|
||||
SecondaryVRF: DigestWithVRF,
|
||||
})
|
||||
export const decodeBabePreRuntime = createDecodeDigestFn(BabePreDigest)
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
_void,
|
||||
Hex,
|
||||
Struct,
|
||||
u32,
|
||||
u64,
|
||||
Variant,
|
||||
Vector,
|
||||
} from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn, FieldList, HexDisplay } from "./components"
|
||||
|
||||
export const beefyConsensus = (payload: HexString) => {
|
||||
const decoded = decodeBeefyConsensus(payload)
|
||||
if (!decoded) return null
|
||||
if (decoded.type === "AuthoritiesChange") {
|
||||
return {
|
||||
summary: {
|
||||
label: "AuthoritiesChange",
|
||||
value: decoded.value.id.toString(),
|
||||
},
|
||||
details: (
|
||||
<FieldList
|
||||
title="Validators"
|
||||
values={decoded.value.validators.map((hash) => (
|
||||
<HexDisplay value={hash} />
|
||||
))}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
if (decoded.type === "MmrRoot") {
|
||||
return {
|
||||
summary: {
|
||||
label: "MMR root",
|
||||
value: <HexDisplay value={decoded.value} />,
|
||||
},
|
||||
details: null,
|
||||
}
|
||||
}
|
||||
if (decoded.type === "OnDisabled") {
|
||||
return {
|
||||
summary: { label: "OnDisabled", value: `#${decoded.value}` },
|
||||
details: null,
|
||||
}
|
||||
}
|
||||
return { summary: { label: "Kind", value: decoded.type }, details: null }
|
||||
}
|
||||
|
||||
const BeefyConsensusLog = Variant({
|
||||
Unknown: _void,
|
||||
AuthoritiesChange: Struct({
|
||||
validators: Vector(Hex(33)),
|
||||
id: u64,
|
||||
}),
|
||||
OnDisabled: u32,
|
||||
MmrRoot: Hex(32),
|
||||
})
|
||||
const decodeBeefyConsensus = createDecodeDigestFn(BeefyConsensusLog)
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
_void,
|
||||
bool,
|
||||
compactNumber,
|
||||
Hex,
|
||||
Struct,
|
||||
u8,
|
||||
Variant,
|
||||
} from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn, FieldGrid, HexDisplay } from "./components"
|
||||
|
||||
export const cmlsPreDigest = (payload: HexString) => {
|
||||
const decoded = decodeCmlsPreRuntime(payload)
|
||||
if (!decoded) return null
|
||||
|
||||
if (decoded.type === "BlockBundleInfo") {
|
||||
return {
|
||||
summary: { label: "Bundle", value: `#${decoded.value.index}` },
|
||||
details: (
|
||||
<FieldGrid
|
||||
fields={[
|
||||
{
|
||||
label: "Is last",
|
||||
value: decoded.value.isLast ? "Yes" : "No",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
if (decoded.type === "CoreInfo") {
|
||||
return {
|
||||
summary: {
|
||||
label: "Core info selector",
|
||||
value: `${decoded.value.selector}`,
|
||||
},
|
||||
details: (
|
||||
<FieldGrid
|
||||
fields={[
|
||||
{
|
||||
label: "Claim offset",
|
||||
value: decoded.value.claimQueueOffset.toString(),
|
||||
},
|
||||
{
|
||||
label: "Cores",
|
||||
value: decoded.value.numberOfCores.toString(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
if (decoded.type === "RelayParent") {
|
||||
return {
|
||||
summary: {
|
||||
label: "Relay parent",
|
||||
value: <HexDisplay value={decoded.value} />,
|
||||
},
|
||||
details: null,
|
||||
}
|
||||
}
|
||||
return { summary: { label: "Kind", value: decoded.type }, details: null }
|
||||
}
|
||||
|
||||
const ClmsDigestItem = Variant({
|
||||
RelayParent: Hex(32),
|
||||
CoreInfo: Struct({
|
||||
selector: u8,
|
||||
claimQueueOffset: u8,
|
||||
numberOfCores: compactNumber,
|
||||
}),
|
||||
BlockBundleInfo: Struct({
|
||||
index: u8,
|
||||
isLast: bool,
|
||||
}),
|
||||
UseFullCore: _void,
|
||||
})
|
||||
const decodeCmlsPreRuntime = createDecodeDigestFn(ClmsDigestItem)
|
||||
@@ -0,0 +1,62 @@
|
||||
import { CopyText } from "@/components/Copy"
|
||||
import { shortStr } from "@/utils"
|
||||
import { Codec, HexString } from "polkadot-api"
|
||||
import { FC, ReactNode } from "react"
|
||||
|
||||
export const FieldGrid: FC<{
|
||||
fields: Array<{
|
||||
label: string
|
||||
value: ReactNode
|
||||
} | null>
|
||||
}> = ({ fields }) => (
|
||||
<div className="grid gap-x-6 gap-y-1 md:grid-cols-2">
|
||||
{fields.map((field, idx) =>
|
||||
field ? <DigestDetailField key={idx} field={field} /> : null,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const FieldList: FC<{
|
||||
title: string
|
||||
values: Array<ReactNode>
|
||||
}> = ({ title, values }) => (
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="text-xs font-medium uppercase tracking-wide">{title}</div>
|
||||
{values.length ? (
|
||||
<ol className="list-decimal space-y-1 pl-5">
|
||||
{values.map((value, i) => (
|
||||
<li key={i}>{value}</li>
|
||||
))}
|
||||
</ol>
|
||||
) : (
|
||||
<div className="text-foreground/50">(Empty)</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const DigestDetailField: FC<{
|
||||
field: { label: string; value: ReactNode }
|
||||
}> = ({ field }) => (
|
||||
<div className="grid grid-cols-[6.5rem_minmax(0,1fr)] items-baseline gap-2 text-sm">
|
||||
<span className="text-foreground/55">{field.label}</span>
|
||||
{field.value}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const HexDisplay: FC<{ value: HexString }> = ({ value }) => (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="truncate font-mono">{shortStr(value, 8)}</span>
|
||||
<CopyText className="shrink-0 text-foreground" text={value} binary={true} />
|
||||
</span>
|
||||
)
|
||||
|
||||
export const createDecodeDigestFn =
|
||||
<T,>(codec: Codec<T>) =>
|
||||
(payload: HexString) => {
|
||||
try {
|
||||
return codec.dec(payload)
|
||||
} catch (ex) {
|
||||
console.error(ex)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
_void,
|
||||
Hex,
|
||||
Struct,
|
||||
Variant,
|
||||
Vector,
|
||||
} from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn, FieldList, HexDisplay } from "./components"
|
||||
|
||||
export const fronConsensus = (payload: HexString) => {
|
||||
const decoded = decodeFrontierConsensus(payload)
|
||||
if (!decoded) return null
|
||||
if (decoded.type === "Hashes") {
|
||||
return {
|
||||
summary: {
|
||||
label: "Block Hash",
|
||||
value: <HexDisplay value={decoded.value.blockHash} />,
|
||||
},
|
||||
details: (
|
||||
<FieldList
|
||||
title="Tx Hashes"
|
||||
values={decoded.value.txHashes.map((hash) => (
|
||||
<HexDisplay value={hash} />
|
||||
))}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
return { summary: { label: "Kind", value: decoded.type }, details: [] }
|
||||
}
|
||||
|
||||
const FrontierConsensusLog = Variant({
|
||||
Unknown: _void,
|
||||
Hashes: Struct({
|
||||
blockHash: Hex(32),
|
||||
txHashes: Vector(Hex(32)),
|
||||
}),
|
||||
// It's actually an ethereum block header, might be too long
|
||||
Block: _void,
|
||||
})
|
||||
const decodeFrontierConsensus = createDecodeDigestFn(FrontierConsensusLog)
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Hex, Struct } from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn, FieldGrid, HexDisplay } from "./components"
|
||||
|
||||
export const ismpConsensus = (payload: HexString) => {
|
||||
const decoded = decodeIsmpConsensus(payload)
|
||||
return decoded
|
||||
? {
|
||||
summary: {
|
||||
label: "Child trie",
|
||||
value: <HexDisplay value={decoded.childTrieRoot} />,
|
||||
},
|
||||
details: (
|
||||
<FieldGrid
|
||||
fields={[
|
||||
{
|
||||
label: "MMR root",
|
||||
value: <HexDisplay value={decoded.mmrRoot} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
const IsmpConsensusDigest = Struct({
|
||||
mmrRoot: Hex(32),
|
||||
childTrieRoot: Hex(32),
|
||||
})
|
||||
const decodeIsmpConsensus = createDecodeDigestFn(IsmpConsensusDigest)
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Struct, u64 } from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn } from "./components"
|
||||
|
||||
export const istmConsensus = (payload: HexString) => {
|
||||
const decoded = decodeIsmpTimestampConsensus(payload)
|
||||
return decoded
|
||||
? {
|
||||
summary: {
|
||||
label: "Timestamp",
|
||||
value: new Date(Number(decoded.timestamp) * 1000).toISOString(),
|
||||
},
|
||||
details: [],
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
const IsmpTimestampDigest = Struct({
|
||||
timestamp: u64,
|
||||
})
|
||||
const decodeIsmpTimestampConsensus = createDecodeDigestFn(IsmpTimestampDigest)
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Hex, Struct } from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn, FieldGrid, HexDisplay } from "./components"
|
||||
|
||||
export const randPreDigest = (payload: HexString) => {
|
||||
const decoded = decodeRandVrfConsensus(payload)
|
||||
return decoded
|
||||
? {
|
||||
summary: {
|
||||
label: "VRF output",
|
||||
value: <HexDisplay value={decoded.vrf_output} />,
|
||||
},
|
||||
details: (
|
||||
<FieldGrid
|
||||
fields={[
|
||||
{
|
||||
label: "VRF proof",
|
||||
value: <HexDisplay value={decoded.vrf_proof} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
const RandVRFPreDigest = Struct({
|
||||
vrf_output: Hex(32),
|
||||
vrf_proof: Hex(64),
|
||||
})
|
||||
const decodeRandVrfConsensus = createDecodeDigestFn(RandVRFPreDigest)
|
||||
@@ -0,0 +1,31 @@
|
||||
import { compactNumber, Hex, Struct } from "@polkadot-api/substrate-bindings"
|
||||
import { HexString } from "polkadot-api"
|
||||
import { createDecodeDigestFn, FieldGrid, HexDisplay } from "./components"
|
||||
|
||||
export const rsprConsensus = (payload: HexString) => {
|
||||
const decoded = decodeRsprConsensus(payload)
|
||||
return decoded
|
||||
? {
|
||||
summary: {
|
||||
label: "Relay block",
|
||||
value: decoded.blockNumber.toString(),
|
||||
},
|
||||
details: (
|
||||
<FieldGrid
|
||||
fields={[
|
||||
{
|
||||
label: "Storage root",
|
||||
value: <HexDisplay value={decoded.storageRoot} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
const RsprConsensusLog = Struct({
|
||||
storageRoot: Hex(32),
|
||||
blockNumber: compactNumber,
|
||||
})
|
||||
const decodeRsprConsensus = createDecodeDigestFn(RsprConsensusLog)
|
||||
Reference in New Issue
Block a user