Skip to content

[PM-21801] fix: return BeneficiaryNotFound for absent beneficiaries in get_unclaimed_amount#1359

Merged
m2ux merged 5 commits into
mainfrom
fix/PM-21801-get-unclaimed-amount-beneficiary-not-found
Apr 30, 2026
Merged

[PM-21801] fix: return BeneficiaryNotFound for absent beneficiaries in get_unclaimed_amount#1359
m2ux merged 5 commits into
mainfrom
fix/PM-21801-get-unclaimed-amount-beneficiary-not-found

Conversation

@m2ux

@m2ux m2ux commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix absent-entity ambiguity in `get_unclaimed_amount`: non-existent beneficiaries now return `Err(LedgerApiError::BeneficiaryNotFound)` instead of `Ok(0)`, allowing RPC consumers to distinguish a missing beneficiary from one with zero unclaimed rewards.

🎫 PM-21801 📐 Engineering


Motivation

`Bridge::get_unclaimed_amount` used `unwrap_or(&0)` when looking up a beneficiary in the ledger state map. A caller querying an address that has never been registered received `Ok(0)` — the same response as a known beneficiary with zero unclaimed. There was no way to distinguish the two cases.

This is the absent-entity ambiguity pattern (AI Audit Finding R-009, severity Medium). The sibling function `get_contract_state` was fixed by the same pattern in PM-19973.


Changes

  • `ledger/src/versions/common/types.rs` — Add `BeneficiaryNotFound` variant to `LedgerApiError` (SCALE code 157); update `Display` and `From for u8`
  • `ledger/src/versions/common/mod.rs` — Replace `unwrap_or(&0)` with `.copied().ok_or(LedgerApiError::BeneficiaryNotFound)` at line 718
  • `pallets/midnight/src/lib.rs` — Add `BeneficiaryNotFound` to pallet `Error` (codec index 14) and `From` impl
  • `pallets/midnight/src/tests.rs` — Add `test_get_unclaimed_amount_beneficiary_not_found` error-path test
  • `changes/node/changed/PM-21801-beneficiary-not-found.md` — Changelog fragment

Note: A runtime metadata rebuild (`/bot rebuild-metadata`) will be required after merge.


📌 Submission Checklist

  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason: change file included
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • No new todos introduced

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other
  • N/A

🗹 TODO before merging

  • Implementation complete
  • Tests passing
  • Ready for review

m2ux added 2 commits April 18, 2026 13:15
Branch for fix: get_unclaimed_amount returns BeneficiaryNotFound for absent beneficiaries.

Jira: PM-21801
Signed-off-by: Mike Clay <mike.clay@shielded.io>
- Add Beneficiary alias for clarifying benefit recipient distinction
- Implement explicit Err(DispatchError::CannotLookup) for missing ledger entries
- Add comprehensive error handling tests for missing beneficiary scenarios
- Return proper error type instead of ambiguous Ok(0) fallback

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux self-assigned this Apr 19, 2026
@m2ux m2ux marked this pull request as ready for review April 19, 2026 16:53
@m2ux m2ux requested a review from a team as a code owner April 19, 2026 16:53
@m2ux m2ux changed the title fix(PM-21801): return BeneficiaryNotFound for absent beneficiaries in get_unclaimed_amount [PM-21801] fix: return BeneficiaryNotFound for absent beneficiaries in get_unclaimed_amount Apr 29, 2026
@m2ux

m2ux commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

/bot rebuild-metadata

@github-actions

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete! Changes have been committed.

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux added this pull request to the merge queue Apr 30, 2026
Merged via the queue into main with commit 52c5c50 Apr 30, 2026
33 checks passed
@m2ux m2ux deleted the fix/PM-21801-get-unclaimed-amount-beneficiary-not-found branch April 30, 2026 16:08
ozgb added a commit that referenced this pull request May 1, 2026
Post-merge: `LedgerApiError::BeneficiaryNotFound` was added on `main`
(#1359). The flattened `From<LedgerApiError> for Error<T>` impls in
`cnight-observation` and `midnight-system` need a matching arm now that
they enumerate variants explicitly rather than wrapping the whole type.

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
RomarQ pushed a commit to RomarQ/midnight-node that referenced this pull request May 5, 2026
* feat(ledger): surface nested error variants in flat ledger error enums

Variants like MalformedTransaction::EffectsCheckFailure(EffectsCheckError)
were previously collapsed to a single flat code, hiding the inner cause
from end-users. Flatten the inner enums (EffectsCheckError,
SequencingCheckError, DisjointCheckError, FeeCalculationError,
MalformedContractDeploy, TransactionApplicationError, zswap MalformedOffer
and TransactionInvalid) into granular variants in InvalidError,
MalformedError, and SystemTransactionError, with stable u8 codes in the
free 212-250 range.

Add version-specific error_ext modules (versions/error_ext/ledger_7.rs,
ledger_8.rs) wired through lib.rs in the same style as block_context. Each
version maps only the variants it knows about; unknown variants fall
through to UnknownError + log rather than being silently misclassified.
This replaces the prior '_ => Ste::MerkleTreeError' pattern, which would
have masked any future ledger 9+ additions as MerkleTreeError.

Adds InvalidError variants for ledger 8's MerkleTreeError (top-level and
zswap-nested) and DivideByZero (present in both versions).

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix: add missing error fallback warning log

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add change files for granular ledger error codes

Covers both the node-side host conversion changes and the runtime-side
pallet error variants exposed to end-users. Runtime metadata rebuild is
required as a result of the new variants.

Fixes: midnightntwrk#1374
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: change file Fixes -> Helps with for issue 1374

Granular error codes don't resolve the underlying issue, just make it
easier to debug.

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* docs: update change files with PR links

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* docs: update comments

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

* feat(ledger): nest granular ledger error variants

Replace flattened variant names like `EffectsCheckRealCallsSubsetCheckFailure`
with nested wrappers like `MalformedError::EffectsCheck(EffectsCheckErrorCode::RealCallsSubsetCheckFailure)`.
Variant names now mirror upstream `mn_ledger` types directly. Each sub-enum
gets its own 256-variant budget, and the compile-time `MAX_MODULE_ERROR_ENCODED_SIZE`
check enforces correctness.

Flatten `LedgerApiError` into `cnight-observation::Error<T>` and
`midnight-system::Error<T>` (mirroring the `midnight` pallet pattern).
The previous `Error::LedgerApiError(LedgerApiError)` wrapper added an extra
byte that, combined with the new nesting, exceeded the 4-byte module error
budget.

u8 codes via `From<LedgerApiError>` are preserved for all reachable variants.
Two fallback paths now produce more specific bytes: unmapped upstream
ZswapTransactionInvalid -> 103 (was 109) and unmapped MalformedOffer -> 127
(was 139), reusing what were previously vestigial flat catch-all bytes.

SCALE pallet error encoding for granular variants changes shape: e.g.
`Error::Transaction(Malformed(EffectsCheck(RealCallsSubsetCheckFailure)))`
is 4 bytes vs the previous 3-byte flat encoding.

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* feat(ledger): reserve retired u8 error codes

Codes 168, 182, 186, 187, 188, 193, 205 were assigned to flat catch-all
variants (`MalformedError::FeeCalculation` etc.) on `main` and were
observable to mempool clients via `InvalidTransaction::Custom`. They are
no longer produced by the nested encoding, but reusing them for new
variants would silently collide on the wire.

Document the reservation in `RETIRED_U8_ERROR_CODES` and assert at test
time that no `LedgerApiError` value encodes to one of them.

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix(pallets): handle BeneficiaryNotFound in wrapper Error conversions

Post-merge: `LedgerApiError::BeneficiaryNotFound` was added on `main`
(midnightntwrk#1359). The flattened `From<LedgerApiError> for Error<T>` impls in
`cnight-observation` and `midnight-system` need a matching arm now that
they enumerate variants explicitly rather than wrapping the whole type.

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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