Skip to content

fix: harden consumer VSC and evidence packet handling - #69

Open
giunatale wants to merge 1 commit into
giunatale/feat/offline-detectionfrom
giunatale/fix/consumer-packet-hardening
Open

fix: harden consumer VSC and evidence packet handling#69
giunatale wants to merge 1 commit into
giunatale/feat/offline-detectionfrom
giunatale/fix/consumer-packet-hardening

Conversation

@giunatale

Copy link
Copy Markdown
Contributor

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.
ValidatorSetChangePacketData is now validated on receipt and rejected with an
error 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:

  • A packet is removed only on a success acknowledgement.
  • On an IBC timeout (non-delivery) the consumer re-queues the evidence from
    the callback payload and it is re-sent.
  • On an error acknowledgement the provider has evaluated and permanently
    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

  • Unit: bad-pubkey update is error-acked and never reaches the apply path; 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
    both send on recovery; success acks neither re-queue nor emit.
  • A new e2e subtest in the liveness suite forces the real thing: a silent
    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 MsgTimeout
    deliveries 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.)

@giunatale

Copy link
Copy Markdown
Contributor Author

Branched from giunatale/feat/offline-detection (#63).
Opened against that branch so only this PR's commit shows; will retarget to main after #63 lands and this rebases.

- 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
giunatale force-pushed the giunatale/fix/consumer-packet-hardening branch from e90972d to 7c12253 Compare July 31, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant