diff --git a/storage/blockchain/src/ops/alt_block/block.rs b/storage/blockchain/src/ops/alt_block/block.rs index 83d04ba7..07878d55 100644 --- a/storage/blockchain/src/ops/alt_block/block.rs +++ b/storage/blockchain/src/ops/alt_block/block.rs @@ -1,16 +1,35 @@ -use crate::ops::alt_block::{ - add_alt_transaction_blob, check_add_alt_chain_info, get_alt_chain_history_ranges, - 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, BlockHash, BlockHeight, CompactAltBlockInfo}; use bytemuck::TransparentWrapper; +use monero_serai::block::{Block, BlockHeader}; + 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}; -use monero_serai::block::{Block, BlockHeader}; +use crate::{ + ops::{ + alt_block::{add_alt_transaction_blob, get_alt_transaction, update_alt_chain_info}, + block::get_block_info, + macros::doc_error, + }, + tables::{Tables, TablesMut}, + types::{AltBlockHeight, BlockHash, BlockHeight, CompactAltBlockInfo}, +}; + +/// Add a [`AltBlockInformation`] to the database. +/// +/// This extracts all the data from the input block and +/// maps/adds them to the appropriate database tables. +/// +#[doc = doc_error!()] +/// +/// # Panics +/// This function will panic if: +/// - `block.height` is == `0` +/// +/// # Already exists +/// This function will operate normally even if `block` already +/// exists, i.e., this function will not return `Err` even if you +/// call this function infinitely with the same block. pub fn add_alt_block( alt_block: &AltBlockInformation, tables: &mut impl TablesMut, @@ -24,7 +43,7 @@ pub fn add_alt_block( .alt_block_heights_mut() .put(&alt_block.block_hash, &alt_block_height)?; - check_add_alt_chain_info(&alt_block_height, &alt_block.block.header.previous, tables)?; + update_alt_chain_info(&alt_block_height, &alt_block.block.header.previous, tables)?; let (cumulative_difficulty_low, cumulative_difficulty_high) = split_u128_into_low_high_bits(alt_block.cumulative_difficulty); @@ -49,13 +68,18 @@ pub fn add_alt_block( )?; assert_eq!(alt_block.txs.len(), alt_block.block.transactions.len()); - for tx in alt_block.txs.iter() { + for tx in &alt_block.txs { add_alt_transaction_blob(tx, tables)?; } Ok(()) } +/// Retrieves an [`AltBlockInformation`] from the database. +/// +/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others +/// even if they are technically part of this chain. +#[doc = doc_error!()] pub fn get_alt_block( alt_block_height: &AltBlockHeight, tables: &impl Tables, @@ -89,13 +113,21 @@ pub fn get_alt_block( }) } +/// Retrieves the hash of the block at the given `block_height` on the alt chain with +/// the given [`ChainId`]. +/// +/// This function will get blocks from the whole chain, for example if you were to ask for height +/// `0` with any [`ChainId`] (as long that chain actually exists) you will get the main chain genesis. +/// +#[doc = doc_error!()] pub fn get_alt_block_hash( block_height: &BlockHeight, alt_chain: ChainId, - tables: &mut impl Tables, + tables: &impl Tables, ) -> Result<BlockHash, RuntimeError> { let alt_chains = tables.alt_chain_infos(); + // First find what [`ChainId`] this block would be stored under. let original_chain = { let mut chain = alt_chain.into(); loop { @@ -115,9 +147,10 @@ pub fn get_alt_block_hash( } }; + // Get the block hash. match original_chain { Chain::Main => { - get_block_info(&block_height, tables.block_infos()).map(|info| info.block_hash) + get_block_info(block_height, tables.block_infos()).map(|info| info.block_hash) } Chain::Alt(chain_id) => tables .alt_blocks_info() @@ -129,37 +162,12 @@ pub fn get_alt_block_hash( } } -pub fn get_alt_extended_headers_in_range( - range: std::ops::Range<BlockHeight>, - alt_chain: ChainId, - tables: &impl Tables, -) -> Result<Vec<ExtendedBlockHeader>, RuntimeError> { - // TODO: this function does not use rayon, however it probably should. - - let alt_chains = tables.alt_chain_infos(); - let ranges = get_alt_chain_history_ranges(range, alt_chain, alt_chains)?; - - let res = ranges - .into_iter() - .rev() - .map(|(chain, range)| { - range.into_iter().map(move |height| match chain { - Chain::Main => get_block_extended_header_from_height(&height, tables), - Chain::Alt(chain_id) => get_alt_block_extended_header_from_height( - &AltBlockHeight { - chain_id: chain_id.into(), - height, - }, - tables, - ), - }) - }) - .flatten() - .collect::<Result<_, _>>()?; - - Ok(res) -} - +/// Retrieves the [`ExtendedBlockHeader`] of the alt-block with an exact [`AltBlockHeight`]. +/// +/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others +/// even if they are technically part of this chain. +/// +#[doc = doc_error!()] pub fn get_alt_block_extended_header_from_height( height: &AltBlockHeight, table: &impl Tables, @@ -171,7 +179,8 @@ pub fn get_alt_block_extended_header_from_height( let block_header = BlockHeader::read(&mut block_blob.as_slice())?; Ok(ExtendedBlockHeader { - version: HardFork::from_version(block_header.hardfork_version).expect("Block in DB must have correct version"), + version: HardFork::from_version(block_header.hardfork_version) + .expect("Block in DB must have correct version"), vote: block_header.hardfork_version, timestamp: block_header.timestamp, cumulative_difficulty: combine_low_high_bits_to_u128( @@ -186,22 +195,33 @@ pub fn get_alt_block_extended_header_from_height( #[cfg(test)] mod tests { use std::num::NonZero; - use cuprate_database::{Env, EnvInner, TxRw}; - use cuprate_test_utils::data::{BLOCK_V1_TX2, BLOCK_V9_TX3, BLOCK_V16_TX0}; - use cuprate_types::ChainId; - use crate::ops::alt_block::{add_alt_block, flush_alt_blocks, get_alt_block, get_alt_extended_headers_in_range}; - use crate::ops::block::{add_block, pop_block}; - use crate::tables::OpenTables; - use crate::tests::{assert_all_tables_are_empty, map_verified_block_to_alt, tmp_concrete_env}; - use crate::types::AltBlockHeight; + use cuprate_database::{Env, EnvInner, TxRw}; + use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3}; + use cuprate_types::{Chain, ChainId}; + + use crate::{ + ops::{ + alt_block::{ + add_alt_block, flush_alt_blocks, get_alt_block, + get_alt_block_extended_header_from_height, get_alt_block_hash, + get_alt_chain_history_ranges, + }, + block::{add_block, pop_block}, + }, + tables::{OpenTables, Tables}, + tests::{assert_all_tables_are_empty, map_verified_block_to_alt, tmp_concrete_env}, + types::AltBlockHeight, + }; + + #[allow(clippy::range_plus_one)] #[test] fn all_alt_blocks() { let (env, _tmp) = tmp_concrete_env(); let env_inner = env.env_inner(); assert_all_tables_are_empty(&env); - let chain_id = ChainId(NonZero::new(1).unwrap()).into(); + let chain_id = ChainId(NonZero::new(1).unwrap()); // Add initial block. { @@ -245,25 +265,44 @@ mod tests { let alt_block_2 = get_alt_block(&alt_height, &tables).unwrap(); assert_eq!(alt_block.block, alt_block_2.block); - let headers = get_alt_extended_headers_in_range(0..(height + 1), chain_id, &tables).unwrap(); - assert_eq!(headers.len(), height); + let headers = get_alt_chain_history_ranges( + 0..(height + 1), + chain_id, + tables.alt_chain_infos(), + ) + .unwrap(); - let last_header = headers.last().unwrap(); - assert_eq!(last_header.timestamp, alt_block.block.header.timestamp); - assert_eq!(last_header.block_weight, alt_block.weight); - assert_eq!(last_header.long_term_weight, alt_block.long_term_weight); - assert_eq!(last_header.cumulative_difficulty, alt_block.cumulative_difficulty); - assert_eq!(last_header.version.as_u8(), alt_block.block.header.hardfork_version); - assert_eq!(last_header.vote, alt_block.block.header.hardfork_signal); + assert_eq!(headers.len(), 2); + assert_eq!(headers[1], (Chain::Main, 0..1)); + assert_eq!(headers[0], (Chain::Alt(chain_id), 1..(height + 1))); prev_hash = alt_block.block_hash; + + let header = + get_alt_block_extended_header_from_height(&alt_height, &tables).unwrap(); + + assert_eq!(header.timestamp, alt_block.block.header.timestamp); + assert_eq!(header.block_weight, alt_block.weight); + assert_eq!(header.long_term_weight, alt_block.long_term_weight); + assert_eq!( + header.cumulative_difficulty, + alt_block.cumulative_difficulty + ); + assert_eq!( + header.version.as_u8(), + alt_block.block.header.hardfork_version + ); + assert_eq!(header.vote, alt_block.block.header.hardfork_signal); + + let block_hash = get_alt_block_hash(&height, chain_id, &tables).unwrap(); + + assert_eq!(block_hash, alt_block.block_hash); } drop(tables); TxRw::commit(tx_rw).unwrap(); } - { let mut tx_rw = env_inner.tx_rw().unwrap(); @@ -278,5 +317,4 @@ mod tests { assert_all_tables_are_empty(&env); } - } diff --git a/storage/blockchain/src/ops/alt_block/chain.rs b/storage/blockchain/src/ops/alt_block/chain.rs index 166a294a..3e27d7d4 100644 --- a/storage/blockchain/src/ops/alt_block/chain.rs +++ b/storage/blockchain/src/ops/alt_block/chain.rs @@ -1,20 +1,43 @@ -use crate::tables::{AltChainInfos, TablesMut}; -use crate::types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight}; -use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError}; -use cuprate_types::{Chain, ChainId}; use std::cmp::max; -pub fn check_add_alt_chain_info( +use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError}; +use cuprate_types::{Chain, ChainId}; + +use crate::{ + ops::macros::{doc_add_alt_block_inner_invariant, doc_error}, + tables::{AltChainInfos, TablesMut}, + types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight}, +}; + +/// Updates the [`AltChainInfo`] with information on a new alt-block. +/// +#[doc = doc_add_alt_block_inner_invariant!()] +#[doc = doc_error!()] +/// +/// # Panics +/// +/// This will panic if [`AltBlockHeight::height`] == `0`. +pub fn update_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(()), + // try update the info if one exists for this chain. + let update = tables + .alt_chain_infos_mut() + .update(&alt_block_height.chain_id, |mut info| { + info.chain_height = alt_block_height.height + 1; + Some(info) + }); + + match update { + Ok(()) => return Ok(()), Err(RuntimeError::KeyNotFound) => (), Err(e) => return Err(e), } + // If one doesn't already exist add it. + 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, @@ -25,12 +48,18 @@ pub fn check_add_alt_chain_info( &alt_block_height.chain_id, &AltChainInfo { parent_chain: parent_chain.into(), - common_ancestor_height: alt_block_height.height - 1, - chain_height: alt_block_height.height, + common_ancestor_height: alt_block_height.height.checked_sub(1).unwrap(), + chain_height: alt_block_height.height + 1, }, ) } +/// Get the height history of an alt-chain in reverse chronological order. +/// +/// Height history is a list of height ranges with the corresponding [`Chain`] they are stored under. +/// For example if your range goes from height `0` the last entry in the list will be [`Chain::Main`] +/// upto the height where the first split occurs. +#[doc = doc_error!()] pub fn get_alt_chain_history_ranges( range: std::ops::Range<BlockHeight>, alt_chain: ChainId, @@ -46,7 +75,7 @@ pub fn get_alt_chain_history_ranges( let start_height = max(range.start, chain_info.common_ancestor_height + 1); ranges.push((Chain::Alt(current_chain_id.into()), start_height..i)); - i = chain_info.common_ancestor_height; + i = chain_info.common_ancestor_height + 1; match chain_info.parent_chain.into() { Chain::Main => { diff --git a/storage/blockchain/src/ops/alt_block/mod.rs b/storage/blockchain/src/ops/alt_block/mod.rs index 72e0933e..36e4768e 100644 --- a/storage/blockchain/src/ops/alt_block/mod.rs +++ b/storage/blockchain/src/ops/alt_block/mod.rs @@ -1,11 +1,62 @@ -mod block; -mod chain; -mod tx; - +//! Alternative Block/Chain Ops +//! +//! Alternative chains are chains that potentially have more proof-of-work than the main-chain +//! which we are tracking to potentially re-org to. +//! +//! Cuprate uses an ID system for alt-chains. When a split is made from the main-chain we generate +//! a random [`ChainID`](cuprate_types::ChainId) and assign it to the chain: +//! +//! ```text +//! | +//! | +//! | split +//! |------------- +//! | | +//! | | +//! \|/ \|/ +//! main-chain ChainID(X) +//! ``` +//! +//! In that example if we were to receive an alt-block which immediately follows the top block of `ChainID(X)` +//! then that block will also be stored under `ChainID(X)`. However if it follows from another block from `ChainID(X)` +//! we will split into a chain with a different ID. +//! +//! ```text +//! | +//! | +//! | split +//! |------------- +//! | | split +//! | |-------------| +//! | | | +//! | | | +//! | | | +//! \|/ \|/ \|/ +//! main-chain ChainID(X) ChainID(Z) +//! ``` +//! +//! As you can see if we wanted to get all the alt-blocks in `ChainID(Z)` that now includes some blocks from `ChainID(X)` as well. +//! [`get_alt_chain_history_ranges`] covers this and is the method to get the ranges of heights needed from each [`ChainID`](cuprate_types::ChainId) +//! to get all the alt-blocks in a given [`ChainID`](cuprate_types::ChainId). +//! +//! Although this should be kept in mind as a possibility because Cuprate's block downloader will only track a single chain it is +//! unlikely that we will be tracking [`ChainID`](cuprate_types::ChainId) that don't immediately connect to the main-chain. +//! +//! ## Why not use block's previous field? +//! +//! Although that would be easier, it makes getting a range of block extremely slow, as we have to build the weight cache to verify +//! blocks, roughly 100,000 block headers needed, this cost was seen as too high. pub use block::*; pub use chain::*; pub use tx::*; +mod block; +mod chain; +mod tx; + +/// Flush all alt-block data from all the alt-block tables. +/// +/// This function completely empties the alt block tables. pub fn flush_alt_blocks<'a, E: cuprate_database::EnvInner<'a>>( env_inner: &E, tx_rw: &mut E::Rw<'_>, diff --git a/storage/blockchain/src/ops/alt_block/tx.rs b/storage/blockchain/src/ops/alt_block/tx.rs index a49c72ae..5671e4b4 100644 --- a/storage/blockchain/src/ops/alt_block/tx.rs +++ b/storage/blockchain/src/ops/alt_block/tx.rs @@ -1,10 +1,22 @@ -use crate::tables::{Tables, TablesMut}; -use crate::types::{AltTransactionInfo, TxHash}; use bytemuck::TransparentWrapper; -use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec}; -use cuprate_types::VerifiedTransactionInformation; use monero_serai::transaction::Transaction; +use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec}; +use cuprate_types::VerifiedTransactionInformation; + +use crate::ops::macros::{doc_add_alt_block_inner_invariant, doc_error}; +use crate::tables::{Tables, TablesMut}; +use crate::types::{AltTransactionInfo, TxHash}; + +/// Adds a [`VerifiedTransactionInformation`] form an alt-block to the DB, if +/// that transaction is not already in the DB. +/// +/// If the transaction is in the main-chain this function will still fill in the +/// [`AltTransactionInfos`](crate::tables::AltTransactionInfos) table, as that +/// table holds data which we don't keep around for main-chain txs. +/// +#[doc = doc_add_alt_block_inner_invariant!()] +#[doc = doc_error!()] pub fn add_alt_transaction_blob( tx: &VerifiedTransactionInformation, tables: &mut impl TablesMut, @@ -29,6 +41,9 @@ pub fn add_alt_transaction_blob( .put(&tx.tx_hash, StorableVec::wrap_ref(&tx.tx_blob)) } +/// Retrieve a [`VerifiedTransactionInformation`] from the database. +/// +#[doc = doc_error!()] pub fn get_alt_transaction( tx_hash: &TxHash, tables: &impl Tables, diff --git a/storage/blockchain/src/ops/block.rs b/storage/blockchain/src/ops/block.rs index b0997f76..45bab41c 100644 --- a/storage/blockchain/src/ops/block.rs +++ b/storage/blockchain/src/ops/block.rs @@ -38,11 +38,6 @@ use crate::{ /// This function will panic if: /// - `block.height > u32::MAX` (not normally possible) /// - `block.height` is not != [`chain_height`] -/// -/// # Already exists -/// This function will operate normally even if `block` already -/// exists, i.e., this function will not return `Err` even if you -/// call this function infinitely with the same block. // no inline, too big. pub fn add_block( block: &VerifiedBlockInformation, @@ -133,6 +128,9 @@ pub fn add_block( /// Remove the top/latest block from the database. /// /// The removed block's data is returned. +/// +/// If a [`ChainId`] is specified the popped block will be added to the alt block tables under +/// that [`ChainId`]. Otherwise, the block will be completely removed from the DB. #[doc = doc_error!()] /// /// In `pop_block()`'s case, [`RuntimeError::KeyNotFound`] @@ -169,7 +167,7 @@ pub fn pop_block( tx_hash: tx.hash(), fee: tx_fee(&tx), tx, - }) + }); } } @@ -180,11 +178,16 @@ pub fn pop_block( block_blob, txs, block_hash: block_info.block_hash, + // We know the PoW is valid for this block so just set it so it will always verify as + // valid. pow_hash: [0; 32], height: block_height, weight: block_info.weight, long_term_weight: block_info.long_term_weight, - cumulative_difficulty: 0, + cumulative_difficulty: combine_low_high_bits_to_u128( + block_info.cumulative_difficulty_low, + block_info.cumulative_difficulty_high, + ), chain_id, }, tables, @@ -229,8 +232,6 @@ pub fn get_block_extended_header_from_height( block_info.cumulative_difficulty_high, ); - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - #[allow(clippy::cast_possible_truncation)] Ok(ExtendedBlockHeader { cumulative_difficulty, version: HardFork::from_version(block_header.hardfork_version) @@ -302,14 +303,14 @@ mod test { use cuprate_database::{Env, EnvInner, TxRw}; use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3}; - use super::*; - use crate::{ ops::tx::{get_tx, tx_exists}, tables::OpenTables, tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen}, }; + use super::*; + /// Tests all above block functions. /// /// Note that this doesn't test the correctness of values added, as the diff --git a/storage/blockchain/src/ops/macros.rs b/storage/blockchain/src/ops/macros.rs index b7cdba47..e547c7f2 100644 --- a/storage/blockchain/src/ops/macros.rs +++ b/storage/blockchain/src/ops/macros.rs @@ -31,3 +31,24 @@ When calling this function, ensure that either: }; } pub(super) use doc_add_block_inner_invariant; + +// This is pretty much the same as [`doc_add_block_inner_invariant`], it's not worth the effort to reduce +// the duplication. +/// Generate `# Invariant` documentation for internal alt block `fn`'s +/// that should be called directly with caution. +macro_rules! doc_add_alt_block_inner_invariant { + () => { + r#"# ⚠️ Invariant ⚠️ +This function mainly exists to be used internally by the parent function [`crate::ops::alt_block::add_alt_block`]. + +`add_alt_block()` makes sure all data related to the input is mutated, while +this function _does not_, it specifically mutates _particular_ tables. + +This is usually undesired - although this function is still available to call directly. + +When calling this function, ensure that either: +1. This effect (incomplete database mutation) is what is desired, or that... +2. ...the other tables will also be mutated to a correct state"# + }; +} +pub(super) use doc_add_alt_block_inner_invariant; diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 70da01b3..416e6137 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -1,28 +1,32 @@ //! Database reader thread-pool definitions and logic. -//---------------------------------------------------------------------------------------------------- Import use std::{ collections::{HashMap, HashSet}, sync::Arc, }; +//---------------------------------------------------------------------------------------------------- Import use rayon::{ iter::{IntoParallelIterator, ParallelIterator}, + prelude::*, ThreadPool, }; use thread_local::ThreadLocal; use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError}; -use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads}; +use cuprate_database_service::{DatabaseReadService, init_thread_pool, ReaderThreads}; use cuprate_helper::map::combine_low_high_bits_to_u128; use cuprate_types::{ blockchain::{BlockchainReadRequest, BlockchainResponse}, - Chain, ExtendedBlockHeader, OutputOnChain, + Chain, ChainId, ExtendedBlockHeader, OutputOnChain, }; use crate::{ ops::{ - alt_block::{get_alt_block_hash, get_alt_extended_headers_in_range}, + alt_block::{ + get_alt_block_extended_header_from_height, get_alt_block_hash, + get_alt_chain_history_ranges, + }, block::{ block_exists, get_block_extended_header_from_height, get_block_height, get_block_info, }, @@ -35,8 +39,11 @@ use crate::{ types::{BlockchainReadHandle, ResponseResult}, }, tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables}, - types::{Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId}, + types::{ + AltBlockHeight, Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId, + }, }; +use crate::ops::alt_block::get_alt_block; //---------------------------------------------------------------------------------------------------- init_read_service /// Initialize the [`BlockchainReadHandle`] thread-pool backed by [`rayon`]. @@ -100,6 +107,7 @@ fn map_request( R::KeyImagesSpent(set) => key_images_spent(env, set), R::CompactChainHistory => compact_chain_history(env), R::FindFirstUnknown(block_ids) => find_first_unknown(env, &block_ids), + R::AltBlocksInChain(chain_id) => alt_blocks_in_chain(env, chain_id), } /* SOMEDAY: post-request handling, run some code for each request? */ @@ -200,7 +208,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) => { - get_alt_block_hash(&block_height, chain, &mut env_inner.open_tables(&tx_ro)?)? + get_alt_block_hash(&block_height, chain, &env_inner.open_tables(&tx_ro)?)? } }; @@ -283,12 +291,36 @@ fn block_extended_header_in_range( }) .collect::<Result<Vec<ExtendedBlockHeader>, RuntimeError>>()?, Chain::Alt(chain_id) => { - let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; - get_alt_extended_headers_in_range( - range, - chain_id, - get_tables!(env_inner, tx_ro, tables)?.as_ref(), - )? + let ranges = { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + let alt_chains = tables.alt_chain_infos(); + + get_alt_chain_history_ranges(range, chain_id, alt_chains)? + }; + + ranges + .par_iter() + .rev() + .map(|(chain, range)| { + range.clone().into_par_iter().map(|height| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + match *chain { + Chain::Main => get_block_extended_header_from_height(&height, tables), + Chain::Alt(chain_id) => get_alt_block_extended_header_from_height( + &AltBlockHeight { + chain_id: chain_id.into(), + height, + }, + tables, + ), + } + }) + }) + .flatten() + .collect::<Result<Vec<_>, _>>()? } }; @@ -524,3 +556,45 @@ fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseRes BlockchainResponse::FindFirstUnknown(Some((idx, last_known_height + 1))) }) } + +/// [`BlockchainReadRequest::AltBlocksInChain`] +fn alt_blocks_in_chain(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult { + // Prepare tx/tables in `ThreadLocal`. + let env_inner = env.env_inner(); + let tx_ro = thread_local(env); + let tables = thread_local(env); + + // Get the history of this alt-chain. + let history = { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + get_alt_chain_history_ranges(0..usize::MAX, chain_id, tables.alt_chain_infos())? + }; + + // Get all the blocks until we join the main-chain. + let blocks = history + .par_iter() + .rev() + .skip(1) + .flat_map(|(chain_id, range)| { + let Chain::Alt(chain_id) = chain_id else { + panic!("Should not have main chain blocks here we skipped last range"); + }; + + range.clone().into_par_iter().map(|height| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + get_alt_block( + &AltBlockHeight { + chain_id: (*chain_id).into(), + height, + }, + tables, + ) + }) + }) + .collect::<Result<_, _>>()?; + + Ok(BlockchainResponse::AltBlocksInChain(blocks)) +} diff --git a/storage/blockchain/src/service/write.rs b/storage/blockchain/src/service/write.rs index 95124d41..849a3030 100644 --- a/storage/blockchain/src/service/write.rs +++ b/storage/blockchain/src/service/write.rs @@ -9,12 +9,13 @@ use cuprate_types::{ 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}, + service::{ + free::map_valid_alt_block_to_verified_block, + types::{BlockchainWriteHandle, ResponseResult}, + }, tables::{OpenTables, Tables, TablesMut}, - types::AltChainInfo, + types::{AltBlockHeight, AltChainInfo}, }; //---------------------------------------------------------------------------------------------------- init_write_service @@ -106,19 +107,23 @@ fn pop_blocks(env: &ConcreteEnv, numb_blocks: usize) -> ResponseResult { let env_inner = env.env_inner(); let mut tx_rw = env_inner.tx_rw()?; + // TODO: try blocks let result = { + // flush all the current alt blocks as they may reference blocks to be popped. crate::ops::alt_block::flush_alt_blocks(&env_inner, &mut tx_rw)?; let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; - + // generate a `ChainId` for the popped blocks. let old_main_chain_id = ChainId(rand::random()); + // pop the blocks 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)?; } + // Update the alt_chain_info with the correct information. tables_mut.alt_chain_infos_mut().put( &old_main_chain_id.into(), &AltChainInfo { @@ -155,11 +160,14 @@ fn reverse_reorg(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult { let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; let chain_info = tables_mut.alt_chain_infos().get(&chain_id.into())?; + // Although this doesn't guarantee the chain was popped from the main-chain, it's an easy + // thing for us to check. assert_eq!(Chain::from(chain_info.parent_chain), Chain::Main); let tob_block_height = crate::ops::blockchain::top_block_height(tables_mut.block_heights())?; + // pop any blocks that were added as part of a re-org. for _ in chain_info.common_ancestor_height..tob_block_height { crate::ops::block::pop_block(None, &mut tables_mut)?; } @@ -177,6 +185,7 @@ fn reverse_reorg(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult { }) .collect::<Vec<_>>(); + // Add the old main chain blocks back to the main chain. for res_alt_block in alt_blocks { let alt_block = res_alt_block?; diff --git a/storage/blockchain/src/tests.rs b/storage/blockchain/src/tests.rs index 60239127..d57a3715 100644 --- a/storage/blockchain/src/tests.rs +++ b/storage/blockchain/src/tests.rs @@ -90,7 +90,10 @@ pub(crate) fn assert_all_tables_are_empty(env: &ConcreteEnv) { assert_eq!(crate::ops::tx::get_num_tx(tables.tx_ids()).unwrap(), 0); } -pub(crate) fn map_verified_block_to_alt(verified_block: VerifiedBlockInformation, chain_id: ChainId) -> AltBlockInformation { +pub(crate) fn map_verified_block_to_alt( + verified_block: VerifiedBlockInformation, + chain_id: ChainId, +) -> AltBlockInformation { AltBlockInformation { block: verified_block.block, block_blob: verified_block.block_blob, @@ -103,4 +106,4 @@ pub(crate) fn map_verified_block_to_alt(verified_block: VerifiedBlockInformation cumulative_difficulty: verified_block.cumulative_difficulty, chain_id, } -} \ No newline at end of file +} diff --git a/types/src/blockchain.rs b/types/src/blockchain.rs index c2a5517d..9f79c3a3 100644 --- a/types/src/blockchain.rs +++ b/types/src/blockchain.rs @@ -3,14 +3,15 @@ //! Tests that assert particular requests lead to particular //! 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}; +//---------------------------------------------------------------------------------------------------- Import +use crate::types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation}; + //---------------------------------------------------------------------------------------------------- ReadRequest /// A read request to the blockchain database. /// @@ -92,12 +93,14 @@ pub enum BlockchainReadRequest { CompactChainHistory, /// A request to find the first unknown block ID in a list of block IDs. - //// + /// /// # Invariant /// The [`Vec`] containing the block IDs must be sorted in chronological block /// order, or else the returned response is unspecified and meaningless, /// as this request performs a binary search. FindFirstUnknown(Vec<[u8; 32]>), + /// A request for all alt blocks in the chain with the given [`ChainId`]. + AltBlocksInChain(ChainId), } //---------------------------------------------------------------------------------------------------- WriteRequest @@ -120,12 +123,14 @@ pub enum BlockchainWriteRequest { /// /// Input is the amount of blocks to pop. /// - /// This request flush all alt-chains from the cache before adding the popped blocks to the alt cache. + /// This request flushes 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. /// + /// # Invariant /// 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. @@ -215,6 +220,11 @@ pub enum BlockchainResponse { /// This will be [`None`] if all blocks were known. FindFirstUnknown(Option<(usize, usize)>), + /// The response for [`BlockchainReadRequest::AltBlocksInChain`]. + /// + /// Contains all the alt blocks in the alt-chain in chronological order. + AltBlocksInChain(Vec<AltBlockInformation>), + //------------------------------------------------------ Writes /// A generic Ok response to indicate a request was successfully handled. ///