Skip to content

Commit 3120812

Browse files
feat(nns): Added (potential|deciding)_voting_power fields to API. (#2880)
# References This is part of the "periodic confirmation" feature that was recently approved in proposal [132411][prop]. [prop]: https://dashboard.internetcomputer.org/proposal/132411 Closes https://dfinity.atlassian.net/browse/NNS1-3430
1 parent ad36e6b commit 3120812

File tree

17 files changed

+536
-35
lines changed

17 files changed

+536
-35
lines changed

rs/nns/governance/api/src/ic_nns_governance.pb.v1.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ pub struct NeuronInfo {
113113
/// after the UNIX epoch).
114114
#[prost(uint64, optional, tag = "13")]
115115
pub voting_power_refreshed_timestamp_seconds: ::core::option::Option<u64>,
116+
/// See analogous field in Neuron.
117+
#[prost(uint64, optional, tag = "14")]
118+
pub deciding_voting_power: Option<u64>,
119+
/// See analogous field in Neuron.
120+
#[prost(uint64, optional, tag = "15")]
121+
pub potential_voting_power: Option<u64>,
116122
}
117123
/// A transfer performed from some account to stake a new neuron.
118124
#[derive(candid::CandidType, candid::Deserialize, serde::Serialize, comparable::Comparable)]
@@ -302,6 +308,63 @@ pub struct Neuron {
302308
/// Cf. \[Neuron::stop_dissolving\] and \[Neuron::start_dissolving\].
303309
#[prost(oneof = "neuron::DissolveState", tags = "9, 10")]
304310
pub dissolve_state: Option<neuron::DissolveState>,
311+
/// The amount of "sway" this neuron has when voting on proposals.
312+
///
313+
/// When a proposal is created, each eligible neuron gets a "blank" ballot. The
314+
/// amount of voting power in that ballot is set to the neuron's deciding
315+
/// voting power at the time of proposal creation. There are two ways that a
316+
/// proposal can become decided:
317+
///
318+
/// 1. Early: Either more than half of the total voting power in the ballots
319+
/// votes in favor (then the proposal is approved), or at least half of the
320+
/// votal voting power in the ballots votes against (then, the proposal is
321+
/// rejected).
322+
///
323+
/// 2. The proposal's voting deadline is reached. At that point, if there is
324+
/// more voting power in favor than against, and at least 3% of the total
325+
/// voting power voted in favor, then the proposal is approved. Otherwise, it
326+
/// is rejected.
327+
///
328+
/// If a neuron regularly refreshes its voting power, this has the same value
329+
/// as potential_voting_power. Actions that cause a refresh are as follows:
330+
///
331+
/// 1. voting directly (not via following)
332+
/// 2. set following
333+
/// 3. refresh voting power
334+
///
335+
/// (All of these actions are performed via the manage_neuron method.)
336+
///
337+
/// However, if a neuron has not refreshed in a "long" time, this will be less
338+
/// than potential voting power. See VotingPowerEconomics. As a further result
339+
/// of less deciding voting power, not only does it have less influence on the
340+
/// outcome of proposals, the neuron receives less voting rewards (when it
341+
/// votes indirectly via following).
342+
///
343+
/// For details, see https://dashboard.internetcomputer.org/proposal/132411.
344+
///
345+
/// Per NNS policy, this is opt. Nevertheless, it will never be null.
346+
#[prost(uint64, optional, tag = "26")]
347+
pub deciding_voting_power: Option<u64>,
348+
/// The amount of "sway" this neuron can have if it refreshes its voting power
349+
/// frequently enough.
350+
///
351+
/// Unlike deciding_voting_power, this does NOT take refreshing into account.
352+
/// Rather, this only takes three factors into account:
353+
///
354+
/// 1. (Net) staked amount - This is the "base" of a neuron's voting power.
355+
/// This primarily consists of the neuron's ICP balance.
356+
///
357+
/// 2. Age - Neurons with more age have more voting power (all else being
358+
/// equal).
359+
///
360+
/// 3. Dissolve delay - Neurons with longer dissolve delay have more voting
361+
/// power (all else being equal). Neurons with a dissolve delay of less
362+
/// than six months are not eligible to vote. Therefore, such neurons
363+
/// are considered to have 0 voting power.
364+
///
365+
/// Per NNS policy, this is opt. Nevertheless, it will never be null.
366+
#[prost(uint64, optional, tag = "27")]
367+
pub potential_voting_power: Option<u64>,
305368
}
306369
/// Nested message and enum types in `Neuron`.
307370
pub mod neuron {

rs/nns/governance/canister/governance.did

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,63 @@ type Neuron = record {
595595
known_neuron_data : opt KnownNeuronData;
596596
spawn_at_timestamp_seconds : opt nat64;
597597
voting_power_refreshed_timestamp_seconds : opt nat64;
598+
599+
// The amount of "sway" this neuron has when voting on proposals.
600+
//
601+
// When a proposal is created, each eligible neuron gets a "blank" ballot. The
602+
// amount of voting power in that ballot is set to the neuron's deciding
603+
// voting power at the time of proposal creation. There are two ways that a
604+
// proposal can become decided:
605+
//
606+
// 1. Early: Either more than half of the total voting power in the ballots
607+
// votes in favor (then the proposal is approved), or at least half of the
608+
// votal voting power in the ballots votes against (then, the proposal is
609+
// rejected).
610+
//
611+
// 2. The proposal's voting deadline is reached. At that point, if there is
612+
// more voting power in favor than against, and at least 3% of the total
613+
// voting power voted in favor, then the proposal is approved. Otherwise, it
614+
// is rejected.
615+
//
616+
// If a neuron regularly refreshes its voting power, this has the same value
617+
// as potential_voting_power. Actions that cause a refresh are as follows:
618+
//
619+
// 1. voting directly (not via following)
620+
// 2. set following
621+
// 3. refresh voting power
622+
//
623+
// (All of these actions are performed via the manage_neuron method.)
624+
//
625+
// However, if a neuron has not refreshed in a "long" time, this will be less
626+
// than potential voting power. See VotingPowerEconomics. As a further result
627+
// of less deciding voting power, not only does it have less influence on the
628+
// outcome of proposals, the neuron receives less voting rewards (when it
629+
// votes indirectly via following).
630+
//
631+
// For details, see https://dashboard.internetcomputer.org/proposal/132411.
632+
//
633+
// Per NNS policy, this is opt. Nevertheless, it will never be null.
634+
deciding_voting_power : opt nat64;
635+
636+
// The amount of "sway" this neuron can have if it refreshes its voting power
637+
// frequently enough.
638+
//
639+
// Unlike deciding_voting_power, this does NOT take refreshing into account.
640+
// Rather, this only takes three factors into account:
641+
//
642+
// 1. (Net) staked amount - This is the "base" of a neuron's voting power.
643+
// This primarily consists of the neuron's ICP balance.
644+
//
645+
// 2. Age - Neurons with more age have more voting power (all else being
646+
// equal).
647+
//
648+
// 3. Dissolve delay - Neurons with longer dissolve delay have more voting
649+
// power (all else being equal). Neurons with a dissolve delay of less
650+
// than six months are not eligible to vote. Therefore, such neurons
651+
// are considered to have 0 voting power.
652+
//
653+
// Per NNS policy, this is opt. Nevertheless, it will never be null.
654+
potential_voting_power : opt nat64;
598655
};
599656

600657
type NeuronBasketConstructionParameters = record {
@@ -633,6 +690,7 @@ type NeuronInFlightCommand = record {
633690
timestamp : nat64;
634691
};
635692

693+
// In general, this is a subset of Neuron.
636694
type NeuronInfo = record {
637695
dissolve_delay_seconds : nat64;
638696
recent_ballots : vec BallotInfo;
@@ -644,9 +702,19 @@ type NeuronInfo = record {
644702
retrieved_at_timestamp_seconds : nat64;
645703
visibility : opt int32;
646704
known_neuron_data : opt KnownNeuronData;
647-
voting_power : nat64;
648705
age_seconds : nat64;
706+
707+
// Deprecated. Use either deciding_voting_power or potential_voting_power
708+
// instead. Has the same value as deciding_voting_power.
709+
//
710+
// Previously, if a neuron had < 6 months dissolve delay (making it ineligible
711+
// to vote), this would not get set to 0 (zero). That was pretty confusing.
712+
// Now that this is set to deciding_voting_power, this actually does get
713+
// zeroed out.
714+
voting_power : nat64;
649715
voting_power_refreshed_timestamp_seconds : opt nat64;
716+
deciding_voting_power : opt nat64;
717+
potential_voting_power : opt nat64;
650718
};
651719

652720
type NeuronStakeTransfer = record {

rs/nns/governance/canister/governance_test.did

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,8 @@ type Neuron = record {
597597
known_neuron_data : opt KnownNeuronData;
598598
spawn_at_timestamp_seconds : opt nat64;
599599
voting_power_refreshed_timestamp_seconds : opt nat64;
600+
deciding_voting_power : opt nat64;
601+
potential_voting_power : opt nat64;
600602
};
601603

602604
type NeuronBasketConstructionParameters = record {
@@ -646,9 +648,11 @@ type NeuronInfo = record {
646648
retrieved_at_timestamp_seconds : nat64;
647649
visibility : opt int32;
648650
known_neuron_data : opt KnownNeuronData;
649-
voting_power : nat64;
650651
age_seconds : nat64;
651652
voting_power_refreshed_timestamp_seconds : opt nat64;
653+
voting_power : nat64;
654+
deciding_voting_power : opt nat64;
655+
potential_voting_power : opt nat64;
652656
};
653657

654658
type NeuronStakeTransfer = record {

rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ message NeuronInfo {
260260
// refreshed, this will be set to 2024-11-05T00:00:01 UTC (1730764801 seconds
261261
// after the UNIX epoch).
262262
optional uint64 voting_power_refreshed_timestamp_seconds = 13;
263+
// See the analogous field in Nueron.
264+
optional uint64 deciding_voting_power = 14;
265+
// See the analogous field in Neuron.
266+
optional uint64 potential_voting_power = 15;
263267
}
264268

265269
// A transfer performed from some account to stake a new neuron.
@@ -463,6 +467,63 @@ message Neuron {
463467
// used for the circular buffer. This is used to determine which entry
464468
// to overwrite next.
465469
optional uint32 recent_ballots_next_entry_index = 25;
470+
471+
// The amount of "sway" this neuron has when voting on proposals.
472+
//
473+
// When a proposal is created, each eligible neuron gets a "blank" ballot. The
474+
// amount of voting power in that ballot is set to the neuron's deciding
475+
// voting power at the time of proposal creation. There are two ways that a
476+
// proposal can become decided:
477+
//
478+
// 1. Early: Either more than half of the total voting power in the ballots
479+
// votes in favor (then the proposal is approved), or at least half of the
480+
// votal voting power in the ballots votes against (then, the proposal is
481+
// rejected).
482+
//
483+
// 2. The proposal's voting deadline is reached. At that point, if there is
484+
// more voting power in favor than against, and at least 3% of the total
485+
// voting power voted in favor, then the proposal is approved. Otherwise, it
486+
// is rejected.
487+
//
488+
// If a neuron regularly refreshes its voting power, this has the same value
489+
// as potential_voting_power. Actions that cause a refresh are as follows:
490+
//
491+
// 1. voting directly (not via following)
492+
// 2. set following
493+
// 3. refresh voting power
494+
//
495+
// (All of these actions are performed via the manage_neuron method.)
496+
//
497+
// However, if a neuron has not refreshed in a "long" time, this will be less
498+
// than potential voting power. See VotingPowerEconomics. As a further result
499+
// of less deciding voting power, not only does it have less influence on the
500+
// outcome of proposals, the neuron receives less voting rewards (when it
501+
// votes indirectly via following).
502+
//
503+
// For details, see https://dashboard.internetcomputer.org/proposal/132411.
504+
//
505+
// Per NNS policy, this is opt. Nevertheless, it will never be null.
506+
optional uint64 deciding_voting_power = 26;
507+
508+
// The amount of "sway" this neuron can have if it refreshes its voting power
509+
// frequently enough.
510+
//
511+
// Unlike deciding_voting_power, this does NOT take refreshing into account.
512+
// Rather, this only takes three factors into account:
513+
//
514+
// 1. (Net) staked amount - This is the "base" of a neuron's voting power.
515+
// This primarily consists of the neuron's ICP balance.
516+
//
517+
// 2. Age - Neurons with more age have more voting power (all else being
518+
// equal).
519+
//
520+
// 3. Dissolve delay - Neurons with longer dissolve delay have more voting
521+
// power (all else being equal). Neurons with a dissolve delay of less
522+
// than six months are not eligible to vote. Therefore, such neurons
523+
// are considered to have 0 voting power.
524+
//
525+
// Per NNS policy, this is opt. Nevertheless, it will never be null.
526+
optional uint64 potential_voting_power = 27;
466527
}
467528

468529
// Subset of Neuron that has no collections or big fields that might not exist in most neurons, and

rs/nns/governance/src/gen/ic_nns_governance.pb.v1.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ pub struct NeuronInfo {
128128
/// after the UNIX epoch).
129129
#[prost(uint64, optional, tag = "13")]
130130
pub voting_power_refreshed_timestamp_seconds: ::core::option::Option<u64>,
131+
/// See the analogous field in Nueron.
132+
#[prost(uint64, optional, tag = "14")]
133+
pub deciding_voting_power: ::core::option::Option<u64>,
134+
/// See the analogous field in Neuron.
135+
#[prost(uint64, optional, tag = "15")]
136+
pub potential_voting_power: ::core::option::Option<u64>,
131137
}
132138
/// A transfer performed from some account to stake a new neuron.
133139
#[derive(
@@ -311,6 +317,63 @@ pub struct Neuron {
311317
/// to overwrite next.
312318
#[prost(uint32, optional, tag = "25")]
313319
pub recent_ballots_next_entry_index: ::core::option::Option<u32>,
320+
/// The amount of "sway" this neuron has when voting on proposals.
321+
///
322+
/// When a proposal is created, each eligible neuron gets a "blank" ballot. The
323+
/// amount of voting power in that ballot is set to the neuron's deciding
324+
/// voting power at the time of proposal creation. There are two ways that a
325+
/// proposal can become decided:
326+
///
327+
/// 1. Early: Either more than half of the total voting power in the ballots
328+
/// votes in favor (then the proposal is approved), or at least half of the
329+
/// votal voting power in the ballots votes against (then, the proposal is
330+
/// rejected).
331+
///
332+
/// 2. The proposal's voting deadline is reached. At that point, if there is
333+
/// more voting power in favor than against, and at least 3% of the total
334+
/// voting power voted in favor, then the proposal is approved. Otherwise, it
335+
/// is rejected.
336+
///
337+
/// If a neuron regularly refreshes its voting power, this has the same value
338+
/// as potential_voting_power. Actions that cause a refresh are as follows:
339+
///
340+
/// 1. voting directly (not via following)
341+
/// 2. set following
342+
/// 3. refresh voting power
343+
///
344+
/// (All of these actions are performed via the manage_neuron method.)
345+
///
346+
/// However, if a neuron has not refreshed in a "long" time, this will be less
347+
/// than potential voting power. See VotingPowerEconomics. As a further result
348+
/// of less deciding voting power, not only does it have less influence on the
349+
/// outcome of proposals, the neuron receives less voting rewards (when it
350+
/// votes indirectly via following).
351+
///
352+
/// For details, see <https://dashboard.internetcomputer.org/proposal/132411.>
353+
///
354+
/// Per NNS policy, this is opt. Nevertheless, it will never be null.
355+
#[prost(uint64, optional, tag = "26")]
356+
pub deciding_voting_power: ::core::option::Option<u64>,
357+
/// The amount of "sway" this neuron can have if it refreshes its voting power
358+
/// frequently enough.
359+
///
360+
/// Unlike deciding_voting_power, this does NOT take refreshing into account.
361+
/// Rather, this only takes three factors into account:
362+
///
363+
/// 1. (Net) staked amount - This is the "base" of a neuron's voting power.
364+
/// This primarily consists of the neuron's ICP balance.
365+
///
366+
/// 2. Age - Neurons with more age have more voting power (all else being
367+
/// equal).
368+
///
369+
/// 3. Dissolve delay - Neurons with longer dissolve delay have more voting
370+
/// power (all else being equal). Neurons with a dissolve delay of less
371+
/// than six months are not eligible to vote. Therefore, such neurons
372+
/// are considered to have 0 voting power.
373+
///
374+
/// Per NNS policy, this is opt. Nevertheless, it will never be null.
375+
#[prost(uint64, optional, tag = "27")]
376+
pub potential_voting_power: ::core::option::Option<u64>,
314377
/// At any time, at most one of `when_dissolved` and
315378
/// `dissolve_delay` are specified.
316379
///

rs/nns/governance/src/governance.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2321,7 +2321,7 @@ impl Governance {
23212321
&& neuron.visibility() == Some(Visibility::Public)
23222322
);
23232323
if let_caller_read_full_neuron {
2324-
let mut proto = NeuronProto::from(neuron.clone());
2324+
let mut proto = neuron.clone().into_proto(now);
23252325
// We get the recent_ballots from the neuron itself, because
23262326
// we are using a circular buffer to store them. This solution is not ideal, but
23272327
// we need to do a larger refactoring to use the correct API types instead of the internal
@@ -3646,9 +3646,11 @@ impl Governance {
36463646
id: &NeuronId,
36473647
caller: &PrincipalId,
36483648
) -> Result<NeuronProto, GovernanceError> {
3649+
let now_seconds = self.env.now();
3650+
36493651
self.neuron_store
36503652
.get_full_neuron(*id, *caller)
3651-
.map(NeuronProto::from)
3653+
.map(|neuron| neuron.into_proto(now_seconds))
36523654
.map_err(GovernanceError::from)
36533655
}
36543656

rs/nns/governance/src/governance/benches.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ fn centralized_following_all_stable() -> BenchResult {
484484
//
485485
#[bench(raw)]
486486
fn compute_ballots_for_new_proposal_with_stable_neurons() -> BenchResult {
487+
let now_seconds = 1732817584;
488+
487489
let _f = temporarily_enable_active_neurons_in_stable_memory();
488490
let neurons = (0..100)
489491
.map(|id| {
@@ -495,7 +497,7 @@ fn compute_ballots_for_new_proposal_with_stable_neurons() -> BenchResult {
495497
1_000_000_000,
496498
hashmap! {}, // get the default followees
497499
)
498-
.into(),
500+
.into_proto(now_seconds),
499501
)
500502
})
501503
.collect::<BTreeMap<u64, NeuronProto>>();
@@ -507,7 +509,7 @@ fn compute_ballots_for_new_proposal_with_stable_neurons() -> BenchResult {
507509

508510
let mut governance = Governance::new(
509511
governance_proto,
510-
Box::new(MockEnvironment::new(Default::default(), 0)),
512+
Box::new(MockEnvironment::new(vec![], now_seconds)),
511513
Box::new(StubIcpLedger {}),
512514
Box::new(StubCMC {}),
513515
);

0 commit comments

Comments
 (0)