RAILGUN 运行状态历史 API
运行状态 API 回答的是“Anon 的监控器现在看到了什么?”。本端点回答的是“它在过去两周里看到了什么?”。对于实时报告列出的每个服务和每条链,它返回最多 14 天的数据,每个 5 分钟的时间桶对应一个字符,并给出最近 24 小时、7 天和 14 天的可用率。足以绘制状态页的柱状图。任何人都可以读取。无需 API 密钥,也无需账户。
GET https://api.anon.inc/api/v1/railgun/liveness/history
这是 Anon 自己的监控器在单一位置得出的视角。它不是 Railgun 官方状态,也不是对 Railgun 本身的测量:每个字符都是 Anon 的监控器发布过的状态。局限列出了它无法告诉你的内容。
测试版: 此 API 处于测试阶段,承诺与实时端点相同。字段、取值和响应结构可能随时更改,恕不另行通知。请让你的客户端忽略未知的字段和值。见测试版状态。
| 属性 | 值 |
|---|---|
| 基础 URL | https://api.anon.inc |
| 身份验证 | 无 |
| CORS | 任意来源,不带凭据(GET、HEAD、OPTIONS) |
| 状态 | 测试版:可能随时更改,恕不通知(测试版状态) |
| 格式 | JSON,schemaVersion 1(JSON Schema) |
| 分辨率 | 5 分钟一个时间桶,最多 14 天 |
| 缓存 | 浏览器 60 秒,共享缓存 120 秒 |
快速开始
curl -i https://api.anon.inc/api/v1/railgun/liveness/history
响应包含历史数据及其缓存响应头:
HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: public, max-age=60, s-maxage=120, stale-while-revalidate=60, stale-if-error=300
etag: "5b3f0c9a1d7e4426"
access-control-allow-origin: *
access-control-expose-headers: ETag
把 ETag 连同 If-None-Match 发回去,即可询问是否有变化。若没有变化,返回 304 Not Modified,没有响应体:
curl -i https://api.anon.inc/api/v1/railgun/liveness/history \
-H 'If-None-Match: "5b3f0c9a1d7e4426"'
没有查询参数。每次请求都会返回全部已存储的历史数据,压缩后只有几 KB,因为这些字符串大多是同一个字符的重复。
下面是一个小型 TypeScript 客户端。它读取一行数据,把时间桶压缩成固定数量的柱条,并格式化可用率。null 的可用率显示为“无数据”,绝不显示为 100%;不认识的字符按无数据处理:
type Uptime = Record<"24h" | "7d" | "14d", number | null>;
interface HistoryRow {
service: string;
chainId: number | null; // null for waku
buckets: string; // one character per bucket, oldest first
uptime: Uptime;
}
interface History {
schemaVersion: number;
generatedAt: string;
bucketSeconds: number;
retentionDays: number;
from: string;
to: string;
rows: HistoryRow[];
}
const res = await fetch("https://api.anon.inc/api/v1/railgun/liveness/history", {
headers: { Accept: "application/json" },
signal: AbortSignal.timeout(8000),
});
if (!res.ok) throw new Error(`liveness history: HTTP ${res.status}`);
const report: History = await res.json();
if (report.schemaVersion !== 1) throw new Error("liveness history: unknown schemaVersion");
// Worst known state wins. Anything else in a bar is only shown if nothing known is.
const SEVERITY: Record<string, number> = { c: 0, k: 1, d: 2, u: 3 };
function barState(chars: string): string {
let worst = "";
for (const ch of chars) {
if (ch in SEVERITY && (worst === "" || SEVERITY[ch] > SEVERITY[worst])) worst = ch;
}
if (worst) return worst;
if (chars.includes("?")) return "?"; // unknown: no evidence
if (chars.includes(".")) return "."; // unmonitored
return "-"; // no data, or a character this client does not know
}
// Split the buckets into `bars` groups of nearly equal size, oldest first. Fewer
// buckets than bars gives one bar per bucket.
function downsample(buckets: string, bars: number): string[] {
const count = Math.min(bars, buckets.length);
const out: string[] = [];
for (let i = 0; i < count; i++) {
const start = Math.floor((i * buckets.length) / count);
const end = Math.floor(((i + 1) * buckets.length) / count);
out.push(barState(buckets.slice(start, end)));
}
return out;
}
// The server rounds to 4 decimals, so this is exact. null means "not enough data",
// which is not the same as 0% or 100%.
function formatUptime(value: number | null | undefined): string {
if (typeof value !== "number" || !(value >= 0 && value <= 1)) return "no data";
return `${(Math.round(value * 10000) / 100).toFixed(2)}%`;
}
for (const row of report.rows) {
const label = `${row.service} ${row.chainId ?? "-"}`;
console.log(label, downsample(row.buckets, 90).join(""), formatUptime(row.uptime["14d"]));
}
请按 service 和 chainId 匹配行,并忽略你用不到的行。不要依赖行的顺序。
端点
GET https://api.anon.inc/api/v1/railgun/liveness/history
- 无需身份验证,也没有参数。任何查询字符串都会被忽略。
HEAD只返回响应头。OPTIONS用于应答 CORS 预检请求。- 响应体为
application/json; charset=utf-8。 - 一次响应涵盖每条链上的每个服务。取一次,再自行筛选。
- 行与
/api/v1/railgun/liveness的实时报告相同,顺序也相同:目前共 21 行。各服务的含义见服务与链。
示例响应
一份精简的历史数据,包含五行,数值仅作示意。它只覆盖两小时,这正是端点刚开始记录时会返回的内容。真实响应会列出每一行,其 buckets 字符串会增长到 4033 个字符(见历史有多长)。
{
"schemaVersion": 1,
"generatedAt": "2026-10-06T14:03:11.402Z",
"bucketSeconds": 300,
"retentionDays": 14,
"from": "2026-10-06T12:00:00Z",
"to": "2026-10-06T14:00:00Z",
"rows": [
{
"service": "ppoi",
"chainId": 1,
"buckets": "cccccccccccccccckcccccccc",
"uptime": { "24h": 0.96, "7d": 0.96, "14d": 0.96 }
},
{
"service": "subsquid",
"chainId": 137,
"buckets": "cccccckkkkdddcccccccccccc",
"uptime": { "24h": 0.72, "7d": 0.72, "14d": 0.72 }
},
{
"service": "subsquid",
"chainId": 11155111,
"buckets": ".........................",
"uptime": { "24h": null, "7d": null, "14d": null }
},
{
"service": "broadcasters",
"chainId": 137,
"buckets": "cccccccccccccuccccccccccc",
"uptime": { "24h": 0.96, "7d": 0.96, "14d": 0.96 }
},
{
"service": "waku",
"chainId": null,
"buckets": "cccccc------ccccccccccccc",
"uptime": { "24h": 1, "7d": 1, "14d": 1 }
}
]
}
from为2026-10-06T12:00:00Z,to为2026-10-06T14:00:00Z:共 24 个间隔,因此每个buckets字符串有 25 个字符,每个时间桶一个,最早的在前。to是最后一个时间桶的起点,在generatedAt(14:03)时它仍在填充。ppoi行有一个k时间桶:在这个 5 分钟时间桶中,该服务处于catching-up状态至少一分钟,且没有更严重的状态持续那么久。其可用率为 25 个已知时间桶中的 24 个,即 0.96。- Polygon(
137)上的subsquid行有四个k时间桶,随后连续三个d时间桶,之后恢复。25 个时间桶中有 18 个是c,因此可用率为 0.72。 - Sepolia 上的
subsquid行全程为.:未被监控。已知时间桶不足 12 个,因此所有可用率都是null。 broadcasters行有一个u时间桶:在这个 5 分钟时间桶中,该服务处于unavailable状态至少一分钟。waku行有六个-时间桶:监控器当时没有运行,或没有记录任何内容。它们不计入可用率;在 19 个已知时间桶上,可用率为 1。- 这里的三个可用率数值相同,是因为历史数据只有两小时。每个窗口都会裁剪到实际存在的数据。
chainId对waku为null,它是唯一的共享行。
字段
历史数据
| 字段 | 类型 | 含义 |
|---|---|---|
schemaVersion |
整数 | 目前为 1。测试期间可能更改。 |
generatedAt |
时间戳 | 已存储的历史数据最后一次写入的时间,而不是响应被提供的时间。它大约每分钟向前推进一次。 |
bucketSeconds |
整数 | 一个时间桶的长度。目前为 300。 |
retentionDays |
整数 | 最多保留的历史长度。目前为 14。 |
from |
时间戳 | buckets[0] 的起点,对齐到 300 秒边界。 |
to |
时间戳 | 最后一个时间桶的起点,该时间桶仍在填充。 |
rows |
数组 | 每个服务和范围一个对象,顺序与实时报告相同。 |
from 和 to 为 UTC,以字面量 Z 结尾,没有小数秒:YYYY-MM-DDTHH:mm:ssZ。generatedAt 与实时报告一样,精度为毫秒。
每个 buckets 字符串的字符数为 (to − from) / bucketSeconds + 1。每一行都相同。
行
| 字段 | 类型 | 含义 |
|---|---|---|
service |
字符串 | 目前为 ppoi、indexer、subsquid、broadcasters 或 waku。以后可能增加。含义与实时报告相同。 |
chainId |
整数或 null |
正整数链 ID。目前 waku 为 null,因为它是一个共享检查。 |
buckets |
字符串 | 从 from 到 to(含)每个时间桶一个字符,最早的在前。 |
uptime |
对象 | 24h、7d 和 14d,每项为 0 到 1 的数字或 null。见可用率。 |
Sepolia(11155111)是测试网。与实时报告一样,计算任何主网数字时都应将它排除在外。
字符
| 字符 | 发布的状态 | 含义 |
|---|---|---|
c |
current |
服务已响应并已同步。 |
k |
catching-up |
服务已响应,但落后。这不是故障。 |
d |
degraded |
服务严重受损。 |
u |
unavailable |
Anon 的监控器连续三次探测都没有得到可用的回答。 |
? |
unknown |
该时间桶内没有可用的证据。 |
. |
unmonitored |
Anon 不监控此检查。 |
- |
无 | 无数据:监控器没有运行,或该时间桶内没有记录任何样本。 |
各状态的含义与实时报告相同:见状态。这个集合可能增加。对不认识的字符,仅对该时间桶按 - 处理,并保留这一行的其余部分。
请用三种彼此不同、且明显不是绿色的方式来绘制 ?、. 和 -。它们代表三件不同的事:没有证据、未被覆盖、没有记录。
时间桶与可用率的计算方式
时间桶
监控器大约每 30 秒为每一行发布一次状态。历史数据保存的正是这些已发布的状态,一个时间桶的字符来自该行在这 5 分钟内发布的状态:
- 取已知状态:
current、catching-up、degraded和unavailable,严重程度为u高于d高于k高于c。字符取在这 5 分钟内累计持续至少一分钟的最差状态。处于更严重状态的时间也计入其中:30 秒degraded加 40 秒unavailable得到d。更短的波动(例如一次重启)不计入,此时时间桶显示为c。 - 若没有已知状态,但有
unknown,字符为?。 - 若只有
unmonitored状态,字符为.。 - 若完全没有样本,字符为
-。
因此,u 表示该服务在这个时间桶中处于 unavailable 状态至少一分钟;而 c 表示没有比 current 更差的状态持续满一分钟。一个时间桶反映的是持续下来的最差状态,而不是平均值。最后一个时间桶仍在填充,所以它的字符在结束之前可能会变化。
可用率
对每个窗口,可用率为:
c / (c + k + d + u)
统计最近 24 小时、7 天或 14 天内的时间桶,并裁剪到实际存在的历史数据。结果四舍五入到 4 位小数。
- 带有
?、.或-的时间桶同时从分子和分母中去掉。它们既不算通过,也不算失败。 k会拉低可用率,d和u同样如此。正在追赶的服务不算在线。- 当一个窗口内已知时间桶少于 12 个(证据不足一小时)时,值为
null。完全未被监控的行为null。 null不是0,也不是1。请显示为“无数据”。- 值恰好为
1,表示该窗口内没有任何已知时间桶不是c。14 天内只要有一个非c的时间桶,值至多为0.9998。
你可以根据 buckets 自行计算窗口,或自行组合服务。你得到的数字在窗口边缘处可能与服务器的略有出入。
历史有多长
from 是监控器曾经记录过的第一个时间桶,或者 14 天窗口的起点(以较晚者为准)。历史数据只从该功能上线之日开始存在,没有回填。因此字符串从一个字符开始,每 5 分钟增加一个,最多到 4033 个字符,这需要最初的 14 天。之后窗口滑动:新时间桶开始时,最旧的时间桶被丢弃。
在此之前,24h、7d 和 14d 可能描述的是同一段很短的时间,历史较短时 7d 和 14d 也可能是 null。在没有说明背后有多少数据的情况下,不要把某个数字标成“14 天”。比较 from 和 to 即可知道。
局限
- 单一观测点。 它是 Anon 的监控器,运行在 Anon 的基础设施上,探测 Anon 配置使用的服务。你对这些服务的看法可能不同。时间桶并不反映你的钱包或你的网络本会看到什么。
- 它是监控器发布的内容,而不是对 Railgun 的测量。
u时间桶表示监控器对它所配置读取的服务,连续三次没有得到可用的回答。这并不证明该服务对其他人也处于宕机状态。它同样不是“可安全交易”的信号:见它不能告诉你什么。 - 历史从上线开始。 在第一个已记录的时间桶之前没有任何数据,也没有回填。历史较短只是记录少,并不代表完美。
- 无数据是监控的缺口。
-时间桶既不是故障,也不是通过。它表示这 5 分钟里监控器没有运行或没有记录任何内容,因此应显示为缺口。可用率会将其排除。 - 未知同样不是故障。
?表示监控器没有证据。可用率会将其排除。 - 只有 14 天。 更早的时间桶已经消失。如果需要更长的记录,请自行保存响应。
- 粒度较粗。 5 分钟的时间桶会掩盖更短的事件。累计不到一分钟的问题完全不会显示,而持续一分钟的问题会让整个时间桶变色。可用率同样继承这两点。
- 测试版。 与实时端点一样,它可能随时更改:见测试版状态。
新鲜度与缓存
历史数据变化缓慢:最新的时间桶会随着样本到达而变化;历史数据达到 14 天之后,窗口每 5 分钟向前移动一个时间桶。响应包含:
Cache-Control: public, max-age=60, s-maxage=120, stale-while-revalidate=60, stale-if-error=300
| 指令 | 作用 |
|---|---|
max-age=60 |
浏览器复用其副本 60 秒。 |
s-maxage=120 |
共享缓存(例如 CDN)复用其副本 2 分钟。 |
stale-while-revalidate=60 |
缓存在此之后最多还可以再提供一分钟的副本,同时去获取新副本。 |
stale-if-error=300 |
如果源站出错,缓存最多可继续提供其副本 5 分钟。 |
它可能有多旧? generatedAt 表示已存储的历史数据最后一次写入的时间。在此基础上,边缘副本到达你手中时可能已经有几分钟:最多 2 分钟的 s-maxage,加上最多一分钟的 stale-while-revalidate,浏览器中还可能再多一分钟。不要用这个端点来判断某个服务此刻是否在线。请改用实时端点,并读取其 expiresAt。
轮询不要快于每分钟一次。 更频繁的轮询只会返回缓存的副本。显示图表的页面可以在打开时获取一次,之后在页面可见时每隔几分钟再获取一次。
ETag 与 304。 ETag 是响应体的带引号哈希。监控程序大约每分钟重写一次历史数据,而 generatedAt 是响应体的一部分,因此即使没有任何时间桶变化,generatedAt 和 ETag 也大约每分钟变化一次。通常只有在同一分钟内重新验证时才会得到 304。If-None-Match 让未变化的历史数据只需一次 304:它带有 ETag、Cache-Control 和 CORS 响应头,没有响应体。响应不发送 Vary: Origin。CDN 会为每个 Origin 请求头的值各保留一份缓存副本,并忽略查询字符串。
HTTP 状态码与错误
| 状态码 | 何时出现 | 说明 |
|---|---|---|
200 |
历史数据。 | 200 表示历史数据可读,并不表示各服务健康。 |
304 |
If-None-Match 匹配。 |
没有响应体。 |
204 |
OPTIONS 预检请求。 |
见 CORS。 |
405 |
其他任何方法。 | Allow: GET, HEAD, OPTIONS。 |
503 |
历史数据不可用。 | 在第一份历史数据写入之前返回,或在已存储的历史数据超过五分钟无法读取时返回。带有 Retry-After: 30 和 Cache-Control: no-store。请带退避地重试,并继续显示你已有的最后一份有效历史数据。 |
404 |
其他任何路径。 | 由网关应答。 |
429 |
超过了合理使用中的速率限制。 | 来自 CDN(Cloudflare 错误 1015),而不是 API。持续约 60 秒,Retry-After 约为 60。 |
请根据 HTTP 状态码分支处理,绝不要根据消息文本。API 自身的错误形如 {"error": "<message>"}。网关的 404 和 CDN 的 429 使用其他响应体:429 是 Cloudflare 的 text/plain 页面。
在浏览器中,429 无法读取。 CDN 的 429 页面不带 Access-Control-Allow-Origin,浏览器会将其拦截,fetch 以 TypeError 拒绝,与任何网络或 CORS 失败相同。你的代码看不到状态码或 Retry-After。请把任何网络失败都当作 429 或 503 处理:退避,继续显示最后一次成功的历史数据,并标注其 generatedAt。
CORS
历史数据是公开的,没有针对调用者的内容,因此每个响应都允许任意来源:
| 响应头 | 值 |
|---|---|
Access-Control-Allow-Origin |
* |
Access-Control-Expose-Headers |
ETag |
没有 Access-Control-Allow-Credentials。带 Accept 头的普通 fetch 属于简单请求,不需要预检。自行设置 If-None-Match 的脚本则需要预检,端点会以 204 应答它自己的 OPTIONS 预检请求。允许的方法和请求头与实时端点相同。
合理使用
适用与实时端点相同的规则:没有密钥,也没有 SLA;遵守 Cache-Control 并使用 ETag;服务器发送描述性的 User-Agent;在该路由上每个 IP 每分钟超过约 120 次请求时会收到 429。遇到 429 和 5xx 时请退避。
此外,由于这些数据变化缓慢,轮询不要快于每分钟一次,并且最好在用户打开展示它的页面时再获取。如果你为许多用户提供服务,请在自己的服务器上获取并缓存。
测试版状态
此 API 处于测试阶段,承诺与实时端点相同,并不更强。
- 可能随时更改,恕不通知。 字段、字符、窗口和响应结构可能被添加、重命名、改变类型或删除,没有通知期,也不承诺弃用或下线流程。保留时长、时间桶长度和可用率窗口都是目前的取值。
schemaVersion可能更改。 如果它不是你认识的值,请把历史数据视为无法读取,直到你完成更新。JSON Schema 是宽松的,描述的是目前的响应,并不是承诺。- 变更会尽力记录在下面的变更日志中。
如何编写能经受这些变化的客户端:
- 忽略未知字段。 在每一层都如此:历史数据、每一行和
uptime。 - 不要依赖封闭集合。 对不认识的时间桶字符,仅对该时间桶按
-处理。忽略你用不到的service或chainId对应的行。忽略不认识的uptime键。 - 只让一行失败,不要让整个历史数据失败。 一行你无法读取(例如缺少
uptime,或buckets字符串长度不对),不应让其他行也无法显示。 - 不要写死长度。 使用
from、to和bucketSeconds。
变更日志
2026-10-07
新增。GET /api/v1/railgun/liveness/history 为实时报告的每一行返回最多 14 天的 5 分钟时间桶,以及 24 小时、7 天、14 天可用率。schemaVersion 为 1,测试版。历史数据从 2026-10-07 01:55 UTC 起记录,没有回填。