mod helpers;
use std::collections::HashMap;
use dig_coinstore::{
coin_store::CoinStore, ApplyBlockResult, BlockData, CoinId, CoinRecord, CoinStoreError,
RollbackResult,
};
#[test]
fn vv_req_api_006_apply_block_result_all_fields_accessible() {
let root = helpers::test_hash(0x71);
let r = ApplyBlockResult {
state_root: root,
coins_created: 11,
coins_spent: 4,
height: 99,
};
assert_eq!(r.state_root, root);
assert_eq!(r.coins_created, 11);
assert_eq!(r.coins_spent, 4);
assert_eq!(r.height, 99);
assert_eq!(r, r.clone());
}
#[test]
fn vv_req_api_006_apply_block_result_bincode_roundtrip() {
let r = ApplyBlockResult {
state_root: helpers::filled_hash(0xCE),
coins_created: 3,
coins_spent: 2,
height: 42,
};
let bytes = bincode::serialize(&r).expect("serialize ApplyBlockResult");
let back: ApplyBlockResult = bincode::deserialize(&bytes).expect("deserialize");
assert_eq!(back, r);
}
#[test]
fn vv_req_api_006_rollback_result_all_fields_and_hashmap_type() {
let coin_a = helpers::test_coin(1, 2, 100);
let coin_b = helpers::test_coin(3, 4, 200);
let id_a: CoinId = coin_a.coin_id();
let id_b: CoinId = coin_b.coin_id();
let rec_a = CoinRecord::new(coin_a, 10, 1_700_000_000, false);
let rec_b = CoinRecord::new(coin_b, 11, 1_700_000_001, true);
let mut modified = HashMap::new();
modified.insert(id_a, rec_a);
modified.insert(id_b, rec_b);
let roll = RollbackResult {
modified_coins: modified,
coins_deleted: 2,
coins_unspent: 0,
new_height: 5,
};
assert_eq!(roll.modified_coins.len(), 2);
assert_eq!(
roll.coins_deleted + roll.coins_unspent,
roll.modified_coins.len()
);
assert_eq!(roll.new_height, 5);
}
#[test]
fn vv_req_api_006_rollback_result_count_invariant_matches_map_len() {
let c1 = helpers::test_coin(5, 6, 1);
let c2 = helpers::test_coin(7, 8, 2);
let c3 = helpers::test_coin(9, 10, 3);
let mut m = HashMap::new();
m.insert(c1.coin_id(), CoinRecord::new(c1, 3, 0, false));
m.insert(c2.coin_id(), CoinRecord::new(c2, 4, 0, false));
m.insert(c3.coin_id(), CoinRecord::new(c3, 4, 0, false));
let roll = RollbackResult {
modified_coins: m,
coins_deleted: 1,
coins_unspent: 2,
new_height: 2,
};
assert_eq!(
roll.coins_deleted + roll.coins_unspent,
roll.modified_coins.len(),
"NORMATIVE API-006 / API-006.md: enriched counts should align with modified_coins cardinality"
);
}
#[test]
fn vv_req_api_006_rollback_result_bincode_roundtrip() {
let coin = helpers::test_coin(0xAA, 0xBB, 999);
let id = coin.coin_id();
let mut m = HashMap::new();
m.insert(id, CoinRecord::new(coin, 20, 1234567890, false));
let roll = RollbackResult {
modified_coins: m,
coins_deleted: 1,
coins_unspent: 0,
new_height: 19,
};
let bytes = bincode::serialize(&roll).expect("serialize RollbackResult");
let back: RollbackResult = bincode::deserialize(&bytes).expect("deserialize");
assert_eq!(back, roll);
}
fn minimal_block_at(height: u64) -> BlockData {
BlockData {
height,
timestamp: 0,
block_hash: helpers::test_hash(0xC1),
parent_hash: helpers::test_hash(0xC2),
additions: vec![],
removals: vec![],
coinbase_coins: vec![],
hints: vec![],
expected_state_root: None,
}
}
#[cfg(feature = "rocksdb-storage")]
#[test]
fn vv_req_api_006_apply_block_not_initialized_without_genesis() {
let dir = helpers::temp_dir();
let mut store = CoinStore::new(dir.path()).unwrap();
let out: Result<ApplyBlockResult, CoinStoreError> = store.apply_block(minimal_block_at(1));
assert!(matches!(out, Err(CoinStoreError::NotInitialized)));
}
#[cfg(feature = "rocksdb-storage")]
#[test]
fn vv_req_api_006_coin_store_apply_block_result_type() {
let dir = helpers::temp_dir();
let mut store = CoinStore::new(dir.path()).unwrap();
store.init_genesis(vec![], 1_700_000_000).unwrap();
let block = minimal_block_at(7);
let out: Result<ApplyBlockResult, CoinStoreError> = store.apply_block(block);
let err = out.expect_err("block at height 7 with current height 0 must fail (BLK-002)");
assert!(
matches!(
err,
CoinStoreError::HeightMismatch {
expected: 1,
got: 7
}
),
"Expected HeightMismatch {{ expected: 1, got: 7 }}, got: {err:?}"
);
}
#[cfg(feature = "rocksdb-storage")]
#[test]
fn vv_req_api_006_coin_store_rollback_to_block_result_type() {
let dir = helpers::temp_dir();
let mut store = CoinStore::new(dir.path()).unwrap();
store.init_genesis(vec![], 1_700_000_000).unwrap();
let out: Result<RollbackResult, CoinStoreError> = store.rollback_to_block(0);
assert!(
out.is_ok(),
"rollback_to_block(0) at height 0 should be a no-op"
);
let result = out.unwrap();
assert_eq!(result.new_height, 0);
assert_eq!(result.coins_deleted, 0);
}
#[cfg(feature = "rocksdb-storage")]
#[test]
fn vv_req_api_006_coin_store_rollback_n_blocks_result_type() {
let dir = helpers::temp_dir();
let mut store = CoinStore::new(dir.path()).unwrap();
store.init_genesis(vec![], 1_700_000_000).unwrap();
let out: Result<RollbackResult, CoinStoreError> = store.rollback_n_blocks(2);
assert!(
out.is_ok(),
"rollback_n_blocks(2) at height 0 should succeed as no-op"
);
let result = out.unwrap();
assert_eq!(result.new_height, 0);
}