From 17849b58ec89fd3370926ed76bbc5c8a5d223abe Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Thu, 3 Oct 2024 13:06:05 +1000 Subject: [PATCH] Fix invalid data column sidecars getting accepted (#6454) * Fix invalid data column sidecars getting accepted. * Update code to match spec function. --- .../src/data_column_verification.rs | 93 ++++++++++++++++++- .../gossip_methods.rs | 3 + 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 1647f190cf..44873fab4a 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -127,6 +127,25 @@ pub enum GossipDataColumnError { slot: Slot, index: ColumnIndex, }, + /// Data column index must be between 0 and `NUMBER_OF_COLUMNS` (exclusive). + /// + /// ## Peer scoring + /// + /// The column sidecar is invalid and the peer is faulty + InvalidColumnIndex(u64), + /// Data column not expected for a block with empty kzg commitments. + /// + /// ## Peer scoring + /// + /// The column sidecar is invalid and the peer is faulty + UnexpectedDataColumn, + /// The data column length must be equal to the number of commitments/proofs, otherwise the + /// sidecar is invalid. + /// + /// ## Peer scoring + /// + /// The column sidecar is invalid and the peer is faulty + InconsistentCommitmentsOrProofLength, } impl From for GossipDataColumnError { @@ -367,7 +386,7 @@ pub fn validate_data_column_sidecar_for_gossip( chain: &BeaconChain, ) -> Result, GossipDataColumnError> { let column_slot = data_column.slot(); - + verify_data_column_sidecar(&data_column, &chain.spec)?; verify_index_matches_subnet(&data_column, subnet, &chain.spec)?; verify_sidecar_not_from_future_slot(chain, column_slot)?; verify_slot_greater_than_latest_finalized_slot(chain, column_slot)?; @@ -396,6 +415,26 @@ pub fn validate_data_column_sidecar_for_gossip( }) } +/// Verify if the data column sidecar is valid. +fn verify_data_column_sidecar( + data_column: &DataColumnSidecar, + spec: &ChainSpec, +) -> Result<(), GossipDataColumnError> { + if data_column.index >= spec.number_of_columns as u64 { + return Err(GossipDataColumnError::InvalidColumnIndex(data_column.index)); + } + if data_column.kzg_commitments.is_empty() { + return Err(GossipDataColumnError::UnexpectedDataColumn); + } + if data_column.column.len() != data_column.kzg_commitments.len() + || data_column.column.len() != data_column.kzg_proofs.len() + { + return Err(GossipDataColumnError::InconsistentCommitmentsOrProofLength); + } + + Ok(()) +} + // Verify that this is the first column sidecar received for the tuple: // (block_header.slot, block_header.proposer_index, column_sidecar.index) fn verify_is_first_sidecar( @@ -613,3 +652,55 @@ fn verify_sidecar_not_from_future_slot( } Ok(()) } + +#[cfg(test)] +mod test { + use crate::data_column_verification::{ + validate_data_column_sidecar_for_gossip, GossipDataColumnError, + }; + use crate::test_utils::BeaconChainHarness; + use types::{DataColumnSidecar, EthSpec, ForkName, MainnetEthSpec}; + + type E = MainnetEthSpec; + + #[tokio::test] + async fn empty_data_column_sidecars_fails_validation() { + let spec = ForkName::latest().make_genesis_spec(E::default_spec()); + let harness = BeaconChainHarness::builder(E::default()) + .spec(spec.into()) + .deterministic_keypairs(64) + .fresh_ephemeral_store() + .mock_execution_layer() + .build(); + harness.advance_slot(); + + let slot = harness.get_current_slot(); + let state = harness.get_current_state(); + let ((block, _blobs_opt), _state) = harness + .make_block_with_modifier(state, slot, |block| { + *block.body_mut().blob_kzg_commitments_mut().unwrap() = vec![].into(); + }) + .await; + + let index = 0; + let column_sidecar = DataColumnSidecar:: { + index, + column: vec![].into(), + kzg_commitments: vec![].into(), + kzg_proofs: vec![].into(), + signed_block_header: block.signed_block_header(), + kzg_commitments_inclusion_proof: block + .message() + .body() + .kzg_commitments_merkle_proof() + .unwrap(), + }; + + let result = + validate_data_column_sidecar_for_gossip(column_sidecar.into(), index, &harness.chain); + assert!(matches!( + result.err(), + Some(GossipDataColumnError::UnexpectedDataColumn) + )); + } +} diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 005536bcf2..3153ce533c 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -711,6 +711,9 @@ impl NetworkBeaconProcessor { | GossipDataColumnError::InvalidSubnetId { .. } | GossipDataColumnError::InvalidInclusionProof { .. } | GossipDataColumnError::InvalidKzgProof { .. } + | GossipDataColumnError::UnexpectedDataColumn + | GossipDataColumnError::InvalidColumnIndex(_) + | GossipDataColumnError::InconsistentCommitmentsOrProofLength | GossipDataColumnError::NotFinalizedDescendant { .. } => { debug!( self.log,