diff --git a/docs/components/FinalityTable.tsx b/docs/components/FinalityTable.tsx new file mode 100644 index 0000000..2c09141 --- /dev/null +++ b/docs/components/FinalityTable.tsx @@ -0,0 +1,158 @@ +import { useState, useEffect } from 'react' +import { GITHUB_REPOSITORIES } from '../constants/config' + +interface NetworkEntry { + name: string + chainSelector: number + finalityTagEnabled?: boolean + finalityConfirmations?: number + minBlockConfirmations?: number +} + +interface FinalityRow { + name: string + chainSelector: string + finalityMethod: string + minBlockConfirmations: number +} + +function parseFinalityMethod(entry: NetworkEntry): string { + if (entry.finalityTagEnabled === true) { + return 'finalized' + } + if (typeof entry.finalityConfirmations === 'number' && entry.finalityConfirmations > 0) { + return String(entry.finalityConfirmations) + } + return 'none' +} + +function parseRows(data: unknown): FinalityRow[] { + if (typeof data !== 'object' || data === null || Array.isArray(data)) return [] + + const rows: FinalityRow[] = [] + + for (const item of Object.values(data as Record)) { + if (typeof item !== 'object' || item === null) continue + + const entry = item as Record + const name = typeof entry.name === 'string' ? entry.name.trim() : null + if (!name) continue + + const chainSelector = entry.chainSelector != null ? String(entry.chainSelector) : '—' + const minBlockConfirmations = typeof entry.minBlockConfirmations === 'number' ? entry.minBlockConfirmations : 1 + + rows.push({ + name, + chainSelector, + finalityMethod: parseFinalityMethod(entry as unknown as NetworkEntry), + minBlockConfirmations, + }) + } + + return rows.filter(row => row.finalityMethod !== 'none').sort((a, b) => a.name.localeCompare(b.name)) +} + +export function FinalityTable() { + const [rows, setRows] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let isActive = true + const controller = new AbortController() + + const fetchData = async () => { + try { + const response = await fetch(GITHUB_REPOSITORIES.V2_NETWORKS.MAINNET_NETWORKS_URL, { + signal: controller.signal, + }) + + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`) + } + + const data = await response.json() + + if (!isActive) return + + setRows(parseRows(data)) + setError(null) + } catch (err) { + if (err instanceof DOMException && err.name === 'AbortError') return + if (isActive) { + setError(err instanceof Error ? err.message : 'An unknown error occurred') + } + } finally { + if (isActive) setLoading(false) + } + } + + fetchData() + + return () => { + isActive = false + controller.abort() + } + }, []) + + if (loading) { + return
Loading finality data...
+ } + + if (error) { + return
Error loading finality data: {error}
+ } + + if (rows.length === 0) { + return
No chain data available.
+ } + + return ( +
+ + + + {[ + 'SRC Blockchain', + 'Chain selector', + 'Finality (tag/finalityConfirmations)', + 'minBlockConfirmations', + ].map(col => ( + + ))} + + + + {rows.map(row => ( + + + + + + + ))} + +
+ {col} +
{row.name}{row.chainSelector}{row.finalityMethod}{row.minBlockConfirmations}
+
+ ) +} + +const cellStyle: React.CSSProperties = { + padding: '8px 12px', + borderBottom: '1px solid var(--vocs-color_tableBorder)', + fontSize: '0.9rem', + color: 'var(--vocs-color_text)', +} diff --git a/docs/pages/integrate-concero/finality.mdx b/docs/pages/integrate-concero/finality.mdx new file mode 100644 index 0000000..c06941a --- /dev/null +++ b/docs/pages/integrate-concero/finality.mdx @@ -0,0 +1,69 @@ +--- +title: Finality +description: Learn about finality in Concero and how to enable it for secure message delivery across chains. +--- + +import { FinalityTable } from '../../components/FinalityTable' + +# Finality + +Finality determines how many block confirmations the Relayer waits for before forwarding a message to the destination chain. Many blockchains support the `finalized` RPC tag. Where it is not supported, block depth is used to ensure safe finalization of L1 blocks and protection against [reorgs](https://blog.trailofbits.com/2023/08/23/the-engineers-guide-to-blockchain-finality/). + +## How to enable finality + +It is controlled via the `srcBlockConfirmations` field in `MessageRequest`. + +```solidity +IConceroRouter.MessageRequest({ + dstChainSelector: dstChainSelector, + srcBlockConfirmations: type(uint64).max, // enable finality + // ... +}); +``` + +:::danger + Be careful when configuring the message. If the message is configured incorrectly (invalid `dstChainSelector` or finality specified for chains that do not support it), it will not be reverted but will simply not reach the dst chain. +::: + +**There are three possible ways to specify finality when sending a message:** + +- If `srcBlockConfirmations` is specified as 0, we use `minBlockConfirmations` +- If `srcBlockConfirmations` is specified as `type(uint64).max`, then we rely on the `finality` tag or on `finalityConfirmations` +- If `srcBlockConfirmations` is specified explicitly, we use this value `[1 : type(uint64).max - 1]` + +:::info +`finalityConfirmations`: the number of blocks after which an L1 transaction is considered final (cannot be reverted and is protected against block reorganizations). Not all blockchains support the `finality` tag. +::: + +:::info +`minBlockConfirmations`: the minimum number of block confirmations required before a log event can be consumed. When `srcBlockConfirmations` is set to 0, the protocol automatically applies this minimum value. If the chain does not define one, the protocol defaults to 1. +::: + +## Configuration file + +Finality data for a specific chain can be found in the [table below](#supported-chains) or in the [chain configuration](https://github.com/concero/v2-networks/blob/master/networks/mainnet.json) file on GitHub. + +The configuration file contains either a `finalityTagEnabled` or a `finalityConfirmations` field. If `minBlockConfirmations` is not specified, it defaults to 1. + +```json +"arbitrum": { + "name": "arbitrum", + "chainId": 42161, + "chainSelector": 42161, + // ... + "finalityTagEnabled": true, + "minBlockConfirmations": 3, + // ... + "finalityConfirmations": 100 // used if the "finalized" tag is not supported, or when it is safer to rely on block depth +} +``` + +:::warning +Block depth-based finality does not guarantee irreversibility. On chains without `finalityTagEnabled` support, chain reorgs remain possible and may affect message delivery. +::: + +## Supported chains + +Finality is only available on chains where `finalityTagEnabled` is true or `finalityConfirmations` is set in the protocol's [chain configuration](https://github.com/concero/v2-networks/blob/master/networks/mainnet.json). + + diff --git a/vocs.config.tsx b/vocs.config.tsx index 07cf9b9..87f876b 100644 --- a/vocs.config.tsx +++ b/vocs.config.tsx @@ -152,6 +152,10 @@ export default defineConfig({ text: 'Send & Receive a message', link: '/integrate-concero/send-a-message', }, + { + text: 'Finality', + link: '/integrate-concero/finality', + }, { text: 'Track a message', link: '/integrate-concero/track-a-message',