test(cketh): migrate integration tests to PocketIC - #10955
Conversation
Add a PocketIC-based test fixture (LedgerSuiteOrchestrator, AddErc20TokenFlow, ManagedCanistersAssert, UniversalCanister, new_pocket_ic()) alongside the existing StateMachine one in test_utils, and migrate tests/tests.rs to it. The StateMachine fixture stays untouched at its current paths since rs/ethereum/cketh still consumes it; it will be deleted once cketh migrates to PocketIC in a later PR of this stack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LedgerSuiteOrchestrator and ManagedCanistersAssert each carried an AsRef<PocketIc> impl left over from porting the StateMachine fixture, where it satisfied `T: AsRef<StateMachine>` bounds on stop_canister/ out_of_band_upgrade and a manual CanisterHttpQuery impl. Those call sites now take `&PocketIc` directly and go through PocketIcHttpQuery, so neither impl has a caller left (G9/G12). Addresses review comments: #10949 (comment) #10949 (comment) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- mod.rs: replace 13 inline `crate::` paths (default_init_arg, the wasm
loaders, GIT_COMMIT_HASH, MAX_TICKS) with a single `use crate::{...}`
block, matching the plain `use crate::MINTER_PRINCIPAL;` style already used
in the sibling flow.rs. This also lets wait_for's panic message go back to
inline-capture ("...{MAX_TICKS} ticks...") instead of a positional arg.
- mod.rs: alias `ic_cdk::management_canister::CanisterStatusResult` as
`CdkCanisterStatusResult` to disambiguate it from the
`ic_management_canister_types::CanisterStatusResult` imported in the same
file, instead of spelling out the full path twice.
- flow.rs: import `ic_management_canister_types::CanisterId` instead of
fully qualifying it once, for the same reason mod.rs already imports it.
Addresses review comments:
#10949 (comment)
#10949 (comment)
#10949 (comment)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ledger_canister_status() returns an owned CanisterStatusResult temporary, so module_hash can be moved straight out of it instead of being cloned first (R3). Addresses review comment: #10949 (comment) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This comment was carried over verbatim from the StateMachine fixture and still referenced "10ms per transfer with state machine tests" inside the module that specifically does not use StateMachine (C2). Dropping the parenthetical rather than guessing a PocketIC-specific number. Addresses review comment: #10949 (comment) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
//:pocket-ic-server was listed in both `data` (needed for the $(rootpath //:pocket-ic-server) used by POCKET_IC_BIN) and `deps`. It's a runtime binary reached only through that env var, not linked, so `data` alone is the accurate declaration -- matching rs/ledger_suite/icp/BUILD.bazel (data only), as opposed to rs/bitcoin/checker and rs/dogecoin/ckdoge/minter (deps only, where the binary is invoked differently). Verified bazel test //rs/ethereum/ledger-suite-orchestrator:integration_tests still passes (POCKET_IC_BIN resolves fine from data alone). Addresses review comment: #10949 (comment) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inline raw-transaction and transaction-hash string literals in minter/tests/cketh.rs were byte-identical to the DEFAULT_WITHDRAWAL_TRANSACTION and DEFAULT_WITHDRAWAL_TRANSACTION_HASH constants already exported by cketh/test_utils. Replace them with the constants so the upcoming PocketIC migration only needs to regenerate the signature in one place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ocketIC Swap ic-state-machine-tests (and its transitive management-canister-types /ic-types/ic-cdk deps) for the sync pocket-ic client in rs/ethereum/cketh/test_utils and the minter's integration_tests target, and wire up the pocket-ic-server binary as test data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flip CkEthSetup and its helpers (mock.rs, flow.rs, ckerc20.rs) onto the
sync PocketIC client: builder uses with_fiduciary_subnet() with canister
execution rate limiting disabled, canisters are funded with
add_cycles(id, u128::MAX), and the minter is initialized with the
fiduciary subnet's "key_1" ECDSA key instead of the StateMachine-only
"master_ecdsa_public_key".
The ECDSA key change means the minter's Ethereum address changes, so
every signature-derived constant is regenerated: MINTER_ADDRESS,
DEFAULT_WITHDRAWAL_TRANSACTION(_HASH), DEFAULT_CKERC20_WITHDRAWAL_
TRANSACTION(_HASH), and the r/s of the default ERC20 signed transaction
in response.rs.
Canister-http mocking is rebuilt around PocketIC's get_canister_http() +
mock_canister_http_response() (replacing the StateMachine-specific manual
cleanup_response/transform dance), while preserving the legacy
"Http body exceeds size limit of {n} bytes." reject the minter's
is_response_too_large matches on. Polling for a specific outcall now also
tolerates ic-cdk-timers' per-timer concurrent-call cap: the block-height-
refresh timer and the log-scraping timer compete for it, and once hit, the
loser defers by a full REFRESH_LATEST_BLOCK_HEIGHT_INTERVAL that
nanosecond-granularity ticking alone can never cross.
Awaiting an update call now goes through a manual ingress_status polling
loop instead of PocketIC's own await_call, since the latter ticks up to
100 rounds with no opportunity to inject mock canister-http responses in
between.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adapt cketh.rs and ckerc20.rs to the PocketIC-backed test_utils API (error_code/reject_message instead of code()/description(), get_canister_ http() instead of canister_http_request_contexts(), stop_canister(id, None), Principal-based canister ids), and regenerate the few remaining signature/key-derivation-dependent literals inline in these test files: the per-account deposit-address in should_record_address_to_deposit and the resubmitted-transaction literal in should_resubmit_new_transaction_with_same_max_fee_per_gas_when_price_increased. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Migrates ckETH/ckERC20 integration test utilities and minter integration tests from ic-state-machine-tests to the synchronous pocket_ic client, aligning the cketh test harness with the PocketIC-based approach introduced earlier for the ledger-suite-orchestrator.
Changes:
- Replaced StateMachine-based fixtures and ingress/query plumbing with PocketIC equivalents (including manual ingress-status polling where needed).
- Rebuilt canister-http mocking around PocketIC’s
get_canister_http()/mock_canister_http_response()and regenerated signature-derived test constants due to ECDSA key differences. - Updated Rust and Bazel dependency graphs to drop
ic-state-machine-testsusage in these test crates and add PocketIC inputs/binaries for test execution.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| rs/ethereum/cketh/test_utils/src/response.rs | Regenerates signature components for default signed tx fixtures under PocketIC-derived minter identity. |
| rs/ethereum/cketh/test_utils/src/mock.rs | Ports JSON-RPC outcall matching/mocking to PocketIC’s canister-http APIs and adds retry logic for timer-cap interactions. |
| rs/ethereum/cketh/test_utils/src/lib.rs | Replaces StateMachine fixture with PocketIC fixture, updates canister lifecycle helpers, and adds manual await/drain helpers for outcalls. |
| rs/ethereum/cketh/test_utils/src/flow.rs | Switches async call tracking from MessageId to PocketIC RawMessageId and uses the new await helper. |
| rs/ethereum/cketh/test_utils/src/ckerc20.rs | Ports ckERC20 test setup/flows to PocketIC and updates orchestrator fixture usage to pocket-ic module. |
| rs/ethereum/cketh/test_utils/Cargo.toml | Swaps StateMachine-related deps for PocketIC + public management-canister-types + metrics assert pocket_ic feature. |
| rs/ethereum/cketh/test_utils/BUILD.bazel | Updates Bazel deps to PocketIC + ic-metrics-assert pocket_ic target; drops state-machine deps. |
| rs/ethereum/cketh/minter/tests/cketh.rs | Updates tests to PocketIC APIs and new regenerated constants; adjusts assertions around outcall queues. |
| rs/ethereum/cketh/minter/tests/ckerc20.rs | Updates ckERC20 integration tests to PocketIC fixtures/APIs and regenerated deposit address constant. |
| rs/ethereum/cketh/minter/Cargo.toml | Drops ic-state-machine-tests and adds PocketIC + public management-canister-types for tests. |
| rs/ethereum/cketh/minter/BUILD.bazel | Adds PocketIC server binary to test data and switches deps to PocketIC. |
| Cargo.lock | Reflects dependency changes (adds pocket-ic, removes ic-state-machine-tests from these crates, adds ic-management-canister-types). |
Comments suppressed due to low confidence (3)
rs/ethereum/cketh/test_utils/src/lib.rs:574
stop_ongoing_https_outcallsreturns even if it never manages to drain pending canister-http requests. Callers (e.g. tests usingPocketIc::await_call) can then hang/flap because outcalls remain open; it’s better to fail loudly with diagnostics when draining doesn’t succeed.
pub fn stop_ongoing_https_outcalls(&self) {
for _ in 0..MAX_TICKS {
if self.drain_pending_https_outcalls() {
return;
}
rs/ethereum/cketh/test_utils/src/lib.rs:841
drain_startup_http_outcallsregisters mocked responses but never ticks afterwards in the final loop iteration, so the last batch of startup outcalls may remain pending (defeating the purpose of draining them before longadvance_timejumps). It should tick after mocking and stop once the queue is empty.
fn drain_startup_http_outcalls(env: &PocketIc) {
for _ in 0..MAX_TICKS {
env.tick();
for request in env.get_canister_http() {
// Reject rather than answer: these are the startup timers' one-shot log-scraping and
rs/ethereum/cketh/test_utils/src/lib.rs:857
drain_stray_latest_block_refresh_callsunconditionallyunwrap()s JSON parsing of every pending outcall body; if any non-JSON/JSON-RPC request is present, tests will panic while trying to drain. Also, like the startup drain, it should tick after registering mocks to actually clear requests.
env.tick();
for request in env.get_canister_http() {
let json_request: serde_json::Value = serde_json::from_slice(&request.body).unwrap();
if json_request["method"] == "eth_getBlockByNumber"
&& json_request["params"] == serde_json::json!(["latest", false])
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
gregorydemay
left a comment
There was a problem hiding this comment.
🤖🧐 VERDICT: CHANGES_REQUESTED — 0 blockers, 6 mediums, 7 nits; CI pending (24 pass / 4 pending / 0 fail). Local: 2 consecutive uncached green runs of //rs/ethereum/cketh/minter:integration_tests.
⚠️ Posted as a comment rather than a formal “Request changes” review: GitHub refuses--request-changeson a PR owned by the same account. The verdict below is still CHANGES_REQUESTED.
Review details
Bottom line
The port itself is excellent. The #[test] sets in tests/cketh.rs (26) and tests/ckerc20.rs (39) are identical to the PR-2 base — no test dropped, renamed, #[ignore]d or #[should_panic]ed — and every assertion change is accounted for by the documented type/constant mapping. No loosened asserts, no println!/dbg!/commented-out asserts survived the constant-regeneration pass. Senders are preserved exactly (execute_ingress/query → Principal::anonymous(), execute_ingress_as/query_as → pid.0). mock.rs keeps the raw-body max_response_bytes check with the verbatim legacy message and never invokes cleanup_response manually. Component boundary is clean: the delta touches only rs/ethereum/cketh/** + Cargo.lock; dump_stable_memory.rs and deposit_from_cex_demo.rs are untouched. Everything I flag is in the new fixture plumbing, not in the tests.
The three declared deviations
find_rpc_call_retrying(positive-only) — the asymmetry is sound.expect_no_matching_rpc_call/expect_no_refresh_gas_fee_estimatestill go throughfind_rpc_call→poll_for_request, whose detection window (10 ×tick()+ 1ns) is byte-for-byte the StateMachinetick_until_next_http_requestit replaces — it has not silently shrunk. The mechanism is deterministic, not a flakiness source. I found no simpler in-repo precedent:rs/bitcoin/checker'stick_until_next_requestis the same plain 10-tick loop, but the checker has no competing periodic timer, so it doesn't face this. One concrete concern remains (see the inline note onmock.rs:152): the retry budget can burn 300s of simulated time, more thanSCRAPING_ETH_LOGS_INTERVAL(180s), so it can cross a scrape boundary.- Custom
await_call— justified: pocket-ic's ownawait_callticks internally with no injection point, andawait_call_no_tickswould deadlock in a sync client that must drive the rounds itself. It is bounded (1000 ticks) and panics with the message id. Two issues inline: the 100× bound relaxation is written as100 * MAX_TICKS, and the helper's implicit "500 every pending outcall" policy is only wanted bystop_minter, not byminter_response/expect_trap. new_pocket_ic()madepub— sound and effectively forced.CkErc20Setup::new_without_ckerc20_activeinstalls the minter, andCkEthSetup::newcallsminter_address(), which needs the fiduciarykey_1; LSO's system-subnet builder cannot serve it. The only semantic delta is that cycles are now charged, and neithershould_trap_when_ckerc20_feature_not_activeasserts a balance — they assert a trap.pubis the minimum surface needed by the two call sites.
Maintainability accounting
- Duplication — found. The latest-block-refresh predicate exists in three places, two of them different implementations (
mock.rs::matches,mock.rs::is_latest_block_refresh,lib.rs::drain_stray_latest_block_refresh_calls). The 500-reply block and the transient-reject block are each duplicated across two sites. Inline onmock.rs:187,lib.rs:851,lib.rs:584. - Divergent invariant handling — found, same predicate: a malformed request body is
.expect(..)in one copy,Err(_) => falsein the second, bare.unwrap()in the third. - Silent fallbacks — found:
stop_ongoing_https_outcallsgives up after 10×10 attempts and returns as though it succeeded (lib.rs:570). The twodrain_*helpers likewise never assert they drained anything. - Unused derives — none; the PR introduces no new types.
- Primitive-obsession parameters — none of substance; the only primitives are the raw
reject_codeliterals (nit,mock.rs:125). - Test-only code in production modules — none.
- Redundant / derivable parameters, caller-owned decisions — one instance:
await_call's drain policy is decided implicitly for all three call sites (lib.rs:885).
Coverage / test-pyramid
No new tests are warranted — this is a harness swap, and the existing suite is the regression oracle. The regenerated constants (MINTER_ADDRESS, both DEFAULT_*_WITHDRAWAL_TRANSACTION(_HASH), both response.rs r/s pairs) are cross-checked by test_utils:lib_tests::should_use_meaningful_constants (encoding + keccak hash) and, for MINTER_ADDRESS, by the assert_eq! in CkEthSetup::new. Note that the per-account deposit address at tests/ckerc20.rs:182 is a pure golden value with no independent oracle — unavoidable, just worth knowing.
Not blocking, FYI
drain_startup_http_outcalls in CkEthSetup::new has no StateMachine counterpart: it rejects the genesis scrape and block-refresh outcalls rather than leaving them pending. I traced this and it does not weaken anything (a rejection leaves last_observed_block_number None exactly as a pending call did, and the scrape timer retries on the next interval), but it is a behavioural delta the PR description does not mention — worth a sentence there. Also: the description says 40 ckerc20 tests; there are 39.
Verification run
bazel test //rs/ethereum/cketh/test_utils:lib_tests //rs/ethereum/cketh/minter:lib_tests //rs/ethereum/cketh/minter:integration_tests --nocache_test_results→ 4/4 PASSED (ckerc20 143.9s, cketh 71.3s)- second uncached run of
//rs/ethereum/cketh/minter:integration_tests→ 2/2 PASSED (141.3s / 71.0s) — no timing drift between runs, consistent with the retry mechanism being deterministic cargo check --all-targets --all-features -p ic-cketh-test-utils -p ic-cketh-minter→ cleantimeout = "long"on the suite is still appropriate (bazel reports the declared size as too big, not too small).
CI is not yet green (4 jobs still pending, none failing), which is an independent gate on a READY verdict.
…odes Addresses review comments on PR #10955: - mock.rs:187 (medium, G5) - the fallback in `matches()` reimplemented `is_latest_block_refresh`, with a different malformed-body behaviour (`.expect(...)` vs. `Err(_) => false`). Collapse to `!is_latest_block_refresh(request)` and make the predicate `pub(crate)` so `lib.rs` can reuse the same implementation. - mock.rs:152 (medium) - `find_rpc_call_retrying`'s retry budget was 10 * REFRESH_LATEST_BLOCK_HEIGHT_INTERVAL (300s), more than SCRAPING_ETH_LOGS_INTERVAL (180s), so it could itself trigger an unrelated log scrape. Capped at 180s (6 attempts): 150s (5 attempts) was tried and left several tests unable to find their target call within budget, so the cap sits at the interval's exact boundary rather than strictly under it. - mock.rs:125 (nit, G25) - named the SysFatal/SysTransient reject-code literals instead of repeating a comment at each site. - poll_for_request's inline SysTransient reject block now calls the shared `reject_stray_http_outcall` (see next commit) instead of duplicating it (nit, G5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses review comments on PR #10955: - lib.rs:885 (medium) - the single `await_call` bundled two policies: "await the message" and "answer every pending outcall with HTTP 500". `stop_minter` needs the draining; `WithdrawalFlow::minter_response`, `Erc20WithdrawalFlow::expect_trap` and `DepositErc20Flow::expect_trap` do not, since the drain could silently swallow an outcall one of those tests intended to stub itself. Split into `await_call` (which only clears stray latest-block-refresh outcalls - never a test-owned stub, see `mock::is_latest_block_refresh` - so it cannot swallow anything a test cares about) and `await_call_draining_outcalls` (the old blanket-500 behaviour), used only by `stop_minter`. - lib.rs:570 (medium) - `stop_ongoing_https_outcalls` returned as if it had succeeded even when it never managed to drain the pending outcalls. Panic instead, with the still-pending requests attached. - lib.rs:576 (medium, C2) - the comment on the same function pointed at `mock::tick_until_next_http_request`, which no longer exists (renamed to `JsonRpcRequestMatcher::poll_for_request` / `find_rpc_call_retrying`). Retargeted. - lib.rs:851 (medium, G5) - `drain_stray_latest_block_refresh_calls` had its own copy of the latest-block-refresh predicate, parsed as a raw `serde_json::Value` and `.unwrap()`-ed. Reuse `mock::is_latest_block_refresh` instead. - lib.rs:837 (nit) - `drain_startup_http_outcalls` and `drain_stray_latest_block_refresh_calls` burned their full tick budget even once the outcall queue was already empty. Added an early exit. - lib.rs:584 (nit, G5) - the HTTP-500 reply block in `drain_pending_https_outcalls` was byte-for-byte the one inside `await_call`. Extracted a shared `reply_500` helper. - lib.rs:881 (nit, C4) - the doc comment said "keeps rejecting" pending outcalls, but the code replies with an HTTP 500 (a different path through the minter than a canister-http reject). Reworded. - lib.rs:889 (nit, G25/G16) - `100 * MAX_TICKS` expressed 1000 as a multiple of an unrelated constant. Wrote the literal with a one-line rationale (this is a 100x relaxation of the `await_ingress(id, MAX_TICKS)` bound it replaces, needed because DTS can slice a large log-response over many more rounds than that). - lib.rs:538 (nit, G12) - `try_stop_minter_without_stopping_ongoing_ https_outcalls`'s 10-iteration loop was dead on the passing path (the first check returns immediately) and re-read a terminal status 9 more times on the failing path. Collapsed to a single tick + assert. - lib.rs:562 (Copilot) - `tick_until_minter_canister_status` re-read `minter_status()` without ever advancing a round itself, relying on `canister_status`'s own update-call processing to make progress. Added an explicit `tick()` so the loop doesn't depend on that incidental side effect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✅ No security or compliance issues detected. Reviewed everything up to 2615f60. Security Overview
Detected Code ChangesThe diff is too large to display a summary of code changes. |
dfinity#10949) Migrates the ledger-suite-orchestrator (LSO) integration tests from the internal `ic-state-machine-tests` harness to PocketIC, following the pattern already used for the EVM-RPC canister (dfinity/evm-rpc-canister#294). This is DEFI-2262: a pure refactor of the test harness — same tests, same coverage, same semantics. `CkErc20Setup` in `rs/ethereum/cketh` embeds LSO's fixture and shares a `StateMachine` with it, so flipping LSO's fixture in place would break cketh in the same commit. To keep this change single-component and the tree green, this PR **adds** a PocketIC fixture in LSO's `test_utils` alongside the existing StateMachine one (precedent: `rs/ledger_suite/icp/test_utils`'s `state_machine_helpers`/`pocket_ic_helpers` coexisting) and migrates only `ledger-suite-orchestrator/tests/tests.rs` to it. The StateMachine fixture stays untouched at its current paths and is still exercised by `cketh`; it is deleted only in the last PR of this stack, once cketh no longer needs it. ## Summary of changes - New `pocket_ic` module in `ledger-suite-orchestrator/test_utils` (`LedgerSuiteOrchestrator`, `AddErc20TokenFlow`, `ManagedCanistersAssert`, `UniversalCanister`, `new_pocket_ic()`), mirroring the existing StateMachine-based types and method names one-for-one so later PRs in the stack only need to swap import paths. - `new_pocket_ic()` builds a system subnet (LSO tests assert exact cycle balances, which only hold on a non-charging subnet) with canister-execution rate limiting disabled (LSO repeatedly installs/upgrades ledger, index and archive canisters). - Metrics assertions now go through `ic-metrics-assert`'s `PocketIcHttpQuery` (feature `pocket_ic`) instead of a hand-rolled `CanisterHttpQuery` impl. - `tests/tests.rs` fully migrated to the new fixture. - `Cargo.toml`/`BUILD.bazel` updated accordingly; `test_utils` keeps `ic-state-machine-tests` for now (still needed by the old fixture). ## Coverage - All spec requirements from the PR are covered: PocketIC fixture added alongside the old one, `tests/tests.rs` fully migrated, old fixture/paths untouched, `cketh` still builds against the untouched fixture. ## 📚 PR stack 1. **dfinity#10949 — test(ledger-suite-orchestrator): migrate integration tests to PocketIC** 👈 you are here 2. dfinity#10950 — test(cketh): deduplicate signed-transaction literals in integration tests 3. dfinity#10955 — test(cketh): migrate integration tests to PocketIC 4. dfinity#10956 — test(ledger-suite-orchestrator): drop the StateMachine test fixture 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…cket-ic # Conflicts: # Cargo.lock # rs/ethereum/cketh/test_utils/Cargo.toml # rs/ethereum/cketh/test_utils/src/ckerc20.rs
…-drop-state-machine
…-drop-state-machine
The server exits hard 10 minutes after it starts whatever the traffic, and every fixture in a test binary shares one server, so the clock runs against the whole suite rather than a single test. A single-threaded run of the ckERC20 suite takes about 8 of those 10 minutes, and losing the server mid-suite fails every test that follows instead of the one that is actually slow. PocketIcBuilder does not expose the lifetime, so start the server here and hand the builder its URL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ixture" This reverts commit 00eaec4.
…rals in integration tests (dfinity#10950) ## Summary Small preparatory cleanup for the cketh integration tests ahead of the PocketIC migration (DEFI-2262). No framework change here — tests still run on `ic-state-machine-tests` and stay green. - Deduplicated the inline raw-transaction and transaction-hash string literals in `minter/tests/cketh.rs` that were byte-identical to the `DEFAULT_WITHDRAWAL_TRANSACTION` / `DEFAULT_WITHDRAWAL_TRANSACTION_HASH` constants already exported by `cketh/test_utils`. Since PR 3 regenerates these signatures for the PocketIC fixture, keeping every occurrence tied to the single constant means the regeneration only needs to touch one place instead of risking a missed or inconsistent literal. ## 📚 PR stack 1. dfinity#10949 — test(ledger-suite-orchestrator): migrate integration tests to PocketIC 2. **dfinity#10950 — test(cketh): deduplicate signed-transaction literals in integration tests** 👈 you are here 3. dfinity#10955 — test(cketh): migrate integration tests to PocketIC 4. dfinity#10956 — test(ledger-suite-orchestrator): drop the StateMachine test fixture 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The parameters state which query the stub answers, so a note repeating it next to each one adds nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ic-http-types, proptest and ic-test-utilities-load-wasm are not referenced anywhere in the crate, and were already unused before this PR removed the StateMachine fixture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🤖 Applied your review from #10956 here, since that PR's content ended up in this one — Also removed the note repeating what the stub parameters already say (
|
📚 PR stack
test(ledger-suite-orchestrator): migrate integration tests to PocketIC #10949— test(ledger-suite-orchestrator): migrate integration tests to PocketIC (merged)test(cketh): deduplicate signed-transaction and transaction-hash literals in integration tests #10950— test(cketh): deduplicate signed-transaction and transaction-hash literals (merged)test(ledger-suite-orchestrator): drop the StateMachine test fixture #10956— test(ledger-suite-orchestrator): drop the StateMachine test fixture — its content is part of this PR, see belowFollow-up, based on this PR: #11123 (PocketIC server lifetime).
Summary
Flips
rs/ethereum/cketh/test_utilsand the minter'scketh.rs/ckerc20.rsintegration tests fromic-state-machine-testsonto the syncpocket_icclient, following the pattern established for the ledger-suite-orchestrator in #10949.The minter is now initialized against the fiduciary subnet's
"key_1"ECDSA key (StateMachine's"master_ecdsa_public_key"isn't available on PocketIC's fiduciary subnet), which changes the minter's derived Ethereum address. Every signature-derived constant is regenerated as a consequence:MINTER_ADDRESS,DEFAULT_WITHDRAWAL_TRANSACTION(_HASH),DEFAULT_CKERC20_WITHDRAWAL_TRANSACTION(_HASH), the r/s of the default ERC20 signed transaction, the per-account deposit address inshould_record_address_to_deposit, and the resubmitted-transaction literal inshould_resubmit_new_transaction_with_same_max_fee_per_gas_when_price_increased.Canister-http mocking is rebuilt around PocketIC's
get_canister_http()/mock_canister_http_response(), preserving the legacy"Http body exceeds size limit of {n} bytes."reject that the minter's response-size check matches on.tests/dump_stable_memory.rsandtests/deposit_from_cex_demo.rsare untouched.Also included: dropping the StateMachine fixture from the orchestrator
This was meant to be a separate PR (#10956) reviewed on top of this one. It was marked merged by mistake — see the note there — and since GitHub cannot reopen a merged PR, its content stays here rather than being shuffled again.
With the ckETH minter's tests on PocketIC, nothing in
rs/ethereumuses the orchestrator's StateMachine-based fixture, so it is deleted along with the now-unusedic-state-machine-tests,ic-types,ic-management-canister-types-privateandassert_matchesdependencies. The environment-agnostic helpers thepocket_icmodule and cketh still import (wasm loaders, init-arg builders,usdc()/usdt(), constants) stay at their existing paths. After this PR, no Rust target underrs/ethereumcompiles againstic-state-machine-tests.Behavioural differences PocketIC forces, and how they are handled
PocketIC differs from StateMachine in two ways the tests are sensitive to. Both are handled in the fixture, in one place each, rather than worked around per test.
Time advances kill the outcalls in flight. PocketIC fails canister-http requests once they cross
CANISTER_HTTP_TIMEOUT_INTERVAL; StateMachine left them pending indefinitely. The requests also stay listed until a round processes the failures, so a stub could bind to a request that was already dead and see its response discarded. Worse, a scraping cycle awaiting such a request keeps holding itsTimerGuard, so the firing due at the new time is dropped asAlreadyProcessingand the query the test expects is never issued.CkEthSetup::advance_timetherefore fails those outcalls up front and deterministically, doing what the advance would have done anyway. It is a no-op when nothing is in flight, because a fixture that has executed no round is expected to trigger the first scraping cycle itself.A stop request takes effect only in the following round. The outcalls the minter's timers issue in that last running round become visible after it, so
stop_minterticks once before draining them.The block number stubs also now match on
["finalized", false], which is what the scraping cycle actually queries — unconstrained, they could be consumed by the block height refresh timer's query for the latest block.Runtime
Measured against master with identical in-test instrumentation on both sides: +5% (ckETH) and +11% (ckERC20) of wall-clock at default parallelism (+15%/+20% single-threaded). Fixture-light tests are indistinguishable from master; the difference tracks the number of IC rounds a test drives, since PocketIC runs out of process. The dominant cost in both harnesses is tests that rebuild the whole fixture several times within one
#[test].🤖 Generated with Claude Code