Browse Source

validator: permanently store ranks as blockchain expands

skoupidi 2 years ago
parent
commit
b70ade1922

+ 30 - 13
bin/darkfid/src/tests/forks.rs

@@ -17,7 +17,7 @@
  */
  */
 
 
 use darkfi::{
 use darkfi::{
-    blockchain::Blockchain,
+    blockchain::{BlockInfo, Blockchain},
     validator::{consensus::Fork, pow::PoWModule},
     validator::{consensus::Fork, pow::PoWModule},
     Result,
     Result,
 };
 };
@@ -26,38 +26,55 @@ use darkfi::{
 fn forks() -> Result<()> {
 fn forks() -> Result<()> {
     smol::block_on(async {
     smol::block_on(async {
         // Dummy records we will insert
         // Dummy records we will insert
-        let record0 = blake3::hash(b"Let there be dark!");
-        let record1 = blake3::hash(b"Never skip brain day.");
+        let record1 = blake3::hash(b"Let there be dark!");
+        let record2 = blake3::hash(b"Never skip brain day.");
 
 
         // Create a temporary blockchain and a PoW module
         // Create a temporary blockchain and a PoW module
         let blockchain = Blockchain::new(&sled::Config::new().temporary(true).open()?)?;
         let blockchain = Blockchain::new(&sled::Config::new().temporary(true).open()?)?;
         let module = PoWModule::new(blockchain.clone(), 90, None)?;
         let module = PoWModule::new(blockchain.clone(), 90, None)?;
 
 
+        // Generate and insert default genesis block
+        let genesis_block = BlockInfo::default();
+        blockchain.add_block(&genesis_block)?;
+        let genesis_block_hash = genesis_block.hash()?;
+
         // Create a fork
         // Create a fork
         let fork = Fork::new(blockchain.clone(), module).await?;
         let fork = Fork::new(blockchain.clone(), module).await?;
 
 
         // Add a dummy record to fork
         // Add a dummy record to fork
-        fork.overlay.lock().unwrap().order.insert(&[0], &[record0])?;
+        fork.overlay.lock().unwrap().order.insert(&[1], &[record1])?;
 
 
         // Verify blockchain doesn't contain the record
         // Verify blockchain doesn't contain the record
-        assert_eq!(blockchain.order.get(&[0], false)?, [None]);
-        assert_eq!(fork.overlay.lock().unwrap().order.get(&[0], true)?, [Some(record0)]);
+        assert_eq!(blockchain.order.get(&[0, 1], false)?, [Some(genesis_block_hash), None]);
+        assert_eq!(
+            fork.overlay.lock().unwrap().order.get(&[0, 1], true)?,
+            [Some(genesis_block_hash), Some(record1)]
+        );
 
 
         // Now we are going to clone the fork
         // Now we are going to clone the fork
         let fork_clone = fork.full_clone()?;
         let fork_clone = fork.full_clone()?;
 
 
-        // Verify it cointains the original record
-        assert_eq!(fork_clone.overlay.lock().unwrap().order.get(&[0], true)?, [Some(record0)]);
+        // Verify it contains the original records
+        assert_eq!(
+            fork_clone.overlay.lock().unwrap().order.get(&[0, 1], true)?,
+            [Some(genesis_block_hash), Some(record1)]
+        );
 
 
         // Add another dummy record to cloned fork
         // Add another dummy record to cloned fork
-        fork_clone.overlay.lock().unwrap().order.insert(&[1], &[record1])?;
+        fork_clone.overlay.lock().unwrap().order.insert(&[2], &[record2])?;
 
 
         // Verify blockchain and original fork don't contain the second record
         // Verify blockchain and original fork don't contain the second record
-        assert_eq!(blockchain.order.get(&[0, 1], false)?, [None, None]);
-        assert_eq!(fork.overlay.lock().unwrap().order.get(&[0, 1], false)?, [Some(record0), None]);
         assert_eq!(
         assert_eq!(
-            fork_clone.overlay.lock().unwrap().order.get(&[0, 1], true)?,
-            [Some(record0), Some(record1)]
+            blockchain.order.get(&[0, 1, 2], false)?,
+            [Some(genesis_block_hash), None, None]
+        );
+        assert_eq!(
+            fork.overlay.lock().unwrap().order.get(&[0, 1, 2], false)?,
+            [Some(genesis_block_hash), Some(record1), None]
+        );
+        assert_eq!(
+            fork_clone.overlay.lock().unwrap().order.get(&[0, 1, 2], true)?,
+            [Some(genesis_block_hash), Some(record1), Some(record2)]
         );
         );
 
 
         Ok(())
         Ok(())

+ 2 - 2
doc/src/arch/consensus.md

@@ -78,9 +78,9 @@ Proof of Work algorithm lowers the difficulty target as hashpower grows.
 This means that blocks will have to be mined for a lower target, therefore
 This means that blocks will have to be mined for a lower target, therefore
 rank higher, as they go further away from `MAX_INT`.
 rank higher, as they go further away from `MAX_INT`.
 
 
-Similar to blocks, forks rank is a tuple, with the first part being the
+Similar to blocks, blockchain/forks rank is a tuple, with the first part being the
 sum of its block's squared target distances, and the second being the sum of
 sum of its block's squared target distances, and the second being the sum of
-their squared hash distances Squared distances are used to disproportionately
+their squared hash distances. Squared distances are used to disproportionately
 favors smaller targets, with the idea being that it will be harder to trigger
 favors smaller targets, with the idea being that it will be harder to trigger
 a longer reorg between forks. When we compare forks, we first check the first
 a longer reorg between forks. When we compare forks, we first check the first
 sum, and if its tied, we use the second as the tie breaker, since we know it
 sum, and if its tied, we use the second as the tie breaker, since we know it

+ 82 - 2
src/blockchain/block_store.rs

@@ -490,6 +490,61 @@ impl BlockOrderStoreOverlay {
     }
     }
 }
 }
 
 
+/// Auxiliary structure used to keep track of block ranking information.
+/// Note: we only need height cummulative ranks, but we also keep its actual
+/// ranks, so we can verify the sequence and/or know specific block height
+/// ranks, if ever needed.
+#[derive(Debug)]
+pub struct BlockRanks {
+    /// Block target rank
+    pub target_rank: BigUint,
+    /// Height cummulative targets rank
+    pub targets_rank: BigUint,
+    /// Block hash rank
+    pub hash_rank: BigUint,
+    /// Height cummulative hashes rank
+    pub hashes_rank: BigUint,
+}
+
+impl BlockRanks {
+    pub fn new(
+        target_rank: BigUint,
+        targets_rank: BigUint,
+        hash_rank: BigUint,
+        hashes_rank: BigUint,
+    ) -> Self {
+        Self { target_rank, targets_rank, hash_rank, hashes_rank }
+    }
+}
+
+// Note: Doing all the imports here as this might get obselete if
+// we implemented Encodable/Decodable for num_bigint::BigUint.
+impl darkfi_serial::Encodable for BlockRanks {
+    fn encode<S: std::io::Write>(&self, mut s: S) -> std::io::Result<usize> {
+        let mut len = 0;
+        len += self.target_rank.to_bytes_be().encode(&mut s)?;
+        len += self.targets_rank.to_bytes_be().encode(&mut s)?;
+        len += self.hash_rank.to_bytes_be().encode(&mut s)?;
+        len += self.hashes_rank.to_bytes_be().encode(&mut s)?;
+        Ok(len)
+    }
+}
+
+impl darkfi_serial::Decodable for BlockRanks {
+    fn decode<D: std::io::Read>(mut d: D) -> std::io::Result<Self> {
+        let bytes: Vec<u8> = darkfi_serial::Decodable::decode(&mut d)?;
+        let target_rank: BigUint = BigUint::from_bytes_be(&bytes);
+        let bytes: Vec<u8> = darkfi_serial::Decodable::decode(&mut d)?;
+        let targets_rank: BigUint = BigUint::from_bytes_be(&bytes);
+        let bytes: Vec<u8> = darkfi_serial::Decodable::decode(&mut d)?;
+        let hash_rank: BigUint = BigUint::from_bytes_be(&bytes);
+        let bytes: Vec<u8> = darkfi_serial::Decodable::decode(&mut d)?;
+        let hashes_rank: BigUint = BigUint::from_bytes_be(&bytes);
+        let ret = Self { target_rank, targets_rank, hash_rank, hashes_rank };
+        Ok(ret)
+    }
+}
+
 /// Auxiliary structure used to keep track of block PoW difficulty information.
 /// Auxiliary structure used to keep track of block PoW difficulty information.
 /// Note: we only need height cummulative difficulty, but we also keep its actual
 /// Note: we only need height cummulative difficulty, but we also keep its actual
 /// difficulty, so we can verify the sequence and/or know specific block height
 /// difficulty, so we can verify the sequence and/or know specific block height
@@ -504,6 +559,8 @@ pub struct BlockDifficulty {
     pub difficulty: BigUint,
     pub difficulty: BigUint,
     /// Height cummulative difficulty (total + height difficulty)
     /// Height cummulative difficulty (total + height difficulty)
     pub cummulative_difficulty: BigUint,
     pub cummulative_difficulty: BigUint,
+    /// Block ranks
+    pub ranks: BlockRanks,
 }
 }
 
 
 impl BlockDifficulty {
 impl BlockDifficulty {
@@ -512,8 +569,20 @@ impl BlockDifficulty {
         timestamp: Timestamp,
         timestamp: Timestamp,
         difficulty: BigUint,
         difficulty: BigUint,
         cummulative_difficulty: BigUint,
         cummulative_difficulty: BigUint,
+        ranks: BlockRanks,
     ) -> Self {
     ) -> Self {
-        Self { height, timestamp, difficulty, cummulative_difficulty }
+        Self { height, timestamp, difficulty, cummulative_difficulty, ranks }
+    }
+
+    /// Represents the genesis block difficulty
+    pub fn genesis(timestamp: Timestamp) -> Self {
+        let ranks = BlockRanks::new(
+            BigUint::from(0u64),
+            BigUint::from(0u64),
+            BigUint::from(0u64),
+            BigUint::from(0u64),
+        );
+        BlockDifficulty::new(0, timestamp, BigUint::from(0u64), BigUint::from(0u64), ranks)
     }
     }
 }
 }
 
 
@@ -526,6 +595,7 @@ impl darkfi_serial::Encodable for BlockDifficulty {
         len += self.timestamp.encode(&mut s)?;
         len += self.timestamp.encode(&mut s)?;
         len += self.difficulty.to_bytes_be().encode(&mut s)?;
         len += self.difficulty.to_bytes_be().encode(&mut s)?;
         len += self.cummulative_difficulty.to_bytes_be().encode(&mut s)?;
         len += self.cummulative_difficulty.to_bytes_be().encode(&mut s)?;
+        len += self.ranks.encode(&mut s)?;
         Ok(len)
         Ok(len)
     }
     }
 }
 }
@@ -538,7 +608,8 @@ impl darkfi_serial::Decodable for BlockDifficulty {
         let difficulty: BigUint = BigUint::from_bytes_be(&bytes);
         let difficulty: BigUint = BigUint::from_bytes_be(&bytes);
         let bytes: Vec<u8> = darkfi_serial::Decodable::decode(&mut d)?;
         let bytes: Vec<u8> = darkfi_serial::Decodable::decode(&mut d)?;
         let cummulative_difficulty: BigUint = BigUint::from_bytes_be(&bytes);
         let cummulative_difficulty: BigUint = BigUint::from_bytes_be(&bytes);
-        let ret = Self { height, timestamp, difficulty, cummulative_difficulty };
+        let ranks: BlockRanks = darkfi_serial::Decodable::decode(&mut d)?;
+        let ret = Self { height, timestamp, difficulty, cummulative_difficulty, ranks };
         Ok(ret)
         Ok(ret)
     }
     }
 }
 }
@@ -605,6 +676,15 @@ impl BlockDifficultyStore {
         Ok(ret)
         Ok(ret)
     }
     }
 
 
+    /// Fetch the last record in the tree, based on the `Ord`
+    /// implementation for `Vec<u8>`. If the tree is empty,
+    /// returns `None`.
+    pub fn get_last(&self) -> Result<Option<BlockDifficulty>> {
+        let Some(found) = self.0.last()? else { return Ok(None) };
+        let block_difficulty = deserialize(&found.1)?;
+        Ok(Some(block_difficulty))
+    }
+
     /// Fetch the last N records from the block difficulties store, in order.
     /// Fetch the last N records from the block difficulties store, in order.
     pub fn get_last_n(&self, n: usize) -> Result<Vec<BlockDifficulty>> {
     pub fn get_last_n(&self, n: usize) -> Result<Vec<BlockDifficulty>> {
         // Build an iterator to retrieve last N records
         // Build an iterator to retrieve last N records

+ 19 - 2
src/blockchain/mod.rs

@@ -28,8 +28,8 @@ use crate::{tx::Transaction, Error, Result};
 /// Block related definitions and storage implementations
 /// Block related definitions and storage implementations
 pub mod block_store;
 pub mod block_store;
 pub use block_store::{
 pub use block_store::{
-    Block, BlockDifficultyStore, BlockDifficultyStoreOverlay, BlockInfo, BlockOrderStore,
-    BlockOrderStoreOverlay, BlockStore, BlockStoreOverlay,
+    Block, BlockDifficulty, BlockDifficultyStore, BlockDifficultyStoreOverlay, BlockInfo,
+    BlockOrderStore, BlockOrderStoreOverlay, BlockStore, BlockStoreOverlay,
 };
 };
 
 
 /// Header definition and storage implementation
 /// Header definition and storage implementation
@@ -223,6 +223,12 @@ impl Blockchain {
         self.order.get_first()
         self.order.get_first()
     }
     }
 
 
+    /// Retrieve genesis (first) block info.
+    pub fn genesis_block(&self) -> Result<BlockInfo> {
+        let (_, hash) = self.genesis()?;
+        Ok(self.get_blocks_by_hash(&[hash])?[0].clone())
+    }
+
     /// Retrieve the last block height and hash.
     /// Retrieve the last block height and hash.
     pub fn last(&self) -> Result<(u64, blake3::Hash)> {
     pub fn last(&self) -> Result<(u64, blake3::Hash)> {
         self.order.get_last()
         self.order.get_last()
@@ -234,6 +240,17 @@ impl Blockchain {
         Ok(self.get_blocks_by_hash(&[hash])?[0].clone())
         Ok(self.get_blocks_by_hash(&[hash])?[0].clone())
     }
     }
 
 
+    /// Retrieve the last block difficulty. If the tree is empty,
+    /// returns `BlockDifficulty::genesis` difficulty.
+    pub fn last_block_difficulty(&self) -> Result<BlockDifficulty> {
+        if let Some(found) = self.difficulties.get_last()? {
+            return Ok(found)
+        }
+
+        let genesis_block = self.genesis_block()?;
+        Ok(BlockDifficulty::genesis(genesis_block.header.timestamp))
+    }
+
     /// Check if block order for the given height is in the database.
     /// Check if block order for the given height is in the database.
     pub fn has_height(&self, height: u64) -> Result<bool> {
     pub fn has_height(&self, height: u64) -> Result<bool> {
         let vec = match self.order.get(&[height], true) {
         let vec = match self.order.get(&[height], true) {

+ 19 - 8
src/validator/consensus.rs

@@ -27,8 +27,8 @@ use smol::lock::RwLock;
 
 
 use crate::{
 use crate::{
     blockchain::{
     blockchain::{
-        block_store::BlockDifficulty, BlockInfo, Blockchain, BlockchainOverlay,
-        BlockchainOverlayPtr, Header,
+        block_store::{BlockDifficulty, BlockRanks},
+        BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr, Header,
     },
     },
     tx::Transaction,
     tx::Transaction,
     util::time::Timestamp,
     util::time::Timestamp,
@@ -478,6 +478,10 @@ impl Fork {
         let mempool =
         let mempool =
             blockchain.get_pending_txs()?.iter().map(|tx| blake3::hash(&serialize(tx))).collect();
             blockchain.get_pending_txs()?.iter().map(|tx| blake3::hash(&serialize(tx))).collect();
         let overlay = BlockchainOverlay::new(&blockchain)?;
         let overlay = BlockchainOverlay::new(&blockchain)?;
+        // Retrieve last block difficulty to access current ranks
+        let last_difficulty = blockchain.last_block_difficulty()?;
+        let targets_rank = last_difficulty.ranks.targets_rank;
+        let hashes_rank = last_difficulty.ranks.hashes_rank;
         Ok(Self {
         Ok(Self {
             blockchain,
             blockchain,
             overlay,
             overlay,
@@ -485,8 +489,8 @@ impl Fork {
             proposals: vec![],
             proposals: vec![],
             diffs: vec![],
             diffs: vec![],
             mempool,
             mempool,
-            targets_rank: BigUint::from(0u64),
-            hashes_rank: BigUint::from(0u64),
+            targets_rank,
+            hashes_rank,
         })
         })
     }
     }
 
 
@@ -542,21 +546,28 @@ impl Fork {
         // Calculate block rank
         // Calculate block rank
         let (target_distance_sq, hash_distance_sq) = block_rank(&proposal.block, &next_target)?;
         let (target_distance_sq, hash_distance_sq) = block_rank(&proposal.block, &next_target)?;
 
 
+        // Update fork ranks
+        self.targets_rank += target_distance_sq.clone();
+        self.hashes_rank += hash_distance_sq.clone();
+
         // Generate block difficulty and update PoW module
         // Generate block difficulty and update PoW module
         let cummulative_difficulty =
         let cummulative_difficulty =
             self.module.cummulative_difficulty.clone() + next_difficulty.clone();
             self.module.cummulative_difficulty.clone() + next_difficulty.clone();
+        let ranks = BlockRanks::new(
+            target_distance_sq,
+            self.targets_rank.clone(),
+            hash_distance_sq,
+            self.hashes_rank.clone(),
+        );
         let block_difficulty = BlockDifficulty::new(
         let block_difficulty = BlockDifficulty::new(
             proposal.block.header.height,
             proposal.block.header.height,
             proposal.block.header.timestamp,
             proposal.block.header.timestamp,
             next_difficulty,
             next_difficulty,
             cummulative_difficulty,
             cummulative_difficulty,
+            ranks,
         );
         );
         self.module.append_difficulty(&self.overlay, block_difficulty)?;
         self.module.append_difficulty(&self.overlay, block_difficulty)?;
 
 
-        // Update fork ranks
-        self.targets_rank += target_distance_sq;
-        self.hashes_rank += hash_distance_sq;
-
         // Push proposal's hash
         // Push proposal's hash
         self.proposals.push(proposal.hash);
         self.proposals.push(proposal.hash);
 
 

+ 28 - 6
src/validator/mod.rs

@@ -26,7 +26,7 @@ use smol::lock::RwLock;
 
 
 use crate::{
 use crate::{
     blockchain::{
     blockchain::{
-        block_store::{BlockDifficulty, BlockInfo},
+        block_store::{BlockDifficulty, BlockInfo, BlockRanks},
         Blockchain, BlockchainOverlay,
         Blockchain, BlockchainOverlay,
     },
     },
     error::TxVerifyFailed,
     error::TxVerifyFailed,
@@ -54,7 +54,7 @@ pub mod fees;
 
 
 /// Helper utilities
 /// Helper utilities
 pub mod utils;
 pub mod utils;
-use utils::deploy_native_contracts;
+use utils::{block_rank, deploy_native_contracts};
 
 
 /// Configuration for initializing [`Validator`]
 /// Configuration for initializing [`Validator`]
 #[derive(Clone)]
 #[derive(Clone)]
@@ -400,6 +400,11 @@ impl Validator {
         // Retrieve last block
         // Retrieve last block
         let mut previous = &overlay.lock().unwrap().last_block()?;
         let mut previous = &overlay.lock().unwrap().last_block()?;
 
 
+        // Retrieve last block difficulty to access current ranks
+        let last_difficulty = self.blockchain.last_block_difficulty()?;
+        let mut current_targets_rank = last_difficulty.ranks.targets_rank;
+        let mut current_hashes_rank = last_difficulty.ranks.hashes_rank;
+
         // Grab current PoW module to validate each block
         // Grab current PoW module to validate each block
         let mut module = self.consensus.module.read().await.clone();
         let mut module = self.consensus.module.read().await.clone();
 
 
@@ -421,14 +426,31 @@ impl Validator {
                 return Err(Error::BlockIsInvalid(block.hash()?.to_string()))
                 return Err(Error::BlockIsInvalid(block.hash()?.to_string()))
             };
             };
 
 
-            // Generate block difficulty
-            let difficulty = module.next_difficulty()?;
-            let cummulative_difficulty = module.cummulative_difficulty.clone() + difficulty.clone();
+            // Grab next mine target and difficulty
+            let (next_target, next_difficulty) = module.next_mine_target_and_difficulty()?;
+
+            // Calculate block rank
+            let (target_distance_sq, hash_distance_sq) = block_rank(block, &next_target)?;
+
+            // Update current ranks
+            current_targets_rank += target_distance_sq.clone();
+            current_hashes_rank += hash_distance_sq.clone();
+
+            // Generate block difficulty and update PoW module
+            let cummulative_difficulty =
+                module.cummulative_difficulty.clone() + next_difficulty.clone();
+            let ranks = BlockRanks::new(
+                target_distance_sq,
+                current_targets_rank.clone(),
+                hash_distance_sq,
+                current_hashes_rank.clone(),
+            );
             let block_difficulty = BlockDifficulty::new(
             let block_difficulty = BlockDifficulty::new(
                 block.header.height,
                 block.header.height,
                 block.header.timestamp,
                 block.header.timestamp,
-                difficulty,
+                next_difficulty,
                 cummulative_difficulty,
                 cummulative_difficulty,
+                ranks,
             );
             );
             module.append_difficulty(&overlay, block_difficulty)?;
             module.append_difficulty(&overlay, block_difficulty)?;