Skip to content

Commit 0a77d78

Browse files
committed
Make slashing protection import more resilient (#2598)
## Issue Addressed Closes #2419 ## Proposed Changes Address a long-standing issue with the import of slashing protection data where the import would fail due to the data appearing slashable w.r.t the existing database. Importing is now idempotent, and will have no issues importing data that has been handed back and forth between different validator clients, or different implementations. The implementation works by updating the high and low watermarks if they need updating, and not attempting to check if the input is slashable w.r.t itself or the database. This is a strengthening of the minification that we started to do by default since #2380, and what Teku has been doing since the beginning. ## Additional Info The only feature we lose by doing this is the ability to do non-minified imports of clock drifted messages (cf. Prysm on Medalla). In theory, with the previous implementation we could import all the messages in case of clock drift and be aware of the "gap" between the real present time and the messages signed in the far future. _However_ for attestations this is close to useless, as the source epoch will advance as soon as justification occurs, which will require us to make slashable attestations with respect to our bogus attestation(s). E.g. if I sign an attestation 100=>200 when the current epoch is 101, then I won't be able to vote in any epochs prior to 101 becoming justified because 101=>102, 101=>103, etc are all surrounded by 100=>200. Seeing as signing attestations gets blocked almost immediately in this case regardless of our import behaviour, there's no point trying to handle it. For blocks the situation is more hopeful due to the lack of surrounds, but losing block proposals from validators who by definition can't attest doesn't seem like an issue (the other block proposers can pick up the slack).
1 parent 34d22b5 commit 0a77d78

File tree

11 files changed

+342
-170
lines changed

11 files changed

+342
-170
lines changed

account_manager/src/validator/slashing_protection.rs

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
3333
Arg::with_name(MINIFY_FLAG)
3434
.long(MINIFY_FLAG)
3535
.takes_value(true)
36-
.default_value("true")
3736
.possible_values(&["false", "true"])
3837
.help(
39-
"Minify the input file before processing. This is *much* faster, \
40-
but will not detect slashable data in the input.",
38+
"Deprecated: Lighthouse no longer requires minification on import \
39+
because it always minifies",
4140
),
4241
),
4342
)
@@ -88,7 +87,7 @@ pub fn cli_run<T: EthSpec>(
8887
match matches.subcommand() {
8988
(IMPORT_CMD, Some(matches)) => {
9089
let import_filename: PathBuf = clap_utils::parse_required(matches, IMPORT_FILE_ARG)?;
91-
let minify: bool = clap_utils::parse_required(matches, MINIFY_FLAG)?;
90+
let minify: Option<bool> = clap_utils::parse_optional(matches, MINIFY_FLAG)?;
9291
let import_file = File::open(&import_filename).map_err(|e| {
9392
format!(
9493
"Unable to open import file at {}: {:?}",
@@ -102,12 +101,17 @@ pub fn cli_run<T: EthSpec>(
102101
.map_err(|e| format!("Error parsing file for import: {:?}", e))?;
103102
eprintln!(" [done].");
104103

105-
if minify {
106-
eprint!("Minifying input file for faster loading");
107-
interchange = interchange
108-
.minify()
109-
.map_err(|e| format!("Minification failed: {:?}", e))?;
110-
eprintln!(" [done].");
104+
if let Some(minify) = minify {
105+
eprintln!(
106+
"WARNING: --minify flag is deprecated and will be removed in a future release"
107+
);
108+
if minify {
109+
eprint!("Minifying input file for faster loading");
110+
interchange = interchange
111+
.minify()
112+
.map_err(|e| format!("Minification failed: {:?}", e))?;
113+
eprintln!(" [done].");
114+
}
111115
}
112116

113117
let slashing_protection_database =
@@ -120,14 +124,16 @@ pub fn cli_run<T: EthSpec>(
120124
})?;
121125

122126
let display_slot = |slot: Option<Slot>| {
123-
slot.map_or("none".to_string(), |slot| format!("{}", slot.as_u64()))
127+
slot.map_or("none".to_string(), |slot| format!("slot {}", slot.as_u64()))
124128
};
125129
let display_epoch = |epoch: Option<Epoch>| {
126-
epoch.map_or("?".to_string(), |epoch| format!("{}", epoch.as_u64()))
130+
epoch.map_or("?".to_string(), |epoch| format!("epoch {}", epoch.as_u64()))
127131
};
128132
let display_attestation = |source, target| match (source, target) {
129133
(None, None) => "none".to_string(),
130-
(source, target) => format!("{}=>{}", display_epoch(source), display_epoch(target)),
134+
(source, target) => {
135+
format!("{} => {}", display_epoch(source), display_epoch(target))
136+
}
131137
};
132138

133139
match slashing_protection_database
@@ -140,18 +146,11 @@ pub fn cli_run<T: EthSpec>(
140146
InterchangeImportOutcome::Success { pubkey, summary } => {
141147
eprintln!("- {:?}", pubkey);
142148
eprintln!(
143-
" - min block: {}",
144-
display_slot(summary.min_block_slot)
145-
);
146-
eprintln!(
147-
" - min attestation: {}",
148-
display_attestation(
149-
summary.min_attestation_source,
150-
summary.min_attestation_target
151-
)
149+
" - latest block: {}",
150+
display_slot(summary.max_block_slot)
152151
);
153152
eprintln!(
154-
" - max attestation: {}",
153+
" - latest attestation: {}",
155154
display_attestation(
156155
summary.max_attestation_source,
157156
summary.max_attestation_target
@@ -168,18 +167,20 @@ pub fn cli_run<T: EthSpec>(
168167
}
169168
}
170169
Err(InterchangeError::AtomicBatchAborted(outcomes)) => {
171-
eprintln!("ERROR, slashable data in input:");
170+
eprintln!("ERROR: import aborted due to one or more errors");
172171
for outcome in &outcomes {
173172
if let InterchangeImportOutcome::Failure { pubkey, error } = outcome {
174173
eprintln!("- {:?}", pubkey);
175174
eprintln!(" - error: {:?}", error);
176175
}
177176
}
178-
return Err(
179-
"ERROR: import aborted due to slashable data, see above.\n\
180-
Please see https://lighthouse-book.sigmaprime.io/slashing-protection.html#slashable-data-in-import\n\
181-
IT IS NOT SAFE TO START VALIDATING".to_string()
182-
);
177+
return Err("ERROR: import aborted due to errors, see above.\n\
178+
No data has been imported and the slashing protection \
179+
database is in the same state it was in before the import.\n\
180+
Due to the failed import it is NOT SAFE to start validating\n\
181+
with any newly imported validator keys, as your database lacks\n\
182+
slashing protection data for them."
183+
.to_string());
183184
}
184185
Err(e) => {
185186
return Err(format!(
@@ -192,7 +193,7 @@ pub fn cli_run<T: EthSpec>(
192193

193194
eprintln!("Import completed successfully.");
194195
eprintln!(
195-
"Please double-check that the minimum and maximum blocks and attestations above \
196+
"Please double-check that the latest blocks and attestations above \
196197
match your expectations."
197198
);
198199

book/src/slashing-protection.md

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Instructions for exporting your existing client's database are out of scope for
7575
please check the other client's documentation for instructions.
7676

7777
When importing an interchange file, you still need to import the validator keystores themselves
78-
separately, using the instructions about [importing keystores into
78+
separately, using the instructions for [importing keystores into
7979
Lighthouse](./validator-import-launchpad.md).
8080

8181
---
@@ -91,31 +91,24 @@ up to date.
9191

9292
[EIP-3076]: https://eips.ethereum.org/EIPS/eip-3076
9393

94-
### Minification
95-
96-
Since version 1.5.0 Lighthouse automatically _minifies_ slashing protection data upon import.
97-
Minification safely shrinks the input file, making it faster to import.
98-
99-
If an import file contains slashable data, then its minification is still safe to import _even
100-
though_ the non-minified file would fail to be imported. This means that leaving minification
101-
enabled is recommended if the input could contain slashable data. Conversely, if you would like to
102-
double-check that the input file is not slashable with respect to itself, then you should disable
103-
minification.
94+
### How Import Works
10495

105-
Minification can be disabled for imports by adding `--minify=false` to the command:
96+
Since version 1.6.0 Lighthouse will ignore any slashable data in the import data and will safely
97+
update the low watermarks for blocks and attestations. It will store only the maximum-slot block
98+
for each validator, and the maximum source/target attestation. This is faster than importing
99+
all data while also being more resilient to repeated imports & stale data.
106100

107-
```
108-
lighthouse account validator slashing-protection import --minify=false <my_interchange.json>
109-
```
101+
### Minification
110102

111-
It can also be enabled for exports (disabled by default):
103+
The exporter can be configured to minify (shrink) the data it exports by keeping only the
104+
maximum-slot and maximum-epoch messages. Provide the `--minify=true` flag:
112105

113106
```
114107
lighthouse account validator slashing-protection export --minify=true <lighthouse_interchange.json>
115108
```
116109

117-
Minifying the export file should make it faster to import, and may allow it to be imported into an
118-
implementation that is rejecting the non-minified equivalent due to slashable data.
110+
This may make the file faster to import into other clients, but is unnecessary for Lighthouse to
111+
Lighthouse transfers since v1.5.0.
119112

120113
## Troubleshooting
121114

@@ -161,46 +154,6 @@ Sep 29 15:15:05.303 CRIT Not signing slashable attestation error: InvalidA
161154
This log is still marked as `CRIT` because in general it should occur only very rarely,
162155
and _could_ indicate a serious error or misconfiguration (see [Avoiding Slashing](#avoiding-slashing)).
163156

164-
### Slashable Data in Import
165-
166-
During import of an [interchange file](#import-and-export) if you receive an error about
167-
the file containing slashable data, then you must carefully consider whether you want to continue.
168-
169-
There are several potential causes for this error, each of which require a different reaction. If
170-
the error output lists multiple validator keys, the cause could be different for each of them.
171-
172-
1. Your validator has actually signed slashable data. If this is the case, you should assess
173-
whether your validator has been slashed (or is likely to be slashed). It's up to you
174-
whether you'd like to continue.
175-
2. You have exported data from Lighthouse to another client, and then back to Lighthouse,
176-
_in a way that didn't preserve the signing roots_. A message with no signing roots
177-
is considered slashable with respect to _any_ other message at the same slot/epoch,
178-
so even if it was signed by Lighthouse originally, Lighthouse has no way of knowing this.
179-
If you're sure you haven't run Lighthouse and the other client simultaneously, you
180-
can [drop Lighthouse's DB in favour of the interchange file](#drop-and-re-import).
181-
3. You have imported the same interchange file (which lacks signing roots) twice, e.g. from Teku.
182-
It might be safe to continue as-is, or you could consider a [Drop and
183-
Re-import](#drop-and-re-import).
184-
185-
If you are running the import command with `--minify=false`, you should consider enabling
186-
[minification](#minification).
187-
188-
#### Drop and Re-import
189-
190-
If you'd like to prioritize an interchange file over any existing database stored by Lighthouse
191-
then you can _move_ (not delete) Lighthouse's database and replace it like so:
192-
193-
```bash
194-
mv $datadir/validators/slashing_protection.sqlite ~/slashing_protection_backup.sqlite
195-
```
196-
197-
```
198-
lighthouse account validator slashing-protection import <my_interchange.json>
199-
```
200-
201-
If your interchange file doesn't cover all of your validators, you shouldn't do this. Please reach
202-
out on Discord if you need help.
203-
204157
## Limitation of Liability
205158

206159
The Lighthouse developers do not guarantee the perfect functioning of this software, or accept

validator_client/slashing_protection/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
TESTS_TAG := v5.1.0
1+
TESTS_TAG := v5.2.0
22
GENERATE_DIR := generated-tests
33
OUTPUT_DIR := interchange-tests
44
TARBALL := $(OUTPUT_DIR)-$(TESTS_TAG).tar.gz

validator_client/slashing_protection/src/bin/test_generator.rs

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ fn main() {
216216
],
217217
),
218218
MultiTestCase::new(
219-
"multiple_interchanges_single_validator_single_message_out_of_order",
219+
"multiple_interchanges_single_validator_single_block_out_of_order",
220220
vec![
221221
TestCase::new(interchange(vec![(0, vec![40], vec![])])),
222222
TestCase::new(interchange(vec![(0, vec![20], vec![])]))
@@ -233,6 +233,131 @@ fn main() {
233233
.with_blocks(vec![(0, 20, false), (0, 50, false)]),
234234
],
235235
),
236+
MultiTestCase::new(
237+
"multiple_interchanges_single_validator_single_att_out_of_order",
238+
vec![
239+
TestCase::new(interchange(vec![(0, vec![], vec![(12, 13)])])),
240+
TestCase::new(interchange(vec![(0, vec![], vec![(10, 11)])]))
241+
.contains_slashable_data()
242+
.with_attestations(vec![
243+
(0, 10, 14, false),
244+
(0, 12, 13, false),
245+
(0, 12, 14, true),
246+
(0, 13, 15, true),
247+
]),
248+
],
249+
),
250+
MultiTestCase::new(
251+
"multiple_interchanges_single_validator_second_surrounds_first",
252+
vec![
253+
TestCase::new(interchange(vec![(0, vec![], vec![(10, 20)])])),
254+
TestCase::new(interchange(vec![(0, vec![], vec![(9, 21)])]))
255+
.contains_slashable_data()
256+
.with_attestations(vec![
257+
(0, 10, 20, false),
258+
(0, 10, 21, false),
259+
(0, 9, 21, false),
260+
(0, 9, 22, false),
261+
(0, 10, 22, true),
262+
]),
263+
],
264+
),
265+
MultiTestCase::new(
266+
"multiple_interchanges_single_validator_first_surrounds_second",
267+
vec![
268+
TestCase::new(interchange(vec![(0, vec![], vec![(9, 21)])])),
269+
TestCase::new(interchange(vec![(0, vec![], vec![(10, 20)])]))
270+
.contains_slashable_data()
271+
.with_attestations(vec![
272+
(0, 10, 20, false),
273+
(0, 10, 21, false),
274+
(0, 9, 21, false),
275+
(0, 9, 22, false),
276+
(0, 10, 22, true),
277+
]),
278+
],
279+
),
280+
MultiTestCase::new(
281+
"multiple_interchanges_multiple_validators_repeat_idem",
282+
vec![
283+
TestCase::new(interchange(vec![
284+
(0, vec![2, 4, 6], vec![(0, 1), (1, 2)]),
285+
(1, vec![8, 10, 12], vec![(0, 1), (0, 3)]),
286+
])),
287+
TestCase::new(interchange(vec![
288+
(0, vec![2, 4, 6], vec![(0, 1), (1, 2)]),
289+
(1, vec![8, 10, 12], vec![(0, 1), (0, 3)]),
290+
]))
291+
.contains_slashable_data()
292+
.with_blocks(vec![
293+
(0, 0, false),
294+
(0, 3, true),
295+
(0, 7, true),
296+
(0, 3, true),
297+
(1, 0, false),
298+
])
299+
.with_attestations(vec![(0, 0, 4, false), (1, 0, 4, true)]),
300+
],
301+
),
302+
MultiTestCase::new(
303+
"multiple_interchanges_overlapping_validators_repeat_idem",
304+
vec![
305+
TestCase::new(interchange(vec![
306+
(0, vec![2, 4, 6], vec![(0, 1), (1, 2)]),
307+
(1, vec![8, 10, 12], vec![(0, 1), (0, 3)]),
308+
])),
309+
TestCase::new(interchange(vec![
310+
(0, vec![2, 4, 6], vec![(0, 1), (1, 2)]),
311+
(2, vec![8, 10, 12], vec![(0, 1), (0, 3)]),
312+
]))
313+
.contains_slashable_data(),
314+
TestCase::new(interchange(vec![
315+
(1, vec![8, 10, 12], vec![(0, 1), (0, 3)]),
316+
(2, vec![8, 10, 12], vec![(0, 1), (0, 3)]),
317+
]))
318+
.contains_slashable_data()
319+
.with_attestations(vec![
320+
(0, 0, 4, false),
321+
(1, 1, 2, false),
322+
(2, 1, 2, false),
323+
]),
324+
],
325+
),
326+
MultiTestCase::new(
327+
"multiple_interchanges_overlapping_validators_merge_stale",
328+
vec![
329+
TestCase::new(interchange(vec![
330+
(0, vec![100], vec![(12, 13)]),
331+
(1, vec![101], vec![(12, 13)]),
332+
(2, vec![4], vec![(4, 5)]),
333+
])),
334+
TestCase::new(interchange(vec![
335+
(0, vec![2], vec![(4, 5)]),
336+
(1, vec![3], vec![(3, 4)]),
337+
(2, vec![102], vec![(12, 13)]),
338+
]))
339+
.contains_slashable_data()
340+
.with_blocks(vec![
341+
(0, 100, false),
342+
(1, 101, false),
343+
(2, 102, false),
344+
(0, 103, true),
345+
(1, 104, true),
346+
(2, 105, true),
347+
])
348+
.with_attestations(vec![
349+
(0, 12, 13, false),
350+
(0, 11, 14, false),
351+
(1, 12, 13, false),
352+
(1, 11, 14, false),
353+
(2, 12, 13, false),
354+
(2, 11, 14, false),
355+
(0, 12, 14, true),
356+
(1, 13, 14, true),
357+
(2, 13, 14, true),
358+
]),
359+
],
360+
),
236361
MultiTestCase::single(
237362
"single_validator_source_greater_than_target",
238363
TestCase::new(interchange(vec![(0, vec![], vec![(8, 7)])])).contains_slashable_data(),

validator_client/slashing_protection/src/interchange.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl Interchange {
119119
}
120120
}
121121
(None, None) => {}
122-
_ => return Err(InterchangeError::MinAndMaxInconsistent),
122+
_ => return Err(InterchangeError::MaxInconsistent),
123123
};
124124

125125
// Find maximum block slot.

0 commit comments

Comments
 (0)