Skip to content

Conversation

@fbac
Copy link
Collaborator

@fbac fbac commented Mar 5, 2025

Summary

  • Closes Nodes contract: adapt xmtpd code #543
  • xmtpd now uses NodesV2. This is a breaking change! Upgrade test is expected to fail!.
  • Payer node selection skips nodes disabled (enforced by admins) or with not enabled api (enforced by NOPs)
  • Sync stops subscribing to disabled nodes (enforced by admins) or with replication disabled (enforced by NOPs)
  • Minor changes: fixed some error strings (linter complains about them starting with uppercase) and removed an unused interface.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Standardized error message capitalization in the CLI for clearer guidance.
  • New Features

    • Enhanced node management capabilities with new commands for enabling, disabling, and configuring nodes.
    • Expanded node information now includes replication status, API availability, disable state, and minimum fee details.
    • Introduced utility functions for generating healthy and unhealthy node configurations for testing.
    • Updated CLI options structure to improve organization and clarity.
  • Tests

    • Updated testing utilities to generate consistent node configurations, improving the reliability of node-related tests.
    • Added new test cases to validate node selection behavior under various conditions.
    • Refactored existing tests to align with updated node structures and functionalities.
    • Enhanced test coverage for the new node management commands and options.

@fbac fbac requested a review from a team as a code owner March 5, 2025 12:21
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2025

Walkthrough

This pull request modifies CLI command-line argument parsing, updates the structure of options, and enhances node management functionalities. Key changes include renaming command-line flags, restructuring the CLI struct, and standardizing error message formatting. The removal of the deprecated NodeRegistry interface and the introduction of new methods in the INodeRegistryAdmin interface reflect a shift in node management capabilities. Additionally, the Node struct has been revised to include new fields while removing outdated ones. Overall, these changes improve the organization and clarity of the codebase.

Changes

Files Change Summary
cmd/cli/cli_test.go, cmd/cli/main.go Updated command-line argument parsing, modified error message formatting, and expanded the CLI struct with new fields and commands.
pkg/api/payer/nodeSelector.go, pkg/api/payer/nodeSelector_test.go, pkg/api/payer/publish_test.go Standardized node instantiation using utility functions, added new test cases, and improved clarity in tests by using consistent node definitions.
pkg/blockchain/interface.go Removed the NodeRegistry interface, eliminating the associated node registration functionality from the blockchain package.
pkg/mocks/registry/mock_NodesContract.go Renamed the AllNodes method to GetAllNodes and updated return types to reflect changes in the nodes package.
pkg/registry/contractRegistry.go, pkg/registry/contractRegistry_test.go Updated method calls from AllNodes to GetAllNodes, adjusted return types, and modified tests to align with new data structures.
pkg/registry/interface.go Replaced AllNodes with GetAllNodes, changing the return type to a slice of nodes.INodesNodeWithId.
pkg/registry/node.go, pkg/server/server_test.go Revised the Node struct by removing certain fields and adding new ones; updated tests to reflect these changes.
pkg/sync/syncWorker.go Simplified the condition for invoking unhealthy node handling based on updated node status.
pkg/testutils/contractRegistry.go Introduced utility functions for creating healthy and unhealthy nodes, improving test clarity and maintainability.
pkg/blockchain/client.go, pkg/blockchain/migrator/migrator.go, pkg/blockchain/registryAdmin.go Introduced new transaction execution helpers, consolidated node types, and updated interfaces to streamline node management processes.
contracts/src/Nodes.sol Significant modifications to the Nodes contract, including the introduction of role-based access control and new methods for node management.
dev/local.env, dev/register-local-node, dev/register-local-node-2 Updated environment variable settings and registration scripts to align with new node management functionalities.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Command Line Interface
    participant NodeManager as Node Management Module
    participant NodeRegistry as Node Registry
    participant Logger as Logging System

    CLI->>NodeManager: Parse command arguments
    NodeManager->>NodeRegistry: Validate node options
    alt Node options valid
        NodeRegistry-->>NodeManager: Return node details
        NodeManager->>Logger: Log success message
    else Node options invalid
        NodeManager-->>CLI: Return error message
    end
Loading

Possibly related PRs

Suggested reviewers

  • richardhuaaa
  • mkysel

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 golangci-lint (1.62.2)

Error: can't load config: the Go language version (go1.23) used to build golangci-lint is lower than the targeted Go version (1.24)
Failed executing command with error: can't load config: the Go language version (go1.23) used to build golangci-lint is lower than the targeted Go version (1.24)

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@fbac fbac changed the title Breaking: Integrate NodesV2 Breaking upgrades: Integrate NodesV2 Mar 5, 2025
@fbac fbac changed the title Breaking upgrades: Integrate NodesV2 Breaking upgrades!: Integrate NodesV2 Mar 5, 2025
@fbac fbac changed the title Breaking upgrades!: Integrate NodesV2 Breaking!: Integrate NodesV2 Mar 5, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
pkg/testutils/contractRegistry.go (1)

1-23: Clarify “unhealthy” vs. “healthy” definitions.
Currently, “unhealthy” simply sets IsDisabled = true but keeps other boolean fields true. This may be sufficient for your test coverage. However, consider setting additional fields (like IsApiEnabled = false) to reflect multiple unhealthy states in your tests. This can improve coverage of different node conditions.

pkg/registry/contractRegistry.go (1)

219-219: Validate new contract method name.
The rename from AllNodes to GetAllNodes is consistent with the code changes. Ensure any mocks or stubs for testing also reflect this new method name.

pkg/server/server_test.go (1)

102-102: Consider validating node functionality with disabled features.

The tests currently only validate nodes with all features enabled. It would be beneficial to add test cases that verify the behavior when nodes have certain features disabled, such as API or replication, to ensure the system correctly handles these cases according to the PR objectives.

Add test cases that verify server behavior with nodes that have various feature flags disabled:

+func TestServerWithDisabledFeatures(t *testing.T) {
+    // Setup similar to TestCreateServer
+    // ...
+
+    // Test with API disabled
+    apiDisabledNode := r.Node{
+        NodeID:               server1NodeID,
+        SigningKey:           &privateKey1.PublicKey,
+        HttpAddress:          fmt.Sprintf("http://localhost:%d", server1Port),
+        IsReplicationEnabled: true,
+        IsApiEnabled:         false, // API disabled
+        IsDisabled:           false,
+        MinMonthlyFee:        big.NewInt(0),
+        IsValidConfig:        true,
+    }
+
+    // Test behavior with API disabled
+    // ...
+
+    // Test with replication disabled
+    // ...
+
+    // Test with completely disabled node
+    // ...
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6f925 and afa459b.

📒 Files selected for processing (14)
  • cmd/cli/cli_test.go (1 hunks)
  • cmd/cli/main.go (2 hunks)
  • pkg/api/payer/nodeSelector.go (2 hunks)
  • pkg/api/payer/nodeSelector_test.go (9 hunks)
  • pkg/api/payer/publish_test.go (3 hunks)
  • pkg/blockchain/interface.go (0 hunks)
  • pkg/mocks/registry/mock_NodesContract.go (3 hunks)
  • pkg/registry/contractRegistry.go (5 hunks)
  • pkg/registry/contractRegistry_test.go (4 hunks)
  • pkg/registry/interface.go (1 hunks)
  • pkg/registry/node.go (5 hunks)
  • pkg/server/server_test.go (3 hunks)
  • pkg/sync/syncWorker.go (1 hunks)
  • pkg/testutils/contractRegistry.go (1 hunks)
💤 Files with no reviewable changes (1)
  • pkg/blockchain/interface.go
✅ Files skipped from review due to trivial changes (2)
  • cmd/cli/main.go
  • cmd/cli/cli_test.go
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Test (Node)
  • GitHub Check: Push Docker Images to GitHub Packages (xmtpd)
  • GitHub Check: Build pre-baked anvil-xmtpd
🔇 Additional comments (27)
pkg/registry/interface.go (1)

5-5: Update to NodesV2 package for interface dependency

This change aligns with the PR objective of integrating NodesV2 into the system.

pkg/api/payer/publish_test.go (3)

5-6: Added the missing testing import

Adding the explicit import for the testing package is a good practice.


132-132: Enhanced test maintainability with standardized node creation

Using testutils.GetHealthyNode(100) instead of direct instantiation of a node object improves maintainability and ensures consistent test data.


186-186: Enhanced test maintainability with standardized node creation

Similar to the previous change, using testutils.GetHealthyNode(100) improves maintainability.

pkg/registry/node.go (4)

6-6: Added math/big import for new MinMonthlyFee field

This import is required for the addition of the *big.Int type field.


50-50: Fixed error message capitalization

The error message now follows proper capitalization conventions.


55-55: Fixed error message capitalization

The error message now follows proper capitalization conventions.


74-74: Fixed error message capitalization

The error message now follows proper capitalization conventions.

pkg/sync/syncWorker.go (1)

176-176:

Details

✅ Verification successful

Updated node health check condition to align with NodesV2 integration

The condition has been changed from checking !node.IsHealthy || !node.IsValidConfig to looking at more specific properties: node.IsDisabled || !node.IsReplicationEnabled || !node.IsValidConfig. This aligns with the PR objective to skip nodes that are disabled or don't have replication enabled.

Let's verify that these new fields are properly initialized when nodes are created:


🏁 Script executed:

#!/bin/bash
# Search for Node struct initialization patterns to verify proper field assignment
rg "Node\{" --type go -A 10

Length of output: 28747


Verified: Node health check condition and field assignments are correct

I reviewed the Node struct initializations across the codebase and confirmed that the new fields—IsDisabled, IsReplicationEnabled, and IsValidConfig—are properly set. In particular:

  • pkg/testutils/contractRegistry.go: Healthy nodes are initialized with IsDisabled: false, IsReplicationEnabled: true, and IsValidConfig: true.
  • pkg/api/payer/nodeSelector_test.go: Similar patterns confirm that test nodes are created with the correct flag values.

The update in pkg/sync/syncWorker.go (line 176) to check for node.IsDisabled || !node.IsReplicationEnabled || !node.IsValidConfig is well aligned with these initializations and meets the PR objective to properly skip nodes that are disabled or lack replication enabled.

Approved.

pkg/api/payer/nodeSelector.go (1)

7-8: Import looks good.
The addition of the "sort" import is appropriate and used by sort.Slice() below.

pkg/registry/contractRegistry.go (3)

50-52: Good use of interface-implementation guard.
Declaring var _ NodeRegistry = &SmartContractRegistry{} is a concise way to ensure SmartContractRegistry implements the NodeRegistry interface at compile time.


235-257: Expanded node fields are properly mapped.
The function correctly sets IsReplicationEnabled, IsApiEnabled, IsDisabled, and MinMonthlyFee. Verify that removed fields like IsHealthy are no longer needed.


60-63:

Details

❓ Verification inconclusive

NodesV2 transition is consistent.
Switching to nodesv2.NewNodesV2Caller aligns with the introduction of your new NodesV2 contracts. Ensure all references to nodes calls have been removed or updated throughout the codebase.

Run this script to locate any leftover references to nodes.:


🏁 Script executed:

#!/bin/bash
rg --context 3 'nodes\.' 

Length of output: 51916


Ensure NodesV2 Transition Consistency and Clarify Legacy Uses

The update in pkg/registry/contractRegistry.go correctly switches to using nodesv2.NewNodesV2Caller, aligning with the new NodesV2 contracts. However, a recursive search for nodes\. reveals many remaining references (mainly in test utilities and legacy registry callers) that still invoke functions from the old nodes package. Please verify that these legacy calls are intentional—i.e., that support for NodesV1 is still required where applicable. If the goal is to fully migrate to NodesV2, then those instances will need further updates.

  • The call in pkg/registry/contractRegistry.go now uses the new nodesv2 API as expected.
  • Multiple files (e.g., in pkg/testutils, pkg/blockchain/registryCaller.go, and related tests) still reference nodes. functions.
  • Confirm whether the leftover legacy references are intended to coexist (supporting NodesV1) or if they should also be updated to nodesv2.
pkg/api/payer/nodeSelector_test.go (3)

62-76: Well-designed test case for an important edge condition.

This test case verifies the behavior when all nodes are unhealthy, which is a critical edge case. It confirms that the system correctly returns the expected error message when no active nodes are available for assignment.


80-101: The node structure has been properly updated with new filtering fields.

The test now includes the full set of node attributes necessary for testing the updated node selection logic, including IsDisabled, IsReplicationEnabled, IsApiEnabled, and IsValidConfig fields. This allows for more comprehensive testing of the new node filtering criteria described in the PR objectives.


17-25: Helper utilities improve test code maintainability.

In the test setup, there's a transition from hardcoded node structures to using utility functions testutils.GetHealthyNode(). This improves maintainability by centralizing node creation logic.

pkg/registry/contractRegistry_test.go (4)

6-14: Successfully migrated to NodesV2 with appropriate import changes.

The file now correctly imports the math/big package to support the BigInt node IDs and has updated to use the nodesv2 package instead of the previous nodes package.


48-64: Successfully refactored to use the new NodesV2 API and data structures.

The test has been updated to use the new GetAllNodes method instead of AllNodes, and it correctly handles the new return type ([]nodesv2.INodesNodeWithId). The node ID is now properly initialized using big.NewInt() instead of a uint32 value.


97-111: Contract registry tests properly handled method signature changes.

The mock setup has been updated to use the proper return type and method name, maintaining test functionality while adapting to the breaking changes in the NodesV2 API.


146-154:

Details

✅ Verification successful

Verify that all other callers of GetAllNodes are updated.

The breaking change from AllNodes to GetAllNodes requires updates across the codebase. Let's verify all call sites have been properly updated.


🏁 Script executed:

#!/bin/bash
# Check for any remaining references to AllNodes that might need updating
grep -r "AllNodes" --include="*.go" . | grep -v "mock_"

Length of output: 30538


Evaluation of GetAllNodes update consistency across the codebase

The grep search confirms that all new functionality and tests now use the updated GetAllNodes API. In particular, the updated test in pkg/registry/contractRegistry_test.go and the V2 methods in the nodesV2 bindings and registry caller are all using GetAllNodes. The remaining references to AllNodes appear only in legacy V1 contexts or in auto‑generated contract bindings (e.g., in contracts/pkg/nodes/Nodes.go and the V1 methods in pkg/blockchain/registryCaller.go), which we assume are maintained for backward compatibility.

No further changes are required here as long as the legacy V1 usage is intentional. Please confirm that supporting both V1 and V2 is by design.

pkg/server/server_test.go (3)

9-9: Added necessary import for big.Int support.

The addition of the math/big package import is necessary for initializing the new MinMonthlyFee field with big.NewInt(0).


95-104: Updated Node structure with new NodesV2 fields.

The test has been updated to include the new fields from the NodesV2 implementation: IsReplicationEnabled, IsApiEnabled, IsDisabled, and MinMonthlyFee. These fields are essential for the enhanced node management capabilities described in the PR objectives.


237-246: Consistent field updates across test cases.

The second test case also correctly initializes all the new fields in the Node structure, maintaining consistency with the first test case and ensuring proper test coverage for the updated node structure.

pkg/mocks/registry/mock_NodesContract.go (4)

9-9: Updated import to use NodesV2 package.

The mock now correctly imports the nodesv2 package instead of the previous nodes package.


25-31: Method renamed from AllNodes to GetAllNodes.

The method has been renamed from AllNodes to GetAllNodes to match the changes in the interface, and the panic message has been updated accordingly.


33-44: Return type updated to use NodesV2 data structures.

The return type has been correctly updated from []nodes.NodesNodeWithId to []nodesv2.INodesNodeWithId, and the type assertions and error handling have been adjusted accordingly.


55-81: Mock helper methods updated for the renamed method.

All related helper methods and structs have been correctly updated to reflect the method name change from AllNodes to GetAllNodes.

if node.IsDisabled || !node.IsApiEnabled {
continue
}
nodeLocations[i] = uint32(i) * spacing
Copy link
Contributor

@neekolas neekolas Mar 6, 2025

Choose a reason for hiding this comment

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

I see your conversation with the AI below about the extra for loop, but I think having empty indexes in this array is going to be a footgun that will make the code harder to reason about.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It shouldn't apply here in the payer. The contract registry gets rid of those empty nodes during refreshData(), right?

	newNodes := []Node{}
	for _, node := range fromContract {
		// nodes realistically start at 100, but the contract fills the array with empty nodes
		if !node.IsValidConfig {
			continue
		}

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we're taking about different things. We have a nodeLocations array of length numNodes, but only the indexes with IsDisabled = false && IsApiEnabled = true have data in them. The rest of the indexes are set to 0 because of the continue on line 62.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I see. Good catch! I just modified a bit the test TestGetNode_NoAvailableActiveNodes and triggered an error because of this. Modifying this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Modified. I moved the banlist filter up as well, now we filter earlier, sort, and return the candidate.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
pkg/api/payer/nodeSelector_test.go (2)

75-75: Avoid exact string matching for error messages

Asserting on the exact error message text might make tests brittle. If the error message is updated slightly, the test will fail even if the behavior is correct.

Consider using string contains or regex matching to assert that the error message contains the essential information:

-require.Equal(t, "no available nodes after filtering", err.Error())
+require.Contains(t, err.Error(), "no available nodes")

78-93: Create a matrix of node health conditions to test

This test confirms that at least one healthy node is correctly returned, but it doesn't validate different combinations of node health properties (like a node with API disabled but not marked as completely disabled).

Consider extending the test cases to create a matrix that covers various node health configurations:

  • Node with IsApiEnabled=false but other fields healthy
  • Node with IsReplicationEnabled=false but other fields healthy
  • Node with IsValidConfig=false but other fields healthy

This would provide more thorough coverage of the filtering logic introduced in this PR.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afa459b and 5c6eb52.

📒 Files selected for processing (2)
  • pkg/api/payer/nodeSelector.go (3 hunks)
  • pkg/api/payer/nodeSelector_test.go (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/api/payer/nodeSelector.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test (Node)
  • GitHub Check: Build pre-baked anvil-xmtpd
🔇 Additional comments (3)
pkg/api/payer/nodeSelector_test.go (3)

62-76: Test the different unhealthy conditions separately

While this test verifies the behavior when all nodes are unhealthy, it doesn't specifically test different unhealthy conditions (e.g., API disabled vs replication disabled). Consider creating test cases that verify each specific filtering condition independently.

The implementation of GetUnhealthyNode in the testutils package may only be setting IsDisabled to true while leaving other fields (like IsApiEnabled or IsReplicationEnabled) unchanged. This test might not fully verify the PR's objective to skip nodes with API disabled.

#!/bin/bash
# Check the implementation of GetUnhealthyNode
grep -r "func GetUnhealthyNode" --include="*.go" .
cat $(grep -l "func GetUnhealthyNode" --include="*.go" .)

98-118: LGTM! Clear node configuration

The expanded node attributes in this test provide explicit control over all health-related fields, making the test intentions clearer.


136-244: Good refactoring using test utilities

Using testutils.GetHealthyNode() across all test cases improves consistency and makes the tests more maintainable.

Comment on lines 51 to 56
// Filter out banned, disabled, or non-API nodes
var availableNodes []registry.Node
for _, node := range nodes {
if node.IsDisabled || !node.IsApiEnabled {
continue
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that this is the right place to do this. reg.GetNodes() already does a bunch of pre-filtering. Why dont we do it there so its all in one place? Look at func (s *SmartContractRegistry) refreshData() error

if node.IsDisabled || !node.IsApiEnabled {
continue
}
if _, exists := banned[node.NodeID]; !exists {
Copy link
Contributor

Choose a reason for hiding this comment

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

If the number of available nodes changes based on the ban list, then the partitions will change every time you add a member to the banlist and retry.

I don't think that works. The consistent hashing ring should be consistent between retries.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if we did something like this, so that the numNodes and spacing doesn't change unless the result set from the registry changes.

// GetNode selects a node for a given topic using stable hashing
func (s *StableHashingNodeSelectorAlgorithm) GetNode(
	topic topic.Topic,
	banlist ...[]uint32,
) (uint32, error) {
	nodes, err := s.reg.GetNodes()
	if err != nil {
		return 0, err
	}

	// Flatten banlist
	banned := make(map[uint32]struct{})
	for _, list := range banlist {
		for _, id := range list {
			banned[id] = struct{}{}
		}
	}

	// Sort availableNodes to ensure stability
	sort.Slice(nodes, func(i, j int) bool {
		return nodes[i].NodeID < nodes[j].NodeID
	})

	topicHash := HashKey(topic)

	numNodes := uint32(len(nodes))
	maxHashSpace := ^uint32(0)
	spacing := maxHashSpace / numNodes

	// Compute virtual positions for each available node
	nodeLocations := make([]uint32, numNodes)
	for i := range availableNodes {
		nodeLocations[i] = uint32(i) * spacing
	}

	// Binary search to find the first node with a virtual position >= topicHash
	idx := sort.Search(len(nodeLocations), func(i int) bool {
		node := nodes[i]
		if node.IsDisabled || !node.IsApiEnabled {
			return false
		}
		if _, banned := banned[node.NodeID]; banned {
			return false
		}
		return topicHash < nodeLocations[i]
	})

	// Select the appropriate node from availableNodes
	candidateIdx := idx % len(nodeLocations)
	return availableNodes[candidateIdx].NodeID, nil
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (2)
pkg/api/payer/nodeSelector_test.go (2)

1-283: 💡 Verification agent

❓ Verification inconclusive

Verify the missing test cases mentioned in the AI summary.

The AI summary mentions two new test functions that should be in this file but aren't visible in the provided code:

  • TestGetNode_NoAvailableActiveNodes
  • TestGetNode_AtLeastOneNodeIsAvailable

These test cases appear to be important for verifying the new node selection criteria mentioned in the PR objectives.


🏁 Script executed:

#!/bin/bash
# Check if these test functions exist elsewhere in the file or in other test files
grep -r "TestGetNode_NoAvailableActiveNodes\|TestGetNode_AtLeastOneNodeIsAvailable" --include="*.go" ./pkg/api/payer/

Length of output: 119


Missing Required Test Cases
Our search indicates that the test functions TestGetNode_NoAvailableActiveNodes and TestGetNode_AtLeastOneNodeIsAvailable—referenced in the AI summary and intended to verify the new node selection criteria—are not present anywhere in the pkg/api/payer/ directory. Please add these tests to ensure that both edge cases (no available active nodes and at least one available node) are covered.


6-283: 💡 Verification agent

🧩 Analysis chain

Missing tests for disabled node conditions.

According to the PR objectives, the payer node selection process has been updated to skip nodes that are disabled or don't have the API enabled. However, the current tests don't verify this functionality.

Consider adding test cases that verify the node selection behavior when some nodes are:

  1. Disabled by administrators (IsDisabled: true)
  2. Have API disabled (IsApiEnabled: false)
  3. Have replication disabled (IsReplicationEnabled: false)

You could add tests similar to the existing ones but include unhealthy nodes in the mix to verify they're properly skipped.


🏁 Script executed:

#!/bin/bash
# Check if there's already a function for unhealthy nodes
grep -r "func GetUnhealthyNode" --include="*.go" ./pkg/testutils

Length of output: 155


Missing Tests for Disabled/Unhealthy Node Conditions

The current tests in pkg/api/payer/nodeSelector_test.go do not verify that the node selection algorithm properly skips nodes that are disabled, have API disabled, or have replication disabled—even though the PR objectives indicate these nodes must be ignored. Notably, we’ve confirmed via the existence of GetUnhealthyNode in pkg/testutils/contractRegistry.go that there is support for simulating unhealthy nodes. However, none of the tests currently validate that nodes flagged with IsDisabled: true, IsApiEnabled: false, or IsReplicationEnabled: false are skipped.

Please add test cases to verify:

  • Disabled Nodes: Ensure that when one or more nodes have IsDisabled: true, the selector bypasses them.
  • API Disabled Nodes: Verify that nodes with IsApiEnabled: false are not chosen.
  • Replication Disabled Nodes: Confirm that nodes with IsReplicationEnabled: false are likewise skipped.

Using helper functions like testutils.GetUnhealthyNode (or similar functions for unhealthy configs) could simplify constructing these scenarios. This will ensure that the selection process behaves as intended under all conditions.

🧹 Nitpick comments (2)
pkg/api/payer/nodeSelector_test.go (2)

64-86: Consider using utility function for consistency.

While other tests have been updated to use testutils.GetHealthyNode(), this test still uses explicit node definitions. Consider updating it for consistency with the rest of the file.

mockRegistry.On("GetNodes").Return([]registry.Node{
-		{
-			NodeID:               100,
-			IsDisabled:           false,
-			IsReplicationEnabled: true,
-			IsApiEnabled:         true,
-			IsValidConfig:        true,
-		},
-		{
-			NodeID:               200,
-			IsDisabled:           false,
-			IsReplicationEnabled: true,
-			IsApiEnabled:         true,
-			IsValidConfig:        true,
-		},
-		{
-			NodeID:               300,
-			IsDisabled:           false,
-			IsReplicationEnabled: true,
-			IsApiEnabled:         true,
-			IsValidConfig:        true,
-		},
+		testutils.GetHealthyNode(100),
+		testutils.GetHealthyNode(200),
+		testutils.GetHealthyNode(300),
}, nil)

183-195: Improve test performance with a more efficient approach.

The brute force approach to find topics that hash into each bucket could be inefficient for large numbers of nodes. Consider using a more deterministic approach or reducing the maximum iterations.

- for i := 0; i < 1000000; i++ {
+ // Reduce maximum iterations to improve test performance
+ for i := 0; i < 100000; i++ {
    tpc := *topic.NewTopic(topic.TOPIC_KIND_IDENTITY_UPDATES_V1, []byte(strconv.Itoa(i)))
    node, err := selector.GetNode(tpc)
    require.NoError(t, err)
    if _, exists := targetBuckets[node]; !exists {
        targetBuckets[node] = tpc
    }

    // Stop early if all buckets are filled
    if len(targetBuckets) == len(nodes) {
        break
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6eb52 and 0fde332.

📒 Files selected for processing (2)
  • pkg/api/payer/nodeSelector.go (1 hunks)
  • pkg/api/payer/nodeSelector_test.go (9 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pkg/api/payer/nodeSelector.go
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Push Docker Images to GitHub Packages (xmtpd-cli)
  • GitHub Check: Build pre-baked anvil-xmtpd
  • GitHub Check: Test (Node)
🔇 Additional comments (3)
pkg/api/payer/nodeSelector_test.go (3)

3-15: Import changes align with new dependency requirements.

The addition of strconv and github.com/xmtp/xmtpd/pkg/testutils imports support the improvements made to the test suite, particularly the new utility for creating standardized node test instances.


19-25: Good use of utility functions for test readability.

Replacing hardcoded node definitions with testutils.GetHealthyNode() improves maintainability and readability by standardizing how healthy nodes are created across tests.


11-13:

Details

❓ Verification inconclusive

Verify compatibility with the new NodesV2 package.

Since this PR is about integrating NodesV2, it's important to verify that these tests are compatible with the new package structure. Let's check for any imports or usage of the old nodes package vs. the new nodesv2 package.


🏁 Script executed:

#!/bin/bash
# Check for imports of the old nodes package versus the new nodesv2 package
grep -r "import.*\"github.com/xmtp/xmtpd/pkg/nodes\"" --include="*.go" ./pkg/api/payer/
grep -r "import.*\"github.com/xmtp/xmtpd/pkg/nodesv2\"" --include="*.go" ./pkg/api/payer/

Length of output: 177


ACTION REQUIRED: Confirm NodesV2 Integration in Payer Tests

The automated search in the pkg/api/payer/ directory did not reveal any import statements for either the old nodes package or the new nodesv2 package within the test file (nodeSelector_test.go). This might indicate that the tests are not directly interacting with either node package. However, given that the goal of this PR is to integrate NodesV2, please manually verify whether the intended functionality from NodesV2 is exercised elsewhere or through indirect dependencies. If these tests are expected to interact with NodesV2 functionality, additional modifications or test coverage might be required.

  • Review the overall test suite: Ensure that NodesV2 functionality is properly validated in related test files or through integration tests.
  • Confirm dependencies: Double-check that any indirect usage of node-related logic (e.g., via the registry or mocks) are updated to reflect the NodesV2 integration.
  • Manual verification: Revisit the test configurations and runtime behavior to confirm that the absence of direct NodesV2 imports in this file does not compromise the verification of the new package integration.

}

// Interface implementation guard.
var _ NodeRegistry = &SmartContractRegistry{}
Copy link
Collaborator

Choose a reason for hiding this comment

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

ooh. I didn't know about this. Thanks for teaching me!

@mkysel
Copy link
Collaborator

mkysel commented Mar 6, 2025

No objections on my end

@mkysel mkysel self-requested a review March 6, 2025 20:21
Borja Aranda added 3 commits March 6, 2025 21:24
Summary
--
- Closes #541
- CLI only uses the new Nodes ABI.
- Deprecate unused testutils.
- Migrator now migrates between V2 registries.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Streamlined CLI commands for node management by removing outdated
health commands and introducing new commands for enabling/disabling
nodes and adjusting configurations.
  - Introduced an optional minimum monthly fee during node registration.
- Expanded administrative capabilities with new options to adjust
commission, set active node limits, and update service endpoints.
  - Enhanced transaction execution for smoother blockchain operations.
- Added new environment variables for improved node registration and
management.
- Introduced new test functions to validate node management
functionalities.

- **Refactor**
- Consolidated node data structures and removed legacy versioning for
simplified logic.
- Updated configuration retrieval to ensure consistent contract
addressing.
- Transitioned to a unified nodes contract for improved management and
access control.

- **Tests**
- Expanded test coverage to validate new node management functions and
transaction handling.
- Introduced tests for new functionalities related to node operations
and configurations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (12)
pkg/blockchain/client.go (1)

19-66: Well-structured helper for blockchain transaction execution

This new function consolidates common blockchain transaction patterns into a clean helper that handles transaction creation, waiting for mining, and log processing. This abstraction will improve code reuse and readability.

Consider logging errors from eventParser rather than silently continuing on line 60:

if err != nil {
+   logger.Debug("error parsing event log", zap.Error(err))
    continue
}
pkg/blockchain/migrator/migrator_test.go (3)

62-62: Consider adding tests for persistent failures.
If AddNode repeatedly fails, the function only short-circuits after a timeout. A test to confirm behavior on invalid inputs or network errors could be helpful.


66-73: Add coverage for non-default field values.
The new fields—MinMonthlyFee, IsReplicationEnabled, and IsApiEnabled—are assigned defaults here. Consider also testing scenarios where these fields differ from their defaults.


106-106: Consider using the returned value for validation.
Registering a node without checking the returned struct might miss validation opportunities.

pkg/blockchain/migrator/migrator.go (2)

32-32: Ensure error paths are fully tested.
The unmarshal step can fail on invalid pubkeys, so consider thorough negative tests.

Also applies to: 37-37


74-74: Partial success is feasible.
If a subset of nodes fail mid-loop, it might partially update the registry. Clarify or document if needed.

pkg/config/cliOptions.go (2)

13-17: NodeManagerOptions is well-defined.
Consider highlighting how it differs from AdminOptions in documentation.


18-22: "Enable" boolean might be clearer as "ShouldEnable."
Refinement can help if toggling states.

pkg/blockchain/registryAdmin_test.go (2)

42-46: Helper function addRandomNode.
Nice DRY approach. Generating random addresses and a fixed minMonthlyFee aids in consistent coverage. If needed, consider verifying node creation with a read-back step.

Also applies to: 49-50, 53-59, 62-62


140-146: TestSetMaxActiveNodes.
Checks for successful acceptance of new maximum. Consider adding a test for the revert scenario when current active nodes exceed the new limit.

pkg/blockchain/registryAdmin.go (1)

377-402: SetMaxActiveNodes method.
Straightforward: updates a single on-chain value and logs the event. Consider any boundary checks if not enforced in the contract.

contracts/src/Nodes.sol (1)

217-227: getAllNodes.
Returns an array of NodeWithId for enumerating all minted nodes. Consider indexing or pagination if the list grows large.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77a92b8 and c14cd2c.

📒 Files selected for processing (28)
  • cmd/cli/cli_test.go (3 hunks)
  • cmd/cli/main.go (5 hunks)
  • contracts/dev/deploy-local (1 hunks)
  • contracts/dev/generate (1 hunks)
  • contracts/dev/lib/common (2 hunks)
  • contracts/script/DeployNodeRegistry.s.sol (2 hunks)
  • contracts/script/DeployNodeRegistryV1.s.sol (0 hunks)
  • contracts/src/Nodes.sol (1 hunks)
  • contracts/src/NodesV2.sol (0 hunks)
  • contracts/test/Nodes.sol (3 hunks)
  • dev/baked/Dockerfile (1 hunks)
  • dev/local.env (1 hunks)
  • dev/register-local-node (1 hunks)
  • dev/register-local-node-2 (1 hunks)
  • pkg/blockchain/client.go (2 hunks)
  • pkg/blockchain/migrator/migrator.go (4 hunks)
  • pkg/blockchain/migrator/migrator_test.go (4 hunks)
  • pkg/blockchain/registryAdmin.go (2 hunks)
  • pkg/blockchain/registryAdmin_test.go (3 hunks)
  • pkg/blockchain/registryCaller.go (2 hunks)
  • pkg/blockchain/registryCaller_test.go (1 hunks)
  • pkg/config/cliOptions.go (1 hunks)
  • pkg/mocks/registry/mock_NodesContract.go (2 hunks)
  • pkg/registry/contractRegistry.go (4 hunks)
  • pkg/registry/contractRegistry_test.go (5 hunks)
  • pkg/registry/interface.go (1 hunks)
  • pkg/testutils/config.go (1 hunks)
  • pkg/testutils/contracts.go (1 hunks)
💤 Files with no reviewable changes (2)
  • contracts/script/DeployNodeRegistryV1.s.sol
  • contracts/src/NodesV2.sol
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/registry/interface.go
  • cmd/cli/cli_test.go
  • pkg/mocks/registry/mock_NodesContract.go
🔇 Additional comments (168)
pkg/testutils/contracts.go (1)

142-142: Function signature changed to use DeployNodes with updated parameters.

The deployment of the Nodes contract now passes auth.From as the third parameter to nodes.DeployNodes. This aligns with the PR's objective of integrating NodesV2 functionality into the main Nodes contract.

pkg/testutils/config.go (1)

79-82: Updated NodesContractAddress to use XMTPNodeRegistry.json.

The path for the Nodes contract configuration has been changed from "Nodes.json" to "XMTPNodeRegistry.json", which aligns with the consolidated contract approach mentioned in the PR objectives.

contracts/dev/lib/common (4)

35-35: Updated nodes deployment to use a script-based approach.

The deployment command for Nodes has been changed to use forge script with the DeployNodeRegistry.s.sol script rather than the previous approach. This standardizes deployment with other contracts that also use scripts.


37-38: Updated error message to reflect the new contract name.

The error message has been modified to simply refer to "Nodes contract" rather than including any version number, which aligns with the standardization effort in this PR.


41-41: Updated success message and output path.

The success message now points to "XMTPNodeRegistry.json" instead of a version-specific file, which matches the changes made in the config.go file and creates consistency across the codebase.


56-56: Updated help text to remove nodes_v2 option.

The list of valid options has been updated to remove 'nodes_v2', reflecting the consolidation of node contracts indicated in the PR objectives.

contracts/dev/generate (1)

54-54: Simplified contract list by removing NodesV2.

The default list of contracts for binding generation has been updated to remove "NodesV2" and reorder the remaining contracts. This is consistent with the PR's objective to integrate NodesV2 functionality into the main Nodes contract.

dev/register-local-node-2 (5)

9-9: Properly exporting the NODE_OPERATOR_PRIVATE_KEY

Added the necessary environment variable for node operations, using the existing ANVIL_ACC_2_PRIVATE_KEY. This aligns with the NodesV2 integration requirements.


11-15: Updated node registration command with proper flag format

The command now correctly uses the --admin.private-key flag format (instead of --admin-private-key) and captures the node ID output using jq. This matches the updated CLI argument structure.


17-20: New node API enabling functionality

Added command to enable the API for the registered node using the operator's private key. This implements the NodesV2 feature where nodes can have their API explicitly enabled/disabled.


22-25: New node replication enabling functionality

Added command to enable replication for the registered node, addressing the PR objective of controlling replication at the node level.


27-27: Added clear success message

Good addition of a colored success message that confirms the node ID and successful enabling of features.

dev/baked/Dockerfile (1)

32-32: Updated contract address source to XMTPNodeRegistry

The environment variable XMTPD_CONTRACTS_NODES_ADDRESS now correctly references the new contract structure from XMTPNodeRegistry.json instead of the previous Nodes.json. This aligns with the migration to NodesV2.

contracts/dev/deploy-local (1)

32-32: Simplified nodes deployment command

The command for deploying nodes has been simplified to forge_deploy_script nodes, removing the explicit source file and contract name parameters. This indicates the deployment logic has been moved to the script itself, aligning with the NodesV2 integration.

dev/register-local-node (5)

8-8: Properly exporting the NODE_OPERATOR_PRIVATE_KEY

Added the necessary environment variable for node operations, using the existing ANVIL_ACC_1_PRIVATE_KEY. This aligns with the NodesV2 integration requirements.


10-14: Updated node registration command with proper flag format

The command now correctly uses the --admin.private-key flag format (instead of --admin-private-key) and captures the node ID output using jq. This matches the updated CLI argument structure.


16-20: New node API enabling functionality

Added command to enable the API for the registered node using the operator's private key. This implements the NodesV2 feature where nodes can have their API explicitly enabled/disabled by NOPs, as mentioned in the PR objectives.


21-25: New node replication enabling functionality

Added command to enable replication for the registered node, directly implementing the PR objective of allowing NOPs to control replication at the node level.


26-26: Added clear success message

Good addition of a colored success message that confirms the node ID and successful enabling of features.

pkg/blockchain/registryCaller_test.go (4)

9-18: Test for GetAllNodes looks good.

The test successfully validates that the GetAllNodes method retrieves nodes correctly, checking that no errors occur and that the expected node count is returned.


20-29: Test for GetNode looks good.

The test correctly verifies that GetNode retrieves a specific node by ID and performs appropriate assertions to validate the result.


31-37: Test for GetNodeNotFound looks good.

This test properly validates the error handling for the case when a node with a non-existent ID is requested.


39-47: Test for OwnerOf looks good.

The test effectively verifies that the OwnerOf method executes without errors after adding a random node.

contracts/script/DeployNodeRegistry.s.sol (3)

8-8: Updated import to Nodes.sol.

The import statement has been updated to reference the new Nodes.sol file instead of NodesV2.sol, aligning with the PR objective to integrate NodesV2.


11-11: Updated variable type from NodesV2 to Nodes.

The variable type has been updated to match the new contract name.


26-26: Updated contract instantiation from NodesV2 to Nodes.

The contract instantiation has been updated to use the new Nodes contract. This change is consistent with the import and variable type changes.

contracts/test/Nodes.sol (3)

7-7: Updated import to Nodes.sol.

The import statement has been properly updated to reference the new Nodes.sol file, consistent with other contract reference updates in the codebase.


17-17: Updated variable type from NodesV2 to Nodes.

The variable type in the test contract has been updated to use the new Nodes contract.


31-31: Updated contract instantiation from NodesV2 to Nodes.

The contract instantiation has been updated to use the new Nodes contract. The tests cover a wide range of functionality including node management, enabling/disabling, API and replication management, which aligns with the PR objectives to support skipping disabled nodes.

dev/local.env (1)

13-13: Updated node address retrieval.

The environment variable assignment has been updated to extract the node registry address from a different location in the JSON file structure, reflecting the changes in contract deployment. This aligns with the move from NodesV2 to Nodes.

pkg/registry/contractRegistry_test.go (3)

49-71: Properly updated to use new V2 node interface and additional flags

The test has been correctly updated to use the new GetAllNodes method and new structure with INodesNodeWithId. The addition of the new fields (IsDisabled, IsApiEnabled, IsReplicationEnabled) aligns with the NodesV2 integration.


104-124: Updated method signature and node field structure correctly

This test has been properly updated to match the new signature and fields from NodesV2 integration. The test logic maintains its original purpose while accommodating the new structure.


159-173: Properly updated to work with NodesV2 interface

The test now correctly uses the NodesV2 interface with appropriate field structure including the added IsDisabled, IsApiEnabled, and IsReplicationEnabled flags that are central to this integration.

pkg/registry/contractRegistry.go (5)

50-51: Excellent addition of interface implementation guard

This helps catch potential interface implementation issues at compile time, ensuring the SmartContractRegistry properly implements the NodeRegistry interface.


219-219: Updated to use new GetAllNodes method correctly

The method call has been properly updated from AllNodes to GetAllNodes to align with the NodesV2 contract interface.


235-235: Updated parameter type to use the new interface

The function signature has been properly updated to use nodes.INodesNodeWithId instead of the previous type, reflecting the V2 contract structure.


249-251: Now correctly handles disabled nodes

This change aligns with the PR objective to skip nodes that are disabled by administrators. Setting isValidConfig to false ensures these nodes won't be selected for operations.


254-262: Node struct fields properly expanded with V2 properties

The Node struct now includes the expanded capabilities from NodesV2 - IsReplicationEnabled, IsApiEnabled, IsDisabled, and MinMonthlyFee, which enables the filtering functionality described in the PR objectives.

pkg/blockchain/registryCaller.go (3)

15-21: Interface updated to support NodesV2 functionality

The interface now properly supports the NodesV2 contract with more specific methods that align with the PR objectives, particularly around node filtering based on enabled/disabled status.


23-27: Simplified caller structure

The structure was renamed and simplified to better reflect its purpose in the updated system.


49-63: Added support for filtering active nodes

These new methods implement the ability to filter nodes based on API and replication status, which directly supports the PR objective to skip nodes that are disabled or don't have API enabled.

pkg/blockchain/migrator/migrator_test.go (12)

22-22: No concerns with the updated contract address.


57-57: Unified return type is consistent with the new design.


77-77: No issues with the registry setup call.


80-81: Good approach for multiple-node testing.


83-83: Reading from the registry is straightforward.


90-92: Adequate assertions for new fields.


108-108: Simple read action looks good.


129-130: Consistent registration for additional nodes.


132-132: Reading the newly added nodes is fine.


136-138: Effective use of a second registry for thorough testing.


141-141: Verifying data consistency across registries.


154-157: Checks default values thoroughly.

pkg/blockchain/migrator/migrator.go (12)

14-14: Consolidating multiple node versions into one struct reduces duplication.


19-19: Switch to int64 for MinMonthlyFee is more straightforward for arithmetic.


24-29: Clear early return of errors in ReadFromRegistry.


30-31: Pre-sizing the slice is efficient and readable.


42-50: Assigning the new fields aligns well with the consolidated struct.


53-53: Straightforward return of the constructed list.


56-56: Iteration logic for WriteToRegistry is clear and consistent.

Also applies to: 58-59


63-63: Context usage per node addition is consistent.


69-69: Directly mapping SerializableNode fields into AddNode keeps things simple.


84-84: DumpNodesToFile uses defer effectively to close the resource.


94-95: ImportNodesFromFile mirrors DumpNodesToFile.


101-101: Decoding into []SerializableNode is straightforward.

pkg/config/cliOptions.go (9)

8-11: Grouping admin info in AdminOptions clarifies usage.


30-30: Empty GetOptions suggests you may extend it later.


33-34: Replacing AdminPrivateKey with AdminOptions is consistent.


37-38: GetPubKeyOptions is clearly scoped for private key conversions.


41-47: RegisterNodeOptions comprehensively captures required fields.


49-51: Verify correct usage of NodeManagerOptions in set-http-address command.
Its NodeId must match the same struct, not a separate one.


54-57: SetMinMonthlyFeeOptions introduced similarly.
Check that the CLI references the correct NodeId field.


59-62: SetMaxActiveNodesOptions reuses AdminOptions seamlessly.


64-67: SetNodeOperatorCommissionPercentOptions reuses AdminOptions as well.

cmd/cli/main.go (24)

24-38: New fields in the CLI struct provide structured subcommand handling.


51-64: Declaring variables for each option group is consistent with the new approach.


66-72: Admin commands are properly grouped and named.


74-74: Register-node and migrate-nodes commands align with the new node management design.

Also applies to: 76-77


79-90: disable/enable-node and removal from API/replication nodes.
Implementation is consistent with AdminOptions usage.


91-105: Set commands for node configuration.
Ensure each command references the correct NodeId from its respective struct.


107-126: Commands for NodeOperatorOptions are well integrated.


129-137: Error message for missing command is clear.


142-155: Assembling the final CLI object with new subcommands looks solid.


164-188: Key management commands are straightforward.


190-230: RegisterNode checks negative fees and uses the new structure effectively.


232-252: disableNode logic is consistent and straightforward.


254-274: enableNode mirrors disableNode with no issues.


276-318: Node removal commands are aligned with the registry admin interface.


320-342: MigrateNodes uses import and write, providing robust migration flow.


344-364: Max active nodes can be configured easily, good usage of AdminOptions.


366-386: setNodeOperatorCommissionPercent is implemented as expected.


450-471: setApiEnabled uses NodeOperatorOptions consistently.


473-494: setReplicationEnabled also follows the same operator pattern.


502-528: getActiveApiNodes retrieves and prints the results without issues.


530-556: getActiveReplicationNodes usage is consistent with getActiveApiNodes.


558-591: getAllNodes fetches and optionally dumps the list of nodes to file.


593-618: getNode references NodeOperator.NodeId properly.


704-736: setupRegistryAdmin is a clear single point for registry construction.

pkg/blockchain/registryAdmin_test.go (12)

14-14: Use of multiple return values is clear and convenient.
Returning both admin and caller interfaces, plus context and cleanup, keeps the tests self-contained and easy to manage.


19-20: Consistent contract deployment for deterministic tests.
Always deploying the nodes contract ensures reproducible behavior across tests. However, be mindful of test runtime if the deployment process is heavy.


31-34: Properly initializing and returning registry and caller.
Good job creating both interfaces and verifying errors. This pattern helps reduce boilerplate in individual test functions.

Also applies to: 37-37


64-70: TestAddNode coverage.
This test verifies successful node creation with a valid owner, random signing key, and typical minMonthlyFee. Looks comprehensive.

Also applies to: 73-73


83-87: TestAddNodeBadOwner scenario.
Good negative test ensuring invalid owner addresses are rejected.


89-98: TestAddNodeBadMinMonthlyFee.
Validates negative fee handling. This confirms robust input validation.


100-108: TestDisableNode correctness.
Verifies that disabling the node by ID triggers no error. Consider confirming the node’s state post-disabling if relevant.


110-118: TestEnableNode correctness.
Re-enables a previously added node. Straightforward coverage. Similar to disable, consider asserting final node state.


120-128: TestRemoveFromApiNodes.
Ensures removing a node from API list is error-free. Good to see coverage for partial deactivation scenarios.


130-138: TestRemoveFromReplicationNodes.
Similar structure to removing from API nodes. The additional coverage for replication ensures consistent node-state transitions.


148-154: TestSetNodeOperatorCommissionPercent coverage.
Verifies a valid commission percent is set. Straightforward test to ensure on-chain parameter acceptance.


156-162: TestSetNodeOperatorCommissionPercentInvalid scenario.
Ensures negative or out-of-range commission values are rejected. Good boundary testing.

pkg/blockchain/registryAdmin.go (17)

12-12: Added import for types.
Inclusion of core/types is standard for event parsing under Geth. No concerns.


26-37: Extended INodeRegistryAdmin interface.
New methods (DisableNode, EnableNode, etc.) align with requirements for node management. All appear consistent with the Nodes contract changes.


40-45: nodeRegistryAdmin struct.
Defines essential fields (client, signer, logger, contract). Straightforward composition for a registry admin utility.

Also applies to: 47-47


49-49: Interface compliance check.
Using var _ INodeRegistryAdmin = &nodeRegistryAdmin{} is a clean way to ensure all interface methods are implemented.


54-54: NewNodeRegistryAdmin signature.
Factory-style constructor returning *nodeRegistryAdmin is clear and typical in Go.


63-66: Allocating logger and contract references.
Well-organized. The logger name “NodeRegistryAdmin” helps with targeted logs.


72-72: AddNode method signature and input validation.
Including a non-negative minMonthlyFee check is a good defensive measure.

Also applies to: 76-76, 82-84


93-123: ExecuteTransaction usage for AddNode.
Nice structured approach: binding the transaction, parsing events, and logging. This pattern is consistent and readable.


127-150: DisableNode transaction.
Flow parallels AddNode, with appropriate event parsing and logging. Straightforward logic.


152-175: EnableNode transaction.
Same transaction pattern, event parse, and logging. No immediate concerns.


177-202: RemoveFromApiNodes method.
Consistent approach to remove from a specialized list, ensuring correct event (ApiDisabled) is parsed.


204-229: RemoveFromReplicationNodes method.
Same pattern as RemoveFromApiNodes. Thorough coverage of replication logic.


231-265: SetHttpAddress method.
Clear, with an event-based callback to confirm the update.


267-301: SetMinMonthlyFee method.
Ensures a transaction with minMonthlyFee updated for the node. Proper event parse with MinMonthlyFeeUpdated.


303-336: SetIsApiEnabled method.
Conditional parsing of ApiEnabled or ApiDisabled is carefully handled. Good approach to unify the method.


338-375: SetIsReplicationEnabled method.
Matches the pattern from SetIsApiEnabled. Clean and symmetrical.


404-443: SetNodeOperatorCommissionPercent method.
Verifies commissionPercent range, executes transaction, logs an event. Good boundary validation.

contracts/src/Nodes.sol (41)

5-7: New imports for AccessControl and EnumerableSet.
They align with the updated design for role-based access and set management.


9-21: Updated doc comments.
Makes it clear what the contract does and how node ownership is managed. Good thorough explanation.


22-29: Role definitions and constants.
ADMIN_ROLE, NODE_MANAGER_ROLE, and MAX_BPS stand out for clarity, enabling flexible role-based permissions.


30-30: NODE_INCREMENT usage.
Incrementing IDs by 100 is unique but presumably intentional for distinct node ranges.


33-34: _baseTokenURI and maxActiveNodes definitions.
Defining a small default maxActiveNodes (20) is reasonable for initial constraints.

Also applies to: 36-38


40-43: _nodeCounter initialization.
Node IDs start at 100, stored as a multiple of NODE_INCREMENT. Straightforward logic.


48-54: Node storage fields and sets.
Keeping track of isDisabled, isApiEnabled, isReplicationEnabled, and minMonthlyFee offers fine-grained node state management.


55-59: nodeOperatorCommissionPercent.
Using basis points (BPS) is standard for percentage math. The upper limit of 10000 BPS is enforced later.


61-73: Constructor with AccessControlDefaultAdminRules.
Requires a non-zero _initialAdmin, grants roles, and sets up default admin logic. Thorough initialization.


75-77: Reserved admin-only comment section.
Clear delineation of privileges helps code organization.


79-96: addNode function.
Checks valid addresses and assigns Node struct. Ensures each new node is minted as an NFT. Straightforward event emission.


98-109: disableNode function.
Marking node as disabled, removing from active sets. Balanced approach for disabling.


111-119: removeFromApiNodes / removeFromReplicationNodes.
Additional granularity, removing the node from only API or replication sets.


121-130: enableNode function.
Clears isDisabled. Straight to the point, includes an event.


132-139: setMaxActiveNodes.
Reverts if new max is lower than current active sets. Good protective measure.


141-146: setNodeOperatorCommissionPercent.
Properly ensures newCommissionPercent <= MAX_BPS. Great boundary logic.


148-155: setBaseURI.
Checks for trailing slash and non-empty string. Thoughtful validation.


160-176: transferFrom override with node disabling.
Ensuring node is deactivated before ownership transfer is a prudent design choice, requiring an explicit re-enable.


178-184: setHttpAddress.
Updates the node’s HTTP address with basic validation.


186-191: setMinMonthlyFee.
Allows adjusting the fee post-mint. Straightforward with event emission.


197-205: setIsApiEnabled.
Calls internal activation/deactivation functions. Only node owner can invoke it, ensuring correct permissions.


207-215: setIsReplicationEnabled.
Follows the same pattern as API enabling. Consistent approach to different node features.


229-230: getAllNodesCount.
Exposes _nodeCounter for external usage. Straightforward.


234-238: getNode.
Requires node to exist, then returns the struct.


240-250: getActiveApiNodes.
Enumerates API-enabled nodes from the set. Clear approach.


252-255: getActiveApiNodesIDs.
Returns the array of node IDs from the set. Concise.


257-260: getActiveApiNodesCount.
The set length is a direct measure of active API nodes.


262-265: getApiNodeIsActive.
Uses .contains(...) on the set to determine if the node is active.


268-278: getActiveReplicationNodes.
Mirrors the logic of getActiveApiNodes.


280-283: getActiveReplicationNodesIDs.
Same approach for replication.


284-287: getActiveReplicationNodesCount.
Again parallels the API nodes pattern.


289-292: getReplicationNodeIsActive.
Checks membership in replication set via .contains(...).


294-298: getNodeOperatorCommissionPercent.
Exposes stored BPS value. Clear read method.


300-304: Modifier whenNotDisabled.
Guards usage if node is flagged disabled. This enforces preconditions effectively.


306-311: _nodeExists internal check.
Uses _ownerOf(...) to confirm if the token is minted.


313-316: _baseURI override.
Retrieves the stored base token URI for ERC721 metadata.


318-327: _activateApiNode internal helper.
Requires that active nodes remain below maxActiveNodes. Good for ensuring capacity constraints.


329-337: _disableApiNode internal helper.
Removes the node from the API set and emits ApiDisabled. Straightforward.


339-348: _activateReplicationNode internal helper.
Same approach for replication. Good consistency.


350-358: _disableReplicationNode internal helper.
Removes node from replication set, logging an event.


360-369: supportsInterface override.
Centralizes bridging for multiple inherited interfaces.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
cmd/cli/main.go (4)

26-41: Add docstrings for new CLI fields for clarity.

Lines 26-41 introduce multiple new fields in the CLI struct (e.g., AdminOptions, NodeManagerOptions, etc.). While this is a good organizational improvement, consider adding short docstrings to each of these fields to provide clarity on which commands or use cases they apply to. This helps later contributors quickly understand the purpose and required usage of each field.


60-60: Ensure consistent naming.

setHttpAddressOptions is clearly named. Just as a cautionary note, keep naming patterns consistent across the codebase (e.g., “Set…” vs. “set…” for variables).


62-66: Consider grouping new options.

These lines add diverse new option structs (nodeOperatorOptions, setMinMonthlyFeeOptions, etc.). While they are logically grouped in the CLI, consider grouping them in the code (or in separate files) if additional complexity grows in the future. This keeps the CLI parser easier to maintain.


758-790: Consider returning errors instead of calling logger.Fatal.

Currently, setupRegistryAdmin uses logger.Fatal on errors, which will terminate the process immediately. Depending on usage, returning errors and allowing the caller to decide how to handle them can provide more flexibility (e.g., retry, fallback). Evaluate whether termination is always desired.

pkg/config/cliOptions.go (4)

37-39: Mark as sensitive if needed.

GetPubKeyOptions.PrivateKey is labeled “required,” but remember that private keys are sensitive. Make sure to handle them carefully, as recommended in other areas of the code.


54-59: Use clear naming for node ownership fields.

The RegisterNodeOptions fields (HttpAddress, OwnerAddress, SigningKeyPub, MinMonthlyFee) are generally clear. As usage grows, consider docstrings or comments clarifying that OwnerAddress is the final on-chain address holding the NFT.


67-71: Ensure no negative fees.

SetMinMonthlyFeeOptions has an int64 fee. While your code blocks negative values, consider adding a meaningful usage message or constraints in the flag definition so that the CLI user immediately knows negatives are invalid.


72-75: Clarify safe ranges for max active nodes.

SetMaxActiveNodesOptions sets a uint8. Consider clarifying the safe range for your system capacity in docs or usage messages to avoid confusion if a user attempts an out-of-range value (e.g., > 255).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c14cd2c and 7866f35.

📒 Files selected for processing (2)
  • cmd/cli/main.go (5 hunks)
  • pkg/config/cliOptions.go (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build pre-baked anvil-xmtpd
  • GitHub Check: Test (Node)
🔇 Additional comments (8)
cmd/cli/main.go (3)

54-55: Initialize all option structs consistently.

Initializing adminOptions and nodeManagerOptions here is consistent, but double-check that each newly introduced struct is properly grouped or initialized in similar fashion for future maintainability and clarity.


445-466: Potential mismatch in node ID usage. (Previously noted)

This code calls setupRegistryAdmin with options.SetHttpAddress.NodeManagerOptions.NodePrivateKey (line 450) but uses options.NodeManagerOptions.NodeId (line 460). If these IDs differ, it can cause incorrect usage.


468-493: Likely the same mismatch for setMinMonthlyFee. (Previously noted)

Here, setupRegistryAdmin is called with options.SetMinMonthlyFee.NodeManagerOptions.NodePrivateKey (line 473) whereas SetMinMonthlyFee uses options.NodeManagerOptions.NodeId (line 487). If there is a mismatch in node IDs, it may lead to unexpected behavior.

pkg/config/cliOptions.go (5)

8-22: Validate required fields for each admin-related operation.

These new structs (AdminOptions, NodeManagerOptions, and NodeOperatorOptions) define private keys and node IDs. Ensure that commands that rely on these fields enforce correctness at runtime, especially if NodeId is mandatory.


30-31: Empty struct is a good placeholder.

GetOptions is an empty struct here, which is fine if you plan to add more fields later. No issues noted.


33-35: Check file existence earlier.

MigrateNodesOptions includes an input file (InFile). In calling code, consider verifying file readability earlier to provide more immediate feedback if the file does not exist.


62-65: Enforce or validate new addresses.

SetHttpAddressOptions holds an address string. If an invalid address can cause issues downstream, add validations or pre-check logic.


77-80: Enforce commission percent boundaries.

SetNodeOperatorCommissionPercentOptions uses an int64. Implementation should ensure it remains within acceptable limits (e.g., 0–100) to prevent invalid or negative commission.

@fbac fbac merged commit 82dfd87 into main Mar 7, 2025
13 of 14 checks passed
@fbac fbac deleted the 04-05-use_nodesv2 branch March 7, 2025 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nodes contract: adapt xmtpd code

4 participants