Τα συμβάντα Anchor είναι τυποποιημένες εγγραφές σειριοποιημένες με Borsh, οι οποίες περιλαμβάνονται στο IDL ενός προγράμματος. Επιτρέπουν στους clients να ερμηνεύουν τη δραστηριότητα του προγράμματος χωρίς να αναλύουν αναγνώσιμα από τον άνθρωπο μηνύματα log.
Τα συμβάντα είναι χρήσιμα για την ευρετηρίαση και τις ειδοποιήσεις, αλλά δεν αποτελούν κατάσταση του προγράμματος. Αποθηκεύστε σε έναν λογαριασμό κάθε τιμή που το πρόγραμμα πρέπει να διαβάσει ξανά και αντιμετωπίστε τα συμβάντα ως εγγραφή που προκύπτει από μια επιτυχημένη συναλλαγή.
Ορισμός και εκπομπή ενός συμβάντος
Προσθέστε το #[event] σε ένα struct και καλέστε το emit! αφού ολοκληρωθεί με επιτυχία η αλλαγή της κατάστασης.
use anchor_lang::prelude::*;#[program]pub mod payments {use super::*;pub fn record_payment(ctx: Context<RecordPayment>, amount: u64) -> Result<()> {emit!(PaymentRecorded {authority: ctx.accounts.authority.key(),amount,});Ok(())}}#[derive(Accounts)]pub struct RecordPayment<'info> {pub authority: Signer<'info>,}#[event]pub struct PaymentRecorded {pub authority: Pubkey,pub amount: u64,}
Το emit! γράφει τα bytes του συμβάντος με το syscall sol_log_data της Solana. Τα logs της συναλλαγής
εμφανίζουν το αποτέλεσμα ως Program data: <base64>. Η κωδικοποιημένη τιμή ξεκινά με
τον διακριτικό αναγνωριστικό (discriminator) του συμβάντος, ακολουθούμενο από τα πεδία του σειριοποιημένα με Borsh.
Διατηρήστε τα συμβάντα συγκεκριμένα. Τα μεγάλα strings και τα vectors καταναλώνουν υπολογιστική ισχύ, αυξάνουν τα μεταδεδομένα της συναλλαγής και είναι πιο πιθανό να επηρεαστούν από την περικοπή των logs.
Επιλογή μεταξύ συμβάντων log και CPI
Το Anchor υποστηρίζει δύο διαδρομές εκπομπής:
| Διαδρομή | Θέση αποθήκευσης | Αντιστάθμισμα |
|---|---|---|
emit! | Logs δεδομένων προγράμματος | Απλούστερο και φθηνότερο, αλλά τα logs μπορεί να περικοπούν |
emit_cpi! | Δεδομένα εσωτερικής εντολής (inner instruction) από ένα CPI | Πιο ανθεκτικά μεταδεδομένα, με επιπλέον λογαριασμούς και υπολογιστική ισχύ |
Για να χρησιμοποιήσετε συμβάντα CPI, ενεργοποιήστε το χαρακτηριστικό event-cpi του Anchor:
[dependencies]anchor-lang = { version = "1.1.2", features = ["event-cpi"] }
Στη συνέχεια, προσθέστε την ένδειξη #[event_cpi] στο struct λογαριασμών της εντολής και εκπέμψτε
από έναν handler του οποίου η μεταβλητή context ονομάζεται ctx:
pub fn record_payment(ctx: Context<RecordPayment>, amount: u64) -> Result<()> {emit_cpi!(PaymentRecorded {authority: ctx.accounts.authority.key(),amount,});Ok(())}#[event_cpi]#[derive(Accounts)]pub struct RecordPayment<'info> {pub authority: Signer<'info>,}
Το #[event_cpi] προσθέτει τον λογαριασμό event authority και τους λογαριασμούς του τρέχοντος προγράμματος που απαιτούνται από
το υπογεγραμμένο self-CPI. Τα συμβάντα CPI δεν μπορούν να καταναλωθούν μέσω της συνδρομής συμβάντων log
του Anchor· αντίθετα, ανακτήστε τη συναλλαγή και αποκωδικοποιήστε την αντίστοιχη εσωτερική
εντολή.
Χρησιμοποιήστε το ledger ως πηγή
Οι πάροχοι RPC μπορεί να περικόψουν τα logs του προγράμματος. Αν η απώλεια ενός συμβάντος θα επηρέαζε υπόλοιπα, δικαιώματα ή άλλη ροή εργασίας κρίσιμη για την ορθότητα, εξαγάγετε το αποτέλεσμα από τους λογαριασμούς του προγράμματος ή από τα δεδομένα εντολών της συναλλαγής. Χρησιμοποιήστε τα συμβάντα για να κάνετε την ευρετηρίαση αποδοτική, όχι ως τη μοναδική καταγραφή της κατάστασης.
Εγγραφή σε συμβάντα από TypeScript
Ο τρέχων TypeScript client του Anchor είναι το @anchor-lang/core. Ένα στιγμιότυπο Program
που δημιουργείται από το IDL του προγράμματος μπορεί να αποκωδικοποιεί τα αντίστοιχα συμβάντα log και να τα παραδίδει
μέσω της συνδρομής log του RPC:
const listenerId = program.addEventListener("PaymentRecorded",(event, slot, signature) => {console.log({ event, slot, signature });},"confirmed");// Remove the websocket subscription when it is no longer needed.await program.removeEventListener(listenerId);
Η συνάρτηση callback λαμβάνει τα αποκωδικοποιημένα δεδομένα του συμβάντος, το slot και την υπογραφή της συναλλαγής. Οι συνδρομές καλύπτουν μόνο τα logs που παραδίδονται ενόσω η σύνδεση είναι ενεργή. Αποθηκεύστε μόνιμα τις υπογραφές που έχουν επεξεργαστεί και συμπληρώστε αναδρομικά τις συναλλαγές που χάθηκαν μετά από αποσύνδεση, εάν ο καταναλωτής χρειάζεται πλήρες ιστορικό.
Αποκωδικοποίηση των logs ανακτημένων συναλλαγών
Για αναδρομική συμπλήρωση και εφάπαξ έλεγχο, ανακτήστε μια συναλλαγή και περάστε κάθε
payload Program data: στον event coder:
const transaction = await connection.getTransaction(signature, {commitment: "confirmed",maxSupportedTransactionVersion: 0});if (!transaction?.meta || transaction.meta.err) {throw new Error("Transaction was not found or did not succeed");}for (const log of transaction.meta.logMessages ?? []) {const prefix = "Program data: ";if (!log.startsWith(prefix)) continue;const decoded = program.coder.events.decode(log.slice(prefix.length));if (decoded) console.log(decoded.name, decoded.data);}
Μια συναλλαγή μπορεί να καλεί αρκετά προγράμματα. Για έναν parser παραγωγής, παρακολουθήστε τα όρια των logs
Program <address> invoke και success ή χρησιμοποιήστε τον event parser του Anchor, ώστε μια εγγραφή Program data: ενός άλλου προγράμματος να μην αποκωδικοποιηθεί ως δική σας.
Οι αποτυχημένες συναλλαγές μπορεί να διατηρούν logs και εσωτερικές εντολές από εργασίες που ολοκληρώθηκαν
πριν από το σφάλμα, γι' αυτό απορρίπτετε πάντα τα μεταδεδομένα συναλλαγής με μη μηδενικό err
πριν από την ευρετηρίαση οποιασδήποτε μορφής συμβάντος. Η συνδρομή log του Anchor πραγματοποιεί αυτόν τον έλεγχο
πριν από την παράδοση ενός callback συμβάντος.
Για ένα συμβάν emit_cpi!, εντοπίστε το self-CPI στο meta.innerInstructions,
επιβεβαιώστε ότι το αναγνωριστικό προγράμματός του είναι το δικό σας πρόγραμμα και αποκωδικοποιήστε με base58 τα δεδομένα
εντολής του. Πριν αφαιρέσετε οποιαδήποτε bytes, επαληθεύστε το
EVENT_IX_TAG_LE του Anchor:
import bs58 from "bs58";const EVENT_IX_TAG_LE = Buffer.from("e445a52e51cb9a1d", "hex");const instructionData = Buffer.from(bs58.decode(innerInstruction.data));if (!instructionData.subarray(0, 8).equals(EVENT_IX_TAG_LE)) {// This is an ordinary self-CPI, not an emit_cpi! event.continue;}const eventData = instructionData.subarray(8).toString("base64");const decoded = program.coder.events.decode(eventData);
Ο έλεγχος τόσο του αναγνωριστικού προγράμματος όσο και του tag αποτρέπει ένα συνηθισμένο self-CPI από το να θεωρηθεί εσφαλμένα συμβάν.
Εκδοσιοποίηση σχημάτων συμβάντων
Από προεπιλογή, το Anchor παράγει ένα διακριτικό αναγνωριστικό (discriminator) 8 byte από το όνομα του συμβάντος.
Το Anchor υποστηρίζει επίσης ρητό discriminator μέσω του
#[event(discriminator = ...)]. Τα discriminators πρέπει να παραμένουν μοναδικά εντός του
προγράμματος και σταθερά για κάθε client που αποκωδικοποιεί το συμβάν.
Η αλλαγή των πεδίων ενός struct που εκπέμπεται αλλάζει τη διάταξή του κατά Borsh. Για μια αλλαγή σχήματος
που προκαλεί ασυμβατότητα, ορίστε ένα νέο συμβάν, όπως το PaymentRecordedV2, αντί να αλλάξετε
αθόρυβα το PaymentRecorded. Συνεχίστε να εκπέμπετε το παλιό συμβάν κατά τη διάρκεια ενός
παραθύρου μετάβασης, όταν οι υπάρχοντες indexers εξακολουθούν να εξαρτώνται από αυτό.
Για δεδομένα που εξελίσσονται, προτιμήστε πεδία σταθερού πλάτους και αναγνωριστικά που επιτρέπουν σε έναν indexer να ανακτά ξεχωριστά πλουσιότερα δεδομένα λογαριασμού. Δημοσιεύστε το ενημερωμένο IDL πριν ή μαζί με την αναβάθμιση του προγράμματος, ώστε οι καταναλωτές να μπορούν να αποκωδικοποιούν αμέσως το νέο συμβάν.
Δοκιμή συμβάντων
Δοκιμάστε το αποκωδικοποιημένο συμβόλαιο δεδομένων, όχι το string base64. Καταχωρίστε τον listener πριν στείλετε την εντολή, ελέγξτε τα αποκωδικοποιημένα πεδία και την υπογραφή της συναλλαγής και αφαιρείτε πάντα τον listener:
let listenerId = 0;const received = new Promise<{event: { authority: PublicKey; amount: BN };signature: string;}>((resolve) => {listenerId = program.addEventListener("PaymentRecorded",(event, _slot, signature) => resolve({ event, signature }),"confirmed");});try {const signature = await program.methods.recordPayment(new BN(500)).rpc();const result = await received;assert.equal(result.signature, signature);assert.equal(result.event.amount.toString(), "500");} finally {await program.removeEventListener(listenerId);}
Δοκιμάστε επίσης ότι ο indexer αγνοεί τα bytes συμβάντων που διατηρούνται στα μεταδεδομένα αποτυχημένων συναλλαγών, και προσθέστε ένα fixture για κάθε υποστηριζόμενη έκδοση συμβάντος. Αν χρησιμοποιείτε
emit_cpi!, δοκιμάστε την αποκωδικοποίηση από το innerInstructions, επειδή οι log listeners δεν
λαμβάνουν απευθείας αυτά τα συμβάντα.
Περαιτέρω ανάγνωση
Is this page helpful?