Skip to content

Commit 98a7a50

Browse files
committed
test(contract): unit-cover VerifierNotConfigured and revert_dstack_store
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.
1 parent 47655a4 commit 98a7a50

2 files changed

Lines changed: 90 additions & 1 deletion

File tree

crates/contract/src/tee/tee_state.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,75 @@ mod tests {
15011501
assert_eq!(stored.node_id, rotated_node);
15021502
}
15031503

1504+
#[test]
1505+
fn revert_dstack_store__restores_the_displaced_entry_on_update() {
1506+
// Given: `alice` has an attestation, then updates it — the second insertion
1507+
// returns the displaced original wrapped in `UpdatedExistingParticipant`.
1508+
const TEE_UPGRADE_DURATION: Duration = Duration::from_secs(10_000);
1509+
let mut tee_state = TeeState::default();
1510+
let tls_public_key = bogus_ed25519_public_key();
1511+
let original_node = NodeId {
1512+
account_id: "alice.near".parse().unwrap(),
1513+
tls_public_key: tls_public_key.clone(),
1514+
account_public_key: bogus_ed25519_public_key(),
1515+
};
1516+
tee_state
1517+
.add_mock_participant(
1518+
original_node.clone(),
1519+
MockAttestation::Valid,
1520+
TEE_UPGRADE_DURATION,
1521+
)
1522+
.expect("initial insertion should succeed");
1523+
let updated_node = NodeId {
1524+
account_id: "alice.near".parse().unwrap(),
1525+
tls_public_key: tls_public_key.clone(),
1526+
account_public_key: bogus_ed25519_public_key(),
1527+
};
1528+
let insertion = tee_state
1529+
.add_mock_participant(updated_node, MockAttestation::Valid, TEE_UPGRADE_DURATION)
1530+
.expect("update should succeed");
1531+
assert_matches!(
1532+
insertion,
1533+
ParticipantInsertion::UpdatedExistingParticipant(_)
1534+
);
1535+
1536+
// When: the store is reverted.
1537+
tee_state.revert_dstack_store(&tls_public_key, insertion);
1538+
1539+
// Then: the original (displaced) entry is back in place.
1540+
let stored = tee_state
1541+
.stored_attestations
1542+
.get(&tls_public_key)
1543+
.expect("original entry must be restored");
1544+
assert_eq!(stored.node_id, original_node);
1545+
}
1546+
1547+
#[test]
1548+
fn revert_dstack_store__removes_the_newly_inserted_entry() {
1549+
// Given: a brand-new attestation for `alice` (no prior entry displaced).
1550+
const TEE_UPGRADE_DURATION: Duration = Duration::from_secs(10_000);
1551+
let mut tee_state = TeeState::default();
1552+
let tls_public_key = bogus_ed25519_public_key();
1553+
let node = NodeId {
1554+
account_id: "alice.near".parse().unwrap(),
1555+
tls_public_key: tls_public_key.clone(),
1556+
account_public_key: bogus_ed25519_public_key(),
1557+
};
1558+
let insertion = tee_state
1559+
.add_mock_participant(node, MockAttestation::Valid, TEE_UPGRADE_DURATION)
1560+
.expect("insertion should succeed");
1561+
assert_matches!(insertion, ParticipantInsertion::NewlyInsertedParticipant);
1562+
1563+
// When: the store is reverted.
1564+
tee_state.revert_dstack_store(&tls_public_key, insertion);
1565+
1566+
// Then: the entry is gone.
1567+
assert!(
1568+
tee_state.stored_attestations.get(&tls_public_key).is_none(),
1569+
"newly inserted entry must be removed on revert"
1570+
);
1571+
}
1572+
15041573
#[test]
15051574
fn add_participant_rejects_invalid_attestations() {
15061575
let mut tee_state = TeeState::default();

crates/contract/tests/inprocess/attestation_submission.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use mpc_contract::{
44
MpcContract,
55
crypto_shared::types::PublicKeyExtended,
6-
errors::Error,
6+
errors::{Error, TeeError},
77
primitives::{
88
key_state::{AttemptId, EpochId, KeyForDomain, Keyset},
99
participants::{ParticipantId, ParticipantInfo},
@@ -24,6 +24,7 @@ use near_account_id::AccountId;
2424
use near_sdk::{NearToken, VMContext, test_utils::VMContextBuilder, testing_env};
2525
use rstest::rstest;
2626
use std::{str::FromStr, time::Duration};
27+
use test_utils::attestation::mock_dto_dstack_attestation;
2728

2829
const SECOND: Duration = Duration::from_secs(1);
2930
const NANOS_IN_SECOND: u64 = SECOND.as_nanos() as u64;
@@ -353,6 +354,25 @@ fn submit_participant_info__should_reject_overwrite_from_other_account() {
353354
assert_eq!(stored_before, stored_after);
354355
}
355356

357+
/// **Test that a `Dstack` submission is rejected when no verifier is configured.** The
358+
/// async path has nowhere to offload DCAP verification, so it must fail up front (before
359+
/// registering a yield) rather than leave a submission that can never resolve.
360+
#[test]
361+
fn submit_participant_info__should_reject_dstack_when_verifier_not_configured() {
362+
// Given: a running contract with no TEE verifier voted in.
363+
let mut setup = TestSetupBuilder::new().build();
364+
let node = setup.get_participant_node_ids()[0].clone();
365+
366+
// When: that participant submits a Dstack attestation.
367+
let result = setup.try_submit_attestation_for_node(&node, mock_dto_dstack_attestation());
368+
369+
// Then: it is rejected with `VerifierNotConfigured`.
370+
assert_matches!(
371+
&result,
372+
Err(Error::TeeError(TeeError::VerifierNotConfigured))
373+
);
374+
}
375+
356376
/// **Test that `clean_tee_status()` is vote-only** — attestations for non-participants
357377
/// remain in `stored_attestations` after the call. Attestation pruning is handled by the
358378
/// separate `clean_invalid_attestations` endpoint.

0 commit comments

Comments
 (0)