Querying Chain and Irreversible Height
BEGINNER
The /chain/height GET endpoint returns the current chain height.
The irreversible height is the highest block that can no longer be rolled back.
On NEM, it is calculated by subtracting the rewrite limit from the current chain height.
This tutorial shows how to poll the chain height in a loop, calculate the irreversible height, and track how long ago
the chain height last changed.
Prerequisites
This tutorial uses the NEM REST API without requiring an SDK.
You only need a way to make HTTP requests.
Full Code
The following is the complete code listing for this tutorial.
A detailed, step-by-step explanation follows in the next section.
import json
import os
import time
import urllib.request
NODE_URL = os . getenv ( 'NODE_URL' , 'http://libertalia.nemtest.net:7890' )
print ( f 'Using node { NODE_URL } ' )
prev_height = None
height_changed_at = None
REWRITE_LIMIT = 360
try :
while True :
with urllib . request . urlopen (
f ' { NODE_URL } /chain/height'
) as response :
chain_height = json . loads ( response . read () . decode ())
height = int ( chain_height [ 'height' ])
irreversible_height = max ( 0 , height - REWRITE_LIMIT )
now = time . time ()
if prev_height is not None and height != prev_height :
height_changed_at = now
if height_changed_at is not None :
height_ago = f ' { int ( now - height_changed_at ) } s ago'
else :
height_ago = '-'
print (
f 'Height: { height : >10, } (changed { height_ago } )'
f ' | Irreversible: { irreversible_height : >10, } '
)
prev_height = height
time . sleep ( 1 )
except KeyboardInterrupt :
pass
except Exception as error :
print ( error )
Download source
const NODE_URL = process . env . NODE_URL ||
'http://libertalia.nemtest.net:7890' ;
console . log ( `Using node ${ NODE_URL } ` );
let prevHeight = null ;
let heightChangedAt = null ;
const REWRITE_LIMIT = 360 ;
for (;;) {
const response = await fetch ( ` ${ NODE_URL } /chain/height` );
if ( ! response . ok )
throw new Error ( `HTTP error! status: ${ response . status } ` );
const chainHeight = await response . json ();
const height = parseInt ( chainHeight . height , 10 );
const irreversibleHeight = Math . max ( 0 , height - REWRITE_LIMIT );
const now = Date . now ();
if ( null !== prevHeight && height !== prevHeight )
heightChangedAt = now ;
const heightAgo = null !== heightChangedAt ?
` ${ Math . floor (( now - heightChangedAt ) / 1000 ) } s ago` :
'-' ;
const heightLabel = height . toLocaleString (). padStart ( 10 );
const irreversibleLabel =
irreversibleHeight . toLocaleString (). padStart ( 10 );
console . log (
`Height: ${ heightLabel } (changed ${ heightAgo } )` +
` | Irreversible: ${ irreversibleLabel } `
);
prevHeight = height ;
await new Promise ( resolve => { setTimeout ( resolve , 1000 ); });
}
Download source
//JAVA 21+
//DEPS org.symbol:symbol-sdk:3.3.3
import java.io.IOException ;
import java.net.URI ;
import java.net.http.HttpClient ;
import java.net.http.HttpRequest ;
import java.net.http.HttpResponse ;
import java.net.http.HttpResponse.BodyHandlers ;
import com.fasterxml.jackson.databind.JsonNode ;
import com.fasterxml.jackson.databind.ObjectMapper ;
public final class ChainHeights {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper ();
private static final HttpClient HTTP_CLIENT =
HttpClient . newHttpClient ();
private static final int REWRITE_LIMIT = 360 ;
private final String nodeUrl = System . getenv (). getOrDefault (
"NODE_URL" , "http://libertalia.nemtest.net:7890" );
public static void main ( final String [] args ) {
try {
new ChainHeights (). run ();
} catch ( final Exception ex ) {
System . out . println ( null == ex . getMessage ()
? ex . toString ()
: ex . getMessage ());
}
}
private void run () throws IOException , InterruptedException {
System . out . printf ( "Using node %s%n" , nodeUrl );
Long prevHeight = null ;
Long heightChangedAt = null ;
while ( true ) {
final HttpRequest request =
HttpRequest . newBuilder (
URI . create ( nodeUrl + "/chain/height" )). GET (). build ();
final HttpResponse < String > response = HTTP_CLIENT . send (
request , BodyHandlers . ofString ());
if ( 200 != response . statusCode ())
throw new IOException ( "HTTP error! status: "
+ response . statusCode ());
final JsonNode chainHeight = JSON_MAPPER . readTree (
response . body ());
final long height = chainHeight . get ( "height" ). asLong ();
final long irreversibleHeight = Math . max (
0 , height - REWRITE_LIMIT );
final long now = System . currentTimeMillis ();
if ( null != prevHeight && height != prevHeight )
heightChangedAt = now ;
final String heightAgo = null != heightChangedAt
? "%ds ago" . formatted (( now - heightChangedAt ) / 1000 )
: "-" ;
System . out . printf (
"Height: %,10d (changed %s)"
+ " | Irreversible: %,10d%n" ,
height , heightAgo , irreversibleHeight );
prevHeight = height ;
Thread . sleep ( 1000 );
}
}
}
Download source
The snippet uses the NODE_URL environment variable to set the NEM API node.
If no value is provided, a default one is used.
The program runs in an infinite loop, printing a status line every second.
A keyboard interrupt (Ctrl+C) stops the loop.
Code Explanation
Fetching Chain Height
On each iteration, the code sends a GET request to the /chain/height GET endpoint.
The response contains a single height field with the current chain height, the latest block known to the node.
The chain height increases each time a new block is produced (approximately every 60 seconds).
Calculating the Irreversible Height
The rewrite limit is the maximum number of blocks a rollback can undo on NEM, set to 360 blocks (approximately
six hours).
Subtracting the rewrite limit from the current chain height gives the irreversible height .
Any block at or below the irreversible height can no longer be rolled back.
See the Consensus textbook section for details on rollbacks and the
rewrite limit.
Tracking Height Changes
To show how long ago the chain height last changed, the code stores the previous height and the time at which it was
last updated.
Whenever a new block arrives and the height changes, the timestamp is refreshed.
The elapsed time is then displayed alongside the current chain height.
Polling Loop
Each iteration prints a single status line showing:
The current chain height and how many seconds have elapsed since it last changed.
The irreversible height.
The loop then sleeps for one second before querying the node again.
Output
The following output shows a typical run monitoring the chain height and the irreversible height:
Using node http://libertalia.nemtest.net:7890
Height: 659,471 (changed -) | Irreversible: 659,111
Height: 659,471 (changed -) | Irreversible: 659,111
Height: 659,471 (changed -) | Irreversible: 659,111
Height: 659,472 (changed 0s ago) | Irreversible: 659,112
Height: 659,472 (changed 1s ago) | Irreversible: 659,112
Height: 659,472 (changed 2s ago) | Irreversible: 659,112
Height: 659,472 (changed 3s ago) | Irreversible: 659,112
Some highlights from the output:
Before a new block (lines 2 to 4): The chain height remains unchanged while the program continues polling.
A new block arrives (line 5): The chain height advances from 659,471 to 659,472.
The irreversible height advances as well.
Chain height vs. irreversible height
The irreversible height always trails the chain height by the rewrite limit .
Transactions near the chain tip may still be rolled back.
Once their block falls below the rewrite limit, they become irreversible.
Conclusion
This tutorial showed how to:
Next Steps
For an event-driven approach to monitoring new blocks, see the
Listening to New Blocks WebSocket tutorial.