Skip to content

feat: flexible canister http outcalls - #254

Draft
mraszyk wants to merge 1 commit into
mainfrom
oggy/flexible-http-outcalls
Draft

feat: flexible canister http outcalls#254
mraszyk wants to merge 1 commit into
mainfrom
oggy/flexible-http-outcalls

Conversation

@mraszyk

@mraszyk mraszyk commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New flexible_http_request management canister method: a variant of http_request where nodes return their individual HTTP responses to the caller instead of reaching consensus.
    Supports configurable replication via an optional replication argument (min_responses, max_responses, total_requests). Returns a vector of responses or a structured error with a global error code and per-node resource reports.
  • New pricing_version field on http_request: adds an optional pricing_version field (1 or 2). Version 1 is now deprecated; version 2 ignores max_response_bytes and refunds unused cycles based on actual execution cost.
  • New ic0.cost_http_request_v2 System API: replaces the deprecated ic0.cost_http_request. Accepts a Candid-encoded parameter record to compute the cost of an outcall at pricing version 2.
  • New ic0.subnet_self_node_count System API: returns the number of nodes currently on the subnet. Useful for callers computing valid replication bounds for flexible_http_request.

@github-actions github-actions Bot added the interface-spec Changes to the IC interface specification label May 13, 2026
@github-actions

github-actions Bot commented May 13, 2026

Copy link
Copy Markdown

🤖 Here's your preview: https://keluh-vqaaa-aaaam-ai7wa-cai.icp0.io

pull Bot pushed a commit to mikeyhodl/ic that referenced this pull request Aug 18, 2026
Implement a new system API function allowing a canister to determine the
size of its current subnet. This will be helpful for flexible outcalls,
in which a canister can individually specify how many replicas should
perform a request, and how many of them should produce a response. This
only makes sense within the context of the own subnet size.

Specified here (draft):
dfinity/developer-docs#254

---------

Co-authored-by: Pierugo Pace <pierugo.pace@dfinity.org>
@marc0olo

Copy link
Copy Markdown
Member

Flagging a downstream dependency, since this PR touches only the spec files (ic-interface-spec/*, ic.did).

Three non-spec pages encode the pricing-v1 assumptions this PR deprecates, and they will need updating in lockstep when v2 becomes the default. Listing them so they are not missed:

Page Line Claim that breaks under v2
concepts/https-outcalls.md 75 "max_response_bytes: … This is what you're charged for, not the actual response size." This PR states v2 ignores max_response_bytes.
concepts/https-outcalls.md 77 "omit … charges roughly 20.85 billion cycles"
concepts/https-outcalls.md 70 names ic0.cost_http_request, deprecated here in favour of _v2
guides/backends/https-outcalls.mdx 142 "costs are based on max_response_bytes, not the actual response size" plus the same 20.85B figure
guides/backends/https-outcalls.mdx 144 names ic0.cost_http_request
references/cycle-costs.md 119-133 the whole base_fee/size_fee formula and the 13/34-node table are v1-only; v2 prices on raw response size, transform instructions, transformed size, and roundtrip time, so it needs a separate section rather than an edit

Line numbers are against main plus #352, which is currently editing the first two of these.

For whoever picks this up: as of dfinity/ic@339d220a83 the replica still gates v2 off for the standard method, so the existing pages are correct today and nothing needs changing before this lands:

// rs/types/management_canister_types/src/http.rs
pub const DEFAULT_HTTP_OUTCALLS_PRICING_VERSION: u32 = PRICING_VERSION_LEGACY;
pub const ALLOWED_HTTP_OUTCALLS_PRICING_VERSIONS: &[u32] = &[PRICING_VERSION_LEGACY];

The cycle-costs.md change is the substantive one: v1 and v2 have different cost shapes, not just different constants, so that page likely needs both documented side by side while v1 remains the default-but-deprecated version.

marc0olo added a commit that referenced this pull request Aug 25, 2026
… default cost (#352)

Closes #351.

The issue reported two problems with how the HTTPS outcalls pages
describe `max_response_bytes`. Both are confirmed against the [interface
spec](https://github.com/dfinity/developer-docs/blob/main/docs/references/ic-interface-spec/management-canister.md)
and fixed here, along with several further defects found while fixing
them.

## What the issue reported

**1. Wrong byte figure.** `2,097,152` → `2,000,000`. The spec: *"the
default value of `2MB` (`2,000,000B`) is used as the limit."* Confirmed
in the replica as `MAX_CANISTER_HTTP_RESPONSE_BYTES = 2_000_000`.

**2. The limit is not body-scoped.** The spec defines the measured
quantity as *"the total number of bytes representing the names and
values of HTTP headers and the HTTP body."* Both pages now say headers
plus body.

**3. The transform bound** (raised in the issue body).
`max_response_bytes` is enforced **twice**: on the raw response as it
arrives, and again on the transform's output. A transform cannot rescue
a response that already exceeded the cap, because the first check runs
before the transform does; it only keeps the transform's own output
within the cap. Stated in the guide's transform section, where a reader
would form the "I'll strip headers to fit" plan, and in the concepts
Limitations bullet.

## Additional defects found

**4. The default-size cost was wrong on both pages.** Both said omitting
`max_response_bytes` costs *~21.5 billion cycles*. The formula already
published on `references/cycle-costs.md` gives:

```
49_140_000 + 10_400 * 2_000_000 = 20_849_140_000   (~20.85 billion)
```

Corrected to ~20.85 billion in both places. 21.5B matches neither the
decimal nor the binary reading, so it appears independently wrong rather
than downstream of the byte-figure error.

**5. `references/cycle-costs.md` said `max_response_bytes` defaults to
"2 MiB".** Same decimal-vs-binary error, on the page the other two link
to for exact pricing. Corrected, with the resulting cycle figure added.

**6. Both pages claimed a single ~30 second timeout, and the guide said
the call *traps*.** There are two timeouts and neither traps:

| Trigger | Reject | Message |
|---|---|---|
| Remote server silent for 30s | `SysFatal` | `Timeout expired` |
| Subnet produces no response within 60s | `SysTransient` | `Canister
http request timed out` |

Telling readers to expect a trap points them at the wrong error
handling.

**7. The Motoko cycle guidance was stale.** Both pages said *"In Motoko,
cycles must be attached explicitly with `await (with cycles = ...)`"*.
The `ic` package provides `Call.httpRequest`, which computes the exact
cost via `ic0.cost_http_request` and attaches it, matching the Rust
wrapper. The pages now also explain why a hand-picked margin is
counterproductive: attached cycles are held for the duration of the
call, so a margin caps outcall concurrency.

## Submodule bump

Item 7 could not be fixed in prose alone, because the embedded Motoko
snippets hardcoded `with cycles = 230_949_972_000`: correcting the text
would have left the page contradicting its own code. That was fixed
upstream first in dfinity/examples#1477, merged as `b4fe175`.

`.sources/examples` is bumped `d4ea422` → `b4fe175` here, so the
snippets now render `await Call.httpRequest(request)` and code and prose
agree.

The old pin predated the examples restructure, so all six `snippet=`
paths moved and are updated:

```
send_http_{get,post}/src/send_http_{get,post}_backend/main.mo    -> send_http_{get,post}/backend/main.mo
send_http_{get,post}/src/send_http_{get,post}_backend/src/lib.rs -> send_http_{get,post}/backend/src/lib.rs
```

Region names (`transform`, `get_request`, `post_request`) are unchanged.

Per `.agents/submodule-bumping.md`: `guides/backends/https-outcalls.mdx`
is the only page using `CodeExample`, so no other page is affected by
the moves, and `examples` tracks master so it carries no
`.sources/VERSIONS` entry.

## Scope

Kept deliberately tight per `CONTRIBUTING.md`: `concepts/` stays
explanatory, and the spec's header limits (≤64 headers, ≤8 KiB per name
or value, ≤48 KiB total) are **not** added. The issue marked them
optional, and enumerating them duplicates content that belongs in the
interface spec and the `https-outcalls` skill.

## Verification

- `npm run validate`: no errors in the touched files.
- `build_and_deploy`: passing against the new submodule. This is the
meaningful check for the bump, since `remark-snippet` treats a missing
file or region as a hard build error.
- Before pushing the bump, all six file+region pairs were confirmed to
resolve at `b4fe175` by replicating the plugin's extraction logic.

## Related

- dfinity/icskills#361 carries the same corrections in the
`https-outcalls` skill, including the reject-message set these pages do
not enumerate.
- #254 (flexible outcalls) will invalidate the v1
pricing assumptions on these pages when it lands: `max_response_bytes`
is *ignored* under pricing v2, and `ic0.cost_http_request` is
deprecated. Flagged there with the specific lines, including that
`references/cycle-costs.md` needs both cost models rather than an edit
in place. As of `dfinity/ic@339d220a83` v2 is still gated off, so the
pages are correct today.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

interface-spec Changes to the IC interface specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants