Разработка смарт-контрактов на Near с использованием Rust, часть 3

(264 nL)
8 min read
To Share and +4 nLEARNs

Это третья часть из серии статей о создании приложения для чата с помощью Rust на блокчейне Near. Предыдущие посты из этой серии вы можете найти здесь и здесь.

В этом посте мы сосредоточимся на оффчейн-частях кода. Мы обсудим необходимость «индексаторов» и рассмотрим некоторые части реализации индексатора в этом примере. Вы можете найти полный репозиторий со всем кодом, который мы сегодня обсудим, на моем GitHub.

Индексаторы, что это и зачем они нужны

В пространстве блокчейна индексатор — это сервис, который получает необработанные данные из источника (обычно это совмещенный экземпляр полного узла для этого блокчейна) и преобразует их в формат, более полезный для конкретного приложения. Например, в случае нашего приложения для чата индексатор потребляет поток блоков Near и создает поток событий (например, полученные сообщения и запросы на контакт).

Индексаторы важны, потому что базы данных, используемые для работы с самой цепочкой блоков, как правило, не оптимизированы для выполнения тех видов запросов, которые нужны приложениям. Например, получение баланса пользователя для токена ERC-20 в Ethereum обычно выполняется путем запуска запроса через EVM, потому что это единственный способ получить информацию из типичного узла Ethereum. Это чрезвычайно дорогая операция по сравнению с поиском записи в традиционной реляционной базе данных. Таким образом, простой оптимизацией любого приложения, которому требуется быстрый доступ к балансам ERC-20, будет запуск индексатора необработанных данных Ethereum, который заполняет традиционную базу данных данными, которые ему нужны. Затем приложение будет использовать эту базу данных в качестве источника для данных, а не напрямую узел Ethereum. Вот как работает обозреватель блоков Etherscan: Etherscan запускает индексатор для заполнения базы данных, которая затем используется для заполнения полей на веб-страницах, которые обслуживает Etherscan.

Индексаторы важны не только для Ethereum, любое высокопроизводительное децентрализованное приложение на любом блокчейне должно включать индексатор где-то в своей архитектуре. Пример приложения для чата, которое мы обсуждали на Near, не является исключением, поэтому давайте углубимся в то, как реализован индексатор.

Получение необработанных данных

Индексаторы только обрабатывают необработанные данные блокчейна в формате, который может использовать соответствующее приложение; они не генерируют данные в первую очередь. Поэтому первый вопрос, на который нам нужно ответить при создании индексатора: откуда берутся данные блокчейна?

Near предоставляет несколько различных источников данных, как описано ниже.

Запуск Nearcore узла

Лучшим источником данных (с точки зрения децентрализации и безопасности) для любого блокчейна является одноранговая сеть самого блокчейна. Чтобы получить доступ к этому источнику данных, вы должны запустить узел, который понимает протокол блокчейна. В случае Near реализация узла называется Nearcore. Его исходный код открыт на GitHub. Доступна документация о том, как запустить собственный узел Nearcore. Основным барьером для входа здесь является объем необходимого для этого дискового пространства; рекомендуется иметь 1 ТБ выделенного хранилища для вашего узла, и для его синхронизации с цепочкой требуется некоторое время из-за необходимости загрузки всех этих данных.

После того, как вы настроили узел NearCore, Near предоставляет удобную структуру индексатора в Rust, которую можно использовать для создания индексаторов с NearCore в качестве источника данных. Для реального проекта это был бы лучший способ создать индексатор. Однако наш пример — всего лишь демонстрация, поэтому мы не хотим тратить часы на загрузку данных цепочки на выделенный сервер объемом 1 ТБ. К счастью, есть и другие варианты.

NEAR данные

Чтобы облегчить разработчикам запуск своих проектов, Near создали фреймворк данных в качестве альтернативного источника данных для использования индексаторами. Фреймворк данных построен поверх упомянутой выше структуры индексатора и использует в качестве источника данных непосредственно узел. Индексатор, заполняющий фреймворк данных, тривиален в том смысле, что он не обрабатывает данные для конкретного приложения, а просто передает данные для хранения в хранилище AWS S3. Однако это позволяет разработчикам получать доступ к этим данным, используя свою собственную учетную запись AWS, а затем создавать свои собственные (нетривиальные) индексаторы, используя это хранилище S3 в качестве источника данных.

Преимущество этого для разработчиков заключается в том, что этот метод работает намного быстрее. Недостатком является то, что данные поступают из централизованного источника и, следовательно, их легче повредить, чем при прямом использовании одноранговой сети.

Чтобы получить доступ к фреймворку данных, вам необходимо заплатить за ресурсы AWS, которые вы используете для доставки этих данных. Для примера с приложением для чата я не хотел заставлять людей регистрироваться в AWS и тратить деньги на запуск индексатора. Поэтому я выбрал окончательный вариант источника данных.

Публичные RPC-узлы

Последний способ получить доступ к данным блокчейна, если вы не используете свой собственный узел или не имеете доступа к чьему-то предварительно созданному хранилищу данных, — это использовать чужие узлы. Узлы RPC — это узлы в сети блокчейн, предназначенные для обслуживания запросов пользователей. У каждого блокчейна есть поставщики узлов RPC (некоторые бесплатные, некоторые платные). Список поставщиков RPC для Near можно найти здесь.

Это наименее эффективный способ доступа к данным блокчейна, поскольку для получения данных, которые обычно используют индексаторы, требуется несколько запросов RPC. Каждый запрос RPC влечет за собой сетевую задержку, из-за чего индексатор медленно реагирует на события, происходящие в цепочке. Единственным преимуществом этого подхода является то, что можно бесплатно установить демо-версию, если для цепочки есть бесплатный поставщик RPC (как в случае с Near). Следовательно, это источник данных, который использует индексатор в нашем примере.

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

Реализация индексатора

Наш индексатор построен в виде приложения tokio на Rust. Tokio — это среда Rust для написания высокопроизводительных приложений, в которых операции ввода-вывода являются основным узким местом. Наш индексатор является таким приложением, потому что фактические вычисления, которые он выполняет, выполняются чрезвычайно быстро по сравнению со временем, которое требуется для запроса данных от узлов RPC. Основные особенности tokio заключаются в том, что он использует неблокирующие асинхронные пимитивы и имеет встроенную многопоточность, позволяющую выполнять параллельное выполнение. Это в дополнение к тому, что он находится в Rust, поэтому он, естественно, имеет гарантии безопасности параллелизма и безопасности памяти, которые предоставляет Rust.

Если tokio — это сцена, на которой установлено наше приложение, то далее следуют актеры в пьесе (это приложение следует актерской модели, но я предпочитаю делать это непосредственно в tokio, а не с помощью библиотеки, такой как actix, потому что я думаю, что каналы tokio обеспечивают более строгую типизацию, чем общие сообщения, используемые в большинстве фреймворков акторов).

У индексатора есть четыре основные роли: менеджер, загрузчик блоков, загрузчик чанков и обработчик квитанций.

Менеджер

Менеджер контролирует весь индексатор. Он отвечает за делегирование работы другим процессам и указание им завершить работу, когда программа закрывается (например, в случае возникновения ошибки). Например, менеджер управляет балансировкой нагрузки загрузчиков чанков, циклически переключая их при назначении чанка для загрузки.

Загрузчик блоков

Как следует из названия, целью процесса загрузки блоков является загрузка блоков. Он периодически опрашивает Near RPC, чтобы проверить, есть ли новые блоки, и если есть, то загружает их и отправляет менеджеру. Если бы мы не использовали RPC в качестве источника данных, этот процесс был бы заменен подключением к ближайшему узлу или фреймворку данных.

Загрузчик чанков

На Near блоки не содержат данных о транзакциях. Блоки дают информацию только о том, какие новые фрагменты доступны. Причина этого в шардинге NEAR (подробнее об этом можно прочитать здесь). Поэтому нам нужны отдельные процессы для загрузки данных чанка для каждого блока. Загрузчики чанков выполняют эту роль. Наш индексатор имеет несколько экземпляров загрузчика фрагментов, что позволяет загружать фрагменты параллельно.

Если бы мы не использовали RPC в качестве источника данных, то в зависимости от того, как данные которые мы использовали изучаются, эти процессы могли бы не существовать (например, инфраструктура near, включает все блоки данных и фрагменты в одно сообщение). Но в нашем случае, когда мы используем RPC, эти процессы необходимы.

Обработчик квитанций

Чанки содержат «квитанции», которые создаются при обработке транзакции. Когда менеджер получает новый фрагмент от загрузчика, он отправляет все квитанции в процесс обработчика квитанций (у нас может быть несколько экземпляров обработчиков квитанций для параллельной обработки квитанций, точно так же, как у нас есть несколько загрузчиков чанков). Этот процесс отфильтровывает квитанции до тех, которые нам интересны, затем загружает результат выполнения для квитанций и, наконец, обрабатывает события из этих результатов. В случае с этим примером мы просто записываем события в файл (для демонстрации в реальном времени вы можете просмотреть файл с чем-то вроде команды tail -f Unix, чтобы увидеть, как приходят события), но вы можете себе представить, что производственная реализация могла бы пересылать эти события в виде push-уведомлений в мобильную версию приложения.

Примечания

Вы можете заметить в коде индексатора, что существует некоторая сложность в отправке фрагментов/квитанций с хэшем блока после блока, который включает эти фрагменты. Это причуда Near RPC, когда он хочет знать, что вы знаете о более поздних блоках для обслуживания результата выполнения. Опять же, это будет обрабатываться намного более плавно, если использовать лучший источник данных.

Мы преднамеренно не вызываем паники ни в одной из функций актора. Когда они сталкиваются с ошибкой, они регистрируют ее и отправляют сообщение о завершении работы менеджеру (и менеджер рассылает его всем остальным участникам). Это важно, потому что паника в многопоточном приложении может привести к неожиданному поведению (в общем, tokio неплохо справляется с изящным завершением работы всего приложения, но все же лучше защищаться от него).

Заключение

В этом посте мы обсудили, почему индексаторы важны для реальных децентрализованных приложений, и рассмотрели некоторые детали примера индексатора, реализованного для децентрализованного приложения чата. Как и в предыдущем посте, в коде индексатора есть упражнения, включенные в комментарии с тегом EXERCISE. Я рекомендую вам попробовать эти упражнения, если вы хотите получить практический опыт работы с кодовой базой.

О серии гайдов

Это последний пост в этой серии. В части 1 мы рассмотрели общие принципы разработки смарт-контрактов и то, как они применяются к примеру контракта для децентрализованного приложения для чата. Во второй части мы подробно рассмотрели, как использовать Near-SDK для написания смарт-контрактов для Near в Rust. Наконец, в этом посте обсуждалось, как индексаторы необходимы для интеграции данных блокчейна с компонентами вне цепочки нашего приложения.

Последний фрагмент кода, который я не рассмотрел, — это интеграционное тестирование. Интеграционное тестирование использует библиотеку для локального моделирования блокчейна и использует тот же асинхронный стиль Rust, что и индексатор. Несмотря на то, что интеграционные тесты не особенно броские или интересные, они важны для обеспечения правильной работы вашего контракта. Я рекомендую вам ознакомиться с интеграционными тестами для контракта с мессенджером и попробовать там выполнить упражнение, чтобы получить некоторый практический опыт и в этой области.

Если вам понравилась эта серия сообщений в блоге, свяжитесь с нами через Type-Driven Consulting. Мы рады предоставить услуги по разработке программного обеспечения для dapps, а также учебные материалы для ваших собственных инженеров.

 

Generate comment with AI 2 nL
2

Оставьте комментарий


To leave a comment you should to:


Пролистать наверх
Report a bug👀