mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 05:44:44 +00:00
Lenient duplicate checks on HTTP API for block publication (#5574)
* start splitting gossip verification * WIP * Gossip verify separate (#7) * save * save * make ProvenancedBlock concrete * delete into gossip verified block contents * get rid of IntoBlobSidecar trait * remove IntoGossipVerified trait * get tests compiling * don't check sidecar slashability in publish * remove second publish closure * drop blob bool. also prefer using message index over index of position in list * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Fix low-hanging tests * Fix tests and clean up * Clean up imports * more cleanup * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Further refine behaviour and add tests * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Remove empty line * Fix test (block is not fully imported just gossip verified) * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Update for unstable & use empty blob list * Update comment * Add test for duplicate block case * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Clarify unreachable case * Fix another publish_block case * Remove unreachable case in filter chain segment * Revert unrelated blob optimisation * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Fix merge conflicts * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Fix some compilation issues. Impl is fucked though * Support peerDAS * Fix tests * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Fix conflict * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate * Address review comments * Merge remote-tracking branch 'origin/unstable' into gossip-verify-separate
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
use beacon_chain::blob_verification::GossipVerifiedBlob;
|
||||
use beacon_chain::{
|
||||
test_utils::{AttestationStrategy, BlockStrategy},
|
||||
GossipVerifiedBlock, IntoGossipVerifiedBlockContents,
|
||||
GossipVerifiedBlock, IntoGossipVerifiedBlock,
|
||||
};
|
||||
use eth2::reqwest::StatusCode;
|
||||
use eth2::types::{BroadcastValidation, PublishBlockRequest};
|
||||
use http_api::test_utils::InteractiveTester;
|
||||
use http_api::{publish_blinded_block, publish_block, reconstruct_block, ProvenancedBlock};
|
||||
use http_api::{publish_blinded_block, publish_block, reconstruct_block, Config, ProvenancedBlock};
|
||||
use std::sync::Arc;
|
||||
use types::{Epoch, EthSpec, FixedBytesExtended, ForkName, Hash256, MainnetEthSpec, Slot};
|
||||
use types::{
|
||||
BlobSidecar, Epoch, EthSpec, FixedBytesExtended, ForkName, Hash256, MainnetEthSpec, Slot,
|
||||
};
|
||||
use warp::Rejection;
|
||||
use warp_utils::reject::CustomBadRequest;
|
||||
|
||||
@@ -81,7 +84,7 @@ pub async fn gossip_invalid() {
|
||||
/* mandated by Beacon API spec */
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: BlockError(NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 })".to_string());
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 }".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is valid from a gossip perspective is accepted when using `broadcast_validation=gossip`.
|
||||
@@ -266,7 +269,7 @@ pub async fn consensus_invalid() {
|
||||
|
||||
/* mandated by Beacon API spec */
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: BlockError(NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 })".to_string());
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 }".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is only valid from a gossip perspective is rejected when using `broadcast_validation=consensus`.
|
||||
@@ -360,10 +363,9 @@ pub async fn consensus_partial_pass_only_consensus() {
|
||||
);
|
||||
assert_ne!(block_a.state_root(), block_b.state_root());
|
||||
|
||||
let gossip_block_contents_b = PublishBlockRequest::new(block_b, blobs_b)
|
||||
.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_contents_b.is_ok());
|
||||
let gossip_block_a = GossipVerifiedBlock::new(block_a.clone().into(), &tester.harness.chain);
|
||||
let gossip_block_b = block_b.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_b.is_ok());
|
||||
let gossip_block_a = block_a.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_a.is_err());
|
||||
|
||||
/* submit `block_b` which should induce equivocation */
|
||||
@@ -372,7 +374,7 @@ pub async fn consensus_partial_pass_only_consensus() {
|
||||
|
||||
let publication_result = publish_block(
|
||||
None,
|
||||
ProvenancedBlock::local(gossip_block_contents_b.unwrap()),
|
||||
ProvenancedBlock::local(gossip_block_b.unwrap(), blobs_b),
|
||||
tester.harness.chain.clone(),
|
||||
&channel.0,
|
||||
test_logger,
|
||||
@@ -382,7 +384,7 @@ pub async fn consensus_partial_pass_only_consensus() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(publication_result.is_ok());
|
||||
assert!(publication_result.is_ok(), "{publication_result:?}");
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
@@ -481,7 +483,7 @@ pub async fn equivocation_invalid() {
|
||||
|
||||
/* mandated by Beacon API spec */
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: BlockError(NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 })".to_string());
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 }".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is valid from both a gossip and consensus perspective is rejected when using `broadcast_validation=consensus_and_equivocation`.
|
||||
@@ -555,10 +557,7 @@ pub async fn equivocation_consensus_early_equivocation() {
|
||||
let error_response: eth2::Error = response.err().unwrap();
|
||||
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(
|
||||
error_response,
|
||||
"BAD_REQUEST: BlockError(Slashable)".to_string(),
|
||||
);
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: Slashable".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is only valid from a gossip perspective is rejected when using `broadcast_validation=consensus_and_equivocation`.
|
||||
@@ -642,7 +641,7 @@ pub async fn equivocation_consensus_late_equivocation() {
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block_a, blobs_a), mut state_after_a) =
|
||||
let ((block_a, _blobs_a), mut state_after_a) =
|
||||
tester.harness.make_block(state_a.clone(), slot_b).await;
|
||||
let ((block_b, blobs_b), mut state_after_b) = tester.harness.make_block(state_a, slot_b).await;
|
||||
|
||||
@@ -657,19 +656,18 @@ pub async fn equivocation_consensus_late_equivocation() {
|
||||
);
|
||||
assert_ne!(block_a.state_root(), block_b.state_root());
|
||||
|
||||
let gossip_block_contents_b = PublishBlockRequest::new(block_b, blobs_b)
|
||||
.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_contents_b.is_ok());
|
||||
let gossip_block_contents_a = PublishBlockRequest::new(block_a, blobs_a)
|
||||
.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_contents_a.is_err());
|
||||
let gossip_block_b = block_b.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_b.is_ok());
|
||||
|
||||
let gossip_block_a = block_a.into_gossip_verified_block(&tester.harness.chain);
|
||||
assert!(gossip_block_a.is_err());
|
||||
|
||||
let channel = tokio::sync::mpsc::unbounded_channel();
|
||||
let network_globals = tester.ctx.network_globals.clone().unwrap();
|
||||
|
||||
let publication_result = publish_block(
|
||||
None,
|
||||
ProvenancedBlock::local(gossip_block_contents_b.unwrap()),
|
||||
ProvenancedBlock::local(gossip_block_b.unwrap(), blobs_b),
|
||||
tester.harness.chain,
|
||||
&channel.0,
|
||||
test_logger,
|
||||
@@ -686,8 +684,8 @@ pub async fn equivocation_consensus_late_equivocation() {
|
||||
assert!(publication_error.find::<CustomBadRequest>().is_some());
|
||||
|
||||
assert_eq!(
|
||||
*publication_error.find::<CustomBadRequest>().unwrap().0,
|
||||
"proposal for this slot and proposer has already been seen".to_string()
|
||||
publication_error.find::<CustomBadRequest>().unwrap().0,
|
||||
"proposal for this slot and proposer has already been seen"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -783,7 +781,7 @@ pub async fn blinded_gossip_invalid() {
|
||||
|
||||
/* mandated by Beacon API spec */
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: BlockError(NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 })".to_string());
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 }".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is valid from a gossip perspective is accepted when using `broadcast_validation=gossip`.
|
||||
@@ -961,7 +959,7 @@ pub async fn blinded_consensus_invalid() {
|
||||
|
||||
/* mandated by Beacon API spec */
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: BlockError(NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 })".to_string());
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 }".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is only valid from a gossip perspective is rejected when using `broadcast_validation=consensus`.
|
||||
@@ -1099,7 +1097,7 @@ pub async fn blinded_equivocation_invalid() {
|
||||
|
||||
/* mandated by Beacon API spec */
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: BlockError(NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 })".to_string());
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: NotFinalizedDescendant { block_parent_root: 0x0000000000000000000000000000000000000000000000000000000000000000 }".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is valid from both a gossip and consensus perspective is rejected when using `broadcast_validation=consensus_and_equivocation`.
|
||||
@@ -1169,10 +1167,7 @@ pub async fn blinded_equivocation_consensus_early_equivocation() {
|
||||
let error_response: eth2::Error = response.err().unwrap();
|
||||
|
||||
assert_eq!(error_response.status(), Some(StatusCode::BAD_REQUEST));
|
||||
assert_server_message_error(
|
||||
error_response,
|
||||
"BAD_REQUEST: BlockError(Slashable)".to_string(),
|
||||
);
|
||||
assert_server_message_error(error_response, "BAD_REQUEST: Slashable".to_string());
|
||||
}
|
||||
|
||||
/// This test checks that a block that is only valid from a gossip perspective is rejected when using `broadcast_validation=consensus_and_equivocation`.
|
||||
@@ -1295,19 +1290,17 @@ pub async fn blinded_equivocation_consensus_late_equivocation() {
|
||||
.unwrap();
|
||||
|
||||
let inner_block_a = match unblinded_block_a {
|
||||
ProvenancedBlock::Local(a, _) => a,
|
||||
ProvenancedBlock::Builder(a, _) => a,
|
||||
ProvenancedBlock::Local(a, _, _) => a,
|
||||
ProvenancedBlock::Builder(a, _, _) => a,
|
||||
};
|
||||
let inner_block_b = match unblinded_block_b {
|
||||
ProvenancedBlock::Local(b, _) => b,
|
||||
ProvenancedBlock::Builder(b, _) => b,
|
||||
ProvenancedBlock::Local(b, _, _) => b,
|
||||
ProvenancedBlock::Builder(b, _, _) => b,
|
||||
};
|
||||
|
||||
let gossip_block_b =
|
||||
GossipVerifiedBlock::new(inner_block_b.clone().deconstruct().0, &tester.harness.chain);
|
||||
let gossip_block_b = GossipVerifiedBlock::new(inner_block_b, &tester.harness.chain);
|
||||
assert!(gossip_block_b.is_ok());
|
||||
let gossip_block_a =
|
||||
GossipVerifiedBlock::new(inner_block_a.clone().deconstruct().0, &tester.harness.chain);
|
||||
let gossip_block_a = GossipVerifiedBlock::new(inner_block_a, &tester.harness.chain);
|
||||
assert!(gossip_block_a.is_err());
|
||||
|
||||
let channel = tokio::sync::mpsc::unbounded_channel();
|
||||
@@ -1374,6 +1367,438 @@ pub async fn blinded_equivocation_full_pass() {
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that an HTTP POST request with the block & blobs succeeds with a 200 response
|
||||
/// even if the block has already been seen on gossip without any blobs.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn block_seen_on_gossip_without_blobs() {
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
||||
let tester = InteractiveTester::<E>::new(Some(spec), validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block, blobs), _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
let blobs = blobs.expect("should have some blobs");
|
||||
assert_ne!(blobs.0.len(), 0);
|
||||
|
||||
// Simulate the block being seen on gossip.
|
||||
block
|
||||
.clone()
|
||||
.into_gossip_verified_block(&tester.harness.chain)
|
||||
.unwrap();
|
||||
|
||||
// It should not yet be added to fork choice because blobs have not been seen.
|
||||
assert!(!tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
|
||||
// Post the block *and* blobs to the HTTP API.
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(
|
||||
&PublishBlockRequest::new(block.clone(), Some(blobs)),
|
||||
validation_level,
|
||||
)
|
||||
.await;
|
||||
|
||||
// This should result in the block being fully imported.
|
||||
response.unwrap();
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that an HTTP POST request with the block & blobs succeeds with a 200 response
|
||||
/// even if the block has already been seen on gossip without all blobs.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn block_seen_on_gossip_with_some_blobs() {
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
||||
let tester = InteractiveTester::<E>::new(Some(spec), validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block, blobs), _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
let blobs = blobs.expect("should have some blobs");
|
||||
assert!(
|
||||
blobs.0.len() >= 2,
|
||||
"need at least 2 blobs for partial reveal"
|
||||
);
|
||||
|
||||
let partial_kzg_proofs = vec![blobs.0.get(0).unwrap().clone()];
|
||||
let partial_blobs = vec![blobs.1.get(0).unwrap().clone()];
|
||||
|
||||
// Simulate the block being seen on gossip.
|
||||
block
|
||||
.clone()
|
||||
.into_gossip_verified_block(&tester.harness.chain)
|
||||
.unwrap();
|
||||
|
||||
// Simulate some of the blobs being seen on gossip.
|
||||
for (i, (kzg_proof, blob)) in partial_kzg_proofs
|
||||
.into_iter()
|
||||
.zip(partial_blobs)
|
||||
.enumerate()
|
||||
{
|
||||
let sidecar = Arc::new(BlobSidecar::new(i, blob, &block, kzg_proof).unwrap());
|
||||
let gossip_blob =
|
||||
GossipVerifiedBlob::new(sidecar, i as u64, &tester.harness.chain).unwrap();
|
||||
tester
|
||||
.harness
|
||||
.chain
|
||||
.process_gossip_blob(gossip_blob, || panic!("should not publish block yet"))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// It should not yet be added to fork choice because all blobs have not been seen.
|
||||
assert!(!tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
|
||||
// Post the block *and* all blobs to the HTTP API.
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(
|
||||
&PublishBlockRequest::new(block.clone(), Some(blobs)),
|
||||
validation_level,
|
||||
)
|
||||
.await;
|
||||
|
||||
// This should result in the block being fully imported.
|
||||
response.unwrap();
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that an HTTP POST request with the block & blobs succeeds with a 200 response
|
||||
/// even if the blobs have already been seen on gossip.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn blobs_seen_on_gossip_without_block() {
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
||||
let tester = InteractiveTester::<E>::new(Some(spec), validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block, blobs), _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
let (kzg_proofs, blobs) = blobs.expect("should have some blobs");
|
||||
|
||||
// Simulate the blobs being seen on gossip.
|
||||
for (i, (kzg_proof, blob)) in kzg_proofs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.zip(blobs.clone())
|
||||
.enumerate()
|
||||
{
|
||||
let sidecar = Arc::new(BlobSidecar::new(i, blob, &block, kzg_proof).unwrap());
|
||||
let gossip_blob =
|
||||
GossipVerifiedBlob::new(sidecar, i as u64, &tester.harness.chain).unwrap();
|
||||
tester
|
||||
.harness
|
||||
.chain
|
||||
.process_gossip_blob(gossip_blob, || panic!("should not publish block yet"))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// It should not yet be added to fork choice because the block has not been seen.
|
||||
assert!(!tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
|
||||
// Post the block *and* all blobs to the HTTP API.
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(
|
||||
&PublishBlockRequest::new(block.clone(), Some((kzg_proofs, blobs))),
|
||||
validation_level,
|
||||
)
|
||||
.await;
|
||||
|
||||
// This should result in the block being fully imported.
|
||||
response.unwrap();
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that an HTTP POST request with the block succeeds with a 200 response
|
||||
/// if just the blobs have already been seen on gossip.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn blobs_seen_on_gossip_without_block_and_no_http_blobs() {
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
||||
let tester = InteractiveTester::<E>::new(Some(spec), validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block, blobs), _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
let (kzg_proofs, blobs) = blobs.expect("should have some blobs");
|
||||
assert!(!blobs.is_empty());
|
||||
|
||||
// Simulate the blobs being seen on gossip.
|
||||
for (i, (kzg_proof, blob)) in kzg_proofs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.zip(blobs.clone())
|
||||
.enumerate()
|
||||
{
|
||||
let sidecar = Arc::new(BlobSidecar::new(i, blob, &block, kzg_proof).unwrap());
|
||||
let gossip_blob =
|
||||
GossipVerifiedBlob::new(sidecar, i as u64, &tester.harness.chain).unwrap();
|
||||
tester
|
||||
.harness
|
||||
.chain
|
||||
.process_gossip_blob(gossip_blob, || panic!("should not publish block yet"))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// It should not yet be added to fork choice because the block has not been seen.
|
||||
assert!(!tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
|
||||
// Post just the block to the HTTP API (blob lists are empty).
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(
|
||||
&PublishBlockRequest::new(
|
||||
block.clone(),
|
||||
Some((Default::default(), Default::default())),
|
||||
),
|
||||
validation_level,
|
||||
)
|
||||
.await;
|
||||
|
||||
// This should result in the block being fully imported.
|
||||
response.unwrap();
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn slashable_blobs_seen_on_gossip_cause_failure() {
|
||||
let validation_level: Option<BroadcastValidation> =
|
||||
Some(BroadcastValidation::ConsensusAndEquivocation);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
||||
let tester = InteractiveTester::<E>::new(Some(spec), validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block_a, blobs_a), _) = tester.harness.make_block(state_a.clone(), slot_b).await;
|
||||
let ((block_b, blobs_b), _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
let (kzg_proofs_a, blobs_a) = blobs_a.expect("should have some blobs");
|
||||
let (kzg_proofs_b, blobs_b) = blobs_b.expect("should have some blobs");
|
||||
|
||||
// Simulate the blobs of block B being seen on gossip.
|
||||
for (i, (kzg_proof, blob)) in kzg_proofs_b.into_iter().zip(blobs_b).enumerate() {
|
||||
let sidecar = Arc::new(BlobSidecar::new(i, blob, &block_b, kzg_proof).unwrap());
|
||||
let gossip_blob =
|
||||
GossipVerifiedBlob::new(sidecar, i as u64, &tester.harness.chain).unwrap();
|
||||
tester
|
||||
.harness
|
||||
.chain
|
||||
.process_gossip_blob(gossip_blob, || panic!("should not publish block yet"))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// It should not yet be added to fork choice because block B has not been seen.
|
||||
assert!(!tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block_b.canonical_root()));
|
||||
|
||||
// Post block A *and* all its blobs to the HTTP API.
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(
|
||||
&PublishBlockRequest::new(block_a.clone(), Some((kzg_proofs_a, blobs_a))),
|
||||
validation_level,
|
||||
)
|
||||
.await;
|
||||
|
||||
// This should not result in block A being fully imported.
|
||||
response.unwrap_err();
|
||||
assert!(!tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block_a.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that an HTTP POST request with a duplicate block & blobs results in the
|
||||
/// `duplicate_status_code` being returned.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn duplicate_block_status_code() {
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
||||
let duplicate_block_status_code = StatusCode::IM_A_TEAPOT;
|
||||
let tester = InteractiveTester::<E>::new_with_initializer_and_mutator(
|
||||
Some(spec),
|
||||
validator_count,
|
||||
None,
|
||||
None,
|
||||
Config {
|
||||
duplicate_block_status_code,
|
||||
..Config::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let ((block, blobs), _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
let (kzg_proofs, blobs) = blobs.expect("should have some blobs");
|
||||
|
||||
// Post the block blobs to the HTTP API once.
|
||||
let block_request = PublishBlockRequest::new(block.clone(), Some((kzg_proofs, blobs)));
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(&block_request, validation_level)
|
||||
.await;
|
||||
|
||||
// This should result in the block being fully imported.
|
||||
response.unwrap();
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
|
||||
// Post again.
|
||||
let duplicate_response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2(&block_request, validation_level)
|
||||
.await;
|
||||
let err = duplicate_response.unwrap_err();
|
||||
assert_eq!(err.status().unwrap(), duplicate_block_status_code);
|
||||
}
|
||||
|
||||
fn assert_server_message_error(error_response: eth2::Error, expected_message: String) {
|
||||
let eth2::Error::ServerMessage(err) = error_response else {
|
||||
panic!("Not a eth2::Error::ServerMessage");
|
||||
|
||||
@@ -386,6 +386,7 @@ async fn bls_to_execution_changes_update_all_around_capella_fork() {
|
||||
.genesis_state_ephemeral_store(genesis_state)
|
||||
})),
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
.await;
|
||||
let harness = &tester.harness;
|
||||
|
||||
@@ -72,6 +72,7 @@ async fn state_by_root_pruned_from_fork_choice() {
|
||||
})
|
||||
})),
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -427,6 +428,7 @@ pub async fn proposer_boost_re_org_test(
|
||||
DisallowedReOrgOffsets::new::<E>(disallowed_offsets).unwrap(),
|
||||
)
|
||||
})),
|
||||
Default::default(),
|
||||
)
|
||||
.await;
|
||||
let harness = &tester.harness;
|
||||
|
||||
Reference in New Issue
Block a user