more docs + cleanup + alt blocks request

This commit is contained in:
Boog900 2024-09-07 02:02:19 +01:00
parent 123aedd6a9
commit ba5c5ac45d
No known key found for this signature in database
GPG key ID: 42AB1287CB0041C2
10 changed files with 368 additions and 117 deletions

View file

@ -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);
}
}

View file

@ -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 => {

View file

@ -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<'_>,

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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))
}

View file

@ -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?;

View file

@ -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,
}
}
}

View file

@ -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.
///