Skip to content

fix(sdk): call on_address_found for seeded balances in incremental-only sync#3468

Closed
lklimek wants to merge 2 commits intov3.1-devfrom
fix/address-sync-incremental-discovery
Closed

fix(sdk): call on_address_found for seeded balances in incremental-only sync#3468
lklimek wants to merge 2 commits intov3.1-devfrom
fix/address-sync-incremental-discovery

Conversation

@lklimek
Copy link
Copy Markdown
Contributor

@lklimek lklimek commented Apr 9, 2026

Summary

Fixes a bug where newly funded platform addresses are never discovered during incremental-only address sync, making them permanently invisible to the wallet.

Root Cause

In sync_address_balances() (incremental-only mode), result.found is seeded from provider.current_balances() but on_address_found is not called for those entries. This callback is the only mechanism that populates the provider's internal found set (e.g., found_balances in WalletAddressProvider).

Failing Scenario

  1. A platform address is funded via top_up() — state transition confirmed on Platform
  2. Next sync_address_balances() call runs in incremental-only mode (last_sync_timestamp > 0)
  3. result.found is seeded from current_balances() (the wallet's stored balances from previous sync)
  4. on_address_found is NOT called for seeded entries
  5. found_balances stays empty in the provider
  6. apply_results_to_wallet() iterates over found_balances — finds nothing — wallet never updates
  7. All subsequent polls repeat this — the address balance is permanently stale

Why Full Scan Works

The full tree scan path (via process_trunk_result and process_branch_result) calls on_address_found for every discovered address (lines 155, 197). Incremental-only mode skips the tree scan entirely and only processes deltas, but the delta callbacks (on_address_found at lines 622, 683) only fire when new_balance != current_balance — if no delta exists in the incremental window, the address is never reported.

Fix

Add provider.on_address_found(index, &key, funds) calls in the incremental-only seed loop, matching the full scan behavior. This ensures the provider's internal state is populated regardless of sync mode.

Related Issue (not fixed here)

In dash-evo-tool, src/backend_task/wallet/fetch_platform_address_balances.rs:116-120: when incremental sync returns no data, new_sync_timestamp = 0 is saved to DB, causing a destructive oscillation between full scan and incremental modes. This is a separate app-layer bug.

Test Plan

  • Verify compilation with cargo build -p dash-sdk --all-features
  • Manual test: fund a platform address, confirm incremental sync discovers the balance
  • Verify full scan path is unaffected (regression check)

🤖 Co-authored by Claudius the Magnificent AI Agent

Summary by CodeRabbit

  • Bug Fixes
    • Fixed address balance synchronization in incremental-only mode to properly track found addresses and update internal state during seeding.

…ly sync

In incremental-only mode, sync_address_balances seeds result.found from
the provider's current_balances() but never calls on_address_found for
those entries. This means the provider's internal state (e.g.,
found_balances in WalletAddressProvider) is never populated, making
previously discovered addresses invisible to apply_results_to_wallet().

The fix adds on_address_found calls for each seeded entry, matching the
behavior of the full tree scan path which already calls this callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54de27b6-7225-4784-a3e7-2cd4cb1f250b

📥 Commits

Reviewing files that changed from the base of the PR and between 9d799d3 and b56bbc3.

📒 Files selected for processing (1)
  • packages/rs-sdk/src/platform/address_sync/mod.rs

📝 Walkthrough

Walkthrough

The sync_address_balances function's incremental-only mode was modified to clone the address key before storing it and to invoke provider.on_address_found() for all current balance entries, ensuring the provider properly updates its internal state during seeding.

Changes

Cohort / File(s) Summary
Address Sync Logic
packages/rs-sdk/src/platform/address_sync/mod.rs
Modified incremental-only seeding to clone keys before insertion and explicitly call provider.on_address_found() for each balance entry to maintain provider state consistency.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops through addresses with care, 🐰
Cloning keys with methodical flair,
Each balance found gets reported true,
Provider's state now shiny and new,
Incremental sync—a detail done right! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: fixing a bug where on_address_found is not called during incremental-only sync, which is the core issue described in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/address-sync-incremental-discovery

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 and usage tips.

@lklimek lklimek marked this pull request as ready for review April 10, 2026 05:48
@thepastaclaw
Copy link
Copy Markdown
Collaborator

thepastaclaw commented Apr 10, 2026

✅ Review complete (commit b56bbc3)

@github-actions
Copy link
Copy Markdown
Contributor

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "87380f0e4c84914164a2f4f962e5c93ddcc31492b049bc1d681106ae4b86467d"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

@lklimek lklimek closed this Apr 10, 2026
Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Clean, minimal fix that correctly adds the missing on_address_found callback in the incremental-only seed loop, mirroring the full-scan pattern at lines 154-155 and 196-197. The change is correct and well-commented. The only gap is the lack of a regression test for the exact code path being fixed.

Reviewed commit: b56bbc3

🟡 1 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/rs-sdk/src/platform/address_sync/mod.rs`:
- [SUGGESTION] lines 343-351: No test covers the incremental-only sync path where this bug lived
  The existing `test_sync_address_balances` test passes `None` for `last_sync_timestamp`, which always triggers a full tree scan. The bug being fixed only manifests in incremental-only mode, yet no test exercises that path.

To regress-protect this fix, a test would need to:
1. Implement `current_balances()` and `last_sync_height()` on the test provider (the current `TestAddressProvider` returns empty defaults for both, making incremental-only mode unreachable)
2. Pass `Some(recent_timestamp)` to `sync_address_balances` to trigger `needs_full_scan == false`
3. Assert that `provider.found` is populated after sync — i.e., `on_address_found` was called for each seeded balance

Without this, the fix could silently regress in a future refactor since the existing suite only validates the full-scan code path.

Comment on lines 343 to 351
for (index, key, funds) in provider.current_balances() {
result.found.insert((index, key), funds);
result.found.insert((index, key.clone()), funds);
// Notify the provider so it can update its internal state
// (e.g., populate found_balances, extend gap limit). Without this,
// addresses discovered during a previous full scan are invisible to
// the consumer in incremental-only mode because on_address_found
// is the only path that populates the provider's found set.
provider.on_address_found(index, &key, funds);
}
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: No test covers the incremental-only sync path where this bug lived

The existing test_sync_address_balances test passes None for last_sync_timestamp, which always triggers a full tree scan. The bug being fixed only manifests in incremental-only mode, yet no test exercises that path.

To regress-protect this fix, a test would need to:

  1. Implement current_balances() and last_sync_height() on the test provider (the current TestAddressProvider returns empty defaults for both, making incremental-only mode unreachable)
  2. Pass Some(recent_timestamp) to sync_address_balances to trigger needs_full_scan == false
  3. Assert that provider.found is populated after sync — i.e., on_address_found was called for each seeded balance

Without this, the fix could silently regress in a future refactor since the existing suite only validates the full-scan code path.

source: ['claude', 'codex']

🤖 Fix this 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/address_sync/mod.rs`:
- [SUGGESTION] lines 343-351: No test covers the incremental-only sync path where this bug lived
  The existing `test_sync_address_balances` test passes `None` for `last_sync_timestamp`, which always triggers a full tree scan. The bug being fixed only manifests in incremental-only mode, yet no test exercises that path.

To regress-protect this fix, a test would need to:
1. Implement `current_balances()` and `last_sync_height()` on the test provider (the current `TestAddressProvider` returns empty defaults for both, making incremental-only mode unreachable)
2. Pass `Some(recent_timestamp)` to `sync_address_balances` to trigger `needs_full_scan == false`
3. Assert that `provider.found` is populated after sync — i.e., `on_address_found` was called for each seeded balance

Without this, the fix could silently regress in a future refactor since the existing suite only validates the full-scan code path.

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