Коллатор

Синхронизация коллации

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

Обычный цикл менеджера коллации (см. статью «Менеджер коллации») предполагает, что узел успевает собирать собственные блоки в темпе сети. Когда узел отстал от сети или только что запустился, это условие нарушается, и коллация переходит к другой процедуре: синхронизации на применённый мастер-блок. Вместо того чтобы продолжать собственную коллацию, менеджер переносит состояние коллации на точку, которую сеть уже приняла, и заново выстраивает внутреннюю очередь сообщений по дифам применённых блоков.

Коллация обращается к CoreStorage и к модулю продвижения по блокам только через один интерфейс — адаптер состояния узла. Через него же восстановление очереди читает нужные дифы и блоки.

Когда узел уходит в синхронизацию

Решение принимается по пяти проверкам в фиксированном порядке. Отвечает первая сработавшая проверка:

  1. в кэше блоков коллации уже отмечено, что из сети получен более новый мастер-блок, чем уже применённый диапазон, — ответ «нет, получен более новый блок»;
  2. у собственного кандидата обнаружено расхождение с уже принятым из сети блоком того же номера — «да»;
  3. известен верхний мастер-блок для следующей коллации, и в кэше есть применённый диапазон. Отставание считается как разница между концом этого диапазона и верхним блоком: если оно меньше порога, ответ «нет, подождём собственный следующий мастер-блок», если нет, ответ «да»;
  4. верхний мастер-блок для следующей коллации известен, но применённых из сети блоков впереди него нет — «нет»;
  5. такого верхнего блока нет вовсе: узел ещё ничего не собрал и ни разу не синхронизировался. Тогда ответ определяется только наличием применённого диапазона в кэше: если диапазон есть, ответ «да», если его нет, ответ «нет».

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

Ответ «да» превращается в отмену всех идущих коллаций с последующей синхронизацией. Ответ «нет, получен более новый блок» синхронизацию не запускает: вместо неё менеджер лишь вычищает из кэша применённые записи, дифы которых уже не нужны для восстановления очереди.

Порядок шагов синхронизации

Получив диапазон применённых мастер-блоков, синхронизация выполняет фиксированную последовательность шагов:

  1. регистрирует идущую синхронизацию, указывая её целевой блок — конец применённого диапазона;
  2. если переопределение конфигурации мемпула вводит новый genesis, заранее сбрасывает незакоммиченную зону внутренней очереди;
  3. собирает позицию обработки по мастерчейну и всем шардам для последнего применённого мастер-блока и берёт по ним минимумы по каждому шарду;
  4. читает в кэше блоков коллации идентификаторы блоков, стоящих непосредственно перед подграфом первого применённого мастер-блока;
  5. при включённом параметре fast_sync вычисляет границу «дифы досюда уже применены», чтобы не применять их заново;
  6. восстанавливает очередь и получает последнее применённое состояние мастерчейна;
  7. если такого состояния не оказалось, завершается без запуска коллации: значит, восстановление не смогло дойти до конца;
  8. обновляет время цепочки по последнему мастер-блоку, кроме случая синхронизации на нулевое состояние, сбрасывает статусы коллации всех шардов, запоминает мастер-блок, на который синхронизировался, и сбрасывает дополнительные сведения о топ-блоках шардов в кэше;
  9. собирает данные мастерчейна и удаляет из кэша собственные собранные блоки новее точки синхронизации;
  10. уведомляет мемпул об обновлении состояния мастерчейна, обрабатывает это состояние и уведомляет мемпул о верхнем обработанном якоре.

Очередь восстанавливается до того, как обрабатывается состояние мастерчейна и потенциально запускается новая коллация: собирать блок по ещё не восстановленной очереди нельзя. Статусы коллации сбрасываются до обработки состояния, а не после: иначе выбор следующего шага коллации опирался бы на статусы, оставшиеся от коллаторов, работавших ещё до синхронизации, и попытался бы запустить уже занятый коллатор.

Позиция обработки на шаге 3 берётся по последнему применённому мастер-блоку, а стартовая точка обхода на шаге 4 — по первому: восстановление идёт от границы, до которой очередь уже вычищена, вперёд до вершины применённого диапазона.

Восстановление очереди

Восстановление очереди состоит из двух проходов, и эти диапазоны не пересекаются: дифы из хранилища доводят очередь до начала применённого диапазона, а дифы из кэша проводят её через сам диапазон.

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

Второй проход идёт вперёд по кэшу блоков коллации: он извлекает из кэша очередной применённый подграф мастер-блока и применяет дифы по мастер-блоку и всем его топ-блокам шардов, беря данные прямо из записи кэша, без обращения к хранилищу. Проход продолжается, пока не встретится подграф, помеченный как последний применённый. При этом восстановление может уйти дальше исходного диапазона, если за время синхронизации в кэш пришли новые блоки из сети.

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

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

Это третья и последняя из трёх точек, в которых менеджер коммитит внутреннюю очередь. Две другие связаны с обычной коллацией и разобраны в статье о менеджере коллации: обработка события «свой блок применён» и коммит подтверждённого мастер-блока.

После этих двух действий восстановление возвращает последнее состояние мастерчейна, предыдущее состояние (по предпоследнему разобранному подграфу), идентификатор предыдущего мастер-блока и ключи блоков, на которые синхронизировались. Это и есть результат шага 6 из списка выше, дальше используемый на шагах 8 и 9.

Адаптер состояния узла

Через адаптер состояния узла коллация читает состояние узла: последний применённый мастер-блок с подпиской на него, начальный мастер-блок, идентификатор нулевого состояния и глубину разбиения шардов. Тем же адаптером коллация читает данные (состояния, блоки, дескрипторы блоков, дифы очереди) и записывает состояние: сохраняет корень состояния шарда и передаёт собранный блок наружу. Кроме того, она кэширует блоки шардов для построения состояний по меркл-обновлениям, ждёт появления блоков и получает события о применённых состояниях.

Единственная рабочая реализация этого интерфейса держит внутри себя CoreStorage и обращается к его подхранилищам: состоянию узла, состояниям шардов, блокам, дескрипторам блоков. Больше нигде в коллации к CoreStorage напрямую не обращаются.

Значение «начальный мастер-блок» коллация читает через адаптер, а адаптер — из того же подхранилища состояния узла внутри CoreStorage. Туда оно записывается только на холодном старте. Сразу после подготовки нулевого состояния оно безусловно становится идентификатором нулевого состояния. Затем, при скачивании цепочки ключевых блоков, оно переписывается поверх на каждом проверенном ключевом блоке, отмеченном как точка устойчивого состояния, и после последнего из них больше не меняется. В отличие от последнего применённого мастер-блока, оно навсегда остаётся нижней границей истории, с которой узел начинал.

То же подхранилище состояния узла, наоборот, отдаёт последний применённый мастер-блок через канал, на который можно подписаться: приёмник этого канала уже содержит актуальное значение в момент подписки, а каждая запись рассылает обновление всем подписчикам, даже если записан тот же идентификатор.

Собранный блок коллация тоже передаёт наружу не записью в хранилище: адаптер кладёт блок в карту «шард → номер → блок» и рассылает уведомление по широковещательному каналу, а модуль продвижения по блокам забирает его оттуда — сначала проверяет карту, а если блока там ещё нет, ждёт уведомления. Только в момент, когда блок действительно выдаётся модулю продвижения по блокам, адаптер строит и сохраняет его доказательство и диф очереди: до этого момента их у сети попросту нет, дать их может только тот, кто блок собрал.

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

Конфигурация

Синхронизация и восстановление очереди читают часть полей секции collator конфигурации узла. При пропуске поля оно принимает значение по умолчанию.

ПараметрЗначение по умолчанию
min_mc_block_delta_from_bc_to_sync3
check_value_flowfalse
validate_configtrue
fast_synctrue
accounts_split_depth4
merkle_split_depth5
slowdown_restore_queue_msне задано
slowdown_resume_collation_msне задано
slowdown_do_collate_msне задано

К синхронизации прямо относится min_mc_block_delta_from_bc_to_sync: он задаёт порог отставания из третьей проверки выше. fast_sync включает пропуск уже применённых дифов при восстановлении очереди. slowdown_restore_queue_ms вводит искусственное замедление самого восстановления очереди, добавленное для тестирования гонок при отставании узла. Остальные поля управляют другими сторонами коллации: проверкой балансового итога и конфигурации собранного блока, разбиением аккаунтов при обработке словаря аккаунтов состояния и построением меркл-обновления, а также отладочным замедлением возобновления коллации и самой сборки блока.

Ещё два поля структуры в конфигурации не читаются и не пишутся — они заданы константами кода, а не значением из файла конфигурации.