diff --git a/Cargo.lock b/Cargo.lock index c64fb8cdf7..1a09bf9494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,6 +242,7 @@ dependencies = [ "async-trait", "bb8-postgres", "chainstate-test-framework", + "clap", "common", "crypto", "futures", diff --git a/api-server/api-server-common/Cargo.toml b/api-server/api-server-common/Cargo.toml index 7bcd0708fb..c116b57beb 100644 --- a/api-server/api-server-common/Cargo.toml +++ b/api-server/api-server-common/Cargo.toml @@ -14,12 +14,14 @@ logging = {path = '../../logging'} serialization = { path = "../../serialization" } async-trait.workspace = true +bb8-postgres = "0.8" +clap = { workspace = true, features = ["derive"] } futures = { workspace = true, default-features = false } parity-scale-codec.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["full"] } tokio-postgres = "0.7" -bb8-postgres = "0.8" + [dev-dependencies] chainstate-test-framework = { path = '../../chainstate/test-framework' } diff --git a/api-server/api-server-common/src/lib.rs b/api-server/api-server-common/src/lib.rs index 29b4f8fac7..dce8861cdf 100644 --- a/api-server/api-server-common/src/lib.rs +++ b/api-server/api-server-common/src/lib.rs @@ -14,3 +14,38 @@ // limitations under the License. pub mod storage; + +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct PostgresConfig { + /// Postgres host + #[clap(long, default_value = "localhost")] + pub postgres_host: String, + + /// Postgres port + #[clap(long, default_value = "5432")] + pub postgres_port: u16, + + /// Postgres user + #[clap(long, default_value = "postgres")] + pub postgres_user: String, + + /// Postgres password + #[clap(long)] + pub postgres_password: Option, + + /// Postgres database + #[clap(long)] + pub postgres_database: Option, + + /// Postgres max connections + #[clap(long, default_value = "10")] + pub postgres_max_connections: u32, +} + +impl Default for PostgresConfig { + fn default() -> Self { + Self::parse() + } +} diff --git a/api-server/scanner-daemon/src/config.rs b/api-server/scanner-daemon/src/config.rs index 123c58f27e..2aa2f9d3e2 100644 --- a/api-server/scanner-daemon/src/config.rs +++ b/api-server/scanner-daemon/src/config.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use api_server_common::PostgresConfig; use clap::Parser; use common::chain::config::ChainType; use std::net::SocketAddr; @@ -46,6 +47,10 @@ pub struct ApiServerScannerArgs { /// RPC password (either provide a username and password, or use a cookie file. You cannot use both) #[clap(long)] pub rpc_password: Option, + + /// Postgres config values + #[clap(flatten)] + pub postgres_config: PostgresConfig, } impl From for ChainType { diff --git a/api-server/scanner-daemon/src/main.rs b/api-server/scanner-daemon/src/main.rs index 27ef292060..7433ce99fc 100644 --- a/api-server/scanner-daemon/src/main.rs +++ b/api-server/scanner-daemon/src/main.rs @@ -16,9 +16,10 @@ use std::sync::Arc; use api_server_common::storage::{ - impls::in_memory::transactional::TransactionalApiServerInMemoryStorage, + impls::postgres::TransactionalApiServerPostgresStorage, storage_api::{ - ApiServerStorage, ApiServerStorageRead, ApiServerStorageWrite, ApiServerTransactionRw, + ApiServerStorage, ApiServerStorageError, ApiServerStorageRead, ApiServerStorageWrite, + ApiServerTransactionRw, }, }; use blockchain_scanner_lib::blockchain_state::BlockchainState; @@ -31,9 +32,26 @@ use rpc::RpcAuthData; use utils::{cookie::COOKIE_FILENAME, default_data_dir::default_data_dir_for_chain}; mod config; -#[must_use] -pub fn make_in_memory_storage(chain_config: &ChainConfig) -> TransactionalApiServerInMemoryStorage { - TransactionalApiServerInMemoryStorage::new(chain_config) +pub async fn make_postgres_storage( + postgres_host: String, + postgres_port: u16, + postgres_user: String, + postgres_password: Option, + postgres_database: Option, + postgres_max_connections: u32, + chain_config: &ChainConfig, +) -> Result { + TransactionalApiServerPostgresStorage::new( + &postgres_host, + postgres_port, + &postgres_user, + postgres_password.as_deref(), + postgres_database.as_deref(), + postgres_max_connections, + chain_config, + ) + .await + .map_err(ApiServerScannerError::PostgresConnectionError) } pub async fn run( @@ -83,6 +101,8 @@ pub enum ApiServerScannerError { RpcError(node_comm::rpc_client::NodeRpcError), #[error("Invalid config: {0}")] InvalidConfig(String), + #[error("Postgres connection error: {0}")] + PostgresConnectionError(ApiServerStorageError), } #[tokio::main] @@ -104,6 +124,7 @@ async fn main() -> Result<(), ApiServerScannerError> { rpc_cookie_file, rpc_username, rpc_password, + postgres_config, } = args; let chain_type: ChainType = network.into(); @@ -134,7 +155,16 @@ async fn main() -> Result<(), ApiServerScannerError> { .await .map_err(ApiServerScannerError::RpcError)?; - let storage = make_in_memory_storage(&chain_config); + let storage = make_postgres_storage( + postgres_config.postgres_host, + postgres_config.postgres_port, + postgres_config.postgres_user, + postgres_config.postgres_password, + postgres_config.postgres_database, + postgres_config.postgres_max_connections, + &chain_config, + ) + .await?; run(&chain_config, &rpc_client, storage).await?; diff --git a/api-server/web-server/src/config.rs b/api-server/web-server/src/config.rs index 23cfc5eef8..97bbb88ab4 100644 --- a/api-server/web-server/src/config.rs +++ b/api-server/web-server/src/config.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use api_server_common::PostgresConfig; use clap::Parser; use std::{net::SocketAddr, ops::Deref}; @@ -27,6 +28,10 @@ pub struct ApiServerWebServerConfig { /// Default: `127.0.0.1:3000` #[clap(long)] pub address: Option, + + /// Postgres config values + #[clap(flatten)] + pub postgres_config: PostgresConfig, } #[derive(Clone, Debug, Parser)] diff --git a/api-server/web-server/src/main.rs b/api-server/web-server/src/main.rs index 7f3194471c..ce9095572b 100644 --- a/api-server/web-server/src/main.rs +++ b/api-server/web-server/src/main.rs @@ -17,7 +17,7 @@ mod api; mod config; mod error; -use api_server_common::storage::impls::in_memory::transactional::TransactionalApiServerInMemoryStorage; +use api_server_common::storage::impls::postgres::TransactionalApiServerPostgresStorage; use axum::{response::IntoResponse, routing::get, Json, Router}; use clap::Parser; use common::chain::config::create_unit_test_config; @@ -43,9 +43,23 @@ async fn main() { // TODO: generalize network configuration let chain_config = Arc::new(create_unit_test_config()); - // TODO: point database to PostgreSQL from command line arguments + let storage = TransactionalApiServerPostgresStorage::new( + &args.postgres_config.postgres_host, + args.postgres_config.postgres_port, + &args.postgres_config.postgres_user, + args.postgres_config.postgres_password.as_deref(), + args.postgres_config.postgres_database.as_deref(), + args.postgres_config.postgres_max_connections, + &chain_config, + ) + .await + .unwrap_or_else(|e| { + log::error!("Error creating Postgres storage: {}", e); + std::process::exit(1); + }); + let state = APIServerWebServerState { - db: Arc::new(TransactionalApiServerInMemoryStorage::new(&chain_config)), + db: Arc::new(storage), chain_config, };