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:
Michael Sproul
2024-09-24 14:52:44 +10:00
committed by GitHub
parent 1447eeb40b
commit 2792705331
21 changed files with 1071 additions and 516 deletions

View File

@@ -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");

View File

@@ -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;

View File

@@ -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;