Repository navigation
[Design][IPC] Bounded parameterised verification services: typed requests and responses, child lifecycle, and upstream interoperability #83
Description
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
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
- Upstream macro + default Channel/JSON: directly interoperable with the pinned baseline; measure total costs including identity and resource checks.
- 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.
- Fixed Molecule/byte transport: baseline extension of existing specialised CellScript calls; implement independently only if evidence on boundaries and maintenance cost warrants it.
- 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
- 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.
- 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.
- 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.
- Freeze wire vectors and a host reference decoder. Feed identical requests to the Rust server and generated candidate, requiring identical outcomes and errors.
- Carry ownership/effects, IR, codegen, checker, LSP/formatter, WASM summary, and builder through the explicitly supported scope; reject unsupported type combinations at compile time.
- 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
- Review complete typed one-shot and repeated-request authoring examples against [0.33][Runtime] Add bounded allocation-backed value collections and deterministic Map/Set #47/[0.33][ABI] Support bounded nested dynamic values with canonical codecs #48/[0.33][Runtime] Extend bounded Cell selection and correspondence for heterogeneous and dynamic data #49, including named limits, bounded helper composition, encoding ownership, early-exit cleanup, and mandatory child completion before stable API freeze.
- Full target, method, context, permission, codec, budget and termination contracts, with owners and decision criteria for unresolved questions.
- Reproducible trade-off assessment of all four approaches; disclose every incompatibility with upstream defaults.
- Equivalent Rust/upstream-IPC/fixed-CellScript experiments and measurement plans with concrete artefacts and expected results.
- Failure matrix mapped to compiler/checker/VM/whole-transaction levels, documenting any unproven properties.
- Upstream discussions, actual review feedback, and maintainer decisions recorded. Create separate implementation acceptance items; design approval is not completion.
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.
Activity
[-][Design][IPC] 有界参数化验证服务:类型化请求响应、子进程生命周期与上游互通[/-][+][Design][IPC] Bounded parameterised verification services: typed requests and responses, child lifecycle, and upstream interoperability[/+]a19q3 commentedon Oct 18, 2026
考虑到本议题涉及的主要开发者和评审者中有不少以中文交流,为方便大家阅读与讨论,也出于对不同语言使用习惯的尊重,特在此附上中文版。英文正文仍作为当前 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 的执行。上游源码观察
固定基线:ckb-script-ipc @ 4044faee。
#[service]宏 生成 Rust trait 的 client/server 以及请求响应 enum,减少手写通信代码。底层依赖 RFC 0050 与固定 CKB 源码。CellScript 当前接受基线为 CKB
f7fa4436737756f97a24e254f22c13a36316ecea/ 0.207.0;新实验仍须记录真实 VM/ckb-std/库版本,不能凭 CKB 名称推断全网一致支持。v1 必须冻结的契约
第一实验建议一个 child、固定方法、一次请求/响应、固定宽度数据、禁止递归;第二实验比较同一 child 上两次顺序请求。具体数值预算需在测量前冻结,不能只依赖全链 cycle 上限。
仅在语义检查通过后才可使用返回值。例如单独的
true不足以授权跨实例动作,至少要绑定 domain、消费方、目标代码/实例、方法及相关状态。若使用完整 raw transaction hash,还需记住 witness/签名不在 raw hash 中,必须单独明确被承诺的 payload,避免遗漏授权或自引用。四种实现选择
SSRI 与 IPC 可以共用抽象接口描述,但 method ID、argv、frame、JSON/Molecule 不会因名字相同便互通。转换需要双向 golden vectors,并披露无法无损转换的类型与错误。
实施与 Rust 对照
ckb-std的原生 Rust 写相同单次验证器,明确检查真实 CellDep、上下文、请求来源与失败传播。Rust 可把检查抽成复用库,不能将重复编写视为 Rust 必然劣势。对抗性失败矩阵
作者接口方向 — 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 全部完成。
端到端教程。