Skip to main content
交易场所需要知道的某些账户信息,没法在区块执行期间算出来。成交量档位要看三十天的交易,奖励要看一个还没关闭的窗口,推荐归因要看几个月前建立的关系。 把这类活儿放进区块执行,会在两头都出错:一头是让每个区块都为一项几乎没有区块用得上的计算买单,另一头是逼内核去背它本来没有理由持有的历史数据。 计划服务(program services)的解法是把方向倒过来。计算跑在区块之外,依据的是已提交的记录;算出来的结果再作为协议状态提交回链上,执行时像读其他任何配置一样,常数时间就能读到。
已提交的交易历史
计划服务窗口计算,在区块之外从链上实时读取手续费表
自上次应用以来是否变化?
不写入
协议交易
链上状态
内核在执行时读取常数时间
比对的是费率,不是档位序号阈值挪动,或某个档位重新定价,都会改变账户实际付多少钱,却不改变它的档位序号。
最终费率由链来解析每一批都提交成功之后,这个周期才记为已应用;崩溃会把整个周期重放一遍,这样做是安全的,因为计算是幂等的。
链仍然是权威。服务并不持有网络依赖的状态——它只是提议一个值,链接受了的才算数。

目前在运行的

基于成交量的费率档位。这个服务消费节点流式推来的交易历史,按账户累计成交量,并按计划做快照。一个周期结束,它算出每个账户在滚动窗口内的成交量,再拿从链上实时读来的手续费配置把成交量映射成档位,最后分批把变了的账户写回链上。 这句话里有几个细节是承重的:
  • 档位表从链上读,绝不写死:服务要是自己留一份副本,网络换了费率表之后,它还会接着用昨天那一张。
  • 比对的是费率,不是档位序号:只比序号会漏掉两种真实情况——阈值挪了,成交量没变的账户落进了另一个档位;档位重新定了价,序号却没动。这两种都会改变账户实际付多少钱,却都不改变序号。
  • 最终费率由链来解析:交易带的是一个档位序号,执行时对照链上实时的手续费配置把它解析成费率。序号超出有效范围,整批交易失败,不会只应用一部分。
  • 每一批都提交成功之后,这个周期才记为已应用:周期做到一半崩了,就把整个周期重放一遍。这样做是安全的,因为计算是幂等的——同一个窗口,算出来的东西一样。

失效模型

这些服务夹在两个系统中间,而这两个系统都会时不时不可用。设计直接把这件事当成前提,不当成异常。 依赖出问题——数据库、节点的数据流、节点的 API——一律带退避重试,不会把进程干掉:重启修不好一个连不上的依赖,只会在故障之上再加一次冷启动。仍然算致命的,是重启确实能修、或者必须让运维看见的那些:启动时配置无效、绑不上健康检查端点,以及 panic。 故障期间,进程照常运行,把自己报成 not-ready,并累计错误数。所以运维信号是“它 not-ready 多少分钟了”,不是“进程还活着吗”——前者才是有用的问题:进程活着、却一个小时没能摄入数据,这才是真正的事故。 收到编排系统发来的信号,进程会优雅关停:停下手上的活、刷写检查点、干净退出。没有这一步,每一次例行部署都要赔上一个没刷写的窗口和一次重放。
算完了但还没应用的周期,不是丢掉的周期。计算是幂等的,而且要写入成功才会记下已应用的周期,所以中断的那次运行恢复时会把这个窗口重做一遍,不会跳过。

这个模式为何可以推广

写回路径是通用的。设置账户级配置、设置全局配置的协议交易本来就有;所谓计划服务,就是任何一个从已提交历史里为这些交易算出一个值的进程。 目前跑着的只有费率档位。激励计划、推荐归因、活动资格是同一个形状:对交易历史做窗口计算,和当前已应用的值比差异,再分批写回。它们该放在这里而不是放进内核,理由和费率档位一样——计算是周期性的、面向历史的,而执行要的答案必须是常数时间的查表。这些都还没建;能推广的是这个模式,不是它们一定会用它的承诺。 这些计划的商业条款在费率与计划里讲。本页讲的是结果怎么上链。

后续阅读

索引器

这些服务所消费的数据流。

手续费

商业面:有哪些档位,各自的费率是多少。

IntentionKernel

写回的配置如何在执行期间被读取。

状态模型

配置键为什么带版本,客户端又为什么该实时解析。