diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index e8349d4eb0..8104a1c910 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -193,6 +193,9 @@ pub enum MixnetContractError { #[error("attempted to perform the operation with 0 coins. This is not allowed")] ZeroCoinAmount, + #[error("key rotation validity below minimum value")] + TooShortRotationInterval, + #[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")] RewardingValidatorMismatch { current_validator: Addr, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index dcd218dd62..0fedbd84e9 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -82,6 +82,18 @@ pub struct InstantiateMsg { #[serde(default)] pub interval_operating_cost: OperatingCostRange, + + #[serde(default)] + pub key_validity_in_epochs: Option, +} + +impl InstantiateMsg { + // needs to give us enough time to pre-announce key for following epoch + // and have an overlap with the preceding epoch + pub const MIN_KEY_ROTATION_VALIDITY: u32 = 3; + pub fn key_validity_in_epochs(&self) -> u32 { + self.key_validity_in_epochs.unwrap_or(24) + } } #[cw_serde] diff --git a/contracts/mixnet-vesting-integration-tests/src/support/fixtures.rs b/contracts/mixnet-vesting-integration-tests/src/support/fixtures.rs index 3b857d6dba..2df1a87e35 100644 --- a/contracts/mixnet-vesting-integration-tests/src/support/fixtures.rs +++ b/contracts/mixnet-vesting-integration-tests/src/support/fixtures.rs @@ -34,5 +34,6 @@ pub fn default_mixnet_init_msg() -> nym_mixnet_contract_common::InstantiateMsg { version_score_params: Default::default(), profit_margin: Default::default(), interval_operating_cost: Default::default(), + key_validity_in_epochs: None, } } diff --git a/contracts/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json index 716d29d452..cf36563949 100644 --- a/contracts/mixnet/schema/nym-mixnet-contract.json +++ b/contracts/mixnet/schema/nym-mixnet-contract.json @@ -41,6 +41,15 @@ } ] }, + "key_validity_in_epochs": { + "default": null, + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, "profit_margin": { "default": { "maximum": "1", diff --git a/contracts/mixnet/schema/raw/instantiate.json b/contracts/mixnet/schema/raw/instantiate.json index 0eca5410be..b17e1ae6bf 100644 --- a/contracts/mixnet/schema/raw/instantiate.json +++ b/contracts/mixnet/schema/raw/instantiate.json @@ -37,6 +37,15 @@ } ] }, + "key_validity_in_epochs": { + "default": null, + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, "profit_margin": { "default": { "maximum": "1", diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index b856630476..522017c762 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -83,6 +83,11 @@ pub fn instantiate( }); } + let key_rotation_validity = msg.key_validity_in_epochs(); + if key_rotation_validity < InstantiateMsg::MIN_KEY_ROTATION_VALIDITY { + return Err(MixnetContractError::TooShortRotationInterval); + } + let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?; let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?; let state = default_initial_state( @@ -110,7 +115,7 @@ pub fn instantiate( msg.current_nym_node_version, )?; RewardingStorage::new().initialise(deps.storage, reward_params)?; - nymnodes_storage::initialise_storage(deps.storage)?; + nymnodes_storage::initialise_storage(deps.storage, key_rotation_validity)?; cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; set_build_information!(deps.storage)?; @@ -690,6 +695,7 @@ mod tests { minimum: "1000".parse().unwrap(), maximum: "10000".parse().unwrap(), }, + key_validity_in_epochs: None, }; let sender = message_info(&deps.api.addr_make("sender"), &[]); diff --git a/contracts/mixnet/src/nodes/storage/helpers.rs b/contracts/mixnet/src/nodes/storage/helpers.rs index 8a2fade399..7c8ae9dc6e 100644 --- a/contracts/mixnet/src/nodes/storage/helpers.rs +++ b/contracts/mixnet/src/nodes/storage/helpers.rs @@ -103,7 +103,10 @@ pub(crate) fn next_nymnode_id_counter(store: &mut dyn Storage) -> StdResult Result<(), MixnetContractError> { +pub(crate) fn initialise_storage( + storage: &mut dyn Storage, + key_rotation_validity: u32, +) -> Result<(), MixnetContractError> { let active_bucket = RoleStorageBucket::default(); let inactive_bucket = active_bucket.other(); @@ -128,7 +131,7 @@ pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), Mixnet KEY_ROTATION_STATE.save( storage, &KeyRotationState { - validity_epochs: 24, + validity_epochs: key_rotation_validity, initial_epoch_id: 0, }, )?; diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index fed6a7e248..1418f0d4cf 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -1858,6 +1858,7 @@ pub mod test_helpers { version_score_params: Default::default(), profit_margin: Default::default(), interval_operating_cost: Default::default(), + key_validity_in_epochs: None, }; let env = mock_env(); let info = sender("creator"); diff --git a/tools/internal/testnet-manager/src/cli/initialise_new_network.rs b/tools/internal/testnet-manager/src/cli/initialise_new_network.rs index 574559bf82..99174e6d51 100644 --- a/tools/internal/testnet-manager/src/cli/initialise_new_network.rs +++ b/tools/internal/testnet-manager/src/cli/initialise_new_network.rs @@ -25,6 +25,10 @@ pub(crate) struct Args { #[clap(long)] custom_epoch_duration_secs: Option, + /// Specifies custom number of epochs sphinx keys are going to be valid for + #[clap(long)] + key_validity_in_epochs: Option, + #[clap(short, long, default_value_t = OutputFormat::default())] output: OutputFormat, } @@ -38,6 +42,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> { args.built_contracts, args.network_name, args.custom_epoch_duration_secs.map(Duration::from_secs), + args.key_validity_in_epochs, ) .await? .into_loaded(); diff --git a/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs b/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs index d302177fad..1e5d24b1bf 100644 --- a/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs +++ b/tools/internal/testnet-manager/src/cli/initialise_post_dkg_network.rs @@ -39,6 +39,10 @@ pub(crate) struct Args { #[clap(long)] custom_epoch_duration_secs: Option, + /// Specifies custom number of epochs sphinx keys are going to be valid for + #[clap(long)] + key_validity_in_epochs: Option, + #[clap(short, long, default_value_t = OutputFormat::default())] output: OutputFormat, } @@ -51,6 +55,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> { args.built_contracts, args.network_name, args.custom_epoch_duration_secs.map(Duration::from_secs), + args.key_validity_in_epochs, ) .await? .into(); diff --git a/tools/internal/testnet-manager/src/cli/local_ecash_apis.rs b/tools/internal/testnet-manager/src/cli/local_ecash_apis.rs index f0f9a4475a..587e5c0375 100644 --- a/tools/internal/testnet-manager/src/cli/local_ecash_apis.rs +++ b/tools/internal/testnet-manager/src/cli/local_ecash_apis.rs @@ -37,6 +37,10 @@ pub(crate) struct Args { #[clap(long)] custom_epoch_duration_secs: Option, + /// Specifies custom number of epochs sphinx keys are going to be valid for + #[clap(long)] + key_validity_in_epochs: Option, + #[clap(short, long, default_value_t = OutputFormat::default())] output: OutputFormat, } @@ -53,6 +57,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> { args.built_contracts, args.network_name, args.custom_epoch_duration_secs.map(Duration::from_secs), + args.key_validity_in_epochs, ) .await? .into(); diff --git a/tools/internal/testnet-manager/src/manager/network_init.rs b/tools/internal/testnet-manager/src/manager/network_init.rs index 1a0fc0e41d..8e978e4fc6 100644 --- a/tools/internal/testnet-manager/src/manager/network_init.rs +++ b/tools/internal/testnet-manager/src/manager/network_init.rs @@ -127,6 +127,7 @@ impl NetworkManager { &self, ctx: &InitCtx, custom_epoch_duration: Option, + key_validity_in_epochs: Option, ) -> Result { Ok(nym_mixnet_contract_common::InstantiateMsg { rewarding_validator_address: ctx @@ -165,6 +166,7 @@ impl NetworkManager { version_score_params: Default::default(), profit_margin: Default::default(), interval_operating_cost: Default::default(), + key_validity_in_epochs, }) } @@ -408,6 +410,7 @@ impl NetworkManager { &self, ctx: &mut InitCtx, custom_epoch_duration: Option, + key_validity_in_epochs: Option, ) -> Result<(), NetworkManagerError> { ctx.println(format!( "💽 {}Instantiating all the contracts...", @@ -422,7 +425,8 @@ impl NetworkManager { let code_id = ctx.network.contracts.mixnet.upload_info()?.code_id; let admin = ctx.network.contracts.mixnet.admin()?.address.clone(); ctx.set_pb_message(format!("attempting to instantiate {name} contract...")); - let init_msg = self.mixnet_init_message(ctx, custom_epoch_duration)?; + let init_msg = + self.mixnet_init_message(ctx, custom_epoch_duration, key_validity_in_epochs)?; let init_fut = ctx.admin.instantiate( code_id, &init_msg, @@ -694,6 +698,7 @@ impl NetworkManager { contracts: P, network_name: Option, custom_epoch_duration: Option, + key_validity_in_epochs: Option, ) -> Result { let network_name = self.get_network_name(network_name); let mut ctx = InitCtx::new(network_name, self.admin.deref().clone(), &self.rpc_endpoint)?; @@ -702,7 +707,7 @@ impl NetworkManager { self.upload_contracts(&mut ctx).await?; self.create_contract_admins_mnemonics(&mut ctx)?; self.transfer_admin_tokens(&ctx).await?; - self.instantiate_contracts(&mut ctx, custom_epoch_duration) + self.instantiate_contracts(&mut ctx, custom_epoch_duration, key_validity_in_epochs) .await?; self.perform_final_migrations(&mut ctx).await?; self.get_build_info(&mut ctx).await?;