Процесс управления Solana ABI
В этом документе предлагается процесс управления Solana ABI. Процесс управления ABI — это инженерная практика и вспомогательная техническая структура, позволяющая избежать внесения непреднамеренных несовместимых изменений ABI.
Проблема
Solana ABI (двоичный интерфейс к кластеру) в настоящее время определяется только неявно реализацией и требует очень внимательного взгляда, чтобы заметить критические изменения. Это чрезвычайно затрудняет обновление программного обеспечения в существующем кластере без перезагрузки реестра.
Требования и цели
- Непреднамеренные изменения ABI могут быть механически обнаружены как сбои CI.
- Новая реализация должна иметь возможность обрабатывать самые старые данные (с момента создания), как только мы перейдем в основную сеть.
- Цель этого предложения состоит в том, чтобы защитить ABI, поддерживая при этом довольно быстрое развитие, выбрав механический процесс, а не очень длительный процесс аудита, управляемый человеком.
- После криптографической подписи большой двоичный объект данных должен быть идентичен, поэтому обновление формата данных на месте невозможно независимо от входящего и исходящего трафика онлайн-системы. Кроме того, учитывая огромный объем транзакций, которые мы собираемся обрабатывать, ретроспективное обновление на месте в лучшем случае нежелательно.
Решение
Вместо естественной проверки человеческого зрения, которая, как предполагается, регулярно дает сбои, нам нужна систематическая гарантия того, что кластер не сломается при изменении исходного кода.
Для этой цели мы вводим механизм маркировки всех вещей, связанных с ABI, в исходном коде (struct
s, enum
s) с новым атрибутом #[frozen_abi]
. Это принимает жестко заданное значение дайджеста, полученное из типов его полей через ser::Serialize
. И атрибут автоматически генерирует модульный тест, чтобы попытаться обнаружить любые несанкционированные изменения в отмеченных вещах, связанных с ABI.
Однако обнаружение не может быть полным; независимо от того, насколько усердно мы статически анализируем исходный код, все равно можно сломать ABI. Например, это включает в себя не производный от руки написанный ser::Serialize, изменения реализации базовой библиотеки (например, bincode), различия в архитектуре ЦП. Обнаружение этих возможных несовместимостей ABI выходит за рамки этого управления ABI.
Определения
Элемент/тип ABI: различные типы, используемые для сериализации, которые в совокупности составляют весь ABI для любых компонентов системы. Например, к этим типам относятся struct и enum.
Дайджест элемента ABI: некоторый фиксированный хэш, полученный из информации о типе полей элемента ABI.
Пример
+#[frozen_abi(digest="eXSMM7b89VY72V...")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote {
/// A stack of votes starting with the oldest vote
pub slots: Vec<Slot>,
/// signature of the bank's state at the last slot
pub hash: Hash,
}
Рабочий процесс разработчика
Чтобы узнать дайджест для новых элементов ABI, разработчики могут добавить frozen_abi
со случайным значением дайджеста, запустить модульные тесты и заменить его правильным дайджестом из сообщения об ошибке проверки утверждений.
В общем, как только мы добавим frozen_abi
и его изменение будет опубликовано в канале стабильного выпуска, его дайджест никогда не должен меняться. Если такое изменение необходимо, мы должны определить новую структуру, например FooV1. И следует подойти к специальному потоку выпуска, такому как хардфорки.
Замечания по реализации
Мы используем некоторую степень макромеханизма для автоматического создания модульных тестов и расчета дайджеста из элементов ABI. Это можно сделать с помощью serde::Serialize
([1]
) и any::type_name
([2]
). В качестве прецедента для аналогичной реализации «чернила» от Parity Technologies «[3]» могут быть информационными.
Детали реализации
Цель реализации — максимальное автоматическое обнаружение непреднамеренных изменений ABI. С этой целью дайджест структурной информации ABI рассчитывается с максимальной точностью и стабильностью.
Когда выполняется проверка дайджеста ABI, он динамически вычисляет дайджест ABI, рекурсивно анализируя ABI полей элемента ABI, повторно используя функциональность сериализации serde
, макрос proc и общую специализацию.
Затем проверка утверждает, что ее итоговое значение дайджеста идентично тому, что указано в атрибуте frozen_abi.
Чтобы понять это, он создает пример экземпляра типа и пользовательский экземпляр Serializer для serde, чтобы рекурсивно перемещаться по его полям, как если бы сериализовал пример по-настоящему. Этот обход должен быть выполнен через serde
, чтобы действительно зафиксировать, какие типы данных на самом деле будут сериализованы serde
, даже с учетом пользовательских не-derive
d реализаций черт Serialize
.
Процесс переваривания ABI
Эта часть немного сложна. Существует три взаимозависимых части: AbiExample
, AbiDigester
и AbiEnumVisitor
.
Во-первых, сгенерированный тест создает пример экземпляра дайджест-типа с трейтом под названием «AbiExample», который должен быть реализован для всех дайджест-типов, таких как «Serialize», и возвращать «Self», как трейт «Default». Обычно это обеспечивается специализацией универсального признака для большинства распространенных типов. Также можно «выводить» для «struct» и «enum» и при необходимости можно написать от руки.
Пользовательский сериализатор называется AbiDigester. И когда он вызывается serde
для сериализации некоторых данных, он рекурсивно собирает информацию ABI, насколько это возможно. Внутреннее состояние AbiDigester
для дайджеста ABI обновляется по-разному в зависимости от типа данных. Эта логика специально перенаправляется через трейт под названием AbiEnumVisitor
для каждого типа enum
. Как следует из названия, нет необходимости реализовывать AbiEnumVisitor для других типов.
Подводя итог этому взаимодействию, serde
обрабатывает поток управления рекурсивной сериализацией в тандеме с AbiDigester
. Начальная точка входа в тестах и дочерние AbiDigester используют AbiExample рекурсивно для создания примера иерархического графа объектов. И AbiDigester
использует AbiEnumVisitor
для запроса фактической информации ABI, используя созданную выборку.
По умолчанию
недостаточно для AbiExample
. Различные коллекции ::default()
пусты, но мы хотим переварить их с помощью реальных элементов. И дайджест ABI не может быть реализован только с помощью AbiEnumVisitor
. AbiExample
требуется, потому что фактический экземпляр типа необходим для фактического прохождения данных через serde
.
С другой стороны, дайджест ABI не может быть выполнен только с помощью AbiExample
.
AbiEnumVisitor
необходим, потому что все варианты enum
не могут быть пройдены только с одним его вариантом в качестве примера ABI.
Усваиваемая информация:
- название типа ржавчины
- имя типа данных
serde
- все поля в
struct
- все варианты в
enum
struct
: обычная(struct {...}
) и в виде кортежа (struct(...)
)enum
: обычные варианты и стилиstruct
иtuple
.- атрибуты:
serde(serialize_with=...)
иserde(skip)
Неперевариваемая информация:
- Любой пользовательский путь сериализации кода, не затронутый образцом, предоставленным
AbiExample
. (технически невозможно) - дженерики (должен быть конкретный тип; используйте
frozen_abi
для псевдонимов конкретного типа)