Просмотр исходного кода

validator/blockchain: consolidate all blockchain structures validation at src/validator/validation.rs

aggstam 2 лет назад
Родитель
Сommit
b8e612138d

+ 1 - 69
src/blockchain/block_store.rs

@@ -28,7 +28,7 @@ use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
 
 use crate::{tx::Transaction, Error, Result};
 
-use super::{parse_record, parse_u64_key_record, validate_slot, Header, SledDbOverlayPtr};
+use super::{parse_record, parse_u64_key_record, Header, SledDbOverlayPtr};
 
 /// Block version number
 pub const BLOCK_VERSION: u8 = 1;
@@ -115,74 +115,6 @@ impl BlockInfo {
         let block: Block = self.clone().into();
         block.blockhash()
     }
-
-    /// A block is considered valid when the following rules apply:
-    ///     1. Parent hash is equal to the hash of the previous block
-    ///     2. Timestamp increments previous block timestamp
-    ///     3. Slot increments previous block slot
-    ///     4. Slots vector is not empty and all its slots are valid
-    ///     5. Slot is the same as the slots vector last slot id
-    /// Additional validity rules can be applied.
-    pub fn validate(&self, previous: &Self, expected_reward: u64) -> Result<()> {
-        let error = Err(Error::BlockIsInvalid(self.blockhash().to_string()));
-        let previous_hash = previous.blockhash();
-
-        // Check previous hash (1)
-        if self.header.previous != previous_hash {
-            return error
-        }
-
-        // Check timestamps are incremental (2)
-        if self.header.timestamp <= previous.header.timestamp {
-            return error
-        }
-
-        // Check slots are incremental (3)
-        if self.header.slot <= previous.header.slot {
-            return error
-        }
-
-        // Verify slots (4)
-        if self.slots.is_empty() {
-            return error
-        }
-
-        // Retrieve previous block last slot
-        let mut previous_slot = previous.slots.last().unwrap();
-
-        // Check if empty slots existed
-        if self.slots.len() > 1 {
-            // All slots exluding the last one must have reward value set to 0.
-            // Slots must already be in correct order (sorted by id).
-            for slot in &self.slots[..self.slots.len() - 1] {
-                validate_slot(
-                    slot,
-                    previous_slot,
-                    &previous_hash,
-                    &previous.header.previous,
-                    &previous.producer.eta,
-                    0,
-                )?;
-                previous_slot = slot;
-            }
-        }
-
-        validate_slot(
-            self.slots.last().unwrap(),
-            previous_slot,
-            &previous_hash,
-            &previous.header.previous,
-            &previous.producer.eta,
-            expected_reward,
-        )?;
-
-        // Check block slot is the last slot id (5)
-        if self.slots.last().unwrap().id != self.header.slot {
-            return error
-        }
-
-        Ok(())
-    }
 }
 
 impl From<BlockInfo> for Block {

+ 1 - 1
src/blockchain/mod.rs

@@ -39,7 +39,7 @@ pub use header_store::{Header, HeaderStore, HeaderStoreOverlay};
 
 /// Slots storage implementation
 pub mod slot_store;
-pub use slot_store::{validate_slot, SlotStore, SlotStoreOverlay};
+pub use slot_store::{SlotStore, SlotStoreOverlay};
 
 /// Transactions related storage implementations
 pub mod tx_store;

+ 1 - 69
src/blockchain/slot_store.rs

@@ -17,81 +17,13 @@
  */
 
 // [`Slot`] is defined in the sdk so contracts can use it
-use darkfi_sdk::{blockchain::Slot, pasta::pallas};
+use darkfi_sdk::blockchain::Slot;
 use darkfi_serial::{deserialize, serialize};
 
 use crate::{Error, Result};
 
 use super::{parse_u64_key_record, SledDbOverlayPtr};
 
-/// A slot is considered valid when the following rules apply:
-///     1. Id increments previous slot id
-///     2. Forks extend previous block hash
-///     3. Forks follow previous block sequence
-///     4. Slot total tokens represent the total network tokens
-///        up until this slot
-///     5. Slot previous error value correspond to previous slot one
-///     6. PID output for this slot is correct
-///     7. Slot last eta is the expected one
-///     8. Slot reward value is the expected one
-/// Additional validity rules can be applied.
-pub fn validate_slot(
-    slot: &Slot,
-    previous: &Slot,
-    previous_block_hash: &blake3::Hash,
-    previous_block_sequence: &blake3::Hash,
-    last_eta: &pallas::Base,
-    expected_reward: u64,
-) -> Result<()> {
-    let error = Err(Error::SlotIsInvalid(slot.id));
-
-    // Check slots are incremental (1)
-    if slot.id <= previous.id {
-        return error
-    }
-
-    // Check previous block hash (2)
-    if !slot.previous.last_hashes.contains(previous_block_hash) {
-        return error
-    }
-
-    // Check previous block sequence (3)
-    if !slot.previous.second_to_last_hashes.contains(previous_block_sequence) {
-        return error
-    }
-
-    // Check total tokens (4)
-    if slot.total_tokens != previous.total_tokens + previous.reward {
-        return error
-    }
-
-    // Check previous slot error (5)
-    if slot.previous.error != previous.pid.error {
-        return error
-    }
-
-    /* TODO: FIXME: blockchain should not depend on validator
-    // Check PID output for this slot (6)
-    if (slot.pid.f, slot.pid.error, slot.pid.sigma1, slot.pid.sigma2) !=
-        slot_pid_output(previous, slot.previous.producers)
-    {
-        return error
-    }
-    */
-
-    // Check reward is the expected one (7)
-    if &slot.last_eta != last_eta {
-        return error
-    }
-
-    // Check reward is the expected one (8)
-    if slot.reward != expected_reward {
-        return error
-    }
-
-    Ok(())
-}
-
 const SLED_SLOT_TREE: &[u8] = b"_slots";
 
 /// The `SlotStore` is a `sled` tree storing the blockhains' slots,

+ 3 - 0
src/validator/mod.rs

@@ -45,6 +45,9 @@ pub mod pid;
 pub mod verification;
 use verification::{verify_block, verify_genesis_block, verify_transactions};
 
+/// Validation functions
+pub mod validation;
+
 /// Helper utilities
 pub mod utils;
 use utils::deploy_native_contracts;

+ 177 - 0
src/validator/validation.rs

@@ -0,0 +1,177 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::{
+    blockchain::{expected_reward, Slot},
+    pasta::pallas,
+};
+
+use crate::{
+    blockchain::{BlockInfo, Blockchain},
+    validator::pid::slot_pid_output,
+    Error, Result,
+};
+
+/// A block is considered valid when the following rules apply:
+///     1. Parent hash is equal to the hash of the previous block
+///     2. Timestamp increments previous block timestamp
+///     3. Slot increments previous block slot
+///     4. Slots vector is not empty and all its slots are valid
+///     5. Slot is the same as the slots vector last slot id
+/// Additional validity rules can be applied.
+pub fn validate_block(block: &BlockInfo, previous: &BlockInfo, expected_reward: u64) -> Result<()> {
+    let error = Err(Error::BlockIsInvalid(block.blockhash().to_string()));
+    let previous_hash = previous.blockhash();
+
+    // Check previous hash (1)
+    if block.header.previous != previous_hash {
+        return error
+    }
+
+    // Check timestamps are incremental (2)
+    if block.header.timestamp <= previous.header.timestamp {
+        return error
+    }
+
+    // Check slots are incremental (3)
+    if block.header.slot <= previous.header.slot {
+        return error
+    }
+
+    // Verify slots (4)
+    if block.slots.is_empty() {
+        return error
+    }
+
+    // Retrieve previous block last slot
+    let mut previous_slot = previous.slots.last().unwrap();
+
+    // Check if empty slots existed
+    if block.slots.len() > 1 {
+        // All slots exluding the last one must have reward value set to 0.
+        // Slots must already be in correct order (sorted by id).
+        for slot in &block.slots[..block.slots.len() - 1] {
+            validate_slot(
+                slot,
+                previous_slot,
+                &previous_hash,
+                &previous.header.previous,
+                &previous.producer.eta,
+                0,
+            )?;
+            previous_slot = slot;
+        }
+    }
+
+    validate_slot(
+        block.slots.last().unwrap(),
+        previous_slot,
+        &previous_hash,
+        &previous.header.previous,
+        &previous.producer.eta,
+        expected_reward,
+    )?;
+
+    // Check block slot is the last slot id (5)
+    if block.slots.last().unwrap().id != block.header.slot {
+        return error
+    }
+
+    Ok(())
+}
+
+/// A slot is considered valid when the following rules apply:
+///     1. Id increments previous slot id
+///     2. Forks extend previous block hash
+///     3. Forks follow previous block sequence
+///     4. Slot total tokens represent the total network tokens
+///        up until this slot
+///     5. Slot previous error value correspond to previous slot one
+///     6. PID output for this slot is correct
+///     7. Slot last eta is the expected one
+///     8. Slot reward value is the expected one
+/// Additional validity rules can be applied.
+pub fn validate_slot(
+    slot: &Slot,
+    previous: &Slot,
+    previous_block_hash: &blake3::Hash,
+    previous_block_sequence: &blake3::Hash,
+    last_eta: &pallas::Base,
+    expected_reward: u64,
+) -> Result<()> {
+    let error = Err(Error::SlotIsInvalid(slot.id));
+
+    // Check slots are incremental (1)
+    if slot.id <= previous.id {
+        return error
+    }
+
+    // Check previous block hash (2)
+    if !slot.previous.last_hashes.contains(previous_block_hash) {
+        return error
+    }
+
+    // Check previous block sequence (3)
+    if !slot.previous.second_to_last_hashes.contains(previous_block_sequence) {
+        return error
+    }
+
+    // Check total tokens (4)
+    if slot.total_tokens != previous.total_tokens + previous.reward {
+        return error
+    }
+
+    // Check previous slot error (5)
+    if slot.previous.error != previous.pid.error {
+        return error
+    }
+
+    // Check PID output for this slot (6)
+    if (slot.pid.f, slot.pid.error, slot.pid.sigma1, slot.pid.sigma2) !=
+        slot_pid_output(previous, slot.previous.producers)
+    {
+        return error
+    }
+
+    // Check reward is the expected one (7)
+    if &slot.last_eta != last_eta {
+        return error
+    }
+
+    // Check reward is the expected one (8)
+    if slot.reward != expected_reward {
+        return error
+    }
+
+    Ok(())
+}
+
+/// A blockchain is considered valid, when every block is valid,
+/// based on validate_block checks.
+/// Be careful as this will try to load everything in memory.
+pub fn validate_blockchain(blockchain: &Blockchain) -> Result<()> {
+    // We use block order store here so we have all blocks in order
+    let blocks = blockchain.order.get_all()?;
+    for (index, block) in blocks[1..].iter().enumerate() {
+        let full_blocks = blockchain.get_blocks_by_hash(&[blocks[index].1, block.1])?;
+        let expected_reward = expected_reward(full_blocks[1].header.slot);
+        validate_block(&full_blocks[1], &full_blocks[0], expected_reward)?;
+    }
+
+    Ok(())
+}

+ 4 - 19
src/validator/verification.rs

@@ -19,7 +19,6 @@
 use std::{collections::HashMap, io::Cursor};
 
 use darkfi_sdk::{
-    blockchain::expected_reward,
     crypto::{PublicKey, CONSENSUS_CONTRACT_ID},
     pasta::pallas,
 };
@@ -27,11 +26,12 @@ use darkfi_serial::{Decodable, Encodable, WriteExt};
 use log::{debug, error, warn};
 
 use crate::{
-    blockchain::{BlockInfo, Blockchain, BlockchainOverlayPtr},
+    blockchain::{BlockInfo, BlockchainOverlayPtr},
     error::TxVerifyFailed,
     runtime::vm_runtime::Runtime,
     tx::Transaction,
     util::time::TimeKeeper,
+    validator::validation::validate_block,
     zk::VerifyingKey,
     Error, Result,
 };
@@ -119,7 +119,7 @@ pub async fn verify_block(
     }
 
     // Validate block, using its previous
-    block.validate(previous, expected_reward)?;
+    validate_block(block, previous, expected_reward)?;
 
     // Validate proposal transaction if not in testing mode
     if !testing_mode {
@@ -222,7 +222,7 @@ pub async fn verify_transaction(
         // Decode the metadata retrieved from the execution
         let mut decoder = Cursor::new(&metadata);
 
-        // The tuple is (zkasa_ns, public_inputs)
+        // The tuple is (zkas_ns, public_inputs)
         let zkp_pub: Vec<(String, Vec<pallas::Base>)> = Decodable::decode(&mut decoder)?;
         let sig_pub: Vec<PublicKey> = Decodable::decode(&mut decoder)?;
         // TODO: Make sure we've read all the bytes above.
@@ -329,18 +329,3 @@ pub async fn verify_transactions(
 
     Ok(erroneous_txs)
 }
-
-/// A blockchain is considered valid, when every block is valid,
-/// based on validate_block checks.
-/// Be careful as this will try to load everything in memory.
-pub fn validate_blockchain(blockchain: &Blockchain) -> Result<()> {
-    // We use block order store here so we have all blocks in order
-    let blocks = blockchain.order.get_all()?;
-    for (index, block) in blocks[1..].iter().enumerate() {
-        let full_blocks = blockchain.get_blocks_by_hash(&[blocks[index].1, block.1])?;
-        let expected_reward = expected_reward(full_blocks[1].header.slot);
-        full_blocks[1].validate(&full_blocks[0], expected_reward)?;
-    }
-
-    Ok(())
-}

+ 5 - 2
tests/blockchain.rs

@@ -18,7 +18,10 @@
 
 use darkfi::{
     blockchain::{BlockInfo, Blockchain, BlockchainOverlay, Header},
-    validator::{pid::slot_pid_output, verification::validate_blockchain},
+    validator::{
+        pid::slot_pid_output,
+        validation::{validate_block, validate_blockchain},
+    },
     Error, Result,
 };
 use darkfi_sdk::{
@@ -111,7 +114,7 @@ impl Harness {
                 let expected_reward = expected_reward(block.header.slot);
 
                 // Validate block
-                block.validate(&p, expected_reward)?;
+                validate_block(block, &p, expected_reward)?;
             }
 
             // Insert block