fix: harden consumer VSC and evidence packet handling - #69
Open
giunatale wants to merge 1 commit into
Open
Conversation
giunatale
requested review from
clockworkgr,
julienrbrt and
tbruyelle
as code owners
July 30, 2026 16:03
Contributor
Author
- reject vsc packets whose validator updates carry an undecodable consensus pubkey or a negative power in ValidatorSetChangePacketData.Validate, so a malformed packet is error-acked on receipt instead of flowing through and panicking ApplyCCValidatorChanges at EndBlock (a chain halt); the decode panic stays as an unreachable defensive backstop. - keep downtime evidence alive across delivery failures without ever looping: a sent packet is removed only on a success ack; an ibc timeout re-queues it from the callback payload (non-delivery, so retrying is correct); an error ack means the provider evaluated and permanently rejected that exact packet, so the consumer drops it and emits a consumer_evidence_rejected event (validator, window-end height, ack error) rather than retrying it forever. - key pending evidence by (validator, window-end height) so a later window's evidence queues alongside an earlier still-unsent one (e.g. the provider client is down across consecutive windows); both are reported on recovery. genesis export/import rebuild the window-end key component from the payload. tests: bad-pubkey vsc is error-acked and never reaches the EndBlock apply; a timed-out packet re-queues while an error-acked one is dropped with exactly one rejection event; two windows during a stalled send both stay queued and send on recovery; a success ack neither re-queues nor emits. an e2e in the liveness suite forces two downtime-evidence packets to genuinely expire through a real relayer outage and asserts both windows are re-queued (per window) and ultimately accepted by the provider.
giunatale
force-pushed
the
giunatale/fix/consumer-packet-hardening
branch
from
July 31, 2026 18:41
e90972d to
7c12253
Compare
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.
Three consumer packet-lifecycle fixes: malformed validator-set updates are
rejected at receipt instead of halting the chain, downtime evidence survives
delivery failure without ever retrying forever, and evidence for consecutive
offence windows no longer coalesces away.
Malformed updates: error-ack, not chain halt
A validator-set update carrying an undecodable consensus pubkey or a negative
power previously flowed through receipt untouched and panicked at EndBlock when
applied — a chain halt any counterparty bug could trigger.
ValidatorSetChangePacketDatais now validated on receipt and rejected with anerror acknowledgement; the EndBlock decode panic remains only as an unreachable
backstop.
Evidence survives timeouts; permanent rejections stop cleanly
A sent downtime-evidence packet was deleted at send time, so an IBC timeout
meant the evidence was lost forever — a validator could escape a downtime
accusation because a relayer was down. Now:
the callback payload and it is re-sent.
rejected that exact packet, so retrying could never succeed: the consumer
drops it and emits an event carrying the validator, window-end height, and
the acknowledgement error, so the rejection is surfaced rather than silently
looping one packet per block forever.
Per-window evidence keying
Pending evidence was keyed by validator only, so if the provider client was
down across two offence windows, the second window's evidence replaced the
first. It is now keyed (validator, window-end height); both windows queue side
by side and both are delivered on recovery. Genesis export/import rebuilds the
key from the payload.
Testing
timed-out packet re-queues while an error-acked one is dropped with exactly
one rejection event; two windows during a stalled send both stay queued and
both send on recovery; success acks neither re-queue nor emit.
validator accrues genuine per-validator downtime evidence, the relayer is
paused across two window closes so both evidence packets genuinely expire on
the consumer's clock (20s packet timeout), the relayer's
MsgTimeoutdeliveries trigger re-queues for both windows, and both windows are
ultimately accepted into the provider's pending downtime slashes — impossible
before this change, where the evidence died with the timeout. Both Docker
e2e suites green. (Harness enablement included: the liveness provider
genesis gets the shortened downtime windows, the native slashing window is
widened so the silent validator isn't natively jailed mid-test, and the
short-unbonding consumer's packet timeout drops from 600s to 20s.)