Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
5b02738
Make the validator client's slashing protection durable: every block …
pablodeymo Oct 29, 2026
dda1224
Address review of the durable slashing protection. Keys registered wi…
pablodeymo Oct 29, 2026
7c3001b
Apply the bot reviews of the durable slashing protection. The durable…
pablodeymo Oct 30, 2026
4db7bb0
Register keys with no slashing-protection history as never having sig…
pablodeymo Nov 5, 2026
0ef17bf
Roll a keymanager import back when its slashing-protection registrati…
pablodeymo Nov 5, 2026
09d1cef
Put SlashingProtection::in_memory behind a test-utils feature: it tre…
pablodeymo Nov 5, 2026
ae16928
Refuse attestation data whose source epoch is at or past its target o…
pablodeymo Nov 5, 2026
81bdc45
Lock the validators directory itself rather than a lock file beside t…
pablodeymo Nov 5, 2026
a0d882f
Close the gaps the second automated review round found in the slashin…
pablodeymo Nov 5, 2026
11f8814
Settle what an unflushed write means for each file, bound the wait fo…
pablodeymo Nov 5, 2026
b53ac02
Read the record with the same rule it is written under, put a failed …
pablodeymo Nov 5, 2026
c2f45b3
Say what an Err from a record write means for the watermarks: nothing…
pablodeymo Nov 5, 2026
4a31fc4
Correct the attestation duty's comment on a failed record write to ma…
pablodeymo Nov 5, 2026
d2d495f
Undo a failed import newest snapshot first, so a key listed twice in …
pablodeymo Nov 5, 2026
b339ff7
Refuse a keymanager import that carries slashing_protection, importin…
pablodeymo Nov 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -846,8 +846,10 @@ transitions are in `ethlambda-types`, per the section above. Nothing above
aggregate when `verified_sync_aggregate` fails (see `docs/spec_deviations.md`).
`SlotClock` picks the
offsets by each slot's own fork, and `/eth/v1/config/spec` supplies them. It
still keeps no slashing-protection record; PTC votes have an in-memory
`(validator, slot)` dedup only, and envelopes are unguarded.
keeps EIP-3076 minimal-watermark slashing protection in
`--validators-dir/slashing_protection.json` (`crate::slashing_protection`),
durable before every block and attestation signature; PTC votes have an
in-memory `(validator, slot)` dedup only, and envelopes are unguarded.
`get_head_node` is one bottom-up walk for every fork, with
the current slot's fork selecting the payload rules and the boost gate, so a
follower on any network that schedules gloas (Sepolia does) pays work
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,16 @@ payload envelope and the payload timeliness vote. It talks to a beacon node over
the standard Beacon API, so it works against `ethlambda beacon` (run with the
`--execution-*` flags) or any other conformant beacon node.

> **Warning:** the validator client keeps no slashing-protection record yet.
> Slashing protection is a work in progress and will be added soon. Until then,
> do not run the same keys in another client while this one runs, and treat a
> restart with the same care as a manual key move. Read
> [Spec deviations](./docs/spec_deviations.md#the-validator-client-keeps-no-slashing-protection-record)
> before using keys that hold real stake. The client repeats this warning at
> startup.
> **Slashing protection:** every block and attestation is checked against a
> durable per-validator record in `--validators-dir` before it is signed, and a
> second validator client pointed at the same directory refuses to start. A key
> with no record is refused at startup: pass `--init-slashing-protection` once,
> and only for keys that have signed nothing anywhere for at least two epochs;
> even then it rules out a double vote, not a surround of an older vote, which
> only the key's own history can.
> Importing history from another client is not supported yet, so never run
> these keys in another client at the same time. See
> [Spec deviations](./docs/spec_deviations.md#the-validator-client-keeps-eip-3076s-minimal-watermarks-not-full-history).

```sh
./target/release/ethlambda validator \
Expand Down
15 changes: 15 additions & 0 deletions bin/ethlambda/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ pub(crate) struct ValidatorOptions {
/// beyond localhost must front it with TLS.
#[arg(long, default_value = "false")]
pub(crate) enable_keymanager: bool,

/// Register every loaded key that has no slashing-protection record,
/// instead of refusing to start.
///
/// DANGEROUS. Use it only for keys that are brand new, or that have signed
/// nothing anywhere for at least two epochs. A key is registered as never
/// having signed, so nothing here stops it from signing again what it
/// signed elsewhere a moment ago; and two idle epochs rule out a double
/// vote, not a surround of an older vote, which only the key's own history
/// can. Keys that already have a record keep it, so the flag never lowers
/// one; remove it once the keys are registered.
#[arg(long, default_value = "false")]
pub(crate) init_slashing_protection: bool,
}

impl ValidatorOptions {
Expand Down Expand Up @@ -154,6 +167,7 @@ pub(crate) async fn run(options: ValidatorOptions) -> eyre::Result<()> {
keymanager: options
.enable_keymanager
.then(|| std::net::SocketAddr::new(options.http_address, options.keymanager_port)),
init_slashing_protection: options.init_slashing_protection,
})
.await
.map_err(Into::into)
Expand All @@ -174,6 +188,7 @@ mod tests {
graffiti: graffiti.to_string(),
suggested_fee_recipient: None,
enable_keymanager: false,
init_slashing_protection: false,
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/net/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jemalloc_pprof.workspace = true
futures-util = "0.3"

[dev-dependencies]
ethlambda-validator.workspace = true
ethlambda-validator = { workspace = true, features = ["test-utils"] }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This using ethlambda-validator just for test is strange

spawned-concurrency.workspace = true
ethlambda-state-transition = { workspace = true, features = ["test-utils"] }
ethlambda-types.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions crates/net/rpc/src/beacon/validator_client_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,7 @@ mod gloas {
None,
),
),
ethlambda_validator::slashing_protection::SlashingProtection::in_memory(),
);
let duty = ProposerDutyDto {
pubkey: encode_hex(&pubkey.0),
Expand Down Expand Up @@ -1606,6 +1607,7 @@ mod gloas {
None,
),
),
ethlambda_validator::slashing_protection::SlashingProtection::in_memory(),
);
let duty = ProposerDutyDto {
pubkey: encode_hex(&pubkey.0),
Expand Down
13 changes: 9 additions & 4 deletions crates/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ serde_yaml_ng.workspace = true
hex.workspace = true
thiserror.workspace = true
tracing.workspace = true
# `time` for the duty loop's sleep between slots. Declared here rather than
# relied on through workspace feature unification, so this crate still builds
# if it is ever compiled on its own.
tokio = { workspace = true, features = ["time", "macros"] }
# `time` for the duty loop's sleep between slots, `rt` for `spawn_blocking`,
# which keeps the slashing-protection record's disk flush off the executor.
# Declared here rather than relied on through workspace feature unification,
# so this crate still builds if it is ever compiled on its own.
tokio = { workspace = true, features = ["time", "macros", "rt"] }
reqwest = { workspace = true, features = ["json"] }

# BLS12-381. Same version and backend the state transition verifies with, so a
Expand Down Expand Up @@ -72,3 +73,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread", "test-util"
tower = { version = "0.5", features = ["util"] }
http-body-util = "0.1"
tempfile = "3"

[features]
# Exposes `SlashingProtection::in_memory` to other crates' tests.
test-utils = []
Loading
Loading