feat: lookup block author
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user