fix: [PM-19973] Return ContractNotPresent instead of default state#916
Merged
Conversation
Contributor
Contributor
Author
|
/bot rebuild-metadata |
Contributor
|
✅ Metadata rebuild complete! Changes have been committed. |
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).
Contributor
Author
|
/bot rebuild-metadata |
Contributor
|
✅ Metadata rebuild complete! Changes have been committed. |
gilescope
approved these changes
Apr 6, 2026
Contributor
|
/bot rebuild-metadata |
Contributor
|
✅ Metadata rebuild complete. No changes detected. |
ozgb
approved these changes
Apr 10, 2026
4 tasks
Contributor
Author
|
/bot rebuild-metadata |
38913ee to
c2b09c5
Compare
Contributor
|
❌ Metadata rebuild failed. Check the workflow logs for details. |
Contributor
Author
|
/bot rebuild-metadata |
Contributor
|
✅ Metadata rebuild complete! Changes have been committed. |
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>
Contributor
|
✅ Metadata rebuild complete! Changes have been committed. |
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>
Contributor
Author
|
/bot rebuild-metadata |
Contributor
|
✅ Metadata rebuild complete! Changes have been committed. |
Signed-off-by: Mike Clay <mike.clay@shielded.io>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
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>
15 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.








Summary
Return an explicit
ContractNotPresenterror 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
ContractNotPresentvariant toLedgerApiErrorwith error code 156,Displayimpl, andFrom<LedgerApiError> for u8mapping. AddPartialEqderives to error enum hierarchy (required for nested error assertions).do_get_contract_stateto returnErr(LedgerApiError::ContractNotPresent)instead ofOk(Vec::new())when a contract is not found in the ledger.ContractNotPresentvariant to palletError<T>with#[codec(index = 13)]and itsFrom<LedgerApiError>mapping.test_get_contract_state_not_presentverifying the error is returned for an undeployed contract address.get_contract_state_returns_error_if_not_presentverifying the RPC endpoint returns the correct error. Fix incorrect RPC method name in E2E client helper (midnight_contractStateinstead ofmidnight_getContractState).📌 Submission Checklist
🔱 Fork Strategy
🗹 TODO before merging