Skip to content

fix(sdk): honor explicit regtest addresses in dapi-client; tolerate empty discovery in wasm-sdk trusted context - #4538

Draft
PastaPastaPasta wants to merge 4 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:fix/client-regtest-addressing
Draft

fix(sdk): honor explicit regtest addresses in dapi-client; tolerate empty discovery in wasm-sdk trusted context#4538
PastaPastaPasta wants to merge 4 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:fix/client-regtest-addressing

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Two client-side bugs surfaced by state-sync e2e work, independent of the feature branches:

  • js-dapi-client silently redirects explicitly-configured addresses on regtest. ListDAPIAddressProvider.getLiveAddress() rewrote EVERY address to 127.0.0.1:2443 + i*100 (the stock local-preset ports) whenever the network is regtest — so a client configured with explicit non-default addresses (e.g. an isolated test network, or any second local network on one machine) silently sent every wallet/DAPI request to whatever occupies the stock ports. On a shared dev machine that was a different chain entirely, which produced a long goose chase of "wallet funding is broken" reports: subscriptions landed on the wrong network's rs-dapi, and wallets watched a chain their payments were never on.
  • wasm-sdk trusted-context prefetch dies when masternode discovery returns no eligible entries. On local networks the quorum sidecar's version checks reject the gateway's self-signed TLS, so empty discovery is the normal case there; discovery only feeds the no-explicit-addresses path and shouldn't be fatal when explicit addresses are configured. Tolerance is scoped to regtest so mainnet/testnet behavior is unchanged.

What was done?

  • js-dapi-client: explicit loopback addresses are honored verbatim on regtest; the rewrite only applies where it originally made sense (+ unit test).
  • wasm-sdk: trusted-context prefetch tolerates empty masternode discovery on regtest instead of failing the whole context build.

How Has This Been Tested?

js-dapi-client unit suite: 319 passing including the new rewrite regression test. The wasm-sdk change was validated live: it is what allowed the state-sync e2e (#4530) to seed identities/contracts/documents through an isolated-port local network and re-read them proof-verified — the full suite there runs green with these fixes.

Both commits were code-reviewed (code-review-validator) as part of the e2e branch changeset before being cherry-picked here.

Breaking Changes

None intended. Behavior change is regtest-only: clients that (perhaps unknowingly) relied on the address rewrite to reach stock-port local networks while configured with different explicit addresses will now use their configured addresses — which is the documented contract.

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 if needed

🤖 Generated with Claude Code

PastaPastaPasta and others added 3 commits August 30, 2026 00:58
The regtest localhost workaround rewrote EVERY live address to 127.0.0.1:2443+i*100 (the stock local gateway ports), including addresses the caller configured explicitly. A local network that moves its ports (the dashmate e2e suites do, to run next to other networks) had every request silently redirected to whatever squats the stock ports on the machine - on a shared dev box, a completely different network. Only rewrite addresses that carry a non-loopback (docker-internal) host, which is the case the workaround exists for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xt prefetch

Discovery only feeds the no-explicit-addresses path of withTrustedContext, but a failure made the whole prefetch unusable - and it fails routinely on local networks, where the quorum sidecar's per-masternode version checks reject the gateway's self-signed TLS and report no eligible masternodes. Degrade to a warning and an empty discovered list; SDKs constructed with explicit addresses are unaffected, and the quorum data proof verification needs is fetched before this point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cherry-picked from the state-sync e2e branch; the companion e2e assertion stays there (the spec file only exists on that branch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 29, 2026
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

⛔ Final review complete — 1 blocking finding(s) (commit c3003da)
Last checked: 2026-08-31 00:01 UTC

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Sol-only technical fallback

The dapi-client change does not fully meet the PR's stated goal because caller-supplied non-default regtest addresses are still rewritten to the stock local gateway. The WASM fallback is correctly limited to regtest, but it suppresses more discovery failures than the advertised empty-result case and lacks deterministic regression coverage. Source: reviewer backends — Claude (sol-fallback-reviewer-general and sol-fallback-reviewer-rust-quality) and Codex; final verifier backend — Claude Agent SDK.

One or more required Phase-1 GLM Flash lanes remained technically unusable after the bounded exact-model retry. Their evidence was discarded as authoritative, and the complete selected role cohort was rerun fresh on exact gpt-5.6-sol before this fresh Sol verifier produced the final decision. No additional Phase-2 reviewer pass ran.

Review provenance

  • Phase 1 GLM evidence: technically unusable after bounded retry; discarded from the decision
  • GLM failure attempts: codex-general-7a7b0ec185e24e1abc9547625b552aef (failed), codex-general-c8cfc5d33caf432d9ab79ff93406551f (failed), codex-rust-quality-0e35283a40384ebaa68eed2cf38840aa (failed), codex-rust-quality-1c3417d033ee4e929bf2af5eb13978b7 (completed)
  • Sol-only fallback reasons: launch_transport_or_nonzero_exit
  • Sol-only fallback reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (completed)
  • Fresh verifier (Sol): gpt-5.6-sol — final-verifier
  • Additional Phase 2 pass: not run; the Sol-only fallback is final

🔴 1 blocking | 🟡 2 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js`:
- [BLOCKING] packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js:42-43: Explicit non-default regtest addresses are still rewritten
  This condition treats only the exact hosts `127.0.0.1` and `localhost` as explicit addresses, but host value does not indicate address provenance. A caller-supplied address such as the README's `127.0.0.2`, a LAN address, or a container hostname still enters this branch and is mutated to `https://127.0.0.1:2443 + i*100`. This was reproduced through `createDAPIAddressProviderFromOptions({ network: 'local', dapiAddresses: ['127.0.0.2:45003:self-signed'] })`, which returned `127.0.0.1:2443`. Gate the compatibility rewrite on whether the provider was constructed from seeds or discovered masternodes, rather than checking two host strings, and add a factory-level regression test for a caller-supplied non-default address.

In `packages/wasm-sdk/src/context_provider.rs`:
- [SUGGESTION] packages/wasm-sdk/src/context_provider.rs:269-280: Only suppress the expected empty-discovery error on regtest
  The PR describes tolerating the normal `No eligible masternode addresses discovered` result, but this match suppresses every discovery error on regtest. `fetch_masternode_addresses` can also fail because of transport errors, non-success HTTP responses, malformed JSON, or a sidecar-declared failure, and `fetch_addresses_from` can reject malformed URIs or addresses. Those failures currently become an empty discovered list, so a builder without explicit addresses silently retains its preset addresses and can contact an unintended endpoint. Preserve the provider error through this layer, represent the expected no-eligible-addresses result as a dedicated typed variant, and downgrade only that variant on regtest.
- [SUGGESTION] packages/wasm-sdk/src/context_provider.rs:269-280: Add deterministic tests for the network-scoped discovery fallback
  No committed test executes the new branches in `prefetch_for`; existing unit tests construct contexts with `for_testing` and inject `discovered_addresses`, while the functional local check depends on a live endpoint and does not verify the public-network boundary. Add a deterministic HTTP fixture that serves valid `/quorums` and `/previous` responses followed by a `/masternodes` response with no eligible entries. Assert that regtest returns a context with no discovered addresses while mainnet or testnet propagates the same result, and verify that malformed or HTTP-failure responses remain errors on regtest once the catch is narrowed.

Comment on lines +42 to +43
const isLoopback = ['127.0.0.1', 'localhost'].includes(liveAddress.getHost());
if (network && network.regtestEnabled && !isLoopback) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Blocking: Explicit non-default regtest addresses are still rewritten

This condition treats only the exact hosts 127.0.0.1 and localhost as explicit addresses, but host value does not indicate address provenance. A caller-supplied address such as the README's 127.0.0.2, a LAN address, or a container hostname still enters this branch and is mutated to https://127.0.0.1:2443 + i*100. This was reproduced through createDAPIAddressProviderFromOptions({ network: 'local', dapiAddresses: ['127.0.0.2:45003:self-signed'] }), which returned 127.0.0.1:2443. Gate the compatibility rewrite on whether the provider was constructed from seeds or discovered masternodes, rather than checking two host strings, and add a factory-level regression test for a caller-supplied non-default address.

source: ['claude']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved in c3003daExplicit non-default regtest addresses are still rewritten no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

Comment on lines +269 to +280
let discovered_addresses = match Self::fetch_addresses_from(&inner).await {
Ok(addresses) => addresses,
Err(e) if network == dash_sdk::dpp::dashcore::Network::Regtest => {
tracing::warn!(
error = %e,
"trusted context: masternode discovery unavailable, continuing without \
discovered addresses (explicitly configured addresses are unaffected)"
);
Vec::new()
}
Err(e) => return Err(e),
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Only suppress the expected empty-discovery error on regtest

The PR describes tolerating the normal No eligible masternode addresses discovered result, but this match suppresses every discovery error on regtest. fetch_masternode_addresses can also fail because of transport errors, non-success HTTP responses, malformed JSON, or a sidecar-declared failure, and fetch_addresses_from can reject malformed URIs or addresses. Those failures currently become an empty discovered list, so a builder without explicit addresses silently retains its preset addresses and can contact an unintended endpoint. Preserve the provider error through this layer, represent the expected no-eligible-addresses result as a dedicated typed variant, and downgrade only that variant on regtest.

source: ['claude']

Comment on lines +269 to +280
let discovered_addresses = match Self::fetch_addresses_from(&inner).await {
Ok(addresses) => addresses,
Err(e) if network == dash_sdk::dpp::dashcore::Network::Regtest => {
tracing::warn!(
error = %e,
"trusted context: masternode discovery unavailable, continuing without \
discovered addresses (explicitly configured addresses are unaffected)"
);
Vec::new()
}
Err(e) => return Err(e),
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Add deterministic tests for the network-scoped discovery fallback

No committed test executes the new branches in prefetch_for; existing unit tests construct contexts with for_testing and inject discovered_addresses, while the functional local check depends on a live endpoint and does not verify the public-network boundary. Add a deterministic HTTP fixture that serves valid /quorums and /previous responses followed by a /masternodes response with no eligible entries. Assert that regtest returns a context with no discovered addresses while mainnet or testnet propagates the same result, and verify that malformed or HTTP-failure responses remain errors on regtest once the catch is narrowed.

source: ['claude']

The regtest docker-IP workaround exempted only the literal hosts 127.0.0.1 and localhost, so any other caller-supplied address (127.0.0.2, a LAN IP, a container hostname) was still clobbered to the stock local gateway ports. Gate the rewrite on address provenance instead: only addresses discovered from the masternode list (they carry a proRegTxHash) can hold an unreachable docker-internal host, so only those are rewritten. Adds a factory-level regression test for a caller-supplied non-default regtest address.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Blocking finding confirmed and fixed properly (pushed in c3003da29f):

  • Explicit non-default regtest addresses were still rewritten — correct: the first fix only exempted literal 127.0.0.1/localhost, so a caller-supplied 127.0.0.2, LAN IP, or container hostname still got clobbered. Replaced the host-based exemption with the provenance gate the review suggested: the rewrite now applies only to addresses that came from the masternode list (they carry a proRegTxHash; caller-supplied lists never do) — which is exactly the population that can carry the unreachable docker-internal hosts the workaround exists for. Tests cover the SML-provenance rewrite, a caller-supplied non-loopback address surviving untouched, and the factory-level regression (dapiAddresses: ['127.0.0.2:45003:self-signed'] stays put). 321 unit tests passing, lint clean.

The two wasm-sdk suggestions (typed no-eligible-addresses error variant instead of the broad regtest-only catch, and deterministic HTTP-fixture tests for prefetch_for) are acknowledged and deferred as a pair — the fixture design depends on the typed variant, which belongs in rs-sdk-trusted-context-provider. The current behavior is regtest-scoped, logged, and covered by the live 15-scenario e2e in the interim.


🤖 Posted autonomously by Claude on behalf of pasta.

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.

2 participants