diff --git a/storage/blockchain/src/ops/alt_block.rs b/storage/blockchain/src/ops/alt_block/block.rs similarity index 61% rename from storage/blockchain/src/ops/alt_block.rs rename to storage/blockchain/src/ops/alt_block/block.rs index 82a37ba8..171cdd7b 100644 --- a/storage/blockchain/src/ops/alt_block.rs +++ b/storage/blockchain/src/ops/alt_block/block.rs @@ -1,22 +1,20 @@ -use std::cmp::max; - +use crate::ops::alt_block::{ + add_alt_transaction_blob, check_add_alt_chain_info, get_alt_chain_history_ranges, + get_alt_transaction_blob, +}; +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 bytemuck::TransparentWrapper; -use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec}; +use cuprate_database::{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 monero_serai::block::BlockHeader; - -use crate::{ - ops::block::{get_block_extended_header_from_height, get_block_info}, - tables::{Tables, TablesMut}, - types::{ - AltBlockHeight, AltChainInfo, AltTransactionInfo, BlockHash, BlockHeight, - CompactAltBlockInfo, - }, -}; +use monero_serai::block::{Block, BlockHeader}; pub fn add_alt_block( alt_block: &AltBlockInformation, @@ -55,64 +53,48 @@ pub fn add_alt_block( StorableVec::wrap_ref(&alt_block.block_blob), )?; - for tx in &alt_block.txs { - add_alt_transaction(&tx, tables)?; + 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)?; } Ok(()) } -pub fn add_alt_transaction( - tx: &VerifiedTransactionInformation, - tables: &mut impl TablesMut, -) -> Result<(), RuntimeError> { - if tables.tx_ids().get(&tx.tx_hash).is_ok() - || tables.alt_transaction_infos().get(&tx.tx_hash).is_ok() - { - return Ok(()); - } - - tables.alt_transaction_infos_mut().put( - &tx.tx_hash, - &AltTransactionInfo { - tx_weight: tx.tx_weight, - fee: tx.fee, - tx_hash: tx.tx_hash, - }, - )?; - - tables - .alt_transaction_blobs_mut() - .put(&tx.tx_hash, StorableVec::wrap_ref(&tx.tx_blob)) -} - -pub fn check_add_alt_chain_info( +pub fn get_alt_block( alt_block_height: &AltBlockHeight, - prev_hash: &BlockHash, - tables: &mut impl TablesMut, -) -> Result<(), RuntimeError> { - match tables.alt_chain_infos().get(&alt_block_height.chain_id) { - Ok(_) => return Ok(()), - Err(RuntimeError::KeyNotFound) => (), - Err(e) => return Err(e), - } + tables: &impl Tables, +) -> Result { + let block_info = tables.alt_blocks_info().get(alt_block_height)?; - let parent_chain = match tables.alt_block_heights().get(prev_hash) { - Ok(alt_parent_height) => Chain::Alt(alt_parent_height.chain_id.into()), - Err(RuntimeError::KeyNotFound) => Chain::Main, - Err(e) => return Err(e), - }; + let block_blob = tables.alt_block_blobs().get(alt_block_height)?.0; - tables.alt_chain_infos_mut().put( - &alt_block_height.chain_id, - &AltChainInfo { - parent_chain: parent_chain.into(), - common_ancestor_height: alt_block_height.height - 1, - }, - ) + let block = Block::read(&mut block_blob.as_slice())?; + + let txs = block + .transactions + .iter() + .map(|tx_hash| get_alt_transaction_blob(tx_hash, tables)) + .collect()?; + + Ok(AltBlockInformation { + block, + block_blob, + txs, + block_hash: block_info.block_hash, + pow_hash: block_info.pow_hash, + height: block_info.height, + weight: block_info.weight, + long_term_weight: block_info.long_term_weight, + cumulative_difficulty: combine_low_high_bits_to_u128( + block_info.cumulative_difficulty_low, + block_info.cumulative_difficulty_high, + ), + chain_id: alt_block_height.chain_id.into(), + }) } -pub fn alt_block_hash( +pub fn get_alt_block_hash( block_height: &BlockHeight, alt_chain: ChainId, tables: &mut impl Tables, @@ -152,37 +134,15 @@ pub fn alt_block_hash( } } -pub fn alt_extended_headers_in_range( +pub fn get_alt_extended_headers_in_range( range: std::ops::Range, alt_chain: ChainId, tables: &impl Tables, ) -> Result, RuntimeError> { // TODO: this function does not use rayon, however it probably should. - let mut ranges = Vec::with_capacity(5); let alt_chains = tables.alt_chain_infos(); - - let mut i = range.end; - let mut current_chain_id = alt_chain.into(); - while i > range.start { - let chain_info = alt_chains.get(¤t_chain_id)?; - - let start_height = max(range.start, chain_info.common_ancestor_height + 1); - - ranges.push((chain_info.parent_chain.into(), start_height..i)); - i = chain_info.common_ancestor_height; - - match chain_info.parent_chain.into() { - Chain::Main => { - ranges.push((Chain::Main, range.start..i)); - break; - } - Chain::Alt(alt_chain_id) => { - current_chain_id = alt_chain_id.into(); - continue; - } - } - } + let ranges = get_alt_chain_history_ranges(range, alt_chain, alt_chains)?; let res = ranges .into_iter() diff --git a/storage/blockchain/src/ops/alt_block/chain.rs b/storage/blockchain/src/ops/alt_block/chain.rs new file mode 100644 index 00000000..4259d4dc --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/chain.rs @@ -0,0 +1,63 @@ +use crate::tables::{AltChainInfos, TablesMut}; +use crate::types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight}; +use cuprate_database::{DatabaseRo, RuntimeError}; +use cuprate_types::{Chain, ChainId}; +use std::cmp::max; + +pub fn check_add_alt_chain_info( + alt_block_height: &AltBlockHeight, + prev_hash: &BlockHash, + tables: &mut impl TablesMut, +) -> Result<(), RuntimeError> { + match tables.alt_chain_infos().get(&alt_block_height.chain_id) { + Ok(_) => return Ok(()), + Err(RuntimeError::KeyNotFound) => (), + Err(e) => return Err(e), + } + + let parent_chain = match tables.alt_block_heights().get(prev_hash) { + Ok(alt_parent_height) => Chain::Alt(alt_parent_height.chain_id.into()), + Err(RuntimeError::KeyNotFound) => Chain::Main, + Err(e) => return Err(e), + }; + + tables.alt_chain_infos_mut().put( + &alt_block_height.chain_id, + &AltChainInfo { + parent_chain: parent_chain.into(), + common_ancestor_height: alt_block_height.height - 1, + }, + ) +} + +pub fn get_alt_chain_history_ranges( + range: std::ops::Range, + alt_chain: ChainId, + alt_chain_infos: &impl DatabaseRo, +) -> Result)>, RuntimeError> { + let mut ranges = Vec::with_capacity(5); + + let mut i = range.end; + let mut current_chain_id = alt_chain.into(); + while i > range.start { + let chain_info = alt_chain_infos.get(¤t_chain_id)?; + + let start_height = max(range.start, chain_info.common_ancestor_height + 1); + + ranges.push((chain_info.parent_chain.into(), start_height..i)); + i = chain_info.common_ancestor_height; + + match chain_info.parent_chain.into() { + Chain::Main => { + ranges.push((Chain::Main, range.start..i)); + break; + } + Chain::Alt(alt_chain_id) => { + current_chain_id = alt_chain_id.into(); + continue; + } + } + } + + Ok(ranges) +} diff --git a/storage/blockchain/src/ops/alt_block/mod.rs b/storage/blockchain/src/ops/alt_block/mod.rs new file mode 100644 index 00000000..8b2d1f17 --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/mod.rs @@ -0,0 +1,7 @@ +mod block; +mod chain; +mod tx; + +pub use block::*; +pub use chain::*; +pub use tx::*; diff --git a/storage/blockchain/src/ops/alt_block/tx.rs b/storage/blockchain/src/ops/alt_block/tx.rs new file mode 100644 index 00000000..aad4dc3d --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/tx.rs @@ -0,0 +1,35 @@ +use crate::tables::{Tables, TablesMut}; +use crate::types::{AltTransactionInfo, TxHash}; +use bytemuck::TransparentWrapper; +use cuprate_database::{RuntimeError, StorableVec}; +use cuprate_types::VerifiedTransactionInformation; + +pub fn add_alt_transaction_blob( + tx_hash: &TxHash, + tx_block: &StorableVec, + tables: &mut impl TablesMut, +) -> Result<(), RuntimeError> { + if tables.tx_ids().get(&tx_hash).is_ok() || tables.alt_transaction_blobs().get(&tx_hash).is_ok() + { + return Ok(()); + } + + tables.alt_transaction_blobs_mut().put(&tx_hash, tx_block) +} + +pub fn get_alt_transaction_blob( + tx_hash: &TxHash, + tables: &impl Tables, +) -> Result, RuntimeError> { + match tables.alt_transaction_blobs().get(tx_hash) { + Ok(blob) => Ok(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) + } + Err(e) => return Err(e), + } +} diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index eef40b5e..70da01b3 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -22,7 +22,7 @@ use cuprate_types::{ use crate::{ ops::{ - alt_block::{alt_block_hash, alt_extended_headers_in_range}, + alt_block::{get_alt_block_hash, get_alt_extended_headers_in_range}, block::{ block_exists, get_block_extended_header_from_height, get_block_height, get_block_info, }, @@ -200,7 +200,7 @@ fn block_hash(env: &ConcreteEnv, block_height: BlockHeight, chain: Chain) -> Res let block_hash = match chain { Chain::Main => get_block_info(&block_height, &table_block_infos)?.block_hash, Chain::Alt(chain) => { - alt_block_hash(&block_height, chain, &mut env_inner.open_tables(&tx_ro)?)? + get_alt_block_hash(&block_height, chain, &mut env_inner.open_tables(&tx_ro)?)? } }; @@ -284,7 +284,7 @@ fn block_extended_header_in_range( .collect::, RuntimeError>>()?, Chain::Alt(chain_id) => { let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; - alt_extended_headers_in_range( + get_alt_extended_headers_in_range( range, chain_id, get_tables!(env_inner, tx_ro, tables)?.as_ref(), diff --git a/storage/blockchain/src/service/write.rs b/storage/blockchain/src/service/write.rs index a2cc71c7..067ba7f1 100644 --- a/storage/blockchain/src/service/write.rs +++ b/storage/blockchain/src/service/write.rs @@ -7,7 +7,7 @@ use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw}; use cuprate_database_service::DatabaseWriteHandle; use cuprate_types::{ blockchain::{BlockchainResponse, BlockchainWriteRequest}, - VerifiedBlockInformation, + AltBlockInformation, VerifiedBlockInformation, }; use crate::{ @@ -29,10 +29,10 @@ fn handle_blockchain_request( ) -> Result { match req { BlockchainWriteRequest::WriteBlock(block) => write_block(env, block), - BlockchainWriteRequest::WriteAltBlock(_) => todo!(), + BlockchainWriteRequest::WriteAltBlock(alt_block) => write_alt_block(env, alt_block), BlockchainWriteRequest::StartReorg(_) => todo!(), BlockchainWriteRequest::ReverseReorg(_) => todo!(), - BlockchainWriteRequest::FlushAltBlocks => todo!(), + BlockchainWriteRequest::FlushAltBlocks => flush_alt_blocks(env), } } @@ -59,7 +59,56 @@ fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseR match result { Ok(()) => { TxRw::commit(tx_rw)?; - Ok(BlockchainResponse::WriteBlockOk) + 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::WriteAltBlock`]. +#[inline] +fn write_alt_block(env: &ConcreteEnv, block: &AltBlockInformation) -> 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)?; + crate::ops::alt_block::add_alt_block(block, &mut tables_mut) + }; + + 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 { + 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) }; + + match result { + Ok(()) => { + TxRw::commit(tx_rw)?; + Ok(BlockchainResponse::Ok) } Err(e) => { // INVARIANT: ensure database atomicity by aborting diff --git a/storage/blockchain/src/tables.rs b/storage/blockchain/src/tables.rs index 381430d7..deb957ea 100644 --- a/storage/blockchain/src/tables.rs +++ b/storage/blockchain/src/tables.rs @@ -145,10 +145,6 @@ cuprate_database::define_tables! { 19 => AltTransactionBlobs, TxHash => TxBlob, - - 20 => AltTransactionInfos, - TxHash => AltTransactionInfo, - } //---------------------------------------------------------------------------------------------------- Tests diff --git a/types/src/blockchain.rs b/types/src/blockchain.rs index 48eab292..33c3e8bd 100644 --- a/types/src/blockchain.rs +++ b/types/src/blockchain.rs @@ -4,12 +4,12 @@ //! responses are also tested in Cuprate's blockchain database crate. //---------------------------------------------------------------------------------------------------- Import +use crate::types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation}; +use crate::{AltBlockInformation, ChainId}; use std::{ collections::{HashMap, HashSet}, ops::Range, }; -use crate::{AltBlockInformation, ChainId}; -use crate::types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation}; //---------------------------------------------------------------------------------------------------- ReadRequest /// A read request to the blockchain database. @@ -223,6 +223,7 @@ pub enum BlockchainResponse { /// /// currently the response for: /// - [`BlockchainWriteRequest::WriteBlock`] + /// - [`BlockchainWriteRequest::WriteAltBlock`] /// - [`BlockchainWriteRequest::ReverseReorg`] /// - [`BlockchainWriteRequest::FlushAltBlocks`] Ok, @@ -231,7 +232,7 @@ pub enum BlockchainResponse { /// The [`ChainId`] of the old main chain blocks that were popped. old_main_chain_id: ChainId, /// The next alt chain blocks. - alt_chain: Vec + alt_chain: Vec, }, } diff --git a/types/src/types.rs b/types/src/types.rs index da4422a1..cc4543e6 100644 --- a/types/src/types.rs +++ b/types/src/types.rs @@ -39,8 +39,7 @@ pub struct ExtendedBlockHeader { //---------------------------------------------------------------------------------------------------- VerifiedTransactionInformation /// Verified information of a transaction. /// -/// - If this is in a [`VerifiedBlockInformation`] this represents a valid transaction -/// - If this is in an [`AltBlockInformation`] this represents a potentially valid transaction +/// This represents a valid transaction #[derive(Clone, Debug, PartialEq, Eq)] pub struct VerifiedTransactionInformation { /// The transaction itself. @@ -121,7 +120,7 @@ pub struct AltBlockInformation { /// [`Block::serialize`]. pub block_blob: Vec, /// All the transactions in the block, excluding the [`Block::miner_transaction`]. - pub txs: Vec, + pub txs: Vec>, /// The block's hash. /// /// [`Block::hash`].