database: implement tables and types (#91)

* table: set `Key` and `Value` to `?Sized`

* types: add `SCHEMA.md` types

* tables: add `SCHEMA.md` tables

* redb: let value be `?Sized`

* types: add casting doc tests and `pub`

* backend: create all tables in `Env::open()`

* backend: open all tables in `tests.rs`

* remove `TestType{1,2}`, use `Output` in tests

* backend: add tests for all tables/keys/values

* add `NumOutputs` table

* small doc fixes

* tables: doc

* types: add `PreRctOutputId`

* types: `BlockInfoV2 { cumulative_rct_outs: u32 -> u64 }`
This commit is contained in:
hinto-janai 2024-03-17 21:15:56 -04:00 committed by GitHub
parent 729b0fb0cf
commit de931f8630
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 657 additions and 138 deletions

View file

@ -171,20 +171,14 @@ impl Env for ConcreteEnv {
// <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L1324> // <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L1324>
// `heed` creates the database if it didn't exist. // `heed` creates the database if it didn't exist.
// <https://docs.rs/heed/0.20.0-alpha.9/src/heed/env.rs.html#223-229> // <https://docs.rs/heed/0.20.0-alpha.9/src/heed/env.rs.html#223-229>
use crate::tables::{TestTable, TestTable2};
let mut tx_rw = env.write_txn()?;
// FIXME:
// These wonderful fully qualified trait types are brought
// to you by `tower::discover::Discover>::Key` collisions.
// TODO: Create all tables when schema is done.
/// Function that creates the tables based off the passed `T: Table`. /// Function that creates the tables based off the passed `T: Table`.
fn create_table<T: Table>( fn create_table<T: Table>(
env: &heed::Env, env: &heed::Env,
tx_rw: &mut heed::RwTxn<'_>, tx_rw: &mut heed::RwTxn<'_>,
) -> Result<(), InitError> { ) -> Result<(), InitError> {
println!("create_table(): {}", T::NAME); // TODO: use tracing.
DatabaseOpenOptions::new(env) DatabaseOpenOptions::new(env)
.name(<T as Table>::NAME) .name(<T as Table>::NAME)
.types::<StorableHeed<<T as Table>::Key>, StorableHeed<<T as Table>::Value>>() .types::<StorableHeed<<T as Table>::Key>, StorableHeed<<T as Table>::Value>>()
@ -192,8 +186,28 @@ impl Env for ConcreteEnv {
Ok(()) Ok(())
} }
create_table::<TestTable>(&env, &mut tx_rw)?; use crate::tables::{
create_table::<TestTable2>(&env, &mut tx_rw)?; BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages,
NumOutputs, Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs,
TxHeights, TxIds, TxUnlockTime,
};
let mut tx_rw = env.write_txn()?;
create_table::<BlockBlobs>(&env, &mut tx_rw)?;
create_table::<BlockHeights>(&env, &mut tx_rw)?;
create_table::<BlockInfoV1s>(&env, &mut tx_rw)?;
create_table::<BlockInfoV2s>(&env, &mut tx_rw)?;
create_table::<BlockInfoV3s>(&env, &mut tx_rw)?;
create_table::<KeyImages>(&env, &mut tx_rw)?;
create_table::<NumOutputs>(&env, &mut tx_rw)?;
create_table::<Outputs>(&env, &mut tx_rw)?;
create_table::<PrunableHashes>(&env, &mut tx_rw)?;
create_table::<PrunableTxBlobs>(&env, &mut tx_rw)?;
create_table::<PrunedTxBlobs>(&env, &mut tx_rw)?;
create_table::<RctOutputs>(&env, &mut tx_rw)?;
create_table::<TxHeights>(&env, &mut tx_rw)?;
create_table::<TxIds>(&env, &mut tx_rw)?;
create_table::<TxUnlockTime>(&env, &mut tx_rw)?;
// TODO: Set dupsort and comparison functions for certain tables // TODO: Set dupsort and comparison functions for certain tables
// <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L1324> // <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L1324>

View file

@ -83,17 +83,11 @@ impl Env for ConcreteEnv {
// Create all database tables. // Create all database tables.
// `redb` creates tables if they don't exist. // `redb` creates tables if they don't exist.
// <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_table> // <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_table>
use crate::tables::{TestTable, TestTable2};
let tx_rw = env.begin_write()?;
// FIXME:
// These wonderful fully qualified trait types are brought
// to you by `tower::discover::Discover>::Key` collisions.
// TODO: Create all tables when schema is done.
/// Function that creates the tables based off the passed `T: Table`. /// Function that creates the tables based off the passed `T: Table`.
fn create_table<T: Table>(tx_rw: &redb::WriteTransaction<'_>) -> Result<(), InitError> { fn create_table<T: Table>(tx_rw: &redb::WriteTransaction<'_>) -> Result<(), InitError> {
println!("create_table(): {}", T::NAME); // TODO: use tracing.
let table: redb::TableDefinition< let table: redb::TableDefinition<
'static, 'static,
StorableRedb<<T as Table>::Key>, StorableRedb<<T as Table>::Key>,
@ -105,8 +99,28 @@ impl Env for ConcreteEnv {
Ok(()) Ok(())
} }
create_table::<TestTable>(&tx_rw)?; use crate::tables::{
create_table::<TestTable2>(&tx_rw)?; BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages,
NumOutputs, Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs,
TxHeights, TxIds, TxUnlockTime,
};
let tx_rw = env.begin_write()?;
create_table::<BlockBlobs>(&tx_rw)?;
create_table::<BlockHeights>(&tx_rw)?;
create_table::<BlockInfoV1s>(&tx_rw)?;
create_table::<BlockInfoV2s>(&tx_rw)?;
create_table::<BlockInfoV3s>(&tx_rw)?;
create_table::<KeyImages>(&tx_rw)?;
create_table::<NumOutputs>(&tx_rw)?;
create_table::<Outputs>(&tx_rw)?;
create_table::<PrunableHashes>(&tx_rw)?;
create_table::<PrunableTxBlobs>(&tx_rw)?;
create_table::<PrunedTxBlobs>(&tx_rw)?;
create_table::<RctOutputs>(&tx_rw)?;
create_table::<TxHeights>(&tx_rw)?;
create_table::<TxIds>(&tx_rw)?;
create_table::<TxUnlockTime>(&tx_rw)?;
tx_rw.commit()?; tx_rw.commit()?;
// Check for file integrity. // Check for file integrity.

View file

@ -5,7 +5,7 @@ use std::{any::Any, borrow::Cow, cmp::Ordering, fmt::Debug, marker::PhantomData}
use redb::{RedbKey, RedbValue, TypeName}; use redb::{RedbKey, RedbValue, TypeName};
use crate::{key::Key, storable::Storable}; use crate::{key::Key, storable::Storable, value_guard::ValueGuard};
//---------------------------------------------------------------------------------------------------- StorableRedb //---------------------------------------------------------------------------------------------------- StorableRedb
/// The glue structs that implements `redb`'s (de)serialization /// The glue structs that implements `redb`'s (de)serialization
@ -17,14 +17,14 @@ pub(super) struct StorableRedb<T>(PhantomData<T>)
where where
T: Storable + ?Sized; T: Storable + ?Sized;
impl<T: Storable> crate::value_guard::ValueGuard<T> for redb::AccessGuard<'_, StorableRedb<T>> { impl<T: Storable + ?Sized> ValueGuard<T> for redb::AccessGuard<'_, StorableRedb<T>> {
#[inline] #[inline]
fn unguard(&self) -> Cow<'_, T> { fn unguard(&self) -> Cow<'_, T> {
self.value() self.value()
} }
} }
impl<T: Storable> crate::value_guard::ValueGuard<T> for &redb::AccessGuard<'_, StorableRedb<T>> { impl<T: Storable + ?Sized> ValueGuard<T> for &redb::AccessGuard<'_, StorableRedb<T>> {
#[inline] #[inline]
fn unguard(&self) -> Cow<'_, T> { fn unguard(&self) -> Cow<'_, T> {
self.value() self.value()
@ -35,7 +35,7 @@ impl<T: Storable> crate::value_guard::ValueGuard<T> for &redb::AccessGuard<'_, S
// If `Key` is also implemented, this can act as a `RedbKey`. // If `Key` is also implemented, this can act as a `RedbKey`.
impl<T> RedbKey for StorableRedb<T> impl<T> RedbKey for StorableRedb<T>
where where
T: Key, T: Key + ?Sized,
{ {
#[inline] #[inline]
fn compare(left: &[u8], right: &[u8]) -> Ordering { fn compare(left: &[u8], right: &[u8]) -> Ordering {

View file

@ -13,6 +13,8 @@
//! //!
//! `redb`, and it only must be enabled for it to be tested. //! `redb`, and it only must be enabled for it to be tested.
#![allow(clippy::items_after_statements, clippy::significant_drop_tightening)]
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
@ -23,9 +25,17 @@ use crate::{
error::{InitError, RuntimeError}, error::{InitError, RuntimeError},
resize::ResizeAlgorithm, resize::ResizeAlgorithm,
table::Table, table::Table,
tables::{TestTable, TestTable2}, tables::{
BlockBlobs, BlockHeights, BlockInfoV1s, BlockInfoV2s, BlockInfoV3s, KeyImages, NumOutputs,
Outputs, PrunableHashes, PrunableTxBlobs, PrunedTxBlobs, RctOutputs, TxHeights, TxIds,
TxUnlockTime,
},
transaction::{TxRo, TxRw}, transaction::{TxRo, TxRw},
types::TestType, types::{
Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfoV1,
BlockInfoV2, BlockInfoV3, KeyImage, Output, PreRctOutputId, PrunableBlob, PrunableHash,
PrunedBlob, RctOutput, TxHash, TxId, UnlockTime,
},
value_guard::ValueGuard, value_guard::ValueGuard,
ConcreteEnv, ConcreteEnv,
}; };
@ -63,7 +73,6 @@ fn tx() {
/// Open (and verify) that all database tables /// Open (and verify) that all database tables
/// exist already after calling [`Env::open`]. /// exist already after calling [`Env::open`].
#[test] #[test]
#[allow(clippy::items_after_statements, clippy::significant_drop_tightening)]
fn open_db() { fn open_db() {
let (env, _tempdir) = tmp_concrete_env(); let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner(); let env_inner = env.env_inner();
@ -72,13 +81,39 @@ fn open_db() {
// Open all tables in read-only mode. // Open all tables in read-only mode.
// This should be updated when tables are modified. // This should be updated when tables are modified.
env_inner.open_db_ro::<TestTable>(&tx_ro).unwrap(); env_inner.open_db_ro::<BlockBlobs>(&tx_ro).unwrap();
env_inner.open_db_ro::<TestTable2>(&tx_ro).unwrap(); env_inner.open_db_ro::<BlockHeights>(&tx_ro).unwrap();
env_inner.open_db_ro::<BlockInfoV1s>(&tx_ro).unwrap();
env_inner.open_db_ro::<BlockInfoV2s>(&tx_ro).unwrap();
env_inner.open_db_ro::<BlockInfoV3s>(&tx_ro).unwrap();
env_inner.open_db_ro::<KeyImages>(&tx_ro).unwrap();
env_inner.open_db_ro::<NumOutputs>(&tx_ro).unwrap();
env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
env_inner.open_db_ro::<PrunableHashes>(&tx_ro).unwrap();
env_inner.open_db_ro::<PrunableTxBlobs>(&tx_ro).unwrap();
env_inner.open_db_ro::<PrunedTxBlobs>(&tx_ro).unwrap();
env_inner.open_db_ro::<RctOutputs>(&tx_ro).unwrap();
env_inner.open_db_ro::<TxHeights>(&tx_ro).unwrap();
env_inner.open_db_ro::<TxIds>(&tx_ro).unwrap();
env_inner.open_db_ro::<TxUnlockTime>(&tx_ro).unwrap();
TxRo::commit(tx_ro).unwrap(); TxRo::commit(tx_ro).unwrap();
// Open all tables in read/write mode. // Open all tables in read/write mode.
env_inner.open_db_rw::<TestTable>(&mut tx_rw).unwrap(); env_inner.open_db_rw::<BlockBlobs>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<TestTable2>(&mut tx_rw).unwrap(); env_inner.open_db_rw::<BlockHeights>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<BlockInfoV1s>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<BlockInfoV2s>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<BlockInfoV3s>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<KeyImages>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<NumOutputs>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<Outputs>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<PrunableHashes>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<PrunableTxBlobs>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<PrunedTxBlobs>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<RctOutputs>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<TxHeights>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<TxIds>(&mut tx_rw).unwrap();
env_inner.open_db_rw::<TxUnlockTime>(&mut tx_rw).unwrap();
TxRw::commit(tx_rw).unwrap(); TxRw::commit(tx_rw).unwrap();
} }
@ -113,6 +148,7 @@ fn non_manual_resize_1() {
env.resize_map(None); env.resize_map(None);
} }
} }
#[test] #[test]
#[should_panic = "unreachable"] #[should_panic = "unreachable"]
fn non_manual_resize_2() { fn non_manual_resize_2() {
@ -126,40 +162,48 @@ fn non_manual_resize_2() {
/// Test all `DatabaseR{o,w}` operations. /// Test all `DatabaseR{o,w}` operations.
#[test] #[test]
#[allow(
clippy::items_after_statements,
clippy::significant_drop_tightening,
clippy::used_underscore_binding
)]
fn db_read_write() { fn db_read_write() {
let (env, _tempdir) = tmp_concrete_env(); let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner(); let env_inner = env.env_inner();
let mut tx_rw = env_inner.tx_rw().unwrap(); let mut tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<TestTable>(&mut tx_rw).unwrap(); let mut table = env_inner.open_db_rw::<Outputs>(&mut tx_rw).unwrap();
const KEY: i64 = 0_i64; /// The (1st) key.
const VALUE: TestType = TestType { const KEY: PreRctOutputId = PreRctOutputId {
u: 1, amount: 1,
b: 255, amount_index: 123,
_pad: [0; 7], };
/// The expected value.
const VALUE: Output = Output {
key: [35; 32],
height: 45_761_798,
output_flags: 0,
tx_idx: 2_353_487,
}; };
/// Assert a passed `Output` is equal to the const value.
fn assert_eq(output: &Output) {
assert_eq!(output, &VALUE);
// Make sure all field accesses are aligned.
assert_eq!(output.key, VALUE.key);
assert_eq!(output.height, VALUE.height);
assert_eq!(output.output_flags, VALUE.output_flags);
assert_eq!(output.tx_idx, VALUE.tx_idx);
}
// Insert `0..100` keys. // Insert `0..100` keys.
let mut key = KEY;
for i in 0..100 { for i in 0..100 {
table.put(&(KEY + i), &VALUE).unwrap(); table.put(&key, &VALUE).unwrap();
key.amount += 1;
} }
// Assert the 1st key is there. // Assert the 1st key is there.
{ {
let guard = table.get(&KEY).unwrap(); let guard = table.get(&KEY).unwrap();
let cow: Cow<'_, TestType> = guard.unguard(); let cow: Cow<'_, Output> = guard.unguard();
let value: &TestType = cow.as_ref(); let value: &Output = cow.as_ref();
assert_eq(value);
// Make sure all field accesses are aligned.
assert_eq!(value, &VALUE);
assert_eq!(value.u, VALUE.u);
assert_eq!(value.b, VALUE.b);
assert_eq!(value._pad, VALUE._pad);
} }
// Assert the whole range is there. // Assert the whole range is there.
@ -168,21 +212,18 @@ fn db_read_write() {
let mut i = 0; let mut i = 0;
for result in range { for result in range {
let guard = result.unwrap(); let guard = result.unwrap();
let cow: Cow<'_, TestType> = guard.unguard(); let cow: Cow<'_, Output> = guard.unguard();
let value: &TestType = cow.as_ref(); let value: &Output = cow.as_ref();
assert_eq(value);
assert_eq!(value, &VALUE);
assert_eq!(value.u, VALUE.u);
assert_eq!(value.b, VALUE.b);
assert_eq!(value._pad, VALUE._pad);
i += 1; i += 1;
} }
assert_eq!(i, 100); assert_eq!(i, 100);
} }
// Assert `get_range()` works. // Assert `get_range()` works.
let range = KEY..(KEY + 100); let mut key = KEY;
key.amount += 100;
let range = KEY..key;
assert_eq!(100, table.get_range(&range).unwrap().count()); assert_eq!(100, table.get_range(&range).unwrap().count());
// Assert deleting works. // Assert deleting works.
@ -190,3 +231,169 @@ fn db_read_write() {
let value = table.get(&KEY); let value = table.get(&KEY);
assert!(matches!(value, Err(RuntimeError::KeyNotFound))); assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
} }
//---------------------------------------------------------------------------------------------------- Table Tests
/// Test multiple tables and their key + values.
///
/// Each one of these tests:
/// - Opens a specific table
/// - Inserts a key + value
/// - Retrieves the key + value
/// - Asserts it is the same
/// - Tests `get_range()`
/// - Tests `delete()`
macro_rules! test_tables {
($(
$table:ident, // Table type
$key_type:ty => // Key (type)
$value_type:ty, // Value (type)
$key:expr => // Key (the value)
$value:expr, // Value (the value)
)* $(,)?) => { paste::paste! { $(
// Test function's name is the table type in `snake_case`.
#[test]
fn [<$table:snake>]() {
// Open the database env and table.
let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner();
let mut tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<$table>(&mut tx_rw).unwrap();
/// The expected key.
const KEY: $key_type = $key;
/// The expected value.
const VALUE: &$value_type = &$value;
/// Assert a passed value is equal to the const value.
fn assert_eq(value: &$value_type) {
assert_eq!(value, VALUE);
}
// Insert the key.
table.put(&KEY, VALUE).unwrap();
// Assert key is there.
{
let guard = table.get(&KEY).unwrap();
let cow: Cow<'_, $value_type> = guard.unguard();
let value: &$value_type = cow.as_ref();
assert_eq(value);
}
// Assert `get_range()` works.
{
let range = KEY..;
assert_eq!(1, table.get_range(&range).unwrap().count());
let mut iter = table.get_range(&range).unwrap();
let guard = iter.next().unwrap().unwrap();
let cow = guard.unguard();
let value = cow.as_ref();
assert_eq(value);
}
// Assert deleting works.
table.delete(&KEY).unwrap();
let value = table.get(&KEY);
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
}
)*}};
}
// Notes:
// - Keep this sorted A-Z (by table name)
test_tables! {
BlockBlobs, // Table type
BlockHeight => BlockBlob, // Key type => Value type
123 => [1,2,3,4,5,6,7,8].as_slice(), // Actual key => Actual value
BlockHeights,
BlockHash => BlockHeight,
[32; 32] => 123,
BlockInfoV1s,
BlockHeight => BlockInfoV1,
123 => BlockInfoV1 {
timestamp: 1,
total_generated_coins: 123,
weight: 321,
cumulative_difficulty: 111,
block_hash: [54; 32],
},
BlockInfoV2s,
BlockHeight => BlockInfoV2,
123 => BlockInfoV2 {
timestamp: 1,
total_generated_coins: 123,
weight: 321,
cumulative_difficulty: 111,
cumulative_rct_outs: 2389,
block_hash: [54; 32],
},
BlockInfoV3s,
BlockHeight => BlockInfoV3,
123 => BlockInfoV3 {
timestamp: 1,
total_generated_coins: 123,
weight: 321,
cumulative_difficulty_low: 111,
cumulative_difficulty_high: 112,
block_hash: [54; 32],
cumulative_rct_outs: 2389,
long_term_weight: 2389,
},
KeyImages,
KeyImage => (),
[32; 32] => (),
NumOutputs,
Amount => AmountIndex,
123 => 123,
TxIds,
TxHash => TxId,
[32; 32] => 123,
TxHeights,
TxId => BlockHeight,
123 => 123,
TxUnlockTime,
TxId => UnlockTime,
123 => 123,
Outputs,
PreRctOutputId => Output,
PreRctOutputId {
amount: 1,
amount_index: 2,
} => Output {
key: [1; 32],
height: 1,
output_flags: 0,
tx_idx: 3,
},
PrunedTxBlobs,
TxId => PrunedBlob,
123 => [1,2,3,4,5,6,7,8].as_slice(),
PrunableTxBlobs,
TxId => PrunableBlob,
123 => [1,2,3,4,5,6,7,8].as_slice(),
PrunableHashes,
TxId => PrunableHash,
123 => [32; 32],
RctOutputs,
AmountIndex => RctOutput,
123 => RctOutput {
key: [1; 32],
height: 1,
output_flags: 0,
tx_idx: 3,
commitment: [3; 32],
},
}

View file

@ -115,6 +115,13 @@ impl<T: Key + Pod, const N: usize> Key for [T; N] {
type Primary = Self; type Primary = Self;
} }
// TODO: temporary for now for `Key` bound, remove later.
impl Key for crate::types::PreRctOutputId {
const DUPLICATE: bool = false;
const CUSTOM_COMPARE: bool = false;
type Primary = Self;
}
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View file

@ -19,10 +19,10 @@ pub trait Table: crate::tables::private::Sealed + 'static {
const NAME: &'static str; const NAME: &'static str;
/// Primary key type. /// Primary key type.
type Key: Key + 'static; type Key: Key + ?Sized + 'static;
/// Value type. /// Value type.
type Value: Storable + 'static; type Value: Storable + ?Sized + 'static;
} }
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests

View file

@ -5,7 +5,11 @@
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use crate::{ use crate::{
table::Table, table::Table,
types::{TestType, TestType2}, types::{
Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfoV1,
BlockInfoV2, BlockInfoV3, KeyImage, Output, PreRctOutputId, PrunableBlob, PrunableHash,
PrunedBlob, RctOutput, TxHash, TxId, UnlockTime,
},
}; };
//---------------------------------------------------------------------------------------------------- Tables //---------------------------------------------------------------------------------------------------- Tables
@ -35,7 +39,6 @@ macro_rules! tables {
$( $(
$(#[$attr:meta])* // Documentation and any `derive`'s. $(#[$attr:meta])* // Documentation and any `derive`'s.
$table:ident, // The table name + doubles as the table struct name. $table:ident, // The table name + doubles as the table struct name.
$size:literal, // Are the table's values all the same size?
$key:ty => // Key type. $key:ty => // Key type.
$value:ty // Value type. $value:ty // Value type.
),* $(,)? ),* $(,)?
@ -56,7 +59,7 @@ macro_rules! tables {
)] )]
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy,Clone,Debug,PartialEq,PartialOrd,Eq,Ord,Hash)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct [<$table:camel>]; pub struct [<$table:camel>];
// Implement the `Sealed` in this file. // Implement the `Sealed` in this file.
@ -74,16 +77,71 @@ macro_rules! tables {
} }
//---------------------------------------------------------------------------------------------------- Tables //---------------------------------------------------------------------------------------------------- Tables
// Notes:
// - Keep this sorted A-Z (by table name)
// - Tables are defined in plural to avoid name conflicts with types
// - If adding/changing a table, also edit the tests in `src/backend/tests.rs`
// and edit `Env::open` to make sure it creates the table
tables! { tables! {
/// Test documentation. /// TODO
TestTable, BlockBlobs,
true, BlockHeight => BlockBlob,
i64 => TestType,
/// Test documentation 2. /// TODO
TestTable2, BlockHeights,
true, BlockHash => BlockHeight,
u8 => TestType2,
/// TODO
BlockInfoV1s,
BlockHeight => BlockInfoV1,
/// TODO
BlockInfoV2s,
BlockHeight => BlockInfoV2,
/// TODO
BlockInfoV3s,
BlockHeight => BlockInfoV3,
/// TODO
KeyImages,
KeyImage => (),
/// TODO
NumOutputs,
Amount => AmountIndex,
/// TODO
PrunedTxBlobs,
TxId => PrunedBlob,
/// TODO
Outputs,
PreRctOutputId => Output,
/// TODO
PrunableTxBlobs,
TxId => PrunableBlob,
/// TODO
PrunableHashes,
TxId => PrunableHash,
/// TODO
RctOutputs,
AmountIndex => RctOutput,
/// TODO
TxIds,
TxHash => TxId,
/// TODO
TxHeights,
TxId => BlockHeight,
/// TODO
TxUnlockTime,
TxId => UnlockTime,
} }
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests

View file

@ -3,10 +3,6 @@
//! This module contains all types used by the database tables. //! This module contains all types used by the database tables.
//! //!
//! TODO: Add schema here or a link to it. //! TODO: Add schema here or a link to it.
//!
//! ## `*Bits`
//! The non-documented items ending in `Bits` can be ignored,
//! they are helper structs generated by `bytemuck`.
/* /*
* <============================================> VERY BIG SCARY SAFETY MESSAGE <============================================> * <============================================> VERY BIG SCARY SAFETY MESSAGE <============================================>
@ -49,97 +45,320 @@ use bytemuck::{AnyBitPattern, NoUninit, Pod, Zeroable};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
//---------------------------------------------------------------------------------------------------- TestType //---------------------------------------------------------------------------------------------------- Aliases
/// TEST // TODO: document these, why they exist, and their purpose.
//
// Notes:
// - Keep this sorted A-Z
/// TODO
pub type Amount = u64;
/// TODO
pub type AmountIndex = u64;
/// TODO
pub type AmountIndices = [AmountIndex];
/// TODO
pub type BlockBlob = [u8];
/// TODO
pub type BlockHash = [u8; 32];
/// TODO
pub type BlockHeight = u64;
/// TODO
pub type KeyImage = [u8; 32];
/// TODO
pub type PrunedBlob = [u8];
/// TODO
pub type PrunableBlob = [u8];
/// TODO
pub type PrunableHash = [u8; 32];
/// TODO
pub type TxId = u64;
/// TODO
pub type TxHash = [u8; 32];
/// TODO
pub type UnlockTime = u64;
//---------------------------------------------------------------------------------------------------- BlockInfoV1
/// TODO
/// ///
/// ```rust /// ```rust
/// # use std::borrow::*;
/// # use cuprate_database::{*, types::*}; /// # use cuprate_database::{*, types::*};
/// // Assert bytemuck is correct.
/// let a = TestType { u: 1, b: 255, _pad: [0; 7] }; // original struct
/// let b = bytemuck::must_cast::<TestType, [u8; 16]>(a); // cast into bytes
/// let c = bytemuck::checked::cast::<[u8; 16], TestType>(b); // cast back into struct
/// assert_eq!(a, c);
/// assert_eq!(c.u, 1);
/// assert_eq!(c.b, 255);
/// assert_eq!(c._pad, [0; 7]);
///
/// // Assert Storable is correct. /// // Assert Storable is correct.
/// let b2 = Storable::as_bytes(&a); /// let a = PreRctOutputId {
/// let c2: &TestType = Storable::from_bytes(b2); /// amount: 1,
/// assert_eq!(a, *c2); /// amount_index: 123,
/// assert_eq!(b, b2); /// };
/// assert_eq!(c, *c2); /// let b = Storable::as_bytes(&a);
/// assert_eq!(c2.u, 1); /// let c: &PreRctOutputId = Storable::from_bytes(b);
/// assert_eq!(c2.b, 255); /// let c2: Cow<'_, PreRctOutputId> = Storable::from_bytes_unaligned(b);
/// assert_eq!(c2._pad, [0; 7]); /// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// ``` /// ```
/// ///
/// # Size & Alignment /// # Size & Alignment
/// ```rust /// ```rust
/// # use cuprate_database::types::*; /// # use cuprate_database::types::*;
/// # use std::mem::*; /// # use std::mem::*;
/// assert_eq!(size_of::<TestType>(), 16); /// assert_eq!(size_of::<PreRctOutputId>(), 16);
/// assert_eq!(align_of::<TestType>(), 8); /// assert_eq!(align_of::<PreRctOutputId>(), 8);
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct TestType { pub struct PreRctOutputId {
/// TEST /// TODO
pub u: usize, pub amount: Amount,
/// TEST /// TODO
pub b: u8, pub amount_index: AmountIndex,
/// TEST
///
/// TODO: is there a cheaper way (CPU instruction wise)
/// to add padding to structs over 0 filled arrays?
///
/// TODO: this is basically leeway to
/// add more things to our structs too,
/// because otherwise this space is wasted.
pub _pad: [u8; 7],
} }
//---------------------------------------------------------------------------------------------------- TestType2 //---------------------------------------------------------------------------------------------------- BlockInfoV1
/// TEST2 /// TODO
/// ///
/// ```rust /// ```rust
/// # use std::borrow::*;
/// # use cuprate_database::{*, types::*}; /// # use cuprate_database::{*, types::*};
/// // Assert bytemuck is correct.
/// let a = TestType2 { u: 1, b: [1; 32] }; // original struct
/// let b = bytemuck::must_cast::<TestType2, [u8; 40]>(a); // cast into bytes
/// let c = bytemuck::must_cast::<[u8; 40], TestType2>(b); // cast back into struct
/// assert_eq!(a, c);
/// assert_eq!(c.u, 1);
/// assert_eq!(c.b, [1; 32]);
///
/// // Assert Storable is correct. /// // Assert Storable is correct.
/// let b2 = Storable::as_bytes(&a); /// let a = BlockInfoV1 {
/// let c2: &TestType2 = Storable::from_bytes(b2); /// timestamp: 1,
/// assert_eq!(a, *c2); /// total_generated_coins: 123,
/// assert_eq!(b, b2); /// weight: 321,
/// assert_eq!(c, *c2); /// cumulative_difficulty: 111,
/// assert_eq!(c.u, 1); /// block_hash: [54; 32],
/// assert_eq!(c.b, [1; 32]); /// };
/// let b = Storable::as_bytes(&a);
/// let c: &BlockInfoV1 = Storable::from_bytes(b);
/// let c2: Cow<'_, BlockInfoV1> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// ``` /// ```
/// ///
/// # Size & Alignment /// # Size & Alignment
/// ```rust /// ```rust
/// # use cuprate_database::types::*; /// # use cuprate_database::types::*;
/// # use std::mem::*; /// # use std::mem::*;
/// assert_eq!(size_of::<TestType2>(), 40); /// assert_eq!(size_of::<BlockInfoV1>(), 64);
/// assert_eq!(align_of::<TestType2>(), 8); /// assert_eq!(align_of::<BlockInfoV1>(), 8);
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct TestType2 { pub struct BlockInfoV1 {
/// TEST /// TODO
pub u: usize, pub timestamp: u64,
/// TEST /// TODO
pub b: [u8; 32], pub total_generated_coins: u64,
/// TODO
pub weight: u64,
/// TODO
pub cumulative_difficulty: u64,
/// TODO
pub block_hash: [u8; 32],
} }
//---------------------------------------------------------------------------------------------------- BlockInfoV2
/// TODO
///
/// ```rust
/// # use std::borrow::*;
/// # use cuprate_database::{*, types::*};
/// // Assert Storable is correct.
/// let a = BlockInfoV2 {
/// timestamp: 1,
/// total_generated_coins: 123,
/// weight: 321,
/// block_hash: [54; 32],
/// cumulative_difficulty: 111,
/// cumulative_rct_outs: 2389,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &BlockInfoV2 = Storable::from_bytes(b);
/// let c2: Cow<'_, BlockInfoV2> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// ```
///
/// # Size & Alignment
/// ```rust
/// # use cuprate_database::types::*;
/// # use std::mem::*;
/// assert_eq!(size_of::<BlockInfoV2>(), 72);
/// assert_eq!(align_of::<BlockInfoV2>(), 8);
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
#[repr(C)]
pub struct BlockInfoV2 {
/// TODO
pub timestamp: u64,
/// TODO
pub total_generated_coins: u64,
/// TODO
pub weight: u64,
/// TODO
pub block_hash: [u8; 32],
/// TODO
pub cumulative_difficulty: u64,
/// TODO
///
/// TODO: note that this is originally u32,
/// but is u64 here for padding reasons.
pub cumulative_rct_outs: u64,
}
//---------------------------------------------------------------------------------------------------- BlockInfoV3
/// TODO
///
/// ```rust
/// # use std::borrow::*;
/// # use cuprate_database::{*, types::*};
/// // Assert Storable is correct.
/// let a = BlockInfoV3 {
/// timestamp: 1,
/// total_generated_coins: 123,
/// weight: 321,
/// cumulative_difficulty_low: 111,
/// cumulative_difficulty_high: 112,
/// block_hash: [54; 32],
/// cumulative_rct_outs: 2389,
/// long_term_weight: 2389,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &BlockInfoV3 = Storable::from_bytes(b);
/// let c2: Cow<'_, BlockInfoV3> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// ```
///
/// # Size & Alignment
/// ```rust
/// # use cuprate_database::types::*;
/// # use std::mem::*;
/// assert_eq!(size_of::<BlockInfoV3>(), 88);
/// assert_eq!(align_of::<BlockInfoV3>(), 8);
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
#[repr(C)]
pub struct BlockInfoV3 {
/// TODO
pub timestamp: u64,
/// TODO
pub total_generated_coins: u64,
/// TODO
pub weight: u64,
// Maintain 8 byte alignment.
/// TODO
pub cumulative_difficulty_low: u64,
/// TODO
pub cumulative_difficulty_high: u64,
/// TODO
pub block_hash: [u8; 32],
/// TODO
pub cumulative_rct_outs: u64,
/// TODO
pub long_term_weight: u64,
}
//---------------------------------------------------------------------------------------------------- Output
/// TODO
///
/// ```rust
/// # use std::borrow::*;
/// # use cuprate_database::{*, types::*};
/// // Assert Storable is correct.
/// let a = Output {
/// key: [1; 32],
/// height: 1,
/// output_flags: 0,
/// tx_idx: 3,
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &Output = Storable::from_bytes(b);
/// let c2: Cow<'_, Output> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// ```
///
/// # Size & Alignment
/// ```rust
/// # use cuprate_database::types::*;
/// # use std::mem::*;
/// assert_eq!(size_of::<Output>(), 48);
/// assert_eq!(align_of::<Output>(), 8);
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
#[repr(C)]
pub struct Output {
/// TODO
pub key: [u8; 32],
/// We could get this from the tx_idx with the Tx Heights table but that would require another look up per out.
pub height: u32,
/// Bit flags for this output, currently only the first bit is used and, if set, it means this output has a non-zero unlock time.
pub output_flags: u32,
/// TODO
pub tx_idx: u64,
}
//---------------------------------------------------------------------------------------------------- RctOutput
/// TODO
///
/// ```rust
/// # use std::borrow::*;
/// # use cuprate_database::{*, types::*};
/// // Assert Storable is correct.
/// let a = RctOutput {
/// key: [1; 32],
/// height: 1,
/// output_flags: 0,
/// tx_idx: 3,
/// commitment: [3; 32],
/// };
/// let b = Storable::as_bytes(&a);
/// let c: &RctOutput = Storable::from_bytes(b);
/// let c2: Cow<'_, RctOutput> = Storable::from_bytes_unaligned(b);
/// assert_eq!(&a, c);
/// assert_eq!(c, c2.as_ref());
/// ```
///
/// # Size & Alignment
/// ```rust
/// # use cuprate_database::types::*;
/// # use std::mem::*;
/// assert_eq!(size_of::<RctOutput>(), 80);
/// assert_eq!(align_of::<RctOutput>(), 8);
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
#[repr(C)]
pub struct RctOutput {
/// TODO
pub key: [u8; 32],
/// We could get this from the tx_idx with the Tx Heights table but that would require another look up per out.
pub height: u32,
/// Bit flags for this output, currently only the first bit is used and, if set, it means this output has a non-zero unlock time.
pub output_flags: u32,
/// TODO
pub tx_idx: u64,
/// The amount commitment of this output.
pub commitment: [u8; 32],
}
// TODO: local_index?
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View file

@ -24,12 +24,12 @@ use crate::{table::Table, Storable, ToOwnedDebug};
/// - `redb` will always be `Cow::Borrowed` for `[u8]` /// - `redb` will always be `Cow::Borrowed` for `[u8]`
/// or any type where `Storable::ALIGN == 1` /// or any type where `Storable::ALIGN == 1`
/// - `redb` will always be `Cow::Owned` for everything else /// - `redb` will always be `Cow::Owned` for everything else
pub trait ValueGuard<T: ToOwnedDebug> { pub trait ValueGuard<T: ToOwnedDebug + ?Sized> {
/// Retrieve the data from the guard. /// Retrieve the data from the guard.
fn unguard(&self) -> Cow<'_, T>; fn unguard(&self) -> Cow<'_, T>;
} }
impl<T: ToOwnedDebug> ValueGuard<T> for Cow<'_, T> { impl<T: ToOwnedDebug + ?Sized> ValueGuard<T> for Cow<'_, T> {
#[inline] #[inline]
fn unguard(&self) -> Cow<'_, T> { fn unguard(&self) -> Cow<'_, T> {
Cow::Borrowed(self.borrow()) Cow::Borrowed(self.borrow())