Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match block and blobs after validating execution #6469

Open
wants to merge 2 commits into
base: unstable
Choose a base branch
from

Conversation

dapplion
Copy link
Collaborator

@dapplion dapplion commented Oct 6, 2024

Issue Addressed

After PeerDAS we request block and data to different peers. Consider the following scenario when doing a by range sync:

  • Block at slot N contains no data
  • Peer serving the block at slot N forges the block adding KZG commitments
  • The peers serving data columns do not return any data columns for slot N

Current unstable will attempt to match block and columns and consider the entire RpcBlock invalid. A similar scenario can occur where the block peer is honest and the column peer is dishonest.

We need to make the code more aware of who is at fault, and specifically do not attempt to match columns before validating the block.

Part of

Note: this PR touches code on the mainnet path, not only PeerDAS

Proposed Changes

Relax RpcBlock construction with new_unchecked and perform matching in verify_kzg_for_rpc_block after the block is execution validated.

This PR does not include a fix for peer scoring. This will be tackled latter as part of #6258

@dapplion dapplion added the ready-for-review The code is ready for review label Oct 6, 2024
@pawanjay176 pawanjay176 added under-review A reviewer has only partially completed a review. and removed ready-for-review The code is ready for review labels Oct 8, 2024
@@ -85,60 +86,41 @@ impl<E: EthSpec> RangeBlockComponentsRequest<E> {
}

pub fn into_responses(self, spec: &ChainSpec) -> Result<Vec<RpcBlock<E>>, String> {
if let Some(expects_custody_columns) = self.expects_custody_columns.clone() {
self.into_responses_with_custody_columns(expects_custody_columns, spec)
if let Some(expected_column_indices) = self.expects_custody_columns.as_ref() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field should probably be renamed to expected_column_indices to better reflect its type. Doesn't need to be part of this PR though

beacon_node/network/src/sync/block_sidecar_coupling.rs Outdated Show resolved Hide resolved
beacon_node/network/src/sync/block_sidecar_coupling.rs Outdated Show resolved Hide resolved
));
}
// Safe to convert to `CustodyDataColumn`: we have asserted that the index of
// this column is in the set of `expects_custody_columns` and with the expected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where have we done this assertion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In sync we only request custody columns

}
let blobs = VariableList::from(blobs_buffer.into_iter().flatten().collect::<Vec<_>>());
responses.push(RpcBlock::new(None, block, Some(blobs)).map_err(|e| format!("{e:?}"))?)
for blob in blobs {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat simplificaiton in the logic 👍

@@ -311,8 +311,13 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
let (block_root, block, blobs, data_columns) = block.deconstruct();
if self.blobs_required_for_block(&block) {
return if let Some(blob_list) = blobs.as_ref() {
if block.num_expected_blobs() != blob_list.len() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check if the block and blob commitments match here?
Same check in RpcBlock::new

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also do this in RpcBlock::deconstruct

Copy link
Collaborator Author

@dapplion dapplion Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we check the inclusion proof, we check that the commitments match the block. I've added a check in the da_checker. It's unnecessary in most paths but would be best to skip it only as an optimization by adding a property to RpcBlock::valid_inclusion_proof, and only if true, skip the check

for block in blocks {
let block_root = get_block_root(&block);
let blobs = blobs_by_block.remove(&block_root).map(VariableList::from);
rpc_blocks.push(RpcBlock::new_unchecked(block_root, block, blobs, None));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using the unchecked variant here might result in an inconsistent state.

Consider the example you used, the peer/peers have sent us a valid block for a block_root x which contains 1 kzg_commitment. The peer sends us 2 blobs for this block root which do not match with the kzg_commitment in the block. The malicious blobs pass the kzg verification in isolation, they just doesn't match with the block commitment.

Now the unchecked RpcBlock is sent for processing. The block goes through the validation pipeline
I couldn't find anywhere we check that the block and blob commitments match up. Correct me if i'm wrong here though.

We should potentially do this check when we doRpcBlock::deconstruct

Copy link
Collaborator Author

@dapplion dapplion Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find anywhere we check that the block and blob commitments match up

In lookup sync when doing a blobs request we check the inclusion proof, here

if !blob.verify_blob_sidecar_inclusion_proof() {

However we don't for range sync. I've added the inclusion proof check in the da_checker. We can remove it after #6497

@@ -193,34 +170,60 @@ impl<E: EthSpec> RpcBlock<E> {
Self::new(Some(block_root), block, blobs)
}

/// Create an RpcBlock from an untrusted block, without checking that the blobs or columns are
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the docs for RpcBlock and RpcBlockInner need to be updated to reflect the fact that they could be constructed without checking for consistency

@pawanjay176 pawanjay176 added waiting-on-author The reviewer has suggested changes and awaits thier implementation. and removed under-review A reviewer has only partially completed a review. labels Oct 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting-on-author The reviewer has suggested changes and awaits thier implementation.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants