Skip to main content

reth_db/implementation/mdbx/
mod.rs

1//! Module that interacts with MDBX.
2
3use crate::{
4    lockfile::StorageLock,
5    metrics::DatabaseEnvMetrics,
6    tables::{self, Tables},
7    utils::default_page_size,
8    DatabaseError, TableSet,
9};
10use eyre::Context;
11use metrics::{gauge, Label};
12use reth_db_api::{
13    cursor::{DbCursorRO, DbCursorRW},
14    database::Database,
15    database_metrics::DatabaseMetrics,
16    models::ClientVersion,
17    transaction::{DbTx, DbTxMut},
18};
19use reth_libmdbx::{
20    ffi, DatabaseFlags, Environment, EnvironmentFlags, Geometry, HandleSlowReadersReturnCode,
21    MaxReadTransactionDuration, Mode, PageSize, SyncMode, RO, RW,
22};
23use reth_storage_errors::db::LogLevel;
24use rustc_hash::FxHashMap;
25use std::{
26    ops::{Deref, Range},
27    path::{Path, PathBuf},
28    sync::Arc,
29    time::{Duration, SystemTime, UNIX_EPOCH},
30};
31use tracing::error;
32use tx::Tx;
33
34pub mod cursor;
35pub mod tx;
36
37mod utils;
38#[cfg(target_os = "linux")]
39mod value_prefetch;
40
41/// 1 KB in bytes
42pub const KILOBYTE: usize = 1024;
43/// 1 MB in bytes
44pub const MEGABYTE: usize = KILOBYTE * 1024;
45/// 1 GB in bytes
46pub const GIGABYTE: usize = MEGABYTE * 1024;
47/// 1 TB in bytes
48pub const TERABYTE: usize = GIGABYTE * 1024;
49
50/// MDBX allows up to 32767 readers (`MDBX_READERS_LIMIT`), but we limit it to slightly below that
51const DEFAULT_MAX_READERS: u64 = 32_000;
52
53/// Space that a read-only transaction can occupy until the warning is emitted.
54/// See [`reth_libmdbx::EnvironmentBuilder::set_handle_slow_readers`] for more information.
55const MAX_SAFE_READER_SPACE: usize = 10 * GIGABYTE;
56
57/// Longest time a [`SyncMode::SafeNoSync`] or [`SyncMode::UtterlyNoSync`] environment goes
58/// without a steady (synced) commit.
59///
60/// Commits in these modes are weak. After an unclean shutdown on a new boot (host reboot or power
61/// loss) MDBX discards all weak commits and rolls back to the last steady one, while static files
62/// and `RocksDB` sync on every commit and stay ahead. Without a period, steady commits only happen
63/// on a clean close or when the file fills up, so the rollback can span days and land below data
64/// that static file pruning already deleted, which startup healing cannot repair. MDBX checks the
65/// period on each commit.
66const NON_DURABLE_SYNC_PERIOD: Duration = Duration::from_secs(60);
67
68/// Environment used when opening a MDBX environment. RO/RW.
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub enum DatabaseEnvKind {
71    /// Read-only MDBX environment.
72    RO,
73    /// Read-write MDBX environment.
74    RW,
75}
76
77impl DatabaseEnvKind {
78    /// Returns `true` if the environment is read-write.
79    pub const fn is_rw(&self) -> bool {
80        matches!(self, Self::RW)
81    }
82}
83
84/// Arguments for database initialization.
85#[derive(Clone, Debug)]
86pub struct DatabaseArguments {
87    /// Client version that accesses the database.
88    client_version: ClientVersion,
89    /// Database geometry settings.
90    geometry: Geometry<Range<usize>>,
91    /// Database log level. If [None], the default value is used.
92    log_level: Option<LogLevel>,
93    /// Maximum duration of a read transaction. If [None], the default value is used.
94    max_read_transaction_duration: Option<MaxReadTransactionDuration>,
95    /// Open environment in exclusive/monopolistic mode. If [None], the default value is used.
96    ///
97    /// This can be used as a replacement for `MDB_NOLOCK`, which don't supported by MDBX. In this
98    /// way, you can get the minimal overhead, but with the correct multi-process and multi-thread
99    /// locking.
100    ///
101    /// If `true` = open environment in exclusive/monopolistic mode or return `MDBX_BUSY` if
102    /// environment already used by other process. The main feature of the exclusive mode is the
103    /// ability to open the environment placed on a network share.
104    ///
105    /// If `false` = open environment in cooperative mode, i.e. for multi-process
106    /// access/interaction/cooperation. The main requirements of the cooperative mode are:
107    /// - Data files MUST be placed in the LOCAL file system, but NOT on a network share.
108    /// - Environment MUST be opened only by LOCAL processes, but NOT over a network.
109    /// - OS kernel (i.e. file system and memory mapping implementation) and all processes that
110    ///   open the given environment MUST be running in the physically single RAM with
111    ///   cache-coherency. The only exception for cache-consistency requirement is Linux on MIPS
112    ///   architecture, but this case has not been tested for a long time).
113    ///
114    /// This flag affects only at environment opening but can't be changed after.
115    exclusive: Option<bool>,
116    /// MDBX allows up to 32767 readers (`MDBX_READERS_LIMIT`). This arg is to configure the max
117    /// readers.
118    max_readers: Option<u64>,
119    /// Defines the synchronization strategy used by the MDBX database when writing data to disk.
120    ///
121    /// This determines how aggressively MDBX ensures data durability versus prioritizing
122    /// performance. The available modes are:
123    ///
124    /// - [`SyncMode::Durable`]: Ensures all transactions are fully flushed to disk before they are
125    ///   considered committed.   This provides the highest level of durability and crash safety
126    ///   but may have a performance cost.
127    /// - [`SyncMode::SafeNoSync`]: Skips certain fsync operations to improve write performance.
128    ///   This mode still maintains database integrity but may lose the transactions committed
129    ///   within the last [`NON_DURABLE_SYNC_PERIOD`] if the system crashes unexpectedly.
130    ///
131    /// Choose `Durable` if consistency and crash safety are critical (e.g., production
132    /// environments). Choose `SafeNoSync` if performance is more important and occasional data
133    /// loss is acceptable (e.g., testing or ephemeral data).
134    sync_mode: SyncMode,
135}
136
137impl Default for DatabaseArguments {
138    fn default() -> Self {
139        Self::new(ClientVersion::default())
140    }
141}
142
143impl DatabaseArguments {
144    /// Create new database arguments with given client version.
145    pub fn new(client_version: ClientVersion) -> Self {
146        Self {
147            client_version,
148            geometry: Geometry {
149                size: Some(0..(8 * TERABYTE)),
150                growth_step: Some(4 * GIGABYTE as isize),
151                shrink_threshold: Some(0),
152                page_size: Some(PageSize::Set(default_page_size())),
153            },
154            log_level: None,
155            max_read_transaction_duration: None,
156            exclusive: None,
157            max_readers: None,
158            sync_mode: SyncMode::Durable,
159        }
160    }
161
162    /// Create database arguments suitable for testing.
163    ///
164    /// Uses a small geometry (64MB max, 4MB growth) to avoid exhausting the system's
165    /// virtual memory map limit (`vm.max_map_count`) when many test databases are open
166    /// concurrently.
167    pub fn test() -> Self {
168        Self {
169            geometry: Geometry {
170                size: Some(0..(64 * MEGABYTE)),
171                growth_step: Some(4 * MEGABYTE as isize),
172                shrink_threshold: Some(0),
173                page_size: Some(PageSize::Set(default_page_size())),
174            },
175            max_read_transaction_duration: Some(MaxReadTransactionDuration::Unbounded),
176            ..Self::new(ClientVersion::default())
177        }
178    }
179
180    /// Sets the upper size limit of the db environment, the maximum database size in bytes.
181    pub const fn with_geometry_max_size(mut self, max_size: Option<usize>) -> Self {
182        if let Some(max_size) = max_size {
183            self.geometry.size = Some(0..max_size);
184        }
185        self
186    }
187
188    /// Sets the database page size value.
189    pub const fn with_geometry_page_size(mut self, page_size: Option<usize>) -> Self {
190        if let Some(size) = page_size {
191            self.geometry.page_size = Some(reth_libmdbx::PageSize::Set(size));
192        }
193
194        self
195    }
196
197    /// Sets the database sync mode.
198    pub const fn with_sync_mode(mut self, sync_mode: Option<SyncMode>) -> Self {
199        if let Some(sync_mode) = sync_mode {
200            self.sync_mode = sync_mode;
201        }
202
203        self
204    }
205
206    /// Configures the database growth step in bytes.
207    pub const fn with_growth_step(mut self, growth_step: Option<usize>) -> Self {
208        if let Some(growth_step) = growth_step {
209            self.geometry.growth_step = Some(growth_step as isize);
210        }
211        self
212    }
213
214    /// Set the log level.
215    pub const fn with_log_level(mut self, log_level: Option<LogLevel>) -> Self {
216        self.log_level = log_level;
217        self
218    }
219
220    /// Set the maximum duration of a read transaction.
221    pub const fn max_read_transaction_duration(
222        &mut self,
223        max_read_transaction_duration: Option<MaxReadTransactionDuration>,
224    ) {
225        self.max_read_transaction_duration = max_read_transaction_duration;
226    }
227
228    /// Set the maximum duration of a read transaction.
229    pub const fn with_max_read_transaction_duration(
230        mut self,
231        max_read_transaction_duration: Option<MaxReadTransactionDuration>,
232    ) -> Self {
233        self.max_read_transaction_duration(max_read_transaction_duration);
234        self
235    }
236
237    /// Set the mdbx exclusive flag.
238    pub const fn with_exclusive(mut self, exclusive: Option<bool>) -> Self {
239        self.exclusive = exclusive;
240        self
241    }
242
243    /// Set `max_readers` flag.
244    pub const fn with_max_readers(mut self, max_readers: Option<u64>) -> Self {
245        self.max_readers = max_readers;
246        self
247    }
248
249    /// Returns the client version if any.
250    pub const fn client_version(&self) -> &ClientVersion {
251        &self.client_version
252    }
253}
254
255/// Wrapper for the libmdbx environment: [Environment]
256#[derive(Debug, Clone)]
257pub struct DatabaseEnv {
258    /// Libmdbx-sys environment.
259    inner: Environment,
260    /// Path to the database directory.
261    path: PathBuf,
262    /// Opened DBIs for reuse.
263    /// Important: Do not manually close these DBIs, like via `mdbx_dbi_close`.
264    /// More generally, do not dynamically create, re-open, or drop tables at
265    /// runtime. It's better to perform table creation and migration only once
266    /// at startup.
267    dbis: Arc<TableDbis>,
268    /// Cache for metric handles. If `None`, metrics are not recorded.
269    metrics: Option<Arc<DatabaseEnvMetrics>>,
270    /// Disables long-lived read safety on transactions opened through this handle.
271    disable_long_read_transaction_safety: bool,
272    /// Write lock for when dealing with a read-write environment.
273    _lock_file: Option<StorageLock>,
274}
275
276/// MDBX DBIs of the tables tracked by a [`DatabaseEnv`].
277#[derive(Debug, Clone)]
278pub(crate) struct TableDbis {
279    /// DBIs of [`Tables`] entries, indexed by the entry's position in [`Tables::ALL`].
280    tables: [Option<ffi::MDBX_dbi>; Tables::COUNT],
281    /// DBIs of tables defined outside of [`Tables`], by name.
282    other: FxHashMap<&'static str, ffi::MDBX_dbi>,
283}
284
285impl Default for TableDbis {
286    fn default() -> Self {
287        Self { tables: [None; Tables::COUNT], other: FxHashMap::default() }
288    }
289}
290
291impl TableDbis {
292    /// Returns the tracked DBI of a table, which is looked up by `table` if it is a [`Tables`]
293    /// entry and by `name` otherwise.
294    ///
295    /// Returns `None` if the table is not tracked.
296    pub(crate) fn get(&self, table: Option<Tables>, name: &str) -> Option<ffi::MDBX_dbi> {
297        match table {
298            Some(table) => self.tables[table as usize],
299            None => self.other.get(name).copied(),
300        }
301    }
302
303    /// Tracks the DBI of the table with the given name.
304    fn insert(&mut self, name: &'static str, dbi: ffi::MDBX_dbi) {
305        match name.parse::<Tables>() {
306            Ok(table) => self.tables[table as usize] = Some(dbi),
307            Err(_) => {
308                self.other.insert(name, dbi);
309            }
310        }
311    }
312}
313
314impl Database for DatabaseEnv {
315    type TX = tx::Tx<RO>;
316    type TXMut = tx::Tx<RW>;
317
318    fn tx(&self) -> Result<Self::TX, DatabaseError> {
319        let mut tx = Tx::new(
320            self.inner.begin_ro_txn().map_err(|e| DatabaseError::InitTx(e.into()))?,
321            self.dbis.clone(),
322            self.metrics.clone(),
323        )
324        .map_err(|e| DatabaseError::InitTx(e.into()))?;
325        if self.disable_long_read_transaction_safety {
326            tx.disable_long_read_transaction_safety();
327        }
328        Ok(tx)
329    }
330
331    fn tx_mut(&self) -> Result<Self::TXMut, DatabaseError> {
332        let mut tx = Tx::new(
333            self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?,
334            self.dbis.clone(),
335            self.metrics.clone(),
336        )
337        .map_err(|e| DatabaseError::InitTx(e.into()))?;
338        if self.disable_long_read_transaction_safety {
339            tx.disable_long_read_transaction_safety();
340        }
341        Ok(tx)
342    }
343
344    fn path(&self) -> PathBuf {
345        self.path.clone()
346    }
347
348    fn oldest_reader_txnid(&self) -> Option<u64> {
349        let info = self.inner.info().ok()?;
350        let txnid = info.latter_reader_txnid();
351        if txnid == 0 {
352            None
353        } else {
354            Some(txnid)
355        }
356    }
357
358    fn last_txnid(&self) -> Option<u64> {
359        let info = self.inner.info().ok()?;
360        let txnid = info.last_txnid();
361        if txnid == 0 {
362            None
363        } else {
364            Some(txnid as u64)
365        }
366    }
367}
368
369impl DatabaseMetrics for DatabaseEnv {
370    fn report_metrics(&self) {
371        for (name, value, labels) in self.gauge_metrics() {
372            gauge!(name, labels).set(value);
373        }
374    }
375
376    fn gauge_metrics(&self) -> Vec<(&'static str, f64, Vec<Label>)> {
377        let mut metrics = Vec::new();
378
379        let _ = self
380            .view(|tx| {
381                for table in Tables::ALL.iter().map(Tables::name) {
382                    let table_db =
383                        tx.inner().open_db(Some(table)).wrap_err("Could not open db.")?;
384
385                    let stats = tx
386                        .inner()
387                        .db_stat(table_db.dbi())
388                        .wrap_err(format!("Could not find table: {table}"))?;
389
390                    let page_size = stats.page_size() as usize;
391                    let leaf_pages = stats.leaf_pages();
392                    let branch_pages = stats.branch_pages();
393                    let overflow_pages = stats.overflow_pages();
394                    let num_pages = leaf_pages + branch_pages + overflow_pages;
395                    let table_size = page_size * num_pages;
396                    let entries = stats.entries();
397
398                    metrics.push((
399                        "db.table_size",
400                        table_size as f64,
401                        vec![Label::new("table", table)],
402                    ));
403                    metrics.push((
404                        "db.table_pages",
405                        leaf_pages as f64,
406                        vec![Label::new("table", table), Label::new("type", "leaf")],
407                    ));
408                    metrics.push((
409                        "db.table_pages",
410                        branch_pages as f64,
411                        vec![Label::new("table", table), Label::new("type", "branch")],
412                    ));
413                    metrics.push((
414                        "db.table_pages",
415                        overflow_pages as f64,
416                        vec![Label::new("table", table), Label::new("type", "overflow")],
417                    ));
418                    metrics.push((
419                        "db.table_entries",
420                        entries as f64,
421                        vec![Label::new("table", table)],
422                    ));
423                }
424
425                Ok::<(), eyre::Report>(())
426            })
427            .map_err(|error| error!(%error, "Failed to read db table stats"));
428
429        if let Ok(freelist) =
430            self.freelist().map_err(|error| error!(%error, "Failed to read db.freelist"))
431        {
432            metrics.push(("db.freelist", freelist as f64, vec![]));
433        }
434
435        if let Ok(stat) = self.stat().map_err(|error| error!(%error, "Failed to read db.stat")) {
436            metrics.push(("db.page_size", stat.page_size() as f64, vec![]));
437        }
438
439        metrics.push((
440            "db.timed_out_not_aborted_transactions",
441            self.timed_out_not_aborted_transactions() as f64,
442            vec![],
443        ));
444
445        metrics
446    }
447}
448
449impl DatabaseEnv {
450    /// Opens the database at the specified path with the given `EnvKind`.
451    ///
452    /// It does not create the tables, for that call [`DatabaseEnv::create_tables`].
453    pub fn open(
454        path: &Path,
455        kind: DatabaseEnvKind,
456        args: DatabaseArguments,
457    ) -> Result<Self, DatabaseError> {
458        let _lock_file = if kind.is_rw() {
459            StorageLock::try_acquire(path)
460                .map_err(|err| DatabaseError::Other(err.to_string()))?
461                .into()
462        } else {
463            None
464        };
465
466        let mut inner_env = Environment::builder();
467
468        let mode = match kind {
469            DatabaseEnvKind::RO => Mode::ReadOnly,
470            DatabaseEnvKind::RW => {
471                // enable writemap mode in RW mode
472                inner_env.write_map();
473                if matches!(args.sync_mode, SyncMode::SafeNoSync | SyncMode::UtterlyNoSync) {
474                    inner_env.set_sync_period(NON_DURABLE_SYNC_PERIOD);
475                }
476                Mode::ReadWrite { sync_mode: args.sync_mode }
477            }
478        };
479
480        // Note: We set max dbs to 256 here to allow for custom tables. This needs to be set on
481        // environment creation.
482        debug_assert!(Tables::ALL.len() <= 256, "number of tables exceed max dbs");
483        inner_env.set_max_dbs(256);
484        inner_env.set_geometry(args.geometry);
485
486        fn is_current_process(id: u32) -> bool {
487            #[cfg(unix)]
488            {
489                id == std::os::unix::process::parent_id() || id == std::process::id()
490            }
491
492            #[cfg(not(unix))]
493            {
494                id == std::process::id()
495            }
496        }
497
498        extern "C" fn handle_slow_readers(
499            _env: *const ffi::MDBX_env,
500            _txn: *const ffi::MDBX_txn,
501            process_id: ffi::mdbx_pid_t,
502            thread_id: ffi::mdbx_tid_t,
503            read_txn_id: u64,
504            gap: std::ffi::c_uint,
505            space: usize,
506            retry: std::ffi::c_int,
507        ) -> HandleSlowReadersReturnCode {
508            if space > MAX_SAFE_READER_SPACE {
509                let message = if is_current_process(process_id as u32) {
510                    "Current process has a long-lived database transaction that grows the database file."
511                } else {
512                    "External process has a long-lived database transaction that grows the database file. \
513                     Use shorter-lived read transactions or shut down the node."
514                };
515                tracing::warn!(
516                    target: "storage::db::mdbx",
517                    ?process_id,
518                    ?thread_id,
519                    ?read_txn_id,
520                    ?gap,
521                    ?space,
522                    ?retry,
523                    "{message}"
524                )
525            }
526
527            reth_libmdbx::HandleSlowReadersReturnCode::ProceedWithoutKillingReader
528        }
529        inner_env.set_handle_slow_readers(handle_slow_readers);
530
531        inner_env.set_flags(EnvironmentFlags {
532            mode,
533            // We disable readahead because it improves performance for linear scans, but
534            // worsens it for random access (which is our access pattern outside of sync)
535            no_rdahead: true,
536            coalesce: true,
537            exclusive: args.exclusive.unwrap_or_default(),
538            ..Default::default()
539        });
540        // Configure more readers
541        inner_env.set_max_readers(args.max_readers.unwrap_or(DEFAULT_MAX_READERS));
542        // This parameter sets the maximum size of the "reclaimed list", and the unit of measurement
543        // is "pages". Reclaimed list is the list of freed pages that's populated during the
544        // lifetime of DB transaction, and through which MDBX searches when it needs to insert new
545        // record with overflow pages. The flow is roughly the following:
546        // 0. We need to insert a record that requires N number of overflow pages (in consecutive
547        //    sequence inside the DB file).
548        // 1. Get some pages from the freelist, put them into the reclaimed list.
549        // 2. Search through the reclaimed list for the sequence of size N.
550        // 3. a. If found, return the sequence.
551        // 3. b. If not found, repeat steps 1-3. If the reclaimed list size is larger than
552        //    the `rp augment limit`, stop the search and allocate new pages at the end of the file:
553        //    https://github.com/paradigmxyz/reth/blob/2a4c78759178f66e30c8976ec5d243b53102fc9a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c#L11479-L11480.
554        //
555        // Basically, this parameter controls for how long do we search through the freelist before
556        // trying to allocate new pages. Smaller value will make MDBX to fallback to
557        // allocation faster, higher value will force MDBX to search through the freelist
558        // longer until the sequence of pages is found.
559        //
560        // The default value of this parameter is set depending on the DB size. The bigger the
561        // database, the larger is `rp augment limit`.
562        // https://github.com/paradigmxyz/reth/blob/2a4c78759178f66e30c8976ec5d243b53102fc9a/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c#L10018-L10024.
563        //
564        // Previously, MDBX set this value as `256 * 1024` constant. Let's fallback to this,
565        // because we want to prioritize freelist lookup speed over database growth.
566        // https://github.com/paradigmxyz/reth/blob/fa2b9b685ed9787636d962f4366caf34a9186e66/crates/storage/libmdbx-rs/mdbx-sys/libmdbx/mdbx.c#L16017.
567        inner_env.set_rp_augment_limit(256 * 1024);
568
569        if let Some(log_level) = args.log_level {
570            // Levels higher than [LogLevel::Notice] require libmdbx built with `MDBX_DEBUG` option.
571            let is_log_level_available = if cfg!(debug_assertions) {
572                true
573            } else {
574                matches!(
575                    log_level,
576                    LogLevel::Fatal | LogLevel::Error | LogLevel::Warn | LogLevel::Notice
577                )
578            };
579            if is_log_level_available {
580                inner_env.set_log_level(match log_level {
581                    LogLevel::Fatal => 0,
582                    LogLevel::Error => 1,
583                    LogLevel::Warn => 2,
584                    LogLevel::Notice => 3,
585                    LogLevel::Verbose => 4,
586                    LogLevel::Debug => 5,
587                    LogLevel::Trace => 6,
588                    LogLevel::Extra => 7,
589                });
590            } else {
591                return Err(DatabaseError::LogLevelUnavailable(log_level))
592            }
593        }
594
595        if let Some(max_read_transaction_duration) = args.max_read_transaction_duration {
596            inner_env.set_max_read_transaction_duration(max_read_transaction_duration);
597        }
598
599        let env = Self {
600            inner: inner_env.open(path).map_err(|e| DatabaseError::Open(e.into()))?,
601            path: path.to_path_buf(),
602            dbis: Arc::default(),
603            metrics: None,
604            disable_long_read_transaction_safety: false,
605            _lock_file,
606        };
607
608        Ok(env)
609    }
610
611    /// Enables metrics on the database.
612    pub fn with_metrics(mut self) -> Self {
613        self.metrics = Some(DatabaseEnvMetrics::new().into());
614        self
615    }
616
617    /// Enables metrics on the database if requested.
618    pub fn with_metrics_if(self, enabled: bool) -> Self {
619        if enabled {
620            self.with_metrics()
621        } else {
622            self
623        }
624    }
625
626    /// Creates all the tables defined in [`Tables`], if necessary.
627    ///
628    /// This keeps tracks of the created table handles and stores them for better efficiency.
629    pub fn create_tables(&mut self) -> Result<(), DatabaseError> {
630        self.create_and_track_tables_for::<Tables>()
631    }
632
633    /// Creates all the tables defined in the given [`TableSet`], if necessary.
634    ///
635    /// This keeps tracks of the created table handles and stores them for better efficiency.
636    pub fn create_and_track_tables_for<TS: TableSet>(&mut self) -> Result<(), DatabaseError> {
637        let handles = self._create_tables::<TS>()?;
638        // Note: This is okay because self has mutable access here and `DatabaseEnv` must be Arc'ed
639        // before it can be shared.
640        let dbis = Arc::make_mut(&mut self.dbis);
641        for (name, dbi) in handles {
642            dbis.insert(name, dbi);
643        }
644
645        Ok(())
646    }
647
648    /// Creates all the tables defined in [`Tables`], if necessary.
649    ///
650    /// If this type is unique the created handle for the tables will be updated.
651    ///
652    /// This is recommended to be called during initialization to create and track additional tables
653    /// after the default [`Self::create_tables`] are created.
654    pub fn create_tables_for<TS: TableSet>(self: &mut Arc<Self>) -> Result<(), DatabaseError> {
655        let handles = self._create_tables::<TS>()?;
656        if let Some(db) = Arc::get_mut(self) {
657            // Note: The db is unique and the dbis as well, and they can also be cloned.
658            let dbis = Arc::make_mut(&mut db.dbis);
659            for (name, dbi) in handles {
660                dbis.insert(name, dbi);
661            }
662        }
663        Ok(())
664    }
665
666    /// Creates the tables and returns the identifiers of the tables.
667    fn _create_tables<TS: TableSet>(
668        &self,
669    ) -> Result<Vec<(&'static str, ffi::MDBX_dbi)>, DatabaseError> {
670        let mut handles = Vec::new();
671        let tx = self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?;
672
673        for table in TS::tables() {
674            let flags =
675                if table.is_dupsort() { DatabaseFlags::DUP_SORT } else { DatabaseFlags::default() };
676
677            let db = tx
678                .create_db(Some(table.name()), flags)
679                .map_err(|e| DatabaseError::CreateTable(e.into()))?;
680            handles.push((table.name(), db.dbi()));
681        }
682
683        tx.commit().map_err(|e| DatabaseError::Commit(e.into()))?;
684        Ok(handles)
685    }
686
687    /// Drops an orphaned table by name.
688    ///
689    /// This is used to clean up tables that are no longer defined in the schema but may still
690    /// exist on disk from previous versions.
691    ///
692    /// Returns `Ok(true)` if the table existed and was dropped, `Ok(false)` if the table was not
693    /// found.
694    ///
695    /// # Safety
696    /// This permanently deletes the table and all its data. Only use for tables that are
697    /// confirmed to be obsolete.
698    pub fn drop_orphan_table(&self, name: &str) -> Result<bool, DatabaseError> {
699        let tx = self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?;
700
701        match tx.open_db(Some(name)) {
702            Ok(db) => {
703                // SAFETY: We just opened the db handle and will commit immediately after dropping.
704                // No other cursors or handles exist for this table.
705                unsafe {
706                    tx.drop_db(db.dbi()).map_err(|e| DatabaseError::Delete(e.into()))?;
707                }
708                tx.commit().map_err(|e| DatabaseError::Commit(e.into()))?;
709                Ok(true)
710            }
711            Err(reth_libmdbx::Error::NotFound) => Ok(false),
712            Err(e) => Err(DatabaseError::Open(e.into())),
713        }
714    }
715
716    /// Records version that accesses the database with write privileges.
717    pub fn record_client_version(&self, version: ClientVersion) -> Result<(), DatabaseError> {
718        if version.is_empty() {
719            return Ok(())
720        }
721
722        let tx = self.tx_mut()?;
723        let mut version_cursor = tx.cursor_write::<tables::VersionHistory>()?;
724
725        let last_version = version_cursor.last()?.map(|(_, v)| v);
726        if Some(&version) != last_version.as_ref() {
727            version_cursor.upsert(
728                SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(),
729                &version,
730            )?;
731            tx.commit()?;
732        }
733
734        Ok(())
735    }
736
737    /// Disables timeouts and long-read backtrace reporting for transactions opened through this
738    /// handle. Existing transactions and other previously cloned handles are unaffected.
739    ///
740    /// Use for offline work that intentionally holds transactions open.
741    pub const fn with_disable_long_read_transaction_safety(mut self) -> Self {
742        self.disable_long_read_transaction_safety = true;
743        self
744    }
745}
746
747impl Deref for DatabaseEnv {
748    type Target = Environment;
749
750    fn deref(&self) -> &Self::Target {
751        &self.inner
752    }
753}
754
755#[cfg(test)]
756mod tests {
757
758    use super::*;
759    use crate::{
760        tables::{
761            AccountsHistory, CanonicalHeaders, Headers, PlainAccountState, PlainStorageState,
762        },
763        test_utils::*,
764        AccountChangeSets,
765    };
766    use alloy_consensus::Header;
767    use alloy_primitives::{address, Address, B256, U256};
768    use reth_db_api::{
769        cursor::{DbDupCursorRO, DbDupCursorRW, ReverseWalker, Walker},
770        models::{AccountBeforeTx, IntegerList, ShardedKey},
771        table::{Encode, Table, TableInfo},
772        tables_to_generic,
773    };
774    use reth_libmdbx::Error;
775    use reth_primitives_traits::{Account, StorageEntry};
776    use reth_storage_errors::db::{DatabaseWriteError, DatabaseWriteOperation};
777    use tempfile::TempDir;
778
779    /// Create database for testing. Returns the `TempDir` to prevent cleanup until test ends.
780    fn create_test_db(kind: DatabaseEnvKind) -> (TempDir, DatabaseEnv) {
781        let tempdir = tempfile::TempDir::new().expect(ERROR_TEMPDIR);
782        let env = create_test_db_with_path(kind, tempdir.path());
783        (tempdir, env)
784    }
785
786    /// Create database for testing with specified path
787    fn create_test_db_with_path(kind: DatabaseEnvKind, path: &Path) -> DatabaseEnv {
788        let mut env =
789            DatabaseEnv::open(path, kind, DatabaseArguments::new(ClientVersion::default()))
790                .expect(ERROR_DB_CREATION);
791        env.create_tables().expect(ERROR_TABLE_CREATION);
792        env
793    }
794
795    const ERROR_DB_CREATION: &str = "Not able to create the mdbx file.";
796    const ERROR_PUT: &str = "Not able to insert value into table.";
797    const ERROR_APPEND: &str = "Not able to append the value to the table.";
798    const ERROR_UPSERT: &str = "Not able to upsert the value to the table.";
799    const ERROR_GET: &str = "Not able to get value from table.";
800    const ERROR_DEL: &str = "Not able to delete from table.";
801    const ERROR_COMMIT: &str = "Not able to commit transaction.";
802    const ERROR_RETURN_VALUE: &str = "Mismatching result.";
803    const ERROR_INIT_TX: &str = "Failed to create a MDBX transaction.";
804
805    #[test]
806    fn db_creation() {
807        let _tempdir = create_test_db(DatabaseEnvKind::RW);
808    }
809
810    #[test]
811    fn db_drop_orphan_table() {
812        let tempdir = tempfile::TempDir::new().expect(ERROR_TEMPDIR);
813        let db = create_test_db_with_path(DatabaseEnvKind::RW, tempdir.path());
814
815        // Create an orphan table by manually creating it
816        let orphan_table_name = "OrphanTestTable";
817        {
818            let tx = db.inner.begin_rw_txn().expect(ERROR_INIT_TX);
819            tx.create_db(Some(orphan_table_name), DatabaseFlags::empty())
820                .expect("Failed to create orphan table");
821            tx.commit().expect(ERROR_COMMIT);
822        }
823
824        // Verify the table exists by opening it
825        {
826            let tx = db.inner.begin_ro_txn().expect(ERROR_INIT_TX);
827            assert!(tx.open_db(Some(orphan_table_name)).is_ok(), "Orphan table should exist");
828        }
829
830        // Drop the orphan table
831        let result = db.drop_orphan_table(orphan_table_name);
832        assert!(result.is_ok(), "drop_orphan_table should succeed");
833        assert!(result.unwrap(), "drop_orphan_table should return true for existing table");
834
835        // Verify the table no longer exists
836        {
837            let tx = db.inner.begin_ro_txn().expect(ERROR_INIT_TX);
838            assert!(
839                tx.open_db(Some(orphan_table_name)).is_err(),
840                "Orphan table should no longer exist"
841            );
842        }
843
844        // Dropping a non-existent table should return Ok(false)
845        let result = db.drop_orphan_table("NonExistentTable");
846        assert!(result.is_ok(), "drop_orphan_table should succeed for non-existent table");
847        assert!(!result.unwrap(), "drop_orphan_table should return false for non-existent table");
848    }
849
850    #[test]
851    fn db_manual_put_get() {
852        let (_tempdir, env) = create_test_db(DatabaseEnvKind::RW);
853
854        let value = Header::default();
855        let key = 1u64;
856
857        // PUT
858        let tx = env.tx_mut().expect(ERROR_INIT_TX);
859        tx.put::<Headers>(key, value.clone()).expect(ERROR_PUT);
860        tx.commit().expect(ERROR_COMMIT);
861
862        // GET
863        let tx = env.tx().expect(ERROR_INIT_TX);
864        let result = tx.get::<Headers>(key).expect(ERROR_GET);
865        assert_eq!(result.expect(ERROR_RETURN_VALUE), value);
866        tx.commit().expect(ERROR_COMMIT);
867    }
868
869    #[test]
870    fn db_dup_cursor_delete_first() {
871        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
872        let tx = db.tx_mut().expect(ERROR_INIT_TX);
873
874        let mut dup_cursor = tx.cursor_dup_write::<PlainStorageState>().unwrap();
875
876        let entry_0 = StorageEntry { key: B256::with_last_byte(1), value: U256::ZERO };
877        let entry_1 = StorageEntry { key: B256::with_last_byte(1), value: U256::ONE };
878
879        dup_cursor.upsert(Address::with_last_byte(1), &entry_0).expect(ERROR_UPSERT);
880        dup_cursor.upsert(Address::with_last_byte(1), &entry_1).expect(ERROR_UPSERT);
881
882        assert_eq!(
883            dup_cursor.walk(None).unwrap().collect::<Result<Vec<_>, _>>().unwrap(),
884            vec![(Address::with_last_byte(1), entry_0), (Address::with_last_byte(1), entry_1),]
885        );
886
887        let mut walker = dup_cursor.walk(None).unwrap();
888        walker.delete_current().expect(ERROR_DEL);
889
890        assert_eq!(walker.next().unwrap().unwrap(), (Address::with_last_byte(1), entry_1));
891
892        // Check the tx view - it correctly holds entry_1
893        assert_eq!(
894            tx.cursor_dup_read::<PlainStorageState>()
895                .unwrap()
896                .walk(None)
897                .unwrap()
898                .collect::<Result<Vec<_>, _>>()
899                .unwrap(),
900            vec![
901                (Address::with_last_byte(1), entry_1), // This is ok - we removed entry_0
902            ]
903        );
904
905        // Check the remainder of walker
906        assert!(walker.next().is_none());
907    }
908
909    #[test]
910    fn db_cursor_walk() {
911        let (_tempdir, env) = create_test_db(DatabaseEnvKind::RW);
912
913        let value = Header::default();
914        let key = 1u64;
915
916        // PUT
917        let tx = env.tx_mut().expect(ERROR_INIT_TX);
918        tx.put::<Headers>(key, value.clone()).expect(ERROR_PUT);
919        tx.commit().expect(ERROR_COMMIT);
920
921        // Cursor
922        let tx = env.tx().expect(ERROR_INIT_TX);
923        let mut cursor = tx.cursor_read::<Headers>().unwrap();
924
925        let first = cursor.first().unwrap();
926        assert!(first.is_some(), "First should be our put");
927
928        // Walk
929        let walk = cursor.walk(Some(key)).unwrap();
930        let first = walk.into_iter().next().unwrap().unwrap();
931        assert_eq!(first.1, value, "First next should be put value");
932    }
933
934    #[test]
935    fn db_cursor_walk_range() {
936        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
937
938        // PUT (0, 0), (1, 0), (2, 0), (3, 0)
939        let tx = db.tx_mut().expect(ERROR_INIT_TX);
940        vec![0, 1, 2, 3]
941            .into_iter()
942            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
943            .expect(ERROR_PUT);
944        tx.commit().expect(ERROR_COMMIT);
945
946        let tx = db.tx().expect(ERROR_INIT_TX);
947        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
948
949        // [1, 3)
950        let mut walker = cursor.walk_range(1..3).unwrap();
951        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
952        assert_eq!(walker.next().unwrap().unwrap(), (2, B256::ZERO));
953        assert!(walker.next().is_none());
954        // next() returns None after walker is done
955        assert!(walker.next().is_none());
956
957        // [1, 2]
958        let mut walker = cursor.walk_range(1..=2).unwrap();
959        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
960        assert_eq!(walker.next().unwrap().unwrap(), (2, B256::ZERO));
961        // next() returns None after walker is done
962        assert!(walker.next().is_none());
963
964        // [1, ∞)
965        let mut walker = cursor.walk_range(1..).unwrap();
966        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
967        assert_eq!(walker.next().unwrap().unwrap(), (2, B256::ZERO));
968        assert_eq!(walker.next().unwrap().unwrap(), (3, B256::ZERO));
969        // next() returns None after walker is done
970        assert!(walker.next().is_none());
971
972        // [2, 4)
973        let mut walker = cursor.walk_range(2..4).unwrap();
974        assert_eq!(walker.next().unwrap().unwrap(), (2, B256::ZERO));
975        assert_eq!(walker.next().unwrap().unwrap(), (3, B256::ZERO));
976        assert!(walker.next().is_none());
977        // next() returns None after walker is done
978        assert!(walker.next().is_none());
979
980        // (∞, 3)
981        let mut walker = cursor.walk_range(..3).unwrap();
982        assert_eq!(walker.next().unwrap().unwrap(), (0, B256::ZERO));
983        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
984        assert_eq!(walker.next().unwrap().unwrap(), (2, B256::ZERO));
985        // next() returns None after walker is done
986        assert!(walker.next().is_none());
987
988        // (∞, ∞)
989        let mut walker = cursor.walk_range(..).unwrap();
990        assert_eq!(walker.next().unwrap().unwrap(), (0, B256::ZERO));
991        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
992        assert_eq!(walker.next().unwrap().unwrap(), (2, B256::ZERO));
993        assert_eq!(walker.next().unwrap().unwrap(), (3, B256::ZERO));
994        // next() returns None after walker is done
995        assert!(walker.next().is_none());
996    }
997
998    #[test]
999    fn db_cursor_walk_range_on_dup_table() {
1000        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1001
1002        let address0 = Address::ZERO;
1003        let address1 = Address::with_last_byte(1);
1004        let address2 = Address::with_last_byte(2);
1005
1006        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1007        tx.put::<AccountChangeSets>(0, AccountBeforeTx { address: address0, info: None })
1008            .expect(ERROR_PUT);
1009        tx.put::<AccountChangeSets>(0, AccountBeforeTx { address: address1, info: None })
1010            .expect(ERROR_PUT);
1011        tx.put::<AccountChangeSets>(0, AccountBeforeTx { address: address2, info: None })
1012            .expect(ERROR_PUT);
1013        tx.put::<AccountChangeSets>(1, AccountBeforeTx { address: address0, info: None })
1014            .expect(ERROR_PUT);
1015        tx.put::<AccountChangeSets>(1, AccountBeforeTx { address: address1, info: None })
1016            .expect(ERROR_PUT);
1017        tx.put::<AccountChangeSets>(1, AccountBeforeTx { address: address2, info: None })
1018            .expect(ERROR_PUT);
1019        tx.put::<AccountChangeSets>(2, AccountBeforeTx { address: address0, info: None }) // <- should not be returned by the walker
1020            .expect(ERROR_PUT);
1021        tx.commit().expect(ERROR_COMMIT);
1022
1023        let tx = db.tx().expect(ERROR_INIT_TX);
1024        let mut cursor = tx.cursor_read::<AccountChangeSets>().unwrap();
1025
1026        let entries = cursor.walk_range(..).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
1027        assert_eq!(entries.len(), 7);
1028
1029        let mut walker = cursor.walk_range(0..=1).unwrap();
1030        assert_eq!(
1031            walker.next().unwrap().unwrap(),
1032            (0, AccountBeforeTx { address: address0, info: None })
1033        );
1034        assert_eq!(
1035            walker.next().unwrap().unwrap(),
1036            (0, AccountBeforeTx { address: address1, info: None })
1037        );
1038        assert_eq!(
1039            walker.next().unwrap().unwrap(),
1040            (0, AccountBeforeTx { address: address2, info: None })
1041        );
1042        assert_eq!(
1043            walker.next().unwrap().unwrap(),
1044            (1, AccountBeforeTx { address: address0, info: None })
1045        );
1046        assert_eq!(
1047            walker.next().unwrap().unwrap(),
1048            (1, AccountBeforeTx { address: address1, info: None })
1049        );
1050        assert_eq!(
1051            walker.next().unwrap().unwrap(),
1052            (1, AccountBeforeTx { address: address2, info: None })
1053        );
1054        assert!(walker.next().is_none());
1055    }
1056
1057    #[expect(clippy::reversed_empty_ranges)]
1058    #[test]
1059    fn db_cursor_walk_range_invalid() {
1060        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1061
1062        // PUT (0, 0), (1, 0), (2, 0), (3, 0)
1063        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1064        vec![0, 1, 2, 3]
1065            .into_iter()
1066            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1067            .expect(ERROR_PUT);
1068        tx.commit().expect(ERROR_COMMIT);
1069
1070        let tx = db.tx().expect(ERROR_INIT_TX);
1071        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1072
1073        // start bound greater than end bound
1074        let mut res = cursor.walk_range(3..1).unwrap();
1075        assert!(res.next().is_none());
1076
1077        // start bound greater than end bound
1078        let mut res = cursor.walk_range(15..=2).unwrap();
1079        assert!(res.next().is_none());
1080
1081        // returning nothing
1082        let mut walker = cursor.walk_range(1..1).unwrap();
1083        assert!(walker.next().is_none());
1084    }
1085
1086    #[test]
1087    fn db_walker() {
1088        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1089
1090        // PUT (0, 0), (1, 0), (3, 0)
1091        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1092        vec![0, 1, 3]
1093            .into_iter()
1094            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1095            .expect(ERROR_PUT);
1096        tx.commit().expect(ERROR_COMMIT);
1097
1098        let tx = db.tx().expect(ERROR_INIT_TX);
1099        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1100
1101        let mut walker = Walker::new(&mut cursor, None);
1102
1103        assert_eq!(walker.next().unwrap().unwrap(), (0, B256::ZERO));
1104        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
1105        assert_eq!(walker.next().unwrap().unwrap(), (3, B256::ZERO));
1106        assert!(walker.next().is_none());
1107
1108        // transform to ReverseWalker
1109        let mut reverse_walker = walker.rev();
1110        assert_eq!(reverse_walker.next().unwrap().unwrap(), (3, B256::ZERO));
1111        assert_eq!(reverse_walker.next().unwrap().unwrap(), (1, B256::ZERO));
1112        assert_eq!(reverse_walker.next().unwrap().unwrap(), (0, B256::ZERO));
1113        assert!(reverse_walker.next().is_none());
1114    }
1115
1116    #[test]
1117    fn db_reverse_walker() {
1118        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1119
1120        // PUT (0, 0), (1, 0), (3, 0)
1121        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1122        vec![0, 1, 3]
1123            .into_iter()
1124            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1125            .expect(ERROR_PUT);
1126        tx.commit().expect(ERROR_COMMIT);
1127
1128        let tx = db.tx().expect(ERROR_INIT_TX);
1129        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1130
1131        let mut reverse_walker = ReverseWalker::new(&mut cursor, None);
1132
1133        assert_eq!(reverse_walker.next().unwrap().unwrap(), (3, B256::ZERO));
1134        assert_eq!(reverse_walker.next().unwrap().unwrap(), (1, B256::ZERO));
1135        assert_eq!(reverse_walker.next().unwrap().unwrap(), (0, B256::ZERO));
1136        assert!(reverse_walker.next().is_none());
1137
1138        // transform to Walker
1139        let mut walker = reverse_walker.forward();
1140        assert_eq!(walker.next().unwrap().unwrap(), (0, B256::ZERO));
1141        assert_eq!(walker.next().unwrap().unwrap(), (1, B256::ZERO));
1142        assert_eq!(walker.next().unwrap().unwrap(), (3, B256::ZERO));
1143        assert!(walker.next().is_none());
1144    }
1145
1146    #[test]
1147    fn db_walk_back() {
1148        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1149
1150        // PUT (0, 0), (1, 0), (3, 0)
1151        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1152        vec![0, 1, 3]
1153            .into_iter()
1154            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1155            .expect(ERROR_PUT);
1156        tx.commit().expect(ERROR_COMMIT);
1157
1158        let tx = db.tx().expect(ERROR_INIT_TX);
1159        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1160
1161        let mut reverse_walker = cursor.walk_back(Some(1)).unwrap();
1162        assert_eq!(reverse_walker.next().unwrap().unwrap(), (1, B256::ZERO));
1163        assert_eq!(reverse_walker.next().unwrap().unwrap(), (0, B256::ZERO));
1164        assert!(reverse_walker.next().is_none());
1165
1166        let mut reverse_walker = cursor.walk_back(Some(2)).unwrap();
1167        assert_eq!(reverse_walker.next().unwrap().unwrap(), (3, B256::ZERO));
1168        assert_eq!(reverse_walker.next().unwrap().unwrap(), (1, B256::ZERO));
1169        assert_eq!(reverse_walker.next().unwrap().unwrap(), (0, B256::ZERO));
1170        assert!(reverse_walker.next().is_none());
1171
1172        let mut reverse_walker = cursor.walk_back(Some(4)).unwrap();
1173        assert_eq!(reverse_walker.next().unwrap().unwrap(), (3, B256::ZERO));
1174        assert_eq!(reverse_walker.next().unwrap().unwrap(), (1, B256::ZERO));
1175        assert_eq!(reverse_walker.next().unwrap().unwrap(), (0, B256::ZERO));
1176        assert!(reverse_walker.next().is_none());
1177
1178        let mut reverse_walker = cursor.walk_back(None).unwrap();
1179        assert_eq!(reverse_walker.next().unwrap().unwrap(), (3, B256::ZERO));
1180        assert_eq!(reverse_walker.next().unwrap().unwrap(), (1, B256::ZERO));
1181        assert_eq!(reverse_walker.next().unwrap().unwrap(), (0, B256::ZERO));
1182        assert!(reverse_walker.next().is_none());
1183    }
1184
1185    #[test]
1186    fn db_cursor_seek_exact_or_previous_key() {
1187        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1188
1189        // PUT
1190        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1191        vec![0, 1, 3]
1192            .into_iter()
1193            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1194            .expect(ERROR_PUT);
1195        tx.commit().expect(ERROR_COMMIT);
1196
1197        // Cursor
1198        let missing_key = 2;
1199        let tx = db.tx().expect(ERROR_INIT_TX);
1200        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1201        assert!(cursor.current().unwrap().is_none());
1202
1203        // Seek exact
1204        let exact = cursor.seek_exact(missing_key).unwrap();
1205        assert_eq!(exact, None);
1206        assert!(cursor.current().unwrap().is_none());
1207    }
1208
1209    #[test]
1210    fn db_cursor_insert() {
1211        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1212
1213        // PUT
1214        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1215        vec![0, 1, 3, 4, 5]
1216            .into_iter()
1217            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1218            .expect(ERROR_PUT);
1219        tx.commit().expect(ERROR_COMMIT);
1220
1221        let key_to_insert = 2;
1222        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1223        let mut cursor = tx.cursor_write::<CanonicalHeaders>().unwrap();
1224
1225        // INSERT
1226        assert!(cursor.insert(key_to_insert, &B256::ZERO).is_ok());
1227        assert_eq!(cursor.current().unwrap(), Some((key_to_insert, B256::ZERO)));
1228        // INSERT (failure)
1229        assert!(matches!(
1230        cursor.insert(key_to_insert, &B256::ZERO).unwrap_err(),
1231        DatabaseError::Write(err) if *err == DatabaseWriteError {
1232            info: Error::KeyExist.into(),
1233            operation: DatabaseWriteOperation::CursorInsert,
1234            table_name: CanonicalHeaders::NAME,
1235            key: key_to_insert.encode().into(),
1236        }));
1237        assert_eq!(cursor.current().unwrap(), Some((key_to_insert, B256::ZERO)));
1238
1239        tx.commit().expect(ERROR_COMMIT);
1240
1241        // Confirm the result
1242        let tx = db.tx().expect(ERROR_INIT_TX);
1243        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1244        let res = cursor.walk(None).unwrap().map(|res| res.unwrap().0).collect::<Vec<_>>();
1245        assert_eq!(res, vec![0, 1, 2, 3, 4, 5]);
1246        tx.commit().expect(ERROR_COMMIT);
1247    }
1248
1249    #[test]
1250    fn db_cursor_insert_dup() {
1251        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1252        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1253
1254        let mut dup_cursor = tx.cursor_dup_write::<PlainStorageState>().unwrap();
1255        let key = Address::random();
1256        let subkey1 = B256::random();
1257        let subkey2 = B256::random();
1258
1259        let entry1 = StorageEntry { key: subkey1, value: U256::ZERO };
1260        assert!(dup_cursor.insert(key, &entry1).is_ok());
1261
1262        // Can't insert
1263        let entry2 = StorageEntry { key: subkey2, value: U256::ZERO };
1264        assert!(dup_cursor.insert(key, &entry2).is_err());
1265    }
1266
1267    #[test]
1268    fn db_cursor_delete_current_non_existent() {
1269        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1270        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1271
1272        let key1 = Address::with_last_byte(1);
1273        let key2 = Address::with_last_byte(2);
1274        let key3 = Address::with_last_byte(3);
1275        let mut cursor = tx.cursor_write::<PlainAccountState>().unwrap();
1276
1277        assert!(cursor.insert(key1, &Account::default()).is_ok());
1278        assert!(cursor.insert(key2, &Account::default()).is_ok());
1279        assert!(cursor.insert(key3, &Account::default()).is_ok());
1280
1281        // Seek & delete key2
1282        cursor.seek_exact(key2).unwrap();
1283        assert!(cursor.delete_current().is_ok());
1284        assert!(cursor.seek_exact(key2).unwrap().is_none());
1285
1286        // Seek & delete key2 again
1287        assert!(cursor.seek_exact(key2).unwrap().is_none());
1288        assert!(matches!(
1289            cursor.delete_current().unwrap_err(),
1290            DatabaseError::Delete(err) if err == reth_libmdbx::Error::NoData.into()));
1291        // Assert that key1 is still there
1292        assert_eq!(cursor.seek_exact(key1).unwrap(), Some((key1, Account::default())));
1293        // Assert that key3 is still there
1294        assert_eq!(cursor.seek_exact(key3).unwrap(), Some((key3, Account::default())));
1295    }
1296
1297    #[test]
1298    fn db_cursor_insert_wherever_cursor_is() {
1299        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1300        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1301
1302        // PUT
1303        vec![0, 1, 3, 5, 7, 9]
1304            .into_iter()
1305            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1306            .expect(ERROR_PUT);
1307        tx.commit().expect(ERROR_COMMIT);
1308
1309        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1310        let mut cursor = tx.cursor_write::<CanonicalHeaders>().unwrap();
1311
1312        // INSERT (cursor starts at last)
1313        cursor.last().unwrap();
1314        assert_eq!(cursor.current().unwrap(), Some((9, B256::ZERO)));
1315
1316        for pos in (2..=8).step_by(2) {
1317            assert!(cursor.insert(pos, &B256::ZERO).is_ok());
1318            assert_eq!(cursor.current().unwrap(), Some((pos, B256::ZERO)));
1319        }
1320        tx.commit().expect(ERROR_COMMIT);
1321
1322        // Confirm the result
1323        let tx = db.tx().expect(ERROR_INIT_TX);
1324        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1325        let res = cursor.walk(None).unwrap().map(|res| res.unwrap().0).collect::<Vec<_>>();
1326        assert_eq!(res, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1327        tx.commit().expect(ERROR_COMMIT);
1328    }
1329
1330    #[test]
1331    fn db_cursor_append() {
1332        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1333
1334        // PUT
1335        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1336        vec![0, 1, 2, 3, 4]
1337            .into_iter()
1338            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1339            .expect(ERROR_PUT);
1340        tx.commit().expect(ERROR_COMMIT);
1341
1342        // APPEND
1343        let key_to_append = 5;
1344        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1345        let mut cursor = tx.cursor_write::<CanonicalHeaders>().unwrap();
1346        assert!(cursor.append(key_to_append, &B256::ZERO).is_ok());
1347        tx.commit().expect(ERROR_COMMIT);
1348
1349        // Confirm the result
1350        let tx = db.tx().expect(ERROR_INIT_TX);
1351        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1352        let res = cursor.walk(None).unwrap().map(|res| res.unwrap().0).collect::<Vec<_>>();
1353        assert_eq!(res, vec![0, 1, 2, 3, 4, 5]);
1354        tx.commit().expect(ERROR_COMMIT);
1355    }
1356
1357    #[test]
1358    fn db_cursor_append_failure() {
1359        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1360
1361        // PUT
1362        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1363        vec![0, 1, 3, 4, 5]
1364            .into_iter()
1365            .try_for_each(|key| tx.put::<CanonicalHeaders>(key, B256::ZERO))
1366            .expect(ERROR_PUT);
1367        tx.commit().expect(ERROR_COMMIT);
1368
1369        // APPEND
1370        let key_to_append = 2;
1371        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1372        let mut cursor = tx.cursor_write::<CanonicalHeaders>().unwrap();
1373        assert!(matches!(
1374        cursor.append(key_to_append, &B256::ZERO).unwrap_err(),
1375        DatabaseError::Write(err) if *err == DatabaseWriteError {
1376            info: Error::KeyMismatch.into(),
1377            operation: DatabaseWriteOperation::CursorAppend,
1378            table_name: CanonicalHeaders::NAME,
1379            key: key_to_append.encode().into(),
1380        }));
1381        assert_eq!(cursor.current().unwrap(), Some((5, B256::ZERO))); // the end of table
1382        tx.commit().expect(ERROR_COMMIT);
1383
1384        // Confirm the result
1385        let tx = db.tx().expect(ERROR_INIT_TX);
1386        let mut cursor = tx.cursor_read::<CanonicalHeaders>().unwrap();
1387        let res = cursor.walk(None).unwrap().map(|res| res.unwrap().0).collect::<Vec<_>>();
1388        assert_eq!(res, vec![0, 1, 3, 4, 5]);
1389        tx.commit().expect(ERROR_COMMIT);
1390    }
1391
1392    #[test]
1393    fn db_cursor_upsert() {
1394        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1395        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1396
1397        let mut cursor = tx.cursor_write::<PlainAccountState>().unwrap();
1398        let key = Address::random();
1399
1400        let account = Account::default();
1401        cursor.upsert(key, &account).expect(ERROR_UPSERT);
1402        assert_eq!(cursor.seek_exact(key).unwrap(), Some((key, account)));
1403
1404        let account = Account { nonce: 1, ..Default::default() };
1405        cursor.upsert(key, &account).expect(ERROR_UPSERT);
1406        assert_eq!(cursor.seek_exact(key).unwrap(), Some((key, account)));
1407
1408        let account = Account { nonce: 2, ..Default::default() };
1409        cursor.upsert(key, &account).expect(ERROR_UPSERT);
1410        assert_eq!(cursor.seek_exact(key).unwrap(), Some((key, account)));
1411
1412        let mut dup_cursor = tx.cursor_dup_write::<PlainStorageState>().unwrap();
1413        let subkey = B256::random();
1414
1415        let value = U256::ONE;
1416        let entry1 = StorageEntry { key: subkey, value };
1417        dup_cursor.upsert(key, &entry1).expect(ERROR_UPSERT);
1418        assert_eq!(dup_cursor.seek_by_key_subkey(key, subkey).unwrap(), Some(entry1));
1419
1420        let value = U256::from(2);
1421        let entry2 = StorageEntry { key: subkey, value };
1422        dup_cursor.upsert(key, &entry2).expect(ERROR_UPSERT);
1423        assert_eq!(dup_cursor.seek_by_key_subkey(key, subkey).unwrap(), Some(entry1));
1424        assert_eq!(dup_cursor.next_dup_val().unwrap(), Some(entry2));
1425    }
1426
1427    #[test]
1428    fn db_cursor_dupsort_append() {
1429        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1430
1431        let transition_id = 2;
1432
1433        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1434        let mut cursor = tx.cursor_write::<AccountChangeSets>().unwrap();
1435        vec![0, 1, 3, 4, 5]
1436            .into_iter()
1437            .try_for_each(|val| {
1438                cursor.append(
1439                    transition_id,
1440                    &AccountBeforeTx { address: Address::with_last_byte(val), info: None },
1441                )
1442            })
1443            .expect(ERROR_APPEND);
1444        tx.commit().expect(ERROR_COMMIT);
1445
1446        // APPEND DUP & APPEND
1447        let subkey_to_append = 2;
1448        let tx = db.tx_mut().expect(ERROR_INIT_TX);
1449        let mut cursor = tx.cursor_write::<AccountChangeSets>().unwrap();
1450        assert!(matches!(
1451        cursor
1452            .append_dup(
1453                transition_id,
1454                AccountBeforeTx {
1455                    address: Address::with_last_byte(subkey_to_append),
1456                    info: None
1457                }
1458            )
1459            .unwrap_err(),
1460        DatabaseError::Write(err) if *err == DatabaseWriteError {
1461            info: Error::KeyMismatch.into(),
1462            operation: DatabaseWriteOperation::CursorAppendDup,
1463            table_name: AccountChangeSets::NAME,
1464            key: transition_id.encode().into(),
1465        }));
1466        assert!(matches!(
1467            cursor
1468                .append(
1469                    transition_id - 1,
1470                    &AccountBeforeTx {
1471                        address: Address::with_last_byte(subkey_to_append),
1472                        info: None
1473                    }
1474                )
1475                .unwrap_err(),
1476            DatabaseError::Write(err) if *err == DatabaseWriteError {
1477                info: Error::KeyMismatch.into(),
1478                operation: DatabaseWriteOperation::CursorAppend,
1479                table_name: AccountChangeSets::NAME,
1480                key: (transition_id - 1).encode().into(),
1481            }
1482        ));
1483        assert!(cursor
1484            .append(
1485                transition_id,
1486                &AccountBeforeTx { address: Address::with_last_byte(subkey_to_append), info: None }
1487            )
1488            .is_ok());
1489    }
1490
1491    #[test]
1492    #[allow(clippy::needless_update)]
1493    #[allow(clippy::clone_on_copy)]
1494    fn db_closure_put_get() {
1495        let tempdir = TempDir::new().expect(ERROR_TEMPDIR);
1496        let path = tempdir.path();
1497
1498        let value = Account {
1499            nonce: 18446744073709551615,
1500            bytecode_hash: Some(B256::random()),
1501            balance: U256::MAX,
1502            ..Default::default()
1503        };
1504        let key = address!("0xa2c122be93b0074270ebee7f6b7292c7deb45047");
1505
1506        {
1507            let env = create_test_db_with_path(DatabaseEnvKind::RW, path);
1508
1509            // PUT
1510            let result = env.update(|tx| {
1511                tx.put::<PlainAccountState>(key, value.clone()).expect(ERROR_PUT);
1512                200
1513            });
1514            assert_eq!(result.expect(ERROR_RETURN_VALUE), 200);
1515        }
1516
1517        let env = DatabaseEnv::open(
1518            path,
1519            DatabaseEnvKind::RO,
1520            DatabaseArguments::new(ClientVersion::default()),
1521        )
1522        .expect(ERROR_DB_CREATION);
1523
1524        // GET
1525        let result =
1526            env.view(|tx| tx.get::<PlainAccountState>(key).expect(ERROR_GET)).expect(ERROR_GET);
1527
1528        assert_eq!(result, Some(value))
1529    }
1530
1531    #[test]
1532    fn db_dup_sort() {
1533        let (_tempdir, env) = create_test_db(DatabaseEnvKind::RW);
1534        let key = address!("0xa2c122be93b0074270ebee7f6b7292c7deb45047");
1535
1536        // PUT (0,0)
1537        let value00 = StorageEntry::default();
1538        env.update(|tx| tx.put::<PlainStorageState>(key, value00).expect(ERROR_PUT)).unwrap();
1539
1540        // PUT (2,2)
1541        let value22 = StorageEntry { key: B256::with_last_byte(2), value: U256::from(2) };
1542        env.update(|tx| tx.put::<PlainStorageState>(key, value22).expect(ERROR_PUT)).unwrap();
1543
1544        // PUT (1,1)
1545        let value11 = StorageEntry { key: B256::with_last_byte(1), value: U256::ONE };
1546        env.update(|tx| tx.put::<PlainStorageState>(key, value11).expect(ERROR_PUT)).unwrap();
1547
1548        // Iterate with cursor
1549        {
1550            let tx = env.tx().expect(ERROR_INIT_TX);
1551            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1552
1553            // Notice that value11 and value22 have been ordered in the DB.
1554            assert_eq!(Some(value00), cursor.next_dup_val().unwrap());
1555            assert_eq!(Some(value11), cursor.next_dup_val().unwrap());
1556            assert_eq!(Some(value22), cursor.next_dup_val().unwrap());
1557        }
1558
1559        // Seek value with exact subkey
1560        {
1561            let tx = env.tx().expect(ERROR_INIT_TX);
1562            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1563            let mut walker = cursor.walk_dup(Some(key), Some(B256::with_last_byte(1))).unwrap();
1564            assert_eq!(
1565                (key, value11),
1566                walker
1567                    .next()
1568                    .expect("element should exist.")
1569                    .expect("should be able to retrieve it.")
1570            );
1571        }
1572    }
1573
1574    #[test]
1575    fn db_walk_dup_with_not_existing_key() {
1576        let (_tempdir, env) = create_test_db(DatabaseEnvKind::RW);
1577        let key = address!("0xa2c122be93b0074270ebee7f6b7292c7deb45047");
1578
1579        // PUT (0,0)
1580        let value00 = StorageEntry::default();
1581        env.update(|tx| tx.put::<PlainStorageState>(key, value00).expect(ERROR_PUT)).unwrap();
1582
1583        // PUT (2,2)
1584        let value22 = StorageEntry { key: B256::with_last_byte(2), value: U256::from(2) };
1585        env.update(|tx| tx.put::<PlainStorageState>(key, value22).expect(ERROR_PUT)).unwrap();
1586
1587        // PUT (1,1)
1588        let value11 = StorageEntry { key: B256::with_last_byte(1), value: U256::ONE };
1589        env.update(|tx| tx.put::<PlainStorageState>(key, value11).expect(ERROR_PUT)).unwrap();
1590
1591        // Try to walk_dup with not existing key should immediately return None
1592        {
1593            let tx = env.tx().expect(ERROR_INIT_TX);
1594            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1595            let not_existing_key = Address::ZERO;
1596            let mut walker = cursor.walk_dup(Some(not_existing_key), None).unwrap();
1597            assert!(walker.next().is_none());
1598        }
1599    }
1600
1601    #[test]
1602    fn db_iterate_over_all_dup_values() {
1603        let (_tempdir, env) = create_test_db(DatabaseEnvKind::RW);
1604        let key1 = Address::repeat_byte(0x11);
1605        let key2 = Address::repeat_byte(0x22);
1606
1607        // PUT key1 (0,0)
1608        let value00 = StorageEntry::default();
1609        env.update(|tx| tx.put::<PlainStorageState>(key1, value00).expect(ERROR_PUT)).unwrap();
1610
1611        // PUT key1 (1,1)
1612        let value11 = StorageEntry { key: B256::with_last_byte(1), value: U256::ONE };
1613        env.update(|tx| tx.put::<PlainStorageState>(key1, value11).expect(ERROR_PUT)).unwrap();
1614
1615        // PUT key2 (2,2)
1616        let value22 = StorageEntry { key: B256::with_last_byte(2), value: U256::from(2) };
1617        env.update(|tx| tx.put::<PlainStorageState>(key2, value22).expect(ERROR_PUT)).unwrap();
1618
1619        // Iterate with walk_dup
1620        {
1621            let tx = env.tx().expect(ERROR_INIT_TX);
1622            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1623            let mut walker = cursor.walk_dup(None, None).unwrap();
1624
1625            // Notice that value11 and value22 have been ordered in the DB.
1626            assert_eq!((key1, value00), walker.next().unwrap().unwrap());
1627            assert_eq!((key1, value11), walker.next().unwrap().unwrap());
1628            // NOTE: Dup cursor does NOT iterates on all values but only on duplicated values of the
1629            // same key. assert_eq!(Ok(Some(value22.clone())), walker.next());
1630            assert!(walker.next().is_none());
1631        }
1632
1633        // Iterate by using `walk`
1634        {
1635            let tx = env.tx().expect(ERROR_INIT_TX);
1636            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1637            let first = cursor.first().unwrap().unwrap();
1638            let mut walker = cursor.walk(Some(first.0)).unwrap();
1639            assert_eq!((key1, value00), walker.next().unwrap().unwrap());
1640            assert_eq!((key1, value11), walker.next().unwrap().unwrap());
1641            assert_eq!((key2, value22), walker.next().unwrap().unwrap());
1642        }
1643    }
1644
1645    #[test]
1646    fn dup_value_with_same_subkey() {
1647        let (_tempdir, env) = create_test_db(DatabaseEnvKind::RW);
1648        let key1 = Address::repeat_byte(0x11);
1649        let key2 = Address::repeat_byte(0x22);
1650
1651        // PUT key1 (0,1)
1652        let value01 = StorageEntry { key: B256::with_last_byte(0), value: U256::ONE };
1653        env.update(|tx| tx.put::<PlainStorageState>(key1, value01).expect(ERROR_PUT)).unwrap();
1654
1655        // PUT key1 (0,0)
1656        let value00 = StorageEntry::default();
1657        env.update(|tx| tx.put::<PlainStorageState>(key1, value00).expect(ERROR_PUT)).unwrap();
1658
1659        // PUT key2 (2,2)
1660        let value22 = StorageEntry { key: B256::with_last_byte(2), value: U256::from(2) };
1661        env.update(|tx| tx.put::<PlainStorageState>(key2, value22).expect(ERROR_PUT)).unwrap();
1662
1663        // Iterate with walk
1664        {
1665            let tx = env.tx().expect(ERROR_INIT_TX);
1666            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1667            let first = cursor.first().unwrap().unwrap();
1668            let mut walker = cursor.walk(Some(first.0)).unwrap();
1669
1670            // NOTE: Both values are present
1671            assert_eq!((key1, value00), walker.next().unwrap().unwrap());
1672            assert_eq!((key1, value01), walker.next().unwrap().unwrap());
1673            assert_eq!((key2, value22), walker.next().unwrap().unwrap());
1674        }
1675
1676        // seek_by_key_subkey
1677        {
1678            let tx = env.tx().expect(ERROR_INIT_TX);
1679            let mut cursor = tx.cursor_dup_read::<PlainStorageState>().unwrap();
1680
1681            // NOTE: There are two values with same SubKey but only first one is shown
1682            assert_eq!(value00, cursor.seek_by_key_subkey(key1, value00.key).unwrap().unwrap());
1683            // key1 but value is greater than the one in the DB
1684            assert_eq!(None, cursor.seek_by_key_subkey(key1, value22.key).unwrap());
1685        }
1686    }
1687
1688    #[test]
1689    fn db_sharded_key() {
1690        let (_tempdir, db) = create_test_db(DatabaseEnvKind::RW);
1691        let real_key = address!("0xa2c122be93b0074270ebee7f6b7292c7deb45047");
1692
1693        let shards = 5;
1694        for i in 1..=shards {
1695            let key = ShardedKey::new(real_key, if i == shards { u64::MAX } else { i * 100 });
1696            let list = IntegerList::new_pre_sorted([i * 100u64]);
1697
1698            db.update(|tx| tx.put::<AccountsHistory>(key.clone(), list.clone()).expect(""))
1699                .unwrap();
1700        }
1701
1702        // Seek value with non existing key.
1703        {
1704            let tx = db.tx().expect(ERROR_INIT_TX);
1705            let mut cursor = tx.cursor_read::<AccountsHistory>().unwrap();
1706
1707            // It will seek the one greater or equal to the query. Since we have `Address | 100`,
1708            // `Address | 200` in the database and we're querying `Address | 150` it will return us
1709            // `Address | 200`.
1710            let mut walker = cursor.walk(Some(ShardedKey::new(real_key, 150))).unwrap();
1711            let (key, list) = walker
1712                .next()
1713                .expect("element should exist.")
1714                .expect("should be able to retrieve it.");
1715
1716            assert_eq!(ShardedKey::new(real_key, 200), key);
1717            let list200 = IntegerList::new_pre_sorted([200u64]);
1718            assert_eq!(list200, list);
1719        }
1720        // Seek greatest index
1721        {
1722            let tx = db.tx().expect(ERROR_INIT_TX);
1723            let mut cursor = tx.cursor_read::<AccountsHistory>().unwrap();
1724
1725            // It will seek the MAX value of transition index and try to use prev to get first
1726            // biggers.
1727            let _unknown = cursor.seek_exact(ShardedKey::new(real_key, u64::MAX)).unwrap();
1728            let (key, list) = cursor
1729                .prev()
1730                .expect("element should exist.")
1731                .expect("should be able to retrieve it.");
1732
1733            assert_eq!(ShardedKey::new(real_key, 400), key);
1734            let list400 = IntegerList::new_pre_sorted([400u64]);
1735            assert_eq!(list400, list);
1736        }
1737    }
1738
1739    #[test]
1740    fn db_tracks_table_dbis() {
1741        #[derive(Debug)]
1742        struct CustomTable;
1743
1744        impl Table for CustomTable {
1745            const NAME: &'static str = "CustomTable";
1746            const DUPSORT: bool = false;
1747
1748            type Key = u64;
1749            type Value = u64;
1750        }
1751
1752        impl TableInfo for CustomTable {
1753            fn name(&self) -> &'static str {
1754                Self::NAME
1755            }
1756
1757            fn is_dupsort(&self) -> bool {
1758                Self::DUPSORT
1759            }
1760        }
1761
1762        impl TableSet for CustomTable {
1763            fn tables() -> Box<dyn Iterator<Item = Box<dyn TableInfo>>> {
1764                Box::new(std::iter::once(Box::new(Self) as Box<dyn TableInfo>))
1765            }
1766        }
1767
1768        let (_tempdir, mut env) = create_test_db(DatabaseEnvKind::RW);
1769        env.create_and_track_tables_for::<CustomTable>().expect(ERROR_TABLE_CREATION);
1770        let env = env.with_metrics();
1771
1772        let tx = env.tx_mut().expect(ERROR_INIT_TX);
1773        for table in Tables::ALL {
1774            let dbi = tables_to_generic!(table, |T| tx.get_dbi::<T>()).unwrap();
1775            assert_eq!(env.dbis.get(Some(*table), table.name()), Some(dbi), "{table}");
1776            assert_eq!(tx.inner().open_db(Some(table.name())).unwrap().dbi(), dbi, "{table}");
1777        }
1778
1779        let dbi = tx.get_dbi::<CustomTable>().unwrap();
1780        assert_eq!(env.dbis.get(None, CustomTable::NAME), Some(dbi));
1781        assert_eq!(tx.inner().open_db(Some(CustomTable::NAME)).unwrap().dbi(), dbi);
1782        tx.put::<CustomTable>(1, 2).expect(ERROR_PUT);
1783        tx.cursor_write::<CustomTable>().unwrap().upsert(3, &4).expect(ERROR_UPSERT);
1784        tx.commit().expect(ERROR_COMMIT);
1785
1786        let tx = env.tx().expect(ERROR_INIT_TX);
1787        assert_eq!(tx.get::<CustomTable>(1).expect(ERROR_GET), Some(2));
1788        assert_eq!(tx.get::<CustomTable>(3).expect(ERROR_GET), Some(4));
1789    }
1790
1791    #[test]
1792    fn non_durable_sync_period() {
1793        let sync_period = |sync_mode| {
1794            let tempdir = tempfile::TempDir::new().expect(ERROR_TEMPDIR);
1795            let env = DatabaseEnv::open(
1796                tempdir.path(),
1797                DatabaseEnvKind::RW,
1798                DatabaseArguments::test().with_sync_mode(Some(sync_mode)),
1799            )
1800            .expect(ERROR_DB_CREATION);
1801            let mut period = 0;
1802            // SAFETY: the pointer is only used within the closure, while `env` is open.
1803            let rc = env.with_raw_env_ptr(|env| unsafe {
1804                ffi::mdbx_env_get_option(env, ffi::MDBX_opt_sync_period, &raw mut period)
1805            });
1806            assert_eq!(rc, ffi::MDBX_SUCCESS);
1807            // MDBX reports the period in 1/65536 of a second.
1808            Duration::from_secs_f64(period as f64 / 65536.0)
1809        };
1810
1811        assert_eq!(sync_period(SyncMode::Durable), Duration::ZERO);
1812        assert_eq!(sync_period(SyncMode::SafeNoSync), NON_DURABLE_SYNC_PERIOD);
1813        assert_eq!(sync_period(SyncMode::UtterlyNoSync), NON_DURABLE_SYNC_PERIOD);
1814    }
1815}