The Cut Wire

BlockArtica1 pts0 comments

The cut wire: autonomous GRANDPA equivocation slashing, end to end · QuantumAI Blockchain | Quantum Blockchain<br>Network notice:Chain RPC unreachable· Public node not responding. We monitor; try again shortly.status.qbc.network ↗

← All postsA blockchain that finalizes is a blockchain that makes promises. GRANDPA, the<br>finality gadget under QuantumAI Blockchain, lets a set of validators vote a block<br>irreversible. Two thirds agree, the block is final, and the rest of the system can<br>treat it as settled. The whole arrangement rests on one assumption: a validator<br>casts at most one vote per round. Cast two conflicting votes in the same round and<br>you are no longer a participant, you are an attacker. That act has a name,<br>equivocation, and a chain that wants to be taken seriously has to make it expensive.

This post is about closing that loop on our chain. It turned out to be a smaller<br>change than the gap looked, for a reason worth writing down: most of the machine was<br>already built. What was missing was a wire between two halves of it.

What slashing actually requires

People say "slashing" as if it is one thing. It is really four, in sequence, and<br>each one has to exist for the next to matter.

First, detection. Somebody has to notice the two conflicting votes. This happens<br>off chain, inside each node, because that is where votes arrive over the gossip<br>network.

Second, a report that can be trusted. A node that shouts "validator X equivocated"<br>cannot be believed on its word, or every node could grief every other. The report<br>has to carry a cryptographic proof: both signed votes, same round, same authority,<br>different targets. Anyone can check the math.

Third, attribution. The proof names a consensus key. Slashing has to hit a stake.<br>So the chain has to map that GRANDPA key back to the bonded validator who put up<br>the stake.

Fourth, the slash itself, applied to that stake, with enough of a delay and enough<br>of a governance brake that an honest validator caught by a bug is not wiped out<br>before a human can look.

We had built three and a half of these. The half that was missing is the<br>interesting part.

The state of the machine before this change

The on chain half was done and tested. Our offences pallet, qbc-offences, has an<br>unsigned extrinsic, report_grandpa_equivocation_unsigned. Hand it an equivocation<br>proof and it does the real work: it re-verifies the proof on chain with the same<br>check_equivocation_proof routine the rest of the Substrate ecosystem uses, it<br>resolves the offending GRANDPA key to a bonded stash through our staking pallet's<br>key registry, and it schedules a slash. The slash is deferred by twenty seven eras,<br>roughly a week, and governance can cancel it in that window with a single call. The<br>pallet also has a pool gate, a ValidateUnsigned implementation, that runs the<br>cryptographic check before the report is even allowed into the transaction pool. A<br>junk report costs the spammer nothing on chain because it never lands. There are<br>seven unit tests over this path, built on genuine signed equivocation proofs, and<br>they cover the cases that matter: a valid proof schedules a deferred slash and never<br>an immediate one, an invalid proof changes nothing, a proof for an unattributable<br>key is rejected, the unsigned path behaves like the signed path, and the pool gate<br>turns away non equivocations.

The node half was done too, though we did not write it. Substrate's GRANDPA voter<br>already watches for equivocations as a side effect of processing votes. When it sees<br>one it builds the proof itself, and it is configured on our nodes with everything it<br>needs to report: a keystore, and an offchain transaction pool to submit through. We<br>had wired both of those in months ago without thinking of them as slashing<br>infrastructure, because they are needed for ordinary operation.

So detection existed. Verification existed. Attribution existed. The deferred slash<br>existed. And yet not one equivocation could ever have been punished, because of how<br>the two halves talk to each other.

The cut wire

The Substrate voter does not know about our custom pallet. It cannot. It is generic<br>code. When it detects an equivocation it reaches into the runtime through a fixed<br>interface, the GrandpaApi, and calls two methods. One asks the runtime to produce<br>a key ownership proof. The other asks the runtime to submit the equivocation report<br>as an extrinsic. The voter's logic is: get the key ownership proof, and if you get<br>one, submit the report.

Both methods, in our runtime, were stubs. generate_key_ownership_proof returned<br>nothing. submit_report_equivocation_unsigned_extrinsic returned nothing too, after<br>logging a warning that an equivocation had been seen and a human should investigate.

That is the whole bug. The voter detected the equivocation, asked the runtime for a<br>key ownership proof, got nothing back, and by its own logic stopped right there. The<br>report was never submitted. The verified, tested, deferred slash path sat downstream<br>of a method...

proof equivocation chain report slash grandpa

Related Articles