mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
Blockchain: add alt-block handling (#260)
Some checks are pending
Audit / audit (push) Waiting to run
CI / fmt (push) Waiting to run
CI / typo (push) Waiting to run
CI / ci (macos-latest, stable, bash) (push) Waiting to run
CI / ci (ubuntu-latest, stable, bash) (push) Waiting to run
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Waiting to run
Deny / audit (push) Waiting to run
Doc / build (push) Waiting to run
Doc / deploy (push) Blocked by required conditions
Some checks are pending
Audit / audit (push) Waiting to run
CI / fmt (push) Waiting to run
CI / typo (push) Waiting to run
CI / ci (macos-latest, stable, bash) (push) Waiting to run
CI / ci (ubuntu-latest, stable, bash) (push) Waiting to run
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Waiting to run
Deny / audit (push) Waiting to run
Doc / build (push) Waiting to run
Doc / deploy (push) Blocked by required conditions
* add new tables & types * add function to fully add an alt block * resolve current todo!s * add new requests * WIP: starting re-orgs * add last service request * commit Cargo.lock * add test * more docs + cleanup + alt blocks request * clippy + fmt * document types * move tx_fee to helper * more doc updates * fmt * fix imports * fix fee * Apply suggestions from code review Co-authored-by: hinto-janai <hinto.janai@protonmail.com> * remove default features from `cuprate-helper` * review fixes * fix find_block * add a test and fix some issues in chain history * fix clippy * fmt * Apply suggestions from code review Co-authored-by: hinto-janai <hinto.janai@protonmail.com> * add dev dep * cargo update * move `flush_alt_blocks` * review fixes * more review fixes * fix clippy * remove INVARIANT comments --------- Co-authored-by: hinto-janai <hinto.janai@protonmail.com>
This commit is contained in:
parent
e3a918bca5
commit
4169c45c58
26 changed files with 1864 additions and 652 deletions
840
Cargo.lock
generated
840
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,8 +9,8 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# All features on by default.
|
# All features off by default.
|
||||||
default = ["std", "atomic", "asynch", "cast", "fs", "num", "map", "time", "thread", "constants"]
|
default = []
|
||||||
std = []
|
std = []
|
||||||
atomic = ["dep:crossbeam"]
|
atomic = ["dep:crossbeam"]
|
||||||
asynch = ["dep:futures", "dep:rayon"]
|
asynch = ["dep:futures", "dep:rayon"]
|
||||||
|
@ -21,6 +21,7 @@ num = []
|
||||||
map = ["cast", "dep:monero-serai"]
|
map = ["cast", "dep:monero-serai"]
|
||||||
time = ["dep:chrono", "std"]
|
time = ["dep:chrono", "std"]
|
||||||
thread = ["std", "dep:target_os_lib"]
|
thread = ["std", "dep:target_os_lib"]
|
||||||
|
tx = ["dep:monero-serai"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam = { workspace = true, optional = true }
|
crossbeam = { workspace = true, optional = true }
|
||||||
|
@ -40,6 +41,7 @@ target_os_lib = { package = "libc", version = "0.2.151", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
curve25519-dalek = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
|
@ -31,6 +31,8 @@ pub mod thread;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
||||||
|
#[cfg(feature = "tx")]
|
||||||
|
pub mod tx;
|
||||||
//---------------------------------------------------------------------------------------------------- Private Usage
|
//---------------------------------------------------------------------------------------------------- Private Usage
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
|
70
helper/src/tx.rs
Normal file
70
helper/src/tx.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//! Utils for working with [`Transaction`]
|
||||||
|
|
||||||
|
use monero_serai::transaction::{Input, Transaction};
|
||||||
|
|
||||||
|
/// Calculates the fee of the [`Transaction`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This will panic if the inputs overflow or the transaction outputs too much, so should only
|
||||||
|
/// be used on known to be valid txs.
|
||||||
|
pub 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 = fee.checked_sub(output.amount.unwrap_or(0)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Transaction::V2 { proofs, .. } => {
|
||||||
|
fee = proofs.as_ref().unwrap().base.fee;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fee
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use curve25519_dalek::{edwards::CompressedEdwardsY, EdwardsPoint};
|
||||||
|
use monero_serai::transaction::{NotPruned, Output, Timelock, TransactionPrefix};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
|
||||||
|
fn tx_fee_panic() {
|
||||||
|
let input = Input::ToKey {
|
||||||
|
amount: Some(u64::MAX),
|
||||||
|
key_offsets: vec![],
|
||||||
|
key_image: EdwardsPoint::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = Output {
|
||||||
|
amount: Some(u64::MAX),
|
||||||
|
key: CompressedEdwardsY::default(),
|
||||||
|
view_tag: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx = Transaction::<NotPruned>::V1 {
|
||||||
|
prefix: TransactionPrefix {
|
||||||
|
additional_timelock: Timelock::None,
|
||||||
|
inputs: vec![input; 2],
|
||||||
|
outputs: vec![output],
|
||||||
|
extra: vec![],
|
||||||
|
},
|
||||||
|
signatures: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
tx_fee(&tx);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,11 +25,12 @@ cuprate-database = { path = "../database" }
|
||||||
cuprate-database-service = { path = "../service" }
|
cuprate-database-service = { path = "../service" }
|
||||||
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] }
|
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] }
|
||||||
cuprate-types = { path = "../../types", features = ["blockchain"] }
|
cuprate-types = { path = "../../types", features = ["blockchain"] }
|
||||||
|
cuprate-pruning = { path = "../../pruning" }
|
||||||
|
|
||||||
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
|
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
|
||||||
bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
|
bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
|
||||||
curve25519-dalek = { workspace = true }
|
curve25519-dalek = { workspace = true }
|
||||||
cuprate-pruning = { path = "../../pruning" }
|
rand = { workspace = true }
|
||||||
monero-serai = { workspace = true, features = ["std"] }
|
monero-serai = { workspace = true, features = ["std"] }
|
||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
|
337
storage/blockchain/src/ops/alt_block/block.rs
Normal file
337
storage/blockchain/src/ops/alt_block/block.rs
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
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 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},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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<'_>,
|
||||||
|
) -> Result<(), 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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:
|
||||||
|
/// - `alt_block.height` is == `0`
|
||||||
|
/// - `alt_block.txs.len()` != `alt_block.block.transactions.len()`
|
||||||
|
///
|
||||||
|
pub fn add_alt_block(
|
||||||
|
alt_block: &AltBlockInformation,
|
||||||
|
tables: &mut impl TablesMut,
|
||||||
|
) -> Result<(), RuntimeError> {
|
||||||
|
let alt_block_height = AltBlockHeight {
|
||||||
|
chain_id: alt_block.chain_id.into(),
|
||||||
|
height: alt_block.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
tables
|
||||||
|
.alt_block_heights_mut()
|
||||||
|
.put(&alt_block.block_hash, &alt_block_height)?;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let alt_block_info = CompactAltBlockInfo {
|
||||||
|
block_hash: alt_block.block_hash,
|
||||||
|
pow_hash: alt_block.pow_hash,
|
||||||
|
height: alt_block.height,
|
||||||
|
weight: alt_block.weight,
|
||||||
|
long_term_weight: alt_block.long_term_weight,
|
||||||
|
cumulative_difficulty_low,
|
||||||
|
cumulative_difficulty_high,
|
||||||
|
};
|
||||||
|
|
||||||
|
tables
|
||||||
|
.alt_blocks_info_mut()
|
||||||
|
.put(&alt_block_height, &alt_block_info)?;
|
||||||
|
|
||||||
|
tables.alt_block_blobs_mut().put(
|
||||||
|
&alt_block_height,
|
||||||
|
StorableVec::wrap_ref(&alt_block.block_blob),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(alt_block.txs.len(), alt_block.block.transactions.len());
|
||||||
|
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,
|
||||||
|
) -> Result<AltBlockInformation, RuntimeError> {
|
||||||
|
let block_info = tables.alt_blocks_info().get(alt_block_height)?;
|
||||||
|
|
||||||
|
let block_blob = tables.alt_block_blobs().get(alt_block_height)?.0;
|
||||||
|
|
||||||
|
let block = Block::read(&mut block_blob.as_slice())?;
|
||||||
|
|
||||||
|
let txs = block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.map(|tx_hash| get_alt_transaction(tx_hash, tables))
|
||||||
|
.collect::<Result<_, RuntimeError>>()?;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: &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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the block hash.
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
) -> Result<ExtendedBlockHeader, RuntimeError> {
|
||||||
|
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(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(
|
||||||
|
block_info.cumulative_difficulty_low,
|
||||||
|
block_info.cumulative_difficulty_high,
|
||||||
|
),
|
||||||
|
block_weight: block_info.weight,
|
||||||
|
long_term_weight: block_info.long_term_weight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::num::NonZero;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(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());
|
||||||
|
|
||||||
|
// Add initial block.
|
||||||
|
{
|
||||||
|
let tx_rw = env_inner.tx_rw().unwrap();
|
||||||
|
let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
|
||||||
|
|
||||||
|
let mut initial_block = BLOCK_V1_TX2.clone();
|
||||||
|
initial_block.height = 0;
|
||||||
|
|
||||||
|
add_block(&initial_block, &mut tables).unwrap();
|
||||||
|
|
||||||
|
drop(tables);
|
||||||
|
TxRw::commit(tx_rw).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let alt_blocks = [
|
||||||
|
map_verified_block_to_alt(BLOCK_V9_TX3.clone(), chain_id),
|
||||||
|
map_verified_block_to_alt(BLOCK_V16_TX0.clone(), chain_id),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add alt-blocks
|
||||||
|
{
|
||||||
|
let tx_rw = env_inner.tx_rw().unwrap();
|
||||||
|
let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
|
||||||
|
|
||||||
|
let mut prev_hash = BLOCK_V1_TX2.block_hash;
|
||||||
|
for (i, mut alt_block) in alt_blocks.into_iter().enumerate() {
|
||||||
|
let height = i + 1;
|
||||||
|
|
||||||
|
alt_block.height = height;
|
||||||
|
alt_block.block.header.previous = prev_hash;
|
||||||
|
alt_block.block_blob = alt_block.block.serialize();
|
||||||
|
|
||||||
|
add_alt_block(&alt_block, &mut tables).unwrap();
|
||||||
|
|
||||||
|
let alt_height = AltBlockHeight {
|
||||||
|
chain_id: chain_id.into(),
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let alt_block_2 = get_alt_block(&alt_height, &tables).unwrap();
|
||||||
|
assert_eq!(alt_block.block, alt_block_2.block);
|
||||||
|
|
||||||
|
let headers = get_alt_chain_history_ranges(
|
||||||
|
0..(height + 1),
|
||||||
|
chain_id,
|
||||||
|
tables.alt_chain_infos(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
flush_alt_blocks(&env_inner, &mut tx_rw).unwrap();
|
||||||
|
|
||||||
|
let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
|
||||||
|
pop_block(None, &mut tables).unwrap();
|
||||||
|
|
||||||
|
drop(tables);
|
||||||
|
TxRw::commit(tx_rw).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_all_tables_are_empty(&env);
|
||||||
|
}
|
||||||
|
}
|
117
storage/blockchain/src/ops/alt_block/chain.rs
Normal file
117
storage/blockchain/src/ops/alt_block/chain.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
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> {
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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| {
|
||||||
|
if info.chain_height < alt_block_height.height + 1 {
|
||||||
|
// If the chain height is increasing we only need to update the chain height.
|
||||||
|
info.chain_height = alt_block_height.height + 1;
|
||||||
|
} else {
|
||||||
|
// If the chain height is not increasing we are popping blocks and need to update the
|
||||||
|
// split point.
|
||||||
|
info.common_ancestor_height = alt_block_height.height.checked_sub(1).unwrap();
|
||||||
|
info.parent_chain = parent_chain.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
tables.alt_chain_infos_mut().put(
|
||||||
|
&alt_block_height.chain_id,
|
||||||
|
&AltChainInfo {
|
||||||
|
parent_chain: parent_chain.into(),
|
||||||
|
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,
|
||||||
|
alt_chain_infos: &impl DatabaseRo<AltChainInfos>,
|
||||||
|
) -> Result<Vec<(Chain, std::ops::Range<BlockHeight>)>, 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);
|
||||||
|
let end_height = min(i, chain_info.chain_height);
|
||||||
|
|
||||||
|
ranges.push((
|
||||||
|
Chain::Alt(current_chain_id.into()),
|
||||||
|
start_height..end_height,
|
||||||
|
));
|
||||||
|
i = chain_info.common_ancestor_height + 1;
|
||||||
|
|
||||||
|
match chain_info.parent_chain.into() {
|
||||||
|
Chain::Main => {
|
||||||
|
ranges.push((Chain::Main, range.start..i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Chain::Alt(alt_chain_id) => {
|
||||||
|
let alt_chain_id = alt_chain_id.into();
|
||||||
|
|
||||||
|
// This shouldn't be possible to hit, however in a test with custom (invalid) block data
|
||||||
|
// this caused an infinite loop.
|
||||||
|
if alt_chain_id == current_chain_id {
|
||||||
|
return Err(RuntimeError::Io(std::io::Error::other(
|
||||||
|
"Loop detected in ChainIDs, invalid alt chain.",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
current_chain_id = alt_chain_id;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ranges)
|
||||||
|
}
|
58
storage/blockchain/src/ops/alt_block/mod.rs
Normal file
58
storage/blockchain/src/ops/alt_block/mod.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
//! 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)s that don't immediately connect to the main-chain.
|
||||||
|
//!
|
||||||
|
//! ## Why not use the 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 is too high.
|
||||||
|
mod block;
|
||||||
|
mod chain;
|
||||||
|
mod tx;
|
||||||
|
|
||||||
|
pub use block::{
|
||||||
|
add_alt_block, flush_alt_blocks, get_alt_block, get_alt_block_extended_header_from_height,
|
||||||
|
get_alt_block_hash,
|
||||||
|
};
|
||||||
|
pub use chain::{get_alt_chain_history_ranges, update_alt_chain_info};
|
||||||
|
pub use tx::{add_alt_transaction_blob, get_alt_transaction};
|
76
storage/blockchain/src/ops/alt_block/tx.rs
Normal file
76
storage/blockchain/src/ops/alt_block/tx.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use bytemuck::TransparentWrapper;
|
||||||
|
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},
|
||||||
|
tables::{Tables, TablesMut},
|
||||||
|
types::{AltTransactionInfo, TxHash},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Adds a [`VerifiedTransactionInformation`] from an alt-block
|
||||||
|
/// if it 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,
|
||||||
|
) -> Result<(), RuntimeError> {
|
||||||
|
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.tx_hash, StorableVec::wrap_ref(&tx.tx_blob))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a [`VerifiedTransactionInformation`] from the database.
|
||||||
|
///
|
||||||
|
#[doc = doc_error!()]
|
||||||
|
pub fn get_alt_transaction(
|
||||||
|
tx_hash: &TxHash,
|
||||||
|
tables: &impl Tables,
|
||||||
|
) -> 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)?;
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,16 +2,23 @@
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
use bytemuck::TransparentWrapper;
|
use bytemuck::TransparentWrapper;
|
||||||
use monero_serai::block::Block;
|
use monero_serai::block::{Block, BlockHeader};
|
||||||
|
|
||||||
use cuprate_database::{
|
use cuprate_database::{
|
||||||
RuntimeError, StorableVec, {DatabaseRo, DatabaseRw},
|
RuntimeError, StorableVec, {DatabaseRo, DatabaseRw},
|
||||||
};
|
};
|
||||||
use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits};
|
use cuprate_helper::{
|
||||||
use cuprate_types::{ExtendedBlockHeader, HardFork, VerifiedBlockInformation};
|
map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits},
|
||||||
|
tx::tx_fee,
|
||||||
|
};
|
||||||
|
use cuprate_types::{
|
||||||
|
AltBlockInformation, ChainId, ExtendedBlockHeader, HardFork, VerifiedBlockInformation,
|
||||||
|
VerifiedTransactionInformation,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ops::{
|
ops::{
|
||||||
|
alt_block,
|
||||||
blockchain::{chain_height, cumulative_generated_coins},
|
blockchain::{chain_height, cumulative_generated_coins},
|
||||||
macros::doc_error,
|
macros::doc_error,
|
||||||
output::get_rct_num_outputs,
|
output::get_rct_num_outputs,
|
||||||
|
@ -33,11 +40,6 @@ use crate::{
|
||||||
/// This function will panic if:
|
/// This function will panic if:
|
||||||
/// - `block.height > u32::MAX` (not normally possible)
|
/// - `block.height > u32::MAX` (not normally possible)
|
||||||
/// - `block.height` is not != [`chain_height`]
|
/// - `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.
|
// no inline, too big.
|
||||||
pub fn add_block(
|
pub fn add_block(
|
||||||
block: &VerifiedBlockInformation,
|
block: &VerifiedBlockInformation,
|
||||||
|
@ -107,9 +109,8 @@ pub fn add_block(
|
||||||
cumulative_rct_outs,
|
cumulative_rct_outs,
|
||||||
timestamp: block.block.header.timestamp,
|
timestamp: block.block.header.timestamp,
|
||||||
block_hash: block.block_hash,
|
block_hash: block.block_hash,
|
||||||
// INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`
|
weight: block.weight,
|
||||||
weight: block.weight as u64,
|
long_term_weight: block.long_term_weight,
|
||||||
long_term_weight: block.long_term_weight as u64,
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -130,23 +131,24 @@ pub fn add_block(
|
||||||
/// Remove the top/latest block from the database.
|
/// Remove the top/latest block from the database.
|
||||||
///
|
///
|
||||||
/// The removed block's data is returned.
|
/// 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!()]
|
#[doc = doc_error!()]
|
||||||
///
|
///
|
||||||
/// In `pop_block()`'s case, [`RuntimeError::KeyNotFound`]
|
/// In `pop_block()`'s case, [`RuntimeError::KeyNotFound`]
|
||||||
/// will be returned if there are no blocks left.
|
/// will be returned if there are no blocks left.
|
||||||
// no inline, too big
|
// no inline, too big
|
||||||
pub fn pop_block(
|
pub fn pop_block(
|
||||||
|
move_to_alt_chain: Option<ChainId>,
|
||||||
tables: &mut impl TablesMut,
|
tables: &mut impl TablesMut,
|
||||||
) -> Result<(BlockHeight, BlockHash, Block), RuntimeError> {
|
) -> Result<(BlockHeight, BlockHash, Block), RuntimeError> {
|
||||||
//------------------------------------------------------ Block Info
|
//------------------------------------------------------ Block Info
|
||||||
// Remove block data from tables.
|
// Remove block data from tables.
|
||||||
let (block_height, block_hash) = {
|
|
||||||
let (block_height, block_info) = tables.block_infos_mut().pop_last()?;
|
let (block_height, block_info) = tables.block_infos_mut().pop_last()?;
|
||||||
(block_height, block_info.block_hash)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Block heights.
|
// Block heights.
|
||||||
tables.block_heights_mut().delete(&block_hash)?;
|
tables.block_heights_mut().delete(&block_info.block_hash)?;
|
||||||
|
|
||||||
// Block blobs.
|
// Block blobs.
|
||||||
// We deserialize the block blob into a `Block`, such
|
// We deserialize the block blob into a `Block`, such
|
||||||
|
@ -156,11 +158,52 @@ pub fn pop_block(
|
||||||
|
|
||||||
//------------------------------------------------------ Transaction / Outputs / Key Images
|
//------------------------------------------------------ Transaction / Outputs / Key Images
|
||||||
remove_tx(&block.miner_transaction.hash(), tables)?;
|
remove_tx(&block.miner_transaction.hash(), tables)?;
|
||||||
for tx_hash in &block.transactions {
|
|
||||||
remove_tx(tx_hash, tables)?;
|
let remove_tx_iter = block.transactions.iter().map(|tx_hash| {
|
||||||
|
let (_, tx) = remove_tx(tx_hash, tables)?;
|
||||||
|
Ok::<_, RuntimeError>(tx)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(chain_id) = move_to_alt_chain {
|
||||||
|
let txs = remove_tx_iter
|
||||||
|
.map(|result| {
|
||||||
|
let tx = result?;
|
||||||
|
Ok(VerifiedTransactionInformation {
|
||||||
|
tx_weight: tx.weight(),
|
||||||
|
tx_blob: tx.serialize(),
|
||||||
|
tx_hash: tx.hash(),
|
||||||
|
fee: tx_fee(&tx),
|
||||||
|
tx,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<VerifiedTransactionInformation>, RuntimeError>>()?;
|
||||||
|
|
||||||
|
alt_block::add_alt_block(
|
||||||
|
&AltBlockInformation {
|
||||||
|
block: block.clone(),
|
||||||
|
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: combine_low_high_bits_to_u128(
|
||||||
|
block_info.cumulative_difficulty_low,
|
||||||
|
block_info.cumulative_difficulty_high,
|
||||||
|
),
|
||||||
|
chain_id,
|
||||||
|
},
|
||||||
|
tables,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
for result in remove_tx_iter {
|
||||||
|
drop(result?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((block_height, block_hash, block))
|
Ok((block_height, block_info.block_hash, block))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- `get_block_extended_header_*`
|
//---------------------------------------------------------------------------------------------------- `get_block_extended_header_*`
|
||||||
|
@ -194,25 +237,21 @@ pub fn get_block_extended_header_from_height(
|
||||||
) -> Result<ExtendedBlockHeader, RuntimeError> {
|
) -> Result<ExtendedBlockHeader, RuntimeError> {
|
||||||
let block_info = tables.block_infos().get(block_height)?;
|
let block_info = tables.block_infos().get(block_height)?;
|
||||||
let block_blob = tables.block_blobs().get(block_height)?.0;
|
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(
|
let cumulative_difficulty = combine_low_high_bits_to_u128(
|
||||||
block_info.cumulative_difficulty_low,
|
block_info.cumulative_difficulty_low,
|
||||||
block_info.cumulative_difficulty_high,
|
block_info.cumulative_difficulty_high,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[expect(
|
|
||||||
clippy::cast_possible_truncation,
|
|
||||||
reason = "INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`"
|
|
||||||
)]
|
|
||||||
Ok(ExtendedBlockHeader {
|
Ok(ExtendedBlockHeader {
|
||||||
cumulative_difficulty,
|
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"),
|
.expect("Stored block must have a valid hard-fork"),
|
||||||
vote: block.header.hardfork_signal,
|
vote: block_header.hardfork_signal,
|
||||||
timestamp: block.header.timestamp,
|
timestamp: block_header.timestamp,
|
||||||
block_weight: block_info.weight as usize,
|
block_weight: block_info.weight,
|
||||||
long_term_weight: block_info.long_term_weight as usize,
|
long_term_weight: block_info.long_term_weight,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,14 +311,14 @@ mod test {
|
||||||
use cuprate_database::{Env, EnvInner, TxRw};
|
use cuprate_database::{Env, EnvInner, TxRw};
|
||||||
use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3};
|
use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3};
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ops::tx::{get_tx, tx_exists},
|
ops::tx::{get_tx, tx_exists},
|
||||||
tables::OpenTables,
|
tables::OpenTables,
|
||||||
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
|
tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// Tests all above block functions.
|
/// Tests all above block functions.
|
||||||
///
|
///
|
||||||
/// Note that this doesn't test the correctness of values added, as the
|
/// Note that this doesn't test the correctness of values added, as the
|
||||||
|
@ -414,7 +453,8 @@ mod test {
|
||||||
for block_hash in block_hashes.into_iter().rev() {
|
for block_hash in block_hashes.into_iter().rev() {
|
||||||
println!("pop_block(): block_hash: {}", hex::encode(block_hash));
|
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);
|
assert_eq!(block_hash, popped_hash);
|
||||||
|
|
||||||
|
|
|
@ -31,3 +31,25 @@ When calling this function, ensure that either:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(super) use doc_add_block_inner_invariant;
|
pub(super) use doc_add_block_inner_invariant;
|
||||||
|
|
||||||
|
/// Generate `# Invariant` documentation for internal alt block `fn`'s
|
||||||
|
/// that should be called directly with caution.
|
||||||
|
///
|
||||||
|
/// This is pretty much the same as [`doc_add_block_inner_invariant`],
|
||||||
|
/// it's not worth the effort to reduce the duplication.
|
||||||
|
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;
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
//! // Read the data, assert it is correct.
|
//! // Read the data, assert it is correct.
|
||||||
//! let tx_rw = env_inner.tx_rw()?;
|
//! let tx_rw = env_inner.tx_rw()?;
|
||||||
//! let mut tables = env_inner.open_tables_mut(&tx_rw)?;
|
//! let mut tables = env_inner.open_tables_mut(&tx_rw)?;
|
||||||
//! let (height, hash, serai_block) = pop_block(&mut tables)?;
|
//! let (height, hash, serai_block) = pop_block(None, &mut tables)?;
|
||||||
//!
|
//!
|
||||||
//! assert_eq!(height, 0);
|
//! assert_eq!(height, 0);
|
||||||
//! assert_eq!(serai_block, block.block);
|
//! assert_eq!(serai_block, block.block);
|
||||||
|
@ -102,6 +102,7 @@
|
||||||
//! # Ok(()) }
|
//! # Ok(()) }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
pub mod alt_block;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod blockchain;
|
pub mod blockchain;
|
||||||
pub mod key_image;
|
pub mod key_image;
|
||||||
|
|
|
@ -4,11 +4,14 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cuprate_database::{ConcreteEnv, InitError};
|
use cuprate_database::{ConcreteEnv, InitError};
|
||||||
|
use cuprate_types::{AltBlockInformation, VerifiedBlockInformation};
|
||||||
|
|
||||||
use crate::service::{init_read_service, init_write_service};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
service::types::{BlockchainReadHandle, BlockchainWriteHandle},
|
service::{
|
||||||
|
init_read_service, init_write_service,
|
||||||
|
types::{BlockchainReadHandle, BlockchainWriteHandle},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Init
|
//---------------------------------------------------------------------------------------------------- Init
|
||||||
|
@ -81,6 +84,44 @@ 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()
|
top_block_height > INITIAL_BLOCKS && !(top_block_height - INITIAL_BLOCKS + 2).is_power_of_two()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- Map Block
|
||||||
|
/// Maps [`AltBlockInformation`] to [`VerifiedBlockInformation`]
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This will panic if the block is invalid, so should only be used on blocks that have been popped from
|
||||||
|
/// the main-chain.
|
||||||
|
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
|
//---------------------------------------------------------------------------------------------------- Tests
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
//!
|
//!
|
||||||
//! // Block write was OK.
|
//! // Block write was OK.
|
||||||
//! let response = response_channel.await?;
|
//! let response = response_channel.await?;
|
||||||
//! assert_eq!(response, BlockchainResponse::WriteBlockOk);
|
//! assert_eq!(response, BlockchainResponse::Ok);
|
||||||
//!
|
//!
|
||||||
//! // Now, let's try getting the block hash
|
//! // Now, let's try getting the block hash
|
||||||
//! // of the block we just wrote.
|
//! // of the block we just wrote.
|
||||||
|
|
|
@ -8,6 +8,7 @@ use std::{
|
||||||
|
|
||||||
use rayon::{
|
use rayon::{
|
||||||
iter::{IntoParallelIterator, ParallelIterator},
|
iter::{IntoParallelIterator, ParallelIterator},
|
||||||
|
prelude::*,
|
||||||
ThreadPool,
|
ThreadPool,
|
||||||
};
|
};
|
||||||
use thread_local::ThreadLocal;
|
use thread_local::ThreadLocal;
|
||||||
|
@ -17,11 +18,15 @@ use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThre
|
||||||
use cuprate_helper::map::combine_low_high_bits_to_u128;
|
use cuprate_helper::map::combine_low_high_bits_to_u128;
|
||||||
use cuprate_types::{
|
use cuprate_types::{
|
||||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||||
Chain, ExtendedBlockHeader, OutputOnChain,
|
Chain, ChainId, ExtendedBlockHeader, OutputOnChain,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ops::{
|
ops::{
|
||||||
|
alt_block::{
|
||||||
|
get_alt_block, get_alt_block_extended_header_from_height, get_alt_block_hash,
|
||||||
|
get_alt_chain_history_ranges,
|
||||||
|
},
|
||||||
block::{
|
block::{
|
||||||
block_exists, get_block_extended_header_from_height, get_block_height, get_block_info,
|
block_exists, get_block_extended_header_from_height, get_block_height, get_block_info,
|
||||||
},
|
},
|
||||||
|
@ -33,8 +38,10 @@ use crate::{
|
||||||
free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
|
free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
|
||||||
types::{BlockchainReadHandle, ResponseResult},
|
types::{BlockchainReadHandle, ResponseResult},
|
||||||
},
|
},
|
||||||
tables::{BlockHeights, BlockInfos, OpenTables, Tables},
|
tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables},
|
||||||
types::{Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId},
|
types::{
|
||||||
|
AltBlockHeight, Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- init_read_service
|
//---------------------------------------------------------------------------------------------------- init_read_service
|
||||||
|
@ -87,7 +94,7 @@ fn map_request(
|
||||||
match request {
|
match request {
|
||||||
R::BlockExtendedHeader(block) => block_extended_header(env, block),
|
R::BlockExtendedHeader(block) => block_extended_header(env, block),
|
||||||
R::BlockHash(block, chain) => block_hash(env, block, chain),
|
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::FilterUnknownHashes(hashes) => filter_unknown_hashes(env, hashes),
|
||||||
R::BlockExtendedHeaderInRange(range, chain) => {
|
R::BlockExtendedHeaderInRange(range, chain) => {
|
||||||
block_extended_header_in_range(env, range, chain)
|
block_extended_header_in_range(env, range, chain)
|
||||||
|
@ -99,6 +106,7 @@ fn map_request(
|
||||||
R::KeyImagesSpent(set) => key_images_spent(env, set),
|
R::KeyImagesSpent(set) => key_images_spent(env, set),
|
||||||
R::CompactChainHistory => compact_chain_history(env),
|
R::CompactChainHistory => compact_chain_history(env),
|
||||||
R::FindFirstUnknown(block_ids) => find_first_unknown(env, &block_ids),
|
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? */
|
/* SOMEDAY: post-request handling, run some code for each request? */
|
||||||
|
@ -197,12 +205,41 @@ fn block_hash(env: &ConcreteEnv, block_height: BlockHeight, chain: Chain) -> Res
|
||||||
|
|
||||||
let block_hash = match chain {
|
let block_hash = match chain {
|
||||||
Chain::Main => get_block_info(&block_height, &table_block_infos)?.block_hash,
|
Chain::Main => get_block_info(&block_height, &table_block_infos)?.block_hash,
|
||||||
Chain::Alt(_) => todo!("Add alt blocks to DB"),
|
Chain::Alt(chain) => {
|
||||||
|
get_alt_block_hash(&block_height, chain, &env_inner.open_tables(&tx_ro)?)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(BlockchainResponse::BlockHash(block_hash))
|
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::<BlockHeights>(&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::<AltBlockHeights>(&tx_ro)?;
|
||||||
|
|
||||||
|
match table_alt_block_heights.get(&block_hash) {
|
||||||
|
Ok(height) => Ok(BlockchainResponse::FindBlock(Some((
|
||||||
|
Chain::Alt(height.chain_id.into()),
|
||||||
|
height.height,
|
||||||
|
)))),
|
||||||
|
Err(RuntimeError::KeyNotFound) => Ok(BlockchainResponse::FindBlock(None)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// [`BlockchainReadRequest::FilterUnknownHashes`].
|
/// [`BlockchainReadRequest::FilterUnknownHashes`].
|
||||||
#[inline]
|
#[inline]
|
||||||
fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet<BlockHash>) -> ResponseResult {
|
fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet<BlockHash>) -> ResponseResult {
|
||||||
|
@ -253,7 +290,37 @@ fn block_extended_header_in_range(
|
||||||
get_block_extended_header_from_height(&block_height, tables)
|
get_block_extended_header_from_height(&block_height, tables)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<ExtendedBlockHeader>, RuntimeError>>()?,
|
.collect::<Result<Vec<ExtendedBlockHeader>, RuntimeError>>()?,
|
||||||
Chain::Alt(_) => todo!("Add alt blocks to DB"),
|
Chain::Alt(chain_id) => {
|
||||||
|
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()
|
||||||
|
.flat_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,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec))
|
Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec))
|
||||||
|
@ -492,3 +559,45 @@ fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseRes
|
||||||
BlockchainResponse::FindFirstUnknown(Some((idx, last_known_height + 1)))
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -13,13 +13,14 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use rand::Rng;
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
use cuprate_database::{ConcreteEnv, DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError};
|
use cuprate_database::{ConcreteEnv, DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError};
|
||||||
use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3};
|
use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3};
|
||||||
use cuprate_types::{
|
use cuprate_types::{
|
||||||
blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest},
|
blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest},
|
||||||
Chain, OutputOnChain, VerifiedBlockInformation,
|
Chain, ChainId, OutputOnChain, VerifiedBlockInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -31,7 +32,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
service::{init, BlockchainReadHandle, BlockchainWriteHandle},
|
service::{init, BlockchainReadHandle, BlockchainWriteHandle},
|
||||||
tables::{OpenTables, Tables, TablesIter},
|
tables::{OpenTables, Tables, TablesIter},
|
||||||
tests::AssertTableLen,
|
tests::{map_verified_block_to_alt, AssertTableLen},
|
||||||
types::{Amount, AmountIndex, PreRctOutputId},
|
types::{Amount, AmountIndex, PreRctOutputId},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,7 +88,7 @@ async fn test_template(
|
||||||
let request = BlockchainWriteRequest::WriteBlock(block);
|
let request = BlockchainWriteRequest::WriteBlock(block);
|
||||||
let response_channel = writer.call(request);
|
let response_channel = writer.call(request);
|
||||||
let response = response_channel.await.unwrap();
|
let response = response_channel.await.unwrap();
|
||||||
assert_eq!(response, BlockchainResponse::WriteBlockOk);
|
assert_eq!(response, BlockchainResponse::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------- Reset the transaction
|
//----------------------------------------------------------------------- Reset the transaction
|
||||||
|
@ -415,3 +416,92 @@ async fn v16_tx0() {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests the alt-chain requests and responses.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn alt_chain_requests() {
|
||||||
|
let (reader, mut writer, _, _tempdir) = init_service();
|
||||||
|
|
||||||
|
// Set up the test by adding blocks to the main-chain.
|
||||||
|
for (i, mut block) in [BLOCK_V9_TX3.clone(), BLOCK_V16_TX0.clone()]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
block.height = i;
|
||||||
|
|
||||||
|
let request = BlockchainWriteRequest::WriteBlock(block);
|
||||||
|
writer.call(request).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the alt-blocks.
|
||||||
|
let mut prev_hash = BLOCK_V9_TX3.block_hash;
|
||||||
|
let mut chain_id = 1;
|
||||||
|
let alt_blocks = [&BLOCK_V16_TX0, &BLOCK_V9_TX3, &BLOCK_V1_TX2]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, block)| {
|
||||||
|
let mut block = (**block).clone();
|
||||||
|
block.height = i + 1;
|
||||||
|
block.block.header.previous = prev_hash;
|
||||||
|
block.block_blob = block.block.serialize();
|
||||||
|
|
||||||
|
prev_hash = block.block_hash;
|
||||||
|
// Randomly either keep the [`ChainId`] the same or change it to a new value.
|
||||||
|
chain_id += rand::thread_rng().gen_range(0..=1);
|
||||||
|
|
||||||
|
map_verified_block_to_alt(block, ChainId(chain_id.try_into().unwrap()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for block in &alt_blocks {
|
||||||
|
// Request a block to be written, assert it was written.
|
||||||
|
let request = BlockchainWriteRequest::WriteAltBlock(block.clone());
|
||||||
|
let response_channel = writer.call(request);
|
||||||
|
let response = response_channel.await.unwrap();
|
||||||
|
assert_eq!(response, BlockchainResponse::Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the full alt-chain
|
||||||
|
let request = BlockchainReadRequest::AltBlocksInChain(ChainId(chain_id.try_into().unwrap()));
|
||||||
|
let response = reader.clone().oneshot(request).await.unwrap();
|
||||||
|
|
||||||
|
let BlockchainResponse::AltBlocksInChain(blocks) = response else {
|
||||||
|
panic!("Wrong response type was returned");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(blocks.len(), alt_blocks.len());
|
||||||
|
for (got_block, alt_block) in blocks.into_iter().zip(alt_blocks) {
|
||||||
|
assert_eq!(got_block.block_blob, alt_block.block_blob);
|
||||||
|
assert_eq!(got_block.block_hash, alt_block.block_hash);
|
||||||
|
assert_eq!(got_block.chain_id, alt_block.chain_id);
|
||||||
|
assert_eq!(got_block.txs, alt_block.txs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush all alt blocks.
|
||||||
|
let request = BlockchainWriteRequest::FlushAltBlocks;
|
||||||
|
let response = writer.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
assert_eq!(response, BlockchainResponse::Ok);
|
||||||
|
|
||||||
|
// Pop blocks from the main chain
|
||||||
|
let request = BlockchainWriteRequest::PopBlocks(1);
|
||||||
|
let response = writer.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
|
||||||
|
let BlockchainResponse::PopBlocks(old_main_chain_id) = response else {
|
||||||
|
panic!("Wrong response type was returned");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check we have popped the top block.
|
||||||
|
let request = BlockchainReadRequest::ChainHeight;
|
||||||
|
let response = reader.clone().oneshot(request).await.unwrap();
|
||||||
|
assert!(matches!(response, BlockchainResponse::ChainHeight(1, _)));
|
||||||
|
|
||||||
|
// Attempt to add the popped block back.
|
||||||
|
let request = BlockchainWriteRequest::ReverseReorg(old_main_chain_id);
|
||||||
|
let response = writer.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
assert_eq!(response, BlockchainResponse::Ok);
|
||||||
|
|
||||||
|
// Check we have the popped block back.
|
||||||
|
let request = BlockchainReadRequest::ChainHeight;
|
||||||
|
let response = reader.clone().oneshot(request).await.unwrap();
|
||||||
|
assert!(matches!(response, BlockchainResponse::ChainHeight(2, _)));
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
//! Database writer thread definitions and logic.
|
//! Database writer thread definitions and logic.
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw};
|
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError, TxRw};
|
||||||
use cuprate_database_service::DatabaseWriteHandle;
|
use cuprate_database_service::DatabaseWriteHandle;
|
||||||
use cuprate_types::{
|
use cuprate_types::{
|
||||||
blockchain::{BlockchainResponse, BlockchainWriteRequest},
|
blockchain::{BlockchainResponse, BlockchainWriteRequest},
|
||||||
VerifiedBlockInformation,
|
AltBlockInformation, Chain, ChainId, VerifiedBlockInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::types::{BlockchainWriteHandle, ResponseResult},
|
service::{
|
||||||
tables::OpenTables,
|
free::map_valid_alt_block_to_verified_block,
|
||||||
|
types::{BlockchainWriteHandle, ResponseResult},
|
||||||
|
},
|
||||||
|
tables::{OpenTables, Tables},
|
||||||
|
types::AltBlockHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Write functions within this module abort if the write transaction
|
||||||
|
/// could not be aborted successfully to maintain atomicity.
|
||||||
|
///
|
||||||
|
/// This is the panic message if the `abort()` fails.
|
||||||
|
const TX_RW_ABORT_FAIL: &str =
|
||||||
|
"Could not maintain blockchain database atomicity by aborting write transaction";
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- init_write_service
|
//---------------------------------------------------------------------------------------------------- init_write_service
|
||||||
/// Initialize the blockchain write service from a [`ConcreteEnv`].
|
/// Initialize the blockchain write service from a [`ConcreteEnv`].
|
||||||
pub fn init_write_service(env: Arc<ConcreteEnv>) -> BlockchainWriteHandle {
|
pub fn init_write_service(env: Arc<ConcreteEnv>) -> BlockchainWriteHandle {
|
||||||
|
@ -29,6 +39,12 @@ fn handle_blockchain_request(
|
||||||
) -> Result<BlockchainResponse, RuntimeError> {
|
) -> Result<BlockchainResponse, RuntimeError> {
|
||||||
match req {
|
match req {
|
||||||
BlockchainWriteRequest::WriteBlock(block) => write_block(env, block),
|
BlockchainWriteRequest::WriteBlock(block) => write_block(env, block),
|
||||||
|
BlockchainWriteRequest::WriteAltBlock(alt_block) => write_alt_block(env, alt_block),
|
||||||
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,13 +71,140 @@ fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseR
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
TxRw::commit(tx_rw)?;
|
TxRw::commit(tx_rw)?;
|
||||||
Ok(BlockchainResponse::WriteBlockOk)
|
Ok(BlockchainResponse::Ok)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// INVARIANT: ensure database atomicity by aborting
|
TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL);
|
||||||
// the transaction on `add_block()` failures.
|
Err(e)
|
||||||
TxRw::abort(tx_rw)
|
}
|
||||||
.expect("could not maintain database atomicity by aborting write transaction");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`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) => {
|
||||||
|
TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`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()?;
|
||||||
|
|
||||||
|
// FIXME: turn this function into a try block once stable.
|
||||||
|
let mut 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
|
||||||
|
for _ in 0..numb_blocks {
|
||||||
|
crate::ops::block::pop_block(Some(old_main_chain_id), &mut tables_mut)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`BlockchainWriteRequest::ReverseReorg`].
|
||||||
|
fn reverse_reorg(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult {
|
||||||
|
let env_inner = env.env_inner();
|
||||||
|
let mut tx_rw = env_inner.tx_rw()?;
|
||||||
|
|
||||||
|
// FIXME: turn this function into a try block once stable.
|
||||||
|
let mut result = || {
|
||||||
|
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 top_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..top_block_height {
|
||||||
|
crate::ops::block::pop_block(None, &mut tables_mut)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the old main chain blocks back to the main chain.
|
||||||
|
for height in (chain_info.common_ancestor_height + 1)..chain_info.chain_height {
|
||||||
|
let alt_block = crate::ops::alt_block::get_alt_block(
|
||||||
|
&AltBlockHeight {
|
||||||
|
chain_id: chain_id.into(),
|
||||||
|
height,
|
||||||
|
},
|
||||||
|
&tables_mut,
|
||||||
|
)?;
|
||||||
|
let verified_block = map_valid_alt_block_to_verified_block(alt_block);
|
||||||
|
crate::ops::block::add_block(&verified_block, &mut tables_mut)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(tables_mut);
|
||||||
|
crate::ops::alt_block::flush_alt_blocks(&env_inner, &mut tx_rw)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
match result() {
|
||||||
|
Ok(()) => {
|
||||||
|
TxRw::commit(tx_rw)?;
|
||||||
|
Ok(BlockchainResponse::Ok)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL);
|
||||||
|
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) => {
|
||||||
|
TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL);
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfo, KeyImage,
|
AltBlockHeight, AltChainInfo, AltTransactionInfo, Amount, AmountIndex, AmountIndices,
|
||||||
Output, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RctOutput, TxBlob, TxHash,
|
BlockBlob, BlockHash, BlockHeight, BlockInfo, CompactAltBlockInfo, KeyImage, Output,
|
||||||
|
PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RawChainId, RctOutput, TxBlob, TxHash,
|
||||||
TxId, UnlockTime,
|
TxId, UnlockTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -129,6 +130,40 @@ cuprate_database::define_tables! {
|
||||||
/// Transactions without unlock times will not exist in this table.
|
/// Transactions without unlock times will not exist in this table.
|
||||||
14 => TxUnlockTime,
|
14 => TxUnlockTime,
|
||||||
TxId => UnlockTime,
|
TxId => UnlockTime,
|
||||||
|
|
||||||
|
/// Information on alt-chains.
|
||||||
|
15 => AltChainInfos,
|
||||||
|
RawChainId => AltChainInfo,
|
||||||
|
|
||||||
|
/// Alt-block heights.
|
||||||
|
///
|
||||||
|
/// Contains the height of all alt-blocks.
|
||||||
|
16 => AltBlockHeights,
|
||||||
|
BlockHash => AltBlockHeight,
|
||||||
|
|
||||||
|
/// Alt-block information.
|
||||||
|
///
|
||||||
|
/// Contains information on all alt-blocks.
|
||||||
|
17 => AltBlocksInfo,
|
||||||
|
AltBlockHeight => CompactAltBlockInfo,
|
||||||
|
|
||||||
|
/// Alt-block blobs.
|
||||||
|
///
|
||||||
|
/// Contains the raw bytes of all alt-blocks.
|
||||||
|
18 => AltBlockBlobs,
|
||||||
|
AltBlockHeight => BlockBlob,
|
||||||
|
|
||||||
|
/// Alt-block transaction blobs.
|
||||||
|
///
|
||||||
|
/// Contains the raw bytes of alt transactions, if those transactions are not in the main-chain.
|
||||||
|
19 => AltTransactionBlobs,
|
||||||
|
TxHash => TxBlob,
|
||||||
|
|
||||||
|
/// Alt-block transaction information.
|
||||||
|
///
|
||||||
|
/// Contains information on all alt transactions, even if they are in the main-chain.
|
||||||
|
20 => AltTransactionInfos,
|
||||||
|
TxHash => AltTransactionInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Tests
|
//---------------------------------------------------------------------------------------------------- Tests
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::{borrow::Cow, fmt::Debug};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner};
|
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner};
|
||||||
|
use cuprate_types::{AltBlockInformation, ChainId, VerifiedBlockInformation};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::ConfigBuilder,
|
config::ConfigBuilder,
|
||||||
|
@ -88,3 +89,21 @@ pub(crate) fn assert_all_tables_are_empty(env: &ConcreteEnv) {
|
||||||
assert!(tables.all_tables_empty().unwrap());
|
assert!(tables.all_tables_empty().unwrap());
|
||||||
assert_eq!(crate::ops::tx::get_num_tx(tables.tx_ids()).unwrap(), 0);
|
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 {
|
||||||
|
AltBlockInformation {
|
||||||
|
block: verified_block.block,
|
||||||
|
block_blob: verified_block.block_blob,
|
||||||
|
txs: verified_block.txs,
|
||||||
|
block_hash: verified_block.block_hash,
|
||||||
|
pow_hash: verified_block.pow_hash,
|
||||||
|
height: verified_block.height,
|
||||||
|
weight: verified_block.weight,
|
||||||
|
long_term_weight: verified_block.long_term_weight,
|
||||||
|
cumulative_difficulty: verified_block.cumulative_difficulty,
|
||||||
|
chain_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,12 +41,14 @@
|
||||||
#![forbid(unsafe_code)] // if you remove this line i will steal your monero
|
#![forbid(unsafe_code)] // if you remove this line i will steal your monero
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
use bytemuck::{Pod, Zeroable};
|
use std::num::NonZero;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use cuprate_database::{Key, StorableVec};
|
use cuprate_database::{Key, StorableVec};
|
||||||
|
use cuprate_types::{Chain, ChainId};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Aliases
|
//---------------------------------------------------------------------------------------------------- Aliases
|
||||||
// These type aliases exist as many Monero-related types are the exact same.
|
// These type aliases exist as many Monero-related types are the exact same.
|
||||||
|
@ -187,7 +189,7 @@ pub struct BlockInfo {
|
||||||
/// The adjusted block size, in bytes.
|
/// The adjusted block size, in bytes.
|
||||||
///
|
///
|
||||||
/// See [`block_weight`](https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#blocks-weight).
|
/// 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.
|
/// Least-significant 64 bits of the 128-bit cumulative difficulty.
|
||||||
pub cumulative_difficulty_low: u64,
|
pub cumulative_difficulty_low: u64,
|
||||||
/// Most-significant 64 bits of the 128-bit cumulative difficulty.
|
/// Most-significant 64 bits of the 128-bit cumulative difficulty.
|
||||||
|
@ -199,7 +201,7 @@ pub struct BlockInfo {
|
||||||
/// The long term block weight, based on the median weight of the preceding `100_000` blocks.
|
/// 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).
|
/// 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
|
//---------------------------------------------------------------------------------------------------- OutputFlags
|
||||||
|
@ -324,6 +326,259 @@ pub struct RctOutput {
|
||||||
}
|
}
|
||||||
// TODO: local_index?
|
// TODO: local_index?
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- RawChain
|
||||||
|
/// [`Chain`] in a format which can be stored in the DB.
|
||||||
|
///
|
||||||
|
/// Implements [`Into`] and [`From`] for [`Chain`].
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::borrow::*;
|
||||||
|
/// # use cuprate_blockchain::{*, types::*};
|
||||||
|
/// use cuprate_database::Storable;
|
||||||
|
/// use cuprate_types::Chain;
|
||||||
|
///
|
||||||
|
/// // Assert Storable is correct.
|
||||||
|
/// let a: RawChain = Chain::Main.into();
|
||||||
|
/// let b = Storable::as_bytes(&a);
|
||||||
|
/// let c: RawChain = Storable::from_bytes(b);
|
||||||
|
/// assert_eq!(a, c);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Size & Alignment
|
||||||
|
/// ```rust
|
||||||
|
/// # use cuprate_blockchain::types::*;
|
||||||
|
/// assert_eq!(size_of::<RawChain>(), 8);
|
||||||
|
/// assert_eq!(align_of::<RawChain>(), 8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct RawChain(u64);
|
||||||
|
|
||||||
|
impl From<Chain> for RawChain {
|
||||||
|
fn from(value: Chain) -> Self {
|
||||||
|
match value {
|
||||||
|
Chain::Main => Self(0),
|
||||||
|
Chain::Alt(chain_id) => Self(chain_id.0.get()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RawChain> for Chain {
|
||||||
|
fn from(value: RawChain) -> Self {
|
||||||
|
NonZero::new(value.0).map_or(Self::Main, |id| Self::Alt(ChainId(id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RawChainId> for RawChain {
|
||||||
|
fn from(value: RawChainId) -> Self {
|
||||||
|
// A [`ChainID`] with an inner value of `0` is invalid.
|
||||||
|
assert_ne!(value.0, 0);
|
||||||
|
|
||||||
|
Self(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- RawChainId
|
||||||
|
/// [`ChainId`] in a format which can be stored in the DB.
|
||||||
|
///
|
||||||
|
/// Implements [`Into`] and [`From`] for [`ChainId`].
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::borrow::*;
|
||||||
|
/// # use cuprate_blockchain::{*, types::*};
|
||||||
|
/// use cuprate_database::Storable;
|
||||||
|
/// use cuprate_types::ChainId;
|
||||||
|
///
|
||||||
|
/// // Assert Storable is correct.
|
||||||
|
/// let a: RawChainId = ChainId(10.try_into().unwrap()).into();
|
||||||
|
/// let b = Storable::as_bytes(&a);
|
||||||
|
/// let c: RawChainId = Storable::from_bytes(b);
|
||||||
|
/// assert_eq!(a, c);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Size & Alignment
|
||||||
|
/// ```rust
|
||||||
|
/// # use cuprate_blockchain::types::*;
|
||||||
|
/// assert_eq!(size_of::<RawChainId>(), 8);
|
||||||
|
/// assert_eq!(align_of::<RawChainId>(), 8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct RawChainId(u64);
|
||||||
|
|
||||||
|
impl From<ChainId> for RawChainId {
|
||||||
|
fn from(value: ChainId) -> Self {
|
||||||
|
Self(value.0.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RawChainId> for ChainId {
|
||||||
|
fn from(value: RawChainId) -> Self {
|
||||||
|
Self(NonZero::new(value.0).expect("RawChainId cannot have a value of `0`"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for RawChainId {}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- AltChainInfo
|
||||||
|
/// Information on an alternative chain.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::borrow::*;
|
||||||
|
/// # use cuprate_blockchain::{*, types::*};
|
||||||
|
/// use cuprate_database::Storable;
|
||||||
|
/// use cuprate_types::Chain;
|
||||||
|
///
|
||||||
|
/// // Assert Storable is correct.
|
||||||
|
/// let a: AltChainInfo = AltChainInfo {
|
||||||
|
/// parent_chain: Chain::Main.into(),
|
||||||
|
/// common_ancestor_height: 0,
|
||||||
|
/// chain_height: 1,
|
||||||
|
/// };
|
||||||
|
/// let b = Storable::as_bytes(&a);
|
||||||
|
/// let c: AltChainInfo = Storable::from_bytes(b);
|
||||||
|
/// assert_eq!(a, c);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Size & Alignment
|
||||||
|
/// ```rust
|
||||||
|
/// # use cuprate_blockchain::types::*;
|
||||||
|
/// assert_eq!(size_of::<AltChainInfo>(), 24);
|
||||||
|
/// assert_eq!(align_of::<AltChainInfo>(), 8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AltChainInfo {
|
||||||
|
/// The chain this alt chain forks from.
|
||||||
|
pub parent_chain: RawChain,
|
||||||
|
/// The height of the first block we share with the parent chain.
|
||||||
|
pub common_ancestor_height: usize,
|
||||||
|
/// The chain height of the blocks in this alt chain.
|
||||||
|
pub chain_height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- AltBlockHeight
|
||||||
|
/// Represents the height of a block on an alt-chain.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::borrow::*;
|
||||||
|
/// # use cuprate_blockchain::{*, types::*};
|
||||||
|
/// use cuprate_database::Storable;
|
||||||
|
/// use cuprate_types::ChainId;
|
||||||
|
///
|
||||||
|
/// // Assert Storable is correct.
|
||||||
|
/// let a: AltBlockHeight = AltBlockHeight {
|
||||||
|
/// chain_id: ChainId(1.try_into().unwrap()).into(),
|
||||||
|
/// height: 1,
|
||||||
|
/// };
|
||||||
|
/// let b = Storable::as_bytes(&a);
|
||||||
|
/// let c: AltBlockHeight = Storable::from_bytes(b);
|
||||||
|
/// assert_eq!(a, c);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Size & Alignment
|
||||||
|
/// ```rust
|
||||||
|
/// # use cuprate_blockchain::types::*;
|
||||||
|
/// assert_eq!(size_of::<AltBlockHeight>(), 16);
|
||||||
|
/// assert_eq!(align_of::<AltBlockHeight>(), 8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AltBlockHeight {
|
||||||
|
/// The [`ChainId`] of the chain this alt block is on, in raw form.
|
||||||
|
pub chain_id: RawChainId,
|
||||||
|
/// The height of this alt-block.
|
||||||
|
pub height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for AltBlockHeight {}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- CompactAltBlockInfo
|
||||||
|
/// Represents information on an alt-chain.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::borrow::*;
|
||||||
|
/// # use cuprate_blockchain::{*, types::*};
|
||||||
|
/// use cuprate_database::Storable;
|
||||||
|
///
|
||||||
|
/// // Assert Storable is correct.
|
||||||
|
/// let a: CompactAltBlockInfo = CompactAltBlockInfo {
|
||||||
|
/// block_hash: [1; 32],
|
||||||
|
/// pow_hash: [2; 32],
|
||||||
|
/// height: 10,
|
||||||
|
/// weight: 20,
|
||||||
|
/// long_term_weight: 30,
|
||||||
|
/// cumulative_difficulty_low: 40,
|
||||||
|
/// cumulative_difficulty_high: 50,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let b = Storable::as_bytes(&a);
|
||||||
|
/// let c: CompactAltBlockInfo = Storable::from_bytes(b);
|
||||||
|
/// assert_eq!(a, c);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Size & Alignment
|
||||||
|
/// ```rust
|
||||||
|
/// # use cuprate_blockchain::types::*;
|
||||||
|
/// assert_eq!(size_of::<CompactAltBlockInfo>(), 104);
|
||||||
|
/// assert_eq!(align_of::<CompactAltBlockInfo>(), 8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct CompactAltBlockInfo {
|
||||||
|
/// The block's hash.
|
||||||
|
pub block_hash: [u8; 32],
|
||||||
|
/// The block's proof-of-work hash.
|
||||||
|
pub pow_hash: [u8; 32],
|
||||||
|
/// The block's height.
|
||||||
|
pub height: usize,
|
||||||
|
/// The adjusted block size, in bytes.
|
||||||
|
pub weight: usize,
|
||||||
|
/// The long term block weight, which is the weight factored in with previous block weights.
|
||||||
|
pub long_term_weight: usize,
|
||||||
|
/// The low 64 bits of the cumulative difficulty.
|
||||||
|
pub cumulative_difficulty_low: u64,
|
||||||
|
/// The high 64 bits of the cumulative difficulty.
|
||||||
|
pub cumulative_difficulty_high: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------------------------- AltTransactionInfo
|
||||||
|
/// Represents information on an alt transaction.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::borrow::*;
|
||||||
|
/// # use cuprate_blockchain::{*, types::*};
|
||||||
|
/// use cuprate_database::Storable;
|
||||||
|
///
|
||||||
|
/// // Assert Storable is correct.
|
||||||
|
/// let a: AltTransactionInfo = AltTransactionInfo {
|
||||||
|
/// tx_weight: 1,
|
||||||
|
/// fee: 6,
|
||||||
|
/// tx_hash: [6; 32],
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let b = Storable::as_bytes(&a);
|
||||||
|
/// let c: AltTransactionInfo = Storable::from_bytes(b);
|
||||||
|
/// assert_eq!(a, c);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Size & Alignment
|
||||||
|
/// ```rust
|
||||||
|
/// # use cuprate_blockchain::types::*;
|
||||||
|
/// assert_eq!(size_of::<AltTransactionInfo>(), 48);
|
||||||
|
/// assert_eq!(align_of::<AltTransactionInfo>(), 8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AltTransactionInfo {
|
||||||
|
/// The transaction's weight.
|
||||||
|
pub tx_weight: usize,
|
||||||
|
/// The transaction's total fees.
|
||||||
|
pub fee: u64,
|
||||||
|
/// The transaction's hash.
|
||||||
|
pub tx_hash: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Tests
|
//---------------------------------------------------------------------------------------------------- Tests
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
|
@ -7,7 +7,7 @@ authors = ["Boog900", "hinto-janai"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cuprate-types = { path = "../types" }
|
cuprate-types = { path = "../types" }
|
||||||
cuprate-helper = { path = "../helper", features = ["map"] }
|
cuprate-helper = { path = "../helper", features = ["map", "tx"] }
|
||||||
cuprate-wire = { path = "../net/wire" }
|
cuprate-wire = { path = "../net/wire" }
|
||||||
cuprate-p2p-core = { path = "../p2p/p2p-core", features = ["borsh"] }
|
cuprate-p2p-core = { path = "../p2p/p2p-core", features = ["borsh"] }
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,11 @@
|
||||||
//! let tx: VerifiedTransactionInformation = TX_V1_SIG0.clone();
|
//! let tx: VerifiedTransactionInformation = TX_V1_SIG0.clone();
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
mod constants;
|
|
||||||
pub use constants::{
|
pub use constants::{
|
||||||
BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_BBD604, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D,
|
BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_BBD604, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D,
|
||||||
TX_9E3F73, TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440,
|
TX_9E3F73, TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440,
|
||||||
};
|
};
|
||||||
|
pub use statics::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3, TX_V1_SIG0, TX_V1_SIG2, TX_V2_RCT3};
|
||||||
|
|
||||||
|
mod constants;
|
||||||
mod statics;
|
mod statics;
|
||||||
pub use statics::{
|
|
||||||
tx_fee, BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3, TX_V1_SIG0, TX_V1_SIG2, TX_V2_RCT3,
|
|
||||||
};
|
|
||||||
|
|
|
@ -8,12 +8,12 @@
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use cuprate_helper::map::combine_low_high_bits_to_u128;
|
|
||||||
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
|
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
use monero_serai::transaction::Input;
|
|
||||||
use monero_serai::{block::Block, transaction::Transaction};
|
use monero_serai::{block::Block, transaction::Transaction};
|
||||||
|
|
||||||
|
use cuprate_helper::{map::combine_low_high_bits_to_u128, tx::tx_fee};
|
||||||
|
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
|
||||||
|
|
||||||
use crate::data::constants::{
|
use crate::data::constants::{
|
||||||
BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73,
|
BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73,
|
||||||
TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440,
|
TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440,
|
||||||
|
@ -110,36 +110,6 @@ fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> VerifiedTransactionInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the fee of the [`Transaction`].
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// This will panic if the inputs overflow or the transaction outputs too much.
|
|
||||||
pub 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
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Blocks
|
//---------------------------------------------------------------------------------------------------- Blocks
|
||||||
/// Generate a `static LazyLock<VerifiedBlockInformation>`.
|
/// Generate a `static LazyLock<VerifiedBlockInformation>`.
|
||||||
///
|
///
|
||||||
|
@ -311,12 +281,12 @@ transaction_verification_data! {
|
||||||
//---------------------------------------------------------------------------------------------------- TESTS
|
//---------------------------------------------------------------------------------------------------- TESTS
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::rpc::client::HttpRpcClient;
|
use crate::rpc::client::HttpRpcClient;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// Assert the defined blocks are the same compared to ones received from a local RPC call.
|
/// Assert the defined blocks are the same compared to ones received from a local RPC call.
|
||||||
#[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node
|
#[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
//! HTTP RPC client.
|
//! HTTP RPC client.
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Use
|
//---------------------------------------------------------------------------------------------------- Use
|
||||||
|
use monero_rpc::Rpc;
|
||||||
|
use monero_serai::block::Block;
|
||||||
|
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
use monero_rpc::Rpc;
|
use cuprate_helper::tx::tx_fee;
|
||||||
use monero_serai::block::Block;
|
|
||||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
|
||||||
|
|
||||||
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
|
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
|
||||||
|
|
||||||
use crate::data::tx_fee;
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Constants
|
//---------------------------------------------------------------------------------------------------- Constants
|
||||||
/// The default URL used for Monero RPC connections.
|
/// The default URL used for Monero RPC connections.
|
||||||
pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081";
|
pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081";
|
||||||
|
@ -184,9 +182,10 @@ impl HttpRpcClient {
|
||||||
//---------------------------------------------------------------------------------------------------- TESTS
|
//---------------------------------------------------------------------------------------------------- TESTS
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// Assert the default address is localhost.
|
/// Assert the default address is localhost.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn localhost() {
|
async fn localhost() {
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
//!
|
//!
|
||||||
//! Tests that assert particular requests lead to particular
|
//! Tests that assert particular requests lead to particular
|
||||||
//! responses are also tested in Cuprate's blockchain database crate.
|
//! responses are also tested in Cuprate's blockchain database crate.
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation};
|
use crate::{
|
||||||
|
types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation},
|
||||||
|
AltBlockInformation, ChainId,
|
||||||
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- ReadRequest
|
//---------------------------------------------------------------------------------------------------- ReadRequest
|
||||||
/// A read request to the blockchain database.
|
/// A read request to the blockchain database.
|
||||||
|
@ -92,26 +94,49 @@ pub enum BlockchainReadRequest {
|
||||||
CompactChainHistory,
|
CompactChainHistory,
|
||||||
|
|
||||||
/// A request to find the first unknown block ID in a list of block IDs.
|
/// A request to find the first unknown block ID in a list of block IDs.
|
||||||
////
|
///
|
||||||
/// # Invariant
|
/// # Invariant
|
||||||
/// The [`Vec`] containing the block IDs must be sorted in chronological block
|
/// The [`Vec`] containing the block IDs must be sorted in chronological block
|
||||||
/// order, or else the returned response is unspecified and meaningless,
|
/// order, or else the returned response is unspecified and meaningless,
|
||||||
/// as this request performs a binary search.
|
/// as this request performs a binary search.
|
||||||
FindFirstUnknown(Vec<[u8; 32]>),
|
FindFirstUnknown(Vec<[u8; 32]>),
|
||||||
|
|
||||||
|
/// A request for all alt blocks in the chain with the given [`ChainId`].
|
||||||
|
AltBlocksInChain(ChainId),
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- WriteRequest
|
//---------------------------------------------------------------------------------------------------- WriteRequest
|
||||||
/// A write request to the blockchain database.
|
/// A write request to the blockchain database.
|
||||||
///
|
|
||||||
/// There is currently only 1 write request to the database,
|
|
||||||
/// as such, the only valid [`BlockchainResponse`] to this request is
|
|
||||||
/// the proper response for a [`BlockchainResponse::WriteBlockOk`].
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum BlockchainWriteRequest {
|
pub enum BlockchainWriteRequest {
|
||||||
/// Request that a block be written to the database.
|
/// Request that a block be written to the database.
|
||||||
///
|
///
|
||||||
/// Input is an already verified block.
|
/// Input is an already verified block.
|
||||||
WriteBlock(VerifiedBlockInformation),
|
WriteBlock(VerifiedBlockInformation),
|
||||||
|
|
||||||
|
/// Write an alternative block to the database,
|
||||||
|
///
|
||||||
|
/// Input is the alternative block.
|
||||||
|
WriteAltBlock(AltBlockInformation),
|
||||||
|
|
||||||
|
/// A request to pop some blocks from the top of the main chain
|
||||||
|
///
|
||||||
|
/// Input is the amount of blocks to pop.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
FlushAltBlocks,
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Response
|
//---------------------------------------------------------------------------------------------------- Response
|
||||||
|
@ -197,12 +222,24 @@ pub enum BlockchainResponse {
|
||||||
/// This will be [`None`] if all blocks were known.
|
/// This will be [`None`] if all blocks were known.
|
||||||
FindFirstUnknown(Option<(usize, usize)>),
|
FindFirstUnknown(Option<(usize, usize)>),
|
||||||
|
|
||||||
//------------------------------------------------------ Writes
|
/// The response for [`BlockchainReadRequest::AltBlocksInChain`].
|
||||||
/// Response to [`BlockchainWriteRequest::WriteBlock`].
|
|
||||||
///
|
///
|
||||||
/// This response indicates that the requested block has
|
/// Contains all the alt blocks in the alt-chain in chronological order.
|
||||||
/// successfully been written to the database without error.
|
AltBlocksInChain(Vec<AltBlockInformation>),
|
||||||
WriteBlockOk,
|
|
||||||
|
//------------------------------------------------------ Writes
|
||||||
|
/// A generic Ok response to indicate a request was successfully handled.
|
||||||
|
///
|
||||||
|
/// currently the response for:
|
||||||
|
/// - [`BlockchainWriteRequest::WriteBlock`]
|
||||||
|
/// - [`BlockchainWriteRequest::WriteAltBlock`]
|
||||||
|
/// - [`BlockchainWriteRequest::ReverseReorg`]
|
||||||
|
/// - [`BlockchainWriteRequest::FlushAltBlocks`]
|
||||||
|
Ok,
|
||||||
|
/// The response for [`BlockchainWriteRequest::PopBlocks`].
|
||||||
|
///
|
||||||
|
/// The inner value is the alt-chain ID for the old main chain blocks.
|
||||||
|
PopBlocks(ChainId),
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Tests
|
//---------------------------------------------------------------------------------------------------- Tests
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
//! Various shared data types in Cuprate.
|
//! Various shared data types in Cuprate.
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Import
|
//---------------------------------------------------------------------------------------------------- Import
|
||||||
|
use std::num::NonZero;
|
||||||
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
block::Block,
|
block::Block,
|
||||||
|
@ -38,8 +40,7 @@ pub struct ExtendedBlockHeader {
|
||||||
//---------------------------------------------------------------------------------------------------- VerifiedTransactionInformation
|
//---------------------------------------------------------------------------------------------------- VerifiedTransactionInformation
|
||||||
/// Verified information of a transaction.
|
/// Verified information of a transaction.
|
||||||
///
|
///
|
||||||
/// - If this is in a [`VerifiedBlockInformation`] this represents a valid transaction
|
/// This represents a valid transaction
|
||||||
/// - If this is in an [`AltBlockInformation`] this represents a potentially valid transaction
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct VerifiedTransactionInformation {
|
pub struct VerifiedTransactionInformation {
|
||||||
/// The transaction itself.
|
/// The transaction itself.
|
||||||
|
@ -79,6 +80,7 @@ pub struct VerifiedBlockInformation {
|
||||||
/// [`Block::hash`].
|
/// [`Block::hash`].
|
||||||
pub block_hash: [u8; 32],
|
pub block_hash: [u8; 32],
|
||||||
/// The block's proof-of-work hash.
|
/// The block's proof-of-work hash.
|
||||||
|
// TODO: make this an option.
|
||||||
pub pow_hash: [u8; 32],
|
pub pow_hash: [u8; 32],
|
||||||
/// The block's height.
|
/// The block's height.
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
|
@ -97,7 +99,7 @@ pub struct VerifiedBlockInformation {
|
||||||
///
|
///
|
||||||
/// The inner value is meaningless.
|
/// The inner value is meaningless.
|
||||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||||
pub struct ChainId(pub u64);
|
pub struct ChainId(pub NonZero<u64>);
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Chain
|
//---------------------------------------------------------------------------------------------------- Chain
|
||||||
/// An identifier for a chain.
|
/// An identifier for a chain.
|
||||||
|
|
Loading…
Reference in a new issue