| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- use {
- crate::{
- api,
- chain::ethereum::PythContract,
- command::register_provider::CommitmentMetadata,
- config::{
- Config,
- RunOptions,
- },
- state::{
- HashChainState,
- PebbleHashChain,
- },
- },
- anyhow::{
- anyhow,
- Result,
- },
- axum::Router,
- std::{
- collections::HashMap,
- sync::Arc,
- },
- tower_http::cors::CorsLayer,
- utoipa::OpenApi,
- utoipa_swagger_ui::SwaggerUi,
- };
- pub async fn run(opts: &RunOptions) -> Result<()> {
- #[derive(OpenApi)]
- #[openapi(
- paths(
- crate::api::revelation,
- crate::api::chain_ids,
- ),
- components(
- schemas(
- crate::api::GetRandomValueResponse,
- crate::api::Blob,
- crate::api::BinaryEncoding,
- )
- ),
- tags(
- (name = "fortuna", description = "Random number service for the Pyth Entropy protocol")
- )
- )]
- struct ApiDoc;
- let config = Config::load(&opts.config.config)?;
- let secret: String;
- match opts.randomness.load_secret() {
- Ok(loaded_secret) => secret = loaded_secret,
- Err(_err) => secret = opts.randomness.secret_file.clone(),
- }
- let mut chains = HashMap::new();
- for (chain_id, chain_config) in &config.chains {
- let contract = Arc::new(PythContract::from_config(&chain_config)?);
- let provider_info = contract.get_provider_info(opts.provider).call().await?;
- // Reconstruct the hash chain based on the metadata and check that it matches the on-chain commitment.
- // TODO: we should instantiate the state here with multiple hash chains.
- // This approach works fine as long as we haven't rotated the commitment (i.e., all user requests
- // are for the most recent chain).
- // TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains,
- // then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain
- // later when a user request comes in for that chain.
- let metadata =
- bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)?;
- let hash_chain = PebbleHashChain::from_config(
- &secret,
- &chain_id,
- &metadata.seed,
- metadata.chain_length,
- )?;
- let chain_state = HashChainState {
- offsets: vec![provider_info
- .original_commitment_sequence_number
- .try_into()?],
- hash_chains: vec![hash_chain],
- };
- if chain_state.reveal(provider_info.original_commitment_sequence_number)?
- != provider_info.original_commitment
- {
- return Err(anyhow!("The root of the generated hash chain for chain id {} does not match the commitment. Are the secret and chain length configured correctly?", &chain_id).into());
- } else {
- tracing::info!("Root of chain id {} matches commitment", &chain_id);
- }
- let state = api::BlockchainState {
- state: Arc::new(chain_state),
- contract,
- provider_address: opts.provider,
- reveal_delay_blocks: chain_config.reveal_delay_blocks,
- };
- chains.insert(chain_id.clone(), state);
- }
- let metrics_registry = api::Metrics::new();
- let api_state = api::ApiState {
- chains: Arc::new(chains),
- metrics: Arc::new(metrics_registry),
- };
- // 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.
- let app = Router::new();
- let app = app
- .merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi()))
- .merge(api::routes(api_state))
- // Permissive CORS layer to allow all origins
- .layer(CorsLayer::permissive());
- tracing::info!("Starting server on: {:?}", &opts.addr);
- // Binds the axum's server to the configured address and port. This is a blocking call and will
- // not return until the server is shutdown.
- axum::Server::try_bind(&opts.addr)?
- .serve(app.into_make_service())
- .await?;
- Ok(())
- }
|