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".
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, logMessages del explorador y cualquier cosa que tu cliente mapee desde ProgramError::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.
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 cuenta
Los 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í.
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.json mantenido manualmente para nativos).
Nombra en la interfaz de usuario por ID de programa cuando una transacción tiene varios programas, porque el mismo u32 puede significar cosas diferentes en programas diferentes.
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 cfg para 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!.
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.
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 errors de 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.
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 temporales
CLI (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.
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.
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
"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 u32 estables 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.
¿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.