Inter-canister trade settlement - #38
Draft
ktimam wants to merge 4 commits into
Draft
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- canister_init.cpp: invoke __wasm_call_ctors() exactly once from the first CanisterInit construction (wasm builds only). Without this, C++ global ctors never run for canisters that have any non-zero-initialised globals (for example a `static std::string g_x = "v";`) and accessing them traps at runtime. - ic_call.cpp: rename exported trampolines from `canister_callback __icpp_*` to plain `__icpp_call_reply_trampoline` / `__icpp_call_reject_trampoline`. The IC reserves the `canister_` prefix for standard entry points and rejects modules that re-use it; trampolines are referenced by function-table index, not by exported name, so they don't need it. - ic_api.h: document the from_wire(CandidType) by-value gotcha and the required `CandidTypeText t(&str)` pointer-style idiom. - test/canisters/canister_call: align with icpp_candid 5.4 API (`.get_v()`), single-space `canister_query` export prefix, drop the disallowed `ic_api.to_wire()` call inside `canister_init`.
The mock header declared ic0_msg_reject_ic0_msg_size / ic0_msg_reject_ic0_msg_copy (a bad search-and-replace), while ic0.cpp defines and ic_call.cpp calls ic0_msg_reject_msg_size / ic0_msg_reject_msg_copy. Any icpp build-native of a canister using IC_Call failed to compile ic_call.cpp with undeclared-identifier errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An upgraded wasm instance never executes canister_init, so globals with dynamic initializers stayed zero-initialized after --mode upgrade. The ic_call pending-callback unordered_map was left with max_load_factor == 0.0, which makes libc++ double its bucket array on EVERY insert: each inter-canister call doubled the wasm heap (buckets ~ 2^calls) until the canister exceeded its 3 GiB wasm_memory_limit (deterministic 3.37 GiB peak after ~28 calls) and every subsequent update failed until reinstall. - icpp_run_global_ctors_once(): shared once-per-instance guard, declared in ic_api.h, called from both CanisterBase constructors so init, post_upgrade, update, query, and callback entries all trigger it. Canisters whose upgrade hooks don't construct an IC_API should call it directly (and never raw __wasm_call_ctors(), which re-runs ctors and resets restored globals). - canister_init.cpp: drop the local CtorRunner in favour of the shared guard. - ic_call.cpp: make the pending-callback registry a function-local static so it is guard-initialized on first use and immune to the global-ctor wiring; fix mojibake and a stale comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Inter-canister trade settlement �
IC_CallhelperSummary
Adds a high-level C++ API for inter-canister calls to
icpp-pro. Before thisPR, an
icpp-procanister could only be called; it had no clean way tocall out to another canister without writing raw
ic0_call_*imports andmanually shepherding env cookies through reply/reject WASM callbacks.
This is the prerequisite for the inter-canister trade settlement work in
the ICSoccerWorld marketplace (
ICMarketplace), where everybuy_listing,accept_offer,place_bidandsettle_auctionneeds to:icrc2_transfer_fromon a currency ledger.icrc37_transfer_fromon the collection canister.seller_receives + royalty + protocol_feevia threeicrc1_transfercalls.All of those require async outbound calls from an update handler, which
this PR makes ergonomic.
What the PR adds
src/icpp/ic/icapi/ic_call.hIC_Call::call(...),IC_Call::raw_call(...),IC_CallBuilder,CallReject,CallRejectCode.src/icpp/ic/icapi/ic_call.cpp__icpp_call_reply_trampoline/__icpp_call_reject_trampolineWASM callbacks, Candid arg serialisation, principal-bytes extraction.test/canisters/canister_call/ping_self("hi")issues an inter-canister call to its ownecho(text)method and finishes the original message from inside theon_replycallback.No build-script changes are needed �
config_default.pyalready globsic/icapi/*.cppandic/icapi/*.hautomatically.API surface
Return value of
perform()/call()is theic0.call_performsystem code:0 = queued, non-zero = system-level failure (call was never sent, no
callback will run).
Design notes
Env-cookie registry � every outbound call gets a monotonically
increasing
uint32_tcookie. The cookie is passed unmodified throughic0.call_newas the reply/reject env and is used to find the right C++handler in an
std::unordered_map<uint32_t, Pending>when the IC firesthe trampoline.
Trampolines �
__icpp_call_reply_trampoline(env)and__icpp_call_reject_trampoline(env)are exported ascanister_callbacksymbols. They're the only WASM-table entries the IC ever sees; they
immediately dispatch into the C++ map.
No state on the message stack � the original update message returns
before the callee replies. State that needs to survive must live on the
canister heap (typically the
*Storagesingleton, mirroring how themarketplace already keeps its in-flight trades).
Failure model � if
ic0.call_performreturns non-zero, the cookie isremoved before
raw_callreturns. If the callee traps or rejects, theregistered
on_rejectis invoked with the IC's reject code + message.Upgrade safety �
IC_Call::clear_pending()drops every callback. Usercode should call it from their
pre_upgradehook (and persist anytrade-state they want to resume in
post_upgrade). This matches theexisting pattern for
set_timercontinuations.Test plan
The new
canister_calltest canister exercises the round trip end-to-end:pytest --network=localdeploys the canister and callsecho("hello")directly to confirm the callee works.
ping_self("ic_call_works")and pollsget_last_echoeduntil theecho round-trips through the inter-canister reply callback.
get_pending_countis0after the call settles, proving theenv cookie was freed (no leak).
Run from the canister directory:
cd test/canisters/canister_call icpp build-wasm dfx start --clean --background dfx deploy pytest --network=localBackwards compatibility
prefixed
__icpp_.are inlinable.
Downstream work that depends on this PR
The
ICMarketplacecanister (https://github.com/ktimam/ICSoccerWorldServer)has a parallel branch
feature/phase-1b-inter-canister-trade-settlementthat:
InFlightTradestate machine toMarketplaceStorage.TODO Phase 1Bcomment blocks inMarketplaceServer.cpp(in
buy_listing,make_offer,accept_offer,cancel_offer,place_bid,settle_auction) with realIC_Callinvocations.memo-tagged retries (memo = sha256(trade_id || step)).the ICRC-7 owner flips.
That PR is blocked on this one merging.
Checklist
icpp-promain(5.4.1).IC_Call::pending_count()exposed for diagnostics.IC_Call::clear_pending()exposed forpre_upgradehooks.canister_calladded withpytestsuite."queued vs not queued".
__icpp_call_*doesn't collide withany reserved icpp internals.