Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
87c77a9
test(contract): sandbox coverage + stub verifier for async attestation
pbeza Jun 30, 2026
fa42980
test(contract): unit-cover VerifierNotConfigured and revert_dstack_store
pbeza Jul 1, 2026
f098b5c
test(contract): cover OOG inside resolve_verification
pbeza Jul 2, 2026
b516d68
test(contract): address review on async attestation tests
pbeza Jul 2, 2026
9c2963b
test(contract): address self-review findings on async attestation tests
pbeza Jul 2, 2026
7ec5ecf
test(contract): fix TODO-format lint in ignored-test reason
pbeza Jul 3, 2026
6f37e23
docs(test-tee-verifier): use intra-doc links instead of bare backticks
pbeza Jul 3, 2026
e86d986
test(contract): share StubResponse via a types crate, drop the mirror
pbeza Jul 3, 2026
5d54a7e
test(contract): tighten async attestation tests
pbeza Jul 3, 2026
3eb5ec7
test(contract): port async attestation tests to the no-yield design
pbeza Jul 13, 2026
c7c141a
docs: add trailing newline to attestation-verifier-contract.md
pbeza Jul 13, 2026
dd57e03
test(contract): address pre-review findings on async attestation tests
pbeza Jul 13, 2026
f1a16be
test(contract): de-duplicate async attestation tests
pbeza Jul 14, 2026
5ff268a
test(contract): cover post-DCAP-fail, dstack store rejection, and dep…
pbeza Jul 14, 2026
947d7cc
test(contract): tighten attestation test assertions
pbeza Jul 15, 2026
6a98267
docs(contract): tighten async attestation test comments
pbeza Jul 15, 2026
2bd43f9
test(contract): drop doc changes, split to a stacked PR
pbeza Jul 15, 2026
aa1906f
test(contract): drop the OOG deposit-refund assertion
pbeza Jul 15, 2026
f582894
test(contract): reconcile async attestation tests with flat-fee parent
pbeza Jul 16, 2026
c521d0d
test(contract): drop tests-only PartialEq/Eq from NodeAttestation
pbeza Jul 17, 2026
6014d11
test(contract): trim redundant Given/When/Then comment explanations
pbeza Jul 17, 2026
198ed04
test(contract): drop unused PartialEq/Eq from VerifiedAttestation and…
pbeza Jul 17, 2026
63f634c
test(contract): drop redundant total_gas_fee doc comment
pbeza Jul 17, 2026
a0e276b
test(contract): drop stub tee-verifier, drive real verifier + in-proc…
pbeza Jul 17, 2026
fe68c93
test(contract): dedup attestation test setup via shared helpers
pbeza Jul 17, 2026
b21cc1e
test(contract): assert the flat fee is consumed net of gas
pbeza Jul 17, 2026
df67ec5
Merge remote-tracking branch 'origin/3642-async-attestation-core' int…
pbeza Jul 22, 2026
de64407
Merge remote-tracking branch 'origin/3642-async-attestation-core' int…
pbeza Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions crates/contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2846,6 +2846,13 @@ mod tests {
use rstest::rstest;
use sha2::{Digest, Sha256};

use crate::tee::{
test_utils::whitelist_dstack_measurements, verification_context::VerificationContext,
};
use test_utils::attestation::{
VALID_ATTESTATION_TIMESTAMP, account_key, image_digest, launcher_image_hash,
mock_dstack_attestation_inner, p2p_tls_key, verified_report,
};
use test_utils::contract_types::dummy_config;
use threshold_signatures::confidential_key_derivation as ckd;
use threshold_signatures::frost_core::Group as _;
Expand Down Expand Up @@ -4542,6 +4549,76 @@ mod tests {
.expect("Expected panic if predecessor != signer");
}

fn dstack_verification_setup() -> (MpcContract, VerificationContext) {
let (_, mut contract, _) = basic_setup(Curve::Edwards25519, &mut OsRng);
let contract_account_id = env::current_account_id();
let context = VMContextBuilder::new()
.current_account_id(contract_account_id.clone())
.predecessor_account_id(contract_account_id)
.attached_deposit(MINIMUM_ATTESTATION_STORAGE_DEPOSIT)
.block_timestamp(VALID_ATTESTATION_TIMESTAMP * 1_000_000_000)
.build();
testing_env!(context);

contract.tee_state = TeeState::default();
whitelist_dstack_measurements(
&mut contract.tee_state,
image_digest(),
launcher_image_hash(),
);

let node_id = NodeId {
account_id: "alice.near".parse().unwrap(),
tls_public_key: Ed25519PublicKey(p2p_tls_key()),
account_public_key: Ed25519PublicKey(account_key()),
};
let attestation = mock_dstack_attestation_inner();
(
contract,
VerificationContext {
node_id,
attestation,
},
)
}

#[test]
fn resolve_verification__should_store_on_verified_verdict() {
// Given
let (mut contract, context) = dstack_verification_setup();
let node_id = context.node_id.clone();

// When
let result = contract
.resolve_verification(context, Ok(VerificationResult::Verified(verified_report())));

// Then
// assert_matches! requires Debug, which PromiseOrValue doesn't implement
assert!(matches!(result, PromiseOrValue::Value(())));
assert_eq!(contract.tee_state.stored_attestations.len(), 1);
let stored = contract
.tee_state
.stored_attestations
.get(&node_id.tls_public_key)
.expect("attestation must be stored");
assert_eq!(stored.node_id, node_id);
}

#[test]
fn resolve_verification__should_return_fail_promise_and_store_nothing_on_verifier_unavailable()
{
// Given
let (mut contract, context) = dstack_verification_setup();

// When
let result = contract.resolve_verification(context, Err(PromiseError::Failed));

// Then
// assert_matches! requires Debug, which PromiseOrValue doesn't implement
assert!(matches!(result, PromiseOrValue::Promise(_)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we generally prefer assert_matches

assert!(contract.tee_state.stored_attestations.is_empty());
}

#[test]
#[should_panic(expected = "Caller must be an attested participant")]
fn test_attested_but_not_participant_panics() {
Expand Down
57 changes: 56 additions & 1 deletion crates/contract/src/tee/tee_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,14 +579,18 @@ mod tests {
authenticate_as, bogus_ed25519_near_public_key, bogus_ed25519_public_key, create_node_id,
gen_participant, gen_participants, node_id_for,
};
use crate::tee::test_utils::set_block_timestamp;
use crate::tee::test_utils::{set_block_timestamp, whitelist_dstack_measurements};
use assert_matches::assert_matches;
use mpc_attestation::attestation::MockAttestation;
use mpc_primitives::hash::{LauncherImageHash, NodeImageHash};
use near_account_id::AccountId;
use near_sdk::test_utils::VMContextBuilder;
use near_sdk::testing_env;
use std::time::Duration;
use test_utils::attestation::{
VALID_ATTESTATION_TIMESTAMP, account_key, image_digest, launcher_image_hash,
mock_dstack_attestation_inner, p2p_tls_key, verified_report,
};

/// Helper to set up the testing environment with a specific signer
fn set_signer(account_id: &AccountId, public_key: &near_sdk::PublicKey) {
Expand Down Expand Up @@ -1412,6 +1416,57 @@ mod tests {
)
}

#[test]
fn verify_and_store_dstack__should_reject_and_store_nothing_when_post_dcap_checks_fail() {
// Given
let mut tee_state = TeeState::default();
let dstack = mock_dstack_attestation_inner();
let node_id = node_id_for(&"alice.near".parse().unwrap());

// When
let result =
tee_state.verify_and_store_dstack(node_id, &dstack, &verified_report(), Duration::MAX);

// Then
assert_matches!(
result,
Err(AttestationSubmissionError::InvalidAttestation(_))
);
assert!(tee_state.stored_attestations.is_empty());
Comment thread
pbeza marked this conversation as resolved.
}

#[test]
fn verify_and_store_dstack__should_store_when_all_post_dcap_checks_pass() {
// Given
set_block_timestamp(VALID_ATTESTATION_TIMESTAMP * 1_000_000_000);
let mut tee_state = TeeState::default();
assert_eq!(tee_state.stored_attestations.len(), 0);
whitelist_dstack_measurements(&mut tee_state, image_digest(), launcher_image_hash());
let node_id = NodeId {
account_id: "alice.near".parse().unwrap(),
tls_public_key: Ed25519PublicKey(p2p_tls_key()),
account_public_key: Ed25519PublicKey(account_key()),
};
let dstack = mock_dstack_attestation_inner();

// When
let result = tee_state.verify_and_store_dstack(
node_id.clone(),
&dstack,
&verified_report(),
Duration::MAX,
);

// Then
assert_matches!(result, Ok(ParticipantInsertion::NewlyInsertedParticipant));
assert_eq!(tee_state.stored_attestations.len(), 1);
let stored = tee_state
.stored_attestations
.get(&node_id.tls_public_key)
.expect("attestation must be stored");
assert_eq!(stored.node_id, node_id);
}

/// Stale CodeHashesVotes entries from removed participants must not count toward
/// quorum after resharing.
///
Expand Down
19 changes: 17 additions & 2 deletions crates/contract/src/tee/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
//! attestation behavior, and general contract state management.

use crate::primitives::test_utils::{gen_account_id, gen_seed};
use crate::tee::{measurements::ContractExpectedMeasurements, tee_state::TeeState};
use mpc_attestation::attestation::default_measurements;
use mpc_primitives::hash::{LauncherImageHash, NodeImageHash};
use near_account_id::AccountId;
use near_sdk::test_utils::VMContextBuilder;
use near_sdk::{BlockHeight, PublicKey, testing_env};
use near_sdk::{BlockHeight, PublicKey, test_utils::VMContextBuilder, testing_env};
use rand::Rng;
use std::time::Duration;

/// Test environment for managing VM context state.
///
Expand Down Expand Up @@ -93,3 +96,15 @@ pub fn set_block_timestamp(timestamp_nanos: u64) {
.build()
);
}

pub fn whitelist_dstack_measurements(
tee_state: &mut TeeState,
image: NodeImageHash,
launcher: LauncherImageHash,
) {
tee_state.whitelist_tee_proposal(image, Duration::MAX);
tee_state.add_launcher_image(launcher, Duration::MAX);
for &measurements in default_measurements() {
tee_state.add_measurement(ContractExpectedMeasurements::from(measurements));
}
}
Loading
Loading