revelation.rs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. use {
  2. crate::api::{ChainId, RequestLabel, RestError},
  3. anyhow::Result,
  4. axum::{
  5. extract::{Path, Query, State},
  6. Json,
  7. },
  8. pythnet_sdk::wire::array,
  9. serde_with::serde_as,
  10. tokio::try_join,
  11. utoipa::{IntoParams, ToSchema},
  12. };
  13. /// Reveal the random value for a given sequence number and blockchain.
  14. ///
  15. /// Given a sequence number, retrieve the corresponding random value that this provider has committed to.
  16. /// This endpoint will not return the random value unless someone has requested the sequence number on-chain.
  17. ///
  18. /// Every blockchain supported by this service has a distinct sequence of random numbers and chain_id.
  19. /// Callers must pass the appropriate chain_id to ensure they fetch the correct random number.
  20. #[utoipa::path(
  21. get,
  22. path = "/v1/chains/{chain_id}/revelations/{sequence}",
  23. responses(
  24. (status = 200, description = "Random value successfully retrieved", body = GetRandomValueResponse),
  25. (status = 403, description = "Random value cannot currently be retrieved", body = String)
  26. ),
  27. params(RevelationPathParams, RevelationQueryParams)
  28. )]
  29. pub async fn revelation(
  30. State(state): State<crate::api::ApiState>,
  31. Path(RevelationPathParams { chain_id, sequence }): Path<RevelationPathParams>,
  32. Query(RevelationQueryParams { encoding }): Query<RevelationQueryParams>,
  33. ) -> Result<Json<GetRandomValueResponse>, RestError> {
  34. state
  35. .metrics
  36. .http_requests
  37. .get_or_create(&RequestLabel {
  38. value: "/v1/chains/{chain_id}/revelations/{sequence}".to_string(),
  39. })
  40. .inc();
  41. let state = state
  42. .chains
  43. .get(&chain_id)
  44. .ok_or(RestError::InvalidChainId)?;
  45. let maybe_request_fut = state.contract.get_request(state.provider_address, sequence);
  46. let current_block_number_fut = state
  47. .contract
  48. .get_block_number(state.confirmed_block_status);
  49. let (maybe_request, current_block_number) =
  50. try_join!(maybe_request_fut, current_block_number_fut).map_err(|e| {
  51. tracing::error!(chain_id = chain_id, "RPC request failed {}", e);
  52. RestError::TemporarilyUnavailable
  53. })?;
  54. match maybe_request {
  55. Some(r)
  56. if current_block_number.saturating_sub(state.reveal_delay_blocks) >= r.block_number =>
  57. {
  58. let value = &state.state.reveal(sequence).map_err(|e| {
  59. tracing::error!(
  60. chain_id = chain_id,
  61. sequence = sequence,
  62. "Reveal failed {}",
  63. e
  64. );
  65. RestError::Unknown
  66. })?;
  67. let encoded_value = Blob::new(encoding.unwrap_or(BinaryEncoding::Hex), *value);
  68. Ok(Json(GetRandomValueResponse {
  69. value: encoded_value,
  70. }))
  71. }
  72. Some(_) => Err(RestError::PendingConfirmation),
  73. None => Err(RestError::NoPendingRequest),
  74. }
  75. }
  76. #[derive(Debug, serde::Serialize, serde::Deserialize, IntoParams)]
  77. #[into_params(parameter_in=Path)]
  78. pub struct RevelationPathParams {
  79. #[param(value_type = String)]
  80. pub chain_id: ChainId,
  81. pub sequence: u64,
  82. }
  83. #[derive(Debug, serde::Serialize, serde::Deserialize, IntoParams)]
  84. #[into_params(parameter_in=Query)]
  85. pub struct RevelationQueryParams {
  86. pub encoding: Option<BinaryEncoding>,
  87. }
  88. #[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema)]
  89. #[serde(rename_all = "kebab-case")]
  90. pub enum BinaryEncoding {
  91. #[serde(rename = "hex")]
  92. Hex,
  93. #[serde(rename = "base64")]
  94. Base64,
  95. #[serde(rename = "array")]
  96. Array,
  97. }
  98. #[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema, PartialEq)]
  99. pub struct GetRandomValueResponse {
  100. pub value: Blob,
  101. }
  102. #[serde_as]
  103. #[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema, PartialEq)]
  104. #[serde(tag = "encoding", rename_all = "kebab-case")]
  105. pub enum Blob {
  106. Hex {
  107. #[serde_as(as = "serde_with::hex::Hex")]
  108. data: [u8; 32],
  109. },
  110. Base64 {
  111. #[serde_as(as = "serde_with::base64::Base64")]
  112. data: [u8; 32],
  113. },
  114. Array {
  115. #[serde(with = "array")]
  116. data: [u8; 32],
  117. },
  118. }
  119. impl Blob {
  120. pub fn new(encoding: BinaryEncoding, data: [u8; 32]) -> Blob {
  121. match encoding {
  122. BinaryEncoding::Hex => Blob::Hex { data },
  123. BinaryEncoding::Base64 => Blob::Base64 { data },
  124. BinaryEncoding::Array => Blob::Array { data },
  125. }
  126. }
  127. pub fn data(&self) -> &[u8; 32] {
  128. match self {
  129. Blob::Hex { data } => data,
  130. Blob::Base64 { data } => data,
  131. Blob::Array { data } => data,
  132. }
  133. }
  134. }