Skip to content

fix(sdk): read contested DPNS names as the bare labels Drive returns - #5482

Merged
QuantumExplorer merged 2 commits into
v5.0-devfrom
claude/eloquent-torvalds-9bbd13
Oct 18, 2026
Merged

QuantumExplorer merged 2 commits into
v5.0-devfrom
claude/eloquent-torvalds-9bbd13

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: The Rust SDK has a helper that lists every DPNS name currently under a vote (a "contested" username, where several identities asked for the same name and masternodes decide who gets it). Platform answers that question correctly, but the helper expected each answer in the wrong shape, threw every one away, and returned an empty list. This PR reads the answers in the shape Platform actually sends, fixes the "continue after this name" option the same way, and lets the helper fetch more than 100 names by asking in pages of 100.

Value: Apps built on the Rust SDK or the C FFI see the real list again. On testnet on 2026-10-17 the helper returns all 1,033 contested names instead of 0. Paging with start_after and a limit above 100 now work too; before, both failed with an error from Platform.

Risks: Low. Only the client SDK changes, with no Platform, consensus or protocol change. Two behaviours change that callers could notice: with no limit the helper now reads every contested name, in several requests (11 on testnet) instead of one; and get_contested_dpns_usernames_by_identity with no limit now reads the contenders of every contested name (at least one query per name, 1,033 on testnet, 8 names at a time), so it takes noticeably longer. Before this PR it checked none, because the list was empty.

Issue being fixed or feature implemented

Sdk::get_contested_dpns_normalized_usernames returns an empty list on a live network. On testnet (Drive 5.0.0-rc.4, protocol version 14), it returned 0 names while direct ContestedResource::fetch_many pages of the same VotePollsByDocumentTypeQuery returned 1,033 resources.

The query fixes the parent domain in start_index_values: [Text("dash")], so it ranges over the next property of the parentNameAndLabel index, normalizedLabel. Drive answers each contest with that property's value alone (ResolvedVotePollsByDocumentTypeQuery::execute_no_proof and the ContestedResources proof verifier both yield one Value per contest), so the SDK gets Text("000"), Text("0000"), and so on. The private extract_label_from_contested_resource only accepted a Value::Array of at least two elements, so it dropped every resource.

start_after had the same mismatch. Drive encodes start_at_value as a key of that same next property (serialize_value_for_vote_poll_key(normalizedLabel, value)), but the helper sent Array([Text("dash"), Text(name)]), which Drive refuses as the wrong type. And limit was passed straight through as one request, so anything over 100 was refused (limit 250 out of bounds of [1, 100]).

I found no open PR or issue for this (searched for "contested usernames", "extract_label", "contested dpns", "parentNameAndLabel", "get_contested_dpns_normalized_usernames", "get_contested_dpns_usernames_by_identity").

What was done?

In packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs:

  • extract_label_from_contested_resource reads Value::Text(label). Any other value is now an Error::InvalidProvedResponse instead of a silently dropped name, so a future mismatch between the query and the index shows up as an error rather than an empty list. The array form is gone: nothing produces it for this query, and no caller relied on it.
  • start_after is sent as (Value::Text(name), false), the next property's value, exclusive.
  • get_contested_dpns_normalized_usernames pages: each request asks for at most DEFAULT_QUERY_LIMIT (100, what Drive answers per query) and starts after the last label read, until limit names are read or a short page ends the list. limit: None reads every name. Every request now carries an explicit limit.
  • get_contested_dpns_usernames_by_identity reads every contender of each name it checks, in pages of 100 that each name their count and start after the last contender read (exclusive), as contest_fund_to_join already does. A contest holds up to 1,000 contenders, so an identity past the first 100 is now found, and each proof is checked against the page Drive proved (a request with no count was checked against no limit and refused for a contest of more than 100). It reads vote tallies only, since it needs the contender identities and not their documents. It checks names 8 at a time with buffered, the same bounded fan-out get_contested_non_resolved_usernames uses; results stay in name order, and the first failed query still fails the call. Its limit still bounds how many names it checks, as before.

Affected callers: the C FFI exports dash_sdk_dpns_get_all_contested_usernames and dash_sdk_dpns_get_contested_usernames_by_identity in packages/rs-sdk-ffi/src/dpns/queries/contested.rs call these two helpers and need no change (signatures are unchanged). No Swift or Kotlin wrapper in this repo calls those two exports today.

packages/rs-sdk/tests/dpns_usernames.rs is left as is: it is #[ignore], needs a live network plus Core RPC, and only prints what it gets, so it passed with the empty list. The new unit tests cover the logic without a network.

Before and after

The same calls against testnet on 2026-10-17 (DPNS GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec):

sdk.get_contested_dpns_normalized_usernames(Some(100), None).await
// before: Ok([])                       (0 names)
// after:  Ok(["000", "0000", "00000", "000000000000000000", "0001", ...])  (100 names)

sdk.get_contested_dpns_normalized_usernames(Some(5), Some("000".into())).await
// before: Err(... "document field type doesn't match \"array of [string dash, string 000]\" document value")
// after:  Ok(["0000", "00000", "000000000000000000", "0001", "00010"])

sdk.get_contested_dpns_normalized_usernames(Some(250), None).await
// before: Err(... "limit 250 out of bounds of [1, 100]")
// after:  Ok([...])  (250 names, the first 250 of the full list)

sdk.get_contested_dpns_normalized_usernames(None, None).await
// after:  Ok([...])  (1,033 names, each once, ascending)

How Has This Been Tested?

Unit tests (new, contested_queries::tests, mock SDK, no network). Each page's expected query is built in the test rather than by the code under test, so a request shaped differently (an array cursor, a limit over 100) matches no expectation and fails:

  • should_read_every_contested_name_once_across_pages: pages of 100, 100 and 50 give 250 names, each once, in order.
  • should_stop_at_an_empty_page_after_a_full_one
  • should_read_a_limit_over_100_in_pages_of_at_most_100: Some(150) asks for 100, then 50 after the 100th name.
  • should_start_after_the_given_name: start_after is sent as Text(name), exclusive.
  • should_extract_a_label_answered_as_text and should_refuse_a_contested_resource_that_is_not_text
  • should_list_the_contested_names_an_identity_contends_for: the by-identity helper over three names, with the identity contending in two.
  • should_find_an_identity_past_the_first_100_contenders: a contest of 160 contenders with the identity on the second page is found and lists all 160; a contest of exactly 100 ends at an empty second page.

With the old array cursor put back, the four paging tests fail; with the fix they pass. With the contender count dropped, both by-identity tests fail; with contenders read from the first page only, the 160-contender test fails.

cargo test -p dash-sdk --lib -- dpns_usernames contest_fund   # 26 passed (8 new, 18 adjacent)
cargo clippy -p dash-sdk --lib --tests                         # clean
cargo fmt --all

Live check against testnet (a temporary example using TrustedHttpContextProvider, not committed), 2026-10-17:

Read Result
Direct ContestedResource::fetch_many pages of 100 with a text cursor 1,033 resources, first Text("000")
get_contested_dpns_normalized_usernames(None, None) 1,033 names, 1,033 unique, strictly ascending, identical to the direct pages
11 calls of (Some(100), start_after = last name) 1,033 names, identical to the above
(Some(250), None) the first 250 of the above

The same check run against the code before this PR gave Ok(0 names), the type error and the limit error shown in the before/after above.

Breaking Changes

None. No public signature changes. The helpers now return the names they were documented to return; limit: None reads every name instead of sending one unlimited request.

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 added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 5a8a547

  • Bots — coderabbitai ✓ · thepastaclaw ✓
  • Self-review — post /self-reviewed
  • Reviewer requests paused — your 5 review slots are occupied; this PR is excluded from reviewers' queues. Required approvals still count without a slot.
  • Build running
  • Approvals
    • rust-sdk (packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs) — lklimek or shumkov

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • Bug Fixes
    • Contested username lookups now retrieve results across pages and respect result limits.
    • Invalid non-text username labels now return an error instead of being silently skipped.
    • Identity-contest results retain name order while vote-state lookups run concurrently.
    • Contest lookups now retrieve complete contender lists across pages.

`get_contested_dpns_normalized_usernames` queries the `parentNameAndLabel`
contested index with the parent domain fixed to "dash". Drive answers each
contest with the value of the next index property alone, `Text(label)`, but
the helper only accepted a `[parent, label]` array, so it dropped every name
and returned an empty list (0 of 1,033 on testnet).

`start_after` had the same mismatch: it sent `[Text("dash"), Text(name)]`
as the start value, which Drive refuses as the wrong type for the label
property. A `limit` over 100 was also refused, since Drive answers at most
100 contests per query.

The helper now reads text labels, starts after a text label, and pages 100
at a time until `limit` names are read or none are left (`None` reads every
name). A value that is not text is an error instead of a silently dropped
name. `get_contested_dpns_usernames_by_identity`, which now sees every
contested name, reads their vote states 8 at a time, as
`get_contested_non_resolved_usernames` already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v5.0.0 milestone Oct 17, 2026
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8d303226-1d88-4691-8a01-b76ba36e2926
📥 Commits

Reviewing files that changed from the base of the PR and between cafcc28 and 5a8a547.

📒 Files selected for processing: 1
  • packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The contested DPNS username query reads names in ascending pages and supports an optional result limit. Identity lookup fetches complete contender lists with bounded concurrency. Invalid non-text labels and query failures return errors.

Changes: Contested username queries

Layer / File(s) Summary
Paginated contested-name queries
packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs
The name query reads pages of up to 100 results, advances with an exclusive label cursor, and stops at the requested limit or after a short page. Label extraction accepts text values and returns InvalidProvedResponse for other values. Tests cover pagination, limits, and label validation.
Complete contender lookup for identity matches
packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs
Identity filtering checks each name’s complete contender list with concurrency capped at 8. Vote-tally requests use pages of up to 100 and advance exclusively after the last identity. Tests cover identity matching and multi-page results.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant IdentityLookup
  participant ContenderHelper
  participant VoteTallyQuery
  IdentityLookup->>ContenderHelper: fetch contender pages for each name
  ContenderHelper->>VoteTallyQuery: request vote tallies with exclusive identity cursor
  VoteTallyQuery-->>ContenderHelper: return tally page
  ContenderHelper-->>IdentityLookup: return complete contender identifiers
Loading

Suggested reviewers: thepastaclaw

Merge Risk: ⚪ Minimal · up to 5a8a5

The reported non-resolved contest omission predates this change. No PR-introduced issue remains that should block merging after normal checks.

Pre-merge checks: Passed 5
Check name Status Explanation
Docstring Coverage Passed Docstring coverage is 82.61% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 1 files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the main change: the SDK now reads contested DPNS names as the bare labels returned by Drive. It is concise and specific.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 17, 2026
@thepastaclaw

thepastaclaw commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 5a8a547) · triage: normal

@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 — Phase 1 + Phase 2

The text-label decoding, exclusive cursor, and bounded name pagination match Drive's implementation. One blocking issue remains in the reactivated identity lookup: a valid large contest can fail proof verification and abort the entire lookup because contender requests omit their count and are not paginated. This was a static review; current CI shows passing Rust workspace tests, an unrelated Swift simulator test-runner launch failure, and pending PR Hygiene.

🔴 1 blocking

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The contained SDK change adds pagination, bounded concurrent lookups, and stricter response handling, requiring ordinary logic review without changing a critical surface.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 71% left, 5h 100% left
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer
🤖 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/rs-sdk/src/platform/dpns_usernames/contested_queries.rs`:
- [BLOCKING] packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs:263-267: Page contender states before filtering contested names by identity
  This loop now receives real contested names, but `get_contested_dpns_vote_state(label, None)` sends an omitted contender count. The server's `query_contested_resource_vote_state_v0` replaces that omission with its default of 100, while `ContestedDocumentVotePollDriveQuery::try_from_request` reconstructs the proof-verification query with `limit: None`. The contender `FetchMany` implementation does not normalize the default. For a sufficiently large contest, such as one with 250 contenders, the server therefore produces a bounded proof for an unbounded verification query. The pinned GroveDB verifier rejects the missing requested contender subtrees, and `vote_state?` aborts the entire identity lookup—even if the searched identity only participates in another contest. Such contests are valid; the current system limit is 1,000 contenders.

  The PR reactivates this path and expands the unlimited lookup to every contested name, so its correctness depends on handling these valid contests. Fetch contender states with explicit page counts and exclusive contender-ID cursors, accumulating all contenders before filtering by identity and constructing the returned list. `platform/documents/contest_fund.rs` already demonstrates this pagination pattern. Add coverage that exercises proof verification for a contest exceeding 100 contenders; the new small, already-decoded mock responses bypass the count/proof mismatch.

Comment thread packages/rs-sdk/src/platform/dpns_usernames/contested_queries.rs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 17, 2026
… by identity lookup

`get_contested_dpns_usernames_by_identity` read each contest with
`get_contested_dpns_vote_state(label, None)`: one request with no count.
Drive answers that with its default page of 100 contenders and proves
that page, so for a contest of more than 100 contenders (a contest takes
up to 1,000) the identity could sit past the page, and the proof was
checked against no limit and refused, failing the whole lookup.

The lookup now reads every contender of each contest in pages of 100,
each request naming its count and starting after the last contender read
(exclusive), as `contest_fund_to_join` already does, and checks the
identity against all of them. It reads vote tallies only, since it needs
the contender identities and not their documents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Oct 18, 2026

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

Re-review — Final validation — Phase 1 + Phase 2

Verified the complete diff at 5a8a547 and found no actionable in-scope defects. The text labels and cursors match Drive's index query, and explicit contender pagination resolves the prior blocker without bypassing proof verification. Validation was static only; current CI has no failed code checks, with Rust workspace tests and Evo SDK tests still pending.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The SDK change introduces pagination, cursor handling, response validation, and bounded concurrent lookups with substantial tests, but does not change a critical surface.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 71% left, 5h 100% left
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 18, 2026
@QuantumExplorer
QuantumExplorer merged commit 5fa100e into v5.0-dev Oct 18, 2026
31 of 33 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/eloquent-torvalds-9bbd13 branch October 18, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Client Only waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants