diff --git a/database/src/service/read.rs b/database/src/service/read.rs index 7de40b44..43e5e77c 100644 --- a/database/src/service/read.rs +++ b/database/src/service/read.rs @@ -121,23 +121,15 @@ impl DatabaseReadHandle { } } - /// TODO + /// Access to the actual database environment. + /// + /// TODO: we need this for testing but should we allow it publicly? + /// Or within `crate`? It allows anyone to start tampering with the + /// database directly instead of going through `service`. #[inline] pub const fn env(&self) -> &Arc { &self.env } - - /// TODO - #[inline] - pub const fn semaphore(&self) -> &PollSemaphore { - &self.semaphore - } - - /// TODO - #[inline] - pub const fn permit(&self) -> &Option { - &self.permit - } } impl tower::Service for DatabaseReadHandle { diff --git a/database/src/service/tests.rs b/database/src/service/tests.rs index 03c03dbc..143fedef 100644 --- a/database/src/service/tests.rs +++ b/database/src/service/tests.rs @@ -9,36 +9,202 @@ #![allow(unused_mut, clippy::significant_drop_tightening)] +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; + //---------------------------------------------------------------------------------------------------- Use use tower::{Service, ServiceExt}; -use cuprate_types::service::{ReadRequest, Response, WriteRequest}; +use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3}; +use cuprate_types::{ + service::{ReadRequest, Response, WriteRequest}, + ExtendedBlockHeader, VerifiedBlockInformation, +}; use crate::{ config::Config, + ops::block::{get_block_extended_header_from_height, get_block_info}, service::{init, DatabaseReadHandle, DatabaseWriteHandle}, + tables::Tables, + tests::AssertTableLen, + ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError, }; -//---------------------------------------------------------------------------------------------------- Tests +//---------------------------------------------------------------------------------------------------- Helper functions /// Initialize the `service`. -fn init_service() -> (DatabaseReadHandle, DatabaseWriteHandle, tempfile::TempDir) { +fn init_service() -> ( + DatabaseReadHandle, + DatabaseWriteHandle, + Arc, + tempfile::TempDir, +) { let tempdir = tempfile::tempdir().unwrap(); let config = Config::low_power(Some(tempdir.path().into())); let (reader, writer) = init(config).unwrap(); - (reader, writer, tempdir) + let env = reader.env().clone(); + (reader, writer, env, tempdir) } +/// Send a write request, and receive a response, +/// asserting the response the expected value. +async fn write_request( + writer: &mut DatabaseWriteHandle, + block_fn: fn() -> &'static VerifiedBlockInformation, +) { + // HACK: `add_block()` asserts blocks with non-sequential heights + // cannot be added, to get around this, manually edit the block height. + let mut block = block_fn().clone(); + block.height = 0; + + // Request a block to be written, assert it was written. + let request = WriteRequest::WriteBlock(block); + let response_channel = writer.call(request); + let response = response_channel.await.unwrap(); + assert_eq!(response, Response::WriteBlockOk); +} + +//---------------------------------------------------------------------------------------------------- Tests /// Simply `init()` the service and then drop it. /// /// If this test fails, something is very wrong. #[test] fn init_drop() { - let (reader, writer, _tempdir) = init_service(); + let (reader, writer, env, _tempdir) = init_service(); } -// TODO: -// un-comment and fix these tests when all `{read,write}` -// service functions are implemented. +/// Assert write/read correctness of [`block_v1_tx2`]. +#[tokio::test] +async fn v1_tx2() { + let (reader, mut writer, env, _tempdir) = init_service(); + + write_request(&mut writer, block_v1_tx2).await; + + // Assert the actual database tables were correctly modified. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); + + AssertTableLen { + block_infos: 1, + block_blobs: 1, + block_heights: 1, + key_images: 65, + num_outputs: 38, + pruned_tx_blobs: 0, + prunable_hashes: 0, + outputs: 107, + prunable_tx_blobs: 0, + rct_outputs: 0, + tx_blobs: 2, + tx_ids: 2, + tx_heights: 2, + tx_unlock_time: 0, + } + .assert(&tables); + + let height = 0; + let extended_block_header = get_block_extended_header_from_height(&height, &tables).unwrap(); + let block_info = get_block_info(&height, tables.block_infos()).unwrap(); + + // Assert reads are correct. + for (request, expected_response) in [ + // Each tuple is a `Request` + `Result` pair. + ( + ReadRequest::BlockExtendedHeader(0), // The request to send to the service + Ok(Response::BlockExtendedHeader(extended_block_header)), // The expected response + ), + ( + ReadRequest::BlockExtendedHeader(1), + Err(RuntimeError::KeyNotFound), + ), + ( + ReadRequest::BlockHash(0), + Ok(Response::BlockHash(block_info.block_hash)), + ), + (ReadRequest::BlockHash(1), Err(RuntimeError::KeyNotFound)), + ( + ReadRequest::BlockExtendedHeaderInRange(0..1), + Ok(Response::BlockExtendedHeaderInRange(vec![ + extended_block_header, + ])), + ), + ( + ReadRequest::BlockExtendedHeaderInRange(0..2), + Err(RuntimeError::KeyNotFound), + ), + ( + ReadRequest::ChainHeight, + Ok(Response::ChainHeight(height, block_info.block_hash)), + ), + (ReadRequest::GeneratedCoins, Ok(Response::GeneratedCoins(0))), + // (ReadRequest::Outputs(HashMap>), ), + // (ReadRequest::NumberOutputsWithAmount(Vec), ), + // (ReadRequest::CheckKIsNotSpent(HashSet<[u8; 32]>), ), + ] { + let response_channel = reader.clone().oneshot(request); + let response = response_channel.await; + println!("response: {response:#?}, expected_response: {expected_response:#?}"); + assert!(matches!(response, expected_response)); + } +} + +/// Assert write/read correctness of [`block_v9_tx3`]. +#[tokio::test] +async fn v9_tx3() { + let (reader, mut writer, env, _tempdir) = init_service(); + + write_request(&mut writer, block_v9_tx3).await; + + // Assert the actual database tables were correctly modified. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); + + assert_eq!(tables.block_infos().len().unwrap(), 1); + assert_eq!(tables.block_blobs().len().unwrap(), 1); + assert_eq!(tables.block_heights().len().unwrap(), 1); + assert_eq!(tables.key_images().len().unwrap(), 4); + assert_eq!(tables.num_outputs().len().unwrap(), 0); + assert_eq!(tables.pruned_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.prunable_hashes().len().unwrap(), 0); + assert_eq!(tables.outputs().len().unwrap(), 0); + assert_eq!(tables.prunable_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.rct_outputs().len().unwrap(), 6); + assert_eq!(tables.tx_blobs().len().unwrap(), 3); + assert_eq!(tables.tx_ids().len().unwrap(), 3); + assert_eq!(tables.tx_heights().len().unwrap(), 3); + assert_eq!(tables.tx_unlock_time().len().unwrap(), 0); +} + +/// Assert write/read correctness of [`block_v16_tx0`]. +#[tokio::test] +async fn v16_tx0() { + let (reader, mut writer, env, _tempdir) = init_service(); + + write_request(&mut writer, block_v16_tx0).await; + + // Assert the actual database tables were correctly modified. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let tables = env_inner.open_tables(&tx_ro).unwrap(); + + assert_eq!(tables.block_infos().len().unwrap(), 1); + assert_eq!(tables.block_blobs().len().unwrap(), 1); + assert_eq!(tables.block_heights().len().unwrap(), 1); + assert_eq!(tables.key_images().len().unwrap(), 0); + assert_eq!(tables.num_outputs().len().unwrap(), 0); + assert_eq!(tables.pruned_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.prunable_hashes().len().unwrap(), 0); + assert_eq!(tables.outputs().len().unwrap(), 0); + assert_eq!(tables.prunable_tx_blobs().len().unwrap(), 0); + assert_eq!(tables.rct_outputs().len().unwrap(), 0); + assert_eq!(tables.tx_blobs().len().unwrap(), 0); + assert_eq!(tables.tx_ids().len().unwrap(), 0); + assert_eq!(tables.tx_heights().len().unwrap(), 0); + assert_eq!(tables.tx_unlock_time().len().unwrap(), 0); +} // /// Send a read request, and receive a response, // /// asserting the response the expected value. @@ -60,23 +226,3 @@ fn init_drop() { // assert_eq!(response, expected_response); // } // } - -// /// Send a write request, and receive a response, -// /// asserting the response the expected value. -// #[tokio::test] -// async fn write_request() { -// let (reader, mut writer, _tempdir) = init_service(); - -// for (request, expected_response) in [ -// (WriteRequest::Example1, Response::Example1), -// (WriteRequest::Example2(123), Response::Example2(123)), -// ( -// WriteRequest::Example3("hello".into()), -// Response::Example3("hello".into()), -// ), -// ] { -// let response_channel = writer.call(request); -// let response = response_channel.await.unwrap(); -// assert_eq!(response, expected_response); -// } -// }