Sui.

Пост

Поделитесь своими знаниями.

Награда+10

Peera Admin.
May 29, 2025
Экспертные Вопросы и Ответы

Почему BCS требует точного порядка полей для десериализации, когда структуры Move содержат именованные поля?

Почему BCS требует точного порядка полей для десериализации, если структуры Move содержат именованные поля? Я углубился в кодирование/декодирование BCS в Move, особенно в том, что касается межсетевой связи и обработки данных вне сети. Изучая примеры из документации Sui Move, я обнаружил, что некоторые действия кажутся мне нелогичными, и я пытаюсь понять основные проектные решения.

Согласно спецификации BCS, «в BCS нет структур (поскольку нет типов); структура просто определяет порядок сериализации полей». Это означает, что при десериализации мы должны использовать peel_*функции в том же порядке, в котором указано определение поля структуры.

Мои конкретные вопросы:

  1. Обоснование проектирования: почему BCS требует точного сопоставления порядка полей, если в структурах Move есть именованные поля? Не лучше ли сериализовать имена полей вместе со значениями, подобно JSON или другим форматам самоописания?
  2. Взаимодействие универсальных типов: в документации упоминается, что «типы, содержащие поля универсальных типов, могут быть проанализированы вплоть до первого поля универсального типа». Рассмотрим следующую структуру:

struct ComplexObject<T, U> has drop, copy {
    id: ID,
    owner: address,
    metadata: Metadata,
    generic_data: T,
    more_metadata: String,
    another_generic: U
}

Как именно здесь работает частичная десериализация? Можно ли десериализовать до more_metadata и игнорировать оба общих поля или первое универсальное поле (generic_data) полностью заблокирует дальнейшую десериализацию? 4. Межъязыковая согласованность: при использовании библиотеки JavaScript @mysten /bcs для сериализации данных, которые будут использоваться контрактами Move, что произойдет, если:

  • Я случайно изменил порядок полей в объекте JavaScript?
  • Определение структуры Move меняет порядок полей при обновлении контракта?
  • У меня есть вложенные структуры со своими общими параметрами?
  1. Практические последствия: как команды справляются с эволюцией схем BCS в производственных системах? Вы редактируете свои схемы BCS или ожидаете, что порядок полей структуры после развертывания останется неизменным?
  • Sui
  • Move
5
2
Поделиться
Комментарии
.

Ответы

2
0xduckmove.
May 31 2025, 15:14

Бинарная каноническая сериализация (BCS) в движении

Вот мои ответы на ваши вопросы

Почему порядок полей должен точно совпадать

Представьте, что у вас есть структура Move, определенная следующим образом:

module example::ordering {
    struct MyStruct has copy, drop, store {
        a: u64,
        b: bool,
    }

    public fun to_bytes_example(): vector<u8> {
        let instance = MyStruct { a: 42, b: true };
        bcs::to_bytes(&instance)
    }

    public fun from_bytes_example(bytes: vector<u8>): MyStruct {
        bcs::from_bytes(&bytes)
    }
}

Все вы знаете, что сериализация MyStruct {a: 42, b: true} приводит к восьми байтам для u64 42 (с прямым порядком байтов), а затем по одному байту для значения true (например, 0x01). Теперь, если вы когда-нибудь измените структуру на:

public struct MyStruct has copy, drop, store {
    b: bool,
    a: u64,
}

сериализованные байты будут начинаться с одного байта для логического значения, а затем восьми байтов для целого числа. Тем не менее, десериализатор в Move по-прежнему будет пытаться прочитать первые восемь байтов как u64, а следующий байт — как bool.

Поскольку BCS так и не записала фразу «сначала это было логическое значение», Move в итоге интерпретирует оставшиеся данные как неправильное число или просто терпит неудачу. Короче говоря, замена полей разрушает все.


Как дженерики влияют на частичную десериализацию

Могу ли я пропустить их, если меня интересуют только некоторые последующие поля

Ответ: нет, если вы точно не знаете, как анализировать эти дженерики. Представьте себе эту структуру Move:

module example::generics {
    use std::string::String;

    public struct ComplexObject<T, U> has copy, drop, store {
        id: ID,
        owner: address,
        metadata: vector<u8>,
        generic_data: T,
        more_metadata: String,
        another_generic: U,
    }

    public fun partial_deserialize(bytes: vector<u8>): String {
        let obj: ComplexObject<Signer, Signer> = bcs::from_bytes(&bytes);
        obj.more_metadata
    }
}


Когда вы звонитеbcs::from_bytes::<ComplexObject<Signer, Signer>>(&bytes), парсер знает, что Tэто Signer, который в Sui представляет собой 32-байтный адрес фиксированного размера. UОн читает 32 байта, определяет, что это ваши generic_data, затем переходит к чтению «more_metadata: String» (с префиксом длины) и, наконец, another_generic:.

Но если вы не указали T или T — это что-то вроде вектора, длина которого заранее не известна, узнать, где начинается more_metadata, невозможно.

В BCS нет встроенных маркеров длины для произвольных полей; вы должны полностью декодировать generic_data, прежде чем приступить к работе с more_metadata. Другими словами, частичная десериализация останавливается на первом общем поле, если вы еще не знаете его точный тип.


Синхронизация JavaScript и перемещений с @mysten /bcs

При сериализации в JavaScript с помощью команды @mysten/bcsMove вы должны зарегистрировать структуру, используя тот же порядок полей, что и Move. В противном случае bcs: :from_bytes в Move неправильно интерпретирует байты. Вот как это выглядит:

import { Bcs, u64, bool } from "@mysten/bcs";

const bcs = new Bcs();

// Match Move’s struct: struct MyStruct { a: u64, b: bool }
bcs.registerStructType("MyStruct", {
  a: u64(),
  b: bool(),
});

// If you accidentally do this instead:
// bcs.registerStructType("MyStruct", {
//   b: bool(),
//   a: u64(),
// });

const instance = { a: 42n, b: true };
const bytes = bcs.ser("MyStruct", instance);

Поскольку Move ожидает [8 байт u64] [1 байт bool], если JavaScript выдает [1 байт bool] [8 байт u64], синтаксический анализатор Move пытается прочитать восемь байт для u64, но обнаруживает мусор. Это несоответствие приводит к ошибке декодирования или повреждению данных.

Теперь предположим, что позже вы попытаетесь обновить структуру Move:

module upgrade::v1 {
    struct MyStruct has copy, drop, store {
        a: u64,
        b: bool,
    }
}

module upgrade::v2 {
    struct MyStruct has copy, drop, store {
        b: bool,
        a: u64,
    }
}

После развертывания upgrade: :v1 интерфейсы JS начинают сериализоваться как [u64] [bool]. Если вы запустите обновление, в котором эти поля будут заменены местами, в Move появятся новые вызовы [bool] [u64]. Внезапно старые байты в версии v2 оказываются бессмысленными, а любая новая сериализация ломает старых клиентов.

Именно поэтому политики обновлений Move запрещают изменять порядок полей структуры при «совместимом» обновлении. Если вам абсолютно необходимо изменить макет, вы публикуете новую структуру (например, MyStructV2) и постепенно переносите данные.

Вложенные или универсальные структуры — это одна и та же история:

module nest::demo {
    struct Inner has copy, drop, store {
        x: u64,
        y: vector<u8>,
    }

    struct Outer<T> has copy, drop, store {
        id: u64,
        payload: T,
        note: vector<u8>,
    }
}

На стороне JavaScript вы можете сделать что-то вроде:

import { Bcs, u64, vector, Struct } from "@mysten/bcs";

const bcs = new Bcs();

// Register Inner exactly as Move defined it
bcs.registerStructType("Inner", {
  x: u64(),
  y: vector(u8()),
});

// Register Outer<Inner>, matching Move’s field order
bcs.registerStructType("Outer<Inner>", {
  id: u64(),
  payload: new Struct("Inner"),
  note: vector(u8()),
});

Когда JS сериализует Outer, он записывает 8 байт идентификатора; затем он рекурсирует в Inner, записывая 8 байт для x и префикс y; затем записывает note. Команда Move bcs: :from_bytes выполняет те же действия.

Если какая-либо часть этой вложенной схемы не совпадает, вы использовали vector () вместо vector (). Все ломается, потому что префиксы длины и размеры элементов будут отличаться.

На последний вопрос

В производстве��ной системе, основанной на Move, после публикации структуры в блокчейне (особенно после того, как клиенты начали читать и записывать данные BCS) макет этой структуры фактически остается неизменным. Как Aptos, так и Sui требуют, чтобы совместимые обновления оставляли существующие макеты структурных полей нетронутыми. Если вы попытаетесь изменить порядок, вставить или удалить поля в опубликованной структуре, обновление считается несовместимым и обычно блокируется руководством или инструментами выпуска.

Если вам все же нужно изменить схему, большинство команд выбирают один из следующих способов:

Во-первых, вы можете встроить небольшое поле «Версия: u8» в начало структуры, чтобы читатели могли видеть номер версии и соответствующую ветку:

public struct DataV1 has copy, drop, store {
    version: u8,       // 1
    field_a: u64,
    field_b: bool,
}

public struct DataV2 has copy, drop, store {
    version: u8,       // 2
    field_a: u64,
    field_b: bool,
    field_c: vector<u8>, // new field in version 2
}

Here, any consumer can read the first byte; if it’s 1, they parse with DataV1. If it’s 2, they parse with DataV2. The downside is old clients that only know about DataV1 may choke on version 2 unless you write special fallback logic.

Another approach is to publish a new struct entirely, such as MyStructV2, leaving MyStructV1 unchanged. You migrate on-chain data via a transaction that reads V1 and writes the new V2. All old code that still understands only MyStructV1 continues to work until you decide to deprecate it.

On Sui, you also have dynamic fields for more flexibility. Instead of tacking on a new field to an existing struct, you store additional data as a separate child object keyed by the original object’s ID. Because the base struct’s BCS layout never changes, clients reading it still see the original fields. Then, whenever they need to read or write the extra data, they know to query the dynamic child. Dynamic fields are a powerful way to extend object schemas without ever touching the original struct layout.

Some teams reserve padding fields:

``rust

struct MyStruct has copy, drop, store { field_a: u64, reserved_1: u64, // unused for now field_b: bool, }


















2
Комментарии
.
Owen.
May 31 2025, 04:13

1.Почему BCS требует точного порядка полей?

Поскольку BCS сериализует только необработанные значения, а не имена полей или метаданные типов.

Структуры Move содержат именованные поля, ноBCS рассматривает их как упорядоченные кортежи. Имена полей представлены во время компиляции ине включаются в сериализованные выходные данные.

Пример:

struct MyStruct {
    a: u64,
    b: bool,
}
  • Сериализован как: [u64 bytes] + [bool bytes] u64``bool- Затем десериализатор должен прочитать:порядок имеет значение

Это делает BCS:

  • Быстрый и компактный
  • Не требует самоописания и гибкости при работе со схемами

2.Как частичная десериализация работает с универсальными типами?

Дано:

struct ComplexObject<T, U> {
    id: ID,
    owner: address,
    metadata: Metadata,
    generic_data: T,      // <-- generic
    more_metadata: String,
    another_generic: U,   // <-- generic
}

Правило ####:

generic_dataМожно десериализовать до первого общего поля (). TПосле этого синтаксический анализ прекращается, если вы не знаете, как декодировать.

Итак: T``more_metadata- Если вы не знаете, как анализироватьanother_generic, вы**не можете безопасно найти или найти его.

  • Если вы хотите получить частичный доступ, вы должны peel_*осторожно использовать низкоуровневые функции.

Только неуниверсальный префикс можно безопасно анализировать без полного знания типов.


3. @mysten/bcsМежъязыковая совместимость с JavaScript ()

-Порядок полей должен точно совпадать, как и в Move.

  • Переупорядочение полей в объекте JS →ошибка десериализации или неверно интерпретированные данные
  • Изменение порядка полей структуры Move после развертывания →нарушает совместимость
  • Вложенные структуры с дженериками → работает только в том случае, если все вложенные схемы правильно зарегистрированы

4.Как команды справляются с эволюцией схем BCS в производственной среде?

Общие стратегии: -Управление версиями схемы: прикрепите номер версии к сериализованным данным -Неизменяемые структуры: после развертывания никогда не меняйте порядок и не удаляйте поля

  • Избегайте переупорядочивания, переименования или удаления полей после развертывания
  • Используйте зарезервированные или заполненные поля, чтобы обеспечить расширение в будущем
  • Внедряйте новые структуры вместо модификации старых
  • Предпочитайте конкретные типы в межъязычных структурах
0
Комментарии
.

Знаете ответ?

Пожалуйста, войдите в систему и поделитесь им.

Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

306Посты450Ответы
Sui.X.Peera.

Заработай свою долю из 1000 Sui

Зарабатывай очки репутации и получай награды за помощь в развитии сообщества Sui.

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