Beitrag
Teile dein Wissen.
+10
Warum benötigt BCS eine genaue Feldreihenfolge für die Deserialisierung, wenn Move-Strukturen benannte Felder haben?
Warum erfordert BCS eine exakte Feldreihenfolge für die Deserialisierung, wenn Move-Strukturen benannte Felder haben? Ich habe mich eingehend mit der BCS-Kodierung/Dekodierung in Move befasst, insbesondere für die kettenübergreifende Kommunikation und die Off-Chain-Datenverarbeitung. Beim Durcharbeiten der Beispiele in der Sui Move-Dokumentation bin ich auf ein Verhalten gestoßen, das kontraintuitiv erscheint, und ich versuche, die zugrunde liegenden Designentscheidungen zu verstehen.
Gemäß der BCS-Spezifikation „gibt es in BCS keine Strukturen (da es keine Typen gibt); die Struktur definiert lediglich die Reihenfolge, in der Felder serialisiert werden.“ Das bedeutet, dass wir beim Deserialisieren peel_*
Funktionen in exakt derselben Reihenfolge wie in der Strukturfelddefinition verwenden müssen.
Meine spezifischen Fragen:
- Entwurfsbegründung: Warum erfordert BCS eine exakte Übereinstimmung der Feldreihenfolge, wenn Move-Strukturen benannte Felder haben? Wäre es nicht robuster, Feldnamen zusammen mit Werten zu serialisieren, ähnlich wie bei JSON oder anderen selbstbeschreibenden Formaten?
- Generische Typinteraktion: In den Dokumenten wird erwähnt, dass „Typen, die generische Typfelder enthalten, bis zum ersten generischen Typfeld analysiert werden können“. Betrachten Sie diese Struktur:
struct ComplexObject<T, U> has drop, copy {
id: ID,
owner: address,
metadata: Metadata,
generic_data: T,
more_metadata: String,
another_generic: U
}
Wie genau funktioniert die partielle Deserialisierung hier? Kann ich bis zu more_metadata deserialisieren und beide generischen Felder ignorieren, oder blockiert das erste generische Feld (generic_data) die weitere Deserialisierung vollständig? 4. Sprachübergreifende Konsistenz: Was passiert, wenn Sie die @mysten /bcs JavaScript-Bibliothek verwenden, um Daten zu serialisieren, die von Move-Verträgen verwendet werden, wenn:
- Ich ordne versehentlich Felder im JavaScript-Objekt neu an?
- Die Move-Strukturdefinition ändert die Feldreihenfolge bei einem Vertrags-Upgrade?
- Ich habe verschachtelte Strukturen mit ihren eigenen generischen Parametern?
- Praktische Implikationen: Wie gehen Teams in Produktionssystemen mit der Entwicklung des BCS-Schemas um? Versionieren Sie Ihre BCS-Schemas oder gehen Sie davon aus, dass die Reihenfolge der Strukturfelder nach der Bereitstellung unveränderlich ist?
- Sui
- Move
Antworten
3Binäre kanonische Serialisierung (BCS) in Bewegung
Hier sind meine Antworten auf deine Fragen
Warum muss die Feldreihenfolge exakt übereinstimmen
Stellen Sie sich vor, Sie haben eine Move-Struktur wie folgt definiert:
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)
}
}
Sie alle wissen, dass die Serialisierung von myStruct {a: 42, b: true} acht Byte für das u64 42 (in Little-Endian) erzeugt, gefolgt von einem Byte für true (zum Beispiel 0x01). Nun, falls du die Struktur jemals änderst in:
public struct MyStruct has copy, drop, store {
b: bool,
a: u64,
}
Die serialisierten Bytes würden mit einem Byte für den booleschen Wert beginnen, dann mit acht Byte für die Ganzzahl. Dennoch wird der Deserializer in Move immer noch versuchen, die ersten acht Byte als u64 und das nächste Byte als Bool zu lesen.
Da BCS nie „das war zuerst ein Bool“ aufgezeichnet hat, interpretiert Move am Ende übrig gebliebene Daten als falsche Zahl oder schlägt direkt fehl. Kurz gesagt, beim Austauschen von Feldern geht alles kaputt.
Wie Generika die partielle Deserialisierung beeinflussen
Kann ich sie überspringen, wenn mir nur einige spätere Felder wichtig sind
Die Antwort lautet: Nicht, es sei denn, Sie wissen genau, wie man diese Generika analysiert. Stellen Sie sich diese Move-Struktur vor:
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
}
}
Wenn Sie aufrufenbcs::from_bytes::<ComplexObject<Signer, Signer>>(&bytes)
, weiß der Parser, dass es sich um T
Signer handelt, was in Sui eine 32-Byte-Adresse mit fester Größe ist. U
Er liest 32 Byte, findet heraus, dass das Ihre generic_data sind, dann liest er „more_metadata: String“ (was ein Längenpräfix hat) und schließlich another_generic:.
Aber wenn Sie T nicht angegeben haben oder wenn T so etwas wie ein Vektor wäre,
BCS hat keine eingebauten Längenmarkierungen für beliebige Felder; Sie müssen generic_data vollständig dekodieren, bevor Sie überhaupt an more_metadata denken können. Mit anderen Worten, die teilweise Deserialisierung stoppt beim ersten generischen Feld, es sei denn, Sie kennen den genauen Typ bereits.
JavaScript und Move mit @mysten /bcs synchron halten
Wenn Sie in JavaScript mit serialisieren@mysten/bcs
, müssen Sie Ihre Struktur registrieren und dabei genau dieselbe Feldreihenfolge wie bei Move verwenden. Andernfalls interpretiert bcs: :from_bytes von Move die Bytes falsch. So sieht das aus:
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);
Da Move [8 Byte von u64] [1 Byte von bool] erwartet, versucht der Parser von Move, acht Byte für das u64 zu lesen, wenn JavaScript [1 Byte bool] [8 Byte von u64] ausgibt, sieht aber Müll. Diese Nichtübereinstimmung führt zu einem Dekodierungsfehler oder zu beschädigten Daten.
Nehmen wir nun an, Sie versuchen später, diese Move-Struktur zu aktualisieren:
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,
}
}
Sobald Sie upgrade: :v1 bereitgestellt haben, beginnen JS-Frontends mit der Serialisierung als [u64] [bool]. Wenn Sie ein Upgrade durchführen, das diese Felder vertauscht, erwarten neue Aufrufe in Move [bool] [u64]. Plötzlich sind die alten Bytes unter Version 2 Unsinn, und jede neue Serialisierung macht alte Clients kaputt.
Genau aus diesem Grund verbieten es die Upgrade-Richtlinien von Move, die Reihenfolge der Strukturfelder bei einem „kompatiblen“ Upgrade zu ändern. Wenn Sie das Layout unbedingt ändern müssen, veröffentlichen Sie eine neue Struktur (z. B. myStructV2) und migrieren die Daten schrittweise.
Verschachtelte oder generische Strukturen sind dasselbe:
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>,
}
}
Auf der JavaScript-Seite würdest du etwas tun wie:
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()),
});
Wenn JS Outer serialisiert
Wenn irgendein Teil dieses verschachtelten Schemas nicht passt, hast du vector
Für die letzte Frage
In einem MOVE-basierten Produktionssystem ist das Layout dieser Struktur praktisch unveränderlich, sobald Sie eine Struktur in der Kette veröffentlicht haben (und insbesondere, nachdem die Clients angefangen haben, BCS-Daten zu lesen und zu schreiben). Sowohl Aptos als auch Sui verlangen, dass bei kompatiblen Upgrades die bestehenden Strukturfeld-Layouts unberührt bleiben. Wenn Sie versuchen, Felder in einer veröffentlichten Struktur neu anzuordnen, einzufügen oder zu löschen, wird das Upgrade als inkompatibel angesehen und in der Regel durch die Governance oder durch Release-Tools blockiert.
Wenn Sie Schemaänderungen benötigen, wählen die meisten Teams einen der folgenden Wege:
Zunächst könnten Sie ein kleines Feld version: u8 an der Vorderseite Ihrer Struktur einbetten, damit die Leser eine Versionsnummer sehen und entsprechend verzweigen können:
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, }
1.Warum benötigt BCS eine exakte Reihenfolge der Felder?
Weil BCS nur Rohwerte serialisiert — keine Feldnamen oder Typ-Metadaten.
Bewegungsstrukturen haben benannte Felder, aberBCS behandelt sie als geordnete Tupel. Feldnamen sind Informationen zur Kompilierzeit und sindnicht in der serialisierten Ausgabe enthalten.
Beispiel:
struct MyStruct {
a: u64,
b: bool,
}
- Serialisiert als:
[u64 bytes] + [bool bytes]
u64``bool
- Der Deserializer muss dann lesen —Reihenfolge ist wichtig
Das macht BCS:
- Schnell und kompakt
- Nicht selbstbeschreibend oder schemaflexibel
2.Wie funktioniert die teilweise Deserialisierung mit generischen Typen?
Gegeben:
struct ComplexObject<T, U> {
id: ID,
owner: address,
metadata: Metadata,
generic_data: T, // <-- generic
more_metadata: String,
another_generic: U, // <-- generic
}
Regel:
generic_data
Sie können bis zum ersten generischen Feld () deserialisieren.T
Danach stoppt das Parsen, es sei denn, Sie wissen, wie man dekodiert.
Also:
T``more_metadata
- Wenn Sie nicht parsen könnenanother_generic
,könnenSie oder nicht sicher erreichen.
- Sie müssen
peel_*
Low-Level-Funktionen vorsichtig verwenden, wenn Sie teilweisen Zugriff wünschen.
Nur nicht-generische Präfixe können ohne vollständige Typkenntnisse sicher analysiert werden.
3. @mysten/bcs
Sprachübergreifende Konsistenz mit JavaScript ()
-Die Reihenfolge der Felder muss exakt übereinstimmen, genau wie in Move.
- Felder im JS-Objekt neu anordnen →Deserialisierungsfehler oder falsch interpretierte Daten
- Änderung der Move-Struct-Feldreihenfolge nach der Bereitstellung →unterbricht die Kompatibilität
- Verschachtelte Strukturen mit Generics → funktioniert nur, wenn alle verschachtelten Schemas korrekt registriert sind
4.Wie gehen Teams mit der Entwicklung des BCS-Schemas in der Produktion um?
Allgemeine Strategien: -Schema-Versionierung: Hängen Sie die Versionsnummer an serialisierte Daten an -Unveränderliche Strukturen: Einmal bereitgestellt, sollten Felder niemals neu angeordnet oder entfernt werden
- Vermeiden Sie es, Felder nach der Bereitstellung neu anzuordnen, umzubenennen oder zu entfernen
- Verwenden Sie reservierte Felder und füllen Sie Felder aus, um zukünftige Erweiterungen zu ermöglichen
- Führen Sie neue Strukturen ein, anstatt alte zu modifizieren
- Bevorzugen Sie konkrete Typen in sprachübergreifenden Strukturen
Meine Antwort (für +10 Bounty):
BCS (Binary Canonical Serialization) erfordert eine exakte Reihenfolge der Felder, da keine Feldnamen gespeichert werden — es werden nur rohe Binärwerte serialisiert. Der Deserializer muss also Felder in derselben Reihenfolge lesen, wie sie in der Move-Struktur definiert ist. Wenn die Reihenfolge auch nur geringfügig geändert wird, werden die Daten falsch interpretiert oder es wird ein Fehler ausgegeben.
Beispiel:
struct myStruct { a: u64, b: bool, }
Die serialisierte Ausgabe ist [8 Byte von a] [1 Byte von b]. Wenn Sie die Reihenfolge umkehren, werden die Bytes falsch interpretiert.
🔍 Warum nicht Feldnamen (wie JSON) einbeziehen?
Weil BCS auf Leistung ausgelegt ist — es ist kompakt und schnell. Das Hinzufügen von Feldnamen oder Typinformationen würde es langsamer und schwerer machen, was für die Blockchain-Leistung nicht ideal ist.
🧬 Generics: Warum das Parsen aufhört
Wenn Ihre Struktur Generika enthält:
<T, U>struct ComplexObject { id: ID, generische_Daten: T, Metadaten: Zeichenfolge, }
Sie können Metadaten nur analysieren, wenn Sie genau wissen, was T ist. Weil BCS keine eingebaute Möglichkeit hat, unbekannte Typen zu überspringen.
⚠️ In JavaScript (@mysten /bcs):
Wenn Sie bcs.registerStructType verwenden, müssen Sie die Feldreihenfolge genau wie in Move einhalten. Andernfalls gilt Folgendes:
Die Deserialisierung wird fehlschlagen
Oder Daten werden falsch gelesen
Ändern Sie niemals die Feldreihenfolge in einer veröffentlichten Move-Struktur! Verwenden Sie bei Bedarf die Versionierung oder eine neue Struktur (z. B. myStructV2).
✅ Zusammenfassung:
Die Reihenfolge der Felder ist wichtig, da BCS sich nicht selbst beschreibt
Frühere unbekannte generische Typen können nicht deserialisiert werden
Passen Sie die Strukturlayouts in Move und im Frontend immer an
Verwenden Sie für Upgrades die Schemaversionierung oder dynamische Felder
Eingereicht von: md rifat hossen Fügt das gerne als Antwort in Peera ein → es sollte euch helfen, das Kopfgeld zu verdienen.
Weißt du die Antwort?
Bitte melde dich an und teile sie.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Verdiene deinen Anteil an 1000 Sui
Sammle Reputationspunkte und erhalte Belohnungen für deine Hilfe beim Wachstum der Sui-Community.
- Warum benötigt BCS eine genaue Feldreihenfolge für die Deserialisierung, wenn Move-Strukturen benannte Felder haben?53
- Fehler bei der Überprüfung mehrerer Quellen“ in den Veröffentlichungen des Sui Move-Moduls — Automatisierte Fehlerbehebung43
- Sui-Transaktion schlägt fehl: Objekte sind für eine andere Transaktion reserviert25
- Wie interagieren Fähigkeitsbeschränkungen mit dynamischen Feldern in heterogenen Sammlungen?05