blueprint_eigenlayer_extra/
discovery.rs

1/// On-chain AVS discovery for EigenLayer operators
2///
3/// Provides functionality to query EigenLayer contracts and discover which AVS services
4/// an operator is registered with. This is useful for:
5/// - Syncing local registration state with on-chain reality
6/// - Auto-discovering AVS registrations without manual configuration
7/// - Validating operator registration status
8use crate::error::{EigenlayerExtraError, Result};
9use crate::registration::AvsRegistrationConfig;
10use alloy_primitives::Address;
11use blueprint_core::{info, warn};
12use blueprint_runner::config::BlueprintEnvironment;
13use eigensdk::client_avsregistry::reader::AvsRegistryChainReader;
14use std::path::PathBuf;
15
16/// AVS discovery service for querying on-chain registrations
17pub struct AvsDiscoveryService {
18    env: BlueprintEnvironment,
19}
20
21/// Discovered AVS information from on-chain queries
22#[derive(Debug, Clone)]
23pub struct DiscoveredAvs {
24    /// Service manager contract address
25    pub service_manager: Address,
26    /// Registry coordinator contract address
27    pub registry_coordinator: Address,
28    /// Operator state retriever contract address
29    pub operator_state_retriever: Address,
30    /// Stake registry contract address
31    pub stake_registry: Address,
32    /// Whether the operator is currently registered
33    pub is_registered: bool,
34}
35
36impl AvsDiscoveryService {
37    /// Create a new AVS discovery service
38    pub fn new(env: BlueprintEnvironment) -> Self {
39        Self { env }
40    }
41
42    /// Discover all AVS services the operator is registered with
43    ///
44    /// Queries the EigenLayer contracts to find active AVS registrations for the operator.
45    ///
46    /// # Arguments
47    ///
48    /// * `operator_address` - The operator's Ethereum address
49    ///
50    /// # Errors
51    ///
52    /// Returns error if:
53    /// - Contract queries fail
54    /// - Configuration is invalid
55    /// - RPC connection fails
56    ///
57    /// # Returns
58    ///
59    /// Vector of discovered AVS registrations
60    pub async fn discover_avs_registrations(
61        &self,
62        operator_address: Address,
63    ) -> Result<Vec<DiscoveredAvs>> {
64        info!(
65            "Discovering AVS registrations for operator {:#x}",
66            operator_address
67        );
68
69        let contract_addresses = self
70            .env
71            .protocol_settings
72            .eigenlayer()
73            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
74
75        // Create AVS registry reader
76        let registry_reader = AvsRegistryChainReader::new(
77            contract_addresses.registry_coordinator_address,
78            contract_addresses.operator_state_retriever_address,
79            self.env.http_rpc_endpoint.to_string(),
80        )
81        .await
82        .map_err(|e| {
83            EigenlayerExtraError::Other(format!("Failed to create AVS registry reader: {e}"))
84        })?;
85
86        // Check if operator is registered
87        let is_registered = registry_reader
88            .is_operator_registered(operator_address)
89            .await
90            .map_err(|e| {
91                EigenlayerExtraError::Other(format!("Failed to check registration status: {e}"))
92            })?;
93
94        if !is_registered {
95            info!(
96                "Operator {:#x} is not registered to any AVS",
97                operator_address
98            );
99            return Ok(Vec::new());
100        }
101
102        info!("Operator {:#x} is registered to AVS", operator_address);
103
104        // For now, we return a single discovered AVS based on the current protocol settings
105        // In a full implementation, we would query the AVSDirectory to get all AVS services
106        let discovered = DiscoveredAvs {
107            service_manager: contract_addresses.service_manager_address,
108            registry_coordinator: contract_addresses.registry_coordinator_address,
109            operator_state_retriever: contract_addresses.operator_state_retriever_address,
110            stake_registry: contract_addresses.stake_registry_address,
111            is_registered,
112        };
113
114        Ok(vec![discovered])
115    }
116
117    /// Verify if an operator is registered with a specific AVS
118    ///
119    /// # Arguments
120    ///
121    /// * `operator_address` - The operator's Ethereum address
122    /// * `registry_coordinator` - The registry coordinator contract address
123    ///
124    /// # Errors
125    ///
126    /// Returns error if contract queries fail
127    ///
128    /// # Returns
129    ///
130    /// `true` if the operator is registered, `false` otherwise
131    pub async fn is_operator_registered_to_avs(
132        &self,
133        operator_address: Address,
134        registry_coordinator: Address,
135    ) -> Result<bool> {
136        let registry_reader = AvsRegistryChainReader::new(
137            registry_coordinator,
138            Address::ZERO, // operator_state_retriever not needed for this query
139            self.env.http_rpc_endpoint.to_string(),
140        )
141        .await
142        .map_err(|e| {
143            EigenlayerExtraError::Other(format!("Failed to create AVS registry reader: {e}"))
144        })?;
145
146        match registry_reader
147            .is_operator_registered(operator_address)
148            .await
149        {
150            Ok(is_registered) => Ok(is_registered),
151            Err(e) => {
152                warn!(
153                    "Failed to query registration for AVS {:#x}: {}",
154                    registry_coordinator, e
155                );
156                Ok(false) // Treat query failures as not registered
157            }
158        }
159    }
160
161    /// Convert discovered AVS to registration config
162    ///
163    /// Creates an `AvsRegistrationConfig` from discovered on-chain data.
164    /// Note: Some fields like blueprint_path must be provided separately.
165    ///
166    /// # Arguments
167    ///
168    /// * `discovered` - The discovered AVS information
169    /// * `blueprint_path` - Path to the blueprint binary
170    ///
171    /// # Returns
172    ///
173    /// A partial registration config that can be completed with additional data
174    pub fn discovered_to_config(
175        &self,
176        discovered: &DiscoveredAvs,
177        blueprint_path: PathBuf,
178    ) -> Result<AvsRegistrationConfig> {
179        let contract_addresses = self
180            .env
181            .protocol_settings
182            .eigenlayer()
183            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
184
185        Ok(AvsRegistrationConfig {
186            service_manager: discovered.service_manager,
187            registry_coordinator: discovered.registry_coordinator,
188            operator_state_retriever: discovered.operator_state_retriever,
189            strategy_manager: contract_addresses.strategy_manager_address,
190            delegation_manager: contract_addresses.delegation_manager_address,
191            avs_directory: contract_addresses.avs_directory_address,
192            rewards_coordinator: contract_addresses.rewards_coordinator_address,
193            permission_controller: contract_addresses.permission_controller_address,
194            allocation_manager: contract_addresses.allocation_manager_address,
195            strategy_address: contract_addresses.strategy_address,
196            stake_registry: discovered.stake_registry,
197            blueprint_path,
198            container_image: None, // Discovered AVS uses binary, not container
199            runtime_target: crate::RuntimeTarget::default(), // Default to hypervisor for discovered AVS
200            allocation_delay: contract_addresses.allocation_delay,
201            deposit_amount: contract_addresses.deposit_amount,
202            stake_amount: contract_addresses.stake_amount,
203            operator_sets: contract_addresses.operator_sets.clone(),
204        })
205    }
206
207    /// Query operator status for a specific AVS
208    ///
209    /// Returns detailed information about the operator's registration status.
210    ///
211    /// # Arguments
212    ///
213    /// * `operator_address` - The operator's Ethereum address
214    /// * `registry_coordinator` - The registry coordinator contract address
215    ///
216    /// # Errors
217    ///
218    /// Returns error if contract queries fail
219    pub async fn get_operator_status(
220        &self,
221        operator_address: Address,
222        registry_coordinator: Address,
223    ) -> Result<OperatorStatus> {
224        let registry_reader = AvsRegistryChainReader::new(
225            registry_coordinator,
226            Address::ZERO,
227            self.env.http_rpc_endpoint.to_string(),
228        )
229        .await
230        .map_err(|e| {
231            EigenlayerExtraError::Other(format!("Failed to create AVS registry reader: {e}"))
232        })?;
233
234        let is_registered = registry_reader
235            .is_operator_registered(operator_address)
236            .await
237            .map_err(|e| {
238                EigenlayerExtraError::Other(format!("Failed to query operator status: {e}"))
239            })?;
240
241        Ok(OperatorStatus {
242            operator_address,
243            registry_coordinator,
244            is_registered,
245        })
246    }
247}
248
249/// Detailed operator registration status
250#[derive(Debug, Clone)]
251pub struct OperatorStatus {
252    /// The operator's address
253    pub operator_address: Address,
254    /// The registry coordinator being queried
255    pub registry_coordinator: Address,
256    /// Whether the operator is registered
257    pub is_registered: bool,
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_operator_status_creation() {
266        let operator = Address::from([1u8; 20]);
267        let registry = Address::from([2u8; 20]);
268
269        let status = OperatorStatus {
270            operator_address: operator,
271            registry_coordinator: registry,
272            is_registered: true,
273        };
274
275        assert_eq!(status.operator_address, operator);
276        assert_eq!(status.registry_coordinator, registry);
277        assert!(status.is_registered);
278    }
279}