Skip to content

[Design][IPC] Bounded parameterised verification services: typed requests and responses, child lifecycle, and upstream interoperability #83

Open

Description

@a19q3

User problem

Status: design proposal. There is no general-purpose service ABI implementation or release commitment. We want a CellScript parent Script to invoke independently implemented verification services, pass bounded arguments, receive typed results, and guarantee that failures cannot masquerade as successful verification. Prefer evaluating reuse of ckb-script-ipc; consider an independent implementation only where a concrete incompatibility justifies it.

CellScript compiles to CKB ELF. The existing open child-verifier profiles in #28/#29 only admit restricted argument-free paths. Separate specialised ZK and byte-context transports also exist. Existing spawn/pipe primitives or specialised calls do not establish a general-purpose, parameterised, arbitrary-typed cross-contract service. #80 constrains Script instances; it does not automatically extend VerifierHandle argv.

The overall choice and SSRI work are linked under Design workstreams below. This issue owns the on-chain invocation contract; an off-chain service-query adapter is no substitute for VM acceptance here.

Define what an “invocation” means

Different Script groups in the same CKB transaction validate their own rules independently. A child spawned by a parent Script executes in the caller's transaction/Script context; it does not automatically become another Script group belonging to the referenced contract, nor does it acquire spending authority over that group's Cells.

The first experiment should use an ordinary, stateless document-digest policy verifier. The parent obtains the inspected data and domain from the real transaction; the child validates one fixed request and returns an explicit result. A request may contain a selected instance, but that instance is explicit data to be checked, not the child's automatically acquired load_script identity. Its response also cannot substitute for actual execution of credential Type and holder Lock Scripts.

sequenceDiagram
  autonumber
  participant P as Parent Script
  participant I as Identity / instance admission
  participant C as Child verifier
  participant G as Other required Script groups
  P->>I: Actual CellDep, policy, concrete target
  I-->>P: Exact permitted code and profile
  P->>P: Construct bounded request and domain from transaction
  P->>C: spawn + request
  C->>C: Check method, context, lengths and business rules
  C-->>P: Bounded response or failure
  P->>P: Validate result, complete protocol, check child status
  Note over P,C: Cleanup, waiting and failure semantics must be frozen for the chosen protocol
  G->>G: Independently validate their Lock / Type rules
  Note over P,G: Every required group must succeed for the transaction to pass
Loading

Pinned source observations, without a premature ranking

Research baseline: ckb-script-ipc @ 4044faee.

  • The #[service] macro generates Rust trait clients/servers and request/response enums, reducing handwritten communication code.
  • The Channel uses serde_json by default; Packet uses VLQ framing. The default RequestPacket method_id is 0; the generated payload expresses the method. One cannot insert an SSRI 64-bit path and claim compatibility.
  • Spawn helpers return two Pipes; the common API does not expose process_id to the caller. The server runs a repeated-request loop. Explicit WAIT/exit inspection or one-shot termination may require adaptation or upstream extensions; do not assume these are already exposed by the high-level API.
  • The inspected packet reader allocates according to a length declared by its peer. A CellScript profile must impose a pre-allocation input ceiling and define VLQ/version/integer-conversion boundaries. This is an integration requirement, not a claim that an upstream vulnerability has been found.
  • The library also has a native service-execution entry point; an SSRI-vs-IPC comparison cannot be reduced to “one is off-chain, the other on-chain”.

Underlying behaviour follows RFC 0050 and pinned CKB source. CellScript's current accepted baseline uses CKB f7fa4436737756f97a24e254f22c13a36316ecea / 0.207.0. New experiments must record the actual VM/ckb-std/library versions; the CKB name alone does not establish universal network support.

Contract to freeze for v1

Layer Required questions
Target Exact versus compatible implementation; instance constraints; binding to actual CellDep bytes, OutPoint, hash_type, and full interface
Method Namespace, complete signature, ID, version and codec hash; rejection of unknown methods, collisions and signature drift
Context Actual parent Script versus explicit selected target instance; permitted source/index reads; who proves the request comes from the real transaction
Authority Effects of read-only computation and verification results; no default rights over foreign Cell lifecycles or asset debits
Encoding Canonical request/response bytes, lengths, endianness, integer domains, trailing bytes, null values and error envelope
Resources Per-frame/total bytes, request count, child count/depth, FD, stack/heap/cycle limits and failure paths
Termination One-shot call versus service session; when to close and wait; handling successful response followed by non-zero child exit
Evidence What is established by compiler, independent checker, runtime, builder, and complete transaction respectively

Suggested initial experiment: one child, one fixed method, one request/response, fixed-width data, and no recursive calls. A second experiment can examine the benefit of two sequential requests to one child. These are decision candidates; numeric budgets must be fixed before measurements rather than delegating all limits to the chain-wide cycle budget.

A return value may be used only after semantic checks. A bare true, for example, cannot authorise cross-instance actions: the request must bind at least the current domain, consumer, target code/instance, method, and relevant state. If binding the full raw transaction hash, account for the fact that witnesses/signatures are not covered by the raw hash. Specify which payloads are committed separately, avoiding self-reference and omitted authorisation.

Implementation approaches to compare

  1. Upstream macro + default Channel/JSON: directly interoperable with the pinned baseline; measure total costs including identity and resource checks.
  2. Reuse upstream framing/runtime + explicitly negotiated bounded codec: can default-macro interoperability be preserved? If not, introduce a distinct profile name and capability-discovery rules.
  3. Fixed Molecule/byte transport: baseline extension of existing specialised CellScript calls; implement independently only if evidence on boundaries and maintenance cost warrants it.
  4. Retain specialised calls and defer generalisation: a valid outcome if there is no further application benefit or measurable costs are unacceptable.

SSRI may share an abstract interface description, but matching names do not make method IDs, argv, frames, or JSON/Molecule interoperable. Conversion requires bidirectional golden vectors and explicit disclosure of types/errors that cannot be mapped losslessly.

Incremental implementation and native Rust comparison

  1. Implement the same one-shot verifier in native Rust with pinned ckb-std, explicitly handling the actual CellDep, context, request origin, and failure propagation. Rust can also wrap these checks in reusable libraries; repeated handwritten code is not an inevitable Rust disadvantage.
  2. Benchmark the unmodified upstream IPC library on the same scenario. Prepare a minimal adaptation list for genuinely missing exit/budget APIs and prioritise discussion of upstream contributions.
  3. Use the existing fixed CellScript path as a third baseline before designing a candidate service description. New syntax is a subsequent choice; first assess whether existing typed metadata can express it.
  4. Freeze wire vectors and a host reference decoder. Feed identical requests to the Rust server and generated candidate, requiring identical outcomes and errors.
  5. Carry ownership/effects, IR, codegen, checker, LSP/formatter, WASM summary, and builder through the explicitly supported scope; reject unsupported type combinations at compile time.
  6. Exercise valid and malicious transactions using the real scheduler, parent/child processes, and all required Script groups. Compare maximum successful and latest-failing cycles, memory, ELF size, and transmitted bytes. Host mocks or native-server success are not substitutes.

Failure matrix (review these first)

Scenario Required property
Policy permits B, but this invocation binds A; dependency or instance is substituted Reject wrong target identity; interface similarity is insufficient
Child returns success and then exits non-zero Fail according to the frozen lifecycle; receiving a response alone is insufficient
Child exits early; EOF, short read/write, truncated frame Bounded deterministic failure; no uninitialised data
Length/VLQ overflow, non-canonical encoding, unknown version Reject before allocation/interpretation or follow an explicit compatibility rule
Both peers wait; FD leak, extra request, recursive spawn Controlled failure under the real scheduler; do not invent a wall-clock timeout syscall
Replay of previous or other-instance response Reject by request-domain and session rules; never treat cross-transaction results as authorisation
Structurally valid result violates a business condition Current Script evaluates its business predicate and rejects
Peer Type fails while child succeeds Entire transaction fails
Metadata modified and outer hash recomputed to remove wait/error branch Independent checker rejects absent execution binding; unsupported forms fail closed

Authoring direction — 2026-10-19

Apply the authoring requirements in #47/#48/#49 to the candidate service interface. Review named service methods with typed requests/results, structurally resolved target policies, and named budgets declared once at the appropriate service or session scope. Ordinary application code should express the business request, checked result, and permitted effects without repeatedly supplying role ordinals, positional size parameters, raw file descriptors, or framing details. Generated adapters may carry that plumbing, but must bind it to the same canonical interface and execution evidence. This is an API design target, not an implemented syntax or a choice of transport.

A one-shot call may encapsulate spawn/send/read/close/wait only if its declared success condition includes the required child termination and error checks. A repeated-request session must expose its bounded lifetime and completion obligation; early returns and failures must have specified cleanup and acceptance behaviour. Helpers must not let an unchecked response or unfinished session be treated as successful verification. Review these obligations explicitly even when the call site becomes concise.

Keep local borrowing separate from messaging. A bounded non-escaping helper may inspect a local view under its original provenance and lifetime; transferring a request across processes requires a specified bounded encoding, never a borrowed pointer or implicit shared ownership. Decoding a response grants no Cell lifecycle authority. Fixed-width messages must remain investigable without #47 allocation; nested messages depend only on the chosen #48 codec scope. If owned buffers or collections are needed, make their allocation scope and named byte/element limits explicit.

Before stable API review, provide complete one-shot and two-request examples with declarations, caller and server, helper composition, and terminal failure paths. Show the Rust and CellScript forms under equivalent identity, context, codec, and budget rules. Keep source ergonomics, wire compatibility, process lifecycle, and independent checker evidence as separately assessed criteria; a shorter call cannot weaken any acceptance condition.

Dependencies on #47/#48/#49 and later applications

Fixed request/response does not require a general heap or nested dynamic values. Nested variable-length messages depend on the specific codec in #48, not automatic support for arbitrary recursive objects on issue closure. Owned dynamic collections depend on #47 when actually required; bounded read-only decoding can stand alone. #49 governs heterogeneous Cell collections and correspondence, and is not replaced by IPC. Hooks #69, economic deltas #71, and routers #75 must separately establish user authorisation, complete asset flows, and actual Script-group execution.

Design closure criteria

Questions for reviewers: How should the service loop lifecycle interact with parent acceptance? Does default JSON convenience outweigh its protocol costs, and what measurements support that? Can we keep upstream-client interoperability while enforcing limits before allocation? Which child results merit typed capabilities and which should remain ordinary data? Who maintains versioning and adapters?

Design workstreams

Overall selection: #81. SSRI entry and off-chain context: #82. This issue is #83. Fixed-message experiments need not wait for the SSRI entry or full completion of #47/#48/#49.

Further reading

ProtocolBundle end-to-end tutorial.

Activity

  1. changed the title [-][Design][IPC] 有界参数化验证服务:类型化请求响应、子进程生命周期与上游互通[/-] [+][Design][IPC] Bounded parameterised verification services: typed requests and responses, child lifecycle, and upstream interoperability[/+] on Oct 18, 2026
  2. a19q3 commented on Oct 18, 2026

    @a19q3
    CollaboratorAuthor

    考虑到本议题涉及的主要开发者和评审者中有不少以中文交流,为方便大家阅读与讨论,也出于对不同语言使用习惯的尊重,特在此附上中文版。英文正文仍作为当前 issue 的主要描述;以下中文内容供对照参考。


    有界参数化验证服务:类型化请求响应、子进程生命周期与上游互通

    状态:设计提案,没有通用服务 ABI 的现成实现或发布承诺。 目标是让 CellScript 父 Script 调用独立验证服务,传递有界参数、取回类型明确的结果,而且不能把任何失败误认为验证通过。优先评估复用 ckb-script-ipc;只有具体边界无法满足时再讨论独立实现。

    CellScript 编译为 CKB ELF;现有 #28/#29 的 open child-verifier profile 只有受限的无参数调用,另有专用 ZK 和 byte-context transport。已有 spawn/pipe 或专用调用不等于拥有可传任意类型参数的通用跨合约服务。 #80 约束 Script 实例,不自动扩展 VerifierHandle argv。总选型由 #81 决定,SSRI 链下入口由 #82 负责;此处聚焦链上调用契约,不能用链下 query adapter 替代 VM 验收。

    明确调用及权限边界

    同一 CKB 交易中的多个 Script group 各自验证规则。父 Script spawn 的 child 使用调用方的交易/Script 上下文,不自动成为被引用合约的另一组验证,也不会获得其 Cell 的花费权。

    建议首个实验使用普通无状态文件摘要策略验证器:父方从真实交易提取数据和 domain,子方检查固定请求并明确返回。请求中的 selected instance 是待核验数据,不是 child 自动拥有的 load_script 身份。子方响应也不能替代凭证 Type 和持有人 Lock 的执行。

    sequenceDiagram
      autonumber
      participant P as 父 Script
      participant I as 身份 / 实例准入
      participant C as 子验证器
      participant G as 其他必要 Script group
      P->>I: 真实 CellDep、策略与目标
      I-->>P: 允许的确切代码和 profile
      P->>P: 从交易构造有界请求与 domain
      P->>C: spawn + request
      C->>C: 核验方法、上下文、长度及业务规则
      C-->>P: 有界 response 或失败
      P->>P: 核验结果、结束协议、检查 child 状态
      Note over P,C: close、wait 和失败语义应按选定协议冻结
      G->>G: 分别验证各自 Lock / Type 规则
      Note over P,G: 所有必要 group 成功才能通过交易
    
    Loading

    上游源码观察

    固定基线:ckb-script-ipc @ 4044faee。

    • #[service] 宏 生成 Rust trait 的 client/server 以及请求响应 enum,减少手写通信代码。
    • Channel 默认 serde_json;Packet 使用 VLQ framing。默认 RequestPacket 的 method_id 为 0,方法由生成的 payload 表达,不能将 SSRI 64-bit path 直接塞进去便称兼容。
    • spawn helpers 返回两个 Pipe;常用 API 不暴露 process_id。server 是多请求循环;需要显式 WAIT/exit 检查或单次终止时,可能要适配或向上游扩展,不能假设高层接口已有。
    • 所查 packet reader 按对端声称长度分配。因此 CellScript 必须定义分配前输入上限及 VLQ/version/整数转换边界。这是集成要求,并非断言上游存在漏洞。
    • 库本身也有 native 执行入口,SSRI 和 IPC 不能简单归类为“前者链下、后者链上”。

    底层依赖 RFC 0050 与固定 CKB 源码。CellScript 当前接受基线为 CKB f7fa4436737756f97a24e254f22c13a36316ecea / 0.207.0;新实验仍须记录真实 VM/ckb-std/库版本,不能凭 CKB 名称推断全网一致支持。

    v1 必须冻结的契约

    层 问题
    目标 exact/compatible implementation;是否限制实例;绑定真实 CellDep bytes、OutPoint、hash_type 与完整接口
    方法 namespace、完整签名、ID、版本、codec hash;如何拒绝未知方法、冲突和签名漂移
    上下文 父 Script 和明确选定的实例分别是谁;可读取的 source/index;谁证明数据来自真实交易
    权限 只读计算与验证返回值的效果;默认不授予外部 Cell 生命周期和资产扣款权限
    编码 规范请求/响应字节、长度、字节序、整数范围、尾随字节、空值和错误 envelope
    资源 单帧/总字节、请求次数、child 数量/深度、FD、stack/heap/cycles 限额及失败路径
    终止 单次请求还是服务会话;何时 close/wait;收到成功响应但 child 非零退出怎么办
    证据 compiler、独立 checker、runtime、builder、完整交易各自保证什么

    第一实验建议一个 child、固定方法、一次请求/响应、固定宽度数据、禁止递归;第二实验比较同一 child 上两次顺序请求。具体数值预算需在测量前冻结,不能只依赖全链 cycle 上限。

    仅在语义检查通过后才可使用返回值。例如单独的 true 不足以授权跨实例动作,至少要绑定 domain、消费方、目标代码/实例、方法及相关状态。若使用完整 raw transaction hash,还需记住 witness/签名不在 raw hash 中,必须单独明确被承诺的 payload,避免遗漏授权或自引用。

    四种实现选择

    1. 上游宏 + 默认 Channel/JSON:可直接对照互通,实测身份和资源约束下的完整成本。
    2. 复用上游 framing/runtime + 明确协商的有界 codec:检查能否保持默认宏互通;否则必须另命名 profile 并规定能力发现规则。
    3. 固定 Molecule/byte transport:对现有 CellScript 固定调用进行扩展;只有边界和维护成本证明必要时才自建。
    4. 保留专用调用,暂缓通用化:若业务收益不足或成本无法接受,这是有效结论。

    SSRI 与 IPC 可以共用抽象接口描述,但 method ID、argv、frame、JSON/Molecule 不会因名字相同便互通。转换需要双向 golden vectors,并披露无法无损转换的类型与错误。

    实施与 Rust 对照

    1. 用固定 ckb-std 的原生 Rust 写相同单次验证器,明确检查真实 CellDep、上下文、请求来源与失败传播。Rust 可把检查抽成复用库,不能将重复编写视为 Rust 必然劣势。
    2. 用未修改上游 IPC 跑相同场景,列出确实缺少的 exit/budget API,优先讨论上游贡献。
    3. 用现有 CellScript 固定路径作为第三基线,再设计候选服务描述;新增语法后置,优先判断 typed metadata 是否足够。
    4. 固定 wire vectors 与 host reference decoder;Rust server 和候选生成代码接受相同请求时必须产生相同结果与错误。
    5. 完成 ownership/effects、IR、codegen、checker、LSP/formatter、WASM summary、builder 的必要贯通;编译时拒绝未支持的类型组合。
    6. 用真实 scheduler 测试父子进程及全部必要 Script group 的合法与恶意交易;比较最大成功及最晚失败时的 cycles、内存、ELF 和传输字节。host mock 或 native server 测试不能替代。

    对抗性失败矩阵

    场景 必须保证
    策略允许 B,当前调用实际绑定 A,却替换 dependency/instance 目标身份拒绝,接口相同不足以通过
    child 返回成功随后非零退出 按冻结生命周期判失败,不能仅凭响应通过
    child 提早退出、EOF、short read/write、残缺 frame 有界确定性失败,不使用未初始化数据
    长度/VLQ 溢出、非规范编码、未知版本 分配/解释前拒绝,或明确兼容规则
    双方互等、FD 泄漏、多余请求、递归 spawn 在真实 scheduler 验证受控失败,不虚构 wall-clock timeout syscall
    重放旧响应或其他实例响应 按请求 domain 与会话规则拒绝,不跨交易授权
    返回结构合法但业务条件不成立 当前 Script 继续执行业务谓词并拒绝
    peer Type 失败但 child 成功 整笔交易失败
    修改 metadata 并重算外层 hash,删除 wait/错误分支 独立 checker 拒绝缺失执行绑定,不支持的形式 fail closed

    作者接口方向 — 2026-10-19

    将 #47/#48/#49 的作者接口要求应用到候选服务接口。评审具名服务方法、类型化请求/结果、结构化解析的目标策略,以及在对应服务或会话作用域集中声明的具名预算。普通业务代码应表达业务请求、受检查的结果与允许的效果,避免反复传入角色序号、位置参数大小、原始文件描述符或 framing 细节。生成的 adapter 可以承担这些底层操作,但须绑定到同一份规范接口与执行证据。这是 API 设计目标,不是已经实现的语法或 transport 选型。

    单次调用只有在声明的成功条件包含必要的 child 终止与错误检查时,才可封装 spawn/send/read/close/wait。多请求会话须明确有界生命周期和完成义务;提前返回和失败均须规定清理及验收行为。helper 不能让未检查的响应或未完成的会话被当成验证成功。即使调用处简洁,这些义务仍须明确评审。

    本地借用与消息传输分别处理。有界且不逃逸的 helper 可以在原有来源与生命周期约束下读取本地 view;跨进程请求须使用明确的有界编码,不能传借用指针或隐含共享所有权。解码响应不赋予 Cell 生命周期权限。固定宽度消息应继续允许在没有 #47 分配器的情况下开展实验;嵌套消息仅依赖选定的 #48 codec 范围。确需 owned buffer 或集合时,明确分配作用域与具名字节/元素上限。

    稳定 API 评审前,提供完整的单次调用及两次请求示例,包含声明、调用方、服务端、helper 组合和终止失败路径。Rust 与 CellScript 使用等价的身份、上下文、codec 和预算规则。作者写法、wire 兼容、进程生命周期和独立 checker 证据须分别评估,不能因调用更短而削弱任何验收条件。

    依赖、验收及评审

    固定消息不必等待通用 heap 或嵌套动态类型。变长嵌套消息只依赖 #48 中对应 codec;owned 动态集合需要时再依赖 #47;#49 的异构 Cell 集合对应关系不被 IPC 替代。#69 hooks、#71 delta、#75 router 须单独证明用户授权、完整资产流和实际 Script group 执行。

    请评审者重点讨论: 服务循环生命周期如何与父方验收结合?默认 JSON 是否值得其成本,有哪些实际数据?能否保留默认上游客户端互通同时做到分配前限额?哪些 child 结果适合类型化能力,哪些只能是普通数据?版本与 adapter 由谁维护?

    Design workstreams / 阅读

    总体选型 #81;SSRI 链下入口 #82;本议题 #83。固定消息实验无须等待 SSRI 接入或 #47/#48/#49 全部完成。

    端到端教程。

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions