api.rs 16 KB

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