feat: lookup block author

This commit is contained in:
Victor Oliva
2025-11-15 13:25:46 -03:00
parent bc0fb2cdd8
commit 928fab8ae2
4 changed files with 129 additions and 29 deletions
+106 -17
View File
@@ -1,7 +1,20 @@
import { CopyText } from "@/components/Copy"
import { Link } from "@/hashParams"
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 { state, useStateObservable } from "@react-rxjs/core"
import { combineKeys } from "@react-rxjs/utils"
import { BlockHeader } from "polkadot-api"
import { AddressIdentity } from "polkahub"
import { FC } from "react"
import { filter, map, startWith, switchMap, take } from "rxjs"
import {
@@ -38,28 +51,104 @@ export const BlockInfoView: FC<{
</div>
</div>
{block.header && (
<div className="text-foreground/80 py-2">
<p>
State root: {block.header.stateRoot.slice(0, 18)}{" "}
<CopyText
className="align-middle"
text={block.header.stateRoot}
binary
/>
</p>
<p>
Extrinsic root: {block.header.extrinsicRoot.slice(0, 18)}{" "}
<CopyText
className="align-middle"
text={block.header.extrinsicRoot}
binary
/>
</p>
<div className="flex flex-wrap justify-between items-center">
<div className="text-foreground/80 py-2">
<p>
State root: {block.header.stateRoot.slice(0, 18)}{" "}
<CopyText
className="align-middle"
text={block.header.stateRoot}
binary
/>
</p>
<p>
Extrinsic root: {block.header.extrinsicRoot.slice(0, 18)}{" "}
<CopyText
className="align-middle"
text={block.header.extrinsicRoot}
binary
/>
</p>
</div>
<Author hash={block.hash} header={block.header} />
</div>
)}
</div>
)
const validators$ = state(
(block: HexString) =>
client$.pipe(
switchMap(async (client) => {
try {
return await client
.getTypedApi(polkadot_people)
.query.Session.Validators.getValue({
at: block,
})
} catch (ex) {
console.error(ex)
return null
}
}),
),
null,
)
const Author: FC<{ hash: HexString; header: BlockHeader }> = ({
hash,
header,
}) => {
const validators = useStateObservable(validators$(hash))
const idx = getAuthorityIdx(header)
if (idx == null || validators == null) return null
return (
<div>
<p className="text-right">Block author</p>
<AddressIdentity
addr={validators[Number(idx % BigInt(validators.length))]}
/>
</div>
)
}
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")?.value
switch (preRuntime?.engine) {
case "BABE": {
const babe = BabePreDigest.dec(preRuntime.payload)
if (!babe.value) return null
return BigInt(babe.value.authority_index)
}
case "aura": {
return u64.dec(preRuntime.payload)
}
}
return null
}
const childBlocks$ = state(
(hash: string) =>
blockInfoState$(hash).pipe(
+21 -11
View File
@@ -1,9 +1,16 @@
import { chopsticksInstance$ } from "@/chopsticks/chopsticks"
import { chainClient$, client$ } from "@/state/chains/chain.state"
import { SystemEvent } from "@polkadot-api/observable-client"
import { blockHeader } from "@polkadot-api/substrate-bindings"
import { state } from "@react-rxjs/core"
import { combineKeys, partitionByKey } from "@react-rxjs/utils"
import { FixedSizeBinary, HexString, PolkadotClient } from "polkadot-api"
import {
BlockHeader,
FixedSizeBinary,
HexString,
PolkadotClient,
BlockInfo as RawBlockInfo,
} from "polkadot-api"
import {
catchError,
combineLatest,
@@ -33,7 +40,6 @@ import {
toArray,
withLatestFrom,
} from "rxjs"
import { BlockInfo as RawBlockInfo } from "polkadot-api"
export const finalized$ = client$.pipeState(
switchMap((client) => client.finalizedBlock$),
@@ -52,13 +58,7 @@ export interface BlockInfo {
number: number
body: string[] | null
events: SystemEvent[] | null
header: {
parentHash: HexString
number: number
stateRoot: HexString
extrinsicRoot: HexString
digests: unknown[]
} | null
header: BlockHeader | null
status: BlockState
diff: Record<string, [string | null, string | null]> | null
}
@@ -175,6 +175,7 @@ const getUnpinnedBlockInfo$ = (hash: string): Observable<BlockInfo> =>
),
)
const digestCodec = blockHeader.inner.digests.inner
const getUnpinnedBlockInfoFallback$ = (
hash: string,
client: PolkadotClient,
@@ -185,7 +186,7 @@ const getUnpinnedBlockInfoFallback$ = (
block: {
extrinsics: HexString[]
header: {
digest: { logs: Array<unknown> }
digest: { logs: Array<string> }
extrinsicsRoot: string
number: HexString
parentHash: HexString
@@ -223,7 +224,16 @@ const getUnpinnedBlockInfoFallback$ = (
body: extrinsics,
events: null,
header: {
digests: header.digest.logs,
digests: header.digest.logs
.map((log) => {
try {
return digestCodec.dec(log)
} catch (ex) {
console.error(ex)
return null
}
})
.filter((v) => v != null),
extrinsicRoot: header.extrinsicsRoot,
number,
parentHash: header.parentHash,