Skip to content

Commit c95e62a

Browse files
authored
Merge branch 'main' into feat/lean-gossip-mesh-peers-metric
2 parents fd71442 + 05e7585 commit c95e62a

1 file changed

Lines changed: 153 additions & 25 deletions

File tree

bin/ethlambda/src/main.rs

Lines changed: 153 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,13 @@ struct CliOptions {
7777
/// use the admin endpoint to rotate duties (hot-standby model).
7878
#[arg(long, default_value = "false")]
7979
is_aggregator: bool,
80-
/// Number of attestation committees (subnets) per slot
81-
#[arg(long, default_value = "1", value_parser = clap::value_parser!(u64).range(1..))]
82-
attestation_committee_count: u64,
80+
/// Number of attestation committees (subnets) per slot.
81+
///
82+
/// If unset, falls back to `config.attestation_committee_count` from
83+
/// `validator-config.yaml` in the network config dir, or `1` if that
84+
/// field is also absent.
85+
#[arg(long, value_parser = clap::value_parser!(u64).range(1..))]
86+
attestation_committee_count: Option<u64>,
8387
/// Subnet IDs this aggregator should subscribe to (comma-separated).
8488
/// Requires --is-aggregator. Defaults to the subnets of the node's validators.
8589
#[arg(long, value_delimiter = ',', requires = "is_aggregator")]
@@ -103,9 +107,6 @@ async fn main() -> eyre::Result<()> {
103107
ethlambda_blockchain::metrics::init();
104108
ethlambda_blockchain::metrics::set_node_info("ethlambda", version::CLIENT_VERSION);
105109
ethlambda_blockchain::metrics::set_node_start_time();
106-
ethlambda_blockchain::metrics::set_attestation_committee_count(
107-
options.attestation_committee_count,
108-
);
109110

110111
let api_socket = SocketAddr::new(options.http_address, options.api_port);
111112
let metrics_socket = SocketAddr::new(options.http_address, options.metrics_port);
@@ -142,7 +143,26 @@ async fn main() -> eyre::Result<()> {
142143
"Loaded genesis configuration"
143144
);
144145

145-
populate_name_registry(&validator_config);
146+
let validator_config_file = read_validator_config_file(&validator_config);
147+
populate_name_registry(&validator_config_file);
148+
149+
// Resolve attestation_committee_count: CLI flag > validator-config.yaml > 1.
150+
// The CLI path is bounded by clap's `range(1..)`; enforce the same lower
151+
// bound here so a YAML value of 0 cannot bypass it.
152+
let attestation_committee_count = options
153+
.attestation_committee_count
154+
.or(validator_config_file.config.attestation_committee_count)
155+
.unwrap_or(1);
156+
eyre::ensure!(
157+
attestation_committee_count >= 1,
158+
"attestation_committee_count must be >= 1 (got {attestation_committee_count})"
159+
);
160+
info!(
161+
attestation_committee_count,
162+
"Loaded attestation committee count"
163+
);
164+
ethlambda_blockchain::metrics::set_attestation_committee_count(attestation_committee_count);
165+
146166
let bootnodes = read_bootnodes(&bootnodes_path);
147167

148168
let validator_keys =
@@ -182,7 +202,7 @@ async fn main() -> eyre::Result<()> {
182202
bootnodes,
183203
listening_socket: p2p_socket,
184204
validator_ids,
185-
attestation_committee_count: options.attestation_committee_count,
205+
attestation_committee_count,
186206
is_aggregator: options.is_aggregator,
187207
aggregate_subnet_ids: options.aggregate_subnet_ids,
188208
})
@@ -258,25 +278,40 @@ async fn main() -> eyre::Result<()> {
258278
Ok(())
259279
}
260280

261-
fn populate_name_registry(validator_config: impl AsRef<Path>) {
262-
#[derive(Deserialize)]
263-
struct Validator {
264-
name: String,
265-
privkey: H256,
266-
}
267-
#[derive(Deserialize)]
268-
struct Config {
269-
validators: Vec<Validator>,
270-
}
271-
let config_yaml =
272-
std::fs::read_to_string(&validator_config).expect("Failed to read validator config file");
273-
let config: Config =
274-
serde_yaml_ng::from_str(&config_yaml).expect("Failed to parse validator config file");
281+
/// Subset of `validator-config.yaml` consumed by ethlambda.
282+
///
283+
/// The `config` block is a network-wide settings bag shared across clients;
284+
/// only fields ethlambda actually reads are deserialized. The `validators`
285+
/// list feeds the metrics name registry.
286+
#[derive(Debug, Deserialize)]
287+
struct ValidatorConfigFile {
288+
#[serde(default)]
289+
config: ValidatorConfigBlock,
290+
validators: Vec<ValidatorConfigEntry>,
291+
}
292+
293+
#[derive(Debug, Default, Deserialize)]
294+
struct ValidatorConfigBlock {
295+
#[serde(default)]
296+
attestation_committee_count: Option<u64>,
297+
}
298+
299+
#[derive(Debug, Deserialize)]
300+
struct ValidatorConfigEntry {
301+
name: String,
302+
privkey: H256,
303+
}
304+
305+
fn read_validator_config_file(path: impl AsRef<Path>) -> ValidatorConfigFile {
306+
let yaml = std::fs::read_to_string(&path).expect("Failed to read validator config file");
307+
serde_yaml_ng::from_str(&yaml).expect("Failed to parse validator config file")
308+
}
275309

276-
let names_and_privkeys = config
310+
fn populate_name_registry(file: &ValidatorConfigFile) {
311+
let names_and_privkeys = file
277312
.validators
278-
.into_iter()
279-
.map(|v| (v.name, v.privkey))
313+
.iter()
314+
.map(|v| (v.name.clone(), v.privkey))
280315
.collect();
281316

282317
// Populates a dictionary used for labeling metrics with node names
@@ -513,3 +548,96 @@ async fn fetch_initial_state(
513548
// Store the anchor state and header, without body
514549
Ok(Store::from_anchor_state(backend, state))
515550
}
551+
552+
#[cfg(test)]
553+
mod tests {
554+
use super::*;
555+
556+
/// Validator-config snippet matching `lean-quickstart`'s ansible-devnet
557+
/// (devnet-4) where networks share a non-default committee count.
558+
const VC_WITH_COMMITTEE_COUNT: &str = r#"
559+
shuffle: roundrobin
560+
deployment_mode: ansible
561+
config:
562+
activeEpoch: 18
563+
keyType: "hash-sig"
564+
attestation_committee_count: 2
565+
validators:
566+
- name: "ethlambda_0"
567+
privkey: "299550529a79bc2dce003747c52fb0639465c893e00b0440ac66144d625e066a"
568+
enrFields:
569+
ip: "127.0.0.1"
570+
quic: 9001
571+
metricsPort: 9095
572+
apiPort: 5055
573+
subnet: 0
574+
isAggregator: false
575+
count: 1
576+
"#;
577+
578+
/// Local-devnet snippet without the optional field — committee count is
579+
/// expected to fall back to the binary default.
580+
const VC_WITHOUT_COMMITTEE_COUNT: &str = r#"
581+
shuffle: roundrobin
582+
deployment_mode: local
583+
config:
584+
activeEpoch: 18
585+
keyType: "hash-sig"
586+
validators:
587+
- name: "ethlambda_0"
588+
privkey: "299550529a79bc2dce003747c52fb0639465c893e00b0440ac66144d625e066a"
589+
enrFields:
590+
ip: "127.0.0.1"
591+
quic: 9001
592+
metricsPort: 8087
593+
apiPort: 5055
594+
isAggregator: false
595+
count: 1
596+
"#;
597+
598+
#[test]
599+
fn parses_committee_count_when_present() {
600+
let file: ValidatorConfigFile = serde_yaml_ng::from_str(VC_WITH_COMMITTEE_COUNT).unwrap();
601+
assert_eq!(file.config.attestation_committee_count, Some(2));
602+
assert_eq!(file.validators.len(), 1);
603+
assert_eq!(file.validators[0].name, "ethlambda_0");
604+
}
605+
606+
#[test]
607+
fn defaults_to_none_when_field_absent() {
608+
let file: ValidatorConfigFile =
609+
serde_yaml_ng::from_str(VC_WITHOUT_COMMITTEE_COUNT).unwrap();
610+
assert_eq!(file.config.attestation_committee_count, None);
611+
}
612+
613+
#[test]
614+
fn cli_overrides_file_value() {
615+
let file: ValidatorConfigFile = serde_yaml_ng::from_str(VC_WITH_COMMITTEE_COUNT).unwrap();
616+
let cli_override: Option<u64> = Some(5);
617+
let resolved = cli_override
618+
.or(file.config.attestation_committee_count)
619+
.unwrap_or(1);
620+
assert_eq!(resolved, 5);
621+
}
622+
623+
#[test]
624+
fn falls_back_to_file_when_cli_absent() {
625+
let file: ValidatorConfigFile = serde_yaml_ng::from_str(VC_WITH_COMMITTEE_COUNT).unwrap();
626+
let cli_override: Option<u64> = None;
627+
let resolved = cli_override
628+
.or(file.config.attestation_committee_count)
629+
.unwrap_or(1);
630+
assert_eq!(resolved, 2);
631+
}
632+
633+
#[test]
634+
fn falls_back_to_default_when_neither_set() {
635+
let file: ValidatorConfigFile =
636+
serde_yaml_ng::from_str(VC_WITHOUT_COMMITTEE_COUNT).unwrap();
637+
let cli_override: Option<u64> = None;
638+
let resolved = cli_override
639+
.or(file.config.attestation_committee_count)
640+
.unwrap_or(1);
641+
assert_eq!(resolved, 1);
642+
}
643+
}

0 commit comments

Comments
 (0)