API 网关专栏 · 第 76 章

Web3 应用的 API 网关:RPC 流量、身份与滥用控制

2026年09月17日
Web3 应用的 API 网关:RPC 流量、身份与滥用控制

Web3 API 网关可以认证应用调用方、分配共享节点容量、路由 RPC 流量,并提供运维证据。但它无法证明 API 调用方拥有某个钱包,也不能验证区块链共识、让被拒绝的交易变为有效交易,或把公共 RPC 端点变成可信身份系统。

设计时真正要解决的问题是:既保护稀缺的节点和服务商容量,又不改变钱包、索引器与应用所依赖的 JSON-RPC 行为。

核心要点

  • 把应用凭据、网络身份、钱包所有权与交易签名视为不同的信任域。
  • 为低成本读取、高成本历史查询、订阅与交易提交设置明确且独立的策略。
  • 保留 JSON-RPC 响应体与 ID;HTTP 200 响应中仍可能包含 JSON-RPC 错误。
  • 对已认证 Consumer 限流,但共享计数器不可用时不能在没有说明的情况下失败开放。
  • 节点超时并不代表可以重放交易提交;应先查询交易状态并完成对账。

先明确网关负责什么

以太坊应用通常通过 JSON-RPC 与节点通信。该协议在请求层无状态,但不同方法的成本与风险差别很大。

RPC 工作负载示例网关职责节点或应用职责
当前状态读取eth_blockNumber准入、路由,以及对安全读取进行有界重试规范链视图与结果正确性
历史查询对大范围执行 eth_getLogsConsumer 预算与请求边界索引可用性与查询执行
交易提交eth_sendRawTransaction认证、准入与一次有界投递签名与交易验证、Mempool 策略
订阅通过 WebSocket 执行 eth_subscribe连接数与消息预算订阅状态与事件语义
管理 RPC客户端专用管理方法默认禁止公开暴露节点管理与授权

多数基于 HTTP 的 JSON-RPC 调用共用一个 URL 与 POST 方法,操作名位于请求体中。因此,只看路径的网关规则无法自动区分低成本读取与高成本 Trace 调用。需要按方法控制时,应使用不同的公共端点或理解 RPC 的策略组件;同时测试批量请求,避免一个 HTTP 请求在后端放大成大量工作。

分清四种身份

1sequenceDiagram
2    participant A as Web3 应用
3    participant G as API 网关
4    participant P as RPC 策略与节点池
5    participant N as 区块链节点
6    participant C as 区块链网络
7    A->>G: JSON-RPC 与应用凭据
8    G->>G: 认证 Consumer 并执行配额
9    G->>P: 获准请求与 Consumer 上下文
10    P->>P: 检查方法与网络策略
11    P->>N: 一次有界 RPC 尝试
12    N->>C: 读取状态或提交已签名交易
13    C-->>N: 网络结果
14    N-->>P: JSON-RPC 结果或错误
15    P-->>G: 保留 ID 与 RPC 语义
16    G-->>A: 响应与网关关联数据

API Key 或访问令牌标识网关中配置的 Consumer。IP 地址只有在部署的可信代理规则成立时,才能标识网络来源。钱包地址只是公开标识符。有效交易签名能够证明签名密钥持有者批准了该笔交易,却不能证明 HTTP 调用方是法律意义上的所有者、获授权员工,或获取网关凭据的同一主体。

每项授权判断都应绑定正确的身份。例如,客户套餐可以依据已认证 Consumer ID 计量;托管钱包流程则必须使用自己的钱包审批与签名控制。切勿根据 X-Forwarded-For、API Key 名称或调用方提供的地址字段推断钱包所有权。

用明确的故障行为保护共享 RPC 容量

下面是 Apache APISIX 3.19.0 的配置片段,用于说明 Schema,不是完整部署。它假设已经创建 Consumer 与 key-auth 凭据,内部 RPC 代理会执行方法与链策略。示例有意省略 Redis 高可用、上游 TLS、健康检查、CA 信任库供应与机密信息供应。

1{
2  "uri": "/ethereum",
3  "methods": ["POST"],
4  "plugins": {
5    "key-auth": {
6      "header": "apikey",
7      "hide_credentials": true
8    },
9    "limit-count": {
10      "count": 120,
11      "time_window": 60,
12      "key_type": "var",
13      "key": "consumer_name",
14      "rejected_code": 429,
15      "policy": "redis",
16      "redis_host": "redis.internal",
17      "redis_port": 6379,
18      "redis_password": "$ENV://RPC_RATE_LIMIT_REDIS_PASSWORD",
19      "redis_ssl": true,
20      "redis_ssl_verify": true,
21      "redis_server_name": "redis.internal",
22      "allow_degradation": false
23    }
24  },
25  "upstream": {
26    "type": "roundrobin",
27    "nodes": {
28      "rpc-policy.internal:8080": 1
29    }
30  }
31}

key-auth 插件的 hide_credentials 默认为 false:false 路径会把凭据留在转发请求中,true 路径则会在代理前移除凭据。本例选择 true,所以上游收到的是 APISIX Consumer 上下文,而不是可重复使用的 Key。但该上下文仍只代表已认证的 API Consumer,不代表钱包身份。

limit-count 插件的 allow_degradation 默认为 false。取值为 false 时,计数器后端故障会返回 HTTP 500;取值为 true 时,APISIX 会跳过限流并继续处理请求。本例保留失败关闭的默认值,因为未被察觉的失败开放路径可能耗尽共享 RPC 容量。应针对每类工作负载主动选择,并记录运维后果。

Redis 连接的 redis_ssl 默认为 false:false 路径不使用 TLS 发送计数器流量,true 路径则会加密连接。启用 TLS 后,redis_ssl_verify 同样默认为 false:false 路径不会认证 Redis 服务端证书,true 路径则会验证证书。启用验证时,redis_server_name 是 TLS SNI 名称,而且必须与证书匹配;它默认取 redis_host,因此当 Host 是证书未覆盖的别名时,需要显式设置。该边界通过 DNS、SNI 与已配置的 CA 信任库认证指定的 Redis 端点,并不会认证 API Consumer、钱包所有者或交易签名者。

show_limit_quota_header 默认为 true,会发送配额请求头;设为 false 只会隐藏这些请求头,不会关闭限流。无论选择哪条路径,都不会让 Consumer 提供的身份变得更可信。

保留 JSON-RPC 错误,不要把它们压成一类

EIP-1474 定义了以太坊 RPC 请求与响应,并列出了标准及以太坊专用错误码。网关需要同时观测两层错误:

  • HTTP 与传输故障,例如 401、429、超时或上游连接失败;
  • 响应体中的 JSON-RPC 错误,例如参数无效对应 -32602、交易被拒对应 -32003、超出已定义限制对应 -32005。

不要把所有 JSON-RPC 错误都改写成泛化的 HTTP 成功指标。仪表盘应解析这两层,客户端关联响应时应保留请求 id。如果网关在 RPC 服务运行前生成过载响应,需要明确它返回 HTTP 错误、JSON-RPC 错误对象,还是两者同时返回。保持一致比假装两层错误完全相同更重要。

批量请求需要独立预算。如果数组中包含许多操作,只把一个 HTTP Envelope 计为一次请求会低估节点工作量。RPC 感知层应限制元素数量、请求体总大小、允许的方法与各操作成本。

按后果路由读取与写入

至少应为以下工作负载建立明确的节点池:

  • 可由副本处理的当前状态读取;
  • 需要高成本历史状态的 Archive 或 Trace 查询;
  • 涉及重复投递与 Mempool 行为的交易提交;
  • 消耗长连接的 WebSocket 订阅;
  • 不应与公共 Listener 共用的内部管理操作。

即使是安全读取,重试也需要预算与一致性策略。区块传播或重组期间,两个健康节点可能报告不同的链头。记录选定网络、节点、Block Tag、延迟与响应类型,应用才能解释重复读取为何得到不同结果。

对于 eth_sendRawTransaction,超时意味着结果不确定:节点可能已经接收已签名交易,只是响应丢失。不要把它当作普通重试策略自动提交给多个服务商。应先推导或保留交易 Hash,查询已知节点,再根据应用的 Nonce 与替换策略对账。这些区块链语义不属于网关职责。

观测成本与滥用,同时避免记录机密信息

分别测量 HTTP 请求数与 JSON-RPC 操作数。有效维度包括已认证 Consumer、网络、方法类别、批量大小、请求与响应字节、节点池、延迟、HTTP 状态、JSON-RPC 错误码,以及缓存或重试结果。

默认不要记录原始已签名交易、认证凭据、完整钱包历史或不受限制的请求体。即使区块链数据公开,交易负载与地址活动仍可能敏感。启用负载日志前,应先定义留存、访问、脱敏与事故用途规则。

应结合行为识别滥用,而不是只看一个信号:大范围历史查询、重复失败的模拟、高 Batch 扇出、快速轮换凭据、连接抖动,以及消耗不成比例节点资源的方法组合。IP 地址可以辅助流量控制,但代理、共享网络和攻击者轮换都会使它无法成为可靠的账户身份。

Web3 网关验证清单

  • 应用身份、IP 来源、钱包地址与签名权限是否彼此分离?
  • 策略能否区分读取、历史查询、交易提交与订阅?
  • 是否限制 Batch 元素数、请求体大小与方法成本?
  • 每条路由在计数器后端故障时采用失败关闭还是失败开放,是否有明确说明?
  • HTTP 故障与 JSON-RPC 错误是否都可观测?
  • 交易超时后是否先查询状态,再决定是否重新提交?
  • 管理 RPC 方法是否与公共流量隔离?
  • 日志是否避免凭据与不必要的交易或地址数据?

常见问题

网关会验证区块链交易吗?

它可以验证粗粒度的请求结构与准入策略。区块链节点根据协议和本地 Mempool 规则验证已签名交易;交易是否纳入区块并最终确认,则由共识决定。

是否应该用钱包地址作为配额 Key?

默认不应该。调用方可以提交自己并不控制的地址。除非存在独立签名 Challenge,把调用方与该操作所用钱包绑定,否则应使用已认证 Consumer 或其他经过验证的账户身份。

读取请求可以缓存吗?

部分不可变或明确指定 Block 的读取可以缓存。相对于链头的查询与 Pending 状态会不断变化。缓存前应定义 Cache Key、Block 引用、新鲜度、重组行为与隐私边界。

后续步骤

把本方案与分布式限流一致性、IP 信任与访问名单运维以及细粒度授权结合使用。

获取方案