Skip to content

Commit 115416e

Browse files
committed
Add PrefixMapping
1 parent 2fcbea9 commit 115416e

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

beacon_node/lighthouse_network/src/discovery/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ use tokio::sync::mpsc;
4848
use tracing::{debug, error, info, trace, warn};
4949
use types::{ChainSpec, EnrForkId, EthSpec};
5050

51+
mod prefix_mapping;
5152
mod subnet_predicate;
53+
5254
use crate::discovery::enr::{NEXT_FORK_DIGEST_ENR_KEY, PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY};
5355
pub use subnet_predicate::subnet_predicate;
5456
use types::non_zero_usize::new_non_zero_usize;
@@ -371,6 +373,7 @@ impl<E: EthSpec> Discovery<E> {
371373
subnets = ?subnets_to_discover.iter().map(|s| s.subnet).collect::<Vec<_>>(),
372374
"Starting discovery query for subnets"
373375
);
376+
374377
for subnet in subnets_to_discover {
375378
self.add_subnet_query(subnet.subnet, subnet.min_ttl, 0);
376379
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use alloy_primitives::U256;
2+
use discv5::enr::NodeId;
3+
use std::collections::HashMap;
4+
use types::{ChainSpec, SubnetId};
5+
6+
/// Provides prefixed `NodeId`s with prefixes, which are computed based on the given `SubnetId`.
7+
/// These prefixed `NodeId`s are used for prefix searching during discovery.
8+
pub(crate) struct PrefixMapping {
9+
chain_spec: ChainSpec,
10+
/// A mapping of `SubnetId` to prefixes.
11+
mapping: HashMap<SubnetId, Vec<i32>>,
12+
}
13+
14+
impl PrefixMapping {
15+
pub fn new(chain_spec: ChainSpec) -> Self {
16+
Self {
17+
mapping: SubnetId::compute_attestation_subnet_prefix_mapping(&chain_spec),
18+
chain_spec,
19+
}
20+
}
21+
22+
/// Returns random `NodeId`s with the prefix that should be subscribed to the given `SubnetId`.
23+
pub fn get_prefixed_node_ids(&self, subnet_id: &SubnetId) -> Result<Vec<NodeId>, &'static str> {
24+
let prefix_bits = self.chain_spec.attestation_subnet_prefix_bits as u32;
25+
26+
// The `mask` is used in a bitwise operation to replace a specific segment of the NodeId
27+
// with the subnet prefix bits.
28+
// This variable indicates which segment of the NodeId should be replaced with the subnet
29+
// prefix bits.
30+
let mask = U256::from(2_i32.pow(prefix_bits) - 1) << (256 - prefix_bits);
31+
32+
Ok(self
33+
.mapping
34+
.get(subnet_id)
35+
.ok_or("No prefix mapping for subnet_id")?
36+
.iter()
37+
.map(|prefix| {
38+
// Generate a random NodeId and replace the first few bits with the prefix.
39+
let random_node_id = U256::from_be_bytes(NodeId::random().raw());
40+
let prefixed_node_id = U256::from(*prefix) << (256 - prefix_bits);
41+
42+
// Replace the first few bits of the random NodeID with the prefix. The bits that we
43+
// want to replace are identified by the `mask`.
44+
let raw_node_id: [u8; 32] =
45+
(random_node_id ^ ((random_node_id ^ prefixed_node_id) & mask)).to_be_bytes();
46+
47+
NodeId::from(raw_node_id)
48+
})
49+
.collect::<Vec<_>>())
50+
}
51+
}
52+
53+
#[cfg(test)]
54+
mod tests {
55+
use super::*;
56+
use itertools::Itertools;
57+
use std::collections::HashSet;
58+
59+
// Test that `get_target_node_ids` returns prefixed random `NodeId`s.
60+
#[test]
61+
fn test_get_prefixed_node_ids() {
62+
let spec = ChainSpec::mainnet();
63+
let prefix_mapping = PrefixMapping::new(spec.clone());
64+
65+
for target_subnet_id in (0..spec.attestation_subnet_count).map(SubnetId::new) {
66+
let node_ids = prefix_mapping
67+
.get_prefixed_node_ids(&target_subnet_id)
68+
.unwrap();
69+
assert!(!node_ids.is_empty());
70+
71+
// Test that the `NodeId`s are correctly prefixed.
72+
for node_id in node_ids.clone() {
73+
let mut computed_subnet_ids =
74+
SubnetId::compute_attestation_subnets(node_id.raw(), &spec);
75+
assert!(computed_subnet_ids.contains(&target_subnet_id));
76+
}
77+
78+
// Test that the `NodeId`s are randomized, excluding its subnet prefix.
79+
let mut check_uniqueness = HashSet::new();
80+
for node_id in node_ids {
81+
let node_id_segment = U256::from_be_bytes(node_id.raw())
82+
<< spec.attestation_subnet_prefix_bits as u32;
83+
assert_ne!(node_id_segment, U256::from(0));
84+
assert!(check_uniqueness.insert(node_id_segment));
85+
}
86+
}
87+
}
88+
89+
// Test that `get_target_node_ids` returns an `Err` if the given `subnet_id` is invalid.
90+
#[test]
91+
fn test_get_prefixed_node_ids_error() {
92+
let spec = ChainSpec::mainnet();
93+
let prefix_mapping = PrefixMapping::new(spec.clone());
94+
let subnet_id = SubnetId::new(spec.attestation_subnet_count);
95+
let node_ids = prefix_mapping.get_prefixed_node_ids(&subnet_id);
96+
assert!(node_ids.is_err());
97+
}
98+
}

0 commit comments

Comments
 (0)