From e9165763b6a0cfdcb44f7677cec3f4711e49645f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 21 Jul 2025 17:32:57 +0100 Subject: [PATCH] Feature/dkg snapshot epoch (#5900) * define storage item for holding historical DKG state * make all epoch storage operations go through proxy functions * make each saving action also apply to the historical item * removed usage of update_epoch function * test correct save heights * exposed query for the epoch state at specified height * regenerated contract schema * restored default cw-plus behaviour as in hindsight it makes more sense --- .../nyxd/contract_traits/dkg_query_client.rs | 8 + .../coconut-dkg/src/msg.rs | 3 + contracts/Cargo.lock | 1 + contracts/coconut-dkg/Cargo.toml | 1 + .../coconut-dkg/schema/nym-coconut-dkg.json | 288 ++++++++++++++++++ contracts/coconut-dkg/schema/raw/query.json | 23 ++ ...response_to_get_epoch_state_at_height.json | 265 ++++++++++++++++ contracts/coconut-dkg/src/contract.rs | 25 +- contracts/coconut-dkg/src/dealers/queries.rs | 8 +- .../coconut-dkg/src/dealers/transactions.rs | 20 +- .../coconut-dkg/src/dealings/transactions.rs | 18 +- .../coconut-dkg/src/epoch_state/queries.rs | 19 +- .../coconut-dkg/src/epoch_state/storage.rs | 216 ++++++++++++- .../transactions/advance_epoch_state.rs | 132 ++++---- .../src/epoch_state/transactions/mod.rs | 19 +- .../coconut-dkg/src/epoch_state/utils.rs | 17 +- contracts/coconut-dkg/src/error.rs | 3 + contracts/coconut-dkg/src/lib.rs | 1 + .../coconut-dkg/src/queued_migrations.rs | 21 ++ .../coconut-dkg/src/support/tests/fixtures.rs | 6 +- .../coconut-dkg/src/support/tests/helpers.rs | 6 +- .../src/verification_key_shares/queries.rs | 6 +- .../verification_key_shares/transactions.rs | 59 ++-- 23 files changed, 1013 insertions(+), 152 deletions(-) create mode 100644 contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json create mode 100644 contracts/coconut-dkg/src/queued_migrations.rs diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs index e601dc75ae..a77d15eda9 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs @@ -41,6 +41,11 @@ pub trait DkgQueryClient { self.query_dkg_contract(request).await } + async fn get_epoch_at_height(&self, height: u64) -> Result, NyxdError> { + let request = DkgQueryMsg::GetEpochStateAtHeight { height }; + self.query_dkg_contract(request).await + } + async fn can_advance_state(&self) -> Result { let request = DkgQueryMsg::CanAdvanceState {}; self.query_dkg_contract(request).await @@ -299,6 +304,9 @@ mod tests { match msg { DkgQueryMsg::GetState {} => client.get_state().ignore(), DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(), + DkgQueryMsg::GetEpochStateAtHeight { height } => { + client.get_epoch_at_height(height).ignore() + } DkgQueryMsg::CanAdvanceState {} => client.can_advance_state().ignore(), DkgQueryMsg::GetCurrentEpochThreshold {} => { client.get_current_epoch_threshold().ignore() diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index 2db47d97dc..151fb2f8bc 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -84,6 +84,9 @@ pub enum QueryMsg { #[cfg_attr(feature = "schema", returns(Epoch))] GetCurrentEpochState {}, + #[cfg_attr(feature = "schema", returns(Option))] + GetEpochStateAtHeight { height: u64 }, + #[cfg_attr(feature = "schema", returns(u64))] GetCurrentEpochThreshold {}, diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index a434eb8778..11389591a5 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1091,6 +1091,7 @@ dependencies = [ name = "nym-coconut-dkg" version = "0.1.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers", diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index e65539ea41..e77171d8b8 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -29,6 +29,7 @@ cw4 = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" } cw-multi-test = { workspace = true } cw4-group = { path = "../multisig/cw4-group" } diff --git a/contracts/coconut-dkg/schema/nym-coconut-dkg.json b/contracts/coconut-dkg/schema/nym-coconut-dkg.json index 720b712c7b..677dadd012 100644 --- a/contracts/coconut-dkg/schema/nym-coconut-dkg.json +++ b/contracts/coconut-dkg/schema/nym-coconut-dkg.json @@ -361,6 +361,29 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_epoch_state_at_height" + ], + "properties": { + "get_epoch_state_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -2036,6 +2059,271 @@ } } }, + "get_epoch_state_at_height": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Epoch", + "anyOf": [ + { + "$ref": "#/definitions/Epoch" + }, + { + "type": "null" + } + ], + "definitions": { + "Epoch": { + "type": "object", + "required": [ + "epoch_id", + "state", + "state_progress", + "time_configuration" + ], + "properties": { + "deadline": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "state": { + "$ref": "#/definitions/EpochState" + }, + "state_progress": { + "$ref": "#/definitions/StateProgress" + }, + "time_configuration": { + "$ref": "#/definitions/TimeConfiguration" + } + }, + "additionalProperties": false + }, + "EpochState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "waiting_initialisation", + "in_progress" + ] + }, + { + "type": "object", + "required": [ + "public_key_submission" + ], + "properties": { + "public_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dealing_exchange" + ], + "properties": { + "dealing_exchange": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_submission" + ], + "properties": { + "verification_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_validation" + ], + "properties": { + "verification_key_validation": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_finalization" + ], + "properties": { + "verification_key_finalization": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "StateProgress": { + "type": "object", + "required": [ + "registered_dealers", + "registered_resharing_dealers", + "submitted_dealings", + "submitted_key_shares", + "verified_keys" + ], + "properties": { + "registered_dealers": { + "description": "Counts the number of dealers that have registered in this epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "registered_resharing_dealers": { + "description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_dealings": { + "description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_key_shares": { + "description": "Counts the number of submitted verification key shared from the dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "verified_keys": { + "description": "Counts the number of verified key shares.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, "get_epoch_threshold": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "uint64", diff --git a/contracts/coconut-dkg/schema/raw/query.json b/contracts/coconut-dkg/schema/raw/query.json index c57d120335..65b9e038e2 100644 --- a/contracts/coconut-dkg/schema/raw/query.json +++ b/contracts/coconut-dkg/schema/raw/query.json @@ -28,6 +28,29 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_epoch_state_at_height" + ], + "properties": { + "get_epoch_state_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json new file mode 100644 index 0000000000..8f6fb68afb --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json @@ -0,0 +1,265 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Epoch", + "anyOf": [ + { + "$ref": "#/definitions/Epoch" + }, + { + "type": "null" + } + ], + "definitions": { + "Epoch": { + "type": "object", + "required": [ + "epoch_id", + "state", + "state_progress", + "time_configuration" + ], + "properties": { + "deadline": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "state": { + "$ref": "#/definitions/EpochState" + }, + "state_progress": { + "$ref": "#/definitions/StateProgress" + }, + "time_configuration": { + "$ref": "#/definitions/TimeConfiguration" + } + }, + "additionalProperties": false + }, + "EpochState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "waiting_initialisation", + "in_progress" + ] + }, + { + "type": "object", + "required": [ + "public_key_submission" + ], + "properties": { + "public_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dealing_exchange" + ], + "properties": { + "dealing_exchange": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_submission" + ], + "properties": { + "verification_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_validation" + ], + "properties": { + "verification_key_validation": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_finalization" + ], + "properties": { + "verification_key_finalization": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "StateProgress": { + "type": "object", + "required": [ + "registered_dealers", + "registered_resharing_dealers", + "submitted_dealings", + "submitted_key_shares", + "verified_keys" + ], + "properties": { + "registered_dealers": { + "description": "Counts the number of dealers that have registered in this epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "registered_resharing_dealers": { + "description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_dealings": { + "description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_key_shares": { + "description": "Counts the number of submitted verification key shared from the dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "verified_keys": { + "description": "Counts the number of verified key shares.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index 4b04a47939..bbd649cea2 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -14,13 +14,14 @@ use crate::dealings::queries::{ use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata}; use crate::epoch_state::queries::{ query_can_advance_state, query_current_epoch, query_current_epoch_threshold, - query_epoch_threshold, + query_epoch_at_height, query_epoch_threshold, }; -use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD}; +use crate::epoch_state::storage::save_epoch; use crate::epoch_state::transactions::{ try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing, }; use crate::error::ContractError; +use crate::queued_migrations::introduce_historical_epochs; use crate::state::queries::query_state; use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE}; use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged}; @@ -68,8 +69,9 @@ pub fn instantiate( }; STATE.save(deps.storage, &state)?; - CURRENT_EPOCH.save( + save_epoch( deps.storage, + env.block.height, &Epoch::new( EpochState::WaitingInitialisation, 0, @@ -101,6 +103,7 @@ pub fn execute( resharing, } => try_add_dealer( deps, + env, info, bte_key_with_proof, identity_key, @@ -119,7 +122,7 @@ pub fn execute( try_commit_verification_key_share(deps, env, info, share, resharing) } ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => { - try_verify_verification_key_share(deps, info, owner, resharing) + try_verify_verification_key_share(deps, env, info, owner, resharing) } ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env), ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info), @@ -132,6 +135,9 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result to_json_binary(&query_state(deps.storage)?)?, QueryMsg::GetCurrentEpochState {} => to_json_binary(&query_current_epoch(deps.storage)?)?, + QueryMsg::GetEpochStateAtHeight { height } => { + to_json_binary(&query_epoch_at_height(deps.storage, height)?)? + } QueryMsg::CanAdvanceState {} => { to_json_binary(&query_can_advance_state(deps.storage, env)?)? } @@ -242,16 +248,11 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result { set_build_information!(deps.storage)?; cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - // MAINNET MIGRATION ASSERTION - let epoch = CURRENT_EPOCH.load(deps.storage)?; - assert_eq!(0, epoch.epoch_id); - - let threshold = THRESHOLD.load(deps.storage)?; - EPOCH_THRESHOLDS.save(deps.storage, 0, &threshold)?; + introduce_historical_epochs(deps, env)?; Ok(Response::new()) } @@ -355,7 +356,7 @@ mod tests { let api = MockApi::default(); const MEMBER_SIZE: usize = 100; let members: [Addr; MEMBER_SIZE] = - std::array::from_fn(|idx| api.addr_make(&format!("member{}", idx))); + std::array::from_fn(|idx| api.addr_make(&format!("member{idx}"))); let mut app = AppBuilder::new().build(|router, _, storage| { router diff --git a/contracts/coconut-dkg/src/dealers/queries.rs b/contracts/coconut-dkg/src/dealers/queries.rs index 41fe63426d..4a309405f7 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -5,7 +5,7 @@ use crate::dealers::storage::{ self, get_dealer_details, get_dealer_index, get_registration_details, DEALERS_INDICES, EPOCH_DEALERS_MAP, }; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::load_current_epoch; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; use nym_coconut_dkg_common::dealer::{ @@ -23,7 +23,7 @@ pub fn query_registered_dealer_details( let epoch_id = match epoch_id { Some(epoch_id) => epoch_id, - None => CURRENT_EPOCH.load(deps.storage)?.epoch_id, + None => load_current_epoch(deps.storage)?.epoch_id, }; Ok(RegisteredDealerDetails { @@ -36,7 +36,7 @@ pub fn query_dealer_details( dealer_address: String, ) -> StdResult { let addr = deps.api.addr_validate(&dealer_address)?; - let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; + let current_epoch_id = load_current_epoch(deps.storage)?.epoch_id; // if the address has registration data for the current epoch, it means it's an active dealer if let Ok(dealer_details) = get_dealer_details(deps.storage, &addr, current_epoch_id) { @@ -157,7 +157,7 @@ pub fn query_current_dealers_paged( start_after: Option, limit: Option, ) -> StdResult { - let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; + let current_epoch_id = load_current_epoch(deps.storage)?.epoch_id; query_epoch_dealers_paged(deps, current_epoch_id, start_after, limit) } diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index a7fee91108..ccb6d57c29 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -4,12 +4,12 @@ use crate::dealers::storage::{ get_or_assign_index, is_dealer, save_dealer_details_if_not_a_dealer, }; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::{load_current_epoch, save_epoch}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::STATE; use crate::Dealer; -use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult}; +use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, Response}; use nym_coconut_dkg_common::dealer::DealerRegistrationDetails; use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; @@ -28,13 +28,14 @@ fn ensure_group_member(deps: Deps, dealer: Dealer) -> Result<(), ContractError> // for a recurring dealer just let it refresh the keys without having to do all the storage operations pub fn try_add_dealer( deps: DepsMut<'_>, + env: Env, info: MessageInfo, bte_key_with_proof: EncodedBTEPublicKeyWithProof, identity_key: String, announce_address: String, resharing: bool, ) -> Result { - let epoch = CURRENT_EPOCH.load(deps.storage)?; + let epoch = load_current_epoch(deps.storage)?; check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?; // make sure this potential dealer actually belong to the group @@ -68,16 +69,16 @@ pub fn try_add_dealer( ); // increment the number of registered dealers - CURRENT_EPOCH.update(deps.storage, |epoch| -> StdResult<_> { - let mut updated_epoch = epoch; + { + let current_epoch = load_current_epoch(deps.storage)?; + let mut updated_epoch = current_epoch; updated_epoch.state_progress.registered_dealers += 1; if is_resharing_dealer { updated_epoch.state_progress.registered_resharing_dealers += 1; } - - Ok(updated_epoch) - })?; + save_epoch(deps.storage, env.block.height, &updated_epoch)?; + } Ok(Response::new().add_attribute("node_index", node_index.to_string())) } @@ -115,10 +116,11 @@ pub(crate) mod tests { .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); add_fixture_dealer(deps.as_mut()); - try_advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_add_dealer( deps.as_mut(), + env, info, bte_key_with_proof, identity, diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 73107c352f..186754c3a8 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -5,7 +5,7 @@ use crate::dealers::storage::ensure_dealer; use crate::dealings::storage::{ metadata_exists, must_read_metadata, store_metadata, StoredDealing, }; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::{load_current_epoch, save_epoch}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::STATE; @@ -42,7 +42,7 @@ pub fn try_submit_dealings_metadata( chunks: Vec, resharing: bool, ) -> Result { - let epoch = CURRENT_EPOCH.load(deps.storage)?; + let epoch = load_current_epoch(deps.storage)?; let state = STATE.load(deps.storage)?; ensure_permission(deps.storage, &info.sender, epoch.epoch_id, resharing)?; @@ -137,7 +137,7 @@ pub fn try_commit_dealings_chunk( // note: checking permissions is implicit as if the metadata exists, // the sender must have been allowed to submit it - let mut epoch = CURRENT_EPOCH.load(deps.storage)?; + let mut epoch = load_current_epoch(deps.storage)?; // read meta let mut metadata = must_read_metadata( @@ -197,7 +197,7 @@ pub fn try_commit_dealings_chunk( // there won't be a lot of them if metadata.is_complete() { epoch.state_progress.submitted_dealings += 1; - CURRENT_EPOCH.save(deps.storage, &epoch)?; + save_epoch(deps.storage, env.block.height, &epoch)?; } Ok(Response::new()) @@ -309,12 +309,10 @@ pub(crate) mod tests { ); // same index, but next epoch - CURRENT_EPOCH - .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| { - epoch.epoch_id += 1; - Ok(epoch) - }) - .unwrap(); + let mut epoch = load_current_epoch(&deps.storage).unwrap(); + epoch.epoch_id += 1; + save_epoch(deps.as_mut().storage, epoch.epoch_id, &epoch).unwrap(); + re_register_dealer(deps.as_mut(), &info.sender); try_submit_dealings_metadata( diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index 15ba780aec..f107d2e59c 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -1,17 +1,19 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD}; +use crate::epoch_state::storage::{ + load_current_epoch, EPOCH_THRESHOLDS, HISTORICAL_EPOCH, THRESHOLD, +}; use crate::epoch_state::utils::check_state_completion; use crate::error::ContractError; -use cosmwasm_std::{Env, Storage}; +use cosmwasm_std::{Env, StdResult, Storage}; use nym_coconut_dkg_common::types::{Epoch, EpochId, EpochState, StateAdvanceResponse}; pub(crate) fn query_can_advance_state( storage: &dyn Storage, env: Env, ) -> Result { - let epoch = CURRENT_EPOCH.load(storage)?; + let epoch = load_current_epoch(storage)?; if epoch.state == EpochState::WaitingInitialisation { return Ok(StateAdvanceResponse::default()); @@ -34,9 +36,14 @@ pub(crate) fn query_can_advance_state( } pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result { - CURRENT_EPOCH - .load(storage) - .map_err(|_| ContractError::EpochNotInitialised) + load_current_epoch(storage).map_err(|_| ContractError::EpochNotInitialised) +} + +pub(crate) fn query_epoch_at_height( + storage: &dyn Storage, + height: u64, +) -> StdResult> { + HISTORICAL_EPOCH.may_load_at_height(storage, height) } pub(crate) fn query_current_epoch_threshold( diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index faed18e431..4318c8cf7b 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -1,11 +1,225 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cw_storage_plus::{Item, Map}; +use cosmwasm_std::{StdResult, Storage}; +use cw_storage_plus::{Item, Map, SnapshotItem, Strategy}; use nym_coconut_dkg_common::types::{Epoch, EpochId}; +#[deprecated] +// leave old values in storage for backwards compatibility, but make sure everything in the contract +// uses the new reference pub(crate) const CURRENT_EPOCH: Item = Item::new("current_epoch"); +pub const HISTORICAL_EPOCH: SnapshotItem = SnapshotItem::new( + "historical_epoch", + "historical_epoch__checkpoints", + "historical_epoch__changelog", + Strategy::EveryBlock, +); pub const THRESHOLD: Item = Item::new("threshold"); pub const EPOCH_THRESHOLDS: Map = Map::new("epoch_thresholds"); + +#[allow(deprecated)] +pub fn save_epoch(storage: &mut dyn Storage, height: u64, epoch: &Epoch) -> StdResult<()> { + CURRENT_EPOCH.save(storage, epoch)?; + HISTORICAL_EPOCH.save(storage, epoch, height) +} + +#[allow(deprecated)] +pub fn load_current_epoch(storage: &dyn Storage) -> StdResult { + #[cfg(debug_assertions)] + { + let current = CURRENT_EPOCH.load(storage); + let historical = HISTORICAL_EPOCH.load(storage); + debug_assert_eq!(current, historical); + } + HISTORICAL_EPOCH.load(storage) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg}; + use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; + use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; + use cosmwasm_std::{Addr, Env}; + use nym_coconut_dkg_common::types::EpochState; + use std::ops::{Deref, DerefMut}; + + #[test] + fn full_dkg_correctly_updates_historical_epoch() -> anyhow::Result<()> { + struct EnvWrapper { + env: Env, + } + + impl EnvWrapper { + fn next_block(&mut self) { + self.env.block.height += 1; + self.env.block.time = self.env.block.time.plus_seconds(5); + } + + fn height(&self) -> u64 { + self.block.height + } + } + + impl Deref for EnvWrapper { + type Target = Env; + fn deref(&self) -> &Self::Target { + &self.env + } + } + + impl DerefMut for EnvWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.env + } + } + + let mut empty_deps = mock_dependencies(); + + // before contract is initialised, there's nothing saved + assert!(HISTORICAL_EPOCH + .may_load(empty_deps.as_mut().storage)? + .is_none()); + + let mut deps = init_contract(); + let mut env = EnvWrapper { env: mock_env() }; + + let init_height = env.height(); + // after init it has initial state + assert_eq!(HISTORICAL_EPOCH.load(deps.as_mut().storage)?.epoch_id, 0); + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::WaitingInitialisation + ); + + env.next_block(); + let pub_key_submission_height = env.height(); + try_initiate_dkg( + deps.as_mut(), + (*env).clone(), + message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]), + )?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::PublicKeySubmission { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let dealing_exchange_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::DealingExchange { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let verification_key_submission_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::VerificationKeySubmission { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let verification_key_validation_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::VerificationKeyValidation { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let verification_key_finalization_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::VerificationKeyFinalization { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let in_progress_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::InProgress {} + ); + + // check old data + assert!(HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, init_height - 1)? + .is_none()); + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, init_height + 1)? + .unwrap() + .state, + EpochState::WaitingInitialisation + ); + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, pub_key_submission_height + 1)? + .unwrap() + .state, + EpochState::PublicKeySubmission { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, dealing_exchange_height + 1)? + .unwrap() + .state, + EpochState::DealingExchange { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height( + deps.as_mut().storage, + verification_key_submission_height + 1 + )? + .unwrap() + .state, + EpochState::VerificationKeySubmission { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height( + deps.as_mut().storage, + verification_key_validation_height + 1 + )? + .unwrap() + .state, + EpochState::VerificationKeyValidation { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height( + deps.as_mut().storage, + verification_key_finalization_height + 1 + )? + .unwrap() + .state, + EpochState::VerificationKeyFinalization { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, in_progress_height + 1)? + .unwrap() + .state, + EpochState::InProgress + ); + + Ok(()) + } +} diff --git a/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs index cfabdc700d..156459f24c 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD}; +use crate::epoch_state::storage::{load_current_epoch, save_epoch, EPOCH_THRESHOLDS, THRESHOLD}; use crate::epoch_state::transactions::reset_dkg_state; use crate::epoch_state::utils::check_state_completion; use crate::error::ContractError; @@ -39,7 +39,7 @@ fn ensure_can_advance_state( pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { // TODO: the only case where this can retrigger itself is when insufficient number of parties completed it, i.e. we don't have threshold - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let current_epoch = load_current_epoch(deps.storage)?; // checks whether the given phase has either completed or reached its deadline ensure_can_advance_state(deps.as_ref(), &env, ¤t_epoch)?; @@ -82,7 +82,7 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result, env: Env) -> Result(storage: &mut dyn Storage, env: &Env, action: A) + where + A: Fn(Epoch) -> Epoch, + { + let current = load_current_epoch(storage).unwrap(); + let updated = action(current); + save_epoch(storage, env.block.height, &updated).unwrap(); + } + #[test] fn short_circuit_advance_state() { fn epoch_in_state(state: EpochState, env: &Env) -> Epoch { Epoch::new(state, 0, Default::default(), env.block.time) } - fn set_epoch(storage: &mut dyn Storage, epoch: Epoch) { - CURRENT_EPOCH.save(storage, &epoch).unwrap(); + fn set_epoch(storage: &mut dyn Storage, env: &Env, epoch: Epoch) { + save_epoch(storage, env.block.height, &epoch).unwrap(); } let mut deps = init_contract(); @@ -114,18 +124,18 @@ mod tests { // it's never possible to short-circuit `WaitingInitialisation` let epoch = epoch_in_state(EpochState::WaitingInitialisation, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); // neither PublicKeySubmission (in either resharing or non-resharing) let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: false }, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: true }, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -138,7 +148,7 @@ mod tests { // no dealings let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env); epoch.state_progress.registered_dealers = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -146,7 +156,7 @@ mod tests { let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_dealings = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -154,7 +164,7 @@ mod tests { let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_dealings = key_size * 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -167,7 +177,7 @@ mod tests { let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true }, &env); epoch.state_progress.registered_dealers = 5; epoch.state_progress.registered_resharing_dealers = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -176,7 +186,7 @@ mod tests { epoch.state_progress.registered_dealers = 5; epoch.state_progress.registered_resharing_dealers = 4; epoch.state_progress.submitted_dealings = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -185,7 +195,7 @@ mod tests { epoch.state_progress.registered_dealers = 5; epoch.state_progress.registered_resharing_dealers = 4; epoch.state_progress.submitted_dealings = key_size * 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -200,7 +210,7 @@ mod tests { &env, ); epoch.state_progress.registered_dealers = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -209,7 +219,7 @@ mod tests { &env, ); epoch.state_progress.registered_dealers = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -219,7 +229,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -229,7 +239,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -239,7 +249,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -254,7 +264,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -268,7 +278,7 @@ mod tests { EpochState::VerificationKeyValidation { resharing: false }, &env, ); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -276,7 +286,7 @@ mod tests { EpochState::VerificationKeyValidation { resharing: true }, &env, ); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -286,7 +296,7 @@ mod tests { &env, ); epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -295,7 +305,7 @@ mod tests { &env, ); epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -305,7 +315,7 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -315,7 +325,7 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -325,7 +335,7 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state(deps.as_ref().storage, EpochState::InProgress).unwrap(); @@ -336,14 +346,14 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state(deps.as_ref().storage, EpochState::InProgress).unwrap(); // it's never possible to short-circuit `InProgress` let epoch = epoch_in_state(EpochState::InProgress, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); } @@ -366,7 +376,7 @@ mod tests { ) .unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::PublicKeySubmission { resharing: false } @@ -390,19 +400,16 @@ mod tests { env.block.time = env.block.time.plus_seconds(1); // add some dealers to prevent short-circuiting - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.registered_dealers = 42; - Ok(e) - }) - .unwrap(); - + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.registered_dealers = 42; + e + }); env.block.time = env .block .time .plus_seconds(epoch.time_configuration.public_key_submission_time_secs); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::DealingExchange { resharing: false } @@ -425,7 +432,7 @@ mod tests { env.block.time = env.block.time.plus_seconds(3); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::VerificationKeySubmission { resharing: false } @@ -452,7 +459,7 @@ mod tests { env.block.time = env.block.time.plus_seconds(3); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::VerificationKeyValidation { resharing: false } @@ -478,16 +485,13 @@ mod tests { ); // add some key shares to prevent short-circuiting - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.submitted_key_shares = 42; - Ok(e) - }) - .unwrap(); - + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.submitted_key_shares = 42; + e + }); env.block.time = env.block.time.plus_seconds(3); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::VerificationKeyFinalization { resharing: false } @@ -512,16 +516,14 @@ mod tests { ); // add some finalized keys to prevent reset - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.verified_keys = 42; - Ok(e) - }) - .unwrap(); + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.verified_keys = 42; + e + }); env.block.time = env.block.time.plus_seconds(1); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!(epoch.state, EpochState::InProgress); assert_eq!( epoch.deadline.unwrap(), @@ -547,9 +549,9 @@ mod tests { // Group hasn't changed, so we remain in the same epoch, with updated finish timestamp env.block.time = env.block.time.plus_seconds(100); - let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let prev_epoch = load_current_epoch(deps.as_mut().storage).unwrap(); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let curr_epoch = load_current_epoch(deps.as_mut().storage).unwrap(); let mut expected_epoch = Epoch::new( EpochState::InProgress, prev_epoch.epoch_id, @@ -570,11 +572,11 @@ mod tests { // fewer than the threshold epoch.state_progress.verified_keys = 41; - CURRENT_EPOCH.save(deps.as_mut().storage, &epoch).unwrap(); + save_epoch(deps.as_mut().storage, env.block.height, &epoch).unwrap(); env.block.time = env.block.time.plus_seconds(5000000); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let curr_epoch = load_current_epoch(deps.as_mut().storage).unwrap(); let expected_epoch = Epoch::new( EpochState::PublicKeySubmission { resharing: false }, epoch.epoch_id + 1, @@ -598,12 +600,10 @@ mod tests { assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none()); - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.registered_dealers = 100; - Ok(e) - }) - .unwrap(); + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.registered_dealers = 100; + e + }); env.block.time = env .block diff --git a/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs index 73d548cdeb..e206fcb379 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs @@ -1,7 +1,7 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; +use crate::epoch_state::storage::{load_current_epoch, save_epoch, THRESHOLD}; use crate::error::ContractError; use crate::state::storage::DKG_ADMIN; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage}; @@ -29,7 +29,7 @@ pub(crate) fn try_initiate_dkg( // only the admin is allowed to kick start the process DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let epoch = CURRENT_EPOCH.load(deps.storage)?; + let epoch = load_current_epoch(deps.storage)?; if !matches!(epoch.state, EpochState::WaitingInitialisation) { return Err(ContractError::AlreadyInitialised); } @@ -37,7 +37,7 @@ pub(crate) fn try_initiate_dkg( // the first exchange won't involve resharing let initial_state = EpochState::PublicKeySubmission { resharing: false }; let initial_epoch = Epoch::new(initial_state, 0, epoch.time_configuration, env.block.time); - CURRENT_EPOCH.save(deps.storage, &initial_epoch)?; + save_epoch(deps.storage, env.block.height, &initial_epoch)?; Ok(Response::default()) } @@ -49,7 +49,7 @@ pub(crate) fn try_trigger_reset( ) -> Result { // only the admin is allowed to trigger DKG reset DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let current_epoch = load_current_epoch(deps.storage)?; // only allow reset when the DKG exchange isn't in progress if !current_epoch.state.is_in_progress() { @@ -57,7 +57,7 @@ pub(crate) fn try_trigger_reset( } let next_epoch = current_epoch.next_reset(env.block.time); - CURRENT_EPOCH.save(deps.storage, &next_epoch)?; + save_epoch(deps.storage, env.block.height, &next_epoch)?; reset_dkg_state(deps.storage)?; @@ -71,7 +71,7 @@ pub(crate) fn try_trigger_resharing( ) -> Result { // only the admin is allowed to trigger DKG resharing DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let current_epoch = load_current_epoch(deps.storage)?; // only allow resharing when the DKG exchange isn't in progress if !current_epoch.state.is_in_progress() { @@ -79,7 +79,7 @@ pub(crate) fn try_trigger_resharing( } let next_epoch = current_epoch.next_resharing(env.block.time); - CURRENT_EPOCH.save(deps.storage, &next_epoch)?; + save_epoch(deps.storage, env.block.height, &next_epoch)?; reset_dkg_state(deps.storage)?; @@ -89,6 +89,7 @@ pub(crate) fn try_trigger_resharing( #[cfg(test)] pub(crate) mod tests { use super::*; + use crate::epoch_state::storage::load_current_epoch; use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; use cosmwasm_std::testing::{message_info, mock_env}; use cosmwasm_std::Addr; @@ -99,7 +100,7 @@ pub(crate) mod tests { let mut deps = init_contract(); let env = mock_env(); - let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap(); + let initial_epoch_info = load_current_epoch(&deps.storage).unwrap(); assert!(initial_epoch_info.deadline.is_none()); let not_admin = deps.api.addr_make("not an admin"); @@ -125,7 +126,7 @@ pub(crate) mod tests { assert_eq!(ContractError::AlreadyInitialised, res); // sets the correct epoch data - let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap(); + let epoch = load_current_epoch(&deps.storage).unwrap(); assert_eq!(epoch.epoch_id, 0); assert_eq!( epoch.state, diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index c468e2af8b..31ce9def13 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -1,7 +1,7 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::load_current_epoch; use crate::error::ContractError; use crate::state::storage::STATE; use cosmwasm_std::Storage; @@ -52,7 +52,7 @@ pub(crate) fn check_epoch_state( storage: &dyn Storage, against: EpochState, ) -> Result<(), ContractError> { - let epoch_state = CURRENT_EPOCH.load(storage)?.state; + let epoch_state = load_current_epoch(storage)?.state; if epoch_state != against { Err(ContractError::IncorrectEpochState { current_state: epoch_state.to_string(), @@ -66,6 +66,7 @@ pub(crate) fn check_epoch_state( #[cfg(test)] pub(crate) mod test { use super::*; + use crate::epoch_state::storage::save_epoch; use crate::support::tests::helpers::init_contract; use cosmwasm_std::testing::mock_env; use cosmwasm_std::Timestamp; @@ -210,12 +211,12 @@ pub(crate) mod test { let env = mock_env(); for fixed_state in EpochState::first().all_until(EpochState::InProgress) { - CURRENT_EPOCH - .save( - deps.as_mut().storage, - &Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time), - ) - .unwrap(); + save_epoch( + deps.as_mut().storage, + env.block.height, + &Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time), + ) + .unwrap(); for against_state in EpochState::first().all_until(EpochState::InProgress) { let ret = check_epoch_state(deps.as_mut().storage, against_state); if fixed_state == against_state { diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index c867fec2be..45f626fe87 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -10,6 +10,9 @@ use thiserror::Error; /// Custom errors for contract failure conditions. #[derive(Error, Debug, PartialEq)] pub enum ContractError { + #[error("could not perform contract migration: {comment}")] + FailedMigration { comment: String }, + #[error(transparent)] Std(#[from] StdError), diff --git a/contracts/coconut-dkg/src/lib.rs b/contracts/coconut-dkg/src/lib.rs index ca3cf4898a..729ba3cede 100644 --- a/contracts/coconut-dkg/src/lib.rs +++ b/contracts/coconut-dkg/src/lib.rs @@ -11,6 +11,7 @@ mod dealers; mod dealings; mod epoch_state; pub mod error; +mod queued_migrations; mod state; mod support; mod verification_key_shares; diff --git a/contracts/coconut-dkg/src/queued_migrations.rs b/contracts/coconut-dkg/src/queued_migrations.rs new file mode 100644 index 0000000000..54c149a518 --- /dev/null +++ b/contracts/coconut-dkg/src/queued_migrations.rs @@ -0,0 +1,21 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_state::storage::HISTORICAL_EPOCH; +use crate::error::ContractError; +use cosmwasm_std::{DepsMut, Env}; + +pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> { + if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() { + return Err(ContractError::FailedMigration { + comment: "this migration has already been run before".to_string(), + }); + } + + #[allow(deprecated)] + let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?; + // we won't have information on intermediate states prior to now, but that's not the end of the world + HISTORICAL_EPOCH.save(deps.storage, ¤t, env.block.height)?; + + Ok(()) +} diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index 04dde90358..87a59af53b 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -12,8 +12,8 @@ pub const TEST_MIX_DENOM: &str = "unym"; pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare { ContractVKShare { - share: format!("share{}", index), - announce_address: format!("localhost:{}", index), + share: format!("share{index}"), + announce_address: format!("localhost:{index}"), node_index: index, owner: Addr::unchecked(owner), epoch_id: index, @@ -43,7 +43,7 @@ pub fn dealing_metadata_fixture() -> Vec { pub fn dealer_details_fixture(api: &MockApi, assigned_index: u64) -> DealerDetails { DealerDetails { - address: api.addr_make(&format!("owner{}", assigned_index)), + address: api.addr_make(&format!("owner{assigned_index}")), bte_public_key_with_proof: "".to_string(), ed25519_identity: "".to_string(), announce_address: "".to_string(), diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index d567888577..5ef8eea22f 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -3,7 +3,7 @@ use crate::contract::instantiate; use crate::dealers::storage::{DEALERS_INDICES, EPOCH_DEALERS_MAP}; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::load_current_epoch; use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env, MockApi, MockQuerier}; use cosmwasm_std::{ from_json, to_json_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps, @@ -27,7 +27,7 @@ pub const MULTISIG_CONTRACT: &str = addr!("multisig contract address"); pub(crate) static GROUP_MEMBERS: Mutex> = Mutex::new(Vec::new()); pub fn re_register_dealer(deps: DepsMut, dealer: &Addr) { - let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id; + let epoch_id = load_current_epoch(deps.storage).unwrap().epoch_id; let previous = epoch_id - 1; let details = EPOCH_DEALERS_MAP .load(deps.storage, (previous, dealer)) @@ -38,7 +38,7 @@ pub fn re_register_dealer(deps: DepsMut, dealer: &Addr) { } pub fn add_current_dealer(deps: DepsMut<'_>, details: &DealerDetails) { - let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id; + let epoch_id = load_current_epoch(deps.storage).unwrap().epoch_id; insert_dealer(deps, epoch_id, details) } diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index e0254eebdd..f337f07a22 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -99,7 +99,7 @@ pub(crate) mod tests { let mut deps = init_contract(); let limit = 2; for n in 0..1000 { - let owner = format!("owner{}", n); + let owner = format!("owner{n}"); let vk_share = vk_share_fixture(&owner, 0); let sender = Addr::unchecked(owner); vk_shares() @@ -115,7 +115,7 @@ pub(crate) mod tests { fn vk_shares_paged_retrieval_has_default_limit() { let mut deps = init_contract(); for n in 0..1000 { - let owner = format!("owner{}", n); + let owner = format!("owner{n}"); let vk_share = vk_share_fixture(&owner, 0); let sender = Addr::unchecked(owner); vk_shares() @@ -136,7 +136,7 @@ pub(crate) mod tests { fn vk_shares_paged_retrieval_has_max_limit() { let mut deps = init_contract(); for n in 0..1000 { - let owner = format!("owner{}", n); + let owner = format!("owner{n}"); let vk_share = vk_share_fixture(&owner, 0); let sender = Addr::unchecked(owner); vk_shares() diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index cff0c88232..5c01b12b1a 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -3,7 +3,7 @@ use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS; use crate::dealers::storage::get_dealer_details; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::{load_current_epoch, save_epoch}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::{MULTISIG, STATE}; @@ -25,7 +25,7 @@ pub fn try_commit_verification_key_share( deps.storage, EpochState::VerificationKeySubmission { resharing }, )?; - let mut epoch = CURRENT_EPOCH.load(deps.storage)?; + let mut epoch = load_current_epoch(deps.storage)?; let epoch_id = epoch.epoch_id; let details = get_dealer_details(deps.storage, &info.sender, epoch_id)?; @@ -43,7 +43,7 @@ pub fn try_commit_verification_key_share( node_index: details.assigned_index, announce_address: details.announce_address, owner: info.sender.clone(), - epoch_id: CURRENT_EPOCH.load(deps.storage)?.epoch_id, + epoch_id: load_current_epoch(deps.storage)?.epoch_id, verified: false, }; vk_shares().save(deps.storage, (&info.sender, epoch_id), &data)?; @@ -60,13 +60,14 @@ pub fn try_commit_verification_key_share( )?; epoch.state_progress.submitted_key_shares += 1; - CURRENT_EPOCH.save(deps.storage, &epoch)?; + save_epoch(deps.storage, env.block.height, &epoch)?; Ok(Response::new().add_message(msg)) } pub fn try_verify_verification_key_share( deps: DepsMut<'_>, + env: Env, info: MessageInfo, owner: String, resharing: bool, @@ -77,7 +78,7 @@ pub fn try_verify_verification_key_share( deps.storage, EpochState::VerificationKeyFinalization { resharing }, )?; - let mut epoch = CURRENT_EPOCH.load(deps.storage)?; + let mut epoch = load_current_epoch(deps.storage)?; let epoch_id = epoch.epoch_id; MULTISIG.assert_admin(deps.as_ref(), &info.sender)?; @@ -93,7 +94,7 @@ pub fn try_verify_verification_key_share( })?; epoch.state_progress.verified_keys += 1; - CURRENT_EPOCH.save(deps.storage, &epoch)?; + save_epoch(deps.storage, env.block.height, &epoch)?; Ok(Response::default()) } @@ -259,9 +260,14 @@ mod tests { let owner = deps.api.addr_make("owner").to_string(); let multisig_info = message_info(&Addr::unchecked(MULTISIG_CONTRACT), &[]); - let ret = - try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false) - .unwrap_err(); + let ret = try_verify_verification_key_share( + deps.as_mut(), + env.clone(), + info.clone(), + owner.clone(), + false, + ) + .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { @@ -291,15 +297,26 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); - try_advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false) - .unwrap_err(); + let ret = try_verify_verification_key_share( + deps.as_mut(), + env.clone(), + info, + owner.clone(), + false, + ) + .unwrap_err(); assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {})); - let ret = - try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false) - .unwrap_err(); + let ret = try_verify_verification_key_share( + deps.as_mut(), + env.clone(), + multisig_info, + owner.clone(), + false, + ) + .unwrap_err(); assert_eq!( ret, ContractError::NoCommitForOwner { @@ -356,9 +373,15 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); - try_advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.to_string(), false) - .unwrap(); + try_verify_verification_key_share( + deps.as_mut(), + env, + multisig_info, + owner.to_string(), + false, + ) + .unwrap(); } }