pub struct SlashingProtection { /* private fields */ }Expand description
Per-validator local slashing-protection state.
Implements DSL-094 (+ DSL-095/096/097 in later commits). Traces to SPEC §14.
§Fields
last_proposed_slot— largest slot the validator has proposed at.check_proposal_slotrequires a strictly greater slot before signing a new proposal.
Future DSLs add last_source_epoch, last_target_epoch,
attested_hash_by_target and similar fields as their
guards come online.
§Default
Default::default() → last_proposed_slot = 0. Any slot
> 0 passes check_proposal_slot on a fresh instance.
Implementations§
Source§impl SlashingProtection
impl SlashingProtection
Sourcepub fn last_proposed_slot(&self) -> u64
pub fn last_proposed_slot(&self) -> u64
Last slot at which the validator proposed. Used for introspection + persistence round-trips.
Sourcepub fn check_proposal_slot(&self, slot: u64) -> bool
pub fn check_proposal_slot(&self, slot: u64) -> bool
true iff the caller MAY sign a new proposal at slot.
Implements DSL-094.
§Predicate
slot > self.last_proposed_slot — strict greater-than
so the same slot cannot be signed twice (that would be
the canonical proposer-equivocation offense).
Fresh validators have last_proposed_slot = 0, so any
slot > 0 is safe to sign.
Sourcepub fn record_proposal(&mut self, slot: u64)
pub fn record_proposal(&mut self, slot: u64)
Record a successful proposal at slot. Subsequent
check_proposal_slot(s) calls with s <= slot will
return false.
Implements DSL-094. Persistence semantics land in DSL-097.
Sourcepub fn last_attested_source_epoch(&self) -> u64
pub fn last_attested_source_epoch(&self) -> u64
source.epoch of the last recorded attestation.
Sourcepub fn last_attested_target_epoch(&self) -> u64
pub fn last_attested_target_epoch(&self) -> u64
target.epoch of the last recorded attestation.
Sourcepub fn last_attested_block_hash(&self) -> Option<&str>
pub fn last_attested_block_hash(&self) -> Option<&str>
Lowercase 0x-prefixed hex of the last recorded
attestation’s block hash. None when no attestation
has been recorded.
Sourcepub fn check_attestation(
&self,
source_epoch: u64,
target_epoch: u64,
block_hash: &Bytes32,
) -> bool
pub fn check_attestation( &self, source_epoch: u64, target_epoch: u64, block_hash: &Bytes32, ) -> bool
true iff the caller MAY sign an attestation at
(source_epoch, target_epoch, block_hash).
Implements DSL-095 (same-(src, tgt) different-hash self-check). DSL-096 (surround-vote self-check) extends this method in a later commit.
§DSL-095 rule
When the candidate FFG coordinates match the stored
last-attested pair EXACTLY, the attestation is allowed
only if the candidate block hash matches the stored
hash (case-insensitive hex compare). This is the
“re-sign the same vote” carve-out — a validator
restarting mid-epoch may re-emit its own attestation,
but may NOT switch to a different block at the same
source/target pair (that would be an
AttesterDoubleVote, DSL-014).
If no prior attestation is stored (last_attested_* = 0, None), the check falls through to the surround
guard (DSL-096 — currently a no-op stub) and returns
true.
Sourcepub fn record_attestation(
&mut self,
source_epoch: u64,
target_epoch: u64,
block_hash: &Bytes32,
)
pub fn record_attestation( &mut self, source_epoch: u64, target_epoch: u64, block_hash: &Bytes32, )
Record a successful attestation. Updates
last_attested_source_epoch, last_attested_target_epoch
and last_attested_block_hash.
Implements the DSL-095/096 persistence primitive. DSL-097
pins the full contract (including the proposer-side
record_proposal companion).
Sourcepub fn rewind_proposal_to_slot(&mut self, new_tip_slot: u64)
pub fn rewind_proposal_to_slot(&mut self, new_tip_slot: u64)
Rewind the proposal watermark on fork-choice reorg.
Previews DSL-156
— DSL-099 composes this fn alongside [rewind_attestation_to_epoch]
(DSL-098) inside [reconcile_with_chain_tip]. The DSL-156
dedicated test file lands in Phase 10.
§Semantics
Caps last_proposed_slot at new_tip_slot using strict >
so the boundary (stored == tip) is a no-op and already-lower
slots remain untouched. Reconcile must never RAISE a
watermark — doing so would weaken slashing protection.
No hash-equivalent to clear on the proposal side: DSL-094 only tracks the slot, not a block binding.
Sourcepub fn save(&self, path: &PathBuf) -> Result<()>
pub fn save(&self, path: &PathBuf) -> Result<()>
Persist slashing-protection state to disk as pretty-printed JSON.
Implements DSL-101. Traces to SPEC §14.4.
§On-disk format
JSON, pretty-printed for operator debuggability. All fields
are primitive (u64) except last_attested_block_hash,
which is stored as the canonical 0x<lowercase-hex> form
produced by [record_attestation]. External tooling that
rewrites the hash to uppercase is tolerated on reload via
[check_attestation]’s case-insensitive compare.
§Errors
Returns any I/O error raised by the underlying std::fs
write. Serialization is infallible (every field is
serde-safe by construction).
§Companion load
The inverse of this call. load handles the missing-file
case by returning Self::default() so a first-boot
validator does not need a bootstrap branch.
Sourcepub fn load(path: &PathBuf) -> Result<Self>
pub fn load(path: &PathBuf) -> Result<Self>
Load slashing-protection state from disk.
Implements DSL-101.
§Behaviour
- Path exists: decode the file via
serde_json::from_slice. Uses the same schema assave; DSL-100 handles legacy JSON lackinglast_attested_block_hashvia#[serde(default)]on that field. - Path does NOT exist: return
Self::default(). This is the intentional first-boot path — a validator with no prior state callsloadand gets a clean instance, avoiding an explicit bootstrap branch at every call site.
§Errors
std::fs::readerrors (permission, I/O) propagate.- Deserialization errors surface as
InvalidDataviaserde_json. NOTE: a legitimate legacy file triggers#[serde(default)], not an error.
Sourcepub fn reconcile_with_chain_tip(&mut self, tip_slot: u64, tip_epoch: u64)
pub fn reconcile_with_chain_tip(&mut self, tip_slot: u64, tip_epoch: u64)
Reconcile local slashing-protection state with the canonical chain tip on validator startup or after a reorg.
Implements DSL-099. Traces to SPEC §14.3.
§Semantics
Composes [rewind_proposal_to_slot] (DSL-156) with
[rewind_attestation_to_epoch] (DSL-098) under a single
entry point. Net effect:
last_proposed_slotcapped attip_slot(never raised).last_attested_source_epoch/last_attested_target_epochcapped attip_epoch(never raised).last_attested_block_hashcleared unconditionally — the hash binds to a specific block that the reorg invalidates.
Idempotent by construction: both legs are caps, and a second
call with the same (tip_slot, tip_epoch) finds the state
already satisfying both caps.
Called by:
- validator boot sequence (rejoin canonical chain after downtime),
- DSL-130 global-reorg orchestration.
Sourcepub fn rewind_attestation_to_epoch(&mut self, new_tip_epoch: u64)
pub fn rewind_attestation_to_epoch(&mut self, new_tip_epoch: u64)
Rewind attestation state on fork-choice reorg or chain-tip refresh.
Implements DSL-098. Traces to SPEC §14.3.
§Semantics
The stored (source, target, hash) triple is the validator’s local memory of “what I already signed.” When a reorg drops the chain back below the attested epochs, that memory is a ghost watermark — the block the hash points to no longer exists on the canonical chain. Keeping it would block honest re-attestation through DSL-095/096.
Two legs:
- Cap
last_attested_source_epochandlast_attested_target_epochatnew_tip_epoch. Use strict>so the boundary case (stored == tip) is a no-op — the cap must never RAISE a watermark, only lower it. - Clear
last_attested_block_hashunconditionally. The hash binds to a specific block; a reorg invalidates that binding regardless of epoch ordering.
After rewind, a re-attestation on the new canonical tip
passes [check_attestation].
Companion DSL-099 (reconcile_with_chain_tip) calls this
alongside the proposal-rewind DSL-156; DSL-130 triggers the
whole bundle on global reorg.
Trait Implementations§
Source§impl Clone for SlashingProtection
impl Clone for SlashingProtection
Source§fn clone(&self) -> SlashingProtection
fn clone(&self) -> SlashingProtection
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SlashingProtection
impl Debug for SlashingProtection
Source§impl Default for SlashingProtection
impl Default for SlashingProtection
Source§fn default() -> SlashingProtection
fn default() -> SlashingProtection
Source§impl<'de> Deserialize<'de> for SlashingProtection
impl<'de> Deserialize<'de> for SlashingProtection
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for SlashingProtection
impl PartialEq for SlashingProtection
Source§impl Serialize for SlashingProtection
impl Serialize for SlashingProtection
impl Eq for SlashingProtection
impl StructuralPartialEq for SlashingProtection
Auto Trait Implementations§
impl Freeze for SlashingProtection
impl RefUnwindSafe for SlashingProtection
impl Send for SlashingProtection
impl Sync for SlashingProtection
impl Unpin for SlashingProtection
impl UnsafeUnpin for SlashingProtection
impl UnwindSafe for SlashingProtection
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.