Εκτέλεση CPI και δικαιώματα

Περίληψη

Τα CPI περνούν από 11 βήματα runtime που περιλαμβάνουν έλεγχο δικαιωμάτων, μετάφραση λογαριασμών και συγχρονισμό δεδομένων. Μέγιστο βάθος κλήσης: 5 (9 με SIMD-0268). Οι κανόνες δικαιωμάτων αποτρέπουν τον καλούμενο από κλιμάκωση πέρα από αυτό που παραχώρησε ο καλών.

Κανόνες δικαιωμάτων

Τα CPI επεκτείνουν τα δικαιώματα λογαριασμού του καλούντος στον καλούμενο με αυστηρή επιβολή. Το runtime ελέγχει αυτούς τους κανόνες στο prepare_next_instruction:

ΣενάριοΕπιτρέπεται;Σημείο επιβολήςΣφάλμα
Ο καλών περνά λογαριασμό ως εγγράψιμο, ο καλούμενος τον σημειώνει ως εγγράψιμοΝαι----
Ο καλών περνά λογαριασμό ως μόνο για ανάγνωση, ο καλούμενος τον σημειώνει ως εγγράψιμοΌχιprepare_next_instructionPrivilegeEscalation
Ο καλών περνά λογαριασμό ως εγγράψιμο, ο καλούμενος τον σημειώνει ως μόνο για ανάγνωσηΝαι----
Ο καλών περνά λογαριασμό ως υπογράφοντα, ο καλούμενος τον σημειώνει ως υπογράφονταΝαι----
Ο καλών περνά λογαριασμό ως μη υπογράφοντα, ο καλούμενος τον σημειώνει ως υπογράφοντα, ο λογαριασμός είναι PDA που προέρχεται από τα seeds του καλούντοςΝαιprepare_next_instruction--
Ο καλών περνά λογαριασμό ως μη υπογράφοντα, ο καλούμενος τον σημειώνει ως υπογράφοντα, ο λογαριασμός ΔΕΝ είναι PDA από τον καλούνταΌχιprepare_next_instructionPrivilegeEscalation
Ο καλών περνά λογαριασμό ως υπογράφοντα, ο καλούμενος τον σημειώνει ως μη υπογράφονταΝαι----
Το πρόγραμμα A καλεί τον εαυτό του απευθείας (A -> A)Ναιpush()--
Το πρόγραμμα A καλεί το B που καλεί το A (έμμεση επανείσοδος)Όχιpush()ReentrancyNotAllowed
CPI σε native loader, bpf_loader, bpf_loader_deprecated ή precompileΌχιcheck_authorized_programProgramNotSupported
Ο λογαριασμός δεν βρέθηκε στη συναλλαγήΌχιprepare_next_instructionMissingAccount

Οι κανόνες προνομίων μπορούν να συνοψιστούν ως εξής:

  1. Το προνόμιο εγγραφής δεν μπορεί να κλιμακωθεί. Εάν ο καλών επισημάνει έναν λογαριασμό ως μόνο για ανάγνωση, ο καλούμενος δεν μπορεί να τον επισημάνει ως εγγράψιμο.
  2. Το προνόμιο υπογραφής απαιτεί εξουσιοδότηση. Ένας λογαριασμός μπορεί να είναι υπογράφων στον καλούμενο μόνο εάν (α) ήταν ήδη υπογράφων στον καλούντα, Ή (β) είναι ένα PDA που προέρχεται από τα seeds του προγράμματος κλήσης μέσω invoke_signed.
  3. Η μείωση προνομίων επιτρέπεται πάντα. Ο καλούμενος μπορεί να χρησιμοποιήσει λιγότερα προνόμια από αυτά που παραχώρησε ο καλών.

Ροή εκτέλεσης 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:

  1. Ελέγχει τον αριθμό των συνόλων signer seed έναντι του MAX_SIGNERS (16).
  2. Ελέγχει το μήκος κάθε συνόλου seed έναντι του MAX_SEEDS (16 seeds ανά σύνολο).
  3. Καλεί το Pubkey::create_program_address με τα seeds και το program ID του καλούντος. Εάν τα seeds δεν παράγουν έγκυρο PDA, το CPI αποτυγχάνει με BadSeeds.
  4. Συλλέγει τα προκύπτοντα PDA pubkeys σε ένα signers vec.

Αυτά τα παραγόμενα PDAs αντιμετωπίζονται ως έγκυροι signers για την εντολή του callee.

Βήμα 6: Έλεγχος εξουσιοδοτημένου προγράμματος

Πριν προχωρήσει, το runtime καλεί το check_authorized_program για να επαληθεύσει ότι το πρόγραμμα-στόχος επιτρέπεται για CPI. Τα ακόλουθα προγράμματα αποκλείονται:

  • Ο native loader
  • bpf_loader και bpf_loader_deprecated
  • bpf_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 το οποίο:

  1. Επικυρώνει τον αριθμό πληροφοριών λογαριασμού έναντι του MAX_CPI_ACCOUNT_INFOS (128, ή 255 με το SIMD-0339).
  2. Χρεώνει το κόστος μετάφρασης πληροφοριών λογαριασμού (μόνο SIMD-0339): (num_account_infos * 80) / 250 CUs.
  3. Για κάθε μη εκτελέσιμο, μη διπλότυπο λογαριασμό, δημιουργεί ένα CallerAccount μεταφράζοντας δείκτες από τη μνήμη VM στη μνήμη host. Αυτό περιλαμβάνει χρέωση κόστους σειριοποίησης δεδομένων ανά λογαριασμό: account_data_len / cpi_bytes_per_unit CUs.

Βήμα 9: Συγχρονισμός λογαριασμού πριν το CPI (από καλούντα σε καλούμενο)

Πριν την εκτέλεση του καλούμενου, το runtime συγχρονίζει τις τροποποιήσεις λογαριασμού του καλούντος ώστε ο καλούμενος να μπορεί να τις δει. Η συνάρτηση update_callee_account καλείται για κάθε μεταφρασμένο λογαριασμό, αντιγράφοντας lamports, δεδομένα και ιδιοκτήτη. Δείτε Συγχρονισμός δεδομένων λογαριασμού για τη λεπτομερή αντιστοίχιση πεδίων.

Βήμα 10: Push πλαισίου εντολής, εκτέλεση καλούμενου και pop

Το runtime καλεί το process_instruction, το οποίο:

  1. Καλεί το push() για να προσθέσει ένα νέο frame στη στοίβα εντολών. Το push() επιβάλλει τον κανόνα επανεισόδου: ένα πρόγραμμα μπορεί να καλέσει τον εαυτό του μόνο αν είναι ο άμεσος καλών (δηλαδή, το πρόγραμμα A μπορεί να καλέσει το A, αλλά το A δεν μπορεί να καλέσει το B που καλεί το A). Η παραβίαση επιστρέφει ReentrancyNotAllowed.
  2. Καλεί το process_executable_chain το οποίο επιλύει το σημείο εισόδου του προγράμματος του καλούμενου και το καλεί. Ο καλούμενος εκτελείται με τον ίδιο κοινόχρηστο μετρητή υπολογισμού. Όλη η κατανάλωση CU από τον καλούμενο μειώνει τον υπόλοιπο προϋπολογισμό του καλούντος.
  3. Καλεί το pop() για να αφαιρέσει το frame του καλούμενου και να επαληθεύσει ότι τα υπόλοιπα lamport παραμένουν αμετάβλητα (UnbalancedInstruction αν όχι).

Βήμα 11: Συγχρονισμός λογαριασμού μετά το CPI (από καλούμενο σε καλούντα)

Αφού το process_instruction επιστρέψει (το οποίο περιλαμβάνει το pop), το runtime συγχρονίζει τις αλλαγές πίσω στον καλούντα μέσω του update_caller_account για κάθε εγγράψιμο λογαριασμό. Επιπλέον, το update_caller_account_region ενημερώνει τις αντιστοιχίσεις περιοχών μνήμης VM για λογαριασμούς των οποίων οι περιοχές δεδομένων άλλαξαν. Δείτε Συγχρονισμός δεδομένων λογαριασμού για τη λεπτομερή αντιστοίχιση πεδίων.

Η κλήση συστήματος CPI επιστρέφει 0 (επιτυχία) στο πρόγραμμα που την καλεί.

Is this page helpful?

Διαχειρίζεται από

© 2026 Ίδρυμα Solana.
Με επιφύλαξη παντός δικαιώματος.
Συνδεθείτε