Транзакция TRON в статусе pending, просрочена или не найдена: что каждое значит для перевода USDT
TRON выпускает блок каждые три секунды, так что пять минут в сети — это сто блоков. Перевод USDT, который и после этого показан как pending, находится в одном из четырёх мест: подписан, но так и не отправлен в сеть; принят нодой, но ещё не попал в блок; лежит в блоке, который ещё не стал необратимым; или окончателен в сети и ждёт, пока принимающая платформа его зачислит. По дороге обычно всплывают ещё два слова: обозреватель, который не находит хеш, и нода, называющая транзакцию просроченной. У каждого своё решение, и одно из них, применённое слишком рано, оплачивает один и тот же перевод дважды.
У каждой транзакции свой дедлайн
Интервал между блоками — константа протокола, 3,000 миллисекунд. Другое число лежит внутри транзакции. Когда кошелёк собирает перевод, он записывает время expiration в raw_data — ту часть, которая подписывается, — и каждая нода его проверяет. Транзакцию, у которой срок истёк, отклоняют, и ту, у которой срок лежит дальше чем на сутки после головного блока ноды, — тоже. Слово ноды для обоих случаев — TRANSACTION_EXPIRATION_ERROR.
Вторая пара полей привязывает транзакцию к недавнему блоку. ref_block_bytes и ref_block_hash называют блок, который сеть ещё помнит, а нода помнит последние 65,536 блоков — около 54 часов цепочки. Ссылка старше этого или взятая из тестовой сети получает вместо этого TAPOS_ERROR. Часы на обоих запускаются в момент сборки транзакции.
Четыре значения слова «pending»
Подписана, но не отправлена в сеть
Кошелёк или скрипт выплат может подписать перевод и так и не передать его ноде — после таймаута или сбоя между подписью и отправкой. Хеш у перевода всё равно есть, потому что хеш считается по собственным байтам транзакции. Это SHA-256 от raw_data, и он существует раньше, чем какая-либо нода хоть что-то увидела. Хеш в ваших логах доказывает лишь то, что перевод был собран. Обозреватель, который отвечает на него «не найдено», прав: ни у одной ноды, откуда он читает, этих байтов нет.
Принята нодой, ещё не в блоке
Нода, принявшая транзакцию, отвечает сразу и держит её в своём пуле ожидающих транзакций, пока её не включит блок. Приём и включение — разные события, и наши собственные статусы заказа их разделяют: в mode B, где мы отправляем в сеть перевод, подписанный клиентом, broadcasted значит, что нода приняла транзакцию, и пока ничего не говорит о блоке. На цепочке с блоком в три секунды это состояние короткое. Нода может принять и транзакцию, которую так никогда и не включит ни один блок, — обычно потому, что её выкинули из этого пула. Такой перевод пропадает насовсем, когда проходит его срок истечения.
В блоке, ещё не необратима
Новейшие блоки в голове цепочки ещё могут быть заменены. За головой TRON хранит второй указатель — последний солидифицированный блок, самый новый из тех, которые сеть считает необратимыми, — и нода отдаёт оба. Расстояние между ними — это то, сколько перевод лежит в блоке, не будучи окончательным. Мы один раз прочитали оба указателя с публичной полной ноды двумя вызовами, сделанными в один и тот же момент.
| Прочитано 27 октября 2026 года, 21:27 UTC | Номер блока | Время блока, UTC |
|---|---|---|
| Последний блок | 87,487,718 | 21:27:06 |
| Последний солидифицированный блок | 87,487,699 | 21:26:09 |
| Разрыв | 19 блоков | 57 секунд |
Разрыв в номерах блоков и разрыв во временных метках согласуются при трёх секундах на блок. Позиции наших заказов в mode B доходят до confirmed только после солидификации, которую наш справочник API оценивает примерно в минуту, и чтение с этим сходится. Отсюда обычный ответ на вопрос, сколько идёт перевод USDT в TRON: блок каждые три секунды и окончательность примерно через минуту после этого. Это одно чтение с одной ноды. Мы не измеряли, как разрыв ведёт себя на протяжении суток, и время наших собственных заказов от доставки до отправки и до блока.
Окончательна в сети, ещё не зачислена
Биржа или платёжный процессор зачисляет депозит после числа блоков, которое сама себе назначает. Числа ни одной платформы мы не знаем и не называем. Прежде чем спрашивать платформу, прочитайте результат, записанный рядом с переводом. Перевод может лежать в блоке и всё равно провалиться — с потраченной комиссией и без сдвинутых токенов, — и всё, что в этом месте не SUCCESS, — провал (что значит OUT_OF_ENERGY и другие способы, которыми проваливается перевод USDT). Успешный перевод в солидифицированном блоке дошёл до адреса получателя. Остаётся учёт платформы, и её поддержка может проследить его по хешу.
Что значит «просрочена» и как отправить заново
После срока истечения транзакция не может попасть ни в один блок. Любая нода её отклонит, так что повторная отправка тех же подписанных байтов лишь соберёт ещё один отказ. Выход — новая транзакция: соберите перевод заново, со свежим сроком истечения и ссылкой на недавний блок, и подпишите.
Пересборка не оплачивает дважды. Транзакция, которую ни один блок не включил, никогда не исполнялась: она не сдвинула токенов и ничего не сожгла, потому что перевод тратит свою энергию и свои TRX, пока исполняется в блоке. Когда транзакция уже в блоке, ни одна нода не исполнит её второй раз. Единственный способ отправить деньги дважды — подписать замену, пока оригинал ещё может дойти до блока. Нода судит о сроке истечения по времени своего головного блока, а оно может отставать от ваших часов, поэтому блок, созданный в последние секунды перед дедлайном, всё ещё может включить оригинал, а отстающая нода или обозреватель могут его пока не показывать. Программа, собравшая оригинал, хранит его и может прочитать raw_data.expiration — в миллисекундах от эпохи. Подождите, пока последний солидифицированный блок не окажется позже этого момента, — по нашему чтению это занимает примерно на минуту дольше, — а затем спросите ноду по хешу. Если нода всё ещё не знает транзакцию, оригинал больше не может попасть в блок, и новый перевод — единственный.
Pending и expired в заказе на энергию
Арендованная энергия добавляет вторые часы. Когда энергия для заказа доставлена, в заказе есть send_before — срок аренды без трёх секунд: 297 секунд на пятиминутной аренде и 3,597 на часовой. Окно отсчитывается от делегации, которую мы увидели подтверждённой, а аренда в сети началась на блок и один цикл опроса раньше, поэтому последние секунды окна считайте уже ушедшими (сколько держится арендованная энергия). Перевод, который попадает в блок после того, как энергия покинула кошелёк, сжигает TRX отправителя по протокольной ставке 100 sun за единицу — около 6.5 TRX за перевод на адрес, где есть USDT.
Что происходит, когда окно закрывается, зависит от того, кто отправляет. В mode A перевод отправляете в сеть вы сами. Мы следим за сетью, помечаем позицию как sent, когда ваш перевод появляется, и отправляем вебхук order.item.sent с его хешем в data.txid. Если к send_before перевод так и не появился, позиция становится expired и оплачивается, потому что энергия была доставлена.
В mode B вы отдаёте нам подписанную транзакцию, а мы отправляем её в сеть, как только энергия приходит (как работает mode B). Позиция проходит broadcasting и broadcasted до confirmed, последнего — только после солидификации. После send_before мы никогда не отправляем: позиция, которую мы не успели отправить вовремя, закрывается как failed с error: broadcast_window_missed, а резерв возвращается целиком. Окно там ограничено ещё и собственным сроком истечения транзакции за вычетом запаса на отправку, так что транзакция, доходящая до нас с малым остатком жизни, получает короткое окно.
Mode B проверяет срок истечения и на входе. Когда мы получаем транзакцию, у неё должно оставаться не меньше 180 секунд жизни и не больше 23 часов 59 минут. Слишком далёкий дедлайн возвращается как 400 invalid_request с details.reason, равным expiration_too_far, а слишком малый остаток жизни — как тот же 400, только с одним details.index. Оба ответа приходят до того, как куплена какая-либо энергия.
Что проверять и в каком порядке
- Принимала ли транзакцию хоть одна нода. Ответ ноды хранит программа, которая её отправляла, либо не хранит ничего, потому что отправки не было. Сам по себе хеш ничего не решает, ведь он существует до любой отправки.
- Находится ли хеш — в обозревателе или у ноды, которую спросили по хешу. Если не находится, а срок истечения ещё впереди, отправьте в сеть те же подписанные байты снова. Если последний солидифицированный блок уже за сроком истечения, а хеш по-прежнему не находится, соберите новую транзакцию и подпишите её.
- Результат рядом с найденным хешем.
SUCCESSзначит, что перевод прошёл. Всё остальное значит, что он исполнился и провалился, а комиссия уже потрачена. - Солидифицирован ли его блок. По нашему чтению самый новый необратимый блок отставал от головного примерно на минуту, так что успешный перевод моложе этого ещё закрепляется.
- Собственное число блоков у принимающей платформы, а после него — её поддержка, с хешем.
- В нашем заказе — позиция:
sentс еёtxidв mode A,confirmedв mode B. Если окно закрылось раньше, mode A показываетexpired, а mode B —failedсerror: broadcast_window_missed.expiredв mode B означает, что нода отклонила транзакцию из-за неё самой или что транзакция откатилась в блоке, и она оплачивается.
Посмотрите, как позиция стоит в ready, а затем становится expired, когда на кону ничего настоящего: в песочнице есть получатель, перевод которого так и не приходит →