НОВОЕ: Helius приобретает Light Protocol
плагины solana geyser
Блог/Основы

Плагины Solana Geyser: потоковая передача данных со скоростью света

Developer Experience Engineer0xIchigo в X0xIchigo в LinkedIn0xIchigo на GitHub
Чтение: 13 мин

О чем эта статья?

Плагины Geyser — это модульные компоненты для передачи данных об аккаунтах, слотах, блоках и транзакциях во внешние хранилища. Они позволяют разработчикам снять с валидатора нагрузку RPC (Remote Procedural Call). Плагины Geyser предоставляют гибкое решение для разработчиков, которым нужна индивидуальная настройка потоковой передачи и обработки данных.

В этой статье мы подробно рассмотрим устройство плагинов Solana Geyser. Сначала изучим реплики AccountsDB — предложенный подход к репликации данных и управлению нагрузкой, от которого в итоге отказались в пользу плагинов Geyser.

Затем разберем, что такое плагины Geyser, как они работают и как устроены через Plugin Interface.

После этого рассмотрим распространенные плагины 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

Разработчики могут создавать плагины Geyser с помощью Solana Geyser Plugin Interface. Интерфейс предоставляет доступ к аккаунтам, транзакциям, слотам, метаданным блоков и записям. Он объявлен в крейте 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 Plugin. Он объявлен как публичный трейт с ограничениями Any, Send, Sync и Debug из стандартной библиотеки Rust. Эти ограничения работают следующим образом:

  • Any обеспечивает рефлексию типов, которая позволяет выполнить нисходящее приведение к конкретному типу
  • Send указывает, что владение типом, реализующим этот трейт, можно передавать между потоками
  • Sync означает, что ссылки на тип, реализующий этот трейт, можно совместно использовать между потоками
  • Debug позволяет форматировать тип для вывода, в частности для отладки

Any и Debug не так важны для нас.

Что действительно важно: GeyserPlugin требует Send и Sync для потокобезопасности программы.

Обязательный метод

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

Метод name обязателен для любого типа, реализующего GeyserPlugin. Этот метод служит идентификатором плагина Geyser. Он возвращает статический строковый срез с именем плагина Geyser.

То, что этот и все остальные методы, кроме on_load и on_unload, используют &self вместо &mut self, появилось в обновлении Solana 1.16. Это значительно повышает производительность: больше не нужно оборачивать плагин Geyser в блокировку чтения и записи и получать блокировку записи при каждом вызове одной из его функций.

Предоставляемые методы

Трейт содержит несколько предоставляемых методов с реализациями по умолчанию. Реализации GeyserPlugin могут их переопределять.

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

Метод on_load — это функция обратного вызова, которая запускается при загрузке плагина системой и выполняет необходимую инициализацию. Она принимает ссылку на string, представляющий путь к файлу конфигурации. Конфигурация должна иметь формат JSON5 и содержать поле libpath с полным путем к разделяемой библиотеке, реализующей этот интерфейс.

Код
fn on_unload(&mut self) { ... }

Метод on_unload — это функция обратного вызова, которая выполняет необходимую очистку перед выгрузкой плагина системой.

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

Метод update_account вызывается при обновлении аккаунта на уровне подтверждения processed. Это может происходить несколько раз в течение одного слота. Здесь крайне важно отслеживать подтвержденные слоты, чтобы получать обновления аккаунта, зафиксированные в канонической цепочке.

Структура ReplicaAccountInfoVersions содержит метаданные и данные передаваемого аккаунта.

Параметр slot указывает на слот, в котором обновляется аккаунт.

Значение true для is_startup означает, что аккаунт загружен из снимков при запуске валидатора. Значение false для is_startup означает, что аккаунт обновляется во время обработки транзакции.

Код
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 enum.

SlotStatus описывает три состояния слота в Solana:

  • Processed — самый высокий слот, над которым работала нода. Пока слот не подтвержден и не финализирован, он входит в цепочку, которую валидатор считает наиболее вероятным кандидатом на роль канонической
  • Confirmed — слот получил достаточно голосов, чтобы считаться безопасным и входить в цепочку. Этот слот поддерживает квалифицированное большинство валидаторов Solana
  • Rooted — слот стал постоянной частью блокчейна, и все остальные версии или форки цепочки должны строиться на его основе. Это означает, что все ветви сети происходят от этого блока
Код
fn notify_transaction(
        &self,
        transaction:ReplicaTransactionInfoVersions<'_>,
        slot: Slot
    ) ->Result<()> { ... }

Метод notify_transaction вызывается при обработке транзакции в слоте и передает плагину сведения о ней.

ReplicaTransactionInfoVersions — это обертка enum, которая обрабатывает ReplicaTransactionInfo. Если структура RepicaTransactionInfo изменится, для новой версии появится новая запись enum. Реализации плагинов будут вынуждены поддержать это изменение, добавив новую запись enum. Сейчас enum оборачивает два варианта:

  1. V0_0_1(&'a ReplicaTransactionInfo<'a>)
  2. 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<()> { ... }

Метод notify_block_metadata вызывается при обновлении метаданных блока. Он принимает экземпляр ReplicaBlockInfoVersions enum со сведениями о блоке. Этот enum служит оберткой для различных версий 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 Plugin: для управления данными и выполнения запросов с помощью PostgreSQL
  • gRPC Service Streaming Plugin: для потоковой передачи обновлений аккаунтов Solana в сервис gRPC
  • RabbitMQ Producer Plugin: для организации очередей сообщений с помощью RabbitMQ
  • Kafka Producer Plugin: для потоковой передачи данных через Kafka
  • Amazon SQS Plugin: для организации очередей сообщений с помощью Amazon Simple Queue Service
  • Google BigTable Plugin: для управления данными и выполнения запросов с помощью Google BigTable

Эти плагины можно адаптировать для множества сценариев использования.

Например, Clockwork использовал плагин Geyser для планирования транзакций и создания автоматизированных событийно-ориентированных программ Solana. Хотя проект уже закрыт, его открытый исходный код остается ценным ресурсом и доступен на GitHub.

Среди других возможных сценариев — применение плагинов Geyser для мониторинга балансов аккаунтов на платформе DeFi, предоставления метрик состояния сети или отслеживания событий в цепочке поставок в реальном времени.

Создание собственного плагина Solana Geyser

Вот несколько ресурсов и компонентов для создания собственного плагина:

Каркас плагина Solana Geyser

Solana Geyser Plugin Scaffold — самый простой ресурс для начала разработки плагинов Solana Geyser. Этот каркас служит минималистичным шаблоном, который журналирует взаимодействия между Plugin Manager и самим плагином. Это отличная отправная точка для знакомства с рабочим процессом плагина и методами отладки.

Plugin Manager

Plugin Manager — основной компонент, который управляет жизненным циклом и взаимодействиями всех плагинов Geyser. Он может динамически загружать и выгружать плагины во время выполнения, повышая гибкость и модульность.

Во время выполнения Plugin Manager передает плагину путь к файлу конфигурации. Это позволяет настраивать параметры плагинов Geyser без изменения их кода.

Чтобы интегрировать плагин в валидатор, укажите путь к динамической библиотеке с помощью параметра --geyser-plugin-config. Так валидатор узнает, где найти плагин и связанную с ним конфигурацию.

Как минимум файл конфигурации должен иметь формат JSON и содержать путь к динамической библиотеке плагина Geyser — файлу .so в Linux. Минимальный файл конфигурации выглядит так:

Код
{
    "libpath": "/.so"
}

Создание плагина Geyser с нуля

Если вы хотите пойти непроторенным путем и создать собственный плагин Geyser без каркаса или модификации существующего плагина, реализуйте его с помощью Geyser Plugin Interface.

Для работы со средой выполнения плагин обязан реализовать трейт GeyserPlugin. Кроме того, динамическая библиотека должна экспортировать функцию «C» _create_plugin, которая создает реализацию плагина.

Например, можно создать плагин Webhook, реализующий трейт GeyserPlugin:

Код
#[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, помещает его в Box как объект трейта, а затем преобразует упакованный объект трейта в сырой указатель, чтобы функция могла его вернуть.

Итак, чтобы создать собственный плагин Geyser, выполните следующие действия:

  • Создайте плагин, реализующий интерфейс Solana Geyser Plugin
  • Получите динамическую библиотеку (файл .so) из папки target/release или target/debug
  • Создайте файл geyser-config.json, который должен содержать путь к динамической библиотеке плагина Geyser в поле «libpath»
  • Запустите валидатор с флагом --geyser-plugin-config geyser-config.json

Эти шаги выглядят довольно просто, однако сам процесс запуска и сопровождения плагина Solana Geyser может быть весьма трудоемким.

Потоковая передача Helius Geyser

Helius известен непревзойденным удобством разработки в Solana. Благодаря специализации исключительно на Solana команда Helius накопила богатый опыт, решая самые разные задачи и выполняя множество масштабных интеграций. Это дает Helius уникальные возможности для решения любых проблем разработчиков.

В Helius мы управляем плагинами Geyser для нескольких высокопроизводительных команд экосистемы Solana. Мы обслуживаем специализированные кластеры Geyser с дополнительным резервированием и отказоустойчивостью, чтобы вам не приходилось беспокоиться о потере данных или простоях. Программный доступ через API позволяет динамически изменять плагины Geyser, не беспокоясь о надежности. Управление плагинами Geyser часто оказывается сложной задачей, поскольку вы отвечаете за согласованность, надежность и доступность данных. Почему бы не доверить это Helius?

Если вас интересует потоковая передача Geyser, закажите выделенную ноду на панели Helius или свяжитесь с нами в Discord, чтобы начать уже сегодня.

Заключение

Поздравляем!

В этой статье мы разобрались в сложностях репликации данных и управления нагрузкой RPC на примере плагинов Solana Geyser. Понять эту систему непросто: это сложная архитектура с крайне скудной документацией. При этом она предоставляет разработчикам Solana множество возможностей для индивидуальной настройки и оптимизации производительности.

Знания из этой статьи особенно ценны для разработчиков и команд, которые создают высокопроизводительные приложения в Solana или управляют ими. Важно понимать устройство плагинов Geyser, поскольку они предлагают масштабируемое и надежное решение для экосистемы Solana.

Если вы дочитали до этого места, анон, спасибо!

Дополнительные ресурсы

Подпишитесь на Helius

Следите за новостями разработки на Solana и получайте уведомления о наших публикациях