Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Fixed the total active stake accounting in PoS when validators move
between active and inactive states. ([\#4833](https://github.com/namada-
net/namada/pull/4833))
17 changes: 17 additions & 0 deletions crates/node/src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,23 @@ where
_ => None,
};

// Temporary migration code to fix the total active stake value
if let Some(height) =
std::env::var("NAMADA_MIGRATION_HEIGHT").ok().map(|height| {
<BlockHeight as std::str::FromStr>::from_str(height.trim())
.expect(
"Invalid block height set in \
`NAMADA_MIGRATION_HEIGHT` env var",
)
})
{
if height == height_to_commit {
proof_of_stake::fix_total_active_stake::<_, governance::Store<_>>(
&mut self.state,
).expect("Must be able to fix total active stake")
}
}

self.state
.commit_block()
.expect("Encountered a storage error while committing a block");
Expand Down
104 changes: 101 additions & 3 deletions crates/proof_of_stake/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub use namada_state::{
pub use namada_systems::proof_of_stake::*;
use namada_systems::{governance, trans_token};
pub use parameters::{OwnedPosParams, PosParams};
use storage::write_validator_name;
use storage::{update_total_active_deltas, write_validator_name};
pub use types::GenesisValidator;
use types::{DelegationEpochs, into_tm_voting_power};

Expand Down Expand Up @@ -2181,7 +2181,8 @@ where
}

// Re-insert the validator into the validator set and update its state
let pipeline_epoch = checked!(current_epoch + params.pipeline_len)?;
let offset = params.pipeline_len;
let pipeline_epoch = checked!(current_epoch + offset)?;
let stake =
read_validator_stake(storage, &params, validator, pipeline_epoch)?;

Expand All @@ -2191,8 +2192,20 @@ where
validator,
stake,
current_epoch,
params.pipeline_len,
offset,
)?;

// Add the validator's stake to active total
let stake =
read_validator_stake(storage, &params, validator, pipeline_epoch)?;
update_total_active_deltas::<S, Gov>(
storage,
&params,
stake.change(),
current_epoch,
Some(offset),
)?;

Ok(())
}

Expand Down Expand Up @@ -2545,6 +2558,22 @@ where
params.pipeline_len,
)?;

// Remove the validator's stake from active total
let stake =
read_validator_stake(storage, &params, validator, pipeline_epoch)?;
if stake.is_positive() {
update_total_active_deltas::<S, Gov>(
storage,
&params,
stake
.change()
.negate()
.expect("Negative stake cannot overflow"),
current_epoch,
Some(params.pipeline_len),
)?;
}

Ok(())
}

Expand Down Expand Up @@ -2609,6 +2638,17 @@ where
params.pipeline_len,
)?;

// Add the validator's stake to active total
let stake =
read_validator_stake(storage, &params, validator, pipeline_epoch)?;
update_total_active_deltas::<S, Gov>(
storage,
&params,
stake.change(),
current_epoch,
Some(params.pipeline_len),
)?;

Ok(())
}

Expand Down Expand Up @@ -3009,6 +3049,23 @@ where
offset,
)?;
}

// Remove the validator's stake from active total
let offset_epoch = checked!(current_epoch + start_offset)?;
let stake = read_validator_stake(storage, params, validator, offset_epoch)?;
if stake.is_positive() {
update_total_active_deltas::<S, Gov>(
storage,
params,
stake
.change()
.negate()
.expect("Negative stake cannot overflow"),
current_epoch,
Some(start_offset),
)?;
}

Ok(())
}

Expand Down Expand Up @@ -3220,6 +3277,47 @@ fn prune_old_delegations(
Ok(())
}

/// Temporary migration code to fix the total active stake value
pub fn fix_total_active_stake<S, Gov>(storage: &mut S) -> Result<()>
where
S: StorageRead + StorageWrite,
Gov: governance::Read<S>,
{
let epoch = storage.get_block_epoch()?;
let validators = storage::read_all_validator_addresses(storage, epoch)?;
let mut total_stake = token::Amount::zero();
let params = read_pos_params::<_, Gov>(storage)?;
for validator in validators {
let state = storage::read_validator_state::<_, Gov>(
storage, &validator, epoch,
)?;
let is_active = matches!(
state,
Some(
ValidatorState::Consensus
| ValidatorState::BelowCapacity
| ValidatorState::BelowThreshold,
)
);
if is_active {
let stake =
read_validator_stake(storage, &params, &validator, epoch)?;
total_stake =
total_stake.checked_add(stake).expect("Must not overflow");
}
}

let total_active_deltas = storage::total_active_deltas_handle();
total_active_deltas.set::<S, Gov>(
storage,
total_stake.change(),
epoch,
0,
)?;

Ok(())
}

#[cfg(any(test, feature = "testing"))]
/// PoS related utility functions to help set up tests.
pub mod test_utils {
Expand Down
43 changes: 34 additions & 9 deletions crates/proof_of_stake/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,6 @@ where
{
let offset = offset_opt.unwrap_or(params.pipeline_len);
let total_deltas = total_deltas_handle();
let total_active_deltas = total_active_deltas_handle();
let offset_epoch = checked!(current_epoch + offset)?;

// Update total deltas
Expand All @@ -744,22 +743,48 @@ where

// Update total active voting power
if update_active_voting_power {
let active_delta = total_active_deltas
.get_delta_val(storage, offset_epoch)?
.unwrap_or_default();
total_active_deltas.set::<S, Gov>(
update_total_active_deltas::<S, Gov>(
storage,
active_delta.checked_add(delta).expect(
"Total active voting power updated amount should not overflow",
),
params,
delta,
current_epoch,
offset,
offset_opt,
)?;
}

Ok(())
}

/// Update PoS total active deltas.
pub fn update_total_active_deltas<S, Gov>(
storage: &mut S,
params: &OwnedPosParams,
delta: token::Change,
current_epoch: namada_core::chain::Epoch,
offset_opt: Option<u64>,
) -> Result<()>
where
S: StorageRead + StorageWrite,
Gov: governance::Read<S>,
{
let offset = offset_opt.unwrap_or(params.pipeline_len);

let total_active_deltas = total_active_deltas_handle();
let offset_epoch = checked!(current_epoch + offset)?;

let active_delta = total_active_deltas
.get_delta_val(storage, offset_epoch)?
.unwrap_or_default();
total_active_deltas.set::<S, Gov>(
storage,
active_delta.checked_add(delta).expect(
"Total active voting power updated amount should not overflow",
),
current_epoch,
offset,
)
}

/// Read PoS validator's email.
pub fn read_validator_email<S>(
storage: &S,
Expand Down
Loading