1+ use std:: collections:: HashSet ;
2+
13use crate :: {
2- helpers:: EthHex ,
4+ helpers:: { EthHex , from_0x_hex_str } ,
35 operator:: { Operator , OperatorV1X1 , OperatorV1X2OrLater } ,
4- version:: versions:: * ,
6+ version:: { CURRENT_VERSION , DKG_ALGO , versions:: * } ,
57} ;
8+ use charon_eth2:: enr:: { Record , RecordError } ;
9+ use charon_p2p:: peer:: { Peer , PeerError } ;
610use chrono:: { DateTime , Utc } ;
11+ use libp2p:: PeerId ;
712use serde:: { Deserialize , Deserializer , Serialize , Serializer } ;
813use serde_with:: {
914 DisplayFromStr , PickFirst ,
@@ -12,6 +17,22 @@ use serde_with::{
1217} ;
1318use uuid:: Uuid ;
1419
20+ /// Length of the fork version in bytes.
21+ pub const FORK_VERSION_LEN : usize = 4 ;
22+
23+ /// Length of the address in bytes.
24+ pub const ADDRESS_LEN : usize = 20 ;
25+
26+ /// NodeIdx represents the index of a node/peer/share in the cluster as operator
27+ /// order in cluster definition.
28+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
29+ pub struct NodeIdx {
30+ /// Index of a peer in the peer list (it's 0-indexed).
31+ pub peer_idx : usize ,
32+ /// tbls share identifier (it is 1-indexed).
33+ pub share_idx : usize ,
34+ }
35+
1536/// Definition defines an intended charon cluster configuration excluding
1637/// validators.
1738#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -159,9 +180,198 @@ pub enum DefinitionError {
159180 /// are found.
160181 #[ error( "Multiple withdrawal or fee recipient addresses found" ) ]
161182 InvalidValidatorAddresses ,
183+
184+ /// Insufficient fee-recipient addresses
185+ #[ error( "Insufficient fee-recipient addresses" ) ]
186+ InsufficientFeeRecipientAddresses ,
187+
188+ /// Insufficient withdrawal addresses
189+ #[ error( "Insufficient withdrawal addresses" ) ]
190+ InsufficientWithdrawalAddresses ,
191+
192+ /// Failed to convert length
193+ #[ error( "Failed to convert length" ) ]
194+ FailedToConvertLength ,
195+
196+ /// Failed to convert hex string
197+ #[ error( "Failed to convert hex string" ) ]
198+ FailedToConvertHexString ( #[ from] hex:: FromHexError ) ,
199+
200+ /// Invalid target gas limit
201+ #[ error( "Invalid target gas limit: {0}" ) ]
202+ InvalidTargetGasLimit ( & ' static str ) ,
203+
204+ /// Invalid deposit amounts
205+ #[ error( "Invalid deposit amounts: {0}" ) ]
206+ InvalidDepositAmounts ( & ' static str ) ,
207+
208+ /// Invalid compounding
209+ #[ error( "Invalid deposit amounts: {0}" ) ]
210+ InvalidCompounding ( & ' static str ) ,
211+
212+ /// Invalid gas limit
213+ #[ error( "Invalid gas limit: {0}" ) ]
214+ InvalidGasLimit ( & ' static str ) ,
215+
216+ /// Peer not found
217+ #[ error( "Peer not in definition: {0}" ) ]
218+ PeerNotFound ( String ) ,
219+
220+ /// Duplicate peer ENRs
221+ #[ error( "Duplicate peer ENRs: {0}" ) ]
222+ DuplicatePeerENRs ( String ) ,
223+
224+ /// Failed to parse ENR
225+ #[ error( "Failed to parse ENR: {0}" ) ]
226+ FailedToParseENR ( #[ from] RecordError ) ,
227+
228+ /// Failed to create peer
229+ #[ error( "Failed to create peer: {0}" ) ]
230+ FailedToCreatePeer ( #[ from] PeerError ) ,
162231}
163232
164233impl Definition {
234+ /// Create a new cluster definition.
235+ #[ allow( clippy:: too_many_arguments) ]
236+ pub fn new (
237+ name : String ,
238+ num_validators : u64 ,
239+ threshold : u64 ,
240+ fee_recipient_addresses : Vec < String > ,
241+ withdrawal_addresses : Vec < String > ,
242+ fork_version_hex : String ,
243+ creator : Creator ,
244+ operators : Vec < Operator > ,
245+ deposit_amounts : Vec < u64 > ,
246+ consensus_protocol : String ,
247+ target_gas_limit : u64 ,
248+ compounding : bool ,
249+ opts : Vec < fn ( & mut Self ) -> Self > ,
250+ ) -> Result < Self , DefinitionError > {
251+ if u64:: try_from ( fee_recipient_addresses. len ( ) )
252+ . map_err ( |_| DefinitionError :: FailedToConvertLength ) ?
253+ != num_validators
254+ {
255+ return Err ( DefinitionError :: InsufficientFeeRecipientAddresses ) ;
256+ }
257+
258+ if u64:: try_from ( withdrawal_addresses. len ( ) )
259+ . map_err ( |_| DefinitionError :: FailedToConvertLength ) ?
260+ != num_validators
261+ {
262+ return Err ( DefinitionError :: InsufficientWithdrawalAddresses ) ;
263+ }
264+
265+ let uuid = Uuid :: new_v4 ( ) ;
266+
267+ let mut def = Definition {
268+ uuid,
269+ name,
270+ version : CURRENT_VERSION . to_string ( ) ,
271+ timestamp : Utc :: now ( ) ,
272+ num_validators,
273+ threshold,
274+ dkg_algorithm : DKG_ALGO . to_string ( ) ,
275+ fork_version : Default :: default ( ) ,
276+ operators,
277+ creator,
278+ validator_addresses : Vec :: new ( ) ,
279+ deposit_amounts,
280+ consensus_protocol,
281+ target_gas_limit,
282+ compounding,
283+ config_hash : Default :: default ( ) ,
284+ definition_hash : Default :: default ( ) ,
285+ } ;
286+
287+ def. validator_addresses = fee_recipient_addresses
288+ . into_iter ( )
289+ . enumerate ( )
290+ . map ( |( i, address) | ValidatorAddresses {
291+ fee_recipient_address : address,
292+ withdrawal_address : withdrawal_addresses[ i] . clone ( ) ,
293+ } )
294+ . collect ( ) ;
295+
296+ def. fork_version = from_0x_hex_str ( & fork_version_hex, FORK_VERSION_LEN ) ?;
297+
298+ for opt in opts {
299+ opt ( & mut def) ;
300+ }
301+
302+ if def. deposit_amounts . len ( ) > 1 && !Self :: support_partial_deposits ( & def. version ) {
303+ return Err ( DefinitionError :: InvalidDepositAmounts (
304+ "the version does not support partial deposits" ,
305+ ) ) ;
306+ }
307+
308+ if def. target_gas_limit != 0 && !Self :: support_target_gas_limit ( & def. version ) {
309+ return Err ( DefinitionError :: InvalidTargetGasLimit (
310+ "the version does not support custom target gas limit" ,
311+ ) ) ;
312+ }
313+
314+ if def. compounding && !Self :: support_compounding ( & def. version ) {
315+ return Err ( DefinitionError :: InvalidCompounding (
316+ "the version does not support compounding" ,
317+ ) ) ;
318+ }
319+
320+ if def. target_gas_limit == 0 && Self :: support_target_gas_limit ( & def. version ) {
321+ return Err ( DefinitionError :: InvalidTargetGasLimit (
322+ "target gas limit should be set" ,
323+ ) ) ;
324+ }
325+
326+ // TODO: Construct and return a Definition. Placeholder for now.
327+ def. set_definition_hashes ( )
328+ }
329+
330+ /// Sets the definition hashes.
331+ pub fn set_definition_hashes ( self ) -> Result < Self , DefinitionError > {
332+ // todo
333+ Ok ( self )
334+ }
335+
336+ /// Returns the node index for a given peer ID.
337+ pub fn node_idx ( & self , pid : & PeerId ) -> Result < NodeIdx , DefinitionError > {
338+ let peers = self . peers ( ) ?;
339+
340+ for ( i, peer) in peers. iter ( ) . enumerate ( ) {
341+ if peer. id == * pid {
342+ return Ok ( NodeIdx {
343+ peer_idx : i,
344+ share_idx : peer. share_idx ( ) ,
345+ } ) ;
346+ }
347+ }
348+
349+ Err ( DefinitionError :: PeerNotFound ( pid. to_string ( ) ) )
350+ }
351+
352+ /// Returns the peers in the cluster.
353+ pub fn peers ( & self ) -> Result < Vec < Peer > , DefinitionError > {
354+ let mut peers = Vec :: new ( ) ;
355+
356+ let mut dedup: HashSet < String > = HashSet :: new ( ) ;
357+
358+ for ( i, operator) in self . operators . iter ( ) . enumerate ( ) {
359+ if dedup. contains ( & operator. enr ) {
360+ return Err ( DefinitionError :: DuplicatePeerENRs ( operator. enr . clone ( ) ) ) ;
361+ }
362+
363+ dedup. insert ( operator. enr . clone ( ) ) ;
364+
365+ let enr = Record :: try_from ( operator. enr . as_str ( ) ) ?;
366+
367+ let peer = Peer :: from_enr ( & enr, i) ?;
368+
369+ peers. push ( peer) ;
370+ }
371+
372+ Ok ( peers)
373+ }
374+
165375 /// Legacy single withdrawal and single
166376 /// fee recipient addresses or an error if multiple addresses are found.
167377 pub fn legacy_validator_addresses ( & self ) -> Result < ValidatorAddresses , DefinitionError > {
@@ -177,6 +387,39 @@ impl Definition {
177387
178388 Ok ( result_validator_addresses)
179389 }
390+
391+ /// Returns true if the provided definition version supports EIP712
392+ /// signatures. Note that Definition versions prior to v1.3.0 don't
393+ /// support EIP712 signatures.
394+ pub fn support_eip712_sigs ( version : & str ) -> bool {
395+ !matches ! ( version, V1_0 | V1_1 | V1_2 )
396+ }
397+
398+ /// Returns true if the provided definition version supports partial
399+ /// deposits.
400+ pub fn support_partial_deposits ( version : & str ) -> bool {
401+ !matches ! (
402+ version,
403+ V1_0 | V1_1 | V1_2 | V1_3 | V1_4 | V1_5 | V1_6 | V1_7
404+ )
405+ }
406+
407+ /// Returns true if the provided definition version supports custom target
408+ /// gas limit.
409+ pub fn support_target_gas_limit ( version : & str ) -> bool {
410+ !matches ! (
411+ version,
412+ V1_0 | V1_1 | V1_2 | V1_3 | V1_4 | V1_5 | V1_6 | V1_7 | V1_8 | V1_9
413+ )
414+ }
415+
416+ /// Returns true if the provided definition version supports compounding.
417+ pub fn support_compounding ( version : & str ) -> bool {
418+ !matches ! (
419+ version,
420+ V1_0 | V1_1 | V1_2 | V1_3 | V1_4 | V1_5 | V1_6 | V1_7 | V1_8 | V1_9
421+ )
422+ }
180423}
181424
182425/// Creator identifies the creator of a cluster definition. They may also be an
0 commit comments