Solana Geyser 插件:以光速传输数据

在本文中,我们将深入研究 Solana Geyser 插件的复杂性。我们将从探索 AccountsDB 副本开始,这是一种拟议的数据复制和负载管理方法,最终被放弃,取而代之的是 Geyser 插件。然后我们将详细介绍什么是 Geyser 插件、它们的功能以及它们如何通过插件接口构建。从这里开始,我们将讨论可用的常见 Geyser 插件,并指导您完成创建自己的插件的复杂过程。最后,我们将讨论 Helius 以及如何简化 Solana 上的数据流。

Solana Geyser 插件:以光速传输数据

这篇文章是关于什么的?

Geyser 插件是模块化组件,旨在将有关帐户、插槽、区块和交易的数据传输到外部数据存储,从而允许开发人员从验证器中删除 RPC(远程过程调用)负载。 Geyser 插件为希望定制数据流和处理需求的开发人员提供了灵活的解决方案。

在本文中,我们将深入研究 Solana Geyser 插件的复杂性,我们将从探索AccountsDB 副本开始,这是一种拟议的数据复制和负载管理方法,最终被放弃,取而代之的是 Geyser 插件。然后我们将详细介绍什么是 Geyser 插件、它们的功能以及它们如何通过插件接口构建。从这里开始,我们将讨论可用的常见 Geyser 插件,并指导您完成创建自己的插件的复杂过程。最后,我们将讨论 Helius 以及如何简化 Solana 上的数据流。

AccountsDB 副本:一种被废弃的数据复制和 RPC 负载方法

Solana 探索了多种途径来解决重 RPC 负载和数据复制的挑战。一种有前途的方法是使用AccountsDB 副本。这些副本旨在将帐户扫描请求从主验证器卸载到 AccountsDB 副本。虽然很有希望,但该系统本质上很复杂,需要一组新的服务来确保主验证器和副本之间的同步。最终,这个提案被放弃,取而代之的是 Geyser 插件系统——这是一种验证器客户端更容易支持的解决方案,并且在实现应用程序时为开发人员提供了更大的灵活性。那么,Solana Geyser 插件到底是什么?

什么是 Solana Geyser 插件?

Solana Geyser 插件提供对 Solana 数据的低延迟访问,并且可以为应用程序提供服务,从而取代对验证器进行 RPC 调用的需要。例如,如果验证器必须快速连续地处理大量getProgramAccounts调用,则验证器可能会由于这种密集的流量而落后于网络。 Geyser 插件通过将有关帐户、块、插槽和交易的信息重定向到外部数据存储(例如关系数据库、NoSQL 数据库或 Kafka)来解决此问题。这种数据重定向允许 RPC 服务为那些寻求从这些外部存储获取数据的人提供更灵活和有针对性的优化,例如缓存和索引。

Geyser 插件充当 Solana 和外部数据存储解决方案之间的桥梁。它们使开发人员能够从验证器中卸载大部分数据管理任务,从而提高性能并降低潜在瓶颈的风险。 Geyser 插件可确保验证器与网络保持同步,无论 RPC 流量如何。

Geyser 插件界面

开发人员可以使用 Solana Geyser 插件接口构建 Geyser 插件。该接口提供对账户、交易、槽、块元数据和条目的访问。它在solana-geyser-plugin-interface中声明,并由GeyserPlugin特征定义。该特征定义了方法,每个方法都以update_为前缀,每当创建新数据或更新现有数据时都会调用这些方法。 Geyser 插件还需要指定其在加载和卸载过程中的行为。该特征概述了 Geyser 插件应实现的基本方法,以确保基于所需插件行为的高效数据流。

源代码


pub trait GeyserPlugin:Any +Send +Sync +Debug {
    // Required method
    fn name(&self) -> &'static str;

    // Provided methods
    fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }
    fn on_unload(&mut self) { ... }
    fn update_account(
        &self,
        account:ReplicaAccountInfoVersions<'_>,
        slot: Slot,
        is_startup:bool
    ) ->Result<()> { ... }
    fn notify_end_of_startup(&self) ->Result<()> { ... }
    fn update_slot_status(
        &self,
        slot: Slot,
        parent:Option,
        status:SlotStatus
    ) ->Result<()> { ... }
    fn notify_transaction(
        &self,
        transaction:ReplicaTransactionInfoVersions<'_>,
        slot: Slot
    ) ->Result<()> { ... }
    fn notify_entry(&self, entry:ReplicaEntryInfoVersions<'_>) ->Result<()> { ... }
    fn notify_block_metadata(
        &self,
        blockinfo:ReplicaBlockInfoVersions<'_>
    ) ->Result<()> { ... }
    fn account_data_notifications_enabled(&self) ->bool { ... }
    fn transaction_notifications_enabled(&self) ->bool { ... }
    fn entry_notifications_enabled(&self) ->bool { ... }
}

特质宣言

GeyserPlugin特征充当 Solana Geyser 插件生态系统中所有插件的基础接口。它被声明为具有Rust 标准库中的AnySendSyncDebug特征边界的公共特征。特征边界如下:

  • Any允许类型反射,从而可以向下转换为具体类型
  • Send表示实现此特征的类型的所有权可以在线程之间转移
  • 同步意味着实现此特征的类型的引用可以在线程之间共享
  • 调试允许格式化输出类型,专门用于调试目的

AnyDebug对我们来说并不重要。真正重要GeyserPlugin需要SendSync来使程序线程安全。

所需方法


fn name(&self) -> &'static str;

任何实现GeyserPlugin的类型都需要name方法。此方法用作 Geyser 插件的标识符。它返回一个静态字符串切片,表示 Geyser 插件的名称。

事实上,该方法和所有其他方法(除了on_loadon_unload)都使用&self而不是&mut self,这是 Solana 1.16 更新中的新增内容。通过消除将 Geyser 插件包装在读写锁中并在每次调用其函数之一时获取写锁的需要,这极大地提高了性能。

提供的方法

该特征提供了许多包含默认实现的方法,这些方法可以被GeyserPlugin的实现覆盖。


fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }

on_load方法是系统加载插件时调用回调,用于插件所需的任何初始化。它接受对表示配置文件路径的字符串的引用。配置必须采用 JSON5 格式,并包含一个libpath字段,该字段指示实现此接口的共享库的完整路径名。


fn on_unload(&mut self) { ... }

on_unload方法是在系统卸载插件之前调用的回调函数,用于执行任何清理操作


fn update_account(
        &self,
        account:ReplicaAccountInfoVersions<'_>,
        slot: Slot,
        is_startup:bool
    ) ->Result<()> { ... }

当帐户在已处理的确认级别更新时,会调用update_account方法,这可能会在一个槽内发生多次。在这里,跟踪已确认的插槽至关重要,以便获取提交给规范链的帐户更新。 ReplicaAccountInfoVersions结构包含流传输的帐户的元数据和数据。 slot参数指向正在更新帐户的槽位。当is_startup为 true 时,表示验证器启动时从快照加载帐户。当is_startup为 false 时,帐户会在交易处理期间更新。


fn notify_end_of_startup(&self) ->Result<()> { ... }

调用notify_end_of_startup方法来发出启动阶段结束的信号。当验证器从快照恢复帐户数据库并且所有帐户都已相应更新时,就会发生这种情况。


fn update_slot_status(
        &self,
        slot: Slot,
        parent:Option,
        status:SlotStatus
    ) ->Result<()> { ... }

update_slot_status方法插槽状态更新时被调用。它接受一个Slot、一个父插槽的Option<u64>和一个SlotStatus 枚举实例。

SlotStatus概述了 Solana 中插槽的三种状态

  • 已处理- 节点已处理的最高插槽。虽然该插槽既未得到确认也未最终确定,但它是验证者认为最有可能成为规范的链的一部分
  • 已确认- 该插槽已获得足够的选票,被认为是安全的并且是链的一部分。该老虎机得到了绝大多数 Solana 验证者的支持
  • 扎根- 该插槽现在是区块链的永久部分,并且链的所有其他版本或分叉都必须基于此插槽构建。这意味着网络上的所有分支都是该块的后代

fn notify_transaction(
        &self,
        transaction:ReplicaTransactionInfoVersions<'_>,
        slot: Slot
    ) ->Result<()> { ... }

当插槽中处理交易时,将调用notification_transaction方法,通知插件交易的详细信息。 ReplicaTransactionInfoVersions是处理ReplicaTransactionInfo的枚举包装器。如果RepicaTransactionInfo的结构发生更改,则新版本将会有一个新的枚举条目。这将迫使插件实现通过容纳新的枚举条目来处理更改。目前,枚举包含两个变体:   V0_0_1(&'a ReplicaTransactionInfo<'a>)V0_0_2(&'a ReplicaTransactionInfoV2<'a>)


pub struct ReplicaTransactionInfo<'a> {
    pub signature: &'a Signature,
    pub is_vote: bool,
    pub transaction: &'a SanitizedTransaction,
    pub transaction_status_meta: &'a TransactionStatusMeta,
}

pub struct ReplicaTransactionInfoV2<'a> {
    pub signature: &'a Signature,
    pub is_vote: bool,
    pub transaction: &'a SanitizedTransaction,
    pub transaction_status_meta: &'a TransactionStatusMeta,
    pub index: usize,
}

变体之间的主要区别在于,第二个变体将交易的索引存储在块中。


fn notify_entry(&self, entry:ReplicaEntryInfoVersions<'_>) ->Result<()> { ... }

notify_entry通知插件有新条目。它接受ReplicaEntryInfoVersions的实例,它是面向未来的ReplicaEntryInfo处理的包装器。它当前包含V0_0_1(&'a ReplicaEntryInfo<'a>)变体。该变体是一个结构体,其中包含有关条目槽、块中索引、自上一个条目以来的哈希数、条目的 SHA-256 哈希值以及条目中已执行事务数的信息。


fn notify_block_metadata(
        &self,
        blockinfo:ReplicaBlockInfoVersions<'_>
    ) ->Result<()> { ... }

当块的元数据更新时,会调用notification_block_metadata方法。它接受ReplicaBlockInfoVersions 枚举实例作为其块信息。枚举是各种ReplicaBlockInfo版本的包装器,其中包含有关区块的信息,例如槽位、哈希值、奖励、区块时间、区块高度等。


fn account_data_notifications_enabled(&self) ->bool { ... }
fn transaction_notifications_enabled(&self) ->bool { ... }
fn entry_notifications_enabled(&self) ->bool { ... }

这些方法返回布尔值,指示插件是否希望分别启用帐户数据、交易和条目的通知。

关于承诺级别的说明

Geyser 在处理完帐户数据和交易后立即发送更新。这有利于端到端索引速度,但是,存在可能跳过已处理槽的风险。跳过的槽是指过去没有产生块的槽,要么是因为领导者离线,要么是包含该槽的分叉被放弃以寻找更好的替代方案。对于流式传输的数据存储系统来说,识别这种可能性并相应地管理更新至关重要。

可用的常见 Solana Geyser 插件

有多种 Solana Geyser 插件可供开发人员使用,甚至可以进行分叉以满足他们的特定需求。一些值得注意的插件包括:

  • PostgreSQL 插件:用于使用 PostgreSQL 管理和查询数据
  • gRPC 服务流插件:用于将 Solana 帐户更新流式传输到 gRPC 服务
  • RabbitMQ Producer Plugin:用于促进 RabbitMQ 的消息队列
  • Kafka Producer Plugin:用于使用 Kafka 流式传输数据
  • Amazon SQS 插件:用于利用 Amazon 简单队列服务的消息队列
  • Google BigTable 插件:用于使用 Google BigTable 管理和查询数据

这些插件可以进行调整以满足无数的用例。例如,Clockwork利用 Geyser 插件来安排事务并构建自动化、事件驱动的 Solana 程序。尽管该项目已经结束,但其开源代码仍然是宝贵的资源,可以在其GitHub上查看。其他可能的用例可能包括使用 Geyser 插件监控 DeFi 平台上的账户余额、提供网络健康指标或实时监控供应链事件。

创建您自己的 Solana Geyser 插件

Solana Geyser 插件支架

Solana Geyser 插件支架是开始 Solana Geyser 插件开发之旅的最简单的资源。该脚手架充当一个简约的模板,用于记录插件管理器和插件本身之间的交互。这是熟悉插件工作流程和调试技术的一个很好的起点。

插件管理器

插件管理器是指导所有 Geyser 插件的生命周期和交互的核心组件。它能够在运行时动态加载和卸载插件,从而实现更大的灵活性和模块化。

在运行时,插件管理器将配置文件的路径传递给您的插件。这使得间歇泉插件中的可自定义设置可以在不更改插件代码的情况下进行修改。要将插件集成到验证器中,您需要使用--geyser-plugin-config参数指定动态库路径。这告诉验证器在哪里可以找到插件及其相关配置。配置文件至少必须采用 JSON 格式,并包含 Geyser 插件动态库的路径 - Linux 上的.so。最小的配置文件如下所示:


{
    "libpath": "/.so"
}

从头开始创建 Geyser 插件

如果您想走上不败之路并创建自己的 Geyser 插件而不使用脚手架或修改现有插件,则需要使用 Geyser 插件接口对插件进行编码。插件必须实现GeyserPlugin特征才能与运行时一起使用。此外,动态库必须导出一个“C”函数_create_plugin,它创建插件的实现。一个例子是创建一个实现GeyserPlugin特征的 Webhook 插件:


#[no_mangle]
#[allow(improper_ctypes_definitions)]
/// # Safety
///
/// This function returns the WebhookPlugin pointer as trait GeyserPlugin.
pub unsafe extern "C" fn _create_plugin() -> *mut dyn GeyserPlugin {
    let plugin = WebhookPlugin::new();
    let plugin: Box = Box::new(plugin);
    Box::into_raw(plugin)
}

在这里,我们创建一个不安全的公共函数,它使用 C 调用约定extern "C",这使得它与 C 和其他语言兼容。函数本身fn _create*_*plugin() -> *mut dyn GeyserPlugin返回一个指向dynGeyserPlugin的可变原始指针,这是GeyserPlugin特征。函数体创建WebhookPlugin的新实例,将该实例装箱为特征对象,然后将装箱的特征对象转换为原始指针,以便函数可以返回它。

因此,创建您自己的 Geyser 插件的步骤如下:

  • 构建实现 Solana Geyser 插件接口的插件
  • 从target/releasetarget/debug文件夹中获取动态库(.so文件)
  • 创建一个geyser-config.json文件,该文件必须在“libpath”字段下包含Geyser Plugin动态库的路径
  • 使用--geyser-plugin-config geyser-config.json标志启动验证器

这些步骤听起来相当简单,但是,实际运行和维护 Solana Geyser 插件的过程可能相当艰巨。

Helius 间歇泉流媒体

Helius 因在 Solana 上提供无与伦比的开发人员体验而闻名。对 Solana 的专注为 Helius 提供了丰富的经验,应对了广泛的挑战并促进了众多大规模集成。 Helius 具有独特的优势,可以解决开发人员可能面临的任何问题。

在 Helius,我们为 Solana 生态系统中的多个高绩效团队管理 Geyser 插件。我们运营专门的 Geyser 集群,增加了冗余和容错能力,以确保您永远不必担心丢失数据或停机。我们的编程 API 访问允许您动态修改您的 Geyser 插件,而无需担心可靠性。管理 Geyser 插件通常是一项艰巨的任务,因为您负责确保数据的一致性、可靠性和可用性。为什么不让 Helius 为您做这件事呢?

如果您对 Geyser 流媒体感兴趣,请立即在Discord上联系我们开始使用。

结论

恭喜!在本文中,我们通过研究 Solana Geyser 插件来解决数据复制和 RPC 负载管理的复杂性。理解这个系统并不是一件容易的事 - 它是一个复杂的架构,几乎没有记录,但却为 Solana 开发人员提供了大量的定制和性能优化机会。

在本文中获得的知识非常宝贵,特别是如果您是希望在 Solana 上构建或管理高性能应用程序的开发人员或团队。 Geyser 插件对于理解至关重要,因为它们为 Solana 生态系统提供了可扩展且可靠的解决方案。

如果您已经阅读了本文,谢谢!

💡
原文链接:Solana Geyser Plugins: Streaming Data at the Speed of Light
本文由SlerfTools翻译,转载请注明出处。

SlerfTools专为Solana设计的工具箱,致力于简化区块链操作,提供无编程全可视化界面,使发币管理流动性无代码创建Dapp等复杂过程变得安全简单。