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再等待 CoprocessorCopro/ 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含size、drain_rate_tps、position字段定义于 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.rspub struct DecryptQueueInfo { /// Readiness queue info (concurrency-limited) pub readiness: ReadinessQueueInfo, /// TX queue info (TPS-limited) pub tx: TxQueueInfo, }三、配置参数从设计表到真实 YAML3.1 核心参数参数含义默认值min_seconds最小轮询间隔下限/floor1max_seconds最大轮询间隔上限/ceiling300safety_margin作用于计算所得 ETA 的乘数0.01.00.2safety_margin的实际作用方式是final_eta computed_eta × (1 safety_margin)即 0.2 表示在估算值上额外预留 20% 的缓冲。3.2 名义处理时间Nominal Processing Times所有名义处理时间必须在配置中显式给出配置必填代码中无默认值——源码中RetryAfterConfig的所有字段均为必填且validate()会强制检查请求类型由谁处理配置字段Input ProofCoproinput_proof_processing_secondsUser DecryptKMSuser_decrypt_processing_secondsPublic DecryptKMSpublic_decrypt_processing_secondsReadiness Check仅解密—readiness_check_secondsTX 确认区块链tx_confirmation_ms3.3 Copro/KMS Backoff 区间仅用于ReceiptReceived状态由于 Copro/KMS 的响应时间本质上不可预测设计上对ReceiptReceived状态采用按已等待时长分级退避的固定间隔表已等待时间Retry-After理由0-60s4s预期很快返回60s-2m10s比平时慢2m-5m30s明显延迟5m-15m60s重大延迟15m300s可能卡住最小化轮询频率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 cant 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强制三类约束任一不满足都会导致启动失败min_seconds必须严格小于max_secondssafety_margin必须落在闭区间[0.0, 1.0]copro_kms_backoff_intervals必须按elapsed_threshold_secs严格递增排序且不允许重复阈值。对应的单元测试同文件的tests模块覆盖了 min≥max、margin 越界负值与大于 1、区间乱序、重复阈值、空区间等场景例如test_validate_unsorted_backoff_intervals与test_validate_duplicate_thresholds。四、公式变量定义变量含义p请求在队列中的位置0 起始QTX 队列大小用于新请求排到队尾的情形DTX 排空速率tpsCReadiness 最大并发数R名义就绪检查时间msP名义处理时间msInput Proof 约 2sDecrypt 约 4s实际以配置值为准T名义 TX 确认时间msM安全边际如 0.2E在当前状态已流逝的时间msB(E)基于已流逝时间的退避函数在源码中这些变量的载体是RetryAfterStaterelayer/src/http/retry_after/state.rs它把配置中的秒全部转换为毫秒存储pub struct RetryAfterState { min_seconds: RwLocku32, max_seconds: RwLocku32, safety_margin: RwLockf32, nominal_readiness_ms: RwLocku32, nominal_input_proof_ms: RwLocku32, nominal_user_decrypt_ms: RwLocku32, nominal_public_decrypt_ms: RwLocku32, nominal_tx_ms: RwLocku32, copro_kms_backoff_intervals: RwLockVecBackoffInterval, }每个字段都用RwLock包裹为后续管理端点的运行时热更新预留了入口见第五节。五、ETA 计算公式5.1 Input Proof状态公式Queuedclamp(⌈(p/D P T) × (1M) / 1000⌉, min, max)Processingclamp(⌈(p/D P T) × (1M) / 1000⌉, min, max)TxInFlightclamp(⌈P × (1M) / 1000⌉, min, max)ReceiptReceivedB(E)Completed/TimedOut/Failure05.2 DecryptUser 与 Public状态队列位置公式Queued在就绪队列中clamp(⌈(p/C Q/D P T) × (1M) / 1000⌉, min, max)Processing已出就绪队列、尚未进入 TX 队列clamp(⌈(R Q/D P T) × (1M) / 1000⌉, min, max)Processing在 TX 队列中clamp(⌈(p/D P T) × (1M) / 1000⌉, min, max)TxInFlight—clamp(⌈P × (1M) / 1000⌉, min, max)ReceiptReceived—B(E)Completed/TimedOut/Failure—05.3 关键要点含源码印证Queued 用请求真实位置p而非队列总大小轮询一个已入队多时的请求时若用队列总大小会高估等待时间——排在位置 5 的请求与刚入队排在位置 100 的请求不应拿到相同 ETA。Processing 状态需判定请求当前在哪条队列。源码用get_decrypt_stage()relayer/src/http/retry_after/state.rs判定DecryptStagereadiness.position为Some(p)→InReadinessQueue套用p/C公式tx.position为Some(p)→InTxQueue套用p/D公式两者均为None→ProcessingReadiness正在做就绪检查套用R Q/D公式。TxInFlight 只算处理时间P交易已发出接下来只剩等待 Copro/KMS 返回。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当position为None新请求排队尾时回退使用size。若drain_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。六、示例计算完整推演以下沿用设计文档的示例参数D10, C50, R2s, P_input2s, P_decrypt4s, T100ms, M0.2, B(E)3s。6.1 Input ProofP 2000msQueued / Processing⌈(p/D × 1000 P T) × (1M) / 1000⌉pp/D (s) P T (ms)× 1.2结果00210025203s10.1220026403s101310037204s10010121001452015s1000100102100122520123sTxInFlight⌈P × 1.2 / 1000⌉⌈2000 × 1.2 / 1000⌉3s恒定值ReceiptReceivedB(E)3s恒定值示例中退避函数恒取 3s6.2 DecryptP 4000msQueued在就绪队列⌈(p/C × 1000 Q/D × 1000 P T) × (1M) / 1000⌉设 p Q两条队列条目数相同pp/C (s)Q/D (s) P T (ms)× 1.2结果000410049205s10.020.1422050646s100.21530063607s100210161001932020s100020100124100148920149sProcessing已出就绪队列、未进 TX 队列⌈(R Q/D × 1000 P T) × (1M) / 1000⌉QR (ms)Q/D (s) P T (ms)× 1.2结果020000610073208s120000.1620074408s1020001710085209s100200010161001932020s10002000100106100127320128sProcessing在 TX 队列⌈(p/D × 1000 P T) × (1M) / 1000⌉pp/D (s) P T (ms)× 1.2结果00410049205s10.1420050406s101510061207s10010141001692017s1000100104100124920125sTxInFlight⌈P × 1.2 / 1000⌉⌈4000 × 1.2 / 1000⌉5s恒定值ReceiptReceivedB(E)3s恒定值6.3 汇总表p100, Q100状态Input ProofDecryptQueued15s20sProcessing就绪队列中—20sProcessingTX 队列中15s17sTxInFlight3s5sReceiptReceived3s3s6.4 源码测试对公式的印证relayer/src/http/retry_after/state.rs 的测试模块中test_compute_for_input_proof_post直接验证了公式size100, drain_rate_tps20时队列等待 5000ms加上processing2000ms, tx250ms得raw_eta7250ms乘 1.2 后ceil(8700/1000)9s。此外test_eta_clamped_to_min、test_eta_clamped_to_max、test_compute_copro_kms_backoff验证 0s→4、60s→10、120s→30 的区间映射分别覆盖了上下限钳制与退避表逻辑。七、HTTP 响应格式7.1 POST 响应202 AcceptedHTTP/1.1 202 Accepted Retry-After: 27 {status: queued, job_id: ..., eta_seconds: 27}7.2 GET 轮询响应202 In ProgressHTTP/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_seconds、retry_after_max_seconds、retry_after_safety_margin、nominal_readiness_check_seconds、nominal_input_proof_processing_seconds、nominal_user_decrypt_processing_seconds、nominal_public_decrypt_processing_seconds、nominal_tx_confirmation_ms等参数并写入RetryAfterState对应的 setter如set_min_seconds、set_safety_margin、set_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明确断言了这一点size10_000而positionSome(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.rsETA 计算核心实现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),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →