1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
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 {
    /// read from a path to a Vec<u8>
    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 {
        // TODO: get rid of this function or make it useful.
        // it should match the result and provide some error handling.
        let _res = std::fs::remove_file(filename);
        true
    }

    //
    // token issuance functions below
    //
    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());
    }
}