feat: punish confirmed light-client attacks at double-sign severity - #71
Conversation
8138d89 to
6ea06c8
Compare
| } else { | ||
| } | ||
|
|
||
| if len(punished) == 0 { |
There was a problem hiding this comment.
This is pretty harsh isn't it?
There was a problem hiding this comment.
Folded into the reply on the other thread: under option A the terminal escalation is dropped outright, under option B it disappears together with the automatic punishment path. Either way this resolves with the choice there.
| // document this as the intended posture ("Light Client Misbehavior: | ||
| // detection and logging"). Validator punishment for equivocation goes through | ||
| // MsgSubmitConsumerDoubleVoting / HandleConsumerDoubleVoting, which slashes | ||
| // against a cryptographically self-contained DuplicateVoteEvidence. Light- |
There was a problem hiding this comment.
Light-
// client misbehaviour evidence is more involved to verify end-to-end against
// a buggy or adversarial consumer chain, and double-vote evidence already
// covers the same validator-equivocation case; punishing twice along two
// different paths is unnecessary.
This concern is still valid, even tho the attack is more involved:
- Create consumer chain
- Provide binary to validators, and pay them (they have no incentive to refuse validating your chain)
- Trigger fake light client attack from your consumer chain binary
- Tombstone the whole AtomOne validator set
There was a problem hiding this comment.
The attack verifies, and it is worth being precise about why: nothing in it is forged. Every signature the punisher would act on is a real signature by a real provider validator's consumer key. The malicious binary orchestrates a fork at the application layer, each node signs each header once believing it legitimate, so the per-node double-sign protection (priv_validator's last-sign state) never triggers. The provider cannot distinguish that evidence from a genuine coalition attack, because the distinction does not exist in the data.
One scoping fact before the options: the same vector already exists on the base branch through MsgSubmitConsumerDoubleVoting, which slashes, jails, and tombstones per validator. A binary that forks the chain makes every honest validator produce valid DuplicateVoteEvidence, and the owner can submit them one by one. So the exposure is a property of punishing consumer-key equivocation under mandatory validation (VAAS has no opt-in), not something this PR introduces. What this PR uniquely adds is coalition-level attribution, and the coalition signal is the more victim-biased one: a same-height double-sign by more than two thirds of the set is not plausible as independent operator error; the dominant explanation is exactly "everyone ran the same binary".
Two concrete resolutions; I would like your pick.
A. Keep automatic punishment, drop the escalation. The byzantine set keeps being slashed, jailed and tombstoned at DoubleSign severity, but an unpunishable attack (amnesia, or all signers unbonded) logs and surfaces instead of moving the consumer to STOPPED terminally. This answers your other comment directly and keeps a confirmed coalition punishable, at the cost of the inconsistency you point at: it still tombstones automatically a set that is most likely victims.
B. Detection plus a governance decision. HandleConsumerMisbehaviour verifies the evidence and stores a confirmed-attack record (byzantine set, height, evidence hash) instead of punishing; a new governance-gated message executes the recorded punishment through the same punishEquivocation primitive, and governance can already stop the chain with MsgRemoveConsumer. Automatic response stays reserved for the per-validator double-vote path, where a lone double-sign is at least plausibly the validator's own fault; coalition punishment, where victims dominate, moves behind human deliberation, the only layer that can weigh what the binary actually was. Note the timing constraint: the record is only punishable while the signers still have bonded stake, so the decision has to fit inside the unbonding period.
I lean B: this PR overturned a deliberate detection-only decision, and your attack is the argument that decision rested on, holding up; B restores it without leaving a confirmed attack unactionable. Either way, the residual exposure gets a security-model entry in #67: consumer-submitted equivocation evidence can tombstone honest provider validators that ran a malicious binary, via the double-vote path; the structural mitigations are model-level (binary vetting on binary_hash, or opt-in validation, which VAAS deliberately does not have).
HandleConsumerMisbehaviour was detection-only: it verified a light-client attack and logged the byzantine set but applied no punishment. It now slashes, jails, and tombstones the validators that signed both conflicting headers at the DoubleSign infraction severity, identical to vote-level double signing. The slash/jail/tombstone path is factored into a shared punishEquivocation primitive that HandleConsumerDoubleVoting also uses, so the two equivocation paths differ only in how the evidence is verified, not in how the equivocation is punished. When an attack is confirmed but no validator can be punished (an amnesia attack has no identifiable signers; other conflicts may leave only unbonded signers), the consumer is stopped and scheduled for removal via StopAndPrepareForConsumerRemoval instead of a silent no-op. The escalation only fires from the launched phase, so re-submitting the same evidence does not schedule removal twice.
6ea06c8 to
fb8ced5
Compare
A confirmed light-client attack on a consumer was previously detection-only:
the byzantine validators were extracted, logged, and returned — no slashing,
jailing, or tombstoning. That left the cheapest way to attack a consumer chain
unpunished, while the equivalent offence via double-voting was tombstoned.
What
HandleConsumerMisbehaviournow punishes:consumer, headers at the same height within the trusting period, genuinely
conflicting, each valid against its trusted consensus state).
same primitive as consumer double-voting — slash, jail, and tombstone at the
double-sign infraction parameters. The punishment code is now factored and
shared (
punishEquivocation); the two evidence types differ only inverification. Already-tombstoned validators are a no-op, so repeated
submissions stay idempotent.
and schedules it for removal rather than no-opping on a proven-compromised
chain. This covers an amnesia attack, which has no attributable signer by
construction, and also the case where every extracted signer turns out to be
unpunishable (all unbonded). This is terminal: nothing moves a consumer out
of
STOPPED, so there is no veto or cancel path — deliberate, since the chainhas been proven byzantine. Escalation only applies to a LAUNCHED consumer; one
already stopping is left alone.
Testing
Unit: identifiable byzantine signers are slashed, jailed, and tombstoned at the
double-sign fraction; repeated submission is idempotent; an unidentifiable
attack stops the consumer and schedules removal; a non-launched consumer is not
escalated. Build, lint, and the module suite are green.