Απλή σειριοποίηση
Απλή σειριοποίηση (SSZ) είναι η μέθοδος σειριοποίησης που χρησιμοποιείται στο Beacon Chain. Αντικαθιστά τη σειριοποίηση RLP που χρησιμοποιείται στο επίπεδο εκτέλεσης παντού στο επίπεδο συναίνεσης εκτός από το πρωτόκολλο ανακάλυψης ομότιμου. Για να μάθετε περισσότερα σχετικά με τη σειριοποίηση RLP, ανατρέξτε στην ενότητα Πρόθεμα αναδρομικού μήκους (RLP). Το SSZ έχει σχεδιαστεί για να είναι ντετερμινιστικό και επίσης να είναι αποτελεσματικό στο merkleize. Το SSZ μπορεί να θεωρηθεί ότι έχει δύο στοιχεία: ένα σχήμα σειριοποίησης και ένα σχήμα Merkleization που έχει σχεδιαστεί για να λειτουργεί αποτελεσματικά με τη σειριακή δομή δεδομένων.
Πώς λειτουργεί το SSZ;
Σειριοποίηση
Το SSZ είναι ένα σχήμα σειριοποίησης που δεν αυτοπεριγράφεται - μάλλον βασίζεται σε ένα σχήμα που πρέπει να είναι γνωστό εκ των προτέρων. Ο στόχος της σειριοποίησης SSZ είναι να αναπαραστούν αντικείμενα αυθαίρετης πολυπλοκότητας ως συμβολοσειρές bytes. Αυτή είναι μια πολύ απλή διαδικασία για «βασικούς τύπους». Το στοιχείο απλώς μετατρέπεται σε δεκαεξαδικά bytes. Οι βασικοί τύποι περιλαμβάνουν:
- unsigned integers
- Booleans
Για τους σύνθετους τύπους, η σειριοποίηση είναι πιο περίπλοκη επειδή ο σύνθετος τύπος περιέχει πολλαπλά στοιχεία που μπορεί να έχουν διαφορετικούς τύπους ή διαφορετικά μεγέθη ή και τα δύο. Όπου όλα αυτά τα αντικείμενα έχουν σταθερά μήκη (δηλαδή το μέγεθος των στοιχείων θα είναι πάντα σταθερό ανεξάρτητα από τις πραγματικές τους τιμές) η σειριοποίηση είναι απλώς μια μετατροπή κάθε στοιχείου στον σύνθετο τύπο ταξινομημένο σε bytestrings little-endian. Αυτά τα bytestrings ενώνονται μεταξύ τους. Το σειριοποιημένο αντικείμενο έχει την αναπαράσταση bytelist των στοιχείων σταθερού μήκους στην ίδια σειρά με την οποία εμφανίζονται στο αποσυσκευασμένο αντικείμενο.
Για τύπους με μεταβλητά μήκη, τα πραγματικά δεδομένα αντικαθίστανται από μια τιμή "offset" στη θέση αυτού του στοιχείου στο σειριοποιημένο αντικείμενο. Τα πραγματικά δεδομένα προστίθενται σε έναν σωρό στο τέλος του σειριοποιημένου αντικειμένου. Η τιμή offset είναι ο δείκτης για την αρχή των πραγματικών δεδομένων στο σωρό, λειτουργώντας ως δείκτης στα σχετικά bytes.
Το παρακάτω παράδειγμα απεικονίζει πώς λειτουργεί η μετατόπιση για έναν περιέκτη με στοιχεία σταθερού και μεταβλητού μήκους:
12 struct Dummy {34 number1: u64,5 number2: u64,6 vector: Vec<u8>,7 number3: u648 }910 dummy = Dummy{1112 number1: 37,13 number2: 55,14 vector: vec![1,2,3,4],15 number3: 22,16 }1718 serialized = ssz.serialize(dummy)19Εμφάνιση όλων
Το serialized
θα είχε την ακόλουθη δομή (εδώ συμπληρώνεται μόνο σε 4 bit, στην πραγματικότητα συμπληρώνεται σε 32 bit και διατηρείται η αναπαράσταση int
για λόγους σαφήνειας):
1[37, 0, 0, 0, 55, 0, 0, 0, 16, 0, 0, 0, 22, 0, 0, 0, 1, 2, 3, 4]2------------ ----------- ----------- ----------- ----------3 | | | | |4 number1 number2 offset for number 3 value for5 vector vector6
Χωρισμένο σε γραμμές για λόγους σαφήνειας:
1[2 37, 0, 0, 0, # little-endian encoding of `number1`.3 55, 0, 0, 0, # little-endian encoding of `number2`.4 16, 0, 0, 0, # The "offset" that indicates where the value of `vector` starts (little-endian 16).5 22, 0, 0, 0, # little-endian encoding of `number3`.6 1, 2, 3, 4, # The actual values in `vector`.7]
Αυτό εξακολουθεί να είναι μια απλοποίηση, οι ακέραιοι και τα μηδενικά στα παραπάνω σχήματα θα αποθηκεύονταν στην πραγματικότητα ως λίστες byte, όπως αυτή:
1[2 10100101000000000000000000000000 # little-endian encoding of `number1`3 10110111000000000000000000000000 # little-endian encoding of `number2`.4 10010000000000000000000000000000 # The "offset" that indicates where the value of `vector` starts (little-endian 16).5 10010110000000000000000000000000 # little-endian encoding of `number3`.6 10000001100000101000001110000100 # The actual value of the `bytes` field.7]
Έτσι, οι πραγματικές τιμές για τους τύπους μεταβλητού μήκους αποθηκεύονται σε ένα σωρό στο τέλος του σειριοποιημένου αντικειμένου με τις μετατοπίσεις τους αποθηκευμένες στις σωστές θέσεις τους στην ταξινομημένη λίστα των πεδίων.
Υπάρχουν επίσης ορισμένες ειδικές περιπτώσεις που απαιτούν ειδική διαχείριση, όπως ο τύπος BitList
που απαιτεί να προστεθεί ένα όριο μήκους κατά τη σειριοποίηση και να αφαιρεθεί κατά την αποσειριοποίηση. Πλήρεις λεπτομέρειες είναι διαθέσιμες στις προδιαγραφές SSZ.
Αποσειριοποίηση
Για να αποσειριοποιήσετε αυτό το αντικείμενο απαιτείται το σχήμα. Το σχήμα ορίζει την ακριβή διάταξη των σειριοποιημένων δεδομένων, έτσι ώστε κάθε συγκεκριμένο στοιχείο να μπορεί να αποσειριοποιηθεί από ένα blob byte σε κάποιο ουσιαστικό αντικείμενο με τα στοιχεία να έχουν τον σωστό τύπο, τιμή, μέγεθος και θέση. Είναι το σχήμα που λέει στον αποσειριοποιητή ποιες τιμές είναι πραγματικές τιμές και ποιες είναι μετατοπίσεις. Όλα τα ονόματα πεδίων εξαφανίζονται όταν ένα αντικείμενο σειριοποιείται, αλλά αποκαθίστανται κατά την αποσειριοποίηση σύμφωνα με το σχήμα.
Δείτε το ssz.dev για μια διαδραστική εξήγηση σχετικά με αυτό.
Merkleization
Αυτό το σειριοποιημένο αντικείμενο SSZ μπορεί στη συνέχεια να μετατραπεί σε Merkle, δηλαδή να μετατραπεί σε μια αναπαράσταση Merkle-δέντρου των ίδιων δεδομένων. Πρώτον, προσδιορίζεται ο αριθμός των chunk των 32 byte στο σειριοποιημένο αντικείμενο. Αυτά είναι τα «φύλλα» του δέντρου. Ο συνολικός αριθμός των φύλλων πρέπει να είναι δύναμη του 2, έτσι ώστε ο τελικός συνδυασμός των φύλλων να παράγει τελικά μια μόνο ρίζα δέντρου κατακερματισμού. Εάν αυτό δεν συμβαίνει φυσικά, προστίθενται επιπλέον φύλλα που περιέχουν 32 byte μηδενικών. Διαγραμματικά:
1 hash tree root2 / \3 / \4 / \5 / \6 hash of leaves hash of leaves7 1 and 2 3 and 48 / \ / \9 / \ / \10 / \ / \11 leaf1 leaf2 leaf3 leaf4Εμφάνιση όλων
Υπάρχουν επίσης περιπτώσεις όπου τα φύλλα του δέντρου δεν κατανέμονται φυσικά ομοιόμορφα όπως στο παραπάνω παράδειγμα. Για παράδειγμα, το φύλλο 4 θα μπορούσε να είναι ένα δοχείο με πολλαπλά στοιχεία που απαιτούν την προσθήκη πρόσθετου "βάθους" στο δέντρο Merkle, δημιουργώντας ένα άνισο δέντρο.
Αντί να αναφερόμαστε σε αυτά τα στοιχεία δέντρου ως φύλλο X, κόμβος X κ.λπ., μπορούμε να τους δώσουμε γενικευμένους δείκτες, ξεκινώντας από τη ρίζα = 1 και μετρώντας από αριστερά προς τα δεξιά σε κάθε επίπεδο. Αυτός είναι ο γενικευμένος δείκτης που εξηγήθηκε παραπάνω. Κάθε στοιχείο στη σειριασμένη λίστα έχει έναν γενικευμένο δείκτη ίσο με 2 ** depth + idx
όπου idx είναι η θέση του μηδενικού δείκτη στο σειριασμένο αντικείμενο και το βάθος είναι ο αριθμός των επιπέδων στο δέντρο Merkle, το οποίο μπορεί να προσδιοριστεί ως ο δυαδικός λογάριθμος του αριθμού των στοιχείων (φύλλα).
Γενικευμένοι δείκτες
Ένας γενικευμένος δείκτης είναι ένας ακέραιος που αντιπροσωπεύει έναν κόμβο σε ένα δυαδικό δέντρο Merkle όπου κάθε κόμβος έχει έναν γενικευμένο δείκτη 2 ** depth + δείκτης στη σειρά
.
1 1 --depth = 0 2**0 + 0 = 12 2 3 --depth = 1 2**1 + 0 = 2, 2**1+1 = 33 4 5 6 7 --depth = 2 2**2 + 0 = 4, 2**2 + 1 = 5...4
Αυτή η αναπαράσταση αποδίδει έναν δείκτη κόμβου για κάθε κομμάτι δεδομένων στο δέντρο Merkle.
Πολλαπλές πιστοποιήσεις
Η παροχή της λίστας των γενικευμένων δεικτών που αντιπροσωπεύουν ένα συγκεκριμένο στοιχείο μας επιτρέπει να το επαληθεύσουμε έναντι της ρίζας του δέντρου hash. Αυτή η ρίζα είναι η αποδεκτή έκδοση της πραγματικότητας από εμάς. Οποιαδήποτε δεδομένα μας παρέχονται μπορούν να επαληθευτούν έναντι αυτής της πραγματικότητας εισάγοντάς τα στη σωστή θέση στο δέντρο Merkle (καθορισμένη από τον γενικευμένο δείκτη του) και παρατηρώντας ότι η ρίζα παραμένει σταθερή. Υπάρχουν συναρτήσεις στις προδιαγραφές εδώ που δείχνουν πώς να υπολογίσουμε το ελάχιστο σύνολο κόμβων που απαιτούνται για την επαλήθευση του περιεχομένου ενός συγκεκριμένου συνόλου γενικευμένων δεικτών.
Για παράδειγμα, για να επαληθεύσουμε δεδομένα στον δείκτη 9 στο παρακάτω δέντρο, χρειαζόμαστε το hash των δεδομένων στους δείκτες 8, 9, 5, 3, 1. Το hash του (8,9) πρέπει να ισούται με το hash (4), το οποίο κάνει hash με το 5 για να παράγει το 2, το οποίο κάνει hash με το 3 για να παράγει τη ρίζα του δέντρου 1. Εάν παρέχθηκαν εσφαλμένα δεδομένα για το 9, η ρίζα θα άλλαζε - θα το ανιχνεύαμε αυτό και θα αποτύχαμε να επαληθεύσουμε το κλάδο.
1* = data required to generate proof23 1*4 2 3*5 4 5* 6 768* 9* 10 11 12 13 14 157