Now that Public & Private Neurons has been approved by motion proposal, I wanted to share more details with developers how the API would change, and request feedback. This will cover not only what the API would eventually look like, but also how we would transition from the current API to the eventual API, paying special attention to breaking changes and migration strategy.
Breaking Changes
The breaking changes would affect apps that read neurons that do not “belong” to the caller (i.e. the caller is neither a controller, nor hotkey of the neuron).
How the behavior will be different in the eventual API: when a) you call call a method whose response contains NeuronInfo (e.g. get_neuron_info), b) the neuron does not belong to you, and c) the neuron is private, then, a couple of fields will eventually be “redacted”. Specifically,
- recent_ballots: This field is of type vec BallotInfo. Here, redacted means that the field will have 0 elements.
- joined_community_fund_timestmap_seconds: This field is of type opt nat64. Here, redacted means that the field will have null as its value.
To interpret these values, apps will need to make use of a new field: visibility. It will be of type opt int32. Possible values in this field, and what they mean:
- opt 1: Private. In this case, if recent_ballots has 0 elements, it does not (necessarily) mean that the neuron did not vote recently. Rather, the caller cannot tell whether the neuron voted recently. Similarly, if joined_community_fund_timestamp_seconds is null, it does not (necessarily) mean that the neuron is not a member of the Neurons’ Fund. Rather, the caller cannot tell whether the neuron is in the NF.
- opt 2: Public. In this case, the aforementioned fields would not be redacted.
- null: Visibility enforcement has not been released yet. See the next section “How to Migrate”.
Structurally, the interface changes would look like this:
type NeuronInfo {
// Existing code elided...
visibility : opt int32;
}
Notice that even apps that do not migrate would still be able to deserialize responses, because the only structural change is the addition of visibility (which is of type opt int32). They would simply misinterpret the redacted fields once visibility enforcement has been released.
How to Migrate
To give apps an ample migration window, the API changes would be released in multiple steps. For breaking changes, two releases would be of particular interest:
- The visibility field would be added to NeuronInfo. This would have one of the values described above, but at this point, there is no redaction, no “enforcement”. At this point, the governance canister would declare this new visibility field, and apps would be able to start reading it, and using it to get ready for the second step.
- Fields are redacted. At this point, we say that visibility is “enforced”.
Thus, the migration window lies between these two points. During this time, apps would need to add code that looks like this pseudocode:
if recent_ballots.is_empty() {
// New: use visibility to deduce why we see recent_ballots is empty:
let is_face_value =
visibility == null || // No enforcement yet.
visibility == opt 2; // Neuron is public.
if is_face_value {
print("The neuron has not voted recently.");
} else {
assert visibility == opt 1; // Neuron is private.
print("Not sure if the neuron voted recently, because it is private.");
}
} else {
// Old behavior.
print("Here are the neuron's recent ballots:");
for ballot in recent_ballots {
print_ballot(ballot);
}
}
Notice that during the migration window, unmigrated apps would still behave the same.
Notice that migrated apps will continue to work correctly after the migration window without further changes.
Non-Breaking Changes
This section describes new functionality that apps can start taking advantage of (if they want to).
Reading and Writing Visibility
Earlier, we mentioned that NeuronInfo would gain a new visibility field. The Neuron type would also gain this field. As explained in the previous section, initially, visibility would not be “enforced”.
In addition to being able to read a neuron’s visibility, users will also need the ability to write the visibility of neurons that belong to them. To do that, there will be an enhanced version of the manage_neuron method. Specifically, the following will be added to the Operation type:
type Operation = variant {
// Existing code elided...
// New.
SetVisibility : SetVisibility;
}
// New.
type SetVisibility = record {
visibility : opt int32;
}
(The visibility field within SetVisibility is analogous to the visibility field in Neuron, except that null will be considered an invalid value, and the manage_neuron call will result in an Error response.)
Reading Public Neurons
Currently, one has to have special privileges to read a full neuron, but once we have public neurons, special privileges will not be necessary to read a full neuron if it is public. To accommodate this, the list_neurons method will be changed as follows:
- ListNeurons.include_public_neurons_in_full_neurons
- This is a new field that controls the behavior of the list_neurons method.
- type: opt bool
- ListNeuronsResponse.full_neurons
- Existing field.
- Previously, this only included neurons that belong to the caller.
- However, going forward, callers can use include_public_neurons_in_full_neurons to specify that they want public neurons to also be included in this field.
Structurally, the changes would look like this:
type ListNeurons = {
// Existing code elided...
// New.
include_public_neurons_in_full_neurons : opt bool;
}
Known Neurons Are Always Public
This will happen automatically. If a known neuron tries to become private, they will get an Error response.
References
This feature was discussed in an earlier thread.