Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 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
4c3b8a1
test(contract): pin the max borsh size of a stored NodeAttestation
pbeza Jul 20, 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.

1 change: 1 addition & 0 deletions crates/attestation/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub(crate) const KEY_PROVIDER_EVENT: &str = "key-provider";
const RTMR3_INDEX: u32 = 3;

#[derive(Clone, Constructor, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
#[cfg_attr(feature = "borsh-schema", derive(borsh::BorshSchema))]
pub struct DstackAttestation {
pub quote: QuoteBytes,
pub collateral: Collateral,
Expand Down
42 changes: 42 additions & 0 deletions crates/attestation/src/tcb_info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use alloc::string::String;
#[cfg(feature = "borsh-schema")]
use alloc::string::ToString;
use alloc::vec::Vec;
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(any(test, feature = "dstack-conversions"))]
Expand All @@ -16,6 +18,7 @@ pub enum ParsingError {

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "borsh-schema", derive(borsh::BorshSchema))]
pub struct TcbInfo {
pub mrtd: HexBytes<48>,
pub rtmr0: HexBytes<48>,
Expand All @@ -32,6 +35,7 @@ pub struct TcbInfo {

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "borsh-schema", derive(borsh::BorshSchema))]
pub struct EventLog {
pub imr: u32,
pub event_type: u32,
Expand Down Expand Up @@ -60,6 +64,33 @@ pub struct EventLog {
#[serde(transparent)]
pub struct HexBytes<const N: usize>(#[serde_as(as = "Hex")] [u8; N]);

/// Manual impl because the derive drops the const parameter from the
/// declaration, so `HexBytes<48>` and `HexBytes<32>` in one schema collide
/// ("Redefining type schema for HexBytes").
#[cfg(feature = "borsh-schema")]
impl<const N: usize> borsh::BorshSchema for HexBytes<N> {
fn declaration() -> borsh::schema::Declaration {
alloc::format!("HexBytes<{N}>")
}

fn add_definitions_recursively(
definitions: &mut alloc::collections::BTreeMap<
borsh::schema::Declaration,
borsh::schema::Definition,
>,
) {
let fields = borsh::schema::Fields::UnnamedFields(alloc::vec![
<[u8; N] as borsh::BorshSchema>::declaration()
]);
borsh::schema::add_definition(
Self::declaration(),
borsh::schema::Definition::Struct { fields },
definitions,
);
<[u8; N] as borsh::BorshSchema>::add_definitions_recursively(definitions);
}
}

#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum HexBytesOrEmpty<const N: usize> {
Expand Down Expand Up @@ -198,6 +229,17 @@ mod tests {
use rstest::rstest;
use serde_json;

/// `TcbInfo` holds both `HexBytes<48>` and `HexBytes<32>`; schema
/// generation panics if their declarations collide.
#[cfg(feature = "borsh-schema")]
#[test]
fn TcbInfo__should_generate_borsh_schema() {
let container = borsh::schema_container_of::<TcbInfo>();

assert!(container.get_definition("HexBytes<48>").is_some());
assert!(container.get_definition("HexBytes<32>").is_some());
}

#[test]
fn TcbInfo__should_deserialize_from_real_test_data() {
// Given
Expand Down
2 changes: 1 addition & 1 deletion crates/chain-gateway/src/transaction_sender/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ mod tests {
method_name: "do_something".to_string(),
args: b"test args".to_vec(),
gas: TEST_GAS,
deposit: NearToken::from_yoctonear(0),
deposit: NearToken::from_near(0),
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/chain-gateway/src/transaction_sender/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ mod tests {
let mut args = vec![0u8; 16];
rng.fill(&mut args[..]);
let gas = NearGas::from_gas(300);
let deposit = NearToken::from_yoctonear(0);
let deposit = NearToken::from_near(0);
(
receiver_id,
FunctionCallArgs {
Expand Down
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
16 changes: 16 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,11 @@ 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;
/// 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;

/// Config for V2 of the contract.
#[near(serializers=[borsh, json])]
Expand All @@ -56,6 +63,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 +77,10 @@ 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,
}

impl Default for Config {
Expand All @@ -85,6 +98,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 +108,8 @@ 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,
}
}
}
15 changes: 15 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,12 @@ 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;
}

config
}
Expand All @@ -510,6 +519,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 +529,8 @@ 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,
}
}
}
Expand All @@ -538,6 +550,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 +560,8 @@ 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,
}
}
}
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(
"No TEE verifier is configured yet. Participants must vote one in via vote_tee_verifier_change before Dstack attestations can be submitted."
)]
VerifierNotConfigured,
#[error("The TEE verifier rejected the quote: {reason}")]
QuoteRejected { reason: String },
#[error("The TEE verifier did not answer the verify_quote call.")]
VerifierUnavailable,
}

#[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