Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion crates/contract-history/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub const fn current_mainnet() -> &'static [u8] {
version_3_2_0()
version_3_3_2()
}

pub const fn current_testnet() -> &'static [u8] {
Expand Down
77 changes: 3 additions & 74 deletions crates/contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ pub mod tee;
pub mod update;
#[cfg(feature = "dev-utils")]
pub mod utils;
pub mod v3_0_2_state;
pub mod v3_2_0_state;
pub mod v3_3_2_state;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not 100% if we were leaving the old states on purpose, but taking into account that our contract size is already near dangerous sizes, I opted for only leaving the necessary code there


#[cfg(feature = "bench-contract-methods")]
mod bench;
Expand Down Expand Up @@ -1285,19 +1284,11 @@ impl MpcContract {
pub fn migrate() -> Result<Self, Error> {
log!("migrating contract");

match try_state_read::<v3_0_2_state::MpcContract>() {
match try_state_read::<v3_3_2_state::MpcContract>() {
Ok(Some(state)) => return Ok(state.into()),
Ok(None) => return Err(InvalidState::ContractStateIsMissing.into()),
Err(err) => {
log!("failed to deserialize state into 3_0_2 state: {:?}", err);
}
};

match try_state_read::<v3_2_0_state::MpcContract>() {
Ok(Some(state)) => return Ok(state.into()),
Ok(None) => return Err(InvalidState::ContractStateIsMissing.into()),
Err(err) => {
log!("failed to deserialize state into 3_2_0 state: {:?}", err);
log!("failed to deserialize state into 3_3_2 state: {:?}", err);
}
};

Expand All @@ -1308,19 +1299,6 @@ impl MpcContract {
}
}

/// Removes stale data from the contract to be removed after a contract upgrade. Some
/// containers are expensive to run destructors on, thus we don't include it in the contract upgrade itself,
/// as it can run out of gas. Thus we create methods to run these destructors manually post upgrade.
pub fn post_upgrade_cleanup(&mut self) {
let Some(mut attestations) = self.stale_data.participant_attestations.take() else {
panic!("stale participant_attestations data has already been cleared");
};

attestations.clear();

log!("Successfully cleared stale TEE attestations.");
}

pub fn state(&self) -> &ProtocolContractState {
&self.protocol_state
}
Expand Down Expand Up @@ -3545,55 +3523,6 @@ mod tests {
assert_eq!(*resharing_state, expected_resharing_state);
}

#[test]
fn test_post_upgrade_cleanup_success() {
// given
let mut contract = MpcContract::init(
ThresholdParameters::new(gen_participants(3), Threshold::new(2)).unwrap(),
None,
)
.unwrap();

let mut mock_stale_map = IterableMap::new(StorageKey::_DeprecatedTeeParticipantAttestation);
let node_pk = bogus_ed25519_near_public_key();
let node_id = NodeId {
account_id: gen_account_id(),
tls_public_key: bogus_ed25519_near_public_key(),
account_public_key: Some(bogus_ed25519_near_public_key()),
};
let attestation = mpc_attestation::attestation::Attestation::Mock(
mpc_attestation::attestation::MockAttestation::Valid,
);

mock_stale_map.insert(node_pk.clone(), (node_id, attestation));

contract.stale_data.participant_attestations = Some(mock_stale_map);

// when
contract.post_upgrade_cleanup();

// then
assert_matches::assert_matches!(contract.stale_data.participant_attestations, None);
}

#[test]
#[should_panic(expected = "stale participant_attestations data has already been cleared")]
fn test_post_upgrade_cleanup_panics_if_already_cleared() {
// given
let mut contract = MpcContract::init(
ThresholdParameters::new(gen_participants(3), Threshold::new(2)).unwrap(),
None,
)
.unwrap();

contract.stale_data.participant_attestations = None;

// when
contract.post_upgrade_cleanup();

// then panic
}

/// Sets up a complete TEE test environment with contract, accounts, mock dstack attestation, TLS key and the node's near public key.
/// This is a helper function that provides all the common components needed for TEE-related tests.
fn setup_tee_test() -> (
Expand Down
60 changes: 60 additions & 0 deletions crates/contract/src/v3_3_2_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! ## Overview
//! This module stores the previous contract state—the one you want to migrate from.
//! The goal is to describe the data layout _exactly_ as it existed before.
//!
//! ## Guideline
//! In theory, you could copy-paste every struct from the specific commit you're migrating from.
//! However, this approach (a) requires manual effort from a developer and (b) increases the binary size.
//! A better approach: only copy the structures that have changed and import the rest from the existing codebase.

use borsh::{BorshDeserialize, BorshSerialize};
use near_sdk::{env, store::LookupMap};

use crate::{
node_migrations::NodeMigrations,
primitives::{
ckd::CKDRequest,
signature::{SignatureRequest, YieldIndex},
},
state::ProtocolContractState,
tee::tee_state::TeeState,
update::ProposedUpdates,
Config, StaleData,
};

#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct MpcContract {
protocol_state: ProtocolContractState,
pending_signature_requests: LookupMap<SignatureRequest, YieldIndex>,
pending_ckd_requests: LookupMap<CKDRequest, YieldIndex>,
proposed_updates: ProposedUpdates,
config: Config,
tee_state: TeeState,
accept_requests: bool,
node_migrations: NodeMigrations,
stale_data: StaleData,
}

impl From<MpcContract> for crate::MpcContract {
fn from(value: MpcContract) -> Self {
let protocol_state = value.protocol_state;

let crate::ProtocolContractState::Running(_running_state) = &protocol_state else {
env::panic_str("Contract must be in running state when migrating.");
};

Self {
protocol_state,
pending_signature_requests: value.pending_signature_requests,
pending_ckd_requests: value.pending_ckd_requests,
proposed_updates: value.proposed_updates,
config: value.config,
tee_state: value.tee_state,
accept_requests: value.accept_requests,
node_migrations: value.node_migrations,
stale_data: crate::StaleData {
participant_attestations: None,
},
}
}
}
18 changes: 1 addition & 17 deletions crates/contract/tests/sandbox/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ use mpc_contract::{
use near_account_id::AccountId;
use near_sdk::NearToken;
use near_workspaces::{
network::Sandbox,
result::{ExecutionFailure, ExecutionSuccess},
types::AccessKeyPermission,
AccessKey, Contract,
network::Sandbox, result::ExecutionSuccess, types::AccessKeyPermission, AccessKey, Contract,
};
use near_workspaces::{result::Execution, Account, Worker};
use rand_core::CryptoRngCore;
Expand Down Expand Up @@ -572,16 +569,3 @@ pub async fn generate_participant_and_submit_attestation(
assert!(result.is_success());
(new_account, account_id, new_participant)
}

pub async fn cleanup_post_migrate(
contract: &Contract,
account: &Account,
) -> Result<ExecutionSuccess, ExecutionFailure> {
account
.call(contract.id(), "post_upgrade_cleanup")
.max_gas()
.transact()
.await
.unwrap()
.into_result()
}
14 changes: 2 additions & 12 deletions crates/contract/tests/sandbox/upgrade_to_current_contract.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::sandbox::{
common::{
call_contract_key_generation, cleanup_post_migrate,
execute_key_generation_and_add_random_state, gen_accounts, init,
propose_and_vote_contract_binary,
call_contract_key_generation, execute_key_generation_and_add_random_state, gen_accounts,
init, propose_and_vote_contract_binary,
},
utils::{
consts::PARTICIPANT_LEN,
Expand Down Expand Up @@ -180,15 +179,6 @@ async fn propose_upgrade_from_production_to_current_binary(
state_pre_upgrade, state_post_upgrade,
"State of the contract should remain the same post upgrade."
);

match network {
Network::Testnet => {}
Network::Mainnet => {
cleanup_post_migrate(&contract, &accounts[0])
.await
.expect("post migration cleanup works");
}
};
}

//// Verifies that upgrading the contract preserves state and functionality.
Expand Down
5 changes: 0 additions & 5 deletions crates/contract/tests/snapshots/abi__abi_has_not_changed.snap
Original file line number Diff line number Diff line change
Expand Up @@ -432,11 +432,6 @@ expression: abi
}
}
},
{
"name": "post_upgrade_cleanup",
"doc": " Removes stale data from the contract to be removed after a contract upgrade. Some\n containers are expensive to run destructors on, thus we don't include it in the contract upgrade itself,\n as it can run out of gas. Thus we create methods to run these destructors manually post upgrade.",
"kind": "call"
},
{
"name": "propose_update",
"doc": " Propose update to either code or config, but not both of them at the same time.",
Expand Down