Puntos Clave de Serialización
Solana almacena el estado de la aplicación como bytes crudos en cuentas propiedad del programa. La serialización es el contrato que convierte esos bytes en campos tipados y viceversa: qué diseño, cuán grande, cómo crece y cómo evoluciona sin inutilizar datos en vivo.
Esta página es el mapa conceptual para la Serialización de Datos de Cuenta: por qué serializa, las estructuras Borsh, los discriminadores, el espacio y el alquiler, realloc, el versionado y la migración, y las compensaciones de copia cero en Agave 4.1.1.
Resumen
- Los
datosde la cuenta propiedad del programa son un búfer opaco; su diseño de serialización es el esquema en cadena con el que deben coincidir todas las inicializaciones, actualizaciones, decodificaciones de cliente y actualizaciones. - Por Qué Importa: Un tamaño incorrecto, un discriminador faltante o un cambio silencioso en el diseño al crear, desperdicia alquiler, consume cómputo o malinterpreta fondos y autoridades. Las elecciones de serialización son diseño de base de datos, no una preferencia de estilo.
- Conceptos Clave: búfer de cuenta opaco, orden de campos Borsh, discriminador de cuenta, ESPACIO / LEN, mínimo exento de alquiler, realloc, byte de versión / ix de migración, copia cero (Pod / bytemuck).
- Cuándo Usar Este Modelo: Definir nuevos tipos de cuenta, dimensionar el alquiler, elegir Borsh vs. copia cero, planificar actualizaciones de programas o depurar fallos de
InvalidAccountDatae inicialización. - Limitaciones / Compensaciones: Paga por cada byte en alquiler y a menudo en CU; los campos de longitud variable necesitan límites; la copia cero necesita diseños fijos y alineados; las migraciones nunca reescriben todas las cuentas atómicamente.
- Temas Relacionados: Fundamentos de Serialización, Discriminadores de Cuenta, Estructuras Borsh, Cálculo de Espacio, realloc y Crecimiento de Cuentas, Versionado y Migración de Datos.
Fundamentos
El campo data de una cuenta es una porción de bytes que el runtime no interpreta. La propiedad decide quién puede escribir; la serialización decide qué significan los bytes.
Sin un diseño compartido, el programa y sus clientes discrepan sobre los límites de los campos. Un u64 puede convertirse en la mitad de un Pubkey, una verificación de autoridad puede leer el desplazamiento incorrecto y una longitud "válida" aún puede contener el tipo incorrecto.
Serialización significa: codificar el estado estructurado en el búfer al escribir; decodificar solo después de verificar la longitud y el tipo al leer. Los datos de instrucción también son bytes, pero efímeros por transacción. Los datos de cuenta persisten a través de ranuras y actualizaciones, por lo que necesitan constantes fijas, discriminadores y planes de migración.
¿Por qué serializar en absoluto cuando se podría indexar manualmente los desplazamientos crudos? Los códecs explícitos (envolturas de Borsh, Anchor, lanzamientos de copia cero) documentan el orden y el tamaño, detectan errores de longitud antes y mantienen los clientes de TypeScript/Rust alineados a través de IDL o constantes compartidas. El empaquetado manual funciona para encabezados diminutos; los campos anidados, las opciones o los programas multotipo necesitan un esquema con nombre.
Borsh es el caballo de batalla predeterminado para los cuerpos de cuenta y las cargas útiles de instrucciones. Es determinista y empaquetado: el orden de declaración de campos es el orden de transmisión, los enteros son little-endian, bool es un byte, Pubkey son 32 bytes, y el flujo no tiene relleno de alineación de Rust. Esa previsibilidad hace que las matemáticas del alquiler y la decodificación entre idiomas sean prácticas.
Datos de cuenta (cuenta Borsh típica):
[ discriminador de 8 bytes ][ campo0 ][ campo1 ][ ... ][ versión / cola opcional ]
etiqueta de tipo cuerpo empaquetado en orden de declaraciónLos discriminadores se colocan al frente de los programas multotipo. Antes de analizar el cuerpo, compare el prefijo con la constante esperada para que un Vault nunca se lea como un Usuario. Anchor 0.32.1 usa ocho bytes de SHA256("account:TypeName"). Los programas nativos eligen constantes únicas y las publican a los clientes. Sin una etiqueta, cualquier cuenta lo suficientemente larga que posea su programa puede ser convertida al struct incorrecto (confusión de tipos).
El Espacio se decide en el momento de la asignación. La creación financia un mínimo exento de alquiler para data_len. La fórmula habitual:
ESPACIO = longitud_discriminador + longitud_cuerpo
+ límites para piezas variables (Vec: 4 + max_n * elem_size; Option: 1 + interno)Documente LEN / ESPACIO junto al struct. El InitSpace de Anchor / space = debe coincidir. Los clientes y las creaciones de CPI utilizan el mismo número para que el alquiler y la asignación no se desvíen.
Mecánicas e Interacciones
Ruta de lectura/escritura de Borsh
Al inicializar: asignar ESPACIO, asignar su programa como propietario, financiar el alquiler, escribir el discriminador y luego serializar el cuerpo inicial. Al actualizar: tomar prestados los datos, verificar la longitud y el discriminador, deserializar, aplicar reglas, serializar de nuevo (o en un búfer reallocado).
// Cuerpo de cuenta Borsh conceptual (el discriminador a menudo se escribe por separado)
#[derive(BorshSerialize, BorshDeserialize)]
pub struct User {
pub authority: Pubkey, // 32
pub points: u64, // 8
pub bump: u8, // 1
}
impl User {
pub const LEN: usize = 32 + 8 + 1;
}
pub const USER_SPACE: usize = 8 + User::LEN; // disc + bodyLas pruebas de host que verifican que la longitud serializada es igual a LEN detectan errores de uno o dos antes de la red principal. Mapee los fallos de deserialización a InvalidAccountData (o errores de cuenta de Anchor), no a pánicos.
Verificación del discriminador antes del análisis del cuerpo
if data.len() < 8 || data[..8] != USER_DISC {
return Err(ProgramError::InvalidAccountData);
}
let user = User::deserialize(&mut &data[8..])?;Escriba el discriminador solo en la inicialización legítima. Cierre poniendo a cero o recuperando para que las etiquetas obsoletas no queden. Nunca cambie el discriminador de un tipo en vivo; eso deja huérfanas todas las cuentas de ese tipo. Evolucione con un campo de versión después del disco, no un nuevo disco para el mismo tipo.
Espacio, alquiler y clientes
let lamports = Rent::get()?.minimum_balance(USER_SPACE);Las cuentas más grandes bloquean más SOL como depósito exento de alquiler. Los campos variables sin límites hacen que el alquiler sea ilimitado y fomentan el abuso; prefiera arreglos fijos o max_entries. Fuera de la cadena, los códecs de @solana/kit 7.0.0 y los clientes de Anchor/Codama deben decodificar el mismo diseño; trate el IDL o las constantes compartidas como la fuente de verdad.
realloc cuando el búfer debe cambiar
Los esquemas y las colecciones crecen. realloc cambia data_len bajo control del propietario. El crecimiento necesita un pagador para la diferencia de alquiler; la reducción puede devolver el exceso de lamports cuando se hace correctamente. Después del crecimiento, ponga a cero los nuevos bytes (o escriba valores predeterminados) para que la memoria obsoleta no se convierta en nuevos campos. Limite la longitud máxima contra el abuso de alquiler/CU. Anchor 0.32.1 utiliza restricciones de realloc; el código nativo utiliza la ruta de realloc del programa.
longitud_anterior ----------------------> nueva_longitud
[ disc | campos conocidos | .... ]
realloc
[ disc | campos conocidos | CEROS nuevos y luego inicialización ]
^
financiar delta de alquiler desde el pagadorVersionado y migración
La actualización del bytecode no reescribe las cuentas de usuario. Si V2 necesita otro u64, las cuentas V1 todavía tienen la longitud antigua. Patrones que funcionan:
| Estrategia | Mecanismo | Cuándo encaja |
|---|---|---|
| Campos de solo anexión | Campos nuevos solo al final; las cuentas antiguas permanecen cortas hasta que se migran | Cambios aditivos pequeños |
| Byte de versión / enum | El campo de versión del cuerpo selecciona el analizador | Diseños concurrentes |
| ix de migración dedicado | Leer V1, escribir V2, opcionalmente realloc | Actualizaciones controladas |
| Migración perezosa | Migrar al próximo toque del usuario | Distribuir costo de CU y alquiler |
| Nuevo tipo de cuenta | Nuevo disco + flujo de copia | Rediseños disruptivos |
Lectura dual V1 y V2 durante la transición. No asuma que todas las cuentas se han migrado después del despliegue. Restrinja la migración (administrador o autoridad de cuenta) para que los extraños no puedan abusar de las rutas de alquiler. No elimine ni reordene campos en su lugar; así es como se pierden fondos o autoridades.
Ruta de copia cero (contraste)
Para diseños fijos grandes (libros de órdenes, matrices de ticks, mapas de bits), el análisis/serialización completa de Borsh en cada instrucción consume CU con el tamaño. La copia cero mapea el búfer a un struct #[repr(C)] Pod + Zeroable (bytemuck o AccountLoader de Anchor) y muta en su lugar. Requisitos: tamaño fijo, sin String/Vec en la vista Pod, disciplina de alineación y generalmente size_of::<T>() como ESPACIO. El discriminador todavía importa (a menudo un u64 en el desplazamiento 0). La capacidad se planifica por adelantado; crecer la copia cero pura significa una migración deliberada, no anexiones de campos casuales.
Consideraciones Avanzadas y Aplicaciones
Trate el diseño como una API pública. Los revisores deben responder: unicidad del disco, fórmula de ESPACIO, campos de autoridad, tamaños variables máximos y la historia de migración si el programa permanece actualizable.
| Enfoque | Fortalezas | Costos / restricciones | Mejor ajuste |
|---|---|---|---|
| Cuenta completa Borsh | Campos flexibles, anexión fácil, versionado simple | La CU escala con el tamaño; reescritura completa al guardar | Configuración, perfiles, estado pequeño/mediano |
| Pod de copia cero | Actualizaciones en su lugar, baja CU en tablas fijas grandes | Tamaño fijo, alineación, cambios de esquema más difíciles | Libros, losas, mapas de bits, estado grande en caliente |
| Híbrido (encabezado Pod + cola Borsh) | Campos calientes rápidos + cola flexible | Dos diseños para documentar y probar | Encabezado + blob variable ocasional |
| Asignación sobredimensionada ahora | Evita realloc temprano | Alquiler adicional bloqueado temprano | Techo de crecimiento conocido |
| realloc bajo demanda | Paga alquiler cuando sea necesario | Pagador, inicialización a cero, CU, límites máximos | Actualizaciones de esquema, colecciones en crecimiento |
Datos de instrucción vs. datos de cuenta: mantenga los esquemas separados. Los enums de instrucción cambian más libremente (efímeros); los diseños de cuenta necesitan migración. Compartir una estructura en evolución para ambos invita a colisiones entre las etiquetas de ix y los campos de versión de cuenta.
Los programas inmutables congelan la lógica y la evolución práctica del esquema. Si revoca la autoridad de actualización, reserve espacio libre (relleno o ESPACIO sobredimensionado) o acepte un nuevo ID de programa más una experiencia de usuario de migración para nuevos campos.
Los clientes e indexadores van a la zaga de la cadena. Prefiera campos de versión (y eventos de migración) que puedan filtrar. Envíe decodificadores de doble versión en los clientes de @solana/kit 7.0.0 / Codama durante los lanzamientos, coincidiendo con el programa.
Enfoque de auditoría: comprobaciones de disco faltantes, ESPACIO incorrecto, Vec/String sin límites, realloc sin poner a cero, migración no autenticada y tratar la propiedad como tipo (un programa, varios tipos de cuenta).
En una pila alineada con Agave 4.1.1 (CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, LiteSVM 0.6.x, Surfpool 0.12.0): serialice ida y vuelta en el host, pruebe discos defectuosos y búferes cortos, y migre instantáneas de bytes reales de V1.
Conceptos Erróneos Comunes
- "La propiedad es suficiente; no necesito un discriminador." El propietario demuestra qué programa puede escribir. Los discriminadores demuestran cuál de sus diseños es el búfer.
- "Puedo reordenar los campos de Borsh; los nombres coinciden en ambos lados." Borsh es posicional. Reordenar rompe las cuentas en vivo y los clientes antiguos.
- "ESPACIO es solo la suma de los tamaños de campo de Rust." Incluya el disco, las etiquetas de Option, los prefijos de longitud de Vec y los máximos de cadena. Para Pod use
size_of, no una suma adivinada. - "realloc inicializa nuevos bytes de forma segura por defecto." Trate la puesta a cero y la inicialización explícita de campos como requeridas después del crecimiento.
- "Desplegar nuevo código de programa actualiza todos los diseños de cuenta." Los cargadores intercambian bytecode; las cuentas de datos permanecen hasta que su ruta de migración las reescribe.
- "La copia cero siempre es más rápida, así que siempre úsela." En cuentas pequeñas, Borsh a menudo está bien; la rigidez del Pod y el costo de migración dominan. Perfile primero.
- "Las cadenas variables en las cuentas están bien si los usuarios son honestos." Los tamaños ilimitados permiten el abuso de alquiler y CU. Limite las longitudes o mantenga el contenido fuera de la cadena.
- "Cambiar el discriminador marca limpiamente V2." Eso deja huérfanas a V1 bajo el tipo antiguo. Prefiera un campo de versión con un disco estable.
- "Si la deserialización tiene éxito, la cuenta es confiable." Aún verifique el disco, el tamaño y las autoridades. Ajustarse al diseño no es corrección semántica.
- "Los clientes pueden inferir el diseño solo del ID del programa." Muchos tipos de cuenta comparten un ID de programa. Los clientes necesitan disco más esquema (IDL, Codama o constantes).
Preguntas Frecuentes
¿Por qué los programas de Solana serializan los datos de la cuenta en absoluto?
Los datos de la cuenta son un búfer crudo sin esquema en tiempo de ejecución. La serialización es el diseño acordado que convierte bytes en campos para programas y clientes, y de nuevo en almacenamiento duradero.
¿Cuándo debería elegir Borsh para las cuentas?
Elija Borsh por defecto para estados pequeños y medianos, configuraciones, registros de usuario y cualquier cosa que pueda ganar campos más adelante. Es el patrón común de Anchor y nativo y funciona bien con el versionado.
¿Qué es un discriminador de cuenta?
Un prefijo fijo corto (comúnmente 8 bytes) que identifica el tipo de cuenta antes de analizar el cuerpo. Evita leer un Vault como un Usuario cuando ambos comparten un programa propietario. Ver Discriminadores de Cuenta.
¿Cómo elige Anchor 0.32.1 los discriminadores?
Los discriminadores de cuenta son los primeros 8 bytes de SHA256("account:TypeName"). Los discriminadores de instrucción usan una preimagen "global:...". Los programas nativos pueden usar constantes manuales si los clientes están de acuerdo.
¿Cómo calculo ESPACIO y alquiler?
ESPACIO = disco + cuerpo (+ límites para campos variables), luego Rent::get()?.minimum_balance(ESPACIO) (o equivalente del cliente/CLI). Mantenga una constante con nombre y pruébela. Ver Cálculo de Espacio.
¿Cuál es el tamaño habitual de Pubkey, u64, bool y Option en Borsh?
Pubkey 32 bytes, u64/i64 8 bytes little-endian, bool 1 byte, Option<T> etiqueta de 1 byte más el tamaño interno cuando es Some.
¿Cuándo necesito realloc?
Cuando la cuenta debe crecer para nuevos campos o más entradas, o encogerse después de la poda. Financie el alquiler en el crecimiento, ponga a cero o inicialice nuevos bytes, y aplique una longitud máxima. Ver realloc y Crecimiento de Cuentas.
¿Cómo debo versionar los diseños de cuenta después de una actualización del programa?
Mantenga el discriminador estable, agregue un campo de versión, lea dualmente durante el lanzamiento y envíe una ruta de migración explícita o perezosa (realloc si V2 es más grande). Ver Versionado y Migración de Datos.
¿Qué es la copia cero y cuándo vale la pena?
La copia cero lanza bytes de cuenta a un struct Pod fijo para actualizaciones en su lugar, omitiendo el análisis/serialización completa de Borsh. Úselo cuando el perfilado muestre que la deserialización domina en tablas fijas grandes; acepte tamaño rígido y migraciones más difíciles.
¿Puedo mezclar Borsh y copia cero?
Sí. Patrón común: encabezado Pod fijo (disco, secuencia, contadores calientes) más una región Borsh o cruda acotada. Documente ambas mitades y pruebe los límites de longitud.
¿La serialización cuesta unidades de cómputo?
Sí. Copiar y analizar búferes grandes cuesta CU. La copia cero ayuda en cuentas grandes; en las pequeñas, la diferencia a menudo es menor en comparación con las llamadas al sistema y las CPI.
¿De dónde deben obtener los clientes el diseño?
De la misma fuente de verdad que el programa: IDL de Anchor, códecs de Codama / @solana/kit 7.0.0, o constantes de disco y orden de campos exportados. Los clientes hechos a mano se desvían.
¿Qué falla si ESPACIO es incorrecto al inicializar?
Demasiado pequeño: la serialización o las escrituras posteriores fallan o se truncan. Demasiado grande: los usuarios pagan en exceso el alquiler hasta que reduce el tamaño. Un error de ocho generalmente significa un discriminador olvidado.
¿Debería cada tipo de cuenta incluir un byte de versión desde el primer día?
Muy recomendado para programas actualizables. Un solo u8 después del disco es un seguro barato frente a una migración forzada "big bang".
¿Cómo se relacionan los códecs de instrucción con los códecs de cuenta?
Ambos son diseños de bytes, pero los datos de instrucción son por transacción y más fáciles de cambiar. Los diseños de cuenta sobreviven a las actualizaciones y requieren discriminadores, planificación de espacio y migración.
Relacionados
- Fundamentos de Serialización - Patrones de inicio de Borsh vs. copia cero
- Discriminadores de Cuenta - etiquetas de tipo y defensas contra la confusión
- Estructuras Borsh - orden de campos, LEN y diseño de estructuras
- Cálculo de Espacio - fórmulas de ESPACIO y mínimos exentos de alquiler
- realloc y Crecimiento de Cuentas - redimensionar, delta de alquiler, puesta a cero
- Versionado y Migración de Datos - campos de versión y rutas de migración
- Cuentas de Copia Cero - diseños Pod y compensaciones de CU
- Mejores Prácticas de Serialización - lista de verificación de diseño, migración y pruebas
Versiones de la pila: Esta página fue escrita 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.