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}