Περίληψη
Τα CPI περνούν από 11 βήματα runtime που περιλαμβάνουν έλεγχο δικαιωμάτων, μετάφραση λογαριασμών και συγχρονισμό δεδομένων. Μέγιστο βάθος κλήσης: 5 (9 με SIMD-0268). Οι κανόνες δικαιωμάτων αποτρέπουν τον καλούμενο από κλιμάκωση πέρα από αυτό που παραχώρησε ο καλών.
Κανόνες δικαιωμάτων
Τα CPI επεκτείνουν τα δικαιώματα λογαριασμού του καλούντος στον καλούμενο με
αυστηρή επιβολή. Το runtime ελέγχει αυτούς τους κανόνες στο
prepare_next_instruction:
| Σενάριο | Επιτρέπεται; | Σημείο επιβολής | Σφάλμα |
|---|---|---|---|
| Ο καλών περνά λογαριασμό ως εγγράψιμο, ο καλούμενος τον σημειώνει ως εγγράψιμο | Ναι | -- | -- |
| Ο καλών περνά λογαριασμό ως μόνο για ανάγνωση, ο καλούμενος τον σημειώνει ως εγγράψιμο | Όχι | prepare_next_instruction | PrivilegeEscalation |
| Ο καλών περνά λογαριασμό ως εγγράψιμο, ο καλούμενος τον σημειώνει ως μόνο για ανάγνωση | Ναι | -- | -- |
| Ο καλών περνά λογαριασμό ως υπογράφοντα, ο καλούμενος τον σημειώνει ως υπογράφοντα | Ναι | -- | -- |
| Ο καλών περνά λογαριασμό ως μη υπογράφοντα, ο καλούμενος τον σημειώνει ως υπογράφοντα, ο λογαριασμός είναι PDA που προέρχεται από τα seeds του καλούντος | Ναι | prepare_next_instruction | -- |
| Ο καλών περνά λογαριασμό ως μη υπογράφοντα, ο καλούμενος τον σημειώνει ως υπογράφοντα, ο λογαριασμός ΔΕΝ είναι PDA από τον καλούντα | Όχι | prepare_next_instruction | PrivilegeEscalation |
| Ο καλών περνά λογαριασμό ως υπογράφοντα, ο καλούμενος τον σημειώνει ως μη υπογράφοντα | Ναι | -- | -- |
| Το πρόγραμμα A καλεί τον εαυτό του απευθείας (A -> A) | Ναι | push() | -- |
| Το πρόγραμμα A καλεί το B που καλεί το A (έμμεση επανείσοδος) | Όχι | push() | ReentrancyNotAllowed |
| CPI σε native loader, bpf_loader, bpf_loader_deprecated ή precompile | Όχι | check_authorized_program | ProgramNotSupported |
| Ο λογαριασμός δεν βρέθηκε στη συναλλαγή | Όχι | prepare_next_instruction | MissingAccount |
Οι κανόνες προνομίων μπορούν να συνοψιστούν ως εξής:
- Το προνόμιο εγγραφής δεν μπορεί να κλιμακωθεί. Εάν ο καλών επισημάνει έναν λογαριασμό ως μόνο για ανάγνωση, ο καλούμενος δεν μπορεί να τον επισημάνει ως εγγράψιμο.
- Το προνόμιο υπογραφής απαιτεί εξουσιοδότηση. Ένας λογαριασμός μπορεί να
είναι υπογράφων στον καλούμενο μόνο εάν (α) ήταν ήδη υπογράφων στον καλούντα,
Ή (β) είναι ένα PDA που προέρχεται από τα seeds του προγράμματος κλήσης μέσω
invoke_signed. - Η μείωση προνομίων επιτρέπεται πάντα. Ο καλούμενος μπορεί να χρησιμοποιήσει λιγότερα προνόμια από αυτά που παραχώρησε ο καλών.
Ροή εκτέλεσης CPI
Ένα CPI διέρχεται από πολλαπλά επίπεδα runtime. Αυτή η ενότητα τεκμηριώνει την πλήρη διαδικασία από την κλήση SDK του προγράμματος μέσω του ορίου syscall στο runtime και πίσω. Κάθε βήμα αναφέρεται στο αρχείο πηγαίου κώδικα που το υλοποιεί.
Το μέγιστο ύψος της κλήσης εντολής προγράμματος ονομάζεται
max_instruction_stack_depth
και ορίζεται στη σταθερά
MAX_INSTRUCTION_STACK_DEPTH
με τιμή 5. Με το MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 ενεργό, αυτό αυξάνεται σε 9.
Το ύψος στοίβας 1 είναι η αρχική εντολή συναλλαγής. Κάθε CPI αυξάνει το ύψος κατά 1. Ένα μέγιστο των 5 σημαίνει ότι ένα πρόγραμμα μπορεί να πραγματοποιήσει CPIs έως 4 επίπεδα βάθους (8 επίπεδα βάθους με το SIMD-0268).
Βήμα 1: Το πρόγραμμα καλεί το invoke ή το invoke_signed
Το πρόγραμμα καλεί το
invoke
ή το
invoke_signed.
Το invoke είναι ένα λεπτό wrapper που καλεί το invoke_signed με έναν
κενό πίνακα signer seeds. Η συνάρτηση SDK σειριοποιεί το Instruction, το
slice AccountInfo και τα signer seeds στη μνήμη VM, και στη συνέχεια
ενεργοποιεί το syscall.
Βήμα 2: Είσοδος syscall
Το SBF VM διανέμει στον χειριστή syscall
sol_invoke_signed_rust,
ο οποίος καλεί το κοινό σημείο εισόδου:
cpi_common.
Βήμα 3: Κατανάλωση κόστους κλήσης
Η πρώτη ενέργεια μέσα στο cpi_common είναι να
χρεώσει το σταθερό κόστος κλήσης
από τον κοινόχρηστο μετρητή υπολογισμού:
invoke_units
= 1.000 CUs (ή 946 CUs με SIMD-0339).
Βήμα 4: Μετάφραση εντολής από τη μνήμη VM
Ο χειριστής syscall μεταφράζει την εντολή από τον χώρο διευθύνσεων VM του
προγράμματος σε τύπους Rust πλευράς host μέσω του
translate_instruction_rust,
ο οποίος διαβάζει ένα struct StableInstruction, επικυρώνει το μήκος
δεδομένων έναντι του
MAX_INSTRUCTION_DATA_LEN
(10.240 bytes), και στη συνέχεια χρεώνει το
κόστος σειριοποίησης δεδομένων.
Βήμα 5: Μετάφραση signer seeds και παραγωγή PDAs
Ο χειριστής καλεί το
translate_signers_rust.
Για κάθε σύνολο signer seeds, το runtime:
- Ελέγχει τον αριθμό των συνόλων signer seed έναντι του
MAX_SIGNERS(16). - Ελέγχει το μήκος κάθε συνόλου seed έναντι του
MAX_SEEDS(16 seeds ανά σύνολο). - Καλεί το
Pubkey::create_program_addressμε τα seeds και το program ID του καλούντος. Εάν τα seeds δεν παράγουν έγκυρο PDA, το CPI αποτυγχάνει μεBadSeeds. - Συλλέγει τα προκύπτοντα PDA pubkeys σε ένα
signersvec.
Αυτά τα παραγόμενα PDAs αντιμετωπίζονται ως έγκυροι signers για την εντολή του callee.
Βήμα 6: Έλεγχος εξουσιοδοτημένου προγράμματος
Πριν προχωρήσει, το runtime καλεί το
check_authorized_program
για να επαληθεύσει ότι το πρόγραμμα-στόχος επιτρέπεται για CPI. Τα ακόλουθα
προγράμματα αποκλείονται:
- Ο native loader
bpf_loaderκαιbpf_loader_deprecatedbpf_loader_upgradeable(εκτός από συγκεκριμένες εντολές διαχείρισης:upgrade,set_authority,set_authority_checked(feature-gated),extend_program_checked(feature-gated),close)- Προγράμματα precompile (ed25519, secp256k1, κ.λπ.)
Η παραβίαση επιστρέφει
ProgramNotSupported.
Βήμα 7: Επαλήθευση προνομίων (prepare_next_instruction)
Το runtime καλεί το
prepare_next_instruction
το οποίο δημιουργεί τη λίστα InstructionAccount του callee και επιβάλλει
τους κανόνες προνομίων. Δείτε Κανόνες προνομίων παρακάτω για
τον πλήρη πίνακα αποφάσεων.
Βήμα 8: Μετάφραση πληροφοριών λογαριασμού
Ο χειριστής καλεί το translate_accounts το οποίο:
- Επικυρώνει τον αριθμό πληροφοριών λογαριασμού
έναντι του
MAX_CPI_ACCOUNT_INFOS(128, ή 255 με το SIMD-0339). - Χρεώνει το κόστος μετάφρασης πληροφοριών λογαριασμού
(μόνο SIMD-0339):
(num_account_infos * 80) / 250CUs. - Για κάθε μη εκτελέσιμο, μη διπλότυπο λογαριασμό, δημιουργεί ένα
CallerAccountμεταφράζοντας δείκτες από τη μνήμη VM στη μνήμη host. Αυτό περιλαμβάνει χρέωση κόστους σειριοποίησης δεδομένων ανά λογαριασμό:account_data_len / cpi_bytes_per_unitCUs.
Βήμα 9: Συγχρονισμός λογαριασμού πριν το CPI (από καλούντα σε καλούμενο)
Πριν την εκτέλεση του καλούμενου, το runtime συγχρονίζει τις τροποποιήσεις
λογαριασμού του καλούντος ώστε ο καλούμενος να μπορεί να τις δει. Η συνάρτηση
update_callee_account
καλείται για κάθε μεταφρασμένο λογαριασμό, αντιγράφοντας lamports, δεδομένα και
ιδιοκτήτη. Δείτε
Συγχρονισμός δεδομένων λογαριασμού
για τη λεπτομερή αντιστοίχιση πεδίων.
Βήμα 10: Push πλαισίου εντολής, εκτέλεση καλούμενου και pop
Το runtime καλεί το
process_instruction,
το οποίο:
- Καλεί το
push()για να προσθέσει ένα νέο frame στη στοίβα εντολών. Τοpush()επιβάλλει τον κανόνα επανεισόδου: ένα πρόγραμμα μπορεί να καλέσει τον εαυτό του μόνο αν είναι ο άμεσος καλών (δηλαδή, το πρόγραμμα A μπορεί να καλέσει το A, αλλά το A δεν μπορεί να καλέσει το B που καλεί το A). Η παραβίαση επιστρέφειReentrancyNotAllowed. - Καλεί το
process_executable_chainτο οποίο επιλύει το σημείο εισόδου του προγράμματος του καλούμενου και το καλεί. Ο καλούμενος εκτελείται με τον ίδιο κοινόχρηστο μετρητή υπολογισμού. Όλη η κατανάλωση CU από τον καλούμενο μειώνει τον υπόλοιπο προϋπολογισμό του καλούντος. - Καλεί το
pop()για να αφαιρέσει το frame του καλούμενου και να επαληθεύσει ότι τα υπόλοιπα lamport παραμένουν αμετάβλητα (UnbalancedInstructionαν όχι).
Βήμα 11: Συγχρονισμός λογαριασμού μετά το CPI (από καλούμενο σε καλούντα)
Αφού το process_instruction επιστρέψει (το οποίο περιλαμβάνει το pop), το
runtime συγχρονίζει τις αλλαγές πίσω στον καλούντα μέσω του
update_caller_account
για κάθε εγγράψιμο λογαριασμό. Επιπλέον, το
update_caller_account_region
ενημερώνει τις αντιστοιχίσεις περιοχών μνήμης VM για λογαριασμούς των οποίων οι
περιοχές δεδομένων άλλαξαν. Δείτε
Συγχρονισμός δεδομένων λογαριασμού
για τη λεπτομερή αντιστοίχιση πεδίων.
Η κλήση συστήματος CPI επιστρέφει 0 (επιτυχία) στο πρόγραμμα που την καλεί.
Is this page helpful?