use blvm_node::storage::*;
use blvm_protocol::*;
use tempfile::TempDir;
mod common;
use blvm_node::storage::blockstore::BlockStore;
use blvm_node::storage::chainstate::ChainState;
use blvm_node::storage::txindex::TxIndex;
use blvm_node::storage::utxostore::UtxoStore;
use common::*;
use std::sync::Arc;
#[test]
fn test_storage_creation() {
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path()).unwrap();
let _blocks = storage.blocks();
let _utxos = storage.utxos();
let _chain = storage.chain();
let _transactions = storage.transactions();
}
#[test]
fn test_block_store() {
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path()).unwrap();
let blockstore = storage.blocks();
let block = Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 1234567890,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![].into_boxed_slice(),
};
blockstore.store_block(&block).unwrap();
assert_eq!(blockstore.block_count().unwrap(), 1);
}
#[test]
fn test_utxo_store() {
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path()).unwrap();
let utxostore = storage.utxos();
let outpoint = OutPoint {
hash: [1u8; 32],
index: 0,
};
let utxo = UTXO {
value: 5000000000, script_pubkey: vec![0x76, 0xa9, 0x14].into(), height: 0,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
assert!(utxostore.has_utxo(&outpoint).unwrap());
let retrieved_utxo = utxostore.get_utxo(&outpoint).unwrap().unwrap();
assert_eq!(retrieved_utxo.value, utxo.value);
assert_eq!(utxostore.total_value().unwrap(), 5000000000);
}
#[test]
fn test_chain_state() {
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path()).unwrap();
let chainstate = storage.chain();
let genesis_header = BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 1231006505, bits: 0x1d00ffff,
nonce: 2083236893,
};
chainstate.initialize(&genesis_header).unwrap();
assert!(chainstate.is_initialized().unwrap());
let height = chainstate.get_height().unwrap().unwrap();
assert_eq!(height, 0);
let tip_hash = chainstate.get_tip_hash().unwrap().unwrap();
assert_ne!(tip_hash, [0u8; 32]);
}
#[test]
fn test_transaction_index() {
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path()).unwrap();
let txindex = storage.transactions();
let tx = Transaction {
version: 1,
inputs: blvm_protocol::tx_inputs![],
outputs: blvm_protocol::tx_outputs![TransactionOutput {
value: 5000000000,
script_pubkey: vec![0x76, 0xa9, 0x14],
}],
lock_time: 0,
};
let block_hash = [1u8; 32];
txindex.index_transaction(&tx, &block_hash, 0, 0).unwrap();
assert_eq!(txindex.transaction_count().unwrap(), 1);
let tx_hash = [0u8; 32]; let _metadata = txindex.get_metadata(&tx_hash);
}
#[test]
fn test_block_store_retrieval_by_hash() {
let temp_db = TempDb::new().unwrap();
let blockstore = &temp_db.block_store;
let block = TestBlockBuilder::new()
.set_prev_hash(random_hash())
.set_timestamp(1234567890u32)
.add_coinbase_transaction(p2pkh_script(random_hash20()))
.build();
let block_hash = random_hash();
blockstore.store_block(&block).unwrap();
let retrieved = blockstore.get_block(&block_hash);
assert!(retrieved.is_ok());
}
#[test]
fn test_block_store_retrieval_by_height() {
let temp_db = TempDb::new().unwrap();
let blockstore = &temp_db.block_store;
for i in 0..5 {
let block = TestBlockBuilder::new()
.set_prev_hash(if i == 0 { [0u8; 32] } else { random_hash() })
.set_timestamp((1234567890 + i) as u32)
.add_coinbase_transaction(p2pkh_script(random_hash20()))
.build();
blockstore.store_block(&block).unwrap();
let block_hash = blockstore.get_block_hash(&block);
blockstore.store_height(i, &block_hash).unwrap();
}
for i in 0..5 {
let blocks = blockstore.get_blocks_by_height_range(i, i + 1).unwrap();
assert!(!blocks.is_empty());
}
}
#[test]
fn test_block_store_header_only() {
let temp_db = TempDb::new().unwrap();
let blockstore = &temp_db.block_store;
let header = valid_block_header();
let block = Block {
header,
transactions: vec![].into_boxed_slice(),
};
blockstore.store_block(&block).unwrap();
let block_hash = blockstore.get_block_hash(&block);
let retrieved_block = blockstore.get_block(&block_hash).unwrap();
assert!(retrieved_block.is_some());
assert_eq!(
retrieved_block.unwrap().header.version,
block.header.version
);
}
#[test]
fn test_block_store_missing_block() {
let temp_db = TempDb::new().unwrap();
let blockstore = &temp_db.block_store;
let missing_hash = random_hash();
let result = blockstore.get_block(&missing_hash).unwrap();
assert!(result.is_none());
}
#[test]
fn test_block_store_duplicate_handling() {
let temp_db = TempDb::new().unwrap();
let blockstore = &temp_db.block_store;
let block = TestBlockBuilder::new()
.add_coinbase_transaction(p2pkh_script(random_hash20()))
.build();
let block_hash = random_hash();
blockstore.store_block(&block).unwrap();
let initial_count = blockstore.block_count().unwrap();
blockstore.store_block(&block).unwrap();
let final_count = blockstore.block_count().unwrap();
assert_eq!(initial_count, final_count);
}
#[test]
fn test_block_store_large_block() {
let temp_db = TempDb::new().unwrap();
let blockstore = &temp_db.block_store;
let large_block = large_block(1000);
let result = blockstore.store_block(&large_block);
assert!(result.is_ok());
assert!(blockstore.block_count().unwrap() > 0);
}
#[test]
fn test_block_store_persistence() {
let temp_dir = TempDir::new().unwrap();
let storage_path = temp_dir.path();
{
let storage = Storage::new(storage_path).unwrap();
let blockstore = storage.blocks();
let block = TestBlockBuilder::new()
.add_coinbase_transaction(p2pkh_script(random_hash20()))
.build();
blockstore.store_block(&block).unwrap();
storage.flush().unwrap();
}
{
let storage = Storage::new(storage_path).unwrap();
let blockstore = storage.blocks();
assert_eq!(blockstore.block_count().unwrap(), 1);
}
}
#[test]
fn test_utxo_store_addition_and_retrieval() {
let temp_db = TempDb::new().unwrap();
let utxostore = &temp_db.utxo_store;
let outpoint = OutPoint {
hash: random_hash(),
index: 0,
};
let utxo = UTXO {
value: 50_0000_0000,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: 100,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
assert!(utxostore.has_utxo(&outpoint).unwrap());
let retrieved = utxostore.get_utxo(&outpoint).unwrap().unwrap();
assert_eq!(retrieved.value, utxo.value);
assert_eq!(retrieved.height, utxo.height);
}
#[test]
fn test_utxo_store_removal() {
let temp_db = TempDb::new().unwrap();
let utxostore = &temp_db.utxo_store;
let outpoint = OutPoint {
hash: random_hash(),
index: 0,
};
let utxo = UTXO {
value: 25_0000_0000,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: 50,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
assert!(utxostore.has_utxo(&outpoint).unwrap());
utxostore.remove_utxo(&outpoint).unwrap();
assert!(!utxostore.has_utxo(&outpoint).unwrap());
}
#[test]
fn test_utxo_store_spent_tracking() {
let temp_db = TempDb::new().unwrap();
let utxostore = &temp_db.utxo_store;
let outpoint = OutPoint {
hash: random_hash(),
index: 0,
};
let utxo = UTXO {
value: 10_0000_0000,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: 25,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
utxostore.mark_spent(&outpoint).unwrap();
assert!(utxostore.is_spent(&outpoint).unwrap());
}
#[test]
fn test_utxo_store_size_queries() {
let temp_db = TempDb::new().unwrap();
let utxostore = &temp_db.utxo_store;
for i in 0..10 {
let outpoint = OutPoint {
hash: random_hash(),
index: i,
};
let utxo = UTXO {
value: (1_0000_0000 * (i + 1)) as i64,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: i as u64,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
}
assert_eq!(utxostore.utxo_count().unwrap(), 10);
let total_value = utxostore.total_value().unwrap();
assert!(total_value > 0);
}
#[test]
fn test_utxo_store_missing_utxo() {
let temp_db = TempDb::new().unwrap();
let utxostore = &temp_db.utxo_store;
let missing_outpoint = OutPoint {
hash: random_hash(),
index: 999,
};
let result = utxostore.get_utxo(&missing_outpoint).unwrap();
assert!(result.is_none());
assert!(!utxostore.has_utxo(&missing_outpoint).unwrap());
}
#[test]
fn test_utxo_store_concurrent_operations() {
let temp_db = TempDb::new().unwrap();
let utxostore = &temp_db.utxo_store;
let mut outpoints = Vec::new();
for i in 0..5 {
let outpoint = OutPoint {
hash: random_hash(),
index: i,
};
let utxo = UTXO {
value: 5_0000_0000,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: 10,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
outpoints.push(outpoint);
}
for outpoint in &outpoints[0..2] {
utxostore.remove_utxo(outpoint).unwrap();
}
assert_eq!(utxostore.utxo_count().unwrap(), 3);
}
#[test]
fn test_chain_state_tip_updates() {
let temp_db = TempDb::new().unwrap();
let chainstate = &temp_db.chain_state;
let genesis_header = valid_block_header();
chainstate.initialize(&genesis_header).unwrap();
let new_tip = valid_block_header();
let tip_hash = random_hash();
chainstate.update_tip(&tip_hash, &new_tip, 1).unwrap();
let current_tip = chainstate.get_tip_hash().unwrap().unwrap();
assert_ne!(current_tip, [0u8; 32]);
}
#[test]
fn test_chain_state_work_accumulation() {
let temp_db = TempDb::new().unwrap();
let chainstate = &temp_db.chain_state;
let genesis_header = valid_block_header();
chainstate.initialize(&genesis_header).unwrap();
let height = chainstate.get_height().unwrap().unwrap();
assert_eq!(height, 0);
let tip_hash = chainstate.get_tip_hash().unwrap().unwrap();
assert_ne!(tip_hash, [0u8; 32]);
}
#[test]
fn test_chain_state_best_chain_queries() {
let temp_db = TempDb::new().unwrap();
let chainstate = &temp_db.chain_state;
let genesis_header = valid_block_header();
chainstate.initialize(&genesis_header).unwrap();
let height = chainstate.get_height().unwrap().unwrap();
assert_eq!(height, 0);
let tip_hash = chainstate.get_tip_hash().unwrap().unwrap();
assert_ne!(tip_hash, [0u8; 32]);
}
#[test]
fn test_chain_state_reorg_handling() {
let temp_db = TempDb::new().unwrap();
let chainstate = &temp_db.chain_state;
let genesis_header = valid_block_header();
chainstate.initialize(&genesis_header).unwrap();
let reorg_header = valid_block_header();
let reorg_hash = random_hash();
chainstate
.update_tip(&reorg_hash, &reorg_header, 1)
.unwrap();
let current_height = chainstate.get_height().unwrap().unwrap();
let _ = current_height;
}
#[test]
fn test_transaction_index_by_hash() {
let temp_db = TempDb::new().unwrap();
let txindex = &temp_db.tx_index;
let tx = valid_transaction();
let block_hash = random_hash();
let tx_hash = random_hash();
txindex.index_transaction(&tx, &block_hash, 0, 0).unwrap();
assert!(txindex.transaction_count().unwrap() > 0);
}
#[test]
fn test_transaction_index_block_lookup() {
let temp_db = TempDb::new().unwrap();
let txindex = &temp_db.tx_index;
let tx = valid_transaction();
let block_hash = random_hash();
txindex.index_transaction(&tx, &block_hash, 0, 0).unwrap();
let block_txs = txindex.get_block_transactions(&block_hash).unwrap();
assert!(!block_txs.is_empty());
}
#[test]
fn test_transaction_index_metadata() {
let temp_db = TempDb::new().unwrap();
let txindex = &temp_db.tx_index;
let tx = valid_transaction();
let block_hash = random_hash();
let tx_hash = random_hash();
txindex.index_transaction(&tx, &block_hash, 0, 0).unwrap();
let metadata = txindex.get_metadata(&tx_hash);
assert!(metadata.is_ok() || metadata.is_err());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_chainstate_work_accumulation() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, default_backend, Database};
let db_arc: std::sync::Arc<dyn Database> =
std::sync::Arc::from(create_database(temp_dir.path(), default_backend(), None).unwrap());
let chainstate = ChainState::new(db_arc).unwrap();
let header1 = TestBlockBuilder::new()
.with_version(1)
.set_timestamp(1234567890u32)
.with_bits(0x1d00ffff)
.with_nonce(12345)
.build_header();
let header2 = TestBlockBuilder::new()
.with_version(1)
.set_timestamp(1234567891u32)
.with_bits(0x1d00ffff)
.with_nonce(12346)
.build_header();
chainstate.initialize(&header1).unwrap();
let tip_hash = random_hash();
chainstate.update_tip(&tip_hash, &header2, 1).unwrap();
let chain_info = chainstate.load_chain_info().unwrap();
assert!(chain_info.is_some());
let info = chain_info.unwrap();
assert_eq!(info.height, 1);
assert_eq!(info.tip_hash, tip_hash);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_chainstate_persistence() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path();
{
use blvm_node::storage::database::{create_database, default_backend};
let db_arc =
std::sync::Arc::from(create_database(db_path, default_backend(), None).unwrap());
let chainstate = ChainState::new(db_arc).unwrap();
let header = TestBlockBuilder::new()
.with_version(1)
.set_timestamp(1234567890u32)
.with_bits(0x1d00ffff)
.with_nonce(12345)
.build_header();
chainstate.initialize(&header).unwrap();
let tip_hash = random_hash();
chainstate.update_tip(&tip_hash, &header, 100).unwrap();
}
{
use blvm_node::storage::database::{create_database, default_backend};
let db_arc =
std::sync::Arc::from(create_database(db_path, default_backend(), None).unwrap());
let chainstate = ChainState::new(db_arc).unwrap();
let chain_info = chainstate.load_chain_info().unwrap();
assert!(chain_info.is_some());
let info = chain_info.unwrap();
assert_eq!(info.height, 100);
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_utxostore_concurrent_operations() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, default_backend, Database};
let db_arc: std::sync::Arc<dyn Database> =
std::sync::Arc::from(create_database(temp_dir.path(), default_backend(), None).unwrap());
let utxostore = UtxoStore::new(db_arc).unwrap();
let utxo1 = TestUtxoSetBuilder::new()
.add_utxo(random_hash(), 0, 1000, p2pkh_script(random_hash20()))
.build();
let utxo2 = TestUtxoSetBuilder::new()
.add_utxo(random_hash(), 1, 2000, p2pkh_script(random_hash20()))
.build();
for (outpoint, utxo) in &utxo1 {
let utxo_struct = UTXO {
value: utxo.value,
script_pubkey: utxo.script_pubkey.clone().into(),
height: 100,
is_coinbase: false,
};
utxostore.add_utxo(outpoint, &utxo_struct).unwrap();
}
for (outpoint, utxo) in &utxo2 {
let utxo_struct = UTXO {
value: utxo.value,
script_pubkey: utxo.script_pubkey.clone().into(),
height: 100,
is_coinbase: false,
};
utxostore.add_utxo(outpoint, &utxo_struct).unwrap();
}
assert_eq!(utxostore.utxo_count().unwrap(), utxo1.len() + utxo2.len());
for outpoint in utxo1.keys() {
let retrieved = utxostore.get_utxo(outpoint).unwrap();
assert!(retrieved.is_some());
}
for outpoint in utxo2.keys() {
let retrieved = utxostore.get_utxo(outpoint).unwrap();
assert!(retrieved.is_some());
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_txindex_lookup_paths() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, default_backend, Database};
let db_arc: std::sync::Arc<dyn Database> =
std::sync::Arc::from(create_database(temp_dir.path(), default_backend(), None).unwrap());
let txindex = TxIndex::new(db_arc).unwrap();
let tx = TestTransactionBuilder::new()
.with_version(1)
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.with_lock_time(0)
.build();
let tx_hash = blvm_protocol::block::calculate_tx_id(&tx);
let block_hash = random_hash();
let block_height = 100;
txindex
.index_transaction(&tx, &block_hash, block_height, 0)
.unwrap();
let retrieved_metadata = txindex.get_metadata(&tx_hash).unwrap();
let retrieved_block = retrieved_metadata.as_ref().map(|m| m.block_hash);
assert!(retrieved_block.is_some());
assert_eq!(retrieved_block.unwrap(), block_hash);
let retrieved_height = retrieved_metadata.as_ref().map(|m| m.block_height);
assert!(retrieved_height.is_some());
assert_eq!(retrieved_height.unwrap(), block_height);
let block_txs = txindex.get_block_transactions(&block_hash).unwrap();
assert!(!block_txs.is_empty());
assert!(block_txs.iter().any(|tx| {
true }));
}
#[cfg(feature = "redb")]
#[test]
fn test_redb_tree_clear() {
use blvm_node::storage::database::{create_database, default_backend, Database, Tree};
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let db_arc: std::sync::Arc<dyn Database> =
std::sync::Arc::from(create_database(temp_dir.path(), default_backend(), None).unwrap());
let tree = db_arc.open_tree("blocks").unwrap();
let test_data = vec![
(b"key1".to_vec(), b"value1".to_vec()),
(b"key2".to_vec(), b"value2".to_vec()),
(b"key3".to_vec(), b"value3".to_vec()),
];
for (key, value) in &test_data {
tree.insert(key, value).unwrap();
}
assert_eq!(tree.len().unwrap(), 3);
assert!(tree.contains_key(b"key1").unwrap());
assert!(tree.contains_key(b"key2").unwrap());
assert!(tree.contains_key(b"key3").unwrap());
tree.clear().unwrap();
assert_eq!(tree.len().unwrap(), 0);
assert!(!tree.contains_key(b"key1").unwrap());
assert!(!tree.contains_key(b"key2").unwrap());
assert!(!tree.contains_key(b"key3").unwrap());
tree.insert(b"new_key", b"new_value").unwrap();
assert_eq!(tree.len().unwrap(), 1);
assert!(tree.contains_key(b"new_key").unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_storage_integration_workflow() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, default_backend, Database};
let db_arc: std::sync::Arc<dyn Database> =
std::sync::Arc::from(create_database(temp_dir.path(), default_backend(), None).unwrap());
let blockstore = BlockStore::new(db_arc.clone()).unwrap();
let chainstate = ChainState::new(db_arc.clone()).unwrap();
let utxostore = UtxoStore::new(db_arc.clone()).unwrap();
let txindex = TxIndex::new(db_arc).unwrap();
let block = TestBlockBuilder::new()
.with_version(1)
.set_timestamp(1234567890u32)
.with_bits(0x1d00ffff)
.with_nonce(12345)
.add_transaction(
TestTransactionBuilder::new()
.with_version(1)
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.with_lock_time(0)
.build(),
)
.build();
let block_hash = blockstore.get_block_hash(&block);
let block_height = 100;
blockstore.store_block(&block).unwrap();
blockstore.store_height(block_height, &block_hash).unwrap();
chainstate.initialize(&block.header).unwrap();
chainstate
.update_tip(&block_hash, &block.header, block_height)
.unwrap();
let tx = valid_transaction();
let tx_hash = blvm_protocol::block::calculate_tx_id(&tx);
txindex
.index_transaction(&tx, &block_hash, block_height, 0)
.unwrap();
let outpoint = OutPoint {
hash: tx_hash,
index: 0,
};
let utxo = UTXO {
value: 1000,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: block_height,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
let retrieved_block = blockstore.get_block(&block_hash).unwrap();
assert!(retrieved_block.is_some());
let chain_info = chainstate.load_chain_info().unwrap();
assert!(chain_info.is_some());
assert_eq!(chain_info.unwrap().height, block_height);
let retrieved_utxo = utxostore.get_utxo(&outpoint).unwrap();
assert!(retrieved_utxo.is_some());
let retrieved_metadata = txindex.get_metadata(&tx_hash).unwrap();
let retrieved_tx_block = retrieved_metadata.map(|m| m.block_hash);
assert!(retrieved_tx_block.is_some());
assert_eq!(retrieved_tx_block.unwrap(), block_hash);
}
#[cfg(feature = "block-compression")]
#[test]
fn test_block_compression_roundtrip() {
let temp_dir = TempDir::new().unwrap();
let db = Arc::from(
blvm_node::storage::database::create_database(
temp_dir.path(),
blvm_node::storage::database::DatabaseBackend::Sled,
None,
)
.unwrap(),
);
let blockstore = BlockStore::new_with_compression(
db, true, 3, false, 2, )
.unwrap();
let block = TestBlockBuilder::new()
.add_coinbase_transaction(p2pkh_script(random_hash20()))
.add_transaction(
TestTransactionBuilder::new()
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.with_lock_time(0)
.build(),
)
.build();
blockstore.store_block(&block).unwrap();
let block_hash = blockstore.get_block_hash(&block);
let retrieved = blockstore.get_block(&block_hash).unwrap();
assert!(retrieved.is_some());
let retrieved_block = retrieved.unwrap();
assert_eq!(retrieved_block.header.version, block.header.version);
assert_eq!(retrieved_block.transactions.len(), block.transactions.len());
}
#[cfg(feature = "redb")]
#[test]
fn test_module_tree_isolation_redb() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, Database, DatabaseBackend};
let db: Arc<dyn Database> =
Arc::from(create_database(temp_dir.path(), DatabaseBackend::Redb, None).unwrap());
let tree1 = db.open_tree("test_abc123_state").unwrap();
let tree2 = db.open_tree("test_xyz789_state").unwrap();
tree1.insert(b"key1", b"value1").unwrap();
tree2.insert(b"key1", b"value2").unwrap();
assert_eq!(tree1.get(b"key1").unwrap(), Some(b"value1".to_vec()));
assert_eq!(tree2.get(b"key1").unwrap(), Some(b"value2".to_vec()));
assert_eq!(tree1.len().unwrap(), 1);
assert_eq!(tree2.len().unwrap(), 1);
}
#[cfg(feature = "redb")]
#[test]
fn test_module_tree_operations_redb() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, Database, DatabaseBackend};
let db: Arc<dyn Database> =
Arc::from(create_database(temp_dir.path(), DatabaseBackend::Redb, None).unwrap());
let tree = db.open_tree("test123_cache").unwrap();
tree.insert(b"key1", b"value1").unwrap();
assert_eq!(tree.get(b"key1").unwrap(), Some(b"value1".to_vec()));
assert!(tree.contains_key(b"key1").unwrap());
assert!(!tree.contains_key(b"nonexistent").unwrap());
assert_eq!(tree.len().unwrap(), 1);
tree.insert(b"key2", b"value2").unwrap();
assert_eq!(tree.len().unwrap(), 2);
tree.remove(b"key1").unwrap();
assert_eq!(tree.get(b"key1").unwrap(), None);
assert_eq!(tree.len().unwrap(), 1);
tree.clear().unwrap();
assert_eq!(tree.len().unwrap(), 0);
assert_eq!(tree.get(b"key2").unwrap(), None);
}
#[cfg(feature = "redb")]
#[test]
fn test_module_tree_iter_redb() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, Database, DatabaseBackend};
let db: Arc<dyn Database> =
Arc::from(create_database(temp_dir.path(), DatabaseBackend::Redb, None).unwrap());
let tree = db.open_tree("test456_data").unwrap();
tree.insert(b"key1", b"value1").unwrap();
tree.insert(b"key2", b"value2").unwrap();
tree.insert(b"key3", b"value3").unwrap();
let mut items: Vec<(Vec<u8>, Vec<u8>)> = tree.iter().map(|r| r.unwrap()).collect();
items.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(items.len(), 3);
assert_eq!(items[0], (b"key1".to_vec(), b"value1".to_vec()));
assert_eq!(items[1], (b"key2".to_vec(), b"value2".to_vec()));
assert_eq!(items[2], (b"key3".to_vec(), b"value3".to_vec()));
}
#[cfg(feature = "redb")]
#[test]
fn test_module_tree_multiple_trees_same_module_redb() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, Database, DatabaseBackend};
let db: Arc<dyn Database> =
Arc::from(create_database(temp_dir.path(), DatabaseBackend::Redb, None).unwrap());
let state_tree = db.open_tree("test_mod123_state").unwrap();
let cache_tree = db.open_tree("test_mod123_cache").unwrap();
state_tree.insert(b"key", b"state_value").unwrap();
cache_tree.insert(b"key", b"cache_value").unwrap();
assert_eq!(
state_tree.get(b"key").unwrap(),
Some(b"state_value".to_vec())
);
assert_eq!(
cache_tree.get(b"key").unwrap(),
Some(b"cache_value".to_vec())
);
assert_eq!(state_tree.len().unwrap(), 1);
assert_eq!(cache_tree.len().unwrap(), 1);
}
#[cfg(feature = "sled")]
#[test]
fn test_module_tree_isolation_sled() {
let temp_dir = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, Database, DatabaseBackend};
let db: Arc<dyn Database> =
Arc::from(create_database(temp_dir.path(), DatabaseBackend::Sled, None).unwrap());
let tree1 = db.open_tree("module_abc123_state").unwrap();
let tree2 = db.open_tree("module_xyz789_state").unwrap();
tree1.insert(b"key1", b"value1").unwrap();
tree2.insert(b"key1", b"value2").unwrap();
assert_eq!(tree1.get(b"key1").unwrap(), Some(b"value1".to_vec()));
assert_eq!(tree2.get(b"key1").unwrap(), Some(b"value2".to_vec()));
assert_eq!(tree1.len().unwrap(), 1);
assert_eq!(tree2.len().unwrap(), 1);
}
#[cfg(feature = "redb")]
#[test]
fn test_module_tree_backend_compatibility() {
let temp_dir1 = TempDir::new().unwrap();
let temp_dir2 = TempDir::new().unwrap();
use blvm_node::storage::database::{create_database, Database, DatabaseBackend};
let db_redb: Arc<dyn Database> =
Arc::from(create_database(temp_dir1.path(), DatabaseBackend::Redb, None).unwrap());
let tree_redb = db_redb.open_tree("test_state_a").unwrap();
tree_redb.insert(b"key", b"value").unwrap();
assert_eq!(tree_redb.get(b"key").unwrap(), Some(b"value".to_vec()));
#[cfg(feature = "sled")]
{
let db_sled: Arc<dyn Database> =
Arc::from(create_database(temp_dir2.path(), DatabaseBackend::Sled, None).unwrap());
let tree_sled = db_sled.open_tree("test_state_b").unwrap();
tree_sled.insert(b"key", b"value").unwrap();
assert_eq!(tree_sled.get(b"key").unwrap(), Some(b"value".to_vec()));
}
#[cfg(not(feature = "sled"))]
let _ = temp_dir2; }
#[cfg(feature = "block-compression")]
#[test]
fn test_block_compression_ratio() {
let temp_dir = TempDir::new().unwrap();
let db: Arc<dyn blvm_node::storage::database::Database> = Arc::from(
blvm_node::storage::database::create_database(
temp_dir.path(),
blvm_node::storage::database::DatabaseBackend::Sled,
None,
)
.unwrap(),
);
let blockstore = BlockStore::new_with_compression(
db.clone(),
true, 3, false, 2, )
.unwrap();
let mut block_builder =
TestBlockBuilder::new().add_coinbase_transaction(p2pkh_script(random_hash20()));
for _ in 0..100 {
block_builder = block_builder.add_transaction(
TestTransactionBuilder::new()
.add_input(OutPoint {
hash: random_hash(),
index: 0,
})
.add_output(1000, p2pkh_script(random_hash20()))
.with_lock_time(0)
.build(),
);
}
let block = block_builder.build();
blockstore.store_block(&block).unwrap();
let block_hash = blockstore.get_block_hash(&block);
let retrieved = blockstore.get_block(&block_hash).unwrap();
assert!(retrieved.is_some());
}
#[cfg(feature = "witness-compression")]
#[test]
fn test_witness_compression_roundtrip() {
let temp_dir = TempDir::new().unwrap();
let db = Arc::from(
blvm_node::storage::database::create_database(
temp_dir.path(),
blvm_node::storage::database::DatabaseBackend::Sled,
None,
)
.unwrap(),
);
let blockstore = BlockStore::new_with_compression(
db, false, 3, true, 2, )
.unwrap();
let block = TestBlockBuilder::new()
.add_coinbase_transaction(p2pkh_script(random_hash20()))
.build();
let block_hash = blockstore.get_block_hash(&block);
let witnesses = vec![vec![vec![0x01, 0x02, 0x03, 0x04, 0x05]]];
blockstore.store_witness(&block_hash, &witnesses).unwrap();
let retrieved = blockstore.get_witness(&block_hash).unwrap();
assert!(retrieved.is_some());
let retrieved_witnesses = retrieved.unwrap();
assert_eq!(retrieved_witnesses.len(), witnesses.len());
assert_eq!(retrieved_witnesses[0].len(), witnesses[0].len());
}
#[cfg(feature = "utxo-compression")]
#[test]
fn test_utxo_compression_roundtrip() {
let temp_dir = TempDir::new().unwrap();
let db = Arc::from(
blvm_node::storage::database::create_database(
temp_dir.path(),
blvm_node::storage::database::DatabaseBackend::Sled,
None,
)
.unwrap(),
);
let utxostore = UtxoStore::new_with_compression(
db, true, 1, )
.unwrap();
let outpoint = OutPoint {
hash: random_hash(),
index: 0,
};
let utxo = UTXO {
value: 5000000000,
script_pubkey: p2pkh_script(random_hash20()).into(),
height: 0,
is_coinbase: false,
};
utxostore.add_utxo(&outpoint, &utxo).unwrap();
assert!(utxostore.has_utxo(&outpoint).unwrap());
let retrieved_utxo = utxostore.get_utxo(&outpoint).unwrap().unwrap();
assert_eq!(retrieved_utxo.value, utxo.value);
assert_eq!(retrieved_utxo.script_pubkey, utxo.script_pubkey);
assert_eq!(retrieved_utxo.height, utxo.height);
}
#[cfg(feature = "utxo-compression")]
#[test]
fn test_utxo_compression_auto_detection() {
let temp_dir = TempDir::new().unwrap();
let db = Arc::from(
blvm_node::storage::database::create_database(
temp_dir.path(),
blvm_node::storage::database::DatabaseBackend::Sled,
None,
)
.unwrap(),
);
let utxostore = UtxoStore::new_with_compression(
db, true, 1, )
.unwrap();
let mut utxo_set = UtxoSet::default();
for i in 0..10 {
let outpoint = OutPoint {
hash: random_hash(),
index: i,
};
let utxo = UTXO {
value: 1000000 * (i + 1),
script_pubkey: p2pkh_script(random_hash20()).into(),
height: 0,
is_coinbase: false,
};
utxo_set.insert(outpoint, utxo);
}
utxostore.store_utxo_set(&utxo_set).unwrap();
let loaded_set = utxostore.load_utxo_set().unwrap();
assert_eq!(loaded_set.len(), 10);
}