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",
"serde",
"slog",
"ssz_types",
"strum",
"tempfile",
"tree_hash",

View File

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

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)]
mod tests {
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::historical_batch::HistoricalBatch;
pub use crate::indexed_attestation::{
IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra,
IndexedAttestationOnDisk, IndexedAttestationRef, IndexedAttestationRefOnDisk,
IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef,
};
pub use crate::light_client_bootstrap::{
LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella,

View File

@@ -29,6 +29,7 @@ tree_hash = { workspace = true }
tree_hash_derive = { workspace = true }
types = { workspace = true }
strum = { workspace = true }
ssz_types = { workspace = true }
# 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 }

View File

@@ -13,13 +13,15 @@ use parking_lot::Mutex;
use serde::de::DeserializeOwned;
use slog::{info, Logger};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::borrow::{Borrow, Cow};
use std::marker::PhantomData;
use std::sync::Arc;
use tree_hash::TreeHash;
use types::{
Epoch, EthSpec, Hash256, IndexedAttestation, IndexedAttestationOnDisk,
IndexedAttestationRefOnDisk, ProposerSlashing, SignedBeaconBlockHeader, Slot,
AggregateSignature, AttestationData, ChainSpec, Epoch, EthSpec, ForkName, Hash256,
IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, ProposerSlashing,
SignedBeaconBlockHeader, Slot, VariableList,
};
/// 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.
attestation_root_cache: Mutex<LruCache<IndexedAttestationId, Hash256>>,
pub(crate) config: Arc<Config>,
pub(crate) spec: Arc<ChainSpec>,
_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]>`.
fn bincode_deserialize<T: DeserializeOwned>(bytes: Cow<[u8]>) -> Result<T, Error> {
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> {
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);
std::fs::create_dir_all(&config.database_path)?;
@@ -269,6 +309,7 @@ impl<E: EthSpec> SlasherDB<E> {
databases,
attestation_root_cache,
config,
spec,
_phantom: PhantomData,
};
@@ -458,9 +499,8 @@ impl<E: EthSpec> SlasherDB<E> {
};
let attestation_key = IndexedAttestationId::new(indexed_att_id);
let indexed_attestation_on_disk: IndexedAttestationRefOnDisk<E> =
indexed_attestation.into();
let data = indexed_attestation_on_disk.as_ssz_bytes();
// IndexedAttestationOnDisk and IndexedAttestation have compatible encodings.
let data = indexed_attestation.as_ssz_bytes();
cursor.put(attestation_key.as_ref(), &data)?;
drop(cursor);
@@ -484,8 +524,8 @@ impl<E: EthSpec> SlasherDB<E> {
.ok_or(Error::MissingIndexedAttestation {
id: indexed_attestation_id.as_u64(),
})?;
let indexed_attestation: IndexedAttestationOnDisk<E> = ssz_decode(bytes)?;
Ok(indexed_attestation.into())
let indexed_attestation_on_disk: IndexedAttestationOnDisk = ssz_decode(bytes)?;
indexed_attestation_on_disk.into_indexed_attestation(&self.spec)
}
fn get_attestation_data_root(
@@ -775,3 +815,84 @@ impl<E: EthSpec> SlasherDB<E> {
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),
DatabasePermissionsError(filesystem::Error),
SszDecodeError(ssz::DecodeError),
SszTypesError(ssz_types::Error),
BincodeError(bincode::Error),
ArithError(safe_arith::ArithError),
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 {
fn from(e: bincode::Error) -> Self {
Error::BincodeError(e)

View File

@@ -13,7 +13,8 @@ use slog::{debug, error, info, Logger};
use std::collections::HashSet;
use std::sync::Arc;
use types::{
AttesterSlashing, Epoch, EthSpec, IndexedAttestation, ProposerSlashing, SignedBeaconBlockHeader,
AttesterSlashing, ChainSpec, Epoch, EthSpec, IndexedAttestation, ProposerSlashing,
SignedBeaconBlockHeader,
};
#[derive(Debug)]
@@ -28,10 +29,10 @@ pub struct Slasher<E: EthSpec> {
}
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()?;
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 proposer_slashings = Mutex::new(HashSet::new());
let attestation_queue = AttestationQueue::default();

View File

@@ -1,9 +1,10 @@
use std::collections::HashSet;
use std::sync::Arc;
use types::{
indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra},
AggregateSignature, AttestationData, AttesterSlashing, AttesterSlashingBase,
AttesterSlashingElectra, BeaconBlockHeader, Checkpoint, Epoch, Hash256, IndexedAttestation,
MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot,
AttesterSlashingElectra, BeaconBlockHeader, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256,
IndexedAttestation, MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot,
};
pub type E = MainnetEthSpec;
@@ -145,3 +146,7 @@ pub fn block(slot: u64, proposer_index: u64, block_root: u64) -> SignedBeaconBlo
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::{
config::DEFAULT_CHUNK_SIZE,
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,
};
@@ -270,7 +271,8 @@ fn slasher_test(
) {
let tempdir = tempdir().unwrap();
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);
for (i, attestation) in attestations.iter().enumerate() {
@@ -299,7 +301,8 @@ fn parallel_slasher_test(
) {
let tempdir = tempdir().unwrap();
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);
attestations

View File

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

View File

@@ -4,7 +4,7 @@ use logging::test_logger;
use rand::prelude::*;
use slasher::{
test_utils::{
block, indexed_att, slashed_validators_from_attestations,
block, chain_spec, indexed_att, slashed_validators_from_attestations,
slashed_validators_from_slashings, E,
},
Config, Slasher,
@@ -49,7 +49,9 @@ fn random_test(seed: u64, test_config: TestConfig) {
config.chunk_size = 1 << chunk_size_exponent;
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>>();

View File

@@ -1,7 +1,10 @@
#![cfg(any(feature = "mdbx", feature = "lmdb"))]
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 types::Epoch;
@@ -9,11 +12,12 @@ use types::Epoch;
fn attestation_pruning_empty_wrap_around() {
let tempdir = tempdir().unwrap();
let mut config = Config::new(tempdir.path().into());
let spec = chain_spec();
config.validator_chunk_size = 1;
config.chunk_size = 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 history_length = config.history_length as u64;