Lockout Violation Detection
Summary
An algorithm designed to catch validators that violate lockout rules when voting.
Motivation
Validators that violate lockout rules unfairly earn rewards and put cluster. consensus at risk
Alternatives Considered
None
New Terminology
None
Detailed Design
Assume:
-
A database that we will store vote information ingested from gossip/turbine, which in turn will be consulted to detect lockout violations as follows.
-
Tracking of all SlotHashes on the canonical/rooted fork in the database for the last epoch.
The following sections will go over the possible lockout violations that will be detected and punished.
Rooting a different fork
If there are any votes made that have a root R
missing from the rooted
SlotHashes list, slash them on the rooted fork since they have committed
to a different fork
Removing lockouts
Attempting to illegally remove a lockout in a newer vote that that should have still existed based on an older vote is a lockout violation. We detect this as follows:
-
For all non root slots
S
in each vote, track a range(S, S + 2^n)
wheren
is the confirmation count. For each slotS
we only have to track the greatest such lockout range in the database. -
For each new vote
V
, for each slotS
in the vote, lookup in the database to see if there's some range(X, X + 2^n)
whereS
is in this range, butX
is missing from the voteV
. This is a lockout violation because this implies that the validator made a vote whereS
popped offX
, but the lockout onX
from an earlier vote should have prevented that from happening.
Note for each interval (S, S + 2^n)
we also need to be able to lookup the
vote. This is important for being able to pull up the votes later as part of a
proof.
Reducing lockout
Attempting to illegally reduce a lockout from an older vote in a newer vote is a lockout violation.
The tricky bit of this is determining that one vote V
is older than
another vote V'
. To this end, we track a flag is_vote_earlier
whose usage
we will highlight in the protocol below.
-
We set
is_vote_earlier = false
. -
For each newly seen vote
V
made by a validatorX
, for each slotS
inV
:- If
S
exists in the database:- Compare the lockout
L_V
in the voteV
onS
against the greatest lockoutL_D
in the database for that slotS
made by that validatorX
.- If
L_V
<L_D
, setis_vote_earlier=true
. - If
L_V
==L_D
, continue. - If
L_V > L_D
, check if the flagis_vote_earlier=true
. If so, this implies this voteV
was older than some voteV'
that comitted the greaterS'
lockout to the database, yetV
has a lesser lockout onS
, which means the validator reduced lockout onS
in a later vote. This is a lockout violation.
- If
- Compare the lockout
- If
S
does not exist in the database, the aboveRemoving lockouts
section describes the protocol that will catch violations.
- If
Reducing roots
Reducing the root from earlier to later votes is a lockout violation. We detect this as follows:
-
For each validator we track a rooted set in the database. We can remove an interval
(S, S + 2^n)
from the database once the slot becomes a root add it to a rooted set for this validator, and any new votes < root also get added to rooted set. -
When we see a vote with root N on the main fork, then we remove all intervals
(M, P)
whereM < N && P >= N
and addM
to the rooted set.
So for example if we see:
(Assume {slot: confirmation count}
format)
-
{root: 0, 1: 4, 3: 3, 5: 2, 7: 1}
-
{root: 5, 7: 1}
-
Then we add
{1, 3}
to rooted set and remove their intervals from the interval tree because both of those are< 5
, but have lockouts that extend past5
Note here also that that artificially increasing your lockout is not a slashable offense (here we root 5 however 7 still has a conf count of 1), because adopting stricter lockout does not weaken commitment on any previously committed fork.
Thus, if we then later saw a vote:
{1: 2, 2: 1}
on a different fork we would say it's slashable because the lockout on 2 extended past a rooted slot 3 in the rooted fork, so 2 should have prevented the vote for 3 from being made, evicted{1: 2, 4: 1}
on a different fork, then because 3 was rooted and 3 does not exist in this vote, it's implied 3 was popped off. However, 3 was rooted so it couldn't have been popped off by 4, so that's slashable
Ordering here is tricky though, for instance what if we saw vote 2 then vote 1?
We would retroactively have to add {1,3}
to the rooted set
Also note here that evicted slots will not be added to the rooted set. For example, imagine:
-
{root: 0, 1: 3, 3: 2, 4: 1}
-
{root: 0, 1: 4, 3: 3, 7: 2, 9: 1}
-
{root: 7, 9: 2, 10: 1}
Here we add {1, 3}
to the rooted set, but 4 doesn't get added because
4 + 2^1 < 7
, so it does not overlap the root of 7
. This means the interval
(4, 4 + 2^1)
remains in the database. This is important because:
- If we see a vote
{root: 0, 1: 3, 3: 2, 5: 1}
on another fork, this is only known to be slashable by seeing this interval(4, 4 + 2^1)
(because it doesn't include4
in the vote, but4's
lockout should have prevented it from being popped off) - We don't want to add
4
to the rooted set to prevent slashing a valid vote on a different fork like{root: 0, 1, 3, 10}
. If4
was present in the rooted set, we would report an error because10
should not have popped off4
Impact
Validators snitching on voting misbehavior will be more effective.
Security Considerations
None
Backwards Compatibility
Not applicable.