Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nns): Added Neuron::voting_power_refreshed_timestamp_seconds field. #2268

Merged
merged 12 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
16 changes: 16 additions & 0 deletions rs/nns/governance/api/src/ic_nns_governance.pb.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ pub struct NeuronInfo {
/// See the Visibility enum.
#[prost(enumeration = "Visibility", optional, tag = "12")]
pub visibility: Option<i32>,
/// The last time that following was "refreshed". There are two ways to refresh
/// the following of a neuron: set following, or vote directly. When this
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
/// becomes > 6 months, the amount of voting power that this neuron can
/// exercise decreases linearly down to 0 over the course of 1 month. After
/// that, following is cleared, except for ManageNeuron proposals.
#[prost(uint64, optional, tag = "13")]
pub following_refreshed_timestamp_seconds: ::core::option::Option<u64>,
}
/// A transfer performed from some account to stake a new neuron.
#[derive(candid::CandidType, candid::Deserialize, serde::Serialize, comparable::Comparable)]
Expand Down Expand Up @@ -272,6 +279,13 @@ pub struct Neuron {
/// Cf. \[Neuron::stop_dissolving\] and \[Neuron::start_dissolving\].
#[prost(oneof = "neuron::DissolveState", tags = "9, 10")]
pub dissolve_state: Option<neuron::DissolveState>,
/// The last time that following was "refreshed". There are two ways to refresh
/// the following of a neuron: set following, or vote directly. When this
/// becomes > 6 months, the amount of voting power that this neuron can
/// exercise decreases linearly down to 0 over the course of 1 month. After
/// that, following is cleared, except for ManageNeuron proposals.
#[prost(uint64, optional, tag = "24")]
pub following_refreshed_timestamp_seconds: ::core::option::Option<u64>,
}
/// Nested message and enum types in `Neuron`.
pub mod neuron {
Expand Down Expand Up @@ -364,6 +378,8 @@ pub struct AbridgedNeuron {
pub neuron_type: Option<i32>,
#[prost(enumeration = "Visibility", optional, tag = "23")]
pub visibility: Option<i32>,
#[prost(uint64, optional, tag = "24")]
pub following_refreshed_timestamp_seconds: ::core::option::Option<u64>,
#[prost(oneof = "abridged_neuron::DissolveState", tags = "9, 10")]
pub dissolve_state: Option<abridged_neuron::DissolveState>,
}
Expand Down
2 changes: 2 additions & 0 deletions rs/nns/governance/canister/governance.did
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ type Neuron = record {
transfer : opt NeuronStakeTransfer;
known_neuron_data : opt KnownNeuronData;
spawn_at_timestamp_seconds : opt nat64;
following_refreshed_timestamp_seconds : opt nat64;
};

type NeuronBasketConstructionParameters = record {
Expand Down Expand Up @@ -628,6 +629,7 @@ type NeuronInfo = record {
known_neuron_data : opt KnownNeuronData;
voting_power : nat64;
age_seconds : nat64;
following_refreshed_timestamp_seconds : opt nat64;
};

type NeuronStakeTransfer = record {
Expand Down
2 changes: 2 additions & 0 deletions rs/nns/governance/canister/governance_test.did
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ type Neuron = record {
transfer : opt NeuronStakeTransfer;
known_neuron_data : opt KnownNeuronData;
spawn_at_timestamp_seconds : opt nat64;
following_refreshed_timestamp_seconds : opt nat64;
};

type NeuronBasketConstructionParameters = record {
Expand Down Expand Up @@ -630,6 +631,7 @@ type NeuronInfo = record {
known_neuron_data : opt KnownNeuronData;
voting_power : nat64;
age_seconds : nat64;
following_refreshed_timestamp_seconds : opt nat64;
};

type NeuronStakeTransfer = record {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ message NeuronInfo {
optional NeuronType neuron_type = 11;
// See the Visibility enum.
optional Visibility visibility = 12;
// The last time that following was "refreshed". There are two ways to refresh
// the following of a neuron: set following, or vote directly. When this
// becomes > 6 months, the amount of voting power that this neuron can
// exercise decreases linearly down to 0 over the course of 1 month. After
// that, following is cleared, except for ManageNeuron proposals.
optional uint64 following_refreshed_timestamp_seconds = 13;
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
}

// A transfer performed from some account to stake a new neuron.
Expand Down Expand Up @@ -429,6 +435,13 @@ message Neuron {

// See the Visibility enum.
optional Visibility visibility = 23;

// The last time that following was "refreshed". There are two ways to refresh
// the following of a neuron: set following, or vote directly. When this
// becomes > 6 months, the amount of voting power that this neuron can
// exercise decreases linearly down to 0 over the course of 1 month. After
// that, following is cleared, except for ManageNeuron proposals.
optional uint64 following_refreshed_timestamp_seconds = 24;
}

// Subset of Neuron that has no collections or big fields that might not exist in most neurons, and
Expand All @@ -454,6 +467,7 @@ message AbridgedNeuron {
optional uint64 joined_community_fund_timestamp_seconds = 17;
optional NeuronType neuron_type = 22;
optional Visibility visibility = 23;
optional uint64 following_refreshed_timestamp_seconds = 24;

reserved 1;
reserved "id";
Expand Down
16 changes: 16 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 @@ -113,6 +113,13 @@ pub struct NeuronInfo {
/// See the Visibility enum.
#[prost(enumeration = "Visibility", optional, tag = "12")]
pub visibility: ::core::option::Option<i32>,
/// The last time that following was "refreshed". There are two ways to refresh
/// the following of a neuron: set following, or vote directly. When this
/// becomes > 6 months, the amount of voting power that this neuron can
/// exercise decreases linearly down to 0 over the course of 1 month. After
/// that, following is cleared, except for ManageNeuron proposals.
#[prost(uint64, optional, tag = "13")]
pub following_refreshed_timestamp_seconds: ::core::option::Option<u64>,
}
/// A transfer performed from some account to stake a new neuron.
#[derive(
Expand Down Expand Up @@ -276,6 +283,13 @@ pub struct Neuron {
/// See the Visibility enum.
#[prost(enumeration = "Visibility", optional, tag = "23")]
pub visibility: ::core::option::Option<i32>,
/// The last time that following was "refreshed". There are two ways to refresh
/// the following of a neuron: set following, or vote directly. When this
/// becomes > 6 months, the amount of voting power that this neuron can
/// exercise decreases linearly down to 0 over the course of 1 month. After
/// that, following is cleared, except for ManageNeuron proposals.
#[prost(uint64, optional, tag = "24")]
pub following_refreshed_timestamp_seconds: ::core::option::Option<u64>,
/// At any time, at most one of `when_dissolved` and
/// `dissolve_delay` are specified.
///
Expand Down Expand Up @@ -401,6 +415,8 @@ pub struct AbridgedNeuron {
pub neuron_type: ::core::option::Option<i32>,
#[prost(enumeration = "Visibility", optional, tag = "23")]
pub visibility: ::core::option::Option<i32>,
#[prost(uint64, optional, tag = "24")]
pub following_refreshed_timestamp_seconds: ::core::option::Option<u64>,
#[prost(oneof = "abridged_neuron::DissolveState", tags = "9, 10")]
pub dissolve_state: ::core::option::Option<abridged_neuron::DissolveState>,
}
Expand Down
49 changes: 49 additions & 0 deletions rs/nns/governance/src/neuron/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ use ic_nns_common::pb::v1::{NeuronId, ProposalId};
use icp_ledger::Subaccount;
use std::collections::{BTreeSet, HashMap};

/// Value: one second after midnight, 2024-11-05 (UTC).
///
/// How this value was chosen: This is the earliest that "refreshing" the follow
/// configuration of a neuron can be released.
///
/// How this value is used: when a neuron does not have a value in the
/// following_refreshed_timestamp_seconds field (because it was created before
/// this feature), we pretend as though this value is in that field.
const GRANDFATHERED_FOLLOWING_REFRESHED_TIMESTAMP_SECONDS: u64 = 1731628801;

/// A neuron type internal to the governance crate. Currently, this type is identical to the
/// prost-generated Neuron type (except for derivations for prost). Gradually, this type will evolve
/// towards having all private fields while exposing methods for mutations, which allows it to hold
Expand Down Expand Up @@ -113,8 +123,20 @@ pub struct Neuron {
/// How much unprivileged principals (i.e. is neither controller, nor
/// hotkey) can see about this neuron.
visibility: Option<Visibility>,
/// The last time that following was "refreshed". There are two ways to refresh
/// the following of a neuron: set following, or vote directly. When this
/// becomes > 6 months, the amount of voting power that this neuron can
/// exercise decreases linearly down to 0 over the course of 1 month. After
/// that, following is cleared, except for ManageNeuron proposals.
following_refreshed_timestamp_seconds: u64,
}

/// This is mostly the same as the version of PartialEq generated by derive. The
/// one difference: visibility: None is considered equal to visibility:
/// Some(Private).
// We can get rid of this if we ever decide to backfill the visibility field.
// More precisely, we change all stored neurons (in heap and stable memory) so
// that instead of not having a value in this field, they have Some(Private).
impl PartialEq for Neuron {
fn eq(&self, other: &Self) -> bool {
#[derive(PartialEq)]
Expand All @@ -139,6 +161,7 @@ impl PartialEq for Neuron {
joined_community_fund_timestamp_seconds: &'a Option<u64>,
known_neuron_data: &'a Option<KnownNeuronData>,
neuron_type: &'a Option<i32>,
following_refreshed_timestamp_seconds: &'a u64,

visibility: Visibility,
}
Expand Down Expand Up @@ -166,6 +189,7 @@ impl PartialEq for Neuron {
joined_community_fund_timestamp_seconds,
known_neuron_data,
neuron_type,
following_refreshed_timestamp_seconds,

visibility: _,
} = src;
Expand Down Expand Up @@ -193,6 +217,7 @@ impl PartialEq for Neuron {
joined_community_fund_timestamp_seconds,
known_neuron_data,
neuron_type,
following_refreshed_timestamp_seconds,

visibility,
}
Expand Down Expand Up @@ -826,6 +851,7 @@ impl Neuron {
known_neuron_data: self.known_neuron_data.clone(),
neuron_type: self.neuron_type,
visibility,
following_refreshed_timestamp_seconds: Some(self.following_refreshed_timestamp_seconds),
}
}

Expand Down Expand Up @@ -993,6 +1019,10 @@ impl Neuron {
.dissolved_at_timestamp_seconds()
}

pub fn following_refreshed_timestamp_seconds(&self) -> u64 {
self.following_refreshed_timestamp_seconds
}

pub fn subtract_staked_maturity(&mut self, amount_e8s: u64) {
let new_staked_maturity_e8s = self
.staked_maturity_e8s_equivalent
Expand Down Expand Up @@ -1044,6 +1074,7 @@ impl From<Neuron> for NeuronProto {
known_neuron_data,
neuron_type,
visibility: _,
following_refreshed_timestamp_seconds,
} = neuron;

let id = Some(id);
Expand All @@ -1053,6 +1084,7 @@ impl From<Neuron> for NeuronProto {
dissolve_state,
aging_since_timestamp_seconds,
} = StoredDissolveStateAndAge::from(dissolve_state_and_age);
let following_refreshed_timestamp_seconds = Some(following_refreshed_timestamp_seconds);

NeuronProto {
id,
Expand All @@ -1077,6 +1109,7 @@ impl From<Neuron> for NeuronProto {
known_neuron_data,
neuron_type,
visibility,
following_refreshed_timestamp_seconds,
}
}
}
Expand Down Expand Up @@ -1108,6 +1141,7 @@ impl TryFrom<NeuronProto> for Neuron {
known_neuron_data,
neuron_type,
visibility,
following_refreshed_timestamp_seconds,
} = proto;

let id = id.ok_or("Neuron ID is missing")?;
Expand All @@ -1127,6 +1161,8 @@ impl TryFrom<NeuronProto> for Neuron {
)
})?),
};
let following_refreshed_timestamp_seconds = following_refreshed_timestamp_seconds
.unwrap_or(GRANDFATHERED_FOLLOWING_REFRESHED_TIMESTAMP_SECONDS);

Ok(Neuron {
id,
Expand All @@ -1150,6 +1186,7 @@ impl TryFrom<NeuronProto> for Neuron {
known_neuron_data,
neuron_type,
visibility,
following_refreshed_timestamp_seconds,
})
}
}
Expand Down Expand Up @@ -1242,6 +1279,7 @@ impl TryFrom<Neuron> for DecomposedNeuron {
known_neuron_data,
neuron_type,
visibility,
following_refreshed_timestamp_seconds,
} = source;

let account = subaccount.to_vec();
Expand All @@ -1252,6 +1290,7 @@ impl TryFrom<Neuron> for DecomposedNeuron {
} = StoredDissolveStateAndAge::from(dissolve_state_and_age);
let dissolve_state = dissolve_state.map(AbridgedNeuronDissolveState::from);
let visibility = visibility.map(|visibility| visibility as i32);
let following_refreshed_timestamp_seconds = Some(following_refreshed_timestamp_seconds);

let main = AbridgedNeuron {
account,
Expand All @@ -1270,6 +1309,7 @@ impl TryFrom<Neuron> for DecomposedNeuron {
neuron_type,
dissolve_state,
visibility,
following_refreshed_timestamp_seconds,
};

Ok(Self {
Expand Down Expand Up @@ -1319,6 +1359,7 @@ impl From<DecomposedNeuron> for Neuron {
neuron_type,
dissolve_state,
visibility,
following_refreshed_timestamp_seconds,
} = main;

let subaccount =
Expand All @@ -1331,6 +1372,9 @@ impl From<DecomposedNeuron> for Neuron {
.expect("Neuron dissolve state and age is invalid");
let visibility = visibility.and_then(|visibility| Visibility::try_from(visibility).ok());

let following_refreshed_timestamp_seconds = following_refreshed_timestamp_seconds
.unwrap_or(GRANDFATHERED_FOLLOWING_REFRESHED_TIMESTAMP_SECONDS);

Neuron {
id,
subaccount,
Expand All @@ -1353,6 +1397,7 @@ impl From<DecomposedNeuron> for Neuron {
known_neuron_data,
neuron_type,
visibility,
following_refreshed_timestamp_seconds,
}
}
}
Expand Down Expand Up @@ -1381,6 +1426,7 @@ pub struct NeuronBuilder {
joined_community_fund_timestamp_seconds: Option<u64>,
neuron_type: Option<i32>,
visibility: Option<Visibility>,
following_refreshed_timestamp_seconds: u64,

// Fields that don't exist when a neuron is first built. We allow them to be set in tests.
#[cfg(test)]
Expand Down Expand Up @@ -1421,6 +1467,7 @@ impl NeuronBuilder {
joined_community_fund_timestamp_seconds: None,
neuron_type: None,
visibility: None,
following_refreshed_timestamp_seconds: created_timestamp_seconds,

#[cfg(test)]
neuron_fees_e8s: 0,
Expand Down Expand Up @@ -1576,6 +1623,7 @@ impl NeuronBuilder {
#[cfg(test)]
known_neuron_data,
visibility,
following_refreshed_timestamp_seconds,
} = self;

let auto_stake_maturity = if auto_stake_maturity {
Expand Down Expand Up @@ -1618,6 +1666,7 @@ impl NeuronBuilder {
known_neuron_data,
neuron_type,
visibility,
following_refreshed_timestamp_seconds,
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions rs/nns/governance/src/neuron/types/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@ fn test_abridged_neuron_size() {
u64::MAX,
)),
visibility: None,
following_refreshed_timestamp_seconds: Some(u64::MAX),
};

assert!(abridged_neuron.encoded_len() as u32 <= AbridgedNeuron::BOUND.max_size());
// This size can be updated. This assertion is created so that we are aware of the available
// headroom.
assert_eq!(abridged_neuron.encoded_len(), 184);
// This size can be updated. This assertion is here to make sure we are very aware of growth.
// Reminder: the amount that we allocated for AbridgedNeuron is 380 bytes.
assert_eq!(abridged_neuron.encoded_len(), 196);
}

fn create_neuron_with_stake_dissolve_state_and_age(
Expand Down
Loading