Skip to content

Commit

Permalink
Make validator selection dependent on accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
paberr committed Nov 18, 2024
1 parent 4632185 commit 5a48af3
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 60 deletions.
27 changes: 13 additions & 14 deletions blockchain/src/block_production/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ impl BlockProducer {
let (state_root, diff_root, executed_txns) = blockchain
.state
.accounts
.exercise_transactions(&transactions, &inherents, &block_state)
.exercise_transactions(&transactions, &inherents, &block_state, None)
.map_err(|error| {
BlockProducerError::accounts_error(
blockchain,
Expand Down Expand Up @@ -209,7 +209,7 @@ impl BlockProducer {
// Create the micro block header.
let header = MicroHeader {
network,
version: Policy::MAX_SUPPORTED_VERSION,
version: blockchain.state().current_version(),
block_number,
timestamp,
parent_hash,
Expand Down Expand Up @@ -310,12 +310,6 @@ impl BlockProducer {
.seed()
.sign_next_with_rng(&self.signing_key, block_number, rng);

// If this is an election block, calculate the validator set for the next epoch.
let validators = match Policy::is_election_block_at(block_number) {
true => Some(blockchain.next_validators(&seed)),
false => None,
};

// Get the staking contract PRIOR to any state changes.
let staking_contract = blockchain
.get_staking_contract_if_complete(None)
Expand All @@ -331,20 +325,20 @@ impl BlockProducer {
// state.
let mut header = MacroHeader {
network,
version: Policy::MAX_SUPPORTED_VERSION,
version: blockchain.state().current_version(),
block_number,
round,
timestamp,
parent_hash,
parent_election_hash,
interlink,
seed,
seed: seed.clone(),
extra_data,
state_root: Blake2bHash::default(),
body_root: Blake2sHash::default(),
diff_root: Blake2bHash::default(),
history_root: Blake2bHash::default(),
validators,
validators: None,
next_batch_initial_punished_set,
..Default::default()
};
Expand All @@ -365,15 +359,22 @@ impl BlockProducer {
let inherents: Vec<Inherent> = blockchain.create_macro_block_inherents(&macro_block);

// Update the state and add the state root to the header.
let mut txn = blockchain.write_transaction();

let block_state = BlockState::new(block_number, timestamp);
let (state_root, diff_root, _) = blockchain
.state
.accounts
.exercise_transactions(&[], &inherents, &block_state)
.exercise_transactions(&[], &inherents, &block_state, Some(&mut txn))
.map_err(|error| {
BlockProducerError::accounts_error(blockchain, error, vec![], inherents.clone())
})?;

// If this is an election block, calculate the validator set for the next epoch.
if Policy::is_election_block_at(block_number) {
macro_block.header.validators = Some(blockchain.next_validators(&seed, Some(&txn)));
}

macro_block.header.state_root = state_root;
macro_block.header.diff_root = diff_root;

Expand All @@ -388,8 +389,6 @@ impl BlockProducer {
);

// Store the historic transactions into the history tree and calculate the history root.
let mut txn = blockchain.write_transaction();

macro_block.header.history_root = blockchain
.history_store
.add_to_history(&mut txn, block_number, &hist_txs)
Expand Down
10 changes: 6 additions & 4 deletions blockchain/src/blockchain/slots.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use nimiq_blockchain_interface::{AbstractBlockchain, BlockchainError};
use nimiq_database::mdbx::MdbxReadTransaction;
use nimiq_database::mdbx::{MdbxReadTransaction, OptionalTransaction};
use nimiq_primitives::{
policy::Policy,
slots_allocation::{Slot, Validators},
Expand Down Expand Up @@ -44,10 +44,12 @@ impl Blockchain {
}

/// Calculates the next validators from a given seed.
pub fn next_validators(&self, seed: &VrfSeed) -> Validators {
let staking_contract = self.get_staking_contract();
pub fn next_validators(&self, seed: &VrfSeed, txn: Option<&MdbxReadTransaction>) -> Validators {
let txn = txn.or_new(&self.db);
let staking_contract = self
.get_staking_contract_if_complete(Some(&txn))
.expect("We should always have the staking contract.");
let data_store = self.get_staking_contract_store();
let txn = self.read_transaction();
staking_contract.select_validators(&data_store.read(&txn), seed)
}

Expand Down
32 changes: 16 additions & 16 deletions blockchain/src/blockchain/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,22 @@ impl Blockchain {
if let Block::Macro(macro_block) = block {
// If we don't have the staking contract, there is nothing we can check.
if let Some(staking_contract) = self.get_staking_contract_if_complete(Some(txn)) {
// Verify validators.
let validators = match macro_block.is_election() {
true => Some(self.next_validators(&macro_block.header.seed, Some(txn))),
false => None,
};
if macro_block.header.validators != validators {
warn!(
%macro_block,
given_validators = ?macro_block.header.validators,
expected_validators = ?validators,
reason = "Invalid validators",
"Rejecting block"
);
return Err(PushError::InvalidBlock(BlockError::InvalidValidators));
}

if macro_block.header.next_batch_initial_punished_set
!= staking_contract
.punished_slots
Expand Down Expand Up @@ -247,22 +263,6 @@ impl Blockchain {
None => return Ok(()),
};

// Verify validators.
let validators = match macro_block.is_election() {
true => Some(self.next_validators(&macro_block.header.seed)),
false => None,
};
if macro_block.header.validators != validators {
warn!(
%macro_block,
given_validators = ?macro_block.header.validators,
expected_validators = ?validators,
reason = "Invalid validators",
"Rejecting block"
);
return Err(PushError::InvalidBlock(BlockError::InvalidValidators));
}

// Verify reward transactions only if we have the complete accounts state as
// `create_reward_transactions` expects the full state to be present.
if !self.state.accounts.is_complete(Some(txn)) {
Expand Down
22 changes: 14 additions & 8 deletions primitives/account/src/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use nimiq_database::{
declare_table,
mdbx::{MdbxDatabase, MdbxReadTransaction as DBTransaction},
traits::{Database, WriteTransaction},
mdbx::{MdbxDatabase, MdbxReadTransaction as DBTransaction, MdbxWriteTransaction},
traits::Database,
};
use nimiq_hash::{Blake2bHash, Hash};
use nimiq_keys::Address;
Expand Down Expand Up @@ -270,14 +270,22 @@ impl Accounts {
missing
}

pub fn exercise_transactions(
&self,
pub fn exercise_transactions<'env>(
&'env self,
transactions: &[Transaction],
inherents: &[Inherent],
block_state: &BlockState,
txn: Option<&mut MdbxWriteTransaction<'env>>,
) -> Result<(Blake2bHash, Blake2bHash, Vec<ExecutedTransaction>), AccountsError> {
let mut raw_txn = self.env.write_transaction();
let mut txn: WriteTransactionProxy = (&mut raw_txn).into();
let mut raw_txn;
let raw_txn_ptr = match txn {
Some(txn) => txn,
None => {
raw_txn = self.env.write_transaction();
&mut raw_txn
}
};
let mut txn: WriteTransactionProxy = raw_txn_ptr.into();
assert!(self.is_complete(Some(&txn)), "Tree must be complete");

txn.start_recording();
Expand All @@ -304,8 +312,6 @@ impl Accounts {

let state_hash = self.get_root_hash_assert(Some(&txn));

raw_txn.abort();

Ok((state_hash, diff_hash, executed_txns))
}

Expand Down
2 changes: 1 addition & 1 deletion primitives/block/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ impl Block {

// Check that the version is supported.
// The blockchain should then make sure that the version is never decreased.
if self.version() <= Policy::MAX_SUPPORTED_VERSION {
if self.version() > Policy::MAX_SUPPORTED_VERSION {
warn!(
header = %self,
obtained_version = self.version(),
Expand Down
2 changes: 1 addition & 1 deletion primitives/block/tests/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn test_verify_header_version() {
let mut block = Block::Micro(MicroBlock {
header: MicroHeader {
network: NetworkId::UnitAlbatross,
version: Policy::MAX_SUPPORTED_VERSION - 1,
version: Policy::MAX_SUPPORTED_VERSION + 1,
block_number: 1,
timestamp: 0,
..Default::default()
Expand Down
36 changes: 20 additions & 16 deletions test-utils/src/test_custom_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub fn next_micro_block(
let (state_root, diff_root, executed_txns) = blockchain
.state
.accounts
.exercise_transactions(&transactions, &inherents, &block_state)
.exercise_transactions(&transactions, &inherents, &block_state, None)
.expect("Failed to compute accounts hash during block production");

let hist_txs = HistoricTransaction::from(
Expand Down Expand Up @@ -156,7 +156,9 @@ pub fn next_micro_block(

let header = MicroHeader {
network,
version: config.version.unwrap_or(Policy::MAX_SUPPORTED_VERSION),
version: config
.version
.unwrap_or(blockchain.state().current_version()),
block_number,
timestamp,
parent_hash,
Expand Down Expand Up @@ -223,7 +225,7 @@ pub fn next_skip_block(
let (real_state_root, real_diff_root, _) = blockchain
.state
.accounts
.exercise_transactions(&[], &inherents, &block_state)
.exercise_transactions(&[], &inherents, &block_state, None)
.expect("Failed to compute accounts hash during block production");

let state_root = config.state_root.clone().unwrap_or(real_state_root);
Expand Down Expand Up @@ -257,7 +259,9 @@ pub fn next_skip_block(

let header = MicroHeader {
network,
version: config.version.unwrap_or(Policy::MAX_SUPPORTED_VERSION),
version: config
.version
.unwrap_or(blockchain.state().current_version()),
block_number,
timestamp,
parent_hash,
Expand Down Expand Up @@ -321,12 +325,6 @@ pub fn next_macro_block_proposal(
.sign_next(signing_key, block_number)
});

let validators = if Policy::is_election_block_at(blockchain.block_number() + 1) {
Some(blockchain.next_validators(&seed))
} else {
None
};

// Get the staking contract PRIOR to any state changes.
let staking_contract = blockchain.get_staking_contract();

Expand All @@ -336,20 +334,22 @@ pub fn next_macro_block_proposal(

let mut header = MacroHeader {
network,
version: config.version.unwrap_or(Policy::MAX_SUPPORTED_VERSION),
version: config
.version
.unwrap_or(blockchain.state().current_version()),
block_number,
round: 0,
timestamp,
parent_hash,
parent_election_hash,
interlink,
seed,
seed: seed.clone(),
extra_data: config.extra_data.clone(),
state_root: Blake2bHash::default(),
body_root: Blake2sHash::default(),
diff_root: Blake2bHash::default(),
history_root: Blake2bHash::default(),
validators,
validators: None,
next_batch_initial_punished_set,
..Default::default()
};
Expand All @@ -372,12 +372,18 @@ pub fn next_macro_block_proposal(

let block_state = BlockState::new(block_number, timestamp);

let mut txn = blockchain.write_transaction();

let (state_root, diff_root, _) = blockchain
.state
.accounts
.exercise_transactions(&[], &inherents, &block_state)
.exercise_transactions(&[], &inherents, &block_state, Some(&mut txn))
.expect("Failed to compute accounts hash during block production.");

if Policy::is_election_block_at(blockchain.block_number() + 1) {
macro_block.header.validators = Some(blockchain.next_validators(&seed, Some(&txn)));
};

macro_block.header.state_root = state_root;
macro_block.header.diff_root = diff_root;

Expand All @@ -390,8 +396,6 @@ pub fn next_macro_block_proposal(
vec![],
);

let mut txn = blockchain.write_transaction();

macro_block.header.history_root = blockchain
.history_store
.add_to_history(&mut txn, block_number, &hist_txs)
Expand Down

0 comments on commit 5a48af3

Please sign in to comment.