Skip to main content

snap_coin/core/
block.rs

1use bincode::{Decode, Encode, error::EncodeError};
2use num_bigint::BigUint;
3use rand::Rng;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::{
8    core::{difficulty::calculate_block_difficulty, transaction::Transaction},
9    crypto::{Hash, address_inclusion_filter::{AddressInclusionFilter, AddressInclusionFilterError}, merkle_tree::MerkleTree},
10};
11
12pub const MAX_TRANSACTIONS_PER_BLOCK: usize = 500;
13
14#[derive(Error, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
15pub enum BlockError {
16    #[error("Block is missing required metadata")]
17    IncompleteBlock,
18
19    #[error("Encoding error")]
20    EncodeError,
21
22    #[error("Block hash is invalid")]
23    InvalidBlockHash,
24
25    #[error("Block difficulties don't match real difficulties")]
26    DifficultyMismatch,
27
28    #[error("Block pow difficulty is not up to target")]
29    BlockPowDifficultyIncorrect,
30
31    #[error("Transaction is incomplete")]
32    IncompleteTransaction,
33
34    #[error("Merkle root tree is invalid")]
35    InvalidMerkleTreeRoot,
36
37    #[error("Address inclusion filter error: {0}")]
38    AddressInclusionFilter(#[from] AddressInclusionFilterError),
39
40    #[error("Address inclusion filter is incorrect")]
41    IncorrectAddressInclusionFilter,
42}
43
44/// Stores transaction, difficulties, its hash, and its nonce
45/// The hash can be often used for indexing, however can only be trusted if this node checked this block already
46#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug)]
47pub struct Block {
48    pub transactions: Vec<Transaction>,
49    pub timestamp: u64,
50    pub nonce: u64,
51    pub meta: BlockMetadata,
52}
53
54impl Block {
55    /// Create a new block timestamped now, with a set of transactions, specifying transaction difficulty and block difficulty
56    pub fn new_block_now(
57        transactions: Vec<Transaction>,
58        block_pow_difficulty: &[u8; 32],
59        tx_pow_difficulty: &[u8; 32],
60        previous_block: Hash,
61        merkle_tree_root: &[u8; 32],
62        address_inclusion_filter: AddressInclusionFilter,
63    ) -> Self {
64        Block {
65            transactions,
66            timestamp: chrono::Utc::now().timestamp() as u64,
67            nonce: 0,
68            meta: BlockMetadata {
69                block_pow_difficulty: *block_pow_difficulty,
70                tx_pow_difficulty: *tx_pow_difficulty,
71                previous_block,
72                hash: None,
73                merkle_tree_root: *merkle_tree_root,
74                address_inclusion_filter,
75            },
76        }
77    }
78
79    /// Get this blocks hashing buffer required to mine this transaction. Essentially makes sure that any hash attached to this block is not included in the block hashing buffer
80    /// WARNING: Slow
81    pub fn get_hashing_buf(&self) -> Result<Vec<u8>, EncodeError> {
82        let mut hash_less_block = self.clone();
83        hash_less_block.meta.hash = None; // Remove hash
84
85        // Remove all transaction inputs because, we can just hash the transaction hash and keep the integrity
86        for transaction in &mut hash_less_block.transactions {
87            transaction.inputs = vec![];
88            transaction.outputs = vec![];
89        }
90        bincode::encode_to_vec(hash_less_block, bincode::config::standard())
91    }
92
93    /// Mine this block and attach its hash.
94    /// DEPRECATED: This is single threaded and cannot be used for actual mining as proper, multi-threaded mining machines outperform this by absolute miles
95    #[deprecated]
96    pub fn compute_pow(&mut self) -> Result<(), EncodeError> {
97        let tx_difficulty_big_int = BigUint::from_bytes_be(&calculate_block_difficulty(
98            &self.meta.block_pow_difficulty,
99            self.transactions.len(),
100        ));
101        let mut rng: rand::prelude::ThreadRng = rand::rng();
102        loop {
103            self.nonce = rng.random();
104            let hashing_buf = self.get_hashing_buf()?;
105            if BigUint::from_bytes_be(&*Hash::new(&hashing_buf)) <= tx_difficulty_big_int {
106                self.meta.hash = Some(Hash::new(&hashing_buf));
107                return Ok(());
108            }
109        }
110    }
111
112    /// Checks if block meta is valid
113    pub fn check_meta(&self) -> Result<(), BlockError> {
114        self.check_completeness()?;
115        self.validate_block_hash()?;
116        self.validate_address_inclusion_filter()?;
117        self.validate_merkle_tree()?;
118        Ok(())
119    }
120
121    /// Checks if this block is complete, and has all required fields to be valid on a blockchain
122    pub fn check_completeness(&self) -> Result<(), BlockError> {
123        self.meta
124            .hash
125            .ok_or(BlockError::IncompleteBlock)
126            .map(|_| ())?;
127        Ok(())
128    }
129
130    /// Checks if the attached block hash is valid
131    pub fn validate_block_hash(&self) -> Result<(), BlockError> {
132        self.check_completeness()?;
133        if !self
134            .meta
135            .hash
136            .ok_or(BlockError::IncompleteBlock)?
137            .compare_with_data(
138                &self
139                    .get_hashing_buf()
140                    .map_err(|_| BlockError::EncodeError)?,
141            )
142        {
143            return Err(BlockError::InvalidBlockHash);
144        }
145        Ok(())
146    }
147
148    /// Checks if the passed difficulties match the blocks difficulties (true = valid, false = invalid)
149    pub fn validate_difficulties(
150        &self,
151        real_block_pow_difficulty: &[u8; 32],
152        real_tx_pow_difficulty: &[u8; 32],
153    ) -> Result<(), BlockError> {
154        if self.meta.block_pow_difficulty != *real_block_pow_difficulty
155            || self.meta.tx_pow_difficulty != *real_tx_pow_difficulty
156        {
157            return Err(BlockError::DifficultyMismatch);
158        }
159        if BigUint::from_bytes_be(&*self.meta.hash.unwrap())
160            > BigUint::from_bytes_be(&calculate_block_difficulty(
161                real_block_pow_difficulty,
162                self.transactions.len(),
163            ))
164        {
165            return Err(BlockError::BlockPowDifficultyIncorrect);
166        }
167        Ok(())
168    }
169
170    /// Check if merkle tree root is correctly calculated
171    pub fn validate_merkle_tree(&self) -> Result<(), BlockError> {
172        let mut ids = vec![];
173        for tx in &self.transactions {
174            tx.check_completeness()
175                .map_err(|_| BlockError::IncompleteTransaction)?;
176            ids.push(tx.transaction_id.unwrap());
177        }
178        let tree = MerkleTree::build(&ids);
179        if tree.root_hash() != self.meta.merkle_tree_root {
180            return Err(BlockError::InvalidMerkleTreeRoot);
181        }
182        Ok(())
183    }
184
185    /// Check if inclusion filter is correctly calculated
186    pub fn validate_address_inclusion_filter(&self) -> Result<(), BlockError> {
187        if AddressInclusionFilter::create_filter(&self.transactions)?
188            != self.meta.address_inclusion_filter
189        {
190            return Err(BlockError::IncorrectAddressInclusionFilter);
191        }
192        Ok(())
193    }
194
195    pub fn address_count(&self) -> usize {
196        self.transactions
197            .iter()
198            .fold(0, |acc, tx| acc + tx.address_count())
199    }
200}
201
202// Represents all block data that is not essential to it's existence (however required)
203#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug)]
204pub struct BlockMetadata {
205    pub block_pow_difficulty: [u8; 32],
206    pub tx_pow_difficulty: [u8; 32],
207    pub previous_block: Hash,
208    pub hash: Option<Hash>,
209    pub merkle_tree_root: [u8; 32],
210    pub address_inclusion_filter: AddressInclusionFilter,
211}