Chainlink LOOP 插件体系全解析:基于 go-plugin 与 gRPC 的进程外插件运行时
Chainlink LOOP 插件体系全解析基于 go-plugin 与 gRPC 的进程外插件运行时【免费下载链接】chainlinknode of the decentralized oracle network, bridging on and off-chain computation项目地址: https://gitcode.com/GitHub_Trending/ch/chainlinkLOOPLocal-Out-Of-Process插件是 Chainlink 节点的一种备选运行时形态将原本内嵌在节点进程中的 relayer、OCR 报告插件等系统拆分为独立进程通过 HashiCorp go-plugin 协议与 gRPC 与主节点通信。本文以仓库 plugins/README.md 为主线结合plugins/目录的源码、plugins/chainlink.Dockerfile 与 core/web/loop_registry.go 等实现系统讲解 LOOP 插件的原理、启用方式、运行时环境变量、超时与 Prometheus 监控两大前置条件以及基于 loopinstall 的构建安装流程帮助读者从配置能跑深入到源码可读。LOOP 插件是什么从单进程到多进程的运行时演进LOOP 是Local-Out-Of-Process的缩写指一种备选的节点运行时部分系统不再作为 goroutine 运行在 Chainlink 节点主进程内而是运行在独立进程中通过 github.com/hashicorp/go-plugin 协议插件化接入并使用gRPC进行通信。当前仓库中主要有两类 LOOP 插件Relayer 插件负责与具体区块链网络对接的中继层组件Median 产品插件实现 OCR2 Median 报告逻辑的产品级插件。plugins/目录下保留了package main形式的可执行入口见 plugins/cmd可通过make install-plugin构建安装。README 同时明确指出Solana 与 Starknet 插件已迁移至各自独立仓库本模块内所有插件最终都会迁出因此plugins/是一个处于过渡期的模块。从源码看本地插件入口 plugins/cmd/chainlink-medianpoc/main.go 清晰展示了 go-plugin 与 gRPC 的接入方式func main() { s : loop.MustNewStartedServer(loggerName) defer s.Stop() p : medianpoc.NewPlugin(s.Logger) defer s.Logger.ErrorIfFn(p.Close, Failed to close) s.MustRegister(p) stop : make(chan struct{}) defer close(stop) plugin.Serve(plugin.ServeConfig{ HandshakeConfig: reportingplugins.ReportingPluginHandshakeConfig(), Plugins: map[string]plugin.Plugin{ reportingplugins.PluginServiceName: reportingplugins.GRPCService[types.MedianProvider]{ PluginServer: p, BrokerConfig: loop.BrokerConfig{ Logger: s.Logger, StopCh: stop, GRPCOpts: s.GRPCOpts, }, }, }, GRPCServer: s.GRPCOpts.NewServer, }) }其业务实现位于 plugins/medianpoc/plugin.gomedianpoc.Plugin内嵌了loop.Plugin与reportingplugins.MedianProviderServer并实现了NewValidationService等接口。这套代码是理解进程内插件如何变成进程外服务的最小完整示例。节点如何拉起插件CmdConfig 与 LoopRegistry 的协作插件能否被节点拉起取决于节点侧如何构造子进程命令。三个关键源码文件构成这条链路1.plugins/cmd.go—— 定义待执行的命令type CmdConfig struct { ID string // unique string used by the node to track the LOOP. typically supplied by the loop logger name Cmd string // string value of executable to exec Env []string // environment variables as described in [exec.Cmd.Env] }NewCmdFactory的作用是保证 loop registry 与待执行命令之间的同步先调用注册函数拿到*RegisteredLoop再返回一个闭包闭包每次执行时都会用exec.Command构造子进程并把RegisteredLoop.EnvCfg.AsCmdEnv()生成的运行环境变量注入其中。2.plugins/registrar.go—— 统一的注册入口RegistrarConfig接口定义了RegisterLOOP(config CmdConfig) (func() *exec.Cmd, loop.GRPCOpts, error)与UnregisterLOOP(ID string)NewRegistrarConfig接收 gRPC 选项、注册函数与注销函数把注册与生成 exec.Cmd两个动作绑定在一起。3.plugins/loop_registry.go—— 插件注册表与端口分配LoopRegistry是插件的全局登记处职责包括分配 Prometheus 端口Register(id string)通过freeport.Take(1)为每个插件申请一个空闲端口作为该插件 HTTP 指标服务的端口重复注册同一 ID 会返回ErrExistsUnregister时通过freeport.Return归还端口向插件注入节点配置在Register内组装loop.EnvConfig把节点侧配置通过环境变量通道传给插件包括数据库连接URL、超时、连接池上限、Mercury 传输器参数、Pyroscope 性能剖析、Tracing 链路追踪、Telemetry 遥测以及 Metering 计量等配置提供并发安全的查询接口List()按插件名排序返回全部注册项Get(id)按 ID 查找供后续 HTTP 路由使用。plugins/env.go中的ParseEnvFile则负责把环境变量文件解析为keyvalue切片供命令工厂注入。如何启用 LOOP 插件Dockerfile 与运行环境变量plugins/chainlink.Dockerfile 在常规的 core/chainlink.Dockerfile 基础之上扩展把插件二进制一并打入镜像并通过设置如下环境变量开启 LOOP 支持环境变量对应二进制说明CL_SOLANA_CMDchainlink-solanaSolana relayer 插件二进制由外部仓库构建CL_STARKNET_CMDchainlink-starknetStarknet relayer 插件README 文档提及对应仓库已独立CL_MEDIAN_CMDchainlink-feedsMedian 产品插件CL_EVM_CMDchainlink-evmEVM 插件Dockerfile 标注为实验性CL_MERCURY_CMDchainlink-mercuryMercury/data-streams 插件实验性当前版本的 Dockerfile 中实际设置了ENV CL_MEDIAN_CMDchainlink-feeds、ENV CL_SOLANA_CMD${CL_SOLANA_CMD}默认为chainlink-solana以及实验性的CL_EVM_CMD、CL_MERCURY_CMD。关闭插件的方式取消unset对应的环境变量即可节点会回退到原始的进程内in-process运行时。也就是说LOOP 是可切换的运行时选项而非强制改造。镜像构建采用多阶段流水线该 Dockerfile 内有详细注释deps-base仅下载 Go 依赖避免源码变更导致缓存失效deps拷贝完整源码树build-delve安装 Delve 调试器供 debug 阶段使用build-remote-plugins只基于plugins.public.yaml/plugins.private.yaml/plugins.testing.yaml清单通过go tool loopinstall编译远程插件典型源码改动可跳过约 160 秒的远程插件构建build-local-plugins基于./plugins/cmd/...编译本地插件当前即 chainlink-medianpocbuild-chainlink编译 Chainlink 主节点二进制CL_IS_PROD_BUILDfalse时走install-chainlink-devfinalUbuntu 24.04 运行镜像暴露 6688 端口入口为chainlink local node并带/health健康检查debug叠加 Delve 的调试镜像。此外构建时支持两个开关CL_INSTALL_PRIVATE_PLUGINS与CL_INSTALL_TESTING_PLUGINS置为true时才会构建私有/测试插件且需要提供GITHUB_TOKEN见 GNUmakefile 中docker目标的校验逻辑。前置条件一gRPC 超时必须真实不能为 0LOOP 插件间通信基于 gRPC而 gRPC 调用始终携带context.Context要求配置现实的超时值。README 明确警告占位/伪值例如MaxDurationQuery 0无法正常工作必须更新为真实值。针对 Solana 等已部署合约不便重新配置的场景可通过环境变量CL_MIN_OCR2_MAX_DURATION_QUERY为 libocr 的LocalConfig.MinOCR2MaxDurationQuery设置新的下限若未设置默认值为100ms。仓库源码印证了这一行为见 core/services/ocr2/validate/config.goconst defaultMinOCR2MaxDurationQuery 100 * time.Millisecond var getMinOCR2MaxDurationQuery sync.OnceValues(func() (time.Duration, error) { str : env.MinOCR2MaxDurationQuery.Get() if ... { return defaultMinOCR2MaxDurationQuery, nil } ... })环境变量定义位于 core/config/env/env.go 的MinOCR2MaxDurationQuery Var(CL_MIN_OCR2_MAX_DURATION_QUERY)。作为参照CCIP 的 OCR 插件在 core/capabilities/ccip/oraclecreator/plugin.go 中将MinOCR2MaxDurationQuery设置为1 * time.Second并在 plugin_test.go 中予以断言——这从侧面说明 100ms 只是下限实际取值应结合业务延迟要求。前置条件二动态插件的 Prometheus 监控LOOP 插件是动态的运行时才注册、端口由freeport动态分配因此必须动态监控。节点的做法是使用插件发现机制Plugin discovery动态决定监控哪些插件并把外部 Prometheus 的抓取请求路由到各插件而不直接暴露插件端口。两个核心端点/discoveryHTTP Service Discovery 端点。Prometheus 服务器轮询该 URL节点根据当前正在运行的插件动态返回目标列表。响应采用 Prometheus 的targetgroup.Group结构节点自身指标也会一并加入见下文源码。/plugins/name/metrics节点充当极薄的中间层thin middleware把 Prometheus 的抓取请求转发到对应插件的/metrics端点。插件通过上述 discovery 机制被发现后Prometheus 会在抓取间隔内持续调用该目标。最小 Prometheus 配置改动README 给出的最小改动是在 scrape 配置中加入一个 HTTP 服务发现- job_name: chainlink_node ... http_sd_configs: - url: http://127.0.0.1:6688/discovery refresh_interval: 30s即让 Prometheus 每 30 秒刷新一次节点上的服务发现列表详见 Prometheus 官方http_sd_config文档。源码级实现core/web/loop_registry.go监控端点的实现位于 core/web/loop_registry.godiscoveryHandler先加入节点自身的/metrics目标再遍历registry.List()为每个注册插件生成目标组并打上__meta_plugin_name标签常量LabelMetaPluginName最终以 JSON 返回pluginMetricHandler根据路径参数name查registry.Get(pluginName)找到后把请求转发到http://loopHostName:PrometheusPort/metrics其中PrometheusPort正是LoopRegistry.Register时分配的端口除指标外还提供了 pprof 转发端点pluginPPROFHandler会把/plugins/name/debug/pprof/*转发到插件的debug/pprof端口profile、gc、seconds 等查询参数均透传initHostNames说明两个 hostname 的来源CL_PROMETHEUS_DISCOVERY_HOSTNAME外部 Prometheus 可访问的地址未设置时回退到os.Hostname()与CL_LOOPP_HOSTNAME节点与插件间内部通信地址默认localhost。路由注册见 core/web/router.gor.GET(/discovery, ginHandlerFromHTTP(loopRegistry.discoveryHandler)) r.GET(/plugins/:name/metrics, loopRegistry.pluginMetricHandler) r.GET(/plugins/:name/debug/pprof/*profile, loopRegistry.pluginPPROFHandler) r.POST(/plugins/:name/debug/pprof/symbol, loopRegistry.pluginPPROFPOSTSymbolHandler)这种服务发现 反向代理转发的设计既满足了插件动态增减的监控需求又避免把插件内部端口暴露到外部网络。构建与安装插件Makefile 与 loopinstall 清单仓库通过 GNUmakefile 提供一组插件安装目标目标用途make install-loopinstall安装loopinstall工具go install github.com/smartcontractkit/chainlink-common/pkg/loop/cmd/loopinstallmake install-plugins-local编译本地插件即./plugins/cmd/chainlink-medianpoc带-ldflags-s去除调试信息make install-plugins-public依据plugins/plugins.public.yaml通过go tool loopinstall --concurrency 5安装公共插件make install-plugins-private依据plugins/plugins.private.yaml安装私有插件需要GOPRIVATE与 git 认证make install-plugins-testing依据plugins/plugins.testing.yaml安装仅测试用插件make install-plugins等价于install-plugins-local install-plugins-public当设置了CL_LOOPINSTALL_OUTPUT_DIR时loopinstall 还会产出public.json/private.json/testing.json安装工件清单供 Dockerfile 的copy_loopinstall_libs.sh收集动态链接库如 Cosmos 的libwasmvm.so。公共插件清单 plugins/plugins.public.yaml 展示了 loopinstall 的清单格式defaults.goflags统一附加-ldflags-s以减小二进制体积plugins下每个条目声明moduleURI外部模块地址、gitRef精确版本、installPath相对入口以及可选的libs需要拷入容器/usr/lib的动态库。清单覆盖链相关 relayeraptos、sui、cosmos、solana、starknet、stellar、ton、tron、evm产品插件feedsMedian对应CL_MEDIAN_CMD、streamsMercuryCapability 类capability-cron、capability-consensus、capability-workflowevent默认enabled: false、capability-httpaction、capability-httptrigger、capability-evm/solana/aptos/stellar机密计算confidential-http、confidential-workflows。这也解释了 README 中Solana Starknet 已迁移到各自仓库的含义它们的代码已迁出本模块但通过moduleURIgitRef的方式仍以远程插件形态参与构建。小结LOOP 插件把 Chainlink 节点的运行时从单进程内嵌演进为多进程、可插拔、gRPC 通信的架构。启用时只需通过 plugins/chainlink.Dockerfile 构建镜像并设置CL_*_CMD系列环境变量取消即回退进程内运行时落地前必须满足两个前置条件为 gRPC 配置真实超时可通过CL_MIN_OCR2_MAX_DURATION_QUERY调整下限默认 100ms以及通过/discovery/plugins/name/metrics完成动态监控。进一步理解底层机制时可依次阅读 plugins/loop_registry.go端口分配与配置注入、plugins/cmd.go子进程构造与 core/web/loop_registry.go发现与转发即可从会配置进阶到懂原理。【免费下载链接】chainlinknode of the decentralized oracle network, bridging on and off-chain computation项目地址: https://gitcode.com/GitHub_Trending/ch/chainlink创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考