Skip to content

Commit 6583ce3

Browse files
committed
Minify slashing protection interchange data (#2380)
## Issue Addressed Closes #2354 ## Proposed Changes Add a `minify` method to `slashing_protection::Interchange` that keeps only the maximum-epoch attestation and maximum-slot block for each validator. Specifically, `minify` constructs "synthetic" attestations (with no `signing_root`) containing the maximum source epoch _and_ the maximum target epoch from the input. This is equivalent to the `minify_synth` algorithm that I've formally verified in this repository: https://github.com/michaelsproul/slashing-proofs ## Additional Info Includes the JSON loading optimisation from #2347
1 parent b84ff9f commit 6583ce3

File tree

11 files changed

+441
-95
lines changed

11 files changed

+441
-95
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

account_manager/src/validator/slashing_protection.rs

Lines changed: 106 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clap::{App, Arg, ArgMatches};
22
use environment::Environment;
33
use slashing_protection::{
4-
interchange::Interchange, InterchangeImportOutcome, SlashingDatabase,
4+
interchange::Interchange, InterchangeError, InterchangeImportOutcome, SlashingDatabase,
55
SLASHING_PROTECTION_FILENAME,
66
};
77
use std::fs::File;
@@ -15,6 +15,8 @@ pub const EXPORT_CMD: &str = "export";
1515
pub const IMPORT_FILE_ARG: &str = "IMPORT-FILE";
1616
pub const EXPORT_FILE_ARG: &str = "EXPORT-FILE";
1717

18+
pub const MINIFY_FLAG: &str = "minify";
19+
1820
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
1921
App::new(CMD)
2022
.about("Import or export slashing protection data to or from another client")
@@ -26,6 +28,17 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
2628
.takes_value(true)
2729
.value_name("FILE")
2830
.help("The slashing protection interchange file to import (.json)"),
31+
)
32+
.arg(
33+
Arg::with_name(MINIFY_FLAG)
34+
.long(MINIFY_FLAG)
35+
.takes_value(true)
36+
.default_value("true")
37+
.possible_values(&["false", "true"])
38+
.help(
39+
"Minify the input file before processing. This is *much* faster, \
40+
but will not detect slashable data in the input.",
41+
),
2942
),
3043
)
3144
.subcommand(
@@ -36,6 +49,17 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
3649
.takes_value(true)
3750
.value_name("FILE")
3851
.help("The filename to export the interchange file to"),
52+
)
53+
.arg(
54+
Arg::with_name(MINIFY_FLAG)
55+
.long(MINIFY_FLAG)
56+
.takes_value(true)
57+
.default_value("false")
58+
.possible_values(&["false", "true"])
59+
.help(
60+
"Minify the output file. This will make it smaller and faster to \
61+
import, but not faster to generate.",
62+
),
3963
),
4064
)
4165
}
@@ -64,6 +88,7 @@ pub fn cli_run<T: EthSpec>(
6488
match matches.subcommand() {
6589
(IMPORT_CMD, Some(matches)) => {
6690
let import_filename: PathBuf = clap_utils::parse_required(&matches, IMPORT_FILE_ARG)?;
91+
let minify: bool = clap_utils::parse_required(&matches, MINIFY_FLAG)?;
6792
let import_file = File::open(&import_filename).map_err(|e| {
6893
format!(
6994
"Unable to open import file at {}: {:?}",
@@ -72,8 +97,18 @@ pub fn cli_run<T: EthSpec>(
7297
)
7398
})?;
7499

75-
let interchange = Interchange::from_json_reader(&import_file)
100+
eprint!("Loading JSON file into memory & deserializing");
101+
let mut interchange = Interchange::from_json_reader(&import_file)
76102
.map_err(|e| format!("Error parsing file for import: {:?}", e))?;
103+
eprintln!(" [done].");
104+
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].");
111+
}
77112

78113
let slashing_protection_database =
79114
SlashingDatabase::open_or_create(&slashing_protection_db_path).map_err(|e| {
@@ -84,16 +119,6 @@ pub fn cli_run<T: EthSpec>(
84119
)
85120
})?;
86121

87-
let outcomes = slashing_protection_database
88-
.import_interchange_info(interchange, genesis_validators_root)
89-
.map_err(|e| {
90-
format!(
91-
"Error during import: {:?}\n\
92-
IT IS NOT SAFE TO START VALIDATING",
93-
e
94-
)
95-
})?;
96-
97122
let display_slot = |slot: Option<Slot>| {
98123
slot.map_or("none".to_string(), |slot| format!("{}", slot.as_u64()))
99124
};
@@ -105,48 +130,77 @@ pub fn cli_run<T: EthSpec>(
105130
(source, target) => format!("{}=>{}", display_epoch(source), display_epoch(target)),
106131
};
107132

108-
let mut num_failed = 0;
109-
110-
for outcome in &outcomes {
111-
match outcome {
112-
InterchangeImportOutcome::Success { pubkey, summary } => {
113-
eprintln!("- {:?} SUCCESS min block: {}, max block: {}, min attestation: {}, max attestation: {}",
114-
pubkey,
115-
display_slot(summary.min_block_slot),
116-
display_slot(summary.max_block_slot),
117-
display_attestation(summary.min_attestation_source, summary.min_attestation_target),
118-
display_attestation(summary.max_attestation_source,
119-
summary.max_attestation_target),
120-
);
133+
match slashing_protection_database
134+
.import_interchange_info(interchange, genesis_validators_root)
135+
{
136+
Ok(outcomes) => {
137+
eprintln!("All records imported successfully:");
138+
for outcome in &outcomes {
139+
match outcome {
140+
InterchangeImportOutcome::Success { pubkey, summary } => {
141+
eprintln!("- {:?}", pubkey);
142+
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+
)
152+
);
153+
eprintln!(
154+
" - max attestation: {}",
155+
display_attestation(
156+
summary.max_attestation_source,
157+
summary.max_attestation_target
158+
)
159+
);
160+
}
161+
InterchangeImportOutcome::Failure { pubkey, error } => {
162+
panic!(
163+
"import should be atomic, but key {:?} was imported despite error: {:?}",
164+
pubkey, error
165+
);
166+
}
167+
}
121168
}
122-
InterchangeImportOutcome::Failure { pubkey, error } => {
123-
eprintln!("- {:?} ERROR: {:?}", pubkey, error);
124-
num_failed += 1;
169+
}
170+
Err(InterchangeError::AtomicBatchAborted(outcomes)) => {
171+
eprintln!("ERROR, slashable data in input:");
172+
for outcome in &outcomes {
173+
if let InterchangeImportOutcome::Failure { pubkey, error } = outcome {
174+
eprintln!("- {:?}", pubkey);
175+
eprintln!(" - error: {:?}", error);
176+
}
125177
}
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+
);
183+
}
184+
Err(e) => {
185+
return Err(format!(
186+
"Fatal error during import: {:?}\n\
187+
IT IS NOT SAFE TO START VALIDATING",
188+
e
189+
));
126190
}
127191
}
128192

129-
if num_failed == 0 {
130-
eprintln!("Import completed successfully.");
131-
eprintln!(
132-
"Please double-check that the minimum and maximum blocks and slots above \
133-
match your expectations."
134-
);
135-
} else {
136-
eprintln!(
137-
"WARNING: history was NOT imported for {} of {} records",
138-
num_failed,
139-
outcomes.len()
140-
);
141-
eprintln!("IT IS NOT SAFE TO START VALIDATING");
142-
eprintln!("Please see https://lighthouse-book.sigmaprime.io/slashing-protection.html#slashable-data-in-import");
143-
return Err("Partial import".to_string());
144-
}
193+
eprintln!("Import completed successfully.");
194+
eprintln!(
195+
"Please double-check that the minimum and maximum blocks and attestations above \
196+
match your expectations."
197+
);
145198

146199
Ok(())
147200
}
148201
(EXPORT_CMD, Some(matches)) => {
149202
let export_filename: PathBuf = clap_utils::parse_required(&matches, EXPORT_FILE_ARG)?;
203+
let minify: bool = clap_utils::parse_required(&matches, MINIFY_FLAG)?;
150204

151205
if !slashing_protection_db_path.exists() {
152206
return Err(format!(
@@ -164,10 +218,17 @@ pub fn cli_run<T: EthSpec>(
164218
)
165219
})?;
166220

167-
let interchange = slashing_protection_database
221+
let mut interchange = slashing_protection_database
168222
.export_interchange_info(genesis_validators_root)
169223
.map_err(|e| format!("Error during export: {:?}", e))?;
170224

225+
if minify {
226+
eprintln!("Minifying output file");
227+
interchange = interchange
228+
.minify()
229+
.map_err(|e| format!("Unable to minify output: {:?}", e))?;
230+
}
231+
171232
let output_file = File::create(export_filename)
172233
.map_err(|e| format!("Error creating output file: {:?}", e))?;
173234

book/src/slashing-protection.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,32 @@ 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.
104+
105+
Minification can be disabled for imports by adding `--minify=false` to the command:
106+
107+
```
108+
lighthouse account validator slashing-protection import --minify=false <my_interchange.json>
109+
```
110+
111+
It can also be enabled for exports (disabled by default):
112+
113+
```
114+
lighthouse account validator slashing-protection export --minify=true <lighthouse_interchange.json>
115+
```
116+
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.
119+
94120
## Troubleshooting
95121

96122
### Misplaced Slashing Database
@@ -137,11 +163,11 @@ and _could_ indicate a serious error or misconfiguration (see [Avoiding Slashing
137163

138164
### Slashable Data in Import
139165

140-
If you receive a warning when trying to import an [interchange file](#import-and-export) about
166+
During import of an [interchange file](#import-and-export) if you receive an error about
141167
the file containing slashable data, then you must carefully consider whether you want to continue.
142168

143-
There are several potential causes for this warning, each of which require a different reaction. If
144-
you have seen the warning for multiple validator keys, the cause could be different for each of them.
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.
145171

146172
1. Your validator has actually signed slashable data. If this is the case, you should assess
147173
whether your validator has been slashed (or is likely to be slashed). It's up to you
@@ -156,6 +182,9 @@ you have seen the warning for multiple validator keys, the cause could be differ
156182
It might be safe to continue as-is, or you could consider a [Drop and
157183
Re-import](#drop-and-re-import).
158184

185+
If you are running the import command with `--minify=false`, you should consider enabling
186+
[minification](#minification).
187+
159188
#### Drop and Re-import
160189

161190
If you'd like to prioritize an interchange file over any existing database stored by Lighthouse

validator_client/slashing_protection/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ serde_utils = { path = "../../consensus/serde_utils" }
1919
filesystem = { path = "../../common/filesystem" }
2020

2121
[dev-dependencies]
22+
lazy_static = "1.4.0"
2223
rayon = "1.4.1"

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 := f495032df9c26c678536cd2b7854e836ea94c217
1+
TESTS_TAG := v5.1.0
22
GENERATE_DIR := generated-tests
33
OUTPUT_DIR := interchange-tests
44
TARBALL := $(OUTPUT_DIR)-$(TESTS_TAG).tar.gz

0 commit comments

Comments
 (0)