mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-31 13:17:09 +00:00
Fix PeerDAS sync scoring (#7352)
* Remove request tracking inside syncing chains * Prioritize by range peers in network context * Prioritize custody peers for columns by range * Explicit error handling of the no peers error case * Remove good_peers_on_sampling_subnets * Count AwaitingDownload towards the buffer limit * Retry syncing chains in AwaitingDownload state * Use same peer priorization for lookups * Review PR * Address TODOs * Revert changes to peer erroring in range sync * Revert metrics changes * Update comment * Pass peers_to_deprioritize to select_columns_by_range_peers_to_request * more idiomatic * Idiomatic while * Add note about infinite loop * Use while let * Fix wrong custody column count for lookup blocks * Remove impl * Remove stale comment * Fix build errors. * Or default * Review PR * BatchPeerGroup * Match block and blob signatures * Explicit match statement to BlockError in range sync * Remove todo in BatchPeerGroup * Remove participating peers from backfill sync * Remove MissingAllCustodyColumns error * Merge fixes * Clean up PR * Consistent naming of batch_peers * Address multiple review comments * Better errors for das * Penalize column peers once * Restore fn * Fix error enum * Removed MismatchedPublicKeyLen * Revert testing changes * Change BlockAndCustodyColumns enum variant * Revert type change in import_historical_block_batch * Drop pubkey cache * Don't collect Vec * Classify errors * Remove ReconstructColumnsError * More detailed UnrequestedSlot error * Lint test * Fix slot conversion * Reduce penalty for missing blobs * Revert changes in peer selection * Lint tests * Rename block matching functions * Reorder block matching in historical blocks * Fix order of block matching * Add store tests * Filter blockchain in assert_correct_historical_block_chain * Also filter before KZG checks * Lint tests * Fix lint * Fix fulu err assertion * Check point is not at infinity * Fix ws sync test * Revert dropping filter fn --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> Co-authored-by: Jimmy Chen <jimmy@sigmaprime.io> Co-authored-by: Pawan Dhananjay <pawandhananjay@gmail.com>
This commit is contained in:
@@ -94,6 +94,7 @@ use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp};
|
||||
use strum::AsRefStr;
|
||||
use task_executor::JoinHandle;
|
||||
use tracing::{debug, error};
|
||||
use types::ColumnIndex;
|
||||
use types::{
|
||||
data_column_sidecar::DataColumnSidecarError, BeaconBlockRef, BeaconState, BeaconStateError,
|
||||
BlobsList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, ExecutionBlockHash, FullPayload,
|
||||
@@ -220,6 +221,10 @@ pub enum BlockError {
|
||||
///
|
||||
/// The block is invalid and the peer is faulty.
|
||||
InvalidSignature(InvalidSignature),
|
||||
/// One or more signatures in a BlobSidecar of an RpcBlock are invalid
|
||||
InvalidBlobsSignature(Vec<u64>),
|
||||
/// One or more signatures in a DataColumnSidecar of an RpcBlock are invalid
|
||||
InvalidDataColumnsSignature(Vec<ColumnIndex>),
|
||||
/// The provided block is not from a later slot than its parent.
|
||||
///
|
||||
/// ## Peer scoring
|
||||
@@ -634,6 +639,34 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
&chain.spec,
|
||||
)?;
|
||||
|
||||
// Verify signatures before matching blocks and data. Otherwise we may penalize blob or column
|
||||
// peers for valid signatures if the block peer sends us an invalid signature.
|
||||
let pubkey_cache = get_validator_pubkey_cache(chain)?;
|
||||
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
|
||||
for (block_root, block) in &chain_segment {
|
||||
let mut consensus_context =
|
||||
ConsensusContext::new(block.slot()).set_current_block_root(*block_root);
|
||||
signature_verifier.include_all_signatures(block.as_block(), &mut consensus_context)?;
|
||||
}
|
||||
if signature_verifier.verify().is_err() {
|
||||
return Err(BlockError::InvalidSignature(InvalidSignature::Unknown));
|
||||
}
|
||||
drop(pubkey_cache);
|
||||
|
||||
// Verify that blobs or data columns signatures match
|
||||
//
|
||||
// TODO(das): Should check correct proposer cheap for added protection if blocks and columns
|
||||
// don't match. This code attributes fault to the blobs / data columns if they don't match the
|
||||
// block
|
||||
for (_, block) in &chain_segment {
|
||||
if let Err(indices) = block.match_block_and_blobs() {
|
||||
return Err(BlockError::InvalidBlobsSignature(indices));
|
||||
}
|
||||
if let Err(indices) = block.match_block_and_data_columns() {
|
||||
return Err(BlockError::InvalidDataColumnsSignature(indices));
|
||||
}
|
||||
}
|
||||
|
||||
// unzip chain segment and verify kzg in bulk
|
||||
let (roots, blocks): (Vec<_>, Vec<_>) = chain_segment.into_iter().unzip();
|
||||
let maybe_available_blocks = chain
|
||||
@@ -655,20 +688,6 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// verify signatures
|
||||
let pubkey_cache = get_validator_pubkey_cache(chain)?;
|
||||
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
|
||||
for svb in &mut signature_verified_blocks {
|
||||
signature_verifier
|
||||
.include_all_signatures(svb.block.as_block(), &mut svb.consensus_context)?;
|
||||
}
|
||||
|
||||
if signature_verifier.verify().is_err() {
|
||||
return Err(BlockError::InvalidSignature(InvalidSignature::Unknown));
|
||||
}
|
||||
|
||||
drop(pubkey_cache);
|
||||
|
||||
if let Some(signature_verified_block) = signature_verified_blocks.first_mut() {
|
||||
signature_verified_block.parent = Some(parent);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ use std::fmt::{Debug, Formatter};
|
||||
use std::sync::Arc;
|
||||
use types::blob_sidecar::BlobIdentifier;
|
||||
use types::{
|
||||
BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, Epoch, EthSpec,
|
||||
Hash256, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
|
||||
BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, ColumnIndex,
|
||||
DataColumnSidecar, Epoch, EthSpec, Hash256, RuntimeVariableList, SignedBeaconBlock,
|
||||
SignedBeaconBlockHeader, Slot,
|
||||
};
|
||||
|
||||
/// A block that has been received over RPC. It has 2 internal variants:
|
||||
@@ -53,7 +54,7 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(block) => block,
|
||||
RpcBlockInner::BlockAndBlobs(block, _) => block,
|
||||
RpcBlockInner::BlockAndCustodyColumns(block, _) => block,
|
||||
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(block) => block.clone(),
|
||||
RpcBlockInner::BlockAndBlobs(block, _) => block.clone(),
|
||||
RpcBlockInner::BlockAndCustodyColumns(block, _) => block.clone(),
|
||||
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(_) => None,
|
||||
RpcBlockInner::BlockAndBlobs(_, blobs) => Some(blobs),
|
||||
RpcBlockInner::BlockAndCustodyColumns(_, _) => None,
|
||||
RpcBlockInner::BlockAndCustodyColumns { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +78,36 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(_) => None,
|
||||
RpcBlockInner::BlockAndBlobs(_, _) => None,
|
||||
RpcBlockInner::BlockAndCustodyColumns(_, data_columns) => Some(data_columns),
|
||||
RpcBlockInner::BlockAndCustodyColumns { data_columns, .. } => Some(data_columns),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Err if any of its inner BlobSidecar's signed_block_header does not match the inner
|
||||
/// block
|
||||
pub fn match_block_and_blobs(&self) -> Result<(), Vec<u64>> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(_) => Ok(()),
|
||||
RpcBlockInner::BlockAndBlobs(block, blobs) => match_block_and_blobs(block, blobs),
|
||||
RpcBlockInner::BlockAndCustodyColumns { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Err if any of its inner DataColumnSidecar's signed_block_header does not match the
|
||||
/// inner block
|
||||
pub fn match_block_and_data_columns(&self) -> Result<(), Vec<ColumnIndex>> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(_) => Ok(()),
|
||||
RpcBlockInner::BlockAndBlobs(..) => Ok(()),
|
||||
RpcBlockInner::BlockAndCustodyColumns {
|
||||
block,
|
||||
data_columns,
|
||||
..
|
||||
} => match_block_and_data_columns(
|
||||
block,
|
||||
data_columns
|
||||
.iter()
|
||||
.map(|data_column| data_column.as_data_column()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,14 +118,20 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
#[derive(Debug, Clone, Derivative)]
|
||||
#[derivative(Hash(bound = "E: EthSpec"))]
|
||||
enum RpcBlockInner<E: EthSpec> {
|
||||
/// Single block lookup response. This should potentially hit the data availability cache.
|
||||
/// **Range sync**: Variant for all pre-Deneb blocks
|
||||
/// **Lookup sync**: Variant used for all blocks of all forks, regardless if the have data or
|
||||
/// not
|
||||
Block(Arc<SignedBeaconBlock<E>>),
|
||||
/// This variant is used with parent lookups and by-range responses. It should have all blobs
|
||||
/// ordered, all block roots matching, and the correct number of blobs for this block.
|
||||
/// **Range sync**: Variant for all post-Deneb blocks regardless if they have data or not
|
||||
/// **Lookup sync**: Not used
|
||||
BlockAndBlobs(Arc<SignedBeaconBlock<E>>, BlobSidecarList<E>),
|
||||
/// This variant is used with parent lookups and by-range responses. It should have all
|
||||
/// requested data columns, all block roots matching for this block.
|
||||
BlockAndCustodyColumns(Arc<SignedBeaconBlock<E>>, CustodyDataColumnList<E>),
|
||||
/// **Range sync**: Variant for all post-Fulu blocks regardless if they have data or not
|
||||
/// **Lookup sync**: Not used
|
||||
BlockAndCustodyColumns {
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
data_columns: CustodyDataColumnList<E>,
|
||||
expected_custody_indices: Vec<ColumnIndex>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<E: EthSpec> RpcBlock<E> {
|
||||
@@ -161,23 +197,24 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
block_root: Option<Hash256>,
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
custody_columns: Vec<CustodyDataColumn<E>>,
|
||||
custody_columns_count: usize,
|
||||
expected_custody_indices: Vec<ColumnIndex>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, AvailabilityCheckError> {
|
||||
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
|
||||
|
||||
if block.num_expected_blobs() > 0 && custody_columns.is_empty() {
|
||||
// The number of required custody columns is out of scope here.
|
||||
return Err(AvailabilityCheckError::MissingCustodyColumns);
|
||||
}
|
||||
// Treat empty data column lists as if they are missing.
|
||||
let inner = if !custody_columns.is_empty() {
|
||||
RpcBlockInner::BlockAndCustodyColumns(
|
||||
block,
|
||||
RuntimeVariableList::new(custody_columns, spec.number_of_columns as usize)?,
|
||||
let custody_columns_count = expected_custody_indices.len();
|
||||
let inner = RpcBlockInner::BlockAndCustodyColumns {
|
||||
block,
|
||||
data_columns: RuntimeVariableList::new(
|
||||
custody_columns,
|
||||
spec.number_of_columns as usize,
|
||||
)
|
||||
} else {
|
||||
RpcBlockInner::Block(block)
|
||||
.map_err(|e| {
|
||||
AvailabilityCheckError::Unexpected(format!(
|
||||
"custody_columns len exceeds number_of_columns: {e:?}"
|
||||
))
|
||||
})?,
|
||||
expected_custody_indices,
|
||||
};
|
||||
Ok(Self {
|
||||
block_root,
|
||||
@@ -193,27 +230,34 @@ impl<E: EthSpec> RpcBlock<E> {
|
||||
Hash256,
|
||||
Arc<SignedBeaconBlock<E>>,
|
||||
Option<BlobSidecarList<E>>,
|
||||
Option<CustodyDataColumnList<E>>,
|
||||
Option<(CustodyDataColumnList<E>, Vec<ColumnIndex>)>,
|
||||
) {
|
||||
let block_root = self.block_root();
|
||||
match self.block {
|
||||
RpcBlockInner::Block(block) => (block_root, block, None, None),
|
||||
RpcBlockInner::BlockAndBlobs(block, blobs) => (block_root, block, Some(blobs), None),
|
||||
RpcBlockInner::BlockAndCustodyColumns(block, data_columns) => {
|
||||
(block_root, block, None, Some(data_columns))
|
||||
}
|
||||
RpcBlockInner::BlockAndCustodyColumns {
|
||||
block,
|
||||
data_columns,
|
||||
expected_custody_indices,
|
||||
} => (
|
||||
block_root,
|
||||
block,
|
||||
None,
|
||||
Some((data_columns, expected_custody_indices)),
|
||||
),
|
||||
}
|
||||
}
|
||||
pub fn n_blobs(&self) -> usize {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(_) | RpcBlockInner::BlockAndCustodyColumns(_, _) => 0,
|
||||
RpcBlockInner::Block(_) | RpcBlockInner::BlockAndCustodyColumns { .. } => 0,
|
||||
RpcBlockInner::BlockAndBlobs(_, blobs) => blobs.len(),
|
||||
}
|
||||
}
|
||||
pub fn n_data_columns(&self) -> usize {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(_) | RpcBlockInner::BlockAndBlobs(_, _) => 0,
|
||||
RpcBlockInner::BlockAndCustodyColumns(_, data_columns) => data_columns.len(),
|
||||
RpcBlockInner::BlockAndCustodyColumns { data_columns, .. } => data_columns.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,17 +572,50 @@ impl<E: EthSpec> AsBlock<E> for RpcBlock<E> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(block) => block,
|
||||
RpcBlockInner::BlockAndBlobs(block, _) => block,
|
||||
RpcBlockInner::BlockAndCustodyColumns(block, _) => block,
|
||||
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block,
|
||||
}
|
||||
}
|
||||
fn block_cloned(&self) -> Arc<SignedBeaconBlock<E>> {
|
||||
match &self.block {
|
||||
RpcBlockInner::Block(block) => block.clone(),
|
||||
RpcBlockInner::BlockAndBlobs(block, _) => block.clone(),
|
||||
RpcBlockInner::BlockAndCustodyColumns(block, _) => block.clone(),
|
||||
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block.clone(),
|
||||
}
|
||||
}
|
||||
fn canonical_root(&self) -> Hash256 {
|
||||
self.as_block().canonical_root()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Err if any of `blobs` BlobSidecar's signed_block_header does not match
|
||||
/// block
|
||||
pub fn match_block_and_blobs<E: EthSpec>(
|
||||
block: &SignedBeaconBlock<E>,
|
||||
blobs: &BlobSidecarList<E>,
|
||||
) -> Result<(), Vec<u64>> {
|
||||
let indices = blobs
|
||||
.iter()
|
||||
.filter(|blob| &blob.signed_block_header.signature != block.signature())
|
||||
.map(|blob| blob.index)
|
||||
.collect::<Vec<_>>();
|
||||
if indices.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(indices)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_block_and_data_columns<'a, E: EthSpec>(
|
||||
block: &SignedBeaconBlock<E>,
|
||||
data_columns: impl Iterator<Item = &'a Arc<DataColumnSidecar<E>>>,
|
||||
) -> Result<(), Vec<ColumnIndex>> {
|
||||
let indices = data_columns
|
||||
.filter(|column| &column.signed_block_header.signature != block.signature())
|
||||
.map(|column| column.index)
|
||||
.collect::<Vec<_>>();
|
||||
if indices.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(indices)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::blob_verification::{verify_kzg_for_blob_list, GossipVerifiedBlob, KzgVerifiedBlobList};
|
||||
use crate::block_verification_types::{
|
||||
AvailabilityPendingExecutedBlock, AvailableExecutedBlock, RpcBlock,
|
||||
match_block_and_blobs, match_block_and_data_columns, AvailabilityPendingExecutedBlock,
|
||||
AvailableExecutedBlock, RpcBlock,
|
||||
};
|
||||
use crate::data_availability_checker::overflow_lru_cache::{
|
||||
DataAvailabilityCheckerInner, ReconstructColumnsDecision,
|
||||
@@ -8,6 +9,7 @@ use crate::data_availability_checker::overflow_lru_cache::{
|
||||
use crate::{metrics, BeaconChain, BeaconChainTypes, BeaconStore};
|
||||
use kzg::Kzg;
|
||||
use slot_clock::SlotClock;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::num::NonZeroUsize;
|
||||
@@ -17,8 +19,8 @@ use task_executor::TaskExecutor;
|
||||
use tracing::{debug, error, info_span, Instrument};
|
||||
use types::blob_sidecar::{BlobIdentifier, BlobSidecar, FixedBlobSidecarList};
|
||||
use types::{
|
||||
BlobSidecarList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, Hash256,
|
||||
RuntimeVariableList, SignedBeaconBlock,
|
||||
BlobSidecarList, ChainSpec, ColumnIndex, DataColumnSidecarList, Epoch, EthSpec, Hash256,
|
||||
SignedBeaconBlock,
|
||||
};
|
||||
|
||||
mod error;
|
||||
@@ -345,7 +347,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
};
|
||||
}
|
||||
if self.data_columns_required_for_block(&block) {
|
||||
return if let Some(data_column_list) = data_columns.as_ref() {
|
||||
return if let Some((data_column_list, _)) = data_columns.as_ref() {
|
||||
verify_kzg_for_data_column_list_with_scoring(
|
||||
data_column_list
|
||||
.iter()
|
||||
@@ -410,14 +412,15 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
|
||||
let all_data_columns = blocks
|
||||
.iter()
|
||||
// TODO(das): we may want to remove this line. If columns are present they should be
|
||||
// verified. The outcome of `data_columns_required_for_block` is time dependant. So we
|
||||
// may end up importing data columns that are not verified.
|
||||
.filter(|block| self.data_columns_required_for_block(block.as_block()))
|
||||
// this clone is cheap as it's cloning an Arc
|
||||
.filter_map(|block| block.custody_columns().cloned())
|
||||
.flatten()
|
||||
.map(CustodyDataColumn::into_inner)
|
||||
.collect::<Vec<_>>();
|
||||
let all_data_columns =
|
||||
RuntimeVariableList::from_vec(all_data_columns, self.spec.number_of_columns as usize);
|
||||
|
||||
// verify kzg for all data columns at once
|
||||
if !all_data_columns.is_empty() {
|
||||
@@ -426,6 +429,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
.map_err(AvailabilityCheckError::InvalidColumn)?;
|
||||
}
|
||||
|
||||
// TODO(das): we could do the matching first before spending CPU cycles on KZG verification
|
||||
for block in blocks {
|
||||
let custody_columns_count = block.custody_columns_count();
|
||||
let (block_root, block, blobs, data_columns) = block.deconstruct();
|
||||
@@ -447,7 +451,21 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
}
|
||||
}
|
||||
} else if self.data_columns_required_for_block(&block) {
|
||||
if let Some(data_columns) = data_columns {
|
||||
if let Some((data_columns, expected_custody_indices)) = data_columns {
|
||||
let received_indices =
|
||||
HashSet::<ColumnIndex>::from_iter(data_columns.iter().map(|d| d.index()));
|
||||
|
||||
let missing_custody_columns = expected_custody_indices
|
||||
.into_iter()
|
||||
.filter(|index| !received_indices.contains(index))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !missing_custody_columns.is_empty() {
|
||||
return Err(AvailabilityCheckError::MissingCustodyColumns(
|
||||
missing_custody_columns,
|
||||
));
|
||||
}
|
||||
|
||||
MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
@@ -458,11 +476,12 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
spec: self.spec.clone(),
|
||||
})
|
||||
} else {
|
||||
MaybeAvailableBlock::AvailabilityPending {
|
||||
block_root,
|
||||
block,
|
||||
custody_columns_count,
|
||||
}
|
||||
// This is unreachable. If a block returns true for
|
||||
// `data_columns_required_for_block` it must be a Fulu block. All Fulu RpcBlocks
|
||||
// are constructed with the `DataColumns` variant, so `data_columns` must be Some
|
||||
return Err(AvailabilityCheckError::Unexpected(
|
||||
"Data columns should be Some for a Fulu block".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
MaybeAvailableBlock::Available(AvailableBlock {
|
||||
@@ -571,7 +590,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
self.availability_cache
|
||||
.handle_reconstruction_failure(block_root);
|
||||
metrics::inc_counter(&KZG_DATA_COLUMN_RECONSTRUCTION_FAILURES);
|
||||
AvailabilityCheckError::ReconstructColumnsError(e)
|
||||
AvailabilityCheckError::Unexpected(format!("Error reconstructing columns: {e:?}"))
|
||||
})?;
|
||||
|
||||
// Check indices from cache again to make sure we don't publish components we've already received.
|
||||
@@ -713,7 +732,7 @@ async fn availability_cache_maintenance_service<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AvailableBlockData<E: EthSpec> {
|
||||
/// Block is pre-Deneb or has zero blobs
|
||||
NoData,
|
||||
@@ -724,7 +743,7 @@ pub enum AvailableBlockData<E: EthSpec> {
|
||||
}
|
||||
|
||||
/// A fully available block that is ready to be imported into fork choice.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AvailableBlock<E: EthSpec> {
|
||||
block_root: Hash256,
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
@@ -784,21 +803,26 @@ impl<E: EthSpec> AvailableBlock<E> {
|
||||
(block_root, block, blob_data)
|
||||
}
|
||||
|
||||
/// Only used for testing
|
||||
pub fn __clone_without_recv(&self) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
block_root: self.block_root,
|
||||
block: self.block.clone(),
|
||||
blob_data: match &self.blob_data {
|
||||
AvailableBlockData::NoData => AvailableBlockData::NoData,
|
||||
AvailableBlockData::Blobs(blobs) => AvailableBlockData::Blobs(blobs.clone()),
|
||||
AvailableBlockData::DataColumns(data_columns) => {
|
||||
AvailableBlockData::DataColumns(data_columns.clone())
|
||||
}
|
||||
},
|
||||
blobs_available_timestamp: self.blobs_available_timestamp,
|
||||
spec: self.spec.clone(),
|
||||
})
|
||||
/// Returns Err if any of its inner BlobSidecar's signed_block_header does not match the inner
|
||||
/// block
|
||||
pub fn match_block_and_blobs(&self) -> Result<(), Vec<u64>> {
|
||||
match &self.blob_data {
|
||||
AvailableBlockData::NoData => Ok(()),
|
||||
AvailableBlockData::Blobs(blobs) => match_block_and_blobs(&self.block, blobs),
|
||||
AvailableBlockData::DataColumns(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Err if any of its inner DataColumnSidecar's signed_block_header does not match the
|
||||
/// inner block
|
||||
pub fn match_block_and_data_columns(&self) -> Result<(), Vec<ColumnIndex>> {
|
||||
match &self.blob_data {
|
||||
AvailableBlockData::NoData => Ok(()),
|
||||
AvailableBlockData::Blobs(_) => Ok(()),
|
||||
AvailableBlockData::DataColumns(data_columns) => {
|
||||
match_block_and_data_columns(&self.block, data_columns.iter())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
use kzg::{Error as KzgError, KzgCommitment};
|
||||
use types::{BeaconStateError, ColumnIndex, Hash256};
|
||||
use types::{BeaconStateError, ColumnIndex};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidBlobs(KzgError),
|
||||
InvalidColumn(Vec<(ColumnIndex, KzgError)>),
|
||||
ReconstructColumnsError(KzgError),
|
||||
KzgCommitmentMismatch {
|
||||
blob_commitment: KzgCommitment,
|
||||
block_commitment: KzgCommitment,
|
||||
},
|
||||
Unexpected(String),
|
||||
SszTypes(ssz_types::Error),
|
||||
MissingBlobs,
|
||||
MissingCustodyColumns,
|
||||
MissingCustodyColumns(Vec<ColumnIndex>),
|
||||
BlobIndexInvalid(u64),
|
||||
DataColumnIndexInvalid(u64),
|
||||
StoreError(store::Error),
|
||||
DecodeError(ssz::DecodeError),
|
||||
ParentStateMissing(Hash256),
|
||||
BlockReplayError(state_processing::BlockReplayError),
|
||||
RebuildingStateCaches(BeaconStateError),
|
||||
SlotClockError,
|
||||
@@ -35,19 +31,15 @@ pub enum ErrorCategory {
|
||||
impl Error {
|
||||
pub fn category(&self) -> ErrorCategory {
|
||||
match self {
|
||||
Error::SszTypes(_)
|
||||
| Error::MissingBlobs
|
||||
| Error::MissingCustodyColumns
|
||||
| Error::StoreError(_)
|
||||
| Error::DecodeError(_)
|
||||
Error::StoreError(_)
|
||||
| Error::Unexpected(_)
|
||||
| Error::ParentStateMissing(_)
|
||||
| Error::BlockReplayError(_)
|
||||
| Error::RebuildingStateCaches(_)
|
||||
| Error::SlotClockError => ErrorCategory::Internal,
|
||||
Error::InvalidBlobs { .. }
|
||||
Error::MissingBlobs
|
||||
| Error::MissingCustodyColumns(_)
|
||||
| Error::InvalidBlobs { .. }
|
||||
| Error::InvalidColumn { .. }
|
||||
| Error::ReconstructColumnsError { .. }
|
||||
| Error::BlobIndexInvalid(_)
|
||||
| Error::DataColumnIndexInvalid(_)
|
||||
| Error::KzgCommitmentMismatch { .. } => ErrorCategory::Malicious,
|
||||
@@ -55,24 +47,12 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ssz_types::Error> for Error {
|
||||
fn from(value: ssz_types::Error) -> Self {
|
||||
Self::SszTypes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<store::Error> for Error {
|
||||
fn from(value: store::Error) -> Self {
|
||||
Self::StoreError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ssz::DecodeError> for Error {
|
||||
fn from(value: ssz::DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<state_processing::BlockReplayError> for Error {
|
||||
fn from(value: state_processing::BlockReplayError) -> Self {
|
||||
Self::BlockReplayError(value)
|
||||
|
||||
@@ -157,9 +157,9 @@ impl<T: BeaconChainTypes> StateLRUCache<T> {
|
||||
parent_block_state_root,
|
||||
)
|
||||
.map_err(AvailabilityCheckError::StoreError)?
|
||||
.ok_or(AvailabilityCheckError::ParentStateMissing(
|
||||
parent_block_state_root,
|
||||
))?;
|
||||
.ok_or(AvailabilityCheckError::Unexpected(format!(
|
||||
"Parent state missing {parent_block_state_root:?}"
|
||||
)))?;
|
||||
|
||||
let state_roots = vec![
|
||||
Ok((parent_state_root, diet_executed_block.parent_block.slot())),
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::data_availability_checker::{AvailableBlock, AvailableBlockData};
|
||||
use crate::block_verification_types::{MaybeAvailableBlock, RpcBlock};
|
||||
use crate::data_availability_checker::{
|
||||
AvailabilityCheckError, AvailableBlock, AvailableBlockData,
|
||||
};
|
||||
use crate::{metrics, BeaconChain, BeaconChainTypes};
|
||||
use itertools::Itertools;
|
||||
use state_processing::{
|
||||
@@ -12,7 +15,7 @@ use store::metadata::DataColumnInfo;
|
||||
use store::{AnchorInfo, BlobInfo, DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp};
|
||||
use strum::IntoStaticStr;
|
||||
use tracing::debug;
|
||||
use types::{FixedBytesExtended, Hash256, Slot};
|
||||
use types::{ColumnIndex, FixedBytesExtended, Hash256, Slot};
|
||||
|
||||
/// Use a longer timeout on the pubkey cache.
|
||||
///
|
||||
@@ -23,19 +26,27 @@ const PUBKEY_CACHE_LOCK_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
pub enum HistoricalBlockError {
|
||||
/// Block root mismatch, caller should retry with different blocks.
|
||||
MismatchedBlockRoot {
|
||||
block_slot: Slot,
|
||||
block_root: Hash256,
|
||||
expected_block_root: Hash256,
|
||||
oldest_block_parent: Hash256,
|
||||
},
|
||||
/// Bad signature, caller should retry with different blocks.
|
||||
SignatureSet(SignatureSetError),
|
||||
/// Bad signature, caller should retry with different blocks.
|
||||
InvalidSignature,
|
||||
InvalidSignature(String),
|
||||
/// One or more signatures in a BlobSidecar of an RpcBlock are invalid
|
||||
InvalidBlobsSignature(Vec<u64>),
|
||||
/// One or more signatures in a DataColumnSidecar of an RpcBlock are invalid
|
||||
InvalidDataColumnsSignature(Vec<ColumnIndex>),
|
||||
/// Unexpected error
|
||||
Unexpected(String),
|
||||
/// Transitory error, caller should retry with the same blocks.
|
||||
ValidatorPubkeyCacheTimeout,
|
||||
/// Logic error: should never occur.
|
||||
IndexOutOfBounds,
|
||||
/// Internal store error
|
||||
StoreError(StoreError),
|
||||
/// Faulty and internal AvailabilityCheckError
|
||||
AvailabilityCheckError(AvailabilityCheckError),
|
||||
}
|
||||
|
||||
impl From<StoreError> for HistoricalBlockError {
|
||||
@@ -44,7 +55,100 @@ impl From<StoreError> for HistoricalBlockError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignatureSetError> for HistoricalBlockError {
|
||||
fn from(err: SignatureSetError) -> Self {
|
||||
match err {
|
||||
// The encoding of the signature is invalid, peer fault
|
||||
e
|
||||
@ (SignatureSetError::SignatureInvalid(_) | SignatureSetError::BadBlsBytes { .. }) => {
|
||||
Self::InvalidSignature(format!("{e:?}"))
|
||||
}
|
||||
// All these variants are internal errors or unreachable for historical block paths,
|
||||
// which only check the proposer signature.
|
||||
// BadBlsBytes = Unreachable
|
||||
e @ (SignatureSetError::BeaconStateError(_)
|
||||
| SignatureSetError::ValidatorUnknown(_)
|
||||
| SignatureSetError::ValidatorPubkeyUnknown(_)
|
||||
| SignatureSetError::IncorrectBlockProposer { .. }
|
||||
| SignatureSetError::PublicKeyDecompressionFailed
|
||||
| SignatureSetError::InconsistentBlockFork(_)) => Self::Unexpected(format!("{e:?}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AvailabilityCheckError> for HistoricalBlockError {
|
||||
fn from(e: AvailabilityCheckError) -> Self {
|
||||
Self::AvailabilityCheckError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
pub fn assert_correct_historical_block_chain(
|
||||
&self,
|
||||
blocks: &[RpcBlock<T::EthSpec>],
|
||||
) -> Result<(), HistoricalBlockError> {
|
||||
let anchor_info = self.store.get_anchor_info();
|
||||
let mut expected_block_root = anchor_info.oldest_block_parent;
|
||||
|
||||
for block in blocks.iter().rev() {
|
||||
if block.as_block().slot() >= anchor_info.oldest_block_slot {
|
||||
continue;
|
||||
}
|
||||
|
||||
if block.block_root() != expected_block_root {
|
||||
return Err(HistoricalBlockError::MismatchedBlockRoot {
|
||||
block_slot: block.as_block().slot(),
|
||||
block_root: block.block_root(),
|
||||
expected_block_root,
|
||||
oldest_block_parent: anchor_info.oldest_block_parent,
|
||||
});
|
||||
}
|
||||
|
||||
expected_block_root = block.as_block().message().parent_root();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_and_import_historical_block_batch(
|
||||
&self,
|
||||
blocks: Vec<RpcBlock<T::EthSpec>>,
|
||||
) -> Result<usize, HistoricalBlockError> {
|
||||
let anchor_info = self.store.get_anchor_info();
|
||||
|
||||
// Take all blocks with slots less than the oldest block slot.
|
||||
let blocks_to_import = blocks
|
||||
.into_iter()
|
||||
.filter(|block| block.as_block().slot() < anchor_info.oldest_block_slot)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// First check that chain of blocks is correct
|
||||
self.assert_correct_historical_block_chain(&blocks_to_import)?;
|
||||
|
||||
// Check that all data columns are present <- faulty failure if missing because we have
|
||||
// checked the block root is correct first.
|
||||
let available_blocks_to_import = self
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_rpc_blocks(blocks_to_import)
|
||||
.and_then(|blocks| {
|
||||
blocks
|
||||
.into_iter()
|
||||
// RpcBlocks must always be Available, otherwise a data peer is faulty of
|
||||
// malicious. `verify_kzg_for_rpc_blocks` returns errors for those cases, but we
|
||||
// haven't updated its function signature. This code block can be deleted later
|
||||
// bigger refactor.
|
||||
.map(|maybe_available| match maybe_available {
|
||||
MaybeAvailableBlock::Available(block) => Ok(block),
|
||||
MaybeAvailableBlock::AvailabilityPending { .. } => Err(
|
||||
AvailabilityCheckError::Unexpected("block not available".to_string()),
|
||||
),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})?;
|
||||
|
||||
self.import_historical_block_batch(available_blocks_to_import)
|
||||
}
|
||||
|
||||
/// Store a batch of historical blocks in the database.
|
||||
///
|
||||
/// The `blocks` should be given in slot-ascending order. One of the blocks should have a block
|
||||
@@ -103,16 +207,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
let mut hot_batch = Vec::with_capacity(blocks_to_import.len());
|
||||
let mut signed_blocks = Vec::with_capacity(blocks_to_import.len());
|
||||
|
||||
for available_block in blocks_to_import.into_iter().rev() {
|
||||
for available_block in blocks_to_import.iter().cloned().rev() {
|
||||
let (block_root, block, block_data) = available_block.deconstruct();
|
||||
|
||||
if block_root != expected_block_root {
|
||||
return Err(HistoricalBlockError::MismatchedBlockRoot {
|
||||
block_root,
|
||||
expected_block_root,
|
||||
});
|
||||
}
|
||||
|
||||
if !self.store.get_config().prune_payloads {
|
||||
// If prune-payloads is set to false, store the block which includes the execution payload
|
||||
self.store
|
||||
@@ -213,18 +310,32 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(HistoricalBlockError::SignatureSet)
|
||||
.map(ParallelSignatureSets::from)?;
|
||||
drop(pubkey_cache);
|
||||
drop(setup_timer);
|
||||
|
||||
let verify_timer = metrics::start_timer(&metrics::BACKFILL_SIGNATURE_VERIFY_TIMES);
|
||||
if !signature_set.verify() {
|
||||
return Err(HistoricalBlockError::InvalidSignature);
|
||||
return Err(HistoricalBlockError::InvalidSignature("invalid".to_owned()));
|
||||
}
|
||||
drop(verify_timer);
|
||||
drop(sig_timer);
|
||||
|
||||
// Check that the proposer signature in the blobs and data columns is the same as the
|
||||
// correct signature in the block.
|
||||
blocks_to_import
|
||||
.iter()
|
||||
.map(|block| {
|
||||
if let Err(indices) = block.match_block_and_blobs() {
|
||||
return Err(HistoricalBlockError::InvalidBlobsSignature(indices));
|
||||
}
|
||||
if let Err(indices) = block.match_block_and_data_columns() {
|
||||
return Err(HistoricalBlockError::InvalidDataColumnsSignature(indices));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Write the I/O batches to disk, writing the blocks themselves first, as it's better
|
||||
// for the hot DB to contain extra blocks than for the cold DB to point to blocks that
|
||||
// do not exist.
|
||||
|
||||
@@ -2372,6 +2372,7 @@ where
|
||||
// Blobs are stored as data columns from Fulu (PeerDAS)
|
||||
if self.spec.is_peer_das_enabled_for_epoch(block.epoch()) {
|
||||
let columns = self.chain.get_data_columns(&block_root).unwrap().unwrap();
|
||||
let expected_custody_indices = columns.iter().map(|d| d.index).collect::<Vec<_>>();
|
||||
let custody_columns = columns
|
||||
.into_iter()
|
||||
.map(CustodyDataColumn::from_asserted_custody)
|
||||
@@ -2380,7 +2381,7 @@ where
|
||||
Some(block_root),
|
||||
block,
|
||||
custody_columns,
|
||||
self.get_sampling_column_count(),
|
||||
expected_custody_indices,
|
||||
&self.spec,
|
||||
)
|
||||
.unwrap()
|
||||
@@ -2409,15 +2410,17 @@ where
|
||||
.take(sampling_column_count)
|
||||
.map(CustodyDataColumn::from_asserted_custody)
|
||||
.collect::<Vec<_>>();
|
||||
let expected_custody_indices =
|
||||
columns.iter().map(|d| d.index()).collect::<Vec<_>>();
|
||||
RpcBlock::new_with_custody_columns(
|
||||
Some(block_root),
|
||||
block,
|
||||
columns,
|
||||
sampling_column_count,
|
||||
expected_custody_indices,
|
||||
&self.spec,
|
||||
)?
|
||||
} else {
|
||||
RpcBlock::new_without_blobs(Some(block_root), block, 0)
|
||||
RpcBlock::new_without_blobs(Some(block_root), block, sampling_column_count)
|
||||
}
|
||||
} else {
|
||||
let blobs = blob_items
|
||||
|
||||
Reference in New Issue
Block a user