use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use zaino_common::network::ActivationHeights;
use zaino_common::{DatabaseConfig, Network, StorageConfig};
use crate::chain_index::finalised_state::capability::{
BlockCoreExt as _, DbCore as _, DbRead as _, DbVersion, DbWrite as _, MigrationStatus,
};
use crate::chain_index::finalised_state::db::v1::DB_SCHEMA_V1_HASH;
use crate::chain_index::finalised_state::db::DbBackend;
use crate::chain_index::finalised_state::entry::StoredEntryVar;
use crate::chain_index::finalised_state::ZainoDB;
use crate::chain_index::tests::init_tracing;
use crate::chain_index::tests::vectors::{
build_mockchain_source, load_test_vectors, TestVectorData,
};
use crate::{
version, BlockCacheConfig, BlockHeaderData, CompactSize, Height, ZainoVersionedSerde as _,
};
#[tokio::test(flavor = "multi_thread")]
async fn v0_to_v1_full() {
init_tracing();
let TestVectorData { blocks, .. } = load_test_vectors().unwrap();
let temp_dir: TempDir = tempfile::tempdir().unwrap();
let db_path: PathBuf = temp_dir.path().to_path_buf();
let v0_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path.clone(),
..Default::default()
},
..Default::default()
},
db_version: 0,
network: Network::Regtest(ActivationHeights::default()),
};
let v1_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path,
..Default::default()
},
..Default::default()
},
db_version: 1,
network: Network::Regtest(ActivationHeights::default()),
};
let source = build_mockchain_source(blocks.clone());
let zaino_db = ZainoDB::spawn(v0_config, source.clone()).await.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(
zaino_db.router(),
blocks.clone(),
None,
)
.await;
zaino_db.wait_until_ready().await;
dbg!(zaino_db.status());
dbg!(zaino_db.db_height().await.unwrap());
dbg!(zaino_db.shutdown().await.unwrap());
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
let zaino_db_2 = ZainoDB::spawn(v1_config, source).await.unwrap();
zaino_db_2.wait_until_ready().await;
dbg!(zaino_db_2.status());
let db_height = dbg!(zaino_db_2.db_height().await.unwrap()).unwrap();
assert_eq!(db_height.0, 200);
dbg!(zaino_db_2.shutdown().await.unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn v0_to_v1_interrupted() {
init_tracing();
let blocks = load_test_vectors().unwrap().blocks;
let temp_dir: TempDir = tempfile::tempdir().unwrap();
let db_path: PathBuf = temp_dir.path().to_path_buf();
let v0_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path.clone(),
..Default::default()
},
..Default::default()
},
db_version: 0,
network: Network::Regtest(ActivationHeights::default()),
};
let v1_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path,
..Default::default()
},
..Default::default()
},
db_version: 1,
network: Network::Regtest(ActivationHeights::default()),
};
let source = build_mockchain_source(blocks.clone());
let zaino_db = ZainoDB::spawn(v0_config, source.clone()).await.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(
zaino_db.router(),
blocks.clone(),
None,
)
.await;
zaino_db.wait_until_ready().await;
dbg!(zaino_db.status());
dbg!(zaino_db.db_height().await.unwrap());
dbg!(zaino_db.shutdown().await.unwrap());
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
let zaino_db = DbBackend::spawn_v1(&v1_config).await.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(&zaino_db, blocks.clone(), Some(50))
.await;
dbg!(zaino_db.shutdown().await.unwrap());
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
let zaino_db_2 = ZainoDB::spawn(v1_config, source).await.unwrap();
zaino_db_2.wait_until_ready().await;
dbg!(zaino_db_2.status());
let db_height = dbg!(zaino_db_2.db_height().await.unwrap()).unwrap();
assert_eq!(db_height.0, 200);
dbg!(zaino_db_2.shutdown().await.unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn v0_to_v1_partial() {
init_tracing();
let blocks = load_test_vectors().unwrap().blocks;
let temp_dir: TempDir = tempfile::tempdir().unwrap();
let db_path: PathBuf = temp_dir.path().to_path_buf();
let v0_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path.clone(),
..Default::default()
},
..Default::default()
},
db_version: 0,
network: Network::Regtest(ActivationHeights::default()),
};
let v1_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path,
..Default::default()
},
..Default::default()
},
db_version: 1,
network: Network::Regtest(ActivationHeights::default()),
};
let source = build_mockchain_source(blocks.clone());
let zaino_db = ZainoDB::spawn(v0_config, source.clone()).await.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(
zaino_db.router(),
blocks.clone(),
None,
)
.await;
zaino_db.wait_until_ready().await;
dbg!(zaino_db.status());
dbg!(zaino_db.db_height().await.unwrap());
dbg!(zaino_db.shutdown().await.unwrap());
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
let zaino_db = DbBackend::spawn_v1(&v1_config).await.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(&zaino_db, blocks.clone(), None)
.await;
dbg!(zaino_db.shutdown().await.unwrap());
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
let zaino_db_2 = ZainoDB::spawn(v1_config, source).await.unwrap();
zaino_db_2.wait_until_ready().await;
dbg!(zaino_db_2.status());
let db_height = dbg!(zaino_db_2.db_height().await.unwrap()).unwrap();
assert_eq!(db_height.0, 200);
dbg!(zaino_db_2.shutdown().await.unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn v1_0_to_v1_1_metadata_migration() {
init_tracing();
let TestVectorData { blocks, .. } = load_test_vectors().unwrap();
let temp_dir: TempDir = tempfile::tempdir().unwrap();
let db_path: PathBuf = temp_dir.path().to_path_buf();
let v1_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path.clone(),
..Default::default()
},
..Default::default()
},
db_version: 1,
network: Network::Regtest(ActivationHeights::default()),
};
let source = build_mockchain_source(blocks.clone());
let zaino_db = ZainoDB::spawn(v1_config.clone(), source.clone())
.await
.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(
zaino_db.router(),
blocks.clone(),
None,
)
.await;
zaino_db.wait_until_ready().await;
dbg!(zaino_db.status());
dbg!(zaino_db.db_height().await.unwrap());
let mut metadata = zaino_db.get_metadata().await.unwrap();
metadata.version = DbVersion {
major: 1,
minor: 0,
patch: 0,
};
metadata.schema_hash = [0u8; 32];
metadata.migration_status = MigrationStatus::PartialBuidInProgress;
zaino_db.router().update_metadata(metadata).await.unwrap();
dbg!(zaino_db.shutdown().await.unwrap());
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let zaino_db = ZainoDB::spawn(v1_config, source).await.unwrap();
zaino_db.wait_until_ready().await;
let post_meta = zaino_db.get_metadata().await.unwrap();
assert_eq!(
post_meta.version,
DbVersion {
major: 1,
minor: 1,
patch: 0
}
);
assert_eq!(post_meta.migration_status, MigrationStatus::Empty);
assert_eq!(post_meta.schema_hash, DB_SCHEMA_V1_HASH);
zaino_db.shutdown().await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn v1_0_to_v1_1_mixed_blockheaderdata_formats() {
init_tracing();
let TestVectorData { blocks, .. } = load_test_vectors().unwrap();
let split = blocks.len() / 2;
let first_half = blocks[..split].to_vec();
let second_half = blocks[split..].to_vec();
let temp_dir: TempDir = tempfile::tempdir().unwrap();
let db_path: PathBuf = temp_dir.path().to_path_buf();
let v1_config = BlockCacheConfig {
storage: StorageConfig {
database: DatabaseConfig {
path: db_path.clone(),
..Default::default()
},
..Default::default()
},
db_version: 1,
network: Network::Regtest(ActivationHeights::default()),
};
let source = build_mockchain_source(blocks.clone());
let db = DbBackend::spawn_v1(&v1_config).await.unwrap();
crate::chain_index::tests::vectors::sync_db_with_blockdata(&db, first_half.clone(), None).await;
db.wait_until_ready().await;
let mut first_half_headers: Vec<(Height, BlockHeaderData)> = Vec::new();
for block_data in &first_half {
let h = Height(block_data.height);
let header = db.get_block_header(h).await.unwrap();
first_half_headers.push((h, header));
}
let mut meta = db.get_metadata().await.unwrap();
meta.version = DbVersion {
major: 1,
minor: 0,
patch: 0,
};
meta.schema_hash = [0u8; 32]; db.update_metadata(meta).await.unwrap();
db.shutdown().await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
{
use lmdb::{Environment, EnvironmentFlags, Transaction as _, WriteFlags};
let lmdb_path = db_path.join("regtest").join("v1");
let env = Environment::new()
.set_max_dbs(12)
.set_map_size(128 * 1024 * 1024) .set_flags(EnvironmentFlags::NO_TLS)
.open(&lmdb_path)
.unwrap();
let headers_db = env.open_db(Some("headers_1_0_0")).unwrap();
let mut txn = env.begin_rw_txn().unwrap();
for (height, header) in &first_half_headers {
let height_key = height.to_bytes().unwrap();
let v1_item_bytes = header.to_bytes_with_version(version::V1).unwrap();
let checksum = StoredEntryVar::<BlockHeaderData>::blake2b256(
&[height_key.as_slice(), v1_item_bytes.as_slice()].concat(),
);
let mut stored_bytes: Vec<u8> = Vec::new();
stored_bytes.push(version::V1); CompactSize::write(&mut stored_bytes, v1_item_bytes.len()).unwrap();
stored_bytes.extend_from_slice(&v1_item_bytes);
stored_bytes.extend_from_slice(&checksum);
txn.put(headers_db, &height_key, &stored_bytes, WriteFlags::empty())
.unwrap();
}
txn.commit().unwrap();
env.sync(true).unwrap();
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let zaino_db = ZainoDB::spawn(v1_config.clone(), source.clone())
.await
.unwrap();
zaino_db.wait_until_ready().await;
let post_meta = zaino_db.get_metadata().await.unwrap();
assert_eq!(
post_meta.version,
DbVersion {
major: 1,
minor: 1,
patch: 0,
},
"DB version should be 1.1.0 after migration"
);
assert_eq!(
post_meta.migration_status,
MigrationStatus::Empty,
"Migration status should be Empty after the metadata-only migration"
);
assert_eq!(
post_meta.schema_hash, DB_SCHEMA_V1_HASH,
"Schema hash should be refreshed to the current value"
);
crate::chain_index::tests::vectors::sync_db_with_blockdata(
zaino_db.router(),
second_half.clone(),
None,
)
.await;
zaino_db.wait_until_ready().await;
let zaino_db = Arc::new(zaino_db);
let reader = zaino_db.to_reader();
for block_data in &first_half {
let h = Height(block_data.height);
let header = reader
.get_block_header(h)
.await
.unwrap_or_else(|e| panic!("failed to read V1-format header at height {}: {e}", h.0));
assert_eq!(
header.context.index.height, h,
"V1-format header at height {} returned wrong height",
h.0
);
}
for block_data in &second_half {
let h = Height(block_data.height);
let header = reader
.get_block_header(h)
.await
.unwrap_or_else(|e| panic!("failed to read V2-format header at height {}: {e}", h.0));
assert_eq!(
header.context.index.height, h,
"V2-format header at height {} returned wrong height",
h.0
);
}
let db_height = zaino_db.db_height().await.unwrap().unwrap();
let expected_tip = Height((blocks.len() - 1) as u32);
assert_eq!(
db_height, expected_tip,
"DB tip should be the last block's height after building the full chain"
);
zaino_db.shutdown().await.unwrap();
}