Errores, Registros y Eventos
Errores, registros y eventos son la forma en que un programa de Solana se comunica cuando el estado por sí solo no es suficiente: falla con un código, deja un rastro humano o publica un registro estructurado para los indexadores. Asigna a cada canal un trabajo y el soporte se reduce, la CU se mantiene predecible y las dApps dejan de mostrar "Transacción fallida".
Conceptos básicos de manejo de errores es la entrada práctica; Enumeraciones de errores personalizadas, Registro con msg!, Emisión de eventos, Decodificación de errores fuera de la cadena y Depuración de transacciones fallidas se centran en cada mecanismo. Esta página es el mapa subyacente: cómo las piezas se componen desde el manejador hasta la notificación de la billetera.
Resumen
- Los programas comunican resultados a través de tres canales con contratos diferentes: errores fallan la instrucción (y la transacción) con un código legible por máquina, registros escriben líneas legibles por humanos en los metadatos de la transacción y eventos serializan cargas útiles tipificadas en registros para consumidores fuera de la cadena confiables.
- Por qué importa: Los usuarios y los bots nunca ven tus rastros de pila de Rust. Ven resultados de simulación,
logMessagesdel explorador y cualquier cosa que tu cliente mapee desdeProgramError::Custom. Diseña esas superficies a propósito o enviarás fallos opacos y desperdicio de CU. - Conceptos Clave: ProgramResult / ProgramError, enumeraciones de errores personalizadas (u32 estable), costo de cómputo de msg!, sol_log_data / Anchor emit!, tablas de errores de IDL, depuración primero por simulación, registros públicos (sin secretos).
- Cuándo usar: Diseñar modos de fallo para una bóveda o mercado, instrumentar una ruta activa, conectar clientes @solana/kit o Anchor para decodificar fallos, clasificar incidentes en mainnet/devnet o explicar la observabilidad del programa a un equipo de frontend.
- Limitaciones / Compensaciones: Cada registro y evento gasta unidades de cómputo; cada código personalizado es una API pública que debes versionar; las instrucciones fallidas revierten el estado (y la emisión de eventos en esa ruta); los registros no son un mecanismo de autorización.
- Temas Relacionados: conceptos básicos de manejo de errores, enumeraciones de errores personalizadas, registro con msg!, emisión de eventos, decodificación de errores fuera de la cadena, depuración de transacciones fallidas.
Fundamentos
Un manejador de instrucciones de Solana es esencialmente una función que devuelve ProgramResult (Result<(), ProgramError>). El éxito confirma las mutaciones de cuenta para esa transacción; el fallo aborta todo y revierte esas mutaciones. No hay confirmación parcial de "campos de error" de la manera en que algunos servidores de aplicaciones escriben una fila de error y aún así tienen éxito. El fallo en sí mismo es la señal.
Ese diseño empuja la observabilidad a los metadatos alrededor de la ejecución, no a cuentas duraderas de "último error" (a menos que construyas dicho estado en rutas de éxito). Tres canales complementarios llenan el vacío:
| Canal | Consumidor principal | Semántica de fallo | Carga útil típica |
|---|---|---|---|
| Error | Runtime, billeteras, decodificadores de dApp | La instrucción falla; el estado se revierte | ProgramError incorporado o Custom(u32) |
Registro (msg!) | Ingenieros, exploradores, soporte | Visible para pasos ejecutados hasta el fallo | Línea(s) de texto libre |
| Evento | Indexadores, análisis, webhooks | Emitido en rutas que tienen éxito lo suficiente como para registrar; la ix fallida no deja estado de evento duradero | Estructura con forma Borsh/IDL en registros |
Manejador de instrucciones
|
+-- verificar invariantes ---- Err(Código personalizado) ----> fallar tx + registros hasta ahora
|
+-- msg!("paso=...") ---- Costo de CU -------------> logMessages
|
+-- emit!(Evento {..}) --- bytes estructurados ----> consumidores de IDL / indexadores
|
+-- Ok(()) -------------- confirmar escrituras de cuentaLos errores son el contrato de flujo de control. Prefiere códigos personalizados tipificados para reglas visibles para el usuario (InsufficientFunds, SlippageExceeded) y los incorporados para fallos genéricos de argumentos o forma de cuenta cuando realmente apliquen. Los registros son un canal de depuración y operaciones: cortos, prefijados, con control de características cuando sea posible. Los eventos son el canal estructurado para sistemas fuera de la cadena que no deberían analizar texto libre.
Los tres comparten una propiedad: los registros de transacciones son públicos. Trata los mensajes, las líneas de registro y los campos de eventos como datos de cartelera. Las semillas, las claves privadas, la PII y los detalles internos listos para exploits no pertenecen allí.
Mecánicas e Interacciones
Enumeraciones de errores personalizadas: códigos estables, no cadenas de formato libre
Rust en la cadena no devuelve errores de String al cliente de manera útil. Los clientes ven un ID de programa más un código de error (a menudo hexadecimal en exploradores: custom program error: 0x1770). Tu trabajo es hacer que ese número sea estable, documentado y decodificable.
Los programas nativos típicamente definen una enumeración #[repr(u32)] e implementan From en ProgramError::Custom. Anchor 0.32.1 usa enumeraciones #[error_code] que aterrizan en el IDL con nombres y cadenas opcionales #[msg("...")], convencionalmente compensadas a partir de 6000. De cualquier manera:
- Variantes solo de adición para que los códigos existentes nunca se remapeen silenciosamente.
- Publica la tabla con la versión del programa (JSON IDL o un
errors.jsonmantenido manualmente para nativos). - Nombra en la interfaz de usuario por ID de programa cuando una transacción tiene varios programas, porque el mismo
u32puede significar cosas diferentes en programas diferentes.
Los detalles y patrones se encuentran en Enumeraciones de errores personalizadas y Conceptos básicos de manejo de errores. El punto explicativo: la enumeración es una superficie de API pública, tan importante como los discriminadores de instrucciones.
Costo de msg!: presupuesta los registros como código de ruta activa
solana_program::msg! (y los envoltorios de Anchor) invocan la llamada al sistema de registro. El costo escala con las llamadas y el tamaño de la carga útil. En un presupuesto de CU ajustado, un bucle de registros formateados puede llevar una transacción a cómputo excedido y parecer misterioso hasta que leas la simulación.
Reglas prácticas:
- Prefiere una línea corta por decisión sobre volcados de bytes de cuenta de varias líneas.
- Controla el registro detallado mediante características de Cargo o
cfgpara que las compilaciones de mainnet permanezcan silenciosas. - Usa prefijos estables (
VAULT:deposit_ok) para que las operaciones puedan buscar sin arqueología. - Nunca registres secretos (semillas, metadatos privados o cargas útiles que no almacenarías públicamente).
El registro brilla para validadores locales, rastreos de Surfpool, pruebas de LiteSVM y instrumentación de incidentes cortos. Es un sustituto a largo plazo pobre para errores tipificados (UX) o eventos (indexadores). Ver Registro con msg!.
Eventos estructurados: éxito (y progreso) legible por máquina
Cuando los paneles, los motores de riesgo o los complementos de Geyser necesitan campos confiables, el análisis de msg! de texto libre se convierte en un pasivo. Anchor emit! / emit_cpi! y las convenciones nativas de sol_log_data colocan estructuras tipificadas en el flujo de registro con un esquema definido por IDL o documentación.
Los eventos aún cuestan CU y permanecen públicos. Mantén las cargas útiles pequeñas (claves públicas, montos, enumeraciones), versiona los diseños cuando los campos se rompan y trata el estado de la cuenta como autoritativo. Los eventos describen resultados para los consumidores; no son un segundo libro mayor. Las instrucciones fallidas no confirman el estado del programa, así que no confíes en "eventos de error" como verdad duradera. Decodifica códigos de error para la UX de fallos; usa eventos para resultados de dominio exitosos. Inmersión profunda: Emisión de eventos.
Decodificación fuera de la cadena: de Custom(n) a lenguaje de producto
Las billeteras a menudo muestran un fallo genérico. Tu dApp (o herramienta de soporte) debería hacerlo mejor:
- Ejecuta
simulateTransaction(o confirma y recupera meta) con las mismas cuentas y datos. - Extrae el ID del programa fallido y el código personalizado (decimal o hexadecimal).
- Mapea a través de los
errorsde IDL (Anchor) o una tabla publicada (nativa) a través de ayudantes generados para @solana/kit 7.0.0 o tu propia búsqueda. - Muestra un mensaje de producto, una cadena i18n opcional y una sugerencia de reintento que depende del código (deslizamiento vs. fondos insuficientes vs. no autorizado).
Fija el artefacto decodificador al hash del programa desplegado. Los IDL obsoletos son una causa común de ruido de soporte de "nombre de error incorrecto". Patrones completos de cliente: Decodificación de errores fuera de la cadena.
Depuración de transacciones fallidas: registros hasta el corte
Cuando una transacción se ejecuta y falla, los metadatos típicamente incluyen logMessages para el trabajo realizado hasta el error. Usa esa pila para ver qué programa se invocó, qué CPI estaba activa y qué código personalizado terminó la ejecución. Cuando una transacción nunca aterriza (hash de bloque, caída, tarifa insuficiente), obtienes una clase de problema diferente: ningún registro del programa porque el programa nunca se ejecutó.
Un bucle confiable:
Informe del usuario / fallo de CI
|
v
Simular mismo mensaje --> leer registros + err
|
v
Mapear código a través de IDL --> formular hipótesis
|
v
Reproducir en LiteSVM / Surfpool --> agregar prueba de regresión
|
v
Corregir (código, cuentas, CU o cliente) + eliminar registros temporalesCLI (solana confirm -v, APIs de simulación), exploradores, rastreos de Surfpool 0.12.0 y pruebas unitarias de LiteSVM consumen las mismas señales fundamentales: códigos y registros. Detalles del flujo de trabajo: Depuración de transacciones fallidas.
Mapa de observabilidad para programas
Usa un mapa compartido entre los equipos de programas y clientes:
| Pregunta | Canal | Herramienta en cadena | Herramienta fuera de cadena |
|---|---|---|---|
| ¿Debería fallar esta ix? | Error | Err(MyError::..) / require! | Decodificar código; bloquear envío ante fallo de simulación |
| ¿Qué paso se ejecutó? | Registro | msg! / vlog con control de características | Explorador / sim logMessages |
| ¿Qué hecho de negocio deberían almacenar los indexadores? | Evento | emit! / sol_log_data | Complemento Geyser, indexador personalizado |
| ¿El estado es correcto después del éxito? | Datos de cuenta | Escrituras bajo reglas del propietario | RPC getAccount / suscripciones |
Los errores deciden el flujo de control. Los registros explican a los ingenieros. Los eventos alimentan a las máquinas. El estado de la cuenta resuelve disputas. Mezclar esos trabajos (por ejemplo, analizar registros de texto libre para contabilidad crítica) crea sistemas frágiles.
Consideraciones y Aplicaciones Avanzadas
Los límites de CPI complican el panorama. Cuando tu programa invoca a otro, el error del llamado puede aparecer en los registros externos. Mapea cuidadosamente: relanza, envuelve en tu propio código de UX o deja el código del llamado para que los exploradores multi-programa se mantengan honestos. Siempre decodifica con (program_id, code), no solo con el código.
Seguridad y UX tiran en direcciones opuestas. Los errores granulares ayudan a los usuarios a corregir las entradas; los excesivamente granulares pueden enseñar a los atacantes qué verificación falló. Prefiere categorías estables sobre mensajes de producción de "volcado de depuración". Coloca detalles de caza de exploits detrás de registros no mainnet controlados, no cadenas #[msg] permanentes.
El presupuesto de cómputo importa en mercados y rutas multi-salto. Los fallos que aparecen solo bajo carga debido a spam de registros son una clase de incidente real. Perfila la CU en simulación después de las actualizaciones (este sitio fija Agave 4.1.1) y trata la eliminación de registros como un cambio de rendimiento legítimo.
El versionado une el binario del programa, el IDL y la tabla de errores. Los programas raramente actualizados congelan la API de errores; planifica los códigos con anticipación. Los programas actualizables deben archivar los artefactos decodificadores por despliegue para que las transacciones históricas sigan siendo explicables.
Las pruebas deben afirmar códigos, no solo el éxito. Las pruebas de LiteSVM y Anchor que esperan Custom(6001) detectan reenumeraciones silenciosas. Las pruebas de cliente deben decodificar errores de simulación de prueba de la misma manera que lo hace producción.
| Enfoque | Fortaleza | Debilidad | Mejor ajuste |
|---|---|---|---|
Solo ProgramError incorporado | Simple, portátil | UX de producto débil | Herramientas internas, prototipos tempranos |
| Enumeraciones personalizadas + IDL | UX estable, listo para i18n | Debe versionarse cuidadosamente | Protocolos visibles para el usuario |
msg! intensivo en todas partes | Depuración local rápida | CU, ruido, datos públicos | Compilaciones de desarrollo, incidentes cortos |
| Eventos estructurados | Esquema amigable para indexadores | Mantenimiento de CU + esquema | Telemetría de protocolo, análisis |
| Simulación + decodificación en dApp | Mejor mensajería para el usuario | Requiere tablas mantenidas | Billeteras, UIs de comercio, herramientas de soporte |
Conceptos erróneos comunes
- "Devolveré un error de cadena y la billetera lo mostrará." Los clientes ven principalmente códigos y líneas de registro; diseña códigos
u32estables y decodifícalos tú mismo. - "Los registros son depuración gratuita." Cada
msg!gasta unidades de cómputo y puede causar fallos en presupuestos ajustados. - "Los eventos son almacenamiento duradero." Son cargas útiles de registro estructuradas para consumidores; el estado de la cuenta es la fuente de verdad, y las instrucciones fallidas no confirman el estado.
- "Puedo registrar semillas y claves de administrador; solo mi equipo lee los registros." Los registros de transacciones son datos públicos de la cadena visibles en exploradores y archivos.
- "El error personalizado 6001 siempre significa lo mismo en Solana." Los códigos son por programa; siempre empareja con el ID del programa.
- "Si el explorador no muestra registros, mi programa entró en pánico sin un código." Muchos "fallos" nunca se ejecutan (caducidad, caída, tarifa). Separa los problemas de aterrizaje de los errores del programa.
- "Reordenar las variantes de enumeración es una refactorización pura." Renombra los códigos y rompe todos los mapas de clientes; solo añadir.
Preguntas frecuentes
¿Cuál es la diferencia entre un error, un registro y un evento?
Un error falla la instrucción con un código y revierte el estado. Un registro es una línea de texto libre en los metadatos de la transacción para humanos. Un evento es una carga útil tipificada escrita en los registros para que los indexadores puedan decodificar campos de manera confiable.
¿Por qué los manejadores de instrucciones devuelven ProgramResult?
Para que el runtime pueda confirmar en Ok(()) o abortar toda la transacción en Err(ProgramError), manteniendo la atomicidad de múltiples instrucciones.
¿Cuándo debo usar ProgramError::Custom en lugar de los incorporados?
Cuando el fallo es específico del dominio y los clientes necesitan un código estable y con nombre para UX o automatización. Usa los incorporados para problemas genéricos de argumento inválido o cuenta que realmente coincidan con esa semántica.
¿Cómo asigna Anchor los códigos de error?
Las enumeraciones #[error_code] se exportan en el IDL con códigos numéricos que convencionalmente comienzan en 6000, más cadenas de mensajes opcionales para herramientas.
¿Por qué msg! cuesta unidades de cómputo?
El registro es una llamada al sistema de runtime; más llamadas y cadenas más grandes consumen más del presupuesto de cómputo de la transacción, lo que puede hacer que la instrucción falle si el presupuesto es ajustado.
¿Deberían los programas de producción mantener llamadas msg! detalladas?
Generalmente no. Prefiere registros detallados con control de características, breves puntos de referencia permanentes solo donde el valor de operaciones sea alto y eventos estructurados para necesidades de indexadores.
¿Son visibles los eventos si la transacción falla?
Las instrucciones fallidas no confirman el estado del programa. Diseña la UX de fallos en torno a códigos de error y los registros capturados hasta el punto de fallo, no en torno a cuentas "de eventos de error" duraderas a menos que construyas eso deliberadamente en una ruta de éxito.
¿Cómo decodifican los clientes de @solana/kit los errores?
Emparejan el código personalizado de la simulación o los metadatos confirmados con el IDL o las tablas de errores generadas, idealmente fijadas a la versión del programa con la que habla tu aplicación.
¿Cuál es el primer paso cuando un usuario dice que una transacción falló?
Determina si aterrizó y se ejecutó. Si es así, recupera los registros y el código de error (o simula el mismo mensaje). Si nunca se ejecutó, depura primero las tarifas, el hash de bloque y la ruta de aterrizaje.
¿Cómo deben decodificarse las transacciones multi-programa?
Recorre la pila de invocación de registros, identifica qué ID de programa falló y luego mapea la tabla de códigos de ese programa. Nunca asumas que un entero desnudo es tuyo.
¿Pueden los pánicos reemplazar los errores tipificados?
No para rutas controladas por el usuario. Los pánicos abortan sin una API de error de producto limpia y son más difíciles de mapear en clientes. Devuelve Err con un código deliberado.
¿Los eventos reemplazan las suscripciones de cuenta para los indexadores?
Son complementarios. Los eventos proporcionan hechos explícitos del dominio; las suscripciones de cuenta proporcionan diferencias de estado. Muchos pipelines de producción usan ambos.
¿Cómo mantengo sincronizadas las tablas de errores con los despliegues?
Publica IDL o errors.json en CI junto con el artefacto del programa, versiona con el hash de despliegue y rechaza lanzamientos de clientes que fijan la tabla incorrecta.
¿Dónde pertenece i18n para errores?
Fuera de la cadena. Mapea códigos estables a cadenas de localización en el cliente; no confíes en las cadenas #[msg] en inglés en la cadena como único texto para el usuario.
¿Cómo cambia esto para Pinocchio o pilas solo nativas?
Los canales de runtime son los mismos: ProgramError, llamadas al sistema de registro y datos de registro binarios opcionales. Eres dueño de más de la historia de publicación de IDL/tabla sin las macros de Anchor.
Relacionados
- Conceptos básicos de manejo de errores - ProgramResult y devolución de fallos
- Enumeraciones de errores personalizadas - Códigos de error estables y amigables con IDL
- Registro con msg! - Registros humanos conscientes de CU
- Emisión de eventos - Eventos estructurados para indexadores
- Decodificación de errores fuera de la cadena - Mapear códigos en clientes
- Depuración de transacciones fallidas - Simulación y triaje de registros
Versiones de la pila: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1 y @solana/kit 7.0.0.