1use bincode::{Decode, Encode, config};
2use num_bigint::BigUint;
3use serde::{Deserialize, Serialize};
4use std::collections::{HashMap, HashSet};
5use std::fs::{self, File};
6use std::path::Path;
7use thiserror::Error;
8
9use crate::blockchain_data_provider::BlockchainDataProvider;
10use crate::core::block::Block;
11use crate::core::difficulty::{DifficultyManager, calculate_block_difficulty};
12use crate::core::transaction::{Transaction, TransactionId};
13use crate::core::utxo::{TransactionError, UTXODiff, UTXOs};
14use crate::crypto::Hash;
15use crate::economics::{
16 DEV_WALLET, EXPIRATION_TIME, GENESIS_PREVIOUS_BLOCK_HASH, calculate_dev_fee, get_block_reward,
17};
18
19#[derive(Error, Debug, Serialize, Deserialize)]
20pub enum BlockchainError {
21 #[error("IO error: {0}")]
22 Io(String),
23
24 #[error("Bincode decode error: {0}")]
25 BincodeDecode(String),
26
27 #[error("Bincode encode error: {0}")]
28 BincodeEncode(String),
29
30 #[error("Block hash mismatch. Expected {expected}, got {actual}")]
31 HashMismatch { expected: String, actual: String },
32
33 #[error("Block timestamp is in the future: {0}")]
34 FutureTimestamp(u64),
35
36 #[error("Invalid block / transaction difficulty")]
37 InvalidDifficulty,
38
39 #[error("Block does not have a hash attached")]
40 MissingHash,
41
42 #[error("Transaction is invalid: {0}")]
43 InvalidTransaction(String),
44
45 #[error("Double spend detected in transaction input")]
46 DoubleSpend,
47
48 #[error("Too many reward transaction in block")]
49 RewardOverspend,
50
51 #[error("Reward transaction is invalid")]
52 InvalidRewardTransaction,
53
54 #[error("Reward transaction's outputs do not sum up to block reward amount")]
55 InvalidRewardTransactionAmount,
56
57 #[error("Reward transaction's id is missing")]
58 RewardTransactionIdMissing,
59
60 #[error("Reward transaction's outputs do not include a dev fee transaction")]
61 NoDevFee,
62
63 #[error("No blocks to to pop")]
64 NoBlocksToPop,
65
66 #[error("Block or transaction timestamp is invalid or expired")]
67 InvalidTimestamp,
68
69 #[error("Previous block hash is invalid")]
70 InvalidPreviousBlockHash,
71}
72
73impl From<TransactionError> for BlockchainError {
74 fn from(err: TransactionError) -> Self {
75 BlockchainError::InvalidTransaction(err.to_string())
76 }
77}
78
79#[derive(Encode, Decode, Debug, Clone)]
81struct BlockchainData {
82 difficulty_manager: DifficultyManager,
83 height: usize,
84 block_lookup: HashMap<Hash, usize>,
85 utxos: UTXOs,
86}
87
88#[derive(Debug)]
90pub struct Blockchain {
91 blockchain_path: String,
92 height: usize,
93 block_lookup: HashMap<Hash, usize>,
94 utxos: UTXOs,
95 difficulty_manager: DifficultyManager,
96}
97
98impl Blockchain {
99 pub fn new(blockchain_path: &str) -> Self {
101 let mut blockchain_path = blockchain_path.to_string();
102 if !blockchain_path.ends_with('/') {
103 blockchain_path.push('/');
104 }
105 blockchain_path.push_str("blockchain/");
106
107 if !Path::new(&blockchain_path).exists() {
108 fs::create_dir_all(format!("{}blocks/", &blockchain_path)).unwrap();
109 }
110
111 match Self::load_cache(&blockchain_path) {
112 Ok(cache) => {
113 return Blockchain {
114 blockchain_path,
115 height: cache.height,
116 block_lookup: cache.block_lookup,
117 utxos: cache.utxos,
118 difficulty_manager: cache.difficulty_manager,
119 };
120 }
121 Err(_) => {
122 return Blockchain {
123 blockchain_path,
124 height: 0,
125 block_lookup: HashMap::new(),
126 utxos: UTXOs::new(),
127 difficulty_manager: DifficultyManager::new(
128 chrono::Utc::now().timestamp() as u64
129 ),
130 };
131 }
132 }
133 }
134
135 fn load_cache(blockchain_path: &str) -> Result<BlockchainData, BlockchainError> {
137 let mut file = File::open(format!("{}blockchain.dat", blockchain_path))
138 .map_err(|e| BlockchainError::Io(e.to_string()))?;
139 Ok(bincode::decode_from_std_read(&mut file, config::standard())
140 .map_err(|e| BlockchainError::BincodeDecode(e.to_string()))?)
141 }
142
143 fn save_cache(&self) -> Result<(), BlockchainError> {
145 let mut file = File::create(format!("{}blockchain.dat", self.blockchain_path))
146 .map_err(|e| BlockchainError::Io(e.to_string()))?;
147 let cache = BlockchainData {
148 difficulty_manager: self.difficulty_manager,
149 height: self.height,
150 block_lookup: self.block_lookup.clone(),
151 utxos: self.utxos.clone(),
152 };
153 file.sync_all()
154 .map_err(|e| BlockchainError::Io(e.to_string()))?;
155
156 bincode::encode_into_std_write(cache, &mut file, config::standard())
157 .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?;
158 Ok(())
159 }
160
161 fn blocks_dir(&self) -> String {
162 format!("{}blocks/", &self.blockchain_path)
163 }
164 fn block_path_by_height(&self, height: usize) -> String {
165 format!("{}{}.dat", self.blocks_dir(), height)
166 }
167 fn utxo_diffs_path_by_height(&self, height: usize) -> String {
168 format!("{}utxo-diffs-{}.dat", self.blocks_dir(), height)
169 }
170 fn block_path_by_hash(&self, hash: &Hash) -> String {
171 format!("{}{}.dat", self.blocks_dir(), self.block_lookup[hash])
172 }
173
174 pub fn add_block(&mut self, new_block: Block) -> Result<(), BlockchainError> {
177 let block_hash = new_block.hash.ok_or(BlockchainError::MissingHash)?;
178
179 if !block_hash.compare_with_data(
180 &new_block
181 .get_hashing_buf()
182 .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?,
183 ) {
184 return Err(BlockchainError::HashMismatch {
185 expected: Hash::new(
186 &new_block
187 .get_hashing_buf()
188 .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?,
189 )
190 .dump_base36(),
191 actual: block_hash.dump_base36(),
192 });
193 }
194
195 if new_block.timestamp > chrono::Utc::now().timestamp() as u64 {
196 return Err(BlockchainError::FutureTimestamp(new_block.timestamp));
197 }
198
199 if new_block.block_pow_difficulty != self.difficulty_manager.block_difficulty
200 || new_block.tx_pow_difficulty != self.difficulty_manager.transaction_difficulty
201 {
202 return Err(BlockchainError::InvalidDifficulty);
203 }
204
205 if self.get_height() == 0 && new_block.previous_block != GENESIS_PREVIOUS_BLOCK_HASH {
206 return Err(BlockchainError::InvalidPreviousBlockHash);
207 } else if self.get_height() != 0 && *self.get_block_hash_by_height(self.get_height() - 1).unwrap() != new_block.previous_block {
208 return Err(BlockchainError::InvalidPreviousBlockHash);
209 }
210
211 if BigUint::from_bytes_be(&*block_hash)
212 > BigUint::from_bytes_be(&calculate_block_difficulty(
213 &self.difficulty_manager.block_difficulty,
214 new_block.transactions.len(),
215 ))
216 {
217 return Err(BlockchainError::InvalidDifficulty);
218 }
219
220 let mut used_inputs: HashSet<(TransactionId, usize)> = HashSet::new();
221
222 let mut seen_reward_transaction = false;
223 for transaction in &new_block.transactions {
224 if !transaction.inputs.is_empty() {
225 self.utxos.validate_transaction(
226 transaction,
227 &BigUint::from_bytes_be(&self.difficulty_manager.transaction_difficulty),
228 )?;
229
230 for input in &transaction.inputs {
231 let key = (input.transaction_id.clone(), input.output_index);
232 if !used_inputs.insert(key.clone()) {
233 return Err(BlockchainError::DoubleSpend);
234 }
235 }
236
237 validate_transaction_timestamp_in_block(&transaction, &new_block)?;
238 } else {
239 if seen_reward_transaction {
240 return Err(BlockchainError::RewardOverspend);
241 }
242 seen_reward_transaction = true;
243
244 if transaction.outputs.len() < 2 {
245 return Err(BlockchainError::InvalidRewardTransaction);
246 }
247
248 if transaction.transaction_id.is_none() {
249 return Err(BlockchainError::RewardTransactionIdMissing);
250 }
251
252 if transaction
253 .outputs
254 .iter()
255 .fold(0, |acc, output| acc + output.amount)
256 != get_block_reward(self.height)
257 {
258 return Err(BlockchainError::InvalidRewardTransactionAmount);
259 }
260
261 let mut has_dev_fee = false;
262 for output in &transaction.outputs {
263 if output.receiver == DEV_WALLET
264 && output.amount == calculate_dev_fee(get_block_reward(self.height))
265 {
266 has_dev_fee = true;
267 break;
268 }
269 }
270
271 if !has_dev_fee {
272 return Err(BlockchainError::NoDevFee);
273 }
274 }
275 }
276
277 let mut utxo_diffs = UTXODiff::new_empty();
278
279 for transaction in &new_block.transactions {
280 utxo_diffs.extend(&mut self.utxos.execute_transaction(transaction));
281 }
282
283 self.difficulty_manager.update_difficulty(&new_block);
284 self.block_lookup.insert(block_hash, self.height);
285
286 let mut file = File::create(self.block_path_by_height(self.height))
288 .map_err(|e| BlockchainError::Io(e.to_string()))?;
289 bincode::encode_into_std_write(new_block, &mut file, config::standard())
290 .map_err(|e| BlockchainError::Io(e.to_string()))?;
291 file.sync_all()
292 .map_err(|e| BlockchainError::Io(e.to_string()))?;
293
294 let mut file = File::create(self.utxo_diffs_path_by_height(self.height))
296 .map_err(|e| BlockchainError::Io(e.to_string()))?;
297 bincode::encode_into_std_write(utxo_diffs, &mut file, config::standard())
298 .map_err(|e| BlockchainError::Io(e.to_string()))?;
299 file.sync_all()
300 .map_err(|e| BlockchainError::Io(e.to_string()))?;
301
302 self.height += 1;
303 self.save_cache()?;
304
305 Ok(())
306 }
307
308 pub fn pop_block(&mut self) -> Result<(), BlockchainError> {
310 if self.height == 0 {
311 return Err(BlockchainError::NoBlocksToPop);
312 }
313
314 let recalled_block = self
315 .get_block_by_height(self.height - 1)
316 .ok_or(BlockchainError::MissingHash)?;
317 let utxo_diffs = self
318 .get_utxo_diffs_by_height(self.height - 1)
319 .ok_or(BlockchainError::MissingHash)?;
320
321 self.utxos.recall_block_utxos(utxo_diffs);
323
324 fs::remove_file(self.block_path_by_height(self.height - 1))
326 .map_err(|e| BlockchainError::Io(e.to_string()))?;
327 fs::remove_file(self.utxo_diffs_path_by_height(self.height - 1))
328 .map_err(|e| BlockchainError::Io(e.to_string()))?;
329
330 self.height -= 1;
332
333 self.difficulty_manager.block_difficulty = recalled_block.block_pow_difficulty;
335 self.difficulty_manager.transaction_difficulty = recalled_block.tx_pow_difficulty;
336 if self.height > 0
337 && let Some(last_block) = self.get_block_by_height(self.height - 1)
338 {
339 self.difficulty_manager.last_timestamp = last_block.timestamp;
340 } else {
341 self.difficulty_manager.last_timestamp = recalled_block.timestamp;
342 }
343
344 self.block_lookup.remove(&recalled_block.hash.unwrap());
346 self.save_cache()?;
347
348 Ok(())
349 }
350
351 pub fn get_block_by_height(&self, height: usize) -> Option<Block> {
352 if height >= self.height {
353 return None;
354 }
355
356 let block_path = self.block_path_by_height(height);
357 let mut file = File::open(&block_path).ok()?;
358 bincode::decode_from_std_read(&mut file, config::standard()).ok()
359 }
360
361 pub fn get_utxo_diffs_by_height(&self, height: usize) -> Option<UTXODiff> {
362 if height >= self.height {
363 return None;
364 }
365
366 let diffs_path = self.utxo_diffs_path_by_height(height);
367 let mut file = File::open(&diffs_path).ok()?;
368 bincode::decode_from_std_read(&mut file, config::standard()).ok()
369 }
370
371 pub fn get_block_by_hash(&self, hash: &Hash) -> Option<Block> {
372 let block_path = self.block_path_by_hash(hash);
373 let mut file = File::open(&block_path).ok()?;
374 bincode::decode_from_std_read(&mut file, config::standard()).ok()
375 }
376
377 pub fn get_height_by_hash(&self, hash: &Hash) -> Option<usize> {
378 self.block_lookup.get(hash).copied()
379 }
380
381 pub fn get_block_hash_by_height(&self, height: usize) -> Option<&Hash> {
382 match self.block_lookup.iter().find(|x| x.1 == &height) {
383 Some(blt) => Some(blt.0),
384 None => None,
385 }
386 }
387
388 pub fn get_height(&self) -> usize {
389 self.height
390 }
391
392 pub fn get_utxos(&self) -> &UTXOs {
393 &self.utxos
394 }
395
396 pub fn get_difficulty_manager(&self) -> &DifficultyManager {
397 &self.difficulty_manager
398 }
399
400 pub fn get_transaction_difficulty(&self) -> [u8; 32] {
401 self.difficulty_manager.transaction_difficulty
402 }
403
404 pub fn get_block_difficulty(&self) -> [u8; 32] {
405 self.difficulty_manager.block_difficulty
406 }
407
408 pub fn get_all_blocks(&self) -> Vec<&Hash> {
409 self.block_lookup.keys().collect()
410 }
411}
412
413#[async_trait::async_trait]
414impl BlockchainDataProvider for Blockchain {
415 async fn get_height(
416 &self,
417 ) -> Result<usize, crate::blockchain_data_provider::BlockchainDataProviderError> {
418 Ok(self.get_height())
419 }
420
421 async fn get_reward(
422 &self,
423 ) -> Result<u64, crate::blockchain_data_provider::BlockchainDataProviderError> {
424 Ok(get_block_reward(self.get_height()))
425 }
426
427 async fn get_block_by_height(
428 &self,
429 height: usize,
430 ) -> Result<Option<Block>, crate::blockchain_data_provider::BlockchainDataProviderError> {
431 Ok(self.get_block_by_height(height))
432 }
433
434 async fn get_block_by_hash(
435 &self,
436 hash: &Hash,
437 ) -> Result<Option<Block>, crate::blockchain_data_provider::BlockchainDataProviderError> {
438 Ok(self.get_block_by_hash(hash))
439 }
440
441 async fn get_height_by_hash(
442 &self,
443 hash: &Hash,
444 ) -> Result<Option<usize>, crate::blockchain_data_provider::BlockchainDataProviderError> {
445 Ok(self.get_height_by_hash(hash))
446 }
447
448 async fn get_block_hash_by_height(
449 &self,
450 height: usize,
451 ) -> Result<Option<Hash>, crate::blockchain_data_provider::BlockchainDataProviderError> {
452 Ok(self.get_block_hash_by_height(height).copied())
453 }
454
455 async fn get_transaction_difficulty(
456 &self,
457 ) -> Result<[u8; 32], crate::blockchain_data_provider::BlockchainDataProviderError> {
458 Ok(self.get_transaction_difficulty())
459 }
460
461 async fn get_block_difficulty(
462 &self,
463 ) -> Result<[u8; 32], crate::blockchain_data_provider::BlockchainDataProviderError> {
464 Ok(self.get_block_difficulty())
465 }
466
467 async fn get_available_transaction_outputs(
468 &self,
469 address: crate::crypto::keys::Public,
470 ) -> Result<
471 Vec<(TransactionId, super::transaction::TransactionOutput, usize)>,
472 crate::blockchain_data_provider::BlockchainDataProviderError,
473 > {
474 let mut available_outputs = vec![];
475
476 for (transaction_id, outputs) in self.utxos.utxos.iter() {
477 for (output_index, output) in outputs.iter().enumerate() {
478 if let Some(output) = output
479 && output.receiver == address
480 {
481 available_outputs.push((*transaction_id, output.clone(), output_index));
482 }
483 }
484 }
485
486 Ok(available_outputs)
487 }
488}
489
490pub fn validate_transaction_timestamp_in_block(
492 transaction: &Transaction,
493 owning_block: &Block,
494) -> Result<(), BlockchainError> {
495 if transaction.timestamp > owning_block.timestamp {
496 return Err(BlockchainError::InvalidTimestamp);
497 }
498 if transaction.timestamp + EXPIRATION_TIME < owning_block.timestamp {
499 return Err(BlockchainError::InvalidTimestamp);
500 }
501
502 Ok(())
503}
504
505pub fn validate_transaction_timestamp(transaction: &Transaction) -> Result<(), BlockchainError> {
507 if transaction.timestamp > chrono::Utc::now().timestamp() as u64 {
508 return Err(BlockchainError::InvalidTimestamp);
509 }
510 if transaction.timestamp + EXPIRATION_TIME < chrono::Utc::now().timestamp() as u64 {
511 return Err(BlockchainError::InvalidTimestamp);
512 }
513
514 Ok(())
515}
516
517pub fn validate_block_timestamp(block: &Block) -> Result<(), BlockchainError> {
519 if block.timestamp > chrono::Utc::now().timestamp() as u64 {
520 return Err(BlockchainError::InvalidTimestamp);
521 }
522 if block.timestamp + EXPIRATION_TIME < chrono::Utc::now().timestamp() as u64 {
523 return Err(BlockchainError::InvalidTimestamp);
524 }
525
526 Ok(())
527}