Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion beacon_node/lighthouse_network/src/peer_manager/peerdb.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::discovery::CombinedKey;
use crate::discovery::enr::PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY;
use crate::discovery::enr::{PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY, ZKVM_ENABLED_ENR_KEY};
use crate::{Enr, Gossipsub, PeerId, SyncInfo, metrics, multiaddr::Multiaddr, types::Subnet};
use itertools::Itertools;
use logging::crit;
Expand Down Expand Up @@ -799,6 +799,26 @@ impl<E: EthSpec> PeerDB<E> {
supernode: bool,
spec: &ChainSpec,
enr_key: CombinedKey,
) -> PeerId {
self.__add_connected_peer_with_opts_testing_only(supernode, false, spec, enr_key)
}

/// Updates the connection state with zkvm option. MUST ONLY BE USED IN TESTS.
pub fn __add_connected_zkvm_peer_testing_only(
&mut self,
spec: &ChainSpec,
enr_key: CombinedKey,
) -> PeerId {
self.__add_connected_peer_with_opts_testing_only(false, true, spec, enr_key)
}

/// Updates the connection state with options. MUST ONLY BE USED IN TESTS.
fn __add_connected_peer_with_opts_testing_only(
&mut self,
supernode: bool,
zkvm_enabled: bool,
spec: &ChainSpec,
enr_key: CombinedKey,
) -> PeerId {
let mut enr = Enr::builder().build(&enr_key).unwrap();
let peer_id = enr.peer_id();
Expand All @@ -812,6 +832,11 @@ impl<E: EthSpec> PeerDB<E> {
.expect("u64 can be encoded");
}

if zkvm_enabled {
enr.insert(ZKVM_ENABLED_ENR_KEY, &true, &enr_key)
.expect("bool can be encoded");
}

self.update_connection_state(
&peer_id,
NewConnectionState::Connected {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ impl<E: EthSpec> PeerInfo<E> {
/// Returns if the peer is subscribed to a given `Subnet` from the metadata attnets/syncnets field.
/// Also returns true if the peer is assigned to custody a given data column `Subnet` computed from the metadata `custody_group_count` field or ENR `cgc` field.
pub fn on_subnet_metadata(&self, subnet: &Subnet) -> bool {
// ExecutionProof capability is advertised via ENR zkvm flag, not metadata.
// Check this separately since it doesn't depend on metadata presence.
if let Subnet::ExecutionProof = subnet {
if let Some(enr) = self.enr.as_ref() {
return enr.zkvm_enabled();
}
return false;
}

if let Some(meta_data) = &self.meta_data {
match subnet {
Subnet::Attestation(id) => {
Expand All @@ -106,12 +115,7 @@ impl<E: EthSpec> PeerInfo<E> {
return self.is_assigned_to_custody_subnet(subnet_id);
}
Subnet::ExecutionProof => {
// ExecutionProof capability is advertised via ENR zkvm flag, not metadata
// A node cannot dynamically change what the support.
if let Some(enr) = self.enr.as_ref() {
return enr.zkvm_enabled();
}
return false;
unreachable!("zkvm flag is only in the ENR")
}
}
}
Expand Down
19 changes: 15 additions & 4 deletions beacon_node/network/src/sync/network_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use lighthouse_network::service::api_types::{
DataColumnsByRangeRequestId, DataColumnsByRangeRequester, DataColumnsByRootRequestId,
DataColumnsByRootRequester, Id, SingleLookupReqId, SyncRequestId,
};
use lighthouse_network::{Client, NetworkGlobals, PeerAction, PeerId, ReportSource};
use lighthouse_network::{Client, NetworkGlobals, PeerAction, PeerId, ReportSource, Subnet};
use lighthouse_tracing::{SPAN_OUTGOING_BLOCK_BY_ROOT_REQUEST, SPAN_OUTGOING_RANGE_REQUEST};
use parking_lot::RwLock;
pub use requests::LookupVerifyError;
Expand Down Expand Up @@ -1048,9 +1048,18 @@ impl<T: BeaconChainTypes> SyncNetworkContext<T> {
min_proofs_required: usize,
) -> Result<LookupRequestResult, RpcRequestSendError> {
let active_request_count_by_peer = self.active_request_count_by_peer();
let peers_db = self.network_globals().peers.read();

// Filter to only zkvm-enabled peers
let Some(peer_id) = lookup_peers
.read()
.iter()
.filter(|peer| {
peers_db
.peer_info(peer)
.map(|info| info.on_subnet_metadata(&Subnet::ExecutionProof))
.unwrap_or(false)
})
.map(|peer| {
(
// Prefer peers with less overall requests
Expand All @@ -1063,9 +1072,11 @@ impl<T: BeaconChainTypes> SyncNetworkContext<T> {
.min()
.map(|(_, _, peer)| *peer)
else {
return Ok(LookupRequestResult::Pending("no peers"));
return Ok(LookupRequestResult::Pending("no zkvm-enabled peers"));
};

drop(peers_db);

// Query DA checker for proofs we already have
let already_have = self
.chain
Expand Down Expand Up @@ -1124,8 +1135,8 @@ impl<T: BeaconChainTypes> SyncNetworkContext<T> {
self.execution_proofs_by_root_requests.insert(
id,
peer_id,
// Don't expect max responses since peer might not have all the proofs we need
false,
// Expect peer to provide all requested proofs - if they can't, penalize
true,
Comment on lines +1138 to +1139
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flagging this change

ExecutionProofsByRootRequestItems::new(request),
Span::none(),
);
Expand Down
90 changes: 20 additions & 70 deletions beacon_node/network/src/sync/tests/execution_proof_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn test_proof_lookup_happy_path() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Get execution payload hash from the block
let block_hash = block
Expand Down Expand Up @@ -62,7 +62,7 @@ fn test_proof_lookup_empty_response() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
Expand All @@ -79,7 +79,7 @@ fn test_proof_lookup_empty_response() {
rig.expect_penalty(peer_id, "NotEnoughResponsesReturned");

// Should retry with different peer
let _new_peer = rig.new_connected_peer();
let _new_peer = rig.new_connected_zkvm_peer();
rig.expect_proof_lookup_request(block_root);
}

Expand All @@ -92,7 +92,7 @@ fn test_proof_lookup_partial_response() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();
let block_hash = block
.message()
.body()
Expand Down Expand Up @@ -128,7 +128,7 @@ fn test_proof_lookup_partial_response() {
rig.expect_penalty(peer_id, "NotEnoughResponsesReturned");

// Should retry with another peer
let new_peer = rig.new_connected_peer();
let new_peer = rig.new_connected_zkvm_peer();
let retry_proof_id = rig.expect_proof_lookup_request(block_root);

// Complete with all proofs
Expand All @@ -148,54 +148,6 @@ fn test_proof_lookup_partial_response() {
rig.expect_no_active_lookups();
}

/// Test unrequested proof triggers penalization
#[test]
fn test_proof_lookup_unrequested_proof() {
Comment on lines -152 to -153
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove tests like this, all zkvm enabled nodes will have historical proofs moving forward

let Some(mut rig) = TestRig::test_setup_after_fulu_with_zkvm() else {
return;
};

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let block_hash = block
.message()
.body()
.execution_payload()
.ok()
.map(|p| p.execution_payload_ref().block_hash())
.unwrap_or_else(ExecutionBlockHash::zero);

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
let block_id = rig.expect_block_lookup_request(block_root);
rig.single_lookup_block_response(block_id, peer_id, Some(block.into()));
rig.expect_block_process(ResponseType::Block);

let proof_id = rig.expect_proof_lookup_request(block_root);

// Requested proofs 0, 1 but peer sends proofs 5 (unrequested)
let unrequested_proof = Arc::new(
ExecutionProof::new(
ExecutionProofId::new(5).unwrap(),
Slot::new(0),
block_hash,
block_root,
vec![1, 2, 3],
)
.unwrap(),
);

rig.single_lookup_proof_response(proof_id, peer_id, Some(unrequested_proof));

// Should penalize peer for sending unrequested data
rig.expect_penalty(peer_id, "UnrequestedProof");

// Should retry
let _new_peer = rig.new_connected_peer();
rig.expect_proof_lookup_request(block_root);
}

/// Test duplicate proofs triggers penalization
#[test]
fn test_proof_lookup_duplicate_proof() {
Expand All @@ -205,7 +157,7 @@ fn test_proof_lookup_duplicate_proof() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();
let block_hash = block
.message()
.body()
Expand Down Expand Up @@ -250,10 +202,10 @@ fn test_proof_lookup_duplicate_proof() {
rig.single_lookup_proof_response(proof_id, peer_id, Some(proof_0_b));

// Should penalize peer for duplicate proof
rig.expect_penalty(peer_id, "DuplicatedProof");
rig.expect_penalty(peer_id, "DuplicatedProofIDs");

// Should retry
let _new_peer = rig.new_connected_peer();
let _new_peer = rig.new_connected_zkvm_peer();
rig.expect_proof_lookup_request(block_root);
}

Expand All @@ -267,7 +219,7 @@ fn test_proof_lookup_wrong_block_root() {
let block = rig.rand_block();
let block_root = block.canonical_root();
let wrong_root = Hash256::random();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();
let block_hash = block
.message()
.body()
Expand Down Expand Up @@ -302,7 +254,7 @@ fn test_proof_lookup_wrong_block_root() {
rig.expect_penalty(peer_id, "UnrequestedBlockRoot");

// Should retry
let _new_peer = rig.new_connected_peer();
let _new_peer = rig.new_connected_zkvm_peer();
rig.expect_proof_lookup_request(block_root);
}

Expand All @@ -315,7 +267,7 @@ fn test_proof_lookup_timeout() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
Expand All @@ -332,11 +284,9 @@ fn test_proof_lookup_timeout() {
error: RPCError::ErrorResponse(RpcErrorResponse::ServerError, "timeout".to_string()),
});

// Should penalize peer for timeout
rig.expect_penalty(peer_id, "rpc_error");

// RPC errors trigger retry without necessarily penalizing the peer
// Should retry with different peer
let _new_peer = rig.new_connected_peer();
let _new_peer = rig.new_connected_zkvm_peer();
rig.expect_proof_lookup_request(block_root);
}

Expand All @@ -349,7 +299,7 @@ fn test_proof_lookup_peer_disconnected() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
Expand All @@ -367,7 +317,7 @@ fn test_proof_lookup_peer_disconnected() {
});

// Should retry with different peer (no penalty for disconnect)
let _new_peer = rig.new_connected_peer();
let _new_peer = rig.new_connected_zkvm_peer();
rig.expect_proof_lookup_request(block_root);
}

Expand All @@ -388,7 +338,7 @@ fn test_proof_lookup_multiple_retries() {
.map(|p| p.execution_payload_ref().block_hash())
.unwrap_or_else(ExecutionBlockHash::zero);

let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
Expand All @@ -402,13 +352,13 @@ fn test_proof_lookup_multiple_retries() {
rig.expect_penalty(peer_id, "NotEnoughResponsesReturned");

// Second attempt - different peer, also fails
let peer_id_2 = rig.new_connected_peer();
let peer_id_2 = rig.new_connected_zkvm_peer();
let proof_id_2 = rig.expect_proof_lookup_request(block_root);
rig.single_lookup_proof_response(proof_id_2, peer_id_2, None);
rig.expect_penalty(peer_id_2, "NotEnoughResponsesReturned");

// Third attempt - succeeds
let peer_id_3 = rig.new_connected_peer();
let peer_id_3 = rig.new_connected_zkvm_peer();
let proof_id_3 = rig.expect_proof_lookup_request(block_root);
rig.complete_single_lookup_proof_download(
proof_id_3,
Expand All @@ -435,7 +385,7 @@ fn test_proof_lookup_no_peers() {

let block = rig.rand_block();
let block_root = block.canonical_root();
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
Expand Down Expand Up @@ -476,7 +426,7 @@ fn test_proof_lookup_with_existing_blobs() {
.ok()
.map(|p| p.execution_payload_ref().block_hash())
.unwrap_or_else(ExecutionBlockHash::zero);
let peer_id = rig.new_connected_peer();
let peer_id = rig.new_connected_zkvm_peer();

// Trigger lookup
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
Expand Down
12 changes: 12 additions & 0 deletions beacon_node/network/src/sync/tests/lookups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,18 @@ impl TestRig {
.__add_connected_peer_testing_only(true, &self.harness.spec, key)
}

/// Create a new connected peer with zkvm enabled (advertises zkvm=true in ENR)
pub fn new_connected_zkvm_peer(&mut self) -> PeerId {
let key = self.determinstic_key();
let peer_id = self
.network_globals
.peers
.write()
.__add_connected_zkvm_peer_testing_only(&self.harness.spec, key);
self.log(&format!("Added new zkvm peer for testing {peer_id:?}"));
peer_id
}

fn determinstic_key(&mut self) -> CombinedKey {
k256::ecdsa::SigningKey::random(&mut self.rng_08).into()
}
Expand Down
Loading