Skip to content

feat: introduce basic extended addresses implementation, stop using platform{HTTP,P2P}Port #6666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

kwvg
Copy link
Collaborator

@kwvg kwvg commented May 12, 2025

Motivation

Currently, we store an address and port pair for all masternodes and two port numbers for Evonodes. The first pair is used by Dash Core and the latter two port numbers are paired with the address from the first pair and are used by Dash Platform.

This arrangement has allowed the network to grow and sustain its current operations but proves to be rigid as it imposes the constraint that all advertised activity (Core P2P, Platform P2P and the Platform HTTPS API) happen only on one network (IPv4), from the same public IP (as we can only register one address).

This prevents specifying different networks (like IPv6), alternate addresses (e.g. on privacy-oriented networks), expanding to advertise other purposes or deferring resolution of the underlying address (e.g. specifying domain names). To allow for these use cases, the changes made to transaction, storage and state formats alongside changes made to RPC input and output fields are collectively referred to as "extended addresses".

This pull request includes the following:

  • A basic extended addresses implementation that allows storing 4 addresses per purpose code, recognizing the following purpose codes, CORE_P2P, PLATFORM_P2P and PLATFORM_HTTPS.

  • Support for specifying (arrays of) addr:port pairs to platformP2PAddrs (formerly known as platformP2PPort) and platformHTTPSAddrs (formerly known as platformHTTPPort).

  • Compatibility code to allow

    • Reporting platformP2PPort and platformHTTPPort for extended address payloads even though they have been subsumed into netInfo

    • Reporting platformP2PPort and platformHTTPPort data from legacy address payloads through addresses even though they aren't stored in netInfo

    • Specifying only ports in platformP2PAddrs and platformHTTPSAddrs when using protx register{,_evo}, protx register_fund{,_evo} and protx update_service{,_evo} to create/update an extended addresses eligible masternode by reading the address of the first coreP2PAddrs entry and pairing it with the supplied port.

This pull request does not include the the full set of validation rules applicable on extended addresses as they have been reserved for a subsequent pull request. This pull request's scope is to lay down the base implementation, its surrounding compatibility code and tests to ensure its sanity.

Additional Information

  • Depends on feat(rpc): allow reporting multiple addresses with new addresses field, deprecate service field, allow submitting multiple addresses #6674

  • The adoption of std::reference_wrapper (starting with dash#6627) has been reverted as while it there were performance considerations that led to its adoption, the risk of dangling references due to a race condition (e.g. iterating through GetEntries() while Clear() is called) are more pronounced for extended addresses.

    The underlying structures (NetInfoEntry, which will predominantly hold a CService) are not heavy enough to justify the usage of locking (i.e. mutexes). Making copies are deemed affordable enough for the safety that it provides.

  • CDeterministicMNStateDiff is an append-only structure populated based on flags, which has made it a source of attention throughout work on extended addresses (see dash#6636). It is the reason NetInfoSerWrapper was introduced, as nVersion is placed after netInfo (source), which means, we cannot determine what format to use when deserializing based on the version of the format.

    To work around this, extended address payloads are prepended with the magic 0x23232323 (source) when serializing and deserialization will read the first four bytes to determine if the payload is extended or legacy.

  • As we require a flattened list of all addresses associated with a masternode in order to check it against the mempool or unique properties map (example), it would be inefficient to regenerate that list every time GetEntries() is called. To get around that, we use a memory-only cache, m_all_entries is used.

    It is populated when deserialized or added to when new entries are successful. This proves to be sufficient as ExtNetInfo is an append-only structure (i.e. they can either be added to with AddEntry() or Clear()ed).

    • This cache is also used for addr:port duplicates checking (while purpose-specific lists are used for addr duplicates checking)
  • As rpc_netinfo.py is unlikely to use regular masternodes (as Platform-related fields can only be tested with Evonodes), non-Evonode code paths were removed and the following additional changes were made

    • Implementing the helper functions reconnect_nodes() and set_active_state(), the former to reconnect restarted nodes to their peers (which is not done automatically by the test framework) and the latter to restart the node to enable it in active masternode state (and/or optionally define extra arguments).

    • Fix a minor bug where destroy_mn() overwrote the ProTx hash of the destroyed masternode before checking for removal from the masternode list and logging it.

Breaking Changes

Refer to release notes.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

Copy link

This pull request has conflicts, please rebase.

Copy link

This pull request has conflicts, please rebase.

PastaPastaPasta added a commit that referenced this pull request Jun 12, 2025
…nfo`) in functional tests, implement helpers, add type annotations

0532baf refactor: add helper for finding collateral vout (Kittywhiskers Van Gogh)
89b2548 refactor: add helper for transaction burial (Kittywhiskers Van Gogh)
f3777c5 refactor: introduce address generation helper, make constructor minimal (Kittywhiskers Van Gogh)
fa56126 refactor: add type annotations for `MasternodeInfo` members (Kittywhiskers Van Gogh)
dfca0ee refactor: use `MasternodeInfo` in feature_dip3_deterministicmns.py (Kittywhiskers Van Gogh)
cd7ffcf refactor: store only the node index in `MasternodeInfo` (Kittywhiskers Van Gogh)
07c7319 refactor: use `nodeIdx` instead of `node.index` with `MasternodeInfo` (Kittywhiskers Van Gogh)
08e81d8 refactor: store P2P port instead of full address in `MasternodeInfo` (Kittywhiskers Van Gogh)
3e25b30 refactor: store the funding address in `MasternodeInfo` (Kittywhiskers Van Gogh)
08d18d7 refactor: annotate usage of `MasternodeInfo` (Kittywhiskers Van Gogh)

Pull request description:

  ## Motivation

  While working on functional tests for [dash#6666](#6666) (which in turn, relies on the foundation built by [dash#6674](#6674), see `rpc_netinfo.py`, [source](https://github.com/dashpay/dash/blob/c788531d35c48820af951799ff8b67ee2dadb8b3/test/functional/rpc_netinfo.py)), the existing test infrastructure, while well optimized for testing interactions _between_ masternodes, didn't offer enough flexibility for testing _creation_ of masternodes.

  The tests that need to be implemented for extended addresses mimic `feature_dip3_deterministicmns.py` ([source](https://github.com/dashpay/dash/blob/09aa42ef0f20dc94ea63bdc8e7e064d494c9ae8f/test/functional/feature_dip3_deterministicmns.py)) in objective and while taking cues from it, it was found that instead of the `MasternodeInfo` used in most of the codebase ([source](https://github.com/dashpay/dash/blob/09aa42ef0f20dc94ea63bdc8e7e064d494c9ae8f/test/functional/test_framework/test_framework.py#L1138-L1153)),  `feature_dip3_deterministicmns.py` implements its own object, `Masternode` ([source](https://github.com/dashpay/dash/blob/09aa42ef0f20dc94ea63bdc8e7e064d494c9ae8f/test/functional/feature_dip3_deterministicmns.py#L223-L227)).

  In a similar vein, `rpc_netinfo.py`, as implemented in c788531, implemented its own variant, `Node` ([source](https://github.com/dashpay/dash/blob/c788531d35c48820af951799ff8b67ee2dadb8b3/test/functional/rpc_netinfo.py#L31-L194)) to address the testing needs for extended addresses. It became clear that without additional intervention, the test suite would have three different ways of representing masternode information (`MasternodeInfo`, `Masternode`, `Node`) and that it would be more beneficial to consolidate all three approaches together.

  So, taking cue from `Node` (from `rpc_netinfo.py`, not included in `develop` as of this writing, [source](https://github.com/dashpay/dash/blob/09aa42ef0f20dc94ea63bdc8e7e064d494c9ae8f/test/functional/test_framework/rpc_netinfo.py)), this pull request aims to clean up, consolidate and improve upon `MasternodeInfo` in an attempt to remove the need for `Node`.

  ## Additional Information

  * PEP 526 ("Syntax for Variable Annotations") prohibit annotation of variables defined by a `for` or `with` statement ([source](https://peps.python.org/pep-0526/#where-annotations-aren-t-allowed)), to get around that, the syntax for type comments ([source](https://peps.python.org/pep-0484)) as defined in PEP 484 ("Type Hints") have been used instead.

  * The decision to remove `node` from `MasternodeInfo` despite its convenience was rooted in the observation that there are cases where `nodeIdx` and `node` are assigned separately ([source](https://github.com/dashpay/dash/blob/09aa42ef0f20dc94ea63bdc8e7e064d494c9ae8f/test/functional/test_framework/test_framework.py#L1480), [source](https://github.com/dashpay/dash/blob/09aa42ef0f20dc94ea63bdc8e7e064d494c9ae8f/test/functional/test_framework/test_framework.py#L1499)), which highlighted the fact that `MasternodeInfo` has no control over the lifetime of the `TestNode` instance as it is ultimately controlled by `BitcoinTestFramework`.

    To avoid potential error and to make it syntactically clear that ownership is vests with `BitcoinTestFramework`, `node` has been removed and replaced with `get_node()`, which alongside some basic sanity checks, is just an alias for `self.nodes[mn.nodeIdx]` but is still less tedious to type or read.

  * The reason why functions like `generate_addresses()` accept a separate `TestNode` ([source](https://github.com/dashpay/dash/blob/c5444b3f7c06aa5b060fd542773df6dd5a9bae1f/test/functional/test_framework/test_framework.py#L1160-L1174)) is due to the practice of using a single node to instantiate all masternodes instead of keeping them as self-contained instances that mine their own blocks and fund their own collaterals (for instance, `DashTestFramework` uses `nodes[0]` to setup all the masternodes even if the masternodes themselves had a different index).

  * The decision to replace `addr` with `nodePort` has been inspired by the need to register masternodes with non-loopback addresses (like in `feature_dip3_deterministicmns.py` or `rpc_netinfo.py`). As a node that is intended to be used will always need the same standardized loopback address, we can safely resort to only storing the port number and save a few `p2p_port()` calls along the way.

  ## Breaking Changes

  None expected. Affects only functional tests.

  ## Checklist

  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [x] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation **(note: N/A)**
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 0532baf
  UdjinM6:
    utACK 0532baf

Tree-SHA512: c2b5b0f9200c2714159a5e0f6f52174e38eae426c01620cf15e9adb8b8e67bbb078d658edb21b052a1fc9896ba098cffa02248aaa980be5d5cb145e9929c4568
Copy link

This pull request has conflicts, please rebase.

Copy link

github-actions bot commented Jul 4, 2025

This pull request has conflicts, please rebase.

PastaPastaPasta added a commit that referenced this pull request Jul 15, 2025
…addresses` field, deprecate `service` field, allow submitting multiple addresses

7fdd642 docs: add documentation for deprecation, new field and renamed input (Kittywhiskers Van Gogh)
5c564c2 test: check pre-fork conditions for ProRegTx and ProUpServTx preparation (Kittywhiskers Van Gogh)
859ce5c test: validate common conditions for input validation (Kittywhiskers Van Gogh)
a60c39a test: add functional test for `addresses` and deprecated `service` field (Kittywhiskers Van Gogh)
e0d2a81 rpc: deprecate key "service" replaced by "addresses" (Kittywhiskers Van Gogh)
d9247ad rpc: add new key "addresses" to allow reporting multiple entries (Kittywhiskers Van Gogh)
33e5f6a refactor: s/ipAndPort/coreP2PAddrs/g (Kittywhiskers Van Gogh)
4fd4e0e rpc: allow `ipAndPort` to accept multiple entries with arrays (Kittywhiskers Van Gogh)
8c28f30 rpc: spin-off `ipAndPort` and `platform{HTTP,P2P}Port` setters to util (Kittywhiskers Van Gogh)

Pull request description:

  ## Motivation

  To enable including functional tests with the extended addresses (ExtAddr) implementation (i.e. [dash#6666](#6666)), we need the ability to _specify_ and _retrieve_ multiple addresses. The current RPCs do not allow for that and this pull request aims to remedy it.

  This _particular_ pull request does not include:
  * Compatibility logic to allow reporting Platform-specific addresses using legacy fields (will be submitted after ExtAddr impl)
  * Compatibility logic to allow deriving Platform ports from extended address entries to populate deprecated fields (will be submitted after ExtAddr impl)
  * The ability to submit multiple Platform addresses (done in conjunction with ExtAddr impl)

  ## Additional Information

  * Depends on #6665

  * Depends on #6720

  * Dependency for #6666

  * ⚠️ The format for the field replacing `service`, `addresses` is not stable and is changed in [dash#6666](#6666) to allow reporting Platform-specific addresses. This should be acceptable as the whole extended addresses changeset across multiple PRs is expected to be part of a major release but is mentioned here for the sake of posterity.

  * As `IsDeprecatedRPCEnabled()` is defined in `libbitcoin_node` (see `rpc/server.cpp`, [source](https://github.com/dashpay/dash/blob/da8a475dfac9ad80d566eef8cf119c8036d6ebcc/src/rpc/server.cpp#L370-L375)), it is not available to `libbitcoin_common`, where special transaction and network information source files are included. To get around this, a practically identical function, `IsServiceDeprecatedRPCEnabled()` is defined and used in non-RPC code.
    * It is located in `evo/netinfo.cpp` instead of a more natural `rpc/evo_util.cpp` to avoid unnecessary circular dependencies.

  ## Breaking Changes

  * The input field `ipAndPort` has been renamed to `coreP2PAddrs`.
    * `coreP2PAddrs` can now, in addition to accepting a string, accept an array of strings, subject to validation rules.

  * The key `service` has been deprecated for some RPCs (`decoderawtransaction`, `decodepsbt`, `getblock`, `getrawtransaction`,
    `gettransaction`, `masternode status` (only for the `dmnState` key), `protx diff`, `protx listdiff`) and has been replaced
    with the field `addresses`.
    * The deprecated field can be re-enabled using `-deprecatedrpc=service` but is liable to be removed in future versions
      of Dash Core.
    * This change does not affect `masternode status` (except for the `dmnState` key) as `service` does not represent a payload
      value but the external address advertised by the active masternode.

  ## Checklist

  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [x] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 7fdd642
  UdjinM6:
    utACK 7fdd642

Tree-SHA512: 75331a467e7170542349c94408b71f5d25ebd2bf418cb6bdf77866a342b9079ec795638535f67c6487739b37651bf8265dce66ef3b06e0732748a173441695ef
Copy link

This pull request has conflicts, please rebase.

kwvg added 3 commits August 10, 2025 13:38
MnNetInfo cannot store duplicate entries as it can store only one entry,
so it cannot emit NetInfoStatus::Duplicate.
kwvg added 20 commits August 10, 2025 14:13
The current name is now a misnomer as we accept (multiple) addr:port
pairs, the name has been updated to reflect that. We still support
submitting only ports but that is transitional support that may be
removed in a future release.
…2p}`

We also update the type annotations to reflect the three different types
of valid input (port, single address, array of addresses).
`coreP2PAddrs` is the name given to the input field in Dash Core but
the test suite follows a different naming convention, let's harmonize it
for good.
MnNetInfo doesn't actually store this information so we are pulling the
data from platform{HTTP,P2P}Port to make it appear like it does. This
is to ensure reporting parity with ExtNetInfo, which stores platform
fields and should allow us to deprecate the platform{HTTP,P2P}Port when
we need to.
…iff`

`CDeterministicMNStateDiff` is special because not all of its fields may
be populated, so we may not have all the data we need to report on the
diff.
This is to ensure that even when the fields have been deprecated, they
can still return values by reading from `netInfo`. This will require us
to constrain ExtNetInfo's validation rules to continue under legacy
assumptions *until* we remove the legacy reporting fields altogether.
`or` will treat a blank string as a bad value and will go for the
default, which creates problems when we need to test for blank value
behaviors. This isn't a problem with `platform_{p2p,http}_port` since we
check against None explicitly.
We are unlikely to create regular masternodes in this test as we need to
specifically validate shims for platform-specific fields, which only
come into play with EvoNodes. This should let us trim down the tests a
little.
Copy link

@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

♻️ Duplicate comments (1)
test/functional/rpc_netinfo.py (1)

73-81: set_active_state: correct use of original extra_args snapshot

Copying node.extra_args is correct in this framework (restart_node doesn’t mutate it). No risk of duplicating -masternodeblsprivkey across restarts.

🧹 Nitpick comments (9)
src/test/evo_netinfo_tests.cpp (1)

60-77: Optional: also assert purpose-filtered GetEntries()

You validate flattened GetEntries() results; consider an extra assertion for GetEntries(purpose) to exercise the optional filter path.

-            ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/1);
+            ValidateGetEntries(netInfo.GetEntries(), /*expected_size=*/1);
+            // Filtered by purpose should match too
+            ValidateGetEntries(netInfo.GetEntries(purpose), /*expected_size=*/1);
src/evo/netinfo.cpp (4)

169-174: Guard against version 0 with assert message or graceful handling

assert(nVersion > 0) is fine for internal callers, but consider a more descriptive assert message for easier debugging.

-    assert(nVersion > 0);
+    assert(nVersion > 0 && "NetInfoInterface::MakeNetInfo requires non-zero ProTx version");

324-327: Typo in comment: “succesfully” → “successfully”

Minor spelling fix.

-    // Candidate succesfully added, update cache
+    // Candidate successfully added, update cache

386-399: Simplify redundant size check in GetPrimary()

After ASSERT_IF_DEBUG(!entries.empty()), the size() >= 1 guard is redundant. Optional micro-simplification.

-        // If a purpose code is in the map, there should be at least one entry
-        ASSERT_IF_DEBUG(!entries.empty());
-        if (entries.size() >= 1) {
-            if (const auto& service_opt{entries[0].GetAddrPort()}) {
-                return *service_opt;
-            }
-        }
+        // If a purpose code is in the map, there should be at least one entry
+        ASSERT_IF_DEBUG(!entries.empty());
+        if (const auto& service_opt{entries[0].GetAddrPort()}) {
+            return *service_opt;
+        }

459-475: Address clang-format diff in ToString() and nested lambdas

CI reported clang-format differences in this file. Please run clang-format to normalize indentation/line breaks, especially around nested lambdas in ToString().

Run:

clang-format -i src/evo/netinfo.cpp
test/functional/rpc_netinfo.py (2)

161-166: Use in dict instead of in dict.keys()

Simplify membership checks per Ruff SIM118.

-        for arr in rpc_output['updatedMNs']:
-            if proregtx_hash in arr.keys():
-                return arr[proregtx_hash]
+        for arr in rpc_output['updatedMNs']:
+            if proregtx_hash in arr:
+                return arr[proregtx_hash]

415-420: Clean up dict membership checks (SIM118): drop .keys()

Replace multiple occurrences of "key" in d.keys() with "key" in d and corresponding negative checks.

-        assert "addresses" in proregtx_rpc['proRegTx'].keys()
-        assert "addresses" in masternode_status['dmnState'].keys()
-        assert "addresses" in proupservtx_rpc['proUpServTx'].keys()
-        assert "addresses" in protx_diff_rpc['mnList'][0].keys()
-        assert "addresses" in protx_listdiff_rpc.keys()
+        assert "addresses" in proregtx_rpc['proRegTx']
+        assert "addresses" in masternode_status['dmnState']
+        assert "addresses" in proupservtx_rpc['proUpServTx']
+        assert "addresses" in protx_diff_rpc['mnList'][0]
+        assert "addresses" in protx_listdiff_rpc
-        assert "core_p2p" not in protx_listdiff_rpc_pl['addresses'].keys()
+        assert "core_p2p" not in protx_listdiff_rpc_pl['addresses']
-        assert "service" not in proregtx_rpc['proRegTx'].keys()
-        assert "service" not in masternode_status['dmnState'].keys()
-        assert "service" not in proupservtx_rpc['proUpServTx'].keys()
-        assert "service" not in protx_diff_rpc['mnList'][0].keys()
-        assert "service" not in protx_listdiff_rpc.keys()
+        assert "service" not in proregtx_rpc['proRegTx']
+        assert "service" not in masternode_status['dmnState']
+        assert "service" not in proupservtx_rpc['proUpServTx']
+        assert "service" not in protx_diff_rpc['mnList'][0]
+        assert "service" not in protx_listdiff_rpc
-        assert "service" in proregtx_rpc['proRegTx'].keys()
-        assert "service" in masternode_status['dmnState'].keys()
-        assert "service" in proupservtx_rpc['proUpServTx'].keys()
-        assert "service" in protx_diff_rpc['mnList'][0].keys()
-        assert "service" in protx_listdiff_rpc.keys()
+        assert "service" in proregtx_rpc['proRegTx']
+        assert "service" in masternode_status['dmnState']
+        assert "service" in proupservtx_rpc['proUpServTx']
+        assert "service" in protx_diff_rpc['mnList'][0]
+        assert "service" in protx_listdiff_rpc
-        assert "platformP2PPort" not in protx_listdiff_rpc.keys()
-        assert "platformHTTPPort" not in protx_listdiff_rpc.keys()
+        assert "platformP2PPort" not in protx_listdiff_rpc
+        assert "platformHTTPPort" not in protx_listdiff_rpc

Also applies to: 430-433, 435-440, 459-464, 546-548

src/evo/netinfo.h (2)

299-309: clang-format the file to satisfy CI

CI flagged formatting diffs around this region. Please run clang-format on this header.

Run:

clang-format -i src/evo/netinfo.h

361-441: Serialization wrapper: magic-based disambiguation for MNStateDiff is pragmatic

The 0x23… magic to distinguish extended vs legacy, plus fixed-size fallback for MnNetInfo, is a reasonable compromise given stream constraints. Consider documenting the extremely low-probability collision scenario in a comment.

-    static constexpr std::array<uint8_t, 4> EXTADDR_MAGIC{0x23, 0x23, 0x23, 0x23};
+    // Magic used to detect extended netinfo in CDeterministicMNStateDiff streams.
+    // Collision with the first 4 bytes of legacy MnNetInfo serialization is extremely unlikely.
+    static constexpr std::array<uint8_t, 4> EXTADDR_MAGIC{0x23, 0x23, 0x23, 0x23};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b24d644 and ede6292.

📒 Files selected for processing (29)
  • contrib/seeds/makeseeds.py (1 hunks)
  • doc/release-notes-6666.md (1 hunks)
  • src/coinjoin/client.cpp (2 hunks)
  • src/evo/core_write.cpp (5 hunks)
  • src/evo/deterministicmns.cpp (8 hunks)
  • src/evo/deterministicmns.h (1 hunks)
  • src/evo/dmnstate.cpp (4 hunks)
  • src/evo/dmnstate.h (2 hunks)
  • src/evo/netinfo.cpp (6 hunks)
  • src/evo/netinfo.h (11 hunks)
  • src/evo/providertx.cpp (5 hunks)
  • src/evo/providertx.h (3 hunks)
  • src/evo/simplifiedmns.cpp (1 hunks)
  • src/evo/simplifiedmns.h (1 hunks)
  • src/evo/specialtxman.cpp (3 hunks)
  • src/rpc/coinjoin.cpp (1 hunks)
  • src/rpc/evo.cpp (7 hunks)
  • src/rpc/evo_util.cpp (4 hunks)
  • src/rpc/evo_util.h (1 hunks)
  • src/rpc/masternode.cpp (3 hunks)
  • src/rpc/quorums.cpp (2 hunks)
  • src/test/block_reward_reallocation_tests.cpp (1 hunks)
  • src/test/evo_deterministicmns_tests.cpp (6 hunks)
  • src/test/evo_netinfo_tests.cpp (4 hunks)
  • src/test/evo_simplifiedmns_tests.cpp (1 hunks)
  • test/functional/feature_dip3_deterministicmns.py (1 hunks)
  • test/functional/rpc_netinfo.py (8 hunks)
  • test/functional/rpc_quorum.py (1 hunks)
  • test/functional/test_framework/test_framework.py (12 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/coinjoin/client.cpp
🚧 Files skipped from review as they are similar to previous changes (23)
  • contrib/seeds/makeseeds.py
  • src/evo/deterministicmns.h
  • test/functional/rpc_quorum.py
  • src/test/evo_simplifiedmns_tests.cpp
  • src/test/block_reward_reallocation_tests.cpp
  • test/functional/feature_dip3_deterministicmns.py
  • src/evo/simplifiedmns.h
  • src/evo/providertx.h
  • src/evo/simplifiedmns.cpp
  • src/evo/core_write.cpp
  • src/rpc/quorums.cpp
  • src/rpc/coinjoin.cpp
  • src/evo/dmnstate.h
  • src/test/evo_deterministicmns_tests.cpp
  • src/rpc/masternode.cpp
  • src/evo/specialtxman.cpp
  • src/evo/providertx.cpp
  • src/evo/dmnstate.cpp
  • src/evo/deterministicmns.cpp
  • src/rpc/evo_util.h
  • doc/release-notes-6666.md
  • src/rpc/evo.cpp
  • src/rpc/evo_util.cpp
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/test/evo_netinfo_tests.cpp
  • src/evo/netinfo.cpp
  • src/evo/netinfo.h
src/{test,wallet/test,qt/test}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit Inference Engine (CLAUDE.md)

Unit tests for C++ code should be placed in src/test/, src/wallet/test/, or src/qt/test/ and use Boost::Test or Qt 5 for GUI tests

Files:

  • src/test/evo_netinfo_tests.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit Inference Engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/netinfo.cpp
  • src/evo/netinfo.h
test/functional/**/*.py

📄 CodeRabbit Inference Engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/rpc_netinfo.py
  • test/functional/test_framework/test_framework.py
🧠 Learnings (4)
📚 Learning: 2025-05-10T00:54:15.597Z
Learnt from: kwvg
PR: dashpay/dash#6629
File: src/evo/deterministicmns.cpp:473-485
Timestamp: 2025-05-10T00:54:15.597Z
Learning: In the current implementation of `NetInfoEntry`, only `CService` is a valid type and `std::monostate` represents an invalid state. Entries that don't provide a valid `CService` through `GetAddrPort()` should be rejected with an exception, not silently skipped.

Applied to files:

  • src/test/evo_netinfo_tests.cpp
  • src/evo/netinfo.cpp
  • src/evo/netinfo.h
📚 Learning: 2025-06-09T16:43:20.996Z
Learnt from: kwvg
PR: dashpay/dash#6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.

Applied to files:

  • test/functional/rpc_netinfo.py
  • test/functional/test_framework/test_framework.py
📚 Learning: 2025-08-10T13:52:46.260Z
Learnt from: kwvg
PR: dashpay/dash#6666
File: test/functional/rpc_netinfo.py:73-81
Timestamp: 2025-08-10T13:52:46.260Z
Learning: In the Bitcoin/Dash test framework, `self.node.extra_args` contains the original startup arguments for a TestNode and is not modified by `test.restart_node()`. Each restart with new `extra_args` doesn't update the stored `self.node.extra_args`, so copying from it always provides a clean slate without accumulated modifications from previous restarts.

Applied to files:

  • test/functional/rpc_netinfo.py
📚 Learning: 2025-06-16T17:59:55.669Z
Learnt from: kwvg
PR: dashpay/dash#6665
File: src/evo/deterministicmns.cpp:1284-1287
Timestamp: 2025-06-16T17:59:55.669Z
Learning: In Dash masternode ProRegTx validation, platform ports (platformHTTPPort and platformP2PPort) are mandatory and must be non-zero, while netInfo (ipAndPort) is optional. This means that even if an empty netInfo returns 0 from GetPrimary().GetPort(), it won't cause false positives in port duplication checks since platform ports cannot be 0.

Applied to files:

  • test/functional/rpc_netinfo.py
  • test/functional/test_framework/test_framework.py
🧬 Code Graph Analysis (1)
src/test/evo_netinfo_tests.cpp (1)
src/evo/netinfo.h (2)
  • MAX_ENTRIES_EXTNETINFO (20-20)
  • CService (193-203)
🪛 GitHub Actions: Clang Diff Format Check
src/evo/netinfo.cpp

[error] 257-270: Clang format differences found. Code formatting does not match the expected style.

src/evo/netinfo.h

[error] 299-309: Clang format differences found. Code formatting does not match the expected style.

🪛 Ruff (0.12.2)
test/functional/rpc_netinfo.py

163-163: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


415-415: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


416-416: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


417-417: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


418-418: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


419-419: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


430-430: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


435-435: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


436-436: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


437-437: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


438-438: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


439-439: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


442-442: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


459-459: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


460-460: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


461-461: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


462-462: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


463-463: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


546-546: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)


547-547: Use key not in dict instead of key not in dict.keys()

Remove .keys()

(SIM118)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: Lint / Run linters
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
🔇 Additional comments (16)
src/test/evo_netinfo_tests.cpp (7)

21-50: Good coverage and purpose-aware expectations match implementation

The mainnet vector captures default-port implication for MnNetInfo, explicit-port requirement for ExtNetInfo, invalid purpose handling, IPv6 allowance only for ExtNetInfo, and platform P2P acceptance only for ExtNetInfo. These align with ValidateService/AddEntry semantics.


79-96: LGTM: ExtNetInfo tests mirror Mn and handle malformed vs success states correctly

The separation of expected return codes for Mn vs Ext and malformed checks for empty states is precise.


102-119: Correct enforcement: single-entry MnNetInfo and purpose restriction

Single-entry overwrite rejection and restricting to CORE_P2P are tested and match MnNetInfo::AddEntry logic.


142-157: Entry limit and per-purpose semantics verified correctly

Per-purpose limit (MAX_ENTRIES_EXTNETINFO) and flattened-size expectations are asserted properly. Nice touch verifying a different purpose does not trip the limit.


159-179: Duplicate rules validated comprehensively

Within-list exact and partial duplicates prohibited; across-lists exact duplicates prohibited and partial allowed. This matches ExtNetInfo::ProcessCandidate and duplicate helpers.


286-324: CService compatibility tests updated appropriately

Using purpose-aware AddEntry calls keeps this compatibility test relevant. Assertions for failed lookups and invalid types leaving MnNetInfo empty are sound.


326-361: Shared_ptr equality semantics well covered

Good exercise of type-sensitive equality across MnNetInfo/ExtNetInfo implementations and state differences.

src/evo/netinfo.cpp (2)

197-220: MnNetInfo AddEntry: behavior matches tests, fast-fail and no DNS lookups

IPv4-only safe char filter, default port implication, and fAllowLookup=false conform to intended behavior and tests.


329-345: Validation excludes Tor/other types; explicit zero/bad ports check is correct

IPv4/IPv6-only and routable checks plus IsBadPort() || port==0 align with policy and tests.

test/functional/rpc_netinfo.py (1)

154-160: Assertion helper covers expected optional fields correctly

Using truthy checks to gate platform entries is fine since tests don’t pass zero here. Keep as-is.

test/functional/test_framework/test_framework.py (3)

1223-1243: validate_inputs centralizes Evo-specific checks — good extraction

The helper enforces presence of platform params for EvoNodes and pairs error code/message usage. This removes repetition in register/update paths.


1248-1266: Default core addr behavior remains intact with new params

Falling back to ['127.0.0.1:{nodePort}'] when addrs_core_p2p is None preserves legacy behavior while allowing extended inputs.


1413-1458: update_service wiring aligns with refactored RPC arguments

Parameter order and submission booleans mirror register paths; Evo and legacy codepaths are cleanly separated.

src/evo/netinfo.h (3)

19-26: Public API additions look coherent

MAX_ENTRIES_EXTNETINFO and Duplicate status are sensible, matching tests and implementation.


63-75: Purpose codes and comments clarify ordering/contiguity constraints

Clear documentation that changes require a format version bump. Good.


169-176: GetAddrPort returns by value — consistent with variant storage

Returning std::optional by value avoids dangling references and matches netinfo.cpp.

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

This PR is really big! I reviewed half of commits, please consider my comments

@@ -244,13 +244,18 @@ NetInfoStatus MnNetInfo::Validate() const

UniValue MnNetInfo::ToJson() const
{
if (IsEmpty()) {
return UniValue(UniValue::VARR);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: UniValue{UniValue::VARR}; is better here I think

// Exact duplicates are prohibited
BOOST_CHECK_EQUAL(netInfo.AddEntry("1.1.1.1:9998"), NetInfoStatus::Duplicate);
// Partial duplicates (same address, different port) are also prohibited
BOOST_CHECK_EQUAL(netInfo.AddEntry("1.1.1.1:9997"), NetInfoStatus::Duplicate);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: may you add tests with duplicates of ipv6 address too?

@@ -311,8 +323,12 @@ NetInfoStatus ExtNetInfo::ValidateService(const CService& service)
return NetInfoStatus::Success;
}

NetInfoStatus ExtNetInfo::AddEntry(const std::string& input)
NetInfoStatus ExtNetInfo::AddEntry(const uint8_t purpose, const std::string& input)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: purpose here should have a type of enum, not uint8_t

// entries must be contiguous and cannot be changed once set without a format version
// update, which will necessitate a hard-fork.
namespace Purpose {
enum : uint8_t {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: enum class for all new code is preferable.

}

// Warning: Used in RPC code, altering existing values is a breaking change
constexpr std::string_view PurposeToString(const uint8_t purpose, const bool lower = false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

here should be enum instead uint8_t

constexpr std::string_view PurposeToString(const Purpose purpose, const bool lower = false)

Copy link
Collaborator

Choose a reason for hiding this comment

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

it seems as capitals are used only in logs. If it is true, consider to drop an argument lower=false and just always output it (in RPC and in logs) as lowercase without capital option. It doesn't seems much useful for me

@@ -440,6 +440,16 @@ static RPCHelpMan getcoinjoininfo()
{RPCResult::Type::STR, "address", ""},
}
},
{RPCResult::Type::ARR, "platform_p2p", /*optional=*/true, "Addresses used for Platform P2P",
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: if you will keep it here, consider mentioning evo nodes here;
maybe something like

                                        {RPCResult::Type::ARR, "platform_p2p", /*optional=*/true, "Addresses used for Platform P2P (EvoNodes only)",

@@ -440,6 +440,16 @@ static RPCHelpMan getcoinjoininfo()
{RPCResult::Type::STR, "address", ""},
}
},
{RPCResult::Type::ARR, "platform_p2p", /*optional=*/true, "Addresses used for Platform P2P",
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO coinjoin related RPC getcoinjoininfo should not return platform_https and platform_p2p; consider just removing these fields comletely.
Probably you need to add helper "get p2p addreses only" for NetInfo, something like that:

-        obj.pushKV("addresses", mixingMasternode->pdmnState->netInfo->ToJson());
+        obj.pushKV("addresses", mixingMasternode->pdmnState->netInfo->FilterBy(NetInfoPurpose::P2P_CORE)->ToJson());

}

// TODO: use real args here
static int mainnetPlatformHTTPPort = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN)->GetDefaultPlatformHTTPPort();
const auto main_params{CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN)};
Copy link
Collaborator

Choose a reason for hiding this comment

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

wait, why here CreateChainParams()?
I haven't noticed it before in prior PRs... Why can't you just use here Params() from header chainparams.h?

Calling CreateChainParams is not good at least from performance point of view. It's not a very heavy object, but it's not cheap at all

Comment on lines +193 to 194
"platformNodeID=%s%s)\n"
" %s",
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: consider merging 2 lines for simplicity

-                     "platformNodeID=%s%s)\n"
-                     "  %s",
+                     "platformNodeID=%s%s)\n   %s",

src/rpc/evo.cpp Outdated
@@ -184,12 +184,14 @@ static RPCArg GetRpcArg(const std::string& strParamName)
"Platform P2P node ID, derived from P2P public key."}
},
{"platformP2PPort",
{"platformP2PPort", RPCArg::Type::NUM, RPCArg::Optional::NO,
"TCP port of Dash Platform peer-to-peer communication between nodes (network byte order)."}
{"platformP2PPort", RPCArg::Type::STR, RPCArg::Optional::NO,
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's not more platormP2PPort; especially if it's string.

I'd suggest to create new field if you really need to return string for any reason; otherwise just return PORT.

Also I see further you requires platform address to has port only (not address), so, I'd suggest to don't change this one.
See:

                                  -8, "ProTx version disallows storing addresses in platformP2PPort (must specify port number only)")

}
}
if (!netInfoObj.empty()) {
obj.pushKV("addresses", netInfoObj);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: IMO should not re-order fields if there's no reason to do it.

addresses will be now the last fields

@@ -246,7 +246,7 @@ class MnNetInfo final : public NetInfoInterface
}

NetInfoStatus AddEntry(const uint8_t purpose, const std::string& service) override;
NetInfoList GetEntries() const override;
NetInfoList GetEntries(std::optional<uint8_t> purpose_opt = std::nullopt) const override;
Copy link
Collaborator

Choose a reason for hiding this comment

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

std::optional here looks a bit too complicated; maybe better to add one more fake value to enum such as Purpose::ALL ?

@@ -1242,7 +1242,7 @@ def register(self, node: TestNode, submit: bool, collateral_txid: Optional[str]
args = [
collateral_txid or self.collateral_txid,
collateral_vout or self.collateral_vout,
addrs_core_p2p or [f'127.0.0.1:{self.nodePort}'],
[f'127.0.0.1:{self.nodePort}'] if addrs_core_p2p is None else addrs_core_p2p,
Copy link
Collaborator

Choose a reason for hiding this comment

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

            [f'127.0.0.1:{self.nodePort}'] if addrs_core_p2p is None else addrs_core_p2p,

            [f'127.0.0.1:{self.nodePort}'] if addrs_core_p2p is None else addrs_core_p2p,

            [f'127.0.0.1:{self.nodePort}'] if addrs_core_p2p is None else addrs_core_p2p,

nit: consider create a helper here


def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def activate_v23(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: consider using here activate_by_name instead self-written helper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RPC Some notable changes to RPC params/behaviour/descriptions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants