@@ -6,17 +6,18 @@ use std::{
66} ;
77
88use alloy:: {
9- primitives:: { Address , U256 , address } ,
9+ primitives:: { address , Address , Bytes , U256 } ,
1010 providers:: ProviderBuilder ,
1111 rpc:: { client:: RpcClient , types:: beacon:: constants:: BLS_PUBLIC_KEY_BYTES_LEN } ,
1212 sol,
1313 transports:: http:: Http ,
1414} ;
15- use eyre:: { Context , bail, ensure} ;
15+ use eyre:: { bail, ensure, Context } ;
1616use reqwest:: Client ;
1717use serde:: { Deserialize , Deserializer , Serialize } ;
1818use tracing:: { debug, info, warn} ;
1919use url:: Url ;
20+ use LidoCSMRegistry :: getNodeOperatorSummaryReturn;
2021
2122use super :: { MUX_PATH_ENV , PbsConfig , RelayConfig , load_optional_env_var} ;
2223use crate :: {
@@ -260,6 +261,13 @@ sol! {
260261 "src/abi/LidoNORegistry.json"
261262}
262263
264+ sol ! {
265+ #[ allow( missing_docs) ]
266+ #[ sol( rpc) ]
267+ LidoCSMRegistry ,
268+ "src/abi/LidoCSModuleNORegistry.json"
269+ }
270+
263271fn lido_registry_addresses_by_module ( ) -> HashMap < Chain , HashMap < u8 , Address > > {
264272 let mut map: HashMap < Chain , HashMap < u8 , Address > > = HashMap :: new ( ) ;
265273
@@ -307,46 +315,128 @@ fn lido_registry_address(chain: Chain, lido_module_id: u8) -> eyre::Result<Addre
307315 ) )
308316}
309317
310- async fn fetch_lido_registry_keys (
311- rpc_url : Url ,
312- chain : Chain ,
318+ fn is_csm_module ( chain : Chain , module_id : u8 ) -> bool {
319+ match chain {
320+ Chain :: Mainnet => module_id == MainnetLidoModule :: CommunityStaking as u8 ,
321+ Chain :: Holesky => module_id == HoleskyLidoModule :: CommunityStaking as u8 ,
322+ Chain :: Hoodi => module_id == HoodiLidoModule :: CommunityStaking as u8 ,
323+ _ => false ,
324+ }
325+ }
326+
327+ fn get_lido_csm_registry < P > (
328+ registry_address : Address ,
329+ provider : P ,
330+ ) -> LidoCSMRegistry :: LidoCSMRegistryInstance < P >
331+ where
332+ P : Clone + Send + Sync + ' static + alloy:: providers:: Provider ,
333+ {
334+ LidoCSMRegistry :: new ( registry_address, provider)
335+ }
336+
337+ fn get_lido_module_registry < P > (
338+ registry_address : Address ,
339+ provider : P ,
340+ ) -> LidoRegistry :: LidoRegistryInstance < P >
341+ where
342+ P : Clone + Send + Sync + ' static + alloy:: providers:: Provider ,
343+ {
344+ LidoRegistry :: new ( registry_address, provider)
345+ }
346+
347+ async fn fetch_lido_csm_keys_total < P > (
348+ registry : & LidoCSMRegistry :: LidoCSMRegistryInstance < P > ,
313349 node_operator_id : U256 ,
314- lido_module_id : u8 ,
315- http_timeout : Duration ,
316- ) -> eyre:: Result < Vec < BlsPublicKey > > {
317- debug ! ( ?chain, %node_operator_id, ?lido_module_id, "loading operator keys from Lido registry" ) ;
350+ ) -> eyre:: Result < u64 >
351+ where
352+ P : Clone + Send + Sync + ' static + alloy:: providers:: Provider ,
353+ {
354+ let summary: getNodeOperatorSummaryReturn = registry
355+ . getNodeOperatorSummary ( node_operator_id)
356+ . call ( )
357+ . await ?;
318358
319- // Create an RPC provider with HTTP timeout support
320- let client = Client :: builder ( ) . timeout ( http_timeout) . build ( ) ?;
321- let http = Http :: with_client ( client, rpc_url) ;
322- let is_local = http. guess_local ( ) ;
323- let rpc_client = RpcClient :: new ( http, is_local) ;
324- let provider = ProviderBuilder :: new ( ) . connect_client ( rpc_client) ;
359+ let total_u256 = summary. totalDepositedValidators + summary. depositableValidatorsCount ;
325360
326- let registry_address = lido_registry_address ( chain, lido_module_id) ?;
327- let registry = LidoRegistry :: new ( registry_address, provider) ;
361+ let total_u64 = u64:: try_from ( total_u256)
362+ . wrap_err_with ( || format ! ( "total keys ({total_u256}) does not fit into u64" ) ) ?;
363+
364+ Ok ( total_u64)
365+ }
366+
367+ async fn fetch_lido_module_keys_total < P > (
368+ registry : & LidoRegistry :: LidoRegistryInstance < P > ,
369+ node_operator_id : U256 ,
370+ ) -> eyre:: Result < u64 >
371+ where
372+ P : Clone + Send + Sync + ' static + alloy:: providers:: Provider ,
373+ {
374+ let total_keys: u64 = registry
375+ . getTotalSigningKeyCount ( node_operator_id)
376+ . call ( )
377+ . await ?
378+ . try_into ( ) ?;
379+
380+ Ok ( total_keys)
381+ }
382+
383+ async fn fetch_lido_csm_keys_batch < P > (
384+ registry : & LidoCSMRegistry :: LidoCSMRegistryInstance < P > ,
385+ node_operator_id : U256 ,
386+ offset : u64 ,
387+ limit : u64
388+ ) -> eyre:: Result < Bytes >
389+ where
390+ P : Clone + Send + Sync + ' static + alloy:: providers:: Provider ,
391+ {
392+ let pubkeys = registry
393+ . getSigningKeys ( node_operator_id, U256 :: from ( offset) , U256 :: from ( limit) )
394+ . call ( )
395+ . await ?;
396+
397+ Ok ( pubkeys)
398+ }
328399
329- let total_keys = registry. getTotalSigningKeyCount ( node_operator_id) . call ( ) . await ?. try_into ( ) ?;
400+ async fn fetch_lido_module_keys_batch < P > (
401+ registry : & LidoRegistry :: LidoRegistryInstance < P > ,
402+ node_operator_id : U256 ,
403+ offset : u64 ,
404+ limit : u64
405+ ) -> eyre:: Result < Bytes >
406+ where
407+ P : Clone + Send + Sync + ' static + alloy:: providers:: Provider ,
408+ {
409+ let pubkeys = registry
410+ . getSigningKeys ( node_operator_id, U256 :: from ( offset) , U256 :: from ( limit) )
411+ . call ( )
412+ . await ?
413+ . pubkeys ;
414+
415+ Ok ( pubkeys)
416+ }
330417
418+ async fn collect_registry_keys < F , Fut > (
419+ total_keys : u64 ,
420+ mut fetch_batch : F ,
421+ ) -> eyre:: Result < Vec < BlsPublicKey > >
422+ where
423+ F : FnMut ( u64 , u64 ) -> Fut ,
424+ Fut : std:: future:: Future < Output = eyre:: Result < Bytes > > ,
425+ {
331426 if total_keys == 0 {
332427 return Ok ( Vec :: new ( ) ) ;
333428 }
334-
335429 debug ! ( "fetching {total_keys} total keys" ) ;
336430
337431 const CALL_BATCH_SIZE : u64 = 250u64 ;
338432
339433 let mut keys = vec ! [ ] ;
340- let mut offset = 0 ;
434+ let mut offset: u64 = 0 ;
341435
342436 while offset < total_keys {
343437 let limit = CALL_BATCH_SIZE . min ( total_keys - offset) ;
344438
345- let pubkeys = registry
346- . getSigningKeys ( node_operator_id, U256 :: from ( offset) , U256 :: from ( limit) )
347- . call ( )
348- . await ?
349- . pubkeys ;
439+ let pubkeys = fetch_batch ( offset, limit) . await ?;
350440
351441 ensure ! (
352442 pubkeys. len( ) % BLS_PUBLIC_KEY_BYTES_LEN == 0 ,
@@ -373,6 +463,58 @@ async fn fetch_lido_registry_keys(
373463 Ok ( keys)
374464}
375465
466+ async fn fetch_lido_csm_registry_keys (
467+ registry_address : Address ,
468+ rpc_client : RpcClient ,
469+ node_operator_id : U256 ,
470+ ) -> eyre:: Result < Vec < BlsPublicKey > > {
471+ let provider = ProviderBuilder :: new ( ) . connect_client ( rpc_client) ;
472+ let registry = get_lido_csm_registry ( registry_address, provider) ;
473+
474+ let total_keys = fetch_lido_csm_keys_total ( & registry, node_operator_id) . await ?. try_into ( ) ?;
475+
476+ collect_registry_keys ( total_keys, |offset, limit| {
477+ fetch_lido_csm_keys_batch ( & registry, node_operator_id, offset, limit)
478+ } ) . await
479+ }
480+
481+ async fn fetch_lido_module_registry_keys (
482+ registry_address : Address ,
483+ rpc_client : RpcClient ,
484+ node_operator_id : U256 ,
485+ ) -> eyre:: Result < Vec < BlsPublicKey > > {
486+ let provider = ProviderBuilder :: new ( ) . connect_client ( rpc_client) ;
487+ let registry = get_lido_module_registry ( registry_address, provider) ;
488+ let total_keys: u64 = fetch_lido_module_keys_total ( & registry, node_operator_id) . await ?. try_into ( ) ?;
489+
490+ collect_registry_keys ( total_keys, |offset, limit| {
491+ fetch_lido_module_keys_batch ( & registry, node_operator_id, offset, limit)
492+ } ) . await
493+ }
494+
495+ async fn fetch_lido_registry_keys (
496+ rpc_url : Url ,
497+ chain : Chain ,
498+ node_operator_id : U256 ,
499+ lido_module_id : u8 ,
500+ http_timeout : Duration ,
501+ ) -> eyre:: Result < Vec < BlsPublicKey > > {
502+ debug ! ( ?chain, %node_operator_id, ?lido_module_id, "loading operator keys from Lido registry" ) ;
503+
504+ // Create an RPC provider with HTTP timeout support
505+ let client = Client :: builder ( ) . timeout ( http_timeout) . build ( ) ?;
506+ let http = Http :: with_client ( client, rpc_url) ;
507+ let is_local = http. guess_local ( ) ;
508+ let rpc_client = RpcClient :: new ( http, is_local) ;
509+ let registry_address = lido_registry_address ( chain, lido_module_id) ?;
510+
511+ if is_csm_module ( chain, lido_module_id) {
512+ fetch_lido_csm_registry_keys ( registry_address, rpc_client, node_operator_id) . await
513+ } else {
514+ fetch_lido_module_registry_keys ( registry_address, rpc_client, node_operator_id) . await
515+ }
516+ }
517+
376518async fn fetch_ssv_pubkeys (
377519 chain : Chain ,
378520 node_operator_id : U256 ,
@@ -520,6 +662,49 @@ mod tests {
520662 Ok ( ( ) )
521663 }
522664
665+ #[ tokio:: test]
666+ async fn test_lido_csm_registry_address ( ) -> eyre:: Result < ( ) > {
667+ use alloy:: { primitives:: U256 , providers:: ProviderBuilder } ;
668+
669+ let url = Url :: parse ( "https://ethereum-rpc.publicnode.com" ) ?;
670+ let provider = ProviderBuilder :: new ( ) . connect_http ( url) ;
671+
672+ let registry = LidoCSMRegistry :: new (
673+ address ! ( "dA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F" ) ,
674+ provider,
675+ ) ;
676+
677+ const LIMIT : usize = 3 ;
678+ let node_operator_id = U256 :: from ( 1 ) ;
679+
680+ let summary = registry
681+ . getNodeOperatorSummary ( node_operator_id)
682+ . call ( )
683+ . await ?;
684+
685+ let total_keys_u256 = summary. totalDepositedValidators + summary. depositableValidatorsCount ;
686+ let total_keys: u64 = total_keys_u256. try_into ( ) ?;
687+
688+ assert ! ( total_keys > LIMIT as u64 , "expected more than {LIMIT} keys, got {total_keys}" ) ;
689+
690+ let pubkeys = registry
691+ . getSigningKeys ( node_operator_id, U256 :: ZERO , U256 :: from ( LIMIT ) )
692+ . call ( )
693+ . await ?;
694+
695+ let mut vec = Vec :: new ( ) ;
696+ for chunk in pubkeys. chunks ( BLS_PUBLIC_KEY_BYTES_LEN ) {
697+ vec. push (
698+ BlsPublicKey :: deserialize ( chunk)
699+ . map_err ( |_| eyre:: eyre!( "invalid BLS public key" ) ) ?,
700+ ) ;
701+ }
702+
703+ assert_eq ! ( vec. len( ) , LIMIT , "expected {LIMIT} keys, got {}" , vec. len( ) ) ;
704+
705+ Ok ( ( ) )
706+ }
707+
523708 #[ tokio:: test]
524709 /// Tests that a successful SSV network fetch is handled and parsed properly
525710 async fn test_ssv_network_fetch ( ) -> eyre:: Result < ( ) > {
0 commit comments