add more docs
Some checks failed
Deny / audit (push) Has been cancelled
Audit / audit (push) Has been cancelled

This commit is contained in:
Boog900 2024-08-06 02:03:23 +01:00
parent 251c467931
commit 9b3ae11832
No known key found for this signature in database
GPG key ID: 42AB1287CB0041C2
13 changed files with 359 additions and 31 deletions

4
Cargo.lock generated
View file

@ -810,11 +810,15 @@ dependencies = [
"cuprate-database",
"cuprate-database-service",
"cuprate-helper",
"cuprate-test-utils",
"cuprate-types",
"hex",
"hex-literal",
"monero-serai",
"rayon",
"tempfile",
"thiserror",
"tokio",
"tower",
]

View file

@ -11,7 +11,7 @@ keywords = ["cuprate", "txpool", "transaction", "pool", "database"]
[features]
[dependencies]
cuprate-database = { path = "../database" }
cuprate-database = { path = "../database", features = ["heed"] }
cuprate-database-service = { path = "../service" }
cuprate-types = { path = "../../types" }
cuprate-helper = { path = "../../helper" , default-features = false, features = ["constants"] }
@ -26,3 +26,8 @@ tower = { workspace = true }
rayon = { workspace = true }
[dev-dependencies]
cuprate-test-utils = { path = "../../test-utils" }
tokio = { workspace = true }
tempfile = { workspace = true }
hex-literal = { workspace = true }

View file

@ -1,7 +1,154 @@
use cuprate_database::config::Config as DbConfig;
use cuprate_database::config::{Config as DbConfig, SyncMode};
use cuprate_database_service::ReaderThreads;
use cuprate_helper::fs::cuprate_txpool_dir;
use cuprate_helper::fs::{cuprate_blockchain_dir, cuprate_txpool_dir};
use std::borrow::Cow;
use std::path::Path;
use cuprate_database::resize::ResizeAlgorithm;
/// The default transaction pool weight limit.
const DEFAULT_TXPOOL_WEIGHT_LIMIT: usize = 600 * 1024 * 1024;
//---------------------------------------------------------------------------------------------------- ConfigBuilder
/// Builder for [`Config`].
///
// SOMEDAY: there's are many more options to add in the future.
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ConfigBuilder {
/// [`Config::db_directory`].
db_directory: Option<Cow<'static, Path>>,
/// [`Config::cuprate_database_config`].
db_config: cuprate_database::config::ConfigBuilder,
/// [`Config::reader_threads`].
reader_threads: Option<ReaderThreads>,
/// [`Config::max_txpool_weight`].
max_txpool_weight: Option<usize>,
}
impl ConfigBuilder {
/// Create a new [`ConfigBuilder`].
///
/// [`ConfigBuilder::build`] can be called immediately
/// after this function to use default values.
pub fn new() -> Self {
Self {
db_directory: None,
db_config: cuprate_database::config::ConfigBuilder::new(Cow::Borrowed(
cuprate_blockchain_dir(),
)),
reader_threads: None,
max_txpool_weight: None
}
}
/// Build into a [`Config`].
///
/// # Default values
/// If [`ConfigBuilder::db_directory`] was not called,
/// the default [`cuprate_blockchain_dir`] will be used.
///
/// For all other values, [`Default::default`] is used.
pub fn build(self) -> Config {
// INVARIANT: all PATH safety checks are done
// in `helper::fs`. No need to do them here.
let db_directory = self
.db_directory
.unwrap_or_else(|| Cow::Borrowed(cuprate_blockchain_dir()));
let reader_threads = self.reader_threads.unwrap_or_default();
let max_txpool_weight = self.max_txpool_weight.unwrap_or(DEFAULT_TXPOOL_WEIGHT_LIMIT);
let db_config = self
.db_config
.db_directory(db_directory)
.reader_threads(reader_threads.as_threads())
.build();
Config {
db_config,
reader_threads,
max_txpool_weight
}
}
/// Sets a new maximum weight for the transaction pool.
pub const fn max_txpool_weight(mut self, max_txpool_weight: usize) -> Self {
self.max_txpool_weight = Some(max_txpool_weight);
self
}
/// Set a custom database directory (and file) [`Path`].
#[must_use]
pub fn db_directory(mut self, db_directory: Cow<'static, Path>) -> Self {
self.db_directory = Some(db_directory);
self
}
/// Calls [`cuprate_database::config::ConfigBuilder::sync_mode`].
#[must_use]
pub fn sync_mode(mut self, sync_mode: SyncMode) -> Self {
self.db_config = self.db_config.sync_mode(sync_mode);
self
}
/// Calls [`cuprate_database::config::ConfigBuilder::resize_algorithm`].
#[must_use]
pub fn resize_algorithm(mut self, resize_algorithm: ResizeAlgorithm) -> Self {
self.db_config = self.db_config.resize_algorithm(resize_algorithm);
self
}
/// Set a custom [`ReaderThreads`].
#[must_use]
pub const fn reader_threads(mut self, reader_threads: ReaderThreads) -> Self {
self.reader_threads = Some(reader_threads);
self
}
/// Tune the [`ConfigBuilder`] for the highest performing,
/// but also most resource-intensive & maybe risky settings.
///
/// Good default for testing, and resource-available machines.
#[must_use]
pub fn fast(mut self) -> Self {
self.db_config =
cuprate_database::config::ConfigBuilder::new(Cow::Borrowed(cuprate_blockchain_dir()))
.fast();
self.reader_threads = Some(ReaderThreads::OnePerThread);
self
}
/// Tune the [`ConfigBuilder`] for the lowest performing,
/// but also least resource-intensive settings.
///
/// Good default for resource-limited machines, e.g. a cheap VPS.
#[must_use]
pub fn low_power(mut self) -> Self {
self.db_config =
cuprate_database::config::ConfigBuilder::new(Cow::Borrowed(cuprate_blockchain_dir()))
.low_power();
self.reader_threads = Some(ReaderThreads::One);
self
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
let db_directory = Cow::Borrowed(cuprate_blockchain_dir());
Self {
db_directory: Some(db_directory.clone()),
db_config: cuprate_database::config::ConfigBuilder::new(db_directory),
reader_threads: Some(ReaderThreads::default()),
max_txpool_weight: Some(DEFAULT_TXPOOL_WEIGHT_LIMIT)
}
}
}
//---------------------------------------------------------------------------------------------------- Config
/// `cuprate_txpool` configuration.

View file

@ -1,13 +1,16 @@
mod config;
pub mod config;
mod free;
mod ops;
mod service;
pub mod service;
mod tables;
mod types;
pub use config::Config;
pub use free::open;
//re-exports
pub use cuprate_database;
#[derive(thiserror::Error, Debug)]
pub enum TxPoolWriteError {
/// The transaction could not be added as it double spends another tx in the pool.

View file

@ -7,6 +7,9 @@ use crate::{tables::SpentKeyImages, types::TransactionHash, TxPoolWriteError};
/// Adds the transaction key images to the [`SpentKeyImages`] table.
///
/// This function will return an error if any of the key images are already spent.
///
/// # Panics
/// This function will panic if any of the [`Input`]s are not [`Input::ToKey`]
pub fn add_tx_key_images(
inputs: &[Input],
tx_hash: &TransactionHash,
@ -24,6 +27,9 @@ pub fn add_tx_key_images(
}
/// Removes key images from the [`SpentKeyImages`] table.
///
/// # Panics
/// This function will panic if any of the [`Input`]s are not [`Input::ToKey`]
pub fn remove_tx_key_images(
inputs: &[Input],
kis_table: &mut impl DatabaseRw<SpentKeyImages>,
@ -39,7 +45,6 @@ pub fn remove_tx_key_images(
///
/// # Panics
/// This function will panic if the [`Input`] is not [`Input::ToKey`]
///
fn ki_from_input(input: &Input) -> [u8; 32] {
match input {
Input::ToKey { key_image, .. } => key_image.compress().0,

View file

@ -1,9 +1,13 @@
use crate::tables::{Tables, TransactionBlobs};
use crate::types::TransactionHash;
use cuprate_database::{DatabaseRw, RuntimeError};
//! Transaction read ops.
//!
//! This module handles reading full transaction data, like getting a transaction from the pool.
use std::sync::Mutex;
use cuprate_database::RuntimeError;
use cuprate_types::TransactionVerificationData;
use monero_serai::transaction::Transaction;
use std::sync::Mutex;
use crate::{tables::Tables, types::TransactionHash};
/// Gets the [`TransactionVerificationData`] of a transaction in the tx-pool, leaving the tx in the pool.
pub fn get_transaction_verification_data(

View file

@ -1,12 +1,20 @@
use crate::ops::key_images::{add_tx_key_images, remove_tx_key_images};
use crate::tables::TablesMut;
use crate::types::{TransactionHash, TransactionInfo};
use crate::TxPoolWriteError;
//! Transaction writing ops.
//!
//! This module handles writing full transaction data, like removing or adding a transaction.
use std::sync::Arc;
use bytemuck::TransparentWrapper;
use monero_serai::transaction::{Input, Transaction};
use cuprate_database::{RuntimeError, StorableVec};
use cuprate_types::TransactionVerificationData;
use monero_serai::transaction::{Input, Transaction};
use std::sync::Arc;
use crate::{
ops::key_images::{add_tx_key_images, remove_tx_key_images},
tables::TablesMut,
types::{TransactionHash, TransactionInfo},
TxPoolWriteError,
};
/// Adds a transaction to the tx-pool.
///
@ -14,7 +22,6 @@ use std::sync::Arc;
///
/// # Panics
/// This function will panic if the transactions inputs are not all of type [`Input::ToKey`].
///
fn add_transaction(
tx: Arc<TransactionVerificationData>,
state_stem: bool,
@ -50,6 +57,9 @@ fn add_transaction(
}
/// Removes a transaction from the transaction pool.
///
/// # Panics
/// This function will panic if the transactions inputs are not all of type [`Input::ToKey`].
fn remove_transaction(
tx_hash: &TransactionHash,
tables: &mut impl TablesMut,

View file

@ -1,5 +1,130 @@
//! [`tower::Service`] integeration + thread-pool.
//!
//! ## `service`
//! The `service` module implements the [`tower`] integration,
//! along with the reader/writer thread-pool system.
//!
//! The thread-pool allows outside crates to communicate with it by
//! sending database [`Request`][req_r]s and receiving [`Response`][resp]s `async`hronously -
//! without having to actually worry and handle the database themselves.
//!
//! The system is managed by this crate, and only requires [`init`] by the user.
//!
//! This module must be enabled with the `service` feature.
//!
//! ## Handles
//! The 2 handles to the database are:
//! - [`BCReadHandle`]
//! - [`BCWriteHandle`]
//!
//! The 1st allows any caller to send [`ReadRequest`][req_r]s.
//!
//! The 2nd allows any caller to send [`WriteRequest`][req_w]s.
//!
//! The `DatabaseReadHandle` can be shared as it is cheaply [`Clone`]able, however,
//! the `DatabaseWriteHandle` cannot be cloned. There is only 1 place in Cuprate that
//! writes, so it is passed there and used.
//!
//! ## Initialization
//! The database & thread-pool system can be initialized with [`init()`].
//!
//! This causes the underlying database/threads to be setup
//! and returns a read/write handle to that database.
//!
//! ## Shutdown
//! Upon the above handles being dropped, the corresponding thread(s) will automatically exit, i.e:
//! - The last [`BCReadHandle`] is dropped => reader thread-pool exits
//! - The last [`BCWriteHandle`] is dropped => writer thread exits
//!
//! Upon dropping the [`cuprate_database::Env`]:
//! - All un-processed database transactions are completed
//! - All data gets flushed to disk (caused by [`Drop::drop`] impl on `Env`)
//!
//! ## Request and Response
//! To interact with the database (whether reading or writing data),
//! a `Request` can be sent using one of the above handles.
//!
//! Both the handles implement `tower::Service`, so they can be [`tower::Service::call`]ed.
//!
//! An `async`hronous channel will be returned from the call.
//! This channel can be `.await`ed upon to (eventually) receive
//! the corresponding `Response` to your `Request`.
//!
//! [req_r]: cuprate_types::blockchain::BCReadRequest
//!
//! [req_w]: cuprate_types::blockchain::BCWriteRequest
//!
//! [resp]: cuprate_types::blockchain::BCResponse
//!
//! # Example
//! Simple usage of `service`.
//!
//! ```rust
//! use std::sync::Arc;
//!
//! use hex_literal::hex;
//! use tower::{Service, ServiceExt};
//!
//! use cuprate_test_utils::data::tx_v1_sig0;
//!
//! use cuprate_txpool::{
//! cuprate_database::Env,
//! config::ConfigBuilder,
//! service::interface::{
//! TxpoolWriteRequest,
//! TxpoolWriteResponse,
//! TxpoolReadRequest,
//! TxpoolReadResponse
//! }
//! };
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Create a configuration for the database environment.
//! let tmp_dir = tempfile::tempdir()?;
//! let db_dir = tmp_dir.path().to_owned();
//! let config = ConfigBuilder::new()
//! .db_directory(db_dir.into())
//! .build();
//!
//! // Initialize the database thread-pool.
//! let (mut read_handle, mut write_handle, _) = cuprate_txpool::service::init(config)?;
//!
//! // Prepare a request to write block.
//! let tx = tx_v1_sig0();
//! let request = TxpoolWriteRequest::AddTransaction {
//! tx: Arc::new(tx.into()),
//! state_stem: false,
//! };
//!
//! // Send the request.
//! // We receive back an `async` channel that will
//! // eventually yield the result when `service`
//! // is done writing the tx.
//! let response_channel = write_handle.ready().await?.call(request);
//!
//! // Block write was OK.
//! let response = response_channel.await?;
//! assert_eq!(response, TxpoolWriteResponse::AddTransaction);
//!
//! // Now, let's try getting the block hash
//! // of the block we just wrote.
//! let request = TxpoolReadRequest::TxBlob(tx_v1_sig0().tx_hash);
//! let response_channel = read_handle.ready().await?.call(request);
//! let response = response_channel.await?;
//!
//! // This causes the writer thread on the
//! // other side of this handle to exit...
//! drop(write_handle);
//! // ...and this causes the reader thread-pool to exit.
//! drop(read_handle);
//! # Ok(()) }
//! ```
mod free;
mod interface;
pub mod interface;
mod read;
mod types;
mod write;
pub use free::init;

View file

@ -2,9 +2,14 @@ use std::sync::Arc;
use cuprate_database::{ConcreteEnv, InitError};
use crate::service::read::init_read_service;
use crate::service::types::{TxpoolReadHandle, TxpoolWriteHandle};
use crate::Config;
use crate::{
service::{
read::init_read_service,
types::{TxpoolReadHandle, TxpoolWriteHandle},
write::init_write_service,
},
Config,
};
//---------------------------------------------------------------------------------------------------- Init
#[cold]

View file

@ -26,13 +26,19 @@ pub enum TxpoolReadResponse {
//---------------------------------------------------------------------------------------------------- TxpoolWriteRequest
pub enum TxpoolWriteRequest {
AddTransaction(NewTransaction),
AddTransaction {
tx: Arc<TransactionVerificationData>,
state_stem: bool
},
RemoveTransaction(TransactionHash),
PromoteTransactionToFluffPool(TransactionHash),
}
//---------------------------------------------------------------------------------------------------- TxpoolWriteResponse
pub enum TxpoolWriteResponse {}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum TxpoolWriteResponse {
AddTransaction
}
pub struct NewTransaction {
tx: Arc<TransactionVerificationData>,

View file

@ -1,7 +1,3 @@
use cuprate_database::{ConcreteEnv, RuntimeError};
use cuprate_database_service::DatabaseWriteHandle;
use std::sync::Arc;
use cuprate_types::blockchain::BCWriteRequest;
use crate::{
service::{
interface::{TxpoolWriteRequest, TxpoolWriteResponse},
@ -9,6 +5,10 @@ use crate::{
},
TxPoolWriteError,
};
use cuprate_database::{ConcreteEnv, RuntimeError};
use cuprate_database_service::DatabaseWriteHandle;
use cuprate_types::blockchain::BCWriteRequest;
use std::sync::Arc;
//---------------------------------------------------------------------------------------------------- init_write_service
/// Initialize the txpool write service from a [`ConcreteEnv`].
@ -23,7 +23,8 @@ fn handle_txpool_request(
req: &TxpoolWriteRequest,
) -> Result<TxpoolWriteResponse, TxPoolWriteError> {
match req {
TxpoolWriteRequest::AddTransaction(tx) =>
TxpoolWriteRequest::AddTransaction { .. } => todo!(),
_ => todo!(),
}
}
@ -35,5 +36,3 @@ fn handle_txpool_request(
//
// Each function will return the [`Response`] that we
// should send back to the caller in [`map_request()`].
/// [`BCWriteRequest::WriteBlock`].

View file

@ -13,7 +13,7 @@ default = ["blockchain", "epee", "serde"]
blockchain = []
epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde"]
protptest = ["dep:proptest-derive"]
proptest = ["dep:proptest-derive"]
[dependencies]
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }

View file

@ -181,6 +181,21 @@ impl TransactionVerificationData {
}
}
// This impl is mainly for testing, going from a verified tx to a tx-pool tx doesn't have
// many use cases.
impl From<VerifiedTransactionInformation> for TransactionVerificationData {
fn from(value: VerifiedTransactionInformation) -> Self {
TransactionVerificationData {
tx: value.tx,
tx_blob: value.tx_blob,
tx_weight: value.tx_weight,
fee: value.fee,
tx_hash: value.tx_hash,
cached_verification_state: StdMutex::new(CachedVerificationState::NotVerified),
}
}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {