Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,80 @@ use state_processing::signature_sets::{
};
use tracing::debug;
use types::{
BeaconState, ChainSpec, EthSpec, ExecutionPayloadBid, SignedExecutionPayloadBid,
SignedProposerPreferences, Slot, consts::gloas::PAYLOAD_BUILDER_VERSION,
BeaconState, ChainSpec, EthSpec, ExecPayload, ExecutionBlockHash, ExecutionPayloadBid, Hash256,
SignedExecutionPayloadBid, SignedProposerPreferences, Slot,
consts::gloas::PAYLOAD_BUILDER_VERSION,
};

/// Find the beacon block carrying the known execution payload referenced by a bid.
pub(crate) fn find_execution_payload_block_root<T: BeaconChainTypes>(

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.

just noting that since is_bid_compatible_with_head runs first this while loop is bounded. if we didnt call that check before, we could end up looping all the way back to finalization which in a non finalized network is still probably not a big deal. but nice to keep this bounded i think

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.

I moved the ancestry lookup after the signature check and cache the gas limit by execution block hash, so we don't repeat the walk across slots.

fork_choice_read: &ForkChoiceReadGuard<'_, T>,
parent_block_root: Hash256,
parent_block_hash: ExecutionBlockHash,
) -> Result<Hash256, PayloadBidError> {
let mut block_root = Some(parent_block_root);
while let Some(root) = block_root {
let Some(block) = fork_choice_read.get_block(&root) else {
break;
};

if let Some(block_hash) = block.execution_payload_block_hash {
if fork_choice_read.is_payload_received(&root) && block_hash == parent_block_hash {

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.

right now we are (correctly assuming) that payload received means payload is valid. but with optimistic sync (when we eventually implement it) this might no longer be the case. might be worth adding a TODO here so that we dont forget

feel like theres probably other places in the codebase that could be affected once we impl optimistic sync

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.

Added a note here.

return Ok(root);
}
} else if !block.execution_status.is_invalid()
&& block.execution_status.block_hash() == Some(parent_block_hash)
{
return Ok(root);
}

block_root = block.parent_root;
}
Err(PayloadBidError::ParentExecutionPayloadUnknown { parent_block_hash })
}

/// Return the gas limit committed by the block carrying an execution payload.
pub(crate) fn get_parent_gas_limit<T: BeaconChainTypes>(
store: &BeaconStore<T>,
execution_payload_block_root: Hash256,
parent_block_hash: ExecutionBlockHash,
) -> Result<u64, PayloadBidError> {
let block = store
.get_blinded_block(&execution_payload_block_root)
.map_err(|e| {
PayloadBidError::InternalError(format!(
"failed to load execution payload block {execution_payload_block_root:?}: {e:?}"
))
})?
.ok_or_else(|| {
PayloadBidError::InternalError(format!(
"execution payload block {execution_payload_block_root:?} unavailable"
))
})?;

if block.fork_name_unchecked().gloas_enabled() {
let bid = &block
.message()
.body()
.signed_execution_payload_bid()?
.message;
if bid.block_hash != parent_block_hash {
return Err(PayloadBidError::InternalError(format!(
"execution payload hash mismatch for block {execution_payload_block_root:?}"
)));
}
Ok(bid.gas_limit)
} else {
let payload = block.message().execution_payload()?;
if payload.block_hash() != parent_block_hash {
return Err(PayloadBidError::InternalError(format!(
"execution payload hash mismatch for block {execution_payload_block_root:?}"
)));
}
Ok(payload.gas_limit())
}
}

/// Verify that an execution payload bid is consistent with the current chain state
/// and proposer preferences.
pub(crate) fn verify_bid_consistency<E: EthSpec>(
Expand Down Expand Up @@ -302,26 +372,26 @@ impl<E: EthSpec> GossipVerifiedPayloadBid<E> {
});
}

// TODO(gloas): [IGNORE] bid.parent_block_hash is the block hash of a known execution
// payload in fork choice.

// TODO(gloas): This uses head state's bid gas_limit as parent_gas_limit, which is only
// correct when the bid's parent is the head. If the parent is an ancestor further back
// this check may be inaccurate. Fixing this requires storing
// gas_limit in fork choice or looking it up from the store by parent_block_hash. Taking the above
// TODO into consideration maybe should persist parent block hash and gas limit in fork choice?
if let Ok(parent_bid) = head_state.latest_execution_payload_bid()
&& !is_gas_limit_target_compatible(
parent_bid.gas_limit,
signed_bid.message.gas_limit,
proposer_preferences.message.target_gas_limit,
)?
{
let execution_payload_block_root = find_execution_payload_block_root(
&fork_choice,
signed_bid.message.parent_block_root,
signed_bid.message.parent_block_hash,
)?;
drop(fork_choice);

let parent_gas_limit = get_parent_gas_limit::<T>(
ctx.store,
execution_payload_block_root,
signed_bid.message.parent_block_hash,
)?;
if !is_gas_limit_target_compatible(
parent_gas_limit,
signed_bid.message.gas_limit,
proposer_preferences.message.target_gas_limit,
)? {
return Err(PayloadBidError::InvalidGasLimit);
}

drop(fork_choice);

verify_bid_consistency(
&signed_bid.message,
current_slot,
Expand Down
6 changes: 5 additions & 1 deletion beacon_node/beacon_chain/src/payload_bid_verification/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! GossipVerifiedPayloadBid -------> Insert into GossipVerifiedPayloadBidCache
//! ```

use types::{BeaconStateError, Hash256, Slot};
use types::{BeaconStateError, ExecutionBlockHash, Hash256, Slot};

pub mod gossip_verified_bid;
pub mod payload_bid_cache;
Expand All @@ -22,6 +22,10 @@ mod tests;
pub enum PayloadBidError {
/// The bid's parent block root is unknown.
ParentBlockRootUnknown { parent_block_root: Hash256 },
/// The bid's parent execution payload is unknown.
ParentExecutionPayloadUnknown {
parent_block_hash: ExecutionBlockHash,
},
/// The bid does not build on the head block or on the head block's parent.
BidNotCompatibleWithHead { parent_block_root: Hash256 },
/// The signature is invalid.
Expand Down
64 changes: 63 additions & 1 deletion beacon_node/beacon_chain/src/payload_bid_verification/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ use crate::{
chain_config::FastConfirmationMode,
payload_bid_verification::{
PayloadBidError,
gossip_verified_bid::{GossipVerificationContext, GossipVerifiedPayloadBid},
gossip_verified_bid::{
GossipVerificationContext, GossipVerifiedPayloadBid, find_execution_payload_block_root,
get_parent_gas_limit, is_gas_limit_target_compatible,
},
payload_bid_cache::{BidParent, GossipVerifiedPayloadBidCache},
},
proposer_preferences_verification::{
Expand Down Expand Up @@ -148,6 +151,13 @@ impl TestContext {
ForkChoice::from_anchor(fc_store, block_root, &signed_block, &state, None, &spec)
.expect("should create fork choice");

store
.put_block(&block_root, signed_block.clone())
.expect("should store genesis block");
fork_choice
.on_valid_payload_envelope_received(block_root)
.expect("should mark genesis payload as received");

let (_, head_payload_status) = fork_choice
.get_head(Slot::new(0), &spec)
.expect("should run get_head");
Expand Down Expand Up @@ -509,6 +519,58 @@ fn gas_limit_mismatch() {
assert!(matches!(result, Err(PayloadBidError::InvalidGasLimit)));
}

#[test]
fn gas_limit_uses_known_execution_parent_after_unreceived_payload() {

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.

it would be nice if we could test GossipVerifiedBid::new directly here

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.

Done. valid_bid now calls GossipVerifiedPayloadBid::new directly with a non-zero EL genesis hash and no stored genesis envelope. The finalized-history regression also goes through the constructor.

if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
return;
}
let ctx = TestContext::new();
let rejected_payload_root = ctx.insert_non_canonical_block();
let fork_choice = ctx.canonical_head.fork_choice_read_lock();

let actual_parent_block_root = find_execution_payload_block_root(
&fork_choice,
rejected_payload_root,
ExecutionBlockHash::zero(),
)
.expect("should resolve the last received execution payload");
drop(fork_choice);
let actual_parent_gas_limit = get_parent_gas_limit::<T>(
&ctx.store,
actual_parent_block_root,
ExecutionBlockHash::zero(),
)
.expect("should load the last received execution payload gas limit");

let rejected_payload_gas_limit = 30_029_295;
let next_gas_limit = 30_029_295;
let target_gas_limit = 60_000_000;
assert_eq!(actual_parent_gas_limit, 30_000_000);
assert!(
is_gas_limit_target_compatible(actual_parent_gas_limit, next_gas_limit, target_gas_limit,)
.expect("gas limit calculation should succeed")
);
assert!(
!is_gas_limit_target_compatible(
rejected_payload_gas_limit,
next_gas_limit,
target_gas_limit,
)
.expect("gas limit calculation should succeed")
);

let fork_choice = ctx.canonical_head.fork_choice_read_lock();
let result = find_execution_payload_block_root(
&fork_choice,
rejected_payload_root,
ExecutionBlockHash::repeat_byte(0xab),
);
assert!(matches!(
result,
Err(PayloadBidError::ParentExecutionPayloadUnknown { .. })
));
}

#[test]
fn execution_payment_nonzero() {
if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4238,6 +4238,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
| PayloadBidError::BuilderAlreadySeen { .. }
| PayloadBidError::BidValueBelowCached { .. }
| PayloadBidError::ParentBlockRootUnknown { .. }
| PayloadBidError::ParentExecutionPayloadUnknown { .. }
| PayloadBidError::BidNotCompatibleWithHead { .. }
| PayloadBidError::BuilderCantCoverBid { .. }
| PayloadBidError::InvalidFeeRecipient
Expand Down
Loading