跳至正文
Anon Wallet

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 分钟内发布的状态:

  1. 取已知状态:current、catching-up、degraded 和 unavailable,严重程度为 u 高于 d 高于 k 高于 c。字符取在这 5 分钟内累计持续至少一分钟的最差状态。处于更严重状态的时间也计入其中:30 秒 degraded 加 40 秒 unavailable 得到 d。更短的波动(例如一次重启)不计入,此时时间桶显示为 c。
  2. 若没有已知状态,但有 unknown,字符为 ?。
  3. 若只有 unmonitored 状态,字符为 .。
  4. 若完全没有样本,字符为 -。

因此,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 起记录,没有回填。