Держите соединения прогретыми
Самая медленная часть холодной отправки заключается в установке соединения, а не в Apex. Открывайте соединение заранее и держите его открытым.- QUIC
- HTTP
Клиент на Rust делает это за вас. Он отправляет пинг каждую секунду при 30-секундном тайм-ауте простоя эндпоинта Apex, возобновляет сессию с 0-RTT и выполняет новое рукопожатие в фоне, как только замечен обрыв.
- Создавайте один клиент на процесс и эндпоинт Apex при запуске, а не по одному на отправку. Потоки мультиплексируются в одном соединении.
Cloneклиента обходится дёшево, и клоны разделяют соединение. Передавайте клон каждой задаче.- Экспортируйте
health(),reconnects_total()иzero_rtt_resumptions_total()в свои метрики, чтобы видеть обрывы.
Отправляйте поближе к эндпоинту Apex
Выберите эндпоинт Apex с наименьшим временем обмена от вашего отправителя и измеряйте его с помощью/ping, а не угадывайте по карте. Каждый эндпоинт Apex направляет транзакции клиентам валидаторов, ближайшим к следующим лидерам, поэтому ваша задача состоит только в том, чтобы быстро достичь эндпоинта.
Если вы работаете в нескольких регионах, отправляйте из каждого региона на свой ближайший эндпоинт. Отправлять одну и ту же подписанную транзакцию на несколько эндпоинтов Apex безопасно, поскольку сеть исполняет подпись один раз, но каждая копия учитывается в вашем лимите запросов.
Соизмеряйте чаевые со сделкой
Минимум (0.001 SOL на стандартном тарифе) задаёт порог для принятия транзакции, а не рекомендацию для каждой транзакции.- Чаевые оплачивают вашу ставку Jito: чаевые за вычетом базовой комиссии 5,000 lamports. В блоке с высокой конкуренцией ставка на уровне минимума может проиграть аукцион. Пути с весом по стейку и TPU всё равно работают, но вы теряете один из трёх своих путей.
- Вы платите чаевые, только когда транзакция попала в блок, поэтому более крупные чаевые на важной транзакции ничего не стоят при промахе.
- Масштабируйте чаевые вместе с ценностью попадания в блок. Рутинный перевод может оставаться на минимуме. Для конкурентной сделки чаевые должны быть пропорциональны тому, сколько для вас стоит попасть в блок первым.
Задавайте вычислительный бюджет в каждой транзакции
Apex доставляет вас к лидеру. Дальше планировщик лидера упорядочивает транзакции по приоритетной комиссии. Без вычислительного бюджета вы конкурируете в самом конце очереди.- Лимит вычислительных единиц: просимулируйте транзакцию на своём обычном RPC, возьмите число потреблённых единиц и добавьте запас примерно от 10 до 20 процентов. Лимит по умолчанию, когда вы его не задаёте, намного выше, чем нужно большинству транзакций, и это впустую расходует ваш приоритет.
- Цена вычислительной единицы: следите за рынком для аккаунтов, в которые вы пишете.
getRecentPrioritizationFeesс вашими записываемыми аккаунтами даёт разумный стартовый сигнал. - Ставьте инструкции ComputeBudget первыми в транзакции.
- Это относится к транзакциям legacy и v0. Транзакция v1 задаёт бюджет в
TransactionConfigсообщения, и инструкции ComputeBudget там не действуют.
Подтверждайте каждую отправку
Подпись в ответе означает принята, а не попала в блок.- Опрашивайте
getSignatureStatusesна Solana RPC или подпишитесь черезsignatureSubscribe, пока статус не станетconfirmedилиfinalized. - Проверяйте поле
err. Транзакция может попасть в блок и всё равно завершиться ошибкой в блокчейне. Чаевые являются частью транзакции, поэтому транзакция, которая завершилась ошибкой при исполнении, откатывает и перевод чаевых, и вы платите только сетевую комиссию. - Прекращайте ждать, когда истекает blockhash. Отслеживайте
lastValidBlockHeightизgetLatestBlockhash. Как только блокчейн его прошёл, транзакция уже никогда не попадёт в блок.
rpc::SolanaRpc::confirm(signature, timeout) выполняет опрос и возвращает слот, None при тайм-ауте или ошибку, если транзакция завершилась ошибкой в блокчейне.
Дайте Apex повторять, затем соберите заново
Apex повторно отправляет принятую транзакцию, пока она не попадёт в блок или пока не истечёт её blockhash. Опирайтесь на это, а не обходите:- Не отправляйте принятую транзакцию повторно в цикле. Это ничего не даёт и расходует ваш лимит запросов.
- Используйте свежий blockhash. Получайте его с уровнем подтверждения
confirmedнепосредственно перед подписанием. Blockhash, которому уже минута, оставляет Apex очень мало времени для работы. - Когда blockhash истёк, соберите транзакцию заново. Получите новый blockhash, подпишите заново и отправьте новую транзакцию. Перед этим убедитесь, что первая уже не может попасть в блок, либо используйте логику, которая безопасна при исполнении обеих.
- Повторяйте при транспортных сбоях. Если отправка не удалась до получения ответа, отправьте те же байты ещё раз. Эндпоинт отсеивает дубликаты по подписи.
- Делайте паузу при
rate limitedиbusy. См. какие ошибки повторять.
maxRetries ограничивает число повторных отправок транзакции со стороны Apex. Оставьте значение по умолчанию, если только вы не хотите, чтобы транзакция сдавалась раньше, например котировка, которая устаревает через секунду-две.
Интегрируйтесь с обратной связью, работайте без неё
На время интеграции используйте транспорт, который сообщает, почему транзакция была отклонена: JSON-RPC, HTTP-маршруты или двунаправленный QUIC-поток. Когда ваши транзакции начнут стабильно приниматься, переведите горячие пути на однонаправленные QUIC-потоки, а двунаправленную или HTTP-отправку оставьте для отладки.Делайте каждую транзакцию уникальной
Apex отсеивает дубликаты по подписи. Две транзакции с одинаковыми инструкциями, подписантами и blockhash имеют одну и ту же подпись и считаются одной. Если вы намерены отправить одно и то же действие дважды, измените что-нибудь, например memo или цену вычислительной единицы на один микро-lamport.Чек-лист
Перед запуском в продакшен
Перед запуском в продакшен
- Одни чаевые на транзакцию, верхний уровень, оплачены подписантом, аккаунт для чаевых в статических ключах
- Аккаунт для чаевых выбирается случайно для каждой транзакции из закэшированного списка
getTipAccounts - Лимит и цена вычислительных единиц в каждой транзакции
- Blockhash получен непосредственно перед подписанием
- Соединение открыто и прогрето при запуске
- Каждая отправка подтверждена, поле
errпроверено - Пауза при rate limited и busy, сборка заново при invalid
- API-ключ в переменной окружения или хранилище секретов, а не в коде