pub mod reindex;
pub mod split;
pub mod strip;
use std::io::{Read, Seek, Write};
use crate::{Era1Block, EraBlockReader, EraRandomReader, error::Error, write::EraBuilder};
pub fn era1_to_era<R, W, F, E>(reader: R, writer: W, mut synthesizer: F) -> Result<(), E>
where
R: Read + Seek,
W: Write,
F: FnMut(&Era1Block, &mut Vec<u8>) -> Result<(), E>,
E: From<Error>,
{
let rr = EraRandomReader::new(reader).map_err(E::from)?;
let block_slots: Vec<u64> = rr
.block_index()
.map(|idx| {
idx.offsets
.iter()
.enumerate()
.filter_map(|(i, &off)| {
if off != 0 {
Some(idx.starting_slot + i as u64)
} else {
None
}
})
.collect()
})
.unwrap_or_default();
let mut reader = rr.into_inner();
reader
.seek(std::io::SeekFrom::Start(0))
.map_err(|e| E::from(Error::Io(e)))?;
let mut ssz_buf = Vec::with_capacity(256 * 1024);
let mut compressed_buf = Vec::with_capacity(256 * 1024);
let mut builder = EraBuilder::new();
let mut slot_idx = 0usize;
let mut block_reader = EraBlockReader::new(&mut reader);
while let Some(block) = block_reader.next_block().map_err(E::from)? {
if let crate::Block::Era1(era1) = block {
let slot = block_slots
.get(slot_idx)
.copied()
.unwrap_or(slot_idx as u64);
ssz_buf.clear();
synthesizer(&era1, &mut ssz_buf)?;
compressed_buf.clear();
compress_into(&ssz_buf, &mut compressed_buf).map_err(E::from)?;
builder.add_block(slot, std::mem::take(&mut compressed_buf));
slot_idx += 1;
} else {
return Err(E::from(Error::E2Store(
crate::error::E2StoreError::InvalidEra("expected ERA1 blocks in input".into()),
)));
}
}
builder
.build(&mut { writer })
.map_err(|e| E::from(Error::Io(e)))?;
Ok(())
}
fn compress_into(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
out.clear();
let mut enc = snap::write::FrameEncoder::new(out);
enc.write_all(data)
.map_err(|e| Error::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))?;
enc.flush()
.map_err(|e| Error::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::era::SlotIndex;
use crate::format::types::*;
use crate::verify::verify_era;
use crate::{format::Entry, write::E2StoreWriter};
use snap::write::FrameEncoder;
use std::io::{Cursor, Write};
fn compress(data: &[u8]) -> Vec<u8> {
let mut enc = FrameEncoder::new(Vec::new());
enc.write_all(data).unwrap();
enc.into_inner().unwrap()
}
fn build_test_era1() -> Vec<u8> {
let mut buf = Vec::new();
let mut w = E2StoreWriter::new(&mut buf);
w.write_entry(&Entry::version()).unwrap();
w.write_entry(&Entry::new(TYPE_COMPRESSED_HEADER, compress(&[0x64])))
.unwrap();
w.write_entry(&Entry::new(TYPE_COMPRESSED_BODY, vec![0x01]))
.unwrap();
let mut td = [0u8; 32];
td[31] = 100;
w.write_entry(&Entry::new(TYPE_TOTAL_DIFFICULTY, td.to_vec()))
.unwrap();
w.write_entry(&Entry::new(TYPE_COMPRESSED_HEADER, compress(&[0x66])))
.unwrap();
w.write_entry(&Entry::new(TYPE_COMPRESSED_BODY, vec![0x02]))
.unwrap();
let mut td2 = [0u8; 32];
td2[31] = 102;
w.write_entry(&Entry::new(TYPE_TOTAL_DIFFICULTY, td2.to_vec()))
.unwrap();
let idx = SlotIndex::with_count(100, vec![-8i64, 0i64, -8i64], 2);
w.write_entry(&Entry::new(TYPE_SLOT_INDEX, idx.encode()))
.unwrap();
buf
}
#[test]
fn era1_to_era_preserves_slot_range() {
let input = build_test_era1();
let mut output = Vec::new();
era1_to_era(
Cursor::new(input),
&mut output,
|_era1_block: &Era1Block, ssz_buf: &mut Vec<u8>| {
ssz_buf.extend_from_slice(&[0x00, 0x01, 0x02, 0x03]);
Ok::<(), Error>(())
},
)
.unwrap();
let result = verify_era(&output[..]);
if !result.valid {
panic!("output failed verification:\n{}", result.errors.join("\n"));
}
assert_eq!(result.format.as_deref(), Some("ERA"));
assert_eq!(result.block_count, 2);
let mut rr = EraRandomReader::new(Cursor::new(&output)).unwrap();
assert_eq!(rr.starting_slot(), Some(100));
assert_eq!(rr.slot_count(), Some(3));
assert!(rr.read_block_at_slot(100).unwrap().is_some());
assert!(rr.read_block_at_slot(101).unwrap().is_none());
assert!(rr.read_block_at_slot(102).unwrap().is_some());
}
#[test]
fn era1_to_era_synthesizer_receives_correct_blocks() {
let input = build_test_era1();
let mut output = Vec::new();
let mut seen_td: Vec<u8> = Vec::new();
era1_to_era(
Cursor::new(input),
&mut output,
|era1_block: &Era1Block, ssz_buf: &mut Vec<u8>| -> Result<(), Error> {
seen_td.push(era1_block.total_difficulty.0[31]);
ssz_buf.extend_from_slice(b"dummy");
Ok(())
},
)
.unwrap();
assert_eq!(seen_td, vec![100, 102]);
}
#[test]
fn era1_to_era_empty_file() {
let mut buf = Vec::new();
let mut w = E2StoreWriter::new(&mut buf);
w.write_entry(&Entry::version()).unwrap();
let idx = SlotIndex::new(0, vec![]);
w.write_entry(&Entry::new(TYPE_SLOT_INDEX, idx.encode()))
.unwrap();
let mut output = Vec::new();
era1_to_era(Cursor::new(buf), &mut output, |_, buf| {
buf.extend_from_slice(b"x");
Ok::<(), Error>(())
})
.unwrap();
let result = verify_era(&output[..]);
assert!(result.valid);
assert_eq!(result.block_count, 0);
}
#[test]
fn era1_to_era_rejects_non_era1() {
let mut buf = Vec::new();
let mut w = E2StoreWriter::new(&mut buf);
w.write_entry(&Entry::version()).unwrap();
w.write_entry(&Entry::new(
TYPE_COMPRESSED_SIGNED_BEACON_BLOCK,
compress(&[0x01]),
))
.unwrap();
let idx = SlotIndex::with_count(0, vec![-8i64], 1);
w.write_entry(&Entry::new(TYPE_SLOT_INDEX, idx.encode()))
.unwrap();
let mut output = Vec::new();
let result = era1_to_era(Cursor::new(buf), &mut output, |_, buf| {
buf.extend_from_slice(b"x");
Ok::<(), Error>(())
});
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expected ERA1"));
}
}