news 2026/9/12 16:49:39

fhEVM Relayer 动态 Retry-After 设计解析:基于队列状态与处理阶段的智能轮询间隔计算

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
fhEVM Relayer 动态 Retry-After 设计解析:基于队列状态与处理阶段的智能轮询间隔计算

fhEVM Relayer 动态 Retry-After 设计解析:基于队列状态与处理阶段的智能轮询间隔计算

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

本文基于 fhEVM 开源仓库中的 动态 Retry-After 设计文档,结合 relayer 的 Rust 源码实现,系统讲解 fhEVM relayer 如何根据队列状态、排空速率(drain rate)与请求处理阶段,为客户端动态计算Retry-After轮询间隔。读完本文,你将掌握单队列(Input Proof)与双队列(User/Public Decrypt)的 ETA 计算模型、全部配置参数的含义与校验规则、各类请求状态下的精确计算公式,以及如何通过管理端点进行运行时热更新。

一、设计目标:让轮询间隔"跟着系统负载走"

在 fhEVM 的架构中,relayer 扮演"网关适配层"的角色:客户端(SDK / dApp)向 relayer 提交 Input Proof(输入证明)、User Decrypt(用户解密)与 Public Decrypt(公共解密)等异步请求,relayer 在后台排队、执行就绪检查、构造交易并发送至 gateway,再等待 Coprocessor(Copro)/ KMS 的返回。

如果 relayer 一律返回固定轮询间隔(例如恒定的 4 秒),会出现两个问题:系统空闲时客户端空转浪费请求;系统高负载时(队列堆积上千条)客户端按过短间隔轮询会进一步放大压力。动态Retry-After的目标正是根据"队列有多长、每秒能消化多少、请求现在处于哪个处理阶段"这三个要素,为每个客户端计算出贴近真实剩余等待时间的轮询间隔,实现负载自适应。

该设计的核心计算逻辑实现在 relayer/src/http/retry_after/state.rs 与 relayer/src/http/retry_after/queue_info.rs,配置结构定义在 relayer/src/config/retry_after.rs。

二、队列架构:单队列与双队列

relayer 按请求类型维护不同的队列结构:

Input Proof(单队列)

[HTTP] → [TX Throttler Queue] → [Gateway TX] ↑ TPS-based drain (per_seconds)

User Decrypt / Public Decrypt(双队列)

[HTTP] → [Readiness Queue] → [Readiness Check] → [TX Throttler Queue] → [Gateway TX] ↑ ↑ Concurrency-based drain TPS-based drain (max_concurrency) (per_seconds)

两类队列的关键差异:

  • TX Throttler(交易节流队列):基于 TPS(每秒令牌数)限速排空,使用 governor 实现。对应源码中的TxQueueInfo(含sizedrain_rate_tpsposition字段,定义于 relayer/src/gateway/arbitrum/transaction/tx_throttler.rs)。
  • Readiness Queue(就绪队列,仅解密请求):基于信号量做并发限制,最多同时执行max_concurrency个并行任务。对应源码中的ReadinessQueueInfo,定义于 relayer/src/readiness/throttler.rs。

解密请求之所以要过两道队列,是因为它要先做"密文是否就绪"的就绪检查(并发受限、单次耗时较短),再进入交易发送队列(TPS 受限)。两类队列的等待时间模型不同,这直接决定了 ETA 公式的分段结构。两个队列的信息被组合进DecryptQueueInfo(见 relayer/src/http/retry_after/queue_info.rs):

pub struct DecryptQueueInfo { /// Readiness queue info (concurrency-limited) pub readiness: ReadinessQueueInfo, /// TX queue info (TPS-limited) pub tx: TxQueueInfo, }

三、配置参数:从设计表到真实 YAML

3.1 核心参数

参数含义默认值
min_seconds最小轮询间隔(下限/floor)1
max_seconds最大轮询间隔(上限/ceiling)300
safety_margin作用于计算所得 ETA 的乘数(0.0~1.0)0.2

safety_margin的实际作用方式是final_eta = computed_eta × (1 + safety_margin),即 0.2 表示在估算值上额外预留 20% 的缓冲。

3.2 名义处理时间(Nominal Processing Times)

所有名义处理时间必须在配置中显式给出(配置必填,代码中无默认值)——源码中RetryAfterConfig的所有字段均为必填,且validate()会强制检查:

请求类型由谁处理配置字段
Input ProofCoproinput_proof_processing_seconds
User DecryptKMSuser_decrypt_processing_seconds
Public DecryptKMSpublic_decrypt_processing_seconds
Readiness Check(仅解密)readiness_check_seconds
TX 确认区块链tx_confirmation_ms

3.3 Copro/KMS Backoff 区间(仅用于ReceiptReceived状态)

由于 Copro/KMS 的响应时间本质上不可预测,设计上对ReceiptReceived状态采用"按已等待时长分级退避"的固定间隔表:

已等待时间Retry-After理由
0-60s4s预期很快返回
60s-2m10s比平时慢
2m-5m30s明显延迟
5m-15m60s重大延迟
15m+300s可能卡住,最小化轮询频率

3.4 真实配置文件示例

仓库中的实际配置示例 relayer/config/local.yaml.example(第 180-221 行)给出了完整的http.retry_after配置块:

http: endpoint: "0.0.0.0:3000" api_retry_after_seconds: 4 # Default retry-after seconds for queued API responses # Dynamic retry-after configuration for V2 handlers # Computes Retry-After based on queue size, drain rate, and processing stage retry_after: # Minimum retry interval in seconds (floor) min_seconds: 1 # Maximum retry interval in seconds (ceiling/cap) # Use this to limit the maximum retry-after value (e.g., 60s or 120s for tighter bounds) max_seconds: 300 # Safety margin: multiplier applied to computed ETA # Formula: final_eta = computed_eta * (1 + safety_margin) # Range: 0.0 to 1.0 (0% to 100% buffer) # Example: 0.2 = 20% buffer, so 10s ETA becomes 12s safety_margin: 0.2 # Nominal processing times per stage (used for ETA computation) # These are admin-updatable at runtime via /admin/config # All fields are required - no defaults in code nominal_times: # Expected time for readiness check (user/public decrypt only) readiness_check_seconds: 4 # Expected processing time for input proof requests input_proof_processing_seconds: 2 # Expected processing time for user decrypt requests user_decrypt_processing_seconds: 6 # Expected processing time for public decrypt requests public_decrypt_processing_seconds: 6 # Expected time for blockchain TX confirmation (in milliseconds) tx_confirmation_ms: 250 # Backoff intervals for ReceiptReceived state ONLY # This is the only state where we can't compute a dynamic ETA because # Copro/KMS response time is unpredictable. # Format: [elapsed_threshold_seconds, retry_interval_seconds] # As time in ReceiptReceived increases, we back off polling frequency. # NOTE: Safety margin is NOT applied to backoff intervals copro_kms_backoff_intervals: - [0, 4] # 0-60s: retry every 4s (expect response soon) - [60, 10] # 60s-2m: retry every 10s - [120, 30] # 2-5m: retry every 30s - [300, 60] # 5-15m: retry every 60s - [900, 300] # 15m+: retry every 5m (likely stuck)

注意 backoff 区间在 YAML 中采用[elapsed_threshold_seconds, retry_interval_seconds]二元组数组的紧凑写法,源码通过deserialize_vec_from_map_or_seq同时兼容"序列形式"与"map 形式"的反序列化(见 relayer/src/config/retry_after.rs 与 relayer/src/config/settings.rs)。

3.5 配置校验规则(源码级)

RetryAfterConfig::validate()(relayer/src/config/retry_after.rs)强制三类约束,任一不满足都会导致启动失败:

  1. min_seconds必须严格小于max_seconds
  2. safety_margin必须落在闭区间[0.0, 1.0]
  3. copro_kms_backoff_intervals必须按elapsed_threshold_secs严格递增排序,且不允许重复阈值。

对应的单元测试(同文件的tests模块)覆盖了 min≥max、margin 越界(负值与大于 1)、区间乱序、重复阈值、空区间等场景,例如test_validate_unsorted_backoff_intervalstest_validate_duplicate_thresholds

四、公式变量定义

变量含义
p请求在队列中的位置(0 起始)
QTX 队列大小(用于新请求排到队尾的情形)
DTX 排空速率(tps)
CReadiness 最大并发数
R名义就绪检查时间(ms)
P名义处理时间(ms),Input Proof 约 2s,Decrypt 约 4s(实际以配置值为准)
T名义 TX 确认时间(ms)
M安全边际(如 0.2)
E在当前状态已流逝的时间(ms)
B(E)基于已流逝时间的退避函数

在源码中,这些变量的载体是RetryAfterState(relayer/src/http/retry_after/state.rs),它把配置中的"秒"全部转换为毫秒存储:

pub struct RetryAfterState { min_seconds: RwLock<u32>, max_seconds: RwLock<u32>, safety_margin: RwLock<f32>, nominal_readiness_ms: RwLock<u32>, nominal_input_proof_ms: RwLock<u32>, nominal_user_decrypt_ms: RwLock<u32>, nominal_public_decrypt_ms: RwLock<u32>, nominal_tx_ms: RwLock<u32>, copro_kms_backoff_intervals: RwLock<Vec<BackoffInterval>>, }

每个字段都用RwLock包裹,为后续管理端点的运行时热更新预留了入口(见第五节)。

五、ETA 计算公式

5.1 Input Proof

状态公式
Queuedclamp(⌈(p/D + P + T) × (1+M) / 1000⌉, min, max)
Processingclamp(⌈(p/D + P + T) × (1+M) / 1000⌉, min, max)
TxInFlightclamp(⌈P × (1+M) / 1000⌉, min, max)
ReceiptReceivedB(E)
Completed/TimedOut/Failure0

5.2 Decrypt(User 与 Public)

状态队列位置公式
Queued在就绪队列中clamp(⌈(p/C + Q/D + P + T) × (1+M) / 1000⌉, min, max)
Processing已出就绪队列、尚未进入 TX 队列clamp(⌈(R + Q/D + P + T) × (1+M) / 1000⌉, min, max)
Processing在 TX 队列中clamp(⌈(p/D + P + T) × (1+M) / 1000⌉, min, max)
TxInFlightclamp(⌈P × (1+M) / 1000⌉, min, max)
ReceiptReceivedB(E)
Completed/TimedOut/Failure0

5.3 关键要点(含源码印证)

  1. Queued 用请求真实位置p,而非队列总大小:轮询一个已入队多时的请求时,若用队列总大小会高估等待时间——排在位置 5 的请求与刚入队排在位置 100 的请求不应拿到相同 ETA。
  2. Processing 状态需判定请求当前在哪条队列。源码用get_decrypt_stage()(relayer/src/http/retry_after/state.rs)判定DecryptStage
    • readiness.positionSome(p)InReadinessQueue,套用p/C公式;
    • tx.positionSome(p)InTxQueue,套用p/D公式;
    • 两者均为NoneProcessingReadiness(正在做就绪检查),套用R + Q/D公式。
  3. TxInFlight 只算处理时间P:交易已发出,接下来只剩等待 Copro/KMS 返回。
  4. ReceiptReceived 使用退避函数B(E):Copro/KMS 响应时间不可预测。

5.4 源码中的实现细节(与文档公式的精确对应)

  • TX 队列等待时间compute_tx_queue_wait_ms()(relayer/src/http/retry_after/state.rs)实现为position / drain_rate_tps × 1000,取整后向上取ceil;当positionNone(新请求排队尾)时回退使用sizedrain_rate_tps为 0,返回兜底值300_000ms(即 300 秒),避免除零。
  • 就绪队列等待时间compute_readiness_queue_wait_ms()(同文件第 411-432 行)实现为ceil(position / max_concurrency) × nominal_readiness_ms——即先算出需要等待几个"并发批次",再乘以单批就绪检查时间。注意:设计文档表格中 Queued 行简写为p/C + Q/D,实际源码实现是"批次数量 × 名义就绪检查时间 + TX 队列等待",即ceil(p/C) × R + Q/D × 1000,后者是更精确的建模。
  • 最终换算to_retry_after_secs()(同文件第 379-382 行)先对raw_eta_ms × (1 + margin)做毫秒级向上取整(apply_safety_margin_ms,第 366-375 行),再div_ceil(1000)换算为秒并clamp(min, max)
  • 退避函数compute_copro_kms_backoff()(第 339-358 行)遍历区间表,取elapsed_secs >= threshold的最后一个区间值,最终同样clamp(min, max);空表时回退到min_seconds

六、示例计算(完整推演)

以下沿用设计文档的示例参数:D=10, C=50, R=2s, P_input=2s, P_decrypt=4s, T=100ms, M=0.2, B(E)=3s

6.1 Input Proof(P = 2000ms)

Queued / Processing:⌈(p/D × 1000 + P + T) × (1+M) / 1000⌉

pp/D (s)+ P + T (ms)× 1.2结果
00210025203s
10.1220026403s
101310037204s
10010121001452015s
1000100102100122520123s

TxInFlight:⌈P × 1.2 / 1000⌉=⌈2000 × 1.2 / 1000⌉=3s(恒定值)

ReceiptReceived:B(E)=3s(恒定值,示例中退避函数恒取 3s)

6.2 Decrypt(P = 4000ms)

Queued(在就绪队列):⌈(p/C × 1000 + Q/D × 1000 + P + T) × (1+M) / 1000⌉,设 p = Q(两条队列条目数相同):

pp/C (s)Q/D (s)+ P + T (ms)× 1.2结果
000410049205s
10.020.1422050646s
100.21530063607s
100210161001932020s
100020100124100148920149s

Processing(已出就绪队列、未进 TX 队列):⌈(R + Q/D × 1000 + P + T) × (1+M) / 1000⌉

QR (ms)Q/D (s)+ P + T (ms)× 1.2结果
020000610073208s
120000.1620074408s
1020001710085209s
100200010161001932020s
10002000100106100127320128s

Processing(在 TX 队列):⌈(p/D × 1000 + P + T) × (1+M) / 1000⌉

pp/D (s)+ P + T (ms)× 1.2结果
00410049205s
10.1420050406s
101510061207s
10010141001692017s
1000100104100124920125s

TxInFlight:⌈P × 1.2 / 1000⌉=⌈4000 × 1.2 / 1000⌉=5s(恒定值)

ReceiptReceived:B(E)=3s(恒定值)

6.3 汇总表(p=100, Q=100)

状态Input ProofDecrypt
Queued15s20s
Processing(就绪队列中)20s
Processing(TX 队列中)15s17s
TxInFlight3s5s
ReceiptReceived3s3s

6.4 源码测试对公式的印证

relayer/src/http/retry_after/state.rs 的测试模块中test_compute_for_input_proof_post直接验证了公式:size=100, drain_rate_tps=20时队列等待 5000ms,加上processing=2000ms, tx=250msraw_eta=7250ms,乘 1.2 后ceil(8700/1000)=9s。此外test_eta_clamped_to_mintest_eta_clamped_to_maxtest_compute_copro_kms_backoff(验证 0s→4、60s→10、120s→30 的区间映射)分别覆盖了上下限钳制与退避表逻辑。

七、HTTP 响应格式

7.1 POST 响应(202 Accepted)

HTTP/1.1 202 Accepted Retry-After: 27 {"status": "queued", "job_id": "...", "eta_seconds": 27}

7.2 GET 轮询响应(202 In Progress)

HTTP/1.1 202 Accepted Retry-After: 10 {"status": "queued", "state": "tx_in_flight", "eta_seconds": 10, "elapsed_seconds": 15}

源码侧的实现位置:

  • V2 的 Input Proof 处理端点 relayer/src/http/endpoints/v2/handlers/input_proof.rs 在 POST 入队时调用compute_for_input_proof_post计算并设置Retry-After头(第 298-338 行),GET 轮询时调用compute_for_input_proof_get(第 518 行附近),OpenAPI 注释明确 202 语义为 "Still processing. Poll again after Retry-After."。
  • 响应头的统一装配逻辑位于 relayer/src/http/utils/responses.rs(第 469-471 行附近):将计算出的秒数写入Retry-After头;同时 relayer/src/http/endpoints/v2/types/error.rs 中定义V2StatusQueued响应体,携带eta_seconds字段。
  • 每次 POST 还会调用compute_raw_eta_ms_for_input_proof/compute_raw_eta_ms_for_decrypt输出"未加安全边际、未钳制"的原始 ETA,用于retry_after_raw_eta_histogram_bucket直方图监控(指标端点见 relayer/src/metrics/retry_after.rs,桶定义见 relayer/config/local.yaml.example 第 231 行)。

八、运行时热更新:Admin 配置端点

所有参数都支持通过管理端点运行时更新,无需重启进程:

  • 各类请求的名义处理时间
  • TX throttler 的 TPS(排空速率)
  • Retry-After 上下限(min/max)
  • 安全边际
  • Copro/KMS 退避区间

实现层面:

  • relayer/src/http/admin/handlers.rs 的update_config(第 112 行起)接收{ "param": "...", "value": ... }请求体,其中is_retry_after_param()(relayer/src/http/admin/config_param.rs)负责识别retry_after_min_secondsretry_after_max_secondsretry_after_safety_marginnominal_readiness_check_secondsnominal_input_proof_processing_secondsnominal_user_decrypt_processing_secondsnominal_public_decrypt_processing_secondsnominal_tx_confirmation_ms等参数,并写入RetryAfterState对应的 setter(如set_min_secondsset_safety_marginset_backoff_intervals,见 relayer/src/http/retry_after/state.rs)。
  • get_config(第 372 行起)返回当前生效的全部参数,便于运维核对。
  • 需要说明的是,http.enable_admin_endpoint默认关闭(见 relayer/config/local.yaml.example 第 178 行),配置注释明确提示生产环境应由 Kong 等网关负责认证与限流。

RetryAfterState之所以用RwLock包裹每个字段而不是整体一个锁,正是为了支持"单参数热更新"——更新safety_margin不会阻塞正在读取其他参数的并发请求。

九、设计决策与取舍

9.1 为什么 ReceiptReceived 用固定退避(不乘安全边际)

  • Copro/KMS 的响应时间从根本上不可预测,套用排队模型没有意义;
  • 退避区间本身已按保守原则设计(4s→10s→30s→60s→300s 逐级放大);
  • 额外叠加安全边际只会无谓地拉长轮询间隔,增加客户端感知延迟。

9.2 为什么内部一律用毫秒

  • 所有内部计算以毫秒为单位,避免秒级取整引入的累积舍入误差;
  • 仅在最终设置Retry-After响应头时才换算为秒(div_ceil(1000)向上取整,确保告知客户端的值永远"足够宽裕")。

9.3 为什么基于位置(position)而非队列大小(size)

  • 对轮询已有Queued请求的 GET 而言,用队列总大小是错的:已在队内等到位置 5 的请求,ETA 应远短于刚入队排在第 100 的请求;
  • 源码中compute_tx_queue_wait_ms/compute_readiness_queue_wait_ms均遵循"position 优先,size 仅作为新请求排队的回退"原则,测试test_position_overrides_size明确断言了这一点(size=10_000position=Some(20)时等待按 20 计算);
  • 此外get_position(id)还能让 ETA 随请求在队列中前进而动态收敛,提升估算精度。

9.4 多实例一致性(从测试推导的工程考量)

relayer/src/http/retry_after/state.rs 中test_get_eta_is_pod_independent的注释揭示了一个值得注意的工程细节:不持有 dispatcher 锁的被动 Pod 内存中的节流队列为空,若直接用内存size: 0计算,会把"600 条积压"误判成"空闲队列",导致 ETA 被钳制到min_seconds,而持有锁的 Pod 却给出真实估算,造成两个实例对同一请求返回不一致的轮询间隔。该设计通过从数据库req_status行集读取一致的队列深度来解决这一问题(Input Proof 端点中insert_result.tx_queue_size即来自 INSERT 返回值,见 relayer/src/http/endpoints/v2/handlers/input_proof.rs 第 291-297 行)。从源码结构看,这一"共享队列深度 + 位置优先"的建模同时服务了单实例准确性与多实例一致性两个目标。

十、延伸阅读

  • 设计文档原文:relayer/docs/dynamic-retry-after-design.md
  • 配置结构体与校验:relayer/src/config/retry_after.rs
  • ETA 计算核心实现:relayer/src/http/retry_after/state.rs
  • 队列信息聚合类型:relayer/src/http/retry_after/queue_info.rs
  • 完整配置示例:relayer/config/local.yaml.example、relayer/config/local.testnet.yaml.example、relayer/config/local.mainnet.yaml.example
  • 管理端点实现:relayer/src/http/admin/handlers.rs、relayer/src/http/admin/config_param.rs
  • 监控指标:relayer/src/metrics/retry_after.rs、relayer/src/metrics/docs_and_dashboards/status_metrics.md
  • 相关运行文档:relayer/docs/http-api-design.md、relayer/docs/idempotency-audit.md

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 16:49:37

LunaTranslator:10分钟给日文视觉小说挂上中文字幕

LunaTranslator&#xff1a;10分钟给日文视觉小说挂上中文字幕 【免费下载链接】LunaTranslator 视觉小说翻译器 / Visual Novel Translator 项目地址: https://gitcode.com/GitHub_Trending/lu/LunaTranslator 刚开一款日文视觉小说&#xff0c;对话框里假名汉字混杂&a…

作者头像 李华
网站建设 2026/9/12 16:48:53

kkFileView文件在线预览实操:多份PDF合并一页查看

kkFileView文件在线预览实操&#xff1a;多份PDF合并一页查看 【免费下载链接】kkFileView Universal File Online Preview Project based on Spring-Boot 项目地址: https://gitcode.com/GitHub_Trending/kk/kkFileView 审一套招投标资料时&#xff0c;主文件、报价单和…

作者头像 李华
网站建设 2026/9/12 16:43:55

Mindustry 安装与快速部署指南:源码到本地运行的 4 步操作

Mindustry 安装与快速部署指南&#xff1a;源码到本地运行的 4 步操作 【免费下载链接】Mindustry The automation tower defense RTS 项目地址: https://gitcode.com/GitHub_Trending/min/Mindustry Mindustry 是一款用 Java 编写的塔防策略游戏&#xff0c;把自动化产…

作者头像 李华
网站建设 2026/9/12 16:41:17

Harbor 评测任务失败时如何区分基础设施故障与模型能力问题?

Harbor 评测任务失败时如何区分基础设施故障与模型能力问题&#xff1f; 【免费下载链接】deepagents The batteries-included agent harness. 项目地址: https://gitcode.com/GitHub_Trending/de/deepagents 当你在 Terminal Bench 之类的 Harbor 沙箱基准上跑 Deep Ag…

作者头像 李华