add last service request

This commit is contained in:
Boog900 2024-09-06 00:23:55 +01:00
parent 029f439f0b
commit 6927b05f81
No known key found for this signature in database
GPG key ID: 42AB1287CB0041C2
14 changed files with 306 additions and 66 deletions

1
Cargo.lock generated
View file

@ -531,6 +531,7 @@ dependencies = [
"monero-serai",
"pretty_assertions",
"proptest",
"rand",
"rayon",
"serde",
"tempfile",

View file

@ -25,11 +25,12 @@ cuprate-database = { path = "../database" }
cuprate-database-service = { path = "../service" }
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] }
cuprate-types = { path = "../../types", features = ["blockchain"] }
cuprate-pruning = { path = "../../pruning" }
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
curve25519-dalek = { workspace = true }
cuprate-pruning = { path = "../../pruning" }
rand = { workspace = true }
monero-serai = { workspace = true, features = ["std"] }
serde = { workspace = true, optional = true }

View file

@ -1,5 +1,6 @@
//! General free functions (related to the database).
use monero_serai::transaction::{Input, Transaction};
//---------------------------------------------------------------------------------------------------- Import
use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw};
@ -61,6 +62,37 @@ pub fn open(config: Config) -> Result<ConcreteEnv, InitError> {
Ok(env)
}
//---------------------------------------------------------------------------------------------------- Tx Fee
/// Calculates the fee of the [`Transaction`].
///
/// # Panics
/// This will panic if the inputs overflow or the transaction outputs too much.
pub(crate) fn tx_fee(tx: &Transaction) -> u64 {
let mut fee = 0_u64;
match &tx {
Transaction::V1 { prefix, .. } => {
for input in &prefix.inputs {
match input {
Input::Gen(_) => return 0,
Input::ToKey { amount, .. } => {
fee = fee.checked_add(amount.unwrap_or(0)).unwrap();
}
}
}
for output in &prefix.outputs {
fee.checked_sub(output.amount.unwrap_or(0)).unwrap();
}
}
Transaction::V2 { proofs, .. } => {
fee = proofs.as_ref().unwrap().base.fee;
}
};
fee
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {

View file

@ -1,19 +1,14 @@
use crate::ops::alt_block::{
add_alt_transaction_blob, check_add_alt_chain_info, get_alt_chain_history_ranges,
get_alt_transaction_blob,
get_alt_transaction,
};
use crate::ops::block::{get_block_extended_header_from_height, get_block_info};
use crate::tables::{Tables, TablesMut};
use crate::types::{
AltBlockHeight, AltTransactionInfo, BlockHash, BlockHeight, CompactAltBlockInfo,
};
use crate::types::{AltBlockHeight, BlockHash, BlockHeight, CompactAltBlockInfo};
use bytemuck::TransparentWrapper;
use cuprate_database::{RuntimeError, StorableVec};
use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec};
use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits};
use cuprate_types::{
AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, HardFork,
VerifiedTransactionInformation,
};
use cuprate_types::{AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, HardFork};
use monero_serai::block::{Block, BlockHeader};
pub fn add_alt_block(
@ -54,8 +49,8 @@ pub fn add_alt_block(
)?;
assert_eq!(alt_block.txs.len(), alt_block.block.transactions.len());
for (tx, tx_hash) in alt_block.txs.iter().zip(&alt_block.block.transactions) {
add_alt_transaction_blob(tx_hash, StorableVec::wrap_ref(tx), tables)?;
for tx in alt_block.txs.iter() {
add_alt_transaction_blob(tx, tables)?;
}
Ok(())
@ -74,8 +69,8 @@ pub fn get_alt_block(
let txs = block
.transactions
.iter()
.map(|tx_hash| get_alt_transaction_blob(tx_hash, tables))
.collect()?;
.map(|tx_hash| get_alt_transaction(tx_hash, tables))
.collect::<Result<_, RuntimeError>>()?;
Ok(AltBlockInformation {
block,

View file

@ -1,6 +1,6 @@
use crate::tables::{AltChainInfos, TablesMut};
use crate::types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight};
use cuprate_database::{DatabaseRo, RuntimeError};
use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError};
use cuprate_types::{Chain, ChainId};
use std::cmp::max;
@ -26,6 +26,7 @@ pub fn check_add_alt_chain_info(
&AltChainInfo {
parent_chain: parent_chain.into(),
common_ancestor_height: alt_block_height.height - 1,
chain_height: alt_block_height.height,
},
)
}

View file

@ -5,3 +5,20 @@ mod tx;
pub use block::*;
pub use chain::*;
pub use tx::*;
pub fn flush_alt_blocks<'a, E: cuprate_database::EnvInner<'a>>(
env_inner: &E,
tx_rw: &mut E::Rw<'_>,
) -> Result<(), cuprate_database::RuntimeError> {
use crate::tables::{
AltBlockBlobs, AltBlockHeights, AltBlocksInfo, AltChainInfos, AltTransactionBlobs,
AltTransactionInfos,
};
env_inner.clear_db::<AltChainInfos>(tx_rw)?;
env_inner.clear_db::<AltBlockHeights>(tx_rw)?;
env_inner.clear_db::<AltBlocksInfo>(tx_rw)?;
env_inner.clear_db::<AltBlockBlobs>(tx_rw)?;
env_inner.clear_db::<AltTransactionBlobs>(tx_rw)?;
env_inner.clear_db::<AltTransactionInfos>(tx_rw)
}

View file

@ -1,35 +1,57 @@
use crate::tables::{Tables, TablesMut};
use crate::types::{AltTransactionInfo, TxHash};
use bytemuck::TransparentWrapper;
use cuprate_database::{RuntimeError, StorableVec};
use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec};
use cuprate_types::VerifiedTransactionInformation;
use monero_serai::transaction::Transaction;
pub fn add_alt_transaction_blob(
tx_hash: &TxHash,
tx_block: &StorableVec<u8>,
tx: &VerifiedTransactionInformation,
tables: &mut impl TablesMut,
) -> Result<(), RuntimeError> {
if tables.tx_ids().get(&tx_hash).is_ok() || tables.alt_transaction_blobs().get(&tx_hash).is_ok()
tables.alt_transaction_infos_mut().put(
&tx.tx_hash,
&AltTransactionInfo {
tx_weight: tx.tx_weight,
fee: tx.fee,
tx_hash: tx.tx_hash,
},
)?;
if tables.tx_ids().get(&tx.tx_hash).is_ok()
|| tables.alt_transaction_blobs().get(&tx.tx_hash).is_ok()
{
return Ok(());
}
tables.alt_transaction_blobs_mut().put(&tx_hash, tx_block)
tables
.alt_transaction_blobs_mut()
.put(&tx.tx_hash, StorableVec::wrap_ref(&tx.tx_blob))
}
pub fn get_alt_transaction_blob(
pub fn get_alt_transaction(
tx_hash: &TxHash,
tables: &impl Tables,
) -> Result<Vec<u8>, RuntimeError> {
match tables.alt_transaction_blobs().get(tx_hash) {
Ok(blob) => Ok(blob.0),
) -> Result<VerifiedTransactionInformation, RuntimeError> {
let tx_info = tables.alt_transaction_infos().get(tx_hash)?;
let tx_blob = match tables.alt_transaction_blobs().get(tx_hash) {
Ok(blob) => blob.0,
Err(RuntimeError::KeyNotFound) => {
let tx_id = tables.tx_ids().get(tx_hash)?;
let blob = tables.tx_blobs().get(&tx_id)?;
Ok(blob.0)
blob.0
}
Err(e) => return Err(e),
}
};
Ok(VerifiedTransactionInformation {
tx: Transaction::read(&mut tx_blob.as_slice()).unwrap(),
tx_blob,
tx_weight: tx_info.tx_weight,
fee: tx_info.fee,
tx_hash: tx_info.tx_hash,
})
}

View file

@ -8,8 +8,13 @@ use cuprate_database::{
RuntimeError, StorableVec, {DatabaseRo, DatabaseRw},
};
use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits};
use cuprate_types::{ExtendedBlockHeader, HardFork, VerifiedBlockInformation};
use cuprate_types::{
AltBlockInformation, ChainId, ExtendedBlockHeader, HardFork, VerifiedBlockInformation,
VerifiedTransactionInformation,
};
use crate::free::tx_fee;
use crate::ops::alt_block;
use crate::{
ops::{
blockchain::{chain_height, cumulative_generated_coins},
@ -106,9 +111,8 @@ pub fn add_block(
cumulative_rct_outs,
timestamp: block.block.header.timestamp,
block_hash: block.block_hash,
// INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`
weight: block.weight as u64,
long_term_weight: block.long_term_weight as u64,
weight: block.weight,
long_term_weight: block.long_term_weight,
},
)?;
@ -135,17 +139,15 @@ pub fn add_block(
/// will be returned if there are no blocks left.
// no inline, too big
pub fn pop_block(
move_to_alt_chain: Option<ChainId>,
tables: &mut impl TablesMut,
) -> Result<(BlockHeight, BlockHash, Block), RuntimeError> {
//------------------------------------------------------ Block Info
// Remove block data from tables.
let (block_height, block_hash) = {
let (block_height, block_info) = tables.block_infos_mut().pop_last()?;
(block_height, block_info.block_hash)
};
let (block_height, block_info) = tables.block_infos_mut().pop_last()?;
// Block heights.
tables.block_heights_mut().delete(&block_hash)?;
tables.block_heights_mut().delete(&block_info.block_hash)?;
// Block blobs.
// We deserialize the block blob into a `Block`, such
@ -154,12 +156,42 @@ pub fn pop_block(
let block = Block::read(&mut block_blob.as_slice())?;
//------------------------------------------------------ Transaction / Outputs / Key Images
let mut txs = Vec::with_capacity(block.transactions.len());
remove_tx(&block.miner_transaction.hash(), tables)?;
for tx_hash in &block.transactions {
remove_tx(tx_hash, tables)?;
let (_, tx) = remove_tx(tx_hash, tables)?;
if move_to_alt_chain.is_some() {
txs.push(VerifiedTransactionInformation {
tx_weight: tx.weight(),
tx_blob: tx.serialize(),
tx_hash: tx.hash(),
fee: tx_fee(&tx),
tx,
})
}
}
Ok((block_height, block_hash, block))
if let Some(chain_id) = move_to_alt_chain {
alt_block::add_alt_block(
&AltBlockInformation {
block: block.clone(),
block_blob,
txs,
block_hash: block_info.block_hash,
pow_hash: [255; 32],
height: block_height,
weight: block_info.weight,
long_term_weight: block_info.long_term_weight,
cumulative_difficulty: 0,
chain_id,
},
tables,
)?;
}
Ok((block_height, block_info.block_hash, block))
}
//---------------------------------------------------------------------------------------------------- `get_block_extended_header_*`
@ -205,8 +237,8 @@ pub fn get_block_extended_header_from_height(
.expect("Stored block must have a valid hard-fork"),
vote: block_header.hardfork_signal,
timestamp: block_header.timestamp,
block_weight: block_info.weight as usize,
long_term_weight: block_info.long_term_weight as usize,
block_weight: block_info.weight,
long_term_weight: block_info.long_term_weight,
})
}
@ -412,7 +444,8 @@ mod test {
for block_hash in block_hashes.into_iter().rev() {
println!("pop_block(): block_hash: {}", hex::encode(block_hash));
let (_popped_height, popped_hash, _popped_block) = pop_block(&mut tables).unwrap();
let (_popped_height, popped_hash, _popped_block) =
pop_block(None, &mut tables).unwrap();
assert_eq!(block_hash, popped_hash);

View file

@ -3,13 +3,13 @@
//---------------------------------------------------------------------------------------------------- Import
use std::sync::Arc;
use cuprate_database::{ConcreteEnv, InitError};
use crate::service::{init_read_service, init_write_service};
use crate::{
config::Config,
service::types::{BlockchainReadHandle, BlockchainWriteHandle},
};
use cuprate_database::{ConcreteEnv, InitError};
use cuprate_types::{AltBlockInformation, VerifiedBlockInformation};
//---------------------------------------------------------------------------------------------------- Init
#[cold]
@ -81,6 +81,39 @@ pub(super) const fn compact_history_genesis_not_included<const INITIAL_BLOCKS: u
top_block_height > INITIAL_BLOCKS && !(top_block_height - INITIAL_BLOCKS + 2).is_power_of_two()
}
//---------------------------------------------------------------------------------------------------- Compact history
pub(super) fn map_valid_alt_block_to_verified_block(
alt_block: AltBlockInformation,
) -> VerifiedBlockInformation {
let total_fees = alt_block.txs.iter().map(|tx| tx.fee).sum::<u64>();
let total_miner_output = alt_block
.block
.miner_transaction
.prefix()
.outputs
.iter()
.map(|out| out.amount.unwrap_or(0))
.sum::<u64>();
VerifiedBlockInformation {
block: alt_block.block,
block_blob: alt_block.block_blob,
txs: alt_block
.txs
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()
.unwrap(),
block_hash: alt_block.block_hash,
pow_hash: alt_block.pow_hash,
height: alt_block.height,
generated_coins: total_miner_output - total_fees,
weight: alt_block.weight,
long_term_weight: alt_block.long_term_weight,
cumulative_difficulty: alt_block.cumulative_difficulty,
}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]

View file

@ -1,18 +1,20 @@
//! Database writer thread definitions and logic.
//---------------------------------------------------------------------------------------------------- Import
use std::sync::Arc;
use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw};
use cuprate_database::{ConcreteEnv, DatabaseRo, DatabaseRw, Env, EnvInner, RuntimeError, TxRw};
use cuprate_database_service::DatabaseWriteHandle;
use cuprate_types::{
blockchain::{BlockchainResponse, BlockchainWriteRequest},
AltBlockInformation, VerifiedBlockInformation,
AltBlockInformation, Chain, ChainId, VerifiedBlockInformation,
};
use crate::service::free::map_valid_alt_block_to_verified_block;
use crate::types::AltBlockHeight;
use crate::{
service::types::{BlockchainWriteHandle, ResponseResult},
tables::OpenTables,
tables::{OpenTables, Tables, TablesMut},
types::AltChainInfo,
};
//---------------------------------------------------------------------------------------------------- init_write_service
@ -30,8 +32,10 @@ fn handle_blockchain_request(
match req {
BlockchainWriteRequest::WriteBlock(block) => write_block(env, block),
BlockchainWriteRequest::WriteAltBlock(alt_block) => write_alt_block(env, alt_block),
BlockchainWriteRequest::StartReorg(_) => todo!(),
BlockchainWriteRequest::ReverseReorg(_) => todo!(),
BlockchainWriteRequest::PopBlocks(numb_blocks) => pop_blocks(env, *numb_blocks),
BlockchainWriteRequest::ReverseReorg(old_main_chain_id) => {
reverse_reorg(env, *old_main_chain_id)
}
BlockchainWriteRequest::FlushAltBlocks => flush_alt_blocks(env),
}
}
@ -97,6 +101,108 @@ fn write_alt_block(env: &ConcreteEnv, block: &AltBlockInformation) -> ResponseRe
}
}
/// [`BlockchainWriteRequest::PopBlocks`].
fn pop_blocks(env: &ConcreteEnv, numb_blocks: usize) -> ResponseResult {
let env_inner = env.env_inner();
let mut tx_rw = env_inner.tx_rw()?;
let result = {
crate::ops::alt_block::flush_alt_blocks(&env_inner, &mut tx_rw)?;
let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?;
let old_main_chain_id = ChainId(rand::random());
let mut last_block_height = 0;
for _ in 0..numb_blocks {
(last_block_height, _, _) =
crate::ops::block::pop_block(Some(old_main_chain_id), &mut tables_mut)?;
}
tables_mut.alt_chain_infos_mut().put(
&old_main_chain_id.into(),
&AltChainInfo {
parent_chain: Chain::Main.into(),
common_ancestor_height: last_block_height - 1,
chain_height: last_block_height + numb_blocks,
},
)?;
Ok(old_main_chain_id)
};
match result {
Ok(old_main_chain_id) => {
TxRw::commit(tx_rw)?;
Ok(BlockchainResponse::PopBlocks(old_main_chain_id))
}
Err(e) => {
// INVARIANT: ensure database atomicity by aborting
// the transaction on `add_block()` failures.
TxRw::abort(tx_rw)
.expect("could not maintain database atomicity by aborting write transaction");
Err(e)
}
}
}
/// [`BlockchainWriteRequest::ReverseReorg`].
fn reverse_reorg(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult {
let env_inner = env.env_inner();
let tx_rw = env_inner.tx_rw()?;
let result = {
let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?;
let chain_info = tables_mut.alt_chain_infos().get(&chain_id.into())?;
assert_eq!(Chain::from(chain_info.parent_chain), Chain::Main);
let tob_block_height =
crate::ops::blockchain::top_block_height(tables_mut.block_heights())?;
for _ in chain_info.common_ancestor_height..tob_block_height {
crate::ops::block::pop_block(None, &mut tables_mut)?;
}
// Rust borrow rules requires us to collect into a Vec first before looping over the Vec.
let alt_blocks = (chain_info.common_ancestor_height..chain_info.chain_height)
.map(|height| {
crate::ops::alt_block::get_alt_block(
&AltBlockHeight {
chain_id: chain_id.into(),
height,
},
&tables_mut,
)
})
.collect::<Vec<_>>();
for res_alt_block in alt_blocks {
let alt_block = res_alt_block?;
let verified_block = map_valid_alt_block_to_verified_block(alt_block);
crate::ops::block::add_block(&verified_block, &mut tables_mut)?;
}
Ok(())
};
match result {
Ok(()) => {
TxRw::commit(tx_rw)?;
Ok(BlockchainResponse::Ok)
}
Err(e) => {
// INVARIANT: ensure database atomicity by aborting
// the transaction on `add_block()` failures.
TxRw::abort(tx_rw)
.expect("could not maintain database atomicity by aborting write transaction");
Err(e)
}
}
}
/// [`BlockchainWriteRequest::FlushAltBlocks`].
#[inline]
fn flush_alt_blocks(env: &ConcreteEnv) -> ResponseResult {

View file

@ -145,6 +145,9 @@ cuprate_database::define_tables! {
19 => AltTransactionBlobs,
TxHash => TxBlob,
20 => AltTransactionInfos,
TxHash => AltTransactionInfo,
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -189,7 +189,7 @@ pub struct BlockInfo {
/// The adjusted block size, in bytes.
///
/// See [`block_weight`](https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#blocks-weight).
pub weight: u64,
pub weight: usize,
/// Least-significant 64 bits of the 128-bit cumulative difficulty.
pub cumulative_difficulty_low: u64,
/// Most-significant 64 bits of the 128-bit cumulative difficulty.
@ -201,7 +201,7 @@ pub struct BlockInfo {
/// The long term block weight, based on the median weight of the preceding `100_000` blocks.
///
/// See [`long_term_weight`](https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#long-term-block-weight).
pub long_term_weight: u64,
pub long_term_weight: usize,
}
//---------------------------------------------------------------------------------------------------- OutputFlags
@ -381,6 +381,7 @@ impl Key for RawChainId {}
pub struct AltChainInfo {
pub parent_chain: RawChain,
pub common_ancestor_height: usize,
pub chain_height: usize,
}
//---------------------------------------------------------------------------------------------------- AltBlockHeight

View file

@ -116,20 +116,17 @@ pub enum BlockchainWriteRequest {
///
/// Input is the alternative block.
WriteAltBlock(AltBlockInformation),
/// A request to start the re-org process.
/// A request to pop some blocks from the top of the main chain
///
/// The inner value is the [`ChainId`] of the alt-chain we want to re-org to.
/// Input is the amount of blocks to pop.
///
/// This will:
/// - pop blocks from the main chain
/// - retrieve all alt-blocks in this alt-chain
/// - flush all other alt blocks
StartReorg(ChainId),
/// This request flush all alt-chains from the cache before adding the popped blocks to the alt cache.
PopBlocks(usize),
/// A request to reverse the re-org process.
///
/// The inner value is the [`ChainId`] of the old main chain.
///
/// It is invalid to call this with a [`ChainId`] that was not returned from [`BlockchainWriteRequest::StartReorg`].
/// It is invalid to call this with a [`ChainId`] that was not returned from [`BlockchainWriteRequest::PopBlocks`].
ReverseReorg(ChainId),
/// A request to flush all alternative blocks.
FlushAltBlocks,
@ -227,13 +224,10 @@ pub enum BlockchainResponse {
/// - [`BlockchainWriteRequest::ReverseReorg`]
/// - [`BlockchainWriteRequest::FlushAltBlocks`]
Ok,
/// The response for [`BlockchainWriteRequest::StartReorg`].
StartReorg {
/// The [`ChainId`] of the old main chain blocks that were popped.
old_main_chain_id: ChainId,
/// The next alt chain blocks.
alt_chain: Vec<AltBlockInformation>,
},
/// The response for [`BlockchainWriteRequest::PopBlocks`].
///
/// The inner value is the alt-chain ID for the old main chain blocks.
PopBlocks(ChainId),
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -79,6 +79,7 @@ pub struct VerifiedBlockInformation {
/// [`Block::hash`].
pub block_hash: [u8; 32],
/// The block's proof-of-work hash.
// TODO: make this an option.
pub pow_hash: [u8; 32],
/// The block's height.
pub height: usize,
@ -120,7 +121,7 @@ pub struct AltBlockInformation {
/// [`Block::serialize`].
pub block_blob: Vec<u8>,
/// All the transactions in the block, excluding the [`Block::miner_transaction`].
pub txs: Vec<Vec<u8>>,
pub txs: Vec<VerifiedTransactionInformation>,
/// The block's hash.
///
/// [`Block::hash`].