mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
validator: validate blocks based on their version
This commit is contained in:
@@ -159,8 +159,10 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
|
||||
let genesis_block = BlockInfo::default();
|
||||
let genesis_txs_total = genesis_txs_total(&genesis_block.txs)?;
|
||||
let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0);
|
||||
// TODO: add miner threads arg
|
||||
let config = ValidatorConfig::new(
|
||||
time_keeper,
|
||||
Some(40),
|
||||
genesis_block,
|
||||
genesis_txs_total,
|
||||
vec![],
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi::{
|
||||
blockchain::BlockInfo, system::sleep, util::time::Timestamp, validator::pow::PoWModule, Result,
|
||||
use darkfi::{blockchain::BlockInfo, system::sleep, util::time::Timestamp, Result};
|
||||
use darkfi_sdk::{
|
||||
blockchain::{expected_reward, PidOutput, PreviousSlot, Slot},
|
||||
pasta::{group::ff::Field, pallas},
|
||||
};
|
||||
use log::info;
|
||||
use smol::channel::Receiver;
|
||||
@@ -57,40 +59,55 @@ pub async fn miner_task(node: &Darkfid, stop_signal: &Receiver<()>) -> Result<()
|
||||
|
||||
/// Miner loop
|
||||
async fn miner_loop(node: &Darkfid, stop_signal: &Receiver<()>) -> Result<()> {
|
||||
// TODO: add miner threads arg
|
||||
// Generate a PoW module
|
||||
let mut module = PoWModule::new(node.validator.read().await.blockchain.clone(), None, Some(90));
|
||||
|
||||
// Miner loop
|
||||
let timekeeper = node.validator.read().await.consensus.time_keeper.clone();
|
||||
loop {
|
||||
// TODO: consensus should generate next block, along with its difficulty,
|
||||
// derived from best fork
|
||||
// Retrieve last block
|
||||
let last = node.validator.read().await.blockchain.last_block()?;
|
||||
let last_hash = last.hash()?;
|
||||
|
||||
// Generate next PoW slot
|
||||
let last_slot = last.slots.last().unwrap().clone();
|
||||
let id = last_slot.id + 1;
|
||||
let producers = 1;
|
||||
let previous = PreviousSlot::new(
|
||||
producers,
|
||||
vec![last_hash],
|
||||
vec![last.header.previous],
|
||||
last_slot.pid.error,
|
||||
);
|
||||
let pid = PidOutput::default();
|
||||
let total_tokens = last_slot.total_tokens + last_slot.reward;
|
||||
let reward = expected_reward(id);
|
||||
let slot = Slot::new(id, previous, pid, pallas::Base::ZERO, total_tokens, reward);
|
||||
|
||||
// Mine next block
|
||||
let difficulty = module.next_difficulty();
|
||||
let mut next_block = BlockInfo::default();
|
||||
next_block.header.version = 0;
|
||||
next_block.header.previous = last.hash()?;
|
||||
next_block.header.height = last.header.height + 1;
|
||||
next_block.header.previous = last_hash;
|
||||
next_block.header.height = slot.id;
|
||||
next_block.header.epoch = timekeeper.slot_epoch(next_block.header.height);
|
||||
next_block.header.timestamp = Timestamp::current_time();
|
||||
module.mine_block(&mut next_block, stop_signal)?;
|
||||
next_block.slots = vec![slot];
|
||||
node.validator.read().await.consensus.module.mine_block(&mut next_block, stop_signal)?;
|
||||
|
||||
// Verify it
|
||||
module.verify_block(&next_block)?;
|
||||
node.validator.read().await.consensus.module.verify_current_block(&next_block)?;
|
||||
|
||||
// Generate stuff before pushing block to blockchain
|
||||
let timestamp = next_block.header.timestamp.0;
|
||||
let message = BlockInfoMessage::from(&next_block);
|
||||
|
||||
// Append block to blockchain
|
||||
node.validator.write().await.add_blocks(&[next_block]).await?;
|
||||
let mut lock = node.validator.write().await;
|
||||
lock.add_blocks(&[next_block]).await?;
|
||||
|
||||
// Update PoW module
|
||||
let difficulty = lock.consensus.module.next_difficulty();
|
||||
lock.consensus.module.append(timestamp, &difficulty);
|
||||
|
||||
// Broadcast block
|
||||
node.sync_p2p.broadcast(&message).await;
|
||||
|
||||
// Update PoW module
|
||||
module.append(timestamp, &difficulty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ fn forks() -> Result<()> {
|
||||
let blockchain = Blockchain::new(&sled::Config::new().temporary(true).open()?)?;
|
||||
|
||||
// Create a fork
|
||||
let fork = Fork::new(&blockchain)?;
|
||||
let fork = Fork::new(&blockchain, Some(90))?;
|
||||
|
||||
// Add a dummy record to fork
|
||||
fork.overlay.lock().unwrap().order.insert(&[0], &[record0])?;
|
||||
|
||||
@@ -29,7 +29,7 @@ use darkfi::{
|
||||
};
|
||||
use darkfi_contract_test_harness::{vks, Holder, TestHarness};
|
||||
use darkfi_sdk::{
|
||||
blockchain::{expected_reward, PidOutput, PreviousSlot, Slot},
|
||||
blockchain::{expected_reward, PidOutput, PreviousSlot, Slot, POS_START},
|
||||
pasta::{group::ff::Field, pallas},
|
||||
};
|
||||
use url::Url;
|
||||
@@ -42,6 +42,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct HarnessConfig {
|
||||
pub pow_target: Option<usize>,
|
||||
pub testing_node: bool,
|
||||
pub alice_initial: u64,
|
||||
pub bob_initial: u64,
|
||||
@@ -81,6 +82,7 @@ impl Harness {
|
||||
let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0);
|
||||
let validator_config = ValidatorConfig::new(
|
||||
time_keeper,
|
||||
config.pow_target,
|
||||
genesis_block,
|
||||
genesis_txs_total,
|
||||
vec![],
|
||||
@@ -134,8 +136,8 @@ impl Harness {
|
||||
let alice = &self.alice.validator.read().await;
|
||||
let bob = &self.bob.validator.read().await;
|
||||
|
||||
alice.validate_blockchain(genesis_txs_total, vec![]).await?;
|
||||
bob.validate_blockchain(genesis_txs_total, vec![]).await?;
|
||||
alice.validate_blockchain(genesis_txs_total, vec![], self.config.pow_target).await?;
|
||||
bob.validate_blockchain(genesis_txs_total, vec![], self.config.pow_target).await?;
|
||||
|
||||
let alice_blockchain_len = alice.blockchain.len();
|
||||
assert_eq!(alice_blockchain_len, bob.blockchain.len());
|
||||
@@ -160,7 +162,7 @@ impl Harness {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn generate_next_block(
|
||||
pub async fn generate_next_pos_block(
|
||||
&self,
|
||||
previous: &BlockInfo,
|
||||
slots_count: usize,
|
||||
@@ -171,7 +173,7 @@ impl Harness {
|
||||
let mut slots = Vec::with_capacity(slots_count);
|
||||
let mut previous_slot = previous.slots.last().unwrap().clone();
|
||||
for i in 0..slots_count {
|
||||
let id = previous_slot.id + 1;
|
||||
let id = if previous_slot.id < POS_START { POS_START } else { previous_slot.id + 1 };
|
||||
// First slot in the sequence has (at least) 1 previous slot producer
|
||||
let producers = if i == 0 { 1 } else { 0 };
|
||||
let previous = PreviousSlot::new(
|
||||
@@ -195,10 +197,11 @@ impl Harness {
|
||||
timestamp.add(1);
|
||||
|
||||
// Generate header
|
||||
let height = slots.last().unwrap().id;
|
||||
let header = Header::new(
|
||||
previous_hash,
|
||||
previous.header.epoch,
|
||||
slots.last().unwrap().id,
|
||||
self.alice.validator.read().await.consensus.time_keeper.slot_epoch(height),
|
||||
height,
|
||||
timestamp,
|
||||
previous.header.nonce,
|
||||
);
|
||||
|
||||
@@ -28,21 +28,23 @@ use harness::{generate_node, Harness, HarnessConfig};
|
||||
|
||||
mod forks;
|
||||
|
||||
async fn sync_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
|
||||
async fn sync_pos_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
|
||||
init_logger();
|
||||
|
||||
// Initialize harness in testing mode
|
||||
let config = HarnessConfig { testing_node: true, alice_initial: 1000, bob_initial: 500 };
|
||||
let pow_target = Some(90);
|
||||
let config =
|
||||
HarnessConfig { testing_node: true, pow_target, alice_initial: 1000, bob_initial: 500 };
|
||||
let th = Harness::new(config, &ex).await?;
|
||||
|
||||
// Retrieve genesis block
|
||||
let previous = th.alice.validator.read().await.blockchain.last_block()?;
|
||||
|
||||
// Generate next block
|
||||
let block1 = th.generate_next_block(&previous, 1).await?;
|
||||
let block1 = th.generate_next_pos_block(&previous, 1).await?;
|
||||
|
||||
// Generate next block, with 4 empty slots inbetween
|
||||
let block2 = th.generate_next_block(&block1, 5).await?;
|
||||
let block2 = th.generate_next_pos_block(&block1, 5).await?;
|
||||
|
||||
// Add it to nodes
|
||||
th.add_blocks(&vec![block1, block2]).await?;
|
||||
@@ -65,7 +67,7 @@ async fn sync_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
|
||||
let genesis_txs_total = th.config.alice_initial + th.config.bob_initial;
|
||||
let alice = &th.alice.validator.read().await;
|
||||
let charlie = &charlie.validator.read().await;
|
||||
charlie.validate_blockchain(genesis_txs_total, vec![]).await?;
|
||||
charlie.validate_blockchain(genesis_txs_total, vec![], pow_target).await?;
|
||||
assert_eq!(alice.blockchain.len(), charlie.blockchain.len());
|
||||
assert_eq!(alice.blockchain.slots.len(), charlie.blockchain.slots.len());
|
||||
|
||||
@@ -74,14 +76,14 @@ async fn sync_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_blocks() -> Result<()> {
|
||||
fn sync_pos_blocks() -> Result<()> {
|
||||
let ex = Arc::new(Executor::new());
|
||||
let (signal, shutdown) = smol::channel::unbounded::<()>();
|
||||
|
||||
easy_parallel::Parallel::new().each(0..4, |_| smol::block_on(ex.run(shutdown.recv()))).finish(
|
||||
|| {
|
||||
smol::block_on(async {
|
||||
sync_blocks_real(ex.clone()).await.unwrap();
|
||||
sync_pos_blocks_real(ex.clone()).await.unwrap();
|
||||
drop(signal);
|
||||
})
|
||||
},
|
||||
|
||||
@@ -139,19 +139,16 @@ impl Blockchain {
|
||||
trees.push(self.order.0.clone());
|
||||
batches.push(blocks_order_batch);
|
||||
|
||||
// Store extra stuff based on block version
|
||||
if block.header.version > 0 {
|
||||
// Store block slots uids vector
|
||||
let slots = block.slots.iter().map(|x| x.id).collect();
|
||||
let blocks_slots_bactch = self.blocks_slots.insert_batch(&block_hash_vec, &[&slots])?;
|
||||
trees.push(self.blocks_slots.0.clone());
|
||||
batches.push(blocks_slots_bactch);
|
||||
// Store block slots uids vector
|
||||
let slots = block.slots.iter().map(|x| x.id).collect();
|
||||
let blocks_slots_bactch = self.blocks_slots.insert_batch(&block_hash_vec, &[&slots])?;
|
||||
trees.push(self.blocks_slots.0.clone());
|
||||
batches.push(blocks_slots_bactch);
|
||||
|
||||
// Store block slots
|
||||
let slots_batch = self.slots.insert_batch(&block.slots)?;
|
||||
trees.push(self.slots.0.clone());
|
||||
batches.push(slots_batch);
|
||||
}
|
||||
// Store block slots
|
||||
let slots_batch = self.slots.insert_batch(&block.slots)?;
|
||||
trees.push(self.slots.0.clone());
|
||||
batches.push(slots_batch);
|
||||
|
||||
// Perform an atomic transaction over the trees and apply the batches.
|
||||
self.atomic_write(&trees, &batches)?;
|
||||
@@ -173,22 +170,19 @@ impl Blockchain {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// Check extra stuff based on block version
|
||||
if block.header.version > 0 {
|
||||
// Check if we have block slots uids vector
|
||||
let slots = match self.blocks_slots.get(&[blockhash], true) {
|
||||
Ok(v) => v[0].clone().unwrap(),
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let provided_block_slots: Vec<u64> = block.slots.iter().map(|x| x.id).collect();
|
||||
if slots != provided_block_slots {
|
||||
return Ok(false)
|
||||
}
|
||||
// Check if we have block slots uids vector
|
||||
let slots = match self.blocks_slots.get(&[blockhash], true) {
|
||||
Ok(v) => v[0].clone().unwrap(),
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let provided_block_slots: Vec<u64> = block.slots.iter().map(|x| x.id).collect();
|
||||
if slots != provided_block_slots {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// Check if we have all slots
|
||||
if self.slots.get(&slots, true).is_err() {
|
||||
return Ok(false)
|
||||
}
|
||||
// Check if we have all slots
|
||||
if self.slots.get(&slots, true).is_err() {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// Check provided info produces the same hash
|
||||
@@ -216,14 +210,10 @@ impl Blockchain {
|
||||
let txs = self.transactions.get(&block.txs, true)?;
|
||||
let txs = txs.iter().map(|x| x.clone().unwrap()).collect();
|
||||
|
||||
// Retrieve extra stuff based on block version
|
||||
let mut block_slots = vec![];
|
||||
if header.version > 0 {
|
||||
let slots = self.blocks_slots.get(&[block.hash()], true)?;
|
||||
let slots = slots[0].clone().unwrap();
|
||||
let slots = self.slots.get(&slots, true)?;
|
||||
block_slots = slots.iter().map(|x| x.clone().unwrap()).collect();
|
||||
}
|
||||
let slots = self.blocks_slots.get(&[block.hash()], true)?;
|
||||
let slots = slots[0].clone().unwrap();
|
||||
let slots = self.slots.get(&slots, true)?;
|
||||
let block_slots = slots.iter().map(|x| x.clone().unwrap()).collect();
|
||||
|
||||
let info = BlockInfo::new(header, txs, block.signature, block_slots);
|
||||
ret.push(info);
|
||||
@@ -491,15 +481,12 @@ impl BlockchainOverlay {
|
||||
// Store block order
|
||||
self.order.insert(&[block.header.height], &block_hash_vec)?;
|
||||
|
||||
// Store extra stuff based on block version
|
||||
if block.header.version > 0 {
|
||||
// Store block slots uids vector
|
||||
let slots = block.slots.iter().map(|x| x.id).collect();
|
||||
self.blocks_slots.insert(&block_hash_vec, &[&slots])?;
|
||||
// Store block slots uids vector
|
||||
let slots = block.slots.iter().map(|x| x.id).collect();
|
||||
self.blocks_slots.insert(&block_hash_vec, &[&slots])?;
|
||||
|
||||
// Store block slots
|
||||
self.slots.insert(&block.slots)?;
|
||||
}
|
||||
// Store block slots
|
||||
self.slots.insert(&block.slots)?;
|
||||
|
||||
Ok(block_hash)
|
||||
}
|
||||
@@ -518,22 +505,19 @@ impl BlockchainOverlay {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// Check extra stuff based on block version
|
||||
if block.header.version > 0 {
|
||||
// Check if we have block slots uids vector
|
||||
let slots = match self.blocks_slots.get(&[blockhash], true) {
|
||||
Ok(v) => v[0].clone().unwrap(),
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let provided_block_slots: Vec<u64> = block.slots.iter().map(|x| x.id).collect();
|
||||
if slots != provided_block_slots {
|
||||
return Ok(false)
|
||||
}
|
||||
// Check if we have block slots uids vector
|
||||
let slots = match self.blocks_slots.get(&[blockhash], true) {
|
||||
Ok(v) => v[0].clone().unwrap(),
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
let provided_block_slots: Vec<u64> = block.slots.iter().map(|x| x.id).collect();
|
||||
if slots != provided_block_slots {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// Check if we have all slots
|
||||
if self.slots.get(&slots, true).is_err() {
|
||||
return Ok(false)
|
||||
}
|
||||
// Check if we have all slots
|
||||
if self.slots.get(&slots, true).is_err() {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// Check provided info produces the same hash
|
||||
@@ -561,14 +545,10 @@ impl BlockchainOverlay {
|
||||
let txs = self.transactions.get(&block.txs, true)?;
|
||||
let txs = txs.iter().map(|x| x.clone().unwrap()).collect();
|
||||
|
||||
// Retrieve extra stuff based on block version
|
||||
let mut block_slots = vec![];
|
||||
if header.version > 0 {
|
||||
let slots = self.blocks_slots.get(&[block.hash()], true)?;
|
||||
let slots = slots[0].clone().unwrap();
|
||||
let slots = self.slots.get(&slots, true)?;
|
||||
block_slots = slots.iter().map(|x| x.clone().unwrap()).collect();
|
||||
}
|
||||
let slots = self.blocks_slots.get(&[block.hash()], true)?;
|
||||
let slots = slots[0].clone().unwrap();
|
||||
let slots = self.slots.get(&slots, true)?;
|
||||
let block_slots = slots.iter().map(|x| x.clone().unwrap()).collect();
|
||||
|
||||
let info = BlockInfo::new(header, txs, block.signature, block_slots);
|
||||
ret.push(info);
|
||||
|
||||
@@ -111,7 +111,7 @@ impl TestHarness {
|
||||
|
||||
let timer = Instant::now();
|
||||
|
||||
wallet.validator.read().await.add_test_producer_transaction(tx, slot, true).await?;
|
||||
wallet.validator.read().await.add_test_producer_transaction(tx, slot, 2, true).await?;
|
||||
wallet.consensus_staked_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
|
||||
tx_action_benchmark.verify_times.push(timer.elapsed());
|
||||
|
||||
@@ -174,7 +174,7 @@ impl TestHarness {
|
||||
.validator
|
||||
.read()
|
||||
.await
|
||||
.add_test_producer_transaction(tx, slot, true)
|
||||
.add_test_producer_transaction(tx, slot, 2, true)
|
||||
.await
|
||||
.is_err());
|
||||
tx_action_benchmark.verify_times.push(timer.elapsed());
|
||||
|
||||
@@ -157,6 +157,7 @@ impl Wallet {
|
||||
let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0);
|
||||
let config = ValidatorConfig::new(
|
||||
time_keeper,
|
||||
Some(90),
|
||||
genesis_block.clone(),
|
||||
0,
|
||||
faucet_pubkeys.to_vec(),
|
||||
|
||||
@@ -100,7 +100,12 @@ impl TestHarness {
|
||||
self.tx_action_benchmarks.get_mut(&TxAction::MoneyPoWReward).unwrap();
|
||||
let timer = Instant::now();
|
||||
|
||||
wallet.validator.read().await.add_test_producer_transaction(tx, block_height, true).await?;
|
||||
wallet
|
||||
.validator
|
||||
.read()
|
||||
.await
|
||||
.add_test_producer_transaction(tx, block_height, 1, true)
|
||||
.await?;
|
||||
wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
|
||||
tx_action_benchmark.verify_times.push(timer.elapsed());
|
||||
|
||||
@@ -122,7 +127,7 @@ impl TestHarness {
|
||||
.validator
|
||||
.read()
|
||||
.await
|
||||
.add_test_producer_transaction(tx, block_height, true)
|
||||
.add_test_producer_transaction(tx, block_height, 1, true)
|
||||
.await
|
||||
.is_err());
|
||||
tx_action_benchmark.verify_times.push(timer.elapsed());
|
||||
|
||||
@@ -354,6 +354,9 @@ pub enum Error {
|
||||
#[error("Block {0} is invalid")]
|
||||
BlockIsInvalid(String),
|
||||
|
||||
#[error("Block version {0} is invalid")]
|
||||
BlockVersionIsInvalid(u8),
|
||||
|
||||
#[error("Block {0} already in database")]
|
||||
BlockAlreadyExists(String),
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::{
|
||||
blockchain::{BlockInfo, Blockchain, BlockchainOverlay, BlockchainOverlayPtr, Header},
|
||||
tx::Transaction,
|
||||
util::time::{TimeKeeper, Timestamp},
|
||||
validator::{pid::slot_pid_output, verify_block, verify_transactions},
|
||||
validator::{pid::slot_pid_output, pow::PoWModule, verify_block, verify_transactions},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
@@ -42,25 +42,38 @@ pub struct Consensus {
|
||||
pub blockchain: Blockchain,
|
||||
/// Helper structure to calculate time related operations
|
||||
pub time_keeper: TimeKeeper,
|
||||
/// Currently configured PoW target
|
||||
pub pow_target: Option<usize>,
|
||||
/// Node is participating to consensus
|
||||
pub participating: bool,
|
||||
/// Last slot node check for finalization
|
||||
pub checked_finalization: u64,
|
||||
/// Fork chains containing block proposals
|
||||
pub forks: Vec<Fork>,
|
||||
// TODO: remove this once everything goes through forks
|
||||
/// Canonical blockchain PoW module state
|
||||
pub module: PoWModule,
|
||||
/// Flag to enable testing mode
|
||||
pub testing_mode: bool,
|
||||
}
|
||||
|
||||
impl Consensus {
|
||||
/// Generate a new Consensus state.
|
||||
pub fn new(blockchain: Blockchain, time_keeper: TimeKeeper, testing_mode: bool) -> Self {
|
||||
pub fn new(
|
||||
blockchain: Blockchain,
|
||||
time_keeper: TimeKeeper,
|
||||
pow_target: Option<usize>,
|
||||
testing_mode: bool,
|
||||
) -> Self {
|
||||
let module = PoWModule::new(blockchain.clone(), None, pow_target);
|
||||
Self {
|
||||
blockchain,
|
||||
time_keeper,
|
||||
pow_target,
|
||||
participating: false,
|
||||
checked_finalization: 0,
|
||||
forks: vec![],
|
||||
module,
|
||||
testing_mode,
|
||||
}
|
||||
}
|
||||
@@ -72,7 +85,7 @@ impl Consensus {
|
||||
|
||||
// If no forks exist, create a new one as a basis to extend
|
||||
if self.forks.is_empty() {
|
||||
self.forks.push(Fork::new(&self.blockchain)?);
|
||||
self.forks.push(Fork::new(&self.blockchain, self.pow_target)?);
|
||||
}
|
||||
|
||||
// Grab previous slot information
|
||||
@@ -238,6 +251,7 @@ impl Consensus {
|
||||
if verify_block(
|
||||
&fork.overlay,
|
||||
&time_keeper,
|
||||
&fork.module,
|
||||
&proposal.block,
|
||||
&previous,
|
||||
expected_reward,
|
||||
@@ -251,6 +265,11 @@ impl Consensus {
|
||||
return Err(Error::BlockIsInvalid(proposal.hash.to_string()))
|
||||
};
|
||||
|
||||
// Update PoW module
|
||||
if proposal.block.header.version == 1 {
|
||||
fork.module.append(proposal.block.header.timestamp.0, &fork.module.next_difficulty());
|
||||
}
|
||||
|
||||
// If a fork index was found, replace forks with the mutated one,
|
||||
// otherwise push the new fork.
|
||||
fork.proposals.push(proposal.hash);
|
||||
@@ -299,7 +318,7 @@ impl Consensus {
|
||||
return Err(Error::ExtendedChainIndexNotFound)
|
||||
}
|
||||
|
||||
return Ok((Fork::new(&self.blockchain)?, None))
|
||||
return Ok((Fork::new(&self.blockchain, self.pow_target)?, None))
|
||||
}
|
||||
|
||||
let (f_index, p_index) = found.unwrap();
|
||||
@@ -310,7 +329,7 @@ impl Consensus {
|
||||
}
|
||||
|
||||
// Rebuild fork
|
||||
let mut fork = Fork::new(&self.blockchain)?;
|
||||
let mut fork = Fork::new(&self.blockchain, self.pow_target)?;
|
||||
fork.proposals = original_fork.proposals[..p_index + 1].to_vec();
|
||||
|
||||
// Retrieve proposals blocks from original fork
|
||||
@@ -334,6 +353,7 @@ impl Consensus {
|
||||
if verify_block(
|
||||
&fork.overlay,
|
||||
&time_keeper,
|
||||
&fork.module,
|
||||
block,
|
||||
previous,
|
||||
expected_reward,
|
||||
@@ -347,6 +367,11 @@ impl Consensus {
|
||||
return Err(Error::BlockIsInvalid(block.hash()?.to_string()))
|
||||
};
|
||||
|
||||
// Update PoW module
|
||||
if block.header.version == 1 {
|
||||
fork.module.append(block.header.timestamp.0, &fork.module.next_difficulty());
|
||||
}
|
||||
|
||||
// Use last inserted block as next iteration previous
|
||||
previous = block;
|
||||
}
|
||||
@@ -444,6 +469,8 @@ impl From<Proposal> for BlockInfo {
|
||||
pub struct Fork {
|
||||
/// Overlay cache over canonical Blockchain
|
||||
pub overlay: BlockchainOverlayPtr,
|
||||
/// Current PoW module state,
|
||||
pub module: PoWModule,
|
||||
/// Fork proposal hashes sequence
|
||||
pub proposals: Vec<blake3::Hash>,
|
||||
/// Hot/live slots
|
||||
@@ -453,11 +480,12 @@ pub struct Fork {
|
||||
}
|
||||
|
||||
impl Fork {
|
||||
pub fn new(blockchain: &Blockchain) -> Result<Self> {
|
||||
pub fn new(blockchain: &Blockchain, pow_target: Option<usize>) -> Result<Self> {
|
||||
let module = PoWModule::new(blockchain.clone(), None, pow_target);
|
||||
let mempool =
|
||||
blockchain.get_pending_txs()?.iter().map(|tx| blake3::hash(&serialize(tx))).collect();
|
||||
let overlay = BlockchainOverlay::new(blockchain)?;
|
||||
Ok(Self { overlay, proposals: vec![], slots: vec![], mempool })
|
||||
Ok(Self { overlay, module, proposals: vec![], slots: vec![], mempool })
|
||||
}
|
||||
|
||||
/// Auxiliary function to retrieve last proposal
|
||||
@@ -573,10 +601,11 @@ impl Fork {
|
||||
/// overlay pointer have been updated to the cloned one.
|
||||
pub fn full_clone(&self) -> Result<Self> {
|
||||
let overlay = self.overlay.lock().unwrap().full_clone()?;
|
||||
let module = self.module.clone();
|
||||
let proposals = self.proposals.clone();
|
||||
let slots = self.slots.clone();
|
||||
let mempool = self.mempool.clone();
|
||||
|
||||
Ok(Self { overlay, proposals, slots, mempool })
|
||||
Ok(Self { overlay, module, proposals, slots, mempool })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ use consensus::Consensus;
|
||||
|
||||
/// DarkFi PoW module
|
||||
pub mod pow;
|
||||
use pow::PoWModule;
|
||||
|
||||
/// DarkFi consensus PID controller
|
||||
pub mod pid;
|
||||
@@ -65,6 +66,8 @@ pub mod float_10;
|
||||
pub struct ValidatorConfig {
|
||||
/// Helper structure to calculate time related operations
|
||||
pub time_keeper: TimeKeeper,
|
||||
/// Currently configured PoW target
|
||||
pub pow_target: Option<usize>,
|
||||
/// Genesis block
|
||||
pub genesis_block: BlockInfo,
|
||||
/// Total amount of minted tokens in genesis block
|
||||
@@ -78,12 +81,20 @@ pub struct ValidatorConfig {
|
||||
impl ValidatorConfig {
|
||||
pub fn new(
|
||||
time_keeper: TimeKeeper,
|
||||
pow_target: Option<usize>,
|
||||
genesis_block: BlockInfo,
|
||||
genesis_txs_total: u64,
|
||||
faucet_pubkeys: Vec<PublicKey>,
|
||||
testing_mode: bool,
|
||||
) -> Self {
|
||||
Self { time_keeper, genesis_block, genesis_txs_total, faucet_pubkeys, testing_mode }
|
||||
Self {
|
||||
time_keeper,
|
||||
pow_target,
|
||||
genesis_block,
|
||||
genesis_txs_total,
|
||||
faucet_pubkeys,
|
||||
testing_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +143,8 @@ impl Validator {
|
||||
overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
|
||||
|
||||
info!(target: "validator::new", "Initializing Consensus");
|
||||
let consensus = Consensus::new(blockchain.clone(), config.time_keeper, testing_mode);
|
||||
let consensus =
|
||||
Consensus::new(blockchain.clone(), config.time_keeper, config.pow_target, testing_mode);
|
||||
|
||||
// Create the actual state
|
||||
let state =
|
||||
@@ -296,8 +308,9 @@ impl Validator {
|
||||
// Retrieve last block
|
||||
let mut previous = &overlay.lock().unwrap().last_block()?;
|
||||
|
||||
// Create a time keeper to validate each block
|
||||
// Create a time keeper and a PoW module to validate each block
|
||||
let mut time_keeper = self.consensus.time_keeper.clone();
|
||||
let mut module = self.consensus.module.clone();
|
||||
|
||||
// Keep track of all blocks transactions to remove them from pending txs store
|
||||
let mut removed_txs = vec![];
|
||||
@@ -314,6 +327,7 @@ impl Validator {
|
||||
if verify_block(
|
||||
&overlay,
|
||||
&time_keeper,
|
||||
&module,
|
||||
block,
|
||||
previous,
|
||||
expected_reward,
|
||||
@@ -327,6 +341,11 @@ impl Validator {
|
||||
return Err(Error::BlockIsInvalid(block.hash()?.to_string()))
|
||||
};
|
||||
|
||||
// Update PoW module
|
||||
if block.header.version == 1 {
|
||||
module.append(block.header.timestamp.0, &module.next_difficulty());
|
||||
}
|
||||
|
||||
// Store block transactions
|
||||
for tx in &block.txs {
|
||||
removed_txs.push(tx.clone());
|
||||
@@ -343,6 +362,9 @@ impl Validator {
|
||||
self.blockchain.remove_pending_txs(&removed_txs)?;
|
||||
self.purge_pending_txs().await?;
|
||||
|
||||
// Update PoW module
|
||||
self.consensus.module = module;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -407,6 +429,7 @@ impl Validator {
|
||||
&self,
|
||||
tx: &Transaction,
|
||||
verifying_slot: u64,
|
||||
block_version: u8,
|
||||
write: bool,
|
||||
) -> Result<()> {
|
||||
debug!(target: "validator::add_test_producer_transaction", "Instantiating BlockchainOverlay");
|
||||
@@ -422,7 +445,8 @@ impl Validator {
|
||||
|
||||
// Verify transaction
|
||||
let mut erroneous_txs = vec![];
|
||||
if let Err(e) = verify_producer_transaction(&overlay, &time_keeper, tx).await {
|
||||
if let Err(e) = verify_producer_transaction(&overlay, &time_keeper, tx, block_version).await
|
||||
{
|
||||
warn!(target: "validator::add_test_producer_transaction", "Transaction verification failed: {}", e);
|
||||
erroneous_txs.push(tx.clone());
|
||||
}
|
||||
@@ -453,6 +477,7 @@ impl Validator {
|
||||
&self,
|
||||
genesis_txs_total: u64,
|
||||
faucet_pubkeys: Vec<PublicKey>,
|
||||
pow_target: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let blocks = self.blockchain.get_all()?;
|
||||
|
||||
@@ -469,8 +494,9 @@ impl Validator {
|
||||
// Set previous
|
||||
let mut previous = &blocks[0];
|
||||
|
||||
// Create a time keeper to validate each block
|
||||
// Create a time keeper and a PoW module to validate each block
|
||||
let mut time_keeper = self.consensus.time_keeper.clone();
|
||||
let mut module = PoWModule::new(blockchain.clone(), None, pow_target);
|
||||
|
||||
// Deploy native wasm contracts
|
||||
deploy_native_contracts(&overlay, &time_keeper, &faucet_pubkeys)?;
|
||||
@@ -490,6 +516,7 @@ impl Validator {
|
||||
if verify_block(
|
||||
&overlay,
|
||||
&time_keeper,
|
||||
&module,
|
||||
block,
|
||||
previous,
|
||||
expected_reward,
|
||||
@@ -503,6 +530,11 @@ impl Validator {
|
||||
return Err(Error::BlockIsInvalid(block.hash()?.to_string()))
|
||||
};
|
||||
|
||||
// Update PoW module
|
||||
if block.header.version == 1 {
|
||||
module.append(block.header.timestamp.0, &module.next_difficulty());
|
||||
}
|
||||
|
||||
// Use last inserted block as next iteration previous
|
||||
previous = block;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ const CUT_BEGIN: usize = 60;
|
||||
const CUT_END: usize = 660;
|
||||
/// Default target block time, in seconds
|
||||
const DIFFICULTY_TARGET: usize = 20;
|
||||
// TODO: maybe add more difficulty targets (testnet, mainnet, etc)
|
||||
/// How many most recent blocks to use to verify new blocks' timestamp
|
||||
const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60;
|
||||
/// Time limit in the future of what blocks can be
|
||||
@@ -175,12 +176,18 @@ impl PoWModule {
|
||||
difficulty == &self.next_difficulty()
|
||||
}
|
||||
|
||||
/// Verify provided block timestamp is valid and matches certain criteria
|
||||
pub fn verify_timestamp(&self, timestamp: u64) -> bool {
|
||||
/// Verify provided block timestamp is not far in the future and
|
||||
/// check its valid acorrding to current timestamps median
|
||||
pub fn verify_current_timestamp(&self, timestamp: u64) -> bool {
|
||||
if timestamp > Timestamp::current_time().0 + BLOCK_FUTURE_TIME_LIMIT {
|
||||
return false
|
||||
}
|
||||
|
||||
self.verify_timestamp_by_median(timestamp)
|
||||
}
|
||||
|
||||
/// Verify provided block timestamp is valid and matches certain criteria
|
||||
pub fn verify_timestamp_by_median(&self, timestamp: u64) -> bool {
|
||||
// If not enough blocks, no proper median yet, return true
|
||||
if self.timestamps.len() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
|
||||
return true
|
||||
@@ -192,17 +199,17 @@ impl PoWModule {
|
||||
timestamp >= median(timestamps)
|
||||
}
|
||||
|
||||
/// Verify provided block timestamp and difficulty pair
|
||||
pub fn verify_pair(&self, timestamp: u64, difficulty: &BigUint) {
|
||||
assert!(self.verify_timestamp(timestamp));
|
||||
assert!(self.verify_difficulty(difficulty));
|
||||
/// Verify provided block timestamp and hash
|
||||
pub fn verify_current_block(&self, block: &BlockInfo) -> Result<()> {
|
||||
// First we verify the block's timestamp
|
||||
assert!(self.verify_current_timestamp(block.header.timestamp.0));
|
||||
|
||||
// Then we verify the block's hash
|
||||
self.verify_block_hash(block)
|
||||
}
|
||||
|
||||
/// Verify provided block corresponds to next mine target
|
||||
pub fn verify_block(&self, block: &BlockInfo) -> Result<()> {
|
||||
// First we verify the block's timestamp
|
||||
assert!(self.verify_timestamp(block.header.timestamp.0));
|
||||
|
||||
pub fn verify_block_hash(&self, block: &BlockInfo) -> Result<()> {
|
||||
// Then we verify the proof of work:
|
||||
let verifier_setup = Instant::now();
|
||||
|
||||
@@ -421,7 +428,7 @@ mod tests {
|
||||
module.mine_block(&mut next_block, &recvr)?;
|
||||
|
||||
// Verify it
|
||||
module.verify_block(&next_block)?;
|
||||
module.verify_current_block(&next_block)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// Additional validity rules can be applied.
|
||||
pub fn validate_block(block: &BlockInfo, previous: &BlockInfo, expected_reward: u64) -> Result<()> {
|
||||
let error = Err(Error::BlockIsInvalid(block.hash()?.to_string()));
|
||||
let previous_hash = previous.hash()?;
|
||||
|
||||
// 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 heights are incremental (3)
|
||||
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
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
/// 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 slot is considered valid when the following rules apply:
|
||||
/// 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 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 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 (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
|
||||
@@ -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(())
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::{
|
||||
runtime::vm_runtime::Runtime,
|
||||
tx::Transaction,
|
||||
util::time::TimeKeeper,
|
||||
validator::validation::validate_block,
|
||||
validator::{pow::PoWModule, validation::validate_block},
|
||||
zk::VerifyingKey,
|
||||
Error, Result,
|
||||
};
|
||||
@@ -51,31 +51,33 @@ pub async fn verify_genesis_block(
|
||||
return Err(Error::BlockAlreadyExists(block_hash))
|
||||
}
|
||||
|
||||
// Block height must be 0
|
||||
if block.header.height != 0 {
|
||||
return Err(Error::BlockIsInvalid(block_hash))
|
||||
}
|
||||
|
||||
// Block height must be the same as the time keeper verifying slot
|
||||
if block.header.height != time_keeper.verifying_slot {
|
||||
return Err(Error::VerifyingSlotMissmatch())
|
||||
}
|
||||
|
||||
// Check extra stuff based on block version
|
||||
if block.header.version > 0 {
|
||||
// Check genesis slot exist
|
||||
if block.slots.len() != 1 {
|
||||
return Err(Error::BlockIsInvalid(block_hash))
|
||||
}
|
||||
// Check genesis slot exist
|
||||
if block.slots.len() != 1 {
|
||||
return Err(Error::BlockIsInvalid(block_hash))
|
||||
}
|
||||
|
||||
// Retrieve genesis slot
|
||||
let genesis_slot = block.slots.last().unwrap();
|
||||
// Retrieve genesis slot
|
||||
let genesis_slot = block.slots.last().unwrap();
|
||||
|
||||
// Genesis block slot total token must correspond to the total
|
||||
// of all genesis transactions public inputs (genesis distribution).
|
||||
if genesis_slot.total_tokens != genesis_txs_total {
|
||||
return Err(Error::SlotIsInvalid(genesis_slot.id))
|
||||
}
|
||||
// Genesis block slot total token must correspond to the total
|
||||
// of all genesis transactions public inputs (genesis distribution).
|
||||
if genesis_slot.total_tokens != genesis_txs_total {
|
||||
return Err(Error::SlotIsInvalid(genesis_slot.id))
|
||||
}
|
||||
|
||||
// Verify there is no reward
|
||||
if genesis_slot.reward != 0 {
|
||||
return Err(Error::SlotIsInvalid(genesis_slot.id))
|
||||
}
|
||||
// Verify there is no reward
|
||||
if genesis_slot.reward != 0 {
|
||||
return Err(Error::SlotIsInvalid(genesis_slot.id))
|
||||
}
|
||||
|
||||
// Verify transactions vector contains at least one(producers) transaction
|
||||
@@ -110,6 +112,7 @@ pub async fn verify_genesis_block(
|
||||
pub async fn verify_block(
|
||||
overlay: &BlockchainOverlayPtr,
|
||||
time_keeper: &TimeKeeper,
|
||||
module: &PoWModule,
|
||||
block: &BlockInfo,
|
||||
previous: &BlockInfo,
|
||||
expected_reward: u64,
|
||||
@@ -128,8 +131,13 @@ pub async fn verify_block(
|
||||
return Err(Error::VerifyingSlotMissmatch())
|
||||
}
|
||||
|
||||
// Block epoch must be the correct one, calculated by the time keeper configuration
|
||||
if block.header.epoch != time_keeper.slot_epoch(block.header.height) {
|
||||
return Err(Error::VerifyingSlotMissmatch())
|
||||
}
|
||||
|
||||
// Validate block, using its previous
|
||||
validate_block(block, previous, expected_reward)?;
|
||||
validate_block(block, previous, expected_reward, module)?;
|
||||
|
||||
// Verify transactions vector contains at least one(producers) transaction
|
||||
if block.txs.is_empty() {
|
||||
@@ -139,7 +147,8 @@ pub async fn verify_block(
|
||||
// Validate proposal transaction if not in testing mode
|
||||
if !testing_mode {
|
||||
let tx = block.txs.last().unwrap();
|
||||
let public_key = verify_producer_transaction(overlay, time_keeper, tx).await?;
|
||||
let public_key =
|
||||
verify_producer_transaction(overlay, time_keeper, tx, block.header.version).await?;
|
||||
verify_producer_signature(block, &public_key)?;
|
||||
}
|
||||
|
||||
@@ -176,22 +185,33 @@ pub async fn verify_producer_transaction(
|
||||
overlay: &BlockchainOverlayPtr,
|
||||
time_keeper: &TimeKeeper,
|
||||
tx: &Transaction,
|
||||
block_version: u8,
|
||||
) -> Result<PublicKey> {
|
||||
let tx_hash = tx.hash()?;
|
||||
debug!(target: "validator::verification::verify_producer_transaction", "Validating proposal transaction {}", tx_hash);
|
||||
|
||||
// Transaction must contain a single call, either Money::PoWReward(0x08) or Consensus::Proposal(0x02)
|
||||
if tx.calls.len() != 1 ||
|
||||
(tx.calls[0].contract_id != *MONEY_CONTRACT_ID &&
|
||||
tx.calls[0].contract_id != *CONSENSUS_CONTRACT_ID) ||
|
||||
(tx.calls[0].contract_id == *MONEY_CONTRACT_ID && tx.calls[0].data[0] != 0x08) ||
|
||||
(tx.calls[0].contract_id == *CONSENSUS_CONTRACT_ID && tx.calls[0].data[0] != 0x02)
|
||||
{
|
||||
error!(target: "validator::verification::verify_producer_transaction", "Proposal transaction is malformed");
|
||||
// Transaction must contain a single call
|
||||
if tx.calls.len() != 1 {
|
||||
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
||||
}
|
||||
|
||||
// Verify call based on version
|
||||
let call = &tx.calls[0];
|
||||
match block_version {
|
||||
1 => {
|
||||
// Version 1 blocks must contain a Money::PoWReward(0x08) call
|
||||
if call.contract_id != *MONEY_CONTRACT_ID || call.data[0] != 0x08 {
|
||||
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// Version 1 blocks must contain a Consensus::Proposal(0x02) call
|
||||
if call.contract_id != *CONSENSUS_CONTRACT_ID || call.data[0] != 0x02 {
|
||||
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::BlockVersionIsInvalid(block_version)),
|
||||
}
|
||||
|
||||
// Map of ZK proof verifying keys for the current transaction
|
||||
let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
|
||||
|
||||
@@ -20,47 +20,63 @@ use darkfi::{
|
||||
blockchain::{BlockInfo, Blockchain, BlockchainOverlay, Header},
|
||||
validator::{
|
||||
pid::slot_pid_output,
|
||||
pow::PoWModule,
|
||||
validation::{validate_block, validate_blockchain},
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
blockchain::{expected_reward, PidOutput, PreviousSlot, Slot},
|
||||
blockchain::{expected_reward, PidOutput, PreviousSlot, Slot, POS_START},
|
||||
pasta::{group::ff::Field, pallas},
|
||||
};
|
||||
|
||||
const POW_TARGET: Option<usize> = Some(10);
|
||||
|
||||
struct Node {
|
||||
blockchain: Blockchain,
|
||||
module: PoWModule,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn new() -> Result<Self> {
|
||||
let blockchain = Blockchain::new(&sled::Config::new().temporary(true).open()?)?;
|
||||
let module = PoWModule::new(blockchain.clone(), None, POW_TARGET);
|
||||
Ok(Self { blockchain, module })
|
||||
}
|
||||
}
|
||||
|
||||
struct Harness {
|
||||
pub alice: Blockchain,
|
||||
pub bob: Blockchain,
|
||||
pub alice: Node,
|
||||
pub bob: Node,
|
||||
}
|
||||
|
||||
impl Harness {
|
||||
fn new() -> Result<Self> {
|
||||
let alice = Blockchain::new(&sled::Config::new().temporary(true).open()?)?;
|
||||
let bob = Blockchain::new(&sled::Config::new().temporary(true).open()?)?;
|
||||
let alice = Node::new()?;
|
||||
let bob = Node::new()?;
|
||||
Ok(Self { alice, bob })
|
||||
}
|
||||
|
||||
fn is_empty(&self) {
|
||||
assert!(self.alice.is_empty());
|
||||
assert!(self.bob.is_empty());
|
||||
assert!(self.alice.blockchain.is_empty());
|
||||
assert!(self.bob.blockchain.is_empty());
|
||||
}
|
||||
|
||||
fn validate_chains(&self) -> Result<()> {
|
||||
validate_blockchain(&self.alice)?;
|
||||
validate_blockchain(&self.bob)?;
|
||||
validate_blockchain(&self.alice.blockchain, POW_TARGET)?;
|
||||
validate_blockchain(&self.bob.blockchain, POW_TARGET)?;
|
||||
|
||||
assert_eq!(self.alice.len(), self.bob.len());
|
||||
assert_eq!(self.alice.blockchain.len(), self.bob.blockchain.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_next_block(&self, previous: &BlockInfo) -> Result<BlockInfo> {
|
||||
fn generate_next_pos_block(&self, previous: &BlockInfo) -> Result<BlockInfo> {
|
||||
let previous_hash = previous.hash()?;
|
||||
|
||||
// Generate slot
|
||||
let previous_slot = previous.slots.last().unwrap();
|
||||
let id = previous_slot.id + 1;
|
||||
let id = if previous_slot.id < POS_START { POS_START } else { previous_slot.id + 1 };
|
||||
let producers = 1;
|
||||
let previous_slot_info = PreviousSlot::new(
|
||||
producers,
|
||||
@@ -94,17 +110,17 @@ impl Harness {
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
fn add_blocks(&self, blocks: &[BlockInfo]) -> Result<()> {
|
||||
self.add_blocks_to_chain(&self.alice, blocks)?;
|
||||
self.add_blocks_to_chain(&self.bob, blocks)?;
|
||||
fn add_pos_blocks(&mut self, blocks: &[BlockInfo]) -> Result<()> {
|
||||
Self::add_pos_blocks_to_chain(&mut self.alice, blocks)?;
|
||||
Self::add_pos_blocks_to_chain(&mut self.bob, blocks)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is what the validator will execute when it receives a block.
|
||||
fn add_blocks_to_chain(&self, blockchain: &Blockchain, blocks: &[BlockInfo]) -> Result<()> {
|
||||
fn add_pos_blocks_to_chain(node: &mut Node, blocks: &[BlockInfo]) -> Result<()> {
|
||||
// Create overlay
|
||||
let blockchain_overlay = BlockchainOverlay::new(blockchain)?;
|
||||
let blockchain_overlay = BlockchainOverlay::new(&node.blockchain)?;
|
||||
let lock = blockchain_overlay.lock().unwrap();
|
||||
|
||||
// When we insert genesis, chain is empty
|
||||
@@ -123,7 +139,12 @@ impl Harness {
|
||||
let expected_reward = expected_reward(block.header.height);
|
||||
|
||||
// Validate block
|
||||
validate_block(block, &p, expected_reward)?;
|
||||
validate_block(block, &p, expected_reward, &node.module)?;
|
||||
|
||||
// Update PoW module
|
||||
if block.header.version == 1 {
|
||||
node.module.append(block.header.timestamp.0, &node.module.next_difficulty());
|
||||
}
|
||||
}
|
||||
|
||||
// Insert block
|
||||
@@ -141,30 +162,30 @@ impl Harness {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockchain_add_blocks() -> Result<()> {
|
||||
fn blockchain_add_pos_blocks() -> Result<()> {
|
||||
smol::block_on(async {
|
||||
// Initialize harness
|
||||
let th = Harness::new()?;
|
||||
let mut th = Harness::new()?;
|
||||
|
||||
// Check that nothing exists
|
||||
th.is_empty();
|
||||
|
||||
// We generate some blocks
|
||||
// We generate some pos blocks
|
||||
let mut blocks = vec![];
|
||||
|
||||
let genesis_block = BlockInfo::default();
|
||||
blocks.push(genesis_block.clone());
|
||||
|
||||
let block = th.generate_next_block(&genesis_block)?;
|
||||
let block = th.generate_next_pos_block(&genesis_block)?;
|
||||
blocks.push(block.clone());
|
||||
|
||||
let block = th.generate_next_block(&block)?;
|
||||
let block = th.generate_next_pos_block(&block)?;
|
||||
blocks.push(block.clone());
|
||||
|
||||
let block = th.generate_next_block(&block)?;
|
||||
let block = th.generate_next_pos_block(&block)?;
|
||||
blocks.push(block.clone());
|
||||
|
||||
th.add_blocks(&blocks)?;
|
||||
th.add_pos_blocks(&blocks)?;
|
||||
|
||||
// Validate chains
|
||||
th.validate_chains()?;
|
||||
|
||||
Reference in New Issue
Block a user