mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-05 13:54:36 +00:00
Merge branch 'alpha-spec-11' into glamsterdam-devnet-6
This commit is contained in:
1
.github/forbidden-files.txt
vendored
1
.github/forbidden-files.txt
vendored
@@ -9,6 +9,7 @@ beacon_node/beacon_chain/src/block_reward.rs
|
|||||||
beacon_node/http_api/src/attestation_performance.rs
|
beacon_node/http_api/src/attestation_performance.rs
|
||||||
beacon_node/http_api/src/block_packing_efficiency.rs
|
beacon_node/http_api/src/block_packing_efficiency.rs
|
||||||
beacon_node/http_api/src/block_rewards.rs
|
beacon_node/http_api/src/block_rewards.rs
|
||||||
|
beacon_node/http_api/src/beacon/execution_payload_envelope.rs
|
||||||
common/eth2/src/lighthouse/attestation_performance.rs
|
common/eth2/src/lighthouse/attestation_performance.rs
|
||||||
common/eth2/src/lighthouse/block_packing_efficiency.rs
|
common/eth2/src/lighthouse/block_packing_efficiency.rs
|
||||||
common/eth2/src/lighthouse/block_rewards.rs
|
common/eth2/src/lighthouse/block_rewards.rs
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2475,7 +2475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
|
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"syn 1.0.109",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -31,11 +31,10 @@ use types::{
|
|||||||
Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra,
|
Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra,
|
||||||
BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError,
|
BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError,
|
||||||
BuilderIndex, ChainSpec, Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid,
|
BuilderIndex, ChainSpec, Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid,
|
||||||
ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, ExecutionRequestsGloas,
|
ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequestsGloas, FullPayload, Graffiti,
|
||||||
FullPayload, Graffiti, Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch,
|
Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock,
|
||||||
SignedBeaconBlock, SignedBlsToExecutionChange, SignedExecutionPayloadBid,
|
SignedBlsToExecutionChange, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope,
|
||||||
SignedExecutionPayloadEnvelope, SignedVoluntaryExit, Slot, SyncAggregate, Withdrawal,
|
SignedVoluntaryExit, Slot, SyncAggregate, Withdrawal, Withdrawals,
|
||||||
Withdrawals,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::pending_payload_envelopes::PendingEnvelopeData;
|
use crate::pending_payload_envelopes::PendingEnvelopeData;
|
||||||
@@ -810,11 +809,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
should_override_builder,
|
should_override_builder,
|
||||||
} = block_proposal_contents;
|
} = block_proposal_contents;
|
||||||
|
|
||||||
// The EL get_payload response carries the standard (Electra-shaped) requests; lift them
|
|
||||||
// into the Gloas variant carried by the envelope and committed to by the bid.
|
|
||||||
// TODO(gloas): plumb builder deposit/exit requests from the EL.
|
|
||||||
let execution_requests = to_gloas_execution_requests(execution_requests);
|
|
||||||
|
|
||||||
// TODO(gloas) since we are defaulting to local building, execution payment is 0
|
// TODO(gloas) since we are defaulting to local building, execution payment is 0
|
||||||
// execution payment should only be set to > 0 for trusted building.
|
// execution payment should only be set to > 0 for trusted building.
|
||||||
let bid = ExecutionPayloadBid::<T::EthSpec> {
|
let bid = ExecutionPayloadBid::<T::EthSpec> {
|
||||||
@@ -1113,26 +1107,6 @@ where
|
|||||||
/// processing. EL-triggered withdrawal-full-exit requests (EIP-7002) and cross-pubkey
|
/// processing. EL-triggered withdrawal-full-exit requests (EIP-7002) and cross-pubkey
|
||||||
/// consolidation requests (EIP-7251) call `initiate_validator_exit`, setting the target's
|
/// consolidation requests (EIP-7251) call `initiate_validator_exit`, setting the target's
|
||||||
/// `exit_epoch`. A voluntary exit for the same validator would then fail with `AlreadyExited`.
|
/// `exit_epoch`. A voluntary exit for the same validator would then fail with `AlreadyExited`.
|
||||||
/// Lift a fork-agnostic `ExecutionRequests` (as received from the EL) into the Gloas variant.
|
|
||||||
///
|
|
||||||
/// The standard request types are carried over; the Gloas-only builder deposit/exit lists are
|
|
||||||
/// left empty.
|
|
||||||
/// TODO(gloas): plumb builder deposit/exit requests from the EL.
|
|
||||||
fn to_gloas_execution_requests<E: EthSpec>(
|
|
||||||
requests: ExecutionRequests<E>,
|
|
||||||
) -> ExecutionRequestsGloas<E> {
|
|
||||||
match requests {
|
|
||||||
ExecutionRequests::Gloas(requests) => requests,
|
|
||||||
other => ExecutionRequestsGloas {
|
|
||||||
deposits: other.deposits().clone(),
|
|
||||||
withdrawals: other.withdrawals().clone(),
|
|
||||||
consolidations: other.consolidations().clone(),
|
|
||||||
builder_deposits: <_>::default(),
|
|
||||||
builder_exits: <_>::default(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter_voluntary_exits_for_parent_execution_requests<E: EthSpec>(
|
fn filter_voluntary_exits_for_parent_execution_requests<E: EthSpec>(
|
||||||
voluntary_exits: &mut Vec<SignedVoluntaryExit>,
|
voluntary_exits: &mut Vec<SignedVoluntaryExit>,
|
||||||
parent_execution_requests: &ExecutionRequestsGloas<E>,
|
parent_execution_requests: &ExecutionRequestsGloas<E>,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use std::assert_matches;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bls::{Keypair, PublicKeyBytes, Signature};
|
use bls::{Keypair, PublicKeyBytes, Signature};
|
||||||
@@ -195,6 +195,40 @@ impl TestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expected_prev_randao(&self) -> Hash256 {
|
||||||
|
let current_slot = self.slot_clock.now().expect("should read slot clock");
|
||||||
|
let head = self.canonical_head.cached_head();
|
||||||
|
*head
|
||||||
|
.snapshot
|
||||||
|
.beacon_state
|
||||||
|
.get_randao_mix(current_slot.epoch(E::slots_per_epoch()))
|
||||||
|
.expect("should read current epoch randao mix")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_signed_bid(
|
||||||
|
&self,
|
||||||
|
slot: Slot,
|
||||||
|
builder_index: u64,
|
||||||
|
fee_recipient: Address,
|
||||||
|
gas_limit: u64,
|
||||||
|
value: u64,
|
||||||
|
parent_block_root: Hash256,
|
||||||
|
) -> Arc<SignedExecutionPayloadBid<E>> {
|
||||||
|
Arc::new(SignedExecutionPayloadBid {
|
||||||
|
message: ExecutionPayloadBid {
|
||||||
|
slot,
|
||||||
|
builder_index,
|
||||||
|
fee_recipient,
|
||||||
|
gas_limit,
|
||||||
|
value,
|
||||||
|
parent_block_root,
|
||||||
|
prev_randao: self.expected_prev_randao(),
|
||||||
|
..ExecutionPayloadBid::default()
|
||||||
|
},
|
||||||
|
signature: Signature::empty(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_non_canonical_block(&self) -> Hash256 {
|
fn insert_non_canonical_block(&self) -> Hash256 {
|
||||||
let shuffling_id = AttestationShufflingId {
|
let shuffling_id = AttestationShufflingId {
|
||||||
shuffling_epoch: Epoch::new(0),
|
shuffling_epoch: Epoch::new(0),
|
||||||
@@ -237,28 +271,6 @@ impl TestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_signed_bid(
|
|
||||||
slot: Slot,
|
|
||||||
builder_index: u64,
|
|
||||||
fee_recipient: Address,
|
|
||||||
gas_limit: u64,
|
|
||||||
value: u64,
|
|
||||||
parent_block_root: Hash256,
|
|
||||||
) -> Arc<SignedExecutionPayloadBid<E>> {
|
|
||||||
Arc::new(SignedExecutionPayloadBid {
|
|
||||||
message: ExecutionPayloadBid {
|
|
||||||
slot,
|
|
||||||
builder_index,
|
|
||||||
fee_recipient,
|
|
||||||
gas_limit,
|
|
||||||
value,
|
|
||||||
parent_block_root,
|
|
||||||
..ExecutionPayloadBid::default()
|
|
||||||
},
|
|
||||||
signature: Signature::empty(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_signed_preferences(
|
fn make_signed_preferences(
|
||||||
proposal_slot: Slot,
|
proposal_slot: Slot,
|
||||||
validator_index: u64,
|
validator_index: u64,
|
||||||
@@ -309,7 +321,7 @@ fn no_proposer_preferences_for_slot() {
|
|||||||
}
|
}
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
let gossip = ctx.gossip_ctx();
|
let gossip = ctx.gossip_ctx();
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
Slot::new(0),
|
Slot::new(0),
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -335,7 +347,7 @@ fn builder_already_seen_for_slot() {
|
|||||||
let slot = Slot::new(1);
|
let slot = Slot::new(1);
|
||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let bid = make_signed_bid(slot, 42, Address::ZERO, 30_000_000, 100, Hash256::ZERO);
|
let bid = ctx.make_signed_bid(slot, 42, Address::ZERO, 30_000_000, 100, Hash256::ZERO);
|
||||||
let verified = GossipVerifiedPayloadBid {
|
let verified = GossipVerifiedPayloadBid {
|
||||||
signed_bid: bid.clone(),
|
signed_bid: bid.clone(),
|
||||||
};
|
};
|
||||||
@@ -362,11 +374,11 @@ fn bid_value_below_cached() {
|
|||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let high_bid = GossipVerifiedPayloadBid {
|
let high_bid = GossipVerifiedPayloadBid {
|
||||||
signed_bid: make_signed_bid(slot, 99, Address::ZERO, 30_000_000, 500, Hash256::ZERO),
|
signed_bid: ctx.make_signed_bid(slot, 99, Address::ZERO, 30_000_000, 500, Hash256::ZERO),
|
||||||
};
|
};
|
||||||
ctx.bid_cache.insert_highest_bid(high_bid);
|
ctx.bid_cache.insert_highest_bid(high_bid);
|
||||||
|
|
||||||
let low_bid = make_signed_bid(slot, 1, Address::ZERO, 30_000_000, 100, Hash256::ZERO);
|
let low_bid = ctx.make_signed_bid(slot, 1, Address::ZERO, 30_000_000, 100, Hash256::ZERO);
|
||||||
let result = GossipVerifiedPayloadBid::new(low_bid, &gossip);
|
let result = GossipVerifiedPayloadBid::new(low_bid, &gossip);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
result,
|
result,
|
||||||
@@ -384,7 +396,7 @@ fn invalid_bid_slot() {
|
|||||||
let slot = Slot::new(5);
|
let slot = Slot::new(5);
|
||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -393,10 +405,7 @@ fn invalid_bid_slot() {
|
|||||||
ctx.genesis_block_root,
|
ctx.genesis_block_root,
|
||||||
);
|
);
|
||||||
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
||||||
assert!(matches!(
|
assert_matches!(result, Err(PayloadBidError::InvalidBidSlot { .. }));
|
||||||
result,
|
|
||||||
Err(PayloadBidError::InvalidBidSlot { .. })
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -409,7 +418,7 @@ fn fee_recipient_mismatch() {
|
|||||||
let slot = Slot::new(1);
|
let slot = Slot::new(1);
|
||||||
seed_preferences(&ctx, slot, Address::repeat_byte(0xaa), 30_000_000);
|
seed_preferences(&ctx, slot, Address::repeat_byte(0xaa), 30_000_000);
|
||||||
|
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -431,7 +440,7 @@ fn gas_limit_mismatch() {
|
|||||||
let slot = Slot::new(1);
|
let slot = Slot::new(1);
|
||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -459,6 +468,7 @@ fn execution_payment_nonzero() {
|
|||||||
gas_limit: 30_000_000,
|
gas_limit: 30_000_000,
|
||||||
execution_payment: 42,
|
execution_payment: 42,
|
||||||
parent_block_root: ctx.genesis_block_root,
|
parent_block_root: ctx.genesis_block_root,
|
||||||
|
prev_randao: ctx.expected_prev_randao(),
|
||||||
..ExecutionPayloadBid::default()
|
..ExecutionPayloadBid::default()
|
||||||
},
|
},
|
||||||
signature: Signature::empty(),
|
signature: Signature::empty(),
|
||||||
@@ -481,7 +491,7 @@ fn unknown_builder_index() {
|
|||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
// Use a builder_index that doesn't exist in the registry.
|
// Use a builder_index that doesn't exist in the registry.
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
9999,
|
9999,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -508,7 +518,7 @@ fn inactive_builder() {
|
|||||||
let slot = Slot::new(1);
|
let slot = Slot::new(1);
|
||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
ctx.inactive_builder_index,
|
ctx.inactive_builder_index,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -534,7 +544,7 @@ fn builder_cant_cover_bid() {
|
|||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
// Builder index 0 exists but bid value far exceeds their balance.
|
// Builder index 0 exists but bid value far exceeds their balance.
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -561,7 +571,7 @@ fn parent_block_root_unknown() {
|
|||||||
|
|
||||||
// Parent block root not in fork choice.
|
// Parent block root not in fork choice.
|
||||||
let unknown_root = Hash256::repeat_byte(0xff);
|
let unknown_root = Hash256::repeat_byte(0xff);
|
||||||
let bid = make_signed_bid(slot, 0, Address::ZERO, 30_000_000, 0, unknown_root);
|
let bid = ctx.make_signed_bid(slot, 0, Address::ZERO, 30_000_000, 0, unknown_root);
|
||||||
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
||||||
assert!(result.is_err(), "expected error, got Ok");
|
assert!(result.is_err(), "expected error, got Ok");
|
||||||
let err = result.unwrap_err();
|
let err = result.unwrap_err();
|
||||||
@@ -584,7 +594,7 @@ fn parent_block_root_not_canonical() {
|
|||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let fork_root = ctx.insert_non_canonical_block();
|
let fork_root = ctx.insert_non_canonical_block();
|
||||||
let bid = make_signed_bid(slot, 0, Address::ZERO, 30_000_000, 0, fork_root);
|
let bid = ctx.make_signed_bid(slot, 0, Address::ZERO, 30_000_000, 0, fork_root);
|
||||||
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
||||||
assert!(result.is_err(), "expected error, got Ok");
|
assert!(result.is_err(), "expected error, got Ok");
|
||||||
let err = result.unwrap_err();
|
let err = result.unwrap_err();
|
||||||
@@ -606,7 +616,7 @@ fn bid_slot_not_after_parent() {
|
|||||||
let slot = Slot::new(0);
|
let slot = Slot::new(0);
|
||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -649,6 +659,7 @@ fn invalid_blob_kzg_commitments() {
|
|||||||
gas_limit: 30_000_000,
|
gas_limit: 30_000_000,
|
||||||
value: 0,
|
value: 0,
|
||||||
parent_block_root: ctx.genesis_block_root,
|
parent_block_root: ctx.genesis_block_root,
|
||||||
|
prev_randao: ctx.expected_prev_randao(),
|
||||||
blob_kzg_commitments: VariableList::new(commitments).unwrap(),
|
blob_kzg_commitments: VariableList::new(commitments).unwrap(),
|
||||||
..ExecutionPayloadBid::default()
|
..ExecutionPayloadBid::default()
|
||||||
},
|
},
|
||||||
@@ -672,7 +683,7 @@ fn bad_signature() {
|
|||||||
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
seed_preferences(&ctx, slot, Address::ZERO, 30_000_000);
|
||||||
|
|
||||||
// All checks pass but signature is empty/invalid.
|
// All checks pass but signature is empty/invalid.
|
||||||
let bid = make_signed_bid(
|
let bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
0,
|
0,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -707,6 +718,7 @@ fn valid_bid() {
|
|||||||
gas_limit: 30_000_000,
|
gas_limit: 30_000_000,
|
||||||
value: 0,
|
value: 0,
|
||||||
parent_block_root: ctx.genesis_block_root,
|
parent_block_root: ctx.genesis_block_root,
|
||||||
|
prev_randao: ctx.expected_prev_randao(),
|
||||||
..ExecutionPayloadBid::default()
|
..ExecutionPayloadBid::default()
|
||||||
});
|
});
|
||||||
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
|
||||||
@@ -734,6 +746,7 @@ fn two_builders_coexist_in_cache() {
|
|||||||
gas_limit: 30_000_000,
|
gas_limit: 30_000_000,
|
||||||
value: 0,
|
value: 0,
|
||||||
parent_block_root: ctx.genesis_block_root,
|
parent_block_root: ctx.genesis_block_root,
|
||||||
|
prev_randao: ctx.expected_prev_randao(),
|
||||||
..ExecutionPayloadBid::default()
|
..ExecutionPayloadBid::default()
|
||||||
});
|
});
|
||||||
let result_0 = GossipVerifiedPayloadBid::new(bid_0, &gossip);
|
let result_0 = GossipVerifiedPayloadBid::new(bid_0, &gossip);
|
||||||
@@ -751,6 +764,7 @@ fn two_builders_coexist_in_cache() {
|
|||||||
gas_limit: 30_000_000,
|
gas_limit: 30_000_000,
|
||||||
value: 1,
|
value: 1,
|
||||||
parent_block_root: ctx.genesis_block_root,
|
parent_block_root: ctx.genesis_block_root,
|
||||||
|
prev_randao: ctx.expected_prev_randao(),
|
||||||
..ExecutionPayloadBid::default()
|
..ExecutionPayloadBid::default()
|
||||||
});
|
});
|
||||||
let result_1 = GossipVerifiedPayloadBid::new(bid_1, &gossip);
|
let result_1 = GossipVerifiedPayloadBid::new(bid_1, &gossip);
|
||||||
@@ -784,7 +798,7 @@ fn bid_equal_to_cached_value_rejected() {
|
|||||||
|
|
||||||
// Seed a cached bid with value 100.
|
// Seed a cached bid with value 100.
|
||||||
let high_bid = GossipVerifiedPayloadBid {
|
let high_bid = GossipVerifiedPayloadBid {
|
||||||
signed_bid: make_signed_bid(
|
signed_bid: ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
99,
|
99,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
@@ -796,7 +810,7 @@ fn bid_equal_to_cached_value_rejected() {
|
|||||||
ctx.bid_cache.insert_highest_bid(high_bid);
|
ctx.bid_cache.insert_highest_bid(high_bid);
|
||||||
|
|
||||||
// Submit a bid with exactly the same value — should be rejected.
|
// Submit a bid with exactly the same value — should be rejected.
|
||||||
let equal_bid = make_signed_bid(
|
let equal_bid = ctx.make_signed_bid(
|
||||||
slot,
|
slot,
|
||||||
1,
|
1,
|
||||||
Address::ZERO,
|
Address::ZERO,
|
||||||
|
|||||||
@@ -341,8 +341,13 @@ pub struct GetPayloadResponse<E: EthSpec> {
|
|||||||
pub blobs_bundle: BlobsBundle<E>,
|
pub blobs_bundle: BlobsBundle<E>,
|
||||||
#[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))]
|
#[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))]
|
||||||
pub should_override_builder: bool,
|
pub should_override_builder: bool,
|
||||||
#[superstruct(only(Electra, Fulu, Gloas))]
|
#[superstruct(
|
||||||
pub requests: ExecutionRequests<E>,
|
only(Electra, Fulu),
|
||||||
|
partial_getter(rename = "execution_requests_electra")
|
||||||
|
)]
|
||||||
|
pub requests: types::ExecutionRequestsElectra<E>,
|
||||||
|
#[superstruct(only(Gloas), partial_getter(rename = "execution_requests_gloas"))]
|
||||||
|
pub requests: types::ExecutionRequestsGloas<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: EthSpec> GetPayloadResponse<E> {
|
impl<E: EthSpec> GetPayloadResponse<E> {
|
||||||
@@ -407,19 +412,19 @@ impl<E: EthSpec> From<GetPayloadResponse<E>>
|
|||||||
ExecutionPayload::Electra(inner.execution_payload),
|
ExecutionPayload::Electra(inner.execution_payload),
|
||||||
inner.block_value,
|
inner.block_value,
|
||||||
Some(inner.blobs_bundle),
|
Some(inner.blobs_bundle),
|
||||||
Some(inner.requests),
|
Some(ExecutionRequests::Electra(inner.requests)),
|
||||||
),
|
),
|
||||||
GetPayloadResponse::Fulu(inner) => (
|
GetPayloadResponse::Fulu(inner) => (
|
||||||
ExecutionPayload::Fulu(inner.execution_payload),
|
ExecutionPayload::Fulu(inner.execution_payload),
|
||||||
inner.block_value,
|
inner.block_value,
|
||||||
Some(inner.blobs_bundle),
|
Some(inner.blobs_bundle),
|
||||||
Some(inner.requests),
|
Some(ExecutionRequests::Electra(inner.requests)),
|
||||||
),
|
),
|
||||||
GetPayloadResponse::Gloas(inner) => (
|
GetPayloadResponse::Gloas(inner) => (
|
||||||
ExecutionPayload::Gloas(inner.execution_payload),
|
ExecutionPayload::Gloas(inner.execution_payload),
|
||||||
inner.block_value,
|
inner.block_value,
|
||||||
Some(inner.blobs_bundle),
|
Some(inner.blobs_bundle),
|
||||||
Some(inner.requests),
|
Some(ExecutionRequests::Gloas(inner.requests)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,6 +474,7 @@ pub enum RequestsError {
|
|||||||
InvalidOrdering,
|
InvalidOrdering,
|
||||||
InvalidPrefix(u8),
|
InvalidPrefix(u8),
|
||||||
DecodeError(String),
|
DecodeError(String),
|
||||||
|
VariantMismatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format of `ExecutionRequests` received over the engine api.
|
/// Format of `ExecutionRequests` received over the engine api.
|
||||||
@@ -606,6 +607,28 @@ impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequestsElectra<E> {
|
||||||
|
type Error = RequestsError;
|
||||||
|
|
||||||
|
fn try_from(value: JsonExecutionRequests) -> Result<Self, Self::Error> {
|
||||||
|
match ExecutionRequests::<E>::try_from(value)? {
|
||||||
|
ExecutionRequests::Electra(requests) => Ok(requests),
|
||||||
|
ExecutionRequests::Gloas(_) => Err(RequestsError::VariantMismatch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequestsGloas<E> {
|
||||||
|
type Error = RequestsError;
|
||||||
|
|
||||||
|
fn try_from(value: JsonExecutionRequests) -> Result<Self, Self::Error> {
|
||||||
|
match ExecutionRequests::<E>::try_from(value)? {
|
||||||
|
ExecutionRequests::Gloas(requests) => Ok(requests),
|
||||||
|
ExecutionRequests::Electra(_) => Err(RequestsError::VariantMismatch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[superstruct(
|
#[superstruct(
|
||||||
variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas),
|
variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas),
|
||||||
variant_attributes(
|
variant_attributes(
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ use tokio::{
|
|||||||
use tokio_stream::wrappers::WatchStream;
|
use tokio_stream::wrappers::WatchStream;
|
||||||
use tracing::{Instrument, debug, debug_span, error, info, instrument, warn};
|
use tracing::{Instrument, debug, debug_span, error, info, instrument, warn};
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::ExecutionPayloadGloas;
|
|
||||||
use types::builder::BuilderBid;
|
use types::builder::BuilderBid;
|
||||||
use types::execution::BlockProductionVersion;
|
use types::execution::BlockProductionVersion;
|
||||||
use types::kzg_ext::KzgCommitments;
|
use types::kzg_ext::KzgCommitments;
|
||||||
@@ -56,6 +55,7 @@ use types::{
|
|||||||
ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload,
|
ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload,
|
||||||
ProposerPreparationData, Slot,
|
ProposerPreparationData, Slot,
|
||||||
};
|
};
|
||||||
|
use types::{ExecutionPayloadGloas, ExecutionRequestsGloas};
|
||||||
|
|
||||||
mod block_hash;
|
mod block_hash;
|
||||||
mod engine_api;
|
mod engine_api;
|
||||||
@@ -206,7 +206,7 @@ pub struct BlockProposalContentsGloas<E: EthSpec> {
|
|||||||
pub payload_value: Uint256,
|
pub payload_value: Uint256,
|
||||||
pub blob_kzg_commitments: KzgCommitments<E>,
|
pub blob_kzg_commitments: KzgCommitments<E>,
|
||||||
pub blobs_and_proofs: (BlobsList<E>, KzgProofs<E>),
|
pub blobs_and_proofs: (BlobsList<E>, KzgProofs<E>),
|
||||||
pub execution_requests: ExecutionRequests<E>,
|
pub execution_requests: ExecutionRequestsGloas<E>,
|
||||||
pub should_override_builder: bool,
|
pub should_override_builder: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -594,15 +594,113 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The custody-column coupling tests below build Fulu data-column sidecars directly, which is
|
/// Returns true when `FORK_NAME` schedules Gloas at genesis. Used to make the custody-column
|
||||||
/// incompatible with a Gloas genesis (Gloas columns have a different structure). Skip them when
|
/// coupling tests fork-aware: under Gloas the columns are coupled into the payload envelope, so
|
||||||
/// `FORK_NAME` schedules Gloas at genesis. TODO(gloas): port the harness to build Gloas columns.
|
/// these tests build Gloas blocks/columns/envelopes and complete the payloads request.
|
||||||
fn skip_under_gloas() -> bool {
|
fn is_gloas_env() -> bool {
|
||||||
test_spec::<E>()
|
test_spec::<E>()
|
||||||
.fork_name_at_epoch(Epoch::new(0))
|
.fork_name_at_epoch(Epoch::new(0))
|
||||||
.gloas_enabled()
|
.gloas_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The fork to build blocks/columns for in the custody-column coupling tests. Under a Gloas
|
||||||
|
/// genesis we must build Gloas columns (and matching envelopes); otherwise we use Fulu.
|
||||||
|
fn custody_test_fork() -> ForkName {
|
||||||
|
if is_gloas_env() {
|
||||||
|
ForkName::Gloas
|
||||||
|
} else {
|
||||||
|
ForkName::Fulu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A spec with custody-column (PeerDAS) coupling enabled at genesis, matching the env fork.
|
||||||
|
/// Under a Gloas env this enables Gloas at genesis (so envelopes are coupled); otherwise it
|
||||||
|
/// enables Fulu at genesis.
|
||||||
|
fn custody_test_spec() -> ChainSpec {
|
||||||
|
let mut spec = test_spec::<E>();
|
||||||
|
spec.deneb_fork_epoch = Some(Epoch::new(0));
|
||||||
|
spec.fulu_fork_epoch = Some(Epoch::new(0));
|
||||||
|
if is_gloas_env() {
|
||||||
|
spec.gloas_fork_epoch = Some(Epoch::new(0));
|
||||||
|
}
|
||||||
|
spec
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A block, its data columns, and (under Gloas) its matching payload envelope.
|
||||||
|
type BlockColumnsEnvelope = (
|
||||||
|
Arc<SignedBeaconBlock<E>>,
|
||||||
|
DataColumnSidecarList<E>,
|
||||||
|
Option<Arc<SignedExecutionPayloadEnvelope<E>>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Builds `count` blocks with their data columns, plus a matching payload envelope under Gloas.
|
||||||
|
/// Under Fulu the envelope is `None`.
|
||||||
|
fn make_blocks_and_columns(
|
||||||
|
count: usize,
|
||||||
|
num_blobs: NumBlobs,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Vec<BlockColumnsEnvelope> {
|
||||||
|
let fork = custody_test_fork();
|
||||||
|
let mut u = types::test_utils::test_unstructured();
|
||||||
|
(0..count)
|
||||||
|
.map(|_| {
|
||||||
|
// `NumBlobs` isn't `Clone`, so rebuild a fresh value for each block.
|
||||||
|
let num_blobs = match &num_blobs {
|
||||||
|
NumBlobs::Random => NumBlobs::Random,
|
||||||
|
NumBlobs::Number(n) => NumBlobs::Number(*n),
|
||||||
|
NumBlobs::None => NumBlobs::None,
|
||||||
|
};
|
||||||
|
let (block, data_columns) =
|
||||||
|
generate_rand_block_and_data_columns::<E>(fork, num_blobs, &mut u, spec)
|
||||||
|
.unwrap();
|
||||||
|
let block = Arc::new(block);
|
||||||
|
let envelope = is_gloas_env().then(|| matching_envelope(&block));
|
||||||
|
(block, data_columns, envelope)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Under Gloas, completes the payloads request with the envelopes from `blocks`. Under Fulu this
|
||||||
|
/// is a no-op (there is no payloads request). Pass the subset of blocks whose envelopes should
|
||||||
|
/// be supplied.
|
||||||
|
fn add_envelopes_if_gloas(
|
||||||
|
info: &mut RangeBlockComponentsRequest<E>,
|
||||||
|
payloads_req_id: Option<PayloadEnvelopesByRangeRequestId>,
|
||||||
|
blocks: &[BlockColumnsEnvelope],
|
||||||
|
) {
|
||||||
|
if let Some(payloads_req_id) = payloads_req_id {
|
||||||
|
info.add_payload_envelopes(
|
||||||
|
payloads_req_id,
|
||||||
|
blocks
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, _, envelope)| envelope.clone())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts the coupled `responses` carry the expected data. Pre-Gloas only the count is checked;
|
||||||
|
/// under Gloas each block must additionally wrap an envelope holding `expected_columns` columns.
|
||||||
|
fn assert_custody_columns_coupled(
|
||||||
|
responses: &[RangeSyncBlock<E>],
|
||||||
|
expected_blocks: usize,
|
||||||
|
expected_columns: usize,
|
||||||
|
) {
|
||||||
|
assert_eq!(responses.len(), expected_blocks);
|
||||||
|
if is_gloas_env() {
|
||||||
|
for response in responses {
|
||||||
|
match response {
|
||||||
|
RangeSyncBlock::Gloas {
|
||||||
|
envelope: Some(envelope),
|
||||||
|
..
|
||||||
|
} => assert_eq!(envelope.columns.len(), expected_columns),
|
||||||
|
other => panic!("expected Gloas block with envelope, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn blocks_id(parent_request_id: ComponentsByRangeRequestId) -> BlocksByRangeRequestId {
|
fn blocks_id(parent_request_id: ComponentsByRangeRequestId) -> BlocksByRangeRequestId {
|
||||||
BlocksByRangeRequestId {
|
BlocksByRangeRequestId {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -798,38 +896,33 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_blobs_into_responses() {
|
fn no_blobs_into_responses() {
|
||||||
// This exercises the pre-Gloas blobs/no-data coupling path. Gloas coupling is covered
|
// Coupling of blocks that carry no data. Pre-Gloas there is simply no data request; under
|
||||||
// by the dedicated `setup_gloas_coupling` tests below.
|
// Gloas each block still couples to its (empty-column) payload envelope, so the envelope
|
||||||
if skip_under_gloas() {
|
// request is driven too.
|
||||||
return;
|
let spec = Arc::new(custody_test_spec());
|
||||||
}
|
|
||||||
let spec = Arc::new(test_spec::<E>());
|
|
||||||
|
|
||||||
let mut u = types::test_utils::test_unstructured();
|
|
||||||
let blocks = (0..4)
|
|
||||||
.map(|_| {
|
|
||||||
generate_rand_block_and_blobs::<E>(
|
|
||||||
spec.fork_name_at_epoch(Epoch::new(0)),
|
|
||||||
NumBlobs::None,
|
|
||||||
&mut u,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.0
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect::<Vec<Arc<SignedBeaconBlock<E>>>>();
|
|
||||||
|
|
||||||
let blocks_req_id = blocks_id(components_id());
|
|
||||||
let mut info =
|
|
||||||
RangeBlockComponentsRequest::<E>::new(blocks_req_id, None, None, None, Span::none());
|
|
||||||
|
|
||||||
// Send blocks and complete terminate response
|
|
||||||
info.add_blocks(blocks_req_id, blocks).unwrap();
|
|
||||||
|
|
||||||
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
||||||
|
let blocks = make_blocks_and_columns(4, NumBlobs::None, &spec);
|
||||||
|
|
||||||
// Assert response is finished and RpcBlocks can be constructed
|
let components_id = components_id();
|
||||||
info.responses(da_checker, spec).unwrap().unwrap();
|
let blocks_req_id = blocks_id(components_id);
|
||||||
|
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
|
||||||
|
let mut info = RangeBlockComponentsRequest::<E>::new(
|
||||||
|
blocks_req_id,
|
||||||
|
None,
|
||||||
|
is_gloas_env().then(|| (vec![], vec![])),
|
||||||
|
payloads_req_id,
|
||||||
|
Span::none(),
|
||||||
|
);
|
||||||
|
|
||||||
|
info.add_blocks(
|
||||||
|
blocks_req_id,
|
||||||
|
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
|
||||||
|
|
||||||
|
let responses = info.responses(da_checker, spec).unwrap().unwrap();
|
||||||
|
assert_custody_columns_coupled(&responses, blocks.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -875,33 +968,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_block_with_custody_columns() {
|
fn rpc_block_with_custody_columns() {
|
||||||
if skip_under_gloas() {
|
let spec = Arc::new(custody_test_spec());
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut spec = test_spec::<E>();
|
|
||||||
spec.deneb_fork_epoch = Some(Epoch::new(0));
|
|
||||||
spec.fulu_fork_epoch = Some(Epoch::new(0));
|
|
||||||
let spec = Arc::new(spec);
|
|
||||||
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
||||||
let expects_custody_columns = da_checker
|
let expects_custody_columns = da_checker
|
||||||
.custody_context()
|
.custody_context()
|
||||||
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let mut u = types::test_utils::test_unstructured();
|
let blocks = make_blocks_and_columns(4, NumBlobs::Number(1), &spec);
|
||||||
let blocks = (0..4)
|
|
||||||
.map(|_| {
|
|
||||||
generate_rand_block_and_data_columns::<E>(
|
|
||||||
ForkName::Fulu,
|
|
||||||
NumBlobs::Number(1),
|
|
||||||
&mut u,
|
|
||||||
&spec,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let components_id = components_id();
|
let components_id = components_id();
|
||||||
let blocks_req_id = blocks_id(components_id);
|
let blocks_req_id = blocks_id(components_id);
|
||||||
|
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
|
||||||
let columns_req_id = expects_custody_columns
|
let columns_req_id = expects_custody_columns
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -919,13 +996,13 @@ mod tests {
|
|||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
None,
|
None,
|
||||||
Some((columns_req_id.clone(), expects_custody_columns.clone())),
|
Some((columns_req_id.clone(), expects_custody_columns.clone())),
|
||||||
None,
|
payloads_req_id,
|
||||||
Span::none(),
|
Span::none(),
|
||||||
);
|
);
|
||||||
// Send blocks and complete terminate response
|
// Send blocks and complete terminate response
|
||||||
info.add_blocks(
|
info.add_blocks(
|
||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
blocks.iter().map(|b| b.0.clone().into()).collect(),
|
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Assert response is not finished
|
// Assert response is not finished
|
||||||
@@ -938,7 +1015,12 @@ mod tests {
|
|||||||
*req,
|
*req,
|
||||||
blocks
|
blocks
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|b| b.1.iter().filter(|d| *d.index() == column_index).cloned())
|
.flat_map(|(_, columns, _)| {
|
||||||
|
columns
|
||||||
|
.iter()
|
||||||
|
.filter(|d| *d.index() == column_index)
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -951,19 +1033,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Under Gloas the columns are coupled into the payload envelope; supply the envelopes so
|
||||||
|
// the request can complete.
|
||||||
|
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
|
||||||
|
|
||||||
// All completed construct response
|
// All completed construct response
|
||||||
info.responses(da_checker, spec).unwrap().unwrap();
|
let responses = info.responses(da_checker, spec).unwrap().unwrap();
|
||||||
|
assert_custody_columns_coupled(&responses, blocks.len(), expects_custody_columns.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_block_with_custody_columns_batched() {
|
fn rpc_block_with_custody_columns_batched() {
|
||||||
if skip_under_gloas() {
|
let spec = Arc::new(custody_test_spec());
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut spec = test_spec::<E>();
|
|
||||||
spec.deneb_fork_epoch = Some(Epoch::new(0));
|
|
||||||
spec.fulu_fork_epoch = Some(Epoch::new(0));
|
|
||||||
let spec = Arc::new(spec);
|
|
||||||
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
||||||
let expected_sampling_columns = da_checker
|
let expected_sampling_columns = da_checker
|
||||||
.custody_context()
|
.custody_context()
|
||||||
@@ -981,6 +1062,7 @@ mod tests {
|
|||||||
|
|
||||||
let components_id = components_id();
|
let components_id = components_id();
|
||||||
let blocks_req_id = blocks_id(components_id);
|
let blocks_req_id = blocks_id(components_id);
|
||||||
|
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
|
||||||
let columns_req_id = batched_column_requests
|
let columns_req_id = batched_column_requests
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -999,27 +1081,16 @@ mod tests {
|
|||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
None,
|
None,
|
||||||
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
||||||
None,
|
payloads_req_id,
|
||||||
Span::none(),
|
Span::none(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut u = types::test_utils::test_unstructured();
|
let blocks = make_blocks_and_columns(4, NumBlobs::Number(1), &spec);
|
||||||
let blocks = (0..4)
|
|
||||||
.map(|_| {
|
|
||||||
generate_rand_block_and_data_columns::<E>(
|
|
||||||
ForkName::Fulu,
|
|
||||||
NumBlobs::Number(1),
|
|
||||||
&mut u,
|
|
||||||
&spec,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Send blocks and complete terminate response
|
// Send blocks and complete terminate response
|
||||||
info.add_blocks(
|
info.add_blocks(
|
||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
blocks.iter().map(|b| b.0.clone().into()).collect(),
|
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Assert response is not finished
|
// Assert response is not finished
|
||||||
@@ -1032,8 +1103,9 @@ mod tests {
|
|||||||
*req,
|
*req,
|
||||||
blocks
|
blocks
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|b| {
|
.flat_map(|(_, columns, _)| {
|
||||||
b.1.iter()
|
columns
|
||||||
|
.iter()
|
||||||
.filter(|d| column_indices.contains(d.index()))
|
.filter(|d| column_indices.contains(d.index()))
|
||||||
.cloned()
|
.cloned()
|
||||||
})
|
})
|
||||||
@@ -1049,8 +1121,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Under Gloas the columns are coupled into the payload envelope; supply the envelopes so
|
||||||
|
// the request can complete.
|
||||||
|
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
|
||||||
|
|
||||||
// All completed construct response
|
// All completed construct response
|
||||||
info.responses(da_checker, spec).unwrap().unwrap();
|
let responses = info.responses(da_checker, spec).unwrap().unwrap();
|
||||||
|
assert_custody_columns_coupled(&responses, blocks.len(), expected_sampling_columns.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1164,31 +1241,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_custody_columns_from_faulty_peers() {
|
fn missing_custody_columns_from_faulty_peers() {
|
||||||
if skip_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// GIVEN: A request expecting sampling columns from multiple peers
|
// GIVEN: A request expecting sampling columns from multiple peers
|
||||||
let spec = Arc::new(test_spec::<E>());
|
let spec = Arc::new(custody_test_spec());
|
||||||
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
||||||
let expected_sampling_columns = da_checker
|
let expected_sampling_columns = da_checker
|
||||||
.custody_context()
|
.custody_context()
|
||||||
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let mut u = types::test_utils::test_unstructured();
|
let blocks = make_blocks_and_columns(2, NumBlobs::Number(1), &spec);
|
||||||
let blocks = (0..2)
|
|
||||||
.map(|_| {
|
|
||||||
generate_rand_block_and_data_columns::<E>(
|
|
||||||
ForkName::Fulu,
|
|
||||||
NumBlobs::Number(1),
|
|
||||||
&mut u,
|
|
||||||
&spec,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let components_id = components_id();
|
let components_id = components_id();
|
||||||
let blocks_req_id = blocks_id(components_id);
|
let blocks_req_id = blocks_id(components_id);
|
||||||
|
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
|
||||||
let columns_req_id = expected_sampling_columns
|
let columns_req_id = expected_sampling_columns
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -1206,16 +1270,19 @@ mod tests {
|
|||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
None,
|
None,
|
||||||
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
||||||
None,
|
payloads_req_id,
|
||||||
Span::none(),
|
Span::none(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// AND: All blocks are received successfully
|
// AND: All blocks are received successfully
|
||||||
info.add_blocks(
|
info.add_blocks(
|
||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
blocks.iter().map(|b| b.0.clone().into()).collect(),
|
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
// Under Gloas the payloads request must be completed for `responses` to proceed; the
|
||||||
|
// faulty-peer detection happens before the envelope wrap and is fork-independent.
|
||||||
|
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
|
||||||
|
|
||||||
// AND: Only the first 2 sampling columns are received successfully
|
// AND: Only the first 2 sampling columns are received successfully
|
||||||
for (i, &column_index) in expected_sampling_columns.iter().take(2).enumerate() {
|
for (i, &column_index) in expected_sampling_columns.iter().take(2).enumerate() {
|
||||||
@@ -1224,7 +1291,12 @@ mod tests {
|
|||||||
*req,
|
*req,
|
||||||
blocks
|
blocks
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|b| b.1.iter().filter(|d| *d.index() == column_index).cloned())
|
.flat_map(|(_, columns, _)| {
|
||||||
|
columns
|
||||||
|
.iter()
|
||||||
|
.filter(|d| *d.index() == column_index)
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1263,34 +1335,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn retry_logic_after_peer_failures() {
|
fn retry_logic_after_peer_failures() {
|
||||||
if skip_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// GIVEN: A request expecting sampling columns where some peers initially fail
|
// GIVEN: A request expecting sampling columns where some peers initially fail
|
||||||
let mut spec = test_spec::<E>();
|
let spec = Arc::new(custody_test_spec());
|
||||||
spec.deneb_fork_epoch = Some(Epoch::new(0));
|
|
||||||
spec.fulu_fork_epoch = Some(Epoch::new(0));
|
|
||||||
let spec = Arc::new(spec);
|
|
||||||
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
||||||
let expected_sampling_columns = da_checker
|
let expected_sampling_columns = da_checker
|
||||||
.custody_context()
|
.custody_context()
|
||||||
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let mut u = types::test_utils::test_unstructured();
|
let blocks = make_blocks_and_columns(2, NumBlobs::Number(1), &spec);
|
||||||
let blocks = (0..2)
|
|
||||||
.map(|_| {
|
|
||||||
generate_rand_block_and_data_columns::<E>(
|
|
||||||
ForkName::Fulu,
|
|
||||||
NumBlobs::Number(1),
|
|
||||||
&mut u,
|
|
||||||
&spec,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let components_id = components_id();
|
let components_id = components_id();
|
||||||
let blocks_req_id = blocks_id(components_id);
|
let blocks_req_id = blocks_id(components_id);
|
||||||
|
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
|
||||||
let columns_req_id = expected_sampling_columns
|
let columns_req_id = expected_sampling_columns
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -1308,16 +1364,18 @@ mod tests {
|
|||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
None,
|
None,
|
||||||
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
||||||
None,
|
payloads_req_id,
|
||||||
Span::none(),
|
Span::none(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// AND: All blocks are received
|
// AND: All blocks are received
|
||||||
info.add_blocks(
|
info.add_blocks(
|
||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
blocks.iter().map(|b| b.0.clone().into()).collect(),
|
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
// Under Gloas the payloads request must be completed for `responses` to proceed.
|
||||||
|
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
|
||||||
|
|
||||||
// AND: Only partial sampling columns are received (first column but not others)
|
// AND: Only partial sampling columns are received (first column but not others)
|
||||||
let (req0, _) = columns_req_id.first().unwrap();
|
let (req0, _) = columns_req_id.first().unwrap();
|
||||||
@@ -1325,8 +1383,9 @@ mod tests {
|
|||||||
*req0,
|
*req0,
|
||||||
blocks
|
blocks
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|b| {
|
.flat_map(|(_, columns, _)| {
|
||||||
b.1.iter()
|
columns
|
||||||
|
.iter()
|
||||||
.filter(|d| *d.index() == expected_sampling_columns[0])
|
.filter(|d| *d.index() == expected_sampling_columns[0])
|
||||||
.cloned()
|
.cloned()
|
||||||
})
|
})
|
||||||
@@ -1363,8 +1422,9 @@ mod tests {
|
|||||||
new_columns_req_id,
|
new_columns_req_id,
|
||||||
blocks
|
blocks
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|b| {
|
.flat_map(|(_, columns, _)| {
|
||||||
b.1.iter()
|
columns
|
||||||
|
.iter()
|
||||||
.filter(|d| failed_column_indices.contains(d.index()))
|
.filter(|d| failed_column_indices.contains(d.index()))
|
||||||
.cloned()
|
.cloned()
|
||||||
})
|
})
|
||||||
@@ -1383,34 +1443,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn max_retries_exceeded_behavior() {
|
fn max_retries_exceeded_behavior() {
|
||||||
if skip_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// GIVEN: A request where peers consistently fail to provide required columns
|
// GIVEN: A request where peers consistently fail to provide required columns
|
||||||
let mut spec = test_spec::<E>();
|
let spec = Arc::new(custody_test_spec());
|
||||||
spec.deneb_fork_epoch = Some(Epoch::new(0));
|
|
||||||
spec.fulu_fork_epoch = Some(Epoch::new(0));
|
|
||||||
let spec = Arc::new(spec);
|
|
||||||
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
|
||||||
let expected_sampling_columns = da_checker
|
let expected_sampling_columns = da_checker
|
||||||
.custody_context()
|
.custody_context()
|
||||||
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
.sampling_columns_for_epoch(Epoch::new(0), &spec)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let mut u = types::test_utils::test_unstructured();
|
let blocks = make_blocks_and_columns(1, NumBlobs::Number(1), &spec);
|
||||||
let blocks = (0..1)
|
|
||||||
.map(|_| {
|
|
||||||
generate_rand_block_and_data_columns::<E>(
|
|
||||||
ForkName::Fulu,
|
|
||||||
NumBlobs::Number(1),
|
|
||||||
&mut u,
|
|
||||||
&spec,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let components_id = components_id();
|
let components_id = components_id();
|
||||||
let blocks_req_id = blocks_id(components_id);
|
let blocks_req_id = blocks_id(components_id);
|
||||||
|
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
|
||||||
let columns_req_id = expected_sampling_columns
|
let columns_req_id = expected_sampling_columns
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -1428,16 +1472,18 @@ mod tests {
|
|||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
None,
|
None,
|
||||||
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
|
||||||
None,
|
payloads_req_id,
|
||||||
Span::none(),
|
Span::none(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// AND: All blocks are received
|
// AND: All blocks are received
|
||||||
info.add_blocks(
|
info.add_blocks(
|
||||||
blocks_req_id,
|
blocks_req_id,
|
||||||
blocks.iter().map(|b| b.0.clone().into()).collect(),
|
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
// Under Gloas the payloads request must be completed for `responses` to proceed.
|
||||||
|
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
|
||||||
|
|
||||||
// AND: Only the first sampling column is provided successfully
|
// AND: Only the first sampling column is provided successfully
|
||||||
let (req0, _) = columns_req_id.first().unwrap();
|
let (req0, _) = columns_req_id.first().unwrap();
|
||||||
@@ -1445,8 +1491,9 @@ mod tests {
|
|||||||
*req0,
|
*req0,
|
||||||
blocks
|
blocks
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|b| {
|
.flat_map(|(_, columns, _)| {
|
||||||
b.1.iter()
|
columns
|
||||||
|
.iter()
|
||||||
.filter(|d| *d.index() == expected_sampling_columns[0])
|
.filter(|d| *d.index() == expected_sampling_columns[0])
|
||||||
.cloned()
|
.cloned()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ use types::{
|
|||||||
|
|
||||||
const D: Duration = Duration::new(0, 0);
|
const D: Duration = Duration::new(0, 0);
|
||||||
|
|
||||||
|
/// Extract the Gloas payload envelope (if any) carried by a stored `RangeSyncBlock`.
|
||||||
|
fn envelope_of(block: &RangeSyncBlock<E>) -> Option<Arc<SignedExecutionPayloadEnvelope<E>>> {
|
||||||
|
match block {
|
||||||
|
RangeSyncBlock::Gloas {
|
||||||
|
envelope: Some(envelope),
|
||||||
|
..
|
||||||
|
} => Some(envelope.envelope().clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gloas genesis needs enough validators to populate `proposer_lookahead`.
|
/// Gloas genesis needs enough validators to populate `proposer_lookahead`.
|
||||||
const TEST_RIG_VALIDATOR_COUNT: usize = 8;
|
const TEST_RIG_VALIDATOR_COUNT: usize = 8;
|
||||||
|
|
||||||
@@ -331,7 +342,6 @@ impl TestRig {
|
|||||||
fork_name,
|
fork_name,
|
||||||
network_blocks_by_root: <_>::default(),
|
network_blocks_by_root: <_>::default(),
|
||||||
network_blocks_by_slot: <_>::default(),
|
network_blocks_by_slot: <_>::default(),
|
||||||
network_envelopes_by_root: <_>::default(),
|
|
||||||
penalties: <_>::default(),
|
penalties: <_>::default(),
|
||||||
seen_lookups: <_>::default(),
|
seen_lookups: <_>::default(),
|
||||||
requests: <_>::default(),
|
requests: <_>::default(),
|
||||||
@@ -669,7 +679,10 @@ impl TestRig {
|
|||||||
if self.complete_strategy.hold_envelope_for_block == Some(block_root) {
|
if self.complete_strategy.hold_envelope_for_block == Some(block_root) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let envelope = self.network_envelopes_by_root.get(&block_root).cloned();
|
let envelope = self
|
||||||
|
.network_blocks_by_root
|
||||||
|
.get(&block_root)
|
||||||
|
.and_then(envelope_of);
|
||||||
self.send_rpc_envelope_response(req_id, peer_id, envelope);
|
self.send_rpc_envelope_response(req_id, peer_id, envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,7 +856,7 @@ impl TestRig {
|
|||||||
if self.complete_strategy.hold_envelope_for_block == Some(block_root) {
|
if self.complete_strategy.hold_envelope_for_block == Some(block_root) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.network_envelopes_by_root.get(&block_root).cloned()
|
envelope_of(block)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.send_rpc_envelopes_response(req_id, peer_id, &envelopes);
|
self.send_rpc_envelopes_response(req_id, peer_id, &envelopes);
|
||||||
@@ -1057,13 +1070,7 @@ impl TestRig {
|
|||||||
.await;
|
.await;
|
||||||
let block = external_harness.get_full_block(&block_root);
|
let block = external_harness.get_full_block(&block_root);
|
||||||
let block_slot = block.slot();
|
let block_slot = block.slot();
|
||||||
self.insert_external_block(
|
self.insert_external_block(block);
|
||||||
block,
|
|
||||||
external_harness
|
|
||||||
.chain
|
|
||||||
.get_payload_envelope(&block_root)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
blocks.push((block_slot, block_root));
|
blocks.push((block_slot, block_root));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1171,8 +1178,7 @@ impl TestRig {
|
|||||||
// Cache every block through the single `get_full_block` + `insert_external_block2` path.
|
// Cache every block through the single `get_full_block` + `insert_external_block2` path.
|
||||||
for root in [g_root, a_root, c_root, b_root] {
|
for root in [g_root, a_root, c_root, b_root] {
|
||||||
let block = external_harness.get_full_block(&root);
|
let block = external_harness.get_full_block(&root);
|
||||||
let envelope = external_harness.chain.get_payload_envelope(&root).unwrap();
|
self.insert_external_block(block);
|
||||||
self.insert_external_block(block, envelope);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.harness.set_current_slot(child_slot);
|
self.harness.set_current_slot(child_slot);
|
||||||
@@ -1200,21 +1206,12 @@ impl TestRig {
|
|||||||
Some((r, fork))
|
Some((r, fork))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_external_block(
|
fn insert_external_block(&mut self, block: RangeSyncBlock<E>) {
|
||||||
&mut self,
|
|
||||||
block: RangeSyncBlock<E>,
|
|
||||||
envelope: Option<SignedExecutionPayloadEnvelope<E>>,
|
|
||||||
) {
|
|
||||||
let block_root = block.canonical_root();
|
let block_root = block.canonical_root();
|
||||||
let block_slot = block.slot();
|
let block_slot = block.slot();
|
||||||
self.network_blocks_by_root
|
self.network_blocks_by_root
|
||||||
.insert(block_root, block.clone());
|
.insert(block_root, block.clone());
|
||||||
self.network_blocks_by_slot.insert(block_slot, block);
|
self.network_blocks_by_slot.insert(block_slot, block);
|
||||||
// Cache Gloas envelopes for lookup RPCs.
|
|
||||||
if let Some(envelope) = envelope {
|
|
||||||
self.network_envelopes_by_root
|
|
||||||
.insert(block_root, envelope.into());
|
|
||||||
}
|
|
||||||
self.log(&format!(
|
self.log(&format!(
|
||||||
"Produced block {block_root:?} slot {block_slot} in external harness",
|
"Produced block {block_root:?} slot {block_slot} in external harness",
|
||||||
));
|
));
|
||||||
@@ -1300,9 +1297,9 @@ impl TestRig {
|
|||||||
let range_sync_block = if block.fork_name_unchecked().gloas_enabled() {
|
let range_sync_block = if block.fork_name_unchecked().gloas_enabled() {
|
||||||
// Gloas carries data columns in the payload envelope, not in `block_data`.
|
// Gloas carries data columns in the payload envelope, not in `block_data`.
|
||||||
let envelope = self
|
let envelope = self
|
||||||
.network_envelopes_by_root
|
.network_blocks_by_root
|
||||||
.get(&block_root)
|
.get(&block_root)
|
||||||
.cloned()
|
.and_then(envelope_of)
|
||||||
.map(|envelope| AvailableEnvelope::new(envelope, columns.unwrap_or_default()));
|
.map(|envelope| AvailableEnvelope::new(envelope, columns.unwrap_or_default()));
|
||||||
RangeSyncBlock::new_gloas(block, envelope).unwrap()
|
RangeSyncBlock::new_gloas(block, envelope).unwrap()
|
||||||
} else {
|
} else {
|
||||||
@@ -1370,6 +1367,8 @@ impl TestRig {
|
|||||||
.unwrap_or_else(|| panic!("No block at slot {slot}"))
|
.unwrap_or_else(|| panic!("No block at slot {slot}"))
|
||||||
.clone();
|
.clone();
|
||||||
let block_root = rpc_block.canonical_root();
|
let block_root = rpc_block.canonical_root();
|
||||||
|
let block_state_root = rpc_block.as_block().state_root();
|
||||||
|
let envelope = envelope_of(&rpc_block);
|
||||||
self.harness
|
self.harness
|
||||||
.chain
|
.chain
|
||||||
.process_block(
|
.process_block(
|
||||||
@@ -1381,6 +1380,18 @@ impl TestRig {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
// Gloas: import the payload envelope so the block counts as full for its children.
|
||||||
|
if let Some(envelope) = envelope {
|
||||||
|
let state = self
|
||||||
|
.harness
|
||||||
|
.chain
|
||||||
|
.get_state(&block_state_root, Some(Slot::new(slot)), false)
|
||||||
|
.expect("should load state")
|
||||||
|
.expect("state should exist");
|
||||||
|
self.harness
|
||||||
|
.process_envelope(block_root, (*envelope).clone(), &state, block_state_root)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.harness.chain.recompute_head_at_current_slot().await;
|
self.harness.chain.recompute_head_at_current_slot().await;
|
||||||
}
|
}
|
||||||
@@ -1495,6 +1506,17 @@ impl TestRig {
|
|||||||
panic!("Some downscore events: {:?}", self.penalties);
|
panic!("Some downscore events: {:?}", self.penalties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_no_block_requests(&self) {
|
||||||
|
assert_eq!(
|
||||||
|
self.requests
|
||||||
|
.iter()
|
||||||
|
.filter(|(request, _)| matches!(request, RequestType::BlocksByRoot(_)))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
Vec::<&(RequestType<E>, AppRequestId)>::new(),
|
||||||
|
"There should be no block requests"
|
||||||
|
);
|
||||||
|
}
|
||||||
fn assert_failed_lookup_sync(&mut self) {
|
fn assert_failed_lookup_sync(&mut self) {
|
||||||
assert!(self.created_lookups() > 0, "no created lookups");
|
assert!(self.created_lookups() > 0, "no created lookups");
|
||||||
assert_eq!(self.completed_lookups(), 0, "some completed lookups");
|
assert_eq!(self.completed_lookups(), 0, "some completed lookups");
|
||||||
@@ -1623,6 +1645,10 @@ impl TestRig {
|
|||||||
genesis_fork().fulu_enabled().then(Self::default)
|
genesis_fork().fulu_enabled().then(Self::default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_after_gloas() -> Option<Self> {
|
||||||
|
genesis_fork().gloas_enabled().then(Self::default)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_fulu_peer_test(fulu_test_type: FuluTestType) -> Option<Self> {
|
pub fn new_fulu_peer_test(fulu_test_type: FuluTestType) -> Option<Self> {
|
||||||
genesis_fork().fulu_enabled().then(|| {
|
genesis_fork().fulu_enabled().then(|| {
|
||||||
Self::new(TestRigConfig {
|
Self::new(TestRigConfig {
|
||||||
@@ -2129,7 +2155,8 @@ async fn happy_path_unknown_data_parent(depth: usize) {
|
|||||||
let Some(mut r) = TestRig::new_after_fulu() else {
|
let Some(mut r) = TestRig::new_after_fulu() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// No unknown-parent data-column trigger post-Gloas.
|
// Fulu-only: the `UnknownDataColumnParent` trigger doesn't exist post-Gloas (columns ride in
|
||||||
|
// the payload envelope, not as standalone data columns).
|
||||||
if r.is_after_gloas() {
|
if r.is_after_gloas() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2359,10 +2386,6 @@ async fn test_single_block_lookup_ignored_response() {
|
|||||||
/// Assert that if the beacon processor returns DuplicateFullyImported, the lookup completes successfully
|
/// Assert that if the beacon processor returns DuplicateFullyImported, the lookup completes successfully
|
||||||
async fn test_single_block_lookup_duplicate_response() {
|
async fn test_single_block_lookup_duplicate_response() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
// The mock only covers block processing; Gloas also needs real envelope/column results.
|
|
||||||
if r.is_after_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.build_chain_and_trigger_last_block(1).await;
|
r.build_chain_and_trigger_last_block(1).await;
|
||||||
// Send a DuplicateFullyImported response, the lookup should complete successfully
|
// Send a DuplicateFullyImported response, the lookup should complete successfully
|
||||||
r.simulate(
|
r.simulate(
|
||||||
@@ -2427,10 +2450,6 @@ async fn lookups_form_chain() {
|
|||||||
/// Assert that if a lookup chain (by appending ancestors) is too long we drop it
|
/// Assert that if a lookup chain (by appending ancestors) is too long we drop it
|
||||||
async fn test_parent_lookup_too_deep_grow_ancestor_one() {
|
async fn test_parent_lookup_too_deep_grow_ancestor_one() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
// TODO(gloas): range sync does not fetch payload envelopes yet.
|
|
||||||
if r.is_after_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.build_chain(PARENT_DEPTH_TOLERANCE + 1).await;
|
r.build_chain(PARENT_DEPTH_TOLERANCE + 1).await;
|
||||||
r.trigger_with_last_block();
|
r.trigger_with_last_block();
|
||||||
r.simulate(SimulateConfig::happy_path()).await;
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
@@ -2575,13 +2594,13 @@ async fn test_same_chain_race_condition() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
/// Assert that if the lookup's block is in the da_checker we don't download it again
|
/// Assert that if the lookup's block is in the da_checker we don't download it again (pre-Gloas).
|
||||||
async fn block_in_da_checker_skips_download() {
|
async fn block_in_da_checker_skips_download_fulu() {
|
||||||
// Only post-Fulu, as the block needs custody columns to remain in the da_checker
|
// Only post-Fulu, as the block needs custody columns to remain in the da_checker.
|
||||||
let Some(mut r) = TestRig::new_after_fulu() else {
|
let Some(mut r) = TestRig::new_after_fulu() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// TODO(gloas): the helper does not populate the envelope missing-component path yet.
|
// Pre-Gloas only; the Gloas equivalent is `block_in_da_checker_skips_download_gloas`.
|
||||||
if r.is_after_gloas() {
|
if r.is_after_gloas() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2594,14 +2613,28 @@ async fn block_in_da_checker_skips_download() {
|
|||||||
r.trigger_with_block_at_slot(1);
|
r.trigger_with_block_at_slot(1);
|
||||||
r.simulate(SimulateConfig::happy_path()).await;
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
r.assert_successful_lookup_sync();
|
r.assert_successful_lookup_sync();
|
||||||
assert_eq!(
|
r.assert_no_block_requests();
|
||||||
r.requests
|
}
|
||||||
.iter()
|
|
||||||
.filter(|(request, _)| matches!(request, RequestType::BlocksByRoot(_)))
|
#[tokio::test]
|
||||||
.collect::<Vec<_>>(),
|
/// Assert that if the lookup's block is in the da_checker we don't download it again (Gloas).
|
||||||
Vec::<&(RequestType<E>, AppRequestId)>::new(),
|
async fn block_in_da_checker_skips_download_gloas() {
|
||||||
"There should be no block requests"
|
let Some(mut r) = TestRig::new_after_gloas() else {
|
||||||
);
|
return;
|
||||||
|
};
|
||||||
|
// A Gloas block carries no inline DA, so a lone block never sits in the da_checker awaiting
|
||||||
|
// components: only a FULL *child* proves the block published a payload and supplies the peers
|
||||||
|
// that serve its columns/envelope. Build a parent + FULL child, insert the PARENT into the
|
||||||
|
// da_checker, then trigger via the child (which is provided by the trigger, not downloaded).
|
||||||
|
// The parent lookup must then skip the parent's block download.
|
||||||
|
r.build_chain(2).await;
|
||||||
|
let parent = r.block_at_slot(1);
|
||||||
|
let child = r.block_at_slot(2);
|
||||||
|
r.import_block_to_da_checker(parent).await;
|
||||||
|
r.trigger_unknown_parent_blocks_from_all_peers(&[child]);
|
||||||
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
|
r.assert_successful_lookup_sync();
|
||||||
|
r.assert_no_block_requests();
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! fulu_peer_matrix_tests {
|
macro_rules! fulu_peer_matrix_tests {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use tokio::sync::mpsc;
|
|||||||
use tracing_subscriber::fmt::MakeWriter;
|
use tracing_subscriber::fmt::MakeWriter;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use types::{ForkName, Hash256, MinimalEthSpec as E, SignedExecutionPayloadEnvelope, Slot};
|
use types::{ForkName, Hash256, MinimalEthSpec as E, Slot};
|
||||||
|
|
||||||
mod lookups;
|
mod lookups;
|
||||||
mod range;
|
mod range;
|
||||||
@@ -77,10 +77,6 @@ struct TestRig {
|
|||||||
/// Blocks that will be used in the test but may not be known to `harness` yet.
|
/// Blocks that will be used in the test but may not be known to `harness` yet.
|
||||||
network_blocks_by_root: HashMap<Hash256, RangeSyncBlock<E>>,
|
network_blocks_by_root: HashMap<Hash256, RangeSyncBlock<E>>,
|
||||||
network_blocks_by_slot: HashMap<Slot, RangeSyncBlock<E>>,
|
network_blocks_by_slot: HashMap<Slot, RangeSyncBlock<E>>,
|
||||||
/// Gloas execution payload envelopes keyed by block root, populated during `build_chain`
|
|
||||||
/// from the external harness store. The rig serves these when a lookup issues a
|
|
||||||
/// `PayloadEnvelopesByRoot` request.
|
|
||||||
network_envelopes_by_root: HashMap<Hash256, Arc<SignedExecutionPayloadEnvelope<E>>>,
|
|
||||||
penalties: Vec<ReportedPenalty>,
|
penalties: Vec<ReportedPenalty>,
|
||||||
/// All seen lookups through the test run
|
/// All seen lookups through the test run
|
||||||
seen_lookups: HashMap<Id, SeenLookup>,
|
seen_lookups: HashMap<Id, SeenLookup>,
|
||||||
|
|||||||
@@ -34,13 +34,6 @@ use types::{Epoch, EthSpec, Hash256, MinimalEthSpec as E, Slot};
|
|||||||
const SLOTS_PER_EPOCH: usize = 8;
|
const SLOTS_PER_EPOCH: usize = 8;
|
||||||
|
|
||||||
impl TestRig {
|
impl TestRig {
|
||||||
/// Range sync doesn't yet ingest Gloas blocks in these tests: the range harness doesn't serve
|
|
||||||
/// payload envelopes, so a Gloas block never becomes fully available and sync can't complete.
|
|
||||||
/// Skip the affected completion tests under a Gloas genesis. TODO(gloas): support range sync.
|
|
||||||
fn skip_range_sync_under_gloas(&self) -> bool {
|
|
||||||
self.fork_name.gloas_enabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_head_peer(&mut self) -> PeerId {
|
fn add_head_peer(&mut self) -> PeerId {
|
||||||
let local_info = self.local_info();
|
let local_info = self.local_info();
|
||||||
self.add_supernode_peer(SyncInfo {
|
self.add_supernode_peer(SyncInfo {
|
||||||
@@ -267,9 +260,6 @@ impl TestRig {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn head_sync_completes() {
|
async fn head_sync_completes() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_head_sync().await;
|
r.setup_head_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path()).await;
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
r.assert_head_sync_completed();
|
r.assert_head_sync_completed();
|
||||||
@@ -281,9 +271,6 @@ async fn head_sync_completes() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn finalized_to_head_transition() {
|
async fn finalized_to_head_transition() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_and_head_sync().await;
|
r.setup_finalized_and_head_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path()).await;
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
r.assert_range_sync_completed();
|
r.assert_range_sync_completed();
|
||||||
@@ -295,9 +282,6 @@ async fn finalized_to_head_transition() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn finalized_sync_completes() {
|
async fn finalized_sync_completes() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_sync().await;
|
r.setup_finalized_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path()).await;
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
r.assert_range_sync_completed();
|
r.assert_range_sync_completed();
|
||||||
@@ -309,9 +293,6 @@ async fn finalized_sync_completes() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn batch_rpc_error_retries() {
|
async fn batch_rpc_error_retries() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_sync().await;
|
r.setup_finalized_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path().return_rpc_error(RPCError::UnsupportedProtocol))
|
r.simulate(SimulateConfig::happy_path().return_rpc_error(RPCError::UnsupportedProtocol))
|
||||||
.await;
|
.await;
|
||||||
@@ -380,9 +361,6 @@ async fn batch_peer_returns_partial_columns_then_succeeds() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn batch_non_faulty_failure_retries() {
|
async fn batch_non_faulty_failure_retries() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_sync().await;
|
r.setup_finalized_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path().with_range_non_faulty_failures(1))
|
r.simulate(SimulateConfig::happy_path().with_range_non_faulty_failures(1))
|
||||||
.await;
|
.await;
|
||||||
@@ -394,9 +372,6 @@ async fn batch_non_faulty_failure_retries() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn batch_faulty_failure_redownloads() {
|
async fn batch_faulty_failure_redownloads() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_sync().await;
|
r.setup_finalized_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path().with_range_faulty_failures(1))
|
r.simulate(SimulateConfig::happy_path().with_range_faulty_failures(1))
|
||||||
.await;
|
.await;
|
||||||
@@ -453,9 +428,6 @@ async fn late_response_for_removed_chain() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ee_offline_then_online_resumes_sync() {
|
async fn ee_offline_then_online_resumes_sync() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_sync().await;
|
r.setup_finalized_sync().await;
|
||||||
r.simulate(SimulateConfig::happy_path().with_ee_offline_for_n_range_responses(2))
|
r.simulate(SimulateConfig::happy_path().with_ee_offline_for_n_range_responses(2))
|
||||||
.await;
|
.await;
|
||||||
@@ -468,9 +440,6 @@ async fn ee_offline_then_online_resumes_sync() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn finalized_sync_with_local_head_partial() {
|
async fn finalized_sync_with_local_head_partial() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
r.setup_finalized_sync_with_local_head(3).await;
|
r.setup_finalized_sync_with_local_head(3).await;
|
||||||
r.simulate(SimulateConfig::happy_path()).await;
|
r.simulate(SimulateConfig::happy_path()).await;
|
||||||
r.assert_range_sync_completed();
|
r.assert_range_sync_completed();
|
||||||
@@ -481,9 +450,6 @@ async fn finalized_sync_with_local_head_partial() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn finalized_sync_with_local_head_near_target() {
|
async fn finalized_sync_with_local_head_near_target() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if r.skip_range_sync_under_gloas() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let target_epochs = 5;
|
let target_epochs = 5;
|
||||||
let local_slots = (target_epochs * SLOTS_PER_EPOCH) - 1; // all blocks except last
|
let local_slots = (target_epochs * SLOTS_PER_EPOCH) - 1; // all blocks except last
|
||||||
r.build_chain(target_epochs * SLOTS_PER_EPOCH).await;
|
r.build_chain(target_epochs * SLOTS_PER_EPOCH).await;
|
||||||
@@ -502,7 +468,7 @@ async fn finalized_sync_with_local_head_near_target() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn not_enough_custody_peers_then_peers_arrive() {
|
async fn not_enough_custody_peers_then_peers_arrive() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if !r.fork_name.fulu_enabled() || r.skip_range_sync_under_gloas() {
|
if !r.fork_name.fulu_enabled() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let remote_info = r.setup_finalized_sync_insufficient_peers().await;
|
let remote_info = r.setup_finalized_sync_insufficient_peers().await;
|
||||||
@@ -529,7 +495,7 @@ async fn not_enough_custody_peers_then_peers_arrive() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn finalized_sync_not_enough_custody_peers_resume_after_peer_cgc_update() {
|
async fn finalized_sync_not_enough_custody_peers_resume_after_peer_cgc_update() {
|
||||||
let mut r = TestRig::default();
|
let mut r = TestRig::default();
|
||||||
if !r.fork_name.fulu_enabled() || r.skip_range_sync_under_gloas() {
|
if !r.fork_name.fulu_enabled() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ impl GenericExecutionEngine for GethEngine {
|
|||||||
.arg(http_auth_port.to_string())
|
.arg(http_auth_port.to_string())
|
||||||
.arg("--port")
|
.arg("--port")
|
||||||
.arg(network_port.to_string())
|
.arg(network_port.to_string())
|
||||||
.arg("--allow-insecure-unlock")
|
|
||||||
.arg("--authrpc.jwtsecret")
|
.arg("--authrpc.jwtsecret")
|
||||||
.arg(jwt_secret_path.as_path().to_str().unwrap())
|
.arg(jwt_secret_path.as_path().to_str().unwrap())
|
||||||
// This flag is required to help Geth perform reliably when feeding it blocks
|
// This flag is required to help Geth perform reliably when feeding it blocks
|
||||||
|
|||||||
Reference in New Issue
Block a user