fix: block status (#86)

This commit is contained in:
Josep M Sobrepere
2025-09-24 22:58:35 +02:00
committed by GitHub
parent 125fecb3da
commit e0e42a5cf1
4 changed files with 103 additions and 48 deletions
+7 -16
View File
@@ -5,15 +5,8 @@ import { runtimeCtx$, runtimeCtxAt$ } from "@/state/chains/chain.state"
import * as Tabs from "@radix-ui/react-tabs"
import { state, useStateObservable } from "@react-rxjs/core"
import { FC, useState } from "react"
import { useLocation, useParams } from "react-router-dom"
import {
catchError,
combineLatest,
distinctUntilChanged,
filter,
map,
take,
} from "rxjs"
import { useLocation } from "react-router-dom"
import { catchError, combineLatest, filter, map, take } from "rxjs"
import { twMerge } from "tailwind-merge"
import { BlockInfo, blockInfoState$ } from "../block.state"
import { BlockEvents } from "./BlockEvents"
@@ -25,15 +18,14 @@ import { fromHex, toHex } from "@polkadot-api/utils"
const blockExtrinsics$ = state((hash: string) => {
const body$ = blockInfoState$(hash).pipe(
filter((v) => !!v),
map((v) => v.body),
filter((v) => !!v),
distinctUntilChanged(),
filter((x) => !!x?.body),
map((v) => v!.body!),
take(1),
)
return combineLatest([
body$,
runtimeCtxAt$(hash).pipeState(catchError(() => runtimeCtx$)),
runtimeCtxAt$(hash).pipe(catchError(() => runtimeCtx$)),
]).pipe(
take(1),
map(([body, { txDecoder }]) =>
@@ -50,9 +42,8 @@ type Tab = "tx" | "events" | "diff"
export const BlockBody: FC<{
block: BlockInfo
}> = ({ block }) => {
const { hash } = useParams()
const extrinsics = useStateObservable(blockExtrinsics$(block.hash))
const [selectedTab, setSelectedTab] = useState<Tab | null>(null)
const extrinsics = useStateObservable(blockExtrinsics$(hash ?? ""))
const location = useLocation()
const hashParams = getHashParams(location)
+1
View File
@@ -101,6 +101,7 @@ const statusValue: Record<BlockState, number> = {
[BlockState.Best]: 2,
[BlockState.Fork]: 1,
[BlockState.Pruned]: 0,
[BlockState.Unknown]: -1,
}
const BlockChildren: FC<{ hash: string }> = ({ hash }) => {
+4
View File
@@ -3,6 +3,7 @@ import { BlockState } from "../block.state"
import {
CircleAlert,
CircleCheck,
CircleQuestionMark,
CircleX,
Clock,
LucideProps,
@@ -14,12 +15,14 @@ const statusIcon: Record<BlockState, ComponentType<LucideProps>> = {
[BlockState.Fork]: CircleAlert,
[BlockState.Finalized]: CircleCheck,
[BlockState.Pruned]: CircleX,
[BlockState.Unknown]: CircleQuestionMark,
}
const statusClassName: Record<BlockState, string> = {
[BlockState.Best]: "text-blue-400",
[BlockState.Fork]: "text-orange-400",
[BlockState.Finalized]: "text-green-400",
[BlockState.Pruned]: "text-red-400",
[BlockState.Unknown]: "text-orange-400",
}
export const BlockStatusIcon: FC<
@@ -38,4 +41,5 @@ export const statusText: Record<BlockState, string> = {
[BlockState.Fork]: "Fork",
[BlockState.Finalized]: "Finalized",
[BlockState.Pruned]: "Pruned",
[BlockState.Unknown]: "Unknown",
}
+91 -32
View File
@@ -2,14 +2,13 @@ import { chopsticksInstance$ } from "@/chopsticks/chopsticks"
import { chainClient$, client$ } from "@/state/chains/chain.state"
import { SystemEvent } from "@polkadot-api/observable-client"
import { state } from "@react-rxjs/core"
import { partitionByKey, toKeySet } from "@react-rxjs/utils"
import { combineKeys, partitionByKey } from "@react-rxjs/utils"
import { HexString, PolkadotClient } from "polkadot-api"
import {
catchError,
combineLatest,
concat,
defer,
distinctUntilChanged,
EMPTY,
filter,
forkJoin,
@@ -34,6 +33,7 @@ import {
toArray,
withLatestFrom,
} from "rxjs"
import { BlockInfo as RawBlockInfo } from "polkadot-api"
export const finalized$ = client$.pipeState(
switchMap((client) => client.finalizedBlock$),
@@ -44,6 +44,7 @@ export enum BlockState {
Best = "best",
Finalized = "finalized",
Pruned = "pruned",
Unknown = "unknown",
}
export interface BlockInfo {
hash: string
@@ -62,6 +63,22 @@ export interface BlockInfo {
diff: Record<string, [string | null, string | null]> | null
}
const finalizedBlocks$ = state(
client$.pipe(
switchMap((client) => {
const data = new Map<string, number>()
return client.finalizedBlock$.pipe(
scan((acc, current: { hash: string; number: number }) => {
acc.set(current.hash, current.number)
return acc
}, data),
startWith(data),
)
}),
),
)
finalizedBlocks$.subscribe()
export const [blockInfo$, recordedBlocks$] = partitionByKey(
client$.pipe(switchMap((client) => client.blocks$)),
(v) => v.hash,
@@ -101,7 +118,7 @@ export const [blockInfo$, recordedBlocks$] = partitionByKey(
return of(null)
}),
),
status: getBlockStatus$(client, hash, number),
status: getBlockStatus$(client, hash, number, parent),
diff: getBlockDiff$(parent, hash),
}),
NEVER,
@@ -122,20 +139,34 @@ const getUnpinnedBlockInfo$ = (hash: string): Observable<BlockInfo> =>
client$.pipe(
switchMap((client) =>
combineLatest({
header: client.getBlockHeader(hash),
headerAndStatus: from(client.getBlockHeader(hash)).pipe(
mergeMap((header) =>
getBlockStatus$(
client,
hash,
header.number,
header.parentHash,
).pipe(
map((status) => ({
header,
status,
})),
),
),
),
body: client.watchBlockBody(hash),
events: client.getUnsafeApi().query.System.Events.getValue({
at: hash,
}),
}).pipe(
map(({ header, body, events }) => ({
map(({ headerAndStatus: { header, status }, body, events }) => ({
hash: hash,
parent: header.parentHash,
number: header.number,
body,
events,
header,
status: BlockState.Finalized,
status,
diff: null,
})),
catchError(() => getUnpinnedBlockInfoFallback$(hash, client)),
@@ -171,11 +202,22 @@ const getUnpinnedBlockInfoFallback$ = (
filter((v) => !!v),
take(1),
catchError(() => EMPTY),
mergeMap((res) => {
const header = res.block.header
const number = Number(header.number)
return getBlockStatus$(client, hash, number, header.parentHash).pipe(
map((status) => ({
...res,
number,
status,
})),
)
}),
)
return throughRpc$.pipe(
map(
({ block: { extrinsics, header } }): BlockInfo => ({
({ block: { extrinsics, header }, status, number }): BlockInfo => ({
hash,
parent: header.parentHash,
body: extrinsics,
@@ -183,26 +225,31 @@ const getUnpinnedBlockInfoFallback$ = (
header: {
digests: header.digest.logs,
extrinsicRoot: header.extrinsicsRoot,
number: Number(header.number),
number,
parentHash: header.parentHash,
stateRoot: header.stateRoot,
},
number: Number(header.number),
status: BlockState.Finalized,
number,
status,
diff: null,
}),
),
)
}
export const inMemoryBlocks$ = state(
combineKeys(recordedBlocks$, (key) =>
blockInfo$(key).pipe(startWith(null)),
).pipe(repeat()),
)
inMemoryBlocks$.subscribe()
export const blockInfoState$ = state(
(hash: string) =>
recordedBlocks$.pipe(
toKeySet(),
map((blocks) => blocks.has(hash)),
distinctUntilChanged(),
switchMap((exists) =>
exists ? blockInfo$(hash) : getUnpinnedBlockInfo$(hash),
inMemoryBlocks$.pipe(
take(1),
switchMap((blocks) =>
blocks.has(hash) ? blockInfo$(hash) : getUnpinnedBlockInfo$(hash),
),
),
null,
@@ -269,27 +316,39 @@ export const blocksByHeight$ = state(
),
)
const getBlockStatus = (
best: Array<RawBlockInfo>,
finBlocks: Map<string, number>,
number: number,
hash: string,
) => {
const finalized = best[best.length - 1]
if (finalized.number === number)
return finalized.hash === hash ? BlockState.Finalized : BlockState.Pruned
return finalized.number < number
? best.some((b) => b.hash === hash)
? BlockState.Best
: BlockState.Fork
: finBlocks.has(hash)
? BlockState.Finalized
: BlockState.Unknown
}
const getBlockStatus$ = (
client: PolkadotClient,
hash: string,
number: number,
parent: string,
): Observable<BlockState> =>
merge(
client.finalizedBlock$.pipe(
take(1),
// If the latest finalized is ahead, assume it is finalized (?)
filter((b) => b.number > number),
map(() => BlockState.Finalized),
),
combineLatest([client.bestBlocks$, client.finalizedBlock$]).pipe(
map(([best, finalized]) => {
if (finalized.hash === hash) return BlockState.Finalized
if (finalized.number === number) return BlockState.Pruned
if (best.some((b) => b.hash === hash)) return BlockState.Best
return BlockState.Fork
}),
),
).pipe(
client.bestBlocks$.pipe(
withLatestFrom(finalizedBlocks$),
map(([best, finBlocks]) => {
const status = getBlockStatus(best, finBlocks, number, hash)
if (status === BlockState.Finalized && !finBlocks.has(parent))
finBlocks.set(parent, number - 1)
return status
}),
takeWhile(
(v) => v !== BlockState.Finalized && v !== BlockState.Pruned,
true,