Skip to content

Checkpoints

Checkpoints are vital components of the Polygon network, representing snapshots of the Bor chain state. These checkpoints are attested by a majority of the validator set before being validated and submitted on Ethereum contracts.

Heimdall, an integral part of this process, manages checkpoint functionalities using the checkpoint module. It coordinates with the Bor chain to verify checkpoint root hashes when a new checkpoint is proposed.

Checkpoint life-cycle and types

Life-cycle

Heimdall selects the next proposer using Tendermint’s leader selection algorithm. The multi-stage checkpoint process is crucial due to potential failures when submitting checkpoints on the Ethereum chain caused by factors like gas limit, network traffic, or high gas fees.

Each checkpoint has a validator as the proposer. The outcome of a checkpoint on the Ethereum chain (success or failure) triggers an ack (acknowledgment) or no-ack (no acknowledgment) transaction, altering the proposer for the next checkpoint on Heimdall.

Flow chart representations

Types and structures

Checkpoint block header

type CheckpointBlockHeader struct {
 Proposer        types.HeimdallAddress `json:"proposer"`
 StartBlock      uint64                `json:"startBlock"`
 EndBlock        uint64                `json:"endBlock"`
 RootHash        types.HeimdallHash    `json:"rootHash"`
 AccountRootHash types.HeimdallHash    `json:"accountRootHash"`
 TimeStamp       uint64                `json:"timestamp"`
}

Root hash calculation

Root Hash Image

The RootHash is calculated as a Merkle hash of Bor block hashes from StartBlock to EndBlock. The process involves hashing each block’s number, time, transaction hash, and receipt hash, then creating a Merkle root of these hashes.

blockHash = keccak256([number, time, tx hash, receipt hash])

Pseudocode for the root hash for 1 to n Bor blocks:

B(1) := keccak256([number, time, tx hash, receipt hash])
B(2) := keccak256([number, time, tx hash, receipt hash])
.
.
.
B(n) := keccak256([number, time, tx hash, receipt hash])

// checkpoint is Merkle root of all block hash
checkpoint's root hash = Merkel[B(1), B(2), ....., B(n)]

Here are some snippets of how checkpoint is created from Bor chain block headers.

Source: https://github.com/maticnetwork/heimdall/blob/develop/checkpoint/types/merkel.go#L60-L114

// Golang representation of block data used in checkpoint
blockData := crypto.Keccak256(appendBytes32(
 blockHeader.Number.Bytes(),
 new(big.Int).SetUint64(blockHeader.Time).Bytes(),
 blockHeader.TxHash.Bytes(),
 blockHeader.ReceiptHash.Bytes(),
))

// array of block hashes of Bor blocks
headers := [blockData1, blockData2, ..., blockDataN]

// merkel tree
tree := merkle.NewTreeWithOpts(merkle.TreeOptions{EnableHashSorting: false, DisableHashLeaves: true})
tree.Generate(convert(headers), sha3.NewLegacyKeccak256())

// create checkpoint's root hash
rootHash := tree.Root().Hash

AccountRootHash

AccountRootHash is the hash of the validator account-related information that needs to pass to the Ethereum chain at each checkpoint.

eachAccountHash := keccak256([validator id, withdraw fee, slash amount])

Pseudocode for the account root hash for 1 to n Bor blocks:

B(1) := keccak256([validator id, withdraw fee, slash amount])
B(2) := keccak256([validator id, withdraw fee, slash amount])
.
.
.
B(n) := keccak256([validator id, withdraw fee, slash amount])

// account root hash is Merkle root of all block hash
checkpoint's account root hash = Merkel[B(1), B(2), ....., B(n)]

Golang code for the account hash can be found here: https://github.com/maticnetwork/heimdall/blob/develop/types/dividend-account.go#L91-L101

// DividendAccount contains Fee, Slashed amount
type DividendAccount struct {
 ID            DividendAccountID `json:"ID"`
 FeeAmount     string            `json:"feeAmount"`     // string representation of big.Int
 SlashedAmount string            `json:"slashedAmount"` // string representation of big.Int
}

// calculate hash for particular account
func (da DividendAccount) CalculateHash() ([]byte, error) {
 fee, _ := big.NewInt(0).SetString(da.FeeAmount, 10)
 slashAmount, _ := big.NewInt(0).SetString(da.SlashedAmount, 10)
 divAccountHash := crypto.Keccak256(appendBytes32(
  new(big.Int).SetUint64(uint64(da.ID)).Bytes(),
  fee.Bytes(),
  slashAmount.Bytes(),
 ))

 return divAccountHash, nil
}

Messages in checkpoint module

MsgCheckpoint

MsgCheckpoint handles checkpoint verification on Heimdall, utilizing RLP encoding for Ethereum chain verification. It prioritizes transactions with high gas consumption to ensure only one MsgCheckpoint transaction per block.

// MsgCheckpoint represents checkpoint transaction
type MsgCheckpoint struct {
 Proposer        types.HeimdallAddress `json:"proposer"`
 StartBlock      uint64                `json:"startBlock"`
 EndBlock        uint64                `json:"endBlock"`
 RootHash        types.HeimdallHash    `json:"rootHash"`
 AccountRootHash types.HeimdallHash    `json:"accountRootHash"`
}

MsgCheckpointAck

MsgCheckpointAck manages successful checkpoint submissions, updating the checkpoint count and clearing the checkpointBuffer.

// MsgCheckpointAck represents checkpoint ack transaction if checkpoint is successful
type MsgCheckpointAck struct {
 From        types.HeimdallAddress `json:"from"`
 HeaderBlock uint64                `json:"headerBlock"`
 TxHash      types.HeimdallHash    `json:"tx_hash"`
 LogIndex    uint64                `json:"log_index"`
}

MsgCheckpointNoAck

MsgCheckpointNoAck deals with unsuccessful checkpoints or offline proposers, allowing a timeout period before selecting a new proposer.

// MsgCheckpointNoAck represents checkpoint no-ack transaction
type MsgCheckpointNoAck struct {
 From types.HeimdallAddress `json:"from"`
}

Parameters and CLI commands

Parameters

The checkpoint module contains the following parameters:

Key Type Default value
CheckpointBufferTime uint64 1000 * time.Second

CLI commands

Commands are available for various actions such as sending checkpoints, sending ack or no-ack transactions, and querying parameters.

Printing all parameters

heimdallcli query checkpoint params --trust-node

Expected Result:

checkpoint_buffer_time: 16m40s

Send Checkpoint

Following command sends checkpoint transaction on Heimdall:

heimdallcli tx checkpoint send-checkpoint \
 --start-block=<start-block> \
 --end-block=<end-block> \
 --root-hash=<root-hash> \
 --account-root-hash=<account-root-hash> \
 --chain-id=<chain-id>

Send ack

Following command sends ack transaction on Heimdall if checkpoint is successful on Ethereum:

heimdallcli tx checkpoint send-ack \
 --tx-hash=<checkpoint-tx-hash>
 --log-index=<checkpoint-event-log-index>
 --header=<checkpoint-index> \
  --chain-id=<chain-id>

Send no-ack

Following command send no-ack transaction on Heimdall:

heimdallcli tx checkpoint send-noack --chain-id <chain-id>

REST APIs

Heimdall provides several REST APIs for interacting with the checkpoint module, including endpoints for preparing messages, querying checkpoints, and more.

Name Method Endpoint
It returns the prepared msg for ack checkpoint POST /checkpoint/ack
It returns the prepared msg for new checkpoint POST /checkpoint/new
It returns the prepared msg for no-ack checkpoint POST /checkpoint/no-ack
Checkpoint by number GET /checkpoints/<checkpoint-number>
Get current checkpoint buffer state GET /checkpoints/buffer
Get checkpoint counts GET /checkpoints/count
Get last no-ack details GET /checkpoints/last-no-ack
Get latest checkpoint GET /checkpoints/latest
All checkpoints GET /checkpoints/list
It returns the checkpoint parameters GET /checkpoints/parama
It returns the prepared checkpoint GET /checkpoints/prepare
Get ack count, buffer, validator set, validator count and last-no-ack details GET /overview

For more details and the response format of these APIs, visit Heimdall API Documentation.


Last update: December 5, 2023
Authors: Nadim Kobeissi