diff --git a/block-production-albatross/Cargo.toml b/block-production-albatross/Cargo.toml index 0458c67d26..40e14f40dc 100644 --- a/block-production-albatross/Cargo.toml +++ b/block-production-albatross/Cargo.toml @@ -32,7 +32,6 @@ nimiq-hash = { path = "../hash", version = "0.1" } nimiq-keys = { path = "../keys", version = "0.1" } nimiq-mempool = { path = "../mempool", version = "0.1" } nimiq-primitives = { path = "../primitives", version = "0.1" } -nimiq-vrf = { path = "../vrf", version = "0.1" } [dev-dependencies] hex = "0.4" @@ -41,3 +40,4 @@ nimiq-account = { path = "../primitives/account", version = "0.1" } nimiq-collections = { path = "../collections", version = "0.1" } nimiq-database = { path = "../database", version = "0.1" } nimiq-transaction = { path = "../primitives/transaction", version = "0.1" } +nimiq-vrf = { path = "../vrf", version = "0.1" } diff --git a/block-production-albatross/src/lib.rs b/block-production-albatross/src/lib.rs index 0fe3fa8796..d55eff3a47 100644 --- a/block-production-albatross/src/lib.rs +++ b/block-production-albatross/src/lib.rs @@ -9,7 +9,6 @@ extern crate nimiq_hash as hash; extern crate nimiq_keys as keys; extern crate nimiq_mempool as mempool; extern crate nimiq_primitives as primitives; -extern crate nimiq_vrf as vrf; use std::sync::Arc; @@ -20,7 +19,9 @@ use block::{ PbftProposal, ViewChangeProof, ViewChanges, }; use blockchain::blockchain::Blockchain; -use blockchain::reward_registry::SlashedSetSelector; +use blockchain::chain_info::ChainInfo; +use blockchain::slots::ForkProofInfos; +use blockchain_base::AbstractBlockchain; use bls::KeyPair; use database::WriteTransaction; use hash::{Blake2bHash, Hash}; @@ -140,34 +141,15 @@ impl BlockProducer { /// Creates the extrinsics for the next macro block. pub fn next_macro_extrinsics(&self, extra_data: Vec) -> MacroExtrinsics { - // Determine slashed sets without txn, so that it is not garbage collected yet. - // Get the number of the current epoch. - let epoch = policy::epoch_at(self.blockchain.height() + 1); - - // Get the slashed set for the previous epoch. - let slashed_set = self.blockchain.state().reward_registry().slashed_set( - epoch - 1, - SlashedSetSelector::All, - None, - ); - - // Get the slashed set for the current epoch. - let current_slashed_set = if policy::is_election_block_at(self.blockchain.height() + 1) { - // election blocks do not have a current slashed set as it holds no information - None - } else { - // all other macro blocks need to maintain the slashed set of the current epoch - Some(self.blockchain.state().reward_registry().slashed_set( - epoch, - SlashedSetSelector::All, - None, - )) - }; + let slashed_set = self + .blockchain + .slashed_set_at(self.blockchain.height() + 1) + .expect("Missing previous block for block production") + .next_slashed_set(self.blockchain.height() + 1); - // Create and return the macro block extrinsics. MacroExtrinsics { - slashed_set, - current_slashed_set, + slashed_set: slashed_set.prev_epoch_state.clone(), + current_slashed_set: slashed_set.current_epoch(), extra_data, } } @@ -272,19 +254,6 @@ impl BlockProducer { // Get and update the state. let state = self.blockchain.state(); - state - .reward_registry() - .commit_block( - txn, - &Block::Macro(MacroBlock { - header: header.clone(), - justification: None, - extrinsics: None, - }), - self.blockchain.view_number(), - ) - .expect("Failed to commit dummy block to reward registry"); - // Initialize the inherents vector. let mut inherents: Vec = vec![]; @@ -296,12 +265,17 @@ impl BlockProducer { // Add it to the header. header.validators = validators.into(); - // Also create the reward inherents from finalizing the previous epoch. - inherents.append( - &mut self - .blockchain - .finalize_previous_epoch(&self.blockchain.state(), &header), - ); + let dummy_macro_block = Block::Macro(MacroBlock { + header: header.clone(), + justification: None, + extrinsics: None, + }); + let prev_chain_info = state.main_chain(); + let fork_proof_infos = ForkProofInfos::empty(); + let chain_info = + ChainInfo::new(dummy_macro_block, prev_chain_info, &fork_proof_infos).unwrap(); + // For election blocks add reward and finalize epoch inherents. + inherents.append(&mut self.blockchain.finalize_previous_epoch(&state, &chain_info)); } // Create the slash inherents for the view changes. diff --git a/blockchain-albatross/src/blockchain.rs b/blockchain-albatross/src/blockchain.rs index c6fbb62e74..81070bfb21 100644 --- a/blockchain-albatross/src/blockchain.rs +++ b/blockchain-albatross/src/blockchain.rs @@ -15,8 +15,8 @@ use account::{Account, Inherent, InherentType}; use accounts::Accounts; use beserial::Serialize; use block::{ - Block, BlockError, BlockHeader, BlockType, ForkProof, MacroBlock, MacroExtrinsics, MacroHeader, - MicroBlock, ViewChange, ViewChangeProof, ViewChanges, + Block, BlockError, BlockHeader, BlockType, ForkProof, MacroBlock, MacroExtrinsics, MicroBlock, + ViewChange, ViewChangeProof, ViewChanges, }; #[cfg(feature = "metrics")] use blockchain_base::chain_metrics::BlockchainMetrics; @@ -41,7 +41,8 @@ use vrf::{AliasMethod, VrfSeed, VrfUseCase}; use crate::chain_info::ChainInfo; use crate::chain_store::ChainStore; -use crate::reward_registry::{EpochStateError, SlashRegistry, SlashedSetSelector}; +use crate::reward::{block_reward_for_epoch, genesis_parameters}; +use crate::slots::{get_slot_at, ForkProofInfos, SlashedSet}; use crate::transaction_cache::TransactionCache; pub type PushResult = blockchain_base::PushResult; @@ -119,17 +120,19 @@ pub struct Blockchain { #[cfg(feature = "metrics")] metrics: BlockchainMetrics, + + genesis_supply: Coin, + genesis_timestamp: u64, } pub struct BlockchainState { pub accounts: Accounts, pub transaction_cache: TransactionCache, - pub reward_registry: SlashRegistry, pub(crate) main_chain: ChainInfo, head_hash: Blake2bHash, - macro_head: MacroBlock, + macro_info: ChainInfo, macro_head_hash: Blake2bHash, election_head: MacroBlock, @@ -172,24 +175,16 @@ impl BlockchainState { /// This includes fork proof slashes and view changes. pub fn current_slashed_set(&self) -> BitSet { - self.reward_registry.slashed_set( - policy::epoch_at(self.block_number()), - SlashedSetSelector::All, - None, - ) + self.main_chain.slashed_set.current_epoch() } /// This includes fork proof slashes and view changes. pub fn last_slashed_set(&self) -> BitSet { - self.reward_registry.slashed_set( - policy::epoch_at(self.block_number()) - 1, - SlashedSetSelector::All, - None, - ) + self.main_chain.slashed_set.prev_epoch_state.clone() } - pub fn reward_registry(&self) -> &SlashRegistry { - &self.reward_registry + pub fn main_chain(&self) -> &ChainInfo { + &self.main_chain } } @@ -213,10 +208,17 @@ impl Blockchain { // Check that the correct genesis block is stored. let network_info = NetworkInfo::from_network_id(network_id); let genesis_info = chain_store.get_chain_info(network_info.genesis_hash(), false, None); - if !genesis_info.map(|i| i.on_main_chain).unwrap_or(false) { + if !genesis_info + .as_ref() + .map(|i| i.on_main_chain) + .unwrap_or(false) + { return Err(BlockchainError::InvalidGenesisBlock); } + let (genesis_supply, genesis_timestamp) = + genesis_parameters(genesis_info.unwrap().head.unwrap_macro_ref()); + // Load main chain from store. let main_chain = chain_store .get_chain_info(&head_hash, true, None) @@ -237,7 +239,7 @@ impl Blockchain { ) .ok_or(BlockchainError::FailedLoadingMainChain)?; let macro_head = match macro_chain_info.head { - Block::Macro(macro_head) => macro_head, + Block::Macro(ref macro_head) => macro_head, Block::Micro(_) => return Err(BlockchainError::InconsistentState), }; let macro_head_hash = macro_head.hash(); @@ -278,9 +280,6 @@ impl Blockchain { .saturating_sub(main_chain.head.block_number() + 1) ); - // Initialize SlashRegistry. - let slash_registry = SlashRegistry::new(env.clone(), Arc::clone(&chain_store)); - // Current slots and validators let current_slots = Self::slots_from_block(&election_head); @@ -309,10 +308,9 @@ impl Blockchain { state: RwLock::new(BlockchainState { accounts, transaction_cache, - reward_registry: slash_registry, main_chain, head_hash, - macro_head, + macro_info: macro_chain_info, macro_head_hash, election_head, election_head_hash, @@ -323,6 +321,8 @@ impl Blockchain { #[cfg(feature = "metrics")] metrics: BlockchainMetrics::default(), + genesis_supply, + genesis_timestamp, }) } @@ -340,6 +340,7 @@ impl Blockchain { _ => None, }) .unwrap(); + let (genesis_supply, genesis_timestamp) = genesis_parameters(genesis_macro_block); let main_chain = ChainInfo::initial(genesis_block.clone()); let head_hash = network_info.genesis_hash().clone(); @@ -359,9 +360,6 @@ impl Blockchain { // Initialize empty TransactionCache. let transaction_cache = TransactionCache::new(); - // Initialize SlashRegistry. - let slash_registry = SlashRegistry::new(env.clone(), Arc::clone(&chain_store)); - // current slots and validators let current_slots = Self::slots_from_block(&genesis_macro_block); let last_slots = Slots::default(); @@ -376,10 +374,9 @@ impl Blockchain { state: RwLock::new(BlockchainState { accounts, transaction_cache, - reward_registry: slash_registry, + macro_info: main_chain.clone(), main_chain, head_hash: head_hash.clone(), - macro_head: genesis_macro_block.clone(), macro_head_hash: head_hash.clone(), election_head: genesis_macro_block.clone(), election_head_hash: head_hash, @@ -390,6 +387,8 @@ impl Blockchain { #[cfg(feature = "metrics")] metrics: BlockchainMetrics::default(), + genesis_supply, + genesis_timestamp, }) } @@ -590,7 +589,7 @@ impl Blockchain { // the branch are the same. Then, we need to follow and compare. let mut view_numbers = vec![block.view_number()]; let mut current: (Blake2bHash, ChainInfo) = - (block.hash(), ChainInfo::new(block.clone())); + (block.hash(), ChainInfo::dummy(block.clone())); let mut prev: (Blake2bHash, ChainInfo) = (prev_info.head.hash(), prev_info.clone()); while !prev.1.on_main_chain { // Macro blocks are final @@ -860,7 +859,18 @@ impl Blockchain { } } - let chain_info = ChainInfo::new(block); + let fork_proof_infos = ForkProofInfos::fetch(&block, &self.chain_store, Some(&read_txn)) + .map_err(|err| { + warn!("Rejecting block - slash commit failed: {:?}", err); + PushError::InvalidSuccessor + })?; + let chain_info = match ChainInfo::new(block, &prev_info, &fork_proof_infos) { + Ok(info) => info, + Err(err) => { + warn!("Rejecting block - slash commit failed: {:?}", err); + return Err(PushError::InvalidSuccessor); + } + }; // Drop read transaction before calling other functions. drop(read_txn); @@ -908,55 +918,12 @@ impl Blockchain { return Err(PushError::DuplicateTransaction); } - // Get the slashed set used to finalize the previous epoch before garbage collecting it below. - // We need to keep track of the slashed set for the previous epoch as well as the current epoch: - // - Because fork proofs might happen after a macro block they are comitted to the previous epochs slashed set. - // Rewards are distributed with one epoch delay to acomodate those fork proofs. - // - Because macro blocks do only change the validator set when it is an election blockas well, - // the current slashed set needs to be stored because without all micro blocks - // of the current epoch it would be impossible to determine if a slot is allowed to produce - // a block or not. - let mut slashed_set: Option = None; - let mut current_slashed_set: Option = None; - let mut is_election_block = false; - - if let Block::Macro(ref macro_block) = chain_info.head { - // Get whole slashed set here. - slashed_set = Some(state.reward_registry.slashed_set( - policy::epoch_at(chain_info.head.block_number()) - 1, - SlashedSetSelector::All, - Some(&txn), - )); - // Macro blocks which do not have an election also need to keep track of the slashed set of the current - // epoch. Macro blocks with an election always have a current_slashed_set of None, as slashes are reset - // on election. - if !macro_block.is_election_block() { - let current_slashes = state.reward_registry.slashed_set( - policy::epoch_at(chain_info.head.block_number()), - SlashedSetSelector::All, - Some(&txn), - ); - current_slashed_set = Some(current_slashes); - } else { - is_election_block = true; - } - } - - if let Err(e) = state.reward_registry.commit_block( - &mut txn, - &chain_info.head, - prev_info.head.next_view_number(), - ) { - warn!("Rejecting block - slash commit failed: {:?}", e); - return Err(PushError::InvalidSuccessor); - } - // Commit block to AccountsTree. if let Err(e) = self.commit_accounts( &state, prev_info.head.next_view_number(), &mut txn, - &chain_info.head, + &chain_info, ) { warn!("Rejecting block - commit failed: {:?}", e); txn.abort(); @@ -968,7 +935,10 @@ impl Blockchain { drop(state); // Only now can we check macro extrinsics. + let mut is_election_block = false; if let Block::Macro(ref mut macro_block) = &mut chain_info.head { + is_election_block = macro_block.is_election_block(); + if is_election_block { let slots = self.next_slots(¯o_block.header.seed, Some(&txn)); if let Some(ref block_slots) = macro_block.header.validators { @@ -981,9 +951,13 @@ impl Blockchain { return Err(PushError::InvalidBlock(BlockError::InvalidValidators)); } } - // extrinsics are the same for macro blocks with and without election - let slashed_set = slashed_set.unwrap(); + // The final list of slashes from the previous epoch. + let slashed_set = chain_info.slashed_set.prev_epoch_state.clone(); + // Macro blocks which do not have an election also need to keep track of the slashed set of the current + // epoch. Macro blocks with an election always have a current_slashed_set of None, as slashes are reset + // on election. + let current_slashed_set = chain_info.slashed_set.current_epoch(); let mut computed_extrinsics = MacroExtrinsics::from_slashed_set(slashed_set, current_slashed_set); // The extra data is only available on the block. @@ -1018,7 +992,7 @@ impl Blockchain { state.transaction_cache.push_block(&chain_info.head); if let Block::Macro(ref macro_block) = chain_info.head { - state.macro_head = macro_block.clone(); + state.macro_info = chain_info.clone(); state.macro_head_hash = block_hash.clone(); if is_election_block { @@ -1111,7 +1085,7 @@ impl Blockchain { current = (state.head_hash.clone(), state.main_chain.clone()); // Check if ancestor is in current epoch - if ancestor.1.head.block_number() < state.macro_head.header.block_number { + if ancestor.1.head.block_number() < state.macro_info.head.block_number() { info!("Ancestor is in finalized epoch"); return Err(PushError::InvalidFork); } @@ -1135,11 +1109,6 @@ impl Blockchain { prev_info.head.view_number(), )?; - state - .reward_registry - .revert_block(&mut write_txn, ¤t.1.head) - .unwrap(); - cache_txn.revert_block(¤t.1.head); assert_eq!( @@ -1184,18 +1153,12 @@ impl Blockchain { Block::Macro(_) => unreachable!(), Block::Micro(ref micro_block) => { let result = if !cache_txn.contains_any(&fork_block.1.head) { - state - .reward_registry - .commit_block(&mut write_txn, &fork_block.1.head, prev_view_number) - .map_err(|_| PushError::InvalidBlock(BlockError::InvalidSlash)) - .and_then(|_| { - self.commit_accounts( - &state, - prev_view_number, - &mut write_txn, - &fork_block.1.head, - ) - }) + self.commit_accounts( + &state, + prev_view_number, + &mut write_txn, + &fork_block.1, + ) } else { Err(PushError::DuplicateTransaction) }; @@ -1294,8 +1257,9 @@ impl Blockchain { state: &BlockchainState, first_view_number: u32, txn: &mut WriteTransaction, - block: &Block, + chain_info: &ChainInfo, ) -> Result<(), PushError> { + let block = &chain_info.head; let accounts = &state.accounts; match block { @@ -1304,7 +1268,7 @@ impl Blockchain { if macro_block.is_election_block() { // On election the previous epoch needs to be finalized. // We can rely on `state` here, since we cannot revert macro blocks. - inherents.append(&mut self.finalize_previous_epoch(state, ¯o_block.header)); + inherents.append(&mut self.finalize_previous_epoch(state, chain_info)); } // Add slashes for view changes. @@ -1537,7 +1501,24 @@ impl Blockchain { // Drop read transaction before creating the write transaction. drop(read_txn); - let chain_info = ChainInfo::new(block); + // We don't know the exact distribution between fork proofs and view changes here. + // But as this block concludes the epoch, it is not of relevance anymore. + let chain_info = ChainInfo { + on_main_chain: false, + main_chain_successor: None, + slashed_set: SlashedSet { + view_change_epoch_state: macro_block + .extrinsics + .as_ref() + .unwrap() + .current_slashed_set + .clone(), + fork_proof_epoch_state: Default::default(), + prev_epoch_state: macro_block.extrinsics.as_ref().unwrap().slashed_set.clone(), + }, + head: block, + cum_tx_fees: transactions.iter().map(|tx| tx.fee).sum(), + }; self.extend_isolated_macro( chain_info.head.hash(), @@ -1560,10 +1541,6 @@ impl Blockchain { let state = self.state.read(); - let block_number = chain_info.head.block_number(); - - let timestamp = chain_info.head.timestamp(); - let macro_block = chain_info.head.unwrap_macro_ref(); let is_election_block = macro_block.is_election_block(); @@ -1578,23 +1555,6 @@ impl Blockchain { .current_slashed_set .clone(); - // `commit_epoch` switches the current reward of the current epoch into the previous epoch, - // which only needs to be done when a new epoch starts. - if is_election_block { - let result = state.reward_registry.commit_epoch( - &mut txn, - block_number, - timestamp, - transactions, - &slashed_set, - ); - - if let Err(e) = result { - warn!("Rejecting block - slash commit failed: {:?}", e); - return Err(PushError::InvalidSuccessor); - } - } - // We cannot check the accounts hash yet. // Apply transactions and inherents to AccountsTree. let slots = state @@ -1605,7 +1565,7 @@ impl Blockchain { // election blocks finalize the previous epoch. if is_election_block { - inherents.append(&mut self.finalize_previous_epoch(&state, ¯o_block.header)); + inherents.append(&mut self.finalize_previous_epoch(&state, &chain_info)); } // Commit epoch to AccountsTree. @@ -1678,7 +1638,7 @@ impl Blockchain { //state.transaction_cache.push_block(&chain_info.head); if let Block::Macro(ref macro_block) = chain_info.head { - state.macro_head = macro_block.clone(); + state.macro_info = chain_info.clone(); state.macro_head_hash = block_hash.clone(); if is_election_block { state.election_head = macro_block.clone(); @@ -1899,7 +1859,7 @@ impl Blockchain { pub fn macro_head(&self) -> MappedRwLockReadGuard { let guard = self.state.read(); - RwLockReadGuard::map(guard, |s| &s.macro_head) + RwLockReadGuard::map(guard, |s| s.macro_info.head.unwrap_macro_ref()) } pub fn macro_head_hash(&self) -> Blake2bHash { @@ -2016,9 +1976,11 @@ impl Blockchain { &slots_owned }; - state - .reward_registry - .get_slot_at(block_number, view_number, slots, Some(&txn)) + let prev_info = self + .chain_store + .get_chain_info_at(block_number - 1, false, Some(&txn))?; + + Some(get_slot_at(block_number, view_number, &prev_info, slots)) } pub fn state(&self) -> RwLockReadGuard { @@ -2115,17 +2077,13 @@ impl Blockchain { .collect::>() } - /// Get slash set of epoch at specific block number + /// Get slashed set at specific block number /// Returns slash set before applying block with that block_number (TODO Tests) - pub fn slashed_set_at( - &self, - epoch_number: u32, - block_number: u32, - set_selector: SlashedSetSelector, - ) -> Result { - let s = self.state.read(); - s.reward_registry - .slashed_set_at(epoch_number, block_number, set_selector, None) + pub fn slashed_set_at(&self, block_number: u32) -> Option { + let prev_info = self + .chain_store + .get_chain_info_at(block_number - 1, false, None)?; + Some(prev_info.slashed_set) } pub fn current_validators(&self) -> MappedRwLockReadGuard { @@ -2141,13 +2099,10 @@ impl Blockchain { pub fn finalize_previous_epoch( &self, state: &BlockchainState, - macro_header: &MacroHeader, + chain_info: &ChainInfo, ) -> Vec { - // make sure the given block is actually an election block, as only on election blocks the previous epoch can be finalized. - assert!( - macro_header.validators.is_some(), - "Trying to finalize_previous_epoch, but given macro header does not have an election!" - ); + let prev_macro_info = &state.macro_info; + let macro_header = &chain_info.head.unwrap_macro_ref().header; // It might be that we don't have any micro blocks, thus we need to look at the next macro block. let epoch = policy::epoch_at(macro_header.block_number) - 1; @@ -2166,12 +2121,17 @@ impl Blockchain { .validator_slots; // Slashed slots (including fork proofs) - let slashed_set = state - .reward_registry - .slashed_set(epoch, SlashedSetSelector::All, None); + let slashed_set = chain_info.slashed_set.prev_epoch_state.clone(); // Total reward for the previous epoch - let reward_pot: Coin = state.reward_registry.previous_reward(); + let block_reward = block_reward_for_epoch( + chain_info.head.unwrap_macro_ref(), + prev_macro_info.head.unwrap_macro_ref(), + self.genesis_supply, + self.genesis_timestamp, + ); + let tx_fees = chain_info.cum_tx_fees; + let reward_pot: Coin = block_reward + tx_fees; // Distribute reward between all slots and calculate the remainder let slot_reward = reward_pot / policy::SLOTS as u64; diff --git a/blockchain-albatross/src/chain_info.rs b/blockchain-albatross/src/chain_info.rs index c58907107a..f4c23eed06 100644 --- a/blockchain-albatross/src/chain_info.rs +++ b/blockchain-albatross/src/chain_info.rs @@ -4,32 +4,87 @@ use beserial::{Deserialize, ReadBytesExt, Serialize, SerializingError, WriteByte use block::{Block, BlockType, MacroExtrinsics, MicroExtrinsics}; use database::{FromDatabaseValue, IntoDatabaseValue}; use hash::Blake2bHash; +use primitives::coin::Coin; +use primitives::policy; -#[derive(Clone, PartialEq, Eq, Debug)] +use crate::slots::{apply_slashes, ForkProofInfos, SlashPushError, SlashedSet}; + +#[derive(Clone, Debug)] pub struct ChainInfo { pub head: Block, pub on_main_chain: bool, pub main_chain_successor: Option, + pub slashed_set: SlashedSet, + pub cum_tx_fees: Coin, } impl ChainInfo { + /// Creates the ChainInfo for the genesis block. pub fn initial(block: Block) -> Self { ChainInfo { head: block, on_main_chain: true, main_chain_successor: None, + slashed_set: Default::default(), + cum_tx_fees: Default::default(), } } - pub fn new(block: Block) -> Self { + /// Creates a new ChainInfo for a block given its predecessor. + /// We need the ForkProofInfos to retrieve the slashed sets just before a fork proof. + pub fn new( + block: Block, + prev_info: &ChainInfo, + fork_proof_infos: &ForkProofInfos, + ) -> Result { + assert_eq!(prev_info.head.block_number(), block.block_number() - 1); + let cum_tx_fees = if policy::is_macro_block_at(prev_info.head.block_number()) { + block.sum_transaction_fees() + } else { + prev_info.cum_tx_fees + block.sum_transaction_fees() + }; + + Ok(ChainInfo { + on_main_chain: false, + main_chain_successor: None, + slashed_set: apply_slashes(&block, prev_info, fork_proof_infos)?, + head: block, + cum_tx_fees, + }) + } + + /// Creates a new dummy ChainInfo for a block ignoring slashes and transaction fees. + pub fn dummy(block: Block) -> Self { ChainInfo { head: block, on_main_chain: false, main_chain_successor: None, + slashed_set: Default::default(), + cum_tx_fees: Default::default(), } } + + /// Calculates the base for the next block's slashed set. + /// If the current block is an election block, the next slashed set needs to account for + /// the new epoch. Otherwise, it is simply the current slashed set. + /// + /// Note that this method does not yet add new slashes to the set! + pub fn next_slashed_set(&self) -> SlashedSet { + self.slashed_set + .next_slashed_set(self.head.block_number() + 1) + } } +impl PartialEq for ChainInfo { + fn eq(&self, other: &Self) -> bool { + self.head.eq(&other.head) + && self.on_main_chain == other.on_main_chain + && self.main_chain_successor.eq(&other.main_chain_successor) + } +} + +impl Eq for ChainInfo {} + // Do not serialize the block body. // XXX Move this into Block.serialize_xxx()? impl Serialize for ChainInfo { @@ -50,6 +105,8 @@ impl Serialize for ChainInfo { } size += Serialize::serialize(&self.on_main_chain, writer)?; size += Serialize::serialize(&self.main_chain_successor, writer)?; + size += Serialize::serialize(&self.slashed_set, writer)?; + size += Serialize::serialize(&self.cum_tx_fees, writer)?; Ok(size) } @@ -70,6 +127,8 @@ impl Serialize for ChainInfo { } size += Serialize::serialized_size(&self.on_main_chain); size += Serialize::serialized_size(&self.main_chain_successor); + size += Serialize::serialized_size(&self.slashed_set); + size += Serialize::serialized_size(&self.cum_tx_fees); size } } @@ -83,11 +142,15 @@ impl Deserialize for ChainInfo { }; let on_main_chain = Deserialize::deserialize(reader)?; let main_chain_successor = Deserialize::deserialize(reader)?; + let slashed_set = Deserialize::deserialize(reader)?; + let cum_tx_fees = Deserialize::deserialize(reader)?; Ok(ChainInfo { head, on_main_chain, main_chain_successor, + slashed_set, + cum_tx_fees, }) } } diff --git a/blockchain-albatross/src/lib.rs b/blockchain-albatross/src/lib.rs index 7aa85f0616..609b9bb4e5 100644 --- a/blockchain-albatross/src/lib.rs +++ b/blockchain-albatross/src/lib.rs @@ -23,5 +23,6 @@ pub use blockchain::{Blockchain, ForkEvent}; pub mod blockchain; pub mod chain_info; pub mod chain_store; -pub mod reward_registry; +pub mod reward; +pub mod slots; pub mod transaction_cache; diff --git a/blockchain-albatross/src/reward.rs b/blockchain-albatross/src/reward.rs new file mode 100644 index 0000000000..27de8ab733 --- /dev/null +++ b/blockchain-albatross/src/reward.rs @@ -0,0 +1,60 @@ +use block::MacroBlock; +use primitives::coin::Coin; +use primitives::policy; +use std::convert::TryInto; + +/// Parses the genesis supply and timestamp from the genesis block. +pub fn genesis_parameters(genesis_block: &MacroBlock) -> (Coin, u64) { + assert_eq!(genesis_block.header.block_number, 0); + + let extrinsics = genesis_block.extrinsics.as_ref().unwrap(); + + let supply; + // Try reading supply from genesis block. + if extrinsics.extra_data.len() < 8 { + warn!("Genesis block does not encode initial supply, assuming zero."); + supply = Coin::ZERO; + } else { + let bytes = extrinsics.extra_data[..8] + .try_into() + .expect("slice has wrong size"); + supply = Coin::from_u64_unchecked(u64::from_be_bytes(bytes)); + } + + (supply, genesis_block.header.timestamp) +} + +/// Compute the block reward for an epoch from the current macro block, the previous macro block, +/// and the genesis block. +/// This does not include the reward from transaction fees. +pub fn block_reward_for_epoch_with_genesis( + current_block: &MacroBlock, + previous_macro: &MacroBlock, + genesis_block: &MacroBlock, +) -> Coin { + let (supply, timestamp) = genesis_parameters(genesis_block); + block_reward_for_epoch(current_block, previous_macro, supply, timestamp) +} + +/// Compute the block reward for an epoch from the current macro block, the previous macro block, +/// and the genesis parameters. +/// This does not include the reward from transaction fees. +pub fn block_reward_for_epoch( + current_block: &MacroBlock, + previous_macro: &MacroBlock, + genesis_supply: Coin, + genesis_timestamp: u64, +) -> Coin { + let genesis_supply_u64 = u64::from(genesis_supply); + let prev_supply = Coin::from_u64_unchecked(policy::supply_at( + genesis_supply_u64, + genesis_timestamp, + previous_macro.header.timestamp, + )); + let current_supply = Coin::from_u64_unchecked(policy::supply_at( + genesis_supply_u64, + genesis_timestamp, + current_block.header.timestamp, + )); + current_supply - prev_supply +} diff --git a/blockchain-albatross/src/reward_registry/mod.rs b/blockchain-albatross/src/reward_registry/mod.rs deleted file mode 100644 index 78a88ab3fd..0000000000 --- a/blockchain-albatross/src/reward_registry/mod.rs +++ /dev/null @@ -1,553 +0,0 @@ -use std::borrow::Cow; -use std::io; -use std::sync::Arc; - -use failure::Fail; - -use beserial::{Deserialize, Serialize}; -use block::{Block, MacroBlock, MicroBlock}; -use collections::bitset::BitSet; -use database::cursor::{ReadCursor, WriteCursor}; -use database::{ - AsDatabaseBytes, Database, DatabaseFlags, Environment, FromDatabaseValue, ReadTransaction, - Transaction, WriteTransaction, -}; -use primitives::coin::Coin; -use primitives::policy; -use primitives::slot::{Slot, SlotIndex, Slots}; -use transaction::Transaction as BlockchainTransaction; -use vrf::rng::Rng; -use vrf::VrfUseCase; - -use crate::chain_store::ChainStore; -use crate::reward_registry::reward_pot::RewardPot; - -mod reward_pot; - -pub struct SlashRegistry { - env: Environment, - chain_store: Arc, - slash_registry_db: Database, - reward_pot: RewardPot, -} - -// TODO: Better error messages -#[derive(Debug, Fail)] -pub enum SlashPushError { - #[fail(display = "Redundant fork proofs in block")] - DuplicateForkProof, - #[fail(display = "Block contains fork proof targeting a slot that was already slashed")] - SlotAlreadySlashed, - #[fail(display = "Block slashes slots in wrong epoch")] - InvalidEpochTarget, - #[fail(display = "Got block with unexpected block number")] - UnexpectedBlock, -} - -#[derive(Debug, Fail)] -pub enum EpochStateError { - #[fail(display = "Block precedes requested epoch")] - BlockPrecedesEpoch, - #[fail(display = "Requested epoch too old to be tracked at block number")] - HistoricEpoch, -} - -#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)] -pub enum SlashedSetSelector { - ViewChanges, - ForkProofs, - All, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -struct BlockDescriptor { - view_change_epoch_state: BitSet, - fork_proof_epoch_state: BitSet, - prev_epoch_state: BitSet, -} - -// TODO: Pass in active validator set + seed through parameters -// or always load from chain store? -impl SlashRegistry { - const SLASH_REGISTRY_DB_NAME: &'static str = "SlashRegistry"; - - pub fn new(env: Environment, chain_store: Arc) -> Self { - let slash_registry_db = env.open_database_with_flags( - SlashRegistry::SLASH_REGISTRY_DB_NAME.to_string(), - DatabaseFlags::UINT_KEYS, - ); - - let reward_pot = RewardPot::new(env.clone()); - - Self { - env, - chain_store, - slash_registry_db, - reward_pot, - } - } - - #[inline] - pub fn current_reward(&self) -> Coin { - self.reward_pot.current_reward() - } - - #[inline] - pub fn previous_reward(&self) -> Coin { - self.reward_pot.previous_reward() - } - - #[inline] - pub fn previous_supply(&self) -> Coin { - self.reward_pot.supply() - } - - /// Register slashes of block - /// * `block` - Block to commit - /// * `seed`- Seed of previous block - /// * `staking_contract` - Contract used to check minimum stakes - #[inline] - pub fn commit_block( - &self, - txn: &mut WriteTransaction, - block: &Block, - prev_view_number: u32, - ) -> Result<(), SlashPushError> { - match block { - Block::Macro(ref macro_block) => { - self.commit_macro_block(txn, macro_block, prev_view_number)?; - if macro_block.is_election_block() { - // interestingly for non election macro blocks there is nothing to do in the reward pot. - // They are just a block without any transaction. That is why there is no commit_macro_block - // in RewardPot. - self.reward_pot.commit_election_block(macro_block, txn); - // only garbage collect when a new epoch starts = the macro block has an election - self.gc(txn, policy::epoch_at(macro_block.header.block_number)); - } - Ok(()) - } - Block::Micro(ref micro_block) => { - self.reward_pot.commit_micro_block(micro_block, txn); - self.commit_micro_block(txn, micro_block, prev_view_number) - } - } - } - - /// Stores the reward of the closing epoch as well as its slashed set. - /// - /// This fundction should only be used for election blocks, as otherwise there is nothing to do - /// and thus will issue a warning while not doing anything. - pub fn commit_epoch( - &self, - txn: &mut WriteTransaction, - block_number: u32, - timestamp: u64, - transactions: &[BlockchainTransaction], - view_change_slashed_slots: &BitSet, - ) -> Result<(), SlashPushError> { - // epochs can only be committed on election blocks. - if !policy::is_election_block_at(block_number) { - warn!("Trying to commit an epoch but given block does not have an election!"); - return Ok(()); - } - - self.reward_pot.commit_epoch(timestamp, transactions, txn); - - // Just put the whole epochs slashed set at the macro blocks position. - // We don't have slash info for the current epoch though. - let descriptor = BlockDescriptor { - view_change_epoch_state: BitSet::new(), - fork_proof_epoch_state: BitSet::new(), - prev_epoch_state: view_change_slashed_slots.clone(), - }; - - // Put descriptor into database. - txn.put(&self.slash_registry_db, &block_number, &descriptor); - - self.gc(txn, policy::epoch_at(block_number)); - - Ok(()) - } - - fn get_epoch_state(&self, txn: &mut WriteTransaction, block_number: u32) -> BlockDescriptor { - let block_epoch = policy::epoch_at(block_number); - - // Lookup slash state. - let mut cursor = txn.cursor(&self.slash_registry_db); - - // Move cursor to first entry with a block number >= ours (or end of the database). - let _: Option<(u32, BlockDescriptor)> = cursor.seek_range_key(&block_number); - - // Then move cursor back by one. - let last_change: Option<(u32, BlockDescriptor)> = cursor.prev(); - - let prev_epoch_state: BitSet; - let view_change_epoch_state: BitSet; - let fork_proof_epoch_state: BitSet; - if let Some((change_block_number, change)) = last_change { - if change_block_number >= policy::first_block_of(block_epoch) { - // last_change was in current epoch - prev_epoch_state = change.prev_epoch_state; - view_change_epoch_state = change.view_change_epoch_state; - fork_proof_epoch_state = change.fork_proof_epoch_state; - } else if block_epoch > 0 - && change_block_number >= policy::first_block_of(block_epoch - 1) - { - // last_change was in previous epoch - // mingle slashes together - prev_epoch_state = change.view_change_epoch_state | change.fork_proof_epoch_state; - view_change_epoch_state = BitSet::new(); - fork_proof_epoch_state = BitSet::new(); - } else { - // no change in the last two epochs - prev_epoch_state = BitSet::new(); - view_change_epoch_state = BitSet::new(); - fork_proof_epoch_state = BitSet::new(); - } - } else { - // no change at all - prev_epoch_state = BitSet::new(); - view_change_epoch_state = BitSet::new(); - fork_proof_epoch_state = BitSet::new(); - } - - BlockDescriptor { - prev_epoch_state, - view_change_epoch_state, - fork_proof_epoch_state, - } - } - - fn slash_view_changes( - &self, - epoch_diff: &mut BitSet, - txn: &mut WriteTransaction, - block_number: u32, - view_number: u32, - prev_view_number: u32, - ) { - // Mark from view changes, ignoring duplicates. - for view in prev_view_number..view_number { - let slot_number = self - .get_slot_number_at(block_number, view, Some(&txn)) - .unwrap(); - epoch_diff.insert(slot_number as usize); - } - } - - fn commit_macro_block( - &self, - txn: &mut WriteTransaction, - block: &MacroBlock, - prev_view_number: u32, - ) -> Result<(), SlashPushError> { - let mut epoch_diff = BitSet::new(); - - let BlockDescriptor { - fork_proof_epoch_state, - prev_epoch_state, - mut view_change_epoch_state, - } = self.get_epoch_state(txn, block.header.block_number); - - self.slash_view_changes( - &mut epoch_diff, - txn, - block.header.block_number, - block.header.view_number, - prev_view_number, - ); - - // Apply slashes. - view_change_epoch_state |= epoch_diff; - - // Push block descriptor and remember slash hashes. - let descriptor = BlockDescriptor { - view_change_epoch_state, - fork_proof_epoch_state, - prev_epoch_state, - }; - - // Put descriptor into database. - txn.put( - &self.slash_registry_db, - &block.header.block_number, - &descriptor, - ); - - Ok(()) - } - - fn commit_micro_block( - &self, - txn: &mut WriteTransaction, - block: &MicroBlock, - prev_view_number: u32, - ) -> Result<(), SlashPushError> { - let block_epoch = policy::epoch_at(block.header.block_number); - let mut view_change_epoch_diff = BitSet::new(); - let mut fork_proof_epoch_diff = BitSet::new(); - let mut fork_proof_prev_epoch_diff = BitSet::new(); - - // Mark from fork proofs. - let fork_proofs = &block.extrinsics.as_ref().unwrap().fork_proofs; - for fork_proof in fork_proofs { - let block_number = fork_proof.header1.block_number; - let view_number = fork_proof.header1.view_number; - let slot_number = self - .get_slot_number_at(block_number, view_number, Some(&txn)) - .unwrap(); - - let slash_epoch = policy::epoch_at(block_number); - if block_epoch == slash_epoch { - if fork_proof_epoch_diff.contains(slot_number as usize) { - return Err(SlashPushError::DuplicateForkProof); - } - fork_proof_epoch_diff.insert(slot_number as usize); - } else if block_epoch == slash_epoch + 1 { - if fork_proof_prev_epoch_diff.contains(slot_number as usize) { - return Err(SlashPushError::DuplicateForkProof); - } - fork_proof_prev_epoch_diff.insert(slot_number as usize); - } else { - return Err(SlashPushError::InvalidEpochTarget); - } - } - - let BlockDescriptor { - mut fork_proof_epoch_state, - mut prev_epoch_state, - mut view_change_epoch_state, - } = self.get_epoch_state(txn, block.header.block_number); - - // Detect duplicate fork proof slashes - if !(&prev_epoch_state & &fork_proof_prev_epoch_diff).is_empty() - || !(&fork_proof_epoch_state & &fork_proof_epoch_diff).is_empty() - { - return Err(SlashPushError::SlotAlreadySlashed); - } - - self.slash_view_changes( - &mut view_change_epoch_diff, - txn, - block.header.block_number, - block.header.view_number, - prev_view_number, - ); - - // Apply slashes. - prev_epoch_state |= fork_proof_prev_epoch_diff; - fork_proof_epoch_state |= fork_proof_epoch_diff; - view_change_epoch_state |= view_change_epoch_diff; - - // Push block descriptor and remember slash hashes. - let descriptor = BlockDescriptor { - view_change_epoch_state, - fork_proof_epoch_state, - prev_epoch_state, - }; - - // Put descriptor into database. - txn.put( - &self.slash_registry_db, - &block.header.block_number, - &descriptor, - ); - - Ok(()) - } - - fn gc(&self, txn: &mut WriteTransaction, current_epoch: u32) { - let cutoff = policy::first_block_of_registry(current_epoch); - if cutoff == 0u32 { - // The first possible block is part of the registry. - // We can't delete any descriptors. - return; - } - - let mut cursor = txn.write_cursor(&self.slash_registry_db); - let mut pos: Option<(u32, BlockDescriptor)> = cursor.first(); - - while let Some((block_number, _)) = pos { - if block_number >= cutoff { - return; - } - cursor.remove(); - pos = cursor.next(); - } - } - - #[inline] - pub fn revert_block( - &self, - txn: &mut WriteTransaction, - block: &Block, - ) -> Result<(), SlashPushError> { - if let Block::Micro(ref block) = block { - self.reward_pot.revert_micro_block(block, txn); - self.revert_micro_block(txn, block) - } else { - unreachable!() - } - } - - fn revert_micro_block( - &self, - txn: &mut WriteTransaction, - block: &MicroBlock, - ) -> Result<(), SlashPushError> { - txn.remove(&self.slash_registry_db, &block.header.block_number); - Ok(()) - } - - /// Get slot and slot number for a given block and view number - pub fn get_slot_at( - &self, - block_number: u32, - view_number: u32, - slots: &Slots, - txn_option: Option<&Transaction>, - ) -> Option<(Slot, u16)> { - let slot_number = self.get_slot_number_at(block_number, view_number, txn_option)?; - let slot = slots - .get(SlotIndex::Slot(slot_number)) - .unwrap_or_else(|| panic!("Expected slot {} to exist", slot_number)); - Some((slot, slot_number)) - } - - /// TODO: Return an error for this, so we don't have to write the same error message for `expect`s all over the place. - pub(crate) fn get_slot_number_at( - &self, - block_number: u32, - view_number: u32, - txn_option: Option<&Transaction>, - ) -> Option { - // Get context - let prev_block = self - .chain_store - .get_block_at(block_number - 1, false, txn_option)?; - - // TODO: Refactor: - // * Use HashRng - // * Instead of sampling from the honest "validators" (which are slots really), - // create a list of slot numbers that are still enabled. Then sample from that and - // you'll have the proper slot_number instead of a "honest slot index". - - // Get slashed set for epoch ignoring fork proofs. - let slashed_set = self - .slashed_set_at( - policy::epoch_at(block_number), - block_number, - SlashedSetSelector::ViewChanges, - txn_option, - ) - .ok()?; - - // RNG for slot selection - let mut rng = prev_block - .seed() - .rng(VrfUseCase::SlotSelection, view_number); - - let slot_number = loop { - let slot_number = rng.next_u64_max(policy::SLOTS as u64) as u16; - - // Sample until we find a slot that is not slashed - if !slashed_set.contains(slot_number as usize) { - break slot_number; - } - }; - - Some(slot_number) - } - - /// Get latest known slash set of epoch - pub fn slashed_set( - &self, - epoch_number: u32, - set_selector: SlashedSetSelector, - txn_option: Option<&Transaction>, - ) -> BitSet { - self.slashed_set_at( - epoch_number, - policy::first_block_of(epoch_number + 2), - set_selector, - txn_option, - ) - .unwrap() - } - - fn select_slashed_set(descriptor: BlockDescriptor, selector: SlashedSetSelector) -> BitSet { - match selector { - SlashedSetSelector::ViewChanges => descriptor.view_change_epoch_state, - SlashedSetSelector::ForkProofs => descriptor.fork_proof_epoch_state, - SlashedSetSelector::All => { - descriptor.view_change_epoch_state | descriptor.fork_proof_epoch_state - } - } - } - - /// Get slash set of epoch at specific block number - /// Returns slash set before applying block with that block_number (TODO Tests) - /// This includes view change slashes, but excludes fork proof slashes! - pub fn slashed_set_at( - &self, - epoch_number: u32, - block_number: u32, - set_selector: SlashedSetSelector, - txn_option: Option<&Transaction>, - ) -> Result { - let epoch_start = policy::first_block_of(policy::epoch_at(block_number)); - - // Epoch cannot have slashes if in the future - if block_number < epoch_start { - return Err(EpochStateError::BlockPrecedesEpoch); - } - - // Epoch slashes are only tracked for two epochs - // First block of (epoch + 2) is fine because upper lookup bound is exclusive. - if block_number > policy::first_block_of(epoch_number + 2) { - return Err(EpochStateError::HistoricEpoch); - } - - let read_txn; - let txn = if let Some(txn) = txn_option { - txn - } else { - read_txn = ReadTransaction::new(&self.env); - &read_txn - }; - - // Lookup slash state. - let mut cursor = txn.cursor(&self.slash_registry_db); - // Move cursor to first entry with a block number >= ours (or end of the database). - let _: Option<(u32, BlockDescriptor)> = cursor.seek_range_key(&block_number); - // Then move cursor back by one. - let last_change: Option<(u32, BlockDescriptor)> = cursor.prev(); - - if let Some((change_block_number, change)) = last_change { - if change_block_number >= epoch_start { - Ok(SlashRegistry::select_slashed_set(change, set_selector)) - } else { - Ok(BitSet::new()) - } - } else { - Ok(BitSet::new()) - } - } -} - -impl AsDatabaseBytes for BlockDescriptor { - fn as_database_bytes(&self) -> Cow<[u8]> { - let v = Serialize::serialize_to_vec(&self); - Cow::Owned(v) - } -} - -impl FromDatabaseValue for BlockDescriptor { - fn copy_from_database(bytes: &[u8]) -> io::Result - where - Self: Sized, - { - let mut cursor = io::Cursor::new(bytes); - Ok(Deserialize::deserialize(&mut cursor)?) - } -} diff --git a/blockchain-albatross/src/reward_registry/reward_pot.rs b/blockchain-albatross/src/reward_registry/reward_pot.rs deleted file mode 100644 index 5092eacce7..0000000000 --- a/blockchain-albatross/src/reward_registry/reward_pot.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::convert::TryInto; - -use block::{MacroBlock, MicroBlock}; -use database::{Database, Environment, ReadTransaction, WriteTransaction}; -use primitives::coin::Coin; -use primitives::policy; -use transaction::Transaction as BlockchainTransaction; - -/// This struct is meant to calculate and keep track of the rewards for the current and previous -/// epochs. We need to remember the reward for the previous epoch because we only distribute rewards -/// for a given epoch on the subsequent epoch. -/// It also keeps track of the current supply, which is defined as all the coins that were ever -/// created (even if those coins were later burned), at the time of the last macro block (the end of -/// the previous epoch). We need the current supply in order to calculate the coinbase for the -/// rewards. -/// Finally, it also stores the timestamp and the supply of when the genesis block was created. We -/// need both these values to calculate the supply at any given time, which in turn we need to -/// calculate the coinbase. -pub struct RewardPot { - env: Environment, - reward_pot: Database, -} - -impl RewardPot { - /// The database name. - const REWARD_POT_DB_NAME: &'static str = "RewardPot"; - - /// The reward for the current epoch. - const CURRENT_REWARD_KEY: &'static str = "curr_reward"; - - /// The reward for the previous epoch. - const PREVIOUS_REWARD_KEY: &'static str = "prev_reward"; - - /// The current supply. - const SUPPLY_KEY: &'static str = "supply"; - - /// The supply at the genesis block. - const GENESIS_SUPPLY_KEY: &'static str = "gen_supply"; - - /// The time at the genesis block. - const GENESIS_TIME_KEY: &'static str = "gen_time"; - - /// Creates a new RewardPot database. - pub fn new(env: Environment) -> Self { - let reward_pot = env.open_database(RewardPot::REWARD_POT_DB_NAME.to_string()); - - Self { env, reward_pot } - } - - /// Returns the reward for the current epoch. - pub fn current_reward(&self) -> Coin { - let txn = ReadTransaction::new(&self.env); - - Coin::from_u64_unchecked( - txn.get(&self.reward_pot, Self::CURRENT_REWARD_KEY) - .unwrap_or(0), - ) - } - - /// Returns the reward for the previous epoch. - pub fn previous_reward(&self) -> Coin { - let txn = ReadTransaction::new(&self.env); - - Coin::from_u64_unchecked( - txn.get(&self.reward_pot, Self::PREVIOUS_REWARD_KEY) - .unwrap_or(0), - ) - } - - /// Returns the current supply. - pub fn supply(&self) -> Coin { - let txn = ReadTransaction::new(&self.env); - - Coin::from_u64_unchecked(txn.get(&self.reward_pot, Self::SUPPLY_KEY).unwrap_or(0)) - } - - /// Updates the RewardPot database for an entire epoch. All we need is the timestamp (to calculate - /// the coinbase) and the transaction list (to calculate the transaction fees). - /// This function is used for the Macro Sync feature. - pub(super) fn commit_epoch( - &self, - timestamp: u64, - transactions: &[BlockchainTransaction], - txn: &mut WriteTransaction, - ) { - // Start reward at zero. - let mut reward = 0; - - // Calculate the reward for the macro block (which corresponds to the coinbase) and the new - // supply. - let (macro_block_reward, new_supply) = self.reward_for_macro_block(timestamp, txn); - - // Update the reward. - reward += macro_block_reward; - - // Add all the transaction fees to the reward. - for transaction in transactions { - reward += u64::from(transaction.fee); - } - - // Set the current reward to zero. - txn.put(&self.reward_pot, Self::CURRENT_REWARD_KEY, &0u64); - - // Set the previous reward to the newly calculated reward. - txn.put( - &self.reward_pot, - Self::PREVIOUS_REWARD_KEY, - &u64::from(reward), - ); - - // Set the supply to the newly calculated supply. - txn.put(&self.reward_pot, Self::SUPPLY_KEY, &new_supply); - } - - /// Updates the RewardPot database for a macro block. It takes the whole macro block as input - /// but all we need is the timestamp. If the macro block is the genesis block, then it will set - /// the GENESIS_SUPPLY and GENESIS_TIME constants. - /// This function is used for normal block syncing. - pub(super) fn commit_election_block(&self, block: &MacroBlock, txn: &mut WriteTransaction) { - if block.is_election_block() { - // This is supposed to be the tuple (current_reward, new_supply). - let tuple: (u64, u64); - - // Get the timestamp from the block. - let timestamp = block.header.timestamp; - - // Check if this is the genesis block. - if block.header.block_number == 0 { - // Get the supply from the extra_data field of the genesis block. We assume that the - // first 8 bytes of the extra_data field will have the supply as a big-endian byte array. - let extrinsics = block.extrinsics.as_ref().unwrap(); - - let bytes = extrinsics.extra_data[..8] - .try_into() - .expect("slice has wrong size"); - - let supply = u64::from_be_bytes(bytes); - - // Set the genesis supply to be the current supply. - txn.put(&self.reward_pot, Self::GENESIS_SUPPLY_KEY, &supply); - - // Set the genesis time to be the current time. - txn.put(&self.reward_pot, Self::GENESIS_TIME_KEY, ×tamp); - - // Initialize the tuple to have current_reward = 0 and new_supply = genesis_supply. - tuple = (0, supply); - } else { - // Calculate the reward for the macro block (which corresponds to the coinbase) and the - // new supply. - tuple = self.reward_for_macro_block(timestamp, txn); - } - - // Set the current reward to zero. - txn.put(&self.reward_pot, Self::CURRENT_REWARD_KEY, &0u64); - - // Set the previous reward to the newly calculated reward. - txn.put(&self.reward_pot, Self::PREVIOUS_REWARD_KEY, &tuple.0); - - // Set the supply to the newly calculated supply. - txn.put(&self.reward_pot, Self::SUPPLY_KEY, &tuple.1); - } - // for macro blocks without election they are just a block without any transactions. - } - - /// Updates the RewardPot database for a micro block. It takes the whole micro block as input - /// since we need all the transactions in it. - /// This function is used for normal block syncing. - pub(super) fn commit_micro_block(&self, block: &MicroBlock, txn: &mut WriteTransaction) { - // Get the current reward from the RewardPot database. - let mut current_reward = txn - .get(&self.reward_pot, Self::CURRENT_REWARD_KEY) - .unwrap_or(0); - - // Get the transactions from the block. - let extrinsics = block.extrinsics.as_ref().unwrap(); - - // Add all the transaction fees to the current reward. - for transaction in extrinsics.transactions.iter() { - current_reward += u64::from(transaction.fee); - } - - // Set the current reward to the newly calculated reward. - txn.put(&self.reward_pot, Self::CURRENT_REWARD_KEY, ¤t_reward); - } - - /// Rolls back the RewardPot database for a micro block. It takes the whole micro block as input - /// since we need all the transactions in it. - /// This function is used for normal block syncing. - pub(super) fn revert_micro_block(&self, block: &MicroBlock, txn: &mut WriteTransaction) { - // Get the current reward from the RewardPot database. - let mut current_reward = txn - .get(&self.reward_pot, Self::CURRENT_REWARD_KEY) - .unwrap_or(0); - - // Get the transactions from the block. - let extrinsics = block.extrinsics.as_ref().unwrap(); - - // Subtract all the transaction fees from the current reward. - for transaction in extrinsics.transactions.iter() { - current_reward -= u64::from(transaction.fee); - } - - // Set the current reward to the newly calculated reward. - txn.put(&self.reward_pot, Self::CURRENT_REWARD_KEY, ¤t_reward); - } - - /// Calculates the reward for a macro block (which is just the coinbase) and the new supply after - /// the end of the corresponding epoch. All it needs is the timestamp of the macro block. - pub(super) fn reward_for_macro_block( - &self, - timestamp: u64, - txn: &mut WriteTransaction, - ) -> (u64, u64) { - // Get the current reward from the RewardPot database. - let mut reward = txn - .get(&self.reward_pot, Self::CURRENT_REWARD_KEY) - .unwrap_or(0); - - // Get the genesis supply from the RewardPot database. - let gen_supply = txn - .get(&self.reward_pot, Self::GENESIS_SUPPLY_KEY) - .unwrap_or(0); - - // Get the genesis time from the RewardPot database. - let gen_time = txn - .get(&self.reward_pot, Self::GENESIS_TIME_KEY) - .unwrap_or(0); - - // Get the current supply from the RewardPot database. - let current_supply = txn.get(&self.reward_pot, Self::SUPPLY_KEY).unwrap_or(0); - - // Calculate what the new supply should be using the supply curve formula from the - // policy file. - let new_supply = policy::supply_at(gen_supply, gen_time, timestamp); - - // Calculate the reward (coinbase) as the difference between the new supply and the - // current supply. - reward += new_supply - current_supply; - - // Return the reward and the new supply. - (reward, new_supply) - } -} diff --git a/blockchain-albatross/src/slots.rs b/blockchain-albatross/src/slots.rs new file mode 100644 index 0000000000..695592edf0 --- /dev/null +++ b/blockchain-albatross/src/slots.rs @@ -0,0 +1,242 @@ +use crate::chain_info::ChainInfo; +use crate::chain_store::ChainStore; +use beserial::{Deserialize, Serialize}; +use block::{Block, MicroBlock}; +use collections::bitset::BitSet; +use database::Transaction; +use failure::Fail; +use primitives::policy; +use primitives::slot::{Slot, SlotIndex, Slots}; +use vrf::{Rng, VrfUseCase}; + +#[derive(Debug, Fail)] +pub enum SlashPushError { + #[fail(display = "Redundant fork proofs in block")] + DuplicateForkProof, + #[fail(display = "Block contains fork proof targeting a slot that was already slashed")] + SlotAlreadySlashed, + #[fail(display = "Fork proof is from a wrong epoch")] + InvalidEpochTarget, + #[fail(display = "Fork proof infos don't match fork proofs")] + InvalidForkProofInfos, + #[fail(display = "Fork proof infos cannot be fetched (predecessor does not exist)")] + InvalidForkProofPredecessor, +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct SlashedSet { + pub view_change_epoch_state: BitSet, + pub fork_proof_epoch_state: BitSet, + pub prev_epoch_state: BitSet, +} + +impl SlashedSet { + /// Returns the full slashed set for the current epoch. + pub fn current_epoch(&self) -> BitSet { + &self.view_change_epoch_state | &self.fork_proof_epoch_state + } + + /// Calculates the slashed set that is used to determine the block producers of + /// the block at `next_block_number` if this is the slashed set after applying the previous + /// block. + /// + /// That means that this function will return an empty current slashed set for the + /// first block in an epoch. + pub fn next_slashed_set(&self, next_block_number: u32) -> Self { + // If `prev_info` is from the previous epoch, we switched epochs. + if policy::is_macro_block_at(next_block_number - 1) { + SlashedSet { + view_change_epoch_state: Default::default(), + fork_proof_epoch_state: Default::default(), + prev_epoch_state: &self.fork_proof_epoch_state | &self.view_change_epoch_state, + } + } else { + self.clone() + } + } +} + +/// Get slot and slot number for a given block and view number. +pub fn get_slot_at( + block_number: u32, + view_number: u32, + prev_chain_info: &ChainInfo, + slots: &Slots, +) -> (Slot, u16) { + // We need to have an up-to-date slashed set to be able to calculate the slots. + assert_eq!(prev_chain_info.head.block_number(), block_number - 1); + + let slot_number = get_slot_number_at(block_number, view_number, prev_chain_info); + let slot = slots + .get(SlotIndex::Slot(slot_number)) + .unwrap_or_else(|| panic!("Expected slot {} to exist", slot_number)); + (slot, slot_number) +} + +/// Calculate the slot number at a given block and view number. +/// In combination with the active `Slots`, this can be used to retrieve the validator info. +pub(crate) fn get_slot_number_at( + block_number: u32, + view_number: u32, + prev_chain_info: &ChainInfo, +) -> u16 { + // We need to have an up-to-date slashed set to be able to calculate the slots. + assert_eq!(prev_chain_info.head.block_number(), block_number - 1); + + // TODO: Refactor: + // * Use HashRng + // * Instead of sampling from the honest "validators" (which are slots really), + // create a list of slot numbers that are still enabled. Then sample from that and + // you'll have the proper slot_number instead of a "honest slot index". + + // Get slashed set for epoch ignoring fork proofs. + let slashed_set = prev_chain_info.next_slashed_set().view_change_epoch_state; + + // RNG for slot selection + let mut rng = prev_chain_info + .head + .seed() + .rng(VrfUseCase::SlotSelection, view_number); + + let slot_number = loop { + let slot_number = rng.next_u64_max(policy::SLOTS as u64) as u16; + + // Sample until we find a slot that is not slashed + if !slashed_set.contains(slot_number as usize) { + break slot_number; + } + }; + + slot_number +} + +/// Mark fork proofs in `SlashedSet`. +fn slash_fork_proofs( + block: &MicroBlock, + fork_proof_infos: &ForkProofInfos, + slashed_set: &mut SlashedSet, +) -> Result<(), SlashPushError> { + let block_epoch = policy::epoch_at(block.header.block_number); + + let mut fork_proof_epoch_diff = BitSet::new(); + let mut fork_proof_prev_epoch_diff = BitSet::new(); + + // Mark from fork proofs. + let fork_proofs = &block.extrinsics.as_ref().unwrap().fork_proofs; + if fork_proofs.len() != fork_proof_infos.predecessor_infos.len() { + return Err(SlashPushError::InvalidForkProofInfos); + } + for (fork_proof, prev_info) in fork_proofs + .iter() + .zip(fork_proof_infos.predecessor_infos.iter()) + { + let block_number = fork_proof.header1.block_number; + let view_number = fork_proof.header1.view_number; + if fork_proof.header1.parent_hash != prev_info.head.hash() { + return Err(SlashPushError::InvalidForkProofInfos); + } + let slot_number = get_slot_number_at(block_number, view_number, &prev_info); + + let slash_epoch = policy::epoch_at(block_number); + if block_epoch == slash_epoch { + if fork_proof_epoch_diff.contains(slot_number as usize) { + return Err(SlashPushError::DuplicateForkProof); + } + fork_proof_epoch_diff.insert(slot_number as usize); + } else if block_epoch == slash_epoch + 1 { + if fork_proof_prev_epoch_diff.contains(slot_number as usize) { + return Err(SlashPushError::DuplicateForkProof); + } + fork_proof_prev_epoch_diff.insert(slot_number as usize); + } else { + return Err(SlashPushError::InvalidEpochTarget); + } + + // Detect duplicate fork proof slashes + if !(&slashed_set.prev_epoch_state & &fork_proof_prev_epoch_diff).is_empty() + || !(&slashed_set.fork_proof_epoch_state & &fork_proof_epoch_diff).is_empty() + { + return Err(SlashPushError::SlotAlreadySlashed); + } + } + + slashed_set.fork_proof_epoch_state |= fork_proof_epoch_diff; + slashed_set.prev_epoch_state |= fork_proof_prev_epoch_diff; + + Ok(()) +} + +/// Mark view change slashes in `SlashedSet`. +fn slash_view_changes( + block: &MicroBlock, + prev_chain_info: &ChainInfo, + slashed_set: &mut SlashedSet, +) { + let prev_view_number = prev_chain_info.head.view_number(); + let view_number = block.header.view_number; + let block_number = block.header.block_number; + + // Mark from view changes, ignoring duplicates. + for view in prev_view_number..view_number { + let slot_number = get_slot_number_at(block_number, view, prev_chain_info); + slashed_set + .view_change_epoch_state + .insert(slot_number as usize); + } +} + +/// Given the previous block's `ChainInfo` and the current block, +/// this function constructs the new slashed set. +/// If a fork proof slashes a slot that has already been slashed, +/// the function returns a `SlashPushError`. +pub fn apply_slashes( + block: &Block, + prev_chain_info: &ChainInfo, + fork_proof_infos: &ForkProofInfos, +) -> Result { + // We need to have an up-to-date slashed set. + assert_eq!(&prev_chain_info.head.hash(), block.parent_hash()); + + let mut slashed_set = prev_chain_info.next_slashed_set(); + + // TODO: Do we apply slashes in macro blocks? + if let Block::Micro(ref micro_block) = block { + slash_fork_proofs(micro_block, fork_proof_infos, &mut slashed_set)?; + slash_view_changes(micro_block, prev_chain_info, &mut slashed_set); + } + + Ok(slashed_set) +} + +/// A struct containing additional information required to process fork proofs. +pub struct ForkProofInfos { + predecessor_infos: Vec, +} + +impl ForkProofInfos { + pub fn empty() -> Self { + ForkProofInfos { + predecessor_infos: vec![], + } + } + + pub fn fetch( + block: &Block, + chain_store: &ChainStore, + txn: Option<&Transaction>, + ) -> Result { + let mut infos = vec![]; + if let Block::Micro(micro_block) = block { + let fork_proofs = µ_block.extrinsics.as_ref().unwrap().fork_proofs; + for fork_proof in fork_proofs { + let prev_info = chain_store + .get_chain_info(&fork_proof.header1.parent_hash, false, txn) + .ok_or(SlashPushError::InvalidForkProofPredecessor)?; + infos.push(prev_info); + } + } + Ok(ForkProofInfos { + predecessor_infos: infos, + }) + } +} diff --git a/build-tools/src/genesis/albatross/mod.rs b/build-tools/src/genesis/albatross/mod.rs index 825d9f0e70..e0ccec6a65 100644 --- a/build-tools/src/genesis/albatross/mod.rs +++ b/build-tools/src/genesis/albatross/mod.rs @@ -199,7 +199,7 @@ impl GenesisBuilder { debug!("Slots: {:#?}", slots); // extrinsics - let extrinsics = MacroExtrinsics::from_slashed_set(BitSet::new(), None); + let extrinsics = MacroExtrinsics::from_slashed_set(BitSet::new(), BitSet::new()); let extrinsics_root = extrinsics.hash::(); debug!("Extrinsics root: {}", &extrinsics_root); diff --git a/primitives/block-albatross/Cargo.toml b/primitives/block-albatross/Cargo.toml index 1aaf9e9b76..06d0154dda 100644 --- a/primitives/block-albatross/Cargo.toml +++ b/primitives/block-albatross/Cargo.toml @@ -29,7 +29,7 @@ nimiq-hash = { path = "../../hash", version = "0.1" } nimiq-hash_derive = { path = "../../hash/hash_derive", version = "0.1" } nimiq-keys = { path = "../../keys", version = "0.1" } nimiq-macros = { path = "../../macros", version = "0.1" } -nimiq-primitives = { path = "..", version = "0.1", features = ["policy", "networks"] } +nimiq-primitives = { path = "..", version = "0.1", features = ["policy", "networks", "coin"] } nimiq-transaction = { path = "../transaction", version = "0.1" } nimiq-utils = { path = "../../utils", version = "0.1", features = ["merkle"] } nimiq-vrf = { path = "../../vrf", version = "0.1" } diff --git a/primitives/block-albatross/src/block.rs b/primitives/block-albatross/src/block.rs index 3cf624de5e..2f2d4a3988 100644 --- a/primitives/block-albatross/src/block.rs +++ b/primitives/block-albatross/src/block.rs @@ -4,7 +4,7 @@ use beserial::{Deserialize, ReadBytesExt, Serialize, SerializingError, WriteByte use block_base; use hash::{Blake2bHash, Hash, SerializeContent}; use hash_derive::SerializeContent; -use primitives::networks::NetworkId; +use primitives::{coin::Coin, networks::NetworkId}; use transaction::Transaction; use vrf::VrfSeed; @@ -126,6 +126,17 @@ impl Block { } } + pub fn sum_transaction_fees(&self) -> Coin { + match self { + Block::Macro(_) => Coin::ZERO, + Block::Micro(ref block) => block + .extrinsics + .as_ref() + .map(|ex| ex.transactions.iter().map(|tx| tx.fee).sum()) + .unwrap_or(Coin::ZERO), + } + } + pub fn unwrap_macro_ref(&self) -> &MacroBlock { if let Block::Macro(ref block) = self { block diff --git a/primitives/block-albatross/src/macro_block.rs b/primitives/block-albatross/src/macro_block.rs index a8bf4b4747..b87f7eb3d5 100644 --- a/primitives/block-albatross/src/macro_block.rs +++ b/primitives/block-albatross/src/macro_block.rs @@ -48,9 +48,8 @@ pub struct MacroHeader { pub struct MacroExtrinsics { /// The final list of slashes from the previous epoch. pub slashed_set: BitSet, - /// the slashed set of the current epoch. - /// None for election blocks as the slashed set of the current epoch is always an empty BitSet for those - pub current_slashed_set: Option, + /// The slashed set of the current epoch. + pub current_slashed_set: BitSet, #[beserial(len_type(u8))] pub extra_data: Vec, } @@ -75,7 +74,7 @@ impl MacroBlock { // CHECKME: Check for performance impl MacroExtrinsics { - pub fn from_slashed_set(slashed_set: BitSet, current_slashed_set: Option) -> Self { + pub fn from_slashed_set(slashed_set: BitSet, current_slashed_set: BitSet) -> Self { MacroExtrinsics { slashed_set, current_slashed_set, diff --git a/primitives/block-albatross/tests/mod.rs b/primitives/block-albatross/tests/mod.rs index f7c0d2b664..5285e09156 100644 --- a/primitives/block-albatross/tests/mod.rs +++ b/primitives/block-albatross/tests/mod.rs @@ -91,7 +91,7 @@ fn it_can_convert_macro_block_into_slots() { justification: None, extrinsics: Some(MacroExtrinsics { slashed_set: BitSet::new(), - current_slashed_set: None, + current_slashed_set: BitSet::new(), extra_data: vec![], }), }; diff --git a/primitives/src/coin.rs b/primitives/src/coin.rs index 48faecdb2e..e47e8a3cea 100644 --- a/primitives/src/coin.rs +++ b/primitives/src/coin.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; use std::fmt; use std::io; +use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Rem, Sub, SubAssign}; use std::str::FromStr; @@ -138,6 +139,12 @@ impl Zero for Coin { } } +impl Sum for Coin { + fn sum>(iter: I) -> Self { + iter.fold(Coin::ZERO, Add::add) + } +} + impl fmt::Display for Coin { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // NOTE: The format string has 5 decimal places hard-coded diff --git a/rpc-server/src/handlers/blockchain_albatross.rs b/rpc-server/src/handlers/blockchain_albatross.rs index 8b1f69cffb..d82e545d7f 100644 --- a/rpc-server/src/handlers/blockchain_albatross.rs +++ b/rpc-server/src/handlers/blockchain_albatross.rs @@ -440,16 +440,14 @@ impl BlockchainAlbatrossHandler { .extrinsics .as_ref() .map(|extrinsics| { - extrinsics.current_slashed_set.as_ref().map(|slashed_set| { - JsonValue::Array( - slashed_set - .iter() - .map(|slot_number| JsonValue::Number(slot_number.into())) - .collect(), - ) - }) + JsonValue::Array( + extrinsics + .current_slashed_set + .iter() + .map(|slot_number| JsonValue::Number(slot_number.into())) + .collect(), + ) }) - .unwrap_or(Some(JsonValue::Null)) .unwrap_or(JsonValue::Null); object! {