Коллатор

Финализация блока

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

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

Закрытие читателя сообщений

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

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

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

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

Финализация и подготовка дифа очереди идут параллельно

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

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

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

Три параллельные ветви внутри финализации

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

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

Резка аккаунтов на потоки

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

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

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

Меркл-обновление состояния

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

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

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

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

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

Сериализация блока

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

Дальше строится корневая ячейка блока и тут же сериализуется в BOC переиспользуемым буфером: заголовок BOC берётся не с нуля, а из общего на коллатор кэша, а сама запись идёт параллельно. Идентификатор блока появляется уже из результата: root hash получает значение хеша корневой ячейки, а file hash — хеш сериализованных байтов. Сериализованные байты не выбрасываются: они едут дальше вместе с блоком и попадают в кандидат блока и в кэш блоков адаптера состояния узла.

Новое состояние шарда

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

Из восемнадцати полей состояния большая часть просто повторяет заголовок блока: идентификатор сети, шард, seqno, время блока, минимальный номер мастер-блока, логическое время, ссылка на мастер-блок, а для мастер-блока ещё и дополнительная часть состояния мастерчейна. Собранный словарь аккаунтов и позиция обработки тоже переносятся как есть. Библиотеки — пустой словарь для шарда и публичные библиотеки мастерчейна для мастер-блока.

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

Специальные транзакции в теле блока

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

Сама дополнительная часть блока мастерчейна состоит из шести полей:

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

У блока шарда этой части нет вовсе.

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

Сжигание комиссий и проверка балансового итога

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

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

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

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

Дополнительная часть состояния мастерчейна и ключевой блок

Для блока шарда финализация просто кладёт в новое состояние ссылку на последний мастер-блок. Для мастер-блока строится отдельная структура, дополнительная часть состояния мастерчейна, и именно в процессе её сборки блок может быть объявлен ключевым.

Сборка идёт последовательностью шагов:

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

Итоговая структура состоит из девяти полей:

  • дерева описаний всех существующих шардов;
  • конфигурации блокчейна (новой, если контракт изменился в этом блоке, иначе копии предыдущей);
  • сведений о валидаторах (короткого хеша их списка, номера сессии и признака обновления);
  • сведений о консенсусе (раунда переключения набора, предыдущего раунда переключения, genesis и предыдущего флага перемешивания);
  • словаря предыдущих мастер-блоков;
  • признака «состояние получено после ключевого блока» (он же признак ключевого блока этого состояния);
  • ссылки на последний известный ключевой блок;
  • статистики создания блоков валидаторами, которую коллатор всегда оставляет пустой;
  • суммарного баланса всех аккаунтов сети.

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

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

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

Вступление новой конфигурации в силу

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

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

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

Расписание смены набора валидаторов

Расчёт расписания — четвёртый шаг сборки дополнительной части состояния мастерчейна, и весь он стоит под условием «блок ключевой». Правила выбора самого набора валидаторов (выборы, параметры конфигурации 32–37) относятся к теме ротации валидаторов и верификации, отдельной области. Здесь коллатор лишь берёт уже готовый действующий набор и решает, когда и кому мастерчейн будет доверен дальше.

Внутри собираются два снимка: первый описывает, что было и что стало по конфигурации (прежний и новый набор валидаторов, прежний и новый флаг перемешивания, режим нумерации сессий). Второй — когда можно переключаться (прежняя и новая конфигурация консенсуса, признаки переопределения genesis и смены конфигурации консенсуса, время цепочки блока).

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

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

Раунд переключения выбирается по первому из пяти подошедших правил:

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

Правила 1 и 2 касаются перезапуска мемпула с новым genesis, а правила 3–5 — обычной смены набора внутри уже живущего консенсуса. У правила 1 приоритет перед правилом 2: если genesis переопределён в этом же блоке, до сравнения конфигурации консенсуса просто не доходит очередь, даже если она тоже изменилась.

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

Признак «конфигурация консенсуса изменилась» в любом ключевом блоке сохраняется в данные коллации и уезжает наружу отдельным полем кандидата блока — не только когда новый genesis действительно записан, но и в блоке, где genesis не тронут из-за приоритета правила 1. Увидев этот признак поднятым, менеджер коллации не уведомляет мемпул о новом состоянии мастерчейна сразу, а откладывает уведомление до верификации блока.

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

Итог — короткий хеш подмножества валидаторов и номер новой сессии, посчитанный одним из двух способов: «исходным», где номером служит сам раунд старта сессии, либо «последовательным», где это прежний номер плюс единица. Режим выбирается по флагу возможностей сети из параметра конфигурации 8, а не по локальной настройке узла: по замыслу протокола этот флаг в сетях на Tycho должен быть выключен. Тогда действовал бы последовательный режим, целевой после миграции. Но фактически он включён везде: его ставит дефолтный конфиг нулевого состояния, и он же входит в список возможностей, которые объявляет поддерживаемыми коллатор. Поэтому на практике сейчас действует исходный режим. Оба поля попадают затем в заголовок каждого блока, собранного в этой сессии: по ним узел, проверяющий блок, восстанавливает, какое подмножество валидаторов имело право его подписать.

Данные мастерчейна для следующей коллации

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

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

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

Заголовок блока

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

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

Последней в заголовок пишется версия программного обеспечения сети. Она берётся не из версии кода узла, а из отдельного параметра конфигурации блокчейна, поэтому одинакова у всех узлов сети независимо от того, какую версию Tycho они запустили.

Кандидат блока

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

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

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

Единицы работы блока

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

Сумма не растёт бесконечно: она обрезается потолком. Сначала считается допустимое отставание обработки якорей от консенсуса: две трети лимита отставания консенсуса из конфигурации консенсуса. Это отставание делится на длину волны, три раунда (один якорь на волну), и к результату прибавляется единица запаса — получается число волн-запасов, то есть число якорей про запас. Умножение этого числа на стоимость импорта следующего якоря переводит его в единицы работы — так и получается потолок.

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

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

Возврат в цикл событий узла

Тело финализации выполняется вне цикла событий узла, и результат параллельной сборки возвращается уже в асинхронной части коллатора.

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

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

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

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

Значения по умолчанию

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

ПараметрЗначение по умолчанию
accounts_split_depth4
merkle_split_depth5
check_value_flowfalse
validate_configtrue

accounts_split_depth задаёт глубину резки словаря аккаунтов при обновлении аккаунтов, merkle_split_depth — глубину резки при построении меркл-обновления состояния, check_value_flow включает дополнительную проверку балансового итога собранного блока, validate_config включает проверку новой конфигурации блокчейна при объявлении ключевого блока.