snap_coin/light_node/
block_meta_store.rs1use 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 pub fn save_block_meta(&self, block_meta: BlockMetadata) -> Result<(), BlockMetaStoreError> {
83 let _add_guard = self.adding_block.lock().unwrap();
84 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 let buffer = bincode::encode_to_vec(&block_meta, bincode::config::standard())
95 .map_err(|_| BlockMetaStoreError::Encode)?;
96
97 {
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 {
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 *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}