Доступность данных (DA) с использованием Celestia

Введение

Технология роллапа снижает стоимость транзакций и увеличивает пропускную способность Ethereum за счет обработки транзакций вне чейна. Транзакции в роллапе сжимаются и размещаются на L1 в пакетах. Пакеты представляют собой тысячи отдельных транзакций вне чейна в рамках одной транзакции на L1. Это позволяет снизить перегрузку базового уровня и уменьшить комиссию для пользователей.

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

Эта проблема называется проблемой доступности данных. На самом деле, если можно проверить, что данные, соответствующие блоку роллапа, доступны непосредственно на L1, то любой желающий может взять данные транзакции и пересчитать правильное состояние роллапа. Проблема доступности данных особенно интересна в случае роллапов, поскольку сам роллап работает как база данных вне чейна, и если операторы роллапа не размещают данные на уровне доступности данных, таком как L1, то проверить состояние роллапа становится невозможно.

Более экономичный и масштабируемый вариант роллапа с использованием Celestia в качестве уровня доступности данных

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

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

Для сравнения, Arbitrum One платит Ethereum около 112 тыс. долл. в день за обеспечение DA. Это примерно 0,15 долл. США за транзакцию в среднем, учитывая средний дневной объем транзакций на Arbitrum One.

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

Celestia - это слой доступности данных (DA), обеспечивающий масштабируемое решение проблемы доступности данных. Две ключевые особенности слоя DA Celestia - выборка доступности данных (DAS) и деревья Меркла (Namespaced Merkle trees, NMTs). Обе функции представляют собой новые решения для масштабирования блокчейна: DAS позволяет легким нодам проверять доступность данных, не загружая весь блок; NMT позволяют слоям исполнения и расчета на Celestia загружать транзакции, которые имеют отношение только к ним.

Интеграция Celestia в AltLayer

В AltLayer мы создаем модульные роллапы. Одним из примеров является использование Celestia в качестве слоя доступности данных для роллапов, созданных на платформе AltLayer rollups-as-a-service, что позволяет снизить затраты на обеспечение доступности данных.

Цикл операций

Мы разработали сервис, который выполняет следующие действия

  1. Инициализация

Сначала сервис получает информацию о генезисе и версии ноды роллапа. Он публикует эту информацию в Celestiab (аналогично шагу 4) и обновляет смарт-контракт с указанием высоты блока, на котором хранятся данные.

  1. Извлечение данных о блоках из L2-роллапа

Задача состоит в том, чтобы извлечь из L2 достаточное количество данных для фиксации на уровне DA, чтобы в случае катастрофы можно было восстановить роллап L2. Для этого служба доступности данных будет постоянно получать блоки и доказательства их достоверности пакетно.

  1. Сжатие данных блоков

Для минимизации стоимости данных данные блоков сжимаются с помощью технологии сжатия zlib. Для достижения большей эффективности сжатия данных размер пакета может быть увеличен. Например, при сжатии пакета из 2 блоков размер данных уменьшается на 48%. Если размер пакета увеличить до 100 блоков, то сокращение увеличится до 67%.

  1. Размещение данных в Celestia

Для отправки данных,

  • Установите легкую ноду Celestia. Легкие ноды обеспечивают доступность данных, в частности,

Легкие ноды обеспечивают доступность данных путем

  • Прослушивания новых заголовков блоков и соответствующих метаданных DA

  • Выборки доступности данных по полученным заголовкам

  • Укажите область имен, версию и обязательства

  • Разместите свои данные в Celestia DA используя blob.Submit

execute(logger, async (data) => {
  const blobArr = [
    {
      namespace: CELESTIA_NODE_NAMESPACE,
      data: "0x" + data.toString("hex"),
      share_version: CELESTIA_NODE_SHARE_VERSION,
      commitment: CELESTIA_NODE_COMMITMENT,
    },
  ];
  const height = await jsonRPC(
    logger,
    CELESTIA_NODE_URL,
    "blob.Submit",
    [blobArr],
    CELESTIA_NODE_AUTH_TOKEN
  );
  return height;
}).catch(logger.error);

Пример запроса:

curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer <Authorization token>" -d '{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "blob.Submit",
  "params": [
    [
      {
        "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAEJpDCBNOWAP3dM=",
        "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
        "share_version": 0,
        "commitment": "R4DRTAENQ7fGawPbt8aMse1+YReCZYg87xZIZf2fAxc="
      }
    ]
  ]
}' http://localhost:26658
  • Celestia API вернет высоту блока, в котором размещены данные

  1. Отслеживание высоты блока, на котором хранятся данные

Чтобы отследить, какой номер блока L2 соответствует высоте блока, на котором размещены данные, смарт-контракт будет развернут на L1. Смарт-контракт будет поддерживать отображение номера блока на высоту блока Celestia.

Для этого сервис вызывает функцию set() на смарт-контракте и заполняет его соответствующими данными.

  • key: Номер стартового блока для данного пакета

  • value: Номер блока, по которому данные сохраняются в celestia

  • metadata_: Номер конечного блока для данного пакета

function set(
        uint256 key,
        uint256 value,
        bytes calldata metadata_
    ) external onlyOperator {
        emit Set(key, value, metadata_, _msgSender());
        // slither-disable-next-line unused-return
        _map.set(key, value);
        metadata[key] = metadata_;
        latestKey = key;
    }

Полный код контракта:

// Идентификатор лицензии SPDX: НЕЛИЦЕНЗИРОВАННО
// СМ. ЛИЦЕНЗИЮ НА https://files.altlayer.io/Alt-Research-License-1.md
// Copyright Alt Research Ltd. 2023. All rights reserved.
//
// Вы признаете и соглашаетесь с тем, что компании Alt Research Ltd. ("Alt Research") (или лицензиары
// Alt Research") принадлежат все законные права, титулы и интересы в отношении
// работы, программного обеспечения, приложений, исходного кода, документации и любых других документов

pragma solidity ^0.8.18;

import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {EnumerableMapUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableMapUpgradeable.sol";

/// @title UintToUintMap
/// @notice @notice Данный контракт позволяет осуществлять отображение между ключами и значениями uint256 с помощью утилиты EnumerableMapUpgradeable из OpenZeppelin.
/// @dev Контракт также включает ролевую систему доступа.
contract UintToUintMap is AccessControlUpgradeable {
    using EnumerableMapUpgradeable for EnumerableMapUpgradeable.UintToUintMap;

    /// @notice Идентификатор роли для операторов.
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

    /// @dev Внутреннее хранилище для карты.
    EnumerableMapUpgradeable.UintToUintMap private _map;
    uint256 public latestKey;
    uint256 public globalMetadata;
    /// @notice Хранилище открытых метаданных, связанных с каждым ключом.
    mapping(uint256 => bytes) public metadata;

    /// @dev Эта ошибка возникает, когда в функции `set` длина массивов `keys` и `values` не равна.
    error LengthMismatch();

    /// @dev Эта ошибка возникает, если вызывающая сторона не является оператором.
    error NotOperator();

    /// @notice Выдается при установке глобальных метаданных.
    event SetGlobalMetadata(uint256 value, address sender);

    /// @notice Выдается при установке пары key-value.
    event Set(
        uint256 indexed key,
        uint256 value,
        bytes metadata,
        address sender
    );

    /// @custom: конструктор oz-upgrades-unsafe-allow
    constructor() {
        _disableInitializers();
    }

    /// @dev Предоставляет возможность вызова функции только операторам.
    modifier onlyOperator() {
        if (!hasRole(OPERATOR_ROLE, _msgSender())) {
            revert NotOperator();
        }
        _;
    }

    /// @notice Инициализирует контракт, устанавливает роли администратора и оператора по умолчанию.
    /// @param initialDefaultAdmin Адрес первого администратора по умолчанию.
    /// @param initialOperator Адрес первого оператора.
    function initialize(
        address initialDefaultAdmin,
        address initialOperator
    ) external initializer {
        _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
        _grantRole(OPERATOR_ROLE, initialOperator);
    }

    /// @notice Задает глобальные метаданные.
    /// @param значение ID глобальных метаданных.
    function setGlobalMetadata(uint256 value) external onlyOperator {
        emit SetGlobalMetadata(value, _msgSender());
        globalMetadata = value;
    }

    /// @notice InsertsВставляет пару ключ-значение в карту и выдает событие Set.
    /// @param key Ключ для вставки.
    /// @param value Значение для вставки.
    /// @param metadata_ Метаданные, связанные с ключом.
    function set(
        uint256 key,
        uint256 value,
        bytes calldata metadata_
    ) external onlyOperator {
        emit Set(key, value, metadata_, _msgSender());
        // slither-disable-next-line unused-return
        _map.set(key, value);
        metadata[key] = metadata_;
        latestKey = key;
    }

    /// @notice Возвращает пару key-value, хранящуюся по заданному индексу в карте.
    /// @param index Индекс, по которому будет получена пара key-value.
    /// @return Key и value по заданному индексу..
    function at(uint256 index) external view returns (uint256, uint256) {
        return _map.at(index);
    }

    /// @notice Извлекает из карты значение, связанное с заданным ключом.
    /// @param key Ключ для получения значения.
    /// @return Значение, связанное с ключом.
    function get(uint256 key) external view returns (uint256) {
        return _map.get(key);
    }

    /// @notice Возвращает общее количество пар key-value в карте.
    /// @return Общее количество пар key-value.
    function total() external view returns (uint256) {
        return _map.length();
    }
}
  1. Восстановление чейна

Запустите ноду с правильной версией Получите отображение номера блока роллапа на высоту блока Celestia из смарт-контракта L1 Загрузите данные блока из Celestia, вызвав blob.GetAll с правильным пространством имен и высотой блока Распакуйте данные блока импортируйте данные блока в ноду Повторяйте до тех пор, пока нода не будет полностью синхронизирована Запустите ноду с необходимой информацией

Время от времени может возникать необходимость в восстановлении чейна. Это может быть вызвано

  • катастрофическими событиями, в результате которых оператор ноды вышел из строя на неопределенное время

  • злонамеренной нодой, отклоняющей или возвращающей оспаривателю недостоверные результаты запроса

В этом случае роллап может быть восстановлен

  1. Получите информацию о генезисе и версии ноды, запустив blob.GetAll с указанием нужной области имен и высоты блока

curl -X POST -H "Content-Type: application/json" -H "Authorization: <auth key>" -d '{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "blob.GetAll",
  "params": [
    110684,
    [
      "AAAAAAAAAAAAAAAAAAAAAAAAAEJpDCBNOWAP3dM="
    ]
  ]
}' http://localhost:26658/
  1. Запустите ноду с правильной версией

  2. Получите из смарт-контракта L1 отображение номера блока роллапа на высоту блока Celestia

  3. Загрузите данные блока из Celestia, запустив blob.GetAll с правильным пространством имен и высотой блока

  4. Распакуйте данные блока

  5. Импортируйте данные блока в ноду

  6. Повторяйте до тех пор, пока нода не будет полностью синхронизирована

  7. Запустите ноду с необходимой информацией

Last updated