| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- /* 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::{block_version, expected_reward, Slot},
- pasta::{group::ff::Field, pallas},
- };
- use crate::{
- blockchain::{BlockInfo, Blockchain},
- validator::{pid::slot_pid_output, pow::PoWModule},
- Error, Result,
- };
- /// Validate provided block, using its previous, based on its version.
- pub fn validate_block(
- block: &BlockInfo,
- previous: &BlockInfo,
- expected_reward: u64,
- module: &PoWModule,
- ) -> Result<()> {
- // TODO: verify block validations work as expected on versions change(cutoff)
- match block_version(block.header.height) {
- 1 => validate_pow_block(block, previous, expected_reward, module)?,
- 2 => validate_pos_block(block, previous, expected_reward)?,
- _ => return Err(Error::BlockVersionIsInvalid(block.header.version)),
- }
- Ok(())
- }
- /// A PoW block is considered valid when the following rules apply:
- /// 1. Block version is equal to 1
- /// 2. Parent hash is equal to the hash of the previous block
- /// 3. Block heigh increments previous block height by 1
- /// 4. Timestamp is valid based on PoWModule validation
- /// 5. Block hash is valid based on PoWModule validation
- /// 6. Slots vector contains a single valid slot
- /// 7. Block height is the same as the slots vector last slot id
- /// Additional validity rules can be applied.
- pub fn validate_pow_block(
- block: &BlockInfo,
- previous: &BlockInfo,
- expected_reward: u64,
- module: &PoWModule,
- ) -> Result<()> {
- let error = Err(Error::BlockIsInvalid(block.hash()?.to_string()));
- // Check block version (1)
- if block.header.version != 1 {
- return error
- }
- // Check previous hash (2)
- let previous_hash = previous.hash()?;
- if block.header.previous != previous_hash {
- return error
- }
- // Check heights are incremental (3)
- if block.header.height != previous.header.height + 1 {
- return error
- }
- // Check timestamp validity (4)
- if !module.verify_timestamp_by_median(block.header.timestamp.0) {
- return error
- }
- // Check block hash corresponds to next one (5)
- module.verify_block_hash(block)?;
- // Verify slots vector contains single slot (6)
- if block.slots.len() != 1 {
- return error
- }
- // Retrieve previous block last slot
- let previous_slot = previous.slots.last().unwrap();
- // Validate last slot
- let last_slot = block.slots.last().unwrap();
- validate_pow_slot(
- last_slot,
- previous_slot,
- &previous_hash,
- &previous.header.previous,
- &pallas::Base::from(previous.header.nonce),
- expected_reward,
- )?;
- // Check block height is the last slot id (7)
- if last_slot.id != block.header.height {
- return error
- }
- Ok(())
- }
- /// A PoW slot is considered valid when the following rules apply:
- /// 1. Id increments previous slot id by 1
- /// 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. Slot previous has only 1 producer(the miner)
- /// 7. PID output for this slot is correct(zero)
- /// 8. Slot last nonce is the expected one
- /// 9. Slot reward value is the expected one
- /// Additional validity rules can be applied.
- pub fn validate_pow_slot(
- slot: &Slot,
- previous: &Slot,
- previous_block_hash: &blake3::Hash,
- previous_block_sequence: &blake3::Hash,
- last_nonce: &pallas::Base,
- expected_reward: u64,
- ) -> Result<()> {
- let error = Err(Error::SlotIsInvalid(slot.id));
- // Check slots are incremental (1)
- if slot.id != previous.id + 1 {
- 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 previous slot producers (6)
- if slot.previous.producers != 1 {
- return error
- }
- // Check PID output for this slot (7)
- if (slot.pid.f, slot.pid.error, slot.pid.sigma1, slot.pid.sigma2) !=
- (0.0, 0.0, pallas::Base::ZERO, pallas::Base::ZERO)
- {
- return error
- }
- // Check nonce is the expected one
- if &slot.last_nonce != last_nonce {
- return error
- }
- // Check reward is the expected one (9)
- if slot.reward != expected_reward {
- return error
- }
- Ok(())
- }
- /// A PoS block is considered valid when the following rules apply:
- /// 1. Block version is equal to 2
- /// 2. Parent hash is equal to the hash of the previous block
- /// 3. Timestamp increments previous block timestamp
- /// 4. Slot increments previous block slot
- /// 5. Slots vector is not empty and all its slots are valid
- /// 6. Slot is the same as the slots vector last slot id
- /// Additional validity rules can be applied.
- pub fn validate_pos_block(
- block: &BlockInfo,
- previous: &BlockInfo,
- expected_reward: u64,
- ) -> Result<()> {
- let error = Err(Error::BlockIsInvalid(block.hash()?.to_string()));
- // Check block version (1)
- if block.header.version != 2 {
- return error
- }
- // Check previous hash (2)
- let previous_hash = previous.hash()?;
- if block.header.previous != previous_hash {
- return error
- }
- // Check timestamps are incremental (3)
- if block.header.timestamp <= previous.header.timestamp {
- return error
- }
- // Check heights are incremental (4)
- if block.header.height <= previous.header.height {
- return error
- }
- // Verify slots (5)
- 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_pos_slot(
- slot,
- previous_slot,
- &previous_hash,
- &previous.header.previous,
- &previous.header.nonce,
- 0,
- )?;
- previous_slot = slot;
- }
- }
- // Validate last slot
- let last_slot = block.slots.last().unwrap();
- validate_pos_slot(
- last_slot,
- previous_slot,
- &previous_hash,
- &previous.header.previous,
- &previous.header.nonce,
- expected_reward,
- )?;
- // Check block height is the last slot id (6)
- if last_slot.id != block.header.height {
- return error
- }
- Ok(())
- }
- /// A PoS 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 nonce(eta) is the expected one
- /// 8. Slot reward value is the expected one
- /// Additional validity rules can be applied.
- pub fn validate_pos_slot(
- slot: &Slot,
- previous: &Slot,
- previous_block_hash: &blake3::Hash,
- previous_block_sequence: &blake3::Hash,
- last_nonce: &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 nonce(eta) is the expected one (7)
- if &slot.last_nonce != last_nonce {
- 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,
- pow_threads: usize,
- pow_target: usize,
- ) -> Result<()> {
- // Generate a PoW module
- let mut module = PoWModule::new(blockchain.clone(), pow_threads, pow_target)?;
- // 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.height);
- let full_block = &full_blocks[1];
- validate_block(full_block, &full_blocks[0], expected_reward, &module)?;
- // Update PoW module
- if full_block.header.version == 1 {
- module.append(full_block.header.timestamp.0, &module.next_difficulty()?);
- }
- }
- Ok(())
- }
|