diff --git a/chain/chain/src/approval_verification.rs b/chain/chain/src/approval_verification.rs new file mode 100644 index 00000000000..5a1b3c8561c --- /dev/null +++ b/chain/chain/src/approval_verification.rs @@ -0,0 +1,96 @@ +use near_chain_primitives::error::Error; +use near_crypto::Signature; +use near_primitives::{ + block_header::{Approval, ApprovalInner}, + epoch_info::EpochInfo, + errors::EpochError, + hash::CryptoHash, + types::{AccountId, ApprovalStake, Balance, BlockHeight}, +}; +use std::{collections::HashSet, sync::Arc}; + +pub fn verify_approval_with_approvers_info( + prev_block_hash: &CryptoHash, + prev_block_height: BlockHeight, + block_height: BlockHeight, + approvals: &[Option>], + info: Vec<(ApprovalStake, bool)>, +) -> Result { + if approvals.len() > info.len() { + return Ok(false); + } + + let message_to_sign = Approval::get_data_for_sig( + &if prev_block_height + 1 == block_height { + ApprovalInner::Endorsement(*prev_block_hash) + } else { + ApprovalInner::Skip(prev_block_height) + }, + block_height, + ); + + for ((validator, is_slashed), may_be_signature) in info.into_iter().zip(approvals.iter()) { + if let Some(signature) = may_be_signature { + if is_slashed || !signature.verify(message_to_sign.as_ref(), &validator.public_key) { + return Ok(false); + } + } + } + Ok(true) +} + +/// Verify approvals and check threshold, but ignore next epoch approvals and slashing +pub fn verify_approvals_and_threshold_orphan( + can_approved_block_be_produced: &dyn Fn( + &[Option>], + &[(Balance, Balance, bool)], + ) -> bool, + prev_block_hash: &CryptoHash, + prev_block_height: BlockHeight, + block_height: BlockHeight, + approvals: &[Option>], + epoch_info: Arc, +) -> Result<(), Error> { + let block_approvers = get_heuristic_block_approvers_ordered(&epoch_info)?; + let message_to_sign = Approval::get_data_for_sig( + &if prev_block_height + 1 == block_height { + ApprovalInner::Endorsement(*prev_block_hash) + } else { + ApprovalInner::Skip(prev_block_height) + }, + block_height, + ); + + for (validator, may_be_signature) in block_approvers.iter().zip(approvals.iter()) { + if let Some(signature) = may_be_signature { + if !signature.verify(message_to_sign.as_ref(), &validator.public_key) { + return Err(Error::InvalidApprovals); + } + } + } + let stakes = block_approvers + .iter() + .map(|stake| (stake.stake_this_epoch, stake.stake_next_epoch, false)) + .collect::>(); + if !can_approved_block_be_produced(approvals, &stakes) { + Err(Error::NotEnoughApprovals) + } else { + Ok(()) + } +} + +fn get_heuristic_block_approvers_ordered( + epoch_info: &EpochInfo, +) -> Result, EpochError> { + let mut result = vec![]; + let mut validators: HashSet = HashSet::new(); + for validator_id in epoch_info.block_producers_settlement().into_iter() { + let validator_stake = epoch_info.get_validator(*validator_id); + let account_id = validator_stake.account_id(); + if validators.insert(account_id.clone()) { + result.push(validator_stake.get_approval_stake(false)); + } + } + + Ok(result) +} diff --git a/chain/chain/src/chain.rs b/chain/chain/src/chain.rs index 366aa522892..def0492a77c 100644 --- a/chain/chain/src/chain.rs +++ b/chain/chain/src/chain.rs @@ -1,3 +1,4 @@ +use crate::approval_verification::verify_approval_with_approvers_info; use crate::block_processing_utils::{ ApplyChunksDoneWaiter, ApplyChunksStillApplying, BlockPreprocessInfo, BlockProcessingArtifact, BlocksInProcessing, @@ -1061,11 +1062,13 @@ impl Chain { // producer, confirmation signatures and finality info. if *provenance != Provenance::PRODUCED { // first verify aggregated signature - if !self.epoch_manager.verify_approval( + let info = self.epoch_manager.get_epoch_block_approvers_ordered(prev_header.hash())?; + if !verify_approval_with_approvers_info( prev_header.hash(), prev_header.height(), header.height(), header.approvals(), + info, )? { return Err(Error::InvalidApprovals); }; diff --git a/chain/chain/src/chain_update.rs b/chain/chain/src/chain_update.rs index b03946b0947..42944b3e813 100644 --- a/chain/chain/src/chain_update.rs +++ b/chain/chain/src/chain_update.rs @@ -1,8 +1,8 @@ +use crate::approval_verification::verify_approvals_and_threshold_orphan; use crate::block_processing_utils::BlockPreprocessInfo; use crate::chain::collect_receipts_from_response; use crate::metrics::{SHARD_LAYOUT_NUM_SHARDS, SHARD_LAYOUT_VERSION}; use crate::store::{ChainStore, ChainStoreAccess, ChainStoreUpdate}; - use crate::types::{ ApplyChunkBlockContext, ApplyChunkResult, ApplyChunkShardContext, RuntimeAdapter, RuntimeStorageConfig, @@ -346,8 +346,8 @@ impl<'a> ChainUpdate<'a> { let height = header.height(); let epoch_id = header.epoch_id(); let approvals = header.approvals(); - self.epoch_manager.verify_approvals_and_threshold_orphan( - epoch_id, + let epoch_info = self.epoch_manager.get_epoch_info(epoch_id)?; + verify_approvals_and_threshold_orphan( &|approvals, stakes| { Doomslug::can_approved_block_be_produced( self.doomslug_threshold_mode, @@ -359,6 +359,7 @@ impl<'a> ChainUpdate<'a> { prev_height, height, approvals, + epoch_info, ) } diff --git a/chain/chain/src/lib.rs b/chain/chain/src/lib.rs index d1e9a3f11fb..1da6776cf1d 100644 --- a/chain/chain/src/lib.rs +++ b/chain/chain/src/lib.rs @@ -13,6 +13,7 @@ pub use store::{ pub use store_validator::{ErrorMessage, StoreValidator}; pub use types::{Block, BlockHeader, BlockStatus, ChainGenesis, LatestKnown, Provenance}; +mod approval_verification; mod block_processing_utils; pub mod blocks_delay_tracker; pub mod chain; diff --git a/chain/chain/src/test_utils/kv_runtime.rs b/chain/chain/src/test_utils/kv_runtime.rs index e82262ce07b..1d3cc421cd1 100644 --- a/chain/chain/src/test_utils/kv_runtime.rs +++ b/chain/chain/src/test_utils/kv_runtime.rs @@ -18,7 +18,6 @@ use near_primitives::account::{AccessKey, Account}; use near_primitives::apply::ApplyChunkReason; use near_primitives::bandwidth_scheduler::BandwidthRequests; use near_primitives::block::Tip; -use near_primitives::block_header::{Approval, ApprovalInner}; use near_primitives::congestion_info::{CongestionInfo, ExtendedCongestionInfo}; use near_primitives::epoch_block_info::BlockInfo; use near_primitives::epoch_info::EpochInfo; @@ -936,64 +935,6 @@ impl EpochManagerAdapter for MockEpochManager { Ok(true) } - fn verify_approval( - &self, - _prev_block_hash: &CryptoHash, - _prev_block_height: BlockHeight, - _block_height: BlockHeight, - _approvals: &[Option>], - ) -> Result { - Ok(true) - } - - fn verify_approval_with_approvers_info( - &self, - _prev_block_hash: &CryptoHash, - _prev_block_height: BlockHeight, - _block_height: BlockHeight, - _approvals: &[Option>], - _info: Vec<(ApprovalStake, bool)>, - ) -> Result { - Ok(true) - } - - fn verify_approvals_and_threshold_orphan( - &self, - epoch_id: &EpochId, - can_approved_block_be_produced: &dyn Fn( - &[Option>], - &[(Balance, Balance, bool)], - ) -> bool, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - ) -> Result<(), Error> { - let validators = self.get_block_producers(self.get_valset_for_epoch(epoch_id)?); - let message_to_sign = Approval::get_data_for_sig( - &if prev_block_height + 1 == block_height { - ApprovalInner::Endorsement(*prev_block_hash) - } else { - ApprovalInner::Skip(prev_block_height) - }, - block_height, - ); - - for (validator, may_be_signature) in validators.iter().zip(approvals.iter()) { - if let Some(signature) = may_be_signature { - if !signature.verify(message_to_sign.as_ref(), validator.public_key()) { - return Err(Error::InvalidApprovals); - } - } - } - let stakes = validators.iter().map(|stake| (stake.stake(), 0, false)).collect::>(); - if !can_approved_block_be_produced(approvals, &stakes) { - Err(Error::NotEnoughApprovals) - } else { - Ok(()) - } - } - fn verify_chunk_endorsement_signature( &self, _endorsement: &ChunkEndorsement, diff --git a/chain/epoch-manager/src/adapter.rs b/chain/epoch-manager/src/adapter.rs index d2c55c3feb7..ff99000dbfc 100644 --- a/chain/epoch-manager/src/adapter.rs +++ b/chain/epoch-manager/src/adapter.rs @@ -2,7 +2,7 @@ use crate::EpochManagerHandle; use near_chain_primitives::Error; use near_crypto::Signature; use near_primitives::block::Tip; -use near_primitives::block_header::{Approval, ApprovalInner, BlockHeader}; +use near_primitives::block_header::BlockHeader; use near_primitives::epoch_block_info::BlockInfo; use near_primitives::epoch_info::EpochInfo; use near_primitives::epoch_manager::{EpochConfig, ShardConfig}; @@ -391,40 +391,6 @@ pub trait EpochManagerAdapter: Send + Sync { shard_id: ShardId, ) -> Result; - /// Verify aggregated bls signature - fn verify_approval( - &self, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - ) -> Result; - - /// Verify aggregated bls signature given block approvers info - fn verify_approval_with_approvers_info( - &self, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - info: Vec<(ApprovalStake, bool)>, - ) -> Result; - - /// Verify approvals and check threshold, but ignore next epoch approvals and slashing - fn verify_approvals_and_threshold_orphan( - &self, - epoch_id: &EpochId, - can_approved_block_be_produced: &dyn Fn( - &[Option>], - // (stake this in epoch, stake in next epoch, is_slashed) - &[(Balance, Balance, bool)], - ) -> bool, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - ) -> Result<(), Error>; - fn verify_chunk_endorsement_signature( &self, endorsement: &ChunkEndorsement, @@ -967,102 +933,6 @@ impl EpochManagerAdapter for EpochManagerHandle { Ok(signature.verify(chunk_hash.as_ref(), chunk_producer.public_key())) } - fn verify_approval( - &self, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - ) -> Result { - let info = { - let epoch_manager = self.read(); - epoch_manager.get_all_block_approvers_ordered(prev_block_hash)? - }; - self.verify_approval_with_approvers_info( - prev_block_hash, - prev_block_height, - block_height, - approvals, - info, - ) - } - - fn verify_approval_with_approvers_info( - &self, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - info: Vec<(ApprovalStake, bool)>, - ) -> Result { - if approvals.len() > info.len() { - return Ok(false); - } - - let message_to_sign = Approval::get_data_for_sig( - &if prev_block_height + 1 == block_height { - ApprovalInner::Endorsement(*prev_block_hash) - } else { - ApprovalInner::Skip(prev_block_height) - }, - block_height, - ); - - for ((validator, is_slashed), may_be_signature) in info.into_iter().zip(approvals.iter()) { - if let Some(signature) = may_be_signature { - if is_slashed || !signature.verify(message_to_sign.as_ref(), &validator.public_key) - { - return Ok(false); - } - } - } - Ok(true) - } - - fn verify_approvals_and_threshold_orphan( - &self, - epoch_id: &EpochId, - can_approved_block_be_produced: &dyn Fn( - &[Option>], - &[(Balance, Balance, bool)], - ) -> bool, - prev_block_hash: &CryptoHash, - prev_block_height: BlockHeight, - block_height: BlockHeight, - approvals: &[Option>], - ) -> Result<(), Error> { - let info = { - let epoch_manager = self.read(); - epoch_manager.get_heuristic_block_approvers_ordered(epoch_id)? - }; - - let message_to_sign = Approval::get_data_for_sig( - &if prev_block_height + 1 == block_height { - ApprovalInner::Endorsement(*prev_block_hash) - } else { - ApprovalInner::Skip(prev_block_height) - }, - block_height, - ); - - for (validator, may_be_signature) in info.iter().zip(approvals.iter()) { - if let Some(signature) = may_be_signature { - if !signature.verify(message_to_sign.as_ref(), &validator.public_key) { - return Err(Error::InvalidApprovals); - } - } - } - let stakes = info - .iter() - .map(|stake| (stake.stake_this_epoch, stake.stake_next_epoch, false)) - .collect::>(); - if !can_approved_block_be_produced(approvals, &stakes) { - Err(Error::NotEnoughApprovals) - } else { - Ok(()) - } - } - fn verify_chunk_endorsement_signature( &self, endorsement: &ChunkEndorsement, diff --git a/chain/epoch-manager/src/lib.rs b/chain/epoch-manager/src/lib.rs index 3d77a6112da..e677a5d30fb 100644 --- a/chain/epoch-manager/src/lib.rs +++ b/chain/epoch-manager/src/lib.rs @@ -1145,27 +1145,6 @@ impl EpochManager { }) } - /// get_heuristic_block_approvers_ordered: block producers for epoch - /// get_all_block_producers_ordered: block producers for epoch, slashing info - /// get_all_block_approvers_ordered: block producers for epoch, slashing info, sometimes block producers for next epoch - pub fn get_heuristic_block_approvers_ordered( - &self, - epoch_id: &EpochId, - ) -> Result, EpochError> { - let epoch_info = self.get_epoch_info(epoch_id)?; - let mut result = vec![]; - let mut validators: HashSet = HashSet::new(); - for validator_id in epoch_info.block_producers_settlement().into_iter() { - let validator_stake = epoch_info.get_validator(*validator_id); - let account_id = validator_stake.account_id(); - if validators.insert(account_id.clone()) { - result.push(validator_stake.get_approval_stake(false)); - } - } - - Ok(result) - } - pub fn get_all_block_approvers_ordered( &self, parent_hash: &CryptoHash,