
Как обеспечить включение транзакций в Solana
Содержание
Конкурируйте за каждую сделку с помощью Sender — сервиса отправки транзакций со сверхнизкой задержкой. Изучите документацию, чтобы начать включать транзакции и получить конкурентное преимущество.
В последнее время Solana сталкивается с беспрецедентным объемом трафика, из-за чего многие транзакции завершаются с ошибкой или отбрасываются.
Количество транзакций Solana в секунду (TPS) составляет около 1 000 и более транзакций без учета голосований. Quinn (реализация сетевого уровня QUIC на Rust) не всегда эффективно обрабатывает спам при высоком спросе. Поэтому лидерам блоков иногда приходится выборочно разрывать соединения. Среди всех неуспешных транзакций примерно 8% были инициированы реальными пользователями, а остальные представляли собой произвольные транзакции ботов.
Чтобы работать с неуспешными транзакциями, важно понимать, как они отправляются и обрабатываются в Solana. В этой статье мы рассмотрим возможные причины сбоев и рекомендации по увеличению пропускной способности транзакций. Предполагается, что читатель знаком с основами модели программирования Solana, а также с созданием и отправкой транзакций.
Транзакции
Выполнение программы начинается с транзакции, отправленной в кластер. Транзакция содержит:
- Массив всех аккаунтов, данные которых она намерена прочитать или изменить
- Одну или несколько инструкций (то есть минимальных единиц выполнения)
- Недавний хеш блока
- Одну или несколько подписей
Среда выполнения последовательно и атомарно обработает все инструкции транзакции. Если какая-либо часть инструкции завершится с ошибкой, вся транзакция завершится с ошибкой.
Что такое хеш блока?
«Хеш блока» — это последний хеш Proof of History (PoH) для слота. Поскольку Solana использует PoH как доверенные часы, недавний хеш блока транзакции можно считать временной меткой. Хеш блока предотвращает дублирование и задает срок действия транзакций. Если хеш блока транзакции слишком старый, она будет отклонена. Максимальный возраст хеша блока составляет 150 блоков, или ~1 минуту 19 секунд.
Как отправляются транзакции?
Solana поддерживает группа валидаторов, которые проверяют транзакции, добавляемые в реестр. Из этой группы выбирается валидатор-лидер, который добавляет записи в реестр. Записью в реестре может быть тик или запись транзакции. Реестр содержит список записей с транзакциями, подписанными клиентами. Концептуально реестр восходит к генезис-блоку. Однако фактический реестр валидатора может содержать только новые блоки, чтобы сократить объем хранилища: старые блоки по архитектуре не нужны для проверки будущих.
Валидатор-лидер может создать только один блок за слот, а хеш блока служит уникальным идентификатором каждого блока. Это хеш всех записей блока, включая хеш предыдущего блока. Расписание лидеров определяется перед каждой эпохой, обычно примерно за два дня. Оно указывает, какой валидатор будет текущим лидером в каждый момент времени. После создания транзакция пересылается текущему и следующему валидатору-лидеру.
Транзакции можно отправлять лидеру следующими способами:
- RPC-сервер: RPC-провайдер может отправить транзакцию через метод JSON-RPC sendTransaction. Получившая ее RPC-нода будет пытаться отправлять ее как UDP-пакет текущему и следующему лидеру каждые две секунды, пока транзакция не будет финализирована или пока не истечет срок действия ее хеша блока (через 150 блоков, или ~1 минуту 19 секунд). До этого момента записи о транзакции нет нигде, кроме клиента и ретранслирующих RPC-нод.
- Клиент TPU: Клиент TPU просто отправляет транзакцию. Клиентское ПО должно самостоятельно управлять повторной отправкой и пересылкой лидеру.
Чтобы использовать метод sendTransaction, необходимо передать объект транзакции, закодированный в виде строки. Другие необязательные параметры:
encoding: Кодировка данных транзакции — base58 или base64.skipPreflight: Предварительные проверки включают проверку подписей транзакции и ее симуляцию относительно банковского слота, заданного уровнем подтверждения предварительной проверки. Если предварительная проверка завершится с ошибкой, будет возвращена ошибка. По умолчанию этот параметр имеет значение false, то есть предварительные проверки не пропускаются.preflightCommitment: Задает уровень подтверждения для предварительных проверок. По умолчанию используется уровень finalized, но его можно изменить, указав строку. Рекомендуется задавать одинаковые уровни подтверждения и подтверждения предварительной проверки, чтобы избежать непредсказуемого поведения.maxRetries: ПараметрmaxRetriesопределяет максимальное количество повторных попыток RPC-ноды отправить транзакцию лидеру. Если параметр не указан, RPC-нода будет повторять попытки до финализации транзакции или истечения срока действия хеша блока.minContextSlot: ПараметрminContextSlotзадает минимальный слот для выполнения предварительных проверок транзакции.
Как обрабатываются транзакции?
Модуль обработки транзакций (TPU) валидатора получает транзакцию, проверяет подпись, выполняет ее и передает другим валидаторам сети.
TPU обрабатывает транзакции в пять отдельных этапов:
Этап получения
Этап получения отвечает за прием транзакций. Он распределяет входящие транзакции по трем портам:
tpu: обрабатывает обычные транзакции, например переводы токенов, выпуск NFT и инструкции программtpu_vote: обрабатывает исключительно транзакции голосованияtpu_forwards: если текущий лидер не может обработать все транзакции, он пересылает необработанные пакеты следующему лидеру
Пакеты объединяются в группы по 128 и передаются на этап SigVerify.
Этап SigVerify
Этап SigVerify проверяет подписи пакетов и отбрасывает пакеты, не прошедшие проверку. Голоса и обычные пакеты проходят по двум отдельным конвейерам. С точки зрения ПО полученные пакеты содержат некоторые метаданные, но на этом этапе еще неясно, являются ли они транзакциями.
Если установлен GPU, он будет использоваться для проверки подписей. Кроме того, при высокой нагрузке применяется логика обработки избыточного количества пакетов, которая отбрасывает их с учетом IP-адресов.
Банковский этап
Этот этап отвечает за фильтрацию и обработку транзакций. Сейчас он состоит из шести независимых рабочих потоков: двух для голосований и четырех для остальных транзакций. Обычные транзакции добавляются в потоки без голосований. Каждый поток имеет локальный буфер, способный хранить до 64 неконфликтующих транзакций в очереди с приоритетом. Затем эти транзакции параллельно обрабатываются благодаря Sealevel. Подробнее о банковском этапе можно узнать из этого видео.
Сервис Proof of History
Модуль сервиса PoH регистрирует прохождение тиков. Каждый тик представляет единицу времени, а один слот содержит 64 тика. Хеш генерируется многократно, пока с банковского этапа не поступит запись:
next_hash = hash(prev_hash, hash(transaction_ids))
Затем эти записи преобразуются в элементы реестра и передаются в сеть на этапе трансляции.
Этап трансляции
Записи сервиса PoH преобразуются в шарды — минимальные единицы блока, — а затем отправляются остальной сети с помощью технологии распространения блоков Turbine. На высоком уровне Turbine делит блок на меньшие части и распространяет их через иерархическую структуру нод. Нодам не нужно связываться со всеми остальными нодами. Достаточно обмениваться данными с несколькими выбранными нодами. Подробнее о Turbine и принципах его работы можно узнать из этой статьи.
Почему транзакции завершаются с ошибкой?
Помимо неверных инструкций и пользовательских ошибок программ, транзакции могут завершаться с ошибкой по следующим причинам:
Потери в сети
Сетевой уровень может отбросить транзакцию еще до того, как лидер начнет ее обрабатывать. Самая простая причина — потеря UDP-пакета. Другая причина связана с fetch stage в TPU. При высокой нагрузке валидаторы могут не справляться с количеством транзакций. Они могут пересылать дополнительные транзакции на порт tpu_forward следующего валидатора. Однако объем пересылаемых данных ограничен, а каждый пакет может пройти только один переход между валидаторами. Поэтому транзакции, полученные через порт tpu_forwards, не пересылаются другим валидаторам. Если очередь ожидающих повторной отправки превышает 10 000 транзакций, новые транзакции будут отброшены.
Устаревший или неверный хеш блока
Каждая транзакция содержит «недавний хеш блока», который служит временной меткой для часов Proof of History (PoH). Этот хеш помогает валидаторам не обрабатывать одну транзакцию дважды и отслеживать время и порядок обработки транзакций. Во время обработки валидатор отклонит транзакцию с недействительным хешем блока.
Истечение срока действия хеша блока
Срок действия хеша блока транзакции истекает, когда он перестает считаться достаточно «недавним». Для обработки транзакции валидаторы Solana ищут в блоке номер слота соответствующего хеша. Если валидатор не может найти номер слота для хеша блока или найденный номер слота более чем на 151 слот меньше номера слота обрабатываемого блока, транзакция будет отклонена. По умолчанию срок действия транзакции Solana истекает, если она не включена в блок за определенное время (~1 минуту 19 секунд).
Отстающие RPC-ноды
При отправке транзакции через RPC часть пула RPC может опережать остальные ноды. Это может вызвать проблемы, когда нодам пула нужно взаимодействовать. Например, если recentBlockhash транзакции запрашивается у опережающей части пула, а затем отправляется в отстающую часть, ноды не распознают более новый хеш блока и отклонят транзакцию. Это можно обнаружить при отправке транзакции, включив предварительные проверки для sendTransaction.
Временные форки сети
Временные форки сети также могут привести к отбрасыванию транзакций. Если валидатор медленно воспроизводит свои блоки на банковском этапе, он может создать миноритарный форк. Когда клиент формирует транзакцию, она может ссылаться на recentBlockhash, существующий только в миноритарном форке. После отправки транзакции кластер может отказаться от этого форка еще до ее обработки. В таком случае транзакция отбрасывается, поскольку хеш блока не найден.
Как обеспечить включение транзакций?
Чтобы диагностировать проблемы с подтверждением, важно понимать механизм истечения срока действия транзакций. Выполните следующие действия, чтобы повысить вероятность успешной обработки:
Кратко
- Получайте последний хеш блока с уровнем подтверждения «
confirmed» или «finalized» - Установите
skipPreflightвtrue - Оптимизируйте запрашиваемое количество вычислительных единиц
- Добавьте и динамически рассчитывайте приоритетные комиссии
- Установите
maxRetriesв0и добавьте собственную логику повторной отправки транзакций. - Рассмотрите соединения с учетом стейка
- Если транзакция не зависит от времени, используйте долговременные nonce
Хеш блока
Время на обработку транзакции валидатором ограничено. Если срок действия связанного с транзакцией хеша блока истечет до ее обработки, транзакция будет отменена. Чтобы транзакция прошла, отправляйте ее с недавним хешем блока. Если его срок действия истек до обработки, можно повторить транзакцию с новым хешем блока. Это можно сделать двумя способами:
1. Задайте новый уровень подтверждения:
Для получения последнего хеша блока рекомендуется использовать метод RPC API getLatestBlockhash. По умолчанию этот метод использует уровень подтверждения finalized и возвращает хеш последнего финализированного блока. Такой уровень означает, что над блоком добавлен как минимум 31 подтвержденный блок. Это исключает риск использования хеша из отброшенного форка. Однако разница между последним подтвержденным и финализированным блоками обычно составляет не менее 32 слотов. Из-за этого срок действия транзакции сокращается примерно на 13 секунд, а при нестабильной работе кластера — еще сильнее.
Уровень подтверждения хеша блока можно изменить с помощью соответствующего параметра. Для RPC-запросов рекомендуется уровень confirmed: обычно он отстает от processed всего на несколько слотов и с малой вероятностью относится к отброшенному форку. Уровень processed получает более новый хеш блока, чем другие уровни, но использовать его не рекомендуется: примерно 5% блоков не финализируются кластером из-за форков в протоколе Solana. Если транзакция использует хеш блока из отброшенного форка, ни один блок финализированной цепочки не будет считать его недавним.
2. Часто запрашивайте новые недавние хеши блоков:
Добавьте скрипт, который часто (каждые 60 секунд) получает и сохраняет последний хеш блока с помощью метода getLatestBlockhash. Тогда при запуске транзакции пользователем у приложения уже будет свежий хеш блока. Кошелькам также следует часто запрашивать новые хеши и заменять недавний хеш транзакции непосредственно перед подписанием, чтобы он был максимально свежим.
Пропуск предварительной проверки
Перед отправкой транзакции выполняются следующие предварительные проверки:
- Проверяются подписи транзакции.
- Транзакция симулируется относительно банковского слота, заданного уровнем подтверждения предварительной проверки. При сбое возвращается ошибка.
Если выбранный для симуляции блок старше блока, хеш которого используется транзакцией, симуляция завершится с печально известной ошибкой «blockhash not found».
Если вы уверены, что подпись транзакции действительна и других ошибок нет, можно пропустить предварительную проверку. Даже при использовании параметра skipPreflight всегда задавайте для preflightCommitment тот же уровень подтверждения, который использовался для получения хеша блока транзакции, как в запросах sendTransaction, так и в simulateTransaction.
Вычислительные единицы
После подтверждения транзакция использует часть доступных в блоке вычислительных единиц (CU). Сейчас общий вычислительный лимит блока составляет 48 млн CU. Разработчики могут задавать бюджет вычислительных единиц для транзакций. Если бюджет не задан, используется значение по умолчанию — 200 000. Многие транзакции расходуют не весь бюджет CU, поскольку штрафа за запрос избыточного бюджета нет. Однако изначальный запрос слишком большого количества вычислительных единиц мешает эффективно планировать транзакции: до выполнения транзакции планировщик не знает, сколько вычислительных ресурсов останется в блоке. Поэтому разработчикам следует точнее подбирать запросы CU в соответствии с требованиями транзакции. Оптимизировать бюджет вычислительных единиц поможет это руководство. В предстоящем обновлении клиента Solana v1.18 транзакции, которым требуется меньше вычислительных единиц, получат более высокий приоритет.
Оптимизация использования вычислительных единиц (CU) дает следующие преимущества:
- У небольшой транзакции больше шансов попасть в блок.
- Более дешевые инструкции упрощают компонуемость программы.
- Снижается общая загрузка блока, что позволяет включить в него больше транзакций.
Добавьте приоритетные комиссии
Приоритетные комиссии можно добавлять к базовой комиссии транзакции, чтобы валидаторы обрабатывали ее в приоритетном порядке. Эти комиссии рассчитываются в микролампортах за вычислительную единицу (то есть в малых долях SOL). Они делают включение транзакций в сетевые блоки экономически привлекательным для нод валидаторов.
Однако важно не переплачивать за приоритет. Комиссия выше обычной не увеличит вероятность успеха транзакции. Поэтому рекомендуется рассчитывать приоритетные комиссии динамически. Так вы сохраните конкурентоспособность и избежите переплаты. Интеграция проста: изучите официальную документацию о приоритетных комиссиях или используйте готовый Helius API.
Реализуйте надежную логику повторных попыток
При перегрузке сети добавьте в код собственную логику обработки сбоев и повторной отправки транзакций вручную. Для этого при отправке транзакции через sendTransaction установите параметр maxRetries в 0. Повторно отправлять транзакции можно разными способами:
- Опрашивайте
transaction statusс разными уровнями подтверждения и продолжайте использовать одну и ту же подписанную транзакцию до ее подтверждения. Чтобы избежать спама, применяйте экспоненциальную задержку. Также можно отправлять транзакции с постоянным интервалом до истечения тайм-аута. - Сохраните
lastValidBlockHeight, полученный изgetLatestBlockhash method. Затем опрашивайте кластер о высоте блока и вручную повторите транзакцию, когда текущая высота превыситlastValidBlockHeight. При опросе черезgetLatestBlockhashрекомендуется указывать нужный уровень подтверждения. Уровень confirmed (за блок проголосовали) или finalized (~30 блоков после confirmed) позволит избежать получения хеша блока из миноритарного форка.
Соединения с учетом стейка
Пропускная способность сети лидера ограничена. Чтобы эффективно ее использовать и не принимать транзакции вслепую в порядке поступления без учета источника, необходимо взвешивание по стейку. Solana работает как сеть proof-of-stake, поэтому логично расширить применение такого взвешивания для повышения качества обслуживания транзакций. Нода с долей стейка 0,5% может отправить лидеру как минимум 0,5% пакетов. Остальная сеть — как и любая комбинация оставшегося стейка — не сможет полностью вытеснить эти пакеты. Этот механизм называется качеством обслуживания с учетом стейка (SWQoS).
Helius предоставляет соединения с учетом стейка на платных тарифах. Подробнее см. в документации: Отправка транзакций в Solana.
Долговременные nonce
Долговременные nonce позволяют создать и подписать транзакцию, которую можно отправить в любой момент в будущем. Они применяются в таких сценариях, как кастодиальные сервисы, которым требуется больше времени для создания подписи транзакции. Если транзакция не зависит от времени, этот метод позволяет обойти короткий срок действия ее recentBlockhash.
Чтобы начать использовать долговременные транзакции, необходимо отправить транзакцию с инструкциями для создания специального ончейн-аккаунта «nonce» и сохранения в нем «долговременного хеша блока». Аккаунт Nonce хранит значение nonce. Пока аккаунт nonce не использован, долговременную транзакцию можно создать при соблюдении двух правил:
- Список инструкций должен начинаться с системной инструкции «advance nonce», которая загружает ончейн-аккаунт nonce.
- Хеш блока транзакции должен совпадать с долговременным хешем блока, сохраненным в ончейн-аккаунте nonce.
О реализации долговременных nonce через CLI и Web3.js читайте в этой статье.
Подход Helius к отправке транзакций
Запрос sendTransaction автоматически направляется к ближайшей RPC-ноде. Если maxRetries не указан, она повторяет отправку транзакции каждые 2 секунды до истечения срока действия хеша блока. Рекомендуется установить maxRetries в 0 и самостоятельно повторно отправлять транзакцию каждые 2 секунды до подтверждения.
Чтобы справиться с текущей перегрузкой сети, мы круглосуточно работаем над повышением вероятности включения транзакций наших пользователей. Мы снизили лимит запросов sendTransaction. Эта мера помогает контролировать перегрузку и предотвращать спам валидатора. Лимиты доступны здесь.
Кроме того, качественный трафик платных тарифов направляется через соединения с учетом стейка. Для них мы используем наш валидатор. Трафик считается качественным, если общая приоритетная комиссия составляет не менее 10 000 лампортов (медианное значение по кластеру).
Мы требуем, чтобы общая комиссия превышала 10 000 лампортов, поскольку хотим предоставлять валидаторам качественный трафик. Валидаторы начали ограничивать (а иногда и полностью блокировать) источники трафика, которые отправляют транзакции с низкой комиссией.
Соединения с учетом стейка могут значительно повысить вероятность включения транзакции. Подробнее о настройке приоритетных комиссий при создании транзакции читайте в этой статье.
Наши рекомендации
Начинающие пользователи и пользователи среднего уровня
Рекомендуется установить skipPreflight в false. Предварительные проверки включают проверку подписей транзакции и ее симуляцию относительно банковского слота, заданного уровнем подтверждения предварительной проверки. Если предварительная проверка завершится с ошибкой, будет возвращена ошибка. Без нее транзакции могут быть отброшены из-за неверной конфигурации.
Опытные пользователи
Опытным пользователям, которым нужна минимально возможная задержка, следует установить skipPreflight в true. Однако в этом случае вы сами отвечаете за правильную настройку транзакции.
Заключение
Чтобы успешно включать транзакции в сеть Solana во время перегрузки, необходимо хорошо понимать архитектуру сети и механизмы обработки транзакций. Знание ключевых принципов — роли хеша блока в обеспечении уникальности и своевременности транзакции, процесса отправки через RPC-серверы или клиенты TPU, а также важности правильной настройки параметров, таких как skipPreflight, preflightCommitment и maxRetries, — позволяет значительно повысить производительность транзакций. Собственный механизм повторных попыток и соединения с учетом стейка помогут увеличить долю успешных транзакций.
Кроме того, важно учитывать текущие ограничения сети и непрерывную работу Anza над их устранением, примером которой служит предстоящий выпуск клиента v1.18. По мере развития и масштабирования сети для эффективного взаимодействия с ней особенно важно следить за изменениями и адаптироваться.
Если вам нужна помощь или поддержка, свяжитесь с нами в Discord. Укажите адрес электронной почты ниже, чтобы не пропускать новости Solana. Хотите узнать больше? Читайте свежие статьи в блоге Helius и продолжайте знакомство с Solana уже сегодня.
Ресурсы
- Транзакции — Solana Cookbook
- Оптимизация блоков в Solana
- Основы валидаторов Solana: обработка транзакций от Jito Labs
- Модуль обработки транзакций в валидаторе Solana
- Планировщик Solana
- Подтверждение транзакций в Solana
- Повторная отправка транзакций в Solana
- Как использовать приоритетные комиссии
- Как оптимизировать вычисления
- Приоритетные комиссии и вычислительные единицы
- Долговременные nonce транзакций
- Долговременное и офлайн-подписание транзакций с помощью nonce
- Транзакции Solana: долговременные nonce от Helius
Похожие статьи
Подпишитесь на Helius
Следите за новостями разработки на Solana и получайте уведомления о наших публикациях


