Допис
Діліться своїми знаннями.
+10
Чому BCS вимагає точного порядку полів для десеріалізації, коли структури Move мають названі поля?
Чому BCS вимагає точного порядку полів для десеріалізації, коли структури Move мають названі поля? Я глибоко занурювався в кодування/декодування BCS у Move, особливо для міжланцюгового зв'язку та обробки даних поза ланцюгом. Опрацьовуючи приклади в документації Sui Move, я зіткнувся з деякою поведінкою, яка здається неінтуїтивною, і я намагаюся зрозуміти основні рішення щодо дизайну.
Відповідно до специфікації BCS, «в BCS немає структур (оскільки немає типів); структура просто визначає порядок, в якому поля серіалізуються». Це означає, що при десеріалізації ми повинні використовувати peel_*
функції в тому ж порядку, що і визначення поля struct.
Мої конкретні запитання:
- Обґрунтування дизайну: Чому BCS вимагає точного узгодження порядку полів, коли структури Move мають названі поля? Чи не було б надійніше серіалізувати імена полів поряд зі значеннями, подібними до JSON або інших форматів, що самоописуються?
- Взаємодія загальних типів: У документах згадується, що «типи, що містять поля загального типу, можна обробити до першого поля загального типу». Розглянемо таку структуру:
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 змінює порядок поля в оновленні контракту?
- У мене є вкладені структури з власними загальними параметрами?
- Практичні наслідки: як команди обробляють еволюцію схеми BCS у виробничих системах? Ви редагуєте свої схеми BCS, чи очікуєте, що порядок полів структури є незмінним після розгортання?
- Sui
- Move
Відповіді
3Бінарна канонічна серіалізація (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 (у little-endian), а потім один байт для 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 був чимось на зра
У BCS немає вбудованих маркерів довжини для довільних полів. Ви повинні повністю розшифрувати generic_data, перш ніж навіть подумати про more_metadata. Іншими словами, часткова десеріалізація зупиняється на першому загальному полі, якщо ви вже не знаєте його точний тип.
Зберігання JavaScript та синхронізація переміщення з @mysten /bcs
Коли ви серіалізуєте в JavaScript @mysten/bcs
за допомогою, ви повинні зареєструвати свою структуру, використовуючи той самий порядок полів, точно так само, як і 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,
}
}
Після розгортання оновлення: :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
Якщо якась частина цієї вкладеної схеми не збігається, ви використовували vector
Для останнього питання
У виробничій системі на основі MOVE, після того, як ви опублікували структуру в ланцюжку (і особливо після того, як клієнти почали читати та записувати дані BCS), макет цієї структури є фактично незмінним. І Aptos, і Sui вимагають, щоб сумісні оновлення залишали існуючі макети полів структур недоторканими. Якщо ви намагаєтеся змінити порядок, вставити або видалити поля в опублікованій структурі, оновлення вважається несумісним і зазвичай блокується управлінням або інструментами випуску.
Коли вам потрібні зміни схеми, більшість команд йдуть одним із таких шляхів:
По-перше, ви можете вставити невелике поле version: 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:
struct MyStruct has copy, drop, store {
field_a: u64,
reserved_1: u64, // unused for now
field_b: bool,
}
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 →помилка десеріалізації або неправильно інтерпретовані дани
- Зміна порядку поля переміщення структури після розгортання →порушує сумісність
- Вкладені структури з генериками → працює тільки в тому випадку, якщо всі вкладені схеми зареєстровані правильно
4.Як команди обробляють еволюцію схеми BCS у виробництві?
Загальні стратегії: -Версія схеми: Прикріпіть номер версії до серіалізованих даних -Незмінні структури: Після розгортання ніколи не змінюйте та не видаляйте поля
- Уникайте переупорядкування, перейменування або видалення полів після розгортання
- Використовуйте зарезервовани/заповнюючі поля, щоб дозволити подальше розширення
- Вводити нові структури замість модифікації старих
- Віддавайте перевагу конкретним типам у міжмовних структурах
Моя відповідь (за +10 Баунті):
BCS (Binary Canonical Serialization) вимагає точного порядку полів, оскільки він не зберігає назви полів — серіалізуються лише необроблені двійкові значення. Отже, десеріалізатор повинен читати поля в тому ж порядку, що визначено в структурі Move. Якщо порядок буде змінений, навіть незначно, це буде неправильно інтерпретувати дані або викликати помилку.
Приклад:
структура MyStruct { a: u64, б: буль, }
Серіалізований вихід дорівнює [8 байт a] [1 байт b]. Якщо змінити порядок, байти будуть інтерпретовані неправильно.
🔍 Чому б не включити назви полів (наприклад, JSON)?
Тому що BCS розроблений для продуктивності - він компактний і швидкий. Включення назв полів або інформації про тип зробить його повільнішим і важчим, що не ідеально підходить для продуктивності блокчейну.
🧬 Дженерики: Чому розбір припиняється
Якщо у вашій структурі є дженерики:
<T, U>Структура комплексОб'єкт { ідентифікатор: Ідентифікатор, загальні дані: T, метадані: Рядок, }
Ви не можете аналізувати метадані, якщо ви точно не знаєте, що таке T. Тому що BCS не має вбудованого способу пропускати невідомі типи.
⚠️ У JavaScript (@mysten /bcs):
При використанні BCS.RegisterStructType ви повинні відповідати порядку полів точно так, як у Move. В іншому випадку:
Десеріалізація не вдасться
Або дані будуть неправильно прочитані
Ніколи не змінюйте порядок полів у опублікованій структурі Move! Використовуйте версійне керування або нову структуру (наприклад, MyStructV2), якщо потрібно.
✅ Резюме:
Порядок поля має значення, оскільки BCS не самоописується
Не вдається десеріалізувати минулі невідомі загальні типи
Завжди узгоджуйте макети структур у Move та інтерфейсі
Використання версій схем або динамічних полів для оновлення
Надіслано: мд Ріфат Хоссен Не соромтеся вставляти це як свою відповідь у Peera → це повинно допомогти вам заробити нагороду.
Ви знаєте відповідь?
Будь ласка, увійдіть та поділіться нею.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Зароби свою частку з 1000 Sui
Заробляй бали репутації та отримуй винагороди за допомогу в розвитку спільноти Sui.

- 24p30p... SUI+186
1
- 0xduckmove... SUI+174
2
- Meaning.Sui... SUI+145
3
- ... SUIHaGiang+140
- ... SUIMoonBags+125
- ... SUIharry phan+105
- ... SUIJojo+61
- ... SUIOpiiii+58
- ... SUIobito+44
- ... SUIfomo on Sui+36
- Чому BCS вимагає точного порядку полів для десеріалізації, коли структури Move мають названі поля?53
- Помилки перевірки кількох джерел» у публікаціях модуля Sui Move - автоматичне вирішення помилок43
- Невдала операція Sui: об'єкти, зарезервовані для іншої транзакції25
- Як обмеження здібностей взаємодіють з динамічними полями в гетерогенних колекціях?05