GetCheckpoint
Access Finalized Blockchain State#
The GetCheckpoint method retrieves checkpoint data from Sui's blockchain, providing access to finalized state snapshots that represent guaranteed points of consistency. Checkpoints bundle transactions into atomic units with validator signatures, making them essential for applications requiring verified finality and historical state reconstruction.
Overview#
Checkpoints are fundamental to Sui's consensus mechanism. Every ~0.5 seconds, the network creates a new checkpoint containing a batch of executed transactions. Once a checkpoint receives validator signatures exceeding the quorum threshold, it becomes immutable and represents an irrevocable point of finality. Applications use checkpoints to verify transaction inclusion, reconstruct historical state, and monitor network progression.
Why Checkpoints Matter#
- Guaranteed Finality: Checkpoints mark irreversible blockchain state
- Audit Trail: Complete record of all network state transitions
- State Snapshots: Point-in-time views of the entire network
- Validator Proof: Cryptographic signatures from consensus participants
- Synchronization: Reference points for syncing blockchain state
Method Signature#
Service: sui.rpc.v2beta2.LedgerService
Method: GetCheckpoint
Type: Unary RPC (single request, single response)
Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
sequence_number | string | Conditional* | Checkpoint identifier by sequence number |
digest | string | Conditional* | Checkpoint identifier by digest hash |
read_mask | FieldMask | No | Fields to include in response |
*Either sequence_number OR digest must be provided (mutually exclusive).
Field Mask Options#
| Path | Description |
|---|---|
sequence_number | Monotonically increasing checkpoint identifier |
digest | Cryptographic hash of checkpoint contents |
network_total_transactions | Cumulative transaction count |
previous_digest | Previous checkpoint's digest (chain linking) |
timestamp_ms | Checkpoint finalization timestamp |
transactions | Array of transaction digests in checkpoint |
end_of_epoch_data | Epoch transition data (epoch boundaries only) |
epoch_rolling_gas_cost_summary | Cumulative gas metrics |
Response Structure#
message Checkpoint {
uint64 sequence_number = 1;
string digest = 2;
uint64 network_total_transactions = 3;
string previous_digest = 4;
uint64 timestamp_ms = 5;
repeated string transactions = 6;
EpochData end_of_epoch_data = 7;
GasCostSummary epoch_rolling_gas_cost_summary = 8;
}
Code Examples#
- TypeScript
- Python
- Go
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@groto/proto-loader';
const ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com';
const API_TOKEN = 'your_api_token_here';
const packageDefinition = protoLoader.loadSync(
'./protos/ledger.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['./protos']
}
);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const credentials = grpc.credentials.createSsl();
const client = new protoDescriptor.sui.rpc.v2beta2.LedgerService(
ENDPOINT,
credentials
);
const metadata = new grpc.Metadata();
metadata.add('x-api-key', API_TOKEN);
// Get checkpoint by sequence number
async function getCheckpointBySequence(sequence: string): Promise<any> {
return new Promise((resolve, reject) => {
const request = {
sequence_number: sequence,
read_mask: {
paths: [
'sequence_number',
'digest',
'timestamp_ms',
'network_total_transactions',
'transactions',
'previous_digest'
]
}
};
client.GetCheckpoint(request, metadata, (error: any, response: any) => {
if (error) {
console.error('GetCheckpoint error:', error.message);
reject(error);
return;
}
resolve(response);
});
});
}
// Get checkpoint by digest
async function getCheckpointByDigest(digest: string): Promise<any> {
return new Promise((resolve, reject) => {
const request = {
digest: digest,
read_mask: {
paths: [
'sequence_number',
'digest',
'timestamp_ms',
'transactions'
]
}
};
client.GetCheckpoint(request, metadata, (error: any, response: any) => {
if (error) reject(error);
else resolve(response);
});
});
}
// Example: Verify transaction finality
async function verifyTransactionFinalized(
txDigest: string,
startSequence: string
): Promise<{ finalized: boolean; checkpoint?: string }> {
// Search forward from start sequence
for (let i = parseInt(startSequence); i < parseInt(startSequence) + 100; i++) {
try {
const checkpoint = await getCheckpointBySequence(i.toString());
if (checkpoint.transactions.includes(txDigest)) {
console.log(`✓ Transaction finalized in checkpoint ${checkpoint.sequence_number}`);
return {
finalized: true,
checkpoint: checkpoint.sequence_number
};
}
} catch (error) {
break;
}
}
return { finalized: false };
}
// Example: Calculate network throughput
async function calculateNetworkTPS(
startSequence: string,
endSequence: string
): Promise<number> {
const start = await getCheckpointBySequence(startSequence);
const end = await getCheckpointBySequence(endSequence);
const txCount = parseInt(end.network_total_transactions) -
parseInt(start.network_total_transactions);
const timeSpan = (parseInt(end.timestamp_ms) - parseInt(start.timestamp_ms)) / 1000;
const tps = txCount / timeSpan;
console.log(`Network TPS over ${timeSpan}s: ${tps.toFixed(2)}`);
return tps;
}
// Usage
const latestCheckpoint = await getCheckpointBySequence('193161953');
console.log('Checkpoint:', latestCheckpoint.sequence_number);
console.log('Transactions:', latestCheckpoint.transactions.length);
console.log('Total Network TXs:', latestCheckpoint.network_total_transactions);
import grpc
from datetime import datetime
from google.protobuf import field_mask_pb2
import ledger_service_pb2
import ledger_service_pb2_grpc
ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com'
API_TOKEN = 'your_api_token_here'
class CheckpointQuery:
def __init__(self, endpoint: str, api_token: str):
self.endpoint = endpoint
self.api_token = api_token
self.channel = None
self.client = None
def connect(self):
"""Establish gRPC connection"""
credentials = grpc.ssl_channel_credentials()
self.channel = grpc.secure_channel(self.endpoint, credentials)
self.client = ledger_service_pb2_grpc.LedgerServiceStub(self.channel)
def get_checkpoint_by_sequence(self, sequence: str, fields=None):
"""Retrieve checkpoint by sequence number"""
if not self.client:
self.connect()
metadata = [('x-api-key', self.api_token)]
if fields is None:
fields = [
'sequence_number',
'digest',
'timestamp_ms',
'network_total_transactions',
'transactions',
'previous_digest'
]
request = ledger_service_pb2.GetCheckpointRequest(
sequence_number=sequence,
read_mask=field_mask_pb2.FieldMask(paths=fields)
)
try:
response = self.client.GetCheckpoint(request, metadata=metadata)
return response
except grpc.RpcError as e:
print(f'Error: {e.code()}: {e.details()}')
raise
def analyze_checkpoint(self, sequence: str):
"""Analyze checkpoint details"""
checkpoint = self.get_checkpoint_by_sequence(sequence)
timestamp = datetime.fromtimestamp(checkpoint.timestamp_ms / 1000)
print(f'\nCheckpoint Analysis:')
print(f'==================')
print(f'Sequence: {checkpoint.sequence_number}')
print(f'Digest: {checkpoint.digest}')
print(f'Time: {timestamp.isoformat()}')
print(f'Transactions: {len(checkpoint.transactions)}')
print(f'Network Total: {checkpoint.network_total_transactions}')
return checkpoint
def find_transaction_checkpoint(
self,
tx_digest: str,
start_sequence: int,
search_range: int = 100
):
"""Find which checkpoint contains a transaction"""
for seq in range(start_sequence, start_sequence + search_range):
try:
checkpoint = self.get_checkpoint_by_sequence(str(seq))
if tx_digest in checkpoint.transactions:
print(f'✓ Found in checkpoint {checkpoint.sequence_number}')
return checkpoint.sequence_number
except Exception as e:
print(f'Search ended at sequence {seq}')
break
print('✗ Transaction not found in search range')
return None
def close(self):
if self.channel:
self.channel.close()
# Usage
query = CheckpointQuery(ENDPOINT, API_TOKEN)
try:
# Analyze specific checkpoint
checkpoint = query.analyze_checkpoint('193161953')
# Find transaction
tx_digest = 'HUwTkTFSGzKaDXUpLqZigkMb59J3T82zWQKcvQEL9G3X'
checkpoint_num = query.find_transaction_checkpoint(
tx_digest,
start_sequence=193161950
)
finally:
query.close()
package main
import (
"context"
"fmt"
"log"
"time"
"sui-grpc-client/config"
pb "sui-grpc-client/sui/rpc/v2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/fieldmaskpb"
)
type CheckpointClient struct {
conn *grpc.ClientConn
client pb.LedgerServiceClient
cfg *config.Config
}
func NewCheckpointClient(cfg *config.Config) (*CheckpointClient, error) {
creds := credentials.NewClientTLSFromCert(nil, "")
conn, err := grpc.NewClient(
cfg.Endpoint,
grpc.WithTransportCredentials(creds),
)
if err != nil {
return nil, fmt.Errorf("connection failed: %w", err)
}
return &CheckpointClient{
conn: conn,
client: pb.NewLedgerServiceClient(conn),
cfg: cfg,
}, nil
}
func (cc *CheckpointClient) GetCheckpointBySequence(
ctx context.Context,
sequence uint64,
fields []string,
) (*pb.Checkpoint, error) {
ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", cc.cfg.APIKey)
if len(fields) == 0 {
fields = []string{
"sequence_number",
"digest",
"summary.timestamp",
"summary.epoch",
"summary.total_network_transactions",
"summary.previous_digest",
"contents.transactions",
}
}
seqNum := sequence
request := &pb.GetCheckpointRequest{
CheckpointId: &pb.GetCheckpointRequest_SequenceNumber{
SequenceNumber: seqNum,
},
ReadMask: &fieldmaskpb.FieldMask{
Paths: fields,
},
}
response, err := cc.client.GetCheckpoint(ctx, request)
if err != nil {
return nil, err
}
return response.Checkpoint, nil
}
func (cc *CheckpointClient) GetLatestCheckpoint(
ctx context.Context,
) (*pb.Checkpoint, error) {
ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", cc.cfg.APIKey)
// Empty request returns latest checkpoint
request := &pb.GetCheckpointRequest{}
response, err := cc.client.GetCheckpoint(ctx, request)
if err != nil {
return nil, err
}
return response.Checkpoint, nil
}
func (cc *CheckpointClient) AnalyzeCheckpoint(
ctx context.Context,
sequence uint64,
) error {
checkpoint, err := cc.GetCheckpointBySequence(ctx, sequence, nil)
if err != nil {
return err
}
summary := checkpoint.GetSummary()
if summary == nil {
return fmt.Errorf("checkpoint summary is nil")
}
var timestamp time.Time
if ts := summary.GetTimestamp(); ts != nil {
timestamp = time.Unix(ts.GetSeconds(), int64(ts.GetNanos()))
}
fmt.Println("\n" + fmt.Sprintf("%s", "=")+fmt.Sprintf("%59s", "=")+"\n")
fmt.Println("Checkpoint Analysis")
fmt.Println(fmt.Sprintf("%s", "=")+fmt.Sprintf("%59s", "=")+"\n")
fmt.Printf("Sequence Number: %d\n", checkpoint.GetSequenceNumber())
fmt.Printf("Digest: %s\n", checkpoint.GetDigest())
fmt.Printf("Epoch: %d\n", summary.GetEpoch())
fmt.Printf("Timestamp: %s\n", timestamp.Format(time.RFC3339))
contents := checkpoint.GetContents()
if contents != nil {
fmt.Printf("Transactions: %d\n", len(contents.GetTransactions()))
}
fmt.Printf("Network Total: %d\n", summary.GetTotalNetworkTransactions())
if prevDigest := summary.GetPreviousDigest(); prevDigest != "" {
fmt.Printf("Previous Digest: %s\n", prevDigest)
}
// Show first few transaction digests if available
if contents := checkpoint.GetContents(); contents != nil {
transactions := contents.GetTransactions()
if len(transactions) > 0 {
fmt.Println("\nTransaction Digest Samples:")
limit := 3
if len(transactions) < limit {
limit = len(transactions)
}
for i := 0; i < limit; i++ {
txInfo := transactions[i]
fmt.Printf("\n [%d] Transaction: %s\n", i+1, txInfo.GetTransaction())
fmt.Printf(" Effects: %s\n", txInfo.GetEffects())
if len(txInfo.GetSignatures()) > 0 {
fmt.Printf(" Signatures: %d\n", len(txInfo.GetSignatures()))
}
}
if len(transactions) > limit {
fmt.Printf("\n ... and %d more transactions\n", len(transactions)-limit)
}
}
}
fmt.Println(fmt.Sprintf("%s", "=")+fmt.Sprintf("%59s", "="))
return nil
}
func (cc *CheckpointClient) Close() error {
return cc.conn.Close()
}
func main() {
// Load configuration from .env file
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
client, err := NewCheckpointClient(cfg)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Get latest checkpoint
fmt.Println("Fetching latest checkpoint...")
latest, err := client.GetLatestCheckpoint(ctx)
if err != nil {
log.Fatalf("Failed to get latest checkpoint: %v", err)
}
latestSeq := latest.GetSequenceNumber()
fmt.Printf("Latest checkpoint: %d\n", latestSeq)
// Analyze a recent checkpoint
if err := client.AnalyzeCheckpoint(ctx, latestSeq); err != nil {
log.Fatalf("Analysis failed: %v", err)
}
}
Use Cases#
1. Transaction Finality Verification#
Confirm a transaction reached immutable finality:
interface FinalityStatus {
isFinalized: boolean;
checkpointSequence?: string;
confirmations?: number;
}
async function checkTransactionFinality(
txDigest: string,
expectedCheckpoint: string
): Promise<FinalityStatus> {
const checkpoint = await getCheckpointBySequence(expectedCheckpoint);
const isIncluded = checkpoint.transactions.includes(txDigest);
if (isIncluded) {
// Get latest checkpoint to calculate confirmations
// (In production, you'd track the latest known checkpoint)
const latestSequence = parseInt(expectedCheckpoint) + 1000;
const confirmations = latestSequence - parseInt(checkpoint.sequence_number);
return {
isFinalized: true,
checkpointSequence: checkpoint.sequence_number,
confirmations: confirmations
};
}
return { isFinalized: false };
}
2. Historical State Reconstruction#
Reconstruct blockchain state at specific checkpoint:
interface StateSnapshot {
sequence: string;
timestamp: Date;
totalTransactions: number;
includedTransactions: string[];
}
async function captureStateSnapshot(
checkpointSequence: string
): Promise<StateSnapshot> {
const checkpoint = await getCheckpointBySequence(checkpointSequence);
return {
sequence: checkpoint.sequence_number,
timestamp: new Date(parseInt(checkpoint.timestamp_ms)),
totalTransactions: parseInt(checkpoint.network_total_transactions),
includedTransactions: checkpoint.transactions
};
}
// Create timeline of snapshots
async function createStateTimeline(
startSequence: string,
endSequence: string,
interval: number
): Promise<StateSnapshot[]> {
const timeline: StateSnapshot[] = [];
const start = parseInt(startSequence);
const end = parseInt(endSequence);
for (let seq = start; seq <= end; seq += interval) {
const snapshot = await captureStateSnapshot(seq.toString());
timeline.push(snapshot);
}
return timeline;
}
3. Network Performance Monitoring#
Track network throughput metrics:
interface NetworkMetrics {
periodStart: Date;
periodEnd: Date;
totalTransactions: number;
avgTPS: number;
peakTPS: number;
checkpointsAnalyzed: number;
}
async function analyzeNetworkPerformance(
startSequence: string,
count: number
): Promise<NetworkMetrics> {
const checkpoints = [];
for (let i = 0; i < count; i++) {
const seq = (parseInt(startSequence) + i).toString();
const checkpoint = await getCheckpointBySequence(seq);
checkpoints.push(checkpoint);
}
const first = checkpoints[0];
const last = checkpoints[checkpoints.length - 1];
const totalTxs = parseInt(last.network_total_transactions) -
parseInt(first.network_total_transactions);
const timeSpan = (parseInt(last.timestamp_ms) - parseInt(first.timestamp_ms)) / 1000;
const avgTPS = totalTxs / timeSpan;
// Calculate peak TPS from individual checkpoints
let peakTPS = 0;
for (let i = 1; i < checkpoints.length; i++) {
const txCount = checkpoints[i].transactions.length;
const timeDiff = (parseInt(checkpoints[i].timestamp_ms) -
parseInt(checkpoints[i-1].timestamp_ms)) / 1000;
const tps = txCount / timeDiff;
peakTPS = Math.max(peakTPS, tps);
}
return {
periodStart: new Date(parseInt(first.timestamp_ms)),
periodEnd: new Date(parseInt(last.timestamp_ms)),
totalTransactions: totalTxs,
avgTPS: avgTPS,
peakTPS: peakTPS,
checkpointsAnalyzed: count
};
}
4. Checkpoint Chain Validation#
Verify checkpoint chain integrity:
async function validateCheckpointChain(
startSequence: string,
count: number
): Promise<boolean> {
let previousDigest: string | null = null;
for (let i = 0; i < count; i++) {
const seq = (parseInt(startSequence) + i).toString();
const checkpoint = await getCheckpointBySequence(seq);
if (i > 0 && checkpoint.previous_digest !== previousDigest) {
console.error(`Chain break at sequence ${seq}`);
console.error(`Expected previous: ${previousDigest}`);
console.error(`Got previous: ${checkpoint.previous_digest}`);
return false;
}
previousDigest = checkpoint.digest;
}
console.log(`✓ Validated ${count} checkpoints in chain`);
return true;
}
Best Practices#
1. Cache Checkpoint Data#
Checkpoints are immutable once finalized:
const checkpointCache = new Map<string, any>();
async function getCachedCheckpoint(sequence: string): Promise<any> {
if (checkpointCache.has(sequence)) {
return checkpointCache.get(sequence);
}
const checkpoint = await getCheckpointBySequence(sequence);
checkpointCache.set(sequence, checkpoint);
return checkpoint;
}
2. Use Field Masking#
Request only required data:
// ✅ Good: Minimal fields
const request = {
sequence_number: seq,
read_mask: {
paths: ['sequence_number', 'transactions']
}
};
// ❌ Bad: All fields
const request = { sequence_number: seq };
3. Handle Missing Checkpoints#
Not all sequence numbers may exist:
async function safeGetCheckpoint(sequence: string): Promise<any | null> {
try {
return await getCheckpointBySequence(sequence);
} catch (error: any) {
if (error.code === grpc.status.NOT_FOUND) {
console.log(`Checkpoint ${sequence} not found`);
return null;
}
throw error;
}
}
Related Methods#
- SubscribeCheckpoints - Real-time checkpoint streaming
- GetTransaction - Transaction details from checkpoint
Need help with checkpoints? Contact support@dwellir.com or check the gRPC overview.