Chainlink Local CRE 拓扑实战:workflow-gateway-don-cache-test 的 DON 布局与模块缓存验证
【免费下载链接】chainlinknode of the decentralized oracle network, bridging on and off-chain computation项目地址: https://gitcode.com/GitHub_Trending/ch/chainlink
本文聚焦 Chainlink 仓库中 Local CRE(Chainlink Runtime Environment)开发环境的workflow-gateway-don-cache-test拓扑:它如何通过单 DON 集群(single-don)+ Docker 基础设施承载 bootstrap-gateway 与 workflow 两类节点,如何将七类能力(capability)本地化部署到 workflow DON,并借助[Capabilities.WorkflowRegistry.ModuleCache]配置验证 WASM 模块缓存(Module Cache)的加载、闲置驱逐与命中行为。读完本文,你将掌握该拓扑的 TOML 配置含义、能力矩阵的生成规则、如何启动并运行对应的缓存冒烟测试,以及模块缓存底层的 LRU + 磁盘两级实现原理。
拓扑概览:一份由工具自动生成的标准参考文档
workflow-gateway-don-cache-test.md位于 core/scripts/cre/environment/docs/topologies/workflow-gateway-don-cache-test.md,它并非手写文档,而是由 Local CRE CLI 的go run . topology generate命令根据拓扑 TOML 自动渲染生成。其头部的三条元信息即是对这份拓扑最直接的定性:
| 元信息 | 值 | 含义 |
|---|---|---|
| Config | configs/workflow-gateway-don-cache-test.toml | 定义该拓扑的 TOML 文件,位于 core/scripts/cre/environment/configs/workflow-gateway-don-cache-test.toml |
| Class | single-don | 拓扑类别为"单 DON"——所有能力均本地化放置在 workflow DON 上,不涉及独立 capabilities DON 或多网关路由 |
| Infra | docker | 基础设施类型为 Docker 容器编排 |
从生成器源码 environment/topology.go 可以看到,topology generate会扫描configs/目录下所有满足条件的 TOML(同时具备blockchains、nodesets、jd、infra四类键),逐份生成docs/topologies/<name>.md与索引文件docs/TOPOLOGIES.md(见 TOPOLOGIES.md 的索引表),并支持--check模式校验文档是否过期。这意味着:要理解任何拓扑文档,真正的"源"是它对应的 TOML 配置文件,文档只是把配置中 DON 布局与能力放置关系"投影"为人类可读形式。
能力矩阵(Capability Matrix):DON 能力放置的事实来源
文档在## Capability Matrix一节强调"This matrix is the source of truth for capability placement by DON."——即能力矩阵是能力放置的权威依据。完整矩阵如下:
| Capability | bootstrap-gateway | workflow |
|---|---|---|
consensus | - | local |
cron | - | local |
don-time | - | local |
evm | - | local (1337,2337) |
http-action | - | local |
http-trigger | - | local |
vault | - | local |
解读这张矩阵需要理解三个关键语义:
local表示能力本地挂载:该能力由本 DON 内的节点直接注册并提供,而非通过远程调用其他 DON 的能力。topologyviz.go中buildCapabilityMatrix将能力分为local与remote-exposed两种模式,只有当节点集声明ExposesRemoteCapabilities且属于 capabilities DON 时才会标记为remote-exposed(见 topologyviz/topologyviz.go)。evm带链 ID 后缀:local (1337,2337)表示 EVM 能力同时挂载到 chain 1337 与 chain 2337 两条链。这在 TOML 中对应capabilities = ["evm-1337", "evm-2337"]两个带链 ID 的能力旗标;生成器通过splitCapabilityFlag把evm-1337拆为基础名evm与链 ID1337(见 topologyviz.go)。-表示该 DON 未放置此能力:bootstrap-gateway 节点集没有声明任何能力,因此矩阵整列为空。
矩阵的生成逻辑还做了两项规范化:能力按基础名排序输出,链 ID 去重排序后以逗号拼接,确保同一能力在多链场景下的表达稳定可比较。
DON 布局详解:bootstrap-gateway 与 workflow 的分工
bootstrap-gateway:1 节点,承担引导与网关双重职责
文档给出的属性如下:
- Types:
bootstrap,gateway—— 该节点集同时承担 DON 引导(bootstrap)与网关(gateway)两种类型 - Nodes:
1—— 单节点部署 - Roles:
bootstrap,gateway - EVM chains:
1337,2337 - Exposes remote capabilities:
false—— 不对外暴露远程能力
对应 TOML 片段(configs/workflow-gateway-don-cache-test.toml):
[[nodesets]] nodes = 1 name = "bootstrap-gateway" don_family = "test-don-family" don_types = ["bootstrap", "gateway"] override_mode = "each" http_port_range_start = 10300 env_vars = { CL_EVM_CMD = "" } supported_evm_chains = [1337, 2337] [nodesets.db] image = "postgres:12.0" port = 13200 [[nodesets.node_specs]] roles = ["bootstrap", "gateway"] [nodesets.node_specs.node] docker_ctx = "../../../.." docker_file = "core/chainlink.Dockerfile" docker_build_args = { "CL_IS_PROD_BUILD" = "false" } custom_ports = ["5002:5002","15002:15002"] user_config_overrides = ""要点说明:
override_mode = "each":每个节点规格(node_specs)单独应用于节点,而非所有节点共享同一套覆盖配置。该节点集只有一个 node_spec,声明roles = ["bootstrap", "gateway"],使同一节点同时运行 bootstrap 与 gateway 两个角色。custom_ports = ["5002:5002","15002:15002"]:显式映射网关/引导相关端口,供本地调试访问。supported_evm_chains = [1337, 2337]:与文档中 "EVM chains:1337,2337" 一致;生成器从该字段聚合出 DONSummary 的 SupportedEVMChains 并渲染进文档(见 topologyviz.go)。- 镜像构建:
docker_ctx = "../../../.."指向仓库根目录,docker_file = "core/chainlink.Dockerfile",CL_IS_PROD_BUILD = "false"表示构建非生产(开发)版本节点镜像。 env_vars = { CL_EVM_CMD = "" }:清空 EVM 命令行参数,使用默认行为。
workflow:4 节点,承载全部能力执行
- Types:
workflow - Nodes:
4 - Roles:
plugin - EVM chains:
1337,2337 - Exposes remote capabilities:
false
对应 TOML 片段(configs/workflow-gateway-don-cache-test.toml):
[[nodesets]] nodes = 4 name = "workflow" don_family = "test-don-family" don_types = ["workflow"] override_mode = "all" http_port_range_start = 10100 env_vars = { CL_EVM_CMD = "" } capabilities = ["vault", "cron", "http-action", "http-trigger", "consensus", "don-time", "evm-1337", "evm-2337"] registry_based_launch_allowlist = ["cron-trigger@1.0.0", "dontime@1.0.0"] [nodesets.db] image = "postgres:12.0" port = 13000 [[nodesets.node_specs]] roles = ["plugin"] [nodesets.node_specs.node] docker_ctx = "../../../.." docker_file = "core/chainlink.Dockerfile" docker_build_args = { "CL_IS_PROD_BUILD" = "false" } user_config_overrides = """ [Capabilities.WorkflowRegistry.ModuleCache] Enabled = true MaxLoaded = 1 IdleEviction = true IdleTimeout = '30s' """本节点集是该拓扑的"主角",四个细节值得展开:
- 能力旗标与矩阵的对应关系:
capabilities列表中的vault、cron、http-action、http-trigger、consensus、don-time以及链绑定的evm-1337、evm-2337,正是能力矩阵中local或local (1337,2337)的来源。矩阵中没有bootstrap-gateway的能力,因为该节点集未声明capabilities字段。 registry_based_launch_allowlist:允许以 registry 方式启动的触发器版本白名单,这里放行cron-trigger@1.0.0与dontime@1.0.0——即 cron 触发器和 don-time 能力按版本从外部 registry 注册。override_mode = "all":所有节点共享同一份覆盖配置(本例中即模块缓存覆盖)。- 模块缓存覆盖(本拓扑的核心差异点):
user_config_overrides注入了[Capabilities.WorkflowRegistry.ModuleCache]配置块:
[Capabilities.WorkflowRegistry.ModuleCache] Enabled = true MaxLoaded = 1 IdleEviction = true IdleTimeout = '30s'这是workflow-gateway-don-cache-test与普通workflow-gateway-don拓扑(见 topologies/workflow-gateway-don.md,其配置 configs/workflow-gateway-don.toml 不带模块缓存覆盖)最本质的区别:它刻意把MaxLoaded压到1、IdleTimeout缩到30s,从而在缓存冒烟测试中快速制造"加载 → 闲置 → 驱逐 → 重载"的循环,用于观察缓存是否按预期工作。对比同目录下的浸泡测试拓扑 configs/workflow-gateway-don-cache-soak-test.toml,其覆盖配置为MaxLoaded = 100、IdleTimeout = '5m',并额外设置[Workflows.Limits] Global = 5000、PerOwner = 5000——两个拓扑分别服务于"快速验证"与"长时间浸泡"两种测试目的。
支撑组件:链、Job Distributor 与辅助服务
两条 Anvil 链
[[blockchains]] type = "anvil" chain_id = "1337" container_name = "anvil-1337" docker_cmd_params = ["-b", "0.5", "--mixed-mining"] [[blockchains]] type = "anvil" chain_id = "2337" container_name = "anvil-2337" port = "8546" docker_cmd_params = ["-b", "0.5", "--mixed-mining"]- 两条链均为 Anvil 本地节点(Foundry 自带),chain_id 分别为 1337 与 2337;
- 第一条使用默认 RPC 端口(8545),第二条显式指定
port = "8546"以避免冲突; docker_cmd_params = ["-b", "0.5", "--mixed-mining"]:-b 0.5表示每 0.5 秒出一个块,--mixed-mining开启混合挖矿模式(同时支持即时与定时出块),为依赖日志触发器与区块高度的能力(如 evm 的 LogTrigger)提供可控的区块节奏。
Job Distributor、fake 服务与基础设施
[jd] csa_encryption_key = "d1093c0060d50a3c89c189b2e485da5a3ce57f3dcb38ab7e2c0d5f0bb2314a44" image = "job-distributor:0.28.0" [fake] port = 8171 [fake_http] port = 8666 [infra] type = "docker"[jd]:Job Distributor 镜像固定为job-distributor:0.28.0(与 setup.toml 中 job_distributor 的本地镜像一致),csa_encryption_key用于节点与 JD 之间的 CSA 密钥加密通信;[fake]/[fake_http]:提供本地 fake 服务端口(8171)与 fake HTTP 服务端口(8666),供 http-action / http-trigger 等能力在测试中回环访问;[infra] type = "docker":与文档头部 "Infra:docker" 一致,所有节点与链均以 Docker 容器方式运行。
能力默认值(全局叠加层)
capability_defaults.toml(configs/capability_defaults.toml)会在拓扑加载时被自动前置合并(loadAndSummarizeConfig中以defaultCapabilitiesConfigFile + "," + configPath方式加载,见 environment/topology.go)。它对本拓扑中涉及的能力给出全局默认值,例如:
[capability_configs.evm.values]:LogTriggerPollInterval = 1500000000(1.5s)、ReceiverGasMinimum = 500;[capability_configs.http-action.values]/[capability_configs.http-trigger.values]:进出向的IncomingGlobalRPS = 50、IncomingPerSenderRPS = 10、对应 Burst 参数等限流配置;[capability_configs.vault.values]:vault 能力内置于节点(无独立 binary),其 auth0 配置指向本地模拟端点http://host.docker.internal:18123/。
拓扑类别(Class)是如何判定的:single-don 的判定逻辑
workflow-gateway-don-cache-test被归类为single-don,其判定逻辑写在classifyTopology(topologyviz.go):
if hasShards { return ClassSharded } if capDONs > 0 || workflowDONs > 1 || len(dons) > 2 { return ClassMultiDON } return ClassSingleDON对照本拓扑:两个 DON(bootstrap-gateway与workflow)、无 shard DON、无独立 capabilities DON、workflow 类型 DON 恰好 1 个、DON 总数 ≤ 2,因此落入single-don。这也解释了为什么该拓扑能够"单 DON 全本地"——bootstrap-gateway 只负责引导与网关流量,能力执行全部集中在 workflow DON 内。
与之形成对照的是 workflow-gateway-sharded-5-dons.md(7 个 DON,sharded类)与 workflow-gateway-capabilities-don.md(3 个 DON,含独立 capabilities DON,multi-don类)。完整索引见 docs/TOPOLOGIES.md。
实战:如何启动该拓扑并运行模块缓存冒烟测试
1. 启动环境
该拓扑专为模块缓存冒烟测试设计。参考系统测试文件 system-tests/tests/smoke/cre/v2_module_cache_test.go 中的前置说明,启动命令为:
cd core/scripts/cre/environment CTF_CONFIGS=configs/workflow-gateway-don-cache-test.toml go run . env startCTF_CONFIGS环境变量显式指定拓扑配置文件,覆盖 CLI 默认值(main.go中shell子命令默认注入configs/workflow-gateway-don.toml,见 environment/main.go);- 若需要先拉取/构建托管镜像,可加
--auto-setup(env start的-a简写,源码见 environment/environment.go):go run . env start --auto-setup - 其他常用生命周期命令:
go run . env stop、go run . env restart、go run . env state purge(完整命令清单见 docs/local-cre/environment/index.md)。
2. 启动前检查拓扑
官方推荐在启动前先用拓扑命令核对最终的能力放置:
# 在 core/scripts/cre/environment 目录下执行 go run . topology list # 列出全部可用拓扑 go run . topology show --config configs/workflow-gateway-don-cache-test.toml # 渲染 ASCII 概览与能力矩阵 go run . topology generate # 重新生成 docs/topologies/*.md 与索引其中topology show默认--config为configs/workflow-gateway-don.toml、--output-dir为state;topology generate默认输出到docs/topologies、索引到docs/TOPOLOGIES.md(见 environment/topology.go)。
3. 运行冒烟测试
go test -timeout 10m -run "^Test_CRE_V2_Module_Cache$" -v测试入口定义于 system-tests/tests/smoke/cre/cre_suite_test.go:
func Test_CRE_V2_Module_Cache(t *testing.T) { testEnv := t_helpers.SetupTestEnvironmentWithConfig(t, t_helpers.GetTestConfig(t, "/configs/workflow-gateway-don-cache-test.toml")) ExecuteModuleCacheTest(t, testEnv) }测试的验证流程(v2_module_cache_test.go)如下:
- 启动 Chip test sink(用于接收 workflow 遥测日志),并保证退出时带排空(drain)地关闭,避免 gRPC Publish 阻塞;
- 编译并部署10 个基于 cron 触发器的 workflow(复用
examples/workflows/cron/main.go,调度为*/30 * * * * *),命名cachetest0~cachetest9; - 等待第一个 workflow 执行成功(以日志
Amazing workflow user log为信号),随后进入1 分钟缓存观察窗口:期间持续排空用户日志,但不做业务断言; - 最终断言节点日志中出现
"Module cache enabled",确认模块缓存确实被启用。
由于拓扑将MaxLoaded = 1,10 个 workflow 的 WASM 模块在同一时刻最多只能有 1 个驻留内存,配合IdleTimeout = '30s',测试期间必然反复触发"驱逐旧模块、从磁盘重载新模块"的行为——这正是该测试验证的核心路径。
模块缓存的底层实现:LRU + 磁盘两级缓存
配置项语义(来自官方配置文档)
core/config/docs/core.toml(core/config/docs/core.toml)对[Capabilities.WorkflowRegistry.ModuleCache]的完整语义说明如下:
| 配置项 | 默认值 | 语义 |
|---|---|---|
Enabled | false | 激活两级模块缓存(LRU + 磁盘)。开启后,编译好的 WASM 模块会驻留内存并持久化到磁盘,避免后续激活时重复编译 |
DiskMonitorEnabled | false | 启用磁盘用量监控(以cacheDir为监控对象,输出到 Beholder 遥测与节点/metrics端点) |
IdleEviction | true | 启用基于闲置时间的驱逐:模块超过IdleTimeout未使用即被移出内存 |
IdleTimeout | 10m | 模块允许闲置的最长时间(仅当IdleEviction = true时生效) |
MaxLoaded | 200 | 同时驻留内存的模块数量上限;超出时立即驱逐最久未使用(LRU)的模块。0表示不设上限 |
CacheDir | '' | 序列化模块二进制的存放目录;为空时使用临时目录 |
注意Enabled = true同时会启动磁盘监控(即使DiskMonitorEnabled = false)。对应的 Go 接口定义于 core/config/capabilities_config.go。
执行侧实现:EvictableModule + ModuleLRU
运行时实现位于 core/services/workflows/syncer/v2/evictable_module.go,核心是两个组件:
EvictableModule:对host.ModuleV2的包装。它持有两层引用——current(强引用 + 引用计数,用于正在执行的模块)与weakInner(弱引用,模块被驱逐后仍存活到 GC 回收为止,用于快速复活)。模块加载遵循三级策略:命中强引用 → 命中弱引用(无需重编译)→ 从磁盘读取序列化二进制重新实例化(ensureLoaded中通过m.store.GetModule读取,见 evictable_module.go)。ModuleLRU:全局 LRU 管理器,内部维护map[string]*EvictableModule,每 30 秒执行一次reap()(defaultScanInterval = 30 * time.Second,见 evictable_module.go)。reap()分两步(evictable_module.go):- 闲置驱逐:遍历所有模块,
now - LastUsed > IdleTimeout且处于加载态(IsLoaded())的模块被Evict(); - 上限驱逐:若加载模块数超过
MaxLoaded,按最近使用时间排序,驱逐最久未使用且超额的模块(enforceCapLocked)。
- 闲置驱逐:遍历所有模块,
Evict()采用"引用计数守卫 + 单次 CAS"设计(evictable_module.go):只有当引用计数为 1(仅所有者、无正在执行的 Execute 钉住)时才允许驱逐,避免"旧模块仍在服务、新模块又加载"导致的瞬时双实例内存抖动;驱逐失败会在下一个 reap 周期重试,保证最终一致。驱逐只释放 WASM 内存,不影响触发器注册与事件通道(这些归引擎所有)。
reap()还会记录loaded数、每次驱逐计数与累计节省内存字节数(recordMemorySaved),供CacheMetrics与磁盘监控导出。磁盘监控实现于 core/services/workflows/syncer/v2/disk_monitor.go,按固定间隔采样cacheDir的磁盘用量并同时输出到 Beholder 遥测与 Prometheus 指标(promModuleCacheDiskUsageBytes)。
配置项与实现的映射
| 拓扑中的覆盖配置 | 实现落点 |
|---|---|
Enabled = true | 创建ModuleLRU与磁盘序列化存储(SerialisedModuleStore),并启动磁盘监控 |
MaxLoaded = 1 | 注入ModuleLRU.maxLoaded(WithMaxLoadedModules,见 evictable_module.go),reap()中触发上限驱逐 |
IdleEviction = true | reap()中启用闲置扫描分支 |
IdleTimeout = '30s' | 注入ModuleLRU.idleTimeout(WithIdleTimeout,见 evictable_module.go) |
这意味着本拓扑中MaxLoaded = 1+IdleTimeout = '30s'的组合,会让ModuleLRU在每次reap()(30 秒周期)时都试图把上一个闲置模块驱逐出去,从而在 1 分钟观察窗口内制造至少 2~3 次完整的"驱逐 → 弱引用/磁盘重载"循环,足以暴露缓存逻辑的并发缺陷(例如 Evict 与 Execute 的竞态)。
常见问题与调参建议
- 想复现"模块缓存命中"而非频繁驱逐:将
MaxLoaded调大(如 soak 拓扑的100)、IdleTimeout调长(如5m),使多个 workflow 模块同时驻留内存; - 需要压测大量 workflow 注册:参考 soak 拓扑追加
[Workflows.Limits],把Global/PerOwner从默认的200(见 core/config/docs/core.toml)提升到目标规模; - 节点启动报镜像缺失:先执行
go run . env setup拉取 Job Distributor、Chip Router、Chip Ingress、Chip Config 等托管镜像,必要时配置MAIN_AWS_ECR与SDLC_AWS_ECR两个 ECR 仓库; - 想用本地源码构建节点/能力:使用
env start的--local-node与--local-capabilities旗标(详见 docs/local-cre/environment/index.md),本地构建可感知replace指令(如本地 chainlink-common),这是 Docker 内构建看不到的。
小结
workflow-gateway-don-cache-test拓扑是 Chainlink Local CRE 中一个"小而专"的参考实现:它用 single-don 类别把七类能力全部本地化到 4 节点的 workflow DON,让 1 节点的 bootstrap-gateway 专注引导与网关流量,两条 Anvil 链(1337/2337)为 EVM 能力提供可控的区块环境;其独特之处在于通过user_config_overrides注入MaxLoaded = 1、IdleTimeout = '30s'的模块缓存覆盖,配合Test_CRE_V2_Module_Cache冒烟测试,定向验证了两级模块缓存的启用、驱逐与重载路径。理解这份拓扑,也就掌握了 Local CRE 拓扑文档"配置为源、文档为投影"的生成范式,以及从 TOML 到能力矩阵、再到运行时实现的完整证据链。
【免费下载链接】chainlinknode of the decentralized oracle network, bridging on and off-chain computation项目地址: https://gitcode.com/GitHub_Trending/ch/chainlink
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考