Solana-dokumentaatioOhjelmien kehittäminen

Anchor-tapahtumat

Anchor-tapahtumat ovat tyypitettyjä, Borsh-serialisoituja tietueita, jotka sisältyvät ohjelman IDL:ään. Niiden avulla asiakasohjelmat voivat tulkita ohjelman toimintaa ilman ihmisluettavien lokiviestien jäsentämistä.

Tapahtumat sopivat hyvin indeksointiin ja ilmoituksiin, mutta ne eivät ole ohjelman tila. Tallenna kaikki arvot, jotka ohjelman täytyy lukea myöhemmin uudelleen, tilille, ja käsittele tapahtumia onnistuneesta transaktiosta johdettuna tietueena.

Tapahtuman määrittäminen ja lähettäminen

Lisää #[event] structiin ja kutsu emit! sen jälkeen, kun tilan muutos on onnistunut.

lib.rs
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! kirjoittaa tapahtuman tavut Solanan sol_log_data-syscallilla. Transaktiolokeissa tulos näkyy muodossa Program data: <base64>. Koodattu arvo alkaa tapahtuman erottimella (discriminator), jota seuraavat sen Borsh-serialisoidut kentät.

Pidä tapahtumat tiiviinä. Suuret merkkijonot ja vektorit kuluttavat laskentaa, kasvattavat transaktion metadataa ja kärsivät todennäköisemmin lokien katkaisusta.

Loki- ja CPI-tapahtumien valinta

Anchor tukee kahta lähetystapaa:

TapaTallennuspaikkaKompromissi
emit!Ohjelman datalokitYksinkertaisempi ja halvempi, mutta lokit voivat katketa
emit_cpi!CPI-kutsun sisäisen käskyn instruction dataKestävämpi metadata, mutta vaatii lisätilejä ja laskentaa

Jos haluat käyttää CPI-tapahtumia, ota käyttöön Anchorin event-cpi-ominaisuus:

Cargo.toml
[dependencies]
anchor-lang = { version = "1.1.2", features = ["event-cpi"] }

Merkitse sitten käskyn tilien struct #[event_cpi]-attribuutilla ja lähetä tapahtuma käsittelijästä, jonka konteksti-muuttujan nimi on ctx:

lib.rs
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] lisää tapahtuman authority-tilin ja nykyisen program accountin, joita allekirjoitettu itse-CPI tarvitsee. CPI-tapahtumia ei voi vastaanottaa Anchorin lokitapahtumatilauksen kautta. Hae sen sijaan transaktio ja pura vastaava sisäinen käsky.

Käytä lohkoketjun tietoja lähteenä

RPC-palveluntarjoajat voivat katkaista ohjelmalokeja. Jos tapahtuman puuttuminen vaikuttaisi saldoihin, oikeuksiin tai muuhun oikeellisuuden kannalta kriittiseen työnkulkuun, johda tulos ohjelman tileistä tai transaktion instruction datasta. Käytä tapahtumia indeksoinnin tehostamiseen, älä ainoana tilan tietueena.

Tilaaminen TypeScriptillä

Anchorin nykyinen TypeScript-asiakasohjelma on @anchor-lang/core. Ohjelman IDL:stä luotu Program-instanssi voi purkaa vastaavat lokitapahtumat ja toimittaa ne RPC-lokitilauksen kautta:

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);

Takaisinkutsu saa puretun tapahtumadatan, slotin ja transaktion allekirjoituksen. Tilaukset kattavat vain ne lokit, jotka toimitetaan yhteyden ollessa aktiivinen. Tallenna käsitellyt allekirjoitukset ja täydennä puuttuvat transaktiot yhteyden katkettua, jos kuluttaja tarvitsee täydellisen historian.

Haettujen transaktiolokien purkaminen

Täydennyksiä ja kertaluonteista tarkastelua varten hae transaktio ja anna jokainen Program data: -sisältö tapahtumakooderille:

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);
}

Yksi transaktio voi kutsua useita ohjelmia. Tuotantokäyttöön tarkoitetussa jäsentimessä seuraa Program <address> invoke- ja success-lokirajoja tai käytä Anchorin tapahtumajäsennintä, jotta toisen ohjelman Program data: -merkintää ei pureta omanasi. Epäonnistuneet transaktiot voivat säilyttää lokeja ja sisäisiä käskyjä ennen virhettä suoritetusta työstä, joten hylkää aina transaktion metadata, jonka err ei ole null, ennen kummankin tapahtumamuodon indeksointia. Anchorin lokitilaus tekee tämän tarkistuksen ennen tapahtuman takaisinkutsun toimittamista.

emit_cpi!-tapahtuman osalta etsi itse-CPI kohdasta meta.innerInstructions, varmista, että sen ohjelmatunnus on sinun ohjelmasi, ja pura sen instruction data base58-muodosta. Ennen kuin poistat mitään tavuja, tarkista Anchorin EVENT_IX_TAG_LE:

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);

Kun tarkistat sekä ohjelmatunnuksen että tagin, tavallista itse-CPI:tä ei voi erehtyä pitämään tapahtumana.

Tapahtumaskeemojen versiointi

Oletuksena Anchor johtaa 8-tavuisen tapahtuman erottimen tapahtuman nimestä. Anchor tukee myös nimenomaista erotinta attribuutilla #[event(discriminator = ...)]. Erottimien täytyy pysyä yksilöllisinä ohjelman sisällä ja vakaina jokaiselle asiakasohjelmalle, joka purkaa tapahtuman.

Lähetetyn structin kenttien muuttaminen muuttaa sen Borsh-rakennetta. Rikkovan skeemamuutoksen yhteydessä määritä uusi tapahtuma, kuten PaymentRecordedV2, sen sijaan että muuttaisit PaymentRecorded-tapahtumaa hiljaisesti. Jatka vanhan tapahtuman lähettämistä siirtymäaikana, jos nykyiset indeksoijat ovat edelleen riippuvaisia siitä.

Kehittyvälle datalle suosi kiinteän leveyden kenttiä ja tunnisteita, joiden avulla indeksoija voi hakea monipuolisemmat tilitiedot erikseen. Julkaise päivitetty IDL ennen ohjelmapäivitystä tai sen mukana, jotta kuluttajat voivat purkaa uuden tapahtuman heti.

Tapahtumien testaus

Testaa purettu sopimus, älä base64-merkkijonoa. Rekisteröi kuuntelija ennen käskyn lähettämistä, varmista puretut kentät ja transaktion allekirjoitus ja poista kuuntelija aina lopuksi:

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);
}

Testaa myös, että indeksoija ohittaa epäonnistuneiden transaktioiden metadataan jääneet tapahtumatavut, ja lisää testiaineisto jokaiselle tuetulle tapahtumaversiolle. Jos käytät emit_cpi!-makroa, testaa purku kohdasta innerInstructions, koska lokikuuntelijat eivät saa näitä tapahtumia suoraan.

Lisälukemista

Is this page helpful?

Sisällysluettelo

Muokkaa sivua
© 2026 Solana Foundation. Kaikki oikeudet pidätetään.