Skip to content

[0.31][Cost] Extend cost budgets to rejection paths, composition, and call-chain stack use #35

Closed

Description

@a19q3

Engineering closure — 2026-09-25

Engineering closure through 0.31 follows the maintainer’s September 25 instruction to skip #30 and exclude 0.32. Release version promotion, fresh distribution bundles, the final release gate and independent release review remain under #30. This closure neither transfers the 0.30 review waiver nor claims an independent security audit or published 0.31 release.

Commit 648bb836, boundary review and acceptance map, isolated attribution, and reproduction/coverage map reconcile the checklist below.

Accepted decisions — 2026-09-21

D1: retain transaction-success baselines and introduce explicit measurement availability in cost-report v2. Use the pinned detailed_run path for exact-group nonzero-exit cycles while normal verification remains the acceptance oracle. Require static call-chain stack bounds for closed acyclic graphs; unknown effects stay unknown. Per-VM observed stack instrumentation is deferred to a follow-up targeting 0.32. Freeze the expanded fixture matrix before accepting new static budgets.

Full decision record — baseline: 8d6e200caa58d81429912df925a9322f691cb41d; research baseline remains 6d75a6e7. The choices below were the original open questions; this decision record now governs implementation. The issue stays open until its implementation acceptance passes.

Parent: #30

Target: 0.31. Baseline: branch 0.30, commit 6d75a6e7e4ce55b940697428d895eda74a51ca86. Implementation and engineering acceptance are complete; the accepted decisions define the bounded scope and deferred work.

Problem and evidence

The current contract covers three matched Rust samples and 18 growth rows. It budgets ELF bytes, successful cycles, witness size, and the largest recorded individual frame. Rejection behavior is tested, but rejection-path cycle costs are not separately budgeted. Multirecord policies, generic-instantiation growth, broader composition, and call-chain stack peaks need additional evidence.

Sources: docs/CELLSCRIPT_COST_REGRESSION.md and tests/support/cost_growth.rs.

Decisions to record

  • What exact programs and bounds belong in the 0.31 corpus? Start with wide constants, repeated/mixed policy parameter layouts, dense/sparse tags, multirecord envelopes, scalar live ranges, and repeated/distinct generic instantiations. Add only admitted executable compositions.
  • Define a stack metric: VM-observed stack high-water mark, a conservative call-graph bound, or both. State handling of recursion, dynamic stack adjustment, external Script execution, and unknown bounds; report unsupported evidence explicitly.
  • How are byte-versus-cycle regressions accepted? Prefer per-fixture absolute ceilings plus the existing matched-Rust ratios, with a written exception for any intentional tradeoff.
  • Which malformed inputs represent expensive rejection paths? Bound input sizes and distinguish a measured maximum over fixtures from a proven worst-case bound.
  • Does the report schema need a version change, and which CI/checker/report consumers need updating?

Acceptance

  • Publish the agreed fixture matrix, metric definitions, and budget-review policy before using them to judge the other optimization issues.
  • Preserve the existing 3 matched and 18 growth rows as comparable baselines, with source/compiler/toolchain identities.
  • Add executed rejection-cycle cases: late tag lookup, last-byte mismatch, malformed envelope/length, and over-bound inputs where applicable.
  • Add representative composition, generic-growth, and nested-call measurements within the supported surface.
  • Store per-entry frame size separately from observed or statically bounded call-chain stack use; add meaningful checks of the new measurement method.
  • Retain fresh machine-readable reports, fail on missing tooling/incomplete execution, and keep budgets reviewed and static rather than regenerated by tests.
  • Run ./scripts/cellscript_gate.sh ci; also run ./scripts/cellscript_gate.sh backend if backend instrumentation or artifact contracts change.
  • Report remaining gaps without claiming universal Rust equivalence or globally optimal costs.

Agree the metric definitions early. Extend the corpus alongside the backend work and use the completed reports for 0.31 acceptance.

Activity

  1. added this to the 0.31 milestone on Sep 21, 2026
  2. a19q3 commented on Sep 21, 2026

    @a19q3
    CollaboratorAuthor

    Research notes, 2026-09-21. CellScript source checked at 6d75a6e7. These are proposed experiments and decisions, not implementation results.

    The first task here is to make failure measurements observable. Adding another budget field to the current report would not be enough.

    Current evidence gap

    tests/support/ckb_script_runner.rs:523 gets successful cycles from Context::verify_tx, but its error branch sets cycles: 0. The result type even documents this behavior. Zero means unavailable on failure, not that rejection was free. New rejection-budget assertions must not accept it as measured evidence.

    Successful values from this path also cover transaction script verification, rather than isolating only the Script under study. Keep those existing figures for baseline continuity and add a separately named per-group metric.

    A concrete upstream API to evaluate

    Cargo.lock pins ckb-script 1.1.0, ckb-testtool 1.1.1, and ckb-vm 0.24.14. In the published ckb-script source, TransactionScriptsVerifier::detailed_run returns TerminatedResult; that result contains both exit_code and consumed_cycles. The ordinary run wrapper subsequently converts a nonzero exit into a validation error and loses those cycles from its return value.

    This offers a concrete experiment for ordinary nonzero script exits without injecting a new syscall or changing the contract. It still requires resolving the same transaction and selecting the exact group. Mirror the existing consensus/hardfork/VM configuration, and keep normal transaction validation as the acceptance oracle. The scheduler result includes its VMs, so document that scope for child execution.

    A VM trap, setup failure, or cycle-limit exhaustion may still return an error rather than a terminated result. Represent unavailable measurements explicitly, or expose separately justified scheduler accounting. Do not silently substitute zero, the configured limit, or an unlabelled lower bound.

    Proposed report and validation work

    • Add measurement status, exit/failure category, group identity and accounting scope alongside cycle values. Record compiler/source revision, dirty state, Cargo.lock and fixture/artifact hashes, VM configuration, Rust profile and strip-tool identity.
    • Preserve existing transaction-success rows. Add measured nonzero exits with two controlled fixtures where extra executed work makes rejection cost strictly larger; this catches a constant-zero implementation.
    • Exercise 1/2/4/8/16/32/64 artifact variants independently from 1/2/4/8 witness records. Use admissible dense, sparse and high tags; include rejection after the last comparison and malformed final records.
    • For stack, record observed per-VM sp displacement from a defined entry baseline separately from a conservative call-chain bound. Include temporary outgoing-argument reservations, not only recorded entry frames.
    • The policy parent reserves 4,192 bytes and an adapter reserves 5,376 bytes before its action frame. Their nested reservation alone is 9,568 bytes, by static inspection of src/codegen/policy.rs:14 and the adapter prologue. This is not a measured runtime peak and must not be compared as though it were the existing 5,376-byte individual-frame metric.
    • Evaluate a static bound first for a closed, acyclic call graph. The policy path already rejects recursive retained calls, but that is not a universal claim about every program. Unknown calls or stack effects must remain unknown, not contribute zero.

    Suggested decision: use explicit measurement availability, preserve the old accounting scope, and prototype detailed_run before building custom instrumentation. Keep actual nonzero-exit costs distinct from fixture maxima and mathematically proven worst-case bounds.

  3. a19q3 commented on Sep 23, 2026

    @a19q3
    CollaboratorAuthor

    Review after the 0.31 implementation, 2026-09-23.

    D1 is implemented at 65df937f: cost-report v2 distinguishes measured group exits from unavailable traps/setup/limit failures, retains transaction-success accounting, and reports static call-chain bounds separately from individual frames. Measurement regressions cover real nonzero-exit cycles, unavailable results and nested frames. CI at 65df937f passed, and the final local backend gate passed. The final implementation and validation record records 6,781 baseline comparisons without regression and 88,204 identical numeric fields between final local and GitHub reports.

    Remaining work is evidence packaging and review:

    1. Make the before/after report pair, exact fixture list, comparison program and baseline source snapshot/patch durably retrievable. Current final reports are available as CI artifacts; the pre-optimization baseline and ad hoc comparison scripts also need a reviewed archive or tracked reproduction path. The original baseline honestly declares dirty source. Preserve its digest and reconstructible source rather than assigning it a clean commit retrospectively. If that source cannot be reconstructed, collect a new explicitly identified baseline and rerun the comparison.
    2. Have the reviewer verify group selection, the ordinary transaction acceptance oracle, child-VM accounting, nonzero exits, and unknown stack effects. Verify that required-but-unavailable values fail budget checks and that no test rewrites its own ceiling.
    3. Publish a compact acceptance matrix mapping each frozen fixture family to its report rows and budget file. Include retained matched/growth rows, all-action policy sweeps, malformed/late rejection paths, generic/scalar/call cases and the multi-Script report. Use the existing measurements; add cases only where this mapping exposes an omission.
    4. Create a separate follow-up for observed per-VM stack high-water instrumentation, including entry baseline, EXEC replacement, SPAWN children and temporary outgoing reservations. The accepted 0.31 contract permits a static bound; observed peaks are not a blocker for D1.

    The child can close once the measurement contract, fixed ceilings and retrievable evidence receive review. “No regression in this corpus” must retain its bounded scope; it does not establish globally optimal code or universal Rust equivalence.

  4. a19q3 commented on Sep 25, 2026

    @a19q3
    CollaboratorAuthor

    Engineering acceptance complete at 648bb836.

    Validation on clean commit 648bb8362a82586821c0d4ae9f8b2250dd56ab11: local dev, full ci, and full backend passed; GitHub CI also passed and retains the fresh cost/shape/strict-audit artifacts. The final clean-source cost pair passes another 6,782-metric comparison against the archived candidate. The full backend gate rebuilt pinned CKB f7fa4436 and passed the local-node 43-action, 17-Lock and 26-stateful-scenario matrix. Local acceptance report SHA-256: 8a1aeb4221409f187e82a342c7335db19432a3fd800e9182e1c65806655a889c. This is local-node evidence, not a new public-network deployment.

    The frozen coverage map, baseline/candidate pair, isolated D2/D3 controls, source reconstruction and native comparison outputs are now durably tracked. Each comparison covers 6,782 comparable metrics without regression. The accepted matrix retains 3 matched and 18 growth rows, 41 policy rows, 9 scalar rows and 1,830 executions, plus 6 multi-Script transactions with 5 measured groups each. Source review confirmed exact-group selection, the normal transaction oracle, nonzero exit accounting and explicit unavailable results. Static call-chain tests include outgoing reservations and unknown effects; #36 owns observed per-VM peaks.

    Boundary review / acceptance map · attribution and exact report pairs · frozen coverage and reproduction.

    Engineering closure through 0.31 follows the maintainer’s September 25 instruction to skip #30 and exclude 0.32. Release version promotion, fresh distribution bundles, the final release gate and independent release review remain under #30. This closure neither transfers the 0.30 review waiver nor claims an independent security audit or published 0.31 release.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions