Update and proof formats
An update decodes asMsgUpdateClient, and verifyMembership and verifyNonMembership decode their proof field as MembershipProof. The client state holds the trust settings trustingPeriod, maxClockDrift, and the fraction trustLevel. It also records the counterparty router ibcRouter, the highest accepted height latestHeight, and the isFrozen flag. A consensus state holds one header’s timestamp, state root, and validator list. The client state and consensus state structs are shown in what the client trusts.
The client stores only a hash of each consensus state. Every update and proof therefore includes the full consensus state it relies on. The client checks that state against the stored hash.
Header checks
An update is accepted only when the header passes every check below.
The overlap comparison is inclusive. The requirement is capped at
floor(2n / 3), so that one validator added or removed by a vote cannot stall updates. The floor(n / 3) + 1 term takes precedence over the cap, so more than a third of the trusted validators must always sign. The Besu light client page explains why both rules exist.
An update also rejects a header more than maxClockDrift seconds ahead of this chain’s block time. Misbehaviour evidence skips this check, so a header from the future still counts as evidence.
Interface
This client implements the five functions of the standard light client interface,ILightClient: updateClient, verifyMembership, verifyNonMembership, misbehaviour, and getClientState. The attestation light client page shows the interface.
BesuLightClient adds getConsensusStateHash. It also inherits the bound router and the pause and unpause functions from ICS26ManagedLightClient, described in Permissions and pausing.
Updates and misbehaviour
TwoILightClient functions write the client’s persistent storage: updateClient and misbehaviour.
An update returns one of three results.
An update may be for a height below
latestHeight, as long as it is above its trusted height.
Membership proofs
Both verify functions check a proof about one slot in the counterparty router’s storage. Each returns the trusted timestamp at the proof height.
Each verify call checks its path, which must have exactly three elements.
The client checks the counterparty router’s account proof against the consensus state’s state root. The proven account holds the router’s storage root. It then checks the storage proof at the slot
keccak256(abi.encode(keccak256(path[2]), IBCSTORE_STORAGE_SLOT)). That constant is the base slot of the router’s commitment mapping in IBCStoreUpgradeable.
The proven storage root is cached for the rest of the transaction, keyed by router and height. A later call at that height may then send an empty account proof. Neither function is a view function, because both write that cache.
Misbehaviour evidence
Anyone can submit evidence through the router’ssubmitMisbehaviour, which forwards it to misbehaviour. The evidence is an ABI-encoded MsgSubmitMisbehaviour.
misbehaviour field.
Views
The getters expose the client state and stored hashes, and they still work after a freeze.
The contract cannot return a full consensus state. A caller rebuilds it from the counterparty header at that height: its timestamp, state root, and validator list.
State and storage
The client stores its client state, one consensus state hash per trusted height, and the pause flag. It also caches proven storage roots for the length of one transaction.
The bound router is fixed in the contract’s code as an immutable value. The contract has no delete path, so a stored hash stays for the life of the deployment.
Events
The client emits one event each time it freezes, naming the conflict.
The only other events the client emits are OpenZeppelin Pausable’s
Paused and Unpaused. The client emits no event for a successful update. The router emits ICS02ClientUpdated for every update it forwards.
What freezes the client
The client freezes when an update or misbehaviour evidence shows two consensus states that conflict in one of the four ways listed in Events. The Besu light client page explains what each conflict means. A frozen client rejectsupdateClient, both verify calls, and misbehaviour with FrozenClientState. The getters, pause, and unpause keep working. The contract has no unfreeze function, so replacing the client is the only recovery, as Deployment, registration, and replacement describes.
Errors
The table lists every error the client declares, plus the three router-binding errors it inherits.
Some reverts come from code the client inherits or calls.
A call with malformed ABI or RLP input reverts while that input is decoded.
Permissions and pausing
The client takes no role configuration of its own. It is bound at construction to one router, and that router’s access manager decides who may pause it.
A paused client rejects
updateClient, verifyMembership, and verifyNonMembership, so the router cannot receive, acknowledge, or time out packets through it. The misbehaviour function has no pause check, so a paused client can still be frozen. Permissions on a light client explains how the role lookup works.
Constructor and configuration
The constructor sets the trust settings and the first trusted consensus state, and checks each against its limits.
A failed check reverts with its own error from Errors. The
trustLevel field is a TrustThreshold fraction:
ibc deploy client besu reads it from a recent counterparty header.
No function changes the counterparty router, the trust settings, or the bound router after deployment.
Deployment, registration, and replacement
The client is deployed directly, with no proxy and no upgrade function. Permissions and upgrades lists every proxy and who may upgrade it. The router uses a deployed client onceaddClient registers it under a client ID. That call also records the counterparty’s client ID and Merkle prefix. The router’s prefixedPath appends the commitment path to the last element of the Merkle prefix. The client expects the prefix [ibcRouter, IBCSTORE_STORAGE_SLOT, ""], which gives the three-element path in Membership proofs. The IBC CLI registers that prefix for every Besu client.
Replacing a client is a router operation: the admin-gated migrateClient replaces the contract registered under an existing client ID. That is the only recovery from a freeze, or from a client whose stored consensus states are all past the trusting period.
Migration overwrites the client address and the counterparty information. It copies no state from the old contract, so the replacement starts from its own first consensus state.