mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-04 09:11:42 +00:00
* Attestation superstruct changes for EIP 7549 (#5644)
* update
* experiment
* superstruct changes
* revert
* superstruct changes
* fix tests
* indexed attestation
* indexed attestation superstruct
* updated TODOs
* `superstruct` the `AttesterSlashing` (#5636)
* `superstruct` Attester Fork Variants
* Push a little further
* Deal with Encode / Decode of AttesterSlashing
* not so sure about this..
* Stop Encode/Decode Bounds from Propagating Out
* Tons of Changes..
* More Conversions to AttestationRef
* Add AsReference trait (#15)
* Add AsReference trait
* Fix some snafus
* Got it Compiling! :D
* Got Tests Building
* Get beacon chain tests compiling
---------
Co-authored-by: Michael Sproul <micsproul@gmail.com>
* Merge remote-tracking branch 'upstream/unstable' into electra_attestation_changes
* Make EF Tests Fork-Agnostic (#5713)
* Finish EF Test Fork Agnostic (#5714)
* Superstruct `AggregateAndProof` (#5715)
* Upgrade `superstruct` to `0.8.0`
* superstruct `AggregateAndProof`
* Merge remote-tracking branch 'sigp/unstable' into electra_attestation_changes
* cargo fmt
* Merge pull request #5726 from realbigsean/electra_attestation_changes
Merge unstable into Electra attestation changes
* EIP7549 `get_attestation_indices` (#5657)
* get attesting indices electra impl
* fmt
* get tests to pass
* fmt
* fix some beacon chain tests
* fmt
* fix slasher test
* fmt got me again
* fix more tests
* fix tests
* Some small changes (#5739)
* cargo fmt (#5740)
* Sketch op pool changes
* fix get attesting indices (#5742)
* fix get attesting indices
* better errors
* fix compile
* only get committee index once
* Ef test fixes (#5753)
* attestation related ef test fixes
* delete commented out stuff
* Fix Aggregation Pool for Electra (#5754)
* Fix Aggregation Pool for Electra
* Remove Outdated Interface
* fix ssz (#5755)
* Get `electra_op_pool` up to date (#5756)
* fix get attesting indices (#5742)
* fix get attesting indices
* better errors
* fix compile
* only get committee index once
* Ef test fixes (#5753)
* attestation related ef test fixes
* delete commented out stuff
* Fix Aggregation Pool for Electra (#5754)
* Fix Aggregation Pool for Electra
* Remove Outdated Interface
* fix ssz (#5755)
---------
Co-authored-by: realbigsean <sean@sigmaprime.io>
* Revert "Get `electra_op_pool` up to date (#5756)" (#5757)
This reverts commit ab9e58aa3d.
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into electra_op_pool
* Compute on chain aggregate impl (#5752)
* add compute_on_chain_agg impl to op pool changes
* fmt
* get op pool tests to pass
* update the naive agg pool interface (#5760)
* Fix bugs in cross-committee aggregation
* Add comment to max cover optimisation
* Fix assert
* Merge pull request #5749 from sigp/electra_op_pool
Optimise Electra op pool aggregation
* update committee offset
* Fix Electra Fork Choice Tests (#5764)
* Subscribe to the correct subnets for electra attestations (#5782)
* subscribe to the correct att subnets for electra
* subscribe to the correct att subnets for electra
* cargo fmt
* fix slashing handling
* Merge remote-tracking branch 'upstream/unstable'
* Send unagg attestation based on fork
* Publish all aggregates
* just one more check bro plz..
* Merge pull request #5832 from ethDreamer/electra_attestation_changes_merge_unstable
Merge `unstable` into `electra_attestation_changes`
* Merge pull request #5835 from realbigsean/fix-validator-logic
Fix validator logic
* Merge pull request #5816 from realbigsean/electra-attestation-slashing-handling
Electra slashing handling
* Electra attestation changes rm decode impl (#5856)
* Remove Crappy Decode impl for Attestation
* Remove Inefficient Attestation Decode impl
* Implement Schema Upgrade / Downgrade
* Update beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs
Co-authored-by: Michael Sproul <micsproul@gmail.com>
---------
Co-authored-by: Michael Sproul <micsproul@gmail.com>
* Fix failing attestation tests and misc electra attestation cleanup (#5810)
* - get attestation related beacon chain tests to pass
- observed attestations are now keyed off of data + committee index
- rename op pool attestationref to compactattestationref
- remove unwraps in agg pool and use options instead
- cherry pick some changes from ef-tests-electra
* cargo fmt
* fix failing test
* Revert dockerfile changes
* make committee_index return option
* function args shouldnt be a ref to attestation ref
* fmt
* fix dup imports
---------
Co-authored-by: realbigsean <seananderson33@GMAIL.com>
* fix some todos (#5817)
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* add consolidations to merkle calc for inclusion proof
* Remove Duplicate KZG Commitment Merkle Proof Code (#5874)
* Remove Duplicate KZG Commitment Merkle Proof Code
* s/tree_lists/fields/
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* fix compile
* Fix slasher tests (#5906)
* Fix electra tests
* Add electra attestations to double vote tests
* Update superstruct to 0.8
* Merge remote-tracking branch 'origin/unstable' into electra_attestation_changes
* Small cleanup in slasher tests
* Clean up Electra observed aggregates (#5929)
* Use consistent key in observed_attestations
* Remove unwraps from observed aggregates
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* De-dup attestation constructor logic
* Remove unwraps in Attestation construction
* Dedup match_attestation_data
* Remove outdated TODO
* Use ForkName Ord in fork-choice tests
* Use ForkName Ord in BeaconBlockBody
* Make to_electra not fallible
* Remove TestRandom impl for IndexedAttestation
* Remove IndexedAttestation faulty Decode impl
* Drop TestRandom impl
* Add PendingAttestationInElectra
* Indexed att on disk (#35)
* indexed att on disk
* fix lints
* Update slasher/src/migrate.rs
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
---------
Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com>
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
* add electra fork enabled fn to ForkName impl (#36)
* add electra fork enabled fn to ForkName impl
* remove inadvertent file
* Update common/eth2/src/types.rs
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
* Dedup attestation constructor logic in attester cache
* Use if let Ok for committee_bits
* Dedup Attestation constructor code
* Diff reduction in tests
* Fix beacon_chain tests
* Diff reduction
* Use Ord for ForkName in pubsub
* Resolve into_attestation_and_indices todo
* Remove stale TODO
* Fix beacon_chain tests
* Test spec invariant
* Use electra_enabled in pubsub
* Remove get_indexed_attestation_from_signed_aggregate
* Use ok_or instead of if let else
* committees are sorted
* remove dup method `get_indexed_attestation_from_committees`
* Merge pull request #5940 from dapplion/electra_attestation_changes_lionreview
Electra attestations #5712 review
* update default persisted op pool deserialization
* ensure aggregate and proof uses serde untagged on ref
* Fork aware ssz static attestation tests
* Electra attestation changes from Lions review (#5971)
* dedup/cleanup and remove unneeded hashset use
* remove irrelevant TODOs
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* Electra attestation changes sean review (#5972)
* instantiate empty bitlist in unreachable code
* clean up error conversion
* fork enabled bool cleanup
* remove a couple todos
* return bools instead of options in `aggregate` and use the result
* delete commented out code
* use map macros in simple transformations
* remove signers_disjoint_from
* get ef tests compiling
* get ef tests compiling
* update intentionally excluded files
* Avoid changing slasher schema for Electra
* Delete slasher schema v4
* Fix clippy
* Fix compilation of beacon_chain tests
* Update database.rs
* Add electra lightclient types
* Update slasher/src/database.rs
* fix imports
* Merge pull request #5980 from dapplion/electra-lightclient
Add electra lightclient types
* Merge pull request #5975 from michaelsproul/electra-slasher-no-migration
Avoid changing slasher schema for Electra
* Update beacon_node/beacon_chain/src/attestation_verification.rs
* Update beacon_node/beacon_chain/src/attestation_verification.rs
626 lines
18 KiB
Rust
626 lines
18 KiB
Rust
use crate::metrics::{self, SLASHER_COMPRESSION_RATIO, SLASHER_NUM_CHUNKS_UPDATED};
|
|
use crate::{
|
|
AttesterSlashingStatus, Config, Database, Error, IndexedAttesterRecord, RwTransaction,
|
|
SlasherDB,
|
|
};
|
|
use flate2::bufread::{ZlibDecoder, ZlibEncoder};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::borrow::Borrow;
|
|
use std::collections::{btree_map::Entry, BTreeMap, HashSet};
|
|
use std::io::Read;
|
|
use std::sync::Arc;
|
|
use types::{AttesterSlashing, Epoch, EthSpec, IndexedAttestation};
|
|
|
|
pub const MAX_DISTANCE: u16 = u16::MAX;
|
|
|
|
/// Terminology:
|
|
///
|
|
/// Let
|
|
/// N = config.history_length
|
|
/// C = config.chunk_size
|
|
/// K = config.validator_chunk_size
|
|
///
|
|
/// Then
|
|
///
|
|
/// `chunk_index` in [0..N/C) is the column of a chunk in the 2D matrix
|
|
/// `validator_chunk_index` in [0..N/K) is the row of a chunk in the 2D matrix
|
|
/// `chunk_offset` in [0..C) is the horizontal (epoch) offset of a value within a 2D chunk
|
|
/// `validator_offset` in [0..K) is the vertical (validator) offset of a value within a 2D chunk
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct Chunk {
|
|
data: Vec<u16>,
|
|
}
|
|
|
|
impl Chunk {
|
|
pub fn get_target(
|
|
&self,
|
|
validator_index: u64,
|
|
epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<Epoch, Error> {
|
|
assert_eq!(
|
|
self.data.len(),
|
|
config.chunk_size * config.validator_chunk_size
|
|
);
|
|
let validator_offset = config.validator_offset(validator_index);
|
|
let chunk_offset = config.chunk_offset(epoch);
|
|
let cell_index = config.cell_index(validator_offset, chunk_offset);
|
|
self.data
|
|
.get(cell_index)
|
|
.map(|distance| epoch + u64::from(*distance))
|
|
.ok_or(Error::ChunkIndexOutOfBounds(cell_index))
|
|
}
|
|
|
|
pub fn set_target(
|
|
&mut self,
|
|
validator_index: u64,
|
|
epoch: Epoch,
|
|
target_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<(), Error> {
|
|
let distance = Self::epoch_distance(target_epoch, epoch)?;
|
|
self.set_raw_distance(validator_index, epoch, distance, config)
|
|
}
|
|
|
|
pub fn set_raw_distance(
|
|
&mut self,
|
|
validator_index: u64,
|
|
epoch: Epoch,
|
|
target_distance: u16,
|
|
config: &Config,
|
|
) -> Result<(), Error> {
|
|
let validator_offset = config.validator_offset(validator_index);
|
|
let chunk_offset = config.chunk_offset(epoch);
|
|
let cell_index = config.cell_index(validator_offset, chunk_offset);
|
|
|
|
let cell = self
|
|
.data
|
|
.get_mut(cell_index)
|
|
.ok_or(Error::ChunkIndexOutOfBounds(cell_index))?;
|
|
*cell = target_distance;
|
|
Ok(())
|
|
}
|
|
|
|
/// Compute the distance (difference) between two epochs.
|
|
///
|
|
/// Error if the distance is greater than or equal to `MAX_DISTANCE`.
|
|
pub fn epoch_distance(epoch: Epoch, base_epoch: Epoch) -> Result<u16, Error> {
|
|
let distance_u64 = epoch
|
|
.as_u64()
|
|
.checked_sub(base_epoch.as_u64())
|
|
.ok_or(Error::DistanceCalculationOverflow)?;
|
|
|
|
let distance = u16::try_from(distance_u64).map_err(|_| Error::DistanceTooLarge)?;
|
|
if distance < MAX_DISTANCE {
|
|
Ok(distance)
|
|
} else {
|
|
Err(Error::DistanceTooLarge)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[serde(transparent)]
|
|
pub struct MinTargetChunk {
|
|
chunk: Chunk,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[serde(transparent)]
|
|
pub struct MaxTargetChunk {
|
|
chunk: Chunk,
|
|
}
|
|
|
|
pub trait TargetArrayChunk: Sized + serde::Serialize + serde::de::DeserializeOwned {
|
|
fn name() -> &'static str;
|
|
|
|
fn empty(config: &Config) -> Self;
|
|
|
|
fn chunk(&mut self) -> &mut Chunk;
|
|
|
|
fn neutral_element() -> u16;
|
|
|
|
fn check_slashable<E: EthSpec>(
|
|
&self,
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_index: u64,
|
|
attestation: &IndexedAttestation<E>,
|
|
config: &Config,
|
|
) -> Result<AttesterSlashingStatus<E>, Error>;
|
|
|
|
fn update(
|
|
&mut self,
|
|
chunk_index: usize,
|
|
validator_index: u64,
|
|
start_epoch: Epoch,
|
|
new_target_epoch: Epoch,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<bool, Error>;
|
|
|
|
fn first_start_epoch(
|
|
source_epoch: Epoch,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Option<Epoch>;
|
|
|
|
fn next_start_epoch(start_epoch: Epoch, config: &Config) -> Epoch;
|
|
|
|
fn select_db<E: EthSpec>(db: &SlasherDB<E>) -> &Database;
|
|
|
|
fn load<E: EthSpec>(
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_chunk_index: usize,
|
|
chunk_index: usize,
|
|
config: &Config,
|
|
) -> Result<Option<Self>, Error> {
|
|
let disk_key = config.disk_key(validator_chunk_index, chunk_index);
|
|
let Some(chunk_bytes) = txn.get(Self::select_db(db), &disk_key.to_be_bytes())? else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let chunk = bincode::deserialize_from(ZlibDecoder::new(chunk_bytes.borrow()))?;
|
|
|
|
Ok(Some(chunk))
|
|
}
|
|
|
|
fn store<E: EthSpec>(
|
|
&self,
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_chunk_index: usize,
|
|
chunk_index: usize,
|
|
config: &Config,
|
|
) -> Result<(), Error> {
|
|
let disk_key = config.disk_key(validator_chunk_index, chunk_index);
|
|
let value = bincode::serialize(self)?;
|
|
let mut encoder = ZlibEncoder::new(&value[..], flate2::Compression::default());
|
|
let mut compressed_value = vec![];
|
|
encoder.read_to_end(&mut compressed_value)?;
|
|
|
|
let compression_ratio = value.len() as f64 / compressed_value.len() as f64;
|
|
metrics::set_float_gauge(&SLASHER_COMPRESSION_RATIO, compression_ratio);
|
|
|
|
txn.put(
|
|
Self::select_db(db),
|
|
disk_key.to_be_bytes(),
|
|
&compressed_value,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl TargetArrayChunk for MinTargetChunk {
|
|
fn name() -> &'static str {
|
|
"min"
|
|
}
|
|
|
|
fn empty(config: &Config) -> Self {
|
|
MinTargetChunk {
|
|
chunk: Chunk {
|
|
data: vec![
|
|
Self::neutral_element();
|
|
config.chunk_size * config.validator_chunk_size
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
fn neutral_element() -> u16 {
|
|
MAX_DISTANCE
|
|
}
|
|
|
|
fn chunk(&mut self) -> &mut Chunk {
|
|
&mut self.chunk
|
|
}
|
|
|
|
fn check_slashable<E: EthSpec>(
|
|
&self,
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_index: u64,
|
|
attestation: &IndexedAttestation<E>,
|
|
config: &Config,
|
|
) -> Result<AttesterSlashingStatus<E>, Error> {
|
|
let min_target =
|
|
self.chunk
|
|
.get_target(validator_index, attestation.data().source.epoch, config)?;
|
|
if attestation.data().target.epoch > min_target {
|
|
let existing_attestation =
|
|
db.get_attestation_for_validator(txn, validator_index, min_target)?;
|
|
|
|
if attestation.data().source.epoch < existing_attestation.data().source.epoch {
|
|
Ok(AttesterSlashingStatus::SurroundsExisting(Box::new(
|
|
existing_attestation,
|
|
)))
|
|
} else {
|
|
Ok(AttesterSlashingStatus::AlreadyDoubleVoted)
|
|
}
|
|
} else {
|
|
Ok(AttesterSlashingStatus::NotSlashable)
|
|
}
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
chunk_index: usize,
|
|
validator_index: u64,
|
|
start_epoch: Epoch,
|
|
new_target_epoch: Epoch,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<bool, Error> {
|
|
let min_epoch = Epoch::from(
|
|
current_epoch
|
|
.as_usize()
|
|
.saturating_sub(config.history_length - 1),
|
|
);
|
|
let mut epoch = start_epoch;
|
|
while config.chunk_index(epoch) == chunk_index && epoch >= min_epoch {
|
|
if new_target_epoch < self.chunk.get_target(validator_index, epoch, config)? {
|
|
self.chunk
|
|
.set_target(validator_index, epoch, new_target_epoch, config)?;
|
|
} else {
|
|
// We can stop.
|
|
return Ok(false);
|
|
}
|
|
epoch -= 1;
|
|
}
|
|
Ok(epoch >= min_epoch)
|
|
}
|
|
|
|
fn first_start_epoch(
|
|
source_epoch: Epoch,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Option<Epoch> {
|
|
if source_epoch > current_epoch - config.history_length as u64 {
|
|
assert_ne!(source_epoch, 0);
|
|
Some(source_epoch - 1)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
// Move to last epoch of previous chunk
|
|
fn next_start_epoch(start_epoch: Epoch, config: &Config) -> Epoch {
|
|
let chunk_size = config.chunk_size as u64;
|
|
start_epoch / chunk_size * chunk_size - 1
|
|
}
|
|
|
|
fn select_db<E: EthSpec>(db: &SlasherDB<E>) -> &Database {
|
|
&db.databases.min_targets_db
|
|
}
|
|
}
|
|
|
|
impl TargetArrayChunk for MaxTargetChunk {
|
|
fn name() -> &'static str {
|
|
"max"
|
|
}
|
|
|
|
fn empty(config: &Config) -> Self {
|
|
MaxTargetChunk {
|
|
chunk: Chunk {
|
|
data: vec![
|
|
Self::neutral_element();
|
|
config.chunk_size * config.validator_chunk_size
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
fn neutral_element() -> u16 {
|
|
0
|
|
}
|
|
|
|
fn chunk(&mut self) -> &mut Chunk {
|
|
&mut self.chunk
|
|
}
|
|
|
|
fn check_slashable<E: EthSpec>(
|
|
&self,
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_index: u64,
|
|
attestation: &IndexedAttestation<E>,
|
|
config: &Config,
|
|
) -> Result<AttesterSlashingStatus<E>, Error> {
|
|
let max_target =
|
|
self.chunk
|
|
.get_target(validator_index, attestation.data().source.epoch, config)?;
|
|
if attestation.data().target.epoch < max_target {
|
|
let existing_attestation =
|
|
db.get_attestation_for_validator(txn, validator_index, max_target)?;
|
|
|
|
if existing_attestation.data().source.epoch < attestation.data().source.epoch {
|
|
Ok(AttesterSlashingStatus::SurroundedByExisting(Box::new(
|
|
existing_attestation,
|
|
)))
|
|
} else {
|
|
Ok(AttesterSlashingStatus::AlreadyDoubleVoted)
|
|
}
|
|
} else {
|
|
Ok(AttesterSlashingStatus::NotSlashable)
|
|
}
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
chunk_index: usize,
|
|
validator_index: u64,
|
|
start_epoch: Epoch,
|
|
new_target_epoch: Epoch,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<bool, Error> {
|
|
let mut epoch = start_epoch;
|
|
while config.chunk_index(epoch) == chunk_index && epoch <= current_epoch {
|
|
if new_target_epoch > self.chunk.get_target(validator_index, epoch, config)? {
|
|
self.chunk
|
|
.set_target(validator_index, epoch, new_target_epoch, config)?;
|
|
} else {
|
|
// We can stop.
|
|
return Ok(false);
|
|
}
|
|
epoch += 1;
|
|
}
|
|
// If the epoch to update now lies beyond the current chunk and is less than
|
|
// or equal to the current epoch, then continue to the next chunk to update it.
|
|
Ok(epoch <= current_epoch)
|
|
}
|
|
|
|
fn first_start_epoch(
|
|
source_epoch: Epoch,
|
|
current_epoch: Epoch,
|
|
_config: &Config,
|
|
) -> Option<Epoch> {
|
|
if source_epoch < current_epoch {
|
|
Some(source_epoch + 1)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
// Move to first epoch of next chunk
|
|
fn next_start_epoch(start_epoch: Epoch, config: &Config) -> Epoch {
|
|
let chunk_size = config.chunk_size as u64;
|
|
(start_epoch / chunk_size + 1) * chunk_size
|
|
}
|
|
|
|
fn select_db<E: EthSpec>(db: &SlasherDB<E>) -> &Database {
|
|
&db.databases.max_targets_db
|
|
}
|
|
}
|
|
|
|
pub fn get_chunk_for_update<'a, E: EthSpec, T: TargetArrayChunk>(
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
updated_chunks: &'a mut BTreeMap<usize, T>,
|
|
validator_chunk_index: usize,
|
|
chunk_index: usize,
|
|
config: &Config,
|
|
) -> Result<&'a mut T, Error> {
|
|
Ok(match updated_chunks.entry(chunk_index) {
|
|
Entry::Occupied(occupied) => occupied.into_mut(),
|
|
Entry::Vacant(vacant) => {
|
|
let chunk = if let Some(disk_chunk) =
|
|
T::load(db, txn, validator_chunk_index, chunk_index, config)?
|
|
{
|
|
disk_chunk
|
|
} else {
|
|
T::empty(config)
|
|
};
|
|
vacant.insert(chunk)
|
|
}
|
|
})
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn apply_attestation_for_validator<E: EthSpec, T: TargetArrayChunk>(
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
updated_chunks: &mut BTreeMap<usize, T>,
|
|
validator_chunk_index: usize,
|
|
validator_index: u64,
|
|
attestation: &IndexedAttestation<E>,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<AttesterSlashingStatus<E>, Error> {
|
|
let mut chunk_index = config.chunk_index(attestation.data().source.epoch);
|
|
let mut current_chunk = get_chunk_for_update(
|
|
db,
|
|
txn,
|
|
updated_chunks,
|
|
validator_chunk_index,
|
|
chunk_index,
|
|
config,
|
|
)?;
|
|
|
|
let slashing_status =
|
|
current_chunk.check_slashable(db, txn, validator_index, attestation, config)?;
|
|
|
|
if slashing_status != AttesterSlashingStatus::NotSlashable {
|
|
return Ok(slashing_status);
|
|
}
|
|
|
|
let Some(mut start_epoch) =
|
|
T::first_start_epoch(attestation.data().source.epoch, current_epoch, config)
|
|
else {
|
|
return Ok(slashing_status);
|
|
};
|
|
|
|
loop {
|
|
chunk_index = config.chunk_index(start_epoch);
|
|
current_chunk = get_chunk_for_update(
|
|
db,
|
|
txn,
|
|
updated_chunks,
|
|
validator_chunk_index,
|
|
chunk_index,
|
|
config,
|
|
)?;
|
|
let keep_going = current_chunk.update(
|
|
chunk_index,
|
|
validator_index,
|
|
start_epoch,
|
|
attestation.data().target.epoch,
|
|
current_epoch,
|
|
config,
|
|
)?;
|
|
if !keep_going {
|
|
break;
|
|
}
|
|
start_epoch = T::next_start_epoch(start_epoch, config);
|
|
}
|
|
|
|
Ok(AttesterSlashingStatus::NotSlashable)
|
|
}
|
|
|
|
pub fn update<E: EthSpec>(
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_chunk_index: usize,
|
|
batch: Vec<Arc<IndexedAttesterRecord<E>>>,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<HashSet<AttesterSlashing<E>>, Error> {
|
|
// Split the batch up into horizontal segments.
|
|
// Map chunk indexes in the range `0..self.config.chunk_size` to attestations
|
|
// for those chunks.
|
|
let mut chunk_attestations = BTreeMap::new();
|
|
for attestation in batch {
|
|
chunk_attestations
|
|
.entry(config.chunk_index(attestation.indexed.data().source.epoch))
|
|
.or_insert_with(Vec::new)
|
|
.push(attestation);
|
|
}
|
|
|
|
let mut slashings = update_array::<_, MinTargetChunk>(
|
|
db,
|
|
txn,
|
|
validator_chunk_index,
|
|
&chunk_attestations,
|
|
current_epoch,
|
|
config,
|
|
)?;
|
|
slashings.extend(update_array::<_, MaxTargetChunk>(
|
|
db,
|
|
txn,
|
|
validator_chunk_index,
|
|
&chunk_attestations,
|
|
current_epoch,
|
|
config,
|
|
)?);
|
|
|
|
// Update all current epochs.
|
|
for validator_index in config.validator_indices_in_chunk(validator_chunk_index) {
|
|
db.update_current_epoch_for_validator(validator_index, current_epoch, txn)?;
|
|
}
|
|
|
|
Ok(slashings)
|
|
}
|
|
|
|
pub fn epoch_update_for_validator<E: EthSpec, T: TargetArrayChunk>(
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
updated_chunks: &mut BTreeMap<usize, T>,
|
|
validator_chunk_index: usize,
|
|
validator_index: u64,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<(), Error> {
|
|
let Some(previous_current_epoch) = db.get_current_epoch_for_validator(validator_index, txn)?
|
|
else {
|
|
return Ok(());
|
|
};
|
|
|
|
let mut epoch = previous_current_epoch;
|
|
|
|
while epoch <= current_epoch {
|
|
let chunk_index = config.chunk_index(epoch);
|
|
let current_chunk = get_chunk_for_update(
|
|
db,
|
|
txn,
|
|
updated_chunks,
|
|
validator_chunk_index,
|
|
chunk_index,
|
|
config,
|
|
)?;
|
|
while config.chunk_index(epoch) == chunk_index && epoch <= current_epoch {
|
|
current_chunk.chunk().set_raw_distance(
|
|
validator_index,
|
|
epoch,
|
|
T::neutral_element(),
|
|
config,
|
|
)?;
|
|
epoch += 1;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
pub fn update_array<E: EthSpec, T: TargetArrayChunk>(
|
|
db: &SlasherDB<E>,
|
|
txn: &mut RwTransaction<'_>,
|
|
validator_chunk_index: usize,
|
|
chunk_attestations: &BTreeMap<usize, Vec<Arc<IndexedAttesterRecord<E>>>>,
|
|
current_epoch: Epoch,
|
|
config: &Config,
|
|
) -> Result<HashSet<AttesterSlashing<E>>, Error> {
|
|
let mut slashings = HashSet::new();
|
|
// Map from chunk index to updated chunk at that index.
|
|
let mut updated_chunks = BTreeMap::new();
|
|
|
|
// Update the arrays for the change of current epoch.
|
|
for validator_index in config.validator_indices_in_chunk(validator_chunk_index) {
|
|
epoch_update_for_validator(
|
|
db,
|
|
txn,
|
|
&mut updated_chunks,
|
|
validator_chunk_index,
|
|
validator_index,
|
|
current_epoch,
|
|
config,
|
|
)?;
|
|
}
|
|
|
|
for attestations in chunk_attestations.values() {
|
|
for attestation in attestations {
|
|
for validator_index in
|
|
config.attesting_validators_in_chunk(&attestation.indexed, validator_chunk_index)
|
|
{
|
|
let slashing_status = apply_attestation_for_validator::<E, T>(
|
|
db,
|
|
txn,
|
|
&mut updated_chunks,
|
|
validator_chunk_index,
|
|
validator_index,
|
|
&attestation.indexed,
|
|
current_epoch,
|
|
config,
|
|
)?;
|
|
if let Some(slashing) = slashing_status.into_slashing(&attestation.indexed) {
|
|
slashings.insert(slashing);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store chunks on disk.
|
|
metrics::inc_counter_vec_by(
|
|
&SLASHER_NUM_CHUNKS_UPDATED,
|
|
&[T::name()],
|
|
updated_chunks.len() as u64,
|
|
);
|
|
|
|
for (chunk_index, chunk) in updated_chunks {
|
|
chunk.store(db, txn, validator_chunk_index, chunk_index, config)?;
|
|
}
|
|
|
|
Ok(slashings)
|
|
}
|