Skip to main content
Function Code View 是 Worker 进程内的一个组件,它把 functionId -> 源码 维护成一串不可变快照,让每个 task 在整个生命周期里都看到同一份代码。函数代码审计是挂在同一条链路上的准入门:派发前用 Python 静态审计器检查源码,不通过则拒绝这次 call。两者都不单独部署。它们在系统中的位置见 架构总览

职责与边界

负责:
  • 维护 Worker 本地的 SnapshotStore:每次代码更新发布一份完整的新快照并分配新 epoch;已发布快照不再修改。
  • 为 task 激活提供 CurrentEpoch()Pin(epoch);为 task 构造 TaskFunctionView,把 snapshot 函数和 task 自带的 inline 源码统一解析成 source-free 的 digest
  • 通过 syncer(BlockDB Online 表订阅,或 Redis 元数据 hash 轮询)把上游函数表变更发布为新快照。
  • 在 Executor compiled cache miss 时按 digest 回源,把源码交还 Executor。
  • 在派发前按 (digest, entrySelector) 查审计缓存,miss 时调用 blockx_audit daemon 做静态审计(Go 侧 internal/functioncode/audit)。
不负责:
  • 定义发布侧如何写函数表,也不要求 BlockDB 保存 epoch 或全局版本号。
  • 让 Executor 直接访问 BlockDB 或直接查询 Function Code View。
  • 保证不同 Worker 的 epoch 一致或可比较。
  • 审计规则本身。规则全部在 Python 包 python/blockx_audit/,Go 侧只做进程池、缓存和 gate。
核心不变量:
  • epoch 是 Worker 本地 opaque 字符串(bootID:seq),只在当前进程内有意义。
  • 同一个 task 在整个生命周期内固定同一个 epoch;新代码只影响新 task。
  • 快照只增不改。InstallSnapshot 只接受完整的下一份视图,逻辑内容不变时不 bump epoch
  • 单个 Worker 内不会发布顺序反转的视图:先看到函数 a 变、再看到 b 变,就不会出现”b 已新、a 仍旧”的快照。
  • digest 是 SHA-256(源码),永远在 Worker 可信入口(快照安装 / inline 归一化)本地计算;上游声明的 Digest 不进入执行路径。空源码的 digest 是 SHA-256(""),不是空字符串。
  • 审计 enforce 模式 fail-closed:违规和审计器不可用都拒绝派发。

代码位置

核心类型与接口

  • SnapshotStoreinternal/functioncode/core/snapshot_store.go):Worker 本地快照仓库。CurrentEpoch()Pin(codeEpoch)Fetch(functionID, codeEpoch)InstallSnapshot(next, publishedAt)PruneSnapshotsBefore(cutoff)
  • SnapshotView(同文件):task 可 pin 的只读句柄。
  • FunctionCode / FunctionCodeRef(同文件):前者是带源码的 payload(SourceCodeDigestVerifiedDigestSpace),VerifiedDigest 标记为 json:"-",不会从序列化输入进入。后者是 source-free 的可信身份(SourceDigestSpace),是快照对外发布的唯一形式。
  • SnapshotMeta(同文件):EpochPublishedAtSupersededAtFunctionCntSupersededAt 是回收时钟的起点。
  • TaskFunctionViewinternal/functioncode/core/task_view.go):task 级 overlay。Resolve(functionID, code) 是源码入口边界;ResolveForDispatch(functionID, expectedDigest) 返回 FunctionCodeRefentrySelector,不加载源码;SourceByDigest(digest) 只在审计 miss 或 Executor miss 时被调用。
  • SourceDigest(source) / PrepareFunctionCode(code)internal/functioncode/core/digest.go):SHA-256 派生。
  • blockdb.Syncerinternal/functioncode/adapters/blockdb/syncer.go):Bootstrap(ctx)(subscribe → scan → replay → 首个快照)、Run(ctx)(稳态 subscribe/apply/reconnect 循环)。依赖 Deps{Store, ScanClient, SubscribeClient, RowReader, InstallAudit}
  • redis.Syncer / redis.RedisSourceinternal/functioncode/adapters/redis/):Bootstrap / Run 语义同上,用 PollInterval 轮询 LoadAll
  • audit.Poolinternal/functioncode/audit/auditor.go):实现 AuditorAudit(ctx, source, entrySelector),源码优先,给 snapshot 报告和 sync-invoker 用)和 DigestAuditorAuditByDigest(ctx, digest, entrySelector, loadSource),Worker 派发用)。
  • audit.Result / audit.Finding / audit.RejectionErrorresult.gogate.go):daemon 返回的 wire 结构、单条违规、确定性拒绝错误。audit.IsRejection(err) 区分”违规”和”审计器故障”。
  • audit.SpaceAllowlistspace_allowlist.go):按 FunctionCode.Space 绕过 gate 的运维白名单;空 Space 永不命中。
  • adapters.FunctionAuditGateinternal/worker/adapters/audit_gate.go):Check(ctx, callID, digest, entrySelector, loadSource) (*uds.AuditGatePayload, error)
  • adapters.FunctionCodeViewProvider / adapters.EpochResolverinternal/worker/adapters/task_runtime.go):Orchestrator 对 Function Code View 的两个依赖接口,分别只有 PinCurrentEpoch
  • uds.AuditGatePayload / uds.ExecuteCallPayload.FunctionCodeDigestapi/uds/types.go):下发给 Executor 的 gate 与 digest。

数据流 / 执行流程

代码更新路径

BlockDB syncer 的关键行为(internal/functioncode/adapters/blockdb/syncer.go):
  • Bootstrap:先 Subscribe([system.function]) 并把事件缓冲进 subscriptionPump,再 ScanAll 得到基础视图,然后按顺序重放缓冲事件,最后 InstallSnapshot。bootstrap 建立的订阅直接交给 Run 继续消费,避免交接窗口漏事件。
  • Run:每收到一个 changeEvent,按 RowIDsBatchGetRows 读取受影响行的当前值,在上一份视图上 copy-on-write 得到下一份完整视图,再 InstallSnapshotio.EOF 和 transient 错误按 ReconnectDelay(默认 200ms)重连,并以最近一次已应用事件的 CreatedAt 作为 StartAt
  • 只读 idcode 两列(readColumns());space 列目前只是机会式读取,缺失时为空字符串,因此永远不会命中 allowlist。
  • 每次安装后调用 pruneIfNeeded()obs.RecordFunctionCodeSnapshotStore
Redis syncer(internal/functioncode/adapters/redis/)走的是同一个 InstallSnapshot 入口,只是来源换成每 PollInterval 一次 HGetAll 全量刷新。decodeFunctionValue 兼容 hash value 为裸源码或 JSON(code / sourceCode / source_code / source 任一字段)。
InstallSnapshot 只比较 FunctionCodeRef(digest + Space)。同一份源码在相邻快照之间复用已算好的 digest(cloneFunctionRefs 用上一份快照的 sourcesByDigest 反查),所以一次只改一个函数的更新只做一次 SHA-256。

查询路径

1

task 激活时 pin epoch

Orchestrator.activateAndRuninternal/worker/adapters/orchestrator_phases.go)调用 epochResolver.CurrentEpoch(),再 functionCodeViewProvider.Pin(epoch) 得到共享只读 SnapshotView,并校验 snapshotView.Epoch() == epoch。然后用它构造 TaskFunctionView 存进 taskRuntime.functionCodeView。任何一步失败都走 HandleTaskActivationFailed
2

进入 Dispatcher 前解析 digest

builder 产出 CallList 后,resolveCallDigests 对每个 call 调用 TaskFunctionView.Resolve(functionID, code):snapshot 函数做一次 Lookup,inline 函数按 *FunctionCode 指针注册一次源码。之后 call 生命周期只携带 FunctionCodeDigest,builder 的 Code 指针被清空。
3

dispatch 时取元数据并过审计 gate

处理 DispatchCallCmd 时(orchestrator_dispatch.go),resolveFunctionCodeForDispatchResolveForDispatch(functionID, expectedDigest),只拿 SpaceentrySelector,不读源码。若 gate 存在且 Space 不在 allowlist,调用 FunctionAuditGate.CheckloadSource 回调是 resolveFunctionCode(taskID, digest),只有审计缓存 miss 才会执行。拒绝时发 evCallFailederrorKindfunction_code_audit_rejected,不可重试。
4

digest 下发 Executor,miss 时回源

ExecuteCallPayload 只设置 FunctionCodeDigestEntrySelector 和可选的 Audit,不携带源码。Executor ModuleRegistry.load_callable 未命中 digest 时发 CallWaiting(waitKind=function_code);Worker 侧 handleFunctionCodeRequest 通过 SetFunctionCodeResolver 注册的 resolveFunctionCodeTaskFunctionView.SourceByDigest,用 ResumeCall(resumeKind=function_code) 回复。这条请求不进入 dispatcher 的 WAITING 状态,也不计 IO 指标。Executor 收到后重算 SHA-256 与 digest 比对,然后 _verify_audit_gate 复核 sourceDigestpass

审计调用链

  • 两个审计入口共用一个 audit.PoolnewFunctionCodeAuditinternal/worker/app/function_code.go)在 enforce / dark 模式下启动 2 个 python -u -m blockx_audit daemon。
  • 快照安装审计:InstallAudit hook 在每次新 epoch 发布后异步跑 audit.BuildReport,逐函数以 functionId 作为 entrySelector 审计并打印 function-code install audit 日志。它不影响快照内容,主要作用是预热 (digest, entrySelector) 缓存,让首个 dispatch 通常直接命中。
  • 派发审计:FunctionAuditGate.Checkaudit.CheckByDigestPool.AuditByDigest。缓存 miss 时 singleflight leader 调 loadSource,重算 digest 与可信 digest 比对,再通过 4 字节长度前缀的 JSON 帧发给 daemon(proc.go)。daemon 返回 Result{Pass, SourceDigest, EntryName, Findings}Pool 再校验 res.SourceDigest == digest
  • Python 侧 blockx_audit.audit(source, function_id):AST 节点默认拒的白名单(_ALLOWED_NODES)、free-name / import / 属性 allowlist(tables.py)、kind 追踪与能力边界(kinds.py)、以及只在 walker 无 finding 时运行的字节码 backstop(bytecode.py),合成一个 pass 位。它只 ast.parse,不执行用户代码,运行时纯 stdlib。
  • 结果对 task / call 的影响:enforce 下 RejectionError(违规)和普通 error(daemon 超时 / 崩溃)都让这次 call 以 function_code_audit_rejected 失败;dark 下只打 warn 日志并以无 gate 的 payload 照常派发;Space 命中 allowlist 时跳过审计且 Executor 在非受限 namespace 加载。
Sync Invoker 的 Precheck 复用同一个 audit.Pool 的源码优先接口 audit.Check / audit.Observe,只审计不执行,详见 Sync Invoker

状态与生命周期

  • epoch 生成:SnapshotStore.installSnapshot 在写锁下 seq++epoch = fmt.Sprintf("%s:%d", bootID, seq)bootIDnewFunctionCodeBootID() 生成为 boot-<pid>-<unixnano>
  • 保留与回收:PruneSnapshotsBefore(cutoff) 只删非当前、且 SupersededAt 早于 cutoff 的快照。计时从被取代那一刻开始,不是从发布开始,所以一个长 task pin 住刚被取代的旧 epoch 仍有完整保留窗口。两个 syncer 都在每次安装后以 now - SnapshotRetention 调用它,没有独立定时器。
  • PinSnapshotView 由 task 的 Go 引用保活;store 中删掉该 epoch 不影响已激活 task。calls phase 结束时 orchestrator_actor.gort.functionCodeViewnil,让 writer phase 不再持有历史快照。
  • 启动:setupFunctionCodeView 在 gRPC 服务启动前完成 syncer.Bootstrap;失败直接 os.Exit(1)。因此 Worker 对外可见时首个快照必然已发布。
  • 幂等:InstallSnapshot 对逻辑相同的视图返回当前 epoch 且 created=falseTaskFunctionView.RegisterInline 对同一 *FunctionCode 指针不重复哈希;同一 functionID 绑定不同源码或与 snapshot 函数撞名时该 ID 被”毒化”,后续 dispatch 确定性失败。
  • 审计模式(FunctionCodeAuditMode):off(默认,不启 daemon、无 gate)、enforce(fail-closed;daemon 池启动失败则 Worker 启动失败)、dark(同样审计并打日志但不拦截;daemon 池启动失败降级为 off 并 warn)。

配置

以下字段来自 internal/worker/app/config.goWorkerFullConfig),环境变量覆盖见 internal/worker/app/app.go 审计池的其余参数在 newFunctionCodeAudit 里写死:Size: 2Module: "blockx_audit"audit.Config.withDefaults 补齐 CallTimeout 10s、MaxCache 4096。BlockDB syncer 的 ReconnectDelay 默认 200ms,Worker 未暴露配置。
setupRedisFunctionCodeView 打印的 redis(key=... poll=...) 描述在未配置 PollIntervalMs 时写的是 120s,实际生效的是 fcredis.SyncConfig.withDefaults 的 60s。

扩展点

  • 新增一种代码来源:实现一个和 redis.Source 形状类似的加载器,写一个持有 *fccore.SnapshotStore 的 syncer,只通过 InstallSnapshot 发布完整视图;在 setupFunctionCodeView 增加分支并返回 functionCodeSetup{provider, resolver, start, close}。不要让查询路径访问外部 IO。
  • 扩大审计 allowlist:改 python/blockx_audit/tables.py(以及涉及 kind 时的 kinds.pyauditor.py),同步更新 python/blockx_executor/restricted_runtime.py 的 facade 和 python/tests/test_audit_*.py。维护表和自检清单见 blockx 仓库 docs/specs/function-code-python-whitelist.md §13。
  • 修改 daemon 协议:Go 侧 internal/functioncode/audit/proc.go 与 Python 侧 python/blockx_audit/daemon.py 必须同步(帧格式、hello 帧、Result 字段名),并更新 python/tests/test_audit_protocol.py
  • 新增 gate 语义或错误分类:改 internal/functioncode/audit/gate.goCheck / Observe / RejectionError)和 internal/worker/adapters/audit_gate.go
  • 本地开发注册测试函数:internal/worker/app/function_code.gonewDevFunctionCodeStore()MemoryFunctionCodeStore.Register

测试

  • internal/functioncode/core/*_test.go:不可变性、epoch 不回退、Pin 共享视图、pruned 后已 pin 视图仍可用、按 SupersededAt 回收、inline 与 snapshot 撞名拒绝。
  • internal/functioncode/adapters/blockdb/syncer_test.goBootstrap 的 subscribe/scan/replay、事件顺序、重连从 lastApplied 续订、重复事件不 bump。
  • internal/functioncode/adapters/redis/*_test.go:payload 解码、刷新失败日志限流、InstallAudit 触发条件;TestRedisSourceLive 需要 FUNCTION_CODE_REDIS_URL,否则 skip。
  • internal/functioncode/audit/*_test.goauditor_test.go 会拉起真实 daemon(依赖 python/.venv,缺失则 skip);gate_test.gospace_allowlist_test.goproc_robustness_test.go 覆盖 gate 语义和进程崩溃 / 超时替换。
  • internal/worker/app/function_code_test.gofunction_code_audit_test.go:来源选择、retention 解析、三种审计模式的启动行为。
  • python/tests/test_audit_*.pytest_audit_features.py(子集规则)、test_audit_tables.py / test_audit_kinds.pytest_audit_bytecode.pytest_audit_protocol.py(daemon 帧协议)、test_audit_corpus.py(v12 函数语料)。test_executor_audit_gate.py 覆盖 Executor 复核 gate。
  • E2E:cmd/worker/worker_process_function_code_test.go(miniredis 端到端)、worker_process_activation_test.goSTUB_EPOCH_FAIL 触发激活失败)、worker_process_localtestservice_test.goFUNCTION_CODE_AUDIT_MODE=enforce 下走 gate)。整体测试组织见 测试

相关文档

blockx 仓库中的 spec:
  • docs/specs/function-code-view.md:快照、epoch、保留回收与查询链路的详细设计。
  • docs/specs/function-code-audit-design.md:审计机制、两道门、错误码、Executor 受限运行时。
  • docs/specs/function-code-python-whitelist.md:完整 allowlist 清单,逐表对齐 blockx_audit
  • docs/specs/2026-07-23-execute-call-function-code-reference.mdExecuteCall 只传 digest、miss 时回源的协议决策。
  • docs/specs/architecture.md §4.2.4.4:Function Code View 在总体架构中的定位。
  • docs/specs/sync-invoker.md §3.1:Precheck 与审计模式的关系。
站内相关页面: