Saltar al contenido principal

Eventos

Los eventos son el mecanismo que las aplicaciones fuera de la cadena pueden usar para monitorear cambios y eventos en contratos dentro de la cadena.

¿Cómo se emiten los eventos?

ContractEvents se emiten en TransactionMeta de Stellar Core. Puedes ver en el XDR de TransactionMetaV3 a continuación que hay una lista de OperationEvents llamada events. Cada OperationEvent corresponde a una operación en una transacción y contiene, a su vez, una lista de ContractEvents. Ten en cuenta que events solo se completará si la transacción tiene éxito.

ContractEvent

Los temas de un evento no tienen que ser del mismo tipo: puedes mezclar diferentes tipos.

Un evento también contiene un objeto de datos de cualquier valor o tipo, incluidos los custom types definidos por contratos usando #[contracttype]:

struct ContractEvent
{
// We can use this to add more fields, or because it
// is first, to change ContractEvent into a union.
ExtensionPoint ext;

Hash* contractID;
ContractEventType type;

union switch (int v)
{
case 0:
struct
{
SCVec topics;
SCVal data;
} v0;
}
body;
};

OperationEvents

struct OperationEvents
{
ContractEvent events<>;
};

TransactionMetaV3

struct TransactionMetaV3
{
LedgerEntryChanges txChangesBefore; // tx level changes before operations
// are applied if any
OperationMeta operations<>; // meta for each operation
LedgerEntryChanges txChangesAfter; // tx level changes after operations are
// applied if any
OperationEvents events<>; // custom events populated by the
// contracts themselves. One list per operation.
TransactionResult txResult;

Hash hashes[3]; // stores sha256(txChangesBefore, operations, txChangesAfter),
// sha256(events), and sha256(txResult)

// Diagnostics events that are not hashed. One list per operation.
// This will contain all contract and diagnostic events. Even ones
// that were emitted in a failed contract call.
OperationDiagnosticEvents diagnosticEvents<>;
};

Enlace al XDR anterior.

Tipos de eventos

Hay tres tipos de ContractEventType -

  1. Los eventos CONTRACT son eventos emitidos por contratos que utilizan la función host contract_event para transmitir cambios de estado.
  2. Los eventos SYSTEM son eventos emitidos por el host. En este momento, solo hay un evento del sistema emitido por el host. Se emite cuando se llama a la función host update_current_contract_wasm, donde topics = ["executable_update", old_executable: ContractExecutable, old_executable: ContractExecutable] y data = [].
  3. Los eventos DIAGNOSTIC están destinados a la depuración y no se emitirán a menos que la instancia del host lo habilite explícitamente. Puedes leer más sobre esto a continuación.

¿Qué son los diagnosticEvents?

Mientras observas la estructura XDR de TransactionMetaV3 anterior, es posible que hayas notado el campo diagnosticEvents. Esta lista estará vacía por defecto a menos que tu instancia de stellar-core tenga ENABLE_SOROBAN_DIAGNOSTIC_EVENTS=true en su archivo de configuración. Si se habilitan eventos de diagnóstico, esta lista no solo incluirá todos los ContractEvents en events, sino que también incluirá eventos de llamadas a contratos fallidas, errores del host, eventos para rastrear la pila de llamadas de contratos y registros de la función host log_from_linear_memory. Estos eventos pueden identificarse por type == DIAGNOSTIC. Los eventos de diagnóstico emitidos por el host para rastrear la pila de llamadas se definen a continuación.

fn_call

El evento de diagnóstico fn_call se emite cuando se llama a un contrato y contiene -

  • Temas
    1. El símbolo "fn_call".
    2. El id del contrato que está a punto de ser llamado.
    3. Un símbolo que contiene el nombre de la función que se está llamando.
  • Datos
    1. Un vector de los argumentos pasados a la función que se está llamando.

fn_return

El evento de diagnóstico fn_return se emite cuando una llamada a contrato se completa y contiene -

  • Temas
    1. El símbolo "fn_return".
    2. Un símbolo que contiene el nombre de la función que está a punto de devolver.
  • Datos
    1. El valor devuelto por la función del contrato.

¿Cuándo se deben habilitar los eventos de diagnóstico?

events contiene ContractEvents que deben transmitir información sobre cambios de estado. diagnosticEvents, por otro lado, contienen eventos que no son útiles para la mayoría de los usuarios, pero pueden ser útiles para depurar incidencias o construir la pila de llamadas del contrato. Dado que no serán utilizados por la mayoría de los usuarios, pueden habilitarse opcionalmente porque no están encriptados en el ledger y, por lo tanto, no son parte del protocolo. Esto se hace para que un nodo de stellar-core pueda mantenerse sincronizado con la red mientras emite estos eventos que normalmente no serían útiles para la mayoría de los usuarios.

Debido a que un nodo con eventos de diagnóstico habilitados ejecutará caminos de código que divergen de un nodo regular, te recomendamos encarecidamente que uses esta función solo en nodos de observación (nodos donde se establece NODE_IS_VALIDATOR=false).

Leer eventos

Puedes usar el endpoint getEvents de cualquier servicio RPC para obtener y filtrar eventos por tipo, contrato y tema.

advertencia

Los eventos son efímeros: los proveedores de RPC generalmente solo mantienen segmentos cortos (menos de una semana) de historia.

Para aprender más sobre cómo trabajar con eventos, échale un vistazo a las guías de eventos y este contrato de ejemplo.

Para una demostración rápida de alto nivel, sin embargo, utilizaremos el TypeScript SDK para obtener infinitamente todos los eventos de transfer (definidos por la Interfaz de Token de Soroban) que involucren el contrato XLM y mostrarlos en un formato amigable para humanos.

import {
humanizeEvents,
nativeToScVal,
scValToNative,
Address,
Networks,
Asset,
xdr,
} from "@stellar/stellar-sdk";
import { Server } from "@stellar/stellar-sdk/rpc";

const s = new Server("https://soroban-testnet.stellar.org");

async function main() {
const response = await s.getLatestLedger();
const xlmFilter = {
type: "contract",
contractIds: [Asset.native().contractId(Networks.TESTNET)],
topics: [
// Defined in https://stellar.org/protocol/sep-41#interface
// for all compatible transfer events.
[
nativeToScVal("transfer", { type: "symbol" }).toXDR("base64"),
"*", // from anyone
"*", // to anyone
"*", // any asset (it'll be XLM anyway)
],
],
};
let page = await s.getEvents({
startLedger: response.sequence - 120, // start ~10m in the past
filters: [xlmFilter],
limit: 10,
});

// Run forever until Ctrl+C'd by user
while (true) {
if (!page.events.length) {
await new Promise((r) => setTimeout(r, 2000));
} else {
//
// Two ways to output a human-friendly version:
// 1. the RPC response itself for human-readable text
// 2. a helper for the XDR structured-equivalent for human-readable JSON
//
console.log(cereal(simpleEventLog(page.events)));
console.log(cereal(fullEventLog(page.events)));
}

// Fetch the next page until events are exhausted, then wait.
page = await s.getEvents({
filters: [xlmFilter],
cursor: page.cursor,
limit: 10,
});
}
}

function simpleEventLog(events) {
return events.map((event) => {
return {
topics: event.topic.map((t) => scValToNative(t)),
value: scValToNative(event.value),
};
});
}

function fullEventLog(events) {
return humanizeEvents(
events.map((event) => {
// rebuild the decomposed response into its original XDR structure
return new xdr.ContractEvent({
contractId: event.contractId.address().toBuffer(),
type: xdr.ContractEventType.contract(), // since we filtered on 'contract'
body: new xdr.ContractEventBody(
0,
new xdr.ContractEventV0({
topics: event.topic,
data: event.value,
}),
),
});
}),
);
}

// A custom JSONification method to handle bigints.
function cereal(data) {
return JSON.stringify(
data,
(k, v) => (typeof v === "bigint" ? v.toString() : v),
2,
);
}

main().catch((e) => console.error(e));

También puedes aprovechar los formatos de codificación alternativos XDR de RPC como JSON para ver eventos legibles por humanos directamente desde la línea de comandos, por ejemplo, pasando xdrFormat: "json" como un parámetro adicional para el getEvents ejemplo.