Skip to content

Commit 394d010

Browse files
chore(fortuna) Config API (#2895)
* chore(fortuna) Config API * update * push * requested comments * fmt changes * fix * requested changes * removed state in tests * arc -> & --------- Co-authored-by: Tejas Badadare <tejasbadadare@gmail.com>
1 parent ece3c3d commit 394d010

File tree

4 files changed

+228
-7
lines changed

4 files changed

+228
-7
lines changed

apps/fortuna/src/api.rs

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use {
22
crate::{
33
chain::reader::{BlockNumber, BlockStatus, EntropyReader},
4+
config::Config,
45
history::History,
56
state::MonitoredHashChainState,
67
},
@@ -22,9 +23,12 @@ use {
2223
tokio::sync::RwLock,
2324
url::Url,
2425
};
25-
pub use {chain_ids::*, explorer::*, index::*, live::*, metrics::*, ready::*, revelation::*};
26+
pub use {
27+
chain_ids::*, config::*, explorer::*, index::*, live::*, metrics::*, ready::*, revelation::*,
28+
};
2629

2730
mod chain_ids;
31+
mod config;
2832
mod explorer;
2933
mod index;
3034
mod live;
@@ -73,13 +77,16 @@ pub struct ApiState {
7377
pub metrics: Arc<ApiMetrics>,
7478

7579
pub explorer_metrics: Arc<ExplorerMetrics>,
80+
81+
pub config: Config,
7682
}
7783

7884
impl ApiState {
7985
pub async fn new(
8086
chains: Arc<RwLock<HashMap<ChainId, ApiBlockChainState>>>,
8187
metrics_registry: Arc<RwLock<Registry>>,
8288
history: Arc<History>,
89+
config: &Config,
8390
) -> ApiState {
8491
let metrics = ApiMetrics {
8592
http_requests: Family::default(),
@@ -100,6 +107,7 @@ impl ApiState {
100107
explorer_metrics,
101108
history,
102109
metrics_registry,
110+
config: config.clone(),
103111
}
104112
}
105113
}
@@ -211,6 +219,7 @@ pub fn routes(state: ApiState) -> Router<(), Body> {
211219
"/v1/chains/:chain_id/revelations/:sequence",
212220
get(revelation),
213221
)
222+
.route("/v1/chains/configs", get(get_chain_configs))
214223
.with_state(state)
215224
}
216225

@@ -230,9 +239,10 @@ mod test {
230239
crate::{
231240
api::{
232241
self, ApiBlockChainState, ApiState, BinaryEncoding, Blob, BlockchainState,
233-
GetRandomValueResponse,
242+
ChainConfigSummary, GetRandomValueResponse,
234243
},
235244
chain::reader::{mock::MockEntropyReader, BlockStatus},
245+
config::Config,
236246
history::History,
237247
state::{HashChainState, MonitoredHashChainState, PebbleHashChain},
238248
},
@@ -311,10 +321,40 @@ mod test {
311321
ApiBlockChainState::Initialized(avax_state),
312322
);
313323

324+
// Create a minimal config for testing
325+
let config = Config {
326+
chains: HashMap::new(),
327+
provider: crate::config::ProviderConfig {
328+
uri: "http://localhost:8080/".to_string(),
329+
address: PROVIDER,
330+
private_key: crate::config::SecretString {
331+
value: Some("0xabcd".to_string()),
332+
file: None,
333+
},
334+
secret: crate::config::SecretString {
335+
value: Some("abcd".to_string()),
336+
file: None,
337+
},
338+
chain_length: 100000,
339+
chain_sample_interval: 10,
340+
fee_manager: None,
341+
},
342+
keeper: crate::config::KeeperConfig {
343+
private_key: crate::config::SecretString {
344+
value: Some("0xabcd".to_string()),
345+
file: None,
346+
},
347+
fee_manager_private_key: None,
348+
other_keeper_addresses: vec![],
349+
replica_config: None,
350+
},
351+
};
352+
314353
let api_state = ApiState::new(
315354
Arc::new(RwLock::new(chains)),
316355
metrics_registry,
317356
Arc::new(History::new().await.unwrap()),
357+
&config,
318358
)
319359
.await;
320360

@@ -534,4 +574,151 @@ mod test {
534574
)
535575
.await;
536576
}
577+
578+
#[tokio::test]
579+
async fn test_chain_configs() {
580+
let (server, _, _) = test_server().await;
581+
582+
// Test the chain configs endpoint
583+
let response = server.get("/v1/chains/configs").await;
584+
response.assert_status(StatusCode::OK);
585+
586+
// Parse the response as JSON
587+
let configs: Vec<ChainConfigSummary> = response.json();
588+
589+
// Verify the response structure - should be empty for test server
590+
assert_eq!(
591+
configs.len(),
592+
0,
593+
"Should return empty configs for test server"
594+
);
595+
}
596+
597+
#[tokio::test]
598+
async fn test_chain_configs_with_data() {
599+
// Create a config with actual chain data
600+
let mut config_chains = HashMap::new();
601+
config_chains.insert(
602+
"ethereum".to_string(),
603+
crate::config::EthereumConfig {
604+
geth_rpc_addr: "http://localhost:8545".to_string(),
605+
contract_addr: Address::from_low_u64_be(0x1234),
606+
reveal_delay_blocks: 1,
607+
confirmed_block_status: BlockStatus::Latest,
608+
backlog_range: 1000,
609+
legacy_tx: false,
610+
gas_limit: 500000,
611+
priority_fee_multiplier_pct: 100,
612+
escalation_policy: crate::config::EscalationPolicyConfig::default(),
613+
min_profit_pct: 0,
614+
target_profit_pct: 20,
615+
max_profit_pct: 100,
616+
min_keeper_balance: 100000000000000000,
617+
fee: 1500000000000000,
618+
sync_fee_only_on_register: true,
619+
commitments: None,
620+
max_num_hashes: None,
621+
block_delays: vec![5],
622+
},
623+
);
624+
config_chains.insert(
625+
"avalanche".to_string(),
626+
crate::config::EthereumConfig {
627+
geth_rpc_addr: "http://localhost:9650".to_string(),
628+
contract_addr: Address::from_low_u64_be(0x5678),
629+
reveal_delay_blocks: 2,
630+
confirmed_block_status: BlockStatus::Latest,
631+
backlog_range: 1000,
632+
legacy_tx: false,
633+
gas_limit: 600000,
634+
priority_fee_multiplier_pct: 100,
635+
escalation_policy: crate::config::EscalationPolicyConfig::default(),
636+
min_profit_pct: 0,
637+
target_profit_pct: 20,
638+
max_profit_pct: 100,
639+
min_keeper_balance: 100000000000000000,
640+
fee: 2000000000000000,
641+
sync_fee_only_on_register: true,
642+
commitments: None,
643+
max_num_hashes: None,
644+
block_delays: vec![5],
645+
},
646+
);
647+
648+
let config = Config {
649+
chains: config_chains,
650+
provider: crate::config::ProviderConfig {
651+
uri: "http://localhost:8080/".to_string(),
652+
address: PROVIDER,
653+
private_key: crate::config::SecretString {
654+
value: Some("0xabcd".to_string()),
655+
file: None,
656+
},
657+
secret: crate::config::SecretString {
658+
value: Some("abcd".to_string()),
659+
file: None,
660+
},
661+
chain_length: 100000,
662+
chain_sample_interval: 10,
663+
fee_manager: None,
664+
},
665+
keeper: crate::config::KeeperConfig {
666+
private_key: crate::config::SecretString {
667+
value: Some("0xabcd".to_string()),
668+
file: None,
669+
},
670+
fee_manager_private_key: None,
671+
other_keeper_addresses: vec![],
672+
replica_config: None,
673+
},
674+
};
675+
676+
let metrics_registry = Arc::new(RwLock::new(Registry::default()));
677+
let api_state = ApiState::new(
678+
Arc::new(RwLock::new(HashMap::new())),
679+
metrics_registry,
680+
Arc::new(History::new().await.unwrap()),
681+
&config,
682+
)
683+
.await;
684+
685+
let app = api::routes(api_state);
686+
let server = TestServer::new(app).unwrap();
687+
688+
// Test the chain configs endpoint
689+
let response = server.get("/v1/chains/configs").await;
690+
response.assert_status(StatusCode::OK);
691+
692+
// Parse the response as JSON
693+
let configs: Vec<ChainConfigSummary> = response.json();
694+
695+
// Verify we have 2 chains
696+
assert_eq!(configs.len(), 2, "Should return 2 chain configs");
697+
698+
// Find ethereum config
699+
let eth_config = configs
700+
.iter()
701+
.find(|c| c.name == "ethereum")
702+
.expect("Ethereum config not found");
703+
assert_eq!(
704+
eth_config.contract_addr,
705+
"0x0000000000000000000000000000000000001234"
706+
);
707+
assert_eq!(eth_config.reveal_delay_blocks, 1);
708+
assert_eq!(eth_config.gas_limit, 500000);
709+
assert_eq!(eth_config.fee, 1500000000000000);
710+
711+
// Find avalanche config
712+
let avax_config = configs
713+
.iter()
714+
.find(|c| c.name == "avalanche")
715+
.expect("Avalanche config not found");
716+
assert_eq!(
717+
avax_config.contract_addr,
718+
"0x0000000000000000000000000000000000005678"
719+
);
720+
assert_eq!(avax_config.reveal_delay_blocks, 2);
721+
assert_eq!(avax_config.gas_limit, 600000);
722+
assert_eq!(avax_config.fee, 2000000000000000);
723+
}
537724
}

apps/fortuna/src/api/config.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use {
2+
crate::api::{ApiState, RestError},
3+
axum::{extract::State, Json},
4+
serde::Serialize,
5+
};
6+
7+
#[derive(Serialize, serde::Deserialize)]
8+
pub struct ChainConfigSummary {
9+
pub name: String,
10+
pub contract_addr: String,
11+
pub reveal_delay_blocks: u64,
12+
pub gas_limit: u32,
13+
pub fee: u128,
14+
}
15+
16+
pub async fn get_chain_configs(
17+
State(state): State<ApiState>,
18+
) -> Result<Json<Vec<ChainConfigSummary>>, RestError> {
19+
let mut configs = Vec::new();
20+
for (name, chain) in state.config.chains.iter() {
21+
configs.push(ChainConfigSummary {
22+
name: name.clone(),
23+
contract_addr: format!("0x{:x}", chain.contract_addr),
24+
reveal_delay_blocks: chain.reveal_delay_blocks,
25+
gas_limit: chain.gas_limit,
26+
fee: chain.fee,
27+
});
28+
}
29+
Ok(Json(configs))
30+
}

apps/fortuna/src/command/run.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub async fn run_api(
2828
chains: Arc<RwLock<HashMap<String, ApiBlockChainState>>>,
2929
metrics_registry: Arc<RwLock<Registry>>,
3030
history: Arc<History>,
31+
config: &Config,
3132
mut rx_exit: watch::Receiver<bool>,
3233
) -> Result<()> {
3334
#[derive(OpenApi)]
@@ -54,7 +55,7 @@ pub async fn run_api(
5455
)]
5556
struct ApiDoc;
5657

57-
let api_state = api::ApiState::new(chains, metrics_registry, history).await;
58+
let api_state = api::ApiState::new(chains, metrics_registry, history, config).await;
5859

5960
// Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the
6061
// `with_state` method which replaces `Body` with `State` in the type signature.
@@ -85,7 +86,7 @@ pub async fn run_api(
8586

8687
pub async fn run(opts: &RunOptions) -> Result<()> {
8788
// Load environment variables from a .env file if present
88-
let _ = dotenv::dotenv()?;
89+
let _ = dotenv::dotenv().map_err(|e| anyhow!("Failed to load .env file: {}", e))?;
8990
let config = Config::load(&opts.config.config)?;
9091
let secret = config.provider.secret.load()?.ok_or(anyhow!(
9192
"Please specify a provider secret in the config file."
@@ -170,6 +171,7 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
170171
chains.clone(),
171172
metrics_registry.clone(),
172173
history,
174+
&config,
173175
rx_exit,
174176
)
175177
.await?;

target_chains/cosmwasm/examples/cw-contract/Cargo.lock

Lines changed: 5 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)