Repository navigation
fix(registry): only allow direct node operator reassignment to known node providers - #11975
pietrodimarco-dfinity wants to merge 1 commit into
Conversation
…node providers `update_node_operator_config_directly` let the current node provider of a node operator record set the record's node provider to any principal, with no consent from, and no NNS approval of, the new principal. The new principal immediately became the one authorised to reassign the record again. Node provider principals are meant to enter node operator records only through NNS proposals (`AddNodeOperator`, `UpdateNodeOperatorConfig`), and node rewards attribute each node's daily performance to the node provider of its node operator, so a node provider must not be able to move that attribution to a principal of its own choosing without the NNS. The direct method now requires the new node provider to already be the node provider of at least one node operator record. It can still move a node operator between node providers the NNS has approved; assigning one to a brand-new node provider needs an `UpdateNodeOperatorConfig` proposal. A rejected call does not consume node provider rate-limit capacity. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JzwvAUeNTvkQ2X5ym8L1YN
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues are recorded for the change restricting direct node operator reassignment to known providers.
No open issues 🎉
What changed in this PR
This PR restricts direct node operator reassignment to node providers already referenced by a node operator record at the latest registry version. Assigning a brand-new provider now requires an NNS proposal.
Changes:
- Adds a known-provider check before updating the node operator record.
- Expands unit and integration coverage for accepted and rejected reassignments, including preservation of rate-limit capacity on rejection.
- Documents the restriction in the security changelog.
| File | Description |
|---|---|
rs/registry/canister/unreleased_changelog.md |
Documents the security restriction and its node-rewards context. |
rs/registry/canister/tests/update_node_operator_config_directly.rs |
Seeds a known target provider and tests rejection of an unknown provider. |
rs/registry/canister/src/mutations/do_update_node_operator_config_directly.rs |
Adds provider validation and unit tests for reassignment and rate-limit behavior. |
Review effort: Balanced
|
CI note: Generated by Claude Code |
There was a problem hiding this comment.
This pull request changes code owned by the Governance team. Therefore, make sure that
you have considered the following (for Governance-owned code):
-
Update
unreleased_changelog.md(if there are behavior changes, even if they are
non-breaking). -
Are there BREAKING changes?
-
Is a data migration needed?
-
Security review?
How to Satisfy This Automatic Review
-
Go to the bottom of the pull request page.
-
Look for where it says this bot is requesting changes.
-
Click the three dots to the right.
-
Select "Dismiss review".
-
In the text entry box, respond to each of the numbered items in the previous
section, declare one of the following:
-
Done.
-
$REASON_WHY_NO_NEED. E.g. for
unreleased_changelog.md, "No
canister behavior changes.", or for item 2, "Existing APIs
behave as before.".
Brief Guide to "Externally Visible" Changes
"Externally visible behavior change" is very often due to some NEW canister API.
Changes to EXISTING APIs are more likely to be "breaking".
If these changes are breaking, make sure that clients know how to migrate, how to
maintain their continuity of operations.
If your changes are behind a feature flag, then, do NOT add entrie(s) to
unreleased_changelog.md in this PR! But rather, add entrie(s) later, in the PR
that enables these changes in production.
Reference(s)
For a more comprehensive checklist, see here.
GOVERNANCE_CHECKLIST_REMINDER_DEDUP
|
✅ No security or compliance issues detected. Reviewed everything up to 2b9492a. Security OverviewDetected Code Changes
|
Summary
update_node_operator_config_directlylets the current node provider (NP) of a node operator record change the record's NP without an NNS proposal. Until now it accepted any principal as the new NP: the new principal needed no consent and no NNS approval, and it immediately became the principal authorised to reassign the record again.NP principals are meant to enter node operator records only through NNS proposals (
AddNodeOperator,UpdateNodeOperatorConfig). Node rewards also attribute each node's daily performance to the NP of its node operator (grouping by the NP found in the last registry version of the day), so an NP must not be able to move that attribution to a principal of its own choosing without the NNS.Change
The direct method now requires the new
node_provider_idto already be the NP of at least one node operator record at the latest registry version. It can still move a node operator between NPs the NNS has approved; assigning a node operator to a brand-new NP needs anUpdateNodeOperatorConfigproposal. A rejected call does not consume the caller's NP rate-limit capacity (the reservation is dropped, not committed).rs/registry/canister/src/mutations/do_update_node_operator_config_directly.rs: the new check and a helper that scans node operator records for the principal.rs/registry/canister/tests/update_node_operator_config_directly.rs: the integration test now seeds a second node operator so the target NP is known, and gained a negative case for an unknown target.rs/registry/canister/unreleased_changelog.md: entry under Security.Notes for reviewers
UpdateNodeOperatorConfigproposal for the first operator; the remaining operators can follow directly.Testing
Run locally on this commit:
cargo check -p registry-canister --all-targets --all-features: passes.cargo fmt -- --check: clean.cargo clippy --locked --all-features -p registry-canister --all-targetswith the deny/allow flags fromci/scripts/rust-lint.sh: no findings.cargo test -p registry-canister --lib do_update_node_operator_config_directly: 5 passed, 0 failed.The PocketIC integration test
node_provider_is_updated_on_receiving_the_requestcould not be run locally (no Bazel in the session); it compiles, and CI runs it as part of//rs/registry/canister:registry_canister_integration_test.🤖 Generated with Claude Code
https://claude.ai/code/session_01JzwvAUeNTvkQ2X5ym8L1YN