diff --git a/apps/fortuna/src/api.rs b/apps/fortuna/src/api.rs index 9c138cba00..1cdb2a0f9c 100644 --- a/apps/fortuna/src/api.rs +++ b/apps/fortuna/src/api.rs @@ -29,6 +29,7 @@ pub use { mod chain_ids; mod config; +pub mod examples; mod explorer; mod index; mod live; @@ -40,10 +41,15 @@ pub type ChainId = String; pub type NetworkId = u64; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, utoipa::ToSchema)] +#[schema(example = "Completed")] pub enum StateTag { + /// Request is pending and waiting to be fulfilled Pending, + /// Request failed to be fulfilled Failed, + /// Request was successfully completed Completed, + /// Request was completed but the callback to the caller failed CallbackErrored, } diff --git a/apps/fortuna/src/api/chain_ids.rs b/apps/fortuna/src/api/chain_ids.rs index 8e644684ca..8df971e87d 100644 --- a/apps/fortuna/src/api/chain_ids.rs +++ b/apps/fortuna/src/api/chain_ids.rs @@ -2,14 +2,28 @@ use { crate::api::{ChainId, RestError}, anyhow::Result, axum::{extract::State, Json}, + utoipa::ToSchema, }; +/// Response containing the list of supported chain IDs +#[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct GetChainIdsResponse( + /// List of supported blockchain identifiers + #[schema(example = json!(["ethereum", "avalanche", "arbitrum", "optimism"]))] + Vec, +); + /// Get the list of supported chain ids +/// +/// Returns all blockchain identifiers that this Fortuna instance supports. +/// Each chain_id can be used in other API endpoints to specify which blockchain to interact with. #[utoipa::path( get, path = "/v1/chains", responses( -(status = 200, description = "Successfully retrieved the list of chain ids", body = GetRandomValueResponse), +(status = 200, description = "Successfully retrieved the list of chain ids", body = [String], + example = json!(["ethereum", "avalanche", "arbitrum", "optimism"]) +), ) )] pub async fn chain_ids( diff --git a/apps/fortuna/src/api/examples.rs b/apps/fortuna/src/api/examples.rs new file mode 100644 index 0000000000..1ed13fcb5f --- /dev/null +++ b/apps/fortuna/src/api/examples.rs @@ -0,0 +1,89 @@ +// Example values for the utoipa API docs. +// Note that each of these expressions is only evaluated once when the documentation is created, +// so the examples don't auto-update over time. + +/// Example value for a chain ID +pub fn chain_id_example() -> &'static str { + "abstract" +} + +/// Example value for a sequence number +pub fn sequence_example() -> u64 { + 42381 +} + +/// Example value for a block number +pub fn block_number_example() -> u64 { + 19000000 +} + +/// Example value for a random value in hex format (32 bytes) +pub fn random_value_hex_example() -> &'static str { + "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe" +} + +/// Example value for a transaction hash +pub fn tx_hash_example() -> &'static str { + "0xfe5f880ac10c0aae43f910b5a17f98a93cdd2eb2dce3a5ae34e5827a3a071a32" +} + +/// Example value for an Ethereum address +pub fn address_example() -> &'static str { + "0x6cc14824ea2918f5de5c2f75a9da968ad4bd6344" +} + +/// Example value for a timestamp in ISO 8601 format +pub fn timestamp_example() -> &'static str { + "2023-10-01T00:00:00Z" +} + +/// Example value for a network ID (Ethereum mainnet) +pub fn network_id_example() -> u64 { + 1 +} + +/// Example value for gas limit +pub fn gas_limit_example() -> u32 { + 500000 +} + +/// Example value for gas used +pub fn gas_used_example() -> &'static str { + "567890" +} + +/// Example value for callback gas used +pub fn callback_gas_used_example() -> u32 { + 100000 +} + +/// Example value for callback return value (error code example) +pub fn callback_return_value_example() -> &'static str { + "0x4e487b710000000000000000000000000000000000000000000000000000000000000011" +} + +/// Example list of chain IDs +pub fn chain_ids_example() -> Vec<&'static str> { + vec!["monad", "avalanche", "arbitrum", "optimism"] +} + +/// Example value for binary encoding type +pub fn encoding_example() -> &'static str { + "hex" +} + +/// Example value for limit parameter +pub fn limit_example() -> u64 { + 100 +} + +/// Example value for offset parameter +pub fn offset_example() -> u64 { + 0 +} + +/// Example value for total results count +pub fn total_results_example() -> i64 { + 42 +} + diff --git a/apps/fortuna/src/api/explorer.rs b/apps/fortuna/src/api/explorer.rs index 09593cfeb3..522a82aa5f 100644 --- a/apps/fortuna/src/api/explorer.rs +++ b/apps/fortuna/src/api/explorer.rs @@ -1,6 +1,6 @@ use { crate::{ - api::{ApiBlockChainState, NetworkId, RestError, StateTag}, + api::{examples, ApiBlockChainState, NetworkId, RestError, StateTag}, config::LATENCY_BUCKETS, history::{RequestQueryBuilder, RequestStatus, SearchField}, }, @@ -93,23 +93,28 @@ pub struct ExplorerQueryParams { #[param(value_type = Option, example = "2033-10-01T00:00:00Z")] pub max_timestamp: Option>, /// The query string to search for. This can be a transaction hash, sender address, or sequence number. + #[param(example = "0xfe5f880ac10c0aae43f910b5a17f98a93cdd2eb2dce3a5ae34e5827a3a071a32")] pub query: Option, - /// The network ID to filter the results by. - #[param(value_type = Option)] + /// The network ID to filter the results by (e.g., 1 for Ethereum mainnet, 43114 for Avalanche). + #[param(value_type = Option, example = examples::network_id_example)] pub network_id: Option, - /// The state to filter the results by. + /// The state to filter the results by (Pending, Completed, Failed, or CallbackErrored). + #[param(example = "Completed")] pub state: Option, /// The maximum number of logs to return. Max value is 1000. - #[param(default = 1000)] + #[param(default = 1000, example = examples::limit_example)] pub limit: Option, /// The offset to start returning logs from. - #[param(default = 0)] + #[param(default = 0, example = examples::offset_example)] pub offset: Option, } #[derive(Debug, serde::Serialize, utoipa::ToSchema)] pub struct ExplorerResponse { + /// List of entropy request logs matching the query pub requests: Vec, + /// Total number of results matching the query (may be more than returned due to limit) + #[schema(example = 42)] pub total_results: i64, } @@ -120,7 +125,33 @@ pub struct ExplorerResponse { #[utoipa::path( get, path = "/v1/logs", - responses((status = 200, description = "A list of Entropy request logs", body = ExplorerResponse)), + responses((status = 200, description = "A list of Entropy request logs", body = ExplorerResponse, + example = json!({ + "requests": [{ + "chain_id": "ethereum", + "network_id": 1, + "provider": "0x6cc14824ea2918f5de5c2f75a9da968ad4bd6344", + "sequence": 12345, + "created_at": "2023-10-01T00:00:00Z", + "last_updated_at": "2023-10-01T00:00:05Z", + "request_block_number": 19000000, + "request_tx_hash": "0x5a3a984f41bb5443f5efa6070ed59ccb25edd8dbe6ce7f9294cf5caa64ed00ae", + "gas_limit": 500000, + "user_random_number": "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe", + "sender": "0x78357316239040e19fc823372cc179ca75e64b81", + "state": "completed", + "reveal_block_number": 19000005, + "reveal_tx_hash": "0xfe5f880ac10c0aae43f910b5a17f98a93cdd2eb2dce3a5ae34e5827a3a071a32", + "provider_random_number": "deeb67cb894c33f7b20ae484228a9096b51e8db11461fcb0975c681cf0875d37", + "gas_used": "567890", + "combined_random_number": "1c26ffa1f8430dc91cb755a98bf37ce82ac0e2cfd961e10111935917694609d5", + "callback_failed": false, + "callback_return_value": "0x", + "callback_gas_used": 100000 + }], + "total_results": 42 + }) + )), params(ExplorerQueryParams) )] pub async fn explorer( diff --git a/apps/fortuna/src/api/revelation.rs b/apps/fortuna/src/api/revelation.rs index 26628cb74f..01a056ef0c 100644 --- a/apps/fortuna/src/api/revelation.rs +++ b/apps/fortuna/src/api/revelation.rs @@ -1,6 +1,6 @@ use { crate::{ - api::{ApiBlockChainState, ChainId, RequestLabel, RestError}, + api::{examples, ApiBlockChainState, ChainId, RequestLabel, RestError}, chain::reader::BlockNumber, }, anyhow::Result, @@ -25,8 +25,15 @@ use { get, path = "/v1/chains/{chain_id}/revelations/{sequence}", responses( -(status = 200, description = "Random value successfully retrieved", body = GetRandomValueResponse), -(status = 403, description = "Random value cannot currently be retrieved", body = String) +(status = 200, description = "Random value successfully retrieved", body = GetRandomValueResponse, + example = json!({ + "encoding": "hex", + "data": "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe" + }) +), +(status = 403, description = "Random value cannot currently be retrieved", body = String, + example = json!("The request with the given sequence number has not been made yet, or the random value has already been revealed on chain.") +) ), params(RevelationPathParams, RevelationQueryParams) )] @@ -130,32 +137,43 @@ pub async fn revelation( #[derive(Debug, serde::Serialize, serde::Deserialize, IntoParams)] #[into_params(parameter_in=Path)] pub struct RevelationPathParams { - #[param(value_type = String)] + /// The unique identifier for the blockchain (e.g., "ethereum", "avalanche") + #[param(value_type = String, example = examples::chain_id_example)] pub chain_id: ChainId, + /// The sequence number of the random value request + #[param(example = examples::sequence_example)] pub sequence: u64, } #[derive(Debug, serde::Serialize, serde::Deserialize, IntoParams)] #[into_params(parameter_in=Query)] pub struct RevelationQueryParams { + /// The encoding format for the random value (hex, base64, or array) + #[param(example = examples::encoding_example)] pub encoding: Option, - #[param(value_type = Option)] + /// Optional block number to verify the request was made at a specific block + #[param(value_type = Option, example = examples::block_number_example)] pub block_number: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(rename_all = "kebab-case")] +#[schema(example = "hex")] pub enum BinaryEncoding { + /// Hexadecimal encoding (default) #[serde(rename = "hex")] Hex, + /// Base64 encoding #[serde(rename = "base64")] Base64, + /// Raw byte array encoding #[serde(rename = "array")] Array, } #[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema, PartialEq)] pub struct GetRandomValueResponse { + /// The random value in the requested encoding format pub value: Blob, } @@ -163,15 +181,23 @@ pub struct GetRandomValueResponse { #[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema, PartialEq)] #[serde(tag = "encoding", rename_all = "kebab-case")] pub enum Blob { + /// Random value encoded as hexadecimal string Hex { + /// The 32-byte random value as a hex string #[serde_as(as = "serde_with::hex::Hex")] + #[schema(example = "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe")] data: [u8; 32], }, + /// Random value encoded as base64 string Base64 { + /// The 32-byte random value as a base64 string #[serde_as(as = "serde_with::base64::Base64")] + #[schema(example = "qQWrVlZ9MafaOO2BnZe8JX8z6+OFxccszibju4VfD+4=")] data: [u8; 32], }, + /// Random value as a raw byte array Array { + /// The 32-byte random value as an array of integers #[serde(with = "array")] data: [u8; 32], }, diff --git a/apps/fortuna/src/history.rs b/apps/fortuna/src/history.rs index af00b58a70..c161e03964 100644 --- a/apps/fortuna/src/history.rs +++ b/apps/fortuna/src/history.rs @@ -25,9 +25,12 @@ const DEFAULT_DATABASE_URL: &str = "sqlite:fortuna.db?mode=rwc"; #[derive(Clone, Debug, Serialize, ToSchema, PartialEq)] #[serde(tag = "state", rename_all = "kebab-case")] pub enum RequestEntryState { + /// Request is pending and waiting to be fulfilled Pending, + /// Request was successfully completed Completed { /// The block number of the reveal transaction. + #[schema(example = 19000005)] reveal_block_number: u64, /// The transaction hash of the reveal transaction. #[schema(example = "0xfe5f880ac10c0aae43f910b5a17f98a93cdd2eb2dce3a5ae34e5827a3a071a32", value_type = String)] @@ -42,25 +45,29 @@ pub enum RequestEntryState { #[serde(with = "crate::serde::u256")] gas_used: U256, /// The combined random number generated from the user and provider contributions. - #[schema(example = "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe")] + #[schema(example = "1c26ffa1f8430dc91cb755a98bf37ce82ac0e2cfd961e10111935917694609d5")] #[serde_as(as = "serde_with::hex::Hex")] combined_random_number: [u8; 32], /// Whether the callback to the caller failed. + #[schema(example = false)] callback_failed: bool, /// Return value from the callback. If the callback failed, this field contains /// the error code and any additional returned data. Note that "" often indicates an out-of-gas error. /// If the callback returns more than 256 bytes, only the first 256 bytes of the callback return value are included. /// NOTE: This field is the raw bytes returned from the callback, not hex-decoded. The client should decode it as needed. - #[schema(example = "0x4e487b710000000000000000000000000000000000000000000000000000000000000011", value_type = String)] + #[schema(example = "0x", value_type = String)] callback_return_value: Bytes, /// How much gas the callback used. - #[schema(example = "567890", value_type = String)] + #[schema(example = 100000, value_type = u32)] #[serde(with = "crate::serde::u32")] callback_gas_used: u32, }, + /// Request failed to be fulfilled Failed { + /// The reason for the failure. + #[schema(example = "Transaction reverted")] reason: String, - /// The provider contribution to the random number. + /// The provider contribution to the random number (if available). #[schema(example = "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe")] #[serde_as(as = "Option")] provider_random_number: Option<[u8; 32]>, @@ -74,22 +81,29 @@ pub struct RequestStatus { #[schema(example = "ethereum", value_type = String)] pub chain_id: ChainId, /// The network ID of the request. This is the response of eth_chainId rpc call. - #[schema(example = "1", value_type = u64)] + #[schema(example = 1, value_type = u64)] pub network_id: NetworkId, + /// The address of the entropy provider. #[schema(example = "0x6cc14824ea2918f5de5c2f75a9da968ad4bd6344", value_type = String)] pub provider: Address, + /// The sequence number of the request. + #[schema(example = 12345)] pub sequence: u64, + /// The timestamp when the request was created. #[schema(example = "2023-10-01T00:00:00Z", value_type = String)] pub created_at: DateTime, + /// The timestamp when the request was last updated. #[schema(example = "2023-10-01T00:00:05Z", value_type = String)] pub last_updated_at: DateTime, + /// The block number when the request was made. + #[schema(example = 19000000)] pub request_block_number: u64, /// The transaction hash of the request transaction. #[schema(example = "0x5a3a984f41bb5443f5efa6070ed59ccb25edd8dbe6ce7f9294cf5caa64ed00ae", value_type = String)] pub request_tx_hash: TxHash, /// Gas limit for the callback in the smallest unit of the chain. /// For example, if the native currency is ETH, this will be in wei. - #[schema(example = "500000", value_type = String)] + #[schema(example = 500000, value_type = u32)] pub gas_limit: u32, /// The user contribution to the random number. #[schema(example = "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe")] @@ -98,6 +112,7 @@ pub struct RequestStatus { /// This is the address that initiated the request. #[schema(example = "0x78357316239040e19fc823372cc179ca75e64b81", value_type = String)] pub sender: Address, + /// The current state of the request. pub state: RequestEntryState, }