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

fix(nns): Fixed a bug where known neuron is not seen as public. #699

Merged
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
2 changes: 1 addition & 1 deletion rs/nns/governance/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2165,7 +2165,7 @@ impl Governance {
// neuron is public, and the caller requested that
// public neurons be included (in full_neurons).
|| (include_public_neurons_in_full_neurons
&& neuron.visibility == Some(Visibility::Public)
&& neuron.visibility() == Some(Visibility::Public)
);
if let_caller_read_full_neuron {
full_neurons.push(NeuronProto::from(neuron.clone()));
Expand Down
38 changes: 13 additions & 25 deletions rs/nns/governance/src/neuron/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ use ic_base_types::PrincipalId;
use ic_nervous_system_common::ONE_DAY_SECONDS;
use ic_nns_common::pb::v1::{NeuronId, ProposalId};
use icp_ledger::Subaccount;
use std::{
borrow::Cow,
collections::{BTreeSet, HashMap},
};
use std::collections::{BTreeSet, HashMap};

/// 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
Expand Down Expand Up @@ -115,27 +112,7 @@ pub struct Neuron {
pub neuron_type: Option<i32>,
/// How much unprivileged principals (i.e. is neither controller, nor
/// hotkey) can see about this neuron.
pub visibility: Option<Visibility>,
}

#[must_use]
pub fn normalized(mut neuron: Cow<Neuron>) -> Cow<Neuron> {
if neuron.known_neuron_data.is_some() {
// Log if there is an inconsistency, but otherwise, do not interrupt the flow.
if neuron.visibility == Some(Visibility::Private) {
println!(
"{}WARNING: Neuron {:?} is a known neuron, but its visibility field is \
set to private. This in-memory neuron will now quietly be set to public. \
However, the underlying source of this inconsistent neuron is not \
being updated.",
LOG_PREFIX, neuron.id,
);
}

neuron.to_mut().visibility = Some(Visibility::Public);
}

neuron
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
visibility: Option<Visibility>,
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
}

impl Neuron {
Expand Down Expand Up @@ -164,6 +141,17 @@ impl Neuron {
self.dissolve_state_and_age
}

/// When we turn on enforcement of private neurons, this will only return
/// Public or Private, not None. When that happens, we should define another
/// Visibility that does NOT have Unspecified.
pub fn visibility(&self) -> Option<Visibility> {
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
if self.known_neuron_data.is_some() {
return Some(Visibility::Public);
}

self.visibility
}
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved

/// Sets a neuron's dissolve state and age.
pub fn set_dissolve_state_and_age(&mut self, dissolve_state_and_age: DissolveStateAndAge) {
self.dissolve_state_and_age = dissolve_state_and_age;
Expand Down
14 changes: 0 additions & 14 deletions rs/nns/governance/src/neuron/types/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,17 +450,3 @@ fn test_neuron_configure_dissolve_delay() {
let now = now + 1;
assert_eq!(neuron.state(now), NeuronState::Dissolved);
}

#[test]
fn test_normalize_makes_known_neurons_always_public() {
let mut neuron =
create_neuron_with_dissolve_state_and_age(DissolveStateAndAge::DissolvingOrDissolved {
when_dissolved_timestamp_seconds: 1721408743,
});
neuron.known_neuron_data = Some(Default::default());
let neuron: std::borrow::Cow<'_, Neuron> = Cow::Owned(neuron);

let neuron = normalized(neuron);

assert_eq!(neuron.visibility, Some(Visibility::Public),);
}
73 changes: 32 additions & 41 deletions rs/nns/governance/src/neuron_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ use crate::{
governance::{
Environment, TimeWarp, LOG_PREFIX, MIN_DISSOLVE_DELAY_FOR_VOTE_ELIGIBILITY_SECONDS,
},
neuron::{
neuron_id_range_to_u64_range,
types::{normalized, Neuron},
},
neuron::{neuron_id_range_to_u64_range, types::Neuron},
pb::v1::{
governance::{followers_map::Followers, FollowersMap},
governance_error::ErrorType,
Expand Down Expand Up @@ -597,48 +594,42 @@ impl NeuronStore {
neuron_id: NeuronId,
sections: NeuronSections,
) -> Result<(Cow<Neuron>, StorageLocation), NeuronStoreError> {
let main = || {
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
let heap_neuron = self.heap_neurons.get(&neuron_id.id).map(Cow::Borrowed);

if let Some(heap_neuron) = heap_neuron.clone() {
// If the neuron is active on heap, return early to avoid any operation on stable
// storage. The StableStorageNeuronValidator ensures that active neuron cannot also be
// on stable storage.
if !heap_neuron.is_inactive(self.now()) {
return Ok((heap_neuron, StorageLocation::Heap));
}
let heap_neuron = self.heap_neurons.get(&neuron_id.id).map(Cow::Borrowed);

if let Some(heap_neuron) = heap_neuron.clone() {
// If the neuron is active on heap, return early to avoid any operation on stable
// storage. The StableStorageNeuronValidator ensures that active neuron cannot also be
// on stable storage.
if !heap_neuron.is_inactive(self.now()) {
return Ok((heap_neuron, StorageLocation::Heap));
}
}

let stable_neuron = with_stable_neuron_store(|stable_neuron_store| {
stable_neuron_store
.read(neuron_id, sections)
.ok()
.map(Cow::Owned)
});

match (stable_neuron, heap_neuron) {
// 1 copy cases.
(Some(stable), None) => Ok((stable, StorageLocation::Stable)),
(None, Some(heap)) => Ok((heap, StorageLocation::Heap)),

// 2 copy case.
(Some(stable), Some(_)) => {
println!(
"{}WARNING: neuron {:?} is in both stable memory and heap memory, \
we are at risk of having stale copies",
LOG_PREFIX, neuron_id
);
Ok((stable, StorageLocation::Stable))
}
let stable_neuron = with_stable_neuron_store(|stable_neuron_store| {
stable_neuron_store
.read(neuron_id, sections)
.ok()
.map(Cow::Owned)
});

// 0 copies case.
(None, None) => Err(NeuronStoreError::not_found(neuron_id)),
match (stable_neuron, heap_neuron) {
// 1 copy cases.
(Some(stable), None) => Ok((stable, StorageLocation::Stable)),
(None, Some(heap)) => Ok((heap, StorageLocation::Heap)),

// 2 copies case.
(Some(stable), Some(_)) => {
println!(
"{}WARNING: neuron {:?} is in both stable memory and heap memory, \
we are at risk of having stale copies",
LOG_PREFIX, neuron_id
);
Ok((stable, StorageLocation::Stable))
}
};

let (neuron, storage_location) = main()?;
let neuron = normalized(neuron);
Ok((neuron, storage_location))
// 0 copies case.
(None, None) => Err(NeuronStoreError::not_found(neuron_id)),
}
}

// Loads the entire neuron from either heap or stable storage and returns its primary storage.
Expand Down
2 changes: 1 addition & 1 deletion rs/nns/governance/src/neuron_store/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl NeuronMetrics {
}

fn increment_public_neuron_subset_metrics(&mut self, now_seconds: u64, neuron: &Neuron) {
let is_public = neuron.visibility == Some(Visibility::Public);
let is_public = neuron.visibility() == Some(Visibility::Public);
if !is_public {
return;
}
Expand Down
18 changes: 16 additions & 2 deletions rs/nns/governance/src/neuron_store/metrics/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;
use crate::{
neuron::{DissolveStateAndAge, NeuronBuilder},
pb::v1::NeuronType,
pb::v1::{KnownNeuronData, NeuronType},
};
use ic_base_types::PrincipalId;
use ic_nervous_system_common::{E8, ONE_DAY_SECONDS, ONE_YEAR_SECONDS};
Expand Down Expand Up @@ -591,7 +591,11 @@ fn test_compute_neuron_metrics_public_neurons() {
.with_cached_neuron_stake_e8s(300_000_000)
.with_staked_maturity_e8s_equivalent(303_000_000)
.with_maturity_e8s_equivalent(330_000_000)
.with_visibility(Some(Visibility::Public))
// (Nominally) the neuron should be treated as public.
.with_known_neuron_data(Some(KnownNeuronData {
name: "Daniel Wong".to_string(),
description: Some("Best engineer of all time. Of all time.".to_string()),
}))
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved
.build();

let voting_power_1 = neuron_1.voting_power(now_seconds);
Expand All @@ -609,6 +613,16 @@ fn test_compute_neuron_metrics_public_neurons() {
2 => neuron_2,
3 => neuron_3,
});
neuron_store
.with_neuron(&NeuronId { id: 3 }, |neuron| {
assert_eq!(
neuron.visibility(),
Some(Visibility::Public),
"{:#?}",
neuron,
);
})
.unwrap(); // Explode if neuron is not found.
daniel-wong-dfinity-org marked this conversation as resolved.
Show resolved Hide resolved

// Step 2: Call code under test.

Expand Down
8 changes: 2 additions & 6 deletions rs/nns/governance/tests/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9878,7 +9878,7 @@ fn test_neuron_set_visibility() {
.with_neuron(&neuron_id, |neuron| neuron.clone())
.unwrap();

assert_eq!(neuron.visibility, expected_visibility, "{:#?}", neuron,);
assert_eq!(neuron.visibility(), expected_visibility, "{:#?}", neuron,);
};

assert_neuron_visibility(typical_neuron.id.unwrap(), Some(Visibility::Public));
Expand Down Expand Up @@ -9977,11 +9977,7 @@ fn test_include_public_neurons_in_full_neurons() {
assert_eq!(
list_neurons_response.full_neurons,
vec![
Neuron {
// Thanks to normalization.
visibility: Some(Visibility::Public as i32),
..known_neuron
},
known_neuron,
explicitly_public_neuron,
// In particular, legacy and explicitly_private are NOT in the result.

Expand Down