Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
29dc047
feat(contract): async TEE attestation verification, drop dcap-qvl
pbeza Jun 30, 2026
16ce226
fix(contract): address review on async attestation flow
pbeza Jul 1, 2026
68837a1
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 1, 2026
43e3be6
refactor(contract): clarify async attestation names and comments
pbeza Jul 1, 2026
79dc9fe
docs(contract): fix migrate doc link in v3_13_0_state
pbeza Jul 1, 2026
cde4a47
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 1, 2026
c21a4db
fix(contract): address review nits on async attestation
pbeza Jul 1, 2026
fd2291f
test(contract): sync ABI snapshot with a/an doc fix
pbeza Jul 1, 2026
e15bc3d
refactor(contract): unify zero-NearToken spelling to from_yoctonear(0)
pbeza Jul 1, 2026
74e9f72
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 1, 2026
5e4ea28
fix(node): treat in-flight attestation verification as executed
pbeza Jul 9, 2026
87937db
test(contract): sync ABI snapshot with is_verification_pending view
pbeza Jul 9, 2026
029f1d8
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 9, 2026
d9d5066
refactor: alternative without yield resume (#3766)
kevindeforth Jul 10, 2026
486d463
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 10, 2026
9c1a346
docs(contract): update submit_participant_info doc for the no-yield flow
pbeza Jul 10, 2026
290b3dc
refactor(contract): read attached deposit from env in charge_attestat…
pbeza Jul 10, 2026
c1a8d07
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 10, 2026
b4fa873
fix(contract): shadow deployed 3.13.0 Config in migration so migrate(…
pbeza Jul 10, 2026
6a6a1a9
style(contract): rustfmt v3_13_0_state migration shadow
pbeza Jul 10, 2026
9b0851e
refactor(contract): drop participant-refresh special case in charge_a…
pbeza Jul 10, 2026
6d27650
refactor(contract): unify deposit refunds into a generic refund_to he…
pbeza Jul 10, 2026
ee9f82e
fix(contract): flush attestation store before charging storage
pbeza Jul 13, 2026
8563815
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 13, 2026
aa46261
fixup! fix(contract): flush attestation store before charging storage
pbeza Jul 13, 2026
d11cb85
fixup! fix(contract): flush attestation store before charging storage
pbeza Jul 13, 2026
721dc6c
fixup! fix(contract): flush attestation store before charging storage
pbeza Jul 13, 2026
756c598
feat(node): attach submit_participant_info storage deposit
pbeza Jul 14, 2026
a44b6b2
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 14, 2026
64a3e94
fixup! feat(node): attach submit_participant_info storage deposit
pbeza Jul 14, 2026
8e75745
fixup! refactor(contract): unify zero-NearToken spelling to from_yoct…
pbeza Jul 14, 2026
f2f470a
refactor(contract): express NEAR amounts via from_near, not from_yoct…
pbeza Jul 14, 2026
6d228ab
docs(contract): document store_verified_attestation flush behavior
pbeza Jul 14, 2026
67d6388
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 14, 2026
de63ecf
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 14, 2026
43d1fb8
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 16, 2026
01d9cea
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 16, 2026
91a7891
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 16, 2026
2b4ea85
refactor(contract): charge a flat fee for attestation storage
pbeza Jul 16, 2026
812488f
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 16, 2026
386d6cd
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 16, 2026
8ab4f4c
test(contract): attach the flat fee when submitting attestations in t…
pbeza Jul 16, 2026
195d7b3
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 18, 2026
90693be
fix(contract): bump verify_quote gas default to 200 Tgas
pbeza Jul 22, 2026
f950275
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 22, 2026
22b50b0
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 22, 2026
8710f65
Merge remote-tracking branch 'origin/main' into 3642-async-attestatio…
pbeza Jul 22, 2026
28f9b90
test(contract): sandbox coverage + stub verifier for async attestatio…
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion crates/contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ abi = [
"near-mpc-contract-interface/abi",
"mpc-attestation/abi",
"mpc-primitives/abi",
"tee-verifier-interface/borsh-schema",
"schemars",
]
# This is used when running `cargo clippy --all-features`, because otherwise `abi` feat will break compilation.
Expand All @@ -74,6 +75,7 @@ __abi-generate = ["abi", "near-sdk/__abi-generate"]

[dependencies]
assert_matches = { workspace = true }
attestation = { workspace = true }
blstrs = { workspace = true }
borsh = { workspace = true }
curve25519-dalek = { workspace = true }
Expand All @@ -87,7 +89,7 @@ k256 = { workspace = true, features = [
"arithmetic",
"expose-field",
] }
mpc-attestation = { workspace = true, features = ["local-verify"] }
mpc-attestation = { workspace = true }
mpc-primitives = { workspace = true }
near-account-id = { workspace = true, features = ["serde"] }
near-mpc-bounded-collections = { workspace = true }
Expand All @@ -102,6 +104,7 @@ rand = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
tee-verifier-interface = { workspace = true }
thiserror = { workspace = true }
threshold-signatures = { workspace = true, optional = true }

Expand Down
23 changes: 23 additions & 0 deletions crates/contract/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const DEFAULT_RETURN_SIGNATURE_AND_CLEAN_STATE_ON_SUCCESS_CALL_TERA_GAS: u64 = 7
const DEFAULT_RETURN_CK_AND_CLEAN_STATE_ON_SUCCESS_CALL_TERA_GAS: u64 = 7;
/// Prepaid gas for a `fail_on_timeout` call
const DEFAULT_FAIL_ON_TIMEOUT_TERA_GAS: u64 = 2;
/// Prepaid gas for a `fail_attestation_submission` call
const DEFAULT_FAIL_ATTESTATION_SUBMISSION_TERA_GAS: u64 = 2;
/// Prepaid gas for a `clean_tee_status` call
const DEFAULT_CLEAN_TEE_STATUS_TERA_GAS: u64 = 10;
/// Prepaid gas for the reshare-time `clean_invalid_attestations` promise.
Expand All @@ -34,6 +36,15 @@ const DEFAULT_REMOVE_NON_PARTICIPANT_UPDATE_VOTES_TERA_GAS: u64 = 5;
const DEFAULT_CLEAN_FOREIGN_CHAIN_DATA_TERA_GAS: u64 = 5;
/// Prepaid gas for a `remove_non_participant_tee_verifier_votes` call
const DEFAULT_REMOVE_NON_PARTICIPANT_TEE_VERIFIER_VOTES_TERA_GAS: u64 = 5;
/// Gas attached to the cross-contract `verify_quote` call on the TEE verifier.
const DEFAULT_VERIFIER_TERA_GAS: u64 = 100;

@barakeinav1 barakeinav1 Jul 21, 2026

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.

Heads-up — this default looks too low for real on-chain DCAP.

Testing this branch end-to-end on a real localnet TDX cluster (real dstack quotes, stock release-v3.13 node, this contract + the real tee-verifier), attestation never gets through. The cross-contract verify_quote call runs out of gas — the receipt fails with Exceeded the prepaid gas, so real dcap_qvl::verify seems to need more than 100 Tgas. After it OOGs the contract hits verifier did not respond within the yield-resume window, the pending_attestation gets stuck, and every retry then fails with already in flight — so no node becomes attested and keygen stalls.

I don't know the exact figure yet — still testing to pin down how much DCAP actually needs (and whether it fits under the 300 Tgas tx cap once you add the resolve + callback gas). Flagging early. Note the sandbox tests in #3715 use the real verifier but only feed a malformed quote that's rejected at parse (≈no gas), so the expensive full-verification path that blows this budget isn't exercised.

@barakeinav1 barakeinav1 Jul 21, 2026

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.

I measured 173 Tgas, but we should probably use a higher number (note, that we max tag is 300, and we need at least 70 for the rest of the method, so 200-220 tgas sounds good) in case we want to upgrade the verifier without change the const.
just thinking out loud
updating the const is a config update vote, correct? so it should be easy enough to update it.
wandering if we should add this to the verifier contract id vote (so it can be done in one vote instead of 2), but I don't think this is crtical.

@barakeinav1 barakeinav1 Jul 21, 2026

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.

I used 200 tgas- and the test passed!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Bumped it here: 90693be. ✅

/// Prepaid gas for the `resolve_verification` callback. Carries the bulk of the
/// post-DCAP work (allowlist match, RTMR3 replay, app-compose validation, store).
const DEFAULT_RESOLVE_VERIFICATION_TERA_GAS: u64 = 60;
Comment on lines +39 to +43

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.

note: That's expensive. How does it compare to the current costs?

@pbeza pbeza Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I never benchmarked it, and I think we have quite a few other unbenchmarked constants too, for example here: #3641 (comment), although that one is in the e2e tests. I’d defer this to #3715 (noted here), where I added the tests. Perhaps we can benchmark it there, or in a follow-up if it’s not trivial.

/// Prepaid gas for the `on_attestation_verified` yield-callback. Sized for its
/// heaviest (timeout) branch, which removes the pending entry and schedules both
/// a refund transfer and the `fail_attestation_submission` promise.
const DEFAULT_ON_ATTESTATION_VERIFIED_TERA_GAS: u64 = 10;

/// Config for V2 of the contract.
#[near(serializers=[borsh, json])]
Expand All @@ -56,6 +67,8 @@ pub(crate) struct Config {
pub(crate) return_ck_and_clean_state_on_success_call_tera_gas: u64,
/// Prepaid gas for a `fail_on_timeout` call.
pub(crate) fail_on_timeout_tera_gas: u64,
/// Prepaid gas for a `fail_attestation_submission` call.
pub(crate) fail_attestation_submission_tera_gas: u64,
/// Prepaid gas for a `clean_tee_status` call.
pub(crate) clean_tee_status_tera_gas: u64,
/// Prepaid gas for the reshare-time `clean_invalid_attestations` promise.
Expand All @@ -68,6 +81,12 @@ pub(crate) struct Config {
pub(crate) clean_foreign_chain_data_tera_gas: u64,
/// Prepaid gas for a `remove_non_participant_tee_verifier_votes` call.
pub(crate) remove_non_participant_tee_verifier_votes_tera_gas: u64,
/// Gas attached to the cross-contract `verify_quote` call on the verifier.
pub(crate) verifier_tera_gas: u64,
/// Prepaid gas for the `resolve_verification` callback.
pub(crate) resolve_verification_tera_gas: u64,
/// Prepaid gas for the `on_attestation_verified` yield-callback.
pub(crate) on_attestation_verified_tera_gas: u64,
}

impl Default for Config {
Expand All @@ -85,6 +104,7 @@ impl Default for Config {
return_ck_and_clean_state_on_success_call_tera_gas:
DEFAULT_RETURN_CK_AND_CLEAN_STATE_ON_SUCCESS_CALL_TERA_GAS,
fail_on_timeout_tera_gas: DEFAULT_FAIL_ON_TIMEOUT_TERA_GAS,
fail_attestation_submission_tera_gas: DEFAULT_FAIL_ATTESTATION_SUBMISSION_TERA_GAS,
clean_tee_status_tera_gas: DEFAULT_CLEAN_TEE_STATUS_TERA_GAS,
clean_invalid_attestations_tera_gas: DEFAULT_CLEAN_INVALID_ATTESTATIONS_TERA_GAS,
cleanup_orphaned_node_migrations_tera_gas:
Expand All @@ -94,6 +114,9 @@ impl Default for Config {
clean_foreign_chain_data_tera_gas: DEFAULT_CLEAN_FOREIGN_CHAIN_DATA_TERA_GAS,
remove_non_participant_tee_verifier_votes_tera_gas:
DEFAULT_REMOVE_NON_PARTICIPANT_TEE_VERIFIER_VOTES_TERA_GAS,
verifier_tera_gas: DEFAULT_VERIFIER_TERA_GAS,
resolve_verification_tera_gas: DEFAULT_RESOLVE_VERIFICATION_TERA_GAS,
on_attestation_verified_tera_gas: DEFAULT_ON_ATTESTATION_VERIFIED_TERA_GAS,
}
}
}
20 changes: 20 additions & 0 deletions crates/contract/src/dto_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ impl From<near_mpc_contract_interface::types::InitConfig> for Config {
if let Some(v) = config_ext.fail_on_timeout_tera_gas {
config.fail_on_timeout_tera_gas = v;
}
if let Some(v) = config_ext.fail_attestation_submission_tera_gas {
config.fail_attestation_submission_tera_gas = v;
}
if let Some(v) = config_ext.clean_tee_status_tera_gas {
config.clean_tee_status_tera_gas = v;
}
Expand All @@ -490,6 +493,15 @@ impl From<near_mpc_contract_interface::types::InitConfig> for Config {
if let Some(v) = config_ext.remove_non_participant_tee_verifier_votes_tera_gas {
config.remove_non_participant_tee_verifier_votes_tera_gas = v;
}
if let Some(v) = config_ext.verifier_tera_gas {
config.verifier_tera_gas = v;
}
if let Some(v) = config_ext.resolve_verification_tera_gas {
config.resolve_verification_tera_gas = v;
}
if let Some(v) = config_ext.on_attestation_verified_tera_gas {
config.on_attestation_verified_tera_gas = v;
}

config
}
Expand All @@ -510,6 +522,7 @@ impl From<&Config> for near_mpc_contract_interface::types::Config {
return_ck_and_clean_state_on_success_call_tera_gas: value
.return_ck_and_clean_state_on_success_call_tera_gas,
fail_on_timeout_tera_gas: value.fail_on_timeout_tera_gas,
fail_attestation_submission_tera_gas: value.fail_attestation_submission_tera_gas,
clean_tee_status_tera_gas: value.clean_tee_status_tera_gas,
clean_invalid_attestations_tera_gas: value.clean_invalid_attestations_tera_gas,
cleanup_orphaned_node_migrations_tera_gas: value
Expand All @@ -519,6 +532,9 @@ impl From<&Config> for near_mpc_contract_interface::types::Config {
clean_foreign_chain_data_tera_gas: value.clean_foreign_chain_data_tera_gas,
remove_non_participant_tee_verifier_votes_tera_gas: value
.remove_non_participant_tee_verifier_votes_tera_gas,
verifier_tera_gas: value.verifier_tera_gas,
resolve_verification_tera_gas: value.resolve_verification_tera_gas,
on_attestation_verified_tera_gas: value.on_attestation_verified_tera_gas,
}
}
}
Expand All @@ -538,6 +554,7 @@ impl From<near_mpc_contract_interface::types::Config> for Config {
return_ck_and_clean_state_on_success_call_tera_gas: value
.return_ck_and_clean_state_on_success_call_tera_gas,
fail_on_timeout_tera_gas: value.fail_on_timeout_tera_gas,
fail_attestation_submission_tera_gas: value.fail_attestation_submission_tera_gas,
clean_tee_status_tera_gas: value.clean_tee_status_tera_gas,
clean_invalid_attestations_tera_gas: value.clean_invalid_attestations_tera_gas,
cleanup_orphaned_node_migrations_tera_gas: value
Expand All @@ -547,6 +564,9 @@ impl From<near_mpc_contract_interface::types::Config> for Config {
clean_foreign_chain_data_tera_gas: value.clean_foreign_chain_data_tera_gas,
remove_non_participant_tee_verifier_votes_tera_gas: value
.remove_non_participant_tee_verifier_votes_tera_gas,
verifier_tera_gas: value.verifier_tera_gas,
resolve_verification_tera_gas: value.resolve_verification_tera_gas,
on_attestation_verified_tera_gas: value.on_attestation_verified_tera_gas,
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions crates/contract/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::crypto_shared::kdf::TweakNotOnCurve;
use crate::primitives::domain::MIN_RECONSTRUCTION_THRESHOLD;
use crate::primitives::key_state::{EpochId, Keyset};
use crate::tee::tee_state::AttestationSubmissionError;
use near_account_id::AccountId;
use near_mpc_contract_interface::types as dtos;
use near_mpc_contract_interface::types::{DomainId, DomainPurpose, ForeignChain, Protocol};
Expand Down Expand Up @@ -28,6 +29,14 @@ pub enum TeeError {
"Due to previously failed TEE validation, the network is not accepting new requests at this point in time. Try again later."
)]
TeeValidationFailed,
#[error(
"A Dstack attestation verification is already in flight for this account; wait for it to finish before resubmitting."
)]
VerificationAlreadyPending,
#[error(
"No TEE verifier is configured yet. Participants must vote one in via vote_tee_verifier_change before Dstack attestations can be submitted."
)]
VerifierNotConfigured,
}

#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
Expand Down Expand Up @@ -318,6 +327,9 @@ pub enum Error {
// Tee errors
#[error(transparent)]
NodeMigrationError(#[from] NodeMigrationError),
// Tee attestation submission errors
#[error(transparent)]
AttestationSubmission(#[from] AttestationSubmissionError),
}

impl near_sdk::FunctionError for Error {
Expand Down
Loading
Loading