Files
explorer-monorepo/frontend/src/utils/activityContext.ts

112 lines
4.4 KiB
TypeScript
Raw Normal View History

import type { Block } from '@/services/api/blocks'
import type { Transaction } from '@/services/api/transactions'
import type { ExplorerFreshnessSnapshot } from '@/services/api/stats'
export type ChainActivityState = 'active' | 'low' | 'inactive' | 'unknown'
export interface ChainActivityContext {
latest_block_number: number | null
latest_block_timestamp: string | null
latest_transaction_block_number: number | null
latest_transaction_timestamp: string | null
last_non_empty_block_number: number | null
last_non_empty_block_timestamp: string | null
block_gap_to_latest_transaction: number | null
latest_transaction_age_seconds: number | null
state: ChainActivityState
head_is_idle: boolean
transaction_visibility_unavailable: boolean
}
function sortDescending(values: number[]): number[] {
return [...values].sort((left, right) => right - left)
}
function toTimestamp(value?: string | null): number | null {
if (!value) return null
const parsed = Date.parse(value)
return Number.isFinite(parsed) ? parsed : null
}
export function summarizeChainActivity(input: {
blocks?: Block[]
transactions?: Transaction[]
latestBlockNumber?: number | null
latestBlockTimestamp?: string | null
freshness?: ExplorerFreshnessSnapshot | null
}): ChainActivityContext {
const freshness = input.freshness || null
const blocks = Array.isArray(input.blocks) ? input.blocks : []
const transactions = Array.isArray(input.transactions) ? input.transactions : []
const latestBlockFromList = sortDescending(blocks.map((block) => block.number).filter((value) => Number.isFinite(value)))[0] ?? null
const latestBlock = freshness?.chain_head.block_number ?? input.latestBlockNumber ?? latestBlockFromList
const latestBlockTimestamp =
freshness?.chain_head.timestamp ??
input.latestBlockTimestamp ??
blocks.find((block) => block.number === latestBlock)?.timestamp ??
blocks[0]?.timestamp ??
null
const latestTransaction = freshness?.latest_indexed_transaction.block_number ?? sortDescending(
transactions.map((transaction) => transaction.block_number).filter((value) => Number.isFinite(value)),
)[0] ?? null
const latestTransactionRecord =
transactions.find((transaction) => transaction.block_number === latestTransaction) ?? transactions[0] ?? null
const nonEmptyBlock =
freshness?.latest_non_empty_block.block_number ??
sortDescending(blocks.filter((block) => block.transaction_count > 0).map((block) => block.number))[0] ?? latestTransaction
const nonEmptyBlockTimestamp =
freshness?.latest_non_empty_block.timestamp ??
blocks.find((block) => block.number === nonEmptyBlock)?.timestamp ??
latestTransactionRecord?.created_at ??
null
const latestTransactionTimestamp = freshness?.latest_indexed_transaction.timestamp ?? latestTransactionRecord?.created_at ?? null
const transactionVisibilityUnavailable =
freshness?.latest_indexed_transaction.source === 'unavailable' ||
freshness?.latest_indexed_transaction.completeness === 'unavailable'
const latestTransactionAgeSeconds =
freshness?.latest_indexed_transaction.age_seconds ??
(() => {
const timestamp = toTimestamp(latestTransactionTimestamp)
if (timestamp == null) return null
return Math.max(0, Math.round((Date.now() - timestamp) / 1000))
})()
const gap = freshness?.latest_non_empty_block.distance_from_head ??
(latestBlock != null && latestTransaction != null
? Math.max(0, latestBlock - latestTransaction)
: null)
const state: ChainActivityState =
latestTransactionAgeSeconds == null
? 'unknown'
: latestTransactionAgeSeconds <= 15 * 60
? 'active'
: latestTransactionAgeSeconds <= 3 * 60 * 60
? 'low'
: 'inactive'
const headIsIdle =
gap != null &&
gap > 0 &&
latestTransactionAgeSeconds != null &&
latestTransactionAgeSeconds > 0
return {
latest_block_number: latestBlock,
latest_block_timestamp: latestBlockTimestamp,
latest_transaction_block_number: latestTransaction,
latest_transaction_timestamp: latestTransactionTimestamp,
last_non_empty_block_number: nonEmptyBlock,
last_non_empty_block_timestamp: nonEmptyBlockTimestamp,
block_gap_to_latest_transaction: gap,
latest_transaction_age_seconds: latestTransactionAgeSeconds,
state,
head_is_idle: headIsIdle,
transaction_visibility_unavailable: transactionVisibilityUnavailable,
}
}