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, MAX_TRANSACTIONS};
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 #[error("Block has too many transactions")]
73 TooManyTransactions,
74}
75
76impl From<TransactionError> for BlockchainError {
77 fn from(err: TransactionError) -> Self {
78 BlockchainError::InvalidTransaction(err.to_string())
79 }
80}
81
82#[derive(Encode, Decode, Debug, Clone)]
84struct BlockchainData {
85 difficulty_manager: DifficultyManager,
86 height: usize,
87 block_lookup: HashMap<Hash, usize>,
88 utxos: UTXOs,
89}
90
91#[derive(Debug)]
93pub struct Blockchain {
94 blockchain_path: String,
95 height: usize,
96 block_lookup: HashMap<Hash, usize>,
97 utxos: UTXOs,
98 difficulty_manager: DifficultyManager,
99}
100
101impl Blockchain {
102 pub fn new(blockchain_path: &str) -> Self {
104 let mut blockchain_path = blockchain_path.to_string();
105 if !blockchain_path.ends_with('/') {
106 blockchain_path.push('/');
107 }
108 blockchain_path.push_str("blockchain/");
109
110 if !Path::new(&blockchain_path).exists() {
111 fs::create_dir_all(format!("{}blocks/", &blockchain_path)).unwrap();
112 }
113
114 match Self::load_cache(&blockchain_path) {
115 Ok(cache) => {
116 return Blockchain {
117 blockchain_path,
118 height: cache.height,
119 block_lookup: cache.block_lookup,
120 utxos: cache.utxos,
121 difficulty_manager: cache.difficulty_manager,
122 };
123 }
124 Err(_) => {
125 return Blockchain {
126 blockchain_path,
127 height: 0,
128 block_lookup: HashMap::new(),
129 utxos: UTXOs::new(),
130 difficulty_manager: DifficultyManager::new(
131 chrono::Utc::now().timestamp() as u64
132 ),
133 };
134 }
135 }
136 }
137
138 fn load_cache(blockchain_path: &str) -> Result<BlockchainData, BlockchainError> {
140 let mut file = File::open(format!("{}blockchain.dat", blockchain_path))
141 .map_err(|e| BlockchainError::Io(e.to_string()))?;
142 Ok(bincode::decode_from_std_read(&mut file, config::standard())
143 .map_err(|e| BlockchainError::BincodeDecode(e.to_string()))?)
144 }
145
146 fn save_cache(&self) -> Result<(), BlockchainError> {
148 let mut file = File::create(format!("{}blockchain.dat", self.blockchain_path))
149 .map_err(|e| BlockchainError::Io(e.to_string()))?;
150 let cache = BlockchainData {
151 difficulty_manager: self.difficulty_manager,
152 height: self.height,
153 block_lookup: self.block_lookup.clone(),
154 utxos: self.utxos.clone(),
155 };
156 file.sync_all()
157 .map_err(|e| BlockchainError::Io(e.to_string()))?;
158
159 bincode::encode_into_std_write(cache, &mut file, config::standard())
160 .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?;
161 Ok(())
162 }
163
164 fn blocks_dir(&self) -> String {
165 format!("{}blocks/", &self.blockchain_path)
166 }
167 fn block_path_by_height(&self, height: usize) -> String {
168 format!("{}{}.dat", self.blocks_dir(), height)
169 }
170 fn utxo_diffs_path_by_height(&self, height: usize) -> String {
171 format!("{}utxo-diffs-{}.dat", self.blocks_dir(), height)
172 }
173 fn block_path_by_hash(&self, hash: &Hash) -> String {
174 format!("{}{}.dat", self.blocks_dir(), self.block_lookup[hash])
175 }
176
177 pub fn add_block(&mut self, new_block: Block) -> Result<(), BlockchainError> {
180 let block_hash = new_block.hash.ok_or(BlockchainError::MissingHash)?;
181
182 if !block_hash.compare_with_data(
183 &new_block
184 .get_hashing_buf()
185 .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?,
186 ) {
187 return Err(BlockchainError::HashMismatch {
188 expected: Hash::new(
189 &new_block
190 .get_hashing_buf()
191 .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?,
192 )
193 .dump_base36(),
194 actual: block_hash.dump_base36(),
195 });
196 }
197
198 if new_block.timestamp > chrono::Utc::now().timestamp() as u64 {
199 return Err(BlockchainError::FutureTimestamp(new_block.timestamp));
200 }
201
202 if new_block.block_pow_difficulty != self.difficulty_manager.block_difficulty
203 || new_block.tx_pow_difficulty != self.difficulty_manager.transaction_difficulty
204 {
205 return Err(BlockchainError::InvalidDifficulty);
206 }
207
208 if self.get_height() == 0 && new_block.previous_block != GENESIS_PREVIOUS_BLOCK_HASH {
209 return Err(BlockchainError::InvalidPreviousBlockHash);
210 } else if self.get_height() != 0
211 && *self
212 .get_block_hash_by_height(self.get_height() - 1)
213 .unwrap()
214 != new_block.previous_block
215 {
216 return Err(BlockchainError::InvalidPreviousBlockHash);
217 }
218
219 if BigUint::from_bytes_be(&*block_hash)
220 > BigUint::from_bytes_be(&calculate_block_difficulty(
221 &self.difficulty_manager.block_difficulty,
222 new_block.transactions.len(),
223 ))
224 {
225 return Err(BlockchainError::InvalidDifficulty);
226 }
227
228 if new_block.transactions.len() > MAX_TRANSACTIONS {
229 return Err(BlockchainError::TooManyTransactions);
230 }
231
232 let mut used_inputs: HashSet<(TransactionId, usize)> = HashSet::new();
233
234 let mut seen_reward_transaction = false;
235 for transaction in &new_block.transactions {
236 if !transaction.inputs.is_empty() {
237 self.utxos.validate_transaction(
238 transaction,
239 &BigUint::from_bytes_be(&self.difficulty_manager.transaction_difficulty),
240 )?;
241
242 for input in &transaction.inputs {
243 let key = (input.transaction_id.clone(), input.output_index);
244 if !used_inputs.insert(key.clone()) {
245 return Err(BlockchainError::DoubleSpend);
246 }
247 }
248
249 validate_transaction_timestamp_in_block(&transaction, &new_block)?;
250 } else {
251 if seen_reward_transaction {
252 return Err(BlockchainError::RewardOverspend);
253 }
254 seen_reward_transaction = true;
255
256 if transaction.outputs.len() < 2 {
257 return Err(BlockchainError::InvalidRewardTransaction);
258 }
259
260 if transaction.transaction_id.is_none() {
261 return Err(BlockchainError::RewardTransactionIdMissing);
262 }
263
264 if transaction
265 .outputs
266 .iter()
267 .fold(0, |acc, output| acc + output.amount)
268 != get_block_reward(self.height)
269 {
270 return Err(BlockchainError::InvalidRewardTransactionAmount);
271 }
272
273 let mut has_dev_fee = false;
274 for output in &transaction.outputs {
275 if output.receiver == DEV_WALLET
276 && output.amount == calculate_dev_fee(get_block_reward(self.height))
277 {
278 has_dev_fee = true;
279 break;
280 }
281 }
282
283 if !has_dev_fee {
284 return Err(BlockchainError::NoDevFee);
285 }
286 }
287 }
288
289 let mut utxo_diffs = UTXODiff::new_empty();
290
291 for transaction in &new_block.transactions {
292 utxo_diffs.extend(&mut self.utxos.execute_transaction(transaction));
293 }
294
295 self.difficulty_manager.update_difficulty(&new_block);
296 self.block_lookup.insert(block_hash, self.height);
297
298 let mut file = File::create(self.block_path_by_height(self.height))
300 .map_err(|e| BlockchainError::Io(e.to_string()))?;
301 bincode::encode_into_std_write(new_block, &mut file, config::standard())
302 .map_err(|e| BlockchainError::Io(e.to_string()))?;
303 file.sync_all()
304 .map_err(|e| BlockchainError::Io(e.to_string()))?;
305
306 let mut file = File::create(self.utxo_diffs_path_by_height(self.height))
308 .map_err(|e| BlockchainError::Io(e.to_string()))?;
309 bincode::encode_into_std_write(utxo_diffs, &mut file, config::standard())
310 .map_err(|e| BlockchainError::Io(e.to_string()))?;
311 file.sync_all()
312 .map_err(|e| BlockchainError::Io(e.to_string()))?;
313
314 self.height += 1;
315 self.save_cache()?;
316
317 Ok(())
318 }
319
320 pub fn pop_block(&mut self) -> Result<(), BlockchainError> {
322 if self.height == 0 {
323 return Err(BlockchainError::NoBlocksToPop);
324 }
325
326 let recalled_block = self
327 .get_block_by_height(self.height - 1)
328 .ok_or(BlockchainError::MissingHash)?;
329 let utxo_diffs = self
330 .get_utxo_diffs_by_height(self.height - 1)
331 .ok_or(BlockchainError::MissingHash)?;
332
333 self.utxos.recall_block_utxos(utxo_diffs);
335
336 fs::remove_file(self.block_path_by_height(self.height - 1))
338 .map_err(|e| BlockchainError::Io(e.to_string()))?;
339 fs::remove_file(self.utxo_diffs_path_by_height(self.height - 1))
340 .map_err(|e| BlockchainError::Io(e.to_string()))?;
341
342 self.height -= 1;
344
345 self.difficulty_manager.block_difficulty = recalled_block.block_pow_difficulty;
347 self.difficulty_manager.transaction_difficulty = recalled_block.tx_pow_difficulty;
348 if self.height > 0
349 && let Some(last_block) = self.get_block_by_height(self.height - 1)
350 {
351 self.difficulty_manager.last_timestamp = last_block.timestamp;
352 } else {
353 self.difficulty_manager.last_timestamp = recalled_block.timestamp;
354 }
355
356 self.block_lookup.remove(&recalled_block.hash.unwrap());
358 self.save_cache()?;
359
360 Ok(())
361 }
362
363 pub fn get_block_by_height(&self, height: usize) -> Option<Block> {
364 if height >= self.height {
365 return None;
366 }
367
368 let block_path = self.block_path_by_height(height);
369 let mut file = File::open(&block_path).ok()?;
370 bincode::decode_from_std_read(&mut file, config::standard()).ok()
371 }
372
373 pub fn get_utxo_diffs_by_height(&self, height: usize) -> Option<UTXODiff> {
374 if height >= self.height {
375 return None;
376 }
377
378 let diffs_path = self.utxo_diffs_path_by_height(height);
379 let mut file = File::open(&diffs_path).ok()?;
380 bincode::decode_from_std_read(&mut file, config::standard()).ok()
381 }
382
383 pub fn get_block_by_hash(&self, hash: &Hash) -> Option<Block> {
384 let block_path = self.block_path_by_hash(hash);
385 let mut file = File::open(&block_path).ok()?;
386 bincode::decode_from_std_read(&mut file, config::standard()).ok()
387 }
388
389 pub fn get_height_by_hash(&self, hash: &Hash) -> Option<usize> {
390 self.block_lookup.get(hash).copied()
391 }
392
393 pub fn get_block_hash_by_height(&self, height: usize) -> Option<&Hash> {
394 match self.block_lookup.iter().find(|x| x.1 == &height) {
395 Some(blt) => Some(blt.0),
396 None => None,
397 }
398 }
399
400 pub fn get_height(&self) -> usize {
401 self.height
402 }
403
404 pub fn get_utxos(&self) -> &UTXOs {
405 &self.utxos
406 }
407
408 pub fn get_difficulty_manager(&self) -> &DifficultyManager {
409 &self.difficulty_manager
410 }
411
412 pub fn get_transaction_difficulty(&self) -> [u8; 32] {
413 self.difficulty_manager.transaction_difficulty
414 }
415
416 pub fn get_block_difficulty(&self) -> [u8; 32] {
417 self.difficulty_manager.block_difficulty
418 }
419
420 pub fn get_all_blocks(&self) -> Vec<&Hash> {
421 self.block_lookup.keys().collect()
422 }
423}
424
425#[async_trait::async_trait]
426impl BlockchainDataProvider for Blockchain {
427 async fn get_height(
428 &self,
429 ) -> Result<usize, crate::blockchain_data_provider::BlockchainDataProviderError> {
430 Ok(self.get_height())
431 }
432
433 async fn get_reward(
434 &self,
435 ) -> Result<u64, crate::blockchain_data_provider::BlockchainDataProviderError> {
436 Ok(get_block_reward(self.get_height()))
437 }
438
439 async fn get_block_by_height(
440 &self,
441 height: usize,
442 ) -> Result<Option<Block>, crate::blockchain_data_provider::BlockchainDataProviderError> {
443 Ok(self.get_block_by_height(height))
444 }
445
446 async fn get_block_by_hash(
447 &self,
448 hash: &Hash,
449 ) -> Result<Option<Block>, crate::blockchain_data_provider::BlockchainDataProviderError> {
450 Ok(self.get_block_by_hash(hash))
451 }
452
453 async fn get_height_by_hash(
454 &self,
455 hash: &Hash,
456 ) -> Result<Option<usize>, crate::blockchain_data_provider::BlockchainDataProviderError> {
457 Ok(self.get_height_by_hash(hash))
458 }
459
460 async fn get_block_hash_by_height(
461 &self,
462 height: usize,
463 ) -> Result<Option<Hash>, crate::blockchain_data_provider::BlockchainDataProviderError> {
464 Ok(self.get_block_hash_by_height(height).copied())
465 }
466
467 async fn get_transaction_difficulty(
468 &self,
469 ) -> Result<[u8; 32], crate::blockchain_data_provider::BlockchainDataProviderError> {
470 Ok(self.get_transaction_difficulty())
471 }
472
473 async fn get_block_difficulty(
474 &self,
475 ) -> Result<[u8; 32], crate::blockchain_data_provider::BlockchainDataProviderError> {
476 Ok(self.get_block_difficulty())
477 }
478
479 async fn get_available_transaction_outputs(
480 &self,
481 address: crate::crypto::keys::Public,
482 ) -> Result<
483 Vec<(TransactionId, super::transaction::TransactionOutput, usize)>,
484 crate::blockchain_data_provider::BlockchainDataProviderError,
485 > {
486 let mut available_outputs = vec![];
487
488 for (transaction_id, outputs) in self.utxos.utxos.iter() {
489 for (output_index, output) in outputs.iter().enumerate() {
490 if let Some(output) = output
491 && output.receiver == address
492 {
493 available_outputs.push((*transaction_id, output.clone(), output_index));
494 }
495 }
496 }
497
498 Ok(available_outputs)
499 }
500}
501
502pub fn validate_transaction_timestamp_in_block(
504 transaction: &Transaction,
505 owning_block: &Block,
506) -> Result<(), BlockchainError> {
507 if transaction.timestamp > owning_block.timestamp {
508 return Err(BlockchainError::InvalidTimestamp);
509 }
510 if transaction.timestamp + EXPIRATION_TIME < owning_block.timestamp {
511 return Err(BlockchainError::InvalidTimestamp);
512 }
513
514 Ok(())
515}
516
517pub fn validate_transaction_timestamp(transaction: &Transaction) -> Result<(), BlockchainError> {
519 if transaction.timestamp - EXPIRATION_TIME > chrono::Utc::now().timestamp() as u64 {
520 return Err(BlockchainError::InvalidTimestamp);
521 }
522 if transaction.timestamp + EXPIRATION_TIME < chrono::Utc::now().timestamp() as u64 {
523 return Err(BlockchainError::InvalidTimestamp);
524 }
525
526 Ok(())
527}
528
529pub fn validate_block_timestamp(block: &Block) -> Result<(), BlockchainError> {
531 if block.timestamp - EXPIRATION_TIME > chrono::Utc::now().timestamp() as u64 {
532 return Err(BlockchainError::InvalidTimestamp);
533 }
534 if block.timestamp + EXPIRATION_TIME < chrono::Utc::now().timestamp() as u64 {
535 return Err(BlockchainError::InvalidTimestamp);
536 }
537
538 Ok(())
539}