Skip to content

Commit e190b37

Browse files
committed
Merge branch 'main' of github.com:pyth-network/pyth-crosschain into feat/pyth-lazer/agent/jrpc-notifications-1
2 parents 63d3c89 + 6853773 commit e190b37

File tree

15 files changed

+829
-84
lines changed

15 files changed

+829
-84
lines changed

Cargo.lock

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

apps/fortuna/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fortuna"
3-
version = "8.2.2"
3+
version = "8.2.5"
44
edition = "2021"
55

66
[lib]

apps/fortuna/src/api.rs

Lines changed: 250 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,212 @@ 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+
use crate::api::get_chain_configs;
600+
use axum::{routing::get, Router};
601+
602+
// Create a config with actual chain data
603+
let mut config_chains = HashMap::new();
604+
config_chains.insert(
605+
"ethereum".to_string(),
606+
crate::config::EthereumConfig {
607+
geth_rpc_addr: "http://localhost:8545".to_string(),
608+
contract_addr: Address::from_low_u64_be(0x1234),
609+
reveal_delay_blocks: 1,
610+
confirmed_block_status: BlockStatus::Latest,
611+
backlog_range: 1000,
612+
legacy_tx: false,
613+
gas_limit: 500000,
614+
priority_fee_multiplier_pct: 100,
615+
escalation_policy: crate::config::EscalationPolicyConfig::default(),
616+
min_profit_pct: 0,
617+
target_profit_pct: 20,
618+
max_profit_pct: 100,
619+
min_keeper_balance: 100000000000000000,
620+
fee: 1500000000000000,
621+
sync_fee_only_on_register: true,
622+
commitments: None,
623+
max_num_hashes: None,
624+
block_delays: vec![5],
625+
},
626+
);
627+
config_chains.insert(
628+
"avalanche".to_string(),
629+
crate::config::EthereumConfig {
630+
geth_rpc_addr: "http://localhost:9650".to_string(),
631+
contract_addr: Address::from_low_u64_be(0x5678),
632+
reveal_delay_blocks: 2,
633+
confirmed_block_status: BlockStatus::Latest,
634+
backlog_range: 1000,
635+
legacy_tx: false,
636+
gas_limit: 600000,
637+
priority_fee_multiplier_pct: 100,
638+
escalation_policy: crate::config::EscalationPolicyConfig::default(),
639+
min_profit_pct: 0,
640+
target_profit_pct: 20,
641+
max_profit_pct: 100,
642+
min_keeper_balance: 100000000000000000,
643+
fee: 2000000000000000,
644+
sync_fee_only_on_register: true,
645+
commitments: None,
646+
max_num_hashes: None,
647+
block_delays: vec![5],
648+
},
649+
);
650+
651+
let config = Config {
652+
chains: config_chains,
653+
provider: crate::config::ProviderConfig {
654+
uri: "http://localhost:8080/".to_string(),
655+
address: PROVIDER,
656+
private_key: crate::config::SecretString {
657+
value: Some("0xabcd".to_string()),
658+
file: None,
659+
},
660+
secret: crate::config::SecretString {
661+
value: Some("abcd".to_string()),
662+
file: None,
663+
},
664+
chain_length: 100000,
665+
chain_sample_interval: 10,
666+
fee_manager: None,
667+
},
668+
keeper: crate::config::KeeperConfig {
669+
private_key: crate::config::SecretString {
670+
value: Some("0xabcd".to_string()),
671+
file: None,
672+
},
673+
fee_manager_private_key: None,
674+
other_keeper_addresses: vec![],
675+
replica_config: None,
676+
},
677+
};
678+
679+
// Create initialized blockchain states with network IDs
680+
let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
681+
let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
682+
683+
let eth_state = MonitoredHashChainState::new(
684+
ETH_CHAIN.clone(),
685+
Default::default(),
686+
"ethereum".into(),
687+
PROVIDER,
688+
);
689+
690+
let eth_blockchain_state = BlockchainState {
691+
id: "ethereum".into(),
692+
network_id: 1, // Ethereum mainnet
693+
state: Arc::new(eth_state),
694+
contract: eth_read.clone(),
695+
provider_address: PROVIDER,
696+
reveal_delay_blocks: 1,
697+
confirmed_block_status: BlockStatus::Latest,
698+
};
699+
700+
let avax_state = MonitoredHashChainState::new(
701+
AVAX_CHAIN.clone(),
702+
Default::default(),
703+
"avalanche".into(),
704+
PROVIDER,
705+
);
706+
707+
let avax_blockchain_state = BlockchainState {
708+
id: "avalanche".into(),
709+
network_id: 43114, // Avalanche C-Chain
710+
state: Arc::new(avax_state),
711+
contract: avax_read.clone(),
712+
provider_address: PROVIDER,
713+
reveal_delay_blocks: 2,
714+
confirmed_block_status: BlockStatus::Latest,
715+
};
716+
717+
// Create chains HashMap with initialized states
718+
let mut chains = HashMap::new();
719+
chains.insert(
720+
"ethereum".into(),
721+
ApiBlockChainState::Initialized(eth_blockchain_state),
722+
);
723+
chains.insert(
724+
"avalanche".into(),
725+
ApiBlockChainState::Initialized(avax_blockchain_state),
726+
);
727+
728+
// Minimal ApiState for this endpoint
729+
let api_state = ApiState {
730+
chains: Arc::new(RwLock::new(chains)),
731+
history: Arc::new(History::new().await.unwrap()),
732+
metrics_registry: Arc::new(RwLock::new(Registry::default())),
733+
metrics: Arc::new(crate::api::ApiMetrics {
734+
http_requests: prometheus_client::metrics::family::Family::default(),
735+
}),
736+
explorer_metrics: Arc::new(
737+
crate::api::ExplorerMetrics::new(Arc::new(RwLock::new(Registry::default()))).await,
738+
),
739+
config,
740+
};
741+
742+
let app = Router::new()
743+
.route("/v1/chains/configs", get(get_chain_configs))
744+
.with_state(api_state);
745+
let server = TestServer::new(app).unwrap();
746+
747+
// Test the chain configs endpoint
748+
let response = server.get("/v1/chains/configs").await;
749+
response.assert_status(StatusCode::OK);
750+
751+
// Parse the response as JSON
752+
let configs: Vec<ChainConfigSummary> = response.json();
753+
754+
// Verify we have 2 chains
755+
assert_eq!(configs.len(), 2, "Should return 2 chain configs");
756+
757+
// Find ethereum config
758+
let eth_config = configs
759+
.iter()
760+
.find(|c| c.name == "ethereum")
761+
.expect("Ethereum config not found");
762+
assert_eq!(
763+
eth_config.contract_addr,
764+
"0x0000000000000000000000000000000000001234"
765+
);
766+
assert_eq!(eth_config.reveal_delay_blocks, 1);
767+
assert_eq!(eth_config.gas_limit, 500000);
768+
assert_eq!(eth_config.default_fee, 1500000000000000);
769+
assert_eq!(eth_config.network_id, 1); // Ethereum mainnet
770+
771+
// Find avalanche config
772+
let avax_config = configs
773+
.iter()
774+
.find(|c| c.name == "avalanche")
775+
.expect("Avalanche config not found");
776+
assert_eq!(
777+
avax_config.contract_addr,
778+
"0x0000000000000000000000000000000000005678"
779+
);
780+
assert_eq!(avax_config.reveal_delay_blocks, 2);
781+
assert_eq!(avax_config.gas_limit, 600000);
782+
assert_eq!(avax_config.default_fee, 2000000000000000);
783+
assert_eq!(avax_config.network_id, 43114); // Avalanche C-Chain
784+
}
537785
}

apps/fortuna/src/api/config.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use {
2+
crate::api::{ApiBlockChainState, 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 network_id: u64,
11+
pub contract_addr: String,
12+
pub reveal_delay_blocks: u64,
13+
pub gas_limit: u32,
14+
pub default_fee: u128,
15+
}
16+
17+
pub async fn get_chain_configs(
18+
State(state): State<ApiState>,
19+
) -> Result<Json<Vec<ChainConfigSummary>>, RestError> {
20+
let mut configs = Vec::new();
21+
for (name, chain) in state.config.chains.iter() {
22+
let network_id = match state.chains.read().await.get(name) {
23+
Some(ApiBlockChainState::Initialized(blockchain_state)) => blockchain_state.network_id,
24+
_ => 0,
25+
};
26+
configs.push(ChainConfigSummary {
27+
name: name.clone(),
28+
network_id,
29+
contract_addr: format!("0x{:x}", chain.contract_addr),
30+
reveal_delay_blocks: chain.reveal_delay_blocks,
31+
gas_limit: chain.gas_limit,
32+
default_fee: chain.fee,
33+
});
34+
}
35+
Ok(Json(configs))
36+
}

0 commit comments

Comments
 (0)