Merge branch 'main' into test

This commit is contained in:
hinto.janai 2024-04-16 16:53:40 -04:00
commit 65d3a5b0c6
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
34 changed files with 1345 additions and 191 deletions

17
Cargo.lock generated
View file

@ -725,7 +725,9 @@ dependencies = [
"bytes",
"bzip2",
"futures",
"hex",
"monero-p2p",
"monero-serai",
"monero-wire",
"reqwest",
"tar",
@ -735,6 +737,17 @@ dependencies = [
"zip",
]
[[package]]
name = "cuprate-types"
version = "0.0.0"
dependencies = [
"borsh",
"cfg-if",
"curve25519-dalek",
"monero-serai",
"serde",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.2"
@ -1143,9 +1156,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.24"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",

View file

@ -16,6 +16,7 @@ members = [
"p2p/address-book",
"pruning",
"test-utils",
"types",
]
[profile.release]
@ -41,6 +42,7 @@ anyhow = { version = "1.0.81", default-features = false }
async-trait = { version = "0.1.74", default-features = false }
bitflags = { version = "2.4.2", default-features = false }
borsh = { version = "1.2.1", default-features = false }
bytemuck = { version = "1.14.3", default-features = false }
bytes = { version = "1.5.0", default-features = false }
cfg-if = { version = "1.0.0", default-features = false }
clap = { version = "4.5.4", default-features = false }
@ -92,7 +94,6 @@ proptest-derive = { version = "0.4.0" }
# open = { version = "5.0.0" } # Open PATH/URL, probably for binaries | https://github.com/byron/open-rs
# regex = { version = "1.10.2" } # Regular expressions | https://github.com/rust-lang/regex
# ryu = { version = "1.0.15" } # Fast float to string formatting | https://github.com/dtolnay/ryu
# strum = { version = "0.25.0" } # Enum macros/traits | https://github.com/Peternator7/strum
# Maybe one day.
# disk = { version = "*" } # (De)serialization to/from disk with various file formats | https://github.com/hinto-janai/disk

View file

@ -9,11 +9,13 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/database"
keywords = ["cuprate", "database"]
[features]
default = ["heed", "redb", "service"]
# default = ["heed", "redb", "service"]
# default = ["redb", "service"]
heed = ["dep:heed"]
redb = ["dep:redb"]
service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"]
default = ["redb-memory", "service"]
heed = ["dep:heed"]
redb = ["dep:redb"]
redb-memory = ["redb"]
service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"]
[dependencies]
bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }

View file

@ -12,6 +12,7 @@ Cuprate's database implementation.
1. [Backends](#backends)
- [`heed`](#heed)
- [`redb`](#redb)
- [`redb-memory`](#redb-memory)
- [`sanakirja`](#sanakirja)
- [`MDBX`](#mdbx)
1. [Layers](#layers)
@ -171,6 +172,11 @@ The upstream versions from [`crates.io`](https://crates.io/crates/redb) are used
TODO: document DB on remote filesystem (does redb allow this?)
## `redb-memory`
This backend is 100% the same as `redb`, although, it uses `redb::backend::InMemoryBackend` which is a key-value store that completely resides in memory instead of a file.
All other details about this should be the same as the normal `redb` backend.
## `sanakirja`
[`sanakirja`](https://docs.rs/sanakirja) was a candidate as a backend, however there were problems with maximum value sizes.

View file

@ -3,6 +3,7 @@
//---------------------------------------------------------------------------------------------------- Import
use std::{
borrow::{Borrow, Cow},
cell::RefCell,
fmt::Debug,
ops::RangeBounds,
sync::RwLockReadGuard,
@ -10,7 +11,7 @@ use std::{
use crate::{
backend::heed::{storable::StorableHeed, types::HeedDb},
database::{DatabaseRo, DatabaseRw},
database::{DatabaseIter, DatabaseRo, DatabaseRw},
error::RuntimeError,
table::Table,
};
@ -42,10 +43,10 @@ pub(super) struct HeedTableRo<'tx, T: Table> {
///
/// Matches `redb::Table` (read & write).
pub(super) struct HeedTableRw<'env, 'tx, T: Table> {
/// TODO
/// An already opened database table.
pub(super) db: HeedDb<T::Key, T::Value>,
/// The associated read/write transaction that opened this table.
pub(super) tx_rw: &'tx mut heed::RwTxn<'env>,
pub(super) tx_rw: &'tx RefCell<heed::RwTxn<'env>>,
}
//---------------------------------------------------------------------------------------------------- Shared functions
@ -53,7 +54,7 @@ pub(super) struct HeedTableRw<'env, 'tx, T: Table> {
// call the functions since the database is held by value, so
// just use these generic functions that both can call instead.
/// Shared generic `get()` between `HeedTableR{o,w}`.
/// Shared [`DatabaseRo::get()`].
#[inline]
fn get<T: Table>(
db: &HeedDb<T::Key, T::Value>,
@ -63,17 +64,76 @@ fn get<T: Table>(
db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound)
}
/// Shared generic `get_range()` between `HeedTableR{o,w}`.
/// Shared [`DatabaseRo::len()`].
#[inline]
fn get_range<'a, T: Table, Range>(
db: &'a HeedDb<T::Key, T::Value>,
tx_ro: &'a heed::RoTxn<'_>,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
Ok(db.range(tx_ro, &range)?.map(|res| Ok(res?.1)))
fn len<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<u64, RuntimeError> {
Ok(db.len(tx_ro)?)
}
/// Shared [`DatabaseRo::first()`].
#[inline]
fn first<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<(T::Key, T::Value), RuntimeError> {
db.first(tx_ro)?.ok_or(RuntimeError::KeyNotFound)
}
/// Shared [`DatabaseRo::last()`].
#[inline]
fn last<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<(T::Key, T::Value), RuntimeError> {
db.last(tx_ro)?.ok_or(RuntimeError::KeyNotFound)
}
/// Shared [`DatabaseRo::is_empty()`].
#[inline]
fn is_empty<T: Table>(
db: &HeedDb<T::Key, T::Value>,
tx_ro: &heed::RoTxn<'_>,
) -> Result<bool, RuntimeError> {
Ok(db.is_empty(tx_ro)?)
}
//---------------------------------------------------------------------------------------------------- DatabaseIter Impl
impl<T: Table> DatabaseIter<T> for HeedTableRo<'_, T> {
#[inline]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
Ok(self.db.range(self.tx_ro, &range)?.map(|res| Ok(res?.1)))
}
#[inline]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>
{
Ok(self.db.iter(self.tx_ro)?.map(|res| Ok(res?)))
}
#[inline]
fn keys(
&self,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
Ok(self.db.iter(self.tx_ro)?.map(|res| Ok(res?.0)))
}
#[inline]
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
Ok(self.db.iter(self.tx_ro)?.map(|res| Ok(res?.1)))
}
}
//---------------------------------------------------------------------------------------------------- DatabaseRo Impl
@ -84,14 +144,23 @@ impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> {
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
get_range::<T, Range>(&self.db, self.tx_ro, range)
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(&self.db, self.tx_ro)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(&self.db, self.tx_ro)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(&self.db, self.tx_ro)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(&self.db, self.tx_ro)
}
}
@ -99,32 +168,92 @@ impl<T: Table> DatabaseRo<T> for HeedTableRo<'_, T> {
impl<T: Table> DatabaseRo<T> for HeedTableRw<'_, '_, T> {
#[inline]
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError> {
get::<T>(&self.db, self.tx_rw, key)
get::<T>(&self.db, &self.tx_rw.borrow(), key)
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
get_range::<T, Range>(&self.db, self.tx_rw, range)
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(&self.db, &self.tx_rw.borrow())
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(&self.db, &self.tx_rw.borrow())
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(&self.db, &self.tx_rw.borrow())
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(&self.db, &self.tx_rw.borrow())
}
}
impl<T: Table> DatabaseRw<T> for HeedTableRw<'_, '_, T> {
#[inline]
fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> {
Ok(self.db.put(self.tx_rw, key, value)?)
Ok(self.db.put(&mut self.tx_rw.borrow_mut(), key, value)?)
}
#[inline]
fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> {
self.db.delete(self.tx_rw, key)?;
self.db.delete(&mut self.tx_rw.borrow_mut(), key)?;
Ok(())
}
#[inline]
fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> {
let tx_rw = &mut self.tx_rw.borrow_mut();
// Get the first value first...
let Some(first) = self.db.first(tx_rw)? else {
return Err(RuntimeError::KeyNotFound);
};
// ...then remove it.
//
// We use an iterator because we want to semantically
// remove the _first_ and only the first `(key, value)`.
// `delete()` removes all keys including duplicates which
// is slightly different behavior.
let mut iter = self.db.iter_mut(tx_rw)?;
// SAFETY:
// It is undefined behavior to keep a reference of
// a value from this database while modifying it.
// We are deleting the value and never accessing
// the iterator again so this should be safe.
unsafe {
iter.del_current()?;
}
Ok(first)
}
#[inline]
fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> {
let tx_rw = &mut self.tx_rw.borrow_mut();
let Some(first) = self.db.last(tx_rw)? else {
return Err(RuntimeError::KeyNotFound);
};
let mut iter = self.db.rev_iter_mut(tx_rw)?;
// SAFETY:
// It is undefined behavior to keep a reference of
// a value from this database while modifying it.
// We are deleting the value and never accessing
// the iterator again so this should be safe.
unsafe {
iter.del_current()?;
}
Ok(first)
}
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -2,6 +2,7 @@
//---------------------------------------------------------------------------------------------------- Import
use std::{
cell::RefCell,
fmt::Debug,
ops::Deref,
sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
@ -16,7 +17,7 @@ use crate::{
types::HeedDb,
},
config::{Config, SyncMode},
database::{DatabaseRo, DatabaseRw},
database::{DatabaseIter, DatabaseRo, DatabaseRw},
env::{Env, EnvInner},
error::{InitError, RuntimeError},
resize::ResizeAlgorithm,
@ -96,7 +97,23 @@ impl Env for ConcreteEnv {
const SYNCS_PER_TX: bool = false;
type EnvInner<'env> = RwLockReadGuard<'env, heed::Env>;
type TxRo<'tx> = heed::RoTxn<'tx>;
type TxRw<'tx> = heed::RwTxn<'tx>;
/// HACK:
/// `heed::RwTxn` is wrapped in `RefCell` to allow:
/// - opening a database with only a `&` to it
/// - allowing 1 write tx to open multiple tables
///
/// Our mutable accesses are safe and will not panic as:
/// - Write transactions are `!Sync`
/// - A table operation does not hold a reference to the inner cell
/// once the call is over
/// - The function to manipulate the table takes the same type
/// of reference that the `RefCell` gets for that function
///
/// Also see:
/// - <https://github.com/Cuprate/cuprate/pull/102#discussion_r1548695610>
/// - <https://github.com/Cuprate/cuprate/pull/104>
type TxRw<'tx> = RefCell<heed::RwTxn<'tx>>;
#[cold]
#[inline(never)] // called once.
@ -263,7 +280,8 @@ impl Env for ConcreteEnv {
}
//---------------------------------------------------------------------------------------------------- EnvInner Impl
impl<'env> EnvInner<'env, heed::RoTxn<'env>, heed::RwTxn<'env>> for RwLockReadGuard<'env, heed::Env>
impl<'env> EnvInner<'env, heed::RoTxn<'env>, RefCell<heed::RwTxn<'env>>>
for RwLockReadGuard<'env, heed::Env>
where
Self: 'env,
{
@ -273,15 +291,15 @@ where
}
#[inline]
fn tx_rw(&'env self) -> Result<heed::RwTxn<'env>, RuntimeError> {
Ok(self.write_txn()?)
fn tx_rw(&'env self) -> Result<RefCell<heed::RwTxn<'env>>, RuntimeError> {
Ok(RefCell::new(self.write_txn()?))
}
#[inline]
fn open_db_ro<T: Table>(
&self,
tx_ro: &heed::RoTxn<'env>,
) -> Result<impl DatabaseRo<T>, RuntimeError> {
) -> Result<impl DatabaseRo<T> + DatabaseIter<T>, RuntimeError> {
// Open up a read-only database using our table's const metadata.
Ok(HeedTableRo {
db: self
@ -294,16 +312,34 @@ where
#[inline]
fn open_db_rw<T: Table>(
&self,
tx_rw: &mut heed::RwTxn<'env>,
tx_rw: &RefCell<heed::RwTxn<'env>>,
) -> Result<impl DatabaseRw<T>, RuntimeError> {
let tx_ro = tx_rw.borrow();
// Open up a read/write database using our table's const metadata.
Ok(HeedTableRw {
db: self
.open_database(tx_rw, Some(T::NAME))?
.open_database(&tx_ro, Some(T::NAME))?
.expect(PANIC_MSG_MISSING_TABLE),
tx_rw,
})
}
#[inline]
fn clear_db<T: Table>(
&self,
tx_rw: &mut RefCell<heed::RwTxn<'env>>,
) -> Result<(), RuntimeError> {
let tx_rw = tx_rw.get_mut();
// Open the table first...
let db: HeedDb<T::Key, T::Value> = self
.open_database(tx_rw, Some(T::NAME))?
.expect(PANIC_MSG_MISSING_TABLE);
// ...then clear it.
Ok(db.clear(tx_rw)?)
}
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -1,6 +1,6 @@
//! Implementation of `trait TxRo/TxRw` for `heed`.
use std::{ops::Deref, sync::RwLockReadGuard};
use std::{cell::RefCell, ops::Deref, sync::RwLockReadGuard};
//---------------------------------------------------------------------------------------------------- Import
use crate::{
@ -11,25 +11,25 @@ use crate::{
//---------------------------------------------------------------------------------------------------- TxRo
impl TxRo<'_> for heed::RoTxn<'_> {
fn commit(self) -> Result<(), RuntimeError> {
Ok(self.commit()?)
Ok(heed::RoTxn::commit(self)?)
}
}
//---------------------------------------------------------------------------------------------------- TxRw
impl TxRo<'_> for heed::RwTxn<'_> {
impl TxRo<'_> for RefCell<heed::RwTxn<'_>> {
fn commit(self) -> Result<(), RuntimeError> {
Ok(self.commit()?)
TxRw::commit(self)
}
}
impl TxRw<'_> for heed::RwTxn<'_> {
impl TxRw<'_> for RefCell<heed::RwTxn<'_>> {
fn commit(self) -> Result<(), RuntimeError> {
Ok(self.commit()?)
Ok(heed::RwTxn::commit(self.into_inner())?)
}
/// This function is infallible.
fn abort(self) -> Result<(), RuntimeError> {
self.abort();
heed::RwTxn::abort(self.into_inner());
Ok(())
}
}

View file

@ -8,12 +8,14 @@ use std::{
ops::{Bound, Deref, RangeBounds},
};
use redb::ReadableTable;
use crate::{
backend::redb::{
storable::StorableRedb,
types::{RedbTableRo, RedbTableRw},
},
database::{DatabaseRo, DatabaseRw},
database::{DatabaseIter, DatabaseRo, DatabaseRw},
error::RuntimeError,
storable::Storable,
table::Table,
@ -24,7 +26,7 @@ use crate::{
// call the functions since the database is held by value, so
// just use these generic functions that both can call instead.
/// Shared generic `get()` between `RedbTableR{o,w}`.
/// Shared [`DatabaseRo::get()`].
#[inline]
fn get<T: Table + 'static>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
@ -33,19 +35,86 @@ fn get<T: Table + 'static>(
Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value())
}
/// Shared generic `get_range()` between `RedbTableR{o,w}`.
/// Shared [`DatabaseRo::len()`].
#[inline]
fn get_range<'a, T: Table, Range>(
db: &'a impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
Ok(db.range(range)?.map(|result| {
let (_key, value_guard) = result?;
Ok(value_guard.value())
}))
fn len<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<u64, RuntimeError> {
Ok(db.len()?)
}
/// Shared [`DatabaseRo::first()`].
#[inline]
fn first<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<(T::Key, T::Value), RuntimeError> {
let (key, value) = db.first()?.ok_or(RuntimeError::KeyNotFound)?;
Ok((key.value(), value.value()))
}
/// Shared [`DatabaseRo::last()`].
#[inline]
fn last<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<(T::Key, T::Value), RuntimeError> {
let (key, value) = db.last()?.ok_or(RuntimeError::KeyNotFound)?;
Ok((key.value(), value.value()))
}
/// Shared [`DatabaseRo::is_empty()`].
#[inline]
fn is_empty<T: Table>(
db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
) -> Result<bool, RuntimeError> {
Ok(db.is_empty()?)
}
//---------------------------------------------------------------------------------------------------- DatabaseIter
impl<T: Table + 'static> DatabaseIter<T> for RedbTableRo<T::Key, T::Value> {
#[inline]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
Ok(ReadableTable::range(self, range)?.map(|result| {
let (_key, value) = result?;
Ok(value.value())
}))
}
#[inline]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>
{
Ok(ReadableTable::iter(self)?.map(|result| {
let (key, value) = result?;
Ok((key.value(), value.value()))
}))
}
#[inline]
fn keys(
&self,
) -> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError> {
Ok(ReadableTable::iter(self)?.map(|result| {
let (key, _value) = result?;
Ok(key.value())
}))
}
#[inline]
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError> {
Ok(ReadableTable::iter(self)?.map(|result| {
let (_key, value) = result?;
Ok(value.value())
}))
}
}
//---------------------------------------------------------------------------------------------------- DatabaseRo
@ -56,14 +125,23 @@ impl<T: Table + 'static> DatabaseRo<T> for RedbTableRo<T::Key, T::Value> {
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
get_range::<T, Range>(self, range)
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(self)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(self)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(self)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(self)
}
}
@ -75,32 +153,52 @@ impl<T: Table + 'static> DatabaseRo<T> for RedbTableRw<'_, T::Key, T::Value> {
}
#[inline]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a,
{
get_range::<T, Range>(self, range)
fn len(&self) -> Result<u64, RuntimeError> {
len::<T>(self)
}
#[inline]
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> {
first::<T>(self)
}
#[inline]
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> {
last::<T>(self)
}
#[inline]
fn is_empty(&self) -> Result<bool, RuntimeError> {
is_empty::<T>(self)
}
}
impl<T: Table + 'static> DatabaseRw<T> for RedbTableRw<'_, T::Key, T::Value> {
// `redb` returns the value after `insert()/remove()`
// we end with Ok(()) instead.
// `redb` returns the value after function calls so we end with Ok(()) instead.
#[inline]
fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> {
self.insert(key, value)?;
redb::Table::insert(self, key, value)?;
Ok(())
}
#[inline]
fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> {
self.remove(key)?;
redb::Table::remove(self, key)?;
Ok(())
}
#[inline]
fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> {
let (key, value) = redb::Table::pop_first(self)?.ok_or(RuntimeError::KeyNotFound)?;
Ok((key.value(), value.value()))
}
#[inline]
fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> {
let (key, value) = redb::Table::pop_last(self)?.ok_or(RuntimeError::KeyNotFound)?;
Ok((key.value(), value.value()))
}
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -9,7 +9,7 @@ use crate::{
types::{RedbTableRo, RedbTableRw},
},
config::{Config, SyncMode},
database::{DatabaseRo, DatabaseRw},
database::{DatabaseIter, DatabaseRo, DatabaseRw},
env::{Env, EnvInner},
error::{InitError, RuntimeError},
table::Table,
@ -72,16 +72,22 @@ impl Env for ConcreteEnv {
// TODO: we can set cache sizes with:
// env_builder.set_cache(bytes);
// Create the database directory if it doesn't exist.
std::fs::create_dir_all(config.db_directory())?;
// Use the in-memory backend if the feature is enabled.
let mut env = if cfg!(feature = "redb-memory") {
env_builder.create_with_backend(redb::backends::InMemoryBackend::new())?
} else {
// Create the database directory if it doesn't exist.
std::fs::create_dir_all(config.db_directory())?;
// Open the database file, create if needed.
let db_file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(config.db_file())?;
let mut env = env_builder.create_file(db_file)?;
// Open the database file, create if needed.
let db_file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(config.db_file())?;
env_builder.create_file(db_file)?
};
// Create all database tables.
// `redb` creates tables if they don't exist.
@ -180,7 +186,7 @@ where
fn open_db_ro<T: Table>(
&self,
tx_ro: &redb::ReadTransaction,
) -> Result<impl DatabaseRo<T>, RuntimeError> {
) -> Result<impl DatabaseRo<T> + DatabaseIter<T>, RuntimeError> {
// Open up a read-only database using our `T: Table`'s const metadata.
let table: redb::TableDefinition<'static, StorableRedb<T::Key>, StorableRedb<T::Value>> =
redb::TableDefinition::new(T::NAME);
@ -192,7 +198,7 @@ where
#[inline]
fn open_db_rw<T: Table>(
&self,
tx_rw: &mut redb::WriteTransaction,
tx_rw: &redb::WriteTransaction,
) -> Result<impl DatabaseRw<T>, RuntimeError> {
// Open up a read/write database using our `T: Table`'s const metadata.
let table: redb::TableDefinition<'static, StorableRedb<T::Key>, StorableRedb<T::Value>> =
@ -202,6 +208,32 @@ where
// <https://docs.rs/redb/latest/redb/struct.WriteTransaction.html#method.open_table>
Ok(tx_rw.open_table(table)?)
}
#[inline]
fn clear_db<T: Table>(&self, tx_rw: &mut redb::WriteTransaction) -> Result<(), RuntimeError> {
let table: redb::TableDefinition<
'static,
StorableRedb<<T as Table>::Key>,
StorableRedb<<T as Table>::Value>,
> = redb::TableDefinition::new(<T as Table>::NAME);
// INVARIANT:
// This `delete_table()` will not run into this `TableAlreadyOpen` error:
// <https://docs.rs/redb/2.0.0/src/redb/transactions.rs.html#382>
// which will panic in the `From` impl, as:
//
// 1. Only 1 `redb::WriteTransaction` can exist at a time
// 2. We have exclusive access to it
// 3. So it's not being used to open a table since that needs `&tx_rw`
//
// Reader-open tables do not affect this, if they're open the below is still OK.
redb::WriteTransaction::delete_table(tx_rw, table)?;
// Re-create the table.
// `redb` creates tables if they don't exist, so this should never panic.
redb::WriteTransaction::open_table(tx_rw, table)?;
Ok(())
}
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -13,14 +13,18 @@
//!
//! `redb`, and it only must be enabled for it to be tested.
#![allow(clippy::items_after_statements, clippy::significant_drop_tightening)]
#![allow(
clippy::items_after_statements,
clippy::significant_drop_tightening,
clippy::cast_possible_truncation
)]
//---------------------------------------------------------------------------------------------------- Import
use std::borrow::{Borrow, Cow};
use crate::{
config::{Config, SyncMode},
database::{DatabaseRo, DatabaseRw},
database::{DatabaseIter, DatabaseRo, DatabaseRw},
env::{Env, EnvInner},
error::{InitError, RuntimeError},
resize::ResizeAlgorithm,
@ -77,7 +81,7 @@ fn open_db() {
let (env, _tempdir) = tmp_concrete_env();
let env_inner = env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let mut tx_rw = env_inner.tx_rw().unwrap();
let tx_rw = env_inner.tx_rw().unwrap();
// Open all tables in read-only mode.
// This should be updated when tables are modified.
@ -99,21 +103,21 @@ fn open_db() {
TxRo::commit(tx_ro).unwrap();
// Open all tables in read/write mode.
env_inner.open_db_rw::<BlockBlobs>(&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();
env_inner.open_db_rw::<BlockBlobs>(&tx_rw).unwrap();
env_inner.open_db_rw::<BlockHeights>(&tx_rw).unwrap();
env_inner.open_db_rw::<BlockInfoV1s>(&tx_rw).unwrap();
env_inner.open_db_rw::<BlockInfoV2s>(&tx_rw).unwrap();
env_inner.open_db_rw::<BlockInfoV3s>(&tx_rw).unwrap();
env_inner.open_db_rw::<KeyImages>(&tx_rw).unwrap();
env_inner.open_db_rw::<NumOutputs>(&tx_rw).unwrap();
env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
env_inner.open_db_rw::<PrunableHashes>(&tx_rw).unwrap();
env_inner.open_db_rw::<PrunableTxBlobs>(&tx_rw).unwrap();
env_inner.open_db_rw::<PrunedTxBlobs>(&tx_rw).unwrap();
env_inner.open_db_rw::<RctOutputs>(&tx_rw).unwrap();
env_inner.open_db_rw::<TxHeights>(&tx_rw).unwrap();
env_inner.open_db_rw::<TxIds>(&tx_rw).unwrap();
env_inner.open_db_rw::<TxUnlockTime>(&tx_rw).unwrap();
TxRw::commit(tx_rw).unwrap();
}
@ -162,11 +166,12 @@ fn non_manual_resize_2() {
/// Test all `DatabaseR{o,w}` operations.
#[test]
#[allow(clippy::too_many_lines)]
fn db_read_write() {
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::<Outputs>(&mut tx_rw).unwrap();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
/// The (1st) key.
const KEY: PreRctOutputId = PreRctOutputId {
@ -180,6 +185,8 @@ fn db_read_write() {
output_flags: 0,
tx_idx: 2_353_487,
};
/// How many `(key, value)` pairs will be inserted.
const N: u64 = 100;
/// Assert 2 `Output`'s are equal, and that accessing
/// their fields don't result in an unaligned panic.
@ -191,22 +198,41 @@ fn db_read_write() {
assert_eq!(output.tx_idx, VALUE.tx_idx);
}
// Insert `0..100` keys.
assert!(table.is_empty().unwrap());
// Insert keys.
let mut key = KEY;
for i in 0..100 {
for i in 0..N {
table.put(&key, &VALUE).unwrap();
key.amount += 1;
}
// Assert the 1st key is there.
assert_eq!(table.len().unwrap(), N);
// Assert the first/last `(key, value)`s are there.
{
let value: Output = table.get(&KEY).unwrap();
assert_same(value);
assert!(table.contains(&KEY).unwrap());
let get: Output = table.get(&KEY).unwrap();
assert_same(get);
let first: Output = table.first().unwrap().1;
assert_same(first);
let last: Output = table.last().unwrap().1;
assert_same(last);
}
// Commit transactions, create new ones.
drop(table);
TxRw::commit(tx_rw).unwrap();
let tx_ro = env_inner.tx_ro().unwrap();
let table_ro = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
// Assert the whole range is there.
{
let range = table.get_range(..).unwrap();
let range = table_ro.get_range(..).unwrap();
let mut i = 0;
for result in range {
let value: Output = result.unwrap();
@ -214,20 +240,23 @@ fn db_read_write() {
i += 1;
}
assert_eq!(i, 100);
assert_eq!(i, N);
}
// `get_range()` tests.
let mut key = KEY;
key.amount += 100;
key.amount += N;
let range = KEY..key;
// Assert count is correct.
assert_eq!(100, table.get_range(range.clone()).unwrap().count());
assert_eq!(
N as usize,
table_ro.get_range(range.clone()).unwrap().count()
);
// Assert each returned value from the iterator is owned.
{
let mut iter = table.get_range(range.clone()).unwrap();
let mut iter = table_ro.get_range(range.clone()).unwrap();
let value: Output = iter.next().unwrap().unwrap(); // 1. take value out
drop(iter); // 2. drop the `impl Iterator + 'a`
assert_same(value); // 3. assert even without the iterator, the value is alive
@ -235,17 +264,49 @@ fn db_read_write() {
// Assert each value is the same.
{
let mut iter = table.get_range(range).unwrap();
for _ in 0..100 {
let mut iter = table_ro.get_range(range).unwrap();
for _ in 0..N {
let value: Output = iter.next().unwrap().unwrap();
assert_same(value);
}
}
// Assert deleting works.
table.delete(&KEY).unwrap();
let value = table.get(&KEY);
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
{
table.delete(&KEY).unwrap();
let value = table.get(&KEY);
assert!(!table.contains(&KEY).unwrap());
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
// Assert the other `(key, value)` pairs are still there.
let mut key = KEY;
key.amount += N - 1; // we used inclusive `0..N`
let value = table.get(&key).unwrap();
assert_same(value);
}
drop(table);
TxRw::commit(tx_rw).unwrap();
// Assert `clear_db()` works.
{
let mut tx_rw = env_inner.tx_rw().unwrap();
env_inner.clear_db::<Outputs>(&mut tx_rw).unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
assert!(table.is_empty().unwrap());
for n in 0..N {
let mut key = KEY;
key.amount += n;
let value = table.get(&key);
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
assert!(!table.contains(&key).unwrap());
}
// Reader still sees old value.
assert!(!table_ro.is_empty().unwrap());
// Writer sees updated value (nothing).
assert!(table.is_empty().unwrap());
}
}
//---------------------------------------------------------------------------------------------------- Table Tests
@ -253,11 +314,7 @@ fn db_read_write() {
///
/// 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()`
/// - Essentially does the `db_read_write` test
macro_rules! test_tables {
($(
$table:ident, // Table type
@ -293,19 +350,47 @@ macro_rules! test_tables {
assert_eq(&value);
}
assert!(table.contains(&KEY).unwrap());
assert_eq!(table.len().unwrap(), 1);
// Commit transactions, create new ones.
drop(table);
TxRw::commit(tx_rw).unwrap();
let mut tx_rw = env_inner.tx_rw().unwrap();
let tx_ro = env_inner.tx_ro().unwrap();
let mut table = env_inner.open_db_rw::<$table>(&tx_rw).unwrap();
let table_ro = env_inner.open_db_ro::<$table>(&tx_ro).unwrap();
// Assert `get_range()` works.
{
let range = KEY..;
assert_eq!(1, table.get_range(range.clone()).unwrap().count());
let mut iter = table.get_range(range).unwrap();
assert_eq!(1, table_ro.get_range(range.clone()).unwrap().count());
let mut iter = table_ro.get_range(range).unwrap();
let value = iter.next().unwrap().unwrap();
assert_eq(&value);
}
// Assert deleting works.
table.delete(&KEY).unwrap();
let value = table.get(&KEY);
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
{
table.delete(&KEY).unwrap();
let value = table.get(&KEY);
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
assert!(!table.contains(&KEY).unwrap());
assert_eq!(table.len().unwrap(), 0);
}
table.put(&KEY, &value).unwrap();
// Assert `clear_db()` works.
{
drop(table);
env_inner.clear_db::<$table>(&mut tx_rw).unwrap();
let table = env_inner.open_db_rw::<$table>(&mut tx_rw).unwrap();
let value = table.get(&KEY);
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
assert!(!table.contains(&KEY).unwrap());
assert_eq!(table.len().unwrap(), 0);
}
}
)*}};
}

View file

@ -13,6 +13,70 @@ use crate::{
transaction::{TxRo, TxRw},
};
//---------------------------------------------------------------------------------------------------- DatabaseRoIter
/// Database (key-value store) read-only iteration abstraction.
///
/// These are read-only iteration-related operations that
/// can only be called from [`DatabaseRo`] objects.
///
/// # Hack
/// This is a HACK to get around the fact our read/write tables
/// cannot safely return values returning lifetimes, as such,
/// only read-only tables implement this trait.
///
/// - <https://github.com/Cuprate/cuprate/pull/102#discussion_r1548695610>
/// - <https://github.com/Cuprate/cuprate/pull/104>
pub trait DatabaseIter<T: Table> {
/// Get an iterator of value's corresponding to a range of keys.
///
/// For example:
/// ```rust,ignore
/// // This will return all 100 values corresponding
/// // to the keys `{0, 1, 2, ..., 100}`.
/// self.get_range(0..100);
/// ```
///
/// Although the returned iterator itself is tied to the lifetime
/// of `&'a self`, the returned values from the iterator are _owned_.
///
/// # Errors
/// Each key in the `range` has the potential to error, for example,
/// if a particular key in the `range` does not exist,
/// [`RuntimeError::KeyNotFound`] wrapped in [`Err`] will be returned
/// from the iterator.
#[allow(clippy::iter_not_returning_iterator)]
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a;
/// TODO
///
/// # Errors
/// TODO
#[allow(clippy::iter_not_returning_iterator)]
fn iter(
&self,
) -> Result<impl Iterator<Item = Result<(T::Key, T::Value), RuntimeError>> + '_, RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn keys(&self)
-> Result<impl Iterator<Item = Result<T::Key, RuntimeError>> + '_, RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn values(
&self,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + '_, RuntimeError>;
}
//---------------------------------------------------------------------------------------------------- DatabaseRo
/// Database (key-value store) read abstraction.
///
@ -29,22 +93,41 @@ pub trait DatabaseRo<T: Table> {
/// It will return other [`RuntimeError`]'s on things like IO errors as well.
fn get(&self, key: &T::Key) -> Result<T::Value, RuntimeError>;
/// Get an iterator of values corresponding to a range of keys.
///
/// Although the returned iterator itself is tied to the lifetime
/// of `&'a self`, the returned values from the iterator are _owned_.
/// TODO
///
/// # Errors
/// Each key in the `range` has the potential to error, for example,
/// if a particular key in the `range` does not exist,
/// [`RuntimeError::KeyNotFound`] wrapped in [`Err`] will be returned
/// from the iterator.
fn get_range<'a, Range>(
&'a self,
range: Range,
) -> Result<impl Iterator<Item = Result<T::Value, RuntimeError>> + 'a, RuntimeError>
where
Range: RangeBounds<T::Key> + 'a;
/// TODO
fn contains(&self, key: &T::Key) -> Result<bool, RuntimeError> {
match self.get(key) {
Ok(_) => Ok(true),
Err(RuntimeError::KeyNotFound) => Ok(false),
Err(e) => Err(e),
}
}
/// TODO
///
/// # Errors
/// TODO
fn len(&self) -> Result<u64, RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn first(&self) -> Result<(T::Key, T::Value), RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn last(&self) -> Result<(T::Key, T::Value), RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn is_empty(&self) -> Result<bool, RuntimeError>;
}
//---------------------------------------------------------------------------------------------------- DatabaseRw
@ -65,4 +148,16 @@ pub trait DatabaseRw<T: Table>: DatabaseRo<T> {
/// # Errors
/// This will return [`RuntimeError::KeyNotFound`] wrapped in [`Err`] if `key` does not exist.
fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError>;
/// TODO
///
/// # Errors
/// TODO
fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError>;
}

View file

@ -5,7 +5,7 @@ use std::{fmt::Debug, ops::Deref};
use crate::{
config::Config,
database::{DatabaseRo, DatabaseRw},
database::{DatabaseIter, DatabaseRo, DatabaseRw},
error::{InitError, RuntimeError},
resize::ResizeAlgorithm,
table::Table,
@ -196,10 +196,15 @@ where
/// ```
///
/// # Errors
/// This function errors upon internal database/IO errors.
///
/// As [`Table`] is `Sealed`, and all tables are created
/// upon [`Env::open`], this function will never error because
/// a table doesn't exist.
fn open_db_ro<T: Table>(&self, tx_ro: &Ro) -> Result<impl DatabaseRo<T>, RuntimeError>;
fn open_db_ro<T: Table>(
&self,
tx_ro: &Ro,
) -> Result<impl DatabaseRo<T> + DatabaseIter<T>, RuntimeError>;
/// Open a database in read/write mode.
///
@ -210,8 +215,26 @@ where
/// passed as a generic to this function.
///
/// # Errors
/// This function errors upon internal database/IO errors.
///
/// As [`Table`] is `Sealed`, and all tables are created
/// upon [`Env::open`], this function will never error because
/// a table doesn't exist.
fn open_db_rw<T: Table>(&self, tx_rw: &mut Rw) -> Result<impl DatabaseRw<T>, RuntimeError>;
fn open_db_rw<T: Table>(&self, tx_rw: &Rw) -> Result<impl DatabaseRw<T>, RuntimeError>;
/// Clear all `(key, value)`'s from a database table.
///
/// This will delete all key and values in the passed
/// `T: Table`, but the table itself will continue to exist.
///
/// Note that this operation is tied to `tx_rw`, as such this
/// function's effects can be aborted using [`TxRw::abort`].
///
/// # Errors
/// This function errors upon internal database/IO errors.
///
/// As [`Table`] is `Sealed`, and all tables are created
/// upon [`Env::open`], this function will never error because
/// a table doesn't exist.
fn clear_db<T: Table>(&self, tx_rw: &mut Rw) -> Result<(), RuntimeError>;
}

View file

@ -222,7 +222,7 @@ pub use constants::{
};
mod database;
pub use database::{DatabaseRo, DatabaseRw};
pub use database::{DatabaseIter, DatabaseRo, DatabaseRw};
mod env;
pub use env::{Env, EnvInner};

View file

@ -9,6 +9,7 @@ authors = ["Boog900"]
monero-wire = {path = "../net/monero-wire"}
monero-p2p = {path = "../p2p/monero-p2p", features = ["borsh"] }
monero-serai = { workspace = true }
futures = { workspace = true, features = ["std"] }
async-trait = { workspace = true }
tokio = { workspace = true, features = ["full"] }
@ -24,4 +25,7 @@ tar = "0.4.40"
bzip2 = "0.4.4"
[target.'cfg(windows)'.dependencies]
zip = "0.6"
zip = "0.6"
[dev-dependencies]
hex = { workspace = true }

View file

@ -3,5 +3,6 @@
This crate contains code that can be shared across multiple Cuprate crates tests, this crate should not be included in any
Cuprate crate, only in tests.
It currently contains code to spawn monerod instances and a testing network zone.
It currently contains:
- Code to spawn monerod instances and a testing network zone
- Real raw and typed Monero data, e.g. `Block, Transaction`

View file

@ -0,0 +1,14 @@
# Data
This module contains:
- Raw binary, hex, or JSON data for testing purposes
- Functions to access that data, either raw or typed
- `.bin` is a data blob, directly deserializable into types, e.g. `monero_serai::block::Block::read::<&[u8]>(&mut blob)`
- `.hex` is just a hex string of the blob
- `.json` is just the data in regular JSON form (as it would be from a JSON-RPC response)
# Actual data
| Directory | File naming scheme | Example |
|-----------|------------------------------|---------|
| `block/` | `$block_hash.{bin,hex,json}` | `bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698.bin`
| `tx/` | `$tx_hash.{bin,hex,json}` | `84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66.bin`

View file

@ -0,0 +1,150 @@
//! Constants holding raw Monero data.
//---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- Block
/// Block with height `202612` and hash `bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698`.
///
/// ```rust
/// use monero_serai::{block::Block, transaction::Input};
///
/// let block = Block::read(&mut
/// cuprate_test_utils::data::BLOCK_BBD604
/// ).unwrap();
///
/// assert_eq!(block.header.major_version, 1);
/// assert_eq!(block.header.minor_version, 0);
/// assert_eq!(block.header.timestamp, 1409804570);
/// assert_eq!(block.header.nonce, 1073744198);
/// assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(202612)));
/// assert_eq!(block.txs.len(), 513);
///
/// assert_eq!(
/// hex::encode(block.hash()),
/// "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698",
/// );
/// ```
pub const BLOCK_BBD604: &[u8] =
include_bytes!("block/bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698.bin");
/// Block with height `2751506` and hash `f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4`.
///
/// ```rust
/// use monero_serai::{block::Block, transaction::Input};
///
/// let block = Block::read(&mut
/// cuprate_test_utils::data::BLOCK_F91043
/// ).unwrap();
///
/// assert_eq!(block.header.major_version, 9);
/// assert_eq!(block.header.minor_version, 9);
/// assert_eq!(block.header.timestamp, 1545423190);
/// assert_eq!(block.header.nonce, 4123173351);
/// assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(1731606)));
/// assert_eq!(block.txs.len(), 3);
///
/// assert_eq!(
/// hex::encode(block.hash()),
/// "f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4",
/// );
/// ```
pub const BLOCK_F91043: &[u8] =
include_bytes!("block/f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4.bin");
/// Block with height `2751506` and hash `43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428`.
///
/// ```rust
/// use monero_serai::{block::Block, transaction::Input};
///
/// let block = Block::read(&mut
/// cuprate_test_utils::data::BLOCK_43BD1F
/// ).unwrap();
///
/// assert_eq!(block.header.major_version, 16);
/// assert_eq!(block.header.minor_version, 16);
/// assert_eq!(block.header.timestamp, 1667941829);
/// assert_eq!(block.header.nonce, 4110909056);
/// assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(2751506)));
/// assert_eq!(block.txs.len(), 0);
///
/// assert_eq!(
/// hex::encode(block.hash()),
/// "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428",
/// );
/// ```
pub const BLOCK_43BD1F: &[u8] =
include_bytes!("block/43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428.bin");
//---------------------------------------------------------------------------------------------------- Transaction
/// Transaction with hash `3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1`.
///
/// ```rust
/// use monero_serai::transaction::{Transaction, Timelock};
///
/// let tx = Transaction::read(&mut
/// cuprate_test_utils::data::TX_3BC7FF
/// ).unwrap();
///
/// assert_eq!(tx.prefix.version, 1);
/// assert_eq!(tx.prefix.timelock, Timelock::Block(100_081));
/// assert_eq!(tx.prefix.inputs.len(), 1);
/// assert_eq!(tx.prefix.outputs.len(), 5);
/// assert_eq!(tx.signatures.len(), 0);
///
/// assert_eq!(
/// hex::encode(tx.hash()),
/// "3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1",
/// );
/// ```
pub const TX_3BC7FF: &[u8] =
include_bytes!("tx/3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1.bin");
/// Transaction with hash `9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34`.
///
/// ```rust
/// use monero_serai::transaction::{Transaction, Timelock};
///
/// let tx = Transaction::read(&mut
/// cuprate_test_utils::data::TX_9E3F73
/// ).unwrap();
///
/// assert_eq!(tx.prefix.version, 1);
/// assert_eq!(tx.prefix.timelock, Timelock::None);
/// assert_eq!(tx.prefix.inputs.len(), 2);
/// assert_eq!(tx.prefix.outputs.len(), 5);
/// assert_eq!(tx.signatures.len(), 2);
///
/// assert_eq!(
/// hex::encode(tx.hash()),
/// "9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34",
/// );
/// ```
pub const TX_9E3F73: &[u8] =
include_bytes!("tx/9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34.bin");
/// Transaction with hash `84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66`.
///
/// ```rust
/// use monero_serai::transaction::{Transaction, Timelock};
///
/// let tx = Transaction::read(&mut
/// cuprate_test_utils::data::TX_84D48D
/// ).unwrap();
///
/// assert_eq!(tx.prefix.version, 2);
/// assert_eq!(tx.prefix.timelock, Timelock::None);
/// assert_eq!(tx.prefix.inputs.len(), 2);
/// assert_eq!(tx.prefix.outputs.len(), 2);
/// assert_eq!(tx.signatures.len(), 0);
///
/// assert_eq!(
/// hex::encode(tx.hash()),
/// "84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66",
/// );
/// ```
pub const TX_84D48D: &[u8] =
include_bytes!("tx/84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66.bin");
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {}

110
test-utils/src/data/free.rs Normal file
View file

@ -0,0 +1,110 @@
//! Free functions to access data.
#![allow(
const_item_mutation, // `R: Read` needs `&mut self`
clippy::missing_panics_doc, // These functions shouldn't panic
)]
//---------------------------------------------------------------------------------------------------- Import
use std::sync::OnceLock;
use monero_serai::{block::Block, transaction::Transaction};
use crate::data::constants::{
BLOCK_43BD1F, BLOCK_BBD604, BLOCK_F91043, TX_3BC7FF, TX_84D48D, TX_9E3F73,
};
//---------------------------------------------------------------------------------------------------- Blocks
/// Return [`BLOCK_BBD604`] as a [`Block`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::block_v1_tx513().serialize(),
/// cuprate_test_utils::data::BLOCK_BBD604
/// );
/// ```
pub fn block_v1_tx513() -> Block {
/// `OnceLock` holding the data.
static BLOCK: OnceLock<Block> = OnceLock::new();
BLOCK
.get_or_init(|| Block::read(&mut BLOCK_BBD604).unwrap())
.clone()
}
/// Return [`BLOCK_F91043`] as a [`Block`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::block_v9_tx3().serialize(),
/// cuprate_test_utils::data::BLOCK_F91043
/// );
/// ```
pub fn block_v9_tx3() -> Block {
/// `OnceLock` holding the data.
static BLOCK: OnceLock<Block> = OnceLock::new();
BLOCK
.get_or_init(|| Block::read(&mut BLOCK_F91043).unwrap())
.clone()
}
/// Return [`BLOCK_43BD1F`] as a [`Block`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::block_v16_tx0().serialize(),
/// cuprate_test_utils::data::BLOCK_43BD1F
/// );
/// ```
pub fn block_v16_tx0() -> Block {
/// `OnceLock` holding the data.
static BLOCK: OnceLock<Block> = OnceLock::new();
BLOCK
.get_or_init(|| Block::read(&mut BLOCK_43BD1F).unwrap())
.clone()
}
//---------------------------------------------------------------------------------------------------- Transactions
/// Return [`TX_3BC7FF`] as a [`Transaction`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::tx_v1_sig0().serialize(),
/// cuprate_test_utils::data::TX_3BC7FF
/// );
/// ```
pub fn tx_v1_sig0() -> Transaction {
/// `OnceLock` holding the data.
static TX: OnceLock<Transaction> = OnceLock::new();
TX.get_or_init(|| Transaction::read(&mut TX_3BC7FF).unwrap())
.clone()
}
/// Return [`TX_9E3F73`] as a [`Transaction`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::tx_v1_sig2().serialize(),
/// cuprate_test_utils::data::TX_9E3F73
/// );
/// ```
pub fn tx_v1_sig2() -> Transaction {
/// `OnceLock` holding the data.
static TX: OnceLock<Transaction> = OnceLock::new();
TX.get_or_init(|| Transaction::read(&mut TX_9E3F73).unwrap())
.clone()
}
/// Return [`TX_84D48D`] as a [`Transaction`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::tx_v2_rct3().serialize(),
/// cuprate_test_utils::data::TX_84D48D
/// );
/// ```
pub fn tx_v2_rct3() -> Transaction {
/// `OnceLock` holding the data.
static TX: OnceLock<Transaction> = OnceLock::new();
TX.get_or_init(|| Transaction::read(&mut TX_84D48D).unwrap())
.clone()
}

View file

@ -0,0 +1,9 @@
//! Testing data and utilities.
//!
//! Raw data is found in `data/`.
mod constants;
pub use constants::{BLOCK_43BD1F, BLOCK_BBD604, BLOCK_F91043, TX_3BC7FF, TX_84D48D, TX_9E3F73};
mod free;
pub use free::{block_v16_tx0, block_v1_tx513, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3};

View file

@ -1,2 +1,7 @@
//! Cuprate testing utilities.
//!
//! See the `README.md` for more info.
pub mod data;
pub mod monerod;
pub mod test_netzone;

View file

@ -17,11 +17,17 @@ use tokio::{task::yield_now, time::timeout};
mod download;
/// IPv4 local host.
const LOCALHOST: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
/// The `monerod` version to use.
const MONEROD_VERSION: &str = "v0.18.3.1";
/// The log line `monerod` emits indicated it has successfully started up.
const MONEROD_STARTUP_TEXT: &str =
"The daemon will start synchronizing with the network. This may take a long time to complete.";
/// The log line `monerod` emits indicated it has stopped.
const MONEROD_SHUTDOWN_TEXT: &str = "Stopping cryptonote protocol";
/// Spawns monerod and returns [`SpawnedMoneroD`].
@ -66,9 +72,7 @@ pub async fn monerod<T: AsRef<OsStr>>(flags: impl IntoIterator<Item = T>) -> Spa
if logs.contains(MONEROD_SHUTDOWN_TEXT) {
panic!("Failed to start monerod, logs: \n {logs}");
}
if logs.contains(MONEROD_STARTUP_TEXT) {
} else if logs.contains(MONEROD_STARTUP_TEXT) {
break;
}
// this is blocking code but as this is for tests performance isn't a priority. However we should still yield so
@ -88,6 +92,7 @@ pub async fn monerod<T: AsRef<OsStr>>(flags: impl IntoIterator<Item = T>) -> Spa
}
}
/// Fetch an available TCP port on the machine for `monerod` to bind to.
fn get_available_port(already_taken: &[u16]) -> u16 {
loop {
// Using `0` makes the OS return a random available port.
@ -119,12 +124,12 @@ pub struct SpawnedMoneroD {
impl SpawnedMoneroD {
/// Returns the p2p port of the spawned monerod
pub fn p2p_addr(&self) -> SocketAddr {
pub const fn p2p_addr(&self) -> SocketAddr {
SocketAddr::new(LOCALHOST, self.p2p_port)
}
/// Returns the RPC port of the spawned monerod
pub fn rpc_port(&self) -> SocketAddr {
pub const fn rpc_port(&self) -> SocketAddr {
SocketAddr::new(LOCALHOST, self.rpc_port)
}
}
@ -135,7 +140,7 @@ impl Drop for SpawnedMoneroD {
if self.process.kill().is_err() {
error = true;
println!("Failed to kill monerod, process id: {}", self.process.id())
println!("Failed to kill monerod, process id: {}", self.process.id());
}
if panicking() {

View file

@ -24,23 +24,23 @@ static DOWNLOAD_MONEROD_MUTEX: Mutex<()> = Mutex::const_new(());
/// Returns the file name to download and the expected extracted folder name.
fn file_name(version: &str) -> (String, String) {
let download_file = match (OS, ARCH) {
("windows", "x64") | ("windows", "x86_64") => format!("monero-win-x64-{}.zip", version),
("windows", "x86") => format!("monero-win-x86-{}.zip", version),
("linux", "x64") | ("linux", "x86_64") => format!("monero-linux-x64-{}.tar.bz2", version),
("linux", "x86") => format!("monero-linux-x86-{}.tar.bz2", version),
("macos", "x64") | ("macos", "x86_64") => format!("monero-mac-x64-{}.tar.bz2", version),
("windows", "x64" | "x86_64") => format!("monero-win-x64-{version}.zip"),
("windows", "x86") => format!("monero-win-x86-{version}.zip"),
("linux", "x64" | "x86_64") => format!("monero-linux-x64-{version}.tar.bz2"),
("linux", "x86") => format!("monero-linux-x86-{version}.tar.bz2"),
("macos", "x64" | "x86_64") => format!("monero-mac-x64-{version}.tar.bz2"),
_ => panic!("Can't get monerod for {OS}, {ARCH}."),
};
let extracted_dir = match (OS, ARCH) {
("windows", "x64") | ("windows", "x86_64") => {
format!("monero-x86_64-w64-mingw32-{}", version)
("windows", "x64" | "x86_64") => {
format!("monero-x86_64-w64-mingw32-{version}")
}
("windows", "x86") => format!("monero-i686-w64-mingw32-{}", version),
("linux", "x64") | ("linux", "x86_64") => format!("monero-x86_64-linux-gnu-{}", version),
("linux", "x86") => format!("monero-i686-linux-gnu-{}", version),
("macos", "x64") | ("macos", "x86_64") => {
format!("monero-x86_64-apple-darwin11-{}", version)
("windows", "x86") => format!("monero-i686-w64-mingw32-{version}"),
("linux", "x64" | "x86_64") => format!("monero-x86_64-linux-gnu-{version}"),
("linux", "x86") => format!("monero-i686-linux-gnu-{version}"),
("macos", "x64" | "x86_64") => {
format!("monero-x86_64-apple-darwin11-{version}")
}
_ => panic!("Can't get monerod for {OS}, {ARCH}."),
};
@ -50,7 +50,7 @@ fn file_name(version: &str) -> (String, String) {
/// Downloads the monerod file provided, extracts it and puts the extracted folder into `path_to_store`.
async fn download_monerod(file_name: &str, path_to_store: &Path) -> Result<(), ReqError> {
let res = get(format!("https://downloads.getmonero.org/cli/{}", file_name)).await?;
let res = get(format!("https://downloads.getmonero.org/cli/{file_name}")).await?;
let monerod_archive = res.bytes().await.unwrap();
#[cfg(unix)]
@ -83,7 +83,7 @@ fn find_target() -> PathBuf {
}
/// Checks if we have monerod or downloads it if we don't and then returns the path to it.
pub async fn check_download_monerod() -> Result<PathBuf, ReqError> {
pub(crate) async fn check_download_monerod() -> Result<PathBuf, ReqError> {
// make sure no other threads are downloading monerod at the same time.
let _guard = DOWNLOAD_MONEROD_MUTEX.lock().await;

View file

@ -1,4 +1,4 @@
//! Test NetZone
//! Test net zone.
//!
//! This module contains a test network zone, this network zone use channels as the network layer to simulate p2p
//! communication.
@ -48,7 +48,7 @@ impl std::fmt::Display for TestNetZoneAddr {
impl From<TestNetZoneAddr> for NetworkAddress {
fn from(value: TestNetZoneAddr) -> Self {
NetworkAddress::Clear(SocketAddr::new(Ipv4Addr::from(value.0).into(), 18080))
Self::Clear(SocketAddr::new(Ipv4Addr::from(value.0).into(), 18080))
}
}
@ -58,13 +58,14 @@ impl TryFrom<NetworkAddress> for TestNetZoneAddr {
fn try_from(value: NetworkAddress) -> Result<Self, Self::Error> {
match value {
NetworkAddress::Clear(soc) => match soc {
SocketAddr::V4(v4) => Ok(TestNetZoneAddr(u32::from_be_bytes(v4.ip().octets()))),
_ => panic!("None v4 address in test code"),
SocketAddr::V4(v4) => Ok(Self(u32::from_be_bytes(v4.ip().octets()))),
SocketAddr::V6(_) => panic!("None v4 address in test code"),
},
}
}
}
/// TODO
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct TestNetZone<const ALLOW_SYNC: bool, const DANDELION_PP: bool, const CHECK_NODE_ID: bool>;

22
types/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "cuprate-types"
version = "0.0.0"
edition = "2021"
description = "Cuprate data types"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/types"
keywords = ["cuprate", "types"]
[features]
default = ["service"]
service = []
[dependencies]
borsh = { workspace = true, optional = true }
cfg-if = { workspace = true }
curve25519-dalek = { workspace = true }
monero-serai = { workspace = true }
serde = { workspace = true, optional = true }
[dev-dependencies]

21
types/README.md Normal file
View file

@ -0,0 +1,21 @@
# `cuprate-types`
Various data types shared by Cuprate.
<!-- Did you know markdown automatically increments number lists, even if they are all 1...? -->
1. [File Structure](#file-structure)
- [`src/`](#src)
---
# File Structure
A quick reference of the structure of the folders & files in `cuprate-types`.
Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`.
## `src/`
The top-level `src/` files.
| File | Purpose |
|---------------------|---------|
| `service.rs` | Types used in database requests; `enum {Request,Response}`
| `types.rs` | Various general types used by Cuprate

101
types/src/lib.rs Normal file
View file

@ -0,0 +1,101 @@
//! Cuprate shared data types.
//!
//! TODO
//---------------------------------------------------------------------------------------------------- Lints
// Forbid lints.
// Our code, and code generated (e.g macros) cannot overrule these.
#![forbid(
// `unsafe` is allowed but it _must_ be
// commented with `SAFETY: reason`.
clippy::undocumented_unsafe_blocks,
// Never.
unused_unsafe,
redundant_semicolons,
unused_allocation,
coherence_leak_check,
single_use_lifetimes,
while_true,
clippy::missing_docs_in_private_items,
// Maybe can be put into `#[deny]`.
unconditional_recursion,
for_loops_over_fallibles,
unused_braces,
unused_doc_comments,
unused_labels,
keyword_idents,
non_ascii_idents,
variant_size_differences,
// Probably can be put into `#[deny]`.
future_incompatible,
let_underscore,
break_with_label_and_loop,
duplicate_macro_attributes,
exported_private_dependencies,
large_assignments,
overlapping_range_endpoints,
semicolon_in_expressions_from_macros,
noop_method_call,
unreachable_pub,
)]
// Deny lints.
// Some of these are `#[allow]`'ed on a per-case basis.
#![deny(
clippy::all,
clippy::correctness,
clippy::suspicious,
clippy::style,
clippy::complexity,
clippy::perf,
clippy::pedantic,
clippy::nursery,
clippy::cargo,
unused_mut,
missing_docs,
deprecated,
unused_comparisons,
nonstandard_style
)]
#![allow(unreachable_code, unused_variables, dead_code, unused_imports)] // TODO: remove
#![allow(
// FIXME: this lint affects crates outside of
// `database/` for some reason, allow for now.
clippy::cargo_common_metadata,
// FIXME: adding `#[must_use]` onto everything
// might just be more annoying than useful...
// although it is sometimes nice.
clippy::must_use_candidate,
// TODO: should be removed after all `todo!()`'s are gone.
clippy::diverging_sub_expression,
clippy::module_name_repetitions,
clippy::module_inception,
clippy::redundant_pub_crate,
clippy::option_if_let_else,
)]
// Allow some lints when running in debug mode.
#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))]
//---------------------------------------------------------------------------------------------------- Public API
// Import private modules, export public types.
//
// Documentation for each module is located in the respective file.
mod types;
pub use types::{
ExtendedBlockHeader, OutputOnChain, TransactionVerificationData, VerifiedBlockInformation,
};
//---------------------------------------------------------------------------------------------------- Feature-gated
cfg_if::cfg_if! {
if #[cfg(feature = "service")] {
pub mod service;
}
}
//---------------------------------------------------------------------------------------------------- Private

88
types/src/service.rs Normal file
View file

@ -0,0 +1,88 @@
//! Database [`ReadRequest`]s, [`WriteRequest`]s, and [`Response`]s.
//---------------------------------------------------------------------------------------------------- Import
use std::{
collections::{HashMap, HashSet},
ops::Range,
};
use monero_serai::{block::Block, transaction::Transaction};
#[cfg(feature = "borsh")]
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::types::{ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation};
//---------------------------------------------------------------------------------------------------- ReadRequest
/// A read request to the database.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub enum ReadRequest {
/// TODO
BlockExtendedHeader(u64),
/// TODO
BlockHash(u64),
/// TODO
BlockExtendedHeaderInRange(Range<u64>),
/// TODO
ChainHeight,
/// TODO
GeneratedCoins,
/// TODO
Outputs(HashMap<u64, HashSet<u64>>),
/// TODO
NumberOutputsWithAmount(Vec<u64>),
/// TODO
CheckKIsNotSpent(HashSet<[u8; 32]>),
/// TODO
BlockBatchInRange(Range<u64>),
}
//---------------------------------------------------------------------------------------------------- WriteRequest
/// A write request to the database.
#[derive(Debug, Clone, PartialEq, Eq)]
// #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub enum WriteRequest {
/// TODO
WriteBlock(VerifiedBlockInformation),
}
//---------------------------------------------------------------------------------------------------- Response
/// A response from the database.
#[derive(Debug, Clone, PartialEq, Eq)]
// #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub enum Response {
//------------------------------------------------------ Reads
/// TODO
BlockExtendedHeader(ExtendedBlockHeader),
/// TODO
BlockHash([u8; 32]),
/// TODO
BlockExtendedHeaderInRange(Vec<ExtendedBlockHeader>),
/// TODO
ChainHeight(u64, [u8; 32]),
/// TODO
GeneratedCoins(u64),
/// TODO
Outputs(HashMap<u64, HashMap<u64, OutputOnChain>>),
/// TODO
NumberOutputsWithAmount(HashMap<u64, usize>),
/// TODO
/// returns true if key images are spent
CheckKIsNotSpent(bool),
/// TODO
BlockBatchInRange(Vec<(Block, Vec<Transaction>)>),
//------------------------------------------------------ Writes
/// TODO
WriteBlockOk,
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

103
types/src/types.rs Normal file
View file

@ -0,0 +1,103 @@
//! TODO
//---------------------------------------------------------------------------------------------------- Import
use std::sync::Arc;
use curve25519_dalek::edwards::EdwardsPoint;
use monero_serai::{
block::Block,
transaction::{Timelock, Transaction},
};
#[cfg(feature = "borsh")]
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
//---------------------------------------------------------------------------------------------------- ExtendedBlockHeader
/// TODO
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub struct ExtendedBlockHeader {
/// TODO
/// This is a `cuprate_consensus::HardFork`.
pub version: u8,
/// TODO
/// This is a `cuprate_consensus::HardFork`.
pub vote: u8,
/// TODO
pub timestamp: u64,
/// TODO
pub cumulative_difficulty: u128,
/// TODO
pub block_weight: usize,
/// TODO
pub long_term_weight: usize,
}
//---------------------------------------------------------------------------------------------------- TransactionVerificationData
/// TODO
#[derive(Clone, Debug, PartialEq, Eq)]
// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] // FIXME: monero_serai
// #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub struct TransactionVerificationData {
/// TODO
pub tx: Transaction,
/// TODO
pub tx_blob: Vec<u8>,
/// TODO
pub tx_weight: usize,
/// TODO
pub fee: u64,
/// TODO
pub tx_hash: [u8; 32],
}
//---------------------------------------------------------------------------------------------------- VerifiedBlockInformation
/// TODO
#[derive(Clone, Debug, PartialEq, Eq)]
// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] // FIXME: monero_serai
// #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub struct VerifiedBlockInformation {
/// TODO
pub block: Block,
/// TODO
pub txs: Vec<Arc<TransactionVerificationData>>,
/// TODO
pub block_hash: [u8; 32],
/// TODO
pub pow_hash: [u8; 32],
/// TODO
pub height: u64,
/// TODO
pub generated_coins: u64,
/// TODO
pub weight: usize,
/// TODO
pub long_term_weight: usize,
/// TODO
pub cumulative_difficulty: u128,
}
//---------------------------------------------------------------------------------------------------- OutputOnChain
/// An already approved previous transaction output.
#[derive(Clone, Debug, PartialEq, Eq)]
// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] // FIXME: monero_serai
// #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
pub struct OutputOnChain {
/// TODO
pub height: u64,
/// TODO
pub time_lock: Timelock,
/// TODO
pub key: Option<EdwardsPoint>,
/// TODO
pub commitment: EdwardsPoint,
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}