Avoid changing slasher schema for Electra

This commit is contained in:
Michael Sproul
2024-06-21 12:02:23 +10:00
parent 27ed90e4dc
commit b6913ae542
13 changed files with 178 additions and 60 deletions

1
Cargo.lock generated
View File

@@ -7653,6 +7653,7 @@ dependencies = [
"safe_arith", "safe_arith",
"serde", "serde",
"slog", "slog",
"ssz_types",
"strum", "strum",
"tempfile", "tempfile",
"tree_hash", "tree_hash",

View File

@@ -80,7 +80,7 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
let builder = ClientBuilder::new(context.eth_spec_instance.clone()) let builder = ClientBuilder::new(context.eth_spec_instance.clone())
.runtime_context(context) .runtime_context(context)
.chain_spec(spec) .chain_spec(spec.clone())
.beacon_processor(client_config.beacon_processor.clone()) .beacon_processor(client_config.beacon_processor.clone())
.http_api_config(client_config.http_api.clone()) .http_api_config(client_config.http_api.clone())
.disk_store( .disk_store(
@@ -113,7 +113,11 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
_ => {} _ => {}
} }
let slasher = Arc::new( let slasher = Arc::new(
Slasher::open(slasher_config, log.new(slog::o!("service" => "slasher"))) Slasher::open(
slasher_config,
Arc::new(spec),
log.new(slog::o!("service" => "slasher")),
)
.map_err(|e| format!("Slasher open error: {:?}", e))?, .map_err(|e| format!("Slasher open error: {:?}", e))?,
); );
builder.slasher(slasher) builder.slasher(slasher)

View File

@@ -240,38 +240,6 @@ mod quoted_variable_list_u64 {
} }
} }
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
#[ssz(enum_behaviour = "union")]
pub enum IndexedAttestationOnDisk<E: EthSpec> {
Base(IndexedAttestationBase<E>),
Electra(IndexedAttestationElectra<E>),
}
#[derive(Debug, Clone, Encode, PartialEq)]
#[ssz(enum_behaviour = "union")]
pub enum IndexedAttestationRefOnDisk<'a, E: EthSpec> {
Base(&'a IndexedAttestationBase<E>),
Electra(&'a IndexedAttestationElectra<E>),
}
impl<'a, E: EthSpec> From<&'a IndexedAttestation<E>> for IndexedAttestationRefOnDisk<'a, E> {
fn from(attestation: &'a IndexedAttestation<E>) -> Self {
match attestation {
IndexedAttestation::Base(attestation) => Self::Base(attestation),
IndexedAttestation::Electra(attestation) => Self::Electra(attestation),
}
}
}
impl<E: EthSpec> From<IndexedAttestationOnDisk<E>> for IndexedAttestation<E> {
fn from(attestation: IndexedAttestationOnDisk<E>) -> Self {
match attestation {
IndexedAttestationOnDisk::Base(attestation) => Self::Base(attestation),
IndexedAttestationOnDisk::Electra(attestation) => Self::Electra(attestation),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -176,8 +176,7 @@ pub use crate::fork_versioned_response::{ForkVersionDeserialize, ForkVersionedRe
pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN};
pub use crate::historical_batch::HistoricalBatch; pub use crate::historical_batch::HistoricalBatch;
pub use crate::indexed_attestation::{ pub use crate::indexed_attestation::{
IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef,
IndexedAttestationOnDisk, IndexedAttestationRef, IndexedAttestationRefOnDisk,
}; };
pub use crate::light_client_bootstrap::{ pub use crate::light_client_bootstrap::{
LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella,

View File

@@ -29,6 +29,7 @@ tree_hash = { workspace = true }
tree_hash_derive = { workspace = true } tree_hash_derive = { workspace = true }
types = { workspace = true } types = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
ssz_types = { workspace = true }
# MDBX is pinned at the last version with Windows and macOS support. # MDBX is pinned at the last version with Windows and macOS support.
mdbx = { package = "libmdbx", git = "https://github.com/sigp/libmdbx-rs", tag = "v0.1.4", optional = true } mdbx = { package = "libmdbx", git = "https://github.com/sigp/libmdbx-rs", tag = "v0.1.4", optional = true }

View File

@@ -13,13 +13,15 @@ use parking_lot::Mutex;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use slog::{info, Logger}; use slog::{info, Logger};
use ssz::{Decode, Encode}; use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::{ use types::{
Epoch, EthSpec, Hash256, IndexedAttestation, IndexedAttestationOnDisk, AggregateSignature, AttestationData, ChainSpec, Epoch, EthSpec, ForkName, Hash256,
IndexedAttestationRefOnDisk, ProposerSlashing, SignedBeaconBlockHeader, Slot, IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, ProposerSlashing,
SignedBeaconBlockHeader, Slot, VariableList,
}; };
/// Current database schema version, to check compatibility of on-disk DB with software. /// Current database schema version, to check compatibility of on-disk DB with software.
@@ -70,6 +72,7 @@ pub struct SlasherDB<E: EthSpec> {
/// LRU cache mapping indexed attestation IDs to their attestation data roots. /// LRU cache mapping indexed attestation IDs to their attestation data roots.
attestation_root_cache: Mutex<LruCache<IndexedAttestationId, Hash256>>, attestation_root_cache: Mutex<LruCache<IndexedAttestationId, Hash256>>,
pub(crate) config: Arc<Config>, pub(crate) config: Arc<Config>,
pub(crate) spec: Arc<ChainSpec>,
_phantom: PhantomData<E>, _phantom: PhantomData<E>,
} }
@@ -236,6 +239,43 @@ impl AsRef<[u8]> for IndexedAttestationId {
} }
} }
/// Indexed attestation that abstracts over Phase0 and Electra variants by using a plain `Vec` for
/// the attesting indices.
///
/// This allows us to avoid rewriting the entire indexed attestation database at Electra, which
/// saves a lot of execution time. The bytes that it encodes to are the same as the bytes that a
/// regular IndexedAttestation encodes to, because SSZ doesn't care about the length-bound.
#[derive(Debug, PartialEq, Decode, Encode)]
pub struct IndexedAttestationOnDisk {
attesting_indices: Vec<u64>,
data: AttestationData,
signature: AggregateSignature,
}
impl IndexedAttestationOnDisk {
fn into_indexed_attestation<E: EthSpec>(
self,
spec: &ChainSpec,
) -> Result<IndexedAttestation<E>, Error> {
let fork_at_target_epoch = spec.fork_name_at_epoch(self.data.target.epoch);
if fork_at_target_epoch >= ForkName::Electra {
let attesting_indices = VariableList::new(self.attesting_indices)?;
Ok(IndexedAttestation::Electra(IndexedAttestationElectra {
attesting_indices,
data: self.data,
signature: self.signature,
}))
} else {
let attesting_indices = VariableList::new(self.attesting_indices)?;
Ok(IndexedAttestation::Base(IndexedAttestationBase {
attesting_indices,
data: self.data,
signature: self.signature,
}))
}
}
}
/// Bincode deserialization specialised to `Cow<[u8]>`. /// Bincode deserialization specialised to `Cow<[u8]>`.
fn bincode_deserialize<T: DeserializeOwned>(bytes: Cow<[u8]>) -> Result<T, Error> { fn bincode_deserialize<T: DeserializeOwned>(bytes: Cow<[u8]>) -> Result<T, Error> {
Ok(bincode::deserialize(bytes.borrow())?) Ok(bincode::deserialize(bytes.borrow())?)
@@ -246,7 +286,7 @@ fn ssz_decode<T: Decode>(bytes: Cow<[u8]>) -> Result<T, Error> {
} }
impl<E: EthSpec> SlasherDB<E> { impl<E: EthSpec> SlasherDB<E> {
pub fn open(config: Arc<Config>, log: Logger) -> Result<Self, Error> { pub fn open(config: Arc<Config>, spec: Arc<ChainSpec>, log: Logger) -> Result<Self, Error> {
info!(log, "Opening slasher database"; "backend" => %config.backend); info!(log, "Opening slasher database"; "backend" => %config.backend);
std::fs::create_dir_all(&config.database_path)?; std::fs::create_dir_all(&config.database_path)?;
@@ -269,6 +309,7 @@ impl<E: EthSpec> SlasherDB<E> {
databases, databases,
attestation_root_cache, attestation_root_cache,
config, config,
spec,
_phantom: PhantomData, _phantom: PhantomData,
}; };
@@ -458,9 +499,8 @@ impl<E: EthSpec> SlasherDB<E> {
}; };
let attestation_key = IndexedAttestationId::new(indexed_att_id); let attestation_key = IndexedAttestationId::new(indexed_att_id);
let indexed_attestation_on_disk: IndexedAttestationRefOnDisk<E> = // IndexedAttestationOnDisk and IndexedAttestation have compatible encodings.
indexed_attestation.into(); let data = indexed_attestation.as_ssz_bytes();
let data = indexed_attestation_on_disk.as_ssz_bytes();
cursor.put(attestation_key.as_ref(), &data)?; cursor.put(attestation_key.as_ref(), &data)?;
drop(cursor); drop(cursor);
@@ -484,8 +524,8 @@ impl<E: EthSpec> SlasherDB<E> {
.ok_or(Error::MissingIndexedAttestation { .ok_or(Error::MissingIndexedAttestation {
id: indexed_attestation_id.as_u64(), id: indexed_attestation_id.as_u64(),
})?; })?;
let indexed_attestation: IndexedAttestationOnDisk<E> = ssz_decode(bytes)?; let indexed_attestation_on_disk: IndexedAttestationOnDisk = ssz_decode(bytes)?;
Ok(indexed_attestation.into()) indexed_attestation_on_disk.into_indexed_attestation(&self.spec)
} }
fn get_attestation_data_root( fn get_attestation_data_root(
@@ -775,3 +815,84 @@ impl<E: EthSpec> SlasherDB<E> {
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod test {
use super::*;
use types::{Checkpoint, MainnetEthSpec};
type E = MainnetEthSpec;
fn indexed_attestation_on_disk_roundtrip_test(
spec: &ChainSpec,
make_attestation: fn(
Vec<u64>,
AttestationData,
AggregateSignature,
) -> IndexedAttestation<E>,
) {
let attestation_data = AttestationData {
slot: Slot::new(1000),
index: 0,
beacon_block_root: Hash256::repeat_byte(0xaa),
source: Checkpoint {
epoch: Epoch::new(0),
root: Hash256::repeat_byte(0xbb),
},
target: Checkpoint {
epoch: Epoch::new(31),
root: Hash256::repeat_byte(0xcc),
},
};
let attesting_indices = vec![1, 14, 160, 812737];
let signature = AggregateSignature::infinity();
let fork_attestation = make_attestation(
attesting_indices.clone(),
attestation_data.clone(),
signature.clone(),
);
let on_disk = IndexedAttestationOnDisk {
attesting_indices,
data: attestation_data,
signature,
};
let encoded = on_disk.as_ssz_bytes();
assert_eq!(encoded, fork_attestation.as_ssz_bytes());
let decoded_on_disk = IndexedAttestationOnDisk::from_ssz_bytes(&encoded).unwrap();
assert_eq!(decoded_on_disk, on_disk);
let decoded = on_disk.into_indexed_attestation(&spec).unwrap();
assert_eq!(decoded, fork_attestation);
}
/// Check that `IndexedAttestationOnDisk` and `IndexedAttestation` have compatible encodings.
#[test]
fn indexed_attestation_on_disk_roundtrip_base() {
let spec = ForkName::Base.make_genesis_spec(E::default_spec());
let make_attestation = |attesting_indices, data, signature| {
IndexedAttestation::<E>::Base(IndexedAttestationBase {
attesting_indices: VariableList::new(attesting_indices).unwrap(),
data,
signature,
})
};
indexed_attestation_on_disk_roundtrip_test(&spec, make_attestation)
}
#[test]
fn indexed_attestation_on_disk_roundtrip_electra() {
let spec = ForkName::Electra.make_genesis_spec(E::default_spec());
let make_attestation = |attesting_indices, data, signature| {
IndexedAttestation::<E>::Electra(IndexedAttestationElectra {
attesting_indices: VariableList::new(attesting_indices).unwrap(),
data,
signature,
})
};
indexed_attestation_on_disk_roundtrip_test(&spec, make_attestation)
}
}

View File

@@ -13,6 +13,7 @@ pub enum Error {
DatabaseIOError(io::Error), DatabaseIOError(io::Error),
DatabasePermissionsError(filesystem::Error), DatabasePermissionsError(filesystem::Error),
SszDecodeError(ssz::DecodeError), SszDecodeError(ssz::DecodeError),
SszTypesError(ssz_types::Error),
BincodeError(bincode::Error), BincodeError(bincode::Error),
ArithError(safe_arith::ArithError), ArithError(safe_arith::ArithError),
ChunkIndexOutOfBounds(usize), ChunkIndexOutOfBounds(usize),
@@ -100,6 +101,12 @@ impl From<ssz::DecodeError> for Error {
} }
} }
impl From<ssz_types::Error> for Error {
fn from(e: ssz_types::Error) -> Self {
Error::SszTypesError(e)
}
}
impl From<bincode::Error> for Error { impl From<bincode::Error> for Error {
fn from(e: bincode::Error) -> Self { fn from(e: bincode::Error) -> Self {
Error::BincodeError(e) Error::BincodeError(e)

View File

@@ -13,7 +13,8 @@ use slog::{debug, error, info, Logger};
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use types::{ use types::{
AttesterSlashing, Epoch, EthSpec, IndexedAttestation, ProposerSlashing, SignedBeaconBlockHeader, AttesterSlashing, ChainSpec, Epoch, EthSpec, IndexedAttestation, ProposerSlashing,
SignedBeaconBlockHeader,
}; };
#[derive(Debug)] #[derive(Debug)]
@@ -28,10 +29,10 @@ pub struct Slasher<E: EthSpec> {
} }
impl<E: EthSpec> Slasher<E> { impl<E: EthSpec> Slasher<E> {
pub fn open(config: Config, log: Logger) -> Result<Self, Error> { pub fn open(config: Config, spec: Arc<ChainSpec>, log: Logger) -> Result<Self, Error> {
config.validate()?; config.validate()?;
let config = Arc::new(config); let config = Arc::new(config);
let db = SlasherDB::open(config.clone(), log.clone())?; let db = SlasherDB::open(config.clone(), spec, log.clone())?;
let attester_slashings = Mutex::new(HashSet::new()); let attester_slashings = Mutex::new(HashSet::new());
let proposer_slashings = Mutex::new(HashSet::new()); let proposer_slashings = Mutex::new(HashSet::new());
let attestation_queue = AttestationQueue::default(); let attestation_queue = AttestationQueue::default();

View File

@@ -1,9 +1,10 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc;
use types::{ use types::{
indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra}, indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra},
AggregateSignature, AttestationData, AttesterSlashing, AttesterSlashingBase, AggregateSignature, AttestationData, AttesterSlashing, AttesterSlashingBase,
AttesterSlashingElectra, BeaconBlockHeader, Checkpoint, Epoch, Hash256, IndexedAttestation, AttesterSlashingElectra, BeaconBlockHeader, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256,
MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot, IndexedAttestation, MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot,
}; };
pub type E = MainnetEthSpec; pub type E = MainnetEthSpec;
@@ -145,3 +146,7 @@ pub fn block(slot: u64, proposer_index: u64, block_root: u64) -> SignedBeaconBlo
signature: Signature::empty(), signature: Signature::empty(),
} }
} }
pub fn chain_spec() -> Arc<ChainSpec> {
Arc::new(E::default_spec())
}

View File

@@ -6,7 +6,8 @@ use rayon::prelude::*;
use slasher::{ use slasher::{
config::DEFAULT_CHUNK_SIZE, config::DEFAULT_CHUNK_SIZE,
test_utils::{ test_utils::{
att_slashing, indexed_att, indexed_att_electra, slashed_validators_from_slashings, E, att_slashing, chain_spec, indexed_att, indexed_att_electra,
slashed_validators_from_slashings, E,
}, },
Config, Slasher, Config, Slasher,
}; };
@@ -270,7 +271,8 @@ fn slasher_test(
) { ) {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let config = Config::new(tempdir.path().into()); let config = Config::new(tempdir.path().into());
let slasher = Slasher::open(config, test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::open(config, spec, test_logger()).unwrap();
let current_epoch = Epoch::new(current_epoch); let current_epoch = Epoch::new(current_epoch);
for (i, attestation) in attestations.iter().enumerate() { for (i, attestation) in attestations.iter().enumerate() {
@@ -299,7 +301,8 @@ fn parallel_slasher_test(
) { ) {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let config = Config::new(tempdir.path().into()); let config = Config::new(tempdir.path().into());
let slasher = Slasher::open(config, test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::open(config, spec, test_logger()).unwrap();
let current_epoch = Epoch::new(current_epoch); let current_epoch = Epoch::new(current_epoch);
attestations attestations

View File

@@ -2,7 +2,7 @@
use logging::test_logger; use logging::test_logger;
use slasher::{ use slasher::{
test_utils::{block as test_block, E}, test_utils::{block as test_block, chain_spec, E},
Config, Slasher, Config, Slasher,
}; };
use tempfile::tempdir; use tempfile::tempdir;
@@ -12,7 +12,8 @@ use types::{Epoch, EthSpec};
fn empty_pruning() { fn empty_pruning() {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let config = Config::new(tempdir.path().into()); let config = Config::new(tempdir.path().into());
let slasher = Slasher::<E>::open(config, test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::<E>::open(config, spec, test_logger()).unwrap();
slasher.prune_database(Epoch::new(0)).unwrap(); slasher.prune_database(Epoch::new(0)).unwrap();
} }
@@ -24,8 +25,9 @@ fn block_pruning() {
let mut config = Config::new(tempdir.path().into()); let mut config = Config::new(tempdir.path().into());
config.chunk_size = 2; config.chunk_size = 2;
config.history_length = 2; config.history_length = 2;
let spec = chain_spec();
let slasher = Slasher::<E>::open(config.clone(), test_logger()).unwrap(); let slasher = Slasher::<E>::open(config.clone(), spec, test_logger()).unwrap();
let current_epoch = Epoch::from(2 * config.history_length); let current_epoch = Epoch::from(2 * config.history_length);
// Pruning the empty database should be safe. // Pruning the empty database should be safe.

View File

@@ -4,7 +4,7 @@ use logging::test_logger;
use rand::prelude::*; use rand::prelude::*;
use slasher::{ use slasher::{
test_utils::{ test_utils::{
block, indexed_att, slashed_validators_from_attestations, block, chain_spec, indexed_att, slashed_validators_from_attestations,
slashed_validators_from_slashings, E, slashed_validators_from_slashings, E,
}, },
Config, Slasher, Config, Slasher,
@@ -49,7 +49,9 @@ fn random_test(seed: u64, test_config: TestConfig) {
config.chunk_size = 1 << chunk_size_exponent; config.chunk_size = 1 << chunk_size_exponent;
config.history_length = 1 << rng.gen_range(chunk_size_exponent..chunk_size_exponent + 3); config.history_length = 1 << rng.gen_range(chunk_size_exponent..chunk_size_exponent + 3);
let slasher = Slasher::<E>::open(config.clone(), test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::<E>::open(config.clone(), spec, test_logger()).unwrap();
let validators = (0..num_validators as u64).collect::<Vec<u64>>(); let validators = (0..num_validators as u64).collect::<Vec<u64>>();

View File

@@ -1,7 +1,10 @@
#![cfg(any(feature = "mdbx", feature = "lmdb"))] #![cfg(any(feature = "mdbx", feature = "lmdb"))]
use logging::test_logger; use logging::test_logger;
use slasher::{test_utils::indexed_att, Config, Slasher}; use slasher::{
test_utils::{chain_spec, indexed_att},
Config, Slasher,
};
use tempfile::tempdir; use tempfile::tempdir;
use types::Epoch; use types::Epoch;
@@ -9,11 +12,12 @@ use types::Epoch;
fn attestation_pruning_empty_wrap_around() { fn attestation_pruning_empty_wrap_around() {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let mut config = Config::new(tempdir.path().into()); let mut config = Config::new(tempdir.path().into());
let spec = chain_spec();
config.validator_chunk_size = 1; config.validator_chunk_size = 1;
config.chunk_size = 16; config.chunk_size = 16;
config.history_length = 16; config.history_length = 16;
let slasher = Slasher::open(config.clone(), test_logger()).unwrap(); let slasher = Slasher::open(config.clone(), spec, test_logger()).unwrap();
let v = vec![0]; let v = vec![0];
let history_length = config.history_length as u64; let history_length = config.history_length as u64;