112 lines
4.4 KiB
TypeScript
112 lines
4.4 KiB
TypeScript
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,
|
|
}
|
|
}
|