api.rs 17 KB

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