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