Skip to content
Merged
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
Expand Up @@ -1979,6 +1979,16 @@ message LoadCanisterSnapshot {
bytes snapshot_id = 2;
}

// Snapshot of a neuron's dissolve state, recorded before clamping to the
// Mission 70 maximum. Used to restore the original dissolve state if the
// maximum dissolve delay is ever increased again.
message NeuronDissolveStateSnapshot {
oneof dissolve_state {
uint64 dissolve_delay_seconds = 1;
uint64 when_dissolved_timestamp_seconds = 2;
}
}

// This represents the whole NNS governance system. It contains all
// information about the NNS governance system that must be kept
// across upgrades of the NNS governance system.
Expand Down Expand Up @@ -2219,6 +2229,10 @@ message Governance {
// This prevents the migration from running more than once.
bool eight_year_gang_bonus_migration_done = 31;

// Snapshot of each neuron's dissolve state taken while clamping to the Mission 70 maximum
// dissolve delay. Enables restoring original dissolve states if the maximum is reversed.
map<uint64, NeuronDissolveStateSnapshot> neuron_id_to_pre_clamp_dissolve_state = 32;

reserved 30;
reserved "first_proposal_id_to_record_voting_history";

Expand Down
41 changes: 41 additions & 0 deletions rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2955,6 +2955,42 @@ pub struct LoadCanisterSnapshot {
#[prost(bytes = "vec", tag = "2")]
pub snapshot_id: ::prost::alloc::vec::Vec<u8>,
}
/// Snapshot of a neuron's dissolve state, recorded before clamping to the
/// Mission 70 maximum. Used to restore the original dissolve state if the
/// maximum dissolve delay is ever increased again.
#[derive(
candid::CandidType,
candid::Deserialize,
serde::Serialize,
comparable::Comparable,
Clone,
Copy,
PartialEq,
::prost::Message,
)]
pub struct NeuronDissolveStateSnapshot {
#[prost(oneof = "neuron_dissolve_state_snapshot::DissolveState", tags = "1, 2")]
pub dissolve_state: ::core::option::Option<neuron_dissolve_state_snapshot::DissolveState>,
}
/// Nested message and enum types in `NeuronDissolveStateSnapshot`.
pub mod neuron_dissolve_state_snapshot {
#[derive(
candid::CandidType,
candid::Deserialize,
serde::Serialize,
comparable::Comparable,
Clone,
Copy,
PartialEq,
::prost::Oneof,
)]
pub enum DissolveState {
#[prost(uint64, tag = "1")]
DissolveDelaySeconds(u64),
#[prost(uint64, tag = "2")]
WhenDissolvedTimestampSeconds(u64),
}
}
/// This represents the whole NNS governance system. It contains all
/// information about the NNS governance system that must be kept
/// across upgrades of the NNS governance system.
Expand Down Expand Up @@ -3078,6 +3114,11 @@ pub struct Governance {
/// This prevents the migration from running more than once.
#[prost(bool, tag = "31")]
pub eight_year_gang_bonus_migration_done: bool,
/// Snapshot of each neuron's dissolve state taken while clamping to the Mission 70 maximum
/// dissolve delay. Enables restoring original dissolve states if the maximum is reversed.
#[prost(map = "uint64, message", tag = "32")]
pub neuron_id_to_pre_clamp_dissolve_state:
::std::collections::HashMap<u64, NeuronDissolveStateSnapshot>,
}
/// Nested message and enum types in `Governance`.
pub mod governance {
Expand Down
40 changes: 34 additions & 6 deletions rs/nns/governance/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::{
HeapGovernanceData, XdrConversionRate, initialize_governance, reassemble_governance_proto,
split_governance_proto,
},
is_comprehensive_neuron_list_enabled, is_neuron_follow_restrictions_enabled,
is_comprehensive_neuron_list_enabled, is_mission_70_voting_rewards_enabled,
is_neuron_follow_restrictions_enabled,
neuron::{DissolveStateAndAge, Neuron, NeuronBuilder, Visibility},
neuron_data_validation::{NeuronDataValidationSummary, NeuronDataValidator},
neuron_store::{
Expand Down Expand Up @@ -199,9 +200,6 @@ pub const PROPOSAL_MOTION_TEXT_BYTES_MAX: usize = 10000;
// The minimum neuron dissolve delay (set when a neuron is first claimed)
pub const INITIAL_NEURON_DISSOLVE_DELAY: u64 = 7 * ONE_DAY_SECONDS;

// The maximum dissolve delay allowed for a neuron.
pub const MAX_DISSOLVE_DELAY_SECONDS: u64 = 8 * ONE_YEAR_SECONDS;

// The age of a neuron that saturates the age bonus for the voting power
// computation.
pub const MAX_NEURON_AGE_FOR_AGE_BONUS: u64 = 4 * ONE_YEAR_SECONDS;
Expand Down Expand Up @@ -286,6 +284,21 @@ pub const MAX_NEURONS_FUND_PARTICIPANTS: u64 = 5_000;
/// in the same limit.
const NEURON_RATE_LIMITER_KEY: &str = "ADD_NEURON";

// The maximum dissolve delay allowed for a neuron.
pub const MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70: u64 = 8 * ONE_YEAR_SECONDS;
pub const MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70: u64 = 2 * ONE_YEAR_SECONDS;

/// Returns the maximum dissolve delay allowed for a neuron. After the flag is enabled, we can
/// replace `max_dissolve_delay_seconds()` with `MAX_DISSOLVE_DELAY_SECONDS` and set
/// `MAX_DISSOLVE_DELAY_SECONDS` to `MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70`.
pub fn max_dissolve_delay_seconds() -> u64 {
if is_mission_70_voting_rewards_enabled() {
MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70
} else {
MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70
}
}

impl GovernanceError {
pub fn new(error_type: ErrorType) -> Self {
Self {
Expand Down Expand Up @@ -1328,6 +1341,21 @@ impl Governance {
// A one-time data migration.
governance.maybe_set_eight_year_gang_bonus_base();

// Clamp all neuron dissolve delays to the Mission 70 maximum exactly once.
// The snapshot serves as the idempotency guard: if it's already populated,
// clamping has already run and we must not overwrite the pre-clamp record.
if is_mission_70_voting_rewards_enabled()
&& governance
.heap_data
.neuron_id_to_pre_clamp_dissolve_state
.is_empty()
{
let now = governance.env.now();
governance.heap_data.neuron_id_to_pre_clamp_dissolve_state = governance
.neuron_store
.clamp_dissolve_delay_for_all_neurons_or_panic(now);
}

governance
}

Expand Down Expand Up @@ -2965,7 +2993,7 @@ impl Governance {

let dissolve_delay_seconds = std::cmp::min(
disburse_to_neuron.dissolve_delay_seconds,
MAX_DISSOLVE_DELAY_SECONDS,
max_dissolve_delay_seconds(),
);

let dissolve_state_and_age = if dissolve_delay_seconds > 0 {
Expand Down Expand Up @@ -3805,7 +3833,7 @@ impl Governance {
let nid = self.neuron_store.new_neuron_id(&mut *self.randomness)?;
let dissolve_delay_seconds = std::cmp::min(
reward_to_neuron.dissolve_delay_seconds,
MAX_DISSOLVE_DELAY_SECONDS,
max_dissolve_delay_seconds(),
);

let dissolve_state_and_age = if dissolve_delay_seconds > 0 {
Expand Down
7 changes: 4 additions & 3 deletions rs/nns/governance/src/governance/create_neuron.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
governance::{
Governance, INITIAL_NEURON_DISSOLVE_DELAY, LOG_PREFIX, MAX_DISSOLVE_DELAY_SECONDS,
Governance, INITIAL_NEURON_DISSOLVE_DELAY, LOG_PREFIX, max_dissolve_delay_seconds,
},
neuron::{DissolveStateAndAge, NeuronBuilder},
pb::v1::{
Expand Down Expand Up @@ -73,12 +73,13 @@ impl Governance {
),
));
}
if dissolve_delay_seconds > MAX_DISSOLVE_DELAY_SECONDS {
if dissolve_delay_seconds > max_dissolve_delay_seconds() {
return Err(GovernanceError::new_with_message(
ErrorType::InvalidCommand,
format!(
"Dissolve delay {dissolve_delay_seconds} is greater than the maximum \
dissolve delay {MAX_DISSOLVE_DELAY_SECONDS}"
dissolve delay {}",
max_dissolve_delay_seconds()
),
));
}
Expand Down
4 changes: 2 additions & 2 deletions rs/nns/governance/src/governance/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::pb::v1::ExecuteNnsFunction;
use crate::storage::with_voting_history_store;
use crate::test_utils::MockRandomness;
use crate::{
governance::MAX_DISSOLVE_DELAY_SECONDS,
governance::MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70,
neuron::{DissolveStateAndAge, NeuronBuilder},
test_utils::{MockEnvironment, StubCMC, StubIcpLedger},
};
Expand Down Expand Up @@ -1735,7 +1735,7 @@ fn test_maybe_set_eight_year_gang_bonus_base() {
let neuron = NeuronBuilder::new_for_test(
1,
DissolveStateAndAge::NotDissolving {
dissolve_delay_seconds: MAX_DISSOLVE_DELAY_SECONDS,
dissolve_delay_seconds: MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70,
aging_since_timestamp_seconds: 0,
},
)
Expand Down
20 changes: 18 additions & 2 deletions rs/nns/governance/src/heap_governance_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::{
neuron::Neuron,
pb::v1::{
Followees, Governance as GovernanceProto, MonthlyNodeProviderRewards, NetworkEconomics,
NeuronStakeTransfer, NodeProvider, ProposalData, RestoreAgingSummary, RewardEvent, Topic,
XdrConversionRate as XdrConversionRatePb,
NeuronDissolveStateSnapshot, NeuronStakeTransfer, NodeProvider, ProposalData,
RestoreAgingSummary, RewardEvent, Topic, XdrConversionRate as XdrConversionRatePb,
governance::{GovernanceCachedMetrics, NeuronInFlightCommand},
},
};
Expand Down Expand Up @@ -37,6 +37,7 @@ pub struct HeapGovernanceData {
pub restore_aging_summary: Option<RestoreAgingSummary>,
pub topic_of_garbage_collected_proposals: HashMap<u64, Topic>,
pub eight_year_gang_bonus_migration_done: bool,
pub neuron_id_to_pre_clamp_dissolve_state: HashMap<u64, NeuronDissolveStateSnapshot>,
}

/// Internal representation for `XdrConversionRatePb`.
Expand Down Expand Up @@ -207,6 +208,7 @@ pub fn initialize_governance(
restore_aging_summary,
topic_of_garbage_collected_proposals: HashMap::new(),
eight_year_gang_bonus_migration_done: false,
neuron_id_to_pre_clamp_dissolve_state: HashMap::new(),
};

// Finally, return the result.
Expand Down Expand Up @@ -247,6 +249,7 @@ pub fn split_governance_proto(
restore_aging_summary,
topic_of_garbage_collected_proposals,
eight_year_gang_bonus_migration_done,
neuron_id_to_pre_clamp_dissolve_state,
rng_seed,
} = governance_proto;

Expand Down Expand Up @@ -291,6 +294,7 @@ pub fn split_governance_proto(
.map(|(k, v)| (k, Topic::try_from(v).unwrap_or(Topic::Unspecified)))
.collect(),
eight_year_gang_bonus_migration_done,
neuron_id_to_pre_clamp_dissolve_state,
},
rng_seed,
)
Expand Down Expand Up @@ -329,6 +333,7 @@ pub fn reassemble_governance_proto(
restore_aging_summary,
topic_of_garbage_collected_proposals,
eight_year_gang_bonus_migration_done,
neuron_id_to_pre_clamp_dissolve_state,
} = heap_governance_proto;

let neuron_management_voting_period_seconds = Some(neuron_management_voting_period_seconds);
Expand Down Expand Up @@ -360,6 +365,7 @@ pub fn reassemble_governance_proto(
.map(|(k, v)| (k, v as i32))
.collect(),
eight_year_gang_bonus_migration_done,
neuron_id_to_pre_clamp_dissolve_state,
rng_seed: rng_seed.map(|seed| seed.to_vec()),
}
}
Expand All @@ -368,6 +374,7 @@ pub fn reassemble_governance_proto(
mod tests {
use super::*;

use crate::governance::MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70;
use crate::pb::v1::ProposalData;

use maplit::{btreemap, hashmap};
Expand Down Expand Up @@ -400,6 +407,15 @@ mod tests {
restore_aging_summary: None,
topic_of_garbage_collected_proposals: hashmap! { 1 => Topic::Unspecified as i32 },
eight_year_gang_bonus_migration_done: true,
neuron_id_to_pre_clamp_dissolve_state: hashmap! {
1 => NeuronDissolveStateSnapshot {
dissolve_state: Some(
crate::pb::v1::neuron_dissolve_state_snapshot::DissolveState::DissolveDelaySeconds(
MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70,
),
Comment thread
jasonz-dfinity marked this conversation as resolved.
),
},
},
rng_seed: Some(vec![1_u8; 32]),
}
}
Expand Down
Loading
Loading