#![forbid(unsafe_code)]
#[path = "common/mod.rs"]
mod common;
use std::path::Path;
use dig_block::constants::ZERO_HASH;
use dig_block::{AttestedBlock, ReceiptList};
use dig_blockstore::constants::ALL_COLUMN_FAMILIES;
use dig_blockstore::encoding::hash_key;
use dig_blockstore::{BlockStore, BlockStoreError, CF_ATTESTED};
use common::{temp_blockstore_dir, test_block, test_config};
use chia_protocol::Bytes32;
fn open_opts() -> rocksdb::Options {
let mut o = rocksdb::Options::default();
o.create_if_missing(true);
o.create_missing_column_families(true);
o
}
fn read_cf_attested_raw(path: &Path, hash: &Bytes32) -> Option<Vec<u8>> {
let cfs: Vec<_> = ALL_COLUMN_FAMILIES
.iter()
.map(|n| rocksdb::ColumnFamilyDescriptor::new(*n, rocksdb::Options::default()))
.collect();
let db = rocksdb::DB::open_cf_descriptors_read_only(&open_opts(), path, cfs, false).ok()?;
let cf = db.cf_handle(CF_ATTESTED)?;
db.get_cf(cf, hash_key(hash).as_slice()).ok().flatten()
}
#[test]
fn test_put_attestation_serializes_to_cf_attested() {
let (_guard, path) = temp_blockstore_dir();
let store = BlockStore::open(test_config(path.clone())).expect("open");
let block = test_block(7, ZERO_HASH);
let hash = block.hash();
let attested = AttestedBlock::new(block, 16, ReceiptList::default());
store
.put_attestation(&hash, &attested)
.expect("put_attestation");
drop(store);
let raw = read_cf_attested_raw(&path, &hash).expect("row exists");
assert!(
raw.len() > 32,
"bincode AttestedBlock payload should be larger than a bare hash key"
);
}
#[test]
fn test_attestation_round_trip_bytes_equal() {
let (_guard, path) = temp_blockstore_dir();
let store = BlockStore::open(test_config(path)).expect("open");
let block = test_block(2, ZERO_HASH);
let hash = block.hash();
let expected = AttestedBlock::new(block, 8, ReceiptList::default());
store
.put_attestation(&hash, &expected)
.expect("put_attestation");
let got = store
.get_attestation(&hash)
.expect("get_attestation")
.expect("some");
assert_eq!(
bincode::serialize(&got).expect("serialize got"),
bincode::serialize(&expected).expect("serialize expected"),
"stored attestation must deserialize to the same bincode bytes as inserted"
);
}
#[test]
fn test_get_attestation_unknown_hash_none() {
let (_guard, path) = temp_blockstore_dir();
let store = BlockStore::open(test_config(path)).expect("open");
let unknown = test_block(99, ZERO_HASH).hash();
assert!(store.get_attestation(&unknown).expect("get").is_none());
}
#[test]
fn test_put_attestation_overwrite_latest_wins() {
let (_guard, path) = temp_blockstore_dir();
let store = BlockStore::open(test_config(path)).expect("open");
let block = test_block(5, ZERO_HASH);
let hash = block.hash();
let first = AttestedBlock::new(block.clone(), 8, ReceiptList::default());
let second = AttestedBlock::new(block, 32, ReceiptList::default());
store.put_attestation(&hash, &first).expect("first");
store.put_attestation(&hash, &second).expect("second");
let got = store.get_attestation(&hash).expect("get").expect("some");
assert_eq!(
bincode::serialize(&got).unwrap(),
bincode::serialize(&second).unwrap()
);
assert_ne!(
bincode::serialize(&got).unwrap(),
bincode::serialize(&first).unwrap(),
"overwrite must change serialized form (validator set / bitmap size differs)"
);
}
#[test]
fn test_raw_cf_attested_manual_bincode_matches() {
let (_guard, path) = temp_blockstore_dir();
let store = BlockStore::open(test_config(path.clone())).expect("open");
let block = test_block(11, ZERO_HASH);
let hash = block.hash();
let attested = AttestedBlock::new(block, 10, ReceiptList::default());
store
.put_attestation(&hash, &attested)
.expect("put_attestation");
drop(store);
let raw = read_cf_attested_raw(&path, &hash).expect("raw row");
let manual: AttestedBlock = bincode::deserialize(&raw).expect("manual bincode");
let via_api = {
let s = BlockStore::open(test_config(path)).expect("reopen");
s.get_attestation(&hash).expect("get").expect("some")
};
assert_eq!(
bincode::serialize(&manual).unwrap(),
bincode::serialize(&via_api).unwrap()
);
}
#[test]
fn test_put_attestation_read_only_rejected() {
let (_guard, path) = temp_blockstore_dir();
let path_ro = path.clone();
{
let s = BlockStore::open(test_config(path)).expect("open rw");
let b = test_block(0, ZERO_HASH);
s.put_block(&b, false).expect("seed block");
}
let ro = BlockStore::open_readonly(path_ro).expect("ro");
let block = test_block(1, ZERO_HASH);
let attested = AttestedBlock::new(block.clone(), 4, ReceiptList::default());
let err = ro
.put_attestation(&block.hash(), &attested)
.expect_err("read-only must error");
match err {
BlockStoreError::Serialization(s) => {
assert!(s.contains("read-only") || s.contains("read only"), "{s}");
}
other => panic!("unexpected {other:?}"),
}
}