From 5f4d211d9542a017a398584608aaf592fc91f092 Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Thu, 6 Oct 2022 22:27:27 -0400 Subject: [PATCH 1/4] Handle mesh-provider ibc failure cases --- contracts/mesh-provider/src/contract.rs | 17 +--- contracts/mesh-provider/src/error.rs | 3 + contracts/mesh-provider/src/ibc.rs | 127 ++++++++++++++++++++---- contracts/mesh-provider/src/state.rs | 2 + 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/contracts/mesh-provider/src/contract.rs b/contracts/mesh-provider/src/contract.rs index e1a3f8f..d5927f1 100644 --- a/contracts/mesh-provider/src/contract.rs +++ b/contracts/mesh-provider/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; use cw_storage_plus::Bound; -use cw_utils::{parse_instantiate_response_data, Expiration}; +use cw_utils::{parse_instantiate_response_data}; use mesh_apis::ClaimProviderMsg; use mesh_ibc::ProviderMsg; @@ -23,6 +23,7 @@ use crate::state::{ // version info for migration info const CONTRACT_NAME: &str = "crates.io:mesh-provider"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const LIST_VALIDATORS_RETRIES: u32 = 5; // for reply callbacks const INIT_CALLBACK_ID: u64 = 1; @@ -43,6 +44,7 @@ pub fn instantiate( lockup: deps.api.addr_validate(&msg.lockup)?, unbonding_period: msg.unbonding_period, rewards_ibc_denom: msg.rewards_ibc_denom, + list_validators_retries_remaining: LIST_VALIDATORS_RETRIES, }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; CONFIG.save(deps.storage, &state)?; @@ -219,16 +221,6 @@ pub fn execute_unstake( STAKED.save(deps.storage, (&info.sender, &validator), &stake)?; VALIDATORS.save(deps.storage, &validator, &val)?; - // create a future claim on number of shares (so we can adjust for later slashing) - let cfg = CONFIG.load(deps.storage)?; - let ready = env.block.time.plus_seconds(cfg.unbonding_period); - CLAIMS.create_claim( - deps.storage, - &info.sender, - amount, - Expiration::AtTime(ready), - )?; - // send out IBC packet for staking change let packet = ProviderMsg::Unstake { validator, @@ -242,6 +234,7 @@ pub fn execute_unstake( }; let mut res = Response::new().add_message(msg); + let cfg = CONFIG.load(deps.storage)?; if let Some(slash) = slash { let msg = WasmMsg::Execute { contract_addr: cfg.lockup.into_string(), @@ -366,7 +359,7 @@ pub fn query_account(deps: Deps, address: String) -> StdResult slashed, }) }) - .collect::>>()?; + .collect::>>()?; Ok(AccountResponse { staked }) } diff --git a/contracts/mesh-provider/src/error.rs b/contracts/mesh-provider/src/error.rs index 04be2ec..10da619 100644 --- a/contracts/mesh-provider/src/error.rs +++ b/contracts/mesh-provider/src/error.rs @@ -72,6 +72,9 @@ pub enum ContractError { #[error("Rewards amount is 0")] ZeroRewardsToSend {}, + #[error("Unable to communicate for message: {0} on channel: {1}")] + NoResponse(String, String), + #[error("Custom Error val: {val:?}")] CustomError { val: String }, // Add any other custom errors you like here. diff --git a/contracts/mesh-provider/src/ibc.rs b/contracts/mesh-provider/src/ibc.rs index 98ed481..b5ed906 100644 --- a/contracts/mesh-provider/src/ibc.rs +++ b/contracts/mesh-provider/src/ibc.rs @@ -7,13 +7,15 @@ use cosmwasm_std::{ IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, Uint128, }; +use cw_utils::Expiration; use mesh_ibc::{ check_order, check_version, ConsumerMsg, ListValidatorsResponse, ProviderMsg, RewardsResponse, StdAck, UpdateValidatorsResponse, }; use crate::error::ContractError; -use crate::state::{ValStatus, Validator, CHANNEL, CONFIG, PACKET_LIFETIME, PORT, VALIDATORS}; +use crate::state::{ValStatus, Validator, CHANNEL, CONFIG, PACKET_LIFETIME, PORT, VALIDATORS, STAKED, CLAIMS}; +use crate::contract::LIST_VALIDATORS_RETRIES; pub fn build_timeout(deps: Deps, env: &Env) -> Result { let packet_time = PACKET_LIFETIME.load(deps.storage)?; @@ -187,7 +189,7 @@ pub fn ibc_packet_ack( let val: ListValidatorsResponse = from_slice(&res.unwrap())?; ack_list_validators(deps, env, val) } - (ProviderMsg::ListValidators {}, false) => fail_list_validators(deps), + (ProviderMsg::ListValidators {}, false) => fail_list_validators(deps, env), ( ProviderMsg::Stake { key, @@ -196,6 +198,14 @@ pub fn ibc_packet_ack( }, false, ) => fail_stake(deps, key, validator, amount), + ( + ProviderMsg::Unstake { + key, + validator, + amount, + }, + true, + ) => ack_unstake(deps, env, key, validator, amount), ( ProviderMsg::Unstake { key, @@ -211,12 +221,12 @@ pub fn ibc_packet_ack( #[cfg_attr(not(feature = "library"), entry_point)] pub fn ibc_packet_timeout( deps: DepsMut, - _env: Env, + env: Env, msg: IbcPacketTimeoutMsg, ) -> Result { let original_packet: ProviderMsg = from_slice(&msg.packet.data)?; match original_packet { - ProviderMsg::ListValidators {} => fail_list_validators(deps), + ProviderMsg::ListValidators {} => fail_list_validators(deps, env), ProviderMsg::Stake { key, validator, @@ -238,33 +248,110 @@ pub fn ack_list_validators( for val in res.validators { VALIDATORS.save(deps.storage, &val, &Validator::new())?; } + + // reset retry counter + let mut config = CONFIG.load(deps.storage)?; + config.list_validators_retries_remaining = LIST_VALIDATORS_RETRIES; + CONFIG.save(deps.storage, &config)?; + Ok(IbcBasicResponse::new()) } -pub fn fail_list_validators(_deps: DepsMut) -> Result { - // TODO: send another ListValidators message - unimplemented!(); +pub fn fail_list_validators(deps: DepsMut, env: Env) -> Result { + // check if we should retry + let cfg = CONFIG.load(deps.storage)?; + let channel_id = CHANNEL.load(deps.storage)?; + if cfg.list_validators_retries_remaining <= 0 { + return Err(ContractError::NoResponse("list_validators".to_string(), channel_id)) + } + let mut cfg_after = cfg.clone(); + cfg_after.list_validators_retries_remaining -= 1; + CONFIG.save(deps.storage, &cfg_after)?; + + // retry + let packet = ProviderMsg::ListValidators {}; + let msg = IbcMsg::SendPacket { + channel_id: channel_id, + data: to_binary(&packet)?, + timeout: build_timeout(deps.as_ref(), &env)?, + }; + Ok(IbcBasicResponse::new().add_message(msg)) } pub fn fail_stake( - _deps: DepsMut, + deps: DepsMut, // _staker is the staker Addr, not used by consumer - _staker: String, - _validator: String, - _amount: Uint128, + staker: String, + validator: String, + amount: Uint128, ) -> Result { - // TODO: release the bonded stake, adjust numer - unimplemented!(); + let staker = deps.api.addr_validate(&staker)?; + + if amount.is_zero() { + return Err(ContractError::ZeroAmount); + } + + let mut val = VALIDATORS + .may_load(deps.storage, &validator)? + .ok_or_else(|| ContractError::UnknownValidator(validator.clone()))?; + let mut stake = STAKED + .may_load(deps.storage, (&staker, &validator))? + .unwrap_or_default(); + stake.unstake_validator(&mut val, amount)?; + STAKED.save(deps.storage, (&staker, &validator), &stake)?; + VALIDATORS.save(deps.storage, &validator, &val)?; + Ok(IbcBasicResponse::new()) +} + +pub fn ack_unstake( + deps: DepsMut, + env: Env, + staker: String, + _validator: String, + amount: Uint128 +) -> Result { + let staker = deps.api.addr_validate(&staker)?; + + if amount.is_zero() { + return Err(ContractError::ZeroAmount); + } + + // create a future claim on number of shares (so we can adjust for later slashing) + let cfg = CONFIG.load(deps.storage)?; + let ready = env.block.time.plus_seconds(cfg.unbonding_period); + CLAIMS.create_claim( + deps.storage, + &staker, + amount, + Expiration::AtTime(ready), + )?; + + // TODO: How should the user be notified of the failure? + Ok(IbcBasicResponse::new()) } pub fn fail_unstake( - _deps: DepsMut, + deps: DepsMut, // _staker is the staker Addr, not used by consumer - _staker: String, - _validator: String, - _amount: Uint128, + staker: String, + validator: String, + amount: Uint128, ) -> Result { - // TODO: unrelease the bonded stake, remove claim - // Maybe we only make Claim on ack? - unimplemented!(); + let staker = deps.api.addr_validate(&staker)?; + + if amount.is_zero() { + return Err(ContractError::ZeroAmount); + } + + // revert state changes associated with stake + let mut val = VALIDATORS + .may_load(deps.storage, &validator)? + .ok_or_else(|| ContractError::UnknownValidator(validator.clone()))?; + let mut stake = STAKED.load(deps.storage, (&staker, &validator))?; + stake.stake_validator(&mut val, amount); + STAKED.save(deps.storage, (&staker, &validator), &stake)?; + VALIDATORS.save(deps.storage, &validator, &val)?; + + // TODO: How should the user be notified of the failure? + Ok(IbcBasicResponse::new()) } diff --git a/contracts/mesh-provider/src/state.rs b/contracts/mesh-provider/src/state.rs index eca4c39..d7d1bde 100644 --- a/contracts/mesh-provider/src/state.rs +++ b/contracts/mesh-provider/src/state.rs @@ -18,6 +18,8 @@ pub struct Config { pub unbonding_period: u64, /// IBC denom string - "port_id/channel_id/denom" pub rewards_ibc_denom: String, + /// The number of retries remaining to query mesh-consumer + pub list_validators_retries_remaining: u32, } pub const CONFIG: Item = Item::new("config"); From abef98de778354127b778d801d595b99faa417ee Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Wed, 19 Oct 2022 09:42:46 -0400 Subject: [PATCH 2/4] Update with code review feedback from Jake, Ethan --- .gitignore | 2 + contracts/mesh-provider/src/contract.rs | 52 ++++--- contracts/mesh-provider/src/ibc.rs | 174 +++++++++++++----------- contracts/mesh-provider/src/state.rs | 43 +++++- 4 files changed, 165 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 4488f30..5649fea 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ internal/ *.log schema + +.DS_Store diff --git a/contracts/mesh-provider/src/contract.rs b/contracts/mesh-provider/src/contract.rs index d5927f1..b1972af 100644 --- a/contracts/mesh-provider/src/contract.rs +++ b/contracts/mesh-provider/src/contract.rs @@ -2,28 +2,27 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ coin, ensure_eq, to_binary, BankMsg, Binary, Decimal, Deps, DepsMut, Env, IbcMsg, MessageInfo, - Order, Reply, Response, StdResult, SubMsg, SubMsgResponse, Uint128, WasmMsg, + Order, Reply, Response, StdResult, SubMsgResponse, Uint128, WasmMsg, SubMsg, }; use cw2::set_contract_version; use cw_storage_plus::Bound; -use cw_utils::{parse_instantiate_response_data}; +use cw_utils::{parse_instantiate_response_data, Expiration}; use mesh_apis::ClaimProviderMsg; use mesh_ibc::ProviderMsg; use crate::error::ContractError; use crate::ibc::build_timeout; use crate::msg::{ - AccountResponse, ConfigResponse, ExecuteMsg, InstantiateMsg, ListValidatorsResponse, QueryMsg, - StakeInfo, ValidatorResponse, + AccountResponse, ConfigResponse, ExecuteMsg, ListValidatorsResponse, QueryMsg, + StakeInfo, ValidatorResponse, InstantiateMsg, }; use crate::state::{ - Config, ValStatus, Validator, CHANNEL, CLAIMS, CONFIG, PACKET_LIFETIME, STAKED, VALIDATORS, + ValStatus, Validator, CHANNEL, CLAIMS, CONFIG, STAKED, VALIDATORS, RETRIES, LIST_VALIDATORS_MAX_RETRIES, STAKE_MAX_RETRIES, UNSTAKE_MAX_RETRIES, RetryState, Config, PACKET_LIFETIME, }; // version info for migration info const CONTRACT_NAME: &str = "crates.io:mesh-provider"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const LIST_VALIDATORS_RETRIES: u32 = 5; // for reply callbacks const INIT_CALLBACK_ID: u64 = 1; @@ -38,16 +37,19 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let state = Config { consumer: msg.consumer, slasher: None, lockup: deps.api.addr_validate(&msg.lockup)?, unbonding_period: msg.unbonding_period, - rewards_ibc_denom: msg.rewards_ibc_denom, - list_validators_retries_remaining: LIST_VALIDATORS_RETRIES, }; - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; CONFIG.save(deps.storage, &state)?; + RETRIES.save(deps.storage, &RetryState { + list_validators_retries_remaining: LIST_VALIDATORS_MAX_RETRIES, + stake_retries_remaining: STAKE_MAX_RETRIES, + unstake_retries_remaining: UNSTAKE_MAX_RETRIES, + })?; // Set packet time from msg or set default PACKET_LIFETIME.save( @@ -130,24 +132,7 @@ pub fn execute_receive_claim( return Err(ContractError::ZeroAmount); } - let mut val = VALIDATORS - .may_load(deps.storage, &validator)? - .ok_or_else(|| ContractError::UnknownValidator(validator.clone()))?; - let mut stake = STAKED - .may_load(deps.storage, (&owner, &validator))? - .unwrap_or_default(); - - // First calculate rewards with old stake (or set default if first delegation) - stake.calc_pending_rewards( - val.rewards.rewards_per_token, - val.shares_to_tokens(stake.shares), - )?; - - stake.stake_validator(&mut val, amount); - STAKED.save(deps.storage, (&owner, &validator), &stake)?; - VALIDATORS.save(deps.storage, &validator, &val)?; - - // send out IBC packet for staking change + // send out IBC packet for staking change, update contract state on ack let packet = ProviderMsg::Stake { validator, amount, @@ -221,6 +206,16 @@ pub fn execute_unstake( STAKED.save(deps.storage, (&info.sender, &validator), &stake)?; VALIDATORS.save(deps.storage, &validator, &val)?; + // create a future claim on number of shares (so we can adjust for later slashing) + let cfg = CONFIG.load(deps.storage)?; + let ready = env.block.time.plus_seconds(cfg.unbonding_period); + CLAIMS.create_claim( + deps.storage, + &info.sender, + amount, + Expiration::AtTime(ready), + )?; + // send out IBC packet for staking change let packet = ProviderMsg::Unstake { validator, @@ -234,7 +229,6 @@ pub fn execute_unstake( }; let mut res = Response::new().add_message(msg); - let cfg = CONFIG.load(deps.storage)?; if let Some(slash) = slash { let msg = WasmMsg::Execute { contract_addr: cfg.lockup.into_string(), @@ -401,7 +395,7 @@ fn build_response((address, val): (String, Validator)) -> ValidatorResponse { #[cfg(test)] mod tests { - use crate::msg::{ConsumerInfo, SlasherInfo}; + use crate::msg::{ConsumerInfo, SlasherInfo, InstantiateMsg}; use super::*; use cosmwasm_std::coins; diff --git a/contracts/mesh-provider/src/ibc.rs b/contracts/mesh-provider/src/ibc.rs index b5ed906..c8023bc 100644 --- a/contracts/mesh-provider/src/ibc.rs +++ b/contracts/mesh-provider/src/ibc.rs @@ -3,19 +3,17 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ from_slice, to_binary, Coin, Deps, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, - IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcMsg, IbcPacketAckMsg, - IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, Uint128, + IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcMsg, + IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, Uint128, Event, StdError, IbcPacketAckMsg, }; -use cw_utils::Expiration; use mesh_ibc::{ check_order, check_version, ConsumerMsg, ListValidatorsResponse, ProviderMsg, RewardsResponse, StdAck, UpdateValidatorsResponse, }; use crate::error::ContractError; -use crate::state::{ValStatus, Validator, CHANNEL, CONFIG, PACKET_LIFETIME, PORT, VALIDATORS, STAKED, CLAIMS}; -use crate::contract::LIST_VALIDATORS_RETRIES; +use crate::state::{ValStatus, Validator, CHANNEL, CONFIG, PACKET_LIFETIME, PORT, VALIDATORS, STAKED, RETRIES}; pub fn build_timeout(deps: Deps, env: &Env) -> Result { let packet_time = PACKET_LIFETIME.load(deps.storage)?; @@ -181,7 +179,6 @@ pub fn ibc_packet_ack( msg: IbcPacketAckMsg, ) -> Result { let res: StdAck = from_slice(&msg.acknowledgement.data)?; - // we need to handle the ack based on our request let original_packet: ProviderMsg = from_slice(&msg.original_packet.data)?; match (original_packet, res.is_ok()) { @@ -196,16 +193,24 @@ pub fn ibc_packet_ack( validator, amount, }, - false, - ) => fail_stake(deps, key, validator, amount), + true, + ) => ack_stake(deps, key, validator, amount), ( - ProviderMsg::Unstake { + ProviderMsg::Stake { key, validator, amount, }, + false, + ) => fail_stake(deps, env, key, validator, amount), + ( + ProviderMsg::Unstake { + key: _, + validator: _, + amount: _, + }, true, - ) => ack_unstake(deps, env, key, validator, amount), + ) => ack_unstake(deps), ( ProviderMsg::Unstake { key, @@ -213,8 +218,7 @@ pub fn ibc_packet_ack( amount, }, false, - ) => fail_unstake(deps, key, validator, amount), - (_, true) => Ok(IbcBasicResponse::new()), + ) => fail_unstake(deps, env, key, validator, amount), } } @@ -231,12 +235,12 @@ pub fn ibc_packet_timeout( key, validator, amount, - } => fail_stake(deps, key, validator, amount), + } => fail_stake(deps, env, key, validator, amount), ProviderMsg::Unstake { key, validator, amount, - } => fail_unstake(deps, key, validator, amount), + } => fail_unstake(deps, env, key, validator, amount), } } @@ -248,110 +252,128 @@ pub fn ack_list_validators( for val in res.validators { VALIDATORS.save(deps.storage, &val, &Validator::new())?; } - - // reset retry counter - let mut config = CONFIG.load(deps.storage)?; - config.list_validators_retries_remaining = LIST_VALIDATORS_RETRIES; - CONFIG.save(deps.storage, &config)?; - - Ok(IbcBasicResponse::new()) + RETRIES.update(deps.storage, |mut r| -> Result { + r.list_validators_reset(); + Ok(r) + })?; + Ok(IbcBasicResponse::new() + .add_attribute("action", "ack list_validators")) } pub fn fail_list_validators(deps: DepsMut, env: Env) -> Result { // check if we should retry - let cfg = CONFIG.load(deps.storage)?; - let channel_id = CHANNEL.load(deps.storage)?; - if cfg.list_validators_retries_remaining <= 0 { - return Err(ContractError::NoResponse("list_validators".to_string(), channel_id)) + let mut retries = RETRIES.load(deps.storage)?; + if !retries.list_validators_should_retry() { + retries.list_validators_reset(); + RETRIES.save(deps.storage, &retries)?; + return Ok(IbcBasicResponse::new().add_event(Event::new("list_validators_fail"))) } - let mut cfg_after = cfg.clone(); - cfg_after.list_validators_retries_remaining -= 1; - CONFIG.save(deps.storage, &cfg_after)?; + RETRIES.save(deps.storage, &retries)?; // retry + let channel_id = CHANNEL.load(deps.storage)?; let packet = ProviderMsg::ListValidators {}; let msg = IbcMsg::SendPacket { channel_id: channel_id, data: to_binary(&packet)?, timeout: build_timeout(deps.as_ref(), &env)?, }; - Ok(IbcBasicResponse::new().add_message(msg)) + Ok(IbcBasicResponse::new() + .add_event(Event::new("list_validators_retry")) + .add_message(msg)) } -pub fn fail_stake( - deps: DepsMut, - // _staker is the staker Addr, not used by consumer - staker: String, - validator: String, - amount: Uint128, -) -> Result { +fn ack_stake(deps: DepsMut, staker: String, validator: String, amount: Uint128) -> Result { let staker = deps.api.addr_validate(&staker)?; if amount.is_zero() { - return Err(ContractError::ZeroAmount); + return Ok(IbcBasicResponse::new().add_event(Event::new("ack_stake_zero_amount"))); } let mut val = VALIDATORS .may_load(deps.storage, &validator)? .ok_or_else(|| ContractError::UnknownValidator(validator.clone()))?; - let mut stake = STAKED - .may_load(deps.storage, (&staker, &validator))? - .unwrap_or_default(); - stake.unstake_validator(&mut val, amount)?; + let mut stake = STAKED.load(deps.storage, (&staker, &validator))?; + stake.stake_validator(&mut val, amount); STAKED.save(deps.storage, (&staker, &validator), &stake)?; VALIDATORS.save(deps.storage, &validator, &val)?; - Ok(IbcBasicResponse::new()) + RETRIES.update(deps.storage, |mut r| -> Result { + r.stake_reset(); + Ok(r) + })?; + + Ok(IbcBasicResponse::new() + .add_event(Event::new("ack_stake"))) } -pub fn ack_unstake( +pub fn fail_stake( deps: DepsMut, env: Env, staker: String, - _validator: String, - amount: Uint128 -) -> Result { - let staker = deps.api.addr_validate(&staker)?; - - if amount.is_zero() { - return Err(ContractError::ZeroAmount); + validator: String, + amount: Uint128, +) -> Result { + let mut retries = RETRIES.load(deps.storage)?; + if !retries.stake_should_retry() { + retries.stake_reset(); + RETRIES.save(deps.storage, &retries)?; + return Ok(IbcBasicResponse::new().add_event(Event::new("fail_stake"))) } + RETRIES.save(deps.storage, &retries)?; - // create a future claim on number of shares (so we can adjust for later slashing) - let cfg = CONFIG.load(deps.storage)?; - let ready = env.block.time.plus_seconds(cfg.unbonding_period); - CLAIMS.create_claim( - deps.storage, - &staker, + let packet = ProviderMsg::Stake { + validator, amount, - Expiration::AtTime(ready), - )?; + key: staker.into(), + }; + let msg = IbcMsg::SendPacket { + channel_id: CHANNEL.load(deps.storage)?, + data: to_binary(&packet)?, + timeout: build_timeout(deps.as_ref(), &env)?, + }; + Ok(IbcBasicResponse::new() + .add_event(Event::new("retry_stake")) + .add_message(msg)) +} - // TODO: How should the user be notified of the failure? - Ok(IbcBasicResponse::new()) +pub fn ack_unstake( + deps: DepsMut, +) -> Result { + RETRIES.update(deps.storage, |mut r| -> Result { + r.unstake_reset(); + Ok(r) + })?; + Ok(IbcBasicResponse::new().add_event(Event::new("ack_unstake"))) } pub fn fail_unstake( deps: DepsMut, - // _staker is the staker Addr, not used by consumer + env: Env, staker: String, validator: String, amount: Uint128, ) -> Result { - let staker = deps.api.addr_validate(&staker)?; - - if amount.is_zero() { - return Err(ContractError::ZeroAmount); + // check if we should retry + let mut retries = RETRIES.load(deps.storage)?; + if !retries.unstake_should_retry() { + retries.unstake_reset(); + RETRIES.save(deps.storage, &retries)?; + return Ok(IbcBasicResponse::new().add_event(Event::new("fail_unstake"))) } + RETRIES.save(deps.storage, &retries)?; - // revert state changes associated with stake - let mut val = VALIDATORS - .may_load(deps.storage, &validator)? - .ok_or_else(|| ContractError::UnknownValidator(validator.clone()))?; - let mut stake = STAKED.load(deps.storage, (&staker, &validator))?; - stake.stake_validator(&mut val, amount); - STAKED.save(deps.storage, (&staker, &validator), &stake)?; - VALIDATORS.save(deps.storage, &validator, &val)?; - - // TODO: How should the user be notified of the failure? - Ok(IbcBasicResponse::new()) + // retry + let packet = ProviderMsg::Unstake { + validator, + amount, + key: staker.to_string(), + }; + let msg = IbcMsg::SendPacket { + channel_id: CHANNEL.load(deps.storage)?, + data: to_binary(&packet)?, + timeout: build_timeout(deps.as_ref(), &env)?, + }; + Ok(IbcBasicResponse::new() + .add_event(Event::new("retry_unstake")) + .add_message(msg)) } diff --git a/contracts/mesh-provider/src/state.rs b/contracts/mesh-provider/src/state.rs index d7d1bde..88ebbff 100644 --- a/contracts/mesh-provider/src/state.rs +++ b/contracts/mesh-provider/src/state.rs @@ -18,8 +18,46 @@ pub struct Config { pub unbonding_period: u64, /// IBC denom string - "port_id/channel_id/denom" pub rewards_ibc_denom: String, - /// The number of retries remaining to query mesh-consumer +} + +pub const LIST_VALIDATORS_MAX_RETRIES: u32 = 5; +pub const STAKE_MAX_RETRIES: u32 = 5; +pub const UNSTAKE_MAX_RETRIES: u32 = 5; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct RetryState { pub list_validators_retries_remaining: u32, + pub stake_retries_remaining: u32, + pub unstake_retries_remaining: u32, +} + +impl RetryState { + pub fn list_validators_should_retry(&mut self) -> bool { + self.list_validators_retries_remaining -= 1; + 0 < self.list_validators_retries_remaining + } + + pub fn stake_should_retry(&mut self) -> bool { + self.stake_retries_remaining -= 1; + 0 < self.stake_retries_remaining + } + + pub fn unstake_should_retry(&mut self) -> bool { + self.unstake_retries_remaining -= 1; + 0 < self.unstake_retries_remaining + } + + pub fn list_validators_reset(&mut self) { + self.list_validators_retries_remaining = LIST_VALIDATORS_MAX_RETRIES; + } + + pub fn stake_reset(&mut self) { + self.stake_retries_remaining = STAKE_MAX_RETRIES; + } + + pub fn unstake_reset(&mut self) { + self.unstake_retries_remaining = UNSTAKE_MAX_RETRIES; + } } pub const CONFIG: Item = Item::new("config"); @@ -27,6 +65,9 @@ pub const PACKET_LIFETIME: Item = Item::new("packet_time"); pub const CHANNEL: Item = Item::new("channel"); pub const PORT: Item = Item::new("port"); +// The number of retries remaining to query mesh-consumer +pub const RETRIES: Item = Item::new("retry_state"); + // info on each validator, including voting and slashing pub const VALIDATORS: Map<&str, Validator> = Map::new("validators"); From ac5de410ad9a41b0387eb5188303ae61fe8601c8 Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Tue, 8 Nov 2022 20:41:00 -0500 Subject: [PATCH 3/4] Update test --- tests/src/cosmwasm.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/cosmwasm.spec.ts b/tests/src/cosmwasm.spec.ts index 7da0459..0db394f 100644 --- a/tests/src/cosmwasm.spec.ts +++ b/tests/src/cosmwasm.spec.ts @@ -34,17 +34,17 @@ let osmosisIds: Record = {}; test.before(async (t) => { console.debug("Upload contracts to wasmd..."); const wasmContracts = { - mesh_consumer: "./internal/mesh_consumer.wasm", - meta_staking: "./internal/meta_staking.wasm", + mesh_consumer: "./src/contracts/mesh_consumer.wasm", + meta_staking: "./src/contracts/meta_staking.wasm", }; const wasmSign = await setupWasmClient(); wasmIds = await setupContracts(wasmSign, wasmContracts); console.debug("Upload contracts to osmosis..."); const osmosisContracts = { - mesh_lockup: "./internal/mesh_lockup.wasm", - mesh_provider: "./internal/mesh_provider.wasm", - mesh_slasher: "./internal/mesh_slasher.wasm", + mesh_lockup: "./src/contracts/mesh_lockup.wasm", + mesh_provider: "./src/contracts/mesh_provider.wasm", + mesh_slasher: "./src/contracts/mesh_slasher.wasm", }; const osmosisSign = await setupOsmosisClient(); osmosisIds = await setupContracts(osmosisSign, osmosisContracts); From 8898193b53d38fb22ab1417abb46506678b61eb0 Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Tue, 8 Nov 2022 22:34:39 -0500 Subject: [PATCH 4/4] Remove stake, unstake retries --- contracts/mesh-provider/src/contract.rs | 2 -- contracts/mesh-provider/src/ibc.rs | 25 ------------------------- contracts/mesh-provider/src/state.rs | 20 -------------------- 3 files changed, 47 deletions(-) diff --git a/contracts/mesh-provider/src/contract.rs b/contracts/mesh-provider/src/contract.rs index b1972af..f37cf92 100644 --- a/contracts/mesh-provider/src/contract.rs +++ b/contracts/mesh-provider/src/contract.rs @@ -47,8 +47,6 @@ pub fn instantiate( CONFIG.save(deps.storage, &state)?; RETRIES.save(deps.storage, &RetryState { list_validators_retries_remaining: LIST_VALIDATORS_MAX_RETRIES, - stake_retries_remaining: STAKE_MAX_RETRIES, - unstake_retries_remaining: UNSTAKE_MAX_RETRIES, })?; // Set packet time from msg or set default diff --git a/contracts/mesh-provider/src/ibc.rs b/contracts/mesh-provider/src/ibc.rs index c8023bc..3dd899c 100644 --- a/contracts/mesh-provider/src/ibc.rs +++ b/contracts/mesh-provider/src/ibc.rs @@ -297,10 +297,6 @@ fn ack_stake(deps: DepsMut, staker: String, validator: String, amount: Uint128) stake.stake_validator(&mut val, amount); STAKED.save(deps.storage, (&staker, &validator), &stake)?; VALIDATORS.save(deps.storage, &validator, &val)?; - RETRIES.update(deps.storage, |mut r| -> Result { - r.stake_reset(); - Ok(r) - })?; Ok(IbcBasicResponse::new() .add_event(Event::new("ack_stake"))) @@ -314,13 +310,6 @@ pub fn fail_stake( amount: Uint128, ) -> Result { let mut retries = RETRIES.load(deps.storage)?; - if !retries.stake_should_retry() { - retries.stake_reset(); - RETRIES.save(deps.storage, &retries)?; - return Ok(IbcBasicResponse::new().add_event(Event::new("fail_stake"))) - } - RETRIES.save(deps.storage, &retries)?; - let packet = ProviderMsg::Stake { validator, amount, @@ -339,10 +328,6 @@ pub fn fail_stake( pub fn ack_unstake( deps: DepsMut, ) -> Result { - RETRIES.update(deps.storage, |mut r| -> Result { - r.unstake_reset(); - Ok(r) - })?; Ok(IbcBasicResponse::new().add_event(Event::new("ack_unstake"))) } @@ -353,16 +338,6 @@ pub fn fail_unstake( validator: String, amount: Uint128, ) -> Result { - // check if we should retry - let mut retries = RETRIES.load(deps.storage)?; - if !retries.unstake_should_retry() { - retries.unstake_reset(); - RETRIES.save(deps.storage, &retries)?; - return Ok(IbcBasicResponse::new().add_event(Event::new("fail_unstake"))) - } - RETRIES.save(deps.storage, &retries)?; - - // retry let packet = ProviderMsg::Unstake { validator, amount, diff --git a/contracts/mesh-provider/src/state.rs b/contracts/mesh-provider/src/state.rs index 88ebbff..308ca5e 100644 --- a/contracts/mesh-provider/src/state.rs +++ b/contracts/mesh-provider/src/state.rs @@ -27,8 +27,6 @@ pub const UNSTAKE_MAX_RETRIES: u32 = 5; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct RetryState { pub list_validators_retries_remaining: u32, - pub stake_retries_remaining: u32, - pub unstake_retries_remaining: u32, } impl RetryState { @@ -37,27 +35,9 @@ impl RetryState { 0 < self.list_validators_retries_remaining } - pub fn stake_should_retry(&mut self) -> bool { - self.stake_retries_remaining -= 1; - 0 < self.stake_retries_remaining - } - - pub fn unstake_should_retry(&mut self) -> bool { - self.unstake_retries_remaining -= 1; - 0 < self.unstake_retries_remaining - } - pub fn list_validators_reset(&mut self) { self.list_validators_retries_remaining = LIST_VALIDATORS_MAX_RETRIES; } - - pub fn stake_reset(&mut self) { - self.stake_retries_remaining = STAKE_MAX_RETRIES; - } - - pub fn unstake_reset(&mut self) { - self.unstake_retries_remaining = UNSTAKE_MAX_RETRIES; - } } pub const CONFIG: Item = Item::new("config");