Sui.

Bài viết

Chia sẻ kiến thức của bạn.

Tiền thưởng+10

Peera Admin.
May 29, 2025
Hỏi đáp Chuyên Gia

Tại sao BCS yêu cầu thứ tự trường chính xác để khử chuỗi khi cấu trúc Move có các trường được đặt tên?

Tại sao BCS yêu cầu thứ tự trường chính xác để khử chuỗi khi cấu trúc Move có các trường được đặt tên? Tôi đã đi sâu vào mã hóa/giải mã BCS trong Move, đặc biệt là cho giao tiếp chuỗi chằng và xử lý dữ liệu ngoài chuỗi. Trong khi xem xét các ví dụ trong tài liệu Sui Move, tôi đã gặp một số hành vi có vẻ phản trực giác và tôi đang cố gắng hiểu các quyết định thiết kế cơ bản.

Theo đặc tả của BCS, “không có cấu trúc trong BCS (vì không có kiểu); cấu trúc chỉ đơn giản xác định thứ tự mà các trường được nối tiếp.” Điều này có nghĩa là khi giải mã, chúng ta phải sử dụng peel_*các hàm theo thứ tự chính xác với định nghĩa trường struct.

Câu hỏi cụ thể của tôi:

  1. Lý do thiết kế: Tại sao BCS yêu cầu khớp thứ tự trường chính xác khi cấu trúc Move có các trường được đặt tên? Sẽ không mạnh mẽ hơn nếu sắp xếp các tên trường cùng với các giá trị, tương tự như JSON hoặc các định dạng tự mô tả khác?
  2. Tương tác kiểu chung: Các tài liệu đề cập rằng “các loại chứa các trường kiểu chung có thể được phân tích thành trường kiểu chung đầu tiên.” Hãy xem xét cấu trúc này:

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

Chính xác thì quá trình khử phân loại một phần hoạt động như thế nào ở đây? Tôi có thể giải chuỗi tối đa more_metadata và bỏ qua cả hai trường chung hay trường chung đầu tiên (generic_data) có chặn hoàn toàn quá trình giải mã hóa tiếp theo không? 4. Tính nhất quán giữa các ngôn ngữ: Khi sử dụng thư viện JavaScript @mysten /bcs để nối tiếp dữ liệu sẽ được sử dụng bởi hợp đồng Move, điều gì sẽ xảy ra nếu:

  • Tôi vô tình sắp xếp lại các trường trong đối tượng JavaScript?
  • Định nghĩa cấu trúc Move thay đổi thứ tự trường trong nâng cấp hợp đồng?
  • Tôi có cấu trúc lồng nhau với các tham số chung của riêng chúng?
  1. Ý nghĩa thực tế: Trong các hệ thống sản xuất, các nhóm xử lý sự tiến hóa lược đồ BCS như thế nào? Bạn có phiên bản lược đồ BCS của mình hay kỳ vọng rằng thứ tự trường cấu trúc là bất biến sau khi được triển khai?
  • Sui
  • Move
5
3
Chia sẻ
Bình luận
.

Câu trả lời

3
0xduckmove.
May 31 2025, 15:14

Trình tự chuẩn hóa nhị phân (BCS) trong Move

Đây là câu trả lời của tôi cho câu hỏi của bạn

Tại sao thứ tự trường phải khớp chính xác

Hãy tưởng tượng bạn có một cấu trúc Move được xác định như sau:

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)
    }
}

Tất cả các bạn đều biết rằng việc nối tiếp MyStruct {a: 42, b: true} tạo ra tám byte cho u64 42 (trong little-endian) theo sau là một byte cho true (ví dụ: 0x01). Bây giờ, nếu bạn đã từng thay đổi struct thành:

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

Các byte nối tiếp sẽ bắt đầu với một byte cho boolean, sau đó tám byte cho số nguyên. Tuy nhiên, bộ deserializer trong Move vẫn sẽ cố gắng đọc tám byte đầu tiên dưới dạng u64 và byte tiếp theo dưới dạng bool.

Bởi vì BCS không bao giờ ghi lại “đây là một điều bool đầu tiên”, Move cuối cùng sẽ giải thích dữ liệu còn sót lại là một số sai hoặc thất bại hoàn toàn. Nói tóm lại, hoán đổi trường phá vỡ mọi thứ.


Thuốc chung ảnh hưởng đến quá trình khử phân loại một phần như thế nào

Tôi có thể bỏ qua chúng không nếu tôi chỉ quan tâm đến một số lĩnh vực sau này

Câu trả lời là: không trừ khi bạn biết chính xác cách phân tích các thuốc generic đó. Hình dung cấu trúc Move này:

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
    }
}


Khi bạn gọibcs::from_bytes::<ComplexObject<Signer, Signer>>(&bytes), trình phân tích cú pháp biết đó Tlà Signer, trong Sui là một địa chỉ 32 byte có kích thước cố định. UNó đọc 32 byte, tìm ra đó là generic_data của bạn, sau đó nó chuyển sang đọc “more_metadata: String” (có tiền tố theo chiều dài) và cuối cùng là another_generic:.

Nhưng nếu bạn chưa chỉ định T hoặc nếu T là một cái gì đó giống như vector có độ dài không được biết trước thì không có cách nào để tìm ra more_metadata bắt đầu từ đâu.

BCS không có dấu hiệu độ dài tích hợp cho các trường tùy ý; bạn phải giải mã generic_data đầy đủ trước khi bạn có thể nghĩ về more_metadata. Nói cách khác, quá trình khử phân loại một phần dừng lại ở trường chung đầu tiên trừ khi bạn đã biết loại chính xác của nó.


Giữ JavaScript và di chuyển đồng bộ với @mysten /bcs

Khi bạn nối tiếp trong JavaScript@mysten/bcs, bạn phải đăng ký cấu trúc của mình bằng cách sử dụng cùng một thứ tự trường chính xác như Move. Nếu không, bcs: :from_bytes của Move sẽ hiểu sai các byte. Đây là những gì nó trông như thế nào:

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);

Vì Move mong đợi [8 byte của u64] [1 byte bool], nếu JavaScript phát ra [1 byte bool] [8 byte của u64], bộ phân tích cú pháp của Move cố gắng đọc tám byte cho u64 nhưng thấy rác. Sự không phù hợp đó dẫn đến lỗi giải mã hoặc dữ liệu bị hỏng.

Bây giờ, giả sử sau này bạn cố gắng nâng cấp cấu trúc 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,
    }
}

Khi bạn triển khai nâng cấp: :v1, các giao diện JS bắt đầu nối tiếp thành [u64] [bool]. Nếu bạn đẩy một bản nâng cấp hoán đổi các trường đó, các lệnh gọi mới trong Move sẽ mong đợi [bool] [u64]. Đột nhiên, các byte cũ trở nên vô nghĩa trong v2 và bất kỳ chuỗi hóa mới nào cũng phá vỡ các máy khách cũ.

Đây chính xác là lý do tại sao các chính sách nâng cấp của Move cấm thay đổi thứ tự trường cấu trúc trong một bản nâng cấp “tương thích”. Nếu bạn hoàn toàn phải thay đổi bố cục, bạn xuất bản một cấu trúc mới (ví dụ: MyStructV2) và dần dần di chuyển dữ liệu.

Các cấu trúc lồng nhau hoặc chung chung là cùng một câu chuyện:

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>,
    }
}

Ở phía JavaScript, bạn sẽ làm một cái gì đó như:

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()),
});

Khi JS tuần tự hóa Outer, nó ghi 8 byte của id; sau đó nó đệ quy vào Inner, viết 8 byte cho x và một tiền tố dài y; sau đó nó viết note. Bcs của Move: :from_bytes thực hiện các bước tương tự.

Nếu bất kỳ phần nào của lược đồ lồng nhau đó không khớp, bạn đã sử dụng vector () thay vì vector () mọi thứ sẽ bị ngắt, bởi vì tiền tố độ dài và kích thước phần tử sẽ khác nhau.

Đối với câu hỏi cuối cùng

Trong một hệ thống dựa trên Move sản xuất, một khi bạn đã xuất bản một cấu trúc trên chuỗi (và đặc biệt là khi khách hàng đã bắt đầu đọc và ghi dữ liệu BCS), bố cục của cấu trúc đó thực sự không thể thay đổi. Cả Aptos và Sui đều yêu cầu các nâng cấp tương thích phải giữ nguyên bố cục trường cấu trúc hiện có. Nếu bạn cố gắng sắp xếp lại, chèn hoặc xóa các trường trong cấu trúc đã xuất bản, nâng cấp được coi là không tương thích và thường bị chặn bởi quản trị hoặc công cụ phát hành.

Khi bạn cần thay đổi lược đồ, hầu hết các nhóm sẽ sử dụng một trong các đường dẫn sau:

Đầu tiên, bạn có thể nhúng một trường phiên bản nhỏ: u8 ở phía trước cấu trúc của bạn để người đọc có thể thấy số phiên bản và phân nhánh tương ứng:

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,
}

4
Câu trả lời hay nhất
Bình luận
.
Owen.
Owen486
May 31 2025, 04:13

1.** Tại sao BCS yêu cầu thứ tự trường chính xác?**

Bởi vì BCS chỉ tuần tự hóa các giá trị thô - không phải tên trường hoặc siêu dữ liệu kiểu.

Các cấu trúc di chuyển có các trường được đặt tên, nhưngBCS coi chúng như các tập hợp có trước. Tên trường là thông tin thời gian biên dịch và** không bao gồm trong đầu ra được nối tiếp**.

Ví dụ:

struct MyStruct {
    a: u64,
    b: bool,
}
  • Được nối tiếp thành: [u64 bytes] + [bool bytes]
  • Deserializer phải đọc u64sau đó bool—** thứ tự quan trọng**

Điều này làm cho BCS:

  • Nhanh chóng và nhỏ gọn
  • Không tự mô tả hoặc linh hoạt theo lược đồ

2.** Quá trình khử phân loại một phần hoạt động như thế nào với các loại chung?**

Đã cho:

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

Quy tắc:

generic_dataBạn có thể giải trình tự cho đến trường chung đầu tiên (). TSau đó, phân tích cú pháp dừng lại trừ khi bạn biết cách giải mã.

Vì vậy: T``more_metadata- Nếu bạn không biết cách phân tích cú phápanother_generic, bạn** không thể**tiếp cận hoặc một cách an toàn.

  • Bạn phải sử dụng các peel_*chức năng cấp thấp một cách cẩn thận nếu bạn muốn truy cập một phần.

Chỉ tiền tố không chung mới có thể được phân tích một cách an toàn mà không cần kiến thức đầy đủ về loại.


3. @mysten/bcsTính nhất quán giữa các ngôn ngữ với JavaScript ()

-Thứ tự trường phải khớp chính xác, giống như trong Move.

  • Sắp xếp lại các trường trong đối tượng JS →** lỗi giải trình tự hoặc dữ liệu được hiểu nhầm
  • Thay đổi thứ tự trường cấu trúc Move sau khi triển khai →** phá vỡ tính tương thích**
  • Cấu trúc lồng nhau với hàm chung → chỉ hoạt động nếu tất cả các lược đồ lồng nhau được đăng ký chính xác

4.** Làm thế nào để các nhóm xử lý sự phát triển lược đồ BCS trong sản xuất?**

Các chiến lược phổ biến: -** Phiên bản lược đồ**: Đính kèm số phiên bản vào dữ liệu được nối tiếp -** Cấu trúc không thể thay đổi**: Sau khi triển khai, không bao giờ sắp xếp lại hoặc xóa các trường

  • Tránh sắp xếp lại, đổi tên hoặc xóa các trường sau khi triển khai
  • Sử dụng các trường dự trữ/đệm để cho phép mở rộng trong tương lai
  • Giới thiệu cấu trúc mới thay vì sửa đổi cấu trúc cũ
  • Thích các loại cụ thể trong cấu trúc đa ngôn ngữ
5
Bình luận
.
md rifat hossen.
Jun 19 2025, 17:17

Câu trả lời của tôi (cho +10 Bounty):

BCS (Binary Canonical Serialization) yêu cầu thứ tự trường chính xác vì nó không lưu trữ tên trường - chỉ các giá trị nhị phân thô được nối tiếp. Vì vậy, bộ deserializer phải đọc các trường theo cùng một thứ tự như được định nghĩa trong cấu trúc Move. Nếu thứ tự bị thay đổi, dù chỉ một chút, nó sẽ hiểu sai dữ liệu hoặc gây ra lỗi.

Ví dụ:

cấu trúc myStruct { A: u64, b: bool, }

Đầu ra nối tiếp là [8 byte của a] [1 byte của b]. Nếu bạn đảo ngược thứ tự, các byte sẽ được giải thích không chính xác.

🔍 Tại sao không bao gồm tên trường (như JSON)?

Bởi vì BCS được thiết kế cho hiệu suất - nó nhỏ gọn và nhanh chóng. Bao gồm tên trường hoặc thông tin loại sẽ làm cho nó chậm hơn và nặng hơn, điều này không lý tưởng cho hiệu suất blockchain.


🧬 Generics: Tại sao phân tích cú pháp dừng lại

Nếu cấu trúc của bạn có thuốc generic:

<T, U>cấu trúc complexObject { ID: ID, dữ liệu chung: T, siêu dữ liệu: Chuỗi, }

Bạn không thể phân tích siêu dữ liệu trừ khi bạn biết chính xác T là gì. Bởi vì BCS không có cách tích hợp để bỏ qua các loại không xác định.


⚠️ Trong JavaScript (@mysten /bcs):

Khi sử dụng BCS.RegisterStructType, bạn phải khớp thứ tự trường chính xác như trong Move. Nếu không:

Quá trình khử phân loại sẽ thất bại

Hoặc dữ liệu sẽ bị đọc sai

Không bao giờ thay đổi thứ tự trường trong cấu trúc Move đã xuất bản! Sử dụng phiên bản hoặc cấu trúc mới (ví dụ: MyStructV2) nếu cần.


✅ Tóm tắt:

Thứ tự hiện trường quan trọng vì BCS không tự mô tả

Không thể giải mã các loại chung chưa xác định trong quá khứ

Luôn khớp bố cục cấu trúc trên Move và frontend

Sử dụng phiên bản lược đồ hoặc trường động để nâng cấp


Gửi bởi: md rifat hossen Hãy dán điều này làm câu trả lời của bạn trong Peera → nó sẽ giúp bạn kiếm được tiền thưởng.

1
Bình luận
.

Bạn có biết câu trả lời không?

Hãy đăng nhập và chia sẻ nó.

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

420Bài viết611Câu trả lời
Sui.X.Peera.

Kiếm phần của bạn từ 1000 Sui

Tích lũy điểm danh tiếng và nhận phần thưởng khi giúp cộng đồng Sui phát triển.

Chiến dịch phần thưởngTháng Bảy