diff --git a/.gitignore b/.gitignore
index 8fddb052..02c92a60 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
target/
Cargo.lock
-.vscode
\ No newline at end of file
+.vscode
+monerod
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 9a10dd62..c78d4bc5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,14 +18,18 @@ authors=[
[workspace]
members = [
- "blockchain_db",
+ "cuprate",
+ "database",
"net/levin",
"net/monero-wire"
]
[workspace.dependencies]
-monero = {version = "*", features = ['serde']}
+monero = { version = "*" }
+bincode = { version = "2.0.0-rc.3" }
serde = { version = "*", features =["derive"]}
+tracing = "*"
+tracing-subscriber = "*"
# As suggested by /u/danda :
thiserror = "*"
@@ -38,4 +42,11 @@ lto = "thin"
panic = "abort"
[build]
-rustflags=["-Zcf-protection=full", "-Zsanitizer=cfi", "-Crelocation-model=pie", "-Cstack-protector=all"]
+linker="clang"
+rustflags=[
+ "-Clink-arg=-fuse-ld=mold",
+ "-Zcf-protection=full",
+ "-Zsanitizer=cfi",
+ "-Crelocation-model=pie",
+ "-Cstack-protector=all",
+]
\ No newline at end of file
diff --git a/blockchain_db/Cargo.toml b/blockchain_db/Cargo.toml
deleted file mode 100644
index 720426af..00000000
--- a/blockchain_db/Cargo.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-[package]
-name = "blockchain_db"
-version = "0.0.1"
-edition = "2021"
-rust-version = "1.67.0"
-license = "AGPL-3.0-only"
-
-# All Contributors on github
-authors=[
- "SyntheticBird45 <@someoneelse495495:matrix.org>"
- ]
-
-[dependencies]
-monero = {workspace = true, features = ['serde']}
-serde = { workspace = true}
-thiserror = {workspace = true }
-
-rocksdb = { version = "*", features = ["multi-threaded-cf"]}
\ No newline at end of file
diff --git a/blockchain_db/src/lib.rs b/blockchain_db/src/lib.rs
deleted file mode 100644
index 4b79f0f6..00000000
--- a/blockchain_db/src/lib.rs
+++ /dev/null
@@ -1,883 +0,0 @@
-// Copyright (C) 2023 Cuprate Contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-//!
-//! blockchain_db crates:
-//! Contains the implementation of interaction between the blockchain and the database backend.
-//! There is actually only one storage engine available:
-//! - RocksDB
-//! There is two other storage engine planned:
-//! - HSE (Heteregeonous Storage Engine)
-//! - LMDB (like monerod)
-
-#![deny(unused_attributes)]
-#![forbid(unsafe_code)]
-#![allow(non_camel_case_types)]
-#![deny(clippy::expect_used, clippy::panic)]
-
-use thiserror::Error;
-use monero::{Hash, Transaction, Block, BlockHeader, consensus::Encodable, util::ringct::RctSig};
-use std::{error::Error, ops::Range};
-
-const MONERO_DEFAULT_LOG_CATEGORY: &str = "blockchain.db";
-
-pub type difficulty_type = u128;
-type Blobdata = Vec;
-type BlobdataRef = [u8];
-type TxOutIndex = (Hash, u64);
-
-/// Methods tracking how a tx was received and relayed
-pub enum RelayMethod {
- none, //< Received via RPC with `do_not_relay` set
- local, //< Received via RPC; trying to send over i2p/tor, etc.
- forward, //< Received over i2p/tor; timer delayed before ipv4/6 public broadcast
- stem, //< Received/send over network using Dandelion++ stem
- fluff, //< Received/sent over network using Dandelion++ fluff
- block, //< Received in block, takes precedence over others
-}
-
-// the database types are going to be defined in the monero rust library.
-pub enum RelayCategory {
- broadcasted, //< Public txes received via block/fluff
- relayable, //< Every tx not marked `relay_method::none`
- legacy, //< `relay_category::broadcasted` + `relay_method::none` for rpc relay requests or historical reasons
- all, //< Everything in the db
-}
-
-fn matches_category(relay_method: RelayMethod, relay_category: RelayCategory) -> bool {
- todo!()
-}
-
-/**
- * @brief a struct containing output metadata
- */
-pub struct output_data_t {
- pubkey: monero::util::key::PublicKey, //< the output's public key (for spend verification)
- unlock_time: u64, //< the output's unlock time (or height)
- height: u64, //< the height of the block which created the output
- commitment: monero::util::ringct::Key, //< the output's amount commitment (for spend verification)
-}
-
-pub struct tx_data_t {
- tx_id: u64,
- unlock_time: u64,
- block_id: u64,
-}
-
-pub struct alt_block_data_t {
- height: u64,
- cumulative_weight: u64,
- cumulative_difficulty_low: u64,
- cumulative_difficulty_high: u64,
- already_generated_coins: u64,
-}
-
-pub struct txpool_tx_meta_t {
- max_used_block_id: monero::cryptonote::hash::Hash,
- last_failed_id: monero::cryptonote::hash::Hash,
- weight: u64,
- fee: u64,
- max_used_block_height: u64,
- last_failed_height: u64,
- receive_time: u64,
- last_relayed_time: u64, //< If received over i2p/tor, randomized forward time. If Dandelion++stem, randomized embargo time. Otherwise, last relayed timestamp
- // 112 bytes
- kept_by_block: u8,
- relayed: u8,
- do_not_relay: u8,
- double_spend_seen: u8,
- pruned: u8,
- is_local: u8,
- dandelionpp_stem: u8,
- is_forwarding: u8,
- bf_padding: u8,
-
- padding: [u8; 76],
-}
-
-impl txpool_tx_meta_t {
- fn set_relay_method(relay_method: RelayMethod) {}
-
- fn upgrade_relay_method(relay_method: RelayMethod) -> bool {
- todo!()
- }
-
- /// See `relay_category` description
- fn matches(category: RelayCategory) -> bool {
- return matches_category(todo!(), category);
- }
-}
-
-pub enum OBJECT_TYPE {
- BLOCK,
- BLOCK_BLOB,
- TRANSACTION,
- TRANSACTION_BLOB,
- OUTPUTS,
- TXPOOL,
-}
-
-#[non_exhaustive] // < to remove
-#[allow(dead_code)] // < to remove
-#[derive(Error, Debug)]
-pub enum DB_FAILURES {
- #[error("DB_ERROR: `{0}`. The database is likely corrupted.")]
- DB_ERROR(String),
- #[error("DB_ERROR_TXN_START: `{0}`. The database failed starting a txn.")]
- DB_ERROR_TXN_START(String),
- #[error("DB_OPEN_FAILURE: Failed to open the database.")]
- DB_OPEN_FAILURE,
- #[error("DB_CREATE_FAILURE: Failed to create the database.")]
- DB_CREATE_FAILURE,
- #[error("DB_SYNC_FAILURE: Failed to sync the database.")]
- DB_SYNC_FAILURE,
- #[error("BLOCK_DNE: `{0}`. The block requested does not exist")]
- BLOCK_DNE(String),
- #[error("BLOCK_PARENT_DNE: `{0}` The parent of the block does not exist")]
- BLOCK_PARENT_DNE(String),
- #[error("BLOCK_EXISTS. The block to be added already exists!")]
- BLOCK_EXISTS,
- #[error("BLOCK_INVALID: `{0}`. The block to be added did not pass validation!")]
- BLOCK_INVALID(String),
- #[error("TX_EXISTS. The transaction to be added already exists!")]
- TX_EXISTS,
- #[error("TX_DNE: `{0}`. The transaction requested does not exist!")]
- TX_DNE(String),
- #[error("OUTPUTS_EXISTS. The output to be added already exists!")]
- OUTPUT_EXISTS,
- #[error("OUTPUT_DNE: `{0}`. The output requested does not exist")]
- OUTPUT_DNE(String),
- #[error("KEY_IMAGE_EXISTS. The spent key imge to be added already exists!")]
- KEY_IMAGE_EXISTS,
-
- #[error("ARITHMETIC_COUNT: `{0}`. An error occured due to a bad arithmetic/count logic")]
- ARITHEMTIC_COUNT(String),
- #[error("HASH_DNE. ")]
- HASH_DNE(Option),
-}
-
-pub trait KeyValueDatabase {
- fn add_data_to_cf(cf: &str, data: &D) -> Result;
-}
-
-pub trait BlockchainDB: KeyValueDatabase {
- // supposed to be private
-
- // TODO: understand
-
- //fn remove_block() -> Result<(), DB_FAILURES>; useless as it just delete data from the table. pop_block that use it internally also use remove_transaction to literally delete the block. Can be implemented without
-
- fn add_spent_key() -> Result<(), DB_FAILURES>;
-
- fn remove_spent_key() -> Result<(), DB_FAILURES>;
-
- fn get_tx_amount_output_indices(tx_id: u64, n_txes: usize) -> Vec>;
-
- fn has_key_image(img: &monero::blockdata::transaction::KeyImage) -> bool;
-
- fn prune_outputs(amount: u64);
-
- fn get_blockchain_pruning_seed() -> u32;
-
- // variables part.
- // uint64_t num_calls = 0; //!< a performance metric
- // uint64_t time_blk_hash = 0; //!< a performance metric
- // uint64_t time_add_block1 = 0; //!< a performance metric
- // uint64_t time_add_transaction = 0; //!< a performance metric
-
- // supposed to be protected
-
- // mutable uint64_t time_tx_exists = 0; //!< a performance metric
- // uint64_t time_commit1 = 0; //!< a performance metric
- // bool m_auto_remove_logs = true; //!< whether or not to automatically remove old logs
-
- // HardFork* m_hardfork; | protected: int *m_hardfork
-
- // bool m_open;
- // mutable epee::critical_section m_synchronization_lock; //!< A lock, currently for when BlockchainLMDB needs to resize the backing db file
-
- // supposed to be public
-
- /* handled by the DB.
- fn batch_start(batch_num_blocks: u64, batch_bytes: u64) -> Result;
-
- fn batch_abort() -> Result<(),DB_FAILURES>;
-
- fn set_batch_transactions() -> Result<(),DB_FAILURES>;
-
- fn block_wtxn_start();
- fn block_wtxn_stop();
- fn block_wtxn_abort();
-
- fn block_rtxn_start();
- fn block_rtxn_stop();
- fn block_rtxn_abort();
- */
-
- //fn set_hard_fork(); // (HardFork* hf)
-
- //fn add_block_public() -> Result;
-
- //fn block_exists(h: monero::cryptonote::hash::Hash, height: u64) -> bool;
-
- // fn tx_exists(h: monero::cryptonote::hash::Hash, tx_id: Option) -> Result<(),()>; // Maybe error should be DB_FAILURES, not specified in docs
-
- //fn tx_exist(h: monero::cryptonote::hash::Hash, tx: monero::Transaction) -> bool;
-
- //fn get_tx_blob(h: monero::cryptonote::hash::Hash, tx: String) -> bool;
-
- //fn get_pruned_tx_blob(h: monero::cryptonote::hash::Hash, tx: &mut String) -> bool;
-
- //fn update_pruning() -> bool;
-
- //fn check_pruning() -> bool;
-
- //fn get_max_block_size() -> u64; It is never used
-
- //fn add_max_block_size() -> u64; For reason above
-
- //fn for_all_txpool_txes(wat: fn(wat1: &monero::Hash, wat2: &txpool_tx_meta_t, wat3: &String) -> bool, include_blob: bool, category: RelayCategory) -> Result;
-
- //fn for_all_keys_images(wat: fn(ki: &monero::blockdata::transaction::KeyImage) -> bool) -> Result;
-
- //fn for_blocks_range(h1: &u64, h2: &u64, wat: fn(u: u64, h: &monero::Hash, blk: &Block) -> bool) -> Result; // u: u64 should be mut u: u64
-
- //fn for_all_transactions(wat: fn(h: &monero::Hash, tx: &monero::Transaction) -> bool, pruned: bool) -> Result;
-
- //fn for_all_outputs();
-
- //fn for_all_alt_blocks();
-
- // ------------------------------------------| Blockchain |------------------------------------------------------------
-
- /// `height` fetch the current blockchain height.
- ///
- /// Return the current blockchain height. In case of failures, a DB_FAILURES will be return.
- ///
- /// No parameters is required.
- fn height(&mut self) -> Result;
-
- /// `set_hard_fork_version` sets which hardfork version a height is on.
- ///
- /// In case of failures, a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `height`: is the height where the hard fork happen.
- /// `version`: is the version of the hard fork.
- fn set_hard_fork_version(&mut self);
-
- /// `get_hard_fork_version` checks which hardfork version a height is on.
- ///
- /// In case of failures, a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `height:` is the height to check.
- fn get_hard_fork_version(&mut self);
-
- /// May not need to be used
- fn fixup(&mut self);
-
- // -------------------------------------------| Outputs |------------------------------------------------------------
-
- /// `add_output` add an output data to it's storage .
- ///
- /// It internally keep track of the global output count. The global output count is also used to index outputs based on
- /// their order of creations.
- ///
- /// Should return the amount output index. In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `tx_hash`: is the hash of the transaction where the output comes from.
- /// `output`: is the output to store.
- /// `index`: is the local output's index (from transaction).
- /// `unlock_time`: is the unlock time (height) of the output.
- /// `commitment`: is the RingCT commitment of this output.
- fn add_output(
- &mut self,
- tx_hash: &Hash,
- output: &Hash,
- index: TxOutIndex,
- unlock_time: u64,
- commitment: RctSig,
- ) -> Result;
-
- /// `add_tx_amount_output_indices` store amount output indices for a tx's outputs
- ///
- /// TODO
- fn add_tx_amount_output_indices() -> Result<(), DB_FAILURES>;
-
- /// `get_output_key` get some of an output's data
- ///
- /// Return the public key, unlock time, and block height for the output with the given amount and index, collected in a struct
- /// In case of failures, a `DB_FAILURES` will be return. Precisely, if the output cannot be found, an `OUTPUT_DNE` error will be return.
- /// If any of the required part for the final struct isn't found, a `DB_ERROR` will be return
- ///
- /// Parameters:
- /// `amount`: is the corresponding amount of the output
- /// `index`: is the output's index (indexed by amount)
- /// `include_commitment` : `true` by default.
- fn get_output_key(
- &mut self,
- amount: u64,
- index: u64,
- include_commitmemt: bool,
- ) -> Result;
-
- /// `get_output_tx_and_index_from_global`gets an output's transaction hash and index from output's global index.
- ///
- /// Return a tuple containing the transaction hash and the output index. In case of failures, a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `index`: is the output's global index.
- fn get_output_tx_and_index_from_global(&mut self, index: u64) -> Result;
-
- /// `get_output_key_list` gets outputs' metadata from a corresponding collection.
- ///
- /// Return a collection of output's metadata. In case of failurse, a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `amounts`: is the collection of amounts corresponding to the requested outputs.
- /// `offsets`: is a collection of outputs' index (indexed by amount).
- /// `allow partial`: `false` by default.
- fn get_output_key_list(
- &mut self,
- amounts: &Vec,
- offsets: &Vec,
- allow_partial: bool,
- ) -> Result, DB_FAILURES>;
-
- /// `get_output_tx_and_index` gets an output's transaction hash and index
- ///
- /// Return a tuple containing the transaction hash and the output index. In case of failures, a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `amount`: is the corresponding amount of the output
- /// `index`: is the output's index (indexed by amount)
- fn get_output_tx_and_index(&mut self, amount: u64, index: u64) -> Result;
-
- /// `get_num_outputs` fetches the number of outputs of a given amount.
- ///
- /// Return a count of outputs of the given amount. in case of failures a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `amount`: is the output amount being looked up.
- fn get_num_outputs(amount: &u64) -> Result;
-
- // -----------------------------------------| Transactions |----------------------------------------------------------
-
- /// `add_transaction` add the corresponding transaction and its hash to the specified block.
- ///
- /// In case of failures, a DB_FAILURES will be return. Precisely, a TX_EXISTS will be returned if the
- /// transaction to be added already exists in the database.
- ///
- /// Parameters:
- /// `blk_hash`: is the hash of the block which inherit the transaction
- /// `tx`: is obviously the transaction to add
- /// `tx_hash`: is the hash of the transaction.
- /// `tx_prunable_hash_ptr`: is the hash of the prunable part of the transaction.
- fn add_transaction(
- &mut self,
- blk_hash: &Hash,
- tx: Transaction,
- tx_hash: &Hash,
- tx_prunable_hash_ptr: &Hash,
- ) -> Result<(), DB_FAILURES>;
-
- /// `add_transaction_data` add the specified transaction data to its storage.
- ///
- /// It only add the transaction blob and tx's metadata, not the collection of outputs.
- ///
- /// Return the hash of the transaction added. In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `blk_hash`: is the hash of the block containing the transaction
- /// `tx_and_hash`: is a tuple containing the transaction and it's hash
- /// `tx_prunable_hash`: is the hash of the prunable part of the transaction
- fn add_transaction_data(
- &mut self,
- blk_hash: &Hash,
- tx_and_hash: (Transaction, &Hash),
- tx_prunable_hash: &Hash,
- ) -> Result;
-
- /// `remove_transaction_data` remove data about a transaction specified by its hash.
- ///
- /// In case of failures, a `DB_FAILURES` will be return. Precisely, a `TX_DNE` will be return if the specified transaction can't be found.
- ///
- /// Parameters:
- /// `tx_hash`: is the transaction's hash to remove data from.
- fn remove_transaction_data(&mut self, tx_hash: &Hash) -> Result<(), DB_FAILURES>;
-
- /// `get_tx_count` fetches the total number of transactions stored in the database
- ///
- /// Should return the count. In case of failure, a DB_FAILURES will be return.
- ///
- /// No parameters is required.
- fn get_tx_count(&mut self) -> Result;
-
- /// `tx_exists` check if a transaction exist with the given hash.
- ///
- /// Return `true` if the transaction exist, `false` otherwise. In case of failure, a DB_FAILURES will be return.
- ///
- /// Parameters :
- /// `h` is the given hash of transaction to check.
- /// `tx_id` is an optional mutable reference to get the transaction id out of the found transaction.
- fn tx_exists(&mut self, h: &Hash, tx_id: &mut Option) -> Result;
-
- /// `get_tx_unlock_time` fetch a transaction's unlock time/height
- ///
- /// Should return the unlock time/height in u64. In case of failure, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of the transaction to check.
- fn get_tx_unlock_time(&mut self, h: &Hash) -> Result;
-
- /// `get_tx` fetches the transaction with the given hash.
- ///
- /// Should return the transaction. In case of failure, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of transaction to fetch.
- fn get_tx(&mut self, h: &Hash) -> Result;
-
- /// `get_pruned_tx` fetches the transaction base with the given hash.
- ///
- /// Should return the transaction. In case of failure, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of transaction to fetch.
- fn get_pruned_tx(&mut self, h: &Hash) -> Result;
-
- /// `get_tx_list` fetches the transactions with given hashes.
- ///
- /// Should return a vector with the requested transactions. In case of failures, a DB_FAILURES will be return.
- /// Precisly, a HASH_DNE error will be returned with the correspondig hash of transaction that is not found in the DB.
- ///
- /// `hlist`: is the given collection of hashes correspondig to the transactions to fetch.
- fn get_tx_list(&mut self, hlist: &Vec) -> Result, DB_FAILURES>;
-
- /// `get_tx_blob` fetches the transaction blob with the given hash.
- ///
- /// Should return the transaction blob. In case of failure, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of the transaction to fetch.
- fn get_tx_blob(&mut self, h: &Hash) -> Result;
-
- /// `get_pruned_tx_blob` fetches the pruned transaction blob with the given hash.
- ///
- /// Should return the transaction blob. In case of failure, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of the transaction to fetch.
- fn get_pruned_tx_blob(&mut self, h: &Hash) -> Result;
-
- /// `get_prunable_tx_blob` fetches the prunable transaction blob with the given hash.
- ///
- /// Should return the transaction blob, In case of failure, a DB_FAILURES, will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of the transaction to fetch.
- fn get_prunable_tx_blob(&mut self, h: &Hash) -> Result;
-
- /// `get_prunable_tx_hash` fetches the prunable transaction hash
- ///
- /// Should return the hash of the prunable transaction data. In case of failures, a DB_FAILURES, will be return.
- ///
- /// Parameters:
- /// `tx_hash`: is the given hash of the transaction to fetch.
- fn get_prunable_tx_hash(&mut self, tx_hash: &Hash) -> Result;
-
- /// `get_pruned_tx_blobs_from` fetches a number of pruned transaction blob from the given hash, in canonical blockchain order.
- ///
- /// Should return the pruned transactions stored from the one with the given hash. In case of failure, a DB_FAILURES will be return.
- /// Precisly, an ARITHMETIC_COUNT error will be returned if the first transaction does not exist or their are fewer transactions than the count.
- ///
- /// Parameters:
- /// `h`: is the given hash of the first transaction/
- /// `count`: is the number of transaction to fetch in canoncial blockchain order.
- fn get_pruned_tx_blobs_from(&mut self, h: &Hash, count: usize) -> Result, DB_FAILURES>;
-
- /// `get_tx_block_height` fetches the height of a transaction's block
- ///
- /// Should return the height of the block containing the transaction with the given hash. In case
- /// of failures, a DB FAILURES will be return. Precisely, a TX_DNE error will be return if the transaction cannot be found.
- ///
- /// Parameters:
- /// `h`: is the fiven hash of the first transaction
- fn get_tx_block_height(&mut self, h: &Hash) -> Result;
-
- // -----------------------------------------| Blocks |----------------------------------------------------------
-
- /// `add_block` add the block and metadata to the db.
- ///
- /// In case of failures, a `DB_FAILURES` will be return. Precisely, a BLOCK_EXISTS error will be returned if
- /// the block to be added already exist. a BLOCK_INVALID will be returned if the block to be added did not pass validation.
- ///
- /// Parameters:
- /// `blk`: is the block to be added
- /// `block_weight`: is the weight of the block (data's total)
- /// `long_term_block_weight`: is the long term weight of the block (data's total)
- /// `cumulative_difficulty`: is the accumulated difficulty at this block.
- /// `coins_generated` is the number of coins generated after this block.
- /// `blk_hash`: is the hash of the block.
- fn add_block(
- blk: Block,
- blk_hash: Hash,
- block_weight: u64,
- long_term_block_weight: u64,
- cumulative_difficulty: u128,
- coins_generated: u64,
- ) -> Result<(), DB_FAILURES>;
-
- /// `pop_block` pops the top block off the blockchain.
- ///
- /// Return the block that was popped. In case of failures, a `DB_FAILURES` will be return.
- ///
- /// No parameters is required.
- fn pop_block(&mut self) -> Result;
-
- /// `blocks_exists` check if the given block exists
- ///
- /// Return `true` if the block exist, `false` otherwise. In case of failures, a `DB_FAILURES` will be return.
- ///
- /// Parameters:
- /// `h`: is the given hash of the requested block.
- fn block_exists(&mut self, h: &Hash) -> Result;
-
- /// `get_block` fetches the block with the given hash.
- ///
- /// Return the requested block. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `h`: is the given hash of the requested block.
- fn get_block(&mut self, h: &Hash) -> Result;
-
- /// `get_block_from_height` fetches the block located at the given height.
- ///
- /// Return the requested block. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the height where the requested block is located.
- fn get_block_from_height(&mut self, height: u64) -> Result;
-
- /// `get_block_from_range` fetches the blocks located from and to the specified heights.
- ///
- /// Return the requested blocks. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if at least requested block can't be found. If the range requested past the end of the blockchain,
- /// an `ARITHMETIC_COUNT` error will be return.
- ///
- /// Parameters:
- /// `height_range`: is the range of height where the requested blocks are located.
- fn get_blocks_from_range(&mut self, height_range: Range) -> Result, DB_FAILURES>;
-
- /// `get_block_blob` fetches the block blob with the given hash.
- ///
- /// Return the requested block blob. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `h`: is the given hash of the requested block.
- fn get_block_blob(&mut self, h: &Hash) -> Result;
-
- /// `get_block_blob_from_height` fetches the block blob located at the given height in the blockchain.
- ///
- /// Return the requested block blob. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height of the corresponding block blob to fetch.
- fn get_block_blob_from_height(&mut self, height: u64) -> Result;
-
- /// `get_block_header` fetches the block's header with the given hash.
- ///
- /// Return the requested block header. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `h`: is the given hash of the requested block.
- fn get_block_header(&mut self, h: &Hash) -> Result;
-
- /// `get_block_hash_from_height` fetch block's hash located at the given height.
- ///
- /// Return the hash of the block with the given height. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height where the requested block is located.
- fn get_block_hash_from_height(&mut self, height: u64) -> Result;
-
- /// `get_blocks_hashes_from_range` fetch blocks' hashes located from, between and to the given heights.
- ///
- /// Return a collection of hases corresponding to the scoped blocks. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if at least one of the requested blocks can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height where the requested block is located.
- fn get_blocks_hashes_from_range(&mut self, range: Range) -> Result, DB_FAILURES>;
-
- /// `get_top_block` fetch the last/top block of the blockchain
- ///
- /// Return the last/top block of the blockchain. In case of failures, a DB_FAILURES, will be return.
- ///
- /// No parameters is required.
- fn get_top_block(&mut self) -> Block;
-
- /// `get_top_block_hash` fetch the block's hash located at the top of the blockchain (the last one).
- ///
- /// Return the hash of the last block. In case of failures, a DB_FAILURES will be return.
- ///
- /// No parameters is required
- fn get_top_block_hash(&mut self) -> Result;
-
- // ! TODO: redefine the result & docs. see what could be improved. Do we really need this function?
- /// `get_blocks_from` fetches a variable number of blocks and transactions from the given height, in canonical blockchain order as long as it meets the parameters.
- ///
- /// Should return the blocks stored starting from the given height. The number of blocks returned is variable, based on the max_size defined. There will be at least `min_block_count`
- /// if possible, even if this contravenes max_tx_count. In case of failures, a `DB_FAILURES` error will be return.
- ///
- /// Parameters:
- /// `start_height`: is the given height to start from.
- /// `min_block_count`: is the minimum number of blocks to return. If there are fewer blocks, it'll return fewer blocks than the minimum.
- /// `max_block_count`: is the maximum number of blocks to return.
- /// `max_size`: is the maximum size of block/transaction data to return (can be exceeded on time if min_count is met).
- /// `max_tx_count`: is the maximum number of txes to return.
- /// `pruned`: is whether to return full or pruned tx data.
- /// `skip_coinbase`: is whether to return or skip coinbase transactions (they're in blocks regardless).
- /// `get_miner_tx_hash`: is whether to calculate and return the miner (coinbase) tx hash.
- fn get_blocks_from(
- &mut self,
- start_height: u64,
- min_block_count: u64,
- max_block_count: u64,
- max_size: usize,
- max_tx_count: u64,
- pruned: bool,
- skip_coinbase: bool,
- get_miner_tx_hash: bool,
- ) -> Result)>, DB_FAILURES>;
-
- /// `get_block_height` gets the height of the block with a given hash
- ///
- /// Return the requested height.
- fn get_block_height(&mut self, h: &Hash) -> Result;
-
- /// `get_block_weights` fetch the block's weight located at the given height.
- ///
- /// Return the requested block weight. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height where the requested block is located.
- fn get_block_weight(&mut self, height: u64) -> Result;
-
- /// `get_block_weights` fetch the last `count` blocks' weights.
- ///
- /// Return a collection of weights. In case of failures, a `DB_FAILURES` will be return. Precisely, an 'ARITHMETIC_COUNT'
- /// error will be returned if there are fewer than `count` blocks.
- ///
- /// Parameters:
- /// `start_height`: is the height to seek before collecting block weights.
- /// `count`: is the number of last blocks' weight to fetch.
- fn get_block_weights(&mut self, start_height: u64, count: usize) -> Result, DB_FAILURES>;
-
- /// `get_block_already_generated_coins` fetch a block's already generated coins
- ///
- /// Return the total coins generated as of the block with the given height. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height of the block to seek.
- fn get_block_already_generated_coins(&mut self, height: u64) -> Result;
-
- /// `get_block_long_term_weight` fetch a block's long term weight.
- ///
- /// Should return block's long term weight. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height where the requested block is located.
- fn get_block_long_term_weight(&mut self, height: u64) -> Result;
-
- /// `get_long_term_block_weights` fetch the last `count` blocks' long term weights
- ///
- /// Should return a collection of blocks' long term weights. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found. If there are fewer than `count` blocks, the returned collection will be
- /// smaller than `count`.
- ///
- /// Parameters:
- /// `start_height`: is the height to seek before collecting block weights.
- /// `count`: is the number of last blocks' long term weight to fetch.
- fn get_long_term_block_weights(&mut self, height: u64, count: usize) -> Result, DB_FAILURES>;
-
- /// `get_block_timestamp` fetch a block's timestamp.
- ///
- /// Should return the timestamp of the block with given height. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `height`: is the given height where the requested block to fetch timestamp is located.
- fn get_block_timestamp(&mut self, height: u64) -> Result;
-
- /// `get_block_cumulative_rct_outputs` fetch a blocks' cumulative number of RingCT outputs
- ///
- /// Should return the number of RingCT outputs in the blockchain up to the blocks located at the given heights. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `heights`: is the collection of height to check for RingCT distribution.
- fn get_block_cumulative_rct_outputs(&mut self, heights: Vec) -> Result, DB_FAILURES>;
-
- /// `get_top_block_timestamp` fetch the top block's timestamp
- ///
- /// Should reutnr the timestamp of the most recent block. In case of failures, a DB_FAILURES will be return.
- ///
- /// No parameters is required.
- fn get_top_block_timestamp(&mut self) -> Result;
-
- /// `correct_block_cumulative_difficulties` correct blocks cumulative difficulties that were incorrectly calculated due to the 'difficulty drift' bug
- ///
- /// Should return nothing. In case of failures, a DB_FAILURES will be return. Precisely, a `BLOCK_DNE`
- /// error will be returned if the requested block can't be found.
- ///
- /// Parameters:
- /// `start_height`: is the height of the block where the drifts start.
- /// `new_cumulative_difficulties`: is the collection of new cumulative difficulties to be stored
- fn correct_block_cumulative_difficulties(
- &mut self,
- start_height: u64,
- new_cumulative_difficulties: Vec,
- ) -> Result<(), DB_FAILURES>;
-
- // --------------------------------------------| Alt-Block |------------------------------------------------------------
-
- /// `add_alt_block` add a new alternative block.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// blkid: is the hash of the original block
- /// data: is the metadata for the block
- /// blob: is the blobdata of this alternative block.
- fn add_alt_block(&mut self, blkid: &Hash, data: &alt_block_data_t, blob: &Blobdata) -> Result<(), DB_FAILURES>;
-
- /// `get_alt_block` gets the specified alternative block.
- ///
- /// Return a tuple containing the blobdata of the alternative block and its metadata. In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `blkid`: is the hash of the requested alternative block.
- fn get_alt_block(&mut self, blkid: &Hash) -> Result<(alt_block_data_t, Blobdata), DB_FAILURES>;
-
- /// `remove_alt_block` remove the specified alternative block
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `blkid`: is the hash of the alternative block to remove.
- fn remove_alt_block(&mut self, blkid: &Hash) -> Result<(), DB_FAILURES>;
-
- /// `get_alt_block` gets the total number of alternative blocks stored
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// No parameters is required.
- fn get_alt_block_count(&mut self) -> Result;
-
- /// `drop_alt_block` drop all alternative blocks.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// No parameters is required.
- fn drop_alt_blocks(&mut self) -> Result<(), DB_FAILURES>;
-
- // --------------------------------------------| TxPool |------------------------------------------------------------
-
- /// `add_txpool_tx` add a Pool's transaction to the database.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `txid`: is the hash of the transaction to add.
- /// `blob`: is the blobdata of the transaction to add.
- /// `details`: is the metadata of the transaction pool at this specific transaction.
- fn add_txpool_tx(&mut self, txid: &Hash, blob: &BlobdataRef, details: &txpool_tx_meta_t)
- -> Result<(), DB_FAILURES>;
-
- /// `update_txpool_tx` replace pool's transaction metadata.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `txid`: is the hash of the transaction to edit
- /// `details`: is the new metadata to insert.
- fn update_txpool_tx(&mut self, txid: &monero::Hash, details: &txpool_tx_meta_t) -> Result<(), DB_FAILURES>;
-
- /// `get_txpool_tx_count` gets the number of transactions in the txpool.
- ///
- /// Return the number of transaction in the txpool. In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `tx_category`: is the relay's category where the tx are coming from. (RelayCategory::broadcasted by default)
- fn get_txpool_tx_count(&mut self, tx_category: RelayCategory) -> Result;
-
- /// `txpool_has_tx`checks if the specified transaction exist in the transaction's pool and if it belongs
- /// to the specified category.
- ///
- /// Return `true` if the condition above are met, `false otherwise`. In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `txid`: is the hash of the transaction to check for
- /// `tx_category`: is the relay's category where the tx is supposed to come from.
- fn txpool_has_tx(&mut self, txid: &Hash, tx_category: &RelayCategory) -> Result;
-
- /// `remove_txpool_tx` remove the specified transaction from the transaction pool.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `txid`: is the hash of the transaction to remove.
- fn remove_txpool_tx(&mut self, txid: &Hash) -> Result<(), DB_FAILURES>;
-
- /// `get_txpool_tx_meta` gets transaction's pool metadata recorded at the specified transaction.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `txid`: is the hash of metadata's transaction hash.
- fn get_txpool_tx_meta(&mut self, txid: &Hash) -> Result;
-
- /// `get_txpool_tx_blob` gets the txpool transaction's blob.
- ///
- /// In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `txid`: is the hash of the transaction to fetch blobdata from.
- /// `tx_category`: is the relay's category where the tx are coming from. < monerod note: for filtering out hidden/private txes.
- fn get_txpool_tx_blob(&mut self, txid: &Hash, tx_category: RelayCategory) -> Result;
-
- /// `txpool_tx_matches_category` checks if the corresponding transaction belongs to the specified category.
- ///
- /// Return `true` if the transaction belongs to the category, `false` otherwise. In case of failures, a DB_FAILURES will be return.
- ///
- /// Parameters:
- /// `tx_hash`: is the hash of the transaction to lookup.
- /// `category`: is the relay's category to check.
- fn txpool_tx_matches_category(&mut self, tx_hash: &Hash, category: RelayCategory) -> Result;
-}
-
-// functions defined as useless : init_options(), is_open(), reset_stats(), show_stats(), open(), close(), get_output_histogram(), safesyncmode, get_filenames(), get_db_name(), remove_data_file(), lock(), unlock(), is_read_only(), get_database_size(), get_output_distribution(), set_auto_remove_logs(), check_hard_fork_info(), drop_hard_fork_info(), get_indexing_base();
diff --git a/blockchain_db/src/rocksdb.rs b/blockchain_db/src/rocksdb.rs
deleted file mode 100644
index 3866e8e5..00000000
--- a/blockchain_db/src/rocksdb.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2023 Cuprate Contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-//!
-//! RocksDB implementation.
-//!
-//! Database Schema:
-//! ---------------------------------------
-//! Column | Key | Data
-//! ---------------------------------------
-//! *block*------------------------------------------------------
-//!
-//! blocks height {blob}
-//! heights hash height
-//! b_metadata height {b_metdata}
-//!
-//! *transactions*-----------------------------------------------
-//!
-//! tx_prefix tx ID {blob}
-//! tx_prunable tx ID {blob}
-//! tx_hash tx ID hash
-//! tx_opti_h hash height
-//! tx_outputs tx ID {amount,output,indices}
-//!
-//! *outputs*----------------------------------------------------
-//!
-//! ouputs_txs op ID {tx hash, l_index}
-//! outputs_am amount {amount output index, metdata}
-//!
-//! *spent keys*--------------------------------------------------
-//!
-//! spent_keys hash well... obvious?
-//!
-//! *tx pool*------------------------------------------------------
-//!
-//! txp_meta hash {txp_metadata}
-//! txp_blob hash {blob}
-//!
-//! *alt blocks*----------------------------------------------------
-//!
-//! alt_blocks hash {bock data, block blob}
-
-// Defining tables
-const CF_BLOCKS: &str = "blocks";
-const CF_HEIGHTS: &str = "heights";
-const CF_BLOCK_METADATA: &str = "b_metadata";
-const CF_TX_PREFIX: &str = "tx_prefix";
-const CF_TX_PRUNABLE: &str = "tx_prunable";
-const CF_TX_HASH: &str = "tx_hash";
-const CF_TX_OPTI_H: &str = "tx_opti_h";
-const CF_TX_OUTPUTS: &str = "tx_outputs";
-const CF_OUTPUTS_TXS: &str = "outputs_txs";
\ No newline at end of file
diff --git a/cuprate/Cargo.toml b/cuprate/Cargo.toml
new file mode 100644
index 00000000..4379627e
--- /dev/null
+++ b/cuprate/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "cuprate-bin"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+clap = { version = "*", features = [] }
+clap_complete = "*"
+tracing = { workspace = true }
+tracing-subscriber = { workspace = true }
\ No newline at end of file
diff --git a/cuprate/src/cli.rs b/cuprate/src/cli.rs
new file mode 100644
index 00000000..9e7c8d7a
--- /dev/null
+++ b/cuprate/src/cli.rs
@@ -0,0 +1,50 @@
+use crate::CUPRATE_VERSION;
+use clap::{value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command};
+use tracing::{event, span, Level, Span};
+
+/// This function simply contains clap arguments
+pub fn args() -> ArgMatches {
+ Command::new("Cuprate")
+ .version(CUPRATE_VERSION)
+ .author("Cuprate's contributors")
+ .about("An upcoming experimental, modern, and secure monero node")
+ // Generic Arguments
+ .arg(
+ Arg::new("log")
+ .long("log-level")
+ .value_name("Level")
+ .help("Set the log level")
+ .value_parser(value_parser!(u8))
+ .default_value("1")
+ .long_help("Set the log level. There is 3 log level: <1~INFO, 2~DEBUG >3~TRACE.")
+ .required(false)
+ .action(ArgAction::Set),
+ )
+ .get_matches()
+}
+
+/// This function initialize the FmtSubscriber used by tracing to display event in the console. It send back a span used during runtime.
+pub fn init(matches: &ArgMatches) -> Span {
+ // Getting the log level from args
+ let log_level = matches.get_one::("log").unwrap();
+ let level_filter = match log_level {
+ 2 => Level::DEBUG,
+ x if x > &2 => Level::TRACE,
+ _ => Level::INFO,
+ };
+
+ // Initializing tracing subscriber and runtime span
+ let subscriber = tracing_subscriber::FmtSubscriber::builder()
+ .with_max_level(level_filter)
+ .with_target(false)
+ .finish();
+ tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber for tracing. We prefer to abort the node since without it you have no output in the console");
+ let runtime_span = span!(Level::INFO, "Runtime");
+ let _guard = runtime_span.enter();
+
+ // Notifying log level
+ event!(Level::INFO, "Log level set to {}", level_filter);
+
+ drop(_guard);
+ runtime_span
+}
diff --git a/cuprate/src/main.rs b/cuprate/src/main.rs
new file mode 100644
index 00000000..eb378f34
--- /dev/null
+++ b/cuprate/src/main.rs
@@ -0,0 +1,13 @@
+use tracing::{event, info, span, Level};
+
+pub mod cli;
+
+const CUPRATE_VERSION: &str = "0.1.0";
+
+fn main() {
+ // Collecting options
+ let matches = cli::args();
+
+ // Initializing tracing subscriber and runtime span
+ let _runtime_span = cli::init(&matches);
+}
diff --git a/database/Cargo.toml b/database/Cargo.toml
new file mode 100644
index 00000000..37c49ae4
--- /dev/null
+++ b/database/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "cuprate-database"
+version = "0.0.1"
+edition = "2021"
+license = "AGPL-3.0-only"
+
+# All Contributors on github
+authors=[
+ "SyntheticBird45 <@someoneelse495495:matrix.org>",
+ "Boog900"
+ ]
+
+[features]
+mdbx = ["dep:libmdbx"]
+hse = []
+
+[dependencies]
+monero = {workspace = true, features = ["serde"]}
+tiny-keccak = { version = "2.0", features = ["sha3"] }
+serde = { workspace = true}
+thiserror = {workspace = true }
+bincode = { workspace = true }
+libmdbx = { version = "0.3.1", optional = true }
+
+[build]
+linker="clang"
+rustflags=[
+ "-Clink-arg=-fuse-ld=mold",
+ "-Zcf-protection=full",
+ "-Zsanitizer=cfi",
+ "-Crelocation-model=pie",
+ "-Cstack-protector=all",
+]
\ No newline at end of file
diff --git a/blockchain_db/LICENSE b/database/LICENSE
similarity index 100%
rename from blockchain_db/LICENSE
rename to database/LICENSE
diff --git a/database/src/encoding.rs b/database/src/encoding.rs
new file mode 100644
index 00000000..aa4681d6
--- /dev/null
+++ b/database/src/encoding.rs
@@ -0,0 +1,78 @@
+//! ### Encoding module
+//! The encoding module contains a trait that permit compatibility between `monero-rs` consensus encoding/decoding logic and `bincode` traits.
+//! The database tables only accept types that implement [`bincode::Encode`] and [`bincode::Decode`] and since we can't implement these on `monero-rs` types directly
+//! we use a wrapper struct `Compat` that permit us to use `monero-rs`'s `consensus_encode`/`consensus_decode` functions under bincode traits.
+//! The choice of using `bincode` comes from performance measurement at encoding. Sometimes `bincode` implementations was 5 times faster than `monero-rs` impl.
+
+use bincode::{de::read::Reader, enc::write::Writer};
+use monero::consensus::{Decodable, Encodable};
+use std::{fmt::Debug, io::Read, ops::Deref};
+
+#[derive(Debug, Clone)]
+/// A single-tuple struct, used to contains monero-rs types that implement [`monero::consensus::Encodable`] and [`monero::consensus::Decodable`]
+pub struct Compat(pub T);
+
+/// A wrapper around a [`bincode::de::read::Reader`] type. Permit us to use [`std::io::Read`] and feed monero-rs functions with an actual `&[u8]`
+pub struct ReaderCompat<'src, R: Reader>(pub &'src mut R);
+
+// Actual implementation of `std::io::read` for `bincode`'s `Reader` types
+impl<'src, R: Reader> Read for ReaderCompat<'src, R> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result {
+ self.0
+ .read(buf)
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "bincode reader Error"))?;
+ Ok(buf.len())
+ }
+}
+
+// Convenient implementation. `Deref` and `From`
+impl Deref for Compat {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl From for Compat {
+ fn from(value: T) -> Self {
+ Compat(value)
+ }
+}
+
+// TODO: Investigate specialization optimization
+// Implementation of `bincode::Decode` for monero-rs `Decodable` type
+impl bincode::Decode for Compat {
+ fn decode(
+ decoder: &mut D,
+ ) -> Result {
+ Ok(Compat(
+ Decodable::consensus_decode(&mut ReaderCompat(decoder.reader()))
+ .map_err(|_| bincode::error::DecodeError::Other("Monero-rs decoding failed"))?,
+ ))
+ }
+}
+
+// Implementation of `bincode::BorrowDecode` for monero-rs `Decodable` type
+impl<'de, T: Encodable + Decodable + Debug> bincode::BorrowDecode<'de> for Compat {
+ fn borrow_decode>(
+ decoder: &mut D,
+ ) -> Result {
+ Ok(Compat(
+ Decodable::consensus_decode(&mut ReaderCompat(decoder.borrow_reader()))
+ .map_err(|_| bincode::error::DecodeError::Other("Monero-rs decoding failed"))?,
+ ))
+ }
+}
+
+// Implementation of `bincode::Encode` for monero-rs `Encodable` type
+impl bincode::Encode for Compat {
+ fn encode(
+ &self,
+ encoder: &mut E,
+ ) -> Result<(), bincode::error::EncodeError> {
+ let writer = encoder.writer();
+ let buf = monero::consensus::serialize(&self.0);
+ writer.write(&buf)
+ }
+}
diff --git a/database/src/error.rs b/database/src/error.rs
new file mode 100644
index 00000000..25b1f8d8
--- /dev/null
+++ b/database/src/error.rs
@@ -0,0 +1,53 @@
+//! ### Error module
+//! This module contains all errors abstraction used by the database crate. By implementing [`From`] to the specific errors of storage engine crates, it let us
+//! handle more easily any type of error that can happen. This module does **NOT** contain interpretation of these errors, as these are defined for Blockchain abstraction. This is another difference
+//! from monerod which interpret these errors directly in its database functions:
+//! ```cpp
+//! /**
+//! * @brief A base class for BlockchainDB exceptions
+//! */
+//! class DB_EXCEPTION : public std::exception
+//! ```
+//! see `blockchain_db/blockchain_db.h` in monerod `src/` folder for more details.
+
+#[derive(thiserror::Error, Debug)]
+/// `DB_FAILURES` is an enum for backend-agnostic, internal database errors. The `From` Trait must be implemented to the specific backend errors to match DB_FAILURES.
+pub enum DB_FAILURES {
+ #[error("MDBX returned an error {0}")]
+ MDBX_Error(#[from] libmdbx::Error),
+
+ #[error("\n Failed to encode some data : `{0}`")]
+ SerializeIssue(DB_SERIAL),
+
+ #[error("\nObject already exist in the database : {0}")]
+ AlreadyExist(&'static str),
+
+ #[error("NotFound? {0}")]
+ NotFound(&'static str),
+
+ #[error("\n `{0}`")]
+ Other(&'static str),
+
+ #[error(
+ "\n A transaction tried to commit to the db, but failed."
+ )]
+ FailedToCommit,
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum DB_SERIAL {
+ #[error("An object failed to be serialized into bytes. It is likely an issue from monero-rs library. Please report this error on cuprate's github : https://github.com/Cuprate/cuprate/issues")]
+ ConsensusEncode,
+
+ #[error("Bytes failed to be deserialized into the requested object. It is likely an issue from monero-rs library. Please report this error on cuprate's github : https://github.com/Cuprate/cuprate/issues")]
+ ConsensusDecode(Vec),
+
+ #[error("monero-rs encoding|decoding logic failed : {0}")]
+ MoneroEncode(#[from] monero::consensus::encode::Error),
+
+ #[error("Bincode failed to decode a type from the database : {0}")]
+ BincodeDecode(#[from] bincode::error::DecodeError),
+
+ #[error("Bincode failed to encode a type for the database : {0}")]
+ BincodeEncode(#[from] bincode::error::EncodeError),
+}
diff --git a/database/src/hse.rs b/database/src/hse.rs
new file mode 100644
index 00000000..2d07b3f7
--- /dev/null
+++ b/database/src/hse.rs
@@ -0,0 +1,11 @@
+/* There is nothing here as no wrapper exist for HSE yet */
+
+/* KVS supported functions :
+-------------------------------------
+hse_kvs_delete
+hse_kvs_get
+hse_kvs_name_get
+hse_kvs_param_get
+hse_kvs_prefix_delete
+hse_kvs_put
+*/
\ No newline at end of file
diff --git a/database/src/interface.rs b/database/src/interface.rs
new file mode 100644
index 00000000..cacd0fd6
--- /dev/null
+++ b/database/src/interface.rs
@@ -0,0 +1,1036 @@
+//! ### Interface module
+//! This module contains all the implementations of the database interface.
+//! These are all the functions that can be executed through DatabaseRequest.
+//!
+//! The following functions have been separated through 6 categories:
+//! -| Blockchain |-
+//! -| Blocks |-
+//! -| Transactions |-
+//! -| Outputs |-
+//! -| SpentKeys |-
+//! -| Categories |-
+
+// TODO: add_transaction() not finished due to ringct zeroCommit missing function
+// TODO: in add_transaction_data() Investigate unprunable_size == 0 condition of monerod
+// TODO: Do we need correct_block_cumulative_difficulties()
+// TODO: remove_tx_outputs() can be done otherwise since we don't use global output index
+// TODO: Check all documentations
+
+use crate::{
+ database::{Database, Interface},
+ error::DB_FAILURES,
+ table::{self},
+ transaction::{self, DupCursor, DupWriteCursor, Transaction, WriteCursor, WriteTransaction},
+ types::{
+ calculate_prunable_hash, get_transaction_prunable_blob, AltBlock, BlockMetadata,
+ OutputMetadata, TransactionPruned, TxIndex, TxOutputIdx,
+ },
+ BINCODE_CONFIG,
+};
+use monero::{
+ blockdata::transaction::KeyImage, cryptonote::hash::Hashable, util::ringct::Key, Block,
+ BlockHeader, Hash, TxIn, TxOut,
+};
+
+// Implementation of Interface
+impl<'service, D: Database<'service>> Interface<'service, D> {
+ // --------------------------------| Blockchain |--------------------------------
+
+ /// `height` fetch the current blockchain height.
+ ///
+ /// Return the current blockchain height. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// No parameters is required.
+ fn height(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ ro_tx.num_entries::().map(|n| n as u64)
+ }
+
+ // ----------------------------------| Blocks |---------------------------------
+
+ /// `add_block` add the block and metadata to the db.
+ ///
+ /// In case of failures, a `DB_FAILURES`
+ ///
+ /// Parameters:
+ /// `blk`: is the block to be added
+ /// `txs`: is the collection of transactions related to this block
+ /// `block_weight`: is the weight of the block (data's total)
+ /// `long_term_block_weight`: is the long term weight of the block (data's total)
+ /// `cumulative_difficulty`: is the accumulated difficulty at this block.
+ /// `coins_generated` is the number of coins generated after this block.
+ fn add_block(
+ &'service self,
+ blk: Block,
+ txs: Vec,
+ block_weight: u64,
+ long_term_block_weight: u64,
+ cumulative_difficulty: u128,
+ coins_generated: u64,
+ ) -> Result<(), DB_FAILURES> {
+ // *sanity*
+ if blk.tx_hashes.len() != txs.len() {
+ return Err(DB_FAILURES::Other("sanity : Inconsistent tx/hashed sizes"));
+ }
+
+ let blk_hash = blk.id();
+
+ // let parent_height = self.height()?;
+
+ let mut num_rct_outs = 0u64;
+ self.add_transaction(blk.miner_tx.clone())?;
+
+ if blk.miner_tx.prefix.version.0 == 2 {
+ num_rct_outs += blk.miner_tx.prefix.outputs.len() as u64;
+ }
+
+ // let mut tx_hash = Hash::null();
+ for tx in txs.into_iter()
+ /*.zip(0usize..)*/
+ {
+ // tx_hash = blk.tx_hashes[tx.1];
+ for out in tx.prefix.outputs.iter() {
+ if out.amount.0 == 0 {
+ num_rct_outs += 1;
+ }
+ }
+ self.add_transaction(tx /*.0*/)?;
+ }
+
+ let blk_metadata = BlockMetadata {
+ timestamp: blk.header.timestamp.0,
+ total_coins_generated: coins_generated,
+ weight: block_weight,
+ cumulative_difficulty,
+ block_hash: blk_hash.into(),
+ cum_rct: num_rct_outs, // num_rct_outs here is the rct outs of the block only. The cumulative rct will be added in `add_block_data` fn
+ long_term_block_weight,
+ };
+
+ self.add_block_data(blk, blk_metadata)
+ }
+
+ /// `add_block_data` add the actual block's data and metadata to the db. Underlying function of `add_block`
+ ///
+ /// In case of failures, a `DB_FAILURES` will be return.
+ ///
+ /// Parameters:
+ /// `blk`: is the block to add
+ /// `blk_metadata`: is the block's metadata to add
+ fn add_block_data(
+ &'service self,
+ blk: Block,
+ mut blk_metadata: BlockMetadata,
+ ) -> Result<(), DB_FAILURES> {
+ let height = self.height()?;
+
+ let mut cursor_blockhash = self.write_cursor_dup::()?;
+ let mut cursor_blockmetadata = self.write_cursor_dup::()?;
+
+ if cursor_blockhash
+ .get_dup(&(), &blk_metadata.block_hash)?
+ .is_some()
+ {
+ return Err(DB_FAILURES::AlreadyExist(
+ "Attempting to insert a block already existent in the database",
+ ))?;
+ }
+
+ if height > 0 {
+ let parent_height: u64 = cursor_blockhash
+ .get_dup(&(), &blk.header.prev_id.into())?
+ .ok_or(DB_FAILURES::NotFound("Can't find parent block"))?;
+
+ if parent_height != height - 1 {
+ return Err(DB_FAILURES::Other("Top block is not a new block's parent"));
+ }
+ }
+
+ if blk.header.major_version.0 > 3 {
+ let last_height = height - 1;
+
+ let parent_cum_rct = self.get_block_cumulative_rct_outputs(last_height)?;
+ blk_metadata.cum_rct += parent_cum_rct;
+ }
+ self.put::(&height, &blk.into())?;
+ cursor_blockhash.put_cursor_dup(&(), &blk_metadata.block_hash, &height)?;
+ cursor_blockmetadata.put_cursor_dup(&(), &height, &blk_metadata)
+ // blockhfversion missing but do we really need this table?
+ }
+
+ /// `pop_block` pops the top block off the blockchain.
+ ///
+ /// Return the block that was popped. In case of failures, a `DB_FAILURES` will be return.
+ ///
+ /// No parameters is required.
+ fn pop_block(&'service self) -> Result {
+ // First we delete block from table
+ let height = self.height()?;
+ if height == 0 {
+ return Err(DB_FAILURES::Other(
+ "Attempting to remove block from an empty blockchain",
+ ));
+ }
+
+ let mut cursor_blockhash = self.write_cursor_dup::()?;
+ let mut cursor_blockmetadata = self.write_cursor_dup::()?;
+
+ let blk = self
+ .get::(&(height - 1))?
+ .ok_or(DB_FAILURES::NotFound(
+ "Attempting to remove block that's not in the db",
+ ))?
+ .0;
+
+ let hash = cursor_blockmetadata
+ .get_dup(&(), &(height - 1))?
+ .ok_or(DB_FAILURES::NotFound("Failed to retrieve block metadata"))?
+ .block_hash;
+
+ self.delete::(&(height - 1), &None)?;
+ if cursor_blockhash.get_dup(&(), &hash)?.is_some() {
+ cursor_blockhash.del()?;
+ }
+
+ cursor_blockmetadata.del()?;
+
+ // Then we delete all its revelent txs
+ for tx_hash in blk.tx_hashes.iter() {
+ // 1 more condition in monerod TODO:
+ self.remove_transaction(*tx_hash)?;
+ }
+ self.remove_transaction(blk.miner_tx.hash())?;
+ Ok(blk)
+ }
+
+ /// `blocks_exists` check if the given block exists
+ ///
+ /// Return `true` if the block exist, `false` otherwise. In case of failures, a `DB_FAILURES` will be return.
+ ///
+ /// Parameters:
+ /// `hash`: is the given hash of the requested block.
+ fn block_exists(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockhash = ro_tx.cursor_dup::()?;
+ Ok(cursor_blockhash.get_dup(&(), &hash.into())?.is_some())
+ }
+
+ /// `get_block_hash` fetch the block's hash located at the give height.
+ ///
+ /// Return the hash of the last block. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// No parameters is required
+ fn get_block_hash(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &height)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.block_hash.0)
+ }
+
+ /// `get_block_height` gets the height of the block with a given hash
+ ///
+ /// Return the requested height.
+ fn get_block_height(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockhash = ro_tx.cursor_dup::()?;
+
+ cursor_blockhash
+ .get_dup(&(), &hash.into())?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block height"))
+ }
+
+ /// `get_block_weights` fetch the block's weight located at the given height.
+ ///
+ /// Return the requested block weight. In case of failures, a `DB_FAILURES` will be return.
+ ///
+ /// Parameters:
+ /// `height`: is the given height where the requested block is located.
+ fn get_block_weight(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &height)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.weight)
+ }
+
+ /// `get_block_already_generated_coins` fetch a block's already generated coins
+ ///
+ /// Return the total coins generated as of the block with the given height. In case of failures, a `DB_FAILURES` will be return.
+ ///
+ /// Parameters:
+ /// `height`: is the given height of the block to seek.
+ fn get_block_already_generated_coins(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &height)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.total_coins_generated)
+ }
+
+ /// `get_block_long_term_weight` fetch a block's long term weight.
+ ///
+ /// Should return block's long term weight. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `height`: is the given height where the requested block is located.
+ fn get_block_long_term_weight(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &height)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.long_term_block_weight)
+ }
+
+ /// `get_block_timestamp` fetch a block's timestamp.
+ ///
+ /// Should return the timestamp of the block with given height. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `height`: is the given height where the requested block to fetch timestamp is located.
+ fn get_block_timestamp(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &height)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.timestamp)
+ }
+
+ /// `get_block_cumulative_rct_outputs` fetch a blocks' cumulative number of RingCT outputs
+ ///
+ /// Should return the number of RingCT outputs in the blockchain up to the blocks located at the given heights. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `height`: is the height to check for RingCT distribution.
+ fn get_block_cumulative_rct_outputs(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &height)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.cum_rct)
+ }
+
+ fn get_block(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockhash = ro_tx.cursor_dup::()?;
+
+ let blk_height: u64 = cursor_blockhash
+ .get_dup(&(), &hash.into())?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?;
+
+ Ok(ro_tx
+ .get::(&blk_height)?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?
+ .0)
+ }
+
+ fn get_block_from_height(&'service self, height: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ Ok(ro_tx
+ .get::(&height)?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?
+ .0)
+ }
+
+ /// `get_block_header` fetches the block's header with the given hash.
+ ///
+ /// Return the requested block header. In case of failures, a `DB_FAILURES` will be return. Precisely, a `BLOCK_DNE`
+ /// error will be returned if the requested block can't be found.
+ ///
+ /// Parameters:
+ /// `hash`: is the given hash of the requested block.
+ fn get_block_header(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_blockhash = ro_tx.cursor_dup::()?;
+
+ let blk_height: u64 = cursor_blockhash
+ .get_dup(&(), &hash.into())?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?;
+
+ Ok(ro_tx
+ .get::(&blk_height)?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?
+ .0
+ .header)
+ }
+
+ fn get_block_header_from_height(
+ &'service self,
+ height: u64,
+ ) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ Ok(ro_tx
+ .get::(&(height - 1))?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?
+ .0
+ .header)
+ }
+
+ /// `get_top_block` fetch the last/top block of the blockchain
+ ///
+ /// Return the last/top block of the blockchain. In case of failures, a DB_FAILURES, will be return.
+ ///
+ /// No parameters is required.
+ fn get_top_block(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ let blk_height = self.height()?;
+
+ Ok(ro_tx
+ .get::(&blk_height)?
+ .ok_or(DB_FAILURES::NotFound("Can't find block"))?
+ .0)
+ }
+
+ /// `get_top_block_hash` fetch the block's hash located at the top of the blockchain (the last one).
+ ///
+ /// Return the hash of the last block. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// No parameters is required
+ fn get_top_block_hash(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let height = self.height()?;
+ let mut cursor_blockmetadata = ro_tx.cursor_dup::()?;
+
+ let metadata = cursor_blockmetadata
+ .get_dup(&(), &(height - 1))?
+ .ok_or(DB_FAILURES::NotFound("Failed to find block's metadata"))?;
+
+ Ok(metadata.block_hash.0)
+ }
+
+ // ------------------------------| Transactions |-----------------------------
+
+ /// `add_transaction` add the corresponding transaction and its hash to the specified block.
+ ///
+ /// In case of failures, a DB_FAILURES will be return. Precisely, a TX_EXISTS will be returned if the
+ /// transaction to be added already exists in the database.
+ ///
+ /// Parameters:
+ /// `blk_hash`: is the hash of the block which inherit the transaction
+ /// `tx`: is obviously the transaction to add
+ /// `tx_hash`: is the hash of the transaction.
+ /// `tx_prunable_hash_ptr`: is the hash of the prunable part of the transaction.
+ fn add_transaction(&'service self, tx: monero::Transaction) -> Result<(), DB_FAILURES> {
+ let is_coinbase: bool = tx.prefix.inputs.is_empty();
+ let tx_hash = tx.hash();
+
+ let mut tx_prunable_blob = Vec::new();
+ get_transaction_prunable_blob(&tx, &mut tx_prunable_blob).unwrap();
+
+ let tx_prunable_hash: Option = calculate_prunable_hash(&tx, &tx_prunable_blob);
+
+ for txin in tx.prefix.inputs.iter() {
+ if let TxIn::ToKey {
+ amount: _,
+ key_offsets: _,
+ k_image,
+ } = txin
+ {
+ self.add_spent_key(k_image.clone())?;
+ } else {
+ return Err(DB_FAILURES::Other(
+ "Unsupported input type, aborting transaction addition",
+ ));
+ }
+ }
+
+ let tx_id =
+ self.add_transaction_data(tx.clone(), tx_prunable_blob, tx_hash, tx_prunable_hash)?;
+
+ let tx_num_outputs = tx.prefix.outputs.len();
+ let amount_output_dinces: Vec = Vec::with_capacity(tx_num_outputs);
+
+ for txout in tx.prefix.outputs.iter().zip(0..tx_num_outputs) {
+ if is_coinbase && tx.prefix.version.0 == 2 {
+ let commitment: Option = None;
+ // ZeroCommit is from RingCT Module, not finishable yet
+ }
+ }
+ todo!()
+ }
+
+ /// `add_transaction_data` add the specified transaction data to its storage.
+ ///
+ /// It only add the transaction blob and tx's metadata, not the collection of outputs.
+ ///
+ /// Return the hash of the transaction added. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `tx`: is the transaction to add
+ /// `tx_prunable_blob`; is its prunable blob.
+ /// `tx_hash`: is the transaction's hash
+ /// `tx_prunable_hash`: is the hash of the prunable part of the transaction
+ fn add_transaction_data(
+ &'service self,
+ tx: monero::Transaction,
+ tx_prunable_blob: Vec,
+ tx_hash: Hash,
+ tx_prunable_hash: Option,
+ ) -> Result {
+ // Checking if the transaction already exist in the database
+ let res = self.get::(&tx_hash.into())?;
+ if res.is_some() {
+ return Err(DB_FAILURES::AlreadyExist(
+ "Attempting to add transaction that's already in the db",
+ ));
+ }
+
+ // Inserting tx index in table::txsindetifier
+ let height = self.height()?;
+ let tx_id = self.get_num_tx()?;
+
+ let txindex = TxIndex {
+ tx_id,
+ unlock_time: tx.prefix.unlock_time.0,
+ height,
+ };
+
+ self.put::(&tx_hash.into(), &txindex)?;
+
+ // TODO: Investigate unprunable_size == 0 condition
+ // Inserting tx pruned part in table::txspruned
+ let tx_pruned = TransactionPruned {
+ prefix: tx.prefix.clone(),
+ rct_signatures: tx.rct_signatures,
+ };
+ self.put::(&tx_id, &tx_pruned)?;
+
+ // Inserting tx prunable part in table::txs
+ self.put::(&tx_id, &tx_prunable_blob)?;
+
+ // Checking to see if the database is pruned and inserting into table::txsprunabletip accordingly
+ if self.get_blockchain_pruning_seed()? > 0 {
+ self.put::(&tx_id, &height)?;
+ }
+
+ // V2 Tx store hash of their prunable part
+ if let Some(tx_prunable_hash) = tx_prunable_hash {
+ self.put::(&tx_id, &tx_prunable_hash.into())?;
+ }
+ Ok(tx_id)
+ }
+
+ fn remove_transaction(&'service self, tx_hash: Hash) -> Result<(), DB_FAILURES> {
+ let txpruned = self.get_pruned_tx(tx_hash)?;
+
+ for input in txpruned.prefix.inputs.iter() {
+ if let TxIn::ToKey {
+ amount: _,
+ key_offsets: _,
+ k_image,
+ } = input
+ {
+ self.remove_spent_key(k_image.clone())?;
+ }
+ }
+
+ self.remove_transaction_data(txpruned.prefix, tx_hash)
+ }
+
+ fn remove_transaction_data(
+ &'service self,
+ txprefix: monero::TransactionPrefix,
+ tx_hash: Hash,
+ ) -> Result<(), DB_FAILURES> {
+ // Checking if the transaction exist and fetching its index
+ let txindex =
+ self.get::(&tx_hash.into())?
+ .ok_or(DB_FAILURES::NotFound(
+ "Attempting to remove transaction that isn't in the db",
+ ))?;
+
+ self.delete::(&txindex.tx_id, &None)?;
+ self.delete::(&txindex.tx_id, &None)?;
+ // If Its in Tip blocks range we must delete it
+ if self.get::(&txindex.tx_id)?.is_some() {
+ self.delete::(&txindex.tx_id, &None)?;
+ }
+ // If v2 Tx we must delete the prunable hash
+ if txprefix.version.0 > 1 {
+ self.delete::(&txindex.tx_id, &None)?;
+ }
+
+ self.remove_tx_outputs(txprefix, txindex.tx_id)?;
+
+ self.delete::(&txindex.tx_id, &None)?;
+ self.delete::(&tx_hash.into(), &None)
+ }
+
+ fn remove_tx_outputs(
+ &'service self,
+ txprefix: monero::TransactionPrefix,
+ tx_id: u64,
+ ) -> Result<(), DB_FAILURES> {
+ let amount_output_indices: TxOutputIdx = self
+ .get::(&tx_id)?
+ .ok_or(DB_FAILURES::NotFound("Failed to find tx's outputs indices"))?;
+
+ if amount_output_indices.0.is_empty() {
+ return Err(DB_FAILURES::Other(
+ "Attempting to remove outputs of a an empty tx",
+ ));
+ }
+
+ // Checking if the input is a coinbase input
+ #[allow(clippy::match_like_matches_macro)]
+ let is_coinbase_input: bool = match &txprefix.inputs[0] {
+ TxIn::Gen { height: _ } if txprefix.version.0 > 1 && txprefix.inputs.len() == 1 => true,
+ _ => false,
+ };
+ for o in 0..txprefix.outputs.len() {
+ let amount = match is_coinbase_input {
+ true => 0,
+ false => txprefix.outputs[o].amount.0,
+ };
+ self.remove_output(Some(amount), amount_output_indices.0[o])?;
+ }
+ Ok(())
+ }
+
+ /// `get_num_tx` fetches the total number of transactions stored in the database
+ ///
+ /// Should return the count. In case of failure, a DB_FAILURES will be return.
+ ///
+ /// No parameters is required.
+ fn get_num_tx(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ ro_tx.num_entries::().map(|n| n as u64)
+ }
+
+ /// `tx_exists` check if a transaction exist with the given hash.
+ ///
+ /// Return `true` if the transaction exist, `false` otherwise. In case of failure, a DB_FAILURES will be return.
+ ///
+ /// Parameters :
+ /// `hash` is the given hash of transaction to check.
+ fn tx_exists(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ Ok(ro_tx.get::(&hash.into())?.is_some())
+ }
+
+ /// `get_tx_unlock_time` fetch a transaction's unlock time/height
+ ///
+ /// Should return the unlock time/height in u64. In case of failure, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `hash`: is the given hash of the transaction to check.
+ fn get_tx_unlock_time(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ // Getting the tx index
+ let txindex =
+ ro_tx
+ .get::(&hash.into())?
+ .ok_or(DB_FAILURES::NotFound(
+ "wasn't able to find a transaction in the database",
+ ))?;
+
+ Ok(txindex.unlock_time)
+ }
+
+ /// `get_tx` fetches the transaction with the given hash.
+ ///
+ /// Should return the transaction. In case of failure, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `hash`: is the given hash of transaction to fetch.
+ fn get_tx(&'service self, hash: Hash) -> Result {
+ // Getting the pruned tx
+ let pruned_tx = self.get_pruned_tx(hash)?;
+
+ // Getting the tx index
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let txindex =
+ ro_tx
+ .get::(&hash.into())?
+ .ok_or(DB_FAILURES::NotFound(
+ "failed to find index of a transaction",
+ ))?;
+
+ // Getting its prunable part
+ let prunable_part =
+ ro_tx
+ .get::(&txindex.tx_id)?
+ .ok_or(DB_FAILURES::NotFound(
+ "failed to find prunable part of a transaction",
+ ))?;
+
+ // Making it a Transaction
+ pruned_tx
+ .into_transaction(&prunable_part)
+ .map_err(|err| DB_FAILURES::SerializeIssue(err.into()))
+ }
+
+ /// `get_tx_list` fetches the transactions with given hashes.
+ ///
+ /// Should return a vector with the requested transactions. In case of failures, a DB_FAILURES will be return.
+ /// Precisly, a HASH_DNE error will be returned with the correspondig hash of transaction that is not found in the DB.
+ ///
+ /// `hlist`: is the given collection of hashes correspondig to the transactions to fetch.
+ fn get_tx_list(
+ &'service self,
+ hash_list: Vec,
+ ) -> Result, DB_FAILURES> {
+ let mut result: Vec = Vec::with_capacity(hash_list.len());
+
+ for hash in hash_list {
+ result.push(self.get_tx(hash)?);
+ }
+ Ok(result)
+ }
+
+ /// `get_pruned_tx` fetches the transaction base with the given hash.
+ ///
+ /// Should return the transaction. In case of failure, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `hash`: is the given hash of transaction to fetch.
+ fn get_pruned_tx(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ let txindex =
+ ro_tx
+ .get::(&hash.into())?
+ .ok_or(DB_FAILURES::NotFound(
+ "wasn't able to find a transaction in the database",
+ ))?;
+
+ ro_tx
+ .get::(&txindex.tx_id)?
+ .ok_or(DB_FAILURES::NotFound(
+ "failed to find prefix of a transaction",
+ ))
+ }
+
+ /// `get_tx_block_height` fetches the height of a transaction's block
+ ///
+ /// Should return the height of the block containing the transaction with the given hash. In case
+ /// of failures, a DB FAILURES will be return. Precisely, a TX_DNE error will be return if the transaction cannot be found.
+ ///
+ /// Parameters:
+ /// `hash`: is the fiven hash of the first transaction
+ fn get_tx_block_height(&'service self, hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let txindex = ro_tx
+ .get::(&hash.into())?
+ .ok_or(DB_FAILURES::NotFound("txindex not found"))?;
+ Ok(txindex.height)
+ }
+
+ // --------------------------------| Outputs |--------------------------------
+
+ /// `add_output` add an output data to it's storage .
+ ///
+ /// It internally keep track of the global output count. The global output count is also used to index outputs based on
+ /// their order of creations.
+ ///
+ /// Should return the amount output index. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `tx_hash`: is the hash of the transaction where the output comes from.
+ /// `output`: is the output's publickey to store.
+ /// `index`: is the local output's index (from transaction).
+ /// `unlock_time`: is the unlock time (height) of the output.
+ /// `commitment`: is the RingCT commitment of this output.
+ fn add_output(
+ &'service self,
+ tx_hash: Hash,
+ output: TxOut,
+ local_index: u64,
+ unlock_time: u64,
+ commitment: Option,
+ ) -> Result {
+ let height = self.height()?;
+
+ let mut cursor_outputmetadata = self.write_cursor_dup::()?;
+
+ let pubkey = output.target.as_one_time_key().map(Into::into);
+ let mut out_metadata = OutputMetadata {
+ tx_hash: tx_hash.into(),
+ local_index,
+ pubkey,
+ unlock_time,
+ height,
+ commitment: None,
+ };
+
+ // RingCT Outputs
+ if let Some(commitment) = commitment {
+ out_metadata.commitment = Some(commitment.into());
+
+ let amount_index = self.get_rct_num_outputs()? + 1;
+ cursor_outputmetadata.put_cursor_dup(&(), &amount_index, &out_metadata)?;
+ Ok(amount_index)
+ }
+ // Pre-RingCT Outputs
+ else {
+ let amount_index = self.get_pre_rct_num_outputs(output.amount.0)? + 1;
+ let mut cursor = self.write_cursor_dup::()?;
+ cursor.put_cursor_dup(&output.amount.0, &amount_index, &out_metadata)?;
+ Ok(amount_index)
+ }
+ }
+
+ fn remove_output(&'service self, amount: Option, index: u64) -> Result<(), DB_FAILURES> {
+ let mut cursor_outputmetadata = self.write_cursor_dup::()?;
+
+ if let Some(amount) = amount {
+ if amount == 0 {
+ cursor_outputmetadata
+ .get_dup(&(), &index)?
+ .ok_or(DB_FAILURES::NotFound(
+ "Failed to find PostRCT output metadata",
+ ))?;
+ cursor_outputmetadata.del()
+ } else {
+ let mut cursor = self.write_cursor_dup::()?;
+ let _ = cursor
+ .get_dup(&amount, &index)?
+ .ok_or(DB_FAILURES::NotFound(
+ "Failed to find PreRCT output metadata",
+ ))?;
+ cursor.del()
+ }
+ } else {
+ cursor_outputmetadata
+ .get_dup(&(), &index)?
+ .ok_or(DB_FAILURES::NotFound(
+ "Failed to find PostRCT output metadata",
+ ))?;
+ cursor_outputmetadata.del()
+ }
+ }
+
+ /// `get_output` get an output's data
+ ///
+ /// Return the public key, unlock time, and block height for the output with the given amount and index, collected in a struct
+ /// In case of failures, a `DB_FAILURES` will be return. Precisely, if the output cannot be found, an `OUTPUT_DNE` error will be return.
+ /// If any of the required part for the final struct isn't found, a `DB_ERROR` will be return
+ ///
+ /// Parameters:
+ /// `amount`: is the corresponding amount of the output
+ /// `index`: is the output's index (indexed by amount)
+ /// `include_commitment` : `true` by default.
+ fn get_output(
+ &'service self,
+ amount: Option,
+ index: u64,
+ ) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_outputmetadata = ro_tx.cursor_dup::()?;
+
+ if let Some(amount) = amount {
+ if amount > 0 {
+ let mut cursor = ro_tx.cursor_dup::()?;
+ return cursor
+ .get_dup(&amount, &index)?
+ .ok_or(DB_FAILURES::NotFound(
+ "Failed to find PreRCT output metadata",
+ ));
+ }
+ }
+ cursor_outputmetadata
+ .get_dup(&(), &index)?
+ .ok_or(DB_FAILURES::NotFound(
+ "Failed to find PostRCT output metadata",
+ ))
+ }
+
+ /// `get_output_list` gets a collection of output's data from a corresponding index collection.
+ ///
+ /// Return a collection of output's data. In case of failurse, a `DB_FAILURES` will be return.
+ ///
+ /// Parameters:
+ /// `amounts`: is the collection of amounts corresponding to the requested outputs.
+ /// `offsets`: is a collection of outputs' index (indexed by amount).
+ /// `allow partial`: `false` by default.
+ fn get_output_list(
+ &'service self,
+ amounts: Option>,
+ offsets: Vec,
+ ) -> Result, DB_FAILURES> {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor_outputmetadata = ro_tx.cursor_dup::()?;
+ let mut result: Vec = Vec::new();
+
+ // Pre-RingCT output to be found.
+ if let Some(amounts) = amounts {
+ let mut cursor = ro_tx.cursor_dup::()?;
+
+ for ofs in amounts.into_iter().zip(offsets) {
+ if ofs.0 == 0 {
+ let output = cursor_outputmetadata.get_dup(&(), &ofs.1)?.ok_or(
+ DB_FAILURES::NotFound("An output hasn't been found in the database"),
+ )?;
+ result.push(output);
+ } else {
+ let output = cursor
+ .get_dup(&ofs.0, &ofs.1)?
+ .ok_or(DB_FAILURES::NotFound(
+ "An output hasn't been found in the database",
+ ))?;
+ result.push(output);
+ }
+ }
+ // No Pre-RingCT outputs to be found.
+ } else {
+ for ofs in offsets {
+ let output =
+ cursor_outputmetadata
+ .get_dup(&(), &ofs)?
+ .ok_or(DB_FAILURES::NotFound(
+ "An output hasn't been found in the database",
+ ))?;
+ result.push(output);
+ }
+ }
+
+ Ok(result)
+ }
+
+ /// `get_rct_num_outputs` fetches the number post-RingCT output.
+ ///
+ /// Return the number of post-RingCT outputs. In case of failures a `DB_FAILURES` will be return.
+ ///
+ /// No parameters is required
+ fn get_rct_num_outputs(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ ro_tx
+ .num_entries::()
+ .map(|n| n as u64)
+ }
+
+ /// `get_pre_rct_num_outputs` fetches the number of preRCT outputs of a given amount.
+ ///
+ /// Return a count of outputs of the given amount. in case of failures a `DB_FAILURES` will be return.
+ ///
+ /// Parameters:
+ /// `amount`: is the output amount being looked up.
+ fn get_pre_rct_num_outputs(&'service self, amount: u64) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ let mut cursor = ro_tx.cursor_dup::()?;
+
+ transaction::Cursor::set(&mut cursor, &amount)?;
+ let out_metadata: Option<(u64, OutputMetadata)> =
+ transaction::DupCursor::last_dup(&mut cursor)?;
+ if let Some(out_metadata) = out_metadata {
+ return Ok(out_metadata.0);
+ }
+ Err(DB_FAILURES::Other("failed to decode the subkey and value"))
+ }
+
+ // ------------------------------| Spent Keys |------------------------------
+
+ /// `add_spent_key` add the supplied key image to the spent key image record
+ fn add_spent_key(&'service self, key_image: KeyImage) -> Result<(), DB_FAILURES> {
+ let mut cursor_spentkeys = self.write_cursor_dup::()?;
+ cursor_spentkeys.put_cursor_dup(&(), &key_image.into(), &())
+ }
+
+ /// `remove_spent_key` remove the specified key image from the spent key image record
+ fn remove_spent_key(&'service self, key_image: KeyImage) -> Result<(), DB_FAILURES> {
+ let mut cursor_spentkeys = self.write_cursor_dup::()?;
+ cursor_spentkeys.get_dup(&(), &key_image.into())?;
+ cursor_spentkeys.del()
+ }
+
+ /// `is_spent_key_recorded` check if the specified key image has been spent
+ fn is_spent_key_recorded(&'service self, key_image: KeyImage) -> Result {
+ let mut cursor_spentkeys = self.write_cursor_dup::()?;
+ Ok(cursor_spentkeys.get_dup(&(), &key_image.into())?.is_some())
+ }
+
+ // --------------------------------------------| Alt-Block |------------------------------------------------------------
+
+ /// `add_alt_block` add a new alternative block.
+ ///
+ /// In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// blkid: is the hash of the original block
+ /// data: is the metadata for the block
+ /// blob: is the blobdata of this alternative block.
+ fn add_alt_block(
+ &'service self,
+ altblock_hash: Hash,
+ data: AltBlock,
+ ) -> Result<(), DB_FAILURES> {
+ self.put::(&altblock_hash.into(), &data)
+ }
+
+ /// `get_alt_block` gets the specified alternative block.
+ ///
+ /// Return a tuple containing the blobdata of the alternative block and its metadata. In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `blkid`: is the hash of the requested alternative block.
+ fn get_alt_block(&'service self, altblock_hash: Hash) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ ro_tx
+ .get::(&altblock_hash.into())?
+ .ok_or(DB_FAILURES::NotFound(
+ "Failed to find an AltBLock in the db",
+ ))
+ }
+
+ /// `remove_alt_block` remove the specified alternative block
+ ///
+ /// In case of failures, a DB_FAILURES will be return.
+ ///
+ /// Parameters:
+ /// `blkid`: is the hash of the alternative block to remove.
+ fn remove_alt_block(&mut self, altblock_hash: Hash) -> Result<(), DB_FAILURES> {
+ self.delete::(&altblock_hash.into(), &None)
+ }
+
+ /// `get_alt_block` gets the total number of alternative blocks stored
+ ///
+ /// In case of failures, a DB_FAILURES will be return.
+ ///
+ /// No parameters is required.
+ fn get_alt_block_count(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+ ro_tx.num_entries::().map(|n| n as u64)
+ }
+
+ /// `drop_alt_block` drop all alternative blocks.
+ ///
+ /// In case of failures, a DB_FAILURES will be return.
+ ///
+ /// No parameters is required.
+ fn drop_alt_blocks(&mut self) -> Result<(), DB_FAILURES> {
+ self.clear::()
+ }
+
+ // --------------------------------| Properties |--------------------------------
+
+ // No pruning yet
+ fn get_blockchain_pruning_seed(&'service self) -> Result {
+ let ro_tx = self.db.tx().map_err(Into::into)?;
+
+ ro_tx
+ .get::(&0)?
+ .ok_or(DB_FAILURES::NotFound("Can't find prunning seed"))
+ }
+}
diff --git a/database/src/lib.rs b/database/src/lib.rs
new file mode 100644
index 00000000..1d880f5e
--- /dev/null
+++ b/database/src/lib.rs
@@ -0,0 +1,221 @@
+// Copyright (C) 2023 Cuprate Contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+//! The cuprate-db crate implement (as its name suggests) the relations between the blockchain/txpool objects and their databases.
+//! `lib.rs` contains all the generics, trait and specification for interfaces between blockchain and a backend-agnostic database
+//! Every other files in this folder are implementation of these traits/methods to real storage engine.
+//!
+//! At the moment, the only storage engine available is MDBX.
+//! The next storage engine planned is HSE (Heteregeonous Storage Engine) from Micron.
+//!
+//! For more informations, please consult this docs:
+
+#![deny(unused_attributes)]
+#![forbid(unsafe_code)]
+#![allow(non_camel_case_types)]
+#![deny(clippy::expect_used, clippy::panic)]
+#![allow(dead_code, unused_macros)] // temporary
+
+use monero::{util::ringct::RctSig, Block, BlockHeader, Hash};
+use std::ops::Range;
+use thiserror::Error;
+
+#[cfg(feature = "mdbx")]
+pub mod mdbx;
+//#[cfg(feature = "hse")]
+//pub mod hse;
+
+pub mod encoding;
+pub mod error;
+pub mod interface;
+pub mod table;
+pub mod types;
+
+const DEFAULT_BLOCKCHAIN_DATABASE_DIRECTORY: &str = "blockchain";
+const DEFAULT_TXPOOL_DATABASE_DIRECTORY: &str = "txpool_mem";
+const BINCODE_CONFIG: bincode::config::Configuration<
+ bincode::config::LittleEndian,
+ bincode::config::Fixint,
+> = bincode::config::standard().with_fixed_int_encoding();
+
+// ------------------------------------------| Database |------------------------------------------
+
+pub mod database {
+ //! This module contains the Database abstraction trait. Any key/value storage engine implemented need
+ //! to fullfil these associated types and functions, in order to be usable. This module also contains the
+ //! Interface struct which is used by the DB Reactor to interact with the database.
+
+ use crate::{
+ error::DB_FAILURES,
+ transaction::{Transaction, WriteTransaction},
+ };
+ use std::{ops::Deref, path::PathBuf, sync::Arc};
+
+ /// `Database` Trait implement all the methods necessary to generate transactions as well as execute specific functions. It also implement generic associated types to identify the
+ /// different transaction modes (read & write) and it's native errors.
+ pub trait Database<'a> {
+ type TX: Transaction<'a>;
+ type TXMut: WriteTransaction<'a>;
+ type Error: Into;
+
+ // Create a transaction from the database
+ fn tx(&'a self) -> Result;
+
+ // Create a mutable transaction from the database
+ fn tx_mut(&'a self) -> Result;
+
+ // Open a database from the specified path
+ fn open(path: PathBuf) -> Result
+ where
+ Self: std::marker::Sized;
+
+ // Check if the database is built.
+ fn check_all_tables_exist(&'a self) -> Result<(), Self::Error>;
+
+ // Build the database
+ fn build(&'a self) -> Result<(), Self::Error>;
+ }
+
+ /// `Interface` is a struct containing a shared pointer to the database and transaction's to be used for the implemented method of Interface.
+ pub struct Interface<'a, D: Database<'a>> {
+ pub db: Arc,
+ pub tx: Option<>::TXMut>,
+ }
+
+ // Convenient implementations for database
+ impl<'service, D: Database<'service>> Interface<'service, D> {
+ fn from(db: Arc) -> Result {
+ Ok(Self { db, tx: None })
+ }
+
+ fn open(&'service mut self) -> Result<(), DB_FAILURES> {
+ let tx = self.db.tx_mut().map_err(Into::into)?;
+ self.tx = Some(tx);
+ Ok(())
+ }
+ }
+
+ impl<'service, D: Database<'service>> Deref for Interface<'service, D> {
+ type Target = >::TXMut;
+
+ fn deref(&self) -> &Self::Target {
+ return self.tx.as_ref().unwrap();
+ }
+ }
+}
+
+// ------------------------------------------| DatabaseTx |------------------------------------------
+
+pub mod transaction {
+ //! This module contains the abstractions of Transactional Key/Value database functions.
+ //! Any key/value database/storage engine can be implemented easily for Cuprate as long as
+ //! these functions or equivalent logic exist for it.
+
+ use crate::{
+ error::DB_FAILURES,
+ table::{DupTable, Table},
+ };
+
+ // Abstraction of a read-only cursor, for simple tables
+ #[allow(clippy::type_complexity)]
+ pub trait Cursor<'t, T: Table> {
+ fn first(&mut self) -> Result