Skip to content

fix: [PM-19973] Return ContractNotPresent instead of default state#916

Merged
gilescope merged 20 commits into
mainfrom
fix/PM-19973-contract-not-present-error
Apr 17, 2026
Merged

fix: [PM-19973] Return ContractNotPresent instead of default state#916
gilescope merged 20 commits into
mainfrom
fix/PM-19973-contract-not-present-error

Conversation

@m2ux

@m2ux m2ux commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Return an explicit ContractNotPresent error when queried for a non-existent contract address instead of returning a default/empty state. This ensures callers can distinguish between an empty state and a missing contract.

🎫 Ticket 📐 Engineering


Motivation

Currently, when a user or system asks the network for the state of a specific smart contract that does not actually exist, the system simply returns an empty or default response instead of clearly indicating that the contract is missing. This is problematic because it makes a non-existent contract look identical to a real contract that just happens to have no data stored in it yet.

As a result, other parts of the system or external applications might mistakenly believe they are interacting with a valid contract and proceed with their operations based on this false assumption. This can lead to silent failures, incorrect data processing, and confusion for developers trying to build on the network, which is why an independent security audit flagged it as an issue that needs to be fixed by returning a clear "contract not present" error.


Changes

  • Ledger error types — Add ContractNotPresent variant to LedgerApiError with error code 156, Display impl, and From<LedgerApiError> for u8 mapping. Add PartialEq derives to error enum hierarchy (required for nested error assertions).
  • Contract state query — Update do_get_contract_state to return Err(LedgerApiError::ContractNotPresent) instead of Ok(Vec::new()) when a contract is not found in the ledger.
  • Pallet error propagation — Add ContractNotPresent variant to pallet Error<T> with #[codec(index = 13)] and its From<LedgerApiError> mapping.
  • Unit test — New test_get_contract_state_not_present verifying the error is returned for an undeployed contract address.
  • E2E test — New get_contract_state_returns_error_if_not_present verifying the RPC endpoint returns the correct error. Fix incorrect RPC method name in E2E client helper (midnight_contractState instead of midnight_getContractState).

📌 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
  • 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

  • Ready for review

@github-actions

github-actions Bot commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

kics-logo

KICS version: v2.1.19

Category Results
CRITICAL CRITICAL 0
HIGH HIGH 2
MEDIUM MEDIUM 52
LOW LOW 3
INFO INFO 64
TRACE TRACE 0
TOTAL TOTAL 121
Metric Values
Files scanned placeholder 27
Files parsed placeholder 27
Files failed to scan placeholder 0
Total executed queries placeholder 73
Queries failed to execute placeholder 0
Execution time placeholder 12

@m2ux m2ux marked this pull request as ready for review March 12, 2026 12:45
@m2ux m2ux requested a review from a team as a code owner March 12, 2026 12:45
@m2ux m2ux self-assigned this Mar 12, 2026
@m2ux

m2ux commented Mar 13, 2026

Copy link
Copy Markdown
Contributor Author

/bot rebuild-metadata

@github-actions

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete! Changes have been committed.

@m2ux m2ux requested a review from gilescope March 24, 2026 10:58
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Mar 24, 2026
Each query is a path of serialized AlignedValue keys, interpreted by the
node type at each level (array index, map key, merkle tree position),
mirroring the VM's idx instruction.

Collections at the end of the path return an error (not serialized).
Non-existent contracts return empty results (TODO: use ContractNotPresent
from PR midnightntwrk#916 once merged).
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Mar 24, 2026
Each query is a path of serialized AlignedValue keys, interpreted by the
node type at each level (array index, map key, merkle tree position),
mirroring the VM's idx instruction.

Collections at the end of the path return an error (not serialized).
Non-existent contracts return empty results (TODO: use ContractNotPresent
from PR midnightntwrk#916 once merged).
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Mar 24, 2026
Each query is a path of serialized AlignedValue keys, interpreted by the
node type at each level (array index, map key, merkle tree position),
mirroring the VM's idx instruction.

Collections at the end of the path return an error (not serialized).
Non-existent contracts return empty results (TODO: use ContractNotPresent
from PR midnightntwrk#916 once merged).
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Mar 24, 2026
Each query is a path of serialized AlignedValue keys, interpreted by the
node type at each level (array index, map key, merkle tree position),
mirroring the VM's idx instruction.

Collections at the end of the path return an error (not serialized).
Non-existent contracts return empty results (TODO: use ContractNotPresent
from PR midnightntwrk#916 once merged).
@m2ux

m2ux commented Mar 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.

@m2ux m2ux changed the title fix: PM-19973 Return ContractNotPresent instead of default state fix: [PM-19973] Return ContractNotPresent instead of default state Mar 30, 2026
@gilescope

Copy link
Copy Markdown
Contributor

/bot rebuild-metadata

@gilescope gilescope enabled auto-merge April 6, 2026 22:07
@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete. No changes detected.

@m2ux

m2ux commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

/bot rebuild-metadata

@m2ux m2ux force-pushed the fix/PM-19973-contract-not-present-error branch from 38913ee to c2b09c5 Compare April 13, 2026 09:05
@github-actions

Copy link
Copy Markdown
Contributor

❌ Metadata rebuild failed. Check the workflow logs for details.

@m2ux

m2ux commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

/bot rebuild-metadata

@github-actions

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete! Changes have been committed.

m2ux added 5 commits April 13, 2026 14:17
Made-with: Cursor
Signed-off-by: Mike Clay <mike.clay@shielded.io>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
Add missing ContractNotPresent variant to pallet Error<T> with codec
index 13 and its From<LedgerApiError> mapping. Add PartialEq derive
to error enums for test assertions. Apply rustfmt formatting.

Signed-off-by: Mike Clay <mike.clay@shielded.io>
The wire name is midnight_contractState (matching the #[method(name)]
attribute in the RPC trait), not midnight_getContractState.

Signed-off-by: Mike Clay <mike.clay@shielded.io>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete! Changes have been committed.

m2ux and others added 3 commits April 14, 2026 11:09
Merge main into fix/PM-19973-contract-not-present-error:
- types.rs: combine Debug (from main) with PartialEq (from this PR)
- metadata .scale files: take main's version (rebuild needed via /bot)

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux

m2ux commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

/bot rebuild-metadata

@github-actions

Copy link
Copy Markdown
Contributor

✅ Metadata rebuild complete! Changes have been committed.

m2ux and others added 3 commits April 17, 2026 16:33
Signed-off-by: Mike Clay <mike.clay@shielded.io>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
@gilescope gilescope added this pull request to the merge queue Apr 17, 2026
Merged via the queue into main with commit aedcd97 Apr 17, 2026
33 checks passed
@gilescope gilescope deleted the fix/PM-19973-contract-not-present-error branch April 17, 2026 16:50
@m2ux m2ux linked an issue Apr 21, 2026 that may be closed by this pull request
4 tasks
m2ux added a commit that referenced this pull request Apr 23, 2026
)

* chore: initialize PR for PM-19973

Made-with: Cursor

* fix: return ContractNotPresent error for missing contracts


* fix: add ContractNotPresent to pallet Error enum and apply rustfmt

Add missing ContractNotPresent variant to pallet Error<T> with codec
index 13 and its From<LedgerApiError> mapping. Add PartialEq derive
to error enums for test assertions. Apply rustfmt formatting.


* fix: correct RPC method name in E2E contract state helper

The wire name is midnight_contractState (matching the #[method(name)]
attribute in the RPC trait), not midnight_getContractState.


* chore: add change file for PM-19973


* fix: match actual RPC error message in E2E contract state test

The RPC layer maps ContractNotPresent to StateRpcError::UnableToGetContractState
which displays as "Unable to get requested contract state". Add this pattern
to the assertion.


* chore: rebuild metadata


* ci: trigger CI after metadata rebuild


* chore: rebuild metadata


* chore: add GitHub issue link to change file


* chore: rebuild metadata


* chore: rebuild metadata

* chore: rebuild metadata

* ci: re-trigger pipeline


* ci: re-trigger pipeline


---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
m2ux added a commit that referenced this pull request Apr 23, 2026
)

* chore: initialize PR for PM-19973

Made-with: Cursor

* fix: return ContractNotPresent error for missing contracts


* fix: add ContractNotPresent to pallet Error enum and apply rustfmt

Add missing ContractNotPresent variant to pallet Error<T> with codec
index 13 and its From<LedgerApiError> mapping. Add PartialEq derive
to error enums for test assertions. Apply rustfmt formatting.


* fix: correct RPC method name in E2E contract state helper

The wire name is midnight_contractState (matching the #[method(name)]
attribute in the RPC trait), not midnight_getContractState.


* chore: add change file for PM-19973


* fix: match actual RPC error message in E2E contract state test

The RPC layer maps ContractNotPresent to StateRpcError::UnableToGetContractState
which displays as "Unable to get requested contract state". Add this pattern
to the assertion.


* chore: rebuild metadata


* ci: trigger CI after metadata rebuild


* chore: rebuild metadata


* chore: add GitHub issue link to change file


* chore: rebuild metadata


* chore: rebuild metadata

* chore: rebuild metadata

* ci: re-trigger pipeline


* ci: re-trigger pipeline


---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
rsporny added a commit that referenced this pull request May 7, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

  Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
rsporny added a commit that referenced this pull request May 7, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

  Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
rsporny added a commit that referenced this pull request May 7, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

  Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
rsporny added a commit that referenced this pull request May 8, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  The third test generates DEPLOY_TX dynamically via the toolkit's
  in-process prover against the live chain rather than using the static
  .mn fixture: that fixture's intent_ttl is baked in at generation time
  and expires once chain time advances past
  genesis.tblock + global_ttl (14 days by default), so static fixtures
  can't carry the test on long-lived or live envs.

  Serialize deploy tests behind a Mutex so concurrent submissions of the
  same DEPLOY_TX don't race in the txpool, and add an
  E2E_SKIP_DEPLOY_GATE escape hatch so the test can be run standalone
  via `cargo test <name>` without the gate semaphore deadlocking on
  unrun pre-deploy tests.

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
rsporny added a commit that referenced this pull request May 8, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  The third test generates DEPLOY_TX dynamically via the toolkit's
  in-process prover against the live chain rather than using the static
  .mn fixture: that fixture's intent_ttl is baked in at generation time
  and expires once chain time advances past
  genesis.tblock + global_ttl (14 days by default), so static fixtures
  can't carry the test on long-lived or live envs.

  Serialize deploy tests behind a Mutex so concurrent submissions of the
  same DEPLOY_TX don't race in the txpool, and add an
  E2E_SKIP_DEPLOY_GATE escape hatch so the test can be run standalone
  via `cargo test <name>` without the gate semaphore deadlocking on
  unrun pre-deploy tests.

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
rsporny added a commit that referenced this pull request May 8, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  The third test generates DEPLOY_TX dynamically via the toolkit's
  in-process prover against the live chain rather than using the static
  .mn fixture: that fixture's intent_ttl is baked in at generation time
  and expires once chain time advances past
  genesis.tblock + global_ttl (14 days by default), so static fixtures
  can't carry the test on long-lived or live envs.

  Serialize deploy tests behind a Mutex so concurrent submissions of the
  same DEPLOY_TX don't race in the txpool, and add an
  E2E_SKIP_DEPLOY_GATE escape hatch so the test can be run standalone
  via `cargo test <name>` without the gate semaphore deadlocking on
  unrun pre-deploy tests.

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
scottbuckel pushed a commit that referenced this pull request May 8, 2026
PR #916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as #1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  The third test generates DEPLOY_TX dynamically via the toolkit's
  in-process prover against the live chain rather than using the static
  .mn fixture: that fixture's intent_ttl is baked in at generation time
  and expires once chain time advances past
  genesis.tblock + global_ttl (14 days by default), so static fixtures
  can't carry the test on long-lived or live envs.

  Serialize deploy tests behind a Mutex so concurrent submissions of the
  same DEPLOY_TX don't race in the txpool, and add an
  E2E_SKIP_DEPLOY_GATE escape hatch so the test can be run standalone
  via `cargo test <name>` without the gate semaphore deadlocking on
  unrun pre-deploy tests.

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

Closes: #1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
Signed-off-by: Scott Buckel <scott.buckel@shielded.io>
Minhcardanian pushed a commit to Minhcardanian/midnight-node that referenced this pull request May 9, 2026
)

PR midnightntwrk#916 added LedgerApiError::ContractNotPresent in the runtime, but the
  midnight_contractState RPC still collapsed every LedgerApiError into
the
  generic UnableToGetContractState — so callers could not distinguish
  "empty state" from "no such contract", which is why audit Issue AD was
  re-opened as midnightntwrk#1166.

  Add StateRpcError::ContractNotPresent and route the matching ledger
  error to it. All other variants still fall through to
  UnableToGetContractState, so this is a strict superset of prior
  behaviour. Re-export LedgerApiError from pallet-midnight so the RPC
  crate can match on it without a new dependency.

  Lock in the audit invariants with three e2e tests in tests/e2e:
  - ContractNotPresent for an undeployed (but well-formed) address
  - BadContractAddress and ContractNotPresent stay distinct paths
  - same address yields ContractNotPresent at block 1 and the deployed
    state at the post-deploy best block

  The third test generates DEPLOY_TX dynamically via the toolkit's
  in-process prover against the live chain rather than using the static
  .mn fixture: that fixture's intent_ttl is baked in at generation time
  and expires once chain time advances past
  genesis.tblock + global_ttl (14 days by default), so static fixtures
  can't carry the test on long-lived or live envs.

  Serialize deploy tests behind a Mutex so concurrent submissions of the
  same DEPLOY_TX don't race in the txpool, and add an
  E2E_SKIP_DEPLOY_GATE escape hatch so the test can be run standalone
  via `cargo test <name>` without the gate semaphore deadlocking on
  unrun pre-deploy tests.

  Bump NUM_PRE_DEPLOY_TESTS / NUM_DEPLOY_TESTS 2→3 and the Earthfile
  test-threads 4→6 so the new deploy gate doesn't deadlock.

Closes: midnightntwrk#1166

Signed-off-by: Radosław Sporny <404@rspo.dev>
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Jun 2, 2026
A deep review surfaced 15 findings spanning correctness, DoS surface,
error semantics, and OpenRPC drift. Fix all of them in the smallest way
that preserves the wire shape.

Per-query failures stay per-query: PathTooDeep and the new KeyTooLarge
(MAX_KEY_BYTES = 4096, defending against multi-MB-per-key requests)
surface in RpcStateQueryResult.error rather than aborting the batch.

Bridge errors are no longer collapsed at the RPC layer.
LedgerApiError::ContractNotPresent forwards to
StateRpcError::ContractNotPresent, matching midnight_contractState. A
new RuntimeApiUnsupported variant distinguishes "this block's runtime
predates v6" from genuine state-read failures. Caller-side variants
map to INVALID_PARAMS_CODE; internal failures map to
INTERNAL_ERROR_CODE so retry layers behave correctly.

Path navigation:
- Array index widened u8 -> u64 with range check; the docstring says
  "serialized AlignedValue" without constraining the width.
- 1u64 << tree.height() guarded with checked_shl; heights >= 64 no
  longer overflow (panic in debug, silent wrap in release).
- tree.index(pos) wrapped with catch_unwind so a Collapsed sub-tree
  surfaces as a per-query error instead of panicking the RPC worker.
- Deserializable::deserialize call sites verify the slice is fully
  consumed; trailing bytes in a StorageKey were silently dropped.

Lazy-access contract is now honoured:
- Empty paths rejected.
- Leaf guard extended to also reject Array (was only rejecting Map and
  BoundedMerkleTree), so a shallow path can no longer serialize the
  entire contract state.

Map misses and BMT empty leaves return a serialized StateValue::Null
to match VM idx semantics; clients no longer have to disambiguate
"missing" from "server bug" via an out-of-band convention.

Bridge: NoLedgerState was being returned for a missing contract — the
TODO referencing PR midnightntwrk#916 was stale; LedgerApiError::ContractNotPresent
exists and is the correct variant.

OpenRPC: midnight_queryContractState was missing from
CUSTOM_METHOD_NAMES and build_custom_method; the OpenRPC doc and
rpc.discover silently omitted it. Added method entry, manual schemas
for RpcStateQuery / RpcStateQueryResult, bumped drift-detection count
16 -> 17, regenerated docs/openrpc.json.

spec_version bumped 001_000_000 -> 001_001_000 so forkless upgrades
pick up the runtime API change.

E2E tests: the new query tests now use submit_expecting_success (waits
for in-block inclusion) instead of swallowing the txpool status; the
nonexistent-contract test asserts "Contract not present" and uses a
32-byte address so it remains valid under the new trailing-bytes
check.

Signed-off-by: Rodrigo Quelhas <rodrigo_quelhas@outlook.pt>
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Jun 2, 2026
A deep review surfaced bugs in the new RPC's path navigation and error
mapping. Fix only the bugs in code introduced by this PR; OpenRPC
drift, runtime spec_version bump, and the cross-method error-code
split are deferred to their own PRs.

Path navigation (resolve_state_path):
- Array index widened u8 -> u64 with usize range check; the docstring
  says "serialized AlignedValue" without constraining the width.
- 1u64 << tree.height() guarded with checked_shl; heights >= 64 no
  longer overflow (panic in debug, silent wrap in release).
- tree.index(pos) wrapped with catch_unwind so a Collapsed sub-tree
  surfaces as a per-query error instead of panicking the RPC worker.
- Deserializable::deserialize call sites verify the slice is fully
  consumed; trailing bytes in a StorageKey were silently dropped.
- Empty paths rejected; the leaf guard now also rejects Array (was
  only rejecting Map and BoundedMerkleTree), so a shallow path can no
  longer serialize the entire contract state.
- Map misses and BMT empty leaves return a serialized StateValue::Null
  matching VM `idx` semantics; clients no longer have to disambiguate
  "missing" from "server bug" via an out-of-band convention.

Per-query failures (RPC server):
- PathTooDeep and the new inline "path key too large" check
  (MAX_KEY_BYTES = 4096, defending against multi-MB-per-key requests)
  surface in RpcStateQueryResult.error rather than aborting the batch.
- LedgerApiError::ContractNotPresent forwards to
  StateRpcError::ContractNotPresent at the RPC layer, matching
  midnight_contractState.

Bridge (Bridge::query_contract_state):
- Returns LedgerApiError::ContractNotPresent on a missing contract
  instead of NoLedgerState; the TODO referencing PR midnightntwrk#916 was stale,
  the variant already exists.

E2E tests:
- The new query tests now use submit_expecting_success (waits for
  in-block inclusion) instead of swallowing the txpool status.
- The nonexistent-contract test asserts "Contract not present" and
  uses a 32-byte address so it remains valid under the new
  trailing-bytes check.

Signed-off-by: Rodrigo Quelhas <rodrigo_quelhas@outlook.pt>
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Jun 3, 2026
A deep review surfaced bugs in the new RPC's path navigation and error
mapping. Fix the bugs in code introduced by this PR; runtime
spec_version bump and the cross-method error-code split (both touch
behavior outside this PR's scope) are deferred to their own PRs.

Path navigation (resolve_state_path):
- Array index widened u8 -> u64 with usize range check; the docstring
  says "serialized AlignedValue" without constraining the width.
- 1u64 << tree.height() guarded with checked_shl; heights >= 64 no
  longer overflow (panic in debug, silent wrap in release).
- tree.index(pos) wrapped with catch_unwind so a Collapsed sub-tree
  surfaces as a per-query error instead of panicking the RPC worker.
- Deserializable::deserialize call sites verify the slice is fully
  consumed; trailing bytes in a StorageKey were silently dropped.
- Empty paths rejected; the leaf guard now also rejects Array (was
  only rejecting Map and BoundedMerkleTree), so a shallow path can no
  longer serialize the entire contract state.
- Map misses and BMT empty leaves return a serialized StateValue::Null
  matching VM `idx` semantics; clients no longer have to disambiguate
  "missing" from "server bug" via an out-of-band convention.

Per-query failures (RPC server):
- PathTooDeep and the new inline "path key too large" check
  (MAX_KEY_BYTES = 4096, defending against multi-MB-per-key requests)
  surface in RpcStateQueryResult.error rather than aborting the batch.
- LedgerApiError::ContractNotPresent forwards to
  StateRpcError::ContractNotPresent at the RPC layer, matching
  midnight_contractState.

Bridge (Bridge::query_contract_state):
- Returns LedgerApiError::ContractNotPresent on a missing contract
  instead of NoLedgerState; the TODO referencing PR midnightntwrk#916 was stale,
  the variant already exists.

OpenRPC:
- midnight_queryContractState was missing from CUSTOM_METHOD_NAMES and
  build_custom_method; the OpenRPC doc and rpc.discover silently
  omitted it. Added method entry, manual schemas for RpcStateQuery /
  RpcStateQueryResult, bumped drift-detection count 16 -> 17,
  regenerated docs/openrpc.json.

E2E tests:
- The new query tests now use submit_expecting_success (waits for
  in-block inclusion) instead of swallowing the txpool status.
- The nonexistent-contract test asserts "Contract not present" and
  uses a 32-byte address so it remains valid under the new
  trailing-bytes check.

Signed-off-by: Rodrigo Quelhas <rodrigo_quelhas@outlook.pt>
RomarQ added a commit to RomarQ/midnight-node that referenced this pull request Jun 3, 2026
A deep review surfaced bugs in the new RPC's path navigation and error
mapping. Fix the bugs in code introduced by this PR; runtime
spec_version bump and the cross-method error-code split (both touch
behavior outside this PR's scope) are deferred to their own PRs.

Path navigation (resolve_state_path):
- Array index widened u8 -> u64 with usize range check; the docstring
  says "serialized AlignedValue" without constraining the width.
- 1u64 << tree.height() guarded with checked_shl; heights >= 64 no
  longer overflow (panic in debug, silent wrap in release).
- tree.index(pos) wrapped with catch_unwind so a Collapsed sub-tree
  surfaces as a per-query error instead of panicking the RPC worker.
- Deserializable::deserialize call sites verify the slice is fully
  consumed; trailing bytes in a StorageKey were silently dropped.
- Empty paths rejected; the leaf guard now also rejects Array (was
  only rejecting Map and BoundedMerkleTree), so a shallow path can no
  longer serialize the entire contract state.
- Map misses and BMT empty leaves return a serialized StateValue::Null
  matching VM `idx` semantics; clients no longer have to disambiguate
  "missing" from "server bug" via an out-of-band convention.

Per-query failures (RPC server):
- PathTooDeep and the new inline "path key too large" check
  (MAX_KEY_BYTES = 4096, defending against multi-MB-per-key requests)
  surface in RpcStateQueryResult.error rather than aborting the batch.
- LedgerApiError::ContractNotPresent forwards to
  StateRpcError::ContractNotPresent at the RPC layer, matching
  midnight_contractState.

Bridge (Bridge::query_contract_state):
- Returns LedgerApiError::ContractNotPresent on a missing contract
  instead of NoLedgerState; the TODO referencing PR midnightntwrk#916 was stale,
  the variant already exists.

OpenRPC:
- midnight_queryContractState was missing from CUSTOM_METHOD_NAMES and
  build_custom_method; the OpenRPC doc and rpc.discover silently
  omitted it. Added method entry, manual schemas for RpcStateQuery /
  RpcStateQueryResult, bumped drift-detection count 16 -> 17,
  regenerated docs/openrpc.json.

E2E tests:
- The new query tests now use submit_expecting_success (waits for
  in-block inclusion) instead of swallowing the txpool status.
- The nonexistent-contract test asserts "Contract not present" and
  uses a 32-byte address so it remains valid under the new
  trailing-bytes check.

Signed-off-by: Rodrigo Quelhas <rodrigo_quelhas@outlook.pt>
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.

[Node] Issue AD: Return ContractNotPresent Instead of Default State

3 participants