api.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. use {
  2. crate::{
  3. chain::reader::{BlockNumber, BlockStatus, EntropyReader},
  4. state::HashChainState,
  5. },
  6. anyhow::Result,
  7. axum::{
  8. body::Body,
  9. http::StatusCode,
  10. response::{IntoResponse, Response},
  11. routing::get,
  12. Router,
  13. },
  14. ethers::core::types::Address,
  15. prometheus_client::{
  16. encoding::EncodeLabelSet,
  17. metrics::{counter::Counter, family::Family},
  18. registry::Registry,
  19. },
  20. std::{collections::HashMap, sync::Arc},
  21. tokio::sync::RwLock,
  22. url::Url,
  23. };
  24. pub use {chain_ids::*, index::*, live::*, metrics::*, ready::*, revelation::*};
  25. mod chain_ids;
  26. mod index;
  27. mod live;
  28. mod metrics;
  29. mod ready;
  30. mod revelation;
  31. pub type ChainId = String;
  32. #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
  33. pub struct RequestLabel {
  34. pub value: String,
  35. }
  36. pub struct ApiMetrics {
  37. pub http_requests: Family<RequestLabel, Counter>,
  38. }
  39. #[derive(Clone)]
  40. pub struct ApiState {
  41. pub chains: Arc<HashMap<ChainId, BlockchainState>>,
  42. pub metrics_registry: Arc<RwLock<Registry>>,
  43. /// Prometheus metrics
  44. pub metrics: Arc<ApiMetrics>,
  45. }
  46. impl ApiState {
  47. pub async fn new(
  48. chains: HashMap<ChainId, BlockchainState>,
  49. metrics_registry: Arc<RwLock<Registry>>,
  50. ) -> ApiState {
  51. let metrics = ApiMetrics {
  52. http_requests: Family::default(),
  53. };
  54. let http_requests = metrics.http_requests.clone();
  55. metrics_registry.write().await.register(
  56. "http_requests",
  57. "Number of HTTP requests received",
  58. http_requests,
  59. );
  60. ApiState {
  61. chains: Arc::new(chains),
  62. metrics: Arc::new(metrics),
  63. metrics_registry,
  64. }
  65. }
  66. }
  67. /// The state of the randomness service for a single blockchain.
  68. #[derive(Clone)]
  69. pub struct BlockchainState {
  70. /// The chain id for this blockchain, useful for logging
  71. pub id: ChainId,
  72. /// The hash chain(s) required to serve random numbers for this blockchain
  73. pub state: Arc<HashChainState>,
  74. /// The contract that the server is fulfilling requests for.
  75. pub contract: Arc<dyn EntropyReader>,
  76. /// The address of the provider that this server is operating for.
  77. pub provider_address: Address,
  78. /// The server will wait for this many block confirmations of a request before revealing
  79. /// the random number.
  80. pub reveal_delay_blocks: BlockNumber,
  81. /// The BlockStatus of the block that is considered to be confirmed on the blockchain.
  82. /// For eg., Finalized, Safe
  83. pub confirmed_block_status: BlockStatus,
  84. }
  85. pub enum RestError {
  86. /// The caller passed a sequence number that isn't within the supported range
  87. InvalidSequenceNumber,
  88. /// The caller passed an unsupported chain id
  89. InvalidChainId,
  90. /// The caller requested a random value that can't currently be revealed (because it
  91. /// hasn't been committed to on-chain)
  92. NoPendingRequest,
  93. /// The request exists, but the server is waiting for more confirmations (more blocks
  94. /// to be mined) before revealing the random number.
  95. PendingConfirmation,
  96. /// The server cannot currently communicate with the blockchain, so is not able to verify
  97. /// which random values have been requested.
  98. TemporarilyUnavailable,
  99. /// A catch-all error for all other types of errors that could occur during processing.
  100. Unknown,
  101. }
  102. impl IntoResponse for RestError {
  103. fn into_response(self) -> Response {
  104. match self {
  105. RestError::InvalidSequenceNumber => (
  106. StatusCode::BAD_REQUEST,
  107. "The sequence number is out of the permitted range",
  108. )
  109. .into_response(),
  110. RestError::InvalidChainId => {
  111. (StatusCode::BAD_REQUEST, "The chain id is not supported").into_response()
  112. }
  113. RestError::NoPendingRequest => (
  114. StatusCode::FORBIDDEN,
  115. "The request with the given sequence number has not been made yet, or the random value has already been revealed on chain.",
  116. ).into_response(),
  117. RestError::PendingConfirmation => (
  118. StatusCode::FORBIDDEN,
  119. "The request needs additional confirmations before the random value can be retrieved. Try your request again later.",
  120. )
  121. .into_response(),
  122. RestError::TemporarilyUnavailable => (
  123. StatusCode::SERVICE_UNAVAILABLE,
  124. "This service is temporarily unavailable",
  125. )
  126. .into_response(),
  127. RestError::Unknown => (
  128. StatusCode::INTERNAL_SERVER_ERROR,
  129. "An unknown error occurred processing the request",
  130. )
  131. .into_response(),
  132. }
  133. }
  134. }
  135. pub fn routes(state: ApiState) -> Router<(), Body> {
  136. Router::new()
  137. .route("/", get(index))
  138. .route("/live", get(live))
  139. .route("/metrics", get(metrics))
  140. .route("/ready", get(ready))
  141. .route("/v1/chains", get(chain_ids))
  142. .route(
  143. "/v1/chains/:chain_id/revelations/:sequence",
  144. get(revelation),
  145. )
  146. .with_state(state)
  147. }
  148. /// We are registering the provider on chain with the following url:
  149. /// `{base_uri}/v1/chains/{chain_id}`
  150. /// The path and API are highly coupled. Please be sure to keep them consistent.
  151. pub fn get_register_uri(base_uri: &str, chain_id: &str) -> Result<String> {
  152. let base_uri = Url::parse(base_uri)?;
  153. let path = format!("/v1/chains/{}", chain_id);
  154. let uri = base_uri.join(&path)?;
  155. Ok(uri.to_string())
  156. }
  157. #[cfg(test)]
  158. mod test {
  159. use {
  160. crate::{
  161. api::{self, ApiState, BinaryEncoding, Blob, BlockchainState, GetRandomValueResponse},
  162. chain::reader::{mock::MockEntropyReader, BlockStatus},
  163. state::{HashChainState, PebbleHashChain},
  164. },
  165. axum::http::StatusCode,
  166. axum_test::{TestResponse, TestServer},
  167. ethers::prelude::Address,
  168. lazy_static::lazy_static,
  169. prometheus_client::registry::Registry,
  170. std::{collections::HashMap, sync::Arc},
  171. tokio::sync::RwLock,
  172. };
  173. const PROVIDER: Address = Address::zero();
  174. lazy_static! {
  175. static ref OTHER_PROVIDER: Address = Address::from_low_u64_be(1);
  176. // Note: these chains are immutable. They are wrapped in Arc because we need Arcs to
  177. // initialize the BlockchainStates below, but they aren't cloneable (nor do they need to be cloned).
  178. static ref ETH_CHAIN: Arc<HashChainState> = Arc::new(HashChainState::from_chain_at_offset(
  179. 0,
  180. PebbleHashChain::new([0u8; 32], 1000, 1),
  181. ));
  182. static ref AVAX_CHAIN: Arc<HashChainState> = Arc::new(HashChainState::from_chain_at_offset(
  183. 100,
  184. PebbleHashChain::new([1u8; 32], 1000, 1),
  185. ));
  186. }
  187. async fn test_server() -> (TestServer, Arc<MockEntropyReader>, Arc<MockEntropyReader>) {
  188. let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
  189. let eth_state = BlockchainState {
  190. id: "ethereum".into(),
  191. state: ETH_CHAIN.clone(),
  192. contract: eth_read.clone(),
  193. provider_address: PROVIDER,
  194. reveal_delay_blocks: 1,
  195. confirmed_block_status: BlockStatus::Latest,
  196. };
  197. let metrics_registry = Arc::new(RwLock::new(Registry::default()));
  198. let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
  199. let avax_state = BlockchainState {
  200. id: "avalanche".into(),
  201. state: AVAX_CHAIN.clone(),
  202. contract: avax_read.clone(),
  203. provider_address: PROVIDER,
  204. reveal_delay_blocks: 2,
  205. confirmed_block_status: BlockStatus::Latest,
  206. };
  207. let mut chains = HashMap::new();
  208. chains.insert("ethereum".into(), eth_state);
  209. chains.insert("avalanche".into(), avax_state);
  210. let api_state = ApiState::new(chains, metrics_registry).await;
  211. let app = api::routes(api_state);
  212. (TestServer::new(app).unwrap(), eth_read, avax_read)
  213. }
  214. async fn get_and_assert_status(
  215. server: &TestServer,
  216. path: &str,
  217. status: StatusCode,
  218. ) -> TestResponse {
  219. let response = server.get(path).await;
  220. response.assert_status(status);
  221. response
  222. }
  223. #[tokio::test]
  224. async fn test_revelation() {
  225. let (server, eth_contract, avax_contract) = test_server().await;
  226. // Can't access a revelation if it hasn't been requested
  227. get_and_assert_status(
  228. &server,
  229. "/v1/chains/ethereum/revelations/0",
  230. StatusCode::FORBIDDEN,
  231. )
  232. .await;
  233. // Once someone requests the number, then it is accessible
  234. eth_contract.insert(PROVIDER, 0, 1, false);
  235. let response =
  236. get_and_assert_status(&server, "/v1/chains/ethereum/revelations/0", StatusCode::OK)
  237. .await;
  238. response.assert_json(&GetRandomValueResponse {
  239. value: Blob::new(BinaryEncoding::Hex, ETH_CHAIN.reveal(0).unwrap()),
  240. });
  241. // Each chain and provider has its own set of requests
  242. eth_contract.insert(PROVIDER, 100, 1, false);
  243. eth_contract.insert(*OTHER_PROVIDER, 101, 1, false);
  244. eth_contract.insert(PROVIDER, 102, 1, false);
  245. avax_contract.insert(PROVIDER, 102, 1, false);
  246. avax_contract.insert(PROVIDER, 103, 1, false);
  247. avax_contract.insert(*OTHER_PROVIDER, 104, 1, false);
  248. let response = get_and_assert_status(
  249. &server,
  250. "/v1/chains/ethereum/revelations/100",
  251. StatusCode::OK,
  252. )
  253. .await;
  254. response.assert_json(&GetRandomValueResponse {
  255. value: Blob::new(BinaryEncoding::Hex, ETH_CHAIN.reveal(100).unwrap()),
  256. });
  257. get_and_assert_status(
  258. &server,
  259. "/v1/chains/ethereum/revelations/101",
  260. StatusCode::FORBIDDEN,
  261. )
  262. .await;
  263. let response = get_and_assert_status(
  264. &server,
  265. "/v1/chains/ethereum/revelations/102",
  266. StatusCode::OK,
  267. )
  268. .await;
  269. response.assert_json(&GetRandomValueResponse {
  270. value: Blob::new(BinaryEncoding::Hex, ETH_CHAIN.reveal(102).unwrap()),
  271. });
  272. get_and_assert_status(
  273. &server,
  274. "/v1/chains/ethereum/revelations/103",
  275. StatusCode::FORBIDDEN,
  276. )
  277. .await;
  278. get_and_assert_status(
  279. &server,
  280. "/v1/chains/ethereum/revelations/104",
  281. StatusCode::FORBIDDEN,
  282. )
  283. .await;
  284. get_and_assert_status(
  285. &server,
  286. "/v1/chains/avalanche/revelations/100",
  287. StatusCode::FORBIDDEN,
  288. )
  289. .await;
  290. get_and_assert_status(
  291. &server,
  292. "/v1/chains/avalanche/revelations/101",
  293. StatusCode::FORBIDDEN,
  294. )
  295. .await;
  296. let response = get_and_assert_status(
  297. &server,
  298. "/v1/chains/avalanche/revelations/102",
  299. StatusCode::OK,
  300. )
  301. .await;
  302. response.assert_json(&GetRandomValueResponse {
  303. value: Blob::new(BinaryEncoding::Hex, AVAX_CHAIN.reveal(102).unwrap()),
  304. });
  305. let response = get_and_assert_status(
  306. &server,
  307. "/v1/chains/avalanche/revelations/103",
  308. StatusCode::OK,
  309. )
  310. .await;
  311. response.assert_json(&GetRandomValueResponse {
  312. value: Blob::new(BinaryEncoding::Hex, AVAX_CHAIN.reveal(103).unwrap()),
  313. });
  314. get_and_assert_status(
  315. &server,
  316. "/v1/chains/avalanche/revelations/104",
  317. StatusCode::FORBIDDEN,
  318. )
  319. .await;
  320. // Bad chain ids fail
  321. get_and_assert_status(
  322. &server,
  323. "/v1/chains/not_a_chain/revelations/0",
  324. StatusCode::BAD_REQUEST,
  325. )
  326. .await;
  327. // Requesting a number that has a request, but isn't in the HashChainState also fails.
  328. // (Note that this shouldn't happen in normal operation)
  329. get_and_assert_status(
  330. &server,
  331. "/v1/chains/avalanche/revelations/99",
  332. StatusCode::FORBIDDEN,
  333. )
  334. .await;
  335. avax_contract.insert(PROVIDER, 99, 1, false);
  336. get_and_assert_status(
  337. &server,
  338. "/v1/chains/avalanche/revelations/99",
  339. StatusCode::INTERNAL_SERVER_ERROR,
  340. )
  341. .await;
  342. }
  343. #[tokio::test]
  344. async fn test_revelation_confirmation_delay() {
  345. let (server, eth_contract, avax_contract) = test_server().await;
  346. eth_contract.insert(PROVIDER, 0, 10, false);
  347. eth_contract.insert(PROVIDER, 1, 11, false);
  348. eth_contract.insert(PROVIDER, 2, 12, false);
  349. avax_contract.insert(PROVIDER, 100, 10, false);
  350. avax_contract.insert(PROVIDER, 101, 11, false);
  351. eth_contract.set_block_number(10);
  352. avax_contract.set_block_number(10);
  353. get_and_assert_status(
  354. &server,
  355. "/v1/chains/ethereum/revelations/0",
  356. StatusCode::FORBIDDEN,
  357. )
  358. .await;
  359. get_and_assert_status(
  360. &server,
  361. "/v1/chains/avalanche/revelations/100",
  362. StatusCode::FORBIDDEN,
  363. )
  364. .await;
  365. eth_contract.set_block_number(11);
  366. avax_contract.set_block_number(11);
  367. get_and_assert_status(&server, "/v1/chains/ethereum/revelations/0", StatusCode::OK).await;
  368. get_and_assert_status(
  369. &server,
  370. "/v1/chains/ethereum/revelations/1",
  371. StatusCode::FORBIDDEN,
  372. )
  373. .await;
  374. get_and_assert_status(
  375. &server,
  376. "/v1/chains/avalanche/revelations/100",
  377. StatusCode::FORBIDDEN,
  378. )
  379. .await;
  380. eth_contract.set_block_number(12);
  381. avax_contract.set_block_number(12);
  382. get_and_assert_status(&server, "/v1/chains/ethereum/revelations/1", StatusCode::OK).await;
  383. get_and_assert_status(
  384. &server,
  385. "/v1/chains/ethereum/revelations/2",
  386. StatusCode::FORBIDDEN,
  387. )
  388. .await;
  389. get_and_assert_status(
  390. &server,
  391. "/v1/chains/avalanche/revelations/100",
  392. StatusCode::OK,
  393. )
  394. .await;
  395. get_and_assert_status(
  396. &server,
  397. "/v1/chains/avalanche/revelations/101",
  398. StatusCode::FORBIDDEN,
  399. )
  400. .await;
  401. }
  402. }