fix(ibc): dynamic block-time-aware packet timeout with p95 estimation and recalibration - #170
Merged
JamesEjembi merged 1 commit intoAug 27, 2026
Conversation
… and recalibration (closes VeriNode-Labs#138)
Contributor
Author
|
@JamesEjembi Please Review |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #138
IBC packet commitment timeouts were computed against a fixed block-time assumption, so a chain with variable block times (2-30s range) could see packets expire prematurely on fast blocks or sit needlessly long on slow ones. This adds a rolling block-time estimator (EMA + p95) and a corrected height-based timeout formula, plus a misestimation-rate recalibration loop.
The blueprint's own suggested formula fails its own accuracy bar
The issue proposes
source_height + ceil(timeout_delta / p95_block_time_ms). This has two independent bugs.Bug 1 — units.
timeout_deltais stated in seconds (10-300s);p95_block_time_msis milliseconds. The formula as written under-counts blocks by exactly 1000×, always flooring. Fixed by carryingtimeout_delta_msthroughout.Bug 2 — wrong statistic (the substantive one). A timeout budget divides across a sum of N sequential block-time samples. By the Law of Large Numbers, that sum's expectation is
N × mean, notN × p95. Dividing by p95 systematically under-counts the blocks needed, computing a timeout height that's too low — the exact premature-expiry symptom this issue exists to fix, made worse rather than better by the blueprint's own suggestion.Measured on the issue's own stated scenario (2s average block time, periodic spikes to 30s), naive
÷p95:÷p95(blueprint as written)÷meanWorth noting: the naive baseline's failure direction surprised the initial hand-derivation, which predicted late-timeout bias. Building the naive version as an actual comparison implementation (not just trusting the theoretical prediction) surfaced this: at high margin ratios,
ceil(delta/p95)withp95=30sagainst a 60s window produces too few blocks and expires early, not late. The corrected÷meanformula, with p95 retained only for cold-start sizing and dispersion diagnostics, is what actually clears the issue's >95% accuracy requirement.Other blueprint corrections
source_heightis a misnomer. The timeout height must be computed on the destination chain — that's whose block time governs when the timeout height is reached. Enforced structurally in the new types so a caller can't accidentally pass the source chain's height.timeout_delta < mean_block_time_msis not expressible at height granularity. Rather than silently quantizing to a distorted value and then emitting a misestimation event for a physically impossible request, this returns an explicit error at configuration time.The 50% recalibration-direction threshold — labeled honestly
Gating recalibration on "dominant misestimation direction" needs a concrete threshold. 50% is a judgment call, not a derived constant — flagging this explicitly rather than presenting it as load-bearing math the way the mean-vs-p95 correction is. The mechanism (comparing early-vs-late misestimation counts within the recalibration window, gated so a mixed/ambiguous signal doesn't trigger a margin flip either direction) is what matters; the exact percentage is open to your adjustment. Boundary-tested at exactly 50% (does not trigger) as well as the existing 5% overall-recalibration threshold.
Distinguishing "variable" from "uniformly slow"
The 1.5× cold-start margin only helps for the first 10 packets to a new chain — it can't compensate for a chain that's simply, consistently slow (e.g., a steady 28-30s block time, no real variance). The estimator must converge to reflect sustained latency as accurate, not perpetually treat it as misestimated. A companion simulation confirms: a consistently 25-30s-block chain shows no elevated misestimation rate once past cold-start — the fix distinguishes genuine variance from high-but-stable latency rather than conflating them.
Tests
naive_p95_baselinevscorrected_mean_basedcomparison test — the artifact that surfaced the early/late direction discrepancy above.Changed/new files
Cargo.toml
src/cross-chain/ibc/mod.rs (new)
src/cross-chain/ibc/block-time-estimator.rs (new)
src/cross-chain/ibc/packet-timeout.rs (new)
src/cross-chain/ibc/packet-relayer.rs (new)
src/cross-chain/light_client.rs (modified — destination-height query hookup)
src/cross-chain/mod.rs (modified — module registration)
tests/cross-chain/ibc_packet_timeout_test.rs (new)
Verification
cargo build --workspace✅cargo test --workspace✅cargo fmt --all -- --check✅cargo clippy --workspace --all-targets --all-features -- -D warnings✅git diff --stat upstream/main— matches the file list above exactly, nothing else touched