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
This commit is contained in:
Jędrzej Stuczyński
2025-07-21 17:32:57 +01:00
committed by GitHub
parent 6c1149708b
commit e9165763b6
23 changed files with 1013 additions and 152 deletions
+1
View File
@@ -1091,6 +1091,7 @@ dependencies = [
name = "nym-coconut-dkg"
version = "0.1.0"
dependencies = [
"anyhow",
"cosmwasm-schema",
"cosmwasm-std",
"cw-controllers",
+1
View File
@@ -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" }
@@ -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",
@@ -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": [
@@ -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"
}
}
}
+13 -12
View File
@@ -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<QueryResponse, C
let response = match msg {
QueryMsg::GetState {} => 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<QueryResponse, C
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
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
+4 -4
View File
@@ -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<DealerDetailsResponse> {
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<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
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)
}
@@ -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<Response, ContractError> {
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,
@@ -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<DealingChunkInfo>,
resharing: bool,
) -> Result<Response, ContractError> {
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(
@@ -1,17 +1,19 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// 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<StateAdvanceResponse, ContractError> {
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<Epoch, ContractError> {
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<Option<Epoch>> {
HISTORICAL_EPOCH.may_load_at_height(storage, height)
}
pub(crate) fn query_current_epoch_threshold(
@@ -1,11 +1,225 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// 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<Epoch> = Item::new("current_epoch");
pub const HISTORICAL_EPOCH: SnapshotItem<Epoch> = SnapshotItem::new(
"historical_epoch",
"historical_epoch__checkpoints",
"historical_epoch__changelog",
Strategy::EveryBlock,
);
pub const THRESHOLD: Item<u64> = Item::new("threshold");
pub const EPOCH_THRESHOLDS: Map<EpochId, u64> = 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<Epoch> {
#[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(())
}
}
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// 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<Response, ContractError> {
// 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, &current_epoch)?;
@@ -82,7 +82,7 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response,
};
// update the epoch state
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
save_epoch(deps.storage, env.block.height, &next_epoch)?;
Ok(Response::new())
}
@@ -90,23 +90,33 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response,
#[cfg(test)]
mod tests {
use super::*;
use crate::epoch_state::storage::load_current_epoch;
use crate::epoch_state::transactions::try_initiate_dkg;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::state::storage::STATE;
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::{Addr, StdResult, Storage};
use cosmwasm_std::{Addr, Storage};
use nym_coconut_dkg_common::types::TimeConfiguration;
fn update_epoch<A>(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
@@ -1,7 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// 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<Response, ContractError> {
// 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<Response, ContractError> {
// 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,
@@ -1,7 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// 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 {
+3
View File
@@ -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),
+1
View File
@@ -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;
@@ -0,0 +1,21 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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, &current, env.block.height)?;
Ok(())
}
@@ -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<DealingChunkInfo> {
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(),
@@ -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<Vec<(Member, u64)>> = 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)
}
@@ -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()
@@ -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();
}
}