From b8e612138d8a6ec973f551e6fe7ae969c4a01d91 Mon Sep 17 00:00:00 2001 From: aggstam Date: Thu, 14 Sep 2023 14:39:20 +0300 Subject: [PATCH] validator/blockchain: consolidate all blockchain structures validation at src/validator/validation.rs --- src/blockchain/block_store.rs | 70 +------------- src/blockchain/mod.rs | 2 +- src/blockchain/slot_store.rs | 70 +------------- src/validator/mod.rs | 3 + src/validator/validation.rs | 177 ++++++++++++++++++++++++++++++++++ src/validator/verification.rs | 23 +---- tests/blockchain.rs | 7 +- 7 files changed, 192 insertions(+), 160 deletions(-) create mode 100644 src/validator/validation.rs diff --git a/src/blockchain/block_store.rs b/src/blockchain/block_store.rs index f0d72eb4b..1e9b8f8bd 100644 --- a/src/blockchain/block_store.rs +++ b/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 for Block { diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 3f9d5e84b..2b4bc28dc 100644 --- a/src/blockchain/mod.rs +++ b/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; diff --git a/src/blockchain/slot_store.rs b/src/blockchain/slot_store.rs index b38d7655f..b749c9dc8 100644 --- a/src/blockchain/slot_store.rs +++ b/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, diff --git a/src/validator/mod.rs b/src/validator/mod.rs index 5c474fb2c..f59b1b30b 100644 --- a/src/validator/mod.rs +++ b/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; diff --git a/src/validator/validation.rs b/src/validator/validation.rs new file mode 100644 index 000000000..19631ae22 --- /dev/null +++ b/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 . + */ + +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(()) +} diff --git a/src/validator/verification.rs b/src/validator/verification.rs index 72babe2a2..c2c66192a 100644 --- a/src/validator/verification.rs +++ b/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)> = Decodable::decode(&mut decoder)?; let sig_pub: Vec = 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(()) -} diff --git a/tests/blockchain.rs b/tests/blockchain.rs index 9ed1797de..469a9fe3e 100644 --- a/tests/blockchain.rs +++ b/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