borsh & bytemuck
Borsh (Binary Object Representation Serializer for Hashing) es el formato de serialización predeterminado para los datos de cuentas e instrucciones de Anchor en Agave 4.1.1. bytemuck permite la conversión de tipo sin copia (zero-copy) de diseños de cuentas plain-old-data (POD) cuando controlas la alineación y la validación. Elige Borsh para flexibilidad; elige bytemuck para rutas críticas sensibles a la Unidad de Cómputo (CU).
Receta
Tarjeta de referencia rápida - lista para copiar y pegar.
[dependencies]
borsh = "1.5"
bytemuck = { version = "1.16", features = ["derive"] }use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Config {
pub bump: u8,
pub fee_bps: u16,
}Cuándo usar esto:
- Codificar argumentos de instrucciones y estado de cuenta en programas nativos o de Anchor.
- Analizar datos de cuentas en clientes y pruebas de Rust.
- Optimizar cuentas grandes de diseño fijo con lecturas de copia cero.
- Compartir esquemas con indexadores fuera de la cadena (discriminador + cola Borsh).
Ejemplo de Trabajo
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::{Pod, Zeroable};
#[derive(BorshSerialize, BorshDeserialize, Clone)]
pub struct UserMeta {
pub authority: [u8; 32],
pub points: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct LedgerHeader {
pub magic: u32,
pub count: u32,
}
pub fn parse_ledger(data: &[u8]) -> Result<(&LedgerHeader, &[u8]), &'static str> {
if data.len() < std::mem::size_of::<LedgerHeader>() {
return Err("demasiado corto");
}
let (head_bytes, rest) = data.split_at(std::mem::size_of::<LedgerHeader>());
let header: &LedgerHeader = bytemuck::from_bytes(head_bytes);
if header.magic != 0xC0FFEE {
return Err("mágica incorrecta");
}
Ok((header, rest))
}Lo que esto demuestra:
- Los derives de Borsh serializan estructuras variables para instrucciones.
Pod+Zeroablemarcan diseños transparentes seguros para bytemuck.- La validación manual (
magic, longitud) precede a la conversión de tipo de copia cero. - Los bytes finales pueden contener entradas codificadas con Borsh después de una cabecera fija.
Inmersión Profunda
Cómo Funciona
- Borsh escribe un diseño canónico little-endian con slices prefijados por longitud.
- Anchor antepone discriminadores de 8 bytes a las cargas útiles de cuentas e instrucciones.
- bytemuck reinterpreta slices de bytes como
&Tsin copia cuando se cumplen las reglas de alineación. - Los clientes (Rust, TS, Python) deben replicar el mismo esquema para la decodificación.
Tabla de Crates de Rust
| Crate | Rol | En cadena |
|---|---|---|
borsh | Serializar/deserializar estructuras | Sí |
borsh-derive | Macros de derive | Sí |
bytemuck | Conversiones de tipo de copia cero | Sí (con cuidado) |
bytemuck_derive | Derives Pod/Zeroable | Sí |
anchor-lang | Borsh integrado para cuentas | Sí (ruta de Anchor) |
Tabla de TypeScript / NPM
| Paquete | Rol | Notas |
|---|---|---|
borsh (npm) | Decodificación fuera de cadena en JS | Coincidir exactamente con el diseño de Rust |
Codecs de @solana/kit | Codecs binarios para mensajes de tx | 7.0.0 |
| Codecs generados por Codama | Diseños basados en IDL | Preferido para clientes de aplicaciones |
Notas de Rust
// Cuenta de Anchor con discriminador
// space = 8 + UserMeta::serialized_size() // o INIT_SPACE en Anchor más reciente
// Nunca uses bytemuck para convertir cuentas no confiables sin comprobaciones de propietario + longitudTrampas
- bytemuck en structs empaquetadas o con muchos
bool- riesgo de comportamiento indefinido. Solución:repr(C)+ campos de relleno explícitos; probar en el objetivo. - Cambio de esquema sin migración - deserializa basura. Solución: byte de versión + instrucción de migración.
- Mezclar Serde JSON con datos en cadena - formato diferente. Solución: Solo Borsh (o personalizado) dentro de los programas.
try_from_sliceno verificado en datos de usuario - provoca pánicos en código nativo. Solución:try_from_slice->ProgramError::InvalidAccountData.- Suposiciones de endianidad - Borsh es LE; bytemuck sigue al host para POD - usa solo tipos LE.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
bincode | Herramientas solo fuera de cadena | En cadena (no es estándar) |
Trait Pack manual (estilo SPL heredado) | Interoperabilidad con diseños antiguos | Cuentas nuevas de Anchor |
serde JSON | APIs y configuraciones | Almacenamiento de cuentas |
Estilo memmap solo fuera de cadena | Analítica | Entorno de ejecución del programa |
Preguntas Frecuentes
¿Anchor siempre usa Borsh?
Sí, para cuerpos de cuentas/instrucciones, a menos que optes por serialización personalizada.
¿Cuándo vale la pena la copia cero?
Cuando el perfilado muestra que la deserialización domina la CU en registros grandes y fijos.
¿bytemuck con Account<T> de Anchor?
Posible para diseños híbridos; muchos equipos usan AccountInfo en bruto para colas de copia cero.
¿Vector en Borsh en cadena?
Permitido pero costoso; prefiere arrays de capacidad fija en cuentas críticas.
¿Decodificación en Python?
borsh-construct o analizadores IDL de AnchorPy - mantén los esquemas sincronizados.
¿Colisiones de discriminadores?
Anchor hashea los nombres de cuentas/instrucciones; no reutilices nombres entre tipos descuidadamente.
¿Endianness en clientes TS?
Usa los codecs de Codama/@solana/kit para evitar errores manuales.
¿Fuzzing de diseños?
Haz fuzzing de la decodificación Borsh con bytes arbitrarios en pruebas de LiteSVM 0.6.x.
¿Redimensionamiento de cuenta?
Los datos variables de Borsh necesitan instrucciones realloc; planifica el espacio con antelación.
¿Hashing con Borsh?
Borsh es determinista; útil para el diseño de preimágenes de semillas de PDA (con separación de dominio).
Relacionado
- Serialización Borsh - guía profunda de Borsh
- Copia Cero con bytemuck - patrones de optimización
- Serialización de Datos de Cuenta - resumen de la sección
- anchor-lang & anchor-spl - derives integrados
Versiones de la pila: Esta página se escribió para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.