Жизненный цикл коллатора
Менеджер коллации решает, какой шаг предпринять дальше (статья «Менеджер коллации»), но сами блоки собирает не он, а коллатор — свой на каждый шард и на мастерчейн. Коллатор живёт дольше одного блока: он хранит рабочее состояние между попытками, кэш якорей мемпула и текущую сессию коллации. Раз за разом он получает от менеджера команду попробовать собрать блок или возобновить работу после того, как менеджер обработает обновление состояния мастерчейна. Эта статья описывает коллатор изнутри одной такой попытки: что он хранит между вызовами, как решает, собирать блок сразу или сначала подтянуть якорь, и что происходит, если менеджер просит его остановиться раньше времени.
Интерфейс коллатора
Менеджер управляет коллатором через набор из двух геттеров (короткий идентификатор следующего блока и шард) и четырёх асинхронных методов:
initинициализирует коллатор и сразу пробует собрать первый блок;resume_collationобновляет данные мастерчейна и запускает очередную попытку;try_collate— сама очередная попытка с проверкой, есть ли вообще что коллировать;do_collate— безусловная сборка блока по прямой команде менеджера, без всяких проверок наличия сообщений.
Все четыре метода забирают коллатор целиком и возвращают его вместе с результатом попытки. Коллатор физически переезжает в задачу и возвращается только по её завершении, поэтому параллельно на один шард может выполняться не больше одной операции коллатора — это гарантия типа, а не блокировки.
Коллатор создаёт фабрика, которая только заполняет поля, ничего не коллируя: заводит пустой кэш якорей и пустое отложенное рабочее состояние, вычисляет короткий идентификатор первого блока и создаёт кэш сериализатора блока с запасом на 100 000 ячеек. Первая попытка коллации запускается только последующим вызовом init.
При создании коллатор получает и хранит всё, что ему нужно на всё время жизни: три адаптера (внутренняя очередь сообщений, адаптер мемпула, адаптер состояния узла), локальная конфигурация коллации, сессия коллации, идентификатор нулевого состояния, шард, список идентификаторов предыдущих блоков, необязательное переопределение конфигурации мемпула и канал отмены коллации, общий для всех коллаторов одного шарда за всё время их жизни. Коллатор ждёт сигнал этого канала в нескольких точках своей работы и не может различить, по какой причине менеджер его останавливает (раздел «Отмена коллации» ниже).
Рабочее состояние
Каждая попытка коллации начинается с рабочего состояния коллатора: это и есть её вход целиком. Структура хранит девять полей:
- короткий идентификатор блока, который будет собран;
- данные последнего известного мастерчейна;
- onchain-конфигурацию коллации, вычитанную из этих данных;
- накопленные единицы работы с момента импорта последнего якоря;
- время, которое заняло возобновление коллации после мастер-блока (оно идёт в расчёт цены единицы работы);
- данные предыдущего блока шарда;
- дерево обращений к ячейкам предыдущего состояния;
- тройственный признак наличия необработанных сообщений, где отсутствие ответа означает «ещё не проверяли»;
- состояние читателей сообщений — позиции обработки, буферы, открытые диапазоны чтения.
При первичной сборке рабочего состояния накопленные единицы работы берутся не из памяти процесса, а из предыдущего состояния шарда, состояние читателей строится из его позиции обработки, время возобновления коллации считается нулевым, а признак необработанных сообщений — «не проверяли».
Данные предыдущего блока шарда коллатор держит в двух экземплярах: чистом, каким он загружен из хранилища, и наблюдаемом — копии, для которой ведётся дерево обращений к ячейкам. Всё, что коллатор читает по ходу сборки блока (аккаунты, время цепочки, логическое время, накопленные комиссии валидаторов, накопленные единицы работы), читается из наблюдаемого состояния и потому попадает в дерево обращений. Позиции обработки, наоборот, читаются из чистого состояния. По дереву обращений в меркл-обновление попадают только реально затронутые ячейки: отслеживание не касается корня чистого состояния, поэтому он остаётся исходным корнем меркл-обновления. Предыдущий блок шарда для рабочего состояния — всегда один.
Отложенное рабочее состояние
Коллатор не строит рабочее состояние заново перед каждой попыткой: он держит его в отложенном виде, с двумя слотами — уже готовое состояние, оставшееся от предыдущей попытки, и фоновая задача, которая ещё строит состояние после только что собранного блока. Попытка коллации начинается с ожидания этого слота: сначала забирается готовое состояние, а если его нет, попытка дожидается фоновой задачи. Если пусты оба слота, коллатор возвращает ошибку.
Если блок собран, в слот фоновой задачи кладётся построение нового состояния уже от собранного блока. Если попытка вернула результат, не дойдя до сборки, то же самое рабочее состояние без изменений возвращается в слот готового. Так заканчиваются все исходы «пропущено» в попытке коллации, отмена на любой из точек импорта якорей и прерывание при импорте якорей. Если же сборка блока уже началась и была отменена или прервана внутри неё, рабочее состояние разбирается на части ещё на входе в сборку — оно не возвращается ни в один из слотов, и коллатор остаётся без рабочего состояния вовсе. Продолжить в этом случае можно только через возобновление коллации с перезапуском, которое строит рабочее состояние заново из хранилища.
Смысл конструкции — вынести построение следующего рабочего состояния за пределы критического пути: пока менеджер решает, что делать дальше, состояние для следующей попытки уже строится в фоне. Построение асимметрично для мастерчейна и рабочей цепочки. Для мастер-блока задача сохранения нового состояния дожидается прямо здесь же, и рабочее состояние строится по тому, что вернуло хранилище. Состояние мастерчейна служит опорным для многих других подсистем узла, поэтому коллатор продолжает работу именно с тем экземпляром, который лёг в хранилище. Для блока шарда задача сохранения уходит в фоновый канал коллатора, а рабочее состояние строится сразу по наблюдаемому состоянию, оставшемуся в памяти после коллации: лишнее ожидание записи только удлинило бы паузу между блоками шарда.
Инициализация и возобновление коллации
Метод init, который менеджер вызывает при создании коллатора, сначала параллельно загружает состояния предыдущих блоков и их дифы очереди и строит по ним рабочее состояние с деревом обращений. Затем коллатор разбирается с генезисом мемпула (следующий раздел) и при необходимости доимпортирует начальные якоря. И только после этого запускается первая попытка коллации. Если импорт начальных якорей на третьем шаге был отменён менеджером или прерван по внутренней причине, инициализация не проваливается с ошибкой: рабочее состояние откладывается, а наружу уходит соответствующий результат — коллатор остаётся живым и готовым к следующему возобновлению коллации.
resume_collation менеджер вызывает по итогам обработки обновления состояния мастерчейна, и при каждом таком возобновлении коллатор первым делом заменяет сессию коллации на переданную — ещё до разбора режима возобновления. Так в собранном блоке оказывается подмножество валидаторов, актуальное на момент сборки: из сессии в заголовок блока попадают короткий хеш этого подмножества и номер сессии. Тройка «шард, номер сессии, короткий хеш» прикладывается к результату коллации: по ней менеджер сверяет, к какой сессии относится кандидат. Сама сессия коллации хранит шард, раунд смены набора валидаторов, номер сессии, подмножество валидаторов с весами и, если узел в него входит, ключевую пару для подписи. Пара «номер сессии и раунд смены набора» отдельно образует идентификатор сессии верификации.
Дальше resume_collation расходится по флагу перезапуска, который менеджер выставляет, когда продолжать с прежнего места нельзя: после синхронизации на применённый мастер-блок и после расхождения собранного блока с блоком из сети (раздел «Расхождение блока» ниже). Без перезапуска коллатор дожидается отложенного рабочего состояния и обновляет его на месте. Если пришедшие данные мастерчейна новее по seqno, подменяются они сами и конфигурация коллации, а ранее закешированный ответ «необработанных сообщений нет» сбрасывается в «не проверяли». Для шарда дополнительно перезагружается предыдущее состояние: так коллатор выбрасывает накопленное дерево обращений. Идентификатор следующего блока при этом не меняется. С перезапуском отложенное рабочее состояние выбрасывается целиком, идентификатор следующего блока пересчитывается от новых предыдущих блоков, а рабочее состояние строится заново — так же, как при инициализации, вместе с обработкой генезиса и импортом начальных якорей.
Обе ветки заканчиваются одинаково: замером времени возобновления, которое уходит в рабочее состояние и дальше в расчёт цены единицы работы, и запуском очередной попытки коллации. Как и при инициализации, отмена или прерывание на импорте начальных якорей не роняет коллатор — он остаётся живым с отложенным рабочим состоянием.
Генезис мемпула
Проверку генезиса мемпула коллатор делает ровно в трёх местах: при инициализации и в обеих ветках возобновления коллации. Обычная попытка коллации по команде менеджера генезис не проверяет вовсе.
Актуальный генезис выбирается из трёх источников (информации о консенсусе в текущих данных мастерчейна, в предыдущих данных мастерчейна и переопределения из секции мемпула глобальной конфигурации), и заодно коллатор получает признак, менялся ли генезис с прошлой коллации мастер-блока. Если не менялся, дальше ничего не происходит. Если менялся, кэш якорей очищается целиком, а в него — если позиция обработки якорей на этот момент определена — кладётся фиктивная запись «последний импортированный якорь»: с идентификатором, равным раунду старта нового генезиса, нулевым числом сообщений и нулевым автором. Время цепочки этой записи берётся из той же позиции обработки якорей, что и при обычном импорте начальных якорей. Для нулевого состояния, где эта позиция не определена, запись не кладётся вовсе. Такая запись безопасна тем, что обработанный якорь после подмены совпадает с последним импортированным: коллатор гарантированно запрашивает у мемпула следующий якорь и получает настоящий, а поле автора у фиктивной записи не задействуется вовсе. Одновременно очищаются буферы внешних сообщений во всех диапазонах чтения и партициях состояния читателей.
Смена генезиса — единственный случай, когда коллатор форсирует импорт ровно одного якоря вне обычных правил: признак изменения генезиса передаётся дальше как требование обязательно импортировать якорь, ведь кэш якорей только что очищен, и без этого требования коллатор рисковал бы пойти собирать блок вообще без якорей.
Кэш якорей
Кэш якорей хранит импортированные из мемпула якоря по-разному в зависимости от того, есть ли в них сообщения для этого шарда. Целиком, вместе с числом адресованных шарду сообщений, в очередь попадают только якоря, у которых это число больше нуля. Кроме того, каждый импортированный якорь без исключения — включая пустые для этого шарда и те, что попали в очередь целиком, — оставляет в кэше короткую справку: идентификатор, время цепочки, число всех и своих сообщений, автор. Кэш хранит отдельным признаком, есть ли в нём вообще якоря с необработанными сообщениями для шарда. Он пересчитывается заново при каждом изъятии и обрезке.
Такое разделение позволяет не держать в памяти якоря, из которых шарду нечего читать, но всегда знать идентификатор и время цепочки последнего импортированного якоря — хвост истории справок. Кэш можно обрезать с двух сторон: коллатор либо изымает первый якорь при чтении сообщений, либо обрезает всё, что импортировано позже заданного времени цепочки. Обе обрезки несимметричны, но каждая оставляет в истории справок минимум одну запись.
Все изменения кэша во время сборки блока идут через транзакцию с журналом отката: перед каждой из двух разрешённых операций (изъятие первого якоря и обрезка по времени цепочки) транзакция записывает в журнал всё нужное для восстановления. Фиксация лишь помечает транзакцию завершённой, а откат проигрывает журнал в обратном порядке, возвращая якоря и справки на прежние места. Незакоммиченная транзакция откатывается сама при уничтожении — при ошибке, прерывании или отмене попытки кэш якорей восстанавливается автоматически, без явного вызова отката. Это часть общего правила: успешная сборка блока коммитит состояние читателей сообщений, кэш якорей и диф очереди разом, а любой другой исход откатывает всё сразу. Так отменённая попытка не съедает якоря, нужные следующей.
Импорт начальных якорей
При инициализации и при возобновлении с перезапуском коллатору нужно доимпортировать якоря от последнего обработанного до определённого целевого времени цепочки. Коллатор берёт точку, с которой продолжать, из более продвинутой из двух позиций обработки: своего предыдущего блока или последнего мастер-блока. Вторая позиция идёт в расчёт, только если шард простаивал — не собрал ни одного блока со времени последнего мастер-блока. Тогда его собственная позиция обработки устарела, а мастерчейн тем временем мог уйти вперёд по якорям, и продолжать с устаревшей позиции значило бы перечитывать давно обработанное. Если кандидатов на позицию нет вовсе, например для нулевого состояния, начальные якоря не импортируются.
Перед самим импортом коллатор решает, нужен ли он вообще, сравнивая обработанный якорь с раундом старта генезиса. Если обработанный якорь позже раунда старта, генезис уже в прошлом, и якоря импортируются как обычно. Если он равен раунду старта, импортировать нечего — мемпул с новым генезисом начинает со следующего раунда. Если обработанный якорь раньше раунда старта, а речь о мастерчейне, коллация прерывается. Мастерчейн задаёт общую для сети позицию обработки, и его отставание за границу генезиса означает, что дальше собирать нельзя, пока узел не получит из сети недостающие блоки. То же отставание у шарда не вызывает прерывания: сеть просто отвергнет блок шарда, собранный из неполного набора якорей, и узел заменит его блоком из сети. При нулевом раунде старта, соответствующем нулевому состоянию, якоря импортируются всегда.
Сам импорт сначала обрезает кэш якорей сверху по целевому времени цепочки, а затем пытается набрать нужные якоря из того, что осталось: перебирает якоря с идентификатором не ниже обработанного, пропуская полностью вычитанный обработанный якорь. Кэшем разрешено пользоваться, только если сам обработанный якорь в нём нашёлся. Иначе кэш очищается целиком и якоря заливаются заново из мемпула. Причина строгая: если предыдущая попытка собрать блок уже успела вычерпать якорь из кэша, а блок затем разошёлся с пришедшим из сети, оставшийся кусок кэша непригоден. Если якорь с целевым временем цепочки в кэше нашёлся, набор полон и обращения к мемпулу не требуется. Иначе коллатор запрашивает недостающий обработанный якорь по идентификатору, а дальше в цикле — следующий, пока время цепочки последнего импортированного не достигнет целевого. Отсутствие якоря в мемпуле в обоих случаях прерывает коллацию.
Побочно этот шаг считает, сколько якорей импортировано выше времени цепочки последнего якоря, импортированного самим шардом: за каждый такой якорь из накопленных единиц работы вычитается порог wu_used_to_import_next_anchor, пока накопленного хватает.
Проверка необработанных сообщений
Прежде чем решать, собирать блок сразу или сначала идти за якорем, коллатор проверяет, остались ли вообще необработанные сообщения. Проверка идёт по трём признакам подряд, от дешёвого к дорогому, и останавливается на первом сработавшем:
- ненулевое смещение обработки в состоянии читателей: предыдущий блок не дочитал сообщения до конца;
- непустые буферы сообщений;
- присутствие непрочитанных внутренних сообщений в очереди — проверяется, только если первые два признака не сработали. Для этого коллатор заводит временный читатель с пустым кэшем якорей (внешние сообщения здесь не участвуют).
Результат кладётся в рабочее состояние и переиспользуется, пока не придут новые данные мастерчейна: при их обновлении ответ «нет необработанных» сбрасывается в «не проверяли», а ответ «да» остаётся в силе.
Попытка коллации в шарде
Обычная попытка коллации шарда использует эту проверку как первый шаг и сводится к одному из пяти исходов. Три ведут к немедленной сборке блока, два — к результату «пропущено» для менеджера, и порядок, в котором коллатор их различает, определяет, когда он вообще пойдёт в мемпул за очередным якорем.
Есть предохранитель, который перебивает всё остальное ещё до первых проверок: если длина незакоммиченной цепочки шарда — сколько уже собранных блоков шарда ещё не попали ни в один мастер-блок — достигла порога max_uncommitted_chain_length из onchain-конфигурации, коллатор всё равно проходит цикл импорта якорей: иначе время цепочки не сдвинется. Затем он безусловно возвращает менеджеру «пропущено» с требованием форсировать мастер-блок: новый блок шарда не собирается, пока не выйдет мастер-блок. Ограничение защищает внутреннюю очередь: без предела незакоммиченная зона росла бы без границ, и её сброс при расхождении блока становился бы всё дороже.
Дальше, без единого обращения к мемпулу, коллатор проверяет условия по порядку, останавливаясь на первом сработавшем:
- запрошен принудительный импорт одного якоря, или достигнут порог по накопленным единицам работы — коллатор идёт импортировать якоря, что бы ни показали остальные проверки;
- есть необработанные сообщения — блок собирается сразу, якорь не импортируется;
- в кэше якорей есть непрочитанные внешние сообщения для этого шарда — тоже сборка сразу, без импорта.
Если не сработало ничего из перечисленного, обрабатывать нечего: накопленные единицы работы обнуляются, и коллатор идёт за якорем. После импорта решение переоценивается заново с учётом сообщений во вновь импортированных якорях. Значит, коллатор с непустым буфером сообщений в мемпул вообще не ходит, пока не сработал порог по единицам работы или принудительный импорт.
Единицы работы служат коллатору шарда «часами» импорта якорей: за каждый импортированный якорь из накопленного вычитается порог wu_used_to_import_next_anchor, и цикл идёт, пока накопленного хватает. Есть и второй, независимый ограничитель по времени: если только что импортированный якорь дотянул время цепочки до отметки «время цепочки последнего мастер-блока плюс mc_block_max_interval_ms», цикл прерывается, даже если накопленных единиц работы хватило бы ещё на несколько якорей. Иначе неточно посчитанные единицы работы могли бы задержать мастер-блок дольше максимального интервала. Цикл прерывается ещё в двух случаях: мемпул не может выдать следующий якорь (тогда коллация прерывается), или импорт пропущен из-за отставания по обработке якорей.
Пропуск импорта у шарда возможен: перед каждым обращением к мемпулу коллатор сравнивает идентификатор последнего импортированного якоря с общей по сети позицией обработки якорей из данных мастерчейна. Если разница превышает две трети допустимого отставания консенсуса (max_consensus_lag_rounds из onchain-конфигурации), импорт пропускается: коллатор не должен убегать вперёд мемпула быстрее, чем узел успевает обрабатывать якоря, иначе мемпул встанет на паузу. Принудительный импорт эту проверку обходит. Коллатор сравнивает момент запроса якоря у мемпула со временем цепочки полученного якоря и получает запаздывание. Это значение либо отправляется тюнеру единиц работы, который копит по нему историю и усредняет с обрезкой по перцентилям, либо, если тюнера нет, коллатор сам публикует мгновенное запаздывание в метрику.
Если обрабатывать нечего даже после импорта, коллатор шарда сравнивает время цепочки последнего импортированного якоря с временем цепочки предыдущего блока. Когда разница не меньше empty_sc_block_interval_ms из onchain-конфигурации, а сам интервал не нулевой, решение меняется: блок собирается принудительно, хотя сообщений в нём не будет. Так шард регулярно фиксирует прогресс по якорям, даже когда в нём пусто. Интервал отсчитывается по времени цепочки из якорей, а не по локальным часам: без импорта новых якорей время не двигается, и таймер пустого блока не срабатывает. Нулевое значение параметра отключает пустые блоки шарда вовсе.
Попытка коллации в мастерчейне
В мастерчейне попытка коллации устроена проще и никогда не доходит до сборки блока сама: она либо импортирует один якорь, либо не импортирует и его, а наружу всегда возвращает готовый результат. Если есть необработанные сообщения и принудительный импорт якоря не запрошен, якорь не импортируется вовсе — коллатор и так хочет собрать следующий мастер-блок, а время цепочки предыдущего блока возвращается менеджеру. Иначе коллатор импортирует ровно один следующий якорь: при успехе накопленные единицы работы обнуляются целиком, без цикла, какой есть у шарда, а время цепочки этого якоря уходит наружу. Если мемпул не отдал следующий якорь, коллация прерывается. Если импорт пропущен из-за отставания по обработке якорей, время цепочки ранее импортированного якоря уходит наружу.
Роли здесь разделены: попытка коллации мастерчейна — это способ продвинуть время цепочки и сообщить менеджеру, что мастер-блок пора или ещё не пора собирать, а момент сборки выбирает менеджер по интервалам и по готовности шардов.
Саму сборку мастер-блока запускает отдельная команда do_collate: менеджер передаёт список описаний топ-блоков шардов и целевое время цепочки. Коллатор дожидается рабочего состояния и сравнивает время цепочки последнего импортированного якоря с заданным. Пока оно меньше, якоря импортируются по одному, и на каждом успешном импорте накопленные единицы работы обнуляются. Только после этого запускается сборка блока — уже без всяких проверок наличия сообщений. Отсутствие в кэше хотя бы одной записи об импортированном якоре на входе в do_collate и любой из двух исходов «якоря больше нет» считаются ошибкой логики. К моменту такой команды коллатор обязан был уже импортировать якоря, а якорь с требуемым временем цепочки обязан существовать, раз менеджер это время назвал. Требуемое время цепочки менеджер берёт как максимум по кандидатам всех шардов, поэтому мастер-блок не может оказаться «раньше» включаемых в него блоков шардов.
Отмена коллации
Все точки ожидания якоря у мемпула — при инициализации, при возобновлении, в обеих попытках коллации и в do_collate — разделяют общее свойство: их может прервать менеджер через общий канал отмены. Коллатор проверяет этот сигнал не постоянно, а в заранее заданных точках, и вне их продолжает работать как ни в чём не бывало.
Запрос якоря у мемпула может зависнуть надолго, если мемпул на паузе, поэтому каждый такой запрос ждётся одновременно с сигналом отмены. Такие точки: импорт начальных якорей при инициализации и при возобновлении с перезапуском, цикл импорта в попытке коллации шарда, одиночный импорт в попытке коллации мастерчейна и доимпорт якорей до заданного времени цепочки в do_collate. Сработавшая отмена везде обрабатывается одинаково: рабочее состояние откладывается обратно нетронутым, а наружу уходит результат отмены с идентификатором предыдущего мастер-блока и коротким идентификатором следующего блока. Уже импортированные в этой попытке якоря остаются в кэше — сам импорт кэш якорей транзакцией не оборачивает.
Сборку блока прервать так же просто не получится: она выполняется вне tokio-реактора, и сбросить задачу нельзя. Вместо этого на каждую попытку заводится атомарный флаг отмены. Асинхронная обёртка ждёт одновременно завершения сборки и сигнала отмены от менеджера. По сигналу она лишь взводит флаг и продолжает ждать, пока сборка сама не дойдёт до ближайшей проверки. Проверок несколько: после дозаполнения буферов сообщений при восстановлении (там флаг опрашивается ещё и по ходу, раз в пять итераций), в цикле исполнения групп сообщений перед взятием очередной группы, перед обращением к очереди за длиной хвоста дифов и перед записью дифа очереди. На любой из них сборка завершается отменой: транзакции состояния читателей и кэша якорей откатываются, диф очереди не пишется, а кандидат блока не производится вовсе.
Отмена поэтому не мгновенна: она занимает столько, сколько идёт текущий шаг конвейера между проверками. За пределами точек ожидания якоря и самой сборки блока (загрузка предыдущих состояний, построение рабочего состояния, проверки наличия сообщений) коллатор сигнал отмены не проверяет вовсе. Кто и по каким причинам его посылает — тема статьи о менеджере коллации.
Прерывание коллации
Кроме отмены снаружи, у коллатора есть прерывание: остановка по внутренней причине, которую обнаруживает он сам. Внутренняя ошибка коллации различает прерывание с одной из трёх причин, отмену извне и всё остальное, что заворачивается в общую ошибку. Ни прерывание, ни отмена наружу как ошибки не всплывают — коллатор превращает их в обычные результаты попытки.
Причин прерывания три:
- Не найден якорь с обработанной позиции — возникает при импорте якорей (начальных и очередных), когда мемпул не отдаёт запрошенный по идентификатору якорь, либо когда мастерчейн отстал за границу раунда старта генезиса.
- Не найден следующий якорь — тоже при импорте, когда мемпул не может выдать якорь, следующий за уже импортированным.
- Не найден диф в очереди — на финализации читателя сообщений, когда собирается статистика по дифам чужих топ-блоков: для мастер-блока это топ-блоки включаемых в него шардов, для блока шарда это топ-блоки других шардов, обновлённые в последнем мастер-блоке, а также сам этот мастер-блок.
Отсутствие дифа любого из них даёт одно и то же прерывание.
Расхождение блока
Расхождение блока со стороны коллатора выглядит как последовательность двух внешних воздействий, а не как замена коллатора новым. Экземпляр коллатора и его запись у менеджера переживают расхождение — теряется только рабочее состояние и, при необходимости, кэш якорей.
Сначала приходит сигнал отмены: идущая попытка коллации прерывается в ближайшей точке проверки и возвращает результат отмены. Затем — resume_collation с флагом перезапуска: отложенное рабочее состояние выбрасывается, идентификатор следующего блока пересчитывается от предыдущих блоков, уже принятых из сети, а состояние читателей и данные предыдущего блока строятся заново из хранилища.
Кэш якорей при этом не очищается автоматически — сам импорт начальных якорей приводит его в порядок: сначала обрезка сверху по новому целевому времени цепочки, затем проверка, лежит ли ещё в кэше якорь с обработанной позиции. Если его уже вычерпали во время отменённой попытки, кэш очищается целиком и заливается из мемпула заново. Именно поэтому возобновление с перезапуском стоит дороже обычного: нужны повторная загрузка состояния и, возможно, повторный импорт якорей.
Здесь же остаётся окно гонки, из-за которого достижимо прерывание «не найден диф в очереди»: расхождение блока сбрасывает незакоммиченную зону внутренней очереди раньше, чем менеджер успевает запросить отмену активных коллаций, а финализация читателя сообщений сигнал отмены не проверяет вовсе — ближайшая проверка стоит уже после неё. Поэтому сборка, запущенная до расхождения, может успеть дойти до финализации и не найти там диф, только что удалённый из очереди.
Синхронизация коллации
Процедура, которой отставший или заново запущенный узел переводит коллацию на мастер-блок, уже принятый сетью, восстанавливает внутреннюю очередь сообщений и опирается на адаптер состояния узла как единственную границу с CoreStorage и модулем продвижения по блокам.
Внутренняя очередь сообщений
Как коллатор хранит и коммитит внутренние сообщения между блоками — зоны очереди, указатели коммита, партиции и сборка мусора.