Skip to content

Commit 0c9d64b

Browse files
committed
Merge remote-tracking branch 'origin/unstable' into jimmy/lh-2271-activate-peerdas-at-fulu-fork-and-remove-eip7594_fork_epoch
# Conflicts: # beacon_node/lighthouse_network/src/rpc/protocol.rs # testing/ef_tests/check_all_files_accessed.py # testing/ef_tests/src/handler.rs
2 parents 64e44e1 + 587c3e2 commit 0c9d64b

File tree

34 files changed

+470
-228
lines changed

34 files changed

+470
-228
lines changed

beacon_node/beacon_chain/tests/validator_monitor.rs

Lines changed: 33 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use beacon_chain::test_utils::{
44
use beacon_chain::validator_monitor::{ValidatorMonitorConfig, MISSED_BLOCK_LAG_SLOTS};
55
use logging::test_logger;
66
use std::sync::LazyLock;
7-
use types::{Epoch, EthSpec, ForkName, Keypair, MainnetEthSpec, PublicKeyBytes, Slot};
7+
use types::{Epoch, EthSpec, Keypair, MainnetEthSpec, PublicKeyBytes, Slot};
88

99
// Should ideally be divisible by 3.
1010
pub const VALIDATOR_COUNT: usize = 48;
@@ -117,7 +117,7 @@ async fn missed_blocks_across_epochs() {
117117
}
118118

119119
#[tokio::test]
120-
async fn produces_missed_blocks() {
120+
async fn missed_blocks_basic() {
121121
let validator_count = 16;
122122

123123
let slots_per_epoch = E::slots_per_epoch();
@@ -127,13 +127,10 @@ async fn produces_missed_blocks() {
127127
// Generate 63 slots (2 epochs * 32 slots per epoch - 1)
128128
let initial_blocks = slots_per_epoch * nb_epoch_to_simulate.as_u64() - 1;
129129

130-
// The validator index of the validator that is 'supposed' to miss a block
131-
let validator_index_to_monitor = 1;
132-
133130
// 1st scenario //
134131
//
135132
// Missed block happens when slot and prev_slot are in the same epoch
136-
let harness1 = get_harness(validator_count, vec![validator_index_to_monitor]);
133+
let harness1 = get_harness(validator_count, vec![]);
137134
harness1
138135
.extend_chain(
139136
initial_blocks as usize,
@@ -153,7 +150,7 @@ async fn produces_missed_blocks() {
153150
let mut prev_slot = Slot::new(idx - 1);
154151
let mut duplicate_block_root = *_state.block_roots().get(idx as usize).unwrap();
155152
let mut validator_indexes = _state.get_beacon_proposer_indices(&harness1.spec).unwrap();
156-
let mut validator_index = validator_indexes[slot_in_epoch.as_usize()];
153+
let mut missed_block_proposer = validator_indexes[slot_in_epoch.as_usize()];
157154
let mut proposer_shuffling_decision_root = _state
158155
.proposer_shuffling_decision_root(duplicate_block_root)
159156
.unwrap();
@@ -170,7 +167,7 @@ async fn produces_missed_blocks() {
170167
beacon_proposer_cache.lock().insert(
171168
epoch,
172169
proposer_shuffling_decision_root,
173-
validator_indexes.into_iter().collect::<Vec<usize>>(),
170+
validator_indexes,
174171
_state.fork()
175172
),
176173
Ok(())
@@ -187,12 +184,15 @@ async fn produces_missed_blocks() {
187184
// Let's validate the state which will call the function responsible for
188185
// adding the missed blocks to the validator monitor
189186
let mut validator_monitor = harness1.chain.validator_monitor.write();
187+
188+
validator_monitor.add_validator_pubkey(KEYPAIRS[missed_block_proposer].pk.compress());
190189
validator_monitor.process_valid_state(nb_epoch_to_simulate, _state, &harness1.chain.spec);
191190

192191
// We should have one entry in the missed blocks map
193192
assert_eq!(
194-
validator_monitor.get_monitored_validator_missed_block_count(validator_index as u64),
195-
1
193+
validator_monitor
194+
.get_monitored_validator_missed_block_count(missed_block_proposer as u64),
195+
1,
196196
);
197197
}
198198

@@ -201,23 +201,7 @@ async fn produces_missed_blocks() {
201201
// Missed block happens when slot and prev_slot are not in the same epoch
202202
// making sure that the cache reloads when the epoch changes
203203
// in that scenario the slot that missed a block is the first slot of the epoch
204-
// We are adding other validators to monitor as these ones will miss a block depending on
205-
// the fork name specified when running the test as the proposer cache differs depending on
206-
// the fork name (cf. seed)
207-
//
208-
// If you are adding a new fork and seeing errors, print
209-
// `validator_indexes[slot_in_epoch.as_usize()]` and add it below.
210-
let validator_index_to_monitor = match harness1.spec.fork_name_at_slot::<E>(Slot::new(0)) {
211-
ForkName::Base => 7,
212-
ForkName::Altair => 2,
213-
ForkName::Bellatrix => 4,
214-
ForkName::Capella => 11,
215-
ForkName::Deneb => 3,
216-
ForkName::Electra => 1,
217-
ForkName::Fulu => 6,
218-
};
219-
220-
let harness2 = get_harness(validator_count, vec![validator_index_to_monitor]);
204+
let harness2 = get_harness(validator_count, vec![]);
221205
let advance_slot_by = 9;
222206
harness2
223207
.extend_chain(
@@ -238,11 +222,7 @@ async fn produces_missed_blocks() {
238222
slot_in_epoch = slot % slots_per_epoch;
239223
duplicate_block_root = *_state2.block_roots().get(idx as usize).unwrap();
240224
validator_indexes = _state2.get_beacon_proposer_indices(&harness2.spec).unwrap();
241-
validator_index = validator_indexes[slot_in_epoch.as_usize()];
242-
// If you are adding a new fork and seeing errors, it means the fork seed has changed the
243-
// validator_index. Uncomment this line, run the test again and add the resulting index to the
244-
// list above.
245-
//eprintln!("new index which needs to be added => {:?}", validator_index);
225+
missed_block_proposer = validator_indexes[slot_in_epoch.as_usize()];
246226

247227
let beacon_proposer_cache = harness2
248228
.chain
@@ -256,7 +236,7 @@ async fn produces_missed_blocks() {
256236
beacon_proposer_cache.lock().insert(
257237
epoch,
258238
duplicate_block_root,
259-
validator_indexes.into_iter().collect::<Vec<usize>>(),
239+
validator_indexes.clone(),
260240
_state2.fork()
261241
),
262242
Ok(())
@@ -271,30 +251,33 @@ async fn produces_missed_blocks() {
271251
// Let's validate the state which will call the function responsible for
272252
// adding the missed blocks to the validator monitor
273253
let mut validator_monitor2 = harness2.chain.validator_monitor.write();
254+
validator_monitor2.add_validator_pubkey(KEYPAIRS[missed_block_proposer].pk.compress());
274255
validator_monitor2.process_valid_state(epoch, _state2, &harness2.chain.spec);
275256
// We should have one entry in the missed blocks map
276257
assert_eq!(
277-
validator_monitor2.get_monitored_validator_missed_block_count(validator_index as u64),
258+
validator_monitor2
259+
.get_monitored_validator_missed_block_count(missed_block_proposer as u64),
278260
1
279261
);
280262

281263
// 3rd scenario //
282264
//
283265
// A missed block happens but the validator is not monitored
284266
// it should not be flagged as a missed block
285-
idx = initial_blocks + (advance_slot_by) - 7;
267+
while validator_indexes[(idx % slots_per_epoch) as usize] == missed_block_proposer
268+
&& idx / slots_per_epoch == epoch.as_u64()
269+
{
270+
idx += 1;
271+
}
286272
slot = Slot::new(idx);
287273
prev_slot = Slot::new(idx - 1);
288274
slot_in_epoch = slot % slots_per_epoch;
289275
duplicate_block_root = *_state2.block_roots().get(idx as usize).unwrap();
290-
validator_indexes = _state2.get_beacon_proposer_indices(&harness2.spec).unwrap();
291-
let not_monitored_validator_index = validator_indexes[slot_in_epoch.as_usize()];
292-
// This could do with a refactor: https://github.com/sigp/lighthouse/issues/6293
293-
assert_ne!(
294-
not_monitored_validator_index,
295-
validator_index_to_monitor,
296-
"this test has a fragile dependency on hardcoded indices. you need to tweak some settings or rewrite this"
297-
);
276+
let second_missed_block_proposer = validator_indexes[slot_in_epoch.as_usize()];
277+
278+
// This test may fail if we can't find another distinct proposer in the same epoch.
279+
// However, this should be vanishingly unlikely: P ~= (1/16)^32 = 2e-39.
280+
assert_ne!(missed_block_proposer, second_missed_block_proposer);
298281

299282
assert_eq!(
300283
_state2.set_block_root(prev_slot, duplicate_block_root),
@@ -306,10 +289,9 @@ async fn produces_missed_blocks() {
306289
validator_monitor2.process_valid_state(epoch, _state2, &harness2.chain.spec);
307290

308291
// We shouldn't have any entry in the missed blocks map
309-
assert_ne!(validator_index, not_monitored_validator_index);
310292
assert_eq!(
311293
validator_monitor2
312-
.get_monitored_validator_missed_block_count(not_monitored_validator_index as u64),
294+
.get_monitored_validator_missed_block_count(second_missed_block_proposer as u64),
313295
0
314296
);
315297
}
@@ -318,7 +300,7 @@ async fn produces_missed_blocks() {
318300
//
319301
// A missed block happens at state.slot - LOG_SLOTS_PER_EPOCH
320302
// it shouldn't be flagged as a missed block
321-
let harness3 = get_harness(validator_count, vec![validator_index_to_monitor]);
303+
let harness3 = get_harness(validator_count, vec![]);
322304
harness3
323305
.extend_chain(
324306
slots_per_epoch as usize,
@@ -338,7 +320,7 @@ async fn produces_missed_blocks() {
338320
prev_slot = Slot::new(idx - 1);
339321
duplicate_block_root = *_state3.block_roots().get(idx as usize).unwrap();
340322
validator_indexes = _state3.get_beacon_proposer_indices(&harness3.spec).unwrap();
341-
validator_index = validator_indexes[slot_in_epoch.as_usize()];
323+
missed_block_proposer = validator_indexes[slot_in_epoch.as_usize()];
342324
proposer_shuffling_decision_root = _state3
343325
.proposer_shuffling_decision_root_at_epoch(epoch, duplicate_block_root)
344326
.unwrap();
@@ -355,7 +337,7 @@ async fn produces_missed_blocks() {
355337
beacon_proposer_cache.lock().insert(
356338
epoch,
357339
proposer_shuffling_decision_root,
358-
validator_indexes.into_iter().collect::<Vec<usize>>(),
340+
validator_indexes,
359341
_state3.fork()
360342
),
361343
Ok(())
@@ -372,11 +354,13 @@ async fn produces_missed_blocks() {
372354
// Let's validate the state which will call the function responsible for
373355
// adding the missed blocks to the validator monitor
374356
let mut validator_monitor3 = harness3.chain.validator_monitor.write();
357+
validator_monitor3.add_validator_pubkey(KEYPAIRS[missed_block_proposer].pk.compress());
375358
validator_monitor3.process_valid_state(epoch, _state3, &harness3.chain.spec);
376359

377360
// We shouldn't have one entry in the missed blocks map
378361
assert_eq!(
379-
validator_monitor3.get_monitored_validator_missed_block_count(validator_index as u64),
362+
validator_monitor3
363+
.get_monitored_validator_missed_block_count(missed_block_proposer as u64),
380364
0
381365
);
382366
}

beacon_node/execution_layer/src/engine_api/json_structures.rs

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use superstruct::superstruct;
77
use types::beacon_block_body::KzgCommitments;
88
use types::blob_sidecar::BlobsList;
99
use types::execution_requests::{
10-
ConsolidationRequests, DepositRequests, RequestPrefix, WithdrawalRequests,
10+
ConsolidationRequests, DepositRequests, RequestType, WithdrawalRequests,
1111
};
1212
use types::{Blob, FixedVector, KzgProof, Unsigned};
1313

@@ -401,47 +401,80 @@ impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
401401
}
402402
}
403403

404+
#[derive(Debug, Clone)]
405+
pub enum RequestsError {
406+
InvalidHex(hex::FromHexError),
407+
EmptyRequest(usize),
408+
InvalidOrdering,
409+
InvalidPrefix(u8),
410+
DecodeError(String),
411+
}
412+
404413
/// Format of `ExecutionRequests` received over the engine api.
405414
///
406-
/// Array of ssz-encoded requests list encoded as hex bytes.
407-
/// The prefix of the request type is used to index into the array.
408-
///
409-
/// For e.g. [0xab, 0xcd, 0xef]
410-
/// Here, 0xab are the deposits bytes (prefix and index == 0)
411-
/// 0xcd are the withdrawals bytes (prefix and index == 1)
412-
/// 0xef are the consolidations bytes (prefix and index == 2)
415+
/// Array of ssz-encoded requests list encoded as hex bytes prefixed
416+
/// with a `RequestType`
413417
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
414418
#[serde(transparent)]
415419
pub struct JsonExecutionRequests(pub Vec<String>);
416420

417421
impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
418-
type Error = String;
422+
type Error = RequestsError;
419423

420424
fn try_from(value: JsonExecutionRequests) -> Result<Self, Self::Error> {
421425
let mut requests = ExecutionRequests::default();
422-
426+
let mut prev_prefix: Option<RequestType> = None;
423427
for (i, request) in value.0.into_iter().enumerate() {
424428
// hex string
425429
let decoded_bytes = hex::decode(request.strip_prefix("0x").unwrap_or(&request))
426-
.map_err(|e| format!("Invalid hex {:?}", e))?;
427-
match RequestPrefix::from_prefix(i as u8) {
428-
Some(RequestPrefix::Deposit) => {
429-
requests.deposits = DepositRequests::<E>::from_ssz_bytes(&decoded_bytes)
430-
.map_err(|e| format!("Failed to decode DepositRequest from EL: {:?}", e))?;
430+
.map_err(RequestsError::InvalidHex)?;
431+
432+
// The first byte of each element is the `request_type` and the remaining bytes are the `request_data`.
433+
// Elements with empty `request_data` **MUST** be excluded from the list.
434+
let Some((prefix_byte, request_bytes)) = decoded_bytes.split_first() else {
435+
return Err(RequestsError::EmptyRequest(i));
436+
};
437+
if request_bytes.is_empty() {
438+
return Err(RequestsError::EmptyRequest(i));
439+
}
440+
// Elements of the list **MUST** be ordered by `request_type` in ascending order
441+
let current_prefix = RequestType::from_u8(*prefix_byte)
442+
.ok_or(RequestsError::InvalidPrefix(*prefix_byte))?;
443+
if let Some(prev) = prev_prefix {
444+
if prev.to_u8() >= current_prefix.to_u8() {
445+
return Err(RequestsError::InvalidOrdering);
431446
}
432-
Some(RequestPrefix::Withdrawal) => {
433-
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(&decoded_bytes)
447+
}
448+
prev_prefix = Some(current_prefix);
449+
450+
match current_prefix {
451+
RequestType::Deposit => {
452+
requests.deposits = DepositRequests::<E>::from_ssz_bytes(request_bytes)
434453
.map_err(|e| {
435-
format!("Failed to decode WithdrawalRequest from EL: {:?}", e)
454+
RequestsError::DecodeError(format!(
455+
"Failed to decode DepositRequest from EL: {:?}",
456+
e
457+
))
436458
})?;
437459
}
438-
Some(RequestPrefix::Consolidation) => {
460+
RequestType::Withdrawal => {
461+
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(request_bytes)
462+
.map_err(|e| {
463+
RequestsError::DecodeError(format!(
464+
"Failed to decode WithdrawalRequest from EL: {:?}",
465+
e
466+
))
467+
})?;
468+
}
469+
RequestType::Consolidation => {
439470
requests.consolidations =
440-
ConsolidationRequests::<E>::from_ssz_bytes(&decoded_bytes).map_err(
441-
|e| format!("Failed to decode ConsolidationRequest from EL: {:?}", e),
442-
)?;
471+
ConsolidationRequests::<E>::from_ssz_bytes(request_bytes).map_err(|e| {
472+
RequestsError::DecodeError(format!(
473+
"Failed to decode ConsolidationRequest from EL: {:?}",
474+
e
475+
))
476+
})?;
443477
}
444-
None => return Err("Empty requests string".to_string()),
445478
}
446479
}
447480
Ok(requests)
@@ -510,7 +543,9 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
510543
block_value: response.block_value,
511544
blobs_bundle: response.blobs_bundle.into(),
512545
should_override_builder: response.should_override_builder,
513-
requests: response.execution_requests.try_into()?,
546+
requests: response.execution_requests.try_into().map_err(|e| {
547+
format!("Failed to convert json to execution requests : {:?}", e)
548+
})?,
514549
}))
515550
}
516551
JsonGetPayloadResponse::V5(response) => {
@@ -519,7 +554,9 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
519554
block_value: response.block_value,
520555
blobs_bundle: response.blobs_bundle.into(),
521556
should_override_builder: response.should_override_builder,
522-
requests: response.execution_requests.try_into()?,
557+
requests: response.execution_requests.try_into().map_err(|e| {
558+
format!("Failed to convert json to execution requests {:?}", e)
559+
})?,
523560
}))
524561
}
525562
}

beacon_node/execution_layer/src/engine_api/new_payload_request.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
128128

129129
let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_VERIFY_BLOCK_HASH);
130130

131+
// Check that no transactions in the payload are zero length
132+
if payload.transactions().iter().any(|slice| slice.is_empty()) {
133+
return Err(Error::ZeroLengthTransaction);
134+
}
135+
131136
let (header_hash, rlp_transactions_root) = calculate_execution_block_hash(
132137
payload,
133138
parent_beacon_block_root,

beacon_node/execution_layer/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ pub enum Error {
157157
payload: ExecutionBlockHash,
158158
transactions_root: Hash256,
159159
},
160+
ZeroLengthTransaction,
160161
PayloadBodiesByRangeNotSupported,
161162
InvalidJWTSecret(String),
162163
InvalidForkForPayload,

0 commit comments

Comments
 (0)