|
|
@@ -17,88 +17,252 @@
|
|
|
*/
|
|
|
|
|
|
use darkfi_sdk::{
|
|
|
- blockchain::{expected_reward, Slot},
|
|
|
- pasta::pallas,
|
|
|
+ blockchain::{block_version, expected_reward, Slot},
|
|
|
+ pasta::{group::ff::Field, pallas},
|
|
|
};
|
|
|
|
|
|
use crate::{
|
|
|
blockchain::{BlockInfo, Blockchain},
|
|
|
- validator::pid::slot_pid_output,
|
|
|
+ validator::{pid::slot_pid_output, pow::PoWModule},
|
|
|
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
|
|
|
+/// 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_block(block: &BlockInfo, previous: &BlockInfo, expected_reward: u64) -> Result<()> {
|
|
|
+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 is empty (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,
|
|
|
+ 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 eta is the expected one(zero)
|
|
|
+/// 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,
|
|
|
+ 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 previous hash (1)
|
|
|
+ // 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 eta is the expected one (8)
|
|
|
+ if slot.last_eta != pallas::Base::ZERO {
|
|
|
+ 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 (2)
|
|
|
+ // Check timestamps are incremental (3)
|
|
|
if block.header.timestamp <= previous.header.timestamp {
|
|
|
return error
|
|
|
}
|
|
|
|
|
|
- // Check heights are incremental (3)
|
|
|
+ // Check heights are incremental (4)
|
|
|
if block.header.height <= previous.header.height {
|
|
|
return error
|
|
|
}
|
|
|
|
|
|
- // Check extra stuff based on block version
|
|
|
- if block.header.version > 0 {
|
|
|
- // Verify slots (4)
|
|
|
- if block.slots.is_empty() {
|
|
|
- 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_slot(
|
|
|
- slot,
|
|
|
- previous_slot,
|
|
|
- &previous_hash,
|
|
|
- &previous.header.previous,
|
|
|
- &previous.header.nonce,
|
|
|
- 0,
|
|
|
- )?;
|
|
|
- previous_slot = slot;
|
|
|
- }
|
|
|
- }
|
|
|
+ // Retrieve previous block last slot
|
|
|
+ let mut previous_slot = previous.slots.last().unwrap();
|
|
|
|
|
|
- validate_slot(
|
|
|
- block.slots.last().unwrap(),
|
|
|
- previous_slot,
|
|
|
- &previous_hash,
|
|
|
- &previous.header.previous,
|
|
|
- &previous.header.nonce,
|
|
|
- expected_reward,
|
|
|
- )?;
|
|
|
-
|
|
|
- // Check block height is the last slot id (5)
|
|
|
- if block.slots.last().unwrap().id != block.header.height {
|
|
|
- return error
|
|
|
+ // 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 slot is considered valid when the following rules apply:
|
|
|
+/// 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
|
|
|
@@ -109,7 +273,7 @@ pub fn validate_block(block: &BlockInfo, previous: &BlockInfo, expected_reward:
|
|
|
/// 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(
|
|
|
+pub fn validate_pos_slot(
|
|
|
slot: &Slot,
|
|
|
previous: &Slot,
|
|
|
previous_block_hash: &blake3::Hash,
|
|
|
@@ -151,7 +315,7 @@ pub fn validate_slot(
|
|
|
return error
|
|
|
}
|
|
|
|
|
|
- // Check reward is the expected one (7)
|
|
|
+ // Check eta is the expected one (7)
|
|
|
if &slot.last_eta != last_eta {
|
|
|
return error
|
|
|
}
|
|
|
@@ -167,13 +331,20 @@ pub fn validate_slot(
|
|
|
/// 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<()> {
|
|
|
+pub fn validate_blockchain(blockchain: &Blockchain, pow_target: Option<usize>) -> Result<()> {
|
|
|
+ // Generate a PoW module
|
|
|
+ let mut module = PoWModule::new(blockchain.clone(), None, 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);
|
|
|
- validate_block(&full_blocks[1], &full_blocks[0], expected_reward)?;
|
|
|
+ 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(())
|