diff --git a/crates/backup-cli/src/adapters/contract_state_fixture.rs b/crates/backup-cli/src/adapters/contract_state_fixture.rs index 03691aff99..ee4526ef2b 100644 --- a/crates/backup-cli/src/adapters/contract_state_fixture.rs +++ b/crates/backup-cli/src/adapters/contract_state_fixture.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; -use near_mpc_contract_interface::types::{Keyset, ProtocolContractState}; +use near_mpc_contract_interface::types::{ + Keyset, ProtocolContractState, ProtocolContractStateCompat, +}; use tokio::fs::File; use tokio::io::AsyncReadExt; @@ -49,7 +51,12 @@ impl ContractStateReader for ContractStateFixture { .await .map_err(Error::Read)?; - serde_json::from_slice(&buffer).map_err(Error::JsonDeserialization) + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + // `state()` still emits the pre-3903 field names; deserialize the compat + // shape and convert to the canonical DTO. + let state: ProtocolContractStateCompat = + serde_json::from_slice(&buffer).map_err(Error::JsonDeserialization)?; + Ok(state.into()) } } @@ -75,7 +82,7 @@ pub fn get_keyset_from_contract_state( mod tests { use std::path::PathBuf; - use near_mpc_contract_interface::types::{ProtocolContractState, Threshold}; + use near_mpc_contract_interface::types::{GovernanceThreshold, ProtocolContractState}; use crate::{ adapters::contract_state_fixture::ContractStateFixture, ports::ContractStateReader, @@ -95,7 +102,10 @@ mod tests { let ProtocolContractState::Running(state) = &contract_state else { panic!("expected Running state, got {contract_state:?}"); }; - assert_eq!(state.parameters.threshold, Threshold::new(7)); + assert_eq!( + state.parameters.governance_threshold, + GovernanceThreshold::new(7) + ); assert_eq!(state.domains.domains.len(), 2); } } diff --git a/crates/contract/src/dto_mapping.rs b/crates/contract/src/dto_mapping.rs index 38d277ce00..927b2cd559 100644 --- a/crates/contract/src/dto_mapping.rs +++ b/crates/contract/src/dto_mapping.rs @@ -24,8 +24,8 @@ use crate::{ domain::{AddDomainsVotes, DomainRegistry}, key_state::{AuthenticatedAccountId, AuthenticatedParticipantId, KeyForDomain, Keyset}, participants::{ParticipantInfo, Participants}, - threshold_votes::ThresholdParametersVotes, - thresholds::{ProposedThresholdParameters, ThresholdParameters}, + threshold_votes::GovernanceThresholdParametersVotes, + thresholds::{GovernanceThresholdParameters, ProposedGovernanceThresholdParameters}, }, state::{ ProtocolContractState, @@ -214,21 +214,27 @@ impl IntoContractType for dtos::Participants { } } -impl TryIntoContractType for dtos::ThresholdParameters { +// TODO(XXXX): Switch to canonical after upgrade 3.14.0 +impl TryIntoContractType + for dtos::GovernanceThresholdParametersCompat +{ type Error = Error; - fn try_into_contract_type(self) -> Result { + fn try_into_contract_type(self) -> Result { // Validate eagerly at the DTO boundary so invalid proposal parameters are rejected here. - ThresholdParameters::new(self.participants.into_contract_type(), self.threshold) + GovernanceThresholdParameters::new(self.participants.into_contract_type(), self.threshold) } } -impl TryIntoContractType for dtos::ProposedThresholdParameters { +// TODO(XXXX): Switch to canonical after upgrade 3.14.0 +impl TryIntoContractType + for dtos::ProposedGovernanceThresholdParametersCompat +{ type Error = Error; - fn try_into_contract_type(self) -> Result { + fn try_into_contract_type(self) -> Result { // Validates the inner threshold parameters; see the conversion above. - Ok(ProposedThresholdParameters::new( + Ok(ProposedGovernanceThresholdParameters::new( self.parameters.try_into_contract_type()?, self.per_domain_thresholds, )) @@ -584,14 +590,30 @@ mod test_conversions { } } - impl From for dtos::ThresholdParameters { - fn from(params: ThresholdParameters) -> Self { + impl From for dtos::GovernanceThresholdParameters { + fn from(params: GovernanceThresholdParameters) -> Self { (¶ms).into_dto_type() } } - impl From for dtos::ProposedThresholdParameters { - fn from(params: ProposedThresholdParameters) -> Self { + impl From for dtos::ProposedGovernanceThresholdParameters { + fn from(params: ProposedGovernanceThresholdParameters) -> Self { + (¶ms).into_dto_type() + } + } + + // TODO(XXXX): Delete this code after upgrade 3.14.0 + impl From for dtos::GovernanceThresholdParametersCompat { + fn from(params: GovernanceThresholdParameters) -> Self { + (¶ms).into_dto_type() + } + } + + // TODO(XXXX): Delete this code after upgrade 3.14.0 + impl From + for dtos::ProposedGovernanceThresholdParametersCompat + { + fn from(params: ProposedGovernanceThresholdParameters) -> Self { (¶ms).into_dto_type() } } @@ -686,34 +708,78 @@ impl IntoInterfaceType for &Participants { } } -impl IntoInterfaceType for &ThresholdParameters { - fn into_dto_type(self) -> dtos::ThresholdParameters { - dtos::ThresholdParameters { +impl IntoInterfaceType for &GovernanceThresholdParameters { + fn into_dto_type(self) -> dtos::GovernanceThresholdParameters { + dtos::GovernanceThresholdParameters { participants: self.participants().into_dto_type(), - threshold: self.threshold(), + governance_threshold: self.threshold(), } } } -impl IntoInterfaceType for &ProposedThresholdParameters { - fn into_dto_type(self) -> dtos::ProposedThresholdParameters { - dtos::ProposedThresholdParameters { +impl IntoInterfaceType + for &ProposedGovernanceThresholdParameters +{ + fn into_dto_type(self) -> dtos::ProposedGovernanceThresholdParameters { + dtos::ProposedGovernanceThresholdParameters { parameters: self.parameters().into_dto_type(), - per_domain_thresholds: self.per_domain_thresholds().clone(), + per_domain_reconstruction_thresholds: self.per_domain_thresholds().clone(), } } } // --- Voting types --- -impl IntoInterfaceType for &ThresholdParametersVotes { - fn into_dto_type(self) -> dtos::ThresholdParametersVotes { +impl IntoInterfaceType + for &GovernanceThresholdParametersVotes +{ + fn into_dto_type(self) -> dtos::GovernanceThresholdParametersVotes { let proposal_by_account = self .proposal_by_account .iter() .map(|(account, params)| (account.into_dto_type(), params.into_dto_type())) .collect(); - dtos::ThresholdParametersVotes { + dtos::GovernanceThresholdParametersVotes { + proposal_by_account, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl IntoInterfaceType + for &GovernanceThresholdParameters +{ + fn into_dto_type(self) -> dtos::GovernanceThresholdParametersCompat { + dtos::GovernanceThresholdParametersCompat { + participants: self.participants().into_dto_type(), + threshold: self.threshold(), + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl IntoInterfaceType + for &ProposedGovernanceThresholdParameters +{ + fn into_dto_type(self) -> dtos::ProposedGovernanceThresholdParametersCompat { + dtos::ProposedGovernanceThresholdParametersCompat { + parameters: self.parameters().into_dto_type(), + per_domain_thresholds: self.per_domain_thresholds().clone(), + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl IntoInterfaceType + for &GovernanceThresholdParametersVotes +{ + fn into_dto_type(self) -> dtos::GovernanceThresholdParametersVotesCompat { + let proposal_by_account = self + .proposal_by_account + .iter() + .map(|(account, params)| (account.into_dto_type(), params.into_dto_type())) + .collect(); + dtos::GovernanceThresholdParametersVotesCompat { proposal_by_account, } } @@ -828,7 +894,7 @@ impl IntoInterfaceType for &ResharingContractState .iter() .map(|a| a.into_dto_type()) .collect(), - per_domain_thresholds: self.per_domain_thresholds.clone(), + per_domain_reconstruction_thresholds: self.per_domain_thresholds.clone(), } } } @@ -868,7 +934,7 @@ mod tests { use super::*; use crate::errors::InvalidThreshold; use crate::primitives::test_utils::gen_participants; - use crate::primitives::thresholds::Threshold; + use crate::primitives::thresholds::GovernanceThreshold; use assert_matches::assert_matches; const TEST_THRESHOLD: u64 = 2; @@ -921,21 +987,25 @@ mod tests { } /// Ensures that the JSON produced by serializing the internal - /// [`ThresholdParameters`] type can be deserialized into the DTO - /// [`dtos::ThresholdParameters`] type and vice versa, producing identical + /// [`GovernanceThresholdParameters`] type can be deserialized into the DTO + /// [`dtos::GovernanceThresholdParametersCompat`] type and vice versa, producing identical /// JSON in both directions. #[test] fn threshold_parameters_serde_is_compatible_with_dto() { - let internal = - ThresholdParameters::new(test_participants(), Threshold::new(TEST_THRESHOLD)).unwrap(); + let internal = GovernanceThresholdParameters::new( + test_participants(), + GovernanceThreshold::new(TEST_THRESHOLD), + ) + .unwrap(); let json = serde_json::to_value(&internal).unwrap(); - let dto: dtos::ThresholdParameters = serde_json::from_value(json.clone()).unwrap(); + let dto: dtos::GovernanceThresholdParametersCompat = + serde_json::from_value(json.clone()).unwrap(); let dto_json = serde_json::to_value(&dto).unwrap(); assert_eq!(json, dto_json, "Internal and DTO JSON must be identical"); - let roundtrip: ThresholdParameters = serde_json::from_value(dto_json).unwrap(); + let roundtrip: GovernanceThresholdParameters = serde_json::from_value(dto_json).unwrap(); assert_eq!(internal, roundtrip); } @@ -943,12 +1013,16 @@ mod tests { /// serialization matches the internal type's serialization. #[test] fn into_dto_type_preserves_serialization() { - let internal = - ThresholdParameters::new(test_participants(), Threshold::new(TEST_THRESHOLD)).unwrap(); + let internal = GovernanceThresholdParameters::new( + test_participants(), + GovernanceThreshold::new(TEST_THRESHOLD), + ) + .unwrap(); let internal_json = serde_json::to_value(&internal).unwrap(); - let dto: dtos::ThresholdParameters = (&internal).into_dto_type(); - let dto_json = serde_json::to_value(&dto).unwrap(); + let dto: dtos::GovernanceThresholdParameters = (&internal).into_dto_type(); + let dto_compat: dtos::GovernanceThresholdParametersCompat = dto.into(); + let dto_json = serde_json::to_value(&dto_compat).unwrap(); assert_eq!(internal_json, dto_json); } @@ -958,13 +1032,13 @@ mod tests { #[test] fn try_into_contract_type__should_reject_threshold_below_relative_requirement() { // Given a DTO with 5 participants and a threshold of 2 (below the 60% bound of 3). - let dto = dtos::ThresholdParameters { + let dto = dtos::GovernanceThresholdParametersCompat { participants: (&gen_participants(5)).into_dto_type(), - threshold: Threshold::new(2), + threshold: GovernanceThreshold::new(2), }; // When converting the DTO into the contract type. - let result: Result = dto.try_into_contract_type(); + let result: Result = dto.try_into_contract_type(); // Then conversion fails with the relative-threshold error. assert_matches!( diff --git a/crates/contract/src/errors.rs b/crates/contract/src/errors.rs index e55266104c..37f558c88f 100644 --- a/crates/contract/src/errors.rs +++ b/crates/contract/src/errors.rs @@ -184,13 +184,13 @@ pub enum InvalidState { #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] pub enum InvalidThreshold { - #[error("Threshold does not meet the minimum absolute requirement")] + #[error("GovernanceThreshold does not meet the minimum absolute requirement")] MinAbsRequirementFailed, #[error( "GovernanceThreshold is below the minimum required relative to the participant count: require at least {required}, found {found}" )] MinRelRequirementFailed { required: u64, found: u64 }, - #[error("Threshold must not exceed number of participants: max {max}, found {found}")] + #[error("GovernanceThreshold must not exceed number of participants: max {max}, found {found}")] MaxRequirementFailed { max: u64, found: u64 }, #[error( "GovernanceThreshold exceeds the maximum allowed relative to the participant count: max {max}, found {found}" diff --git a/crates/contract/src/foreign_chain_rpc.rs b/crates/contract/src/foreign_chain_rpc.rs index 69d27e547e..21b798e680 100644 --- a/crates/contract/src/foreign_chain_rpc.rs +++ b/crates/contract/src/foreign_chain_rpc.rs @@ -20,7 +20,7 @@ use near_sdk::near; use near_sdk::store::IterableMap; use crate::errors::{ChainEntryValidationError, ConversionError, Error, InvalidParameters}; -use crate::primitives::thresholds::ThresholdParameters; +use crate::primitives::thresholds::GovernanceThresholdParameters; use crate::primitives::votes::{ProposalHash, ProposalHashEncoding, Votes}; use crate::primitives::{key_state::AuthenticatedParticipantId, participants::Participants}; use crate::storage_keys::StorageKey; @@ -174,7 +174,7 @@ impl ProviderVotes { chain: ForeignChain, hash: ProposalHash, participant: AuthenticatedParticipantId, - threshold_parameters: &ThresholdParameters, + threshold_parameters: &GovernanceThresholdParameters, ) -> Result { let protocol_threshold = threshold_parameters.threshold().value(); let participants = threshold_parameters.participants(); @@ -223,7 +223,7 @@ impl ForeignChainRpcWhitelist { &mut self, participant: AuthenticatedParticipantId, votes: NonEmptyBTreeMap, - threshold_parameters: &ThresholdParameters, + threshold_parameters: &GovernanceThresholdParameters, ) -> Result, Error> { let mut applied: Vec = Vec::new(); let votes: BTreeMap = votes.into(); @@ -250,15 +250,18 @@ mod tests { key_state::AuthenticatedParticipantId, test_utils::gen_authenticated_participants, }; use assert_matches::assert_matches; - use mpc_primitives::Threshold; + use mpc_primitives::GovernanceThreshold; use near_mpc_contract_interface::types::AuthScheme; - /// Build a `ThresholdParameters` for tests, bypassing the relative-threshold + /// Build a `GovernanceThresholdParameters` for tests, bypassing the relative-threshold /// validation so tests can express edge-case combinations (e.g. the stale-votes /// test deliberately uses a threshold > current participant count to assert /// the count_for predicate filters out non-participant rows). - fn tp(participants: &Participants, n: u64) -> ThresholdParameters { - ThresholdParameters::new_unvalidated(participants.clone(), Threshold::new(n)) + fn tp(participants: &Participants, n: u64) -> GovernanceThresholdParameters { + GovernanceThresholdParameters::new_unvalidated( + participants.clone(), + GovernanceThreshold::new(n), + ) } fn provider(id: &str) -> (ProviderId, ProviderConfig) { diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index cab21a7fb0..5688055a8c 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -85,7 +85,9 @@ use primitives::{ key_state::{AuthenticatedParticipantId, EpochId, KeyEventId, Keyset}, participants::ParticipantInfo, signature::{SignRequestArgs, SignatureRequest, YieldIndex}, - thresholds::{ProposedThresholdParameters, Threshold, ThresholdParameters}, + thresholds::{ + GovernanceThreshold, GovernanceThresholdParameters, ProposedGovernanceThresholdParameters, + }, }; use tee::measurements::{ContractExpectedMeasurements, MeasurementVoteAction, MeasurementVotes}; use tee::proposal::{CodeHashesVotes, LauncherHashVotes}; @@ -232,7 +234,7 @@ impl MpcContract { self.protocol_state.public_key(domain_id) } - fn threshold(&self) -> Result { + fn threshold(&self) -> Result { self.protocol_state.threshold() } @@ -912,10 +914,11 @@ impl MpcContract { pub fn vote_new_parameters( &mut self, prospective_epoch_id: EpochId, - proposal: dtos::ProposedThresholdParameters, + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + proposal: dtos::ProposedGovernanceThresholdParametersCompat, ) -> Result<(), Error> { Self::assert_caller_is_signer(); - let proposal: ProposedThresholdParameters = proposal.try_into_contract_type()?; + let proposal: ProposedGovernanceThresholdParameters = proposal.try_into_contract_type()?; log!( "vote_new_parameters: signer={}, proposal={:?}", env::signer_account_id(), @@ -1750,11 +1753,13 @@ impl MpcContract { // wait for manual intervention. let max_reconstruction_threshold = max_reconstruction_threshold(running_state.domains.domains()); - if let Err(err) = ThresholdParameters::validate_governance_against_reconstruction( - u64::try_from(remaining).expect("participant count fits in u64"), - current_params.threshold(), - max_reconstruction_threshold, - ) { + if let Err(err) = + GovernanceThresholdParameters::validate_governance_against_reconstruction( + u64::try_from(remaining).expect("participant count fits in u64"), + current_params.threshold(), + max_reconstruction_threshold, + ) + { log!( "Kicking out participants with an invalid TEE status would break the threshold relation ({:?}); {} participants remain with a valid TEE status. This requires manual intervention. We will not accept new signature requests as a safety precaution.", err, @@ -1775,16 +1780,18 @@ impl MpcContract { let new_threshold = usize::try_from(current_params.threshold().value()) .expect("threshold value fits in usize"); - let threshold_parameters = ThresholdParameters::new( + let threshold_parameters = GovernanceThresholdParameters::new( participants_with_valid_attestation, - Threshold::new(new_threshold as u64), + GovernanceThreshold::new(new_threshold as u64), ) .expect("Require valid threshold parameters"); // this should never happen. current_params.validate_incoming_proposal(&threshold_parameters)?; // This resharing only changes the participant set, so the // per-domain reconstruction-threshold updates map is empty. - let proposed_parameters = - ProposedThresholdParameters::new(threshold_parameters, BTreeMap::new()); + let proposed_parameters = ProposedGovernanceThresholdParameters::new( + threshold_parameters, + BTreeMap::new(), + ); let res = running_state.transition_to_resharing_no_checks(&proposed_parameters); if let Some(resharing) = res { self.protocol_state = ProtocolContractState::Resharing(resharing); @@ -1950,10 +1957,11 @@ impl MpcContract { #[handle_result] #[init] pub fn init( - parameters: dtos::ThresholdParameters, + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + parameters: dtos::GovernanceThresholdParametersCompat, init_config: Option, ) -> Result { - let parameters: ThresholdParameters = parameters.try_into_contract_type()?; + let parameters: GovernanceThresholdParameters = parameters.try_into_contract_type()?; // Log participant count and hash - full parameters exceed NEAR's 16KB log limit at ~100 participants let params_hash = env::sha256_array(borsh::to_vec(¶meters).unwrap()); log!( @@ -2006,10 +2014,11 @@ impl MpcContract { domains: Vec, next_domain_id: u64, keyset: Keyset, - parameters: dtos::ThresholdParameters, + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + parameters: dtos::GovernanceThresholdParametersCompat, init_config: Option, ) -> Result { - let parameters: ThresholdParameters = parameters.try_into_contract_type()?; + let parameters: GovernanceThresholdParameters = parameters.try_into_contract_type()?; // Log participant count and hash - full parameters exceed NEAR's 16KB log limit at ~100 participants let params_hash = env::sha256_array(borsh::to_vec(¶meters).unwrap()); log!( @@ -2032,7 +2041,7 @@ impl MpcContract { )?; } // Keep the GovernanceThreshold at least as large as the largest ReconstructionThreshold. - ThresholdParameters::validate_governance_against_reconstruction( + GovernanceThresholdParameters::validate_governance_against_reconstruction( num_participants, parameters.threshold(), max_reconstruction_threshold(domains.domains()), @@ -2107,8 +2116,11 @@ impl MpcContract { } } - pub fn state(&self) -> near_mpc_contract_interface::types::ProtocolContractState { - (&self.protocol_state).into_dto_type() + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + pub fn state(&self) -> near_mpc_contract_interface::types::ProtocolContractStateCompat { + let state: near_mpc_contract_interface::types::ProtocolContractState = + (&self.protocol_state).into_dto_type(); + state.into() } pub fn metrics(&self) -> near_mpc_contract_interface::types::Metrics { @@ -3039,7 +3051,9 @@ mod tests { attempt: AttemptId::new(), }; let keyset = Keyset::new(epoch_id, vec![key_for_domain]); - let parameters = ThresholdParameters::new(gen_participants(4), Threshold::new(3)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(gen_participants(4), GovernanceThreshold::new(3)) + .unwrap(); let contract = MpcContract::init_running(domains, 1, keyset, (¶meters).into_dto_type(), None) .unwrap(); @@ -4026,8 +4040,9 @@ mod tests { .build(); testing_env!(context); - let threshold = Threshold::new(threshold_value); - let parameters = ThresholdParameters::new(participants.clone(), threshold).unwrap(); + let threshold = GovernanceThreshold::new(threshold_value); + let parameters = + GovernanceThresholdParameters::new(participants.clone(), threshold).unwrap(); let contract = MpcContract::init((¶meters).into_dto_type(), None).unwrap(); (contract, participants, first_participant_id) @@ -4145,8 +4160,11 @@ mod tests { let ProtocolContractState::Running(ref mut state) = contract.protocol_state else { panic!("expected Running"); }; - state.parameters = - ThresholdParameters::new(participants.subset(1..3), Threshold::new(2)).unwrap(); + state.parameters = GovernanceThresholdParameters::new( + participants.subset(1..3), + GovernanceThreshold::new(2), + ) + .unwrap(); } Environment::new(None, Some(env::current_account_id()), None); contract @@ -4209,7 +4227,7 @@ mod tests { contract: &mut MpcContract, first_participant_id: &AccountId, participants: Participants, - threshold: Threshold, + threshold: GovernanceThreshold, ) -> Result<(), Error> { let voting_context = VMContextBuilder::new() .signer_account_id(first_participant_id.clone()) @@ -4218,8 +4236,8 @@ mod tests { .build(); testing_env!(voting_context); - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new(participants, threshold).unwrap(), + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new(participants, threshold).unwrap(), BTreeMap::new(), ); contract.vote_new_parameters(EpochId::new(1), (&proposal).into_dto_type()) @@ -4232,7 +4250,7 @@ mod tests { #[test] fn test_vote_new_parameters_succeeds_with_default_tee_status() { let (mut contract, participants, first_participant_id) = setup_tee_test_contract(3, 2); - let threshold = Threshold::new(2); + let threshold = GovernanceThreshold::new(2); // No attestations submitted - all participants have default TEE status None let result = setup_voting_context_and_vote( @@ -4254,7 +4272,7 @@ mod tests { #[test] fn test_vote_new_parameters_succeeds_when_all_participants_have_valid_tee() { let (mut contract, participants, first_participant_id) = setup_tee_test_contract(3, 2); - let threshold = Threshold::new(2); + let threshold = GovernanceThreshold::new(2); // Submit valid attestations for all participants submit_valid_attestations(&mut contract, &participants, &[0, 1, 2]); @@ -4281,7 +4299,7 @@ mod tests { #[test] fn test_vote_new_parameters_succeeds_after_invalid_attestation_rejected() { let (mut contract, participants, first_participant_id) = setup_tee_test_contract(4, 3); - let threshold = Threshold::new(3); + let threshold = GovernanceThreshold::new(3); // Submit valid attestations for first 3 participants submit_valid_attestations(&mut contract, &participants, &[0, 1, 2]); @@ -4337,8 +4355,11 @@ mod tests { .build() ); - let parameters = - ThresholdParameters::new(participants.clone(), Threshold::new(threshold)).unwrap(); + let parameters = GovernanceThresholdParameters::new( + participants.clone(), + GovernanceThreshold::new(threshold), + ) + .unwrap(); let domain_id = DomainId::default(); let domains = vec![DomainConfig { id: domain_id, @@ -4365,7 +4386,7 @@ mod tests { fn vote_params( contract: &mut MpcContract, signer: &AccountId, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, ) -> Result<(), Error> { testing_env!( VMContextBuilder::new() @@ -4385,8 +4406,8 @@ mod tests { // ...and a proposal raising that domain's reconstruction threshold to 4. let mut per_domain = BTreeMap::new(); per_domain.insert(domain_id, ReconstructionThreshold::new(4)); - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new(participants, Threshold::new(2)).unwrap(), + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(2)).unwrap(), per_domain, ); @@ -4410,8 +4431,12 @@ mod tests { setup_running_contract_with_domain(4, 3, 3); // ...and a proposal that shrinks the participant set to 2 without touching // the per-domain thresholds. - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new(participants.subset(0..2), Threshold::new(2)).unwrap(), + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new( + participants.subset(0..2), + GovernanceThreshold::new(2), + ) + .unwrap(), BTreeMap::new(), ); @@ -4434,8 +4459,11 @@ mod tests { let (mut contract, participants, signer, _domain_id) = setup_running_contract_with_domain(3, 2, 2); // ...and a proposal whose signing threshold (4) exceeds the participant set. - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new_unvalidated(participants, Threshold::new(4)), + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new_unvalidated( + participants, + GovernanceThreshold::new(4), + ), BTreeMap::new(), ); @@ -4458,8 +4486,8 @@ mod tests { // which fits the 5 participants and does not exceed the GovernanceThreshold. let mut per_domain = BTreeMap::new(); per_domain.insert(domain_id, ReconstructionThreshold::new(4)); - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new(participants, Threshold::new(4)).unwrap(), + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(4)).unwrap(), per_domain, ); @@ -4478,8 +4506,8 @@ mod tests { setup_running_contract_with_domain(5, 4, 4); // ...and a proposal lowering the GovernanceThreshold to 3 (valid on its own) // while the domain keeps its reconstruction threshold of 4. - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new(participants, Threshold::new(3)).unwrap(), + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(3)).unwrap(), BTreeMap::new(), ); @@ -4503,9 +4531,9 @@ mod tests { // Given: a participant whose vote is forwarded through another contract, // so signer_account_id (the participant) != predecessor_account_id (the forwarder). let (mut contract, participants, first_participant_id) = setup_tee_test_contract(3, 2); - let threshold = Threshold::new(2); - let proposal = ProposedThresholdParameters::new( - ThresholdParameters::new(participants, threshold).unwrap(), + let threshold = GovernanceThreshold::new(2); + let proposal = ProposedGovernanceThresholdParameters::new( + GovernanceThresholdParameters::new(participants, threshold).unwrap(), BTreeMap::new(), ); @@ -5742,7 +5770,8 @@ mod tests { // given: a running state with 3 participants and threshold of 2 let mut running_state = gen_running_state(1); running_state.parameters = - ThresholdParameters::new(gen_participants(3), Threshold::new(2)).unwrap(); + GovernanceThresholdParameters::new(gen_participants(3), GovernanceThreshold::new(2)) + .unwrap(); let participants = running_state.parameters.participants().participants(); let participant_1 = participants[0].0.clone(); @@ -5955,7 +5984,9 @@ mod tests { const TEE_UPGRADE_DURATION: Duration = Duration::MAX; let participants = gen_participants(PARTICIPANT_COUNT); - let parameters = ThresholdParameters::new(participants.clone(), Threshold::new(2)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(participants.clone(), GovernanceThreshold::new(2)) + .unwrap(); // Set up contract in Running state let domain_id = DomainId::default(); @@ -6042,7 +6073,8 @@ mod tests { .collect(), ); let expected_params = - ThresholdParameters::new(expected_participants, parameters.threshold()).unwrap(); + GovernanceThresholdParameters::new(expected_participants, parameters.threshold()) + .unwrap(); let expected_resharing_state = ResharingContractState { previous_running_state: running_state_before, @@ -6073,9 +6105,9 @@ mod tests { // to 4 participants would leave the GovernanceThreshold above the participant // count, breaking the threshold relation. let participants = gen_participants(PARTICIPANT_COUNT); - let parameters = ThresholdParameters::new( + let parameters = GovernanceThresholdParameters::new( participants.clone(), - Threshold::new( + GovernanceThreshold::new( u64::try_from(PARTICIPANT_COUNT).expect("participant count fits in u64"), ), ) @@ -7410,9 +7442,9 @@ mod tests { let ProtocolContractState::Running(ref mut state) = contract.protocol_state else { panic!("expected Running"); }; - state.parameters = ThresholdParameters::new( + state.parameters = GovernanceThresholdParameters::new( state.parameters.participants().clone(), - Threshold::new(4), + GovernanceThreshold::new(4), ) .unwrap(); for domain in state.domains.domains_mut() { @@ -7477,13 +7509,16 @@ mod tests { let ProtocolContractState::Running(ref mut state) = contract.protocol_state else { panic!("expected Running"); }; - // Reconstruct ThresholdParameters with the updated participants. + // Reconstruct GovernanceThresholdParameters with the updated participants. let mut updated_participants = state.parameters.participants().clone(); updated_participants .update_info(operator4.clone(), new_info) .unwrap(); - state.parameters = - ThresholdParameters::new(updated_participants, Threshold::new(4)).unwrap(); + state.parameters = GovernanceThresholdParameters::new( + updated_participants, + GovernanceThreshold::new(4), + ) + .unwrap(); } contract.recompute_available_foreign_chains(); @@ -7514,9 +7549,9 @@ mod tests { let ProtocolContractState::Running(ref mut state) = contract.protocol_state else { panic!("expected Running"); }; - state.parameters = ThresholdParameters::new( + state.parameters = GovernanceThresholdParameters::new( state.parameters.participants().clone(), - Threshold::new(4), + GovernanceThreshold::new(4), ) .unwrap(); for domain in state.domains.domains_mut() { @@ -7703,7 +7738,7 @@ mod tests { basic_setup_with_protocol(Protocol::CaitSith, DomainPurpose::ForeignTx, &mut OsRng); let participants = participant_account_ids(&contract); - // Threshold (3) participants already cover Bitcoin — but the chain is not whitelisted, + // GovernanceThreshold (3) participants already cover Bitcoin — but the chain is not whitelisted, // so the cache must be empty. for account_id in participants.iter().take(3) { register_foreign_chain_config(&mut contract, account_id, [dtos::ForeignChain::Bitcoin]); @@ -7754,7 +7789,10 @@ mod tests { }; new_participants.remove(&participants[2]); // New Running: participants {0, 1, 3}, threshold 3. Only 0 and 1 cover Bitcoin → 2 < 3. - let new_params = ThresholdParameters::new_unvalidated(new_participants, Threshold::new(3)); + let new_params = GovernanceThresholdParameters::new_unvalidated( + new_participants, + GovernanceThreshold::new(3), + ); contract.protocol_state = ProtocolContractState::Running(RunningContractState::new( domains, keyset, @@ -7793,8 +7831,10 @@ mod tests { let ProtocolContractState::Running(ref mut state) = contract.protocol_state else { panic!("expected Running state"); }; - let proposal = - ProposedThresholdParameters::new(state.parameters.clone(), BTreeMap::new()); + let proposal = ProposedGovernanceThresholdParameters::new( + state.parameters.clone(), + BTreeMap::new(), + ); state .transition_to_resharing_no_checks(&proposal) .expect("contract has at least one domain") @@ -7842,7 +7882,9 @@ mod tests { attempt: AttemptId::new(), }; let keyset = Keyset::new(EpochId::new(0), vec![key_for_domain]); - let parameters = ThresholdParameters::new(gen_participants(4), Threshold::new(3)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(gen_participants(4), GovernanceThreshold::new(3)) + .unwrap(); let mut contract = MpcContract::init_running(domains, 1, keyset, (¶meters).into_dto_type(), None) .unwrap(); @@ -7902,7 +7944,9 @@ mod tests { }) .collect(); let keyset = Keyset::new(EpochId::new(0), keys_for_domains); - let parameters = ThresholdParameters::new(gen_participants(4), Threshold::new(3)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(gen_participants(4), GovernanceThreshold::new(3)) + .unwrap(); let mut contract = MpcContract::init_running(domains, 2, keyset, (¶meters).into_dto_type(), None) .unwrap(); @@ -7959,9 +8003,12 @@ mod tests { new_participants .insert(new_account_id.clone(), new_info) .expect("new participant should be inserted"); - let new_params = - ThresholdParameters::new(new_participants.clone(), Threshold::new(3)).unwrap(); - let new_proposal = ProposedThresholdParameters::new(new_params, BTreeMap::new()); + let new_params = GovernanceThresholdParameters::new( + new_participants.clone(), + GovernanceThreshold::new(3), + ) + .unwrap(); + let new_proposal = ProposedGovernanceThresholdParameters::new(new_params, BTreeMap::new()); let resharing = { let ProtocolContractState::Running(ref mut state) = contract.protocol_state else { diff --git a/crates/contract/src/primitives/domain.rs b/crates/contract/src/primitives/domain.rs index 82b77e2da7..3429826523 100644 --- a/crates/contract/src/primitives/domain.rs +++ b/crates/contract/src/primitives/domain.rs @@ -74,7 +74,7 @@ pub fn validate_domain_reconstruction_threshold( /// The largest `ReconstructionThreshold` across `domains`, or `None` if there are none /// (an empty set imposes no cross-domain lower bound on the GovernanceThreshold). -/// Feeds [`ThresholdParameters::validate_governance_against_reconstruction`](crate::primitives::thresholds::ThresholdParameters::validate_governance_against_reconstruction). +/// Feeds [`GovernanceThresholdParameters::validate_governance_against_reconstruction`](crate::primitives::thresholds::GovernanceThresholdParameters::validate_governance_against_reconstruction). pub fn max_reconstruction_threshold(domains: &[DomainConfig]) -> Option { domains .iter() diff --git a/crates/contract/src/primitives/test_utils.rs b/crates/contract/src/primitives/test_utils.rs index d30e19f30e..e3637e1ffc 100644 --- a/crates/contract/src/primitives/test_utils.rs +++ b/crates/contract/src/primitives/test_utils.rs @@ -5,8 +5,9 @@ use crate::{ key_state::AuthenticatedParticipantId, participants::{ParticipantInfo, Participants}, thresholds::{ - ProposedThresholdParameters, Threshold, ThresholdParameters, - governance_threshold_lower_relative_bound, governance_threshold_upper_relative_bound, + GovernanceThreshold, GovernanceThresholdParameters, + ProposedGovernanceThresholdParameters, governance_threshold_lower_relative_bound, + governance_threshold_upper_relative_bound, }, }, }; @@ -189,7 +190,7 @@ pub fn gen_seed() -> [u8; 32] { seed } -pub fn gen_threshold_params(max_n: usize) -> ThresholdParameters { +pub fn gen_threshold_params(max_n: usize) -> GovernanceThresholdParameters { // Lower bound is 3 (not 2) so the produced parameters are compatible with // every protocol — `DamgardEtAl` requires `n >= 2t - 1`, which forces // `n >= 3` even at the minimum `t = 2`. @@ -198,14 +199,15 @@ pub fn gen_threshold_params(max_n: usize) -> ThresholdParameters { let k_min = governance_threshold_lower_relative_bound(n as u64) as usize; let k_max = governance_threshold_upper_relative_bound(n as u64) as usize; let k = rng.gen_range(k_min..k_max + 1); - ThresholdParameters::new(gen_participants(n), Threshold::new(k as u64)).unwrap() + GovernanceThresholdParameters::new(gen_participants(n), GovernanceThreshold::new(k as u64)) + .unwrap() } /// Like [`gen_threshold_params`] but wrapped as a proposal with an empty /// (no-change) set of per-domain threshold updates — the shape /// `vote_new_parameters` accepts. -pub fn gen_proposed_threshold_params(max_n: usize) -> ProposedThresholdParameters { - ProposedThresholdParameters::new(gen_threshold_params(max_n), BTreeMap::new()) +pub fn gen_proposed_threshold_params(max_n: usize) -> ProposedGovernanceThresholdParameters { + ProposedGovernanceThresholdParameters::new(gen_threshold_params(max_n), BTreeMap::new()) } /// Infer a default purpose from the protocol. diff --git a/crates/contract/src/primitives/threshold_votes.rs b/crates/contract/src/primitives/threshold_votes.rs index 59e6442855..df9ed7eaec 100644 --- a/crates/contract/src/primitives/threshold_votes.rs +++ b/crates/contract/src/primitives/threshold_votes.rs @@ -1,4 +1,4 @@ -use crate::primitives::thresholds::ProposedThresholdParameters; +use crate::primitives::thresholds::ProposedGovernanceThresholdParameters; use crate::primitives::{key_state::AuthenticatedAccountId, participants::Participants}; use near_sdk::{log, near}; use std::collections::BTreeMap; @@ -9,15 +9,16 @@ use std::collections::BTreeMap; // once this type is moved out of RunningContractState (which requires Clone + PartialEq + JSON). #[near(serializers=[borsh, json])] #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct ThresholdParametersVotes { - pub(crate) proposal_by_account: BTreeMap, +pub struct GovernanceThresholdParametersVotes { + pub(crate) proposal_by_account: + BTreeMap, } -impl ThresholdParametersVotes { +impl GovernanceThresholdParametersVotes { /// return the number of votes for `proposal` cast by members of `participants` pub fn n_votes( &self, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, participants: &Participants, ) -> u64 { u64::try_from( @@ -40,7 +41,7 @@ impl ThresholdParametersVotes { /// vote). pub fn vote( &mut self, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, participant: AuthenticatedAccountId, ) -> u64 { if self @@ -62,7 +63,7 @@ impl ThresholdParametersVotes { #[cfg(test)] mod tests { - use super::ThresholdParametersVotes; + use super::GovernanceThresholdParametersVotes; use crate::primitives::{ key_state::AuthenticatedAccountId, participants::Participants, @@ -83,7 +84,7 @@ mod tests { let participant = AuthenticatedAccountId::new(&participants).expect("expected authentication"); let params = gen_proposed_threshold_params(30); - let mut votes = ThresholdParametersVotes::default(); + let mut votes = GovernanceThresholdParametersVotes::default(); assert_eq!(votes.vote(¶ms, participant.clone()), 1); assert_eq!(votes.n_votes(¶ms, &participants), 1); let params2 = gen_proposed_threshold_params(30); @@ -136,7 +137,7 @@ mod tests { let proposal_b = base.with_per_domain_thresholds(updates_b); // When each voter casts a different proposal - let mut votes = ThresholdParametersVotes::default(); + let mut votes = GovernanceThresholdParametersVotes::default(); votes.vote(&proposal_a, auth_p0); votes.vote(&proposal_b, auth_p1); @@ -166,7 +167,7 @@ mod tests { }; let params = gen_proposed_threshold_params(30); - let mut votes = ThresholdParametersVotes::default(); + let mut votes = GovernanceThresholdParametersVotes::default(); votes.vote(¶ms, auth_p0); votes.vote(¶ms, auth_p1); assert_eq!(votes.n_votes(¶ms, &old_participants), 2); diff --git a/crates/contract/src/primitives/thresholds.rs b/crates/contract/src/primitives/thresholds.rs index 42e870dbd9..f097993020 100644 --- a/crates/contract/src/primitives/thresholds.rs +++ b/crates/contract/src/primitives/thresholds.rs @@ -5,7 +5,7 @@ use near_mpc_contract_interface::types::{DomainId, ReconstructionThreshold}; use near_sdk::near; use std::collections::BTreeMap; -pub use near_mpc_contract_interface::types::Threshold; +pub use near_mpc_contract_interface::types::GovernanceThreshold; /// Minimum absolute threshold required. const MIN_THRESHOLD_ABSOLUTE: u64 = 2; @@ -24,24 +24,27 @@ pub(crate) fn governance_threshold_upper_relative_bound(n: u64) -> u64 { n } -/// Stores the threshold key parameters: the owners of key shares -/// (`participants`) and the cryptographic `threshold`. This is the stored, -/// always-current shape. +/// Stores the governance parameters: the current `participants` and the +/// governance `threshold` (the number of participants that must agree to +/// approve a governance action). This is the stored, always-current shape. #[near(serializers=[borsh, json])] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct ThresholdParameters { +pub struct GovernanceThresholdParameters { participants: Participants, - threshold: Threshold, + threshold: GovernanceThreshold, } -impl ThresholdParameters { - /// Constructs Threshold parameters from `participants` and `threshold` if the +impl GovernanceThresholdParameters { + /// Constructs GovernanceThreshold parameters from `participants` and `threshold` if the /// threshold meets the absolute and relative validation criteria. - pub fn new(participants: Participants, threshold: Threshold) -> Result { - match Self::validate_governance_threshold(participants.len() as u64, threshold) { - Ok(_) => Ok(ThresholdParameters { + pub fn new( + participants: Participants, + governance_threshold: GovernanceThreshold, + ) -> Result { + match Self::validate_governance_threshold(participants.len() as u64, governance_threshold) { + Ok(_) => Ok(GovernanceThresholdParameters { participants, - threshold, + threshold: governance_threshold, }), Err(err) => Err(err), } @@ -53,7 +56,7 @@ impl ThresholdParameters { /// - threshold can not exceed the number of shares `n_shares`. /// - threshold must be at least 60% of the number of shares (rounded upwards). /// - threshold must not exceed the upper bound (now set to 100%) - fn validate_governance_threshold(n_shares: u64, k: Threshold) -> Result<(), Error> { + fn validate_governance_threshold(n_shares: u64, k: GovernanceThreshold) -> Result<(), Error> { if k.value() > n_shares { return Err(InvalidThreshold::MaxRequirementFailed { max: n_shares, @@ -91,7 +94,7 @@ impl ThresholdParameters { /// a ReconstructionThreshold, or the participant set changes. pub fn validate_governance_against_reconstruction( num_participants: u64, - governance: Threshold, + governance: GovernanceThreshold, max_reconstruction_threshold: Option, ) -> Result<(), Error> { Self::validate_governance_threshold(num_participants, governance)?; @@ -115,7 +118,10 @@ impl ThresholdParameters { /// Validates the incoming proposal against the current one, ensuring it's allowed based on the /// current participants and threshold settings. Also verifies the TEE quote of the participant /// who submitted the proposal. - pub fn validate_incoming_proposal(&self, proposal: &ThresholdParameters) -> Result<(), Error> { + pub fn validate_incoming_proposal( + &self, + proposal: &GovernanceThresholdParameters, + ) -> Result<(), Error> { // ensure the proposed threshold parameters are valid: // if performance issue, inline and merge with loop below proposal.validate()?; @@ -182,7 +188,7 @@ impl ThresholdParameters { Ok(()) } - pub fn threshold(&self) -> Threshold { + pub fn threshold(&self) -> GovernanceThreshold { self.threshold } /// Returns the map of Participants. @@ -192,10 +198,13 @@ impl ThresholdParameters { /// Test-only: builds parameters without threshold validation. #[cfg(feature = "test-utils")] - pub fn new_unvalidated(participants: Participants, threshold: Threshold) -> Self { - ThresholdParameters { + pub fn new_unvalidated( + participants: Participants, + governance_threshold: GovernanceThreshold, + ) -> Self { + GovernanceThresholdParameters { participants, - threshold, + threshold: governance_threshold, } } @@ -214,25 +223,25 @@ impl ThresholdParameters { } } -/// A proposal submitted to `vote_new_parameters`: the new [`ThresholdParameters`] +/// A proposal submitted to `vote_new_parameters`: the new [`GovernanceThresholdParameters`] /// plus per-domain `ReconstructionThreshold` updates applied to the /// [`super::domain::DomainRegistry`] when resharing completes. An empty map keeps /// the current thresholds; a populated map must reference only existing domains /// (validated in `RunningContractState::process_new_parameters_proposal`). #[near(serializers=[borsh, json])] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct ProposedThresholdParameters { - parameters: ThresholdParameters, +pub struct ProposedGovernanceThresholdParameters { + parameters: GovernanceThresholdParameters, #[serde(default)] per_domain_thresholds: BTreeMap, } -impl ProposedThresholdParameters { +impl ProposedGovernanceThresholdParameters { pub fn new( - parameters: ThresholdParameters, + parameters: GovernanceThresholdParameters, per_domain_thresholds: BTreeMap, ) -> Self { - ProposedThresholdParameters { + ProposedGovernanceThresholdParameters { parameters, per_domain_thresholds, } @@ -250,7 +259,7 @@ impl ProposedThresholdParameters { } /// The proposed stored parameters (participants + threshold). - pub fn parameters(&self) -> &ThresholdParameters { + pub fn parameters(&self) -> &GovernanceThresholdParameters { &self.parameters } @@ -265,7 +274,7 @@ impl ProposedThresholdParameters { } /// Delegates to the proposed parameters' threshold. - pub fn threshold(&self) -> Threshold { + pub fn threshold(&self) -> GovernanceThreshold { self.parameters.threshold() } } @@ -279,8 +288,8 @@ mod tests { participants::{ParticipantId, Participants}, test_utils::{gen_participant, gen_participants, gen_threshold_params}, thresholds::{ - ProposedThresholdParameters, Threshold, ThresholdParameters, - governance_threshold_lower_relative_bound, + GovernanceThreshold, GovernanceThresholdParameters, + ProposedGovernanceThresholdParameters, governance_threshold_lower_relative_bound, governance_threshold_upper_relative_bound, }, }, @@ -295,7 +304,7 @@ mod tests { fn test_threshold() { for _ in 0..20 { let v = rand::thread_rng().r#gen::(); - let x = Threshold::new(v); + let x = GovernanceThreshold::new(v); assert_eq!(v, x.value()); } } @@ -306,16 +315,26 @@ mod tests { let min_threshold = governance_threshold_lower_relative_bound(n); let max_threshold = governance_threshold_upper_relative_bound(n); for k in 0..min_threshold { - let _ = ThresholdParameters::validate_governance_threshold(n, Threshold::new(k)) - .unwrap_err(); + let _ = GovernanceThresholdParameters::validate_governance_threshold( + n, + GovernanceThreshold::new(k), + ) + .unwrap_err(); } for k in min_threshold..=max_threshold { - ThresholdParameters::validate_governance_threshold(n, Threshold::new(k)).unwrap(); + GovernanceThresholdParameters::validate_governance_threshold( + n, + GovernanceThreshold::new(k), + ) + .unwrap(); } // Anything above the upper cap (up to and beyond n) must be rejected. for k in (max_threshold + 1)..=(n + 1) { - let _ = ThresholdParameters::validate_governance_threshold(n, Threshold::new(k)) - .unwrap_err(); + let _ = GovernanceThresholdParameters::validate_governance_threshold( + n, + GovernanceThreshold::new(k), + ) + .unwrap_err(); } } @@ -327,19 +346,23 @@ mod tests { let participants = gen_participants(n); for k in 1..min_threshold { - let invalid_threshold = Threshold::new(k as u64); - let _ = ThresholdParameters::new(participants.clone(), invalid_threshold).unwrap_err(); + let invalid_threshold = GovernanceThreshold::new(k as u64); + let _ = GovernanceThresholdParameters::new(participants.clone(), invalid_threshold) + .unwrap_err(); } // Thresholds above the upper cap (including up to n and beyond) are rejected. for k in (max_threshold + 1)..=(n + 1) { - let invalid_threshold = Threshold::new(k as u64); - let _ = ThresholdParameters::new(participants.clone(), invalid_threshold).unwrap_err(); + let invalid_threshold = GovernanceThreshold::new(k as u64); + let _ = GovernanceThresholdParameters::new(participants.clone(), invalid_threshold) + .unwrap_err(); } for k in min_threshold..=max_threshold { - let threshold = Threshold::new(k as u64); - let tp = ThresholdParameters::new(participants.clone(), threshold); - let tp = tp.expect("Threshold parameters should be valid for the given threshold"); - tp.validate().expect("Threshold parameters should validate"); + let threshold = GovernanceThreshold::new(k as u64); + let tp = GovernanceThresholdParameters::new(participants.clone(), threshold); + let tp = + tp.expect("GovernanceThreshold parameters should be valid for the given threshold"); + tp.validate() + .expect("GovernanceThreshold parameters should validate"); assert_eq!(tp.threshold(), threshold); assert_eq!(tp.participants.len(), participants.len()); assert_eq!(participants, *tp.participants()); @@ -365,7 +388,11 @@ mod tests { let upper = governance_threshold_upper_relative_bound(n); assert!(upper >= lower, "empty window at n={n}: [{lower}, {upper}]"); // The clamped boundary value must validate. - ThresholdParameters::validate_governance_threshold(n, Threshold::new(upper)).unwrap(); + GovernanceThresholdParameters::validate_governance_threshold( + n, + GovernanceThreshold::new(upper), + ) + .unwrap(); } } @@ -374,11 +401,11 @@ mod tests { { // Given 10 participants and a governance threshold of 6 (a valid value on its own). let n = 10; - let governance = Threshold::new(6); + let governance = GovernanceThreshold::new(6); // When the largest reconstruction threshold is 7 (above governance). // Then the relation is rejected. assert_matches!( - ThresholdParameters::validate_governance_against_reconstruction( + GovernanceThresholdParameters::validate_governance_against_reconstruction( n, governance, Some(ReconstructionThreshold::new(7)) @@ -391,13 +418,13 @@ mod tests { )) ); // ...but is accepted when governance meets or exceeds the max reconstruction threshold. - ThresholdParameters::validate_governance_against_reconstruction( + GovernanceThresholdParameters::validate_governance_against_reconstruction( n, governance, Some(ReconstructionThreshold::new(6)), ) .unwrap(); - ThresholdParameters::validate_governance_against_reconstruction( + GovernanceThresholdParameters::validate_governance_against_reconstruction( n, governance, Some(ReconstructionThreshold::new(5)), @@ -423,7 +450,8 @@ mod tests { .participants .subset(0..params.threshold.value() as usize); new_participants.add_random_participants_till_n(params.participants.len()); - let proposal = ThresholdParameters::new_unvalidated(new_participants, params.threshold); + let proposal = + GovernanceThresholdParameters::new_unvalidated(new_participants, params.threshold); assert_matches!( params.validate_incoming_proposal(&proposal), @@ -436,11 +464,14 @@ mod tests { // Proposal with less than threshold number of shared participants should not be allowed. // Use a fixed-size set to ensure the threshold arithmetic is predictable. let large_params = - ThresholdParameters::new(gen_participants(10), Threshold::new(6)).unwrap(); + GovernanceThresholdParameters::new(gen_participants(10), GovernanceThreshold::new(6)) + .unwrap(); let mut new_participants = large_params.participants.subset(0..5); // 5 < threshold of 6 new_participants.add_random_participants_till_n(10); - let proposal = - ThresholdParameters::new_unvalidated(new_participants, large_params.threshold); + let proposal = GovernanceThresholdParameters::new_unvalidated( + new_participants, + large_params.threshold, + ); assert_eq!( large_params .validate_incoming_proposal(&proposal) @@ -453,13 +484,16 @@ mod tests { .participants .subset(0..params.threshold.value() as usize); new_participants.add_random_participants_till_n(50); - let proposal = ThresholdParameters::new_unvalidated(new_participants, params.threshold); + let proposal = + GovernanceThresholdParameters::new_unvalidated(new_participants, params.threshold); let _ = params.validate_incoming_proposal(&proposal).unwrap_err(); } #[test] fn test_proposal_participant_id_changed() { - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(3)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(3)) + .unwrap(); // Take an existing participant and change their ID let (account, old_id, info) = params.participants.participants()[0].clone(); @@ -474,7 +508,7 @@ mod tests { .collect(); new_participants_vec.push((account.clone(), wrong_id, info)); - let proposal = ThresholdParameters::new_unvalidated( + let proposal = GovernanceThresholdParameters::new_unvalidated( Participants::init(ParticipantId(wrong_id.get() + 1), new_participants_vec), params.threshold, ); @@ -490,7 +524,9 @@ mod tests { #[test] fn test_proposal_participant_info_changed() { - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(3)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(3)) + .unwrap(); // Take an existing participant and change their info let (account, id, _) = params.participants.participants()[0].clone(); @@ -505,7 +541,7 @@ mod tests { .collect(); new_participants_vec.push((account.clone(), id, changed_info)); - let proposal = ThresholdParameters::new_unvalidated( + let proposal = GovernanceThresholdParameters::new_unvalidated( Participants::init(params.participants.next_id(), new_participants_vec), params.threshold, ); @@ -519,7 +555,9 @@ mod tests { #[test] fn test_proposal_new_participant_reuses_old_id() { - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(3)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(3)) + .unwrap(); // Remove one old participant and add a new one that reuses their ID. // This way the proposal passes basic validate() (no duplicates), but @@ -537,7 +575,7 @@ mod tests { .collect(); new_participants_vec.push((new_account.clone(), reused_id, new_info)); - let proposal = ThresholdParameters::new_unvalidated( + let proposal = GovernanceThresholdParameters::new_unvalidated( Participants::init(params.participants.next_id(), new_participants_vec), params.threshold, ); @@ -555,7 +593,9 @@ mod tests { fn test_proposal_non_contiguous_new_ids_fail() { // Test that the lowest new id equals to the `next_id` of the previous set. // Use a high threshold so adding one participant doesn't violate the 60% rule. - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(5)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(5)) + .unwrap(); let wrong_id = params.participants.next_id().0 + 1; @@ -567,7 +607,7 @@ mod tests { .unwrap(); let tampered_params = - ThresholdParameters::new_unvalidated(tampered_participants, params.threshold); + GovernanceThresholdParameters::new_unvalidated(tampered_participants, params.threshold); assert_eq!( params @@ -579,7 +619,9 @@ mod tests { #[test] fn test_proposal_non_unique_ids() { - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(5)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(5)) + .unwrap(); // Create proposal with duplicate participants (doubled list) let tampered_participants = Participants::init( @@ -594,9 +636,9 @@ mod tests { ); // Use a valid threshold for the doubled size so validate_governance_threshold passes // and the duplicate check in participants.validate() is reached. - let tampered_params = ThresholdParameters::new_unvalidated( + let tampered_params = GovernanceThresholdParameters::new_unvalidated( tampered_participants, - Threshold::new(6), // 60% of 10 = 6 + GovernanceThreshold::new(6), // 60% of 10 = 6 ); assert_eq!( params @@ -608,13 +650,16 @@ mod tests { #[test] fn test_remove_only() { - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(3)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(3)) + .unwrap(); let new_participants = params .participants .subset(0..params.threshold.value() as usize); - let new_params = ThresholdParameters::new(new_participants, params.threshold).unwrap(); + let new_params = + GovernanceThresholdParameters::new(new_participants, params.threshold).unwrap(); let result = params.validate_incoming_proposal(&new_params); result.unwrap(); @@ -623,13 +668,16 @@ mod tests { #[test] fn test_simultaneous_remove_and_insert() { let n = 5; - let params = ThresholdParameters::new(gen_participants(n), Threshold::new(3)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(n), GovernanceThreshold::new(3)) + .unwrap(); let mut new_participants = params.participants.clone(); new_participants.add_random_participants_till_n(n + 2); let new_participants = new_participants.subset(2..n + 2); - let new_params = ThresholdParameters::new(new_participants, params.threshold).unwrap(); + let new_params = + GovernanceThresholdParameters::new(new_participants, params.threshold).unwrap(); let result = params.validate_incoming_proposal(&new_params); result.unwrap(); @@ -639,7 +687,9 @@ mod tests { fn test_new_participant_id_too_high() { // When proposal's next_id is higher than max_id + 1, it should fail with // NewParticipantIdsTooHigh. - let params = ThresholdParameters::new(gen_participants(5), Threshold::new(5)).unwrap(); + let params = + GovernanceThresholdParameters::new(gen_participants(5), GovernanceThreshold::new(5)) + .unwrap(); let next_id = params.participants.next_id(); // Add one new participant with the correct next_id, but set the proposal's @@ -649,12 +699,12 @@ mod tests { new_participants_vec.push((new_account, next_id, new_info)); // 6 participants with threshold 5 - let proposal = ThresholdParameters::new_unvalidated( + let proposal = GovernanceThresholdParameters::new_unvalidated( Participants::init( ParticipantId(next_id.get() + 2), // too high: should be next_id + 1 new_participants_vec, ), - Threshold::new(5), + GovernanceThreshold::new(5), ); assert_eq!( params.validate_incoming_proposal(&proposal).unwrap_err(), @@ -669,7 +719,7 @@ mod tests { let mut updates = BTreeMap::new(); updates.insert(DomainId(0), ReconstructionThreshold::new(3)); updates.insert(DomainId(2), ReconstructionThreshold::new(4)); - let proposal = ProposedThresholdParameters::new(params.clone(), updates.clone()); + let proposal = ProposedGovernanceThresholdParameters::new(params.clone(), updates.clone()); // When / Then the accessors expose the wrapped parameters and the updates, // and the participants/threshold delegates match the wrapped parameters. @@ -686,7 +736,7 @@ mod tests { // stripped out — the shape an older client predating per-domain // reconstruction thresholds would submit to `vote_new_parameters`. let params = gen_threshold_params(10); - let proposal = ProposedThresholdParameters::new(params, BTreeMap::new()); + let proposal = ProposedGovernanceThresholdParameters::new(params, BTreeMap::new()); let mut json = serde_json::to_value(&proposal).unwrap(); json.as_object_mut() .unwrap() @@ -694,7 +744,7 @@ mod tests { .expect("empty map should still serialize as a field"); // When deserializing the field-less JSON - let parsed: ProposedThresholdParameters = serde_json::from_value(json).unwrap(); + let parsed: ProposedGovernanceThresholdParameters = serde_json::from_value(json).unwrap(); // Then the missing field defaults to an empty (no-change) map and the // rest of the proposal is preserved. @@ -709,11 +759,11 @@ mod tests { let mut updates = BTreeMap::new(); updates.insert(DomainId(0), ReconstructionThreshold::new(3)); updates.insert(DomainId(2), ReconstructionThreshold::new(4)); - let proposal = ProposedThresholdParameters::new(params, updates); + let proposal = ProposedGovernanceThresholdParameters::new(params, updates); // When serializing to JSON and back let json = serde_json::to_string(&proposal).unwrap(); - let parsed: ProposedThresholdParameters = serde_json::from_str(&json).unwrap(); + let parsed: ProposedGovernanceThresholdParameters = serde_json::from_str(&json).unwrap(); // Then the proposal round-trips unchanged assert_eq!(parsed, proposal); @@ -726,11 +776,11 @@ mod tests { let mut updates = BTreeMap::new(); updates.insert(DomainId(0), ReconstructionThreshold::new(3)); updates.insert(DomainId(2), ReconstructionThreshold::new(4)); - let proposal = ProposedThresholdParameters::new(params, updates); + let proposal = ProposedGovernanceThresholdParameters::new(params, updates); // When serializing to borsh and back let bytes = borsh::to_vec(&proposal).unwrap(); - let parsed: ProposedThresholdParameters = borsh::from_slice(&bytes).unwrap(); + let parsed: ProposedGovernanceThresholdParameters = borsh::from_slice(&bytes).unwrap(); // Then the proposal round-trips unchanged assert_eq!(parsed, proposal); diff --git a/crates/contract/src/snapshots/mpc_contract__tests__mpc_contract_borsh_schema_has_not_changed.snap b/crates/contract/src/snapshots/mpc_contract__tests__mpc_contract_borsh_schema_has_not_changed.snap index 73ee9018ba..aebed64fe9 100644 --- a/crates/contract/src/snapshots/mpc_contract__tests__mpc_contract_borsh_schema_has_not_changed.snap +++ b/crates/contract/src/snapshots/mpc_contract__tests__mpc_contract_borsh_schema_has_not_changed.snap @@ -15,10 +15,10 @@ BorshSchemaContainer { "ParticipantInfo", ], }, - "(AuthenticatedAccountId, ProposedThresholdParameters)": Tuple { + "(AuthenticatedAccountId, ProposedGovernanceThresholdParameters)": Tuple { elements: [ "AuthenticatedAccountId", - "ProposedThresholdParameters", + "ProposedGovernanceThresholdParameters", ], }, "(AuthenticatedParticipantId, DockerImageHash)": Tuple { @@ -123,10 +123,10 @@ BorshSchemaContainer { ], ), }, - "BTreeMap": Sequence { + "BTreeMap": Sequence { length_width: 4, length_range: 0..=4294967295, - elements: "(AuthenticatedAccountId, ProposedThresholdParameters)", + elements: "(AuthenticatedAccountId, ProposedGovernanceThresholdParameters)", }, "BTreeMap": Sequence { length_width: 4, @@ -368,6 +368,37 @@ BorshSchemaContainer { ], ), }, + "GovernanceThreshold": Struct { + fields: UnnamedFields( + [ + "u64", + ], + ), + }, + "GovernanceThresholdParameters": Struct { + fields: NamedFields( + [ + ( + "participants", + "Participants", + ), + ( + "threshold", + "GovernanceThreshold", + ), + ], + ), + }, + "GovernanceThresholdParametersVotes": Struct { + fields: NamedFields( + [ + ( + "proposal_by_account", + "BTreeMap", + ), + ], + ), + }, "HashSet": Sequence { length_width: 4, length_range: 0..=4294967295, @@ -436,7 +467,7 @@ BorshSchemaContainer { ), ( "parameters", - "ThresholdParameters", + "GovernanceThresholdParameters", ), ( "instance", @@ -838,12 +869,12 @@ BorshSchemaContainer { ], ), }, - "ProposedThresholdParameters": Struct { + "ProposedGovernanceThresholdParameters": Struct { fields: NamedFields( [ ( "parameters", - "ThresholdParameters", + "GovernanceThresholdParameters", ), ( "per_domain_thresholds", @@ -1087,11 +1118,11 @@ BorshSchemaContainer { ), ( "parameters", - "ThresholdParameters", + "GovernanceThresholdParameters", ), ( "parameters_votes", - "ThresholdParametersVotes", + "GovernanceThresholdParametersVotes", ), ( "add_domains_votes", @@ -1187,37 +1218,6 @@ BorshSchemaContainer { ], ), }, - "Threshold": Struct { - fields: UnnamedFields( - [ - "u64", - ], - ), - }, - "ThresholdParameters": Struct { - fields: NamedFields( - [ - ( - "participants", - "Participants", - ), - ( - "threshold", - "Threshold", - ), - ], - ), - }, - "ThresholdParametersVotes": Struct { - fields: NamedFields( - [ - ( - "proposal_by_account", - "BTreeMap", - ), - ], - ), - }, "Timestamp": Struct { fields: UnnamedFields( [ diff --git a/crates/contract/src/state.rs b/crates/contract/src/state.rs index 9d0ba80080..80f4981afd 100644 --- a/crates/contract/src/state.rs +++ b/crates/contract/src/state.rs @@ -11,7 +11,9 @@ use crate::primitives::{ domain::DomainRegistry, key_state::{AuthenticatedParticipantId, EpochId, KeyEventId}, participants::Participants, - thresholds::{ProposedThresholdParameters, Threshold, ThresholdParameters}, + thresholds::{ + GovernanceThreshold, GovernanceThresholdParameters, ProposedGovernanceThresholdParameters, + }, }; use initializing::InitializingContractState; use near_account_id::AccountId; @@ -49,7 +51,7 @@ impl ProtocolContractState { _ => Err(InvalidState::ProtocolStateNotRunningNorResharing.into()), } } - pub fn threshold(&self) -> Result { + pub fn threshold(&self) -> Result { match self { ProtocolContractState::Initializing(state) => { Ok(state.generating_key.proposed_parameters().threshold()) @@ -126,7 +128,7 @@ impl ProtocolContractState { pub fn vote_new_parameters( &mut self, prospective_epoch_id: EpochId, - proposed_parameters: &ProposedThresholdParameters, + proposed_parameters: &ProposedGovernanceThresholdParameters, ) -> Result, Error> { match self { ProtocolContractState::Running(state) => { @@ -178,7 +180,7 @@ impl ProtocolContractState { pub(super) fn threshold_parameters( &self, - ) -> Result<&ThresholdParameters, ContractNotInitialized> { + ) -> Result<&GovernanceThresholdParameters, ContractNotInitialized> { match self { ProtocolContractState::NotInitialized => Err(ContractNotInitialized), ProtocolContractState::Initializing(initializing_contract_state) => { @@ -196,7 +198,7 @@ impl ProtocolContractState { } /// Active threshold parameters, panicking on [`ContractNotInitialized`]. - pub(super) fn threshold_parameters_or_panic(&self) -> &ThresholdParameters { + pub(super) fn threshold_parameters_or_panic(&self) -> &GovernanceThresholdParameters { self.threshold_parameters() .unwrap_or_else(|ContractNotInitialized| env::panic_str("contract is not initialized")) } diff --git a/crates/contract/src/state/initializing.rs b/crates/contract/src/state/initializing.rs index 78ebdba13b..7484e6fef6 100644 --- a/crates/contract/src/state/initializing.rs +++ b/crates/contract/src/state/initializing.rs @@ -157,7 +157,7 @@ pub mod tests { use crate::primitives::test_utils::{ NUM_PROTOCOLS, bogus_ed25519_public_key_extended, gen_account_id, }; - use crate::primitives::threshold_votes::ThresholdParametersVotes; + use crate::primitives::threshold_votes::GovernanceThresholdParametersVotes; use crate::state::key_event::tests::find_leader; use crate::state::running::RunningContractState; use crate::state::test_utils::gen_initializing_state; @@ -309,7 +309,7 @@ pub mod tests { assert_eq!(running_state.domains, state.domains); assert_eq!( running_state.parameters_votes, - ThresholdParametersVotes::default() + GovernanceThresholdParametersVotes::default() ); assert_eq!(running_state.add_domains_votes, AddDomainsVotes::default()); } diff --git a/crates/contract/src/state/key_event.rs b/crates/contract/src/state/key_event.rs index 1744c5d307..8a5f35ba71 100644 --- a/crates/contract/src/state/key_event.rs +++ b/crates/contract/src/state/key_event.rs @@ -4,7 +4,7 @@ use crate::errors::KeyEventError; use crate::errors::VoteError; use crate::primitives::key_state::KeyEventId; use crate::primitives::key_state::{AttemptId, EpochId}; -use crate::primitives::thresholds::ThresholdParameters; +use crate::primitives::thresholds::GovernanceThresholdParameters; use crate::state::AuthenticatedParticipantId; use near_mpc_contract_interface::types::{DomainConfig, DomainId}; use near_sdk::BlockHeight; @@ -20,7 +20,7 @@ pub struct KeyEvent { /// The domain that we're generating or resharing the key for. domain: DomainConfig, /// The participants and threshold that shall participate in the key event. - parameters: ThresholdParameters, + parameters: GovernanceThresholdParameters, /// If exists, the current attempt to generate or reshare the key. instance: Option, /// The ID of the next attempt to generate or reshare the key. @@ -31,7 +31,7 @@ impl KeyEvent { pub fn new( epoch_id: EpochId, domain: DomainConfig, - proposed_parameters: ThresholdParameters, + proposed_parameters: GovernanceThresholdParameters, ) -> Self { KeyEvent { epoch_id, @@ -47,7 +47,7 @@ impl KeyEvent { pub fn from_raw( epoch_id: EpochId, domain: DomainConfig, - parameters: ThresholdParameters, + parameters: GovernanceThresholdParameters, instance: Option, next_attempt_id: AttemptId, ) -> Self { @@ -105,7 +105,7 @@ impl KeyEvent { self.domain.clone() } - pub fn proposed_parameters(&self) -> &ThresholdParameters { + pub fn proposed_parameters(&self) -> &GovernanceThresholdParameters { &self.parameters } diff --git a/crates/contract/src/state/resharing.rs b/crates/contract/src/state/resharing.rs index 756255c797..683089e4c4 100644 --- a/crates/contract/src/state/resharing.rs +++ b/crates/contract/src/state/resharing.rs @@ -6,7 +6,7 @@ use crate::errors::{Error, InvalidParameters}; use crate::primitives::key_state::{ AuthenticatedAccountId, EpochId, KeyEventId, KeyForDomain, Keyset, }; -use crate::primitives::thresholds::ProposedThresholdParameters; +use crate::primitives::thresholds::ProposedGovernanceThresholdParameters; use near_account_id::AccountId; use near_mpc_contract_interface::types::{DomainId, ReconstructionThreshold}; use near_sdk::near; @@ -18,8 +18,8 @@ use near_sdk::near; /// This state is reached by calling vote_new_parameters from the Running state. /// /// This state keeps the previous running state because: -/// - The previous running state's ThresholdParameters are needed in order to facilitate the -/// possible re-proposal of a new ThresholdParameters, in case the currently proposed set of +/// - The previous running state's GovernanceThresholdParameters are needed in order to facilitate the +/// possible re-proposal of a new GovernanceThresholdParameters, in case the currently proposed set of /// participants are no longer all online. For tracking the votes we also use the same /// tracking structure in the running state. /// - The previous running state's keys are needed to copy the public keys. @@ -56,7 +56,7 @@ impl ResharingContractState { pub fn vote_new_parameters( &mut self, prospective_epoch_id: EpochId, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, ) -> Result, Error> { let expected_prospective_epoch_id = self.prospective_epoch_id().next(); if prospective_epoch_id != expected_prospective_epoch_id { @@ -217,8 +217,11 @@ pub mod tests { domain::AddDomainsVotes, key_state::{AttemptId, KeyEventId}, test_utils::gen_account_id, - threshold_votes::ThresholdParametersVotes, - thresholds::{ProposedThresholdParameters, Threshold, ThresholdParameters}, + threshold_votes::GovernanceThresholdParametersVotes, + thresholds::{ + GovernanceThreshold, GovernanceThresholdParameters, + ProposedGovernanceThresholdParameters, + }, }, state::test_utils::{gen_resharing_state, gen_running_state_with_params}, }; @@ -355,7 +358,7 @@ pub mod tests { assert_eq!(running_state.domains, state.previous_running_state.domains); assert_eq!( running_state.parameters_votes, - ThresholdParametersVotes::default() + GovernanceThresholdParametersVotes::default() ); assert_eq!(running_state.add_domains_votes, AddDomainsVotes::default()); } @@ -416,17 +419,21 @@ pub mod tests { // Reproposing with new_params_1 should succeed, but then reproposing with new_params_2 // should be rejected, since all re-proposals must be valid against the original. let mut new_participants_1 = old_participants.clone(); - // Threshold valid for the grown set (1.5x): old_len sits within + // GovernanceThreshold valid for the grown set (1.5x): old_len sits within // [ceil(0.6 * 1.5n), 1.5n]. - let new_threshold = Threshold::new(old_participants.len() as u64); + let new_threshold = GovernanceThreshold::new(old_participants.len() as u64); new_participants_1.add_random_participants_till_n((old_participants.len() * 3).div_ceil(2)); let new_participants_2 = new_participants_1 .subset(new_participants_1.len() - old_participants.len()..new_participants_1.len()); - let new_params_1 = ThresholdParameters::new(new_participants_1, new_threshold).unwrap(); - let new_params_2 = ThresholdParameters::new(new_participants_2, new_threshold).unwrap(); + let new_params_1 = + GovernanceThresholdParameters::new(new_participants_1, new_threshold).unwrap(); + let new_params_2 = + GovernanceThresholdParameters::new(new_participants_2, new_threshold).unwrap(); // Proposals carry an empty (no-change) set of per-domain threshold updates. - let proposed_1 = ProposedThresholdParameters::new(new_params_1.clone(), BTreeMap::new()); - let proposed_2 = ProposedThresholdParameters::new(new_params_2.clone(), BTreeMap::new()); + let proposed_1 = + ProposedGovernanceThresholdParameters::new(new_params_1.clone(), BTreeMap::new()); + let proposed_2 = + ProposedGovernanceThresholdParameters::new(new_params_2.clone(), BTreeMap::new()); state .previous_running_state .parameters @@ -492,7 +499,7 @@ pub mod tests { /// `per_domain_thresholds` updates must be applied to the new /// `DomainRegistry`. The updates live only on the proposal / /// resharing state, so the stored `RunningContractState.parameters` - /// (a plain `ThresholdParameters`) cannot carry them at all. + /// (a plain `GovernanceThresholdParameters`) cannot carry them at all. /// /// Pins a participant set with GovernanceThreshold >= 3 (unchanged across the /// resharing) and moves the domain to that threshold — the max reconstruction the @@ -511,7 +518,8 @@ pub mod tests { assert_ne!(new_threshold, original_threshold); let mut threshold_updates = BTreeMap::new(); threshold_updates.insert(domain_id, new_threshold); - let proposal = ProposedThresholdParameters::new(current_params.clone(), threshold_updates); + let proposal = + ProposedGovernanceThresholdParameters::new(current_params.clone(), threshold_updates); // Drive the proposal to acceptance so we transition into Resharing // through the real vote path (which also exercises the fail-fast @@ -550,7 +558,7 @@ pub mod tests { } // Then the new running state's registry carries the updated - // threshold. (The stored parameters are a plain `ThresholdParameters` + // threshold. (The stored parameters are a plain `GovernanceThresholdParameters` // and structurally cannot carry pending threshold updates.) let new_running = new_running.expect("resharing should have transitioned to Running"); assert_eq!( @@ -577,7 +585,8 @@ pub mod tests { assert_ne!(frost_new_threshold, default_threshold); let mut threshold_updates = BTreeMap::new(); threshold_updates.insert(frost_id, frost_new_threshold); - let proposal = ProposedThresholdParameters::new(current_params.clone(), threshold_updates); + let proposal = + ProposedGovernanceThresholdParameters::new(current_params.clone(), threshold_updates); // Drive the proposal to acceptance via the real vote path. let prospective_epoch_id = running.prospective_epoch_id(); diff --git a/crates/contract/src/state/running.rs b/crates/contract/src/state/running.rs index 4b33c96cf8..0ff73a51f6 100644 --- a/crates/contract/src/state/running.rs +++ b/crates/contract/src/state/running.rs @@ -8,8 +8,8 @@ use crate::primitives::{ validate_domain_reconstruction_threshold, }, key_state::{AuthenticatedAccountId, AuthenticatedParticipantId, EpochId, Keyset}, - threshold_votes::ThresholdParametersVotes, - thresholds::{ProposedThresholdParameters, ThresholdParameters}, + threshold_votes::GovernanceThresholdParametersVotes, + thresholds::{GovernanceThresholdParameters, ProposedGovernanceThresholdParameters}, }; use near_account_id::AccountId; use near_mpc_contract_interface::types::DomainConfig; @@ -33,9 +33,9 @@ pub struct RunningContractState { /// distributed key, so that the nodes can identify which local keyshare to use. pub keyset: Keyset, /// The current participants and threshold. - pub parameters: ThresholdParameters, + pub parameters: GovernanceThresholdParameters, /// Votes for proposals for a new set of participants and threshold. - pub parameters_votes: ThresholdParametersVotes, + pub parameters_votes: GovernanceThresholdParametersVotes, /// Votes for proposals to add new domains. pub add_domains_votes: AddDomainsVotes, /// The previous epoch id for a resharing state that was cancelled. @@ -48,7 +48,7 @@ impl RunningContractState { pub fn new( domains: DomainRegistry, keyset: Keyset, - parameters: ThresholdParameters, + parameters: GovernanceThresholdParameters, add_domains_votes: AddDomainsVotes, ) -> Self { let remaining_add_domain_votes = @@ -57,7 +57,7 @@ impl RunningContractState { domains, keyset, parameters, - parameters_votes: ThresholdParametersVotes::default(), + parameters_votes: GovernanceThresholdParametersVotes::default(), add_domains_votes: remaining_add_domain_votes, previously_cancelled_resharing_epoch_id: None, } @@ -65,7 +65,7 @@ impl RunningContractState { pub fn transition_to_resharing_no_checks( &mut self, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, ) -> Option { if let Some(first_domain) = self.domains.get_domain_by_index(0) { let epoch_id = self.prospective_epoch_id(); @@ -106,7 +106,7 @@ impl RunningContractState { pub fn vote_new_parameters( &mut self, prospective_epoch_id: EpochId, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, ) -> Result, Error> { let expected_prospective_epoch_id = self.prospective_epoch_id(); @@ -142,7 +142,7 @@ impl RunningContractState { /// Returns true if all participants of the proposed parameters voted for it. pub(super) fn process_new_parameters_proposal( &mut self, - proposal: &ProposedThresholdParameters, + proposal: &ProposedGovernanceThresholdParameters, ) -> Result { // ensure the proposal is valid against the current parameters self.parameters @@ -182,7 +182,7 @@ impl RunningContractState { // The GovernanceThreshold must dominate every domain's effective ReconstructionThreshold; // enforced here so the state transition is self-contained (single source of truth). - ThresholdParameters::validate_governance_against_reconstruction( + GovernanceThresholdParameters::validate_governance_against_reconstruction( new_num_participants, proposal.threshold(), max_reconstruction_threshold(&effective_domains), @@ -227,7 +227,7 @@ impl RunningContractState { // Keep trust assumptions consistent: a domain must never require more shares to // reconstruct than the GovernanceThreshold demands to govern. Route through the // canonical helper so the cross-domain invariant has a single source of truth. - ThresholdParameters::validate_governance_against_reconstruction( + GovernanceThresholdParameters::validate_governance_against_reconstruction( num_participants, self.parameters.threshold(), max_reconstruction_threshold(&domains), @@ -268,7 +268,7 @@ pub mod running_tests { use crate::errors::{Error, InvalidThreshold}; use crate::primitives::domain::AddDomainsVotes; use crate::primitives::test_utils::{NUM_PROTOCOLS, gen_proposed_threshold_params}; - use crate::primitives::threshold_votes::ThresholdParametersVotes; + use crate::primitives::threshold_votes::GovernanceThresholdParametersVotes; use crate::state::key_event::tests::Environment; use crate::state::test_utils::{ gen_running_state, gen_running_state_with_params, gen_valid_params_proposal, @@ -380,7 +380,10 @@ pub mod running_tests { // If there are no domains, we should transition directly to Running with a higher // epoch ID, not resharing. assert_eq!(state.keyset.epoch_id, original_epoch_id.next()); - assert_eq!(state.parameters_votes, ThresholdParametersVotes::default()); + assert_eq!( + state.parameters_votes, + GovernanceThresholdParametersVotes::default() + ); assert_eq!(state.add_domains_votes, AddDomainsVotes::default()); } else { let resharing = resharing.unwrap(); diff --git a/crates/contract/src/state/test_utils.rs b/crates/contract/src/state/test_utils.rs index 41d39f3d4e..a8faffb94d 100644 --- a/crates/contract/src/state/test_utils.rs +++ b/crates/contract/src/state/test_utils.rs @@ -12,7 +12,7 @@ use crate::primitives::{ participants::{ParticipantId, Participants}, test_utils::{gen_participant, gen_participants, gen_threshold_params}, thresholds::{ - ProposedThresholdParameters, Threshold, ThresholdParameters, + GovernanceThreshold, GovernanceThresholdParameters, ProposedGovernanceThresholdParameters, governance_threshold_lower_relative_bound, }, }; @@ -21,7 +21,9 @@ use std::collections::BTreeMap; /// Generates a valid resharing proposal from `params` with empty (no-change) /// per-domain threshold updates. -pub fn gen_valid_params_proposal(params: &ThresholdParameters) -> ProposedThresholdParameters { +pub fn gen_valid_params_proposal( + params: &GovernanceThresholdParameters, +) -> ProposedGovernanceThresholdParameters { let mut rng = rand::thread_rng(); let current_k = params.threshold().value() as usize; let current_n = params.participants().len(); @@ -62,9 +64,11 @@ pub fn gen_valid_params_proposal(params: &ThresholdParameters) -> ProposedThresh } let threshold = governance_threshold_lower_relative_bound(new_participants.len() as u64); - let parameters = ThresholdParameters::new(new_participants, Threshold::new(threshold)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(new_participants, GovernanceThreshold::new(threshold)) + .unwrap(); // Empty per-domain threshold updates (no change); see the doc comment. - ProposedThresholdParameters::new(parameters, BTreeMap::new()) + ProposedGovernanceThresholdParameters::new(parameters, BTreeMap::new()) } /// Generates a resharing state with the given number of domains. @@ -106,9 +110,9 @@ pub fn gen_running_state_with_params( num_participants: usize, governance_threshold: u64, ) -> RunningContractState { - let threshold_parameters = ThresholdParameters::new( + let threshold_parameters = GovernanceThresholdParameters::new( gen_participants(num_participants), - Threshold::new(governance_threshold), + GovernanceThreshold::new(governance_threshold), ) .expect("valid threshold parameters"); diff --git a/crates/contract/src/tee/verifier_votes.rs b/crates/contract/src/tee/verifier_votes.rs index 37c0372c2c..2c26058d21 100644 --- a/crates/contract/src/tee/verifier_votes.rs +++ b/crates/contract/src/tee/verifier_votes.rs @@ -9,7 +9,7 @@ use crate::{ primitives::{ key_state::AuthenticatedParticipantId, participants::Participants, - thresholds::ThresholdParameters, + thresholds::GovernanceThresholdParameters, votes::{ProposalHash, ProposalHashEncoding, Votes}, }, storage_keys::StorageKey, @@ -65,7 +65,7 @@ impl TeeVerifierVotes { &mut self, proposal: VerifierChangeProposal, participant: AuthenticatedParticipantId, - threshold_parameters: &ThresholdParameters, + threshold_parameters: &GovernanceThresholdParameters, ) -> Result, Error> { let protocol_threshold = threshold_parameters.threshold().value(); let participants = threshold_parameters.participants(); @@ -111,10 +111,16 @@ impl TeeVerifierVotes { mod tests { use super::*; use crate::primitives::test_utils::{gen_authenticated_participants, gen_participants}; - use mpc_primitives::Threshold; + use mpc_primitives::GovernanceThreshold; - fn threshold_params(participants: &Participants, threshold: u64) -> ThresholdParameters { - ThresholdParameters::new_unvalidated(participants.clone(), Threshold::new(threshold)) + fn threshold_params( + participants: &Participants, + threshold: u64, + ) -> GovernanceThresholdParameters { + GovernanceThresholdParameters::new_unvalidated( + participants.clone(), + GovernanceThreshold::new(threshold), + ) } fn proposal(account: &str, hash_byte: u8) -> VerifierChangeProposal { @@ -130,7 +136,7 @@ mod tests { threshold: u64, ) -> ( Participants, - ThresholdParameters, + GovernanceThresholdParameters, Vec, TeeVerifierVotes, ) { diff --git a/crates/contract/tests/inprocess/attestation_submission.rs b/crates/contract/tests/inprocess/attestation_submission.rs index 3a790ab3db..663f0cc157 100644 --- a/crates/contract/tests/inprocess/attestation_submission.rs +++ b/crates/contract/tests/inprocess/attestation_submission.rs @@ -8,13 +8,16 @@ use mpc_contract::{ key_state::EpochId, participants::{ParticipantId, ParticipantInfo}, test_utils::{create_node_id, gen_participants, node_id_for}, - thresholds::{ProposedThresholdParameters, Threshold, ThresholdParameters}, + thresholds::{ + GovernanceThreshold, GovernanceThresholdParameters, + ProposedGovernanceThresholdParameters, + }, }, tee::tee_state::{AttestationSubmissionError, NodeId}, }; use near_mpc_contract_interface::{ deposits::SUBMIT_PARTICIPANT_INFO_DEPOSIT_MILLINEAR, - types::{Attestation, InitConfig, MockAttestation, ProtocolContractState}, + types::{Attestation, InitConfig, MockAttestation, ProtocolContractStateCompat}, }; use std::collections::BTreeMap; @@ -97,8 +100,9 @@ impl TestSetupBuilder { let participants = gen_participants(participant_count); let participants_list = participants.participants().clone(); - let parameters = ThresholdParameters::new(participants, Threshold::new(threshold)) - .expect("failed to create threshold parameters"); + let parameters = + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(threshold)) + .expect("failed to create threshold parameters"); let contract = common::init_contract(¶meters, self.init_config); let mut setup = TestSetup { @@ -125,15 +129,20 @@ impl TestSetupBuilder { for node_id in threshold_nodes { testing_env!(common::participant_context(&node_id.account_id)); - let proposal = - ProposedThresholdParameters::new(parameters.clone(), BTreeMap::new()); + let proposal = ProposedGovernanceThresholdParameters::new( + parameters.clone(), + BTreeMap::new(), + ); setup .contract .vote_new_parameters(EpochId::new(6), proposal.into()) .unwrap(); } - assert_matches!(setup.contract.state(), ProtocolContractState::Running(_)); + assert_matches!( + setup.contract.state(), + ProtocolContractStateCompat::Running(_) + ); } }; @@ -357,7 +366,7 @@ fn clean_tee_status__should_not_touch_attestations() { assert_matches!( setup.contract.state(), - ProtocolContractState::Running(r) + ProtocolContractStateCompat::Running(r) if r.parameters.participants.participants.len() == PARTICIPANT_COUNT ); @@ -373,7 +382,7 @@ fn clean_tee_status__should_not_touch_attestations() { // State should remain Running with same participant count assert_matches!( setup.contract.state(), - ProtocolContractState::Running(r) + ProtocolContractStateCompat::Running(r) if r.parameters.participants.participants.len() == PARTICIPANT_COUNT ); } diff --git a/crates/contract/tests/inprocess/common.rs b/crates/contract/tests/inprocess/common.rs index fee8523840..91376cf9b9 100644 --- a/crates/contract/tests/inprocess/common.rs +++ b/crates/contract/tests/inprocess/common.rs @@ -4,12 +4,12 @@ use mpc_contract::{ primitives::{ key_state::{AttemptId, EpochId, KeyForDomain, Keyset}, participants::{ParticipantId, ParticipantInfo}, - thresholds::ThresholdParameters, + thresholds::GovernanceThresholdParameters, }, }; use near_account_id::AccountId; use near_mpc_contract_interface::types::{ - DomainConfig, DomainId, DomainPurpose, InitConfig, Protocol, ProtocolContractState, + DomainConfig, DomainId, DomainPurpose, InitConfig, Protocol, ProtocolContractStateCompat, ReconstructionThreshold, }; use near_sdk::{NearToken, VMContext, test_utils::VMContextBuilder, testing_env}; @@ -36,7 +36,7 @@ pub fn participant_context_with_deposit(account_id: &AccountId, deposit: NearTok /// Initializes a `Running` contract with a single Sign domain from `parameters`. pub fn init_contract( - parameters: &ThresholdParameters, + parameters: &GovernanceThresholdParameters, init_config: Option, ) -> MpcContract { let near_public_key = @@ -84,5 +84,8 @@ pub fn transition_to_initializing( }]) .unwrap(); } - assert_matches!(contract.state(), ProtocolContractState::Initializing(_)); + assert_matches!( + contract.state(), + ProtocolContractStateCompat::Initializing(_) + ); } diff --git a/crates/contract/tests/inprocess/update_participant_url.rs b/crates/contract/tests/inprocess/update_participant_url.rs index 4998826593..f536c4d788 100644 --- a/crates/contract/tests/inprocess/update_participant_url.rs +++ b/crates/contract/tests/inprocess/update_participant_url.rs @@ -9,12 +9,12 @@ use mpc_contract::{ errors::{Error, InvalidState}, primitives::{ test_utils::gen_participants, - thresholds::{Threshold, ThresholdParameters}, + thresholds::{GovernanceThreshold, GovernanceThresholdParameters}, }, }; use near_account_id::AccountId; use near_mpc_contract_interface::types::{ - ParticipantInfo as DtoParticipantInfo, ProtocolContractState, + ParticipantInfo as DtoParticipantInfo, ProtocolContractStateCompat, }; use near_sdk::{NearToken, testing_env}; use std::str::FromStr; @@ -22,7 +22,7 @@ use std::str::FromStr; use assert_matches::assert_matches; fn participant_info(contract: &MpcContract, account_id: &AccountId) -> DtoParticipantInfo { - let ProtocolContractState::Running(running) = contract.state() else { + let ProtocolContractStateCompat::Running(running) = contract.state() else { panic!("expected Running state"); }; running @@ -40,7 +40,8 @@ fn update_participant_url__should_change_url_keeping_tls_key_and_id() { // Given let participants = gen_participants(3); let participant_list = participants.participants().clone(); - let parameters = ThresholdParameters::new(participants, Threshold::new(2)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(2)).unwrap(); let mut contract = init_contract(¶meters, None); let (account_id, _, original_info) = participant_list[0].clone(); let (other_account, _, other_info) = participant_list[1].clone(); @@ -68,7 +69,8 @@ fn update_participant_url__should_reject_when_no_deposit_attached() { // Given let participants = gen_participants(3); let participant_list = participants.participants().clone(); - let parameters = ThresholdParameters::new(participants, Threshold::new(2)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(2)).unwrap(); let mut contract = init_contract(¶meters, None); let (account_id, _, _) = participant_list[0].clone(); @@ -82,7 +84,8 @@ fn update_participant_url__should_reject_when_no_deposit_attached() { fn update_participant_url__should_reject_non_participant() { // Given let participants = gen_participants(3); - let parameters = ThresholdParameters::new(participants, Threshold::new(2)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(2)).unwrap(); let mut contract = init_contract(¶meters, None); let outsider = AccountId::from_str("outsider.near").unwrap(); @@ -102,7 +105,8 @@ fn update_participant_url__should_reject_when_not_running() { // Given let participants = gen_participants(3); let participant_list = participants.participants().clone(); - let parameters = ThresholdParameters::new(participants, Threshold::new(2)).unwrap(); + let parameters = + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(2)).unwrap(); let mut contract = init_contract(¶meters, None); transition_to_initializing(&mut contract, &participant_list); let (account_id, _, _) = participant_list[0].clone(); diff --git a/crates/contract/tests/sandbox/common.rs b/crates/contract/tests/sandbox/common.rs index bb8b7549ff..0186ef4d9d 100644 --- a/crates/contract/tests/sandbox/common.rs +++ b/crates/contract/tests/sandbox/common.rs @@ -7,7 +7,7 @@ use crate::sandbox::utils::{ sign_utils::{PendingSignRequest, make_and_submit_requests}, }; use digest::Digest; -use dtos::ProtocolContractState; +use dtos::ProtocolContractStateCompat; use k256::ecdsa::SigningKey; use mpc_contract::{ crypto_shared::types::PublicKeyExtended, @@ -15,7 +15,10 @@ use mpc_contract::{ key_state::{AttemptId, EpochId, KeyForDomain, Keyset}, participants::{ParticipantInfo, Participants}, test_utils::{bogus_ed25519_public_key, infer_purpose_from_protocol}, - thresholds::{ProposedThresholdParameters, Threshold, ThresholdParameters}, + thresholds::{ + GovernanceThreshold, GovernanceThresholdParameters, + ProposedGovernanceThresholdParameters, + }, }, tee::tee_state::NodeId, update::UpdateId, @@ -113,15 +116,15 @@ pub async fn init_with_wasm(wasm: &[u8]) -> (Worker, Contract) { } /// Creates threshold parameters with 60% threshold (rounded up). -pub fn make_threshold_params(participants: &Participants) -> ThresholdParameters { - let threshold = Threshold::new(((participants.len() as f64) * 0.6).ceil() as u64); - ThresholdParameters::new(participants.clone(), threshold).unwrap() +pub fn make_threshold_params(participants: &Participants) -> GovernanceThresholdParameters { + let threshold = GovernanceThreshold::new(((participants.len() as f64) * 0.6).ceil() as u64); + GovernanceThresholdParameters::new(participants.clone(), threshold).unwrap() } /// Initialize the contract with the given parameters. pub async fn init_contract( contract: &Contract, - params: ThresholdParameters, + params: GovernanceThresholdParameters, init_config: Option, ) -> ExecutionSuccess { let result = contract @@ -144,7 +147,7 @@ pub async fn init_contract_running( domains: Vec, next_domain_id: u64, keyset: Keyset, - params: ThresholdParameters, + params: GovernanceThresholdParameters, init_config: Option, ) -> ExecutionSuccess { let result = contract @@ -373,7 +376,7 @@ pub async fn propose_and_vote_contract_binary( .await .expect("state request succeeds"); - let _state: ProtocolContractState = state_request_execution + let _state: ProtocolContractStateCompat = state_request_execution .json() .expect("state is deserializable."); @@ -512,10 +515,10 @@ pub async fn call_contract_key_generation( let mut domain_keys = vec![]; let existing_domains = { - let state: ProtocolContractState = get_state(contract).await; + let state: ProtocolContractStateCompat = get_state(contract).await; match state { - ProtocolContractState::Running(state) => state.domains.domains.len(), - _ => panic!("ProtocolContractState must be Running"), + ProtocolContractStateCompat::Running(state) => state.domains.domains.len(), + _ => panic!("ProtocolContractStateCompat must be Running"), } }; @@ -523,9 +526,9 @@ pub async fn call_contract_key_generation( .await .unwrap(); - let state: ProtocolContractState = get_state(contract).await; + let state: ProtocolContractStateCompat = get_state(contract).await; match state { - ProtocolContractState::Initializing(state) => { + ProtocolContractStateCompat::Initializing(state) => { assert_eq!( state.domains.domains.len(), existing_domains + domains_to_add.len() @@ -556,9 +559,9 @@ pub async fn call_contract_key_generation( .unwrap(); } - let state: ProtocolContractState = get_state(contract).await; + let state: ProtocolContractStateCompat = get_state(contract).await; match state { - ProtocolContractState::Running(state) => { + ProtocolContractStateCompat::Running(state) => { assert_eq!(state.keyset.epoch_id.0, expected_epoch_id); assert_eq!( state.domains.domains.len(), @@ -592,10 +595,11 @@ pub async fn execute_key_generation_and_add_random_state( // 1. Submit a threshold proposal (raise threshold to threshold + 1). let dummy_threshold_parameters = - ThresholdParameters::new(participants, Threshold::new(threshold.0 + 1)).unwrap(); + GovernanceThresholdParameters::new(participants, GovernanceThreshold::new(threshold.0 + 1)) + .unwrap(); let dummy_proposal = json!({ "prospective_epoch_id": 1, - "proposal": ProposedThresholdParameters::new( + "proposal": ProposedGovernanceThresholdParameters::new( dummy_threshold_parameters, BTreeMap::new(), ), diff --git a/crates/contract/tests/sandbox/sign.rs b/crates/contract/tests/sandbox/sign.rs index 9cf11513c9..25659b87c4 100644 --- a/crates/contract/tests/sandbox/sign.rs +++ b/crates/contract/tests/sandbox/sign.rs @@ -14,7 +14,7 @@ use mpc_contract::{ errors, primitives::{ participants::Participants, - thresholds::{Threshold, ThresholdParameters}, + thresholds::{GovernanceThreshold, GovernanceThresholdParameters}, }, }; use near_account_id::AccountId; @@ -298,8 +298,9 @@ async fn test_contract_initialization() -> anyhow::Result<()> { // Empty candidates should fail. let participants = Participants::new(); - let threshold = Threshold::new(0); - let proposed_parameters = ThresholdParameters::new_unvalidated(participants, threshold); + let threshold = GovernanceThreshold::new(0); + let proposed_parameters = + GovernanceThresholdParameters::new_unvalidated(participants, threshold); let result = contract .call(method_names::INIT) .args_json(serde_json::json!({ @@ -313,7 +314,7 @@ async fn test_contract_initialization() -> anyhow::Result<()> { ); let proposed_parameters = - ThresholdParameters::new(candidates(None), Threshold::new(3)).unwrap(); + GovernanceThresholdParameters::new(candidates(None), GovernanceThreshold::new(3)).unwrap(); let result = contract .call(method_names::INIT) .args_json(serde_json::json!({ diff --git a/crates/contract/tests/sandbox/tee.rs b/crates/contract/tests/sandbox/tee.rs index 3255d4cbe8..564dfd83e0 100644 --- a/crates/contract/tests/sandbox/tee.rs +++ b/crates/contract/tests/sandbox/tee.rs @@ -247,8 +247,8 @@ pub async fn get_participants(contract: &Contract) -> Result { .max_gas() .transact() .await?; - let value: dtos::ProtocolContractState = state.json()?; - let dtos::ProtocolContractState::Running(running) = value else { + let value: dtos::ProtocolContractStateCompat = state.json()?; + let dtos::ProtocolContractStateCompat::Running(running) = value else { panic!("Expected running state") }; Ok(running.parameters.participants.participants.len()) @@ -811,7 +811,7 @@ async fn test_verify_tee_expired_attestation_triggers_resharing() -> Result<()> // Verify contract transitioned to Resharing state let state_after_verify = get_state(&contract).await; let prospective_epoch_id = match &state_after_verify { - dtos::ProtocolContractState::Resharing(resharing_state) => { + dtos::ProtocolContractStateCompat::Resharing(resharing_state) => { resharing_state.resharing_key.epoch_id } _ => panic!("expected Resharing state, got {:?}", state_after_verify), @@ -942,7 +942,7 @@ async fn verify_tee__should_keep_participants_and_stop_signing_when_kickout_drop // Then: no participant is kicked out — the contract stays Running with all participants. let state_after_verify = get_state(&contract).await; - let dtos::ProtocolContractState::Running(running_after) = &state_after_verify else { + let dtos::ProtocolContractStateCompat::Running(running_after) = &state_after_verify else { panic!("expected Running state (no resharing), got {state_after_verify:?}"); }; assert_eq!( diff --git a/crates/contract/tests/sandbox/tee_cleanup_after_resharing.rs b/crates/contract/tests/sandbox/tee_cleanup_after_resharing.rs index 8b0803a3b7..a46ef2eda4 100644 --- a/crates/contract/tests/sandbox/tee_cleanup_after_resharing.rs +++ b/crates/contract/tests/sandbox/tee_cleanup_after_resharing.rs @@ -18,7 +18,7 @@ use anyhow::Result; use mpc_contract::{ primitives::{ participants::Participants, test_utils::bogus_ed25519_public_key, - thresholds::ThresholdParameters, + thresholds::GovernanceThresholdParameters, }, tee::tee_state::NodeId, }; @@ -121,9 +121,9 @@ async fn reshare__should_leave_valid_non_participant_attestations_in_storage() - } let post_reshare_participants = build_sandbox_node_ids(&new_participants, &mpc_signer_accounts); - let new_threshold_parameters = ThresholdParameters::new( + let new_threshold_parameters = GovernanceThresholdParameters::new( new_participants, - mpc_contract::primitives::thresholds::Threshold::new(threshold.0), + mpc_contract::primitives::thresholds::GovernanceThreshold::new(threshold.0), ) .unwrap(); @@ -230,9 +230,9 @@ async fn reshare__should_evict_expired_attestations_via_post_reshare_sweep() -> ) .expect("Failed to insert participant"); } - let new_threshold_parameters = ThresholdParameters::new( + let new_threshold_parameters = GovernanceThresholdParameters::new( new_participants, - mpc_contract::primitives::thresholds::Threshold::new(threshold.0), + mpc_contract::primitives::thresholds::GovernanceThreshold::new(threshold.0), ) .unwrap(); do_resharing( diff --git a/crates/contract/tests/sandbox/update_votes_cleanup_after_resharing.rs b/crates/contract/tests/sandbox/update_votes_cleanup_after_resharing.rs index ee8f4adb75..db63a1e05c 100644 --- a/crates/contract/tests/sandbox/update_votes_cleanup_after_resharing.rs +++ b/crates/contract/tests/sandbox/update_votes_cleanup_after_resharing.rs @@ -11,7 +11,7 @@ use crate::sandbox::{ }; use anyhow::Result; use mpc_contract::{ - primitives::{participants::Participants, thresholds::ThresholdParameters}, + primitives::{participants::Participants, thresholds::GovernanceThresholdParameters}, update::UpdateId, }; use near_account_id::AccountId; @@ -96,9 +96,9 @@ async fn update_votes_from_kicked_out_participants_are_cleared_after_resharing() .map_err(|e| anyhow::anyhow!("Failed to insert participant: {}", e))?; } - let new_threshold_parameters = ThresholdParameters::new( + let new_threshold_parameters = GovernanceThresholdParameters::new( new_participants, - mpc_contract::primitives::thresholds::Threshold::new(threshold.0), + mpc_contract::primitives::thresholds::GovernanceThreshold::new(threshold.0), ) .map_err(|e| anyhow::anyhow!("{}", e))?; let prospective_epoch_id = dtos::EpochId(6); @@ -163,8 +163,8 @@ async fn add_domain_votes_from_kicked_out_participants_are_cleared_after_reshari let threshold = assert_running_return_threshold(&contract).await; let next_domain_id = { - let state: dtos::ProtocolContractState = get_state(&contract).await; - let dtos::ProtocolContractState::Running(running) = &state else { + let state: dtos::ProtocolContractStateCompat = get_state(&contract).await; + let dtos::ProtocolContractStateCompat::Running(running) = &state else { panic!("Expected running state"); }; running.domains.next_domain_id @@ -184,8 +184,8 @@ async fn add_domain_votes_from_kicked_out_participants_are_cleared_after_reshari ) .await?; - let state: dtos::ProtocolContractState = get_state(&contract).await; - let dtos::ProtocolContractState::Running(running) = &state else { + let state: dtos::ProtocolContractStateCompat = get_state(&contract).await; + let dtos::ProtocolContractStateCompat::Running(running) = &state else { panic!("Expected running state"); }; assert_eq!(running.add_domains_votes.proposal_by_account.len(), 2); @@ -210,9 +210,9 @@ async fn add_domain_votes_from_kicked_out_participants_are_cleared_after_reshari .map_err(|e| anyhow::anyhow!("Failed to insert participant: {}", e))?; } - let new_threshold_parameters = ThresholdParameters::new( + let new_threshold_parameters = GovernanceThresholdParameters::new( new_participants, - mpc_contract::primitives::thresholds::Threshold::new(threshold.0), + mpc_contract::primitives::thresholds::GovernanceThreshold::new(threshold.0), ) .map_err(|e| anyhow::anyhow!("{}", e))?; let prospective_epoch_id = dtos::EpochId(6); @@ -226,8 +226,8 @@ async fn add_domain_votes_from_kicked_out_participants_are_cleared_after_reshari .await?; // Then - let final_state: dtos::ProtocolContractState = get_state(&contract).await; - let dtos::ProtocolContractState::Running(final_running) = &final_state else { + let final_state: dtos::ProtocolContractStateCompat = get_state(&contract).await; + let dtos::ProtocolContractStateCompat::Running(final_running) = &final_state else { panic!("Expected running state after resharing"); }; diff --git a/crates/contract/tests/sandbox/upgrade_from_current_contract.rs b/crates/contract/tests/sandbox/upgrade_from_current_contract.rs index 913b36ba73..d35d556d24 100644 --- a/crates/contract/tests/sandbox/upgrade_from_current_contract.rs +++ b/crates/contract/tests/sandbox/upgrade_from_current_contract.rs @@ -17,7 +17,7 @@ use crate::sandbox::{ }; use mpc_contract::update::UpdateId; use near_mpc_contract_interface::method_names; -use near_mpc_contract_interface::types::{ProposeUpdateArgs, ProtocolContractState}; +use near_mpc_contract_interface::types::{ProposeUpdateArgs, ProtocolContractStateCompat}; use near_workspaces::types::NearToken; use rand_core::OsRng; @@ -149,7 +149,7 @@ async fn test_propose_update_config() { .unwrap() .json() .unwrap(); - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; // check that each participant can vote on a singular proposal and have it reflect changes: let first_proposal = &proposals[0]; @@ -242,7 +242,7 @@ async fn test_invalid_contract_deploy() { .unwrap(); dbg!(&execution); - let state: ProtocolContractState = execution.json().unwrap(); + let state: ProtocolContractStateCompat = execution.json().unwrap(); dbg!(state); } @@ -302,7 +302,7 @@ async fn test_propose_update_contract_many() { } // Let's check that we can call into the state and see all the proposals. - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; dbg!(state); } diff --git a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs index c41c6c6e94..686e1fb04f 100644 --- a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs +++ b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs @@ -14,13 +14,13 @@ use crate::sandbox::{ use mpc_contract::primitives::{ key_state::{EpochId, Keyset}, participants::Participants, - thresholds::{Threshold, ThresholdParameters}, + thresholds::{GovernanceThreshold, GovernanceThresholdParameters}, }; use near_account_id::AccountId; use near_mpc_bounded_collections::NonEmptyBTreeSet; use near_mpc_contract_interface::method_names; use near_mpc_contract_interface::types as dtos; -use near_mpc_contract_interface::types::ProtocolContractState; +use near_mpc_contract_interface::types::ProtocolContractStateCompat; use near_mpc_contract_interface::types::{ CKDResponse, DomainConfig, DomainPurpose, Protocol, ReconstructionThreshold, }; @@ -52,8 +52,9 @@ async fn init_old_contract( let (accounts, participants) = gen_accounts(worker, number_of_participants).await; let threshold = ((participants.len() as f64) * 0.6).ceil() as u64; - let threshold = Threshold::new(threshold); - let threshold_parameters = ThresholdParameters::new(participants.clone(), threshold).unwrap(); + let threshold = GovernanceThreshold::new(threshold); + let threshold_parameters = + GovernanceThresholdParameters::new(participants.clone(), threshold).unwrap(); contract .call(method_names::INIT) @@ -178,11 +179,11 @@ async fn propose_upgrade_from_production_to_current_binary( ) .await; - let state_pre_upgrade: ProtocolContractState = get_state(&contract).await; + let state_pre_upgrade: ProtocolContractStateCompat = get_state(&contract).await; propose_and_vote_contract_binary(&accounts, &contract, current_contract()).await; - let state_post_upgrade: ProtocolContractState = get_state(&contract).await; + let state_post_upgrade: ProtocolContractStateCompat = get_state(&contract).await; assert_eq!( state_pre_upgrade, state_post_upgrade, @@ -229,7 +230,7 @@ async fn upgrade_preserves_state_and_requests( ) .await; - let state_pre_upgrade: ProtocolContractState = get_state(&contract).await; + let state_pre_upgrade: ProtocolContractStateCompat = get_state(&contract).await; assert!(healthcheck(&contract).await.unwrap()); let contract = upgrade_to_new(contract).await.unwrap(); @@ -237,7 +238,7 @@ async fn upgrade_preserves_state_and_requests( .await .expect("❌ migration() failed"); - let state_post_upgrade: ProtocolContractState = get_state(&contract).await; + let state_post_upgrade: ProtocolContractStateCompat = get_state(&contract).await; assert_eq!( state_pre_upgrade, state_post_upgrade, @@ -353,7 +354,7 @@ async fn upgrade_allows_new_request_types( ) .await; - let state_pre_upgrade: ProtocolContractState = get_state(&contract).await; + let state_pre_upgrade: ProtocolContractStateCompat = get_state(&contract).await; assert!(healthcheck(&contract).await.unwrap()); let contract = upgrade_to_new(contract).await.unwrap(); @@ -361,7 +362,7 @@ async fn upgrade_allows_new_request_types( .await .expect("❌ migration() failed"); - let state_post_upgrade: ProtocolContractState = get_state(&contract).await; + let state_post_upgrade: ProtocolContractStateCompat = get_state(&contract).await; assert_eq!( state_pre_upgrade, state_post_upgrade, @@ -440,9 +441,9 @@ async fn init_running_rejects_external_callers_pre_initialization() { let number_of_participants = 2; let (accounts, participants) = gen_accounts(&worker, number_of_participants).await; - let threshold_parameters = ThresholdParameters::new( + let threshold_parameters = GovernanceThresholdParameters::new( participants.clone(), - Threshold::new(number_of_participants as u64), + GovernanceThreshold::new(number_of_participants as u64), ) .unwrap(); diff --git a/crates/contract/tests/sandbox/utils/initializing_utils.rs b/crates/contract/tests/sandbox/utils/initializing_utils.rs index 946e162c27..c117b49118 100644 --- a/crates/contract/tests/sandbox/utils/initializing_utils.rs +++ b/crates/contract/tests/sandbox/utils/initializing_utils.rs @@ -36,14 +36,14 @@ pub async fn start_keygen_instance( ) -> anyhow::Result<()> { let state = get_state(contract).await; let active = match &state { - dtos::ProtocolContractState::Initializing(s) => { + dtos::ProtocolContractStateCompat::Initializing(s) => { &s.generating_key.parameters.participants.participants } - dtos::ProtocolContractState::Running(s) => &s.parameters.participants.participants, - dtos::ProtocolContractState::Resharing(s) => { + dtos::ProtocolContractStateCompat::Running(s) => &s.parameters.participants.participants, + dtos::ProtocolContractStateCompat::Resharing(s) => { &s.resharing_key.parameters.participants.participants } - dtos::ProtocolContractState::NotInitialized => { + dtos::ProtocolContractStateCompat::NotInitialized => { panic!("protocol state must be initialized") } }; diff --git a/crates/contract/tests/sandbox/utils/interface.rs b/crates/contract/tests/sandbox/utils/interface.rs index 246de2c9fc..ed308e5fe3 100644 --- a/crates/contract/tests/sandbox/utils/interface.rs +++ b/crates/contract/tests/sandbox/utils/interface.rs @@ -27,14 +27,17 @@ impl IntoContractType for &dtos::Participants { } } -impl IntoContractType - for &dtos::ThresholdParameters +// TODO(XXXX): Switch to canonical after upgrade 3.14.0 +impl IntoContractType + for &dtos::GovernanceThresholdParametersCompat { - fn into_contract_type(self) -> mpc_contract::primitives::thresholds::ThresholdParameters { + fn into_contract_type( + self, + ) -> mpc_contract::primitives::thresholds::GovernanceThresholdParameters { let participants: Participants = (&self.participants).into_contract_type(); - mpc_contract::primitives::thresholds::ThresholdParameters::new( + mpc_contract::primitives::thresholds::GovernanceThresholdParameters::new( participants, - mpc_contract::primitives::thresholds::Threshold::new(self.threshold.0), + mpc_contract::primitives::thresholds::GovernanceThreshold::new(self.threshold.0), ) .unwrap() } diff --git a/crates/contract/tests/sandbox/utils/mpc_contract.rs b/crates/contract/tests/sandbox/utils/mpc_contract.rs index 2d01dda080..f92965cff6 100644 --- a/crates/contract/tests/sandbox/utils/mpc_contract.rs +++ b/crates/contract/tests/sandbox/utils/mpc_contract.rs @@ -6,7 +6,10 @@ use mpc_primitives::hash::{LauncherImageHash, NodeImageHash, TeeVerifierCodeHash use near_mpc_contract_interface::{ client::MpcContractHandle, method_names, - types::{Attestation, Ed25519PublicKey, Participants, ProtocolContractState, Threshold}, + types::{ + Attestation, Ed25519PublicKey, GovernanceThreshold, Participants, + ProtocolContractStateCompat, + }, }; use near_workspaces::{ Account, AccountId, Contract, result::ExecutionFinalResult, types::NearToken, @@ -20,7 +23,7 @@ pub fn total_gas_fee(result: &ExecutionFinalResult) -> NearToken { .fold(NearToken::from_yoctonear(0), NearToken::saturating_add) } -pub async fn get_state(contract: &Contract) -> ProtocolContractState { +pub async fn get_state(contract: &Contract) -> ProtocolContractStateCompat { contract .view(method_names::STATE) .await @@ -31,7 +34,7 @@ pub async fn get_state(contract: &Contract) -> ProtocolContractState { pub async fn get_participants(contract: &Contract) -> anyhow::Result { let state = get_state(contract).await; - let ProtocolContractState::Running(running) = state else { + let ProtocolContractStateCompat::Running(running) = state else { panic!("Expected running state") }; @@ -114,8 +117,9 @@ pub async fn assert_running_return_participants( contract: &Contract, ) -> anyhow::Result { // Verify contract is back to running state with new threshold - let final_state: ProtocolContractState = contract.view(method_names::STATE).await?.json()?; - let ProtocolContractState::Running(running_state) = final_state else { + let final_state: ProtocolContractStateCompat = + contract.view(method_names::STATE).await?.json()?; + let ProtocolContractStateCompat::Running(running_state) = final_state else { panic!( "Expected contract to be in Running state after resharing, but got: {:?}", final_state @@ -124,9 +128,9 @@ pub async fn assert_running_return_participants( Ok(running_state.parameters.participants) } -pub async fn assert_running_return_threshold(contract: &Contract) -> Threshold { - let final_state: ProtocolContractState = get_state(contract).await; - let ProtocolContractState::Running(running_state) = final_state else { +pub async fn assert_running_return_threshold(contract: &Contract) -> GovernanceThreshold { + let final_state: ProtocolContractStateCompat = get_state(contract).await; + let ProtocolContractStateCompat::Running(running_state) = final_state else { panic!( "Expected contract to be in Running state: {:?}", final_state diff --git a/crates/contract/tests/sandbox/utils/resharing_utils.rs b/crates/contract/tests/sandbox/utils/resharing_utils.rs index 8a8f1f20de..3fbf00636e 100644 --- a/crates/contract/tests/sandbox/utils/resharing_utils.rs +++ b/crates/contract/tests/sandbox/utils/resharing_utils.rs @@ -4,9 +4,11 @@ use crate::sandbox::utils::{ transactions::execute_async_transactions, }; use dtos::{AttemptId, EpochId, KeyEventId}; -use mpc_contract::primitives::thresholds::{ProposedThresholdParameters, ThresholdParameters}; +use mpc_contract::primitives::thresholds::{ + GovernanceThresholdParameters, ProposedGovernanceThresholdParameters, +}; use near_mpc_contract_interface::method_names; -use near_mpc_contract_interface::types::{self as dtos, ProtocolContractState}; +use near_mpc_contract_interface::types::{self as dtos, ProtocolContractStateCompat}; use near_workspaces::{Account, Contract}; use serde_json::json; use std::collections::BTreeMap; @@ -16,7 +18,7 @@ pub async fn conclude_resharing( all_participants: &[Account], prospective_epoch_id: EpochId, ) -> anyhow::Result<()> { - let ProtocolContractState::Resharing(resharing_state) = get_state(contract).await else { + let ProtocolContractStateCompat::Resharing(resharing_state) = get_state(contract).await else { anyhow::bail!("expected resharing state"); }; if resharing_state.resharing_key.epoch_id != prospective_epoch_id { @@ -34,7 +36,7 @@ pub async fn conclude_resharing( attempt_id: AttemptId(0), }; let state = get_state(contract).await; - if !matches!(state, ProtocolContractState::Resharing(_)) { + if !matches!(state, ProtocolContractStateCompat::Resharing(_)) { anyhow::bail!("expected resharing state"); } start_reshare_instance(contract, all_participants, key_event_id).await?; @@ -58,11 +60,11 @@ pub async fn vote_cancel_reshaing(contract: &Contract, accounts: &[Account]) -> pub async fn vote_new_parameters( contract: &Contract, prospective_epoch_id: u64, - proposal: &ThresholdParameters, + proposal: &GovernanceThresholdParameters, persistent_participants: &[Account], new_participants: &[Account], ) -> anyhow::Result<()> { - let proposal = ProposedThresholdParameters::new(proposal.clone(), BTreeMap::new()); + let proposal = ProposedGovernanceThresholdParameters::new(proposal.clone(), BTreeMap::new()); let json_args = json!({ "prospective_epoch_id": prospective_epoch_id, "proposal": proposal, @@ -97,14 +99,14 @@ pub async fn start_reshare_instance( ) -> anyhow::Result<()> { let state = get_state(contract).await; let active = match &state { - ProtocolContractState::Initializing(s) => { + ProtocolContractStateCompat::Initializing(s) => { &s.generating_key.parameters.participants.participants } - ProtocolContractState::Running(s) => &s.parameters.participants.participants, - ProtocolContractState::Resharing(s) => { + ProtocolContractStateCompat::Running(s) => &s.parameters.participants.participants, + ProtocolContractStateCompat::Resharing(s) => { &s.resharing_key.parameters.participants.participants } - ProtocolContractState::NotInitialized => { + ProtocolContractStateCompat::NotInitialized => { panic!("protocol state must be initialized") } }; @@ -150,7 +152,7 @@ pub async fn vote_reshared( pub async fn do_resharing( remaining_accounts: &[Account], contract: &Contract, - new_threshold_parameters: ThresholdParameters, + new_threshold_parameters: GovernanceThresholdParameters, prospective_epoch_id: EpochId, ) -> anyhow::Result<()> { vote_new_parameters( diff --git a/crates/contract/tests/sandbox/vote.rs b/crates/contract/tests/sandbox/vote.rs index c8ea56d1ae..b4c0ad94d2 100644 --- a/crates/contract/tests/sandbox/vote.rs +++ b/crates/contract/tests/sandbox/vote.rs @@ -15,12 +15,14 @@ use crate::sandbox::{ }; use assert_matches::assert_matches; use dtos::{ - AttemptId, Curve, DomainConfig, DomainPurpose, KeyEventId, Protocol, ProtocolContractState, - RunningContractState, + AttemptId, Curve, DomainConfig, DomainPurpose, KeyEventId, Protocol, + ProtocolContractStateCompat, RunningContractStateCompat, }; use mpc_contract::primitives::{ test_utils::infer_purpose_from_protocol, - thresholds::{ProposedThresholdParameters, Threshold, ThresholdParameters}, + thresholds::{ + GovernanceThreshold, GovernanceThresholdParameters, ProposedGovernanceThresholdParameters, + }, }; use near_mpc_contract_interface::types::ReconstructionThreshold; use near_mpc_contract_interface::{method_names, types as dtos}; @@ -40,7 +42,7 @@ async fn test_keygen() -> anyhow::Result<()> { .build() .await; let init_state = get_state(&contract).await; - let ProtocolContractState::Running(ref init_running) = init_state else { + let ProtocolContractStateCompat::Running(ref init_running) = init_state else { panic!("expected running state"); }; let epoch_id = init_running.keyset.epoch_id; @@ -61,7 +63,7 @@ async fn test_keygen() -> anyhow::Result<()> { .await .unwrap(); let state = get_state(&contract).await; - let ProtocolContractState::Initializing(ref init) = state else { + let ProtocolContractStateCompat::Initializing(ref init) = state else { panic!("expected initializing state"); }; assert_eq!(init.domains.next_domain_id, domain_id + 1); @@ -102,7 +104,7 @@ async fn test_keygen() -> anyhow::Result<()> { // ensure the protocol resumed running state and the public key was added let state = get_state(&contract).await; - let ProtocolContractState::Running(ref running) = state else { + let ProtocolContractStateCompat::Running(ref running) = state else { panic!("expected running state"); }; let found_key: near_sdk::PublicKey = running @@ -146,7 +148,7 @@ async fn test_cancel_keygen() -> anyhow::Result<()> { .build() .await; let init_state = get_state(&contract).await; - let ProtocolContractState::Running(ref init_running) = init_state else { + let ProtocolContractStateCompat::Running(ref init_running) = init_state else { panic!("expected running state"); }; let epoch_id: u64 = init_running.keyset.epoch_id.0; @@ -176,7 +178,7 @@ async fn test_cancel_keygen() -> anyhow::Result<()> { .unwrap(); let state = get_state(&contract).await; - let ProtocolContractState::Initializing(ref init) = state else { + let ProtocolContractStateCompat::Initializing(ref init) = state else { panic!("expected initializing state"); }; assert_eq!(init.domains.next_domain_id, next_domain_id + 1); @@ -211,7 +213,7 @@ async fn test_cancel_keygen() -> anyhow::Result<()> { // ensure we return to running state and that no key was registered let state = get_state(&contract).await; - let ProtocolContractState::Running(ref running) = state else { + let ProtocolContractStateCompat::Running(ref running) = state else { panic!("expected running state"); }; assert!( @@ -264,9 +266,9 @@ async fn test_resharing() -> anyhow::Result<()> { .await .unwrap(); - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; match state { - ProtocolContractState::Running(state) => { + ProtocolContractStateCompat::Running(state) => { assert_eq!( state.parameters.participants.participants.len(), PARTICIPANT_LEN + 1 @@ -296,7 +298,8 @@ async fn test_repropose_resharing() -> anyhow::Result<()> { + 1, ); let prospective_epoch_id = dtos::EpochId(prospective_epoch_id.0 + 1); - let proposal: ThresholdParameters = (&initial_running_state.parameters).into_contract_type(); + let proposal: GovernanceThresholdParameters = + (&initial_running_state.parameters).into_contract_type(); vote_new_parameters( &contract, prospective_epoch_id.0, @@ -307,10 +310,10 @@ async fn test_repropose_resharing() -> anyhow::Result<()> { .await .unwrap(); - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; match state { - ProtocolContractState::Resharing(state) => { - let state_params: ThresholdParameters = + ProtocolContractStateCompat::Resharing(state) => { + let state_params: GovernanceThresholdParameters = (&state.resharing_key.parameters).into_contract_type(); assert_eq!(state_params, proposal); assert_eq!(state.resharing_key.epoch_id, prospective_epoch_id); @@ -325,8 +328,8 @@ struct ResharingTestContext { contract: Contract, persistent_participants: Vec, new_participant_accounts: Vec, - threshold_parameters: ThresholdParameters, - initial_running_state: RunningContractState, + threshold_parameters: GovernanceThresholdParameters, + initial_running_state: RunningContractStateCompat, } /// Test helper: Initialize environment and transition to resharing state @@ -345,8 +348,8 @@ async fn setup_resharing_state( .build() .await; - let state: ProtocolContractState = get_state(&contract).await; - let ProtocolContractState::Running(initial_running_state) = state else { + let state: ProtocolContractStateCompat = get_state(&contract).await; + let ProtocolContractStateCompat::Running(initial_running_state) = state else { panic!("State is not running: {:#?}", state) }; @@ -362,8 +365,11 @@ async fn setup_resharing_state( new_participants .insert(new_account_id.clone(), new_participant_info) .unwrap(); - let proposal = - ThresholdParameters::new(new_participants, Threshold::new(threshold.0 + 1)).unwrap(); + let proposal = GovernanceThresholdParameters::new( + new_participants, + GovernanceThreshold::new(threshold.0 + 1), + ) + .unwrap(); let prospective_epoch_id = dtos::EpochId( initial_running_state @@ -384,7 +390,7 @@ async fn setup_resharing_state( // Verify we're in resharing state match get_state(&contract).await { - ProtocolContractState::Resharing(state) => { + ProtocolContractStateCompat::Resharing(state) => { // Compare proposal parameters via JSON roundtrip (internal vs DTO types) let proposal_json = serde_json::to_value(&proposal).unwrap(); let state_params_json = serde_json::to_value(&state.resharing_key.parameters).unwrap(); @@ -436,7 +442,7 @@ async fn test_cancel_resharing_vote_is_idempotent( // Verify still in resharing state - multiple votes from same account don't count let state = get_state(&contract).await; assert!( - matches!(state, ProtocolContractState::Resharing(_)), + matches!(state, ProtocolContractStateCompat::Resharing(_)), "Contract should still be in resharing state. A threshold number of unique votes have not been cast." ); @@ -447,8 +453,8 @@ async fn test_cancel_resharing_vote_is_idempotent( .unwrap(); // Check that state transitions back to running - let new_state: ProtocolContractState = get_state(&contract).await; - let ProtocolContractState::Running(mut found) = new_state else { + let new_state: ProtocolContractStateCompat = get_state(&contract).await; + let ProtocolContractStateCompat::Running(mut found) = new_state else { panic!("expected running state"); }; @@ -492,9 +498,9 @@ async fn test_cancel_resharing_requires_threshold_votes( .unwrap(); // Verify still in resharing state (not cancelled yet) - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; assert!( - matches!(state, ProtocolContractState::Resharing(_)), + matches!(state, ProtocolContractStateCompat::Resharing(_)), "Should still be in resharing state with insufficient votes" ); @@ -506,9 +512,9 @@ async fn test_cancel_resharing_requires_threshold_votes( assert!(result.is_success(), "{result:#?}"); // Verify transition back to running state - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; assert!( - matches!(state, ProtocolContractState::Running(_)), + matches!(state, ProtocolContractStateCompat::Running(_)), "Should transition to running state after threshold votes" ); @@ -564,7 +570,7 @@ async fn test_cancel_resharing_reverts_to_previous_running_state( // Check that state transitions back to running let new_state = get_state(&contract).await; - let ProtocolContractState::Running(mut new_running_state) = new_state else { + let ProtocolContractStateCompat::Running(mut new_running_state) = new_state else { panic!( "State must transition back to running after voting for cancellation {:#?}", new_state @@ -624,7 +630,7 @@ async fn test_cancelled_epoch_cannot_be_reused( // Verify state tracks cancelled epoch let state = get_state(&contract).await; - if let ProtocolContractState::Running(running_state) = state { + if let ProtocolContractStateCompat::Running(running_state) = state { assert_eq!( running_state.previously_cancelled_resharing_epoch_id, Some(cancelled_epoch_id) @@ -638,7 +644,7 @@ async fn test_cancelled_epoch_cannot_be_reused( .call(contract.id(), method_names::VOTE_NEW_PARAMETERS) .args_json(json!({ "prospective_epoch_id": cancelled_epoch_id.0, - "proposal": ProposedThresholdParameters::new( + "proposal": ProposedGovernanceThresholdParameters::new( threshold_parameters.clone(), BTreeMap::new(), ), @@ -664,7 +670,7 @@ async fn test_cancelled_epoch_cannot_be_reused( // Verify successful transition to resharing with correct epoch let state = get_state(&contract).await; match state { - ProtocolContractState::Resharing(resharing_contract_state) => { + ProtocolContractStateCompat::Resharing(resharing_contract_state) => { assert_eq!( serde_json::to_value(&resharing_contract_state.resharing_key.parameters).unwrap(), serde_json::to_value(&threshold_parameters).unwrap() @@ -709,8 +715,8 @@ async fn test_successful_resharing_after_cancellation_clears_cancelled_epoch_id( let prospective_epoch_id = dtos::EpochId(cancelled_epoch_id.0 + 1); // Verify cancellation tracked - let state: ProtocolContractState = get_state(&contract).await; - if let ProtocolContractState::Running(running_state) = state { + let state: ProtocolContractStateCompat = get_state(&contract).await; + if let ProtocolContractStateCompat::Running(running_state) = state { assert_eq!( running_state.previously_cancelled_resharing_epoch_id, Some(cancelled_epoch_id), @@ -730,9 +736,9 @@ async fn test_successful_resharing_after_cancellation_clears_cancelled_epoch_id( .unwrap(); // Verify in resharing state - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; match state { - ProtocolContractState::Resharing(resharing_state) => { + ProtocolContractStateCompat::Resharing(resharing_state) => { assert_eq!(resharing_state.resharing_key.epoch_id, prospective_epoch_id); } _ => panic!("should be in resharing state"), @@ -746,9 +752,9 @@ async fn test_successful_resharing_after_cancellation_clears_cancelled_epoch_id( .unwrap(); // Step 5: Verify final state - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; match state { - ProtocolContractState::Running(running_state) => { + ProtocolContractStateCompat::Running(running_state) => { assert_eq!( running_state.keyset.epoch_id, prospective_epoch_id, "Should be running with new epoch ID" @@ -782,7 +788,7 @@ async fn vote_new_parameters_errors_if_new_participant_is_missing_valid_attestat .await; let state = get_state(&contract).await; - let ProtocolContractState::Running(ref running_state) = state else { + let ProtocolContractStateCompat::Running(ref running_state) = state else { panic!("expected running state"); }; let threshold = running_state.parameters.threshold; @@ -798,8 +804,11 @@ async fn vote_new_parameters_errors_if_new_participant_is_missing_valid_attestat .insert(new_account_id.clone(), new_participant_info) .unwrap(); - let threshold_parameters = - ThresholdParameters::new(proposed_participants, Threshold::new(threshold.0 + 1)).unwrap(); + let threshold_parameters = GovernanceThresholdParameters::new( + proposed_participants, + GovernanceThreshold::new(threshold.0 + 1), + ) + .unwrap(); mpc_signer_accounts.push(new_account.clone()); @@ -810,7 +819,7 @@ async fn vote_new_parameters_errors_if_new_participant_is_missing_valid_attestat .max_gas() .args_json(json!({ "prospective_epoch_id": dtos::EpochId(epoch_id.0 + 1), - "proposal": ProposedThresholdParameters::new( + "proposal": ProposedGovernanceThresholdParameters::new( threshold_parameters.clone(), BTreeMap::new(), ), @@ -833,11 +842,11 @@ async fn vote_new_parameters_errors_if_new_participant_is_missing_valid_attestat ); } - let state: ProtocolContractState = get_state(&contract).await; + let state: ProtocolContractStateCompat = get_state(&contract).await; assert_matches!( state, - ProtocolContractState::Running(_), + ProtocolContractStateCompat::Running(_), "Protocol state should not transition when new participant has invalid TEE status." ); } diff --git a/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap b/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap index 902e3c438f..464de96f9e 100644 --- a/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap +++ b/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap @@ -789,7 +789,7 @@ expression: abi { "name": "parameters", "type_schema": { - "$ref": "#/definitions/ThresholdParameters" + "$ref": "#/definitions/GovernanceThresholdParametersCompat" } }, { @@ -844,7 +844,7 @@ expression: abi { "name": "parameters", "type_schema": { - "$ref": "#/definitions/ThresholdParameters" + "$ref": "#/definitions/GovernanceThresholdParametersCompat" } }, { @@ -2336,7 +2336,7 @@ expression: abi "result": { "serialization_type": "json", "type_schema": { - "$ref": "#/definitions/ProtocolContractState" + "$ref": "#/definitions/ProtocolContractStateCompat" } } }, @@ -2650,7 +2650,7 @@ expression: abi { "name": "proposal", "type_schema": { - "$ref": "#/definitions/ProposedThresholdParameters" + "$ref": "#/definitions/ProposedGovernanceThresholdParametersCompat" } } ] @@ -3886,7 +3886,7 @@ expression: abi } }, "EpochId": { - "description": "An EpochId uniquely identifies a ThresholdParameters (but not vice-versa). Every time we change the ThresholdParameters (participants and threshold), we increment EpochId. Locally on each node, each keyshare is uniquely identified by the tuple (EpochId, DomainId, AttemptId).", + "description": "An EpochId uniquely identifies a GovernanceThresholdParametersCompat (but not vice-versa). Every time we change the GovernanceThresholdParametersCompat (participants and threshold), we increment EpochId. Locally on each node, each keyshare is uniquely identified by the tuple (EpochId, DomainId, AttemptId).", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -4208,6 +4208,43 @@ expression: abi "$ref": "#/definitions/ForeignChainsConfig" } }, + "GovernanceThreshold": { + "description": "Network-wide governance threshold (`k`): the minimum number of participants that must agree to approve a governance action (participant set changes, resharing, parameter updates). Distinct from a domain's [`ReconstructionThreshold`], the per-domain `t` needed to reconstruct a key.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "GovernanceThresholdParametersCompat": { + "description": "GovernanceThreshold parameters for distributed key operations: the current participant set and the governance threshold. This is the stored, always-current shape; per-domain reconstruction-threshold *proposals* live on [`ProposedGovernanceThresholdParametersCompat`].", + "type": "object", + "required": [ + "participants", + "threshold" + ], + "properties": { + "participants": { + "$ref": "#/definitions/Participants" + }, + "threshold": { + "$ref": "#/definitions/GovernanceThreshold" + } + } + }, + "GovernanceThresholdParametersVotesCompat": { + "description": "Votes for threshold parameter changes.", + "type": "object", + "required": [ + "proposal_by_account" + ], + "properties": { + "proposal_by_account": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ProposedGovernanceThresholdParametersCompat" + } + } + } + }, "Hash256": { "type": "string", "pattern": "^(?:[0-9A-Fa-f]{2})*$" @@ -4383,7 +4420,7 @@ expression: abi } } }, - "InitializingContractState": { + "InitializingContractStateCompat": { "description": "State when the contract is generating keys for new domains.", "type": "object", "required": [ @@ -4414,7 +4451,7 @@ expression: abi } }, "generating_key": { - "$ref": "#/definitions/KeyEvent" + "$ref": "#/definitions/KeyEventCompat" } } }, @@ -4443,7 +4480,7 @@ expression: abi } } }, - "KeyEvent": { + "KeyEventCompat": { "description": "Key generation or resharing event state.", "type": "object", "required": [ @@ -4473,12 +4510,12 @@ expression: abi "$ref": "#/definitions/AttemptId" }, "parameters": { - "$ref": "#/definitions/ThresholdParameters" + "$ref": "#/definitions/GovernanceThresholdParametersCompat" } } }, "KeyEventId": { - "description": "A unique identifier for a key event (generation or resharing): `epoch_id`: identifies the ThresholdParameters that this key is intended to function in. `domain_id`: the domain this key is intended for. `attempt_id`: identifies a particular attempt for this key event, in case multiple attempts yielded partially valid results. This is incremented for each attempt within the same epoch and domain.", + "description": "A unique identifier for a key event (generation or resharing): `epoch_id`: identifies the GovernanceThresholdParametersCompat that this key is intended to function in. `domain_id`: the domain this key is intended for. `attempt_id`: identifies a particular attempt for this key event, in case multiple attempts yielded partially valid results. This is incremented for each attempt within the same epoch and domain.", "type": "object", "required": [ "attempt_id", @@ -4910,7 +4947,7 @@ expression: abi } }, "Participants": { - "description": "DTO representation of the contract-internal `Participants` type.\n\nIt decouples the JSON wire format (used in view methods like `state()` via [`ThresholdParameters`](crate::types::state::ThresholdParameters)) from the internal `Participants` representation, allowing internal changes (e.g., migrating to [`BTreeMap`](std::collections::BTreeMap) in [#1861](https://github.com/near/mpc/pull/1861)) without breaking the public API.", + "description": "DTO representation of the contract-internal `Participants` type.\n\nIt decouples the JSON wire format (used in view methods like `state()` via [`GovernanceThresholdParametersCompat`](crate::types::state::GovernanceThresholdParametersCompat)) from the internal `Participants` representation, allowing internal changes (e.g., migrating to [`BTreeMap`](std::collections::BTreeMap) in [#1861](https://github.com/near/mpc/pull/1861)) without breaking the public API.", "type": "object", "required": [ "next_id", @@ -5054,15 +5091,15 @@ expression: abi } } }, - "ProposedThresholdParameters": { - "description": "A proposed set of threshold parameters submitted to `vote_new_parameters`: the proposed [`ThresholdParameters`] plus per-domain `ReconstructionThreshold` updates for the resharing it would trigger. An empty `per_domain_thresholds` keeps the current ones; a populated map must reference only existing domains (contract-validated), is applied to the `DomainRegistry` on resharing, and never persists onto the stored [`ThresholdParameters`].", + "ProposedGovernanceThresholdParametersCompat": { + "description": "A proposed set of threshold parameters submitted to `vote_new_parameters`: the proposed [`GovernanceThresholdParametersCompat`] plus per-domain `ReconstructionThreshold` updates for the resharing it would trigger. An empty `per_domain_thresholds` keeps the current ones; a populated map must reference only existing domains (contract-validated), is applied to the `DomainRegistry` on resharing, and never persists onto the stored [`GovernanceThresholdParametersCompat`].", "type": "object", "required": [ "parameters" ], "properties": { "parameters": { - "$ref": "#/definitions/ThresholdParameters" + "$ref": "#/definitions/GovernanceThresholdParametersCompat" }, "per_domain_thresholds": { "default": {}, @@ -5108,7 +5145,7 @@ expression: abi "DamgardEtAl" ] }, - "ProtocolContractState": { + "ProtocolContractStateCompat": { "description": "The main protocol contract state enum.", "oneOf": [ { @@ -5124,7 +5161,7 @@ expression: abi ], "properties": { "Initializing": { - "$ref": "#/definitions/InitializingContractState" + "$ref": "#/definitions/InitializingContractStateCompat" } }, "additionalProperties": false @@ -5136,7 +5173,7 @@ expression: abi ], "properties": { "Running": { - "$ref": "#/definitions/RunningContractState" + "$ref": "#/definitions/RunningContractStateCompat" } }, "additionalProperties": false @@ -5148,7 +5185,7 @@ expression: abi ], "properties": { "Resharing": { - "$ref": "#/definitions/ResharingContractState" + "$ref": "#/definitions/ResharingContractStateCompat" } }, "additionalProperties": false @@ -5356,7 +5393,7 @@ expression: abi } ] }, - "ResharingContractState": { + "ResharingContractStateCompat": { "description": "State when the contract is resharing keys to new participants.", "type": "object", "required": [ @@ -5384,7 +5421,7 @@ expression: abi } }, "previous_running_state": { - "$ref": "#/definitions/RunningContractState" + "$ref": "#/definitions/RunningContractStateCompat" }, "reshared_keys": { "type": "array", @@ -5393,7 +5430,7 @@ expression: abi } }, "resharing_key": { - "$ref": "#/definitions/KeyEvent" + "$ref": "#/definitions/KeyEventCompat" } } }, @@ -5426,7 +5463,7 @@ expression: abi "minLength": 96, "pattern": "^[0-9a-fA-F]+$" }, - "RunningContractState": { + "RunningContractStateCompat": { "description": "State when the contract is ready for signature operations.", "type": "object", "required": [ @@ -5447,10 +5484,10 @@ expression: abi "$ref": "#/definitions/Keyset2" }, "parameters": { - "$ref": "#/definitions/ThresholdParameters" + "$ref": "#/definitions/GovernanceThresholdParametersCompat" }, "parameters_votes": { - "$ref": "#/definitions/ThresholdParametersVotes" + "$ref": "#/definitions/GovernanceThresholdParametersVotesCompat" }, "previously_cancelled_resharing_epoch_id": { "anyOf": [ @@ -5861,43 +5898,6 @@ expression: abi "minLength": 64, "pattern": "^[0-9a-fA-F]+$" }, - "Threshold": { - "description": "Cryptographic threshold (`k`) for a distributed key: the minimum number of participants that must collaborate to produce a signature.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "ThresholdParameters": { - "description": "Threshold parameters for distributed key operations: the current participant set and the governance threshold. This is the stored, always-current shape; per-domain reconstruction-threshold *proposals* live on [`ProposedThresholdParameters`].", - "type": "object", - "required": [ - "participants", - "threshold" - ], - "properties": { - "participants": { - "$ref": "#/definitions/Participants" - }, - "threshold": { - "$ref": "#/definitions/Threshold" - } - } - }, - "ThresholdParametersVotes": { - "description": "Votes for threshold parameter changes.", - "type": "object", - "required": [ - "proposal_by_account" - ], - "properties": { - "proposal_by_account": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ProposedThresholdParameters" - } - } - } - }, "TonAddress": { "type": "object", "required": [ diff --git a/crates/devnet/src/mpc.rs b/crates/devnet/src/mpc.rs index 3951c474e6..ab15ee81fe 100644 --- a/crates/devnet/src/mpc.rs +++ b/crates/devnet/src/mpc.rs @@ -24,9 +24,10 @@ use near_jsonrpc_primitives::types::query::QueryResponseKind; use near_mpc_contract_interface::call_args::VoteUpdateArgs; use near_mpc_contract_interface::method_names; use near_mpc_contract_interface::types::{ - DomainConfig, DomainPurpose, EpochId, NodeImageHash, ParticipantId, ParticipantInfo, - Participants, ProposeUpdateArgs, ProposedThresholdParameters, Protocol, ProtocolContractState, - ReconstructionThreshold, Threshold, ThresholdParameters, protocol_state_to_string, + DomainConfig, DomainPurpose, EpochId, GovernanceThreshold, GovernanceThresholdParametersCompat, + NodeImageHash, ParticipantId, ParticipantInfo, Participants, ProposeUpdateArgs, + ProposedGovernanceThresholdParametersCompat, Protocol, ProtocolContractState, + ProtocolContractStateCompat, ReconstructionThreshold, protocol_state_to_string, }; use near_primitives::types::{BlockReference, Finality, FunctionArgs}; use near_primitives::views::QueryRequest; @@ -379,12 +380,12 @@ impl MpcInitContractCmd { participant_entries.push((account_id.clone(), next_id, info)); next_id = next_id.next(); } - let parameters = ThresholdParameters { + let parameters = GovernanceThresholdParametersCompat { participants: Participants { next_id, participants: participant_entries, }, - threshold: Threshold::new(self.threshold), + threshold: GovernanceThreshold::new(self.threshold), }; let args = serde_json::to_vec(&InitV2Args { parameters, @@ -410,7 +411,8 @@ impl MpcInitContractCmd { #[derive(Serialize)] struct InitV2Args { - parameters: ThresholdParameters, + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + parameters: GovernanceThresholdParametersCompat, init_config: near_mpc_contract_interface::types::InitConfig, } @@ -747,9 +749,9 @@ impl MpcVoteNewParametersCmd { participants.next_id = id.next(); } let threshold = if let Some(threshold) = self.set_threshold { - Threshold::new(threshold) + GovernanceThreshold::new(threshold) } else { - parameters.threshold + parameters.governance_threshold }; let per_domain_thresholds: std::collections::BTreeMap<_, _> = self .per_domain_thresholds @@ -761,8 +763,8 @@ impl MpcVoteNewParametersCmd { ) }) .collect(); - let proposal = ProposedThresholdParameters { - parameters: ThresholdParameters { + let proposal = ProposedGovernanceThresholdParametersCompat { + parameters: GovernanceThresholdParametersCompat { participants, threshold, }, @@ -837,7 +839,7 @@ impl MpcVoteApprovedHashCmd { } }; - let threshold: u64 = running_state.parameters.threshold.value(); + let threshold: u64 = running_state.parameters.governance_threshold.value(); let accounts = get_voter_account_ids(mpc_setup, &self.voters); let mut voting_futures = vec![]; @@ -895,10 +897,15 @@ pub async fn read_contract_state( match result { Ok(result) => match result.kind { QueryResponseKind::CallResult(result) => { - serde_json::from_slice(&result.result).expect(&format!( - "Failed to deserialize contract state: {}", - String::from_utf8_lossy(&result.result) - )) + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + // `state()` still emits the pre-3903 field names; deserialize the + // compat shape and convert to the canonical DTO. + let state: ProtocolContractStateCompat = serde_json::from_slice(&result.result) + .expect(&format!( + "Failed to deserialize contract state: {}", + String::from_utf8_lossy(&result.result) + )); + state.into() } _ => panic!("Unexpected response: {:?}", result), }, @@ -916,7 +923,8 @@ pub async fn read_contract_state( #[derive(Serialize)] struct VoteNewParametersArgs { prospective_epoch_id: EpochId, - proposal: ProposedThresholdParameters, + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + proposal: ProposedGovernanceThresholdParametersCompat, } #[derive(Serialize)] diff --git a/crates/e2e-tests/src/cluster.rs b/crates/e2e-tests/src/cluster.rs index 9c1f3c44c6..7160c5a93a 100644 --- a/crates/e2e-tests/src/cluster.rs +++ b/crates/e2e-tests/src/cluster.rs @@ -13,10 +13,10 @@ use near_mpc_contract_interface::{ types::{ AccountId as ContractAccountId, Attestation, AuthScheme, CKDAppPublicKey, ChainEntry, ChainRouting, DomainConfig, DomainId, DomainPurpose, Ed25519PublicKey, EpochId, - ForeignChain, MockAttestation, ParticipantId, ParticipantInfo, Participants, Payload, - ProposeUpdateArgs, ProposedThresholdParameters, Protocol, ProtocolContractState, - ProviderConfig, ProviderId, ReconstructionThreshold, SignRequestArgs, Threshold, - ThresholdParameters, + ForeignChain, GovernanceThreshold, GovernanceThresholdParametersCompat, MockAttestation, + ParticipantId, ParticipantInfo, Participants, Payload, ProposeUpdateArgs, + ProposedGovernanceThresholdParametersCompat, Protocol, ProtocolContractState, + ProviderConfig, ProviderId, ReconstructionThreshold, SignRequestArgs, }, }; use rand::SeedableRng; @@ -77,7 +77,7 @@ const KEY_SEED_MIGRATION_NEAR_SIGNER: u64 = 400; pub struct MpcClusterConfig { /// Number of MPC nodes to start. pub num_nodes: usize, - /// Threshold for signing (number of nodes required). + /// GovernanceThreshold for signing (number of nodes required). pub threshold: usize, /// Signature domains to initialize after contract setup. pub domains: Vec, @@ -147,7 +147,7 @@ pub fn placeholder_chain_entry(chain: ForeignChain) -> ChainEntry { /// Mainnet/Testnet, drop the obsolete variant. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum ContractInitFormat { - /// Current `ThresholdParameters` shape. + /// Current `GovernanceThresholdParametersCompat` shape. #[default] Current, } @@ -157,7 +157,8 @@ impl ContractInitFormat { /// in the wire format that the targeted contract expects. fn init_parameters_json( self, - params: &ThresholdParameters, + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + params: &GovernanceThresholdParametersCompat, ) -> serde_json::Result { match self { Self::Current => serde_json::to_value(params), @@ -578,9 +579,9 @@ impl MpcCluster { let participants = build_participants_from_nodes(new_participants, &self.nodes, current_participants); - let proposal = ProposedThresholdParameters { - parameters: ThresholdParameters { - threshold: Threshold(new_threshold as u64), + let proposal = ProposedGovernanceThresholdParametersCompat { + parameters: GovernanceThresholdParametersCompat { + threshold: GovernanceThreshold(new_threshold as u64), participants, }, per_domain_thresholds: std::collections::BTreeMap::new(), @@ -1309,8 +1310,8 @@ async fn init_contract( } = args; let participants = build_participants(&participant_indices, &p2p_keys, ports); - let params = ThresholdParameters { - threshold: Threshold(threshold as u64), + let params = GovernanceThresholdParametersCompat { + threshold: GovernanceThreshold(threshold as u64), participants, }; diff --git a/crates/near-mpc-contract-interface/src/lib.rs b/crates/near-mpc-contract-interface/src/lib.rs index 0d045a2acf..ab8364cb6a 100644 --- a/crates/near-mpc-contract-interface/src/lib.rs +++ b/crates/near-mpc-contract-interface/src/lib.rs @@ -28,10 +28,14 @@ pub mod types { pub use sign::*; pub use state::{ AddDomainsVotes, AttemptId, AuthenticatedAccountId, AuthenticatedParticipantId, Curve, - DomainConfig, DomainPurpose, DomainRegistry, EpochId, InitializingContractState, KeyEvent, - KeyEventId, KeyEventInstance, KeyForDomain, Keyset, ProposedThresholdParameters, Protocol, - ProtocolContractState, ReconstructionThreshold, ResharingContractState, - RunningContractState, Threshold, ThresholdParameters, ThresholdParametersVotes, + DomainConfig, DomainPurpose, DomainRegistry, EpochId, GovernanceThreshold, + GovernanceThresholdParameters, GovernanceThresholdParametersCompat, + GovernanceThresholdParametersVotes, GovernanceThresholdParametersVotesCompat, + InitializingContractState, InitializingContractStateCompat, KeyEvent, KeyEventCompat, + KeyEventId, KeyEventInstance, KeyForDomain, Keyset, ProposedGovernanceThresholdParameters, + ProposedGovernanceThresholdParametersCompat, Protocol, ProtocolContractState, + ProtocolContractStateCompat, ReconstructionThreshold, ResharingContractState, + ResharingContractStateCompat, RunningContractState, RunningContractStateCompat, protocol_state_to_string, }; pub use tee::{AllowedMpcDockerImageHash, NodeId}; diff --git a/crates/near-mpc-contract-interface/src/types/participants.rs b/crates/near-mpc-contract-interface/src/types/participants.rs index 78ef82d485..8b85efb746 100644 --- a/crates/near-mpc-contract-interface/src/types/participants.rs +++ b/crates/near-mpc-contract-interface/src/types/participants.rs @@ -30,7 +30,7 @@ pub struct ParticipantInfo { /// DTO representation of the contract-internal `Participants` type. /// /// It decouples the JSON wire format (used in view methods like `state()` via -/// [`ThresholdParameters`](crate::types::state::ThresholdParameters)) from the +/// [`GovernanceThresholdParameters`](crate::types::state::GovernanceThresholdParameters)) from the /// internal `Participants` representation, allowing internal changes (e.g., /// migrating to [`BTreeMap`](std::collections::BTreeMap) in [#1861](https://github.com/near/mpc/pull/1861)) /// without breaking the public API. diff --git a/crates/near-mpc-contract-interface/src/types/state.rs b/crates/near-mpc-contract-interface/src/types/state.rs index d90e65c6dc..82e65f40a2 100644 --- a/crates/near-mpc-contract-interface/src/types/state.rs +++ b/crates/near-mpc-contract-interface/src/types/state.rs @@ -16,7 +16,7 @@ use super::primitives::DomainId; // Simple Wrapper Types (newtypes) // ============================================================================= -pub use mpc_primitives::{AttemptId, EpochId, ReconstructionThreshold, Threshold}; +pub use mpc_primitives::{AttemptId, EpochId, GovernanceThreshold, ReconstructionThreshold}; /// A participant ID that has been authenticated (i.e., the caller is this participant). #[derive( @@ -127,40 +127,104 @@ pub use mpc_primitives::KeyEventId; pub use near_mpc_crypto_types::{KeyForDomain, Keyset}; // ============================================================================= -// Threshold/Participants Types +// GovernanceThreshold/Participants Types // ============================================================================= -/// Threshold parameters for distributed key operations: the current +/// GovernanceThreshold parameters for distributed key operations: the current /// participant set and the governance threshold. This is the stored, /// always-current shape; per-domain reconstruction-threshold *proposals* live -/// on [`ProposedThresholdParameters`]. +/// on [`ProposedGovernanceThresholdParameters`]. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr( all(feature = "abi", not(target_arch = "wasm32")), derive(schemars::JsonSchema) )] -pub struct ThresholdParameters { +pub struct GovernanceThresholdParameters { pub participants: Participants, - pub threshold: Threshold, + pub governance_threshold: GovernanceThreshold, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct GovernanceThresholdParametersCompat { + pub participants: Participants, + pub threshold: GovernanceThreshold, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for GovernanceThresholdParameters { + fn from(value: GovernanceThresholdParametersCompat) -> Self { + Self { + participants: value.participants, + governance_threshold: value.threshold, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for GovernanceThresholdParametersCompat { + fn from(value: GovernanceThresholdParameters) -> Self { + Self { + participants: value.participants, + threshold: value.governance_threshold, + } + } } /// A proposed set of threshold parameters submitted to `vote_new_parameters`: -/// the proposed [`ThresholdParameters`] plus per-domain `ReconstructionThreshold` -/// updates for the resharing it would trigger. An empty `per_domain_thresholds` -/// keeps the current ones; a populated map must reference only existing domains -/// (contract-validated), is applied to the `DomainRegistry` on resharing, and -/// never persists onto the stored [`ThresholdParameters`]. +/// the proposed [`GovernanceThresholdParameters`] plus per-domain `ReconstructionThreshold` +/// updates for the resharing it would trigger. An empty +/// `per_domain_reconstruction_thresholds` keeps the current ones; a populated map +/// must reference only existing domains (contract-validated), is applied to the +/// `DomainRegistry` on resharing, and never persists onto the stored +/// [`GovernanceThresholdParameters`]. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr( all(feature = "abi", not(target_arch = "wasm32")), derive(schemars::JsonSchema) )] -pub struct ProposedThresholdParameters { - pub parameters: ThresholdParameters, +pub struct ProposedGovernanceThresholdParameters { + pub parameters: GovernanceThresholdParameters, + #[serde(default)] + pub per_domain_reconstruction_thresholds: BTreeMap, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct ProposedGovernanceThresholdParametersCompat { + pub parameters: GovernanceThresholdParametersCompat, #[serde(default)] pub per_domain_thresholds: BTreeMap, } +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for ProposedGovernanceThresholdParameters { + fn from(value: ProposedGovernanceThresholdParametersCompat) -> Self { + Self { + parameters: value.parameters.into(), + per_domain_reconstruction_thresholds: value.per_domain_thresholds, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for ProposedGovernanceThresholdParametersCompat { + fn from(value: ProposedGovernanceThresholdParameters) -> Self { + Self { + parameters: value.parameters.into(), + per_domain_thresholds: value.per_domain_reconstruction_thresholds, + } + } +} + // ============================================================================= // Voting Types // ============================================================================= @@ -171,8 +235,46 @@ pub struct ProposedThresholdParameters { all(feature = "abi", not(target_arch = "wasm32")), derive(schemars::JsonSchema) )] -pub struct ThresholdParametersVotes { - pub proposal_by_account: BTreeMap, +pub struct GovernanceThresholdParametersVotes { + pub proposal_by_account: + BTreeMap, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct GovernanceThresholdParametersVotesCompat { + pub proposal_by_account: + BTreeMap, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for GovernanceThresholdParametersVotes { + fn from(value: GovernanceThresholdParametersVotesCompat) -> Self { + Self { + proposal_by_account: value + .proposal_by_account + .into_iter() + .map(|(account, proposal)| (account, proposal.into())) + .collect(), + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for GovernanceThresholdParametersVotesCompat { + fn from(value: GovernanceThresholdParametersVotes) -> Self { + Self { + proposal_by_account: value + .proposal_by_account + .into_iter() + .map(|(account, proposal)| (account, proposal.into())) + .collect(), + } + } } /// Votes for adding new domains. @@ -212,11 +314,51 @@ pub struct KeyEventInstance { pub struct KeyEvent { pub epoch_id: EpochId, pub domain: DomainConfig, - pub parameters: ThresholdParameters, + pub parameters: GovernanceThresholdParameters, + pub instance: Option, + pub next_attempt_id: AttemptId, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct KeyEventCompat { + pub epoch_id: EpochId, + pub domain: DomainConfig, + pub parameters: GovernanceThresholdParametersCompat, pub instance: Option, pub next_attempt_id: AttemptId, } +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for KeyEvent { + fn from(value: KeyEventCompat) -> Self { + Self { + epoch_id: value.epoch_id, + domain: value.domain, + parameters: value.parameters.into(), + instance: value.instance, + next_attempt_id: value.next_attempt_id, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for KeyEventCompat { + fn from(value: KeyEvent) -> Self { + Self { + epoch_id: value.epoch_id, + domain: value.domain, + parameters: value.parameters.into(), + instance: value.instance, + next_attempt_id: value.next_attempt_id, + } + } +} + // ============================================================================= // Contract State Types // ============================================================================= @@ -235,6 +377,46 @@ pub struct InitializingContractState { pub cancel_votes: BTreeSet, } +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct InitializingContractStateCompat { + pub domains: DomainRegistry, + pub epoch_id: EpochId, + pub generated_keys: Vec, + pub generating_key: KeyEventCompat, + pub cancel_votes: BTreeSet, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for InitializingContractState { + fn from(value: InitializingContractStateCompat) -> Self { + Self { + domains: value.domains, + epoch_id: value.epoch_id, + generated_keys: value.generated_keys, + generating_key: value.generating_key.into(), + cancel_votes: value.cancel_votes, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for InitializingContractStateCompat { + fn from(value: InitializingContractState) -> Self { + Self { + domains: value.domains, + epoch_id: value.epoch_id, + generated_keys: value.generated_keys, + generating_key: value.generating_key.into(), + cancel_votes: value.cancel_votes, + } + } +} + /// State when the contract is ready for signature operations. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr( @@ -244,12 +426,55 @@ pub struct InitializingContractState { pub struct RunningContractState { pub domains: DomainRegistry, pub keyset: Keyset, - pub parameters: ThresholdParameters, - pub parameters_votes: ThresholdParametersVotes, + pub parameters: GovernanceThresholdParameters, + pub parameters_votes: GovernanceThresholdParametersVotes, pub add_domains_votes: AddDomainsVotes, pub previously_cancelled_resharing_epoch_id: Option, } +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct RunningContractStateCompat { + pub domains: DomainRegistry, + pub keyset: Keyset, + pub parameters: GovernanceThresholdParametersCompat, + pub parameters_votes: GovernanceThresholdParametersVotesCompat, + pub add_domains_votes: AddDomainsVotes, + pub previously_cancelled_resharing_epoch_id: Option, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for RunningContractState { + fn from(value: RunningContractStateCompat) -> Self { + Self { + domains: value.domains, + keyset: value.keyset, + parameters: value.parameters.into(), + parameters_votes: value.parameters_votes.into(), + add_domains_votes: value.add_domains_votes, + previously_cancelled_resharing_epoch_id: value.previously_cancelled_resharing_epoch_id, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for RunningContractStateCompat { + fn from(value: RunningContractState) -> Self { + Self { + domains: value.domains, + keyset: value.keyset, + parameters: value.parameters.into(), + parameters_votes: value.parameters_votes.into(), + add_domains_votes: value.add_domains_votes, + previously_cancelled_resharing_epoch_id: value.previously_cancelled_resharing_epoch_id, + } + } +} + /// State when the contract is resharing keys to new participants. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr( @@ -264,9 +489,50 @@ pub struct ResharingContractState { /// Per-domain `ReconstructionThreshold` updates carried from the accepted /// proposal. Applied to the `DomainRegistry` when resharing completes. #[serde(default)] + pub per_domain_reconstruction_thresholds: BTreeMap, +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub struct ResharingContractStateCompat { + pub previous_running_state: RunningContractStateCompat, + pub reshared_keys: Vec, + pub resharing_key: KeyEventCompat, + pub cancellation_requests: HashSet, + #[serde(default)] pub per_domain_thresholds: BTreeMap, } +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for ResharingContractState { + fn from(value: ResharingContractStateCompat) -> Self { + Self { + previous_running_state: value.previous_running_state.into(), + reshared_keys: value.reshared_keys, + resharing_key: value.resharing_key.into(), + cancellation_requests: value.cancellation_requests, + per_domain_reconstruction_thresholds: value.per_domain_thresholds, + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for ResharingContractStateCompat { + fn from(value: ResharingContractState) -> Self { + Self { + previous_running_state: value.previous_running_state.into(), + reshared_keys: value.reshared_keys, + resharing_key: value.resharing_key.into(), + cancellation_requests: value.cancellation_requests, + per_domain_thresholds: value.per_domain_reconstruction_thresholds, + } + } +} + /// The main protocol contract state enum. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr( @@ -280,14 +546,51 @@ pub enum ProtocolContractState { Resharing(ResharingContractState), } -fn params_to_string(output: &mut String, parameters: &ThresholdParameters) { +// TODO(XXXX): Delete this code after upgrade 3.14.0 +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + all(feature = "abi", not(target_arch = "wasm32")), + derive(schemars::JsonSchema) +)] +pub enum ProtocolContractStateCompat { + NotInitialized, + Initializing(InitializingContractStateCompat), + Running(RunningContractStateCompat), + Resharing(ResharingContractStateCompat), +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for ProtocolContractState { + fn from(value: ProtocolContractStateCompat) -> Self { + match value { + ProtocolContractStateCompat::NotInitialized => Self::NotInitialized, + ProtocolContractStateCompat::Initializing(state) => Self::Initializing(state.into()), + ProtocolContractStateCompat::Running(state) => Self::Running(state.into()), + ProtocolContractStateCompat::Resharing(state) => Self::Resharing(state.into()), + } + } +} + +// TODO(XXXX): Delete this code after upgrade 3.14.0 +impl From for ProtocolContractStateCompat { + fn from(value: ProtocolContractState) -> Self { + match value { + ProtocolContractState::NotInitialized => Self::NotInitialized, + ProtocolContractState::Initializing(state) => Self::Initializing(state.into()), + ProtocolContractState::Running(state) => Self::Running(state.into()), + ProtocolContractState::Resharing(state) => Self::Resharing(state.into()), + } + } +} + +fn params_to_string(output: &mut String, parameters: &GovernanceThresholdParameters) { output.push_str(" Participants:\n"); for (account_id, id, info) in ¶meters.participants.participants { output.push_str(&format!(" ID {}: {} ({})\n", id, account_id, info.url)); } output.push_str(&format!( - " Threshold: {}\n", - parameters.threshold.value() + " GovernanceThreshold: {}\n", + parameters.governance_threshold.value() )); } @@ -460,17 +763,18 @@ mod tests { fn proposed_threshold_parameters__handles_current_proposal_payload() { // Given let participants = sample_participants(); - let proposal = serde_json::to_value(ProposedThresholdParameters { - parameters: ThresholdParameters { + let proposal = serde_json::to_value(ProposedGovernanceThresholdParametersCompat { + parameters: GovernanceThresholdParametersCompat { participants, - threshold: Threshold::new(1), + threshold: GovernanceThreshold::new(1), }, per_domain_thresholds: BTreeMap::from([(DomainId(0), ReconstructionThreshold::new(1))]), }) .unwrap(); // When - let parsed: ProposedThresholdParameters = serde_json::from_value(proposal).unwrap(); + let parsed: ProposedGovernanceThresholdParametersCompat = + serde_json::from_value(proposal).unwrap(); // Then assert_eq!(parsed.per_domain_thresholds.len(), 1); @@ -485,13 +789,14 @@ mod tests { let proposal = serde_json::from_value(serde_json::json!( { "parameters": { "participants": participants, - "threshold": Threshold::new(1), + "threshold": GovernanceThreshold::new(1), } })) .unwrap(); // When - let parsed: ProposedThresholdParameters = serde_json::from_value(proposal).unwrap(); + let parsed: ProposedGovernanceThresholdParametersCompat = + serde_json::from_value(proposal).unwrap(); // Then assert_eq!(parsed.per_domain_thresholds.len(), 0); diff --git a/crates/node/src/assets/test_utils.rs b/crates/node/src/assets/test_utils.rs index f3f1c6a313..211011e744 100644 --- a/crates/node/src/assets/test_utils.rs +++ b/crates/node/src/assets/test_utils.rs @@ -13,7 +13,7 @@ use crate::{ use ed25519_dalek::{SigningKey, VerifyingKey}; use k256::ProjectivePoint; use mpc_contract::primitives::test_utils::gen_participants; -use mpc_contract::primitives::thresholds::{Threshold, ThresholdParameters}; +use mpc_contract::primitives::thresholds::{GovernanceThreshold, GovernanceThresholdParameters}; use mpc_primitives::{EpochId, ReconstructionThreshold, domain::DomainId}; use near_time::FakeClock; use rand::RngCore; @@ -48,12 +48,14 @@ pub fn triple_v2_key(t: ReconstructionThreshold, id: UniqueId) -> Vec { pub fn gen_four_participants() -> (EpochData, ParticipantId, ReconstructionThreshold) { let reconstruction_threshold = ReconstructionThreshold::new(3); let epoch_id = EpochId::new(rand::thread_rng().next_u64()); - let parameters = ThresholdParameters::new( + let parameters = GovernanceThresholdParameters::new( gen_participants(4), - Threshold::new(reconstruction_threshold.inner()), + GovernanceThreshold::new(reconstruction_threshold.inner()), ) .unwrap(); - let parameters_dto: near_mpc_contract_interface::types::ThresholdParameters = parameters.into(); + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + let parameters_dto: near_mpc_contract_interface::types::GovernanceThresholdParameters = + parameters.into(); let participants: ParticipantsConfig = convert_participant_infos(parameters_dto, None).unwrap(); let epoch_data = EpochData { epoch_id, @@ -78,7 +80,7 @@ pub struct TestContext { pub my_participant_id: ParticipantId, pub alive_participants: Arc>>, pub presign_domain_ids: Vec, - /// Threshold whose `TripleV2` prefix `populate`/`assert_owned` operate on; + /// GovernanceThreshold whose `TripleV2` prefix `populate`/`assert_owned` operate on; /// matches the fixture from [`gen_four_participants`]. pub triple_threshold: ReconstructionThreshold, } diff --git a/crates/node/src/indexer.rs b/crates/node/src/indexer.rs index 95e713b104..9e49bbd70b 100644 --- a/crates/node/src/indexer.rs +++ b/crates/node/src/indexer.rs @@ -337,7 +337,13 @@ impl IndexerViewClient { &self, mpc_contract_id: AccountId, ) -> anyhow::Result<(u64, dtos::ProtocolContractState)> { - self.get_mpc_state(mpc_contract_id, STATE).await + // TODO(XXXX): Switch to canonical after upgrade 3.14.0 + // The contract's `state()` view still emits the pre-3903 field names, so + // deserialize the compat shape and convert to the canonical DTO used + // throughout the node. + let (height, state): (u64, dtos::ProtocolContractStateCompat) = + self.get_mpc_state(mpc_contract_id, STATE).await?; + Ok((height, state.into())) } pub(crate) async fn get_mpc_allowed_image_hashes( diff --git a/crates/node/src/indexer/fake.rs b/crates/node/src/indexer/fake.rs index e24e742e63..1235dd34dd 100644 --- a/crates/node/src/indexer/fake.rs +++ b/crates/node/src/indexer/fake.rs @@ -25,7 +25,7 @@ use mpc_contract::primitives::{ domain::DomainRegistry, key_state::{EpochId, KeyEventId, Keyset}, participants::{ParticipantId, ParticipantInfo, Participants}, - thresholds::{Threshold, ThresholdParameters}, + thresholds::{GovernanceThreshold, GovernanceThresholdParameters}, }; use mpc_contract::state::{ ProtocolContractState, initializing::InitializingContractState, key_event::KeyEvent, @@ -214,7 +214,7 @@ impl FakeMpcContractState { /// TLS key of `account_id` in any participant set of the current phase. fn participant_tls_key(&self, account_id: &AccountId) -> Option { - let parameter_sets: Vec<&ThresholdParameters> = match &self.state { + let parameter_sets: Vec<&GovernanceThresholdParameters> = match &self.state { ProtocolContractState::NotInitialized => vec![], ProtocolContractState::Initializing(state) => { vec![state.generating_key.proposed_parameters()] @@ -471,9 +471,11 @@ impl FakeMpcContractState { new_participants .update_info(account_id, participant_info) .unwrap(); - let new_parameters = - ThresholdParameters::new(new_participants, state.parameters.threshold()) - .unwrap(); + let new_parameters = GovernanceThresholdParameters::new( + new_participants, + state.parameters.threshold(), + ) + .unwrap(); let new_state = RunningContractState { domains: state.domains.clone(), keyset: state.keyset.clone(), @@ -522,7 +524,7 @@ pub fn participant_info_from_config(info: &config::ParticipantInfo) -> Participa fn participants_config_to_threshold_parameters( participants_config: &ParticipantsConfig, -) -> ThresholdParameters { +) -> GovernanceThresholdParameters { let mut participants = Participants::new(); let mut infos = participants_config.participants.clone(); infos.sort_by_key(|info| info.id); @@ -536,7 +538,11 @@ fn participants_config_to_threshold_parameters( ) .expect("Failed to insert participant"); } - ThresholdParameters::new(participants, Threshold::new(participants_config.threshold)).unwrap() + GovernanceThresholdParameters::new( + participants, + GovernanceThreshold::new(participants_config.threshold), + ) + .unwrap() } /// Runs the fake indexer's shared state and logic. There's one instance of this per test. diff --git a/crates/node/src/indexer/participants.rs b/crates/node/src/indexer/participants.rs index 623aca889d..be16b12cb3 100644 --- a/crates/node/src/indexer/participants.rs +++ b/crates/node/src/indexer/participants.rs @@ -6,7 +6,9 @@ use ed25519_dalek::VerifyingKey; use mpc_primitives::KeyEventId as ContractKeyEventId; use near_account_id::AccountId; use near_mpc_contract_interface::types as dtos; -use near_mpc_contract_interface::types::{KeyEvent, ProtocolContractState, ThresholdParameters}; +use near_mpc_contract_interface::types::{ + GovernanceThresholdParameters, KeyEvent, ProtocolContractState, +}; use near_mpc_crypto_types::{KeyForDomain as ContractKeyForDomain, Keyset as ContractKeyset}; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; @@ -217,7 +219,7 @@ impl ContractState { &state.resharing_key, height, state.reshared_keys.clone(), - &state.per_domain_thresholds, + &state.per_domain_reconstruction_thresholds, ) .context("failed to convert resharing key event")?; @@ -386,8 +388,9 @@ fn parse_participant_address( Ok((address.to_string(), port)) } +// TODO(XXXX): Switch to canonical after upgrade 3.14.0 pub fn convert_participant_infos( - threshold_parameters: ThresholdParameters, + threshold_parameters: GovernanceThresholdParameters, port_override: Option, ) -> anyhow::Result { let mut converted = Vec::new(); @@ -426,7 +429,7 @@ pub fn convert_participant_infos( } Ok(ParticipantsConfig { participants: converted, - threshold: threshold_parameters.threshold.0, + threshold: threshold_parameters.governance_threshold.0, }) } @@ -552,8 +555,8 @@ mod tests { use near_indexer_primitives::types::AccountId; use near_mpc_contract_interface::types::AccountId as DtoAccountId; use near_mpc_contract_interface::types::{ - ParticipantId, ParticipantInfo, Participants, ProtocolContractState, - ReconstructionThreshold, Threshold, ThresholdParameters, + GovernanceThreshold, GovernanceThresholdParameters, ParticipantId, ParticipantInfo, + Participants, ProtocolContractState, ReconstructionThreshold, }; use std::collections::{BTreeMap, HashMap}; @@ -619,9 +622,9 @@ mod tests { account_id_to_pk.insert(account_id.clone(), info.tls_public_key.clone()); } assert!(account_ids.is_sorted()); - let params = ThresholdParameters { + let params = GovernanceThresholdParameters { participants: chain_infos.clone(), - threshold: Threshold(3), + governance_threshold: GovernanceThreshold(3), }; let converted = convert_participant_infos(params, None).unwrap(); @@ -645,9 +648,9 @@ mod tests { fn test_port_override() { let chain_infos = create_chain_participant_infos(); - let params = ThresholdParameters { + let params = GovernanceThresholdParameters { participants: chain_infos, - threshold: Threshold(3), + governance_threshold: GovernanceThreshold(3), }; let converted = convert_participant_infos(params.clone(), None) .unwrap() @@ -672,12 +675,12 @@ mod tests { let original_count = entries.len(); entries[0].2.url = "http://:3000".to_string(); let account = entries[0].0.clone(); - let params = ThresholdParameters { + let params = GovernanceThresholdParameters { participants: Participants { next_id: ParticipantId(entries.len() as u32), participants: entries, }, - threshold: Threshold(3), + governance_threshold: GovernanceThreshold(3), }; // When @@ -701,12 +704,12 @@ mod tests { let mut entries = create_chain_participant_infos().participants; entries[0].2.url = "http://:3000".to_string(); let account = entries[0].0.clone(); - let params = ThresholdParameters { + let params = GovernanceThresholdParameters { participants: Participants { next_id: ParticipantId(entries.len() as u32), participants: entries, }, - threshold: Threshold(3), + governance_threshold: GovernanceThreshold(3), }; // When diff --git a/crates/primitives/src/key_state.rs b/crates/primitives/src/key_state.rs index a59ccee2f5..86224ed0c1 100644 --- a/crates/primitives/src/key_state.rs +++ b/crates/primitives/src/key_state.rs @@ -2,8 +2,8 @@ use crate::domain::DomainId; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -/// An EpochId uniquely identifies a ThresholdParameters (but not vice-versa). -/// Every time we change the ThresholdParameters (participants and threshold), +/// An EpochId uniquely identifies a GovernanceThresholdParameters (but not vice-versa). +/// Every time we change the GovernanceThresholdParameters (participants and threshold), /// we increment EpochId. /// Locally on each node, each keyshare is uniquely identified by the tuple /// (EpochId, DomainId, AttemptId). @@ -91,7 +91,7 @@ impl AttemptId { } /// A unique identifier for a key event (generation or resharing): -/// `epoch_id`: identifies the ThresholdParameters that this key is intended to function in. +/// `epoch_id`: identifies the GovernanceThresholdParameters that this key is intended to function in. /// `domain_id`: the domain this key is intended for. /// `attempt_id`: identifies a particular attempt for this key event, in case multiple attempts /// yielded partially valid results. This is incremented for each attempt within the diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 4d77e03374..daa1ccc157 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -14,7 +14,7 @@ pub mod yield_index; pub use account_id::AccountId; pub use key_state::{AttemptId, EpochId, KeyEventId}; pub use participant_id::ParticipantId; -pub use threshold::{ReconstructionThreshold, Threshold}; +pub use threshold::{GovernanceThreshold, ReconstructionThreshold}; pub use yield_index::YieldIndex; /// Re-exports used by the [`define_hash!`] macro. Not part of the public API. diff --git a/crates/primitives/src/threshold.rs b/crates/primitives/src/threshold.rs index 977ae09ccc..bb728c1df5 100644 --- a/crates/primitives/src/threshold.rs +++ b/crates/primitives/src/threshold.rs @@ -1,8 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -/// Cryptographic threshold (`k`) for a distributed key: the minimum number of -/// participants that must collaborate to produce a signature. +/// Network-wide governance threshold (`k`): the minimum number of participants +/// that must agree to approve a governance action (participant set changes, +/// resharing, parameter updates). Distinct from a domain's +/// [`ReconstructionThreshold`], the per-domain `t` needed to reconstruct a key. #[derive( Clone, Copy, @@ -24,9 +26,9 @@ use serde::{Deserialize, Serialize}; all(feature = "abi", not(target_arch = "wasm32")), derive(schemars::JsonSchema, borsh::BorshSchema) )] -pub struct Threshold(pub u64); +pub struct GovernanceThreshold(pub u64); -impl Threshold { +impl GovernanceThreshold { pub fn new(value: u64) -> Self { Self(value) } @@ -69,8 +71,8 @@ impl ReconstructionThreshold { } } -impl From for ReconstructionThreshold { - fn from(value: Threshold) -> Self { +impl From for ReconstructionThreshold { + fn from(value: GovernanceThreshold) -> Self { Self(value.value()) } } diff --git a/docs/securing-mpc-with-tee-design-doc.md b/docs/securing-mpc-with-tee-design-doc.md index 5e572748cd..128092e9d1 100644 --- a/docs/securing-mpc-with-tee-design-doc.md +++ b/docs/securing-mpc-with-tee-design-doc.md @@ -365,7 +365,7 @@ pub struct Contract { pub fn vote_new_parameters( &mut self, prospective_epoch_id: EpochId, - proposal: ThresholdParameters, + proposal: GovernanceThresholdParameters, ) -> Result<(), Error> ///If the vote threshold is reached and the new Docker image hash