diff --git a/bin/darkfid2/src/tests/harness.rs b/bin/darkfid2/src/tests/harness.rs index 5ef8241ad..94d923750 100644 --- a/bin/darkfid2/src/tests/harness.rs +++ b/bin/darkfid2/src/tests/harness.rs @@ -19,7 +19,10 @@ use darkfi::{ blockchain::{BlockInfo, Header}, util::time::TimeKeeper, - validator::{Validator, ValidatorConfig}, + validator::{ + consensus::{next_block_reward, pid::slot_pid_output}, + Validator, ValidatorConfig, + }, Result, }; use darkfi_contract_test_harness::vks; @@ -81,11 +84,52 @@ impl Harness { Ok(()) } - pub async fn generate_next_block(&self) -> Result { - // Retrieve last block - let previous = self.alice._validator.read().await.blockchain.last_block()?; + pub async fn generate_next_block( + &self, + previous: &BlockInfo, + slots_count: usize, + ) -> Result { let previous_hash = previous.blockhash(); + // Generate empty slots + let mut slots = Vec::with_capacity(slots_count); + let mut previous_slot = previous.slots.last().unwrap().clone(); + for _ in 0..slots_count - 1 { + let (f, error, sigma1, sigma2) = slot_pid_output(&previous_slot); + let slot = Slot::new( + previous_slot.id + 1, + pallas::Base::ZERO, + vec![previous_hash], + vec![previous.header.previous.clone()], + f, + error, + previous_slot.error, + previous_slot.total_tokens + previous_slot.reward, + 0, + sigma1, + sigma2, + ); + slots.push(slot.clone()); + previous_slot = slot; + } + + // Generate slot + let (f, error, sigma1, sigma2) = slot_pid_output(&previous_slot); + let slot = Slot::new( + previous_slot.id + 1, + pallas::Base::ZERO, + vec![previous_hash], + vec![previous.header.previous.clone()], + f, + error, + previous_slot.error, + previous_slot.total_tokens + previous_slot.reward, + next_block_reward(), + sigma1, + sigma2, + ); + slots.push(slot); + // We increment timestamp so we don't have to use sleep let mut timestamp = previous.header.timestamp; timestamp.add(1); @@ -94,28 +138,13 @@ impl Harness { let header = Header::new( previous_hash, previous.header.epoch, - previous.header.slot + 1, + previous_slot.id + 1, timestamp, previous.header.root.clone(), ); - // Generate slot - let slot = Slot::new( - previous.header.slot + 1, - pallas::Base::ZERO, - vec![previous_hash], - vec![previous.header.previous.clone()], - 0.0, - 0.0, - 0.0, - 0, - 0, - pallas::Base::ZERO, - pallas::Base::ZERO, - ); - // Generate block - let block = BlockInfo::new(header, vec![], previous.producer.clone(), vec![slot]); + let block = BlockInfo::new(header, vec![], previous.producer.clone(), slots); Ok(block) } diff --git a/bin/darkfid2/src/tests/mod.rs b/bin/darkfid2/src/tests/mod.rs index db263e7b8..52c5bdf25 100644 --- a/bin/darkfid2/src/tests/mod.rs +++ b/bin/darkfid2/src/tests/mod.rs @@ -29,11 +29,17 @@ async fn add_blocks() -> Result<()> { // Initialize harness in testing mode let th = Harness::new(true).await?; + // Retrieve genesis block + let previous = th.alice._validator.read().await.blockchain.last_block()?; + // Generate next block - let block = th.generate_next_block().await?; + let block1 = th.generate_next_block(&previous, 1).await?; + + // Generate next block, with 4 empty slots inbetween + let block2 = th.generate_next_block(&block1, 5).await?; // Add it to nodes - th.add_blocks(&vec![block]).await?; + th.add_blocks(&vec![block1, block2]).await?; // Validate chains th.validate_chains().await?; diff --git a/src/blockchain/block_store.rs b/src/blockchain/block_store.rs index e1b2964aa..1da786f6b 100644 --- a/src/blockchain/block_store.rs +++ b/src/blockchain/block_store.rs @@ -116,7 +116,7 @@ impl BlockInfo { /// 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) -> Result<()> { + pub fn validate(&self, previous: &Self, expected_reward: u64) -> Result<()> { let error = Err(Error::BlockIsInvalid(self.blockhash().to_string())); let previous_hash = previous.blockhash(); @@ -143,12 +143,24 @@ impl BlockInfo { // Retrieve previous block last slot let mut previous_slot = previous.slots.last().unwrap(); - // Slots must already be in correct order (sorted by id) - for slot in &self.slots { - validate_slot(slot, previous_slot, &previous_hash, &previous.header.previous)?; - previous_slot = slot; + // 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, 0)?; + previous_slot = slot; + } } + validate_slot( + self.slots.last().unwrap(), + previous_slot, + &previous_hash, + &previous.header.previous, + expected_reward, + )?; + // Check block slot is the last slot id (5) if self.slots.last().unwrap().id != self.header.slot { return error diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 82c89789d..2d313f159 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -24,7 +24,7 @@ use sled::Transactional; use darkfi_sdk::blockchain::Slot; use darkfi_serial::{deserialize, serialize, Decodable}; -use crate::{tx::Transaction, Error, Result}; +use crate::{tx::Transaction, validator::consensus::next_block_reward, Error, Result}; /// Block related definitions and storage implementations pub mod block_store; @@ -110,7 +110,8 @@ impl Blockchain { let blocks = self.order.get_all()?; for (index, block) in blocks[1..].iter().enumerate() { let full_blocks = self.get_blocks_by_hash(&[blocks[index].1, block.1])?; - full_blocks[1].validate(&full_blocks[0])?; + let expected_reward = next_block_reward(); + full_blocks[1].validate(&full_blocks[0], expected_reward)?; } Ok(()) diff --git a/src/blockchain/slot_store.rs b/src/blockchain/slot_store.rs index b410752de..1d7fe11cf 100644 --- a/src/blockchain/slot_store.rs +++ b/src/blockchain/slot_store.rs @@ -20,7 +20,7 @@ use darkfi_sdk::blockchain::Slot; use darkfi_serial::{deserialize, serialize}; -use crate::{validator::consensus::pid::slot_sigmas, Error, Result}; +use crate::{validator::consensus::pid::slot_pid_output, Error, Result}; use super::{parse_record, SledDbOverlayPtr}; @@ -28,20 +28,21 @@ use super::{parse_record, SledDbOverlayPtr}; /// 1. Id increments previous slot id /// 2. Forks extend previous block hash /// 3. Forks follow previous block sequence -/// 4. Sigmas are the expected ones, based on consensus PID +/// 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 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, + expected_reward: u64, ) -> Result<()> { let error = Err(Error::SlotIsInvalid(slot.id)); - // TODO: Validate previous slot stuff - // slot.total_tokens = previous.total_tokens + previous.reward - // slot.previous_slot_err = previous.err; - // Check slots are incremental (1) if slot.id <= previous.id { return error @@ -57,8 +58,23 @@ pub fn validate_slot( return error } - // Check sigmas (4) - if (slot.sigma1, slot.sigma2) != slot_sigmas() { + // Check total tokens (4) + if slot.total_tokens != previous.total_tokens + previous.reward { + return error + } + + // Check previous slot error (5) + if slot.previous_slot_error != previous.error { + return error + } + + // Check PID output for this slot (6) + if (slot.f, slot.error, slot.sigma1, slot.sigma2) != slot_pid_output(previous) { + return error + } + + // Check reward is the expected one (7) + if slot.reward != expected_reward { return error } diff --git a/src/validator/consensus/float_10.rs b/src/validator/consensus/float_10.rs index 42e03fb1b..1f2c22422 100644 --- a/src/validator/consensus/float_10.rs +++ b/src/validator/consensus/float_10.rs @@ -50,6 +50,10 @@ impl Float10 { pub fn ln(&self) -> Self { Self(self.0.ln()) } + + pub fn to_f64(&self) -> f64 { + self.0.to_f64().value() + } } impl Add for Float10 { @@ -124,7 +128,7 @@ impl TryFrom for Float10 { type Error = crate::Error; fn try_from(value: f64) -> Result { - Ok(Self(FBig::try_from(value)?.with_base().value())) + Ok(Self(FBig::try_from(value)?.with_precision(RADIX_BITS).value().with_base().value())) } } diff --git a/src/validator/consensus/mod.rs b/src/validator/consensus/mod.rs index 538ab8193..7f4f05bce 100644 --- a/src/validator/consensus/mod.rs +++ b/src/validator/consensus/mod.rs @@ -38,3 +38,11 @@ impl Consensus { Self { blockchain, time_keeper } } } + +/// Block producer reward. +/// TODO (res) implement reward mechanism with accord to DRK, DARK token-economics. +pub fn next_block_reward() -> u64 { + // Configured block reward (1 DRK == 1 * 10^8) + let reward: u64 = 100_000_000; + reward +} diff --git a/src/validator/consensus/pid.rs b/src/validator/consensus/pid.rs index 11c47ab4f..3ea550469 100644 --- a/src/validator/consensus/pid.rs +++ b/src/validator/consensus/pid.rs @@ -16,11 +16,6 @@ * along with this program. If not, see . */ -//! TODO: this is just the foundation layout, so we can complete -//! the basic validator. We will use pallas::Base::zero() everywhere, -//! since we just want to simulate its functionality. After layout is -//! complete, the proper pid functionality will be implemented. - use darkfi_sdk::{blockchain::Slot, pasta::pallas}; use lazy_static::lazy_static; @@ -41,23 +36,23 @@ lazy_static! { } /// Return 2-term target approximation sigma coefficients, -/// corresponding to provided slot consensus state. -pub fn slot_sigmas() -> (pallas::Base, pallas::Base) { - (pallas::Base::zero(), pallas::Base::zero()) -} - -/// Return 2-term target approximation sigma coefficients, -/// corresponding to provided slot consensus state. -pub fn sigmass(previous_slot: &Slot) -> (pallas::Base, pallas::Base) { - let f = calculate_f(previous_slot); +/// alogn with the inverse probability `f` of becoming a +/// block producer and the feedback error, corresponding +/// to provided slot consensus state, +pub fn slot_pid_output(previous_slot: &Slot) -> (f64, f64, pallas::Base, pallas::Base) { + let (f, error) = calculate_f(previous_slot); let total_tokens = Float10::try_from(previous_slot.total_tokens + previous_slot.reward).unwrap(); - calculate_sigmas(f, total_tokens) + let (sigma1, sigma2) = calculate_sigmas(f.clone(), total_tokens); + + // TODO: log values + + (f.to_f64(), error.to_f64(), sigma1, sigma2) } /// Calculate the inverse probability `f` of becoming a block producer (winning the lottery) -/// having all the tokens, represented as Float10. -fn calculate_f(previous_slot: &Slot) -> Float10 { +/// having all the tokens, and the feedback error, represented as Float10. +fn calculate_f(previous_slot: &Slot) -> (Float10, Float10) { // PID controller K values based on constants let k1 = KP.clone() + KI.clone() + KD.clone(); let k2 = FLOAT10_NEG_ONE.clone() * KP.clone() + FLOAT10_NEG_TWO.clone() * KD.clone(); @@ -77,7 +72,7 @@ fn calculate_f(previous_slot: &Slot) -> Float10 { // Calculate f let mut f = previous_slot_f + - k1 * error + + k1 * error.clone() + k2 * previous_slot_error + k3 * previous_slot_previous_slot_error; @@ -88,7 +83,7 @@ fn calculate_f(previous_slot: &Slot) -> Float10 { f = MAX_F.clone() } - f + (f, error) } /// Return 2-term target approximation sigma coefficients, diff --git a/src/validator/mod.rs b/src/validator/mod.rs index 92961be40..63a79d127 100644 --- a/src/validator/mod.rs +++ b/src/validator/mod.rs @@ -30,7 +30,7 @@ use crate::{ /// DarkFi consensus module pub mod consensus; -use consensus::Consensus; +use consensus::{next_block_reward, Consensus}; /// Verification functions pub mod verification; @@ -91,8 +91,15 @@ impl Validator { // Add genesis block if blockchain is empty if blockchain.genesis().is_err() { info!(target: "validator", "Appending genesis block"); - verify_block(&overlay, &config.time_keeper, &config.genesis_block, None, testing_mode) - .await?; + verify_block( + &overlay, + &config.time_keeper, + &config.genesis_block, + None, + 0, + testing_mode, + ) + .await?; }; // Deploy native wasm contracts @@ -145,9 +152,20 @@ impl Validator { // Use block slot in time keeper time_keeper.verifying_slot = block.header.slot; - if verify_block(&overlay, &time_keeper, block, previous, self.testing_mode) - .await - .is_err() + // Retrieve expected reward + let expected_reward = next_block_reward(); + + // Verify block + if verify_block( + &overlay, + &time_keeper, + block, + previous, + expected_reward, + self.testing_mode, + ) + .await + .is_err() { error!(target: "validator", "Erroneous block found in set"); overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?; @@ -242,9 +260,20 @@ impl Validator { // Use block slot in time keeper time_keeper.verifying_slot = block.header.slot; - if verify_block(&overlay, &time_keeper, block, previous, self.testing_mode) - .await - .is_err() + // Retrieve expected reward + let expected_reward = next_block_reward(); + + // Verify block + if verify_block( + &overlay, + &time_keeper, + block, + previous, + expected_reward, + self.testing_mode, + ) + .await + .is_err() { error!(target: "validator", "Erroneous block found in set"); overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?; diff --git a/src/validator/verification.rs b/src/validator/verification.rs index 7739e1b58..16a06de5e 100644 --- a/src/validator/verification.rs +++ b/src/validator/verification.rs @@ -41,6 +41,7 @@ pub async fn verify_block( time_keeper: &TimeKeeper, block: &BlockInfo, previous: Option<&BlockInfo>, + expected_reward: u64, testing_mode: bool, ) -> Result<()> { let block_hash = block.blockhash(); @@ -56,12 +57,15 @@ pub async fn verify_block( return Err(Error::VerifyingSlotMissmatch()) } + // TODO: on genesis block, verify slot.total_tokens = txs.total, + // thats our genesis distribution, and reward is 0 + // Validate block using its previous, excluding genesis if block.header.slot != 0 { if previous.is_none() { return Err(Error::BlockPreviousMissing()) } - block.validate(previous.unwrap())?; + block.validate(previous.unwrap(), expected_reward)?; } // Validate proposal transaction if not in testing mode diff --git a/tests/blockchain.rs b/tests/blockchain.rs index f66bacc85..add1b491f 100644 --- a/tests/blockchain.rs +++ b/tests/blockchain.rs @@ -18,6 +18,7 @@ use darkfi::{ blockchain::{BlockInfo, Blockchain, BlockchainOverlay, Header}, + validator::consensus::{next_block_reward, pid::slot_pid_output}, Error, Result, }; use darkfi_sdk::{ @@ -53,9 +54,29 @@ impl Harness { fn generate_next_block(&self, previous: &BlockInfo) -> BlockInfo { let previous_hash = previous.blockhash(); + + // Generate slot + let previous_slot = previous.slots.last().unwrap(); + let (f, error, sigma1, sigma2) = slot_pid_output(&previous_slot); + let slot = Slot::new( + previous_slot.id + 1, + pallas::Base::ZERO, + vec![previous_hash], + vec![previous.header.previous.clone()], + f, + error, + previous_slot.error, + previous_slot.total_tokens + previous_slot.reward, + next_block_reward(), + sigma1, + sigma2, + ); + // We increment timestamp so we don't have to use sleep let mut timestamp = previous.header.timestamp; timestamp.add(1); + + // Generate header let header = Header::new( previous_hash, previous.header.epoch, @@ -63,19 +84,7 @@ impl Harness { timestamp, previous.header.root.clone(), ); - let slot = Slot::new( - previous.header.slot + 1, - pallas::Base::ZERO, - vec![previous_hash], - vec![previous.header.previous.clone()], - 0.0, - 0.0, - 0.0, - 0, - 0, - pallas::Base::ZERO, - pallas::Base::ZERO, - ); + BlockInfo::new(header, vec![], previous.producer.clone(), vec![slot]) } @@ -104,7 +113,11 @@ impl Harness { // This will be true for every insert, apart from genesis if let Some(p) = previous { - block.validate(&p)?; + // Retrieve expected reward + let expected_reward = next_block_reward(); + + // Validate block + block.validate(&p, expected_reward)?; } // Insert block