Процесс управления Solana ABI

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

Проблема

Solana ABI (двоичный интерфейс к кластеру) в настоящее время определяется только неявно реализацией и требует очень внимательного взгляда, чтобы заметить критические изменения. Это чрезвычайно затрудняет обновление программного обеспечения в существующем кластере без перезагрузки реестра.

Требования и цели

Решение

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

Для этой цели мы вводим механизм маркировки всех вещей, связанных с ABI, в исходном коде (structs, enums) с новым атрибутом #[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, даже с учетом пользовательских не-derived реализаций черт 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.

Усваиваемая информация:

Неперевариваемая информация:

Использованная литература

  1. (Де)сериализация с информацией о типе · Проблема № 1095 · serde-rs/serde
  2. std::any::type_name - Rust
  3. ink! Parity для написания смарт-контрактов