Skip to content

fix(provider): resolve bids from sibling wallets at a shared hostUri#3232

Open
baktun14 wants to merge 5 commits into
mainfrom
fix/provider-dedup-prefer-active-leases
Open

fix(provider): resolve bids from sibling wallets at a shared hostUri#3232
baktun14 wants to merge 5 commits into
mainfrom
fix/provider-dedup-prefer-active-leases

Conversation

@baktun14
Copy link
Copy Markdown
Contributor

@baktun14 baktun14 commented May 27, 2026

Why

Multiple Provider rows can share the same hostUri on-chain (different owner wallets registering the same daemon — e.g. an operator who rotated wallets but didn't deregister the old ones). The API dedupes by hostUri and only returns one canonical wallet per hostUri, so a bid submitted from a non-canonical sibling wallet failed the providers.find(p => p.owner === bid.provider) lookup in deploy-web — leaving empty rows in the bid list, breaking the search/audited filters, and throwing Cannot find bid provider on manifest send.

Fixes CON-375.

Surfaced via a community report (provider with 3 wallets at provider.cmolls.de:8443, the bidding wallet wasn't the dedup-winning one in our DB).

What

API (apps/api):

  • LeaseRepository.getActiveLeaseCountByProviders(owners) — single grouped query against the existing (providerAddress, closedHeight, createdHeight) index.
  • Stronger dedup tiebreaker in ProviderService.getProviderList: isOnline first, then active lease count DESC (new), then createdHeight DESC as a last resort. Picks the wallet that's actually serving when there's a real signal.
  • New aliasOwners: string[] field on the provider list response — the sibling wallets that share the canonical row's hostUri (excluding the canonical owner).

deploy-web:

  • findProviderForBidProvider(providers, bidProvider) helper — resolves a bid by owner OR by aliasOwners.
  • Used in BidGroup (display) and CreateLease (manifest send, search filter, audited filter).
  • Bid list now hides rows whose resolved provider is offline or on an invalid version (was previously showing empty rows on mainnet, breaking the user's ability to select).

Why aliasOwners resolves it even when all sibling wallets have 0 active leases: the dedup just picks a representative per hostUri. Whichever wallet wins, the others land in aliasOwners. The UI resolves bids from any sibling against the canonical row, and manifest send routes by hostUri (or by the bid's raw owner via findActiveByAddress, which doesn't dedupe), so the actual chain identity stays attached for on-chain operations.

Follow-ups (separate Linear issues, not in this PR)

  • Stronger tiebreaker tier: recent bid count (last N blocks) between active leases and createdHeight.
  • TLS cert CN validation during the status check (mark a sibling row offline if its cert doesn't match the owner).
  • Admin/provider UI surface for "this hostUri has N sibling registrations" to give operators visibility.

Companion PR

Same UI changes mirrored to console-air: akash-network/console-air#25 — depends on this PR's API change being deployed before alias resolution does anything useful.

Test plan

  • API unit tests: npm test in apps/api (provider area — 49/49)
  • deploy-web unit tests: npm test in apps/deploy-web (utils + new-deployment — 227/227)
  • npx tsc --noEmit clean for changed files in both apps
  • npm run lint -- --quiet clean for changed files
  • Verify in a deployed env that /v1/providers returns aliasOwners and a bid from a sibling wallet at a multi-wallet hostUri now renders and can be selected end-to-end (manifest send works).

Summary by CodeRabbit

  • New Features

    • Provider lists now include aliasOwners (associated wallet addresses) so sibling wallets are surfaced.
  • Improvements

    • Provider selection prefers online providers with higher active lease counts when multiple providers share an endpoint.
    • Client UI filtering and bid visibility resolve providers via aliasOwners and require the provider to be online.
  • Tests

    • Added coverage for provider selection, lease-count handling, aliasOwners population, and bid-filtering behavior.

Review Change Stack

Multiple Provider rows can share the same hostUri on-chain (different
owner wallets registering the same daemon). The API dedupes by hostUri
and only returns one canonical row per hostUri, so bids submitted from
non-canonical wallets at the same hostUri previously failed the
`providers.find(p => p.owner === bid.provider)` lookup in the UI —
leaving empty rows in the bid list, breaking the search/audited
filters, and throwing "Cannot find bid provider" on manifest send.

API:
- Strengthen the dedup tiebreaker to prefer the wallet with active
  leases (via new `LeaseRepository.getActiveLeaseCountByProviders`).
  Falls back to `createdHeight DESC` only when leases are tied.
- Expose the sibling wallets that share each canonical row's hostUri
  as `aliasOwners: string[]` on the provider list response.

deploy-web:
- Add `findProviderForBidProvider(providers, bidProvider)` helper that
  resolves a bid by `owner` OR by `aliasOwners`.
- Use the helper in BidGroup display + CreateLease manifest send,
  search filter, and audited filter.
- Hide bid rows whose resolved provider is offline or on an invalid
  version so users no longer see unselectable empty rows on mainnet.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 288684c0-2097-4456-9b01-a6c0e5fb7e5b

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4d3cf and 4baa168.

📒 Files selected for processing (1)
  • apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.spec.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.spec.tsx

📝 Walkthrough

Walkthrough

Adds provider aliasing: LeaseRepository counts active leases per provider owner; ProviderService selects a representative per hostUri using online status, lease counts, and createdHeight; aliasOwners are returned in provider list responses; frontend resolves bids by owner or aliasOwners and uses the lookup in components and tests.

Changes

Provider Aliasing Support

Layer / File(s) Summary
Lease counting and ProviderList schema
apps/api/src/deployment/repositories/lease/lease.repository.ts, apps/api/src/types/provider.ts, apps/api/src/provider/http-schemas/provider.schema.ts
LeaseRepository gains getActiveLeaseCountByProviders() to query active leases per provider address. ProviderList interface and HTTP response schema add aliasOwners: string[].
Provider selection by active leases
apps/api/src/provider/services/provider/provider.service.ts, apps/api/src/utils/map/provider.ts, apps/api/src/provider/services/provider/provider.service.spec.ts
ProviderService injects LeaseRepository, computes active lease counts by owner, and selects provider representatives per hostUri using online status → active lease count → createdHeight. Tracks non-representative owners as aliasOwners and passes them into mapProviderToList. Adds isBetterProviderRepresentative() and test coverage for lease-based selection and tied-lease scenarios.
Frontend provider lookup utility
apps/deploy-web/src/utils/providerUtils.ts, apps/deploy-web/src/utils/providerUtils.spec.ts, apps/deploy-web/src/types/provider.ts
New findProviderForBidProvider resolves providers by matching bid provider against owner or aliasOwners. ApiProviderList type extended with aliasOwners. Tests cover owner match, alias match, and undefined cases.
Frontend component integration
apps/deploy-web/src/components/new-deployment/BidGroup.tsx, apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx, apps/deploy-web/tests/seeders/provider.ts
BidGroup and CreateLease now use findProviderForBidProvider for bid resolution. Bid visibility requires resolved provider to be online. CreateLease applies the lookup in manifest sending, search filtering, and audited-provider checks. Test seeder populates aliasOwners: [].

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • stalniy
  • ygrishajev
✨ 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/provider-dedup-prefer-active-leases

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 94.59459% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.54%. Comparing base (5dcff08) to head (4baa168).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...loy-web/src/components/new-deployment/BidGroup.tsx 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3232      +/-   ##
==========================================
- Coverage   66.69%   65.54%   -1.15%     
==========================================
  Files        1065      981      -84     
  Lines       26123    24091    -2032     
  Branches     6291     5883     -408     
==========================================
- Hits        17423    15791    -1632     
+ Misses       7602     7241     -361     
+ Partials     1098     1059      -39     
Flag Coverage Δ *Carryforward flag
api 84.74% <100.00%> (+0.06%) ⬆️
deploy-web 50.67% <80.00%> (+0.12%) ⬆️
log-collector ?
notifications 91.06% <ø> (ø) Carriedforward from de8a9cb
provider-console 81.38% <ø> (ø) Carriedforward from de8a9cb
provider-inventory ?
provider-proxy 86.08% <ø> (ø) Carriedforward from de8a9cb
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
.../deployment/repositories/lease/lease.repository.ts 80.95% <100.00%> (+3.80%) ⬆️
...s/api/src/provider/http-schemas/provider.schema.ts 100.00% <ø> (ø)
...src/provider/services/provider/provider.service.ts 95.76% <100.00%> (+0.52%) ⬆️
apps/api/src/utils/map/provider.ts 66.08% <100.00%> (+1.73%) ⬆️
...ponents/new-deployment/CreateLease/CreateLease.tsx 84.21% <100.00%> (+9.06%) ⬆️
apps/deploy-web/src/utils/providerUtils.ts 14.28% <100.00%> (+8.03%) ⬆️
...loy-web/src/components/new-deployment/BidGroup.tsx 6.45% <0.00%> (ø)

... and 84 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds the new `aliasOwners` field (and its required-list entry) to the
provider-list response in the docs snapshot.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx (1)

263-265: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Favorites filter still uses raw bid owner instead of resolved provider.

Line 264 compares favoriteProviders against bid.provider; with alias bids, this can hide bids from favorited canonical providers.

Proposed fix
       if (isFilteringFavorites) {
-        filteredBids = filteredBids.filter(bid => favoriteProviders.some(y => y === bid.provider));
+        filteredBids = filteredBids.filter(bid => {
+          const provider = findProviderForBidProvider(providers, bid.provider);
+          return !!provider && favoriteProviders.includes(provider.owner);
+        });
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx`
around lines 263 - 265, The favorites filter is comparing favoriteProviders to
the raw bid.provider which misses alias bids; update the filter inside the
isFilteringFavorites block so it compares favoriteProviders against the bid's
resolved/canonical provider identifier used elsewhere (replace bid.provider with
the resolved provider field used in the codebase, e.g., bid.resolvedProvider or
bid.canonicalProvider), ensuring alias-to-canonical resolution is checked when
building filteredBids.
🧹 Nitpick comments (1)
apps/api/src/provider/services/provider/provider.service.spec.ts (1)

405-417: ⚡ Quick win

Replace as unknown as Provider casts with typed mocks/builders in these new tests.

The new cases still rely on double-casts, which bypasses type safety and conflicts with your test mocking guideline. Prefer mock<Provider>() / MockProxy<Provider> and assign only required fields for each scenario.

As per coding guidelines "Use vitest-mock-extended with mock() and MockProxy<T> for mocking in tests" and "Use mock<T>() instead of as unknown as <Type> for type casting in tests".

Also applies to: 463-479

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/provider/services/provider/provider.service.spec.ts` around
lines 405 - 417, Replace the double-cast patterns used for test fixtures (e.g.,
olderOnlineWithLeases and newerOnlineNoLeases created from
createProviderWithAttributeSignatures(AUDITOR)) with proper typed mocks using
vitest-mock-extended: create mock<Provider>() or MockProxy<Provider> instances,
then set only the required properties (hostUri, isOnline, createdHeight and any
signature attributes produced by createProviderWithAttributeSignatures) on those
mocks instead of using "as unknown as Provider"; update the other occurrences
(the block around lines ~463-479) the same way so the tests use type-safe mocks
rather than bypassing type checking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/provider/services/provider/provider.service.ts`:
- Around line 164-167: ownersByHostUri is collecting duplicate owner entries per
provider.hostUri which causes aliasOwners to contain repeats; change the map
value to a Set (e.g., ownersByHostUri: Map<string, Set<string>>) and when
iterating use ownersByHostUri.get(provider.hostUri)?.add(provider.owner) instead
of pushing to an array, then when building aliasOwners convert each Set to an
array (or dedupe there) so aliasOwners for each hostUri is stable and unique;
apply the same change at the other occurrence referenced around lines 183-184.

---

Outside diff comments:
In `@apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx`:
- Around line 263-265: The favorites filter is comparing favoriteProviders to
the raw bid.provider which misses alias bids; update the filter inside the
isFilteringFavorites block so it compares favoriteProviders against the bid's
resolved/canonical provider identifier used elsewhere (replace bid.provider with
the resolved provider field used in the codebase, e.g., bid.resolvedProvider or
bid.canonicalProvider), ensuring alias-to-canonical resolution is checked when
building filteredBids.

---

Nitpick comments:
In `@apps/api/src/provider/services/provider/provider.service.spec.ts`:
- Around line 405-417: Replace the double-cast patterns used for test fixtures
(e.g., olderOnlineWithLeases and newerOnlineNoLeases created from
createProviderWithAttributeSignatures(AUDITOR)) with proper typed mocks using
vitest-mock-extended: create mock<Provider>() or MockProxy<Provider> instances,
then set only the required properties (hostUri, isOnline, createdHeight and any
signature attributes produced by createProviderWithAttributeSignatures) on those
mocks instead of using "as unknown as Provider"; update the other occurrences
(the block around lines ~463-479) the same way so the tests use type-safe mocks
rather than bypassing type checking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92d37941-07ce-418d-8857-c0a2d93ea141

📥 Commits

Reviewing files that changed from the base of the PR and between 4bcbf74 and 81e2d36.

📒 Files selected for processing (12)
  • apps/api/src/deployment/repositories/lease/lease.repository.ts
  • apps/api/src/provider/http-schemas/provider.schema.ts
  • apps/api/src/provider/services/provider/provider.service.spec.ts
  • apps/api/src/provider/services/provider/provider.service.ts
  • apps/api/src/types/provider.ts
  • apps/api/src/utils/map/provider.ts
  • apps/deploy-web/src/components/new-deployment/BidGroup.tsx
  • apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx
  • apps/deploy-web/src/types/provider.ts
  • apps/deploy-web/src/utils/providerUtils.spec.ts
  • apps/deploy-web/src/utils/providerUtils.ts
  • apps/deploy-web/tests/seeders/provider.ts

Comment thread apps/api/src/provider/services/provider/provider.service.ts Outdated
@stalniy
Copy link
Copy Markdown
Contributor

stalniy commented May 28, 2026

This is non-portable hack. It cannot be solved in the same way in provider-intventory.

baktun14 added 3 commits May 27, 2026 23:55
Use Set<string> for owners-per-hostUri accumulator so any duplicate
provider rows don't produce repeated entries in aliasOwners.
Add unit tests for the search and audited filters in CreateLease that
exercise findProviderForBidProvider, including resolving a bid submitted
from a sibling wallet via aliasOwners. Raises deploy-web patch coverage
of the changed lines above the 50% codecov target (was 40%).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@baktun14
Copy link
Copy Markdown
Contributor Author

This is non-portable hack. It cannot be solved in the same way in provider-intventory.

Any idea what to do then?

@stalniy
Copy link
Copy Markdown
Contributor

stalniy commented Jun 1, 2026

This is non-portable hack. It cannot be solved in the same way in provider-intventory.

Any idea what to do then?

@baktun14 let's get back at the point when the initial dedup logic was introduced and question whether it's needed. If provider sends a bid, it means it's alive, maybe we don't need to dedup anything at all?

@baktun14
Copy link
Copy Markdown
Contributor Author

baktun14 commented Jun 2, 2026

This is non-portable hack. It cannot be solved in the same way in provider-intventory.

Any idea what to do then?

@baktun14 let's get back at the point when the initial dedup logic was introduced and question whether it's needed. If provider sends a bid, it means it's alive, maybe we don't need to dedup anything at all?

It's needed in a few places like the list of providers and the bid list, but I think we can come up with something different and check wallet activity to see which wallet is the good one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants