Skip to content

Commit 6dd3da7

Browse files
committed
Kintsugi on_merge_block tests (#2811)
* Start v1.1.5 updates * Implement new payload creation logic * Tidy, add comments * Remove unused error enums * Add validate payload for gossip * Refactor validate_merge_block * Split payload verification in per block processing * Add execute_payload * Tidy * Tidy * Start working on new fork choice tests * Fix failing merge block test * Skip block_lookup_failed test * Fix failing terminal block test * Fixes from self-review * Address review comments
1 parent e615f1d commit 6dd3da7

File tree

14 files changed

+583
-269
lines changed

14 files changed

+583
-269
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::chain_config::ChainConfig;
1515
use crate::errors::{BeaconChainError as Error, BlockProductionError};
1616
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
1717
use crate::events::ServerSentEventHandler;
18+
use crate::execution_payload::get_execution_payload;
1819
use crate::head_tracker::HeadTracker;
1920
use crate::historical_blocks::HistoricalBlockError;
2021
use crate::migrate::BackgroundMigrator;
@@ -65,9 +66,7 @@ use ssz::Encode;
6566
use state_processing::{
6667
common::get_indexed_attestation,
6768
per_block_processing,
68-
per_block_processing::{
69-
compute_timestamp_at_slot, errors::AttestationValidationError, is_merge_complete,
70-
},
69+
per_block_processing::{errors::AttestationValidationError, is_merge_complete},
7170
per_slot_processing,
7271
state_advance::{complete_state_advance, partial_state_advance},
7372
BlockSignatureStrategy, SigVerifiedOp,
@@ -2881,63 +2880,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
28812880
SyncAggregate::new()
28822881
}))
28832882
};
2884-
// Closure to fetch a sync aggregate in cases where it is required.
2885-
let get_execution_payload = |latest_execution_payload_header: &ExecutionPayloadHeader<
2886-
T::EthSpec,
2887-
>|
2888-
-> Result<ExecutionPayload<_>, BlockProductionError> {
2889-
let execution_layer = self
2890-
.execution_layer
2891-
.as_ref()
2892-
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
2893-
2894-
let parent_hash;
2895-
if !is_merge_complete(&state) {
2896-
let terminal_pow_block_hash = execution_layer
2897-
.block_on(|execution_layer| {
2898-
execution_layer.get_terminal_pow_block_hash(&self.spec)
2899-
})
2900-
.map_err(BlockProductionError::TerminalPoWBlockLookupFailed)?;
2901-
2902-
if let Some(terminal_pow_block_hash) = terminal_pow_block_hash {
2903-
parent_hash = terminal_pow_block_hash;
2904-
} else {
2905-
return Ok(<_>::default());
2906-
}
2907-
} else {
2908-
parent_hash = latest_execution_payload_header.block_hash;
2909-
}
2910-
2911-
let timestamp =
2912-
compute_timestamp_at_slot(&state, &self.spec).map_err(BeaconStateError::from)?;
2913-
let random = *state.get_randao_mix(state.current_epoch())?;
2914-
let finalized_root = state.finalized_checkpoint().root;
2915-
2916-
let finalized_block_hash =
2917-
if let Some(block) = self.fork_choice.read().get_block(&finalized_root) {
2918-
block.execution_status.block_hash()
2919-
} else {
2920-
self.store
2921-
.get_block(&finalized_root)
2922-
.map_err(BlockProductionError::FailedToReadFinalizedBlock)?
2923-
.ok_or(BlockProductionError::MissingFinalizedBlock(finalized_root))?
2924-
.message()
2925-
.body()
2926-
.execution_payload()
2927-
.map(|ep| ep.block_hash)
2928-
};
2929-
2930-
execution_layer
2931-
.block_on(|execution_layer| {
2932-
execution_layer.get_payload(
2933-
parent_hash,
2934-
timestamp,
2935-
random,
2936-
finalized_block_hash.unwrap_or_else(Hash256::zero),
2937-
)
2938-
})
2939-
.map_err(BlockProductionError::GetPayloadFailed)
2940-
};
29412883

29422884
let inner_block = match &state {
29432885
BeaconState::Base(_) => BeaconBlock::Base(BeaconBlockBase {
@@ -2976,10 +2918,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
29762918
},
29772919
})
29782920
}
2979-
BeaconState::Merge(state) => {
2921+
BeaconState::Merge(_) => {
29802922
let sync_aggregate = get_sync_aggregate()?;
2981-
let execution_payload =
2982-
get_execution_payload(&state.latest_execution_payload_header)?;
2923+
let execution_payload = get_execution_payload(self, &state)?;
29832924
BeaconBlock::Merge(BeaconBlockMerge {
29842925
slot,
29852926
proposer_index,

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 42 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
//! END
4141
//!
4242
//! ```
43+
use crate::execution_payload::{
44+
execute_payload, validate_execution_payload_for_gossip, validate_merge_block,
45+
};
4346
use crate::snapshot_cache::PreProcessingSnapshot;
4447
use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS;
4548
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
@@ -50,15 +53,14 @@ use crate::{
5053
},
5154
metrics, BeaconChain, BeaconChainError, BeaconChainTypes,
5255
};
53-
use execution_layer::ExecutePayloadResponseStatus;
5456
use fork_choice::{ForkChoice, ForkChoiceStore, PayloadVerificationStatus};
5557
use parking_lot::RwLockReadGuard;
56-
use proto_array::{Block as ProtoBlock, ExecutionStatus};
58+
use proto_array::Block as ProtoBlock;
5759
use safe_arith::ArithError;
58-
use slog::{debug, error, info, Logger};
60+
use slog::{debug, error, Logger};
5961
use slot_clock::SlotClock;
6062
use ssz::Encode;
61-
use state_processing::per_block_processing::{is_execution_enabled, is_merge_block};
63+
use state_processing::per_block_processing::is_merge_block;
6264
use state_processing::{
6365
block_signature_verifier::{BlockSignatureVerifier, Error as BlockSignatureVerifierError},
6466
per_block_processing, per_slot_processing,
@@ -71,9 +73,9 @@ use std::io::Write;
7173
use store::{Error as DBError, HotColdDB, HotStateSummary, KeyValueStore, StoreOp};
7274
use tree_hash::TreeHash;
7375
use types::{
74-
BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec,
75-
ExecutionPayload, Hash256, InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch,
76-
SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
76+
BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, Hash256,
77+
InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, SignedBeaconBlock,
78+
SignedBeaconBlockHeader, Slot,
7779
};
7880

7981
/// Maximum block slot number. Block with slots bigger than this constant will NOT be processed.
@@ -266,38 +268,47 @@ pub enum ExecutionPayloadError {
266268
///
267269
/// The block is invalid and the peer is faulty
268270
RejectedByExecutionEngine,
269-
/// The execution engine returned SYNCING for the payload
270-
///
271-
/// ## Peer scoring
272-
///
273-
/// It is not known if the block is valid or invalid.
274-
ExecutionEngineIsSyncing,
275271
/// The execution payload timestamp does not match the slot
276272
///
277273
/// ## Peer scoring
278274
///
279275
/// The block is invalid and the peer is faulty
280276
InvalidPayloadTimestamp { expected: u64, found: u64 },
281-
/// The execution payload transaction list data exceeds size limits
277+
/// The execution payload references an execution block that cannot trigger the merge.
282278
///
283279
/// ## Peer scoring
284280
///
285-
/// The block is invalid and the peer is faulty
286-
TransactionDataExceedsSizeLimit,
287-
/// The execution payload references an execution block that cannot trigger the merge.
281+
/// The block is invalid and the peer sent us a block that passes gossip propagation conditions,
282+
/// but is invalid upon further verification.
283+
InvalidTerminalPoWBlock { parent_hash: Hash256 },
284+
/// The `TERMINAL_BLOCK_HASH` is set, but the block has not reached the
285+
/// `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH`.
288286
///
289287
/// ## Peer scoring
290288
///
291289
/// The block is invalid and the peer sent us a block that passes gossip propagation conditions,
292290
/// but is invalid upon further verification.
293-
InvalidTerminalPoWBlock,
294-
/// The execution payload references execution blocks that are unavailable on our execution
295-
/// nodes.
291+
InvalidActivationEpoch {
292+
activation_epoch: Epoch,
293+
epoch: Epoch,
294+
},
295+
/// The `TERMINAL_BLOCK_HASH` is set, but does not match the value specified by the block.
296296
///
297297
/// ## Peer scoring
298298
///
299-
/// It's not clear if the peer is invalid or if it's on a different execution fork to us.
300-
TerminalPoWBlockNotFound,
299+
/// The block is invalid and the peer sent us a block that passes gossip propagation conditions,
300+
/// but is invalid upon further verification.
301+
InvalidTerminalBlockHash {
302+
terminal_block_hash: Hash256,
303+
payload_parent_hash: Hash256,
304+
},
305+
/// The execution node failed to provide a parent block to a known block. This indicates an
306+
/// issue with the execution node.
307+
///
308+
/// ## Peer scoring
309+
///
310+
/// The peer is not necessarily invalid.
311+
PoWParentMissing(Hash256),
301312
}
302313

303314
impl From<execution_layer::Error> for ExecutionPayloadError {
@@ -768,8 +779,8 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
768779
});
769780
}
770781

771-
// validate the block's execution_payload
772-
validate_execution_payload(&parent_block, block.message(), chain)?;
782+
// Validate the block's execution_payload (if any).
783+
validate_execution_payload_for_gossip(&parent_block, block.message(), chain)?;
773784

774785
Ok(Self {
775786
block,
@@ -1103,83 +1114,16 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
11031114
// - Doing the check here means we can keep our fork-choice implementation "pure". I.e., no
11041115
// calls to remote servers.
11051116
if is_merge_block(&state, block.message().body()) {
1106-
let execution_layer = chain
1107-
.execution_layer
1108-
.as_ref()
1109-
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
1110-
let execution_payload =
1111-
block
1112-
.message()
1113-
.body()
1114-
.execution_payload()
1115-
.ok_or_else(|| InconsistentFork {
1116-
fork_at_slot: eth2::types::ForkName::Merge,
1117-
object_fork: block.message().body().fork_name(),
1118-
})?;
1119-
1120-
let is_valid_terminal_pow_block = execution_layer
1121-
.block_on(|execution_layer| {
1122-
execution_layer.is_valid_terminal_pow_block_hash(
1123-
execution_payload.parent_hash,
1124-
&chain.spec,
1125-
)
1126-
})
1127-
.map_err(ExecutionPayloadError::from)?;
1128-
1129-
match is_valid_terminal_pow_block {
1130-
Some(true) => Ok(()),
1131-
Some(false) => Err(ExecutionPayloadError::InvalidTerminalPoWBlock),
1132-
None => {
1133-
info!(
1134-
chain.log,
1135-
"Optimistically accepting terminal block";
1136-
"block_hash" => ?execution_payload.parent_hash,
1137-
"msg" => "the terminal block/parent was unavailable"
1138-
);
1139-
Ok(())
1140-
}
1141-
}?;
1117+
validate_merge_block(chain, block.message())?
11421118
}
11431119

1144-
// This is the soonest we can run these checks as they must be called AFTER per_slot_processing
1120+
// The specification declares that this should be run *inside* `per_block_processing`,
1121+
// however we run it here to keep `per_block_processing` pure (i.e., no calls to external
1122+
// servers).
11451123
//
1146-
// TODO(merge): handle the latest_valid_hash of an invalid payload.
1147-
let (_latest_valid_hash, payload_verification_status) =
1148-
if is_execution_enabled(&state, block.message().body()) {
1149-
let execution_layer = chain
1150-
.execution_layer
1151-
.as_ref()
1152-
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
1153-
let execution_payload =
1154-
block
1155-
.message()
1156-
.body()
1157-
.execution_payload()
1158-
.ok_or_else(|| InconsistentFork {
1159-
fork_at_slot: eth2::types::ForkName::Merge,
1160-
object_fork: block.message().body().fork_name(),
1161-
})?;
1162-
1163-
let execute_payload_response = execution_layer
1164-
.block_on(|execution_layer| execution_layer.execute_payload(execution_payload));
1165-
1166-
match execute_payload_response {
1167-
Ok((status, latest_valid_hash)) => match status {
1168-
ExecutePayloadResponseStatus::Valid => {
1169-
(latest_valid_hash, PayloadVerificationStatus::Verified)
1170-
}
1171-
ExecutePayloadResponseStatus::Invalid => {
1172-
return Err(ExecutionPayloadError::RejectedByExecutionEngine.into());
1173-
}
1174-
ExecutePayloadResponseStatus::Syncing => {
1175-
(latest_valid_hash, PayloadVerificationStatus::NotVerified)
1176-
}
1177-
},
1178-
Err(_) => (None, PayloadVerificationStatus::NotVerified),
1179-
}
1180-
} else {
1181-
(None, PayloadVerificationStatus::Irrelevant)
1182-
};
1124+
// It is important that this function is called *after* `per_slot_processing`, since the
1125+
// `randao` may change.
1126+
let payload_verification_status = execute_payload(chain, &state, block.message())?;
11831127

11841128
// If the block is sufficiently recent, notify the validator monitor.
11851129
if let Some(slot) = chain.slot_clock.now() {
@@ -1290,64 +1234,6 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
12901234
}
12911235
}
12921236

1293-
/// Validate the gossip block's execution_payload according to the checks described here:
1294-
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#beacon_block
1295-
fn validate_execution_payload<T: BeaconChainTypes>(
1296-
parent_block: &ProtoBlock,
1297-
block: BeaconBlockRef<'_, T::EthSpec>,
1298-
chain: &BeaconChain<T>,
1299-
) -> Result<(), BlockError<T::EthSpec>> {
1300-
// Only apply this validation if this is a merge beacon block.
1301-
if let Some(execution_payload) = block.body().execution_payload() {
1302-
// This logic should match `is_execution_enabled`. We use only the execution block hash of
1303-
// the parent here in order to avoid loading the parent state during gossip verification.
1304-
1305-
let is_merge_complete = match parent_block.execution_status {
1306-
// Optimistically declare that an "unknown" status block has completed the merge.
1307-
ExecutionStatus::Valid(_) | ExecutionStatus::Unknown(_) => true,
1308-
// It's impossible for an irrelevant block to have completed the merge. It is pre-merge
1309-
// by definition.
1310-
ExecutionStatus::Irrelevant(_) => false,
1311-
// If the parent has an invalid payload then it's impossible to build a valid block upon
1312-
// it. Reject the block.
1313-
ExecutionStatus::Invalid(_) => {
1314-
return Err(BlockError::ParentExecutionPayloadInvalid {
1315-
parent_root: parent_block.root,
1316-
})
1317-
}
1318-
};
1319-
let is_merge_block =
1320-
!is_merge_complete && *execution_payload != <ExecutionPayload<T::EthSpec>>::default();
1321-
if !is_merge_block && !is_merge_complete {
1322-
return Ok(());
1323-
}
1324-
1325-
let expected_timestamp = chain
1326-
.slot_clock
1327-
.compute_timestamp_at_slot(block.slot())
1328-
.ok_or(BlockError::BeaconChainError(
1329-
BeaconChainError::UnableToComputeTimeAtSlot,
1330-
))?;
1331-
// The block's execution payload timestamp is correct with respect to the slot
1332-
if execution_payload.timestamp != expected_timestamp {
1333-
return Err(BlockError::ExecutionPayloadError(
1334-
ExecutionPayloadError::InvalidPayloadTimestamp {
1335-
expected: expected_timestamp,
1336-
found: execution_payload.timestamp,
1337-
},
1338-
));
1339-
}
1340-
// The execution payload transaction list data is within expected size limits
1341-
if execution_payload.transactions.len() > T::EthSpec::max_transactions_per_payload() {
1342-
return Err(BlockError::ExecutionPayloadError(
1343-
ExecutionPayloadError::TransactionDataExceedsSizeLimit,
1344-
));
1345-
}
1346-
}
1347-
1348-
Ok(())
1349-
}
1350-
13511237
/// Check that the count of skip slots between the block and its parent does not exceed our maximum
13521238
/// value.
13531239
///

beacon_node/beacon_chain/src/errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ pub enum BlockProductionError {
181181
state_slot: Slot,
182182
},
183183
ExecutionLayerMissing,
184+
BlockingFailed(execution_layer::Error),
184185
TerminalPoWBlockLookupFailed(execution_layer::Error),
185186
GetPayloadFailed(execution_layer::Error),
186187
FailedToReadFinalizedBlock(store::Error),

0 commit comments

Comments
 (0)