cuprate_txpool/
free.rs

1//! General free functions (related to the tx-pool database).
2
3use std::borrow::Cow;
4
5use cuprate_database::{
6    ConcreteEnv, DatabaseRo, Env, EnvInner, InitError, RuntimeError, StorableStr, TxRw,
7};
8use cuprate_database::{DatabaseRw, TxRo};
9
10use crate::{
11    config::Config,
12    tables::{Metadata, OpenTables, TransactionBlobs},
13    types::TransactionBlobHash,
14};
15
16/// The current version of the database format.
17pub const DATABASE_VERSION: StorableStr = StorableStr(Cow::Borrowed("0.1"));
18
19/// The key used to store the database version in the [`Metadata`] table.
20pub const VERSION_KEY: StorableStr = StorableStr(Cow::Borrowed("version"));
21
22//---------------------------------------------------------------------------------------------------- Free functions
23/// Open the txpool database using the passed [`Config`].
24///
25/// This calls [`cuprate_database::Env::open`] and prepares the
26/// database to be ready for txpool-related usage, e.g.
27/// table creation, table sort order, etc.
28///
29/// All tables found in [`crate::tables`] will be
30/// ready for usage in the returned [`ConcreteEnv`].
31///
32/// # Errors
33/// This will error if:
34/// - The database file could not be opened
35/// - A write transaction could not be opened
36/// - A table could not be created/opened
37#[cold]
38#[inline(never)] // only called once
39pub fn open(config: &Config) -> Result<ConcreteEnv, InitError> {
40    // Attempt to open the database environment.
41    let env = <ConcreteEnv as Env>::open(config.db_config.clone())?;
42
43    /// Convert runtime errors to init errors.
44    ///
45    /// INVARIANT:
46    /// [`cuprate_database`]'s functions mostly return the former
47    /// so we must convert them. We have knowledge of which errors
48    /// makes sense in this functions context so we panic on
49    /// unexpected ones.
50    fn runtime_to_init_error(runtime: RuntimeError) -> InitError {
51        match runtime {
52            RuntimeError::Io(io_error) => io_error.into(),
53            RuntimeError::KeyNotFound => InitError::InvalidVersion,
54
55            // These errors shouldn't be happening here.
56            RuntimeError::KeyExists | RuntimeError::ResizeNeeded | RuntimeError::TableNotFound => {
57                unreachable!()
58            }
59        }
60    }
61
62    let fresh_db;
63
64    // INVARIANT: We must ensure that all tables are created,
65    // `cuprate_database` has no way of knowing _which_ tables
66    // we want since it is agnostic, so we are responsible for this.
67    {
68        let env_inner = env.env_inner();
69
70        // Store if this DB has been used before by checking if the [`TransactionBlobs`] table exists.
71        let tx_ro = env_inner.tx_ro().map_err(runtime_to_init_error)?;
72        fresh_db = env_inner.open_db_ro::<TransactionBlobs>(&tx_ro).is_err();
73        TxRo::commit(tx_ro).map_err(runtime_to_init_error)?;
74
75        let tx_rw = env_inner.tx_rw().map_err(runtime_to_init_error)?;
76
77        // Create all tables.
78        OpenTables::create_tables(&env_inner, &tx_rw).map_err(runtime_to_init_error)?;
79
80        TxRw::commit(tx_rw).map_err(runtime_to_init_error)?;
81    }
82
83    {
84        let env_inner = env.env_inner();
85        let tx_rw = env_inner.tx_rw().map_err(runtime_to_init_error)?;
86
87        let mut metadata = env_inner
88            .open_db_rw::<Metadata>(&tx_rw)
89            .map_err(runtime_to_init_error)?;
90
91        if fresh_db {
92            // If the database is new, add the version.
93            metadata
94                .put(&VERSION_KEY, &DATABASE_VERSION)
95                .map_err(runtime_to_init_error)?;
96        }
97
98        let print_version_err = || {
99            tracing::error!(
100                "The database follows an old format, please delete the database at: {}",
101                config.db_config.db_directory().display()
102            );
103        };
104
105        let version = metadata
106            .get(&VERSION_KEY)
107            .inspect_err(|_| print_version_err())
108            .map_err(runtime_to_init_error)?;
109
110        if version != DATABASE_VERSION {
111            // TODO: database migration when stable? This is the tx-pool so is not critical.
112            print_version_err();
113            return Err(InitError::InvalidVersion);
114        }
115
116        drop(metadata);
117        TxRw::commit(tx_rw).map_err(runtime_to_init_error)?;
118    }
119
120    Ok(env)
121}
122
123/// Calculate the transaction blob hash.
124///
125/// This value is supposed to be quick to compute just based of the tx-blob without needing to parse the tx.
126///
127/// The exact way the hash is calculated is not stable and is subject to change, as such it should not be exposed
128/// as a way to interact with Cuprate externally.
129pub fn transaction_blob_hash(tx_blob: &[u8]) -> TransactionBlobHash {
130    blake3::hash(tx_blob).into()
131}