Skip to content

Commit

Permalink
Merge #4657
Browse files Browse the repository at this point in the history
4657: Prevent stray `accounts.toml` files from breaking upgrades r=fizyk20 a=fizyk20

The rewards calculation used to take the validators from `accounts.toml` when writing zero rewards to validators at genesis or at an upgrade point. This is now just replaced with an empty map, which has the same effect and doesn't depend on external data.

Co-authored-by: Bartłomiej Kamiński <[email protected]>
  • Loading branch information
casperlabs-bors-ng[bot] and fizyk20 authored Apr 25, 2024
2 parents a528ab8 + 591c3c2 commit 42276e4
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 161 deletions.
40 changes: 0 additions & 40 deletions node/src/components/contract_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,46 +394,6 @@ impl ContractRuntime {
}
.ignore()
}
ContractRuntimeRequest::GetTotalSupply {
request: total_supply_request,
responder,
} => {
trace!(?total_supply_request, "total supply request");
let metrics = Arc::clone(&self.metrics);
let data_access_layer = Arc::clone(&self.data_access_layer);
async move {
let start = Instant::now();
let result = data_access_layer.total_supply(total_supply_request);
metrics
.get_total_supply
.observe(start.elapsed().as_secs_f64());
trace!(?result, "total supply results");
responder.respond(result).await
}
.ignore()
}
ContractRuntimeRequest::GetRoundSeigniorageRate {
request: round_seigniorage_rate_request,
responder,
} => {
trace!(
?round_seigniorage_rate_request,
"round seigniorage rate request"
);
let metrics = Arc::clone(&self.metrics);
let data_access_layer = Arc::clone(&self.data_access_layer);
async move {
let start = Instant::now();
let result =
data_access_layer.round_seigniorage_rate(round_seigniorage_rate_request);
metrics
.get_round_seigniorage_rate
.observe(start.elapsed().as_secs_f64());
trace!(?result, "round seigniorage rate results");
responder.respond(result).await
}
.ignore()
}
ContractRuntimeRequest::GetTaggedValues {
request: tagged_values_request,
responder,
Expand Down
140 changes: 85 additions & 55 deletions node/src/components/contract_runtime/rewards.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#[cfg(test)]
mod tests;

use std::{collections::BTreeMap, ops::Range};
use std::{collections::BTreeMap, ops::Range, sync::Arc};

use casper_storage::data_access_layer::{
EraValidatorsRequest, RoundSeigniorageRateRequest, RoundSeigniorageRateResult,
TotalSupplyRequest, TotalSupplyResult,
use casper_storage::{
data_access_layer::{
DataAccessLayer, EraValidatorsRequest, RoundSeigniorageRateRequest,
RoundSeigniorageRateResult, TotalSupplyRequest, TotalSupplyResult,
},
global_state::state::{lmdb::LmdbGlobalState, StateProvider},
};
use futures::stream::{self, StreamExt as _, TryStreamExt as _};

use itertools::Itertools;
use num_rational::Ratio;
use num_traits::{CheckedAdd, CheckedMul};

Expand Down Expand Up @@ -62,6 +66,7 @@ impl CitedBlock {
pub(crate) struct RewardsInfo {
eras_info: BTreeMap<EraId, EraInfo>,
cited_blocks: Vec<CitedBlock>,
cited_block_height_start: u64,
}

/// The era information needed in the rewards computation:
Expand Down Expand Up @@ -97,7 +102,10 @@ pub enum RewardsError {
impl RewardsInfo {
pub async fn new<REv: ReactorEventT>(
effect_builder: EffectBuilder<REv>,
data_access_layer: Arc<DataAccessLayer<LmdbGlobalState>>,
protocol_version: ProtocolVersion,
activation_era_id: EraId,
maybe_upgraded_validators: Option<&BTreeMap<PublicKey, U512>>,
signature_rewards_max_delay: u64,
executable_block: ExecutableBlock,
) -> Result<Self, RewardsError> {
Expand All @@ -114,12 +122,20 @@ impl RewardsInfo {
.await
.ok_or(RewardsError::MissingSwitchBlock(previous_era_id))?;

// Here we do not substract 1, because we want one block more:
previous_era_switch_block_header
.height()
.saturating_sub(signature_rewards_max_delay)
if previous_era_id.is_genesis() || previous_era_id == activation_era_id {
// We do not attempt to reward blocks from before an upgrade!
previous_era_switch_block_header.height()
} else {
// Here we do not substract 1, because we want one block more:
previous_era_switch_block_header
.height()
.saturating_sub(signature_rewards_max_delay)
}
};
let range_to_fetch = cited_block_height_start..executable_block.height;

// We need just one block from before the upgrade to determine the validators in
// the following era.
let range_to_fetch = cited_block_height_start.saturating_sub(1)..executable_block.height;

let mut cited_blocks =
collect_past_blocks_batched(effect_builder, range_to_fetch.clone()).await?;
Expand All @@ -131,8 +147,13 @@ impl RewardsInfo {
"blocks fetched",
);

let eras_info =
Self::create_eras_info(effect_builder, current_era_id, cited_blocks.iter()).await?;
let eras_info = Self::create_eras_info(
data_access_layer,
activation_era_id,
current_era_id,
maybe_upgraded_validators,
cited_blocks.iter(),
)?;

cited_blocks.push(CitedBlock::from_executable_block(
executable_block,
Expand All @@ -142,23 +163,28 @@ impl RewardsInfo {
Ok(RewardsInfo {
eras_info,
cited_blocks,
cited_block_height_start,
})
}

#[cfg(test)]
pub fn new_testing(eras_info: BTreeMap<EraId, EraInfo>, cited_blocks: Vec<CitedBlock>) -> Self {
let cited_block_height_start = cited_blocks.first().map(|block| block.height).unwrap_or(0);
Self {
eras_info,
cited_blocks,
cited_block_height_start,
}
}

/// `block_hashs` is an iterator over the era ID to get the information about + the block
/// hash to query to have such information (which may not be from the same era).
async fn create_eras_info<REv: ReactorEventT>(
effect_builder: EffectBuilder<REv>,
fn create_eras_info<'a>(
data_access_layer: Arc<DataAccessLayer<LmdbGlobalState>>,
activation_era_id: EraId,
current_era_id: EraId,
mut cited_blocks: impl Iterator<Item = &CitedBlock>,
maybe_upgraded_validators: Option<&BTreeMap<PublicKey, U512>>,
mut cited_blocks: impl Iterator<Item = &'a CitedBlock>,
) -> Result<BTreeMap<EraId, EraInfo>, RewardsError> {
let oldest_block = cited_blocks.next();

Expand Down Expand Up @@ -193,29 +219,33 @@ impl RewardsInfo {
let num_eras_to_fetch =
eras_and_state_root_hashes.len() + usize::from(oldest_block_is_genesis);

let mut eras_info: BTreeMap<_, _> = stream::iter(eras_and_state_root_hashes)
.then(|(era_id, protocol_version, state_root_hash)| async move {
let era_validators_result = effect_builder
.get_era_validators_from_contract_runtime(EraValidatorsRequest::new(
state_root_hash,
protocol_version,
))
.await;
let msg = format!("{}", era_validators_result);
let weights = era_validators_result
.take_era_validators()
.ok_or(msg)
.map_err(RewardsError::FailedToFetchEra)?
// We consume the map to not clone the value:
.into_iter()
.find(|(key, _)| key == &era_id)
.ok_or_else(|| RewardsError::FailedToFetchEraValidators(state_root_hash))?
.1;
let data_access_layer = &data_access_layer;

let mut eras_info: BTreeMap<_, _> = eras_and_state_root_hashes
.into_iter()
.map(|(era_id, protocol_version, state_root_hash)| {
let weights = if let (true, Some(upgraded_validators)) =
(era_id == activation_era_id, maybe_upgraded_validators)
{
upgraded_validators.clone()
} else {
let request = EraValidatorsRequest::new(state_root_hash, protocol_version);
let era_validators_result = data_access_layer.era_validators(request);
let msg = format!("{}", era_validators_result);
era_validators_result
.take_era_validators()
.ok_or(msg)
.map_err(RewardsError::FailedToFetchEra)?
// We consume the map to not clone the value:
.into_iter()
.find(|(key, _)| key == &era_id)
.ok_or_else(|| RewardsError::FailedToFetchEraValidators(state_root_hash))?
.1
};

let total_supply_request =
TotalSupplyRequest::new(state_root_hash, protocol_version);
let total_supply = match effect_builder.get_total_supply(total_supply_request).await
{
let total_supply = match data_access_layer.total_supply(total_supply_request) {
TotalSupplyResult::RootNotFound
| TotalSupplyResult::MintNotFound
| TotalSupplyResult::ValueNotFound(_)
Expand All @@ -227,18 +257,16 @@ impl RewardsInfo {

let seignorate_rate_request =
RoundSeigniorageRateRequest::new(state_root_hash, protocol_version);
let seignorate_rate = match effect_builder
.get_round_seigniorage_rate(seignorate_rate_request)
.await
{
RoundSeigniorageRateResult::RootNotFound
| RoundSeigniorageRateResult::MintNotFound
| RoundSeigniorageRateResult::ValueNotFound(_)
| RoundSeigniorageRateResult::Failure(_) => {
return Err(RewardsError::FailedToFetchSeigniorageRate);
}
RoundSeigniorageRateResult::Success { rate } => rate,
};
let seignorate_rate =
match data_access_layer.round_seigniorage_rate(seignorate_rate_request) {
RoundSeigniorageRateResult::RootNotFound
| RoundSeigniorageRateResult::MintNotFound
| RoundSeigniorageRateResult::ValueNotFound(_)
| RoundSeigniorageRateResult::Failure(_) => {
return Err(RewardsError::FailedToFetchSeigniorageRate);
}
RoundSeigniorageRateResult::Success { rate } => rate,
};

let reward_per_round = seignorate_rate * total_supply;
let total_weights = weights.values().copied().sum();
Expand All @@ -252,8 +280,7 @@ impl RewardsInfo {
},
))
})
.try_collect()
.await?;
.try_collect()?;

// We cannot get the genesis info from a root hash, so we copy it from era 1 when needed.
if oldest_block_is_genesis {
Expand Down Expand Up @@ -352,6 +379,7 @@ impl EraInfo {
/// It is done in 2 steps so that it is easier to unit test the rewards calculation.
pub(crate) async fn fetch_data_and_calculate_rewards_for_era<REv: ReactorEventT>(
effect_builder: EffectBuilder<REv>,
data_access_layer: Arc<DataAccessLayer<LmdbGlobalState>>,
chainspec: &Chainspec,
executable_block: ExecutableBlock,
) -> Result<BTreeMap<PublicKey, U512>, RewardsError> {
Expand All @@ -366,16 +394,18 @@ pub(crate) async fn fetch_data_and_calculate_rewards_for_era<REv: ReactorEventT>
{
// Special case: genesis block and immediate switch blocks do not yield any reward, because
// there is no block producer, and no signatures from previous blocks to be rewarded:
Ok(chainspec
.network_config
.accounts_config
.validators()
.map(|account| (account.public_key.clone(), U512::zero()))
.collect())
Ok(BTreeMap::new())
} else {
let rewards_info = RewardsInfo::new(
effect_builder,
data_access_layer,
chainspec.protocol_version(),
chainspec.protocol_config.activation_point.era_id(),
chainspec
.protocol_config
.global_state_update
.as_ref()
.and_then(|gsu| gsu.validators.as_ref()),
chainspec.core_config.signature_rewards_max_delay,
executable_block,
)
Expand Down Expand Up @@ -442,7 +472,7 @@ pub(crate) fn rewards_for_era(
for (signature_rewards, signed_block_height) in block
.rewarded_signatures
.iter()
.zip((0..block.height).rev())
.zip((rewards_info.cited_block_height_start..block.height).rev())
{
let signed_block_era = rewards_info.era_for_block_height(signed_block_height)?;
let validators_providing_signature =
Expand Down
2 changes: 1 addition & 1 deletion node/src/components/contract_runtime/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub(super) async fn exec_or_requeue<REv>(
executable_block.rewards = Some(if chainspec.core_config.compute_rewards {
let rewards = match rewards::fetch_data_and_calculate_rewards_for_era(
effect_builder,
data_access_layer.clone(),
chainspec.as_ref(),
executable_block.clone(),
)
Expand All @@ -106,7 +107,6 @@ pub(super) async fn exec_or_requeue<REv>(

rewards
} else {
//TODO instead, use a list of all the validators with 0
BTreeMap::new()
});
}
Expand Down
34 changes: 1 addition & 33 deletions node/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ use casper_storage::{
tagged_values::{TaggedValuesRequest, TaggedValuesResult},
AddressableEntityResult, BalanceRequest, BalanceResult, EraValidatorsRequest,
EraValidatorsResult, ExecutionResultsChecksumResult, PutTrieRequest, PutTrieResult,
QueryRequest, QueryResult, RoundSeigniorageRateRequest, RoundSeigniorageRateResult,
TotalSupplyRequest, TotalSupplyResult, TrieRequest, TrieResult,
QueryRequest, QueryResult, TrieRequest, TrieResult,
},
DbRawBytesSpec,
};
Expand Down Expand Up @@ -2076,37 +2075,6 @@ impl<REv> EffectBuilder<REv> {
.await
}

/// Returns the total supply from the given `root_hash`.
///
/// This operation is read only.
pub(crate) async fn get_total_supply(self, request: TotalSupplyRequest) -> TotalSupplyResult
where
REv: From<ContractRuntimeRequest>,
{
self.make_request(
move |responder| ContractRuntimeRequest::GetTotalSupply { request, responder },
QueueKind::ContractRuntime,
)
.await
}

/// Returns the seigniorage rate from the given `root_hash`.
///
/// This operation is read only.
pub(crate) async fn get_round_seigniorage_rate(
self,
request: RoundSeigniorageRateRequest,
) -> RoundSeigniorageRateResult
where
REv: From<ContractRuntimeRequest>,
{
self.make_request(
move |responder| ContractRuntimeRequest::GetRoundSeigniorageRate { request, responder },
QueueKind::ContractRuntime,
)
.await
}

/// Requests a query be executed on the Contract Runtime component.
pub(crate) async fn get_tagged_values(self, request: TaggedValuesRequest) -> TaggedValuesResult
where
Expand Down
Loading

0 comments on commit 42276e4

Please sign in to comment.