Home

Awesome

tendermint-sol

Solidity implementation of IBC (Inter-Blockchain Communication Protocol) compatible Tendermint Light Client intended to run on Celo EVM (but not limited to it).

Features:

The Light Client comes in two branches:

Light client in the nutshell

The light client ingests the block headers coming from the full node and verifies them. Once the verification succeeds, the light client will update its ConsensusState with:

We can verify the inclusion/exclusion in the Merkle tree with a valid proof and commitment root. For example, you can check if a transaction has been committed to transactions Merkle tree. But, how can you trust the commitment root? How do you know it has not been forged?

Both values are members of the block header, so we need to check whether a header is valid. The verification requires:

The core verification is quite simple. We build the Canonical structures with provided data, serialize them (with protobuf) and check via the cryptographic function (e.g., ed25519) if it matches the given signature. The validator set hash is checked against the ConsensusState prior signature verification to ensure the trust to validator set.

The light client offers two verification modes:

Sequential mode is obvious, but when would one use the non-adjacent method?

Syncing up headers after some time (e.g., relayer was down) might be expensive because the light client must process all missing headers up to the latest one. With the non-adjacent mode, we can quick-sync to the latest height, but it requires a trusted_validator_set to be passed on additionally.

At the time of writing, the Cosmos Hub validator set contains 150 validators, so:

To learn more about the light client theory, see this article

Performance analysis

The benchmark aims to gauge the gas usage across the Tendermint Light Client contract and help out to identify potential optimization areas.

There are a few segments/tests outlined:

Some of the segments can also be measured via unittests (see test/.*js).

Setup

Running tests

The Rust Demo program relays four headers from the Tendermint RPC node (e.g., cosmos hub) and calls light client code, particularly CreateClient and CheckHeaderAndUpdateState. In the non-adjacent mode, the second header is being skipped.

cd test/demo

# adjacent mode
cargo run  -- --max-headers 4 --celo-gas-price 500000000 --celo-usd-price 5.20 --tendermint-url "https://rpc.atomscan.com" --gas 40000000 --celo-url http://localhost:8545 --from-height 8619996

# non-adjacent mode
cargo run  -- --max-headers 4 --celo-gas-price 500000000 --celo-usd-price 5.20 --tendermint-url "https://rpc.atomscan.com" --gas 40000000 --celo-url http://localhost:8545 --from-height 8619996 --non-adjacent-mode

Vanilla Client (branch: main)

header heightsmodesegmentGas (init)gas (h2)gas (h3)gas (h4)
8619996-8619999adjacentall359531164000331638049016404617
8619996-8619999adjacentno-precompile373215162935001627396016297936
8619996-8619999adjacentno-check-validity373215126349041261698412638759
8619996-8619999adjacentunmarshal-header359531122581091228648012308085
8619996-8619999adjacentearly-return373215479499524934525989
--------------
8619996-8619999non-adjacentall373215--2673446623511707
8619996-8619999non-adjacentno-precompile359531--2657797523394015
8619996-8619999non-adjacentno-check-validity359531--1938627719407358
8619996-8619999non-adjacentunmarshal-header373215--1899229019059787
8619996-8619999non-adjacentearly-return359531--640815688152

heightmodebase costserialization costcheck-validity costprecompile costtotalgas limitgas usage
8619997adjacent4794991177861037651291065331640003320M82.00 %
----2.923 %71.820 %22.958 %0.6495 %100 %----
8619998non-adjacent5249341846735673481891564912673446620M133.67 %
----1.963 %69.07 %27.485 %0.585 %100 %----

Optimized Client (branch: optimized)

header heightsmodesegmentGas (init)gas (h2)gas (h3)gas (h4)
8619996-8619999adjacentall373191126572901263857112662331
8619996-8619999adjacentno-precompile359507125600731254135512565112
8619996-8619999adjacentno-check-validity373191962713096099759631564
8619996-8619999adjacentunmarshal-header373191936493493476509369069
8619996-8619999adjacentearly-return359507418250463841464895
--------------
8619996-8619999non-adjacentall359507--1797639115550856
8619996-8619999non-adjacentno-precompile373191--1784358415450647
8619996-8619999non-adjacentno-check-validity359507--1244249712463389
8619996-8619999non-adjacentunmarshal-header359507--1217564912196539
8619996-8619999non-adjacentearly-return373191--518520565852

heightmodebase costserialization costcheck-validity costprecompile costtotalgas limitgas usage
8619997adjacent41825089466843030160972171265729020M63.28 %
----3.30 %70.68 %23.94 %0.768 %100 %----
8619998non-adjacent5185201165712955338941328071797639120M89.88 %
----2.88 %64.846 %30.784 %0.738 %100 %----

Results overview

By looking at the results, it's clear that:

The optimized branch removes unused fields from proto/TendermintLight.proto and flattens some structures such as PublicKey to reduce deserialization costs. As shown, the gas usage in non-adjacent mode was lowered from 26734466 to 17976391 (89.88% of max allowed gas).

The Light Client contract fits into Celo Blockchain, but running it may be expensive.

Potential optimizations:

NOTE: gas limit (20M) is the maximum allowed gas per block on Celo blockchain mainnet (2021-12-16)

Quick Start

git clone https://github.com/ChorusOne/tendermint-sol.git
cd tendermint-sol && git checkout optimized

export NETWORK=celo

# deploy with truffle
make deploy

# run demo program (local celo node must be running)
cd test/demo
cargo run  -- --max-headers 4 --tendermint-url "https://rpc.atomscan.com" --gas 20000000 --celo-url http://localhost:8545 --from-height 8619996

Demo

asciicast