use crate::blockchain::MAX_TOKEN_SUPPLY;
use crate::crypto::SaitoPublicKey;
use crate::slip::{Slip, SlipType};
use std::{
fs::{self, File},
io::{self, BufRead, Read, Write},
path::Path,
sync::Arc,
};
use tokio::sync::RwLock;
use crate::{
block::{Block, BlockType},
blockchain::Blockchain,
};
lazy_static::lazy_static! {
pub static ref BLOCKS_DIR_PATH: String = configure_storage();
}
pub const ISSUANCE_FILE_PATH: &'static str = "./data/issuance/issuance";
pub const EARLYBIRDS_FILE_PATH: &'static str = "./data/issuance/earlybirds";
pub const DEFAULT_FILE_PATH: &'static str = "./data/issuance/default";
pub struct StorageConfigurer {}
pub fn configure_storage() -> String {
if cfg!(test) {
String::from("./data/test/blocks/")
} else {
String::from("./data/blocks/")
}
}
pub struct Storage {}
impl Storage {
pub fn read(path: &str) -> io::Result<Vec<u8>> {
let mut f = std::fs::File::open(path)?;
let mut data = Vec::<u8>::new();
f.read_to_end(&mut data)?;
Ok(data)
}
pub fn write(data: Vec<u8>, filename: &str) {
let mut buffer = File::create(filename).unwrap();
buffer.write_all(&data[..]).unwrap();
}
pub fn file_exists(filename: &str) -> bool {
let path = Path::new(&filename);
path.exists()
}
pub fn generate_block_filename(block: &Block) -> String {
let mut filename = BLOCKS_DIR_PATH.clone();
filename.push_str(&hex::encode(block.get_timestamp().to_be_bytes()));
filename.push_str(&String::from("-"));
filename.push_str(&hex::encode(&block.get_hash()));
filename.push_str(&".sai");
filename
}
pub fn write_block_to_disk(block: &mut Block) -> String {
if block.get_block_type() == BlockType::Pruned {
panic!("pruned blocks cannot be saved");
}
let filename = Storage::generate_block_filename(block);
if !Path::new(&filename).exists() {
let mut buffer = File::create(filename.clone()).unwrap();
let byte_array: Vec<u8> = block.serialize_for_net(BlockType::Full);
buffer.write_all(&byte_array[..]).unwrap();
}
filename
}
pub async fn load_blocks_from_disk(blockchain_lock: Arc<RwLock<Blockchain>>) {
let mut paths: Vec<_> = fs::read_dir(BLOCKS_DIR_PATH.clone())
.unwrap()
.map(|r| r.unwrap())
.collect();
paths.sort_by(|a, b| {
let a_metadata = fs::metadata(a.path()).unwrap();
let b_metadata = fs::metadata(b.path()).unwrap();
a_metadata
.modified()
.unwrap()
.partial_cmp(&b_metadata.modified().unwrap())
.unwrap()
});
for (_pos, path) in paths.iter().enumerate() {
if !path.path().to_str().unwrap().ends_with(".gitignore") {
let mut f = File::open(path.path()).unwrap();
let mut encoded = Vec::<u8>::new();
f.read_to_end(&mut encoded).unwrap();
let mut block = Block::deserialize_for_net(&encoded);
let mut blockchain = blockchain_lock.write().await;
block.generate_metadata();
blockchain.add_block(block).await;
}
}
}
pub async fn load_block_from_disk(filename: String) -> Block {
let file_to_load = &filename;
let mut f = File::open(file_to_load).unwrap();
let mut encoded = Vec::<u8>::new();
f.read_to_end(&mut encoded).unwrap();
Block::deserialize_for_net(&encoded)
}
pub async fn delete_block_from_disk(filename: String) -> bool {
let _res = std::fs::remove_file(filename);
true
}
pub fn return_token_supply_slips_from_disk() -> Vec<Slip> {
let mut v: Vec<Slip> = vec![];
let mut tokens_issued = 0;
if let Ok(lines) = Storage::read_lines_from_file(ISSUANCE_FILE_PATH) {
for line in lines {
if let Ok(ip) = line {
let s = Storage::convert_issuance_into_slip(ip);
v.push(s);
}
}
}
if let Ok(lines) = Storage::read_lines_from_file(EARLYBIRDS_FILE_PATH) {
for line in lines {
if let Ok(ip) = line {
let s = Storage::convert_issuance_into_slip(ip);
v.push(s);
}
}
}
for i in 0..v.len() {
tokens_issued += v[i].get_amount();
}
if let Ok(lines) = Storage::read_lines_from_file(DEFAULT_FILE_PATH) {
for line in lines {
if let Ok(ip) = line {
let mut s = Storage::convert_issuance_into_slip(ip);
s.set_amount(MAX_TOKEN_SUPPLY - tokens_issued);
v.push(s);
}
}
}
return v;
}
pub fn read_lines_from_file<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
pub fn convert_issuance_into_slip(line: std::string::String) -> Slip {
let mut iter = line.split_whitespace();
let tmp = iter.next().unwrap();
let tmp2 = iter.next().unwrap();
let typ = iter.next().unwrap();
let amt: u64 = tmp.parse::<u64>().unwrap();
let tmp3 = tmp2.as_bytes();
let mut add: SaitoPublicKey = [0; 33];
for i in 0..33 {
add[i] = tmp3[i];
}
let mut slip = Slip::new();
slip.set_publickey(add);
slip.set_amount(amt);
if typ.eq("VipOutput") {
slip.set_slip_type(SlipType::VipOutput);
}
if typ.eq("StakerDeposit") {
slip.set_slip_type(SlipType::StakerDeposit);
}
if typ.eq("Normal") {
slip.set_slip_type(SlipType::Normal);
}
return slip;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utilities::test_manager::TestManager;
use crate::time::create_timestamp;
use crate::wallet::Wallet;
impl Drop for Blockchain {
fn drop(&mut self) {
let paths: Vec<_> = fs::read_dir(BLOCKS_DIR_PATH.clone())
.unwrap()
.map(|r| r.unwrap())
.collect();
for (_pos, path) in paths.iter().enumerate() {
if !path.path().to_str().unwrap().ends_with(".gitignore") {
match std::fs::remove_file(path.path()) {
Err(err) => {
eprintln!("Error cleaning up after tests {}", err);
}
_ => {
log::trace!(
"block removed from disk : {}",
path.path().to_str().unwrap()
);
}
}
}
}
}
}
#[test]
fn read_issuance_file_test() {
let slips = Storage::return_token_supply_slips_from_disk();
let mut total_issuance = 0;
for i in 0..slips.len() {
total_issuance += slips[i].get_amount();
}
assert_eq!(total_issuance, MAX_TOKEN_SUPPLY);
}
#[tokio::test]
#[serial_test::serial]
async fn write_read_block_to_file_test() {
let wallet_lock = Arc::new(RwLock::new(Wallet::new()));
let blockchain_lock = Arc::new(RwLock::new(Blockchain::new(wallet_lock.clone())));
let test_manager = TestManager::new(blockchain_lock.clone(), wallet_lock.clone());
let current_timestamp = create_timestamp();
let mut block = test_manager
.generate_block_and_metadata([0; 32], current_timestamp, 0, 1, false, vec![])
.await;
let filename = Storage::write_block_to_disk(&mut block);
log::trace!("block written to file : {}", filename);
let retrieved_block = Storage::load_block_from_disk(filename).await;
assert_eq!(block.get_hash(), retrieved_block.get_hash());
}
}