run.rs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. use {
  2. crate::{
  3. api,
  4. chain::ethereum::PythContract,
  5. command::register_provider::CommitmentMetadata,
  6. config::{
  7. Config,
  8. RunOptions,
  9. },
  10. state::{
  11. HashChainState,
  12. PebbleHashChain,
  13. },
  14. },
  15. anyhow::{
  16. anyhow,
  17. Result,
  18. },
  19. axum::Router,
  20. std::{
  21. collections::HashMap,
  22. sync::Arc,
  23. },
  24. tower_http::cors::CorsLayer,
  25. utoipa::OpenApi,
  26. utoipa_swagger_ui::SwaggerUi,
  27. };
  28. pub async fn run(opts: &RunOptions) -> Result<()> {
  29. #[derive(OpenApi)]
  30. #[openapi(
  31. paths(
  32. crate::api::revelation,
  33. crate::api::chain_ids,
  34. ),
  35. components(
  36. schemas(
  37. crate::api::GetRandomValueResponse,
  38. crate::api::Blob,
  39. crate::api::BinaryEncoding,
  40. )
  41. ),
  42. tags(
  43. (name = "fortuna", description = "Random number service for the Pyth Entropy protocol")
  44. )
  45. )]
  46. struct ApiDoc;
  47. let config = Config::load(&opts.config.config)?;
  48. let secret: String;
  49. match opts.randomness.load_secret() {
  50. Ok(loaded_secret) => secret = loaded_secret,
  51. Err(_err) => secret = opts.randomness.secret_file.clone(),
  52. }
  53. let mut chains = HashMap::new();
  54. for (chain_id, chain_config) in &config.chains {
  55. let contract = Arc::new(PythContract::from_config(&chain_config)?);
  56. let provider_info = contract.get_provider_info(opts.provider).call().await?;
  57. // Reconstruct the hash chain based on the metadata and check that it matches the on-chain commitment.
  58. // TODO: we should instantiate the state here with multiple hash chains.
  59. // This approach works fine as long as we haven't rotated the commitment (i.e., all user requests
  60. // are for the most recent chain).
  61. // TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains,
  62. // then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain
  63. // later when a user request comes in for that chain.
  64. let metadata =
  65. bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)?;
  66. let hash_chain = PebbleHashChain::from_config(
  67. &secret,
  68. &chain_id,
  69. &metadata.seed,
  70. metadata.chain_length,
  71. )?;
  72. let chain_state = HashChainState {
  73. offsets: vec![provider_info
  74. .original_commitment_sequence_number
  75. .try_into()?],
  76. hash_chains: vec![hash_chain],
  77. };
  78. if chain_state.reveal(provider_info.original_commitment_sequence_number)?
  79. != provider_info.original_commitment
  80. {
  81. 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());
  82. } else {
  83. tracing::info!("Root of chain id {} matches commitment", &chain_id);
  84. }
  85. let state = api::BlockchainState {
  86. state: Arc::new(chain_state),
  87. contract,
  88. provider_address: opts.provider,
  89. reveal_delay_blocks: chain_config.reveal_delay_blocks,
  90. };
  91. chains.insert(chain_id.clone(), state);
  92. }
  93. let metrics_registry = api::Metrics::new();
  94. let api_state = api::ApiState {
  95. chains: Arc::new(chains),
  96. metrics: Arc::new(metrics_registry),
  97. };
  98. // Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the
  99. // `with_state` method which replaces `Body` with `State` in the type signature.
  100. let app = Router::new();
  101. let app = app
  102. .merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi()))
  103. .merge(api::routes(api_state))
  104. // Permissive CORS layer to allow all origins
  105. .layer(CorsLayer::permissive());
  106. tracing::info!("Starting server on: {:?}", &opts.addr);
  107. // Binds the axum's server to the configured address and port. This is a blocking call and will
  108. // not return until the server is shutdown.
  109. axum::Server::try_bind(&opts.addr)?
  110. .serve(app.into_make_service())
  111. .await?;
  112. Ok(())
  113. }