Skip to content

fix(registry): only allow direct node operator reassignment to known node providers - #11975

Open
pietrodimarco-dfinity wants to merge 1 commit into
masterfrom
pdm/restrict-direct-node-operator-np-reassignment
Open

pietrodimarco-dfinity wants to merge 1 commit into
masterfrom
pdm/restrict-direct-node-operator-np-reassignment

Conversation

@pietrodimarco-dfinity

@pietrodimarco-dfinity pietrodimarco-dfinity commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

update_node_operator_config_directly lets 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_id to 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 an UpdateNodeOperatorConfig proposal. 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.
  • Unit tests: rejection of an unknown principal (record unchanged, rate-limit capacity untouched), no-op reassignment to the current NP still accepted, happy path reassigning to an NP that another record already names.
  • 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

  • Any principal already present as an NP in some node operator record stays a valid target, including ones introduced through this method before the change. It may be worth auditing mainnet node operator records for NP principals that are not in governance's NP list.
  • Two NNS-approved NPs can still move an operator between themselves directly; that needs both of them to act and is visible in registry history.
  • Tooling that rotates an NP to a brand-new principal via this method now needs one UpdateNodeOperatorConfig proposal for the first operator; the remaining operators can follow directly.
  • The node rewards canister still attributes a whole UTC day to the end-of-day NP. For proposal-executed NP changes that is acceptable; hardening the attribution there is a possible follow-up.

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-targets with the deny/allow flags from ci/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_request could 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

…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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

Copy link
Copy Markdown
Contributor Author

CI note: CI RBE Evaluation / Bazel Test All on RBE failed on 2b9492a with a remote-cache error, not a build error. Every failed action is a Remote Execution Failure: Internal: cache operation failed: unexpected EOF (Bazel exit 34) from the Namespace cache while compiling rs/registry/canister (canister, canister--test_feature, canister--canbench_feature), and the build was cut short by --keep_going at 78 of 653 targets. The same job passed on master's head 020946c, and cargo check -p registry-canister --all-targets --all-features plus the crate's unit tests pass locally on this commit. I'll re-run the failed job once the Kickoff run has finished (GitHub refuses a re-run while it is still running).


Generated by Claude Code

@pietrodimarco-dfinity
pietrodimarco-dfinity marked this pull request as ready for review October 28, 2026 18:14
@pietrodimarco-dfinity
pietrodimarco-dfinity requested a review from a team as a code owner October 28, 2026 18:14

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request changes code owned by the Governance team. Therefore, make sure that
you have considered the following (for Governance-owned code):

  1. Update unreleased_changelog.md (if there are behavior changes, even if they are
    non-breaking).

  2. Are there BREAKING changes?

  3. Is a data migration needed?

  4. Security review?

How to Satisfy This Automatic Review

  1. Go to the bottom of the pull request page.

  2. Look for where it says this bot is requesting changes.

  3. Click the three dots to the right.

  4. Select "Dismiss review".

  5. 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

@zeropath-ai

zeropath-ai Bot commented Oct 28, 2026

Copy link
Copy Markdown

✅ No security or compliance issues detected. Reviewed everything up to 2b9492a.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/registry/canister/src/mutations/do_update_node_operator_config_directly.rs
      Implement validation to ensure new Node Provider is already associated with some node operator
► rs/registry/canister/tests/update_node_operator_config_directly.rs
      Add tests for direct node operator config updates including new validation cases
Other ► rs/registry/canister/unreleased_changelog.md
      Document behavior change for update_node_operator_config_directly

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants