test(contract): sandbox coverage + stub verifier for async attestation#3715
test(contract): sandbox coverage + stub verifier for async attestation#3715pbeza wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds deterministic sandbox-test infrastructure for the async TEE attestation verifier-contract flow by introducing a stub tee-verifier contract, new sandbox helpers, and targeted sandbox tests; also refreshes related docs/design notes to reflect updated error messages and verifier configuration behavior.
Changes:
- Introduces a new
test-tee-verifierstub contract that returns a test-chosenverify_quoteoutcome (Verified / Rejected / Panic). - Adds sandbox utilities + tests covering key
submit_participant_infoasync-attestation branches (verifier not configured, verifier rejection w/ refund, yield-timeout cleanup). - Updates the verifier-contract design doc and refreshes TDX/localnet documentation examples for the updated error wording.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/running-an-mpc-node-in-tdx-external-guide.md | Updates the example on-chain attestation failure message wording. |
| docs/localnet/tee-localnet.md | Updates localnet example error output to match the new attestation verification failure wording. |
| docs/design/attestation-verifier-contract.md | Refreshes design doc to reflect Option verifier account and the async/yield flow return shape. |
| crates/test-tee-verifier/src/lib.rs | Adds a test-only stub verifier contract implementing verify_quote with canned outcomes. |
| crates/test-tee-verifier/Cargo.toml | Declares the stub contract crate and its ABI/schema feature wiring. |
| crates/contract/tests/sandbox/utils/mpc_contract.rs | Adds sandbox helpers for deposits, pending-attestation checks, and verifier-vote wiring. |
| crates/contract/tests/sandbox/utils/contract_build.rs | Adds build helper for the stub verifier WASM artifact. |
| crates/contract/tests/sandbox/tee_verifier.rs | Adds sandbox tests that deterministically exercise async attestation resolution branches. |
| crates/contract/tests/sandbox/mod.rs | Registers the new tee_verifier sandbox test module. |
| crates/contract/src/sandbox_test_methods.rs | Adds a sandbox-only view method has_pending_attestation. |
| Cargo.toml | Adds crates/test-tee-verifier to the workspace members. |
| Cargo.lock | Locks the new workspace crate dependency graph entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| ``` | ||
| (ExecutionError("Smart contract panicked: Invalid TEE Remote Attestation.: TeeQuoteStatus is invalid: the allowed mpc image hashes list is empty" | ||
| (ExecutionError("Smart contract panicked: Invalid TEE Remote Attestation.: attestation verification failed: the allowed mpc image hashes list is empty" |
| pub fn has_pending_attestation(&self, account_id: AccountId) -> bool { | ||
| self.pending_attestations.contains_key(&account_id) | ||
| } | ||
| } |
|
Pull request overview Adds sandbox test coverage for the new async Changes:
Findings Blocking (must fix before merge):
Non-blocking (nits, follow-ups, suggestions):
|
0e60532 to
47655a4
Compare
|
PR title type suggestion: This PR includes changes to source code files (crates/contract/src/tee_state.rs, crates/contract/src/sandbox_test_methods.rs), not just tests. The type should likely be |
Adds the test-tee-verifier stub contract (a wire-compatible verify_quote that returns a test-chosen response instead of running dcap-qvl) and sandbox tests exercising the async submit_participant_info branches: verifier-not-configured, Rejected, and no-verdict (yield timeout). Also adds the has_pending_attestation sandbox view and the design doc for the verifier-contract flow. Builds on the async-verification feature PR.
Adds in-process coverage the reviewer asked for on the async attestation flow: - submit_participant_info rejects a Dstack submission with VerifierNotConfigured when no verifier is voted in (fails before the yield) - TeeState::revert_dstack_store restores the displaced entry on an update and removes a newly-inserted one The VerificationAlreadyPending / pending-insert and InsufficientDeposit invariants aren't unit-testable: near_sdk's mock VM does not support promise_yield_create and does not simulate storage_usage() deltas. Those paths are exercised by the sandbox tee_verifier tests.
Adds a sandbox test that configures resolve_verification_tera_gas far below what the post-DCAP work needs, so the callback receipt runs out of gas and rolls back atomically without resuming the yield. Asserts the ~200-block timeout branch of on_attestation_verified still cleans up the pending entry and refunds the deposit, guarding the invariant that a partial resolve_verification receipt cannot leave a refunded-but-still-pending entry that wedges the account
98a7a50 to
d3ecae2
Compare
|
PR title type suggestion: This PR adds source code (a new test utility crate for the stub verifier and modifies contract source files), not just tests. The type prefix should be Suggested title: |
- assert_deposit_refunded: use raw subtraction instead of saturating_sub so an over-refund (balance_after > balance_before) panics rather than clamping to 0 and silently passing (CLAUDE.md forbids saturating arithmetic in tests) - document the has_pending_attestation sandbox view, matching its siblings - fix a stray period in the example panic string in tee-localnet.md (Invalid TEE Remote Attestation: ..., no period before the colon)
Self-review (adversarially verified) surfaced 11 issues; applied the mechanical + correctness ones: - init_config was silently dropped on the init_running setup path, so the OOG test ran with default gas and re-tested the rejection branch. Plumb init_config through init_contract_running. - assert_deposit_refunded: bound to the gas envelope (~50 mNEAR) so a partial refund fails, instead of the loose < 1 NEAR check. - not-configured test asserts the specific VerifierNotConfigured error rather than only is_failure(). - StubResponse: pin Borsh discriminants in a unit test + keep-in-sync comments on both mirror declarations. - rename revert_dstack_store tests to the __should_ form; drop em dashes. - docs: correct the submission-path panic strings (no "Invalid TEE Remote Attestation" prefix), request_verify_foreign_tx -> verify_foreign_transaction, resolve_verification sample param, and drop the non-existent DstackAttestation::dcap_report reference. - deny.toml: ignore RUSTSEC-2026-0194/0195 (transitive quick-xml, fixed in >=0.41.0) to unblock cargo-deny. Add a verified_report() test-utils fixture (mints the real report via verify_dcap_quote) toward the deferred Verified-path coverage. The OOG test is #[ignore]d with TODO(#3730): forcing a real OOG needs the allowlist populated so execution reaches the gas-heavy RTMR3 replay; that governance setup is shared with the pending happy-path test.
|
PR title type suggestion: This PR adds async attestation verification functionality to the contract (substantial changes to Suggested title: |
|
PR title type suggestion: Since this PR modifies source files under Suggested title: |
Convert linkable code references in the stub's doc comments to proper
[`...`] intra-doc links (TestTeeVerifier::verify_quote, the StubResponse
variants, VerificationResult::{Verified,Rejected}). Leave unlinkable
references as prose: cross-crate non-deps (dcap_qvl, the real tee-verifier
method), the mpc-contract crate name, and refs inside plain // comments
that can't host intra-doc links.
StubResponse was declared twice (stub contract + test mirror) kept aligned by a KEEP-IN-SYNC comment and a discriminant-pinning test, because importing the #[near] stub crate as a dep breaks cargo test --all-features (duplicate contract-ABI symbol + abi-feature unification; see PR #3664). Extract it into a new plain-lib crate test-tee-verifier-types that both the stub and the contract's test binary depend on. A non-#[near] crate emits no contract-ABI symbol, so it can be a dev-dep of mpc-contract without the collision, giving a real single source of truth: the mirror, the sync comment, and the stub_response_discriminants test are removed. Verified in nix: cargo test --no-run --all-features -p mpc-contract links cleanly (the step that regressed in #3664), and the sandbox tee_verifier suite passes driven through the shared type.
- revert_dstack_store unit tests: reuse create_node_id, compare the whole NodeAttestation (add PartialEq/Eq to NodeAttestation, VerifiedAttestation, MockAttestation, ValidatedDstackAttestation) instead of a field + a _ match - add a verified_report() test-utils fixture (mints the real report via verify_dcap_quote) and rework the OOG test onto the Verified path; it stays #[ignore]d pending the allowlist fixture setup (TODO(#3730)) - compress the sandbox tee_verifier tests: submit_dstack + setup_with_stub + assert_submission_cleaned_up helpers remove the repeated preamble/asserts; inline the trivial dstack_attestation()/tls_key() wrappers - switch the sandbox tests to unwrap() style (repo-wide majority) instead of Result<()>/? - assert the exact VerifierNotConfigured message via the error Display - gate/name has_pending_attestation via method_names::HAS_PENDING_ATTESTATION - extract TEST_COLLATERAL_STRING / SUBMIT_DEPOSIT consts; doc + intra-doc-link cleanups
|
PR title type suggestion: Since this PR modifies production source code (crates/contract/src/tee/tee_state.rs, crates/mpc-attestation/src/attestation.rs, etc.), the type should probably be |
Second half of the #3664 split, stacked on #3714 (the async-attestation feature). Pure test infrastructure and docs, with zero production-code impact.
What changes
test-tee-verifierstub contract: a wire-compatibleverify_quotethat returns a test-chosen response (Verified / Rejected / Panic) instead of running realdcap-qvl, so sandbox tests can drive every branch of the async flow deterministically.submit_participant_info's Dstack branches: verifier-not-configured, verifier rejection (refund + nothing stored), no-verdict (the ~200-block yield timeout cleans up), and an out-of-gasresolve_verification(its receipt rolls back atomically and the same timeout cleans up, guarding against a refunded-but-still-pending entry that would wedge the account).has_pending_attestationsandbox view and the supporting test helpers.