Skip to content

Commit b014675

Browse files
dapplionjimmygchenpawanjay176
authored
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 <[email protected]> Co-authored-by: Jimmy Chen <[email protected]> Co-authored-by: Pawan Dhananjay <[email protected]>
1 parent f06d1d0 commit b014675

File tree

27 files changed

+1104
-655
lines changed

27 files changed

+1104
-655
lines changed

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp};
9494
use strum::AsRefStr;
9595
use task_executor::JoinHandle;
9696
use tracing::{debug, error};
97+
use types::ColumnIndex;
9798
use types::{
9899
data_column_sidecar::DataColumnSidecarError, BeaconBlockRef, BeaconState, BeaconStateError,
99100
BlobsList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, ExecutionBlockHash, FullPayload,
@@ -220,6 +221,10 @@ pub enum BlockError {
220221
///
221222
/// The block is invalid and the peer is faulty.
222223
InvalidSignature(InvalidSignature),
224+
/// One or more signatures in a BlobSidecar of an RpcBlock are invalid
225+
InvalidBlobsSignature(Vec<u64>),
226+
/// One or more signatures in a DataColumnSidecar of an RpcBlock are invalid
227+
InvalidDataColumnsSignature(Vec<ColumnIndex>),
223228
/// The provided block is not from a later slot than its parent.
224229
///
225230
/// ## Peer scoring
@@ -634,6 +639,34 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
634639
&chain.spec,
635640
)?;
636641

642+
// Verify signatures before matching blocks and data. Otherwise we may penalize blob or column
643+
// peers for valid signatures if the block peer sends us an invalid signature.
644+
let pubkey_cache = get_validator_pubkey_cache(chain)?;
645+
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
646+
for (block_root, block) in &chain_segment {
647+
let mut consensus_context =
648+
ConsensusContext::new(block.slot()).set_current_block_root(*block_root);
649+
signature_verifier.include_all_signatures(block.as_block(), &mut consensus_context)?;
650+
}
651+
if signature_verifier.verify().is_err() {
652+
return Err(BlockError::InvalidSignature(InvalidSignature::Unknown));
653+
}
654+
drop(pubkey_cache);
655+
656+
// Verify that blobs or data columns signatures match
657+
//
658+
// TODO(das): Should check correct proposer cheap for added protection if blocks and columns
659+
// don't match. This code attributes fault to the blobs / data columns if they don't match the
660+
// block
661+
for (_, block) in &chain_segment {
662+
if let Err(indices) = block.match_block_and_blobs() {
663+
return Err(BlockError::InvalidBlobsSignature(indices));
664+
}
665+
if let Err(indices) = block.match_block_and_data_columns() {
666+
return Err(BlockError::InvalidDataColumnsSignature(indices));
667+
}
668+
}
669+
637670
// unzip chain segment and verify kzg in bulk
638671
let (roots, blocks): (Vec<_>, Vec<_>) = chain_segment.into_iter().unzip();
639672
let maybe_available_blocks = chain
@@ -655,20 +688,6 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
655688
})
656689
.collect::<Vec<_>>();
657690

658-
// verify signatures
659-
let pubkey_cache = get_validator_pubkey_cache(chain)?;
660-
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
661-
for svb in &mut signature_verified_blocks {
662-
signature_verifier
663-
.include_all_signatures(svb.block.as_block(), &mut svb.consensus_context)?;
664-
}
665-
666-
if signature_verifier.verify().is_err() {
667-
return Err(BlockError::InvalidSignature(InvalidSignature::Unknown));
668-
}
669-
670-
drop(pubkey_cache);
671-
672691
if let Some(signature_verified_block) = signature_verified_blocks.first_mut() {
673692
signature_verified_block.parent = Some(parent);
674693
}

beacon_node/beacon_chain/src/block_verification_types.rs

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use std::fmt::{Debug, Formatter};
99
use std::sync::Arc;
1010
use types::blob_sidecar::BlobIdentifier;
1111
use types::{
12-
BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, Epoch, EthSpec,
13-
Hash256, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
12+
BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, ColumnIndex,
13+
DataColumnSidecar, Epoch, EthSpec, Hash256, RuntimeVariableList, SignedBeaconBlock,
14+
SignedBeaconBlockHeader, Slot,
1415
};
1516

1617
/// A block that has been received over RPC. It has 2 internal variants:
@@ -53,31 +54,60 @@ impl<E: EthSpec> RpcBlock<E> {
5354
match &self.block {
5455
RpcBlockInner::Block(block) => block,
5556
RpcBlockInner::BlockAndBlobs(block, _) => block,
56-
RpcBlockInner::BlockAndCustodyColumns(block, _) => block,
57+
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block,
5758
}
5859
}
5960

6061
pub fn block_cloned(&self) -> Arc<SignedBeaconBlock<E>> {
6162
match &self.block {
6263
RpcBlockInner::Block(block) => block.clone(),
6364
RpcBlockInner::BlockAndBlobs(block, _) => block.clone(),
64-
RpcBlockInner::BlockAndCustodyColumns(block, _) => block.clone(),
65+
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block.clone(),
6566
}
6667
}
6768

6869
pub fn blobs(&self) -> Option<&BlobSidecarList<E>> {
6970
match &self.block {
7071
RpcBlockInner::Block(_) => None,
7172
RpcBlockInner::BlockAndBlobs(_, blobs) => Some(blobs),
72-
RpcBlockInner::BlockAndCustodyColumns(_, _) => None,
73+
RpcBlockInner::BlockAndCustodyColumns { .. } => None,
7374
}
7475
}
7576

7677
pub fn custody_columns(&self) -> Option<&CustodyDataColumnList<E>> {
7778
match &self.block {
7879
RpcBlockInner::Block(_) => None,
7980
RpcBlockInner::BlockAndBlobs(_, _) => None,
80-
RpcBlockInner::BlockAndCustodyColumns(_, data_columns) => Some(data_columns),
81+
RpcBlockInner::BlockAndCustodyColumns { data_columns, .. } => Some(data_columns),
82+
}
83+
}
84+
85+
/// Returns Err if any of its inner BlobSidecar's signed_block_header does not match the inner
86+
/// block
87+
pub fn match_block_and_blobs(&self) -> Result<(), Vec<u64>> {
88+
match &self.block {
89+
RpcBlockInner::Block(_) => Ok(()),
90+
RpcBlockInner::BlockAndBlobs(block, blobs) => match_block_and_blobs(block, blobs),
91+
RpcBlockInner::BlockAndCustodyColumns { .. } => Ok(()),
92+
}
93+
}
94+
95+
/// Returns Err if any of its inner DataColumnSidecar's signed_block_header does not match the
96+
/// inner block
97+
pub fn match_block_and_data_columns(&self) -> Result<(), Vec<ColumnIndex>> {
98+
match &self.block {
99+
RpcBlockInner::Block(_) => Ok(()),
100+
RpcBlockInner::BlockAndBlobs(..) => Ok(()),
101+
RpcBlockInner::BlockAndCustodyColumns {
102+
block,
103+
data_columns,
104+
..
105+
} => match_block_and_data_columns(
106+
block,
107+
data_columns
108+
.iter()
109+
.map(|data_column| data_column.as_data_column()),
110+
),
81111
}
82112
}
83113
}
@@ -88,14 +118,20 @@ impl<E: EthSpec> RpcBlock<E> {
88118
#[derive(Debug, Clone, Derivative)]
89119
#[derivative(Hash(bound = "E: EthSpec"))]
90120
enum RpcBlockInner<E: EthSpec> {
91-
/// Single block lookup response. This should potentially hit the data availability cache.
121+
/// **Range sync**: Variant for all pre-Deneb blocks
122+
/// **Lookup sync**: Variant used for all blocks of all forks, regardless if the have data or
123+
/// not
92124
Block(Arc<SignedBeaconBlock<E>>),
93-
/// This variant is used with parent lookups and by-range responses. It should have all blobs
94-
/// ordered, all block roots matching, and the correct number of blobs for this block.
125+
/// **Range sync**: Variant for all post-Deneb blocks regardless if they have data or not
126+
/// **Lookup sync**: Not used
95127
BlockAndBlobs(Arc<SignedBeaconBlock<E>>, BlobSidecarList<E>),
96-
/// This variant is used with parent lookups and by-range responses. It should have all
97-
/// requested data columns, all block roots matching for this block.
98-
BlockAndCustodyColumns(Arc<SignedBeaconBlock<E>>, CustodyDataColumnList<E>),
128+
/// **Range sync**: Variant for all post-Fulu blocks regardless if they have data or not
129+
/// **Lookup sync**: Not used
130+
BlockAndCustodyColumns {
131+
block: Arc<SignedBeaconBlock<E>>,
132+
data_columns: CustodyDataColumnList<E>,
133+
expected_custody_indices: Vec<ColumnIndex>,
134+
},
99135
}
100136

101137
impl<E: EthSpec> RpcBlock<E> {
@@ -161,23 +197,24 @@ impl<E: EthSpec> RpcBlock<E> {
161197
block_root: Option<Hash256>,
162198
block: Arc<SignedBeaconBlock<E>>,
163199
custody_columns: Vec<CustodyDataColumn<E>>,
164-
custody_columns_count: usize,
200+
expected_custody_indices: Vec<ColumnIndex>,
165201
spec: &ChainSpec,
166202
) -> Result<Self, AvailabilityCheckError> {
167203
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
168204

169-
if block.num_expected_blobs() > 0 && custody_columns.is_empty() {
170-
// The number of required custody columns is out of scope here.
171-
return Err(AvailabilityCheckError::MissingCustodyColumns);
172-
}
173-
// Treat empty data column lists as if they are missing.
174-
let inner = if !custody_columns.is_empty() {
175-
RpcBlockInner::BlockAndCustodyColumns(
176-
block,
177-
RuntimeVariableList::new(custody_columns, spec.number_of_columns as usize)?,
205+
let custody_columns_count = expected_custody_indices.len();
206+
let inner = RpcBlockInner::BlockAndCustodyColumns {
207+
block,
208+
data_columns: RuntimeVariableList::new(
209+
custody_columns,
210+
spec.number_of_columns as usize,
178211
)
179-
} else {
180-
RpcBlockInner::Block(block)
212+
.map_err(|e| {
213+
AvailabilityCheckError::Unexpected(format!(
214+
"custody_columns len exceeds number_of_columns: {e:?}"
215+
))
216+
})?,
217+
expected_custody_indices,
181218
};
182219
Ok(Self {
183220
block_root,
@@ -193,27 +230,34 @@ impl<E: EthSpec> RpcBlock<E> {
193230
Hash256,
194231
Arc<SignedBeaconBlock<E>>,
195232
Option<BlobSidecarList<E>>,
196-
Option<CustodyDataColumnList<E>>,
233+
Option<(CustodyDataColumnList<E>, Vec<ColumnIndex>)>,
197234
) {
198235
let block_root = self.block_root();
199236
match self.block {
200237
RpcBlockInner::Block(block) => (block_root, block, None, None),
201238
RpcBlockInner::BlockAndBlobs(block, blobs) => (block_root, block, Some(blobs), None),
202-
RpcBlockInner::BlockAndCustodyColumns(block, data_columns) => {
203-
(block_root, block, None, Some(data_columns))
204-
}
239+
RpcBlockInner::BlockAndCustodyColumns {
240+
block,
241+
data_columns,
242+
expected_custody_indices,
243+
} => (
244+
block_root,
245+
block,
246+
None,
247+
Some((data_columns, expected_custody_indices)),
248+
),
205249
}
206250
}
207251
pub fn n_blobs(&self) -> usize {
208252
match &self.block {
209-
RpcBlockInner::Block(_) | RpcBlockInner::BlockAndCustodyColumns(_, _) => 0,
253+
RpcBlockInner::Block(_) | RpcBlockInner::BlockAndCustodyColumns { .. } => 0,
210254
RpcBlockInner::BlockAndBlobs(_, blobs) => blobs.len(),
211255
}
212256
}
213257
pub fn n_data_columns(&self) -> usize {
214258
match &self.block {
215259
RpcBlockInner::Block(_) | RpcBlockInner::BlockAndBlobs(_, _) => 0,
216-
RpcBlockInner::BlockAndCustodyColumns(_, data_columns) => data_columns.len(),
260+
RpcBlockInner::BlockAndCustodyColumns { data_columns, .. } => data_columns.len(),
217261
}
218262
}
219263
}
@@ -528,17 +572,50 @@ impl<E: EthSpec> AsBlock<E> for RpcBlock<E> {
528572
match &self.block {
529573
RpcBlockInner::Block(block) => block,
530574
RpcBlockInner::BlockAndBlobs(block, _) => block,
531-
RpcBlockInner::BlockAndCustodyColumns(block, _) => block,
575+
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block,
532576
}
533577
}
534578
fn block_cloned(&self) -> Arc<SignedBeaconBlock<E>> {
535579
match &self.block {
536580
RpcBlockInner::Block(block) => block.clone(),
537581
RpcBlockInner::BlockAndBlobs(block, _) => block.clone(),
538-
RpcBlockInner::BlockAndCustodyColumns(block, _) => block.clone(),
582+
RpcBlockInner::BlockAndCustodyColumns { block, .. } => block.clone(),
539583
}
540584
}
541585
fn canonical_root(&self) -> Hash256 {
542586
self.as_block().canonical_root()
543587
}
544588
}
589+
590+
/// Returns Err if any of `blobs` BlobSidecar's signed_block_header does not match
591+
/// block
592+
pub fn match_block_and_blobs<E: EthSpec>(
593+
block: &SignedBeaconBlock<E>,
594+
blobs: &BlobSidecarList<E>,
595+
) -> Result<(), Vec<u64>> {
596+
let indices = blobs
597+
.iter()
598+
.filter(|blob| &blob.signed_block_header.signature != block.signature())
599+
.map(|blob| blob.index)
600+
.collect::<Vec<_>>();
601+
if indices.is_empty() {
602+
Ok(())
603+
} else {
604+
Err(indices)
605+
}
606+
}
607+
608+
pub fn match_block_and_data_columns<'a, E: EthSpec>(
609+
block: &SignedBeaconBlock<E>,
610+
data_columns: impl Iterator<Item = &'a Arc<DataColumnSidecar<E>>>,
611+
) -> Result<(), Vec<ColumnIndex>> {
612+
let indices = data_columns
613+
.filter(|column| &column.signed_block_header.signature != block.signature())
614+
.map(|column| column.index)
615+
.collect::<Vec<_>>();
616+
if indices.is_empty() {
617+
Ok(())
618+
} else {
619+
Err(indices)
620+
}
621+
}

0 commit comments

Comments
 (0)