Skip to content

[Candidate][Consensus runtime] Implement executable bounded Cell-group consumption #7

Closed

Description

@a19q3

Summary

Implement the positive runtime semantics for BoundedCellSet<T, N>::consume_each on the CKB target.

The frontend already preserves the bounded operation and its predicate, but production builds correctly reject it because the emitted verifier does not yet select, authenticate, decode, count, and discharge the lifecycle of every matching input Cell. This issue is complete only when the selected shape executes in CKB-VM and the standalone checker can prove that the corresponding machine blocks are present.

This is a consensus-facing feature. An accepted specification must precede positive lowering.

Portfolio status

This is a candidate consensus-runtime workstream, not an authorized 0.26 or P0 implementation commitment. Until the post-0.25 evidence review accepts concrete use cases, an accountable owner, an independent reviewer, capacity, and stop conditions, only Phase 0 specification and use-case discovery are authorized. The issue priority describes semantic risk, not a promised release slot.

Why this is a real gap

Today, N is only a static complexity declaration. It is not evidence that runtime execution observed at most N Cells. The current boundary is documented in the collections support matrix.

Explicit fixed-arity parameters work, but they do not express protocols whose input cardinality is bounded yet chosen by each transaction—for example batched account updates, bounded receipt settlement, or a capped set of claims.

Argent's open range PR #44 is a useful design signal, not production evidence: its runtime builder and several interaction classes remain follow-up work. CellScript must define the CKB Script Group, Molecule, identity, and linear-lifecycle contract directly.

Required semantic contract

The first accepted version should be deliberately narrow:

  • support Type Script group inputs first;
  • require an explicit group source; never scan the whole transaction implicitly;
  • bind T to an exact Type Script identity and canonical schema;
  • compute the actual group cardinality in CKB-VM and require actual_count <= N;
  • decode every selected Cell exactly once;
  • execute the predicate exactly once for every decoded element;
  • fail the whole Script on any predicate, identity, source, count, or decoding failure;
  • discharge every selected Cell through the linear consume lifecycle;
  • preserve canonical group-relative order in metadata, traces, and builder evidence.

Lock Script support must be a separate explicitly justified shape if its observable group and authority rules differ.

flowchart LR
    TX["Candidate CKB transaction"] --> SG["Exact Type Script group inputs"]
    SG --> COUNT["Load actual group count"]
    COUNT --> BOUND{"count <= N?"}
    BOUND -- "no" --> REJECT["Stable runtime error"]
    BOUND -- "yes" --> LOOP["For each group-relative input"]
    LOOP --> ID["Verify Script identity"]
    ID --> DECODE["Decode canonical Molecule T"]
    DECODE --> PRED["Run predicate exactly once"]
    PRED --> LIFE["Record/discharge consume"]
    LIFE --> ACCEPT["All selected Cells covered"]
Loading

Required failure taxonomy

Each failure must have a stable registered runtime error and a source-linked ProofPlan obligation:

Failure Required behavior
wrong source kind Reject before trusting data
wrong Type Script identity Reject the selected Cell
missing or ambiguous identity Reject; never fall back to schema-only matching
malformed Molecule data Reject with a decoding-specific error
actual count greater than N Reject before iteration
duplicate logical identity Reject when the resource policy requires uniqueness
predicate returns false Reject the complete transaction
predicate or decode path omitted by codegen Standalone checker mutation must reject the artifact
lifecycle not discharged Compile-time ownership error or production rejection

Implementation phases

Phase 0 — specification

Add an accepted bounded-lifecycle RFC covering:

  1. observable source and group ordering;
  2. Type Script identity policy;
  3. canonical Molecule codec;
  4. zero-input semantics;
  5. uniqueness policy;
  6. predicate evaluation and side-effect rules;
  7. error-code allocation;
  8. ProofPlan and typed-semantics records;
  9. Lock versus Type Script differences;
  10. cycle, stack, and serialized-size budgets.

No positive runtime lowering should land before this phase is accepted.

Phase 1 — typed and metadata boundary

  • keep the predicate and source selection in typed semantics;
  • make actual-cardinality observation explicit;
  • add independent ProofPlan records for selection, identity, decode, count, predicate, and lifecycle discharge;
  • update entry ABI and builder assumptions;
  • ensure the executable-surface classifier distinguishes the newly supported shape from all unsupported variants.

Phase 2 — CKB runtime lowering

  • use canonical Script Group input syscalls;
  • enforce actual count before iteration;
  • lower a bounded loop with deterministic memory use;
  • decode each element once and retain a typed addressable view;
  • propagate exact runtime errors;
  • keep unsupported shapes fail-closed with E2105.

Phase 3 — verification and tooling

  • extend the standalone artifact checker and mutation corpus;
  • add simulator, CKB-VM, and stateful CKB acceptance fixtures;
  • update builder validation, LSP, VS Code, Playground summaries, docs, and release notes;
  • measure worst-case cycles, stack, code size, transaction size, and occupied capacity.

Acceptance matrix

The same fixture identity must be exercised in simulator, CKB-VM, and the stateful harness.

Case Expected
zero matching inputs, when minimum is zero pass
one matching input pass
exactly N matching inputs pass
N + 1 matching inputs stable count error
wrong Type Script hash stable identity error
malformed Molecule payload stable decode error
predicate false on first/middle/last element same predicate error
duplicate logical identity stable identity-policy error
transaction-wide match outside the Script Group ignored or rejected according to the accepted source rule; never silently selected
removed count/identity/decode/predicate machine block standalone checker rejects the mutation

Completion criteria

  • The bounded-lifecycle RFC is accepted.
  • A selected supported shape no longer emits runtime error 24 or E2105.
  • Unsupported sources and element shapes remain fail-closed.
  • Typed semantics and ProofPlan record actual runtime cardinality observation.
  • The machine lowering has deterministic mutation coverage.
  • Simulator, CKB-VM, and stateful CKB fixtures agree.
  • dev, ci, and backend gates pass.
  • At least one independent security review focuses on source selection, identity, decoding, and lifecycle coverage.
  • Release evidence includes worst-case cycles and size bounds.

Non-goals

  • allocator-backed collections;
  • arbitrary transaction-wide queries;
  • unbounded loops;
  • generic Cell-backed collection returns;
  • implicit schema-only identity;
  • hiding builder assumptions as on-chain authorization;
  • implementing create_each in this issue.

References

Activity

  1. a19q3 commented on Aug 27, 2026

    @a19q3
    CollaboratorAuthor

    Research update after reviewing Argent PR #44 and the current CellScript lowering

    Comment audit

    There were no prior comments on this issue at the time of review, so there is no unresolved maintainer feedback to answer here.

    Argent PR #44 does have active review discussion. The most important items are:

    The complete review note is also visible at:
    https://github.com/argent-lang/argent/blob/84cb74b65a7d4b8218ce505009ff017312fdf990/docs/entry-ranges-review.md

    What the Argent review actually changes for this issue

    The strongest lesson is not merely “add a bounded loop.” It is that the loop element must retain its physical input-reference identity.

    Argent's scalar consumed actor value denotes an input reference. The current range branch instead makes accounts[i] behave like an already-decoded authored state value. That erases the distinction between:

    • the physical input being authenticated and consumed;
    • the authored state decoded from that input;
    • native input properties such as value and covenant/script identity.

    The review proposes the more precise relationship:

    • accounts is a range of input references;
    • accounts[i] is one input reference;
    • state(accounts[i]) is the authenticated authored value;
    • native input fields remain operations on the reference.

    CellScript needs the same separation even though its vocabulary is CKB-specific. A BoundedCellSet element should not become T before the source, Script identity, count, data length, and codec have been checked. The internal model should preserve something equivalent to GroupInputRef plus a separately authenticated decoded T.

    Required execution order

    The first supported shape should enforce this order in generated code and evidence:

    1. Enumerate only the declared CKB source, initially Type Script Group inputs.
    2. Determine actual group cardinality and reject count greater than N before entering the body.
    3. For each group-relative index, load and validate the exact Script identity and required data length.
    4. Validate every dynamic offset/count before any indexed read.
    5. Decode the canonical state once.
    6. Cache or bind the authenticated projection so field reads cannot trigger a second inconsistent decode.
    7. Execute the pure predicate exactly once.
    8. Discharge the physical input's linear consume obligation.
    9. Reject unless all selected inputs complete all steps.

    This maps directly to CKB's consensus model: GroupInput is the current Script Group view, while transaction-wide Input is a different explicit source. See the official transaction and syscall specifications:

    ABI hardening requirement

    Argent's review found a second, independent issue: ordinary builder encoding was producing valid shapes, but the decoder path could still read dynamic struct-array leaves without first validating the claimed leaf cardinality. This is exactly the distinction this issue already tries to preserve: builder behavior is not consensus evidence.

    For CellScript, the accepted RFC should require all of the following before an element is addressable:

    • total witness/data size is within the declared bound;
    • offset table fits in the loaded byte region;
    • offsets are monotonic and in range;
    • item count is derived from validated structure, not trusted from a header;
    • fixed leaf count and dynamic leaf count agree with the schema;
    • multiplication/addition for offsets cannot overflow;
    • no element pointer is formed before those checks.

    The standalone checker mutation corpus should delete or bypass each class of guard independently.

    Scope refinement

    The Argent review also recommends deferring expanded consumed ranges because an expanded element may not have a complete authored state without a validated opening. CellScript should make the same conservative choice:

    The reported impossible-range example around unrestricted(next[3].value) is another useful warning: every compile-time range constraint must be checked for satisfiability, and unreachable index assumptions must not silently become runtime obligations.

    Acceptance additions

    Please add explicit tests for:

    • malformed dynamic offset tables at element 0, middle, and last;
    • count headers that fit N but whose byte region contains fewer items;
    • more physical group inputs than decoded elements;
    • the same data bytes under the wrong Type Script;
    • predicate success with one omitted lifecycle discharge;
    • a second decode returning different bytes in a mutation fixture;
    • an input outside the current Script Group that happens to match the schema;
    • zero inputs when the declared minimum is zero;
    • a review fixture in which the workspace builder emits a valid transaction and an adversarial post-build mutation corrupts only one offset/count.

    Conclusion

    The issue remains correctly classified as P0, but the implementation target should be described as “bounded authenticated input-reference iteration,” not merely bounded collection iteration. Argent #44 is useful because its current review exposes the exact abstraction leak CellScript must avoid; it is not evidence that the feature is complete upstream.

    The current CellScript fail-closed boundary is still honest and correct:

    • support matrix:
      | `HashMap<u64, u64>` | Limited | Limited | No production helper surface | Unsupported/fail-closed for production contracts |
      | `HashMap<Hash, Token>` | No | No | No | Unsupported; must fail closed |
      | `HashSet<T>` | Limited | Limited | No production helper surface | Unsupported/fail-closed for production contracts |
      | `BoundedCellSet<Resource, N>` + `consume_each` | Frontend and metadata only | Explicit fail-closed IR; checked predicates retained | No source scan/decoder | Production-rejected with E2105; non-production artifacts return error 24 |
      | `BoundedList<Plan, N>` + `create_each` | Frontend and metadata only | Explicit fail-closed IR; predicates and create template retained | No witness codec/output verifier | Production-rejected with E2105; non-production artifacts return error 24 |
      | Generic Cell-backed resource collections | No executable ownership model | No | No | Unsupported |
      ## Stack-Backed Local Vec Rule
      The current backend supports bounded local `Vec<T>` operations only when `T`
    • executable-surface reason:
      ["cell-backed-collection-return"]
      ),
      entry!(
      "artifact:bounded-consume-each-runtime",
      "artifact-policy",
      "reserved",
      "rejected by production policy",
      "BoundedCellSet source selection, element decoding, and per-element predicate execution are not yet an executable contract.",
      ["bounded-consume-each-runtime"]
      ),
      entry!(
      "artifact:bounded-create-each-runtime",
      "artifact-policy",
      "reserved",
      "rejected by production policy",
      "BoundedList witness codec, output correspondence, ordering, identity, and capacity enforcement are not yet an executable contract.",
      ["bounded-create-each-runtime"]
      ),
      ];
    • ProofPlan planned-helper boundary:
      preserved_fields: Vec::new(),
      witness_fields: if source == "witness" { vec![format!("witness.{}", operation.collection_binding)] } else { Vec::new() },
      lock_args_fields: Vec::new(),
      on_chain_checked: false,
      on_chain_checked_obligations: Vec::new(),
      builder_assumptions,
      codegen_coverage_status: codegen_coverage_status.to_string(),
      status: status.to_string(),
      detail: if is_create {
      format!(
      "bounded create_each over witness plan '{}' requires runtime iteration plus output-count and capacity builder evidence",
      operation.collection_binding
      )
      } else {
      format!(
      "bounded consume_each over input Cell set '{}' requires emitted source iteration coverage",
      operation.collection_binding
      )
      },
      diagnostics: vec![ProofPlanDiagnosticMetadata {
      severity: "warning".to_string(),
      message: if is_create {
      "bounded create_each is non-deployable until a canonical witness codec, output correspondence/order, identity, capacity, and runtime predicate contract are implemented"
      .to_string()
      } else {
      format!(
      "bounded consume_each is non-deployable: {helper} is only a planned helper name; source selection, decoding, cardinality, and per-element predicate execution are not emitted"
      )
      },
      }],
      }
  2. changed the title [-][0.26][P0] Implement executable bounded Cell-group consumption[/-] [+][Candidate][Consensus runtime] Implement executable bounded Cell-group consumption[/+] on Aug 27, 2026
  3. a19q3 commented on Sep 12, 2026

    @a19q3
    CollaboratorAuthor

    0.30 closure update: the admitted bounded Type-group input contract is complete. Native BoundedCellSet input now enumerates the complete canonical GroupInput set, checks runtime cardinality in 0..=N before iteration, preserves authenticated physical references through fixed-width decoding, evaluates the declared per-element predicate/accumulator, and discharges lifecycle coverage. Unsupported sources and shapes remain fail-closed.

    The independent checker and mutation corpus bind count, source, identity, decode, predicate, and lifecycle machine blocks; simulator, CKB-VM, stateful, and production-node evidence agree. The clean release replay at 3cf60a1 passed, including pinned CKB 0.207.0 production admission with complete 43-action/17-lock coverage.

    Independent review was explicitly waived by the maintainer for this bounded 0.30 scope; it was not performed or claimed as passed. Generic transaction iteration, non-Type roles, arbitrary loop effects, and dynamic outputs remain outside this issue. Closing as complete for the accepted contract.

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

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions