Восстановление пиров узла ADAMANT после сбоя сети: сиды, обнаружение и выбор синхронизации
Узлы ADAMANT поддерживают подключение к пирам через три отдельных механизма, которые легко спутать при чтении логов консоли после сбоя сети. В этом материале объясняется, как они взаимодействуют, почему синхронизация может остановиться, даже если сид-пиры по-прежнему запрашиваются, и чего следует ожидать операторам во время восстановления.
Предпосылки
Узел хранит таблицу пиров в памяти, заполняемую из трёх источников: сид-пиров, перечисленных в конфиге peers.list, сохранённых пиров, загружаемых из базы данных при запуске, и обнаруженных пиров, возвращаемых другими узлами через GET /peer/list. Сид-пиры заморожены — они никогда не удаляются из таблицы, даже если запросы к ним завершаются неудачей.
Каждый пир имеет состояние: BANNED (0, исключён из обычного использования), DISCONNECTED (1, известен, но в данный момент не пригоден для синхронизации или рассылки) или CONNECTED (2, недавно успешно ответил и подходит для синхронизации). При неудачных запросах показатель успешности пира снижается. Когда у ранее CONNECTED пира показатель падает ниже 80%, его состояние понижается до DISCONNECTED. Таймауты сети (ECONNABORTED) не удаляют пир; они только снижают показатель успешности.
Три параллельных механизма
Пинг сидов (тихий). При запуске и каждые ~5 секунд узел отправляет ping каждому сид-пиру из конфига через GET /peer/height. Ошибки логируются на уровне trace и обычно не видны в стандартном выводе консоли. Успешный ping возвращает пир в состояние CONNECTED.
Обнаружение пиров (громкий). Каждые ~5 секунд узел выбирает одного случайного пира из памяти (состояния DISCONNECTED или CONNECTED) и запрашивает GET /peer/list, чтобы узнать новые адреса. Если этот единственный случайный выбор завершается таймаутом, в консоли отображается:
Discovering new peers failed. ECONNABORTED Request failed GET http://<peer>/peer/list
Эта ошибка указывает только на случайно выбранный пир, а не на всю таблицу пиров. В процессе восстановления часто появляются малознакомые узлы, размещённые в облаке, которые были обнаружены ранее и сохранены в базе данных. Это не означает, что узел игнорирует сид-пиры.
Синхронизация блокчейна (строгая). Путь загрузки использует peers.list() с фильтром по умолчанию: только пиры в состоянии CONNECTED. Если ни один пир в данный момент не находится в состоянии CONNECTED с пригодной высотой, синхронизация завершается с:
Failed to find enough good peers
В этой ситуации узел не отключён от сети в смысле отсутствия записей о пирах. Просто нет активных пиров, подходящих для загрузки блоков.
Типовая временная шкала сбоя
Когда происходит потеря сети, HTTP-запросы ко всем пирам начинают завершаться неудачно. Ранее CONNECTED пиры становятся DISCONNECTED, и загрузчик не может выбрать хорошие пиры, поэтому высота перестаёт увеличиваться. Ошибки обнаружения продолжаются для случайных устаревших записей, в то время как пинг сидов работает тихо в фоновом режиме. Как только хотя бы один сид или другой известный пир снова отвечает на ping, он возвращается в состояние CONNECTED, и синхронизация возобновляется.
Разрыв между «интернет восстановлен» и «узел снова синхронизируется» может составлять несколько минут — или дольше, если удалённые пиры всё ещё недоступны, — поскольку восстановление зависит от успешного обмена сообщениями с пиром, который становится CONNECTED, а не просто от локального подключения.
Ожидания оператора
Наличие ошибок обнаружения к незнакомым адресам после сбоя — нормальное явление и само по себе не указывает на неправильную конфигурацию. Сид-пиры из конфига по-прежнему запрашиваются; просто их ошибки ping не выделяются в стандартных логах. Сообщение Failed to find enough good peers означает отсутствие активных пиров в данный момент, а не то, что таблица пиров была очищена. Перезапуск узла перезагружает сиды и пиры из базы данных, но для восстановления всё равно требуется, чтобы хотя бы один удалённый пир ответил.
Возможные улучшения
Несколько изменений могли бы улучшить опыт оператора: логирование сбоев ping сидов на уровне warn, если отсутствуют CONNECTED пиры дольше заданного порога, предпочтение сид-пиров или недавно работавших пиров в getFromRandomPeer вместо равномерного случайного выбора, параллельный повторный запрос всех сид-пиров при появлении ошибки Failed to find enough good peers в синхронизации, а также сокращение дублирующихся строк предупреждений при исчерпании всех попыток синхронизации в async.retry.