Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions beacon_node/beacon_chain/src/block_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ use slot_clock::SlotClock;
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use state_processing::per_block_processing::errors::IntoWithIndex;
use state_processing::per_block_processing::{
process_operations::verify_operation_list_lengths, verify_execution_request_list_lengths,
};
use state_processing::{
AllCaches, BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError,
VerifyBlockRoot,
Expand Down Expand Up @@ -896,6 +899,23 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
}
}

if let Ok(parent_execution_requests) = block.message().body().parent_execution_requests() {
verify_operation_list_lengths(block.message().body())
.map_err(BlockError::PerBlockProcessingError)?;
verify_execution_request_list_lengths(parent_execution_requests)
.map_err(BlockError::PerBlockProcessingError)?;
let deposits_len = block.message().body().deposits().len();
if deposits_len > 0 {
return Err(BlockError::PerBlockProcessingError(
BlockProcessingError::OperationListTooLong {
kind: "deposits",
length: deposits_len,
max: 0,
},
));
}
}

let block_root = get_block_header_root(block_header);

// Do not gossip a block from a finalized slot.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,46 @@ pub(crate) fn verify_envelope_consistency<E: EthSpec>(
});
}

let requests = &envelope.execution_requests;
if requests.withdrawals.len() > E::max_withdrawal_requests_per_payload() {
return Err(EnvelopeError::OperationListTooLong {
kind: "withdrawal_requests",
length: requests.withdrawals.len(),
max: E::max_withdrawal_requests_per_payload(),
});
}
if requests.consolidations.len() > E::max_consolidation_requests_per_payload() {
return Err(EnvelopeError::OperationListTooLong {
kind: "consolidation_requests",
length: requests.consolidations.len(),
max: E::max_consolidation_requests_per_payload(),
});
}
if requests.builder_deposits.len() > E::max_builder_deposit_requests_per_payload() {
return Err(EnvelopeError::OperationListTooLong {
kind: "builder_deposit_requests",
length: requests.builder_deposits.len(),
max: E::max_builder_deposit_requests_per_payload(),
});
}
if requests.builder_exits.len() > E::max_builder_exit_requests_per_payload() {
return Err(EnvelopeError::OperationListTooLong {
kind: "builder_exit_requests",
length: requests.builder_exits.len(),
max: E::max_builder_exit_requests_per_payload(),
});
}
if envelope.payload.withdrawals.len() > E::max_withdrawals_per_payload() {
return Err(EnvelopeError::OperationListTooLong {
kind: "withdrawals",
length: envelope.payload.withdrawals.len(),
max: E::max_withdrawals_per_payload(),
});
}

// The SSZ root of the envelope's execution requests must match the committed bid, per
// `verify_execution_payload_envelope` in the spec.
let execution_requests_root = envelope.execution_requests.tree_hash_root();
let execution_requests_root = requests.tree_hash_root();
if execution_requests_root != execution_bid.execution_requests_root {
return Err(EnvelopeError::ExecutionRequestsRootMismatch {
committed_bid: execution_bid.execution_requests_root,
Expand Down Expand Up @@ -322,13 +359,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
mod tests {
use std::marker::PhantomData;

use bls::Signature;
use bls::{PublicKeyBytes, Signature, SignatureBytes};
use ssz_types::ProgressiveVariableList;
use types::{
BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, Eth1Data, ExecutionBlockHash,
Address, BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BuilderDepositRequest,
BuilderExitRequest, ConsolidationRequest, Eth1Data, EthSpec, ExecutionBlockHash,
ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas,
ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock,
SignedExecutionPayloadBid, Slot, SyncAggregate,
SignedExecutionPayloadBid, Slot, SyncAggregate, Withdrawal, WithdrawalRequest,
};

use super::verify_envelope_consistency;
Expand Down Expand Up @@ -489,4 +527,128 @@ mod tests {
Err(EnvelopeError::BlockHashMismatch { .. })
));
}

#[test]
fn test_payload_withdrawals_over_limit() {
Comment on lines +531 to +532

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are five types of execution requests and we have two tests checking the execution requests - max_withdrawals_per_payload and max_builder_exit_requests_per_payload.

I understand that the tests are repetitive and so that's probably why you didn't include a test for the other 3 types of requests. So I will leave it to you whether to add them or not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

let slot = Slot::new(10);
let builder_index = 1;
let block_hash = ExecutionBlockHash::repeat_byte(0xaa);

let mut envelope = make_envelope(slot, builder_index, block_hash);
let block = make_block(slot);
let bid = make_bid(builder_index, block_hash);

let withdrawal = Withdrawal {
index: 0,
validator_index: 0,
address: Address::ZERO,
amount: 0,
};
let max = E::max_withdrawals_per_payload();
envelope.payload.withdrawals = ProgressiveVariableList::new(vec![withdrawal.clone(); max]);
assert!(verify_envelope_consistency::<E>(&envelope, &block, &bid, Slot::new(0)).is_ok());

envelope.payload.withdrawals = ProgressiveVariableList::new(vec![withdrawal; max + 1]);
let result = verify_envelope_consistency::<E>(&envelope, &block, &bid, Slot::new(0));
assert!(matches!(
result,
Err(EnvelopeError::OperationListTooLong {
kind: "withdrawals",
..
})
));
}

fn assert_requests_list_bound(
kind: &'static str,
max: usize,
set_len: impl Fn(&mut ExecutionRequestsGloas<E>, usize),
) {
let slot = Slot::new(10);
let builder_index = 1;
let block_hash = ExecutionBlockHash::repeat_byte(0xaa);

let mut envelope = make_envelope(slot, builder_index, block_hash);
let block = make_block(slot);

set_len(&mut envelope.execution_requests, max);
let bid = ExecutionPayloadBid {
builder_index,
block_hash,
execution_requests_root: envelope.execution_requests.tree_hash_root(),
..ExecutionPayloadBid::default()
};
assert!(
verify_envelope_consistency::<E>(&envelope, &block, &bid, Slot::new(0)).is_ok(),
"{kind} at max should be accepted"
);

set_len(&mut envelope.execution_requests, max + 1);
let result = verify_envelope_consistency::<E>(&envelope, &block, &bid, Slot::new(0));
assert!(
matches!(
result,
Err(EnvelopeError::OperationListTooLong { kind: k, .. }) if k == kind
),
"{kind} over max should be rejected"
);
}

#[test]
fn test_execution_requests_over_limit() {
assert_requests_list_bound(
"withdrawal_requests",
E::max_withdrawal_requests_per_payload(),
|requests, len| {
let withdrawal_request = WithdrawalRequest {
source_address: Address::ZERO,
validator_pubkey: PublicKeyBytes::empty(),
amount: 0,
};
requests.withdrawals = ProgressiveVariableList::new(vec![withdrawal_request; len]);
},
);

assert_requests_list_bound(
"consolidation_requests",
E::max_consolidation_requests_per_payload(),
|requests, len| {
let consolidation_request = ConsolidationRequest {
source_address: Address::ZERO,
source_pubkey: PublicKeyBytes::empty(),
target_pubkey: PublicKeyBytes::empty(),
};
requests.consolidations =
ProgressiveVariableList::new(vec![consolidation_request; len]);
},
);

assert_requests_list_bound(
"builder_deposit_requests",
E::max_builder_deposit_requests_per_payload(),
|requests, len| {
let builder_deposit_request = BuilderDepositRequest {
pubkey: PublicKeyBytes::empty(),
withdrawal_credentials: Hash256::ZERO,
amount: 0,
signature: SignatureBytes::empty(),
};
requests.builder_deposits =
ProgressiveVariableList::new(vec![builder_deposit_request; len]);
},
);

assert_requests_list_bound(
"builder_exit_requests",
E::max_builder_exit_requests_per_payload(),
|requests, len| {
let builder_exit_request = BuilderExitRequest {
source_address: Address::ZERO,
pubkey: PublicKeyBytes::empty(),
};
requests.builder_exits =
ProgressiveVariableList::new(vec![builder_exit_request; len]);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ pub enum EnvelopeError {
payload_slot: Slot,
latest_finalized_slot: Slot,
},
/// An envelope list exceeds its spec limit
OperationListTooLong {
kind: &'static str,
length: usize,
max: usize,
},
/// Some Beacon Chain Error
BeaconChainError(Box<BeaconChainError>),
/// Some Beacon State error
Expand Down Expand Up @@ -275,6 +281,7 @@ impl EnvelopeError {
| EnvelopeError::ExecutionRequestsRootMismatch { .. }
| EnvelopeError::UnknownValidator { .. }
| EnvelopeError::IncorrectBlockProposer { .. }
| EnvelopeError::OperationListTooLong { .. }
| EnvelopeError::EnvelopeProcessingError(_) => true,
EnvelopeError::ExecutionPayloadError(e) => e.penalize_peer(),
EnvelopeError::BlockRootUnknown { .. }
Expand Down
42 changes: 42 additions & 0 deletions beacon_node/beacon_chain/tests/block_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,48 @@ async fn block_gossip_verification() {
"should not import a block with higher blob_kzg_commitment length than the max_blobs at epoch"
);
}

/*
* This test ensures that:
*
* We do not accept gloas blocks with a non-empty `deposits` list. Gloas removes legacy
* eth1 deposits, so the effective limit for this progressive list is zero.
*/
let (mut block, signature) = chain_segment[block_index]
.beacon_block
.as_ref()
.clone()
.deconstruct();

if let BeaconBlock::Gloas(gloas_block) = &mut block {
let deposit = Deposit {
proof: ssz_types::FixedVector::default(),
data: DepositData {
pubkey: bls::PublicKeyBytes::empty(),
withdrawal_credentials: Hash256::ZERO,
amount: 0,
signature: bls::SignatureBytes::empty(),
},
};
gloas_block.body.deposits = ssz_types::ProgressiveVariableList::new(vec![deposit]);
assert!(
matches!(
unwrap_err(
harness
.chain
.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(
block, signature
)))
.await
),
BlockError::PerBlockProcessingError(BlockProcessingError::OperationListTooLong {
kind: "deposits",
..
})
),
"should not accept a gloas block with a non-empty deposits list"
);
}
}

async fn verify_and_process_gossip_data_sidecars(
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/lighthouse_network/src/rpc/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ pub fn rpc_data_column_limits<E: EthSpec>(
if fork_name.gloas_enabled() {
RpcLimits::new(
DataColumnSidecarGloas::<E>::min_size(),
DataColumnSidecarFulu::<E>::max_size(max_blobs),
E::max_data_column_sidecar_size(),
)
} else {
RpcLimits::new(
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/lighthouse_network/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,7 +1432,7 @@ impl<E: EthSpec> Network<E> {
.ok()?;

if let Some(message) = message {
match decode_partial::<E>(&topic, &group_id, &message) {
match decode_partial::<E>(&topic, &group_id, &message, &self.fork_context) {
Err(error) => {
debug!(
topic = ?topic_hash,
Expand Down
Loading
Loading