Skip to content

feat: exponential backoff polling for monitored deposit addresses#157

Open
lpahlavi wants to merge 3 commits intomainfrom
lpahlavi/defi-2780-exponential-backoff-polling
Open

feat: exponential backoff polling for monitored deposit addresses#157
lpahlavi wants to merge 3 commits intomainfrom
lpahlavi/defi-2780-exponential-backoff-polling

Conversation

@lpahlavi
Copy link
Copy Markdown
Contributor

@lpahlavi lpahlavi commented Apr 21, 2026

Summary

  • Adds a StableSortKeyMap-backed AutomaticDepositCache (two stable-memory maps keyed by account and poll time) that survives canister upgrades
  • Implements exponential backoff: first poll 1 min after registration, then 2, 4, 8, …, 512 min (10 polls total = 1023 min max)
  • After MAX_GET_SIGNATURES_FOR_ADDRESS_CALLS (10) polls with no deposit found, emits StoppedMonitoringAccount and retains the entry with next_poll_at = u64::MAX so it is never rescheduled
  • The getSignaturesForAddress calls quota is still consumed when an RPC call fails

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings April 21, 2026 06:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds durable, upgrade-safe polling of monitored deposit addresses using a stable-memory schedule with exponential backoff, wiring it into the canister timer loop and extending RPC/event plumbing to support address polling.

Changes:

  • Introduces a StableBTreeMap-backed poll schedule (StorableAccount -> PollEntry) and exposes access/reset helpers.
  • Adds getSignaturesForAddress RPC helper + tests, and integrates a new periodic timer task to poll due accounts with exponential backoff.
  • Extends audit/event/state plumbing with StoppedMonitoringAccount, plus unit + integration test coverage.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
minter/src/storage/mod.rs Adds stable poll schedule storage types and accessors.
minter/src/state/mod.rs Removes monitored account on stop; adds PollMonitoredAddresses task type.
minter/src/state/event.rs Introduces StoppedMonitoringAccount event variant.
minter/src/state/audit.rs Applies StoppedMonitoringAccount during audit replay/state transitions.
minter/src/rpc/mod.rs Adds get_signatures_for_address RPC wrapper + error type.
minter/src/rpc/tests.rs Adds unit tests for get_signatures_for_address.
minter/src/main.rs Wires periodic timer to poll_monitored_addresses; maps new event type.
minter/src/deposit/automatic/mod.rs Implements scheduling + polling logic with exponential backoff.
minter/src/deposit/automatic/tests.rs Adds unit tests for scheduling/backoff/max-poll behavior and storable round-trips.
libs/types-internal/src/event.rs Adds StoppedMonitoringAccount to Candid event types; derives PartialEq for comparisons.
integration_tests/src/fixtures.rs Adds update_balance args helper; exposes NUM_RPC_PROVIDERS; adds mock for getSignaturesForAddress.
integration_tests/tests/tests.rs Adds automated deposit polling integration test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread minter/src/deposit/automatic/mod.rs
Comment thread minter/src/deposit/automatic/mod.rs Outdated
Comment thread integration_tests/tests/tests.rs Outdated
Comment thread minter/src/deposit/automatic/mod.rs Outdated
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from ef334a9 to d697e96 Compare April 21, 2026 06:10
Copilot AI review requested due to automatic review settings April 21, 2026 06:17
@lpahlavi lpahlavi changed the base branch from main to lpahlavi/defi-2780-poll-deposit-addresses April 21, 2026 06:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

lpahlavi added a commit that referenced this pull request Apr 21, 2026
## Summary

Part of [DEFI-2780](https://dfinity.atlassian.net/browse/DEFI-2780).

Adds a `poll_monitored_addresses` timer that calls
`getSignaturesForAddress` for each account in `monitored_accounts`.
After polling, each account is removed from monitoring via a
`StoppedMonitoringAccount` event. Discovered signatures are currently
ignored — upcoming PRs will process them.

### Design

The timer follows the same pattern as other timers
(`finalize_transactions`, `consolidate_deposits`): it processes up to
`MAX_CONCURRENT_RPC_CALLS` accounts per invocation in parallel and
reschedules immediately via scopeguard if there is more work remaining.

Each account is polled with `getSignaturesForAddress` and then removed
from monitoring with a `StoppedMonitoringAccount` event regardless of
success or failure. The signature limit per call matches
`MAX_TRANSACTIONS_PER_ACCOUNT` (10).

A follow-up PR (#157) replaces this simple poll-once-and-remove approach
with an exponential backoff schedule (1 min, 2, 4, …, 512 min, up to 10
polls total) backed by a stable-memory `StableBTreeMap` that survives
canister upgrades.

### Constants

| Constant | Value | Purpose |
|---|---|---|
| `POLL_MONITORED_ADDRESSES_DELAY` | 1 min | Timer interval |
| `MAX_TRANSACTIONS_PER_ACCOUNT` | 10 | Max signatures to fetch per
account per poll |

🤖 Generated with [Claude Code](https://claude.com/claude-code)

[DEFI-2780]:
https://dfinity.atlassian.net/browse/DEFI-2780?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Base automatically changed from lpahlavi/defi-2780-poll-deposit-addresses to main April 21, 2026 12:22
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from f248ea0 to 2b694f6 Compare April 21, 2026 13:17
Copilot AI review requested due to automatic review settings April 21, 2026 13:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread minter/src/deposit/automatic/mod.rs
Comment thread Cargo.toml
Comment thread minter/src/deposit/automatic/mod.rs Outdated
Copilot AI review requested due to automatic review settings April 21, 2026 13:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings April 21, 2026 14:20
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch 2 times, most recently from 744a859 to 480ad33 Compare April 21, 2026 14:21
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from 6741032 to 41bc2a4 Compare April 21, 2026 14:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from 41bc2a4 to f5870ac Compare April 21, 2026 15:40
Copilot AI review requested due to automatic review settings April 21, 2026 15:55
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from f5870ac to efcc773 Compare April 21, 2026 15:55
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from efcc773 to 32375b8 Compare April 21, 2026 16:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch 3 times, most recently from ad8cb1c to a140a23 Compare April 22, 2026 06:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread integration_tests/tests/tests.rs
Comment thread minter/src/utils/stable_sort_key_map/mod.rs
Comment thread minter/src/deposit/automatic/mod.rs Outdated
Comment thread minter/src/deposit/automatic/mod.rs
Comment thread minter/src/deposit/automatic/mod.rs Outdated
- Add StableSortKeyMap-backed AutomaticDepositCache (stable memory, survives upgrades)
- Implement exponential backoff: 1, 2, 4, …, 512 min (10 polls = 1023 min max)
- Stop monitoring after MAX_GET_SIGNATURES_FOR_ADDRESS_CALLS (10); emit StoppedMonitoringAccount
- Retain stopped entries at next_poll_at=u64::MAX so they are never rescheduled
- Rename AutomaticDepositCache entry struct → AutomaticDepositCacheEntry
- Add proptest storable round-trip for AutomaticDepositCacheEntry
- Consolidate lifecycle tests into should_follow_full_polling_lifecycle (all 10 polls)
- Move arb_cache_entry to test_fixtures::arb

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lpahlavi lpahlavi force-pushed the lpahlavi/defi-2780-exponential-backoff-polling branch from a140a23 to dd314a6 Compare April 22, 2026 06:28
@lpahlavi lpahlavi requested a review from THLO April 22, 2026 06:28
@lpahlavi lpahlavi marked this pull request as ready for review April 22, 2026 06:28
@lpahlavi lpahlavi requested a review from a team as a code owner April 22, 2026 06:28
Copilot AI review requested due to automatic review settings April 22, 2026 06:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread minter/src/deposit/automatic/mod.rs Outdated
Comment thread integration_tests/tests/tests.rs
Comment thread minter/src/deposit/automatic/cache/mod.rs Outdated
Comment thread minter/src/deposit/automatic/mod.rs Outdated
Comment thread minter/src/deposit/automatic/mod.rs Outdated
Comment thread minter/src/utils/stable_sort_key_map/mod.rs
Comment thread minter/src/utils/stable_sort_key_map/tests.rs Outdated
Comment thread minter/src/utils/stable_sort_key_map/tests.rs
Comment thread minter/src/utils/stable_sort_key_map/tests.rs Outdated
Comment thread minter/src/utils/stable_sort_key_map/tests.rs Outdated
lpahlavi and others added 2 commits April 22, 2026 17:24
…iter_by_index_up_to

- Add public Iter<K, I, V> struct backed by ic_stable_structures::btreemap::Iter,
  yielding (index, key, value) triples in ascending index order
- iter() returns Iter<K, I, V> — a concrete, nameable type (not impl Iterator)
- Remove iter_by_index_up_to: callers use iter().take_while(|(i,..)| *i <= max)
  with standard iterator adapters instead
- Remove peek: poll_monitored_addresses uses due.is_empty() as its early-return guard
- Remove private iter_raw helper (logic is now in Iter::next directly)
- Fix test name: should_store_value_in_by_key → should_store_value_by_key
- Update should_index_entry and should_update_index_when_index_changes in
  mod insert to not depend on peek (use iter and get_with_index respectively)
- Remove mod peek tests (method deleted)
- Consolidate iter tests into mod iter: basic iteration tests plus filtered-range
  tests (formerly in mod iter_by_index_up_to) now use iter() + take_while + map

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ial backoff

- Replace get_signatures_calls: u8 with rpc_quota_left: u64 (starts at INITIAL_RPC_QUOTA=50)
  and next_backoff_delay_mins: u64 (starts at 1, doubles on each poll)
- Remove MAX_GET_SIGNATURES_FOR_ADDRESS_CALLS and MAX_GET_TRANSACTION_CALLS constants
- update_balance: return MonitoringQuotaExhausted when quota is 0; preserve remaining
  quota when rescheduling a previously stopped account; reset backoff delay to 1
- poll_account: decrement rpc_quota_left on each call; stop when it reaches 0; use
  saturating arithmetic to avoid overflow on long-running accounts
- Add UpdateBalanceError::MonitoringQuotaExhausted to types and DID
- Add tests: should_schedule_unmonitored_account, should_not_modify_existing_cache_entry,
  should_reschedule_account_with_remaining_quota, should_not_start_monitoring_with_exhausted_quota
- Consolidate lifecycle test into should_follow_full_polling_lifecycle with quota=3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 23, 2026 08:16
/// The monitoring lifecycle state of an account, as derived from the cache.
pub enum AccountMonitoringState {
/// No monitoring information has been recorded for this account.
Unknown,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what scenario is the state Unknown? If an account has never been seen, there simply shouldn't be any record, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, it is used in the monitoring_state function. An alternative would be to return Option<AccountMonitoringState>, right?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

entry: AutomaticDepositCacheEntry,
},
/// Polling was stopped after the quota was exhausted, but a subsequent deposit
/// reset the quota. The account can be rescheduled via `update_balance`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: A deposit may not (necessarily) "reset" the quota as it is prorated. In particular, the quota is not fully replenished if a) it has been fully exhausted and b) the process_deposit call resulted in a mint, in which case cycles are charged for the subsequent consolidation.

This is explained in more detail in the design document in case you haven't seen this part yet.

Comment thread minter/src/deposit/automatic/cache/mod.rs
/// The RPC quota for this account has been exhausted and no deposit has reset it.
/// `update_balance` will return `MonitoringQuotaExhausted` until a deposit resets
/// the quota.
Exhausted {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't Exhausted mean that there is no quota left? In this case, there is no need to store an AutomaticDepositCacheEntry object, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants