ethereum-ibc-rs
is an implementation of a light client based on the sync protocol introduced with the Altair hard fork in the beacon chain (also called as the consensus layer). In addition to verifying beacon headers, it also supports verifying IBC commitments in ibc-solidity using the state_root
of the finalized execution payload contained within the header.
The sync protocol is a protocol that enables efficient verification of the beacon chain using fewer resources than a full node, which is referred to as a light client.
A sync committee is a group of validators, chosen from the active validators of the beacon chain, for every 256 epochs (i.e., 1 period, approximately 27.3h). The sync committee continuously signs block headers for each slot of the beacon chain.
A beacon header signed by the sync committee is referred to as an attested header. We can consider a finalized header if it is based on a finalized checkpoint included in the state root of a valid attested header. Furthermore, a finalized header contains a body_root
, which is the root of a tree constructed with the fields of the BeaconBlockBody
. This BeaconBlockBody
includes an execution_payload
, so we can get a finalized execution payload by traversing a tree with valid attested header as root.
Additionally, the state_root
of an attested header indicates the root of a tree constructed with the fields of the BeaconState
. The BeaconState
includes the public keys of the current and next sync committees based on the attested header's period. By tracking the committee information, which is updated per period, the light client can verify headers of next period.
For more details, refer to this link.
Our light client verifies attestations of headers by the sync committee defined in the sync protocol. Note that our light client will only accept headers where there are sufficient attest stations. This means that it does not track optimistic_header
in the spec.
It also can verify commitments of the IBC contract by traversing a tree with the finalized header as root.
The light client implements the semantics and interfaces of ICS-02.
The state consists of ClientState
, which includes parameters for verification, and ConsensusState
, which contains information based on verified finalized headers and the committee information for the corresponding period. Note that the "Height" in ICS-02 is represented as the block number of the execution layer, while the revision number is always set to 0.
Currently, the light client implementation suports forks bellatrix to electra.
Our light client protocol relies on the following trust assumptions:
- Supermajority (i.e., at least 2/3) of sync committee are honest.
- As liveness assumption, it is assumed that the beacon chain achieves finality in at least one epoch within each period. This is required to get at least one finalized next sync committee from the attested headers per period.
Initially, the light client is set up by a relayer, which is a trustless component, using the ClientState
and the ConsensusState
corresponding to its latest_execution_block_number
.
After initialization, the light client receives Header
s from the relayer, verifies them, and updates its state. The relayer constructs the Header
with LightClientUpdate
and FinalityUpdate
obtained from the beacon chain's RPC, along with height of the consensus state required to verify these Header.consensus_update
.
The header verification by the light client mainly involves the following steps:
- Retrieve the consensus state corresponding to
Header.trusted_sync_committee.height
. - Confirm that the aggregated pubkey of
current_sync_committee
in the consensus state matchesHeader.trusted_sync_committee.sync_committee
. Note that the consensus state maintains both current and next sync committee information, so it is necessary to checkHeader.trusted_sync_committee.is_next
to determine which one it refers to. - Ensure there are sufficient attestations by the sync committee corresponding to the
period
ofHeader.consensus_update.signature_slot
forHeader.consensus_update.attested_header
. - Verify each merkle branch contained in
Header.consensus_update
andHeader.execution_update
. If the verification is successful, the finalized execution payload'sstate_root
andblock_number
andnext_sync_committee
(if contained) can be obtained. - Verify the account existence proof and account storage root from
Header.account_update
with thestate_root
as the root of MPT.
After the all verification process is successful, the light client constructs a new consensus state from the account's storage_root
, block_number
and current/next sync committee information and persists it in the store. These logic is implemented in check_header_and_update_state()
.
Typically, the relayer performs this process when it detects that unrelayed packets are contained in a block older than or equal to the latest finalized block number. However, if calc_period_at_slot(latest ConsensusState.slot) + 1 < calc_period_at_slot(Header.consensus_update.signature_slot)
is true, it is necessary to persits ConsensusState
containing committee information in advance using an intermediate period of LightClientUpdate
to verify subsequent headers.
Based on the storage_root
of the consensus state stored in the light client, it is possible to verify the membership of the commitments in the IBC contract. The storage_root
represents the storage root of the IBC contract corresponding to the ibc_address
of the client state. Therefore, based on the corresponding Merkle Patricia Tree, we can check the existence of commitments for each path defined in IBCCommitment.sol.
Currently, the sync protocol does not define misbehavior for sync committee in the spec. In our light client, we define two types of misbehavior, FinalizedHeaderMisbehaviour
and NextSyncCommitteeMisbehaviour
, following the misbehaviour definition of ICS-02.
If either type of misbehavior is detected, the light client sets the frozenHeight
in the client state to the height of the consensus state referred to check the validity of the misbehavior.
-
FinalizedHeaderMisbehaviour
is defined as the existence of two validconsensus_update
instances satisfying the following conditions:- Both updates are valid with the client's consensus state.
- Each finalized header in the two updates corresponds to the same slot.
- The two finalized headers are different from each other.
-
NextSyncCommitteeMisbehaviour
is defined as the existence of two validconsensus_update
instances satisfying the following conditions:- Both updates are valid with the client's consensus state.
- Each attested header in the two updates corresponds to the same period with a finalized next sync committee.
- The two next sync committees differ from each other.
These logic is implemented in check_misbehaviour_and_update_state()
.
message ClientState {
// `genesis_validators_root` of the target beacon chain's BeaconState
bytes genesis_validators_root = 1;
// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/specs/altair/light-client/sync-protocol.md#misc
uint64 min_sync_committee_participants = 2;
// `genesis_time` of the target beacon chain's BeaconState
uint64 genesis_time = 3;
/// fork parameters of the target beacon chain
ForkParameters fork_parameters = 4;
// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/configs/mainnet.yaml#L69
uint64 seconds_per_slot = 5;
// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/presets/mainnet/phase0.yaml#L36
uint64 slots_per_epoch = 6;
// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/presets/mainnet/altair.yaml#L18
uint64 epochs_per_sync_committee_period = 7;
// An address of IBC contract on execution layer
bytes ibc_address = 8;
// The IBC contract's base storage location for storing commitments
// https://github.com/hyperledger-labs/yui-ibc-solidity/blob/0e83dc7aadf71380dae6e346492e148685510663/docs/architecture.md#L46
bytes ibc_commitments_slot = 9;
// `trust_level` is threshold of sync committee participants to consider the attestation as valid. Highly recommended to be 2/3.
Fraction trust_level = 10;
// `trusting_period` is the period in which the consensus state is considered trusted
google.protobuf.Duration trusting_period = 11;
// `max_clock_drift` defines how much new finalized header's time can drift into the future
google.protobuf.Duration max_clock_drift = 12;
// The latest block number of the stored consensus state
uint64 latest_execution_block_number = 13;
// `frozen_height` is the height at which the client is considered frozen. If `None`, the client is unfrozen.
ibc.core.client.v1.Height frozen_height = 14;
}
message ConsensusState {
// finalized header's slot
uint64 slot = 1;
// the storage root of the IBC contract
bytes storage_root = 2;
// timestamp of finalized header
google.protobuf.Timestamp timestamp = 3;
// aggregate public key of current sync committee
// "current" indicates a period corresponding to the `slot`
bytes current_sync_committee = 4;
// aggregate public key of next sync committee
// "next" indicates `current + 1` period
bytes next_sync_committee = 5;
}