Skip to content

Commit a48d280

Browse files
committed
CreateDkgError enum for creating DKG config
1 parent fb6cee4 commit a48d280

2 files changed

Lines changed: 183 additions & 68 deletions

File tree

crates/cli/src/commands/create_dkg.rs

Lines changed: 179 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ use pluto_eth2util::{
2222
valid_network,
2323
},
2424
};
25+
use thiserror::Error;
2526
use tracing::{info, warn};
2627

27-
use crate::error::{CliError, Result};
28-
2928
const DEFAULT_NETWORK: &str = "mainnet";
3029
const ZERO_ADDRESS: &str = "0x0000000000000000000000000000000000000000";
3130
const MIN_NODES: usize = 3;
@@ -119,49 +118,130 @@ pub struct CreateDkgArgs {
119118
pub operator_addresses: Vec<String>,
120119
}
121120

121+
#[derive(Error, Debug)]
122+
pub enum CreateDkgError {
123+
#[error("existing cluster-definition.json found. Try again after deleting it")]
124+
DefinitionAlreadyExists,
125+
126+
#[error("invalid ENR (operator {index}): {source}")]
127+
InvalidEnr {
128+
index: usize,
129+
#[source]
130+
source: pluto_eth2util::enr::RecordError,
131+
},
132+
133+
#[error("invalid operator address: {source} (operator {index})")]
134+
InvalidOperatorAddress {
135+
index: usize,
136+
#[source]
137+
source: pluto_eth2util::helpers::HelperError,
138+
},
139+
140+
#[error("operator count overflow")]
141+
OperatorCountOverflow,
142+
143+
#[error(
144+
"number of operators is below minimum: got {num_operators}, need at least {MIN_NODES} via --operator-enrs or --operator-addresses"
145+
)]
146+
TooFewOperators { num_operators: usize },
147+
148+
#[error("unsupported network")]
149+
UnsupportedNetwork,
150+
151+
#[error("unsupported consensus protocol")]
152+
UnsupportedConsensusProtocol,
153+
154+
#[error("address count overflow")]
155+
AddressCountOverflow,
156+
157+
#[error("mismatching --num-validators and --fee-recipient-addresses")]
158+
MismatchingFeeRecipientAddresses,
159+
160+
#[error("mismatching --num-validators and --withdrawal-addresses")]
161+
MismatchingWithdrawalAddresses,
162+
163+
#[error("num_validators is greater than usize::MAX")]
164+
NumValidatorsOverflow,
165+
166+
#[error("threshold overflow")]
167+
ThresholdOverflow,
168+
169+
#[error("threshold must be greater than 1")]
170+
ThresholdTooLow,
171+
172+
#[error("threshold cannot be greater than number of operators")]
173+
ThresholdTooHigh,
174+
175+
#[error("cannot provide both --operator-enrs and --operator-addresses")]
176+
MutuallyExclusiveOperatorFlags,
177+
178+
#[error(r#"required flag(s) "operator-enrs" or "operator-addresses" not set"#)]
179+
MissingOperatorEnrsOrAddresses,
180+
181+
#[error(r#"required flag(s) "operator-enrs" not set"#)]
182+
MissingOperatorEnrs,
183+
184+
#[error(transparent)]
185+
WithdrawalValidation(#[from] WithdrawalValidationError),
186+
187+
#[error(transparent)]
188+
Network(#[from] pluto_eth2util::network::NetworkError),
189+
190+
#[error(transparent)]
191+
Definition(#[from] pluto_cluster::definition::DefinitionError),
192+
193+
#[error(transparent)]
194+
Eip712(#[from] pluto_cluster::eip712sigs::EIP712Error),
195+
196+
#[error(transparent)]
197+
Eth1wrap(#[from] pluto_eth1wrap::EthClientError),
198+
199+
#[error(transparent)]
200+
Json(#[from] serde_json::Error),
201+
202+
#[error(transparent)]
203+
Io(#[from] std::io::Error),
204+
205+
#[error(transparent)]
206+
Deposit(#[from] pluto_eth2util::deposit::DepositError),
207+
208+
#[error(transparent)]
209+
ObolApi(#[from] pluto_app::obolapi::ObolApiError),
210+
}
211+
122212
/// Runs the create dkg command.
123-
pub async fn run(args: CreateDkgArgs) -> Result<()> {
124-
run_create_dkg(parse_args(args)?).await
213+
pub async fn run(args: CreateDkgArgs) -> crate::error::Result<()> {
214+
Ok(run_create_dkg(parse_args(args)?).await?)
125215
}
126216

127-
fn parse_args(args: CreateDkgArgs) -> Result<CreateDkgArgs> {
217+
fn parse_args(args: CreateDkgArgs) -> Result<CreateDkgArgs, CreateDkgError> {
128218
if args.threshold != 0 {
129219
if args.threshold < MIN_THRESHOLD {
130-
return Err(CliError::Other(
131-
"threshold must be greater than 1".to_string(),
132-
));
220+
return Err(CreateDkgError::ThresholdTooLow);
133221
}
134222
let num_enrs = u64::try_from(args.operator_enrs.len())
135-
.map_err(|_| CliError::Other("operator count overflow".to_string()))?;
223+
.map_err(|_| CreateDkgError::OperatorCountOverflow)?;
136224
if args.threshold > num_enrs {
137-
return Err(CliError::Other(
138-
"threshold cannot be greater than number of operators".to_string(),
139-
));
225+
return Err(CreateDkgError::ThresholdTooHigh);
140226
}
141227
}
142228

143229
if !args.operator_enrs.is_empty() && !args.operator_addresses.is_empty() {
144-
return Err(CliError::Other(
145-
"cannot provide both --operator-enrs and --operator-addresses".to_string(),
146-
));
230+
return Err(CreateDkgError::MutuallyExclusiveOperatorFlags);
147231
}
148232

149233
if args.publish {
150234
if args.operator_enrs.is_empty() && args.operator_addresses.is_empty() {
151-
return Err(CliError::Other(
152-
r#"required flag(s) "operator-enrs" or "operator-addresses" not set"#.to_string(),
153-
));
235+
return Err(CreateDkgError::MissingOperatorEnrsOrAddresses);
154236
}
155237
} else if args.operator_enrs.is_empty() {
156-
return Err(CliError::Other(
157-
r#"required flag(s) "operator-enrs" not set"#.to_string(),
158-
));
238+
return Err(CreateDkgError::MissingOperatorEnrs);
159239
}
160240

161241
Ok(args)
162242
}
163243

164-
async fn run_create_dkg(mut args: CreateDkgArgs) -> Result<()> {
244+
async fn run_create_dkg(mut args: CreateDkgArgs) -> Result<(), CreateDkgError> {
165245
// Map prater to goerli to ensure backwards compatibility with older cluster
166246
// definitions.
167247
if args.network == PRATER {
@@ -194,16 +274,14 @@ async fn run_create_dkg(mut args: CreateDkgArgs) -> Result<()> {
194274

195275
let def_path = args.output_dir.join("cluster-definition.json");
196276
if def_path.exists() {
197-
return Err(CliError::Other(
198-
"existing cluster-definition.json found. Try again after deleting it".to_string(),
199-
));
277+
return Err(CreateDkgError::DefinitionAlreadyExists);
200278
}
201279

202280
let mut operators: Vec<Operator> = Vec::new();
203281

204282
for (i, enr_str) in args.operator_enrs.iter().enumerate() {
205283
Record::try_from(enr_str.as_str())
206-
.map_err(|e| CliError::Other(format!("invalid ENR (operator {i}): {e}")))?;
284+
.map_err(|source| CreateDkgError::InvalidEnr { index: i, source })?;
207285

208286
operators.push(Operator {
209287
enr: enr_str.clone(),
@@ -212,17 +290,16 @@ async fn run_create_dkg(mut args: CreateDkgArgs) -> Result<()> {
212290
}
213291

214292
for (i, addr) in args.operator_addresses.iter().enumerate() {
215-
let checksum_addr = checksum_address(addr).map_err(|e| {
216-
CliError::Other(format!("invalid operator address: {e} (operator {i})"))
217-
})?;
293+
let checksum_addr = checksum_address(addr)
294+
.map_err(|source| CreateDkgError::InvalidOperatorAddress { index: i, source })?;
218295
operators.push(Operator {
219296
address: checksum_addr,
220297
..Default::default()
221298
});
222299
}
223300

224-
let num_operators = u64::try_from(operators.len())
225-
.map_err(|_| CliError::Other("operator count overflow".to_string()))?;
301+
let num_operators =
302+
u64::try_from(operators.len()).map_err(|_| CreateDkgError::OperatorCountOverflow)?;
226303
let safe_thresh = safe_threshold(num_operators)?;
227304
let threshold = if args.threshold == 0 {
228305
safe_thresh
@@ -311,15 +388,13 @@ fn validate_dkg_config(
311388
deposit_amounts: &[u64],
312389
consensus_protocol: &str,
313390
compounding: bool,
314-
) -> Result<()> {
391+
) -> Result<(), CreateDkgError> {
315392
if num_operators < MIN_NODES {
316-
return Err(CliError::Other(format!(
317-
"number of operators is below minimum: got {num_operators}, need at least {MIN_NODES} via --operator-enrs or --operator-addresses",
318-
)));
393+
return Err(CreateDkgError::TooFewOperators { num_operators });
319394
}
320395

321396
if !valid_network(network) {
322-
return Err(CliError::Other("unsupported network".to_string()));
397+
return Err(CreateDkgError::UnsupportedNetwork);
323398
}
324399

325400
if !deposit_amounts.is_empty() {
@@ -328,9 +403,7 @@ fn validate_dkg_config(
328403
}
329404

330405
if !consensus_protocol.is_empty() && !is_supported_protocol_name(consensus_protocol) {
331-
return Err(CliError::Other(
332-
"unsupported consensus protocol".to_string(),
333-
));
406+
return Err(CreateDkgError::UnsupportedConsensusProtocol);
334407
}
335408

336409
Ok(())
@@ -340,27 +413,23 @@ fn validate_addresses(
340413
num_validators: u64,
341414
fee_recipient_addrs: Vec<String>,
342415
withdrawal_addrs: Vec<String>,
343-
) -> Result<(Vec<String>, Vec<String>)> {
416+
) -> Result<(Vec<String>, Vec<String>), CreateDkgError> {
344417
let num_vals = num_validators;
345418
let num_fee = u64::try_from(fee_recipient_addrs.len())
346-
.map_err(|_| CliError::Other("address count overflow".to_string()))?;
347-
let num_wa = u64::try_from(withdrawal_addrs.len())
348-
.map_err(|_| CliError::Other("address count overflow".to_string()))?;
419+
.map_err(|_| CreateDkgError::AddressCountOverflow)?;
420+
let num_wa =
421+
u64::try_from(withdrawal_addrs.len()).map_err(|_| CreateDkgError::AddressCountOverflow)?;
349422

350423
if num_fee != num_vals && num_fee != 1 {
351-
return Err(CliError::Other(
352-
"mismatching --num-validators and --fee-recipient-addresses".to_string(),
353-
));
424+
return Err(CreateDkgError::MismatchingFeeRecipientAddresses);
354425
}
355426

356427
if num_wa != num_vals && num_wa != 1 {
357-
return Err(CliError::Other(
358-
"mismatching --num-validators and --withdrawal-addresses".to_string(),
359-
));
428+
return Err(CreateDkgError::MismatchingWithdrawalAddresses);
360429
}
361430

362-
let num_validators = usize::try_from(num_validators)
363-
.map_err(|_| CliError::Other("num_validators is greater than usize::MAX".to_string()))?;
431+
let num_validators =
432+
usize::try_from(num_validators).map_err(|_| CreateDkgError::NumValidatorsOverflow)?;
364433
let expand = |addrs: Vec<String>| -> Vec<String> {
365434
if addrs.len() == 1 {
366435
vec![addrs[0].clone(); num_validators]
@@ -372,21 +441,63 @@ fn validate_addresses(
372441
Ok((expand(fee_recipient_addrs), expand(withdrawal_addrs)))
373442
}
374443

375-
fn validate_withdrawal_addrs(addrs: &[String], network: &str) -> Result<()> {
444+
/// Errors that can occur during withdrawal address validation.
445+
#[derive(Error, Debug)]
446+
pub enum WithdrawalValidationError {
447+
/// Invalid withdrawal address.
448+
#[error("invalid withdrawal address: {address}: {reason}")]
449+
InvalidWithdrawalAddress {
450+
/// The invalid address.
451+
address: String,
452+
/// The reason for the invalid address.
453+
reason: String,
454+
},
455+
456+
/// Invalid checksummed address.
457+
#[error("invalid checksummed address: {address}")]
458+
InvalidChecksummedAddress {
459+
/// The address with invalid checksum.
460+
address: String,
461+
},
462+
463+
/// Zero address forbidden on mainnet/gnosis.
464+
#[error("zero address forbidden on this network: {network}")]
465+
ZeroAddressForbiddenOnNetwork {
466+
/// The network name.
467+
network: String,
468+
},
469+
470+
/// Eth2util helpers error.
471+
#[error("Eth2util helpers error: {0}")]
472+
Eth2utilHelperError(#[from] pluto_eth2util::helpers::HelperError),
473+
}
474+
475+
/// Validates withdrawal addresses for the given network.
476+
///
477+
/// Returns an error if any of the provided withdrawal addresses is invalid.
478+
pub fn validate_withdrawal_addrs(
479+
addrs: &[String],
480+
network: &str,
481+
) -> Result<(), WithdrawalValidationError> {
376482
for addr in addrs {
377-
let checksum = checksum_address(addr)
378-
.map_err(|e| CliError::Other(format!("invalid withdrawal address: {e}")))?;
483+
let checksum_addr = checksum_address(addr).map_err(|e| {
484+
WithdrawalValidationError::InvalidWithdrawalAddress {
485+
address: addr.clone(),
486+
reason: e.to_string(),
487+
}
488+
})?;
379489

380-
if checksum != *addr {
381-
return Err(CliError::Other(format!(
382-
"invalid checksummed address: {addr}"
383-
)));
490+
if checksum_addr != *addr {
491+
return Err(WithdrawalValidationError::InvalidChecksummedAddress {
492+
address: addr.clone(),
493+
});
384494
}
385495

496+
// We cannot allow a zero withdrawal address on mainnet or gnosis.
386497
if is_main_or_gnosis(network) && addr == ZERO_ADDRESS {
387-
return Err(CliError::Other(format!(
388-
"zero address forbidden on this network: {network}"
389-
)));
498+
return Err(WithdrawalValidationError::ZeroAddressForbiddenOnNetwork {
499+
network: network.to_string(),
500+
});
390501
}
391502
}
392503

@@ -397,13 +508,13 @@ fn is_main_or_gnosis(network: &str) -> bool {
397508
network == MAINNET.name || network == GNOSIS.name
398509
}
399510

400-
fn safe_threshold(num_operators: u64) -> Result<u64> {
511+
fn safe_threshold(num_operators: u64) -> Result<u64, CreateDkgError> {
401512
let two_n = num_operators
402513
.checked_mul(2)
403-
.ok_or_else(|| CliError::Other("threshold overflow".to_string()))?;
514+
.ok_or(CreateDkgError::ThresholdOverflow)?;
404515
Ok(two_n
405516
.checked_add(2)
406-
.ok_or_else(|| CliError::Other("threshold overflow".to_string()))?
517+
.ok_or(CreateDkgError::ThresholdOverflow)?
407518
/ 3)
408519
}
409520

@@ -432,7 +543,7 @@ async fn publish_partial_definition(
432543
args: CreateDkgArgs,
433544
priv_key: SecretKey,
434545
def: Definition,
435-
) -> Result<()> {
546+
) -> Result<(), CreateDkgError> {
436547
let api_client = Client::new(
437548
&args.publish_address,
438549
ClientOptions::builder()
@@ -603,17 +714,17 @@ mod tests {
603714

604715
#[test_case(
605716
CreateDkgArgs { operator_enrs: vec![], operator_addresses: vec![], publish: false, ..default_args() },
606-
r#"required flag(s) "operator-enrs" not set"# ;
717+
r#"Create DKG error: required flag(s) "operator-enrs" not set"# ;
607718
"no_enrs"
608719
)]
609720
#[test_case(
610721
CreateDkgArgs { threshold: 1, ..default_args() },
611-
"threshold must be greater than 1" ;
722+
"Create DKG error: threshold must be greater than 1" ;
612723
"threshold_below_minimum"
613724
)]
614725
#[test_case(
615726
CreateDkgArgs { operator_enrs: VALID_ENRS[..3].iter().map(|s| s.to_string()).collect(), threshold: 4, ..default_args() },
616-
"threshold cannot be greater than number of operators" ;
727+
"Create DKG error: threshold cannot be greater than number of operators" ;
617728
"threshold_above_maximum"
618729
)]
619730
#[tokio::test]

0 commit comments

Comments
 (0)