一台“对象操作系统”的分工
Agoric 的运行时 SwingSet 借用了操作系统的比喻:每个智能对象组(官方称 vat,取自 machine 的谐音)相当于一个用户态进程,各自持有 JavaScript 引擎实例;vat 之间不许直接摸对方的内存,一切交互都要经过一个“内核”。vat 启动时向内核注册一个 dispatch 函数用于收消息,同时拿到一个 syscall 对象用于发消息、建承诺、存取状态。你调用另一个对象上的方法,实际发生的是一次跨 vat 的消息投递:参数被序列化,目标 vat 在自己的回合里收到并执行。这个设计的直接后果是,谁也不能把别人的状态搅乱——一个对象写了死循环,被处理的方式是掐掉它自己,而不是拖垮整台链。
crank:一条消息一个回合
内核维护着一串待办队列,消息投递与承诺解析通知都排在里面。SwingSet 把一次队列事件引发的连续执行称为 crank(曲柄):内核把一条消息投给某个 vat,这个 vat 就在自己的 crank 里跑,直到它不再往队列里添新工作。官方文档给出的调度次序是有优先级层的——垃圾回收动作最先被处理,然后是要求 vat 自查“谁的对象可以宣告死亡”的 reap 队列,之后才是普通的消息投递、承诺解析通知与新建 vat。每个 crank 结束都会产出一份“投递结果”,标明成功还是失败;vat 也可能在执行中因出错或超额被直接终止。

computron:把“算了多少”变成可共识的数字
要防死循环,先要能数计算步数,而停机问题决定了静态分析数不出任意程序会跑多久。SwingSet 的答案是在 JavaScript 引擎底层插桩计数,计量单位叫 computron:读一次对象属性、做一次加法,各扣掉固定额度。官方文档给了一组参考值——一个空函数的调用基础开销是三万六千多 computron,一句自增是 4,一次控制台打印约一千出头。关键在于一致性:同一版内核、同一版 vat 代码、同样的输入序列(官方称 transcript),在任何机器上数出来的 computron 应该相同,不看墙上时钟也不看 CPU 型号——这正是把它放进共识机器的前提。文档也提醒,计量映射依赖引擎细节,升级引擎时各节点必须同步换版本,否则计量结果会分叉。
额度管理有两层。第一层是单 crank 上限:一条消息无论循环多少次,算到上限就掐。第二层是 Meter(计量器):一个可变容量的执行额度池,vat 每个 crank 消耗多少就从里面扣多少,容量跌破零 vat 就被终止,此前它发出的消息一并作废;持有 Meter 对象的一方可以随时给它续额,Meter 还带一个 Notifier,在容量低于阈值时提前通知续杯。没配额度的 vat 走“不限额”档,靠的是运行策略兜底。
块怎么切,承诺怎么排
SwingSet 自己并不决定“一个区块干多少活”,这是宿主应用(比如 Agoric 的链节点)的职责:宿主反复驱动内核跑 crank,跑到自己设定的预算为止,然后把 SwingSet 状态提交到持久存储、宣布这个“块”结束。官方文档建议宿主用 Run Policy 对象来切块——策略能在每个 crank 后看到它花了多少 computron,据此决定继续还是收工;块太大则回复延迟高、中途崩溃要整块重跑,块太小则提交开销摊薄不下去,文档给的直觉是把单块执行控制在几秒量级。还有个反直觉的细节:出站消息在状态提交前被 embargo 扣住,防止还没落盘的状态提前泄露出去。
跨 vat 调用常见“我还没算完,但你可以先拿着结果继续用”的场景,这就是内核承诺:vat 本地造出的 Promise 发不出去(解析函数无法序列化),发送承诺实际是让内核建一行承诺表、由内核把最终结果转发给所有订阅方。未解析的承诺可以被继续“点属性”——对它的返回值再发消息会排在这条承诺的队列上,等它兑现后按序补发,即承诺流水线。文档明确内核不做投机投递:目标没定下来之前不会先跑用户代码,顺序因此可复现,这条纪律和计量一样服务于共识确定性。
边界与代价
拿它和 EVM 对照能看清取舍:EVM 用 gas 给每条字节码操作定价,SwingSet 则给真实 JavaScript 引擎的操作计数,语言自由度更高,代价是计量与引擎版本强耦合、升级协调更重;对象模型也比“一个合约一个账户”的心智更陌生。机制类描述以 Agoric SDK 仓库文档当前版本为准,具体数值示例可能随版本调整。本文只做机制解释,不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。