Skip to main content

snap_coin/light_node/
block_meta_store.rs

1use std::{
2    collections::HashMap,
3    fs::{self, File},
4    io::Write,
5    path::PathBuf,
6    sync::{Mutex, RwLock},
7};
8
9use bincode::{Decode, Encode};
10use thiserror::Error;
11
12use crate::{
13    core::{block::BlockMetadata, difficulty::DifficultyState},
14    crypto::Hash,
15    economics::GENESIS_PREVIOUS_BLOCK_HASH,
16};
17
18#[derive(Error, Debug, Clone)]
19pub enum BlockMetaStoreError {
20    #[error("Previous block hash does not match the last added block")]
21    IncorrectPreviousBlock,
22
23    #[error("Encoding failed")]
24    Encode,
25
26    #[error("Decoding failed")]
27    Decode,
28
29    #[error("IO error: {0}")]
30    Io(String),
31}
32
33impl From<std::io::Error> for BlockMetaStoreError {
34    fn from(e: std::io::Error) -> Self {
35        BlockMetaStoreError::Io(e.to_string())
36    }
37}
38
39#[derive(Encode, Decode, Clone)]
40pub struct BlockMetaIndex {
41    by_hash: HashMap<Hash, usize>,
42    by_height: HashMap<usize, Hash>,
43}
44
45impl BlockMetaIndex {
46    pub fn new_empty() -> Self {
47        Self {
48            by_hash: HashMap::new(),
49            by_height: HashMap::new(),
50        }
51    }
52}
53
54#[derive(Encode, Decode)]
55pub struct BlockMetaStore {
56    pub difficulty_state: DifficultyState,
57    node_path: PathBuf,
58    last_block: RwLock<Hash>,
59    meta_index: RwLock<BlockMetaIndex>,
60    height: RwLock<usize>,
61    adding_block: Mutex<()>
62}
63
64impl BlockMetaStore {
65    pub fn new(node_path: PathBuf) -> Self {
66        fs::create_dir_all(&node_path).ok();
67
68        match Self::load_meta_store_data(&node_path) {
69            Ok(block_meta_store) => block_meta_store,
70            Err(_) => Self {
71                difficulty_state: DifficultyState::new_default(),
72                node_path,
73                last_block: RwLock::new(GENESIS_PREVIOUS_BLOCK_HASH),
74                meta_index: RwLock::new(BlockMetaIndex::new_empty()),
75                height: RwLock::new(0),
76                adding_block: Mutex::new(())
77            },
78        }
79    }
80
81    /// Saves block metadata to disk and updates indices
82    pub fn save_block_meta(&self, block_meta: BlockMetadata) -> Result<(), BlockMetaStoreError> {
83        let _add_guard = self.adding_block.lock().unwrap();
84        // Enforce chain continuity
85        if block_meta.previous_block != *self.last_block.read().unwrap() {
86            return Err(BlockMetaStoreError::IncorrectPreviousBlock);
87        }
88
89        let height = *self.height.read().unwrap();
90        let final_path = self.meta_path_by_height(height);
91        let tmp_path = final_path.with_extension("dat.tmp");
92
93        // Serialize
94        let buffer = bincode::encode_to_vec(&block_meta, bincode::config::standard())
95            .map_err(|_| BlockMetaStoreError::Encode)?;
96
97        // Atomic write
98        {
99            let mut f = File::create(&tmp_path)?;
100            f.write_all(&buffer)?;
101            f.sync_all()?;
102        }
103        fs::rename(&tmp_path, &final_path)?;
104
105        let hash = block_meta.hash.expect("BlockMetadata must contain hash");
106
107        // Update index
108        {
109            let mut index = self.meta_index.write().unwrap();
110            index.by_hash.insert(hash, height);
111            index.by_height.insert(height, hash);
112        }
113
114        // Update height + last block
115        *self.height.write().unwrap() = height + 1;
116        *self.last_block.write().unwrap() = hash;
117
118        self.save_meta_store_data()?;
119
120        Ok(())
121    }
122
123    pub fn get_height(&self) -> usize {
124        *self.height.read().unwrap()
125    }
126
127    pub fn get_last_block_hash(&self) -> Hash {
128        *self.last_block.read().unwrap()
129    }
130
131    pub fn get_meta_by_height(&self, height: usize) -> Option<BlockMetadata> {
132        let path = self.meta_path_by_height(height);
133        let data = fs::read(path).ok()?;
134        let (meta, _) = bincode::decode_from_slice(&data, bincode::config::standard()).ok()?;
135        Some(meta)
136    }
137
138    pub fn get_meta_by_hash(&self, hash: Hash) -> Option<BlockMetadata> {
139        let height = *self.meta_index.read().unwrap().by_hash.get(&hash)?;
140        self.get_meta_by_height(height)
141    }
142
143    fn meta_path_by_height(&self, height: usize) -> PathBuf {
144        self.node_path.join(format!("meta-{}.dat", height))
145    }
146
147    fn save_meta_store_data(&self) -> Result<(), BlockMetaStoreError> {
148        let mut file = File::create(format!(
149            "{}/meta-store.dat",
150            self.node_path.to_str().unwrap()
151        ))
152        .map_err(|e| BlockMetaStoreError::Io(e.to_string()))?;
153
154        bincode::encode_into_std_write(self.clone(), &mut file, bincode::config::standard())
155            .map_err(|_| BlockMetaStoreError::Encode)?;
156
157        file.sync_all()
158            .map_err(|e| BlockMetaStoreError::Io(e.to_string()))?;
159        Ok(())
160    }
161
162    fn load_meta_store_data(node_path: &PathBuf) -> Result<Self, BlockMetaStoreError> {
163        let mut file = File::open(format!("{}/meta-store.dat", node_path.to_str().unwrap()))
164            .map_err(|e| BlockMetaStoreError::Io(e.to_string()))?;
165        Ok(
166            bincode::decode_from_std_read(&mut file, bincode::config::standard())
167                .map_err(|_| BlockMetaStoreError::Decode)?,
168        )
169    }
170}
171
172impl Clone for BlockMetaStore {
173    fn clone(&self) -> Self {
174        Self {
175            node_path: self.node_path.clone(),
176            last_block: RwLock::new(*self.last_block.read().unwrap()),
177            meta_index: RwLock::new(self.meta_index.read().unwrap().clone()),
178            height: RwLock::new(*self.height.read().unwrap()),
179            difficulty_state: self.difficulty_state.clone(),
180            adding_block: Mutex::new(())
181        }
182    }
183}