From 915d5a9d202c29cc531315f5a1669bc77f70778c Mon Sep 17 00:00:00 2001 From: Alfie John Date: Fri, 15 Sep 2023 21:12:56 +1000 Subject: [PATCH 1/4] Add Genesis accessor to be used in API Server --- common/src/chain/genesis.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/chain/genesis.rs b/common/src/chain/genesis.rs index cc08e36680..818b4e1e95 100644 --- a/common/src/chain/genesis.rs +++ b/common/src/chain/genesis.rs @@ -40,6 +40,10 @@ impl Genesis { } } + pub fn fun_message(&self) -> &str { + &self.fun_message + } + pub fn utxos(&self) -> &[TxOutput] { &self.utxos } From 54449ab960242f02d6803b2e49cb76d7ae89c1cb Mon Sep 17 00:00:00 2001 From: Alfie John Date: Fri, 15 Sep 2023 21:14:12 +1000 Subject: [PATCH 2/4] Add database accessor to Axum state --- .../api-server-common/src/storage/impls/in_memory/mod.rs | 1 + api-server/web-server/src/lib.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/api-server/api-server-common/src/storage/impls/in_memory/mod.rs b/api-server/api-server-common/src/storage/impls/in_memory/mod.rs index e21d00ab21..0d0fcf4b95 100644 --- a/api-server/api-server-common/src/storage/impls/in_memory/mod.rs +++ b/api-server/api-server-common/src/storage/impls/in_memory/mod.rs @@ -26,6 +26,7 @@ use crate::storage::storage_api::{block_aux_data::BlockAuxData, ApiServerStorage use super::CURRENT_STORAGE_VERSION; +#[derive(Clone)] struct ApiServerInMemoryStorage { block_table: BTreeMap, Block>, block_aux_data_table: BTreeMap, BlockAuxData>, diff --git a/api-server/web-server/src/lib.rs b/api-server/web-server/src/lib.rs index 1820d92b97..a1b625e310 100644 --- a/api-server/web-server/src/lib.rs +++ b/api-server/web-server/src/lib.rs @@ -17,7 +17,11 @@ pub mod error; pub use error::APIServerWebServerError; +use common::chain::ChainConfig; +use std::sync::Arc; + #[derive(Debug, Clone)] -pub struct APIServerWebServerState { - pub example_shared_value: String, +pub struct APIServerWebServerState { + pub db: T, + pub chain_config: Arc, } From b34e38274578d60594398b405aa2bb9048168768 Mon Sep 17 00:00:00 2001 From: Alfie John Date: Fri, 15 Sep 2023 21:18:29 +1000 Subject: [PATCH 3/4] Replace mock response data with database acccessors --- api-server/web-server/src/api/v1.rs | 364 ++++++++++++++++++++-------- api-server/web-server/src/main.rs | 16 +- 2 files changed, 277 insertions(+), 103 deletions(-) diff --git a/api-server/web-server/src/api/v1.rs b/api-server/web-server/src/api/v1.rs index f504e75138..245cd5139b 100644 --- a/api-server/web-server/src/api/v1.rs +++ b/api-server/web-server/src/api/v1.rs @@ -13,18 +13,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -use axum::{extract::Path, response::IntoResponse, routing::get, Json, Router}; +use api_server_common::storage::storage_api::{ApiServerStorage, ApiServerStorageRead}; +use axum::{ + extract::{Path, State}, + response::IntoResponse, + routing::get, + Json, Router, +}; use common::{ - chain::{Block, Genesis, Transaction}, - primitives::{BlockHeight, Id, H256}, + chain::{Block, Transaction, TxOutput}, + primitives::{BlockHeight, Id, Idable, H256}, }; use crypto::random::{make_true_rng, Rng}; use serde_json::json; -use web_server::{error::APIServerWebServerError, APIServerWebServerState}; +use std::{str::FromStr, sync::Arc}; +use web_server::{ + error::{ + APIServerWebServerClientError, APIServerWebServerError, APIServerWebServerServerError, + }, + APIServerWebServerState, +}; pub const API_VERSION: &str = "1.0.0"; -pub fn routes() -> Router { +pub fn routes( +) -> Router>> { let router = Router::new(); let router = router @@ -68,74 +81,173 @@ pub fn routes() -> Router { // #[allow(clippy::unused_async)] -pub async fn block( - Path(_block_id): Path>, +pub async fn block( + Path(block_id): Path, + State(state): State>>, ) -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); - - Ok(Json(json!({ - "previous_block_id": Id::::new(H256::random_using(&mut rng)), - "height": BlockHeight::from(rng.gen_range(1..1_000_000)), - "timestamp": rng.gen_range(1..1_000_000_000), - "merkle_root": H256::random_using(&mut rng), - "reward": (0..rng.gen_range(1..5)).map(|_| { json!({ - "destination": H256::random_using(&mut rng), - "amount": rng.gen_range(1..100_000_000), - }) - }).collect::>(), - "transaction_ids": (0..rng.gen_range(1..100)).map(|_| { - Id::::new(H256::random_using(&mut rng) - )}).collect::>(), - }))) + let block = { + let block_id: Id = H256::from_str(&block_id) + .map_err(|_| { + APIServerWebServerError::ClientError(APIServerWebServerClientError::BadRequest) + })? + .into(); + + state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + .get_block(block_id) + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + }; + + match block { + Some(block) => { + //: TODO expand this with a usable JSON response + let transactions: Vec = vec![]; + + Ok(Json(json!({ + "previous_block_id": block.prev_block_id(), + "timestamp": block.timestamp(), + "merkle_root": block.merkle_root(), + "transactions": transactions, + }))) + } + None => Err(APIServerWebServerError::ClientError( + APIServerWebServerClientError::BadRequest, + )), + } } #[allow(clippy::unused_async)] -pub async fn block_header( - Path(_block_id): Path>, +pub async fn block_header( + Path(block_id): Path, + State(state): State>>, ) -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); - - Ok(Json(json!({ - "previous_block_id": Id::::new(H256::random_using(&mut rng)), - "height": BlockHeight::from(rng.gen_range(1..1_000_000)), - "timestamp": rng.gen_range(1..1_000_000_000), - "merkle_root": H256::random_using(&mut rng), - }))) + let block = { + let block_id: Id = H256::from_str(&block_id) + .map_err(|_| { + APIServerWebServerError::ClientError(APIServerWebServerClientError::BadRequest) + })? + .into(); + + state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + .get_block(block_id) + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + }; + + match block { + Some(block) => Ok(Json(json!({ + "previous_block_id": block.prev_block_id(), + "timestamp": block.timestamp(), + "merkle_root": block.merkle_root(), + }))), + None => Err(APIServerWebServerError::ClientError( + APIServerWebServerClientError::BadRequest, + )), + } } #[allow(clippy::unused_async)] -pub async fn block_reward( - Path(_block_id): Path>, +pub async fn block_reward( + Path(block_id): Path, + State(state): State>>, ) -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); - - Ok(Json(json!([(0..rng.gen_range(1..5)) - .map(|_| { - json!({ - "destination": H256::random_using(&mut rng), - "amount": rng.gen_range(1..100_000_000), - }) - }) - .collect::>()]))) + let block = { + let block_id: Id = H256::from_str(&block_id) + .map_err(|_| { + APIServerWebServerError::ClientError(APIServerWebServerClientError::BadRequest) + })? + .into(); + + state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + .get_block(block_id) + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + }; + + match block { + Some(_block) => Ok(Json(json!({ + // TODO: expand this with a usable JSON response + }))), + None => Err(APIServerWebServerError::ClientError( + APIServerWebServerClientError::BadRequest, + )), + } } #[allow(clippy::unused_async)] -pub async fn block_transaction_ids( - Path(_block_id): Path>, +pub async fn block_transaction_ids( + Path(block_id): Path, + State(state): State>>, ) -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); - - Ok(Json(json!([(0..rng.gen_range(1..100)) - .map(|_| Id::::new(H256::random_using(&mut rng))) - .collect::>()]))) + let block = { + let block_id: Id = H256::from_str(&block_id) + .map_err(|_| { + APIServerWebServerError::ClientError(APIServerWebServerClientError::BadRequest) + })? + .into(); + + state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + .get_block(block_id) + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + }; + + match block { + Some(block) => { + let transaction_ids = block + .transactions() + .iter() + .map(|tx| tx.transaction().get_id()) + .collect::>(); + + Ok(Json(json!({ + "transaction_ids": transaction_ids, + }))) + } + None => Err(APIServerWebServerError::ClientError( + APIServerWebServerClientError::BadRequest, + )), + } } // @@ -143,40 +255,68 @@ pub async fn block_transaction_ids( // #[allow(clippy::unused_async)] -pub async fn chain_genesis() -> Result { - // TODO replace mock with database calls +pub async fn chain_genesis( + State(state): State>>, +) -> Result { + let genesis = state.chain_config.genesis_block(); - let mut rng = make_true_rng(); + // TODO: expand this with a usable JSON response + let utxos: Vec = vec![]; Ok(Json(json!({ - "block_id": Id::::new(H256::random_using(&mut rng)), - "merkle_root": H256::random_using(&mut rng), - "transaction_ids": (0..rng.gen_range(1..100)).map(|_| { - Id::::new(H256::random_using(&mut rng) - )}).collect::>(), + "block_id": genesis.get_id(), + "fun_message": genesis.fun_message(), + "timestamp": genesis.timestamp(), + "utxos": utxos, }))) } #[allow(clippy::unused_async)] -pub async fn chain_at_height( - Path(_block_height): Path, +pub async fn chain_at_height( + Path(block_height): Path, + State(state): State>>, ) -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); - - Ok(Json(json!(Id::::new(H256::random_using(&mut rng))))) + let block_height = block_height.parse::().map_err(|_| { + APIServerWebServerError::ClientError(APIServerWebServerClientError::BadRequest) + })?; + + let block_id = state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError(APIServerWebServerServerError::InternalServerError) + })? + .get_main_chain_block_id(block_height) + .map_err(|_| { + APIServerWebServerError::ServerError(APIServerWebServerServerError::InternalServerError) + })?; + + match block_id { + Some(block_id) => Ok(Json(block_id)), + None => Err(APIServerWebServerError::ClientError( + APIServerWebServerClientError::BadRequest, + )), + } } #[allow(clippy::unused_async)] -pub async fn chain_tip() -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); +pub async fn chain_tip( + State(state): State>>, +) -> Result { + let best_block = state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError(APIServerWebServerServerError::InternalServerError) + })? + .get_best_block() + .map_err(|_| { + APIServerWebServerError::ServerError(APIServerWebServerServerError::InternalServerError) + })?; Ok(Json(json!({ - "block_id": Id::::new(H256::random_using(&mut rng)), - "block_height": BlockHeight::from(rng.gen_range(1..1_000_000)), + "block_height": best_block.0, + "block_id": best_block.1, }))) } @@ -185,24 +325,52 @@ pub async fn chain_tip() -> Result { // #[allow(clippy::unused_async)] -pub async fn transaction( - Path(_transaction_id): Path, +pub async fn transaction( + Path(transaction_id): Path, + State(state): State>>, ) -> Result { - // TODO replace mock with database calls - - let mut rng = make_true_rng(); - - Ok(Json(json!({ - "timestamp": rng.gen_range(1..1_000_000_000), - "inputs": (0..rng.gen_range(1..20)).map(|_| { json!({ - "transaction_id": Id::::new(H256::random_using(&mut rng)), - "index": rng.gen_range(1..100), - })}).collect::>(), - "outputs": (0..rng.gen_range(1..20)).map(|_| { json!({ - "destination": H256::random_using(&mut rng), - "amount": rng.gen_range(1..100_000_000), - })}).collect::>(), - }))) + let transaction = { + let transaction_id: Id = H256::from_str(&transaction_id) + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + .into(); + + state + .db + .transaction_ro() + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + .get_transaction(transaction_id) + .map_err(|_| { + APIServerWebServerError::ServerError( + APIServerWebServerServerError::InternalServerError, + ) + })? + }; + + match transaction { + Some((block_id, transaction)) => { + let transaction = transaction.transaction(); + + Ok(Json(json!({ + "block_id": if let Some(block_id) = block_id { block_id.to_string() } else { "".into() }, + "version_byte": transaction.version_byte(), + "is_replaceable": transaction.is_replaceable(), + "flags": transaction.flags(), + "inputs": transaction.inputs(), + "outputs": transaction.outputs(), + }))) + } + None => Err(APIServerWebServerError::ClientError( + APIServerWebServerClientError::BadRequest, + )), + } } #[allow(clippy::unused_async)] diff --git a/api-server/web-server/src/main.rs b/api-server/web-server/src/main.rs index e4fe80afeb..1cb66367bc 100644 --- a/api-server/web-server/src/main.rs +++ b/api-server/web-server/src/main.rs @@ -17,11 +17,14 @@ mod api; mod config; mod error; -use axum::{extract::State, response::IntoResponse, routing::get, Json, Router}; +use api_server_common::storage::impls::in_memory::transactional::TransactionalApiServerInMemoryStorage; +use axum::{response::IntoResponse, routing::get, Json, Router}; use clap::Parser; +use common::chain::config::create_unit_test_config; use config::ApiServerWebServerConfig; use logging::log; use serde_json::json; +use std::sync::Arc; use web_server::{ error::APIServerWebServerClientError, APIServerWebServerError, APIServerWebServerState, }; @@ -37,8 +40,13 @@ async fn main() { let args = ApiServerWebServerConfig::parse(); log::info!("Command line options: {args:?}"); + // TODO: generalise network configuration + let chain_config = Arc::new(create_unit_test_config()); + + // TODO: point database to PostgreSQL from command line arguments let state = APIServerWebServerState { - example_shared_value: "test value".to_string(), + db: Arc::new(TransactionalApiServerInMemoryStorage::new(&chain_config)), + chain_config, }; let routes = Router::new() @@ -54,9 +62,7 @@ async fn main() { } #[allow(clippy::unused_async)] -async fn server_status( - State(_state): State, -) -> Result { +async fn server_status() -> Result { Ok(Json(json!({ "versions": [api::v1::API_VERSION] //"network": "testnet", From 9aad375a681ce7b2f7151120da9c9ffb5ec99f30 Mon Sep 17 00:00:00 2001 From: Alfie John Date: Fri, 15 Sep 2023 21:50:28 +1000 Subject: [PATCH 4/4] Fix typo --- api-server/web-server/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-server/web-server/src/main.rs b/api-server/web-server/src/main.rs index 1cb66367bc..b6a8a6afe7 100644 --- a/api-server/web-server/src/main.rs +++ b/api-server/web-server/src/main.rs @@ -40,7 +40,7 @@ async fn main() { let args = ApiServerWebServerConfig::parse(); log::info!("Command line options: {args:?}"); - // TODO: generalise network configuration + // TODO: generalize network configuration let chain_config = Arc::new(create_unit_test_config()); // TODO: point database to PostgreSQL from command line arguments