Programas Nativos en Detalle
Un programa nativo en Solana es un crate de Rust que depende de solana-program, tiene como objetivo SBF como cdylib, y expone un punto de entrada que recibe un ID de programa, un slice de AccountInfos y datos de instrucción. No hay macro de struct de cuentas, ni generación automática de restricciones, ni IDL incorporado. Eres dueño del camino completo desde los bytes de entrada hasta los cambios de estado de salida.
Esta página es el mapa de la sección. Úsala para ubicar la distribución del punto de entrada, la validación manual, la (des)serialización, la lectura/escritura, la creación de cuentas CPI, el registro y las compensaciones de frameworks en un continuo antes de seguir las páginas de recetas enfocadas.
Resumen
Los programas nativos de Solana son módulos SBF ordinarios. El runtime llama a process_instruction una vez por instrucción, te entrega solo lo que la transacción declaró, y espera Ok(()) o un ProgramError.
Sin Anchor, tres tareas recaen en tu código. Analizar los datos de instrucción en un enum estable o un diseño de opcode. Validar cada cuenta que el manejador utiliza: firmantes, capacidad de escritura, propietarios, igualdad de claves, semillas de PDA y relaciones comerciales. Modificar los bytes de las cuentas propiedad del programa bajo las reglas de préstamo de Solana, incluida la CPI del Sistema de Programas cuando debe existir una nueva cuenta.
Esas tareas son también la principal superficie de riesgo. Una verificación is_signer faltante, una suposición de propietario incorrecta o una ruta de reinicialización en una cuenta existente es un error crítico. Los frameworks codifican las mismas reglas en macros; el código nativo las hace visibles y fáciles de omitir.
Elige nativo cuando necesites control explícito, binarios más pequeños, CU base más bajo, diseños inusuales o verificaciones visibles en auditorías. Elige Anchor 0.32.1 (o un wrapper delgado como Pinocchio) cuando la velocidad, la generación de IDL/cliente y las restricciones declarativas importen más. Muchos equipos prototipan en Anchor, y luego reescriben las rutas críticas como programas nativos compuestos a través de CPI.
Fundamentos
Qué significa "nativo" aquí
Nativo significa que programas contra la API solana-program sin la capa #[program] / #[account] de Anchor. Todavía despliegas un .so, pagas tarifas en lamports, declaras metadatos de cuenta desde clientes como @solana/kit 7.0.0, y construyes con herramientas SBF de Agave 4.1.1 / Solana CLI 3.0.10. Nativo es una capa de aplicación más delgada, no una cadena diferente.
Los tres argumentos de cada instrucción
| Entrada | Rol |
|---|---|
program_id | La clave pública de tu programa; verificaciones de propietario y derivación de PDA |
accounts: &[AccountInfo] | Cuentas que el cliente listó, con indicadores de firmante/escritura verificados por el runtime |
instruction_data: &[u8] | Bytes opacos que decodificas en un enum de instrucción u opcode |
El runtime no re-deriva la intención de tus tipos de Rust. Una cuenta incorrecta en la ranura 2, datos cortos o un diseño incorrecto deben fallar de forma segura en tu manejador.
AccountInfo es una capacidad, no un recurso tipado
AccountInfo expone clave, lamports, datos, propietario, ejecutable, is_signer e is_writable. Esos indicadores reflejan lo que la transacción declaró y el runtime verificó para las firmas. No prueban "esta es la bóveda para este usuario" o "este es el estado seguro del programa". Esa política es tuya: comparaciones, verificaciones de PDA e inspección de campos dentro de los datos.
Dónde se asientan los frameworks sobre el mismo fundamento
| Capa | Qué añade | Qué no elimina |
|---|---|---|
| Nativo crudo | Control total de validación, diseño, CPI | Verificaciones correctas y disciplina de esquema de cliente |
| Pinocchio / Estilo Steel | Menos código repetitivo, CU cercano al nativo | Revisión de seguridad de las verificaciones |
| Anchor 0.32.1 | Restricciones, IDL, ergonomía del cliente | Necesidad de entender el modelo subyacente |
La alfabetización nativa hace que cada opción sea más segura, incluso cuando usas Anchor para la mayor parte de la superficie del producto.
Forma mínima del crate
Diseño típico: cdylib, entrypoint!(process_instruction), módulos para instruction, processor, state, y error, más solana-program y un serializador (a menudo Borsh). Un punto de entrada por binario; múltiples tipos de instrucciones viven detrás de una coincidencia (match) en datos analizados. Fundamentos de Programas Nativos recorre el esqueleto con ejemplos.
Mecánicas
Una instrucción es un pipeline fijo: entrar, decodificar, validar, actuar (leer/escribir y CPI opcional), registrar cuidadosamente, retornar.
Pipeline de instrucción de extremo a extremo
Cliente (@solana/kit / CLI / llamador de CPI)
| Lista AccountMeta + instruction_data
| firmar (o invoke_signed desde el llamador)
v
Runtime: cargar cuentas, verificar firmas y metadatos de escritura
|
v
entrypoint! -> process_instruction(program_id, accounts, data)
|
| 1. deserializar instruction_data
| 2. next_account_info / orden fijo de cuentas
| 3. validar firmantes, propietarios, PDAs, relaciones
| 4. tomar prestado/modificar datos, o CPI System Program
| 5. msg! opcional / sol_log_data
v
Ok(()) confirma | Err(ProgramError) aborta la instrucción
Valida antes de los efectos secundarios siempre que sea posible. Una instrucción (o transacción) fallida no deja efectos parciales del programa de esa ruta de fallo.
1. Punto de entrada y distribución
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResultDeserializa una vez en la parte superior, luego match a los manejadores. Pasa program_id a las rutas de PDA y propietario. Mapea los errores del dominio a códigos estables ProgramError::Custom para los clientes.
2. Datos de instrucción como contrato externo
Los datos de instrucción son el formato de transmisión entre los constructores fuera de la cadena y el código en la cadena. Un patrón común es un enum de Borsh (primer byte índice de variante, luego campos):
#[derive(BorshSerialize, BorshDeserialize)]
pub enum VaultInstruction {
Initialize { bump: u8 },
Deposit { amount: u64 },
}No reordenes las variantes en vivo. Limita las cadenas y vectores ilimitados. Agrega un byte de versión cuando debas romper el diseño. Refleja la misma codificación en TypeScript con @solana/kit 7.0.0 o un pipeline de Codama/schema compartido para que los clientes no puedan desviarse silenciosamente. Ver Serialización/Deserialización de Datos de Instrucción.
3. Validación manual de cuentas
La seguridad nativa es una lista de verificación explícita en cada ruta:
| Verificación | Patrón |
|---|---|
| Firmante | account.is_signer |
| Escribible | account.is_writable |
| Propietario es tu programa | account.owner == program_id |
| Propietario es System/Token/etc. | igualdad en el ID de programa esperado |
| Identidad PDA | find_program_address + bump almacenado |
| Relaciones | igualdad de claves; campos de mint/autoridad en los datos |
| Sysvars | Sysvar::get() o claves conocidas, no falsificaciones falsificables |
Ejecuta verificaciones de indicadores baratas antes de CPI o deserialización pesada. Documenta el orden de las cuentas; consume con next_account_info. Para las cuentas de tokens, el propietario es el programa Token; la autoridad y el mint viven dentro de los datos.
if !payer.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let (expected, bump) = Pubkey::find_program_address(
&[b"vault", payer.key.as_ref()],
program_id,
);
if expected != *vault.key || bump != stored_bump {
return Err(ProgramError::InvalidSeeds);
}Validación Manual de Cuentas cubre ataques de sustitución y listas de verificación reutilizables.
4. Lectura y escritura de datos de cuenta
El estado propiedad del programa es un buffer de bytes. Toma prestado, analiza, modifica, vuelve a escribir dentro de la longitud de la cuenta.
- Prefiere
try_borrow_data/try_borrow_mut_data. - Libera los préstamos antes de la CPI en la misma cuenta.
- Protege la longitud; mapea los fallos de serialización claramente (por ejemplo, cuenta demasiado pequeña).
- Diseños de versión; planifica migraciones.
- Solo el programa propietario puede escribir datos (propiedad establecida en la creación/asignación).
let mut data = account.try_borrow_mut_data()?;
let mut state = State::deserialize(&mut &data[..])
.map_err(|_| ProgramError::InvalidAccountData)?;
state.count = state.count.checked_add(1).ok_or(ProgramError::InvalidArgument)?;
state.serialize(&mut &mut data[..])
.map_err(|_| ProgramError::AccountDataTooSmall)?;Detalle: Lectura y Escritura de Datos de Cuenta.
5. Creación de cuentas a través de CPI
Los programas crean cuentas mediante CPI al System Program (create_account o allocate/assign relacionados). El pagador financia los lamports exentos de alquiler; space dimensiona el buffer; owner se convierte en tu program_id para el estado del programa.
Las PDAs no tienen clave privada. Usa invoke_signed con semillas y bump exactos:
let rent = Rent::get()?;
let lamports = rent.minimum_balance(State::LEN);
invoke_signed(
&system_instruction::create_account(
payer.key, pda.key, lamports, State::LEN as u64, program_id,
),
&[payer.clone(), pda.clone(), system_program.clone()],
&[&[b"vault", owner.key.as_ref(), &[bump]]],
)?;
// inicializar datos después de la creaciónValida el ID del System Program, las suposiciones de cuenta vacía/nueva y la coincidencia de semillas. Las rutas de reinicialización y "la cuenta ya existe" son clases clásicas de explotación. Flujo completo: Creación de Cuentas a través de CPI.
6. Registro
msg! y las utilidades relacionadas escriben registros de transacciones para simulación, exploradores e indexadores. El costo escala con el volumen y el tamaño de la cadena. Usa puntos de referencia estructurados en desarrollo y registros de producción dispersos. Prefiere errores personalizados tipados para fallos esperados. Ver Emisión de Registros.
7. Clientes y pruebas
Nativo todavía necesita una historia de cliente: constructores escritos a mano, un crate compartido o herramientas de Codama/IDL. Las pruebas deben afirmar fallos de validación (propietario incorrecto, firmante faltante, PDA incorrecta), no solo rutas felices. Las pruebas unitarias estilo LiteSVM y los validadores locales bajo Solana CLI 3.0.10 cubren la velocidad de la lógica frente a los bucles de despliegue RPC completos.
Avanzado
Compensaciones frente a frameworks
| Factor | Nativo | Anchor 0.32.1 | Nativo delgado (ej. Pinocchio) |
|---|---|---|---|
| Velocidad | Más lento | Más rápido para aplicaciones | Medio |
| Rieles de seguridad | Listas de verificación manuales | Restricciones declarativas | Ayudantes, aún explícito |
| CU / tamaño binario | Mejor control | Base más alta | Cercano al nativo |
| IDL / clientes | Manual o externo | Incorporado | Varía |
| Visibilidad de auditoría | Línea por línea | Expansión de macros | Superficie delgada |
| Mejor ajuste | Rutas críticas, primitivas, diseños extraños | La mayoría de los programas de aplicaciones | Estilo nativo sensible a CU |
Nativo no es automáticamente más seguro. Es más transparente. La transparencia solo ayuda si la lista de verificación se aplica cada vez.
Cuándo el nativo es la elección de producto correcta
Prefiere nativo cuando al menos dos se cumplen: la CU de instrucción es crítica para el negocio; el diseño de la cuenta lucha contra Anchor; el tamaño binario está restringido; los auditores exigen validación totalmente explícita; o despliegas una primitiva en la que otros programas harán CPI.
No elijas nativo solo porque "Anchor es lento" sin perfilar. La contención, RPC y los algoritmos a menudo dominan la sobrecarga de macros.
Arquitecturas híbridas
Forma común: Anchor para administración e instrucciones de baja frecuencia; nativo para el bucle cerrado (paso de coincidencia, tick del juego, liquidación). Compón con CPI y diseños compartidos. Documenta qué ID de programa posee qué estado.
Lente de revisión de seguridad
Para cada instrucción pregunta: ¿quién debe firmar?; ¿qué cuentas se escriben y con qué propietario?; ¿se puede sustituir una PDA, mint o cuenta de token diferente?; ¿es initialize invocable de nuevo en datos no vacíos?; ¿son las semillas/bumps canónicas?; ¿se liberan los préstamos antes de la CPI?; ¿coincide el diseño de la instrucción con los clientes publicados?
Disciplina operativa
Fija Agave, CLI y solana-program como dependencias de la aplicación. Documenta los códigos de error personalizados para los consumidores de @solana/kit. Trata los cambios de diseño como migraciones con puertas de versión.
Conceptos erróneos comunes
Concepto erróneo: Nativo omite la seguridad porque el runtime ya verificó las cuentas. El runtime verifica las firmas y los metadatos de escritura para las cuentas listadas. No conoce las semillas de tu bóveda, las vinculaciones de mint o la política de administración.
Concepto erróneo: is_writable significa que la cuenta es segura para tratarla como tu estado.
Escribible solo significa que la transacción permitió la mutación. Aún así, verifica el propietario, la identidad y los discriminadores.
Concepto erróneo: Propietario igual a Token Program es suficiente para "la cuenta de token del usuario". La autoridad, el mint y la cantidad viven en los datos. Valida los campos (y a menudo la derivación de ATA) para la acción económica.
Concepto erróneo: Los enums de instrucción se pueden reordenar si Rust todavía compila. Los índices de variante son el formato de transmisión. Reordenar rompe los clientes antiguos y puede mapear opcodes antiguos a manejadores nuevos.
Concepto erróneo: Crear una PDA es simplemente create_account sin semillas.
Las acciones autorizadas por PDA necesitan invoke_signed con las semillas que el runtime espera.
Concepto erróneo: Los registros de msg! son gratuitos y observables.
Los registros cuestan CU y aumentan el tamaño de los metadatos. No son un sustituto de los errores o las métricas fuera de la cadena.
Concepto erróneo: Los programas nativos son automáticamente más seguros que Anchor. Ambos fallan bajo validación incorrecta. Nativo mueve el predeterminado de "la macro podría omitir" a "el autor podría omitir".
Concepto erróneo: Los programas nativos no necesitan un IDL o esquema de cliente. Sin una codificación publicada, los constructores divergen. Despliega un esquema, un crate compartido o un pipeline de Codama.
Preguntas frecuentes
¿Qué es un programa nativo de Solana en una oración?
Un crate SBF solana-program con un punto de entrada que analiza manualmente los datos de instrucción, valida AccountInfos y actualiza el estado propiedad del programa (a menudo con System Program CPI), sin macros de Anchor.
¿Los programas nativos usan un runtime diferente al de los programas Anchor?
No. Ambos son SBF bajo el mismo runtime de Agave. Anchor genera más pegamento; la cadena ve un ID de programa y bytecode de cualquier manera.
¿Qué recibe el punto de entrada?
program_id, un slice de AccountInfo para las cuentas listadas y un slice de bytes de datos de instrucción.
¿Por qué el orden de las cuentas es parte de la API?
Los clientes construyen una lista paralela de AccountMeta. Los manejadores típicamente consumen cuentas en orden fijo con next_account_info. Documenta ese orden o genéralo a partir de una única fuente de verdad.
¿Qué debo validar que el runtime no hace?
Identidad comercial: qué PDA, mint y autoridad; si initialize ya se ejecutó; relaciones entre cuentas; y política de propietario más allá de la presencia de la firma.
¿Cómo debo estructurar los datos de instrucción?
Prefiere un diseño documentado y versionado (comúnmente un enum de Borsh). Mantén las variantes estables, limita los tamaños dinámicos y refleja el diseño en los clientes.
¿Cómo creo una cuenta propiedad del programa desde código nativo?
CPI system_instruction::create_account con lamports exentos de alquiler, espacio deseado y owner = program_id. Usa invoke_signed cuando la nueva dirección sea una PDA.
¿Cuándo necesito `invoke_signed`?
Siempre que una PDA deba autorizar una acción porque no existe una clave privada Ed25519 para esa dirección.
¿Cómo leo y escribo estado de forma segura?
Verifica el propietario y la longitud, toma prestados los datos, deserializa o usa una vista segura, modifica con aritmética verificada, serializa dentro de la capacidad y libera los préstamos antes de la CPI en la misma cuenta.
¿Son necesarios los registros `msg!`?
No. Úsalos para depuración y puntos de referencia dispersos en producción. Prefiere valores ProgramError claros para fallos esperados.
¿Cómo se comunican los clientes con los programas nativos?
Construye transacciones que listen las cuentas correctas y serialicen los mismos bytes de instrucción que espera el programa, usando @solana/kit 7.0.0 u otros SDKs.
¿Es el nativo siempre menor en CU que Anchor?
Nativo tiene un piso más bajo y más control, pero la CU real depende de las verificaciones, registros, serialización y CPI. Mide las instrucciones críticas.
¿Puedo mezclar Anchor y nativo?
Sí. Los programas separados compuestos con CPI son comunes: Anchor para superficies administrativas, nativo para lógica central sensible al rendimiento.
¿Cuál es el mayor error de seguridad en los programas nativos?
Validación incompleta: firmante faltante, propietario incorrecto, sustitución de PDA o reinicialización en cuenta existente. Trata cada instrucción como entrada hostil.
¿A dónde debo ir ahora en esta sección?
Comienza con Fundamentos de Programas Nativos, luego profundiza en la validación, datos de instrucción, creación de CPI, E/S de cuentas y registros a través de los elementos relacionados a continuación.
Relacionado
- Fundamentos de Programas Nativos - diseño del crate, esqueleto del punto de entrada y ejemplos de inicio
- Validación Manual de Cuentas - firmantes, propietarios, PDAs y defensas contra sustitución
- Serialización/Deserialización de Datos de Instrucción - enums de Borsh, opcodes y paridad con el cliente
- Lectura y Escritura de Datos de Cuenta - préstamos, bucles de serialización y cuidado del diseño
- Creación de Cuentas a través de CPI - creación de System Program e
invoke_signedde PDA - Emisión de Registros -
msg!, costo de CU y disciplina de registro en producción
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.