Aditya Arora 4 місяців тому
батько
коміт
5b08ea6ede

+ 217 - 1
apps/fortuna/src/api.rs

@@ -1,6 +1,7 @@
 use {
     crate::{
         chain::reader::{BlockNumber, BlockStatus, EntropyReader},
+        config::Config,
         history::History,
         state::MonitoredHashChainState,
     },
@@ -76,6 +77,9 @@ pub struct ApiState {
     pub metrics: Arc<ApiMetrics>,
 
     pub explorer_metrics: Arc<ExplorerMetrics>,
+
+    /// Parsed configuration
+    pub config: Config,
 }
 
 impl ApiState {
@@ -83,6 +87,7 @@ impl ApiState {
         chains: Arc<RwLock<HashMap<ChainId, ApiBlockChainState>>>,
         metrics_registry: Arc<RwLock<Registry>>,
         history: Arc<History>,
+        config: Config,
     ) -> ApiState {
         let metrics = ApiMetrics {
             http_requests: Family::default(),
@@ -103,6 +108,7 @@ impl ApiState {
             explorer_metrics,
             history,
             metrics_registry,
+            config,
         }
     }
 }
@@ -234,9 +240,10 @@ mod test {
         crate::{
             api::{
                 self, ApiBlockChainState, ApiState, BinaryEncoding, Blob, BlockchainState,
-                GetRandomValueResponse,
+                ChainConfigSummary, GetRandomValueResponse,
             },
             chain::reader::{mock::MockEntropyReader, BlockStatus},
+            config::Config,
             history::History,
             state::{HashChainState, MonitoredHashChainState, PebbleHashChain},
         },
@@ -315,10 +322,40 @@ mod test {
             ApiBlockChainState::Initialized(avax_state),
         );
 
+        // Create a minimal config for testing
+        let config = Config {
+            chains: HashMap::new(),
+            provider: crate::config::ProviderConfig {
+                uri: "http://localhost:8080/".to_string(),
+                address: PROVIDER,
+                private_key: crate::config::SecretString {
+                    value: Some("0xabcd".to_string()),
+                    file: None,
+                },
+                secret: crate::config::SecretString {
+                    value: Some("abcd".to_string()),
+                    file: None,
+                },
+                chain_length: 100000,
+                chain_sample_interval: 10,
+                fee_manager: None,
+            },
+            keeper: crate::config::KeeperConfig {
+                private_key: crate::config::SecretString {
+                    value: Some("0xabcd".to_string()),
+                    file: None,
+                },
+                fee_manager_private_key: None,
+                other_keeper_addresses: vec![],
+                replica_config: None,
+            },
+        };
+
         let api_state = ApiState::new(
             Arc::new(RwLock::new(chains)),
             metrics_registry,
             Arc::new(History::new().await.unwrap()),
+            config,
         )
         .await;
 
@@ -538,4 +575,183 @@ mod test {
         )
         .await;
     }
+
+    #[tokio::test]
+    async fn test_chain_configs() {
+        let (server, _, _) = test_server().await;
+
+        // Test the chain configs endpoint
+        let response = server.get("/v1/chains/configs").await;
+        response.assert_status(StatusCode::OK);
+
+        // Parse the response as JSON
+        let configs: Vec<ChainConfigSummary> = response.json();
+        
+        // Verify the response structure - should be empty for test server
+        assert_eq!(configs.len(), 0, "Should return empty configs for test server");
+    }
+
+    #[tokio::test]
+    async fn test_chain_configs_with_data() {
+        // Create a test server with actual chain configurations
+        let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
+        let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
+
+        let eth_state = MonitoredHashChainState::new(
+            ETH_CHAIN.clone(),
+            Default::default(),
+            "ethereum".into(),
+            PROVIDER,
+        );
+
+        let eth_state = BlockchainState {
+            id: "ethereum".into(),
+            network_id: 1,
+            state: Arc::new(eth_state),
+            contract: eth_read.clone(),
+            provider_address: PROVIDER,
+            reveal_delay_blocks: 1,
+            confirmed_block_status: BlockStatus::Latest,
+        };
+
+        let avax_state = MonitoredHashChainState::new(
+            AVAX_CHAIN.clone(),
+            Default::default(),
+            "avalanche".into(),
+            PROVIDER,
+        );
+
+        let avax_state = BlockchainState {
+            id: "avalanche".into(),
+            network_id: 43114,
+            state: Arc::new(avax_state),
+            contract: avax_read.clone(),
+            provider_address: PROVIDER,
+            reveal_delay_blocks: 2,
+            confirmed_block_status: BlockStatus::Latest,
+        };
+
+        let mut chains = HashMap::new();
+        chains.insert(
+            "ethereum".into(),
+            ApiBlockChainState::Initialized(eth_state),
+        );
+        chains.insert(
+            "avalanche".into(),
+            ApiBlockChainState::Initialized(avax_state),
+        );
+
+        // Create a config with actual chain data
+        let mut config_chains = HashMap::new();
+        config_chains.insert(
+            "ethereum".to_string(),
+            crate::config::EthereumConfig {
+                geth_rpc_addr: "http://localhost:8545".to_string(),
+                contract_addr: Address::from_low_u64_be(0x1234),
+                reveal_delay_blocks: 1,
+                confirmed_block_status: BlockStatus::Latest,
+                backlog_range: 1000,
+                legacy_tx: false,
+                gas_limit: 500000,
+                priority_fee_multiplier_pct: 100,
+                escalation_policy: crate::config::EscalationPolicyConfig::default(),
+                min_profit_pct: 0,
+                target_profit_pct: 20,
+                max_profit_pct: 100,
+                min_keeper_balance: 100000000000000000,
+                fee: 1500000000000000,
+                sync_fee_only_on_register: true,
+                commitments: None,
+                max_num_hashes: None,
+                block_delays: vec![5],
+            },
+        );
+        config_chains.insert(
+            "avalanche".to_string(),
+            crate::config::EthereumConfig {
+                geth_rpc_addr: "http://localhost:9650".to_string(),
+                contract_addr: Address::from_low_u64_be(0x5678),
+                reveal_delay_blocks: 2,
+                confirmed_block_status: BlockStatus::Latest,
+                backlog_range: 1000,
+                legacy_tx: false,
+                gas_limit: 600000,
+                priority_fee_multiplier_pct: 100,
+                escalation_policy: crate::config::EscalationPolicyConfig::default(),
+                min_profit_pct: 0,
+                target_profit_pct: 20,
+                max_profit_pct: 100,
+                min_keeper_balance: 100000000000000000,
+                fee: 2000000000000000,
+                sync_fee_only_on_register: true,
+                commitments: None,
+                max_num_hashes: None,
+                block_delays: vec![5],
+            },
+        );
+
+        let config = Config {
+            chains: config_chains,
+            provider: crate::config::ProviderConfig {
+                uri: "http://localhost:8080/".to_string(),
+                address: PROVIDER,
+                private_key: crate::config::SecretString {
+                    value: Some("0xabcd".to_string()),
+                    file: None,
+                },
+                secret: crate::config::SecretString {
+                    value: Some("abcd".to_string()),
+                    file: None,
+                },
+                chain_length: 100000,
+                chain_sample_interval: 10,
+                fee_manager: None,
+            },
+            keeper: crate::config::KeeperConfig {
+                private_key: crate::config::SecretString {
+                    value: Some("0xabcd".to_string()),
+                    file: None,
+                },
+                fee_manager_private_key: None,
+                other_keeper_addresses: vec![],
+                replica_config: None,
+            },
+        };
+
+        let metrics_registry = Arc::new(RwLock::new(Registry::default()));
+        let api_state = ApiState::new(
+            Arc::new(RwLock::new(chains)),
+            metrics_registry,
+            Arc::new(History::new().await.unwrap()),
+            config,
+        )
+        .await;
+
+        let app = api::routes(api_state);
+        let server = TestServer::new(app).unwrap();
+
+        // Test the chain configs endpoint
+        let response = server.get("/v1/chains/configs").await;
+        response.assert_status(StatusCode::OK);
+
+        // Parse the response as JSON
+        let configs: Vec<ChainConfigSummary> = response.json();
+        
+        // Verify we have 2 chains
+        assert_eq!(configs.len(), 2, "Should return 2 chain configs");
+        
+        // Find ethereum config
+        let eth_config = configs.iter().find(|c| c.name == "ethereum").expect("Ethereum config not found");
+        assert_eq!(eth_config.contract_addr, "0x0000000000000000000000000000000000001234");
+        assert_eq!(eth_config.reveal_delay_blocks, 1);
+        assert_eq!(eth_config.gas_limit, 500000);
+        assert_eq!(eth_config.fee, 1500000000000000);
+        
+        // Find avalanche config
+        let avax_config = configs.iter().find(|c| c.name == "avalanche").expect("Avalanche config not found");
+        assert_eq!(avax_config.contract_addr, "0x0000000000000000000000000000000000005678");
+        assert_eq!(avax_config.reveal_delay_blocks, 2);
+        assert_eq!(avax_config.gas_limit, 600000);
+        assert_eq!(avax_config.fee, 2000000000000000);
+    }
 }

+ 7 - 17
apps/fortuna/src/api/config.rs

@@ -1,10 +1,10 @@
-use crate::{
-    api::{ApiState, RestError},
-    config::Config,
+use {
+    crate::api::{ApiState, RestError},
+    axum::{extract::State, Json},
+    serde::Serialize,
 };
-use axum::{extract::State, Json};
 
-#[derive(serde::Serialize)]
+#[derive(Serialize, serde::Deserialize)]
 pub struct ChainConfigSummary {
     pub name: String,
     pub contract_addr: String,
@@ -13,19 +13,9 @@ pub struct ChainConfigSummary {
     pub fee: u128,
 }
 
-pub async fn get_chain_configs(
-    State(_state): State<ApiState>,
-) -> anyhow::Result<Json<Vec<ChainConfigSummary>>, RestError> {
-    let yaml_content = std::fs::read_to_string("config.yaml").map_err(|e| {
-        tracing::error!("Failed to read config file: {}", e);
-        RestError::Unknown
-    })?;
-    let config: Config = serde_yaml::from_str(&yaml_content).map_err(|e| {
-        tracing::error!("Failed to parse config file: {}", e);
-        RestError::Unknown
-    })?;
+pub async fn get_chain_configs(State(state): State<ApiState>) -> Result<Json<Vec<ChainConfigSummary>>, RestError> {
     let mut configs = Vec::new();
-    for (name, chain) in config.chains.iter() {
+    for (name, chain) in state.config.chains.iter() {
         configs.push(ChainConfigSummary {
             name: name.clone(),
             contract_addr: format!("0x{:x}", chain.contract_addr),

+ 3 - 1
apps/fortuna/src/command/run.rs

@@ -28,6 +28,7 @@ pub async fn run_api(
     chains: Arc<RwLock<HashMap<String, ApiBlockChainState>>>,
     metrics_registry: Arc<RwLock<Registry>>,
     history: Arc<History>,
+    config: Config,
     mut rx_exit: watch::Receiver<bool>,
 ) -> Result<()> {
     #[derive(OpenApi)]
@@ -54,7 +55,7 @@ pub async fn run_api(
     )]
     struct ApiDoc;
 
-    let api_state = api::ApiState::new(chains, metrics_registry, history).await;
+    let api_state = api::ApiState::new(chains, metrics_registry, history, config).await;
 
     // Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the
     // `with_state` method which replaces `Body` with `State` in the type signature.
@@ -170,6 +171,7 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
         chains.clone(),
         metrics_registry.clone(),
         history,
+        config,
         rx_exit,
     )
     .await?;

+ 5 - 3
target_chains/cosmwasm/examples/cw-contract/Cargo.lock

@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "ahash"
@@ -485,7 +485,9 @@ dependencies = [
 
 [[package]]
 name = "pyth-sdk-cw"
-version = "0.1.0"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c04e9f2961bce1ef13b09afcdb5aee7d4ddde83669e5f9d2824ba422cb00de48"
 dependencies = [
  "cosmwasm-schema",
  "cosmwasm-std",
@@ -747,4 +749,4 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 name = "zeroize"
 version = "1.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
+checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"