#![allow(unsafe_code)]
use std::fs::{File, OpenOptions};
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use chia_protocol::Bytes32;
use memmap2::{Mmap, MmapMut, MmapOptions};
use rocksdb::{ColumnFamily, DB};
use crate::constants::CF_CANONICAL;
use crate::encoding::decode_height_key;
use crate::error::BlockStoreError;
pub const CANONICAL_BIN_FILE: &str = "canonical.bin";
#[must_use]
pub(crate) fn canonical_bin_path(db_dir: &Path) -> PathBuf {
db_dir.join(CANONICAL_BIN_FILE)
}
fn read_slice_nonzero(mmap: &[u8], off: usize) -> Option<[u8; 32]> {
if off + 32 > mmap.len() {
return None;
}
let s = &mmap[off..off + 32];
if s.iter().all(|&b| b == 0) {
return None;
}
s.try_into().ok()
}
pub struct CanonicalDenseFile {
file: File,
mmap: Option<MmapMut>,
}
impl CanonicalDenseFile {
pub fn open_read_write(path: impl AsRef<Path>) -> Result<Self, BlockStoreError> {
let path = path.as_ref().to_path_buf();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin create parent {}: {e}",
parent.display()
))
})?;
}
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(&path)
.map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin open rw {}: {e}",
path.display()
))
})?;
Self::map_existing(file, &path)
}
fn map_existing(file: File, path_for_errors: &Path) -> Result<Self, BlockStoreError> {
let len = file
.metadata()
.map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin metadata {}: {e}",
path_for_errors.display()
))
})?
.len() as usize;
let mmap = unsafe { MmapOptions::new().len(len).map_mut(&file) }.map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin mmap_mut {}: {e}",
path_for_errors.display()
))
})?;
Ok(Self {
file,
mmap: Some(mmap),
})
}
fn mmap_active(&self) -> &MmapMut {
self.mmap
.as_ref()
.expect("canonical.bin: mmap must be installed after open/remap")
}
fn mmap_active_mut(&mut self) -> &mut MmapMut {
self.mmap
.as_mut()
.expect("canonical.bin: mmap must be installed after open/remap")
}
#[must_use]
pub fn len_bytes(&self) -> usize {
self.mmap_active().len()
}
pub fn read_hash(&self, height: u64) -> Result<Option<Bytes32>, BlockStoreError> {
let offset = (height as usize).checked_mul(32).ok_or_else(|| {
BlockStoreError::Serialization("canonical.bin read: height overflow".into())
})?;
let end = offset + 32;
if end > self.mmap_active().len() {
return Ok(None);
}
let slice = &self.mmap_active()[offset..end];
let arr: [u8; 32] = <[u8; 32]>::try_from(slice).map_err(|_| {
BlockStoreError::Serialization("canonical.bin read: 32-byte window".into())
})?;
Ok(Some(Bytes32::new(arr)))
}
pub(crate) fn read_slot_nonzero_bytes(&self, height: u64) -> Option<[u8; 32]> {
let off = usize::try_from(height.checked_mul(32)?).ok()?;
read_slice_nonzero(self.mmap_active().as_ref(), off)
}
pub fn write_hash(&mut self, height: u64, hash: &Bytes32) -> Result<(), BlockStoreError> {
let need = (height as usize)
.checked_add(1)
.and_then(|n| n.checked_mul(32))
.ok_or_else(|| {
BlockStoreError::Serialization("canonical.bin write: height overflow".into())
})?;
let cur = self.mmap_active().len();
if need > cur {
self.mmap.take();
self.file.set_len(need as u64).map_err(|e| {
BlockStoreError::Serialization(format!("canonical.bin set_len: {e}"))
})?;
self.mmap = Some(
unsafe { MmapOptions::new().len(need).map_mut(&self.file) }.map_err(|e| {
BlockStoreError::Serialization(format!("canonical.bin remap after extend: {e}"))
})?,
);
}
let o = (height as usize) * 32;
self.mmap_active_mut()[o..o + 32].copy_from_slice(hash.as_ref());
Ok(())
}
pub fn truncate(&mut self, max_height: u64) -> Result<(), BlockStoreError> {
let new_len = (max_height as usize)
.checked_add(1)
.and_then(|n| n.checked_mul(32))
.ok_or_else(|| {
BlockStoreError::Serialization("canonical.bin truncate: height overflow".into())
})?;
self.mmap.take();
self.file.set_len(new_len as u64).map_err(|e| {
BlockStoreError::Serialization(format!("canonical.bin truncate set_len: {e}"))
})?;
self.mmap = Some(
unsafe { MmapOptions::new().len(new_len).map_mut(&self.file) }.map_err(|e| {
BlockStoreError::Serialization(format!("canonical.bin truncate remap: {e}"))
})?,
);
Ok(())
}
}
pub(crate) fn dense_bytes_from_cf(db: &DB, cf: &ColumnFamily) -> Result<Vec<u8>, BlockStoreError> {
let mut heights: Vec<(u64, Bytes32)> = Vec::new();
let iter = db.iterator_cf(cf, rocksdb::IteratorMode::Start);
for item in iter {
let (k, v) = item.map_err(BlockStoreError::RocksDb)?;
let key: [u8; 8] = k.as_ref().try_into().map_err(|_| {
BlockStoreError::Serialization(
"canonical.bin rebuild: CF_CANONICAL key must be exactly 8 bytes".into(),
)
})?;
let height = decode_height_key(&key);
let arr: [u8; 32] = <[u8; 32]>::try_from(v.as_ref()).map_err(|_| {
BlockStoreError::Serialization(
"canonical.bin rebuild: CF_CANONICAL value must be exactly 32 bytes".into(),
)
})?;
heights.push((height, Bytes32::new(arr)));
}
if heights.is_empty() {
return Ok(Vec::new());
}
let max_h = heights.iter().map(|(h, _)| *h).max().unwrap_or(0);
let mut buf = vec![0u8; (max_h as usize + 1) * 32];
for (h, hash) in heights {
let o = (h as usize).saturating_mul(32);
if o + 32 > buf.len() {
return Err(BlockStoreError::Serialization(
"canonical.bin rebuild: height exceeds dense buffer (internal error)".into(),
));
}
buf[o..o + 32].copy_from_slice(hash.as_ref());
}
Ok(buf)
}
pub(crate) enum CanonicalBin {
Rw(CanonicalDenseFile),
Ro { _file: File, mmap: Mmap },
Disabled,
}
impl CanonicalBin {
pub(crate) fn open_synced(
db: &Arc<DB>,
db_dir: &Path,
writable: bool,
) -> Result<Self, BlockStoreError> {
let cf = db.cf_handle(CF_CANONICAL).ok_or_else(|| {
BlockStoreError::Serialization(format!(
"open_synced canonical.bin: missing column family {CF_CANONICAL}"
))
})?;
let expected = dense_bytes_from_cf(db, cf)?;
let path = canonical_bin_path(db_dir);
let on_disk = if path.exists() {
std::fs::read(&path).map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin read {}: {e}",
path.display()
))
})?
} else {
Vec::new()
};
if on_disk != expected {
if !writable {
return Ok(Self::Disabled);
}
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin create parent {}: {e}",
parent.display()
))
})?;
}
let mut f = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.read(true)
.open(&path)
.map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin open {} for rebuild: {e}",
path.display()
))
})?;
f.write_all(&expected).map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin write {}: {e}",
path.display()
))
})?;
f.flush().map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin flush {}: {e}",
path.display()
))
})?;
} else if expected.is_empty() && !path.exists() {
if writable {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin create parent {}: {e}",
parent.display()
))
})?;
}
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&path)
.map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin touch empty {}: {e}",
path.display()
))
})?;
} else {
return Ok(Self::Disabled);
}
}
Self::map_file(&path, writable)
}
fn map_file(path: &Path, writable: bool) -> Result<Self, BlockStoreError> {
if writable {
Ok(Self::Rw(CanonicalDenseFile::open_read_write(path)?))
} else {
let file = OpenOptions::new().read(true).open(path).map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin ro open {}: {e}",
path.display()
))
})?;
let mmap = unsafe { MmapOptions::new().map(&file) }.map_err(|e| {
BlockStoreError::Serialization(format!(
"canonical.bin mmap {}: {e}",
path.display()
))
})?;
Ok(Self::Ro { _file: file, mmap })
}
}
pub(crate) fn read_hash_bytes(&self, height: u64) -> Option<[u8; 32]> {
match self {
Self::Rw(d) => d.read_slot_nonzero_bytes(height),
Self::Ro { mmap, .. } => {
let off = usize::try_from(height.checked_mul(32)?).ok()?;
read_slice_nonzero(mmap.as_ref(), off)
}
Self::Disabled => None,
}
}
pub(crate) fn extend_write(
&mut self,
height: u64,
hash: &Bytes32,
) -> Result<(), BlockStoreError> {
match self {
Self::Rw(d) => d.write_hash(height, hash),
_ => Ok(()),
}
}
pub(crate) fn truncate_to_height(&mut self, max_height: u64) -> Result<(), BlockStoreError> {
match self {
Self::Rw(d) => d.truncate(max_height),
_ => Ok(()),
}
}
pub(crate) fn disable(&mut self) {
*self = Self::Disabled;
}
}