|
| 1 | +--- |
| 2 | +simd: '0009' |
| 3 | +title: Lockout Violation Detection |
| 4 | +authors: |
| 5 | + - carllin |
| 6 | + - ashwinsekar |
| 7 | + - wencoding |
| 8 | +category: Standard |
| 9 | +type: Core |
| 10 | +status: Draft |
| 11 | +created: 2022-12-12 |
| 12 | +feature: (fill in with feature tracking issues once accepted) |
| 13 | +--- |
| 14 | + |
| 15 | +## Summary |
| 16 | + |
| 17 | +An algorithm designed to catch validators that violate lockout rules when |
| 18 | +voting. |
| 19 | + |
| 20 | +## Motivation |
| 21 | + |
| 22 | +Validators that violate lockout rules unfairly earn rewards and put cluster. |
| 23 | +consensus at risk |
| 24 | + |
| 25 | +## Alternatives Considered |
| 26 | + |
| 27 | +None |
| 28 | + |
| 29 | +## New Terminology |
| 30 | + |
| 31 | +None |
| 32 | + |
| 33 | +## Detailed Design |
| 34 | + |
| 35 | +Assume: |
| 36 | + |
| 37 | +1. A database that we will store vote information ingested from |
| 38 | +gossip/turbine, which in turn will be consulted to detect lockout violations |
| 39 | +as follows. |
| 40 | + |
| 41 | +2. Tracking of all SlotHashes on the canonical/rooted fork in the database |
| 42 | +for the last epoch. |
| 43 | + |
| 44 | +The following sections will go over the possible lockout violations that will |
| 45 | +be detected and punished. |
| 46 | + |
| 47 | +### Rooting a different fork |
| 48 | + |
| 49 | +If there are any votes made that have a root `R` missing from the rooted |
| 50 | +SlotHashes list, slash them on the rooted fork since they have committed |
| 51 | +to a different fork |
| 52 | + |
| 53 | +### Removing lockouts |
| 54 | + |
| 55 | +Attempting to illegally *remove* a lockout in a newer vote that that should |
| 56 | +have still existed based on an older vote is a lockout violation. We detect |
| 57 | +this as follows: |
| 58 | + |
| 59 | +1. For all non root slots `S` in each vote, track a range `(S, S + 2^n)` where |
| 60 | +`n` is the confirmation count. For each slot `S` we only have to track the |
| 61 | +greatest such lockout range in the database. |
| 62 | + |
| 63 | +2. For each new vote `V`, for each slot `S` in the vote, lookup in the database |
| 64 | +to see if there's some range `(X, X + 2^n)` where `S` is in this range, but |
| 65 | +`X` is missing from the vote `V`. This is a lockout violation because this |
| 66 | +implies that the validator made a vote where `S` popped off `X`, but the |
| 67 | +lockout on `X` from an earlier vote should have prevented that from happening. |
| 68 | + |
| 69 | +Note for each interval `(S, S + 2^n)` we also need to be able to lookup the |
| 70 | +vote. This is important for being able to pull up the votes later as part of a |
| 71 | +proof. |
| 72 | + |
| 73 | +### Reducing lockout |
| 74 | + |
| 75 | +Attempting to illegally *reduce* a lockout from an older vote in a newer vote |
| 76 | +is a lockout violation. |
| 77 | + |
| 78 | +The tricky bit of this is determining that one vote `V` is *older* than |
| 79 | +another vote `V'`. To this end, we track a flag `is_vote_earlier` whose usage |
| 80 | +we will highlight in the protocol below. |
| 81 | + |
| 82 | +1. We set `is_vote_earlier = false`. |
| 83 | + |
| 84 | +2. For each newly seen vote `V` made by a validator `X`, for each slot `S` in |
| 85 | +`V`: |
| 86 | + - If `S` exists in the database: |
| 87 | + - Compare the lockout `L_V` in the vote `V` on `S` against the greatest |
| 88 | + lockout `L_D` in the database for that slot `S` made by that validator |
| 89 | + `X`. |
| 90 | + - If `L_V` < `L_D`, set `is_vote_earlier=true`. |
| 91 | + - If `L_V` == `L_D`, continue. |
| 92 | + - If `L_V > L_D`, check if the flag `is_vote_earlier=true`. If so, |
| 93 | + this implies this vote `V` was *older* than some vote `V'` that |
| 94 | + comitted the greater `S'` lockout to the database, yet `V` has a |
| 95 | + lesser lockout on `S`, which means the validator reduced lockout on |
| 96 | + `S` in a later vote. This is a lockout violation. |
| 97 | + - If `S` does not exist in the database, the above `Removing lockouts` |
| 98 | + section describes the protocol that will catch violations. |
| 99 | + |
| 100 | +### Reducing roots |
| 101 | + |
| 102 | +Reducing the root from earlier to later votes is a lockout violation. We detect |
| 103 | +this as follows: |
| 104 | + |
| 105 | +1. For each validator we track a rooted set in the database. We can remove an |
| 106 | +interval `(S, S + 2^n)` from the database once the slot becomes a root add it |
| 107 | +to a rooted set for this validator, and any new votes < root also get added to |
| 108 | +rooted set. |
| 109 | + |
| 110 | +2. When we see a vote with root N on the main fork, then we remove all |
| 111 | +intervals `(M, P)` where `M < N && P >= N` and add `M` to the rooted set. |
| 112 | + |
| 113 | +So for example if we see: |
| 114 | +(Assume `{slot: confirmation count}` format) |
| 115 | + |
| 116 | +- `{root: 0, 1: 4, 3: 3, 5: 2, 7: 1}` |
| 117 | + |
| 118 | +- `{root: 5, 7: 1}` |
| 119 | + |
| 120 | +- Then we add `{1, 3}` to rooted set and remove their intervals from the |
| 121 | +interval tree because both of those are `< 5`, but have lockouts that extend |
| 122 | +past `5` |
| 123 | + |
| 124 | +Note here also that that artificially increasing your lockout is not a |
| 125 | +slashable offense (here we root 5 however 7 still has a conf count of 1), |
| 126 | +because adopting stricter lockout does not weaken commitment on any previously |
| 127 | +committed fork. |
| 128 | + |
| 129 | +Thus, if we then later saw a vote: |
| 130 | + |
| 131 | +- `{1: 2, 2: 1}` on a different fork we would say it's slashable because the |
| 132 | +lockout on 2 extended past a rooted slot 3 in the rooted fork, so 2 should have |
| 133 | +prevented the vote for 3 from being made, evicted |
| 134 | +- `{1: 2, 4: 1}` on a different fork, then because 3 was rooted and 3 does not |
| 135 | +exist in this vote, it's implied 3 was popped off. However, 3 was rooted so it |
| 136 | +couldn't have been popped off by 4, so that's slashable |
| 137 | + |
| 138 | +Ordering here is tricky though, for instance what if we saw vote 2 then vote 1? |
| 139 | +We would retroactively have to add `{1,3}` to the rooted set |
| 140 | + |
| 141 | +Also note here that evicted slots will not be added to the rooted set. For |
| 142 | +example, imagine: |
| 143 | + |
| 144 | +- `{root: 0, 1: 3, 3: 2, 4: 1}` |
| 145 | + |
| 146 | +- `{root: 0, 1: 4, 3: 3, 7: 2, 9: 1}` |
| 147 | + |
| 148 | +- `{root: 7, 9: 2, 10: 1}` |
| 149 | + |
| 150 | +Here we add `{1, 3}` to the rooted set, but 4 doesn't get added because |
| 151 | +`4 + 2^1 < 7`, so it does not overlap the root of `7`. This means the interval |
| 152 | +`(4, 4 + 2^1)` remains in the database. This is important because: |
| 153 | + |
| 154 | +- If we see a vote `{root: 0, 1: 3, 3: 2, 5: 1}` on another fork, this is only |
| 155 | +known to be slashable by seeing this interval `(4, 4 + 2^1)` (because it |
| 156 | +doesn't include `4` in the vote, but `4's` lockout should have prevented it |
| 157 | +from being popped off) |
| 158 | +- We don't want to add `4` to the rooted set to prevent slashing a valid vote |
| 159 | +on a different fork like `{root: 0, 1, 3, 10}`. If `4` was present in the |
| 160 | +rooted set, we would report an error because `10` should not have popped off `4` |
| 161 | + |
| 162 | +## Impact |
| 163 | + |
| 164 | +Validators snitching on voting misbehavior will be more effective. |
| 165 | + |
| 166 | +## Security Considerations |
| 167 | + |
| 168 | +None |
| 169 | + |
| 170 | +## Backwards Compatibility |
| 171 | + |
| 172 | +Not applicable. |
0 commit comments