Add broadcast validation routes to Beacon Node HTTP API (#4316)

## Issue Addressed

 - #4293 
 - #4264 

## Proposed Changes

*Changes largely follow those suggested in the main issue*.

 - Add new routes to HTTP API
   - `post_beacon_blocks_v2`
   - `post_blinded_beacon_blocks_v2`
 - Add new routes to `BeaconNodeHttpClient`
   - `post_beacon_blocks_v2`
   - `post_blinded_beacon_blocks_v2`
 - Define new Eth2 common types
   - `BroadcastValidation`, enum representing the level of validation to apply to blocks prior to broadcast
   - `BroadcastValidationQuery`, the corresponding HTTP query string type for the above type
 - ~~Define `_checked` variants of both `publish_block` and `publish_blinded_block` that enforce a validation level at a type level~~
 - Add interactive tests to the `bn_http_api_tests` test target covering each validation level (to their own test module, `broadcast_validation_tests`)
   - `beacon/blocks`
       - `broadcast_validation=gossip`
         - Invalid (400)
         - Full Pass (200)
         - Partial Pass (202)
        - `broadcast_validation=consensus`
          - Invalid (400)
          - Only gossip (400)
          - Only consensus pass (i.e., equivocates) (200)
          - Full pass (200)
        - `broadcast_validation=consensus_and_equivocation`
          - Invalid (400)
          - Invalid due to early equivocation (400)
          - Only gossip (400)
          - Only consensus (400)
          - Pass (200)
   - `beacon/blinded_blocks`
       - `broadcast_validation=gossip`
         - Invalid (400)
         - Full Pass (200)
         - Partial Pass (202)
        - `broadcast_validation=consensus`
          - Invalid (400)
          - Only gossip (400)
          - ~~Only consensus pass (i.e., equivocates) (200)~~
          - Full pass (200)
        - `broadcast_validation=consensus_and_equivocation`
          - Invalid (400)
          - Invalid due to early equivocation (400)
          - Only gossip (400)
          - Only consensus (400)
          - Pass (200)
 - Add a new trait, `IntoGossipVerifiedBlock`, which allows type-level guarantees to be made as to gossip validity
 - Modify the structure of the `ObservedBlockProducers` cache from a `(slot, validator_index)` mapping to a `((slot, validator_index), block_root)` mapping
 - Modify `ObservedBlockProducers::proposer_has_been_observed` to return a `SeenBlock` rather than a boolean on success
 - Punish gossip peer (low) for submitting equivocating blocks
 - Rename `BlockError::SlashablePublish` to `BlockError::SlashableProposal`

## Additional Info

This PR contains changes that directly modify how blocks are verified within the client. For more context, consult [comments in-thread](https://github.com/sigp/lighthouse/pull/4316#discussion_r1234724202).


Co-authored-by: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Jack McPherson
2023-06-29 12:02:38 +00:00
co-authored by Michael Sproul
parent 23b06aa51e
commit 1aff082eea
22 changed files with 1963 additions and 181 deletions
@@ -351,6 +351,7 @@ async fn assert_invalid_signature(
snapshots[block_index].beacon_block.canonical_root(),
snapshots[block_index].beacon_block.clone(),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await;
assert!(
@@ -415,6 +416,7 @@ async fn invalid_signature_gossip_block() {
signed_block.canonical_root(),
Arc::new(signed_block),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await,
Err(BlockError::InvalidSignature)
@@ -727,6 +729,7 @@ async fn block_gossip_verification() {
gossip_verified.block_root,
gossip_verified,
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.expect("should import valid gossip verified block");
@@ -923,11 +926,7 @@ async fn block_gossip_verification() {
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await),
BlockError::RepeatProposal {
proposer,
slot,
}
if proposer == other_proposer && slot == block.message().slot()
BlockError::BlockIsAlreadyKnown,
),
"should register any valid signature against the proposer, even if the block failed later verification"
);
@@ -956,11 +955,7 @@ async fn block_gossip_verification() {
.await
.err()
.expect("should error when processing known block"),
BlockError::RepeatProposal {
proposer,
slot,
}
if proposer == block.message().proposer_index() && slot == block.message().slot()
BlockError::BlockIsAlreadyKnown
),
"the second proposal by this validator should be rejected"
);
@@ -998,6 +993,7 @@ async fn verify_block_for_gossip_slashing_detection() {
verified_block.block_root,
verified_block,
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
@@ -1037,6 +1033,7 @@ async fn verify_block_for_gossip_doppelganger_detection() {
verified_block.block_root,
verified_block,
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
@@ -1184,6 +1181,7 @@ async fn add_base_block_to_altair_chain() {
base_block.canonical_root(),
Arc::new(base_block.clone()),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.err()
@@ -1318,6 +1316,7 @@ async fn add_altair_block_to_base_chain() {
altair_block.canonical_root(),
Arc::new(altair_block.clone()),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.err()
@@ -697,6 +697,7 @@ async fn invalidates_all_descendants() {
fork_block.canonical_root(),
Arc::new(fork_block),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
@@ -793,6 +794,7 @@ async fn switches_heads() {
fork_block.canonical_root(),
Arc::new(fork_block),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
@@ -1046,7 +1048,9 @@ async fn invalid_parent() {
// Ensure the block built atop an invalid payload is invalid for import.
assert!(matches!(
rig.harness.chain.process_block(block.canonical_root(), block.clone(), NotifyExecutionLayer::Yes).await,
rig.harness.chain.process_block(block.canonical_root(), block.clone(), NotifyExecutionLayer::Yes,
|| Ok(()),
).await,
Err(BlockError::ParentExecutionPayloadInvalid { parent_root: invalid_root })
if invalid_root == parent_root
));
@@ -1332,7 +1336,12 @@ async fn build_optimistic_chain(
for block in blocks {
rig.harness
.chain
.process_block(block.canonical_root(), block, NotifyExecutionLayer::Yes)
.process_block(
block.canonical_root(),
block,
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
}
@@ -1892,6 +1901,7 @@ async fn recover_from_invalid_head_by_importing_blocks() {
fork_block.canonical_root(),
fork_block.clone(),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
@@ -2151,6 +2151,7 @@ async fn weak_subjectivity_sync() {
full_block.canonical_root(),
Arc::new(full_block),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
+1
View File
@@ -687,6 +687,7 @@ async fn run_skip_slot_test(skip_slots: u64) {
harness_a.chain.head_snapshot().beacon_block_root,
harness_a.chain.head_snapshot().beacon_block.clone(),
NotifyExecutionLayer::Yes,
|| Ok(())
)
.await
.unwrap(),