chain_subscribeFinalizedHeads - JSON-RPC Method
Description#
Creates a subscription to receive notifications about finalized block headers. This JSON-RPC method only sends headers for blocks that have been finalized by GRANDPA consensus, ensuring they will never be reverted. Available only via WebSocket connections.
Parameters#
This method does not require any parameters.
Returns#
| Field | Type | Description |
|---|---|---|
subscription | string | Subscription ID for managing the subscription |
Subscription Updates#
Each notification contains a finalized block header with:
| Field | Type | Description |
|---|---|---|
parentHash | string | Hash of the parent block |
number | string | Block number (hex-encoded) |
stateRoot | string | Root of the state trie |
extrinsicsRoot | string | Root of the extrinsics trie |
digest | object | Digest items including consensus messages |
Request Example#
{
"jsonrpc": "2.0",
"method": "chain_subscribeFinalizedHeads",
"params": [],
"id": 1
}
Response Example#
{
"jsonrpc": "2.0",
"result": "0xabcdef1234567890",
"id": 1
}
Subscription Update Example#
{
"jsonrpc": "2.0",
"method": "chain_finalizedHead",
"params": {
"subscription": "0xabcdef1234567890",
"result": {
"parentHash": "0x4a84d1c5fc5c725b26a3c8e126d8f65e1c2d2dc5b3fcbc91e67e7637fa136789",
"number": "0x123450",
"stateRoot": "0x8f7a82e2e0f3e73f5e232e68de18144f6e7a52a6db39bb5e5e3d9ca6e1f72845",
"extrinsicsRoot": "0x2e6f9a7d2c4b3e1a8f5c6d9e4b1a7c3f8e5d2a9b6c4e1f8a5d3c2b9e7f6a4d38",
"digest": {
"logs": [
"0x0642414245b501013c0000009d2ef70f00000000"
]
}
}
}
}
Code Examples#
- Python
- JavaScript
- TypeScript (@polkadot/api)
import asyncio
import json
import websockets
from datetime import datetime
class FinalityMonitor:
def __init__(self, ws_url):
self.ws_url = ws_url
self.finalized_blocks = []
self.last_finalized = None
async def subscribe(self):
async with websockets.connect(self.ws_url) as websocket:
# Subscribe to finalized heads
subscribe_msg = json.dumps({
"jsonrpc": "2.0",
"method": "chain_subscribeFinalizedHeads",
"params": [],
"id": 1
})
await websocket.send(subscribe_msg)
subscription_id = None
async for message in websocket:
data = json.loads(message)
# Handle subscription confirmation
if data.get("id") == 1:
subscription_id = data["result"]
print(f"Monitoring finality - Subscription: {subscription_id}")
# Handle finalized headers
elif data.get("method") == "chain_finalizedHead":
header = data["params"]["result"]
await self.process_finalized(header)
async def process_finalized(self, header):
block_number = int(header["number"], 16)
timestamp = datetime.now()
# Track finalization lag
if self.last_finalized:
blocks_finalized = block_number - self.last_finalized["number"]
time_elapsed = (timestamp - self.last_finalized["timestamp"]).total_seconds()
print(f"Finalized block {block_number}")
print(f" Blocks finalized: {blocks_finalized}")
print(f" Time elapsed: {time_elapsed:.2f}s")
print(f" Finalization rate: {blocks_finalized/time_elapsed:.2f} blocks/s")
self.last_finalized = {
"number": block_number,
"timestamp": timestamp,
"hash": header["parentHash"]
}
self.finalized_blocks.append(block_number)
# Run the monitor
monitor = FinalityMonitor("wss://api-asset-hub-polkadot.n.dwellir.com")
asyncio.run(monitor.subscribe())
const WebSocket = require('ws');
const subscribeFinalizedHeads = () => {
const ws = new WebSocket('wss://api-asset-hub-polkadot.n.dwellir.com');
let subscriptionId = null;
ws.on('open', () => {
// Subscribe to finalized heads
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'chain_subscribeFinalizedHeads',
params: [],
id: 1
}));
});
ws.on('message', (data) => {
const response = JSON.parse(data);
// Handle subscription confirmation
if (response.id === 1) {
subscriptionId = response.result;
console.log('Subscribed to finalized heads:', subscriptionId);
}
// Handle finalized block headers
if (response.method === 'chain_finalizedHead') {
const header = response.params.result;
const blockNumber = parseInt(header.number, 16);
console.log(`Block #${blockNumber} finalized`);
console.log(` Hash: ${header.parentHash}`);
console.log(` State Root: ${header.stateRoot}`);
// Process finalized block
processFinalizedBlock(header);
}
});
return ws;
};
const processFinalizedBlock = async (header) => {
const blockNumber = parseInt(header.number, 16);
// Safe to process transactions as finalized
console.log(`Processing finalized block ${blockNumber}`);
// Example: Update database with finalized state
// await updateDatabase(blockNumber, header);
// Example: Trigger post-finalization actions
// await notifyServices(blockNumber);
};
import { ApiPromise, WsProvider } from '@polkadot/api';
interface FinalityStats {
lastFinalized: number;
averageLag: number;
finalizedCount: number;
}
class FinalityTracker {
private api: ApiPromise;
private stats: FinalityStats;
private unsubscribe: (() => void) | null = null;
constructor() {
this.stats = {
lastFinalized: 0,
averageLag: 0,
finalizedCount: 0
};
}
async connect(wsUrl: string) {
const provider = new WsProvider(wsUrl);
this.api = await ApiPromise.create({ provider });
// Get current head for comparison
const currentHead = await this.api.rpc.chain.getHeader();
const currentNumber = currentHead.number.toNumber();
// Subscribe to finalized heads
this.unsubscribe = await this.api.rpc.chain.subscribeFinalizedHeads(
(header) => {
const finalizedNumber = header.number.toNumber();
const lag = currentNumber - finalizedNumber;
// Update statistics
this.stats.finalizedCount++;
this.stats.lastFinalized = finalizedNumber;
this.stats.averageLag =
(this.stats.averageLag * (this.stats.finalizedCount - 1) + lag) /
this.stats.finalizedCount;
console.log(`Finalized: #${finalizedNumber}`);
console.log(` Current lag: ${lag} blocks`);
console.log(` Average lag: ${this.stats.averageLag.toFixed(1)} blocks`);
// Trigger finalized block processing
this.onFinalized(header);
}
);
}
private onFinalized(header: any) {
// Implement your finalized block logic here
// This is safe for irreversible operations
}
async disconnect() {
if (this.unsubscribe) {
this.unsubscribe();
}
await this.api.disconnect();
}
getStats(): FinalityStats {
return this.stats;
}
}
// Usage
const tracker = new FinalityTracker();
await tracker.connect('wss://api-asset-hub-polkadot.n.dwellir.com');
// Get stats after some time
setTimeout(() => {
const stats = tracker.getStats();
console.log('Finality Statistics:', stats);
}, 60000);
Finality Lag Monitoring#
class FinalityLagMonitor {
constructor(wsUrl) {
this.wsUrl = wsUrl;
this.latestBlock = 0;
this.finalizedBlock = 0;
}
async start() {
const ws = new WebSocket(this.wsUrl);
ws.on('open', () => {
// Subscribe to both new heads and finalized heads
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'chain_subscribeNewHeads',
params: [],
id: 1
}));
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'chain_subscribeFinalizedHeads',
params: [],
id: 2
}));
});
ws.on('message', (data) => {
const response = JSON.parse(data);
if (response.method === 'chain_newHead') {
const blockNumber = parseInt(response.params.result.number, 16);
this.latestBlock = blockNumber;
this.updateLag();
} else if (response.method === 'chain_finalizedHead') {
const blockNumber = parseInt(response.params.result.number, 16);
this.finalizedBlock = blockNumber;
this.updateLag();
}
});
}
updateLag() {
const lag = this.latestBlock - this.finalizedBlock;
console.log(`Finality lag: ${lag} blocks (Latest: ${this.latestBlock}, Finalized: ${this.finalizedBlock})`);
// Alert if lag is too high
if (lag > 10) {
console.warn(`High finality lag detected: ${lag} blocks`);
}
}
}
Use Cases#
- Transaction Confirmation: Wait for finalization before confirming payments
- Database Updates: Only store finalized state to avoid reorgs
- Cross-chain Bridges: Process transfers after finalization
- Exchange Integration: Credit deposits after finalization
- Analytics: Track network finality performance
Notes#
- Finalized blocks are irreversible and safe for permanent storage
- Finalization typically lags 2-3 blocks behind the head
- GRANDPA provides deterministic finality
- Use this for operations requiring absolute certainty
- WebSocket connection required (not available over HTTP)
Related Methods#
chain_getFinalizedHead- Get current finalized headchain_subscribeNewHeads- Subscribe to all new headersgrandpa_roundState- Monitor GRANDPA finality