Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Version Upgrade Mechanism #3096

Open
wants to merge 5 commits into
base: albatross
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 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::VERSION,
version: blockchain.state().current_version(),
block_number,
timestamp,
parent_hash,
Expand Down Expand Up @@ -253,12 +253,15 @@ impl BlockProducer {
round: u32,
// Extra data for this block.
extra_data: Vec<u8>,
// Version for this block.
version: Option<u16>,
) -> Result<MacroBlock, BlockProducerError> {
self.next_macro_block_proposal_with_rng(
blockchain,
timestamp,
round,
extra_data,
version,
&mut rand::thread_rng(),
)
}
Expand All @@ -277,6 +280,8 @@ impl BlockProducer {
round: u32,
// Extra data for this block.
extra_data: Vec<u8>,
// Version for this block (or current default).
version: Option<u16>,
// The rng seed. We need this parameterized in order to have determinism when running unit tests.
rng: &mut R,
) -> Result<MacroBlock, BlockProducerError> {
Expand Down Expand Up @@ -310,12 +315,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 +330,20 @@ impl BlockProducer {
// state.
let mut header = MacroHeader {
network,
version: Policy::VERSION,
version: version.unwrap_or_else(|| 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 +364,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 +394,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
17 changes: 16 additions & 1 deletion blockchain/src/blockchain/history_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ impl Blockchain {
"Inserting final macro block"
);
block_numbers.push(block.block_number());
block_timestamps.push(0); // FIXME
block_timestamps.push(block.timestamp());
block_transactions.push(vec![]);
block_inherents.push(vec![]);
}
Expand All @@ -303,10 +303,25 @@ impl Blockchain {
.push(Inherent::FinalizeBatch);

if Policy::is_election_block_at(*block_number) {
assert_eq!(
*block_number,
block.block_number(),
"Only the last block can be an election block"
);
block_inherents
.get_mut(i)
.unwrap()
.push(Inherent::FinalizeEpoch);

// If the version changed, add the upgrade inherent.
if this.state.current_version() < block.version() {
block_inherents
.get_mut(i)
.unwrap()
.push(Inherent::VersionUpgrade {
new_version: block.version(),
});
}
}
}
}
Expand Down
13 changes: 10 additions & 3 deletions blockchain/src/blockchain/inherents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Blockchain {
if Policy::is_election_block_at(macro_block.block_number()) {
// On election the previous epoch needs to be finalized.
// We can rely on `state` here, since we cannot revert macro blocks.
inherents.push(self.finalize_previous_epoch());
inherents.append(&mut self.finalize_previous_epoch(macro_block.header.version));
}

inherents
Expand Down Expand Up @@ -321,8 +321,15 @@ impl Blockchain {
}

/// Creates the inherent to finalize an epoch. The inherent is for updating the StakingContract.
pub fn finalize_previous_epoch(&self) -> Inherent {
pub fn finalize_previous_epoch(&self, version: u16) -> Vec<Inherent> {
// Create the FinalizeEpoch inherent.
Inherent::FinalizeEpoch
let mut inherents = vec![Inherent::FinalizeEpoch];
// Add version upgrade inherent if needed.
if version != self.state.current_version() {
inherents.push(Inherent::VersionUpgrade {
new_version: version,
})
}
inherents
}
}
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
7 changes: 7 additions & 0 deletions blockchain/src/blockchain_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ pub struct BlockchainState {
/// The validator slots for the previous epoch.
pub previous_slots: Option<Validators>,
}

impl BlockchainState {
/// The current protocol version.
pub fn current_version(&self) -> u16 {
self.main_chain.head.version()
}
}
5 changes: 5 additions & 0 deletions blockchain/tests/block_production.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ fn it_can_produce_macro_blocks() {
bc.timestamp() + Policy::BLOCK_SEPARATION_TIME,
0u32,
vec![],
None,
)
.unwrap();

Expand Down Expand Up @@ -218,6 +219,7 @@ fn it_can_produce_macro_block_after_punishment() {
bc.timestamp() + Policy::BLOCK_SEPARATION_TIME,
0u32,
vec![],
None,
)
.unwrap();

Expand Down Expand Up @@ -268,6 +270,7 @@ fn it_can_produce_macro_block_after_punishment() {
bc.timestamp() + Policy::BLOCK_SEPARATION_TIME,
0u32,
vec![],
None,
)
.unwrap();

Expand Down Expand Up @@ -322,6 +325,7 @@ fn it_can_produce_election_blocks() {
bc.timestamp() + Policy::BLOCK_SEPARATION_TIME,
0u32,
vec![0x42],
None,
)
.unwrap();

Expand Down Expand Up @@ -368,6 +372,7 @@ fn it_can_produce_a_chain_with_txns() {
blockchain.timestamp() + Policy::BLOCK_SEPARATION_TIME,
0u32,
vec![],
None,
)
.unwrap();

Expand Down
Loading
Loading