Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions rs/dogecoin/ckdoge/minter/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ mod withdrawal {

setup
.withdrawal_flow()
.minter_await_fee_refresh()
.ledger_approve_minter(account, RETRIEVE_DOGE_MIN_AMOUNT)
.minter_retrieve_doge_with_approval(
RETRIEVE_DOGE_MIN_AMOUNT,
Expand Down Expand Up @@ -415,6 +416,7 @@ mod withdrawal {

let withdrawal_flow = setup
.withdrawal_flow()
.minter_await_fee_refresh()
.ledger_approve_minter(account, RETRIEVE_DOGE_MIN_AMOUNT);

setup.ledger().stop();
Expand Down Expand Up @@ -454,6 +456,7 @@ mod withdrawal {

setup
.withdrawal_flow()
.minter_await_fee_refresh()
.ledger_approve_minter(account, RETRIEVE_DOGE_MIN_AMOUNT)
.minter_retrieve_doge_with_approval(
RETRIEVE_DOGE_MIN_AMOUNT,
Expand Down Expand Up @@ -542,6 +545,7 @@ mod withdrawal {

setup
.withdrawal_flow()
.minter_await_fee_refresh()
.ledger_approve_minter(account, withdrawal_amount)
.minter_retrieve_doge_with_approval(
withdrawal_amount,
Expand Down Expand Up @@ -584,6 +588,7 @@ mod withdrawal {

setup
.withdrawal_flow()
.minter_await_fee_refresh()
.ledger_approve_minter(account, withdrawal_amount)
.minter_retrieve_doge_with_approval(
withdrawal_amount,
Expand Down Expand Up @@ -634,6 +639,8 @@ fn should_estimate_withdrawal_fee() {
.minter_update_balance()
.expect_mint();

minter.await_fee_refresh();

assert_eq!(
estimate_withdrawal_fee_and_check(&minter, DOGE, 2),
Err(EstimateWithdrawalFeeError::AmountTooLow {
Expand Down Expand Up @@ -697,6 +704,7 @@ mod post_upgrade {

setup
.withdrawal_flow()
.minter_await_fee_refresh()
.ledger_approve_minter(account, RETRIEVE_DOGE_MIN_AMOUNT)
.minter_retrieve_doge_with_approval(
RETRIEVE_DOGE_MIN_AMOUNT,
Expand Down
17 changes: 16 additions & 1 deletion rs/dogecoin/ckdoge/test_utils/src/flow/withdrawal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use std::time::Duration;

/// Entry point in the withdrawal flow
///
/// Step 1: approve the minter to burn user's funds
/// Step 1: await a refresh of the minter's median fee percentiles
pub struct WithdrawalFlowStart<S> {
setup: S,
}
Expand All @@ -38,6 +38,21 @@ impl<S> WithdrawalFlowStart<S> {
Self { setup }
}

pub fn minter_await_fee_refresh(self) -> WithdrawalFlowApproval<S>
where
S: AsRef<Setup>,
{
self.setup.as_ref().minter().await_fee_refresh();
WithdrawalFlowApproval { setup: self.setup }
}
}

/// Step 2: approve the minter to burn user's funds
pub struct WithdrawalFlowApproval<S> {
setup: S,
}

impl<S> WithdrawalFlowApproval<S> {
pub fn ledger_approve_minter<A>(self, account: A, amount: u64) -> RetrieveDogeFlow<S>
where
A: Into<Account>,
Expand Down
27 changes: 27 additions & 0 deletions rs/dogecoin/ckdoge/test_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,31 @@ pub const LEDGER_TRANSFER_FEE: u64 = DOGE / 100;
const MAX_TIME_IN_QUEUE: Duration = Duration::from_secs(10);
pub const MIN_CONFIRMATIONS: u32 = 60;
pub const BLOCK_TIME: Duration = Duration::from_secs(60);
/// Must be at least the minter's `refresh_fee_percentiles_frequency` (currently `SIX_MINUTES`,
/// i.e. 360s, defined in `rs/dogecoin/ckdoge/minter/src/lib.rs`). There is no compile-time link
/// between the two values: if the minter ever lowers that frequency below this interval, the fee
/// refresh in `MinterCanister::await_fee_refresh` may not fire and the withdrawal-fee flake
/// returns. Keep this value in sync with, and no smaller than, the minter's refresh frequency.
pub const FEE_PERCENTILES_REFRESH_INTERVAL: Duration = Duration::from_secs(360);
Comment thread
gregorydemay marked this conversation as resolved.

/// Drains the minter's startup timer tasks.
///
/// Installing or upgrading the minter calls `setup_tasks()`, which schedules both `ProcessLogic`
/// and `RefreshFeePercentiles` at T0. If left unfired, `RefreshFeePercentiles` would fire during
/// the first `DogecoinSyncGuard` tick in a subsequent `mine_blocks` call, sending a fee-percentile
/// XNet call to the dogecoin canister while it is syncing new blocks. This puts the canister in a
/// "not fully synced" state that prevents `DogecoinSyncGuard` from observing the target block
/// height, causing a timeout.
///
/// Tick 1 fires `ProcessLogic`; tick 2 fires `RefreshFeePercentiles` and starts the XNet call; the
/// remaining ticks let the round-trip complete. Afterwards the timer is at T0+360s, far beyond any
/// `DogecoinSyncGuard` window.
pub(crate) fn drain_startup_tasks(env: &PocketIc) {
const STARTUP_DRAIN_TICKS: usize = 20;
for _ in 0..STARTUP_DRAIN_TICKS {
env.tick();
}
}

pub struct Setup {
pub env: Arc<PocketIc>,
Expand Down Expand Up @@ -186,6 +211,8 @@ impl Setup {
);
}

drain_startup_tasks(&env);

Self {
env,
doge_network,
Expand Down
32 changes: 29 additions & 3 deletions rs/dogecoin/ckdoge/test_utils/src/minter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::events::MinterEventAssert;
use crate::{MAX_TIME_IN_QUEUE, NNS_ROOT_PRINCIPAL};
use crate::{
FEE_PERCENTILES_REFRESH_INTERVAL, MAX_TIME_IN_QUEUE, NNS_ROOT_PRINCIPAL, drain_startup_tasks,
};
use candid::{Decode, Encode, Principal};
use canlog::LogEntry;
use ic_ckdoge_minter::{
Expand Down Expand Up @@ -163,6 +165,31 @@ impl MinterCanister {
Decode!(&call_result, Result<WithdrawalFee, EstimateWithdrawalFeeError>).unwrap()
}

pub fn await_fee_refresh(&self) {
Comment thread
gregorydemay marked this conversation as resolved.
let refreshes_before = self.count_fee_percentile_refreshes();
self.env
.advance_time(FEE_PERCENTILES_REFRESH_INTERVAL + Duration::from_secs(1));
let max_ticks = 100;
for _ in 0..max_ticks {
self.env.tick();
if self.count_fee_percentile_refreshes() > refreshes_before {
return;
}
}
dbg!(self.get_logs());
panic!(
"BUG: did not observe a successful fee-percentile refresh within {max_ticks} ticks \
(the RefreshFeePercentiles task may have run but failed to compute a median fee)"
);
}

fn count_fee_percentile_refreshes(&self) -> usize {
self.get_logs()
.iter()
.filter(|entry| entry.message.contains("update median fee per vbyte"))
.count()
}

pub fn retrieve_doge_status(&self, ledger_burn_index: u64) -> RetrieveDogeStatus {
let call_result = self
.env
Expand Down Expand Up @@ -351,8 +378,7 @@ impl MinterCanister {
Some(NNS_ROOT_PRINCIPAL),
)
.expect("BUG: failed to upgrade minter");
// run immediate tasks after upgrade, like refreshing fee percentiles.
self.env.tick();
drain_startup_tasks(&self.env);
}
}

Expand Down
Loading