方法论
一个已发布的数字如何定义,以及你怎么把它核一遍
本站发布的每一个数字,都是公开链上数据与本页口径的函数。整套设计就是为了这一点:你应该能在不向我们要任何东西的前提下重算我们发布的任意一个数字,并在认为口径有问题时公开反对它。
1 · 版本
口径有版本号,每个已发布的数字都写明自己在哪个版本下算出。这就是测量与断言的区别:没有版本号,一个数字与一年后发布的同名数字不可比,而照样拿去比,正是现有那几份公开估计彼此矛盾的由来。
改口径永远不会重写已发布的数字。在新版本下重算的数字作为一条新记录发布并引用旧的那条,旧记录连同它的旧版本号一起继续可读。变更日志是口径的一部分,不是它的脚注。
口径按组成部分分别标版本,而不是作为一整块,每个已发布的数字都写明它所依赖的每个部分的版本。第 3 节的端点定义与第 4 节的震级定义现在都已冻结在 v1.0。任何仍是候选的部分之下都不发布内容——在一个可能还会改的定义下算出的数字无法与下一个比较——因此无论某个部分今天是否已冻结,这套版本机制都成立。
2 · 记录的单位
一条记录对应一次已经生效的执法动作:一笔链上交易,此后一个或多个目标无法再在某条链上转移某种资产。被计入的是效果,不是意图,也不是公告。
一笔交易影响多个目标时,仍然只是一条记录。受影响目标的数量属于这条记录;目标本身不发布——公报是动作的记录,不是当事方的名录。
在限制附着于代币账户而非钱包的链上,代币账户才是单位。同一钱包控制的两个账户,两个都被限制就是两条记录,只有一个被限制就是一条。把它们当作一次钱包级事件,会随链的不同而悄悄把计数翻倍或减半。
3 · 端点与间隔
每个间隔的两端都是区块时间戳,都不是观测时间。因此我们的时延、可用性与采集节奏不进入任何已发布数字——两个读者在两个不同节点上得到同一个数,而且不管我们在不在运行都得得到。这些端点定义已冻结在 v1.0。
- 执行端
- 目标此后无法再转移该资产的那笔交易的区块时间戳。以链上状态的效果判定,不以被调用函数的名字或签名判定。
- 发起端
- 同一条链上、构成执行之必要前置且指向同一目标的最早那笔交易的区块时间戳。条目会把那笔交易作为 precursor_tx_ref 一并发布,于是这一端是任何人都能自己取的读数:取回那笔交易,读它所在区块的时间戳。条目不发布这样一个引用时,该端为 null。
- 间隔
- 执行端减发起端,取整到分钟,且仅在两端都存在时给出。两个区块时间戳,两笔条目里写明的交易,一次减法。
端点与它的引用同进同出。条目发布发起端时,必定同时发布它所对应的那笔前置交易;发布那个引用时,也必定同时发布这一端——内容 schema 拒收只有其一的条目,因为读者核不了的端点是断言,不是测量。验证器会从引用重新算出这一端,并核对该前置交易确实在执行交易之前的区块里、且带着同一个目标。
发起端为 null 就按 null 发布,既不从记录里丢掉,也不读作零。测不到的间隔是一个盲区,而把盲区记成零,会把每一个聚合值往下拽,看上去却像是覆盖变好了。间隔一般有多长,这一页和这份记录都给不出结论:有多少提前量是发行方治理结构的副产品,它随时可以归零,且不必通知任何人。
4 · 震级
震级是执行端被冻结量占该资产自身链上供应量份额的以十为底的对数,平移到 0 到 10 的刻度上,取一位小数。整条链上供应被全部冻结是 10.0,冻结其千分之一是 7.0,百万分之一是 4.0。低于供应量的百亿分之一时,震级为 0.0。
M = round(max(0, 10 + log₁₀(U / T)), 1),当 0 < U ≤ T · U = 被冻结的单位数(按冻结类型读取,见下),T = 同一条链上该资产合约在该类型所读区块的总供应量
用供应量份额,不用原始单位数,也不用美元。原始单位数在不同资产之间不可比——decimals 不同,而且代币计数不等于价值。美元数需要执行时点的价格,来自链外、要读者去相信、事后也难以重建。而链上供应量份额是量纲无关的,与资产的 decimals 无关,也不需要价格。在 EVM 链上,U 与 T 都是执行区块的历史状态读取,二者之比因此可以只凭一个 archive 节点确定性地重算。震级在不同资产之间可比,它不是对一次动作有多严重的评判。该震级定义已冻结 v1.0;已冻结的定义如何修订见第 1 节。
分母 T 取总供应量(total supply),不取流通量——这是一个有意的取舍。总供应量是在冻结类型所读区块对链上状态的一次确定性读取;而流通量要扣除国库与锁仓余额,其规则活在链外、无法只凭链上数据重建,因而会让这个数字不可复算。在 EVM v1.0 定义域内,两者的差异通常不足以改变取整后的数字——失真一般在 0.1 个震级以内。
4.1 · 余额读不回去时,那个绝对量怎么来
比值是盲区,不等于绝对量也是盲区。在只提供最新态的链上,执行区块上的余额改由该资产合约自身的转账日志重算——日志是历史的、可按地址检索的:把每一笔指向该目标的转账在一个起点上加减一遍,执行区块上的余额就从这套算术里落出来。增发与销毁不需要特例,它们就是与零地址之间的转账。
起点有两个,而且互为校验:它们读的是同一份日志的不同区段,算出来必须是同一个数。一个从合约的第一个区块、余额为零起算,一路加到执行区块。另一个从节点此刻报出的余额起算(那个区块要被钉住),把日志倒推回执行区块。前者要读完合约的全部历史,在一次日志查询上限 5000 区块的端点上是几千次查询;后者只读冻结之后那些区块。任取其一都足以核对一条条目。
重算宁可停下,也不猜。某一页读不回来就停——少一页的求和与「余额本来就更小」在结果上完全一样。算出负数也停。所读区间里出现任何指向该目标、却既不是转账也不是授权的事件,同样停:转账日志描述不了的东西可能动过这个余额,那个数字就不再可证。若执行交易是销毁而非就地冻结,则根本不重算余额:绝对量取该笔交易事件日志里解出的销毁金额——对销毁而言,这就是「被冻结量」的含义,也是唯一精确的读法。
分母没法这样找回来。总供应量的变化有许多单个目标的转账史不记录的原因,所以在这类链上,条目并列给出绝对量与 null 震级。这个组合才是诚实的:说清被冻结了多少,同时承认它占多大份额在这里测不出来。
边界规则
就地冻结型。一次把余额冻住、但不销毁的 blacklist 或 freeze authority。U 取执行区块末该目标的余额;T 取同一区块末该合约的总供应量。两者都在区块末读,意味着即使有 mint 或 burn 也落在这个区块里,两个验证者读同一个区块也会得到同一个 T。
销毁 / 回收型。执行交易在同一个区块里烧掉被冻结的余额并减少总供应,因此到区块末余额已经是零。U 因此不从区块末状态读,而取执行回执事件日志里解码出的销毁金额——发行方自己的事件,验证器按开源规则解码。T 取执行区块前一区块末的总供应量,也就是这些单位当时所占份额的那个供应量。这样 U ≤ T 成立、比值有定义,也正是读取点因类型而异、并非一刀切的原因。
异常不 clamp。如果算出 U > T、或 T 为零、或任一值读不出来,就不产出震级:条目记 null 并附原因,不记 10.0。把这种情形夹到刻度顶端,等于把一个读不出或自相矛盾的测量,打扮成一次全供应冻结。读作 10.0 表示被冻结份额在舍入距离内达到全供应——是刻度顶端,而不是在声称 U 恰好等于 T。
舍入。取对数之后、把平移值舍到一位小数,逢半进上,绝不在取对数之前舍。验证器逐位复现这一位小数;一个不把舍入模式钉死的定义,会让两个都正确的实现在最后一位上分歧。
震级是份额刻度,不是绝对规模。它说的是「一条链上、一个合约自身供应量的多大一份」被冻结了——不是多少单位,也不是多少价值。由此有两件事。记录不跨链、不跨合约相加,因为「占某一份供应的份额」不是「占另一份供应的份额」。而且这个数字对冻结机制的粒度敏感:一次被拆成许多个按账户的条目来表达的动作——比如在 Solana 上,粒度是 token account——会被记成好几条各自较低震级的记录,而不是一条大的,尽管被冻结的总量是同一个。这正是每条条目都在震级旁边同时发布被冻结绝对量的原因:刻度回答「占多大份额」,数量回答「有多少」,两者互不替代。
下面这幅示例记录里,纵向偏移随震级缩放,每一道是一天。取值为合成数据,不对应任何真实事件。
5 · 计入什么,排除什么
一个已发布的中位数,被边界条件决定的成分比被算术决定的成分更多。下面是真正起作用的那些,事先写清楚,而不是逐案再定。
| 情形 | 判定 | 理由 |
|---|---|---|
| 在该链声明的终局规则下已终局的动作 | 计入 | 基准情形。终局规则按链声明,一条记录要等它。 |
| 交易回滚 | 排除 | 什么都没生效,也就没有可记录的动作。 |
| 影响全体持有人的全局暂停或合约升级 | 排除 | 不是针对特定目标。把它计入,等于把针对某一方的决定和针对所有人的决定混在一起。 |
| 对先前动作的解除或撤销 | 计入 | 作为一条独立记录,引用原记录。原记录永不编辑、永不删除——看过它的读者需要能看到它变了。 |
| 测试网 | 排除 | 不属于被测量的总体。 |
| 在链外施加的限制 | 排除 | 链上状态里看不见,读者因而无法重算。排除它是这套方法的局限,也按局限写明。 |
| 对已被限制目标的重复动作 | 排除 | 效果没有变化,因而不产生新记录。把它计入会让每一个比率都虚高。 |
6 · 聚合
时间数字跨发行方、跨链聚合。任何研究、任何粒度上都不发布按发行方拆分的序列。这是一条长期约束,不是当下的编辑偏好:按发行方拆分的间隔表是一张作业地图,也一定会被那样读。
季度序列采用 UTC 自然季度,一个季度只在它走完之后才发布。集中趋势用中位数配 25 与 75 分位给出,因为这个分布是重尾的,单给均值会被少数几个事件主导。
每个数字背后的样本量都会给出。样本量低于声明下限的切片,会以「样本过少不予报告」的形式发布,而不是被悄悄略去——被略去的切片看起来像是那段时间没有事件,而那是完全另一个断言。
7 · 验证器
验证器是一个独立的开源程序。它接受一个记录编号,以及一个具备 archive 能力的链节点的访问方式——能提供执行区块的历史状态,这是震级复算所必需的——输出一个摘要。每条已发布记录都写明能复现它的发布 tag,该 tag 在后续版本发布后仍可运行,旧记录因而始终可核。
它不从我们这里读取任何东西:没有密钥,没有账号,没有私有数据源,也没有只存在于我们这一侧的数据。我们的服务宕机时,这个核对照样跑得通。这正是设计的要点——一个依赖被验证方的验证,什么都没验证。
收到的不一致会被公开答复。若记录确实错了,就更正为一个写明改动内容与原因的新修订,被取代的文本仍然可读;若记录是对的,这个判断的理由同样公开。撤稿是发布的一种形式,永远不是删除。
一个重算出的数字不会在确认的那一刻就发布。一条公报条目从我方完成复算的那一刻起——不是从动作执行的那一刻起——至少等 24 小时才发布。这段冷却期让口径、摘要与复核有一段固定的沉淀期,并且由内容 schema 强制,而不是交给编辑习惯。
8 · 这里为什么没有任何名字
本站任何页面、条目或研究都不带个人署名,全部以公司名义发布。这不是谦逊,也不是为匿名而匿名。
作者名字的作用,是请你凭一个人的信誉先给出信任。本站的建法让这个请求根本不必提出:可验证性是署名的替代品。一个数字是对的,验证器就会这么说,与谁算的无关;一个数字是错的,署上名字也不会让它变对,只会让人更难指出来。
判断标准很简单:上面没有任何一段要求你相信某个人;上面每一段都在告诉你去取哪些公开数据,以及取到之后应该看到什么。
9 · 本页刻意不讲的部分
本页止于「定义」与「架设」之间的那条线。定义与验证公开,因为读者需要它们来核一个数字。这台仪器架在哪里——观测什么、来源怎么选、记录在哪里薄——不公开。
两个理由,都不是为保密而保密。公开架设方式会改变被测对象的行为,那是毁掉一次测量的直接办法。它同时会把一张作业地图交到一些读者手里,而这些读者对它的兴趣并不是研究。
这两个理由都不要求你凭信任接受什么。重算一条公报条目需要的是上面的口径、一个具备 archive 能力的链节点和验证器——被略去的部分不是这次核对的输入。合并了一个不列出的总体的研究,会在它自己的页面上说明,并标注为「计数不可复现」,但它的方法仍然可以复现。如果你发现其他已发布数字必须知道我们没公开的东西才能核,那是口径的缺陷,欢迎告诉我们。