diff --git a/storage/blockchain/src/lib.rs b/storage/blockchain/src/lib.rs index 0dea345b..8a6f96b6 100644 --- a/storage/blockchain/src/lib.rs +++ b/storage/blockchain/src/lib.rs @@ -98,6 +98,8 @@ clippy::too_many_lines ) )] +extern crate core; + // Only allow building 64-bit targets. // // This allows us to assume 64-bit diff --git a/storage/blockchain/src/ops/alt_block.rs b/storage/blockchain/src/ops/alt_block.rs index d04899ef..82a37ba8 100644 --- a/storage/blockchain/src/ops/alt_block.rs +++ b/storage/blockchain/src/ops/alt_block.rs @@ -1,12 +1,21 @@ -use bytemuck::TransparentWrapper; +use std::cmp::max; -use cuprate_database::{DatabaseRw, RuntimeError, StorableVec, DatabaseRo}; -use cuprate_helper::map::split_u128_into_low_high_bits; -use cuprate_types::{AltBlockInformation, Chain, VerifiedTransactionInformation}; +use bytemuck::TransparentWrapper; +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 monero_serai::block::BlockHeader; use crate::{ - tables::TablesMut, - types::{AltBlockHeight, AltChainInfo, AltTransactionInfo, BlockHash, CompactAltBlockInfo}, + ops::block::{get_block_extended_header_from_height, get_block_info}, + tables::{Tables, TablesMut}, + types::{ + AltBlockHeight, AltChainInfo, AltTransactionInfo, BlockHash, BlockHeight, + CompactAltBlockInfo, + }, }; pub fn add_alt_block( @@ -102,3 +111,119 @@ pub fn check_add_alt_chain_info( }, ) } + +pub fn alt_block_hash( + block_height: &BlockHeight, + alt_chain: ChainId, + tables: &mut impl Tables, +) -> Result { + let alt_chains = tables.alt_chain_infos(); + + let original_chain = { + let mut chain = alt_chain.into(); + loop { + let chain_info = alt_chains.get(&chain)?; + + if chain_info.common_ancestor_height < *block_height { + break Chain::Alt(chain.into()); + } + + match chain_info.parent_chain.into() { + Chain::Main => break Chain::Main, + Chain::Alt(alt_chain_id) => { + chain = alt_chain_id.into(); + continue; + } + } + } + }; + + match original_chain { + Chain::Main => { + get_block_info(&block_height, tables.block_infos()).map(|info| info.block_hash) + } + Chain::Alt(chain_id) => tables + .alt_blocks_info() + .get(&AltBlockHeight { + chain_id: chain_id.into(), + height: *block_height, + }) + .map(|info| info.block_hash), + } +} + +pub fn 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 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::>()?; + + Ok(res) +} + +pub fn get_alt_block_extended_header_from_height( + height: &AltBlockHeight, + table: &impl Tables, +) -> Result { + let block_info = table.alt_blocks_info().get(height)?; + + let block_blob = table.alt_block_blobs().get(height)?.0; + + let block_header = BlockHeader::read(&mut block_blob.as_slice())?; + + Ok(ExtendedBlockHeader { + version: HardFork::from_version(0).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( + block_info.cumulative_difficulty_low, + block_info.cumulative_difficulty_high, + ), + block_weight: block_info.weight, + long_term_weight: block_info.long_term_weight, + }) +} diff --git a/storage/blockchain/src/ops/block.rs b/storage/blockchain/src/ops/block.rs index 4f77d736..2e110fed 100644 --- a/storage/blockchain/src/ops/block.rs +++ b/storage/blockchain/src/ops/block.rs @@ -2,7 +2,7 @@ //---------------------------------------------------------------------------------------------------- Import use bytemuck::TransparentWrapper; -use monero_serai::block::Block; +use monero_serai::block::{Block, BlockHeader}; use cuprate_database::{ RuntimeError, StorableVec, {DatabaseRo, DatabaseRw}, @@ -190,7 +190,7 @@ pub fn get_block_extended_header_from_height( ) -> Result { let block_info = tables.block_infos().get(block_height)?; let block_blob = tables.block_blobs().get(block_height)?.0; - let block = Block::read(&mut block_blob.as_slice())?; + let block_header = BlockHeader::read(&mut block_blob.as_slice())?; let cumulative_difficulty = combine_low_high_bits_to_u128( block_info.cumulative_difficulty_low, @@ -201,10 +201,10 @@ pub fn get_block_extended_header_from_height( #[allow(clippy::cast_possible_truncation)] Ok(ExtendedBlockHeader { cumulative_difficulty, - version: HardFork::from_version(block.header.hardfork_version) + version: HardFork::from_version(block_header.hardfork_version) .expect("Stored block must have a valid hard-fork"), - vote: block.header.hardfork_signal, - timestamp: block.header.timestamp, + 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, }) diff --git a/storage/blockchain/src/ops/mod.rs b/storage/blockchain/src/ops/mod.rs index 1ec9c237..8a8f0f15 100644 --- a/storage/blockchain/src/ops/mod.rs +++ b/storage/blockchain/src/ops/mod.rs @@ -102,12 +102,12 @@ //! # Ok(()) } //! ``` +pub mod alt_block; pub mod block; pub mod blockchain; pub mod key_image; pub mod output; pub mod property; pub mod tx; -pub mod alt_block; mod macros; diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 207da416..eef40b5e 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -22,6 +22,7 @@ use cuprate_types::{ use crate::{ ops::{ + alt_block::{alt_block_hash, alt_extended_headers_in_range}, block::{ block_exists, get_block_extended_header_from_height, get_block_height, get_block_info, }, @@ -33,7 +34,7 @@ use crate::{ free::{compact_history_genesis_not_included, compact_history_index_to_height_offset}, types::{BlockchainReadHandle, ResponseResult}, }, - tables::{BlockHeights, BlockInfos, OpenTables, Tables}, + tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables}, types::{Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId}, }; @@ -87,7 +88,7 @@ fn map_request( match request { R::BlockExtendedHeader(block) => block_extended_header(env, block), R::BlockHash(block, chain) => block_hash(env, block, chain), - R::FindBlock(_) => todo!("Add alt blocks to DB"), + R::FindBlock(block_hash) => find_block(env, block_hash), R::FilterUnknownHashes(hashes) => filter_unknown_hashes(env, hashes), R::BlockExtendedHeaderInRange(range, chain) => { block_extended_header_in_range(env, range, chain) @@ -198,12 +199,39 @@ 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(_) => todo!("Add alt blocks to DB"), + Chain::Alt(chain) => { + alt_block_hash(&block_height, chain, &mut env_inner.open_tables(&tx_ro)?)? + } }; Ok(BlockchainResponse::BlockHash(block_hash)) } +/// [`BlockchainReadRequest::FindBlock`] +fn find_block(env: &ConcreteEnv, block_hash: BlockHash) -> ResponseResult { + // Single-threaded, no `ThreadLocal` required. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro()?; + + let table_block_heights = env_inner.open_db_ro::(&tx_ro)?; + + // Check the main chain first. + match table_block_heights.get(&block_hash) { + Ok(height) => return Ok(BlockchainResponse::FindBlock(Some((Chain::Main, height)))), + Err(RuntimeError::KeyNotFound) => (), + Err(e) => return Err(e), + } + + let table_alt_block_heights = env_inner.open_db_ro::(&tx_ro)?; + + let height = table_alt_block_heights.get(&block_hash)?; + + Ok(BlockchainResponse::FindBlock(Some(( + Chain::Alt(height.chain_id.into()), + height.height, + )))) +} + /// [`BlockchainReadRequest::FilterUnknownHashes`]. #[inline] fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet) -> ResponseResult { @@ -254,7 +282,14 @@ fn block_extended_header_in_range( get_block_extended_header_from_height(&block_height, tables) }) .collect::, RuntimeError>>()?, - Chain::Alt(_) => todo!("Add alt blocks to DB"), + Chain::Alt(chain_id) => { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + alt_extended_headers_in_range( + range, + chain_id, + get_tables!(env_inner, tx_ro, tables)?.as_ref(), + )? + } }; Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec))