dfinity / dfinity/pocketic

Feature requests from a money-handling canister's test suite: drop-callback injection, ic_env for makeLive, settable canister env vars

Open
#70 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
35
Forks
6
Avg merge
9m
Merged PRs (30d)
1

Description

Context and versions: PocketIC-JS (@dfinity/pic) 0.22.0, pocket-ic-server 14.0.0 (source for the JS client: https://github.com/dfinity/pic-js).

We test a Stripe→cycles payment gateway written in Motoko. The suite runs 67 scenarios against the real ICP ledger, real CMC and real cycles ledger via icpFeatures, uses time control for a 2 h alert and 72 h terminate bound, stops the NNS canisters to inject outages, and uses makeLive() to drive the webhook route over genuine HTTP. PocketIC is doing the heavy lifting and it is very good at this — these are the three places we hit a wall, ranked by what they block.

Related bug filed separately: #69 (ii feature panics during instance creation).


1. A way to drop an in-flight callback, not drain it

Highest value: this is the only money-safety assertion we cannot make.

stopCanister drains outstanding callbacks rather than discarding them. That is correct and useful — it is what makes stop→upgrade→start work, and we rely on it. But it means we cannot simulate a canister dying between two awaits.

Why that matters for us specifically. Our delivery path is mint-to-self-then-forward:

notify_top_up(self)     → cycles land in our own balance
  ↓  a pre-forward marker is journaled here
forward to destination  → deposit_cycles / cycles-ledger deposit

If the canister dies between the marker and delivery, the forward's outcome is unknowable: the cycles may or may not have arrived. Re-forwarding risks double delivery, so we escalate to an ambiguousForward state whose operator instruction is "check the destination balance; never re-forward, never re-notify."

That is the single most dangerous cell in our recovery matrix — a wrong instruction there causes double delivery or a lost mint. Every other money position is integration-tested end to end. This one is only unit-tested, because we cannot produce it: stopCanister completes the forward instead of losing it.

What would help, roughly in order of preference:

  • a dropCanisterCallbacks(canisterId) / killCanister primitive that discards outstanding callbacks rather than draining them, or
  • an option on stopCanister such as { drainCallbacks: false }, or
  • more generally: fail the next inter-canister call from canister A to canister B with a chosen reject code (see 3 below), which would cover this and more.

We would happily settle for anything that makes "this call never comes back" reproducible.

2. An ic_env-equivalent when running under makeLive()

makeLive() gives a real HTTP gateway, and we already use it to POST genuinely signed Stripe webhooks over real HTTP — that works well.

What we cannot do is point a browser at a frontend served from PocketIC. An IC frontend gets its backend canister id and the root key from the ic_env cookie that the asset canister (or the icp-cli dev server) sets. PocketIC does not set it, so the page loads and then cannot construct an agent.

The practical consequence: our entire UI layer — the only layer with no automated coverage — has to be tested against a long-running local icp network instead of PocketIC. That means giving up determinism, time control and CI.

What would help: either the gateway serving that cookie for a designated asset canister, or a documented way to inject it (even a header/query parameter would do). Combined with #69 being fixed, the whole browser flow could live in PocketIC.

3. Settable canister-settings environment variables

icp deploy stamps PUBLIC_CANISTER_ID:<name> into canister settings, and canisters read them at runtime (Motoko: Runtime.envVar<system>). It is the idiomatic way to wire inter-canister references without setter methods or init args.

We use it to resolve the Exchange Rate Canister: default to the mainnet id, override via PUBLIC_CANISTER_ID:xrc. In PocketIC we install the XRC mock at the mainnet id, so no variable is injected and only the fallback branch ever executes. The override branch is untestable in PocketIC — the one place it runs is a local icp-cli network.

What would help: an updateCanisterSettings-style call (or a field on installCode/setupCanister) that sets environment variables on a canister. Small surface, and it would let a suite cover both branches of any env-var-driven wiring.

Explicitly not requesting: a CMC conversion-rate API

Recording this because it is the obvious ask and I think it would be the wrong one.

The rate only looks like a problem if the instance clock has moved away from the CMC's hard-coded rate timestamp. The cyclesMinting docs already say the instance default timestamp is pinned to 10 May 2021 for exactly that reason, and that design is sound — our suite never trips over it. And where we do need a different rate, impersonating the governance principal via a sender-scoped call is already first-class and works fine.

The friction we hit was on an icp-cli-managed local network, which runs at real wall-clock time and so breaks that alignment. That is icp-cli's side, not PocketIC's.


Happy to test patches for any of these, and to contribute a repro repo if useful. Item 1 is the one that would change what we can prove rather than what is convenient.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the existing stopCanister and makeLive() entry points, then inspect installCode/setupCanister for how canister settings are configured. A complete solution would make an in-flight callback reproducibly disappear, provide an ic_env-equivalent for live HTTP use, and allow canister environment variables to be set for tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
blockchain, testing-qa
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.