@@ -22,10 +22,9 @@ use pluto_eth2util::{
2222 valid_network,
2323 } ,
2424} ;
25+ use thiserror:: Error ;
2526use tracing:: { info, warn} ;
2627
27- use crate :: error:: { CliError , Result } ;
28-
2928const DEFAULT_NETWORK : & str = "mainnet" ;
3029const ZERO_ADDRESS : & str = "0x0000000000000000000000000000000000000000" ;
3130const 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