use chia_protocol::Bytes32;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SnapshotManifest {
pub version: u32,
pub start_height: u64,
pub end_height: u64,
pub block_count: u64,
pub state_root: Bytes32,
pub checksum: Bytes32,
}
pub const SNAPSHOT_VERSION: u32 = 1;
use crate::constants::CF_BLOCKS;
use crate::encoding::hash_key;
use crate::error::BlockStoreError;
use crate::store::BlockStore;
impl BlockStore {
pub fn export_snapshot(
&self,
start_height: u64,
end_height: u64,
writer: &mut impl std::io::Write,
) -> Result<crate::snapshot::SnapshotManifest, BlockStoreError> {
use crate::snapshot::{SnapshotManifest, SNAPSHOT_VERSION};
use chia_sha2::Sha256;
let block_count = end_height.saturating_sub(start_height) + 1;
let end_header = self.get_header_by_height(end_height)?.ok_or_else(|| {
BlockStoreError::Serialization(format!(
"export_snapshot: no canonical block at end_height {end_height}"
))
})?;
let mut manifest = SnapshotManifest {
version: SNAPSHOT_VERSION,
start_height,
end_height,
block_count,
state_root: end_header.state_root,
checksum: Bytes32::default(), };
let mut hasher = Sha256::new();
let manifest_bytes = bincode::serialize(&manifest)
.map_err(|e| BlockStoreError::Serialization(e.to_string()))?;
writer
.write_all(&manifest_bytes)
.map_err(|e| BlockStoreError::Serialization(format!("snapshot write: {e}")))?;
hasher.update(&manifest_bytes);
let cf_b = self.cf(CF_BLOCKS)?;
for height in start_height..=end_height {
let hash = self.get_hash_by_height(height)?.ok_or_else(|| {
BlockStoreError::Serialization(format!(
"export_snapshot: no canonical hash at height {height}"
))
})?;
let compressed = self
.db
.get_cf(cf_b, hash_key(&hash).as_slice())?
.ok_or(BlockStoreError::BlockNotFound(hash))?;
let len = compressed.len() as u32;
let len_bytes = len.to_le_bytes();
writer
.write_all(&len_bytes)
.map_err(|e| BlockStoreError::Serialization(format!("snapshot write: {e}")))?;
hasher.update(len_bytes);
writer
.write_all(&compressed)
.map_err(|e| BlockStoreError::Serialization(format!("snapshot write: {e}")))?;
hasher.update(&compressed);
}
let checksum_arr: [u8; 32] = hasher.finalize();
let checksum = Bytes32::new(checksum_arr);
writer
.write_all(checksum.as_ref())
.map_err(|e| BlockStoreError::Serialization(format!("snapshot write: {e}")))?;
manifest.checksum = checksum;
Ok(manifest)
}
pub fn import_snapshot(
&self,
reader: &mut impl std::io::Read,
) -> Result<crate::snapshot::SnapshotManifest, BlockStoreError> {
use crate::snapshot::SNAPSHOT_VERSION;
use chia_sha2::Sha256;
let mut hasher = Sha256::new();
let manifest: crate::snapshot::SnapshotManifest = bincode::deserialize_from(&mut *reader)
.map_err(|e| {
BlockStoreError::Serialization(format!("invalid snapshot manifest: {e}"))
})?;
let manifest_bytes = bincode::serialize(&manifest)
.map_err(|e| BlockStoreError::Serialization(e.to_string()))?;
hasher.update(&manifest_bytes);
if manifest.version != SNAPSHOT_VERSION {
return Err(BlockStoreError::Serialization(format!(
"unsupported snapshot version: {}",
manifest.version
)));
}
let mut prev_hash: Option<Bytes32> = None;
for expected_height in manifest.start_height..=manifest.end_height {
let mut len_bytes = [0u8; 4];
reader
.read_exact(&mut len_bytes)
.map_err(|e| BlockStoreError::Serialization(format!("snapshot read: {e}")))?;
hasher.update(len_bytes);
let block_len = u32::from_le_bytes(len_bytes) as usize;
let mut compressed = vec![0u8; block_len];
reader
.read_exact(&mut compressed)
.map_err(|e| BlockStoreError::Serialization(format!("snapshot read: {e}")))?;
hasher.update(&compressed);
let block = self.deserialize_block(&compressed)?;
if block.height() != expected_height {
return Err(BlockStoreError::Serialization(format!(
"non-contiguous height: expected {expected_height}, got {}",
block.height()
)));
}
if let Some(prev) = &prev_hash {
if block.header.parent_hash != *prev {
return Err(BlockStoreError::Serialization(format!(
"broken parent link at height {expected_height}"
)));
}
}
prev_hash = Some(block.hash());
self.put_block(&block, true)?;
}
let mut checksum_bytes = [0u8; 32];
reader
.read_exact(&mut checksum_bytes)
.map_err(|e| BlockStoreError::Serialization(format!("snapshot checksum read: {e}")))?;
let expected_checksum = Bytes32::new(checksum_bytes);
let computed_arr: [u8; 32] = hasher.finalize();
let computed_checksum = Bytes32::new(computed_arr);
if expected_checksum != computed_checksum {
return Err(BlockStoreError::Serialization(
"snapshot checksum mismatch".to_string(),
));
}
Ok(manifest)
}
}