NUEVO: Helius adquiere Light Protocol
El modelo de programación de Solana
Blog/Fundamentos

El modelo de programación de Solana: introducción al desarrollo en Solana

Developer Experience Engineer0xIchigo en X0xIchigo en LinkedIn0xIchigo en GitHub
24 min de lectura

¿De qué trata este artículo?

El enfoque de Solana para la computación descentralizada se basa en un principio sencillo: todo se almacena en su propia región de memoria, conocida como cuenta. Solana funciona como un almacén global de pares clave/valor, donde las claves públicas sirven como identificadores únicos de sus cuentas correspondientes. Las cuentas son la base de Solana porque almacenan el estado; contienen desde programas hasta saldos de tokens. Las transacciones se utilizan para actualizar las cuentas y reflejar los cambios de estado.

En este artículo, exploramos las complejidades de la arquitectura de Solana. Comenzamos con un resumen de los clústeres y el concepto de estado. Luego analizamos la función de las cuentas y los programas como componentes fundamentales de Solana. Por último, examinamos cómo las transacciones permiten interacciones dinámicas entre cuentas y programas.

Al terminar este artículo, comprenderás a fondo el modelo de programación de Solana. Conocerás la arquitectura de los clústeres, la función crucial de las cuentas en el almacenamiento de datos y el proceso mediante el cual las transacciones actualizan los datos de las cuentas. También explorarás características exclusivas de Solana, como su sistema de renta y las transacciones versionadas.

¿Qué son los clústeres de Solana?

En el centro de la arquitectura de Solana se encuentran los clústeres: un conjunto de validadores que trabajan juntos para procesar transacciones y mantener un único libro mayor. Solana tiene varios clústeres independientes y cada uno cumple un propósito específico:

  • Localhost: un clúster de desarrollo local que se encuentra en el puerto predeterminado 8899. La interfaz de línea de comandos de Solana (CLI) incluye un validador de prueba integrado que puede personalizarse según las necesidades de cada desarrollador, sin necesitar airdrops ni sufrir límites de solicitudes
  • Devnet: un entorno aislado sin consecuencias para hacer pruebas y experimentar en Solana
  • Testnet: un entorno donde los principales colaboradores de Solana prueban nuevas actualizaciones y funciones antes de que lleguen a mainnet. También sirve como entorno de prueba para desarrolladores que quieren realizar pruebas de rendimiento
  • Mainnet Beta: el clúster activo y sin permisos donde ocurren transacciones reales. Esta es la Solana «real», donde usuarios, desarrolladores, titulares de tokens y validadores interactúan a diario

Cada clúster funciona de forma independiente, sin conocimiento alguno de los demás. Las transacciones enviadas al clúster equivocado se rechazan para garantizar la integridad de cada entorno operativo.

Imagina los clústeres como un heap monolítico de datos. En informática, un heap es una región de memoria donde los datos pueden almacenarse y modificarse de forma dinámica. Sin embargo, es importante señalar que los clústeres no utilizan literalmente una estructura de datos heap. Esta analogía sirve como herramienta conceptual para entender que los clústeres constan de varias regiones de memoria que pueden asignarse y liberarse cuando sea necesario. Entender los clústeres como un heap dinámico es clave para comprender cómo se gestionan, se acceden y se protegen los datos dentro de la red.

También puedes imaginar este heap monolítico de datos como una especie de almacén digital. Aquí, los datos son como cajas en estantes, cada una con etiquetas únicas y reglas específicas para moverlas y cambiar su contenido. Esto garantiza un sistema seguro y ordenado en el que solo se permiten los movimientos o cambios autorizados.

Los contratos inteligentes, conocidos como programas en Solana, tienen asignada su propia parte del almacén, o del heap, que pueden gestionar. Aunque un programa puede leer cualquier parte del espacio de este almacén, necesita determinados permisos para modificar el contenido de un espacio que no posee. La única acción permitida de forma universal es transferir lamports, la criptomoneda nativa de Solana, a cualquier espacio del almacén.

Todo el estado reside en este heap, incluso los programas. Cada región tiene un programa que la posee y la gestiona. Por ejemplo, los programas pertenecen al BPFLoader, un programa responsable de cargar, implementar y actualizar programas on-chain. Llamamos cuentas a estas regiones de memoria, las cajas de nuestro almacén digital.

¿Qué son las cuentas?

Todo en Solana es una cuenta. Piensa en las cuentas como contenedores que almacenan datos de forma persistente, de manera similar a los archivos de una computadora. Son los componentes básicos del modelo de programación de Solana y se utilizan para almacenar el estado (es decir, el saldo de la cuenta, la información de propiedad, si la cuenta contiene un programa y la información sobre la renta).

Hay tres tipos de cuentas en Solana:

  • Cuentas que almacenan datos
  • Cuentas que almacenan programas ejecutables
  • Cuentas que almacenan programas nativos

Estos tipos de cuentas pueden distinguirse aún más según sus capacidades:

  • Cuentas ejecutables: cuentas capaces de ejecutar código
  • Cuentas no ejecutables: cuentas utilizadas para almacenar datos sin capacidad para ejecutar código (¡porque no contienen código!)

En la imagen anterior aparecen algunos ejemplos de cuentas ejecutables y no ejecutables. Entre las cuentas ejecutables, Bubblegum es un ejemplo de cuenta de programa. Es un programa de Metaplex que se utiliza para crear y gestionar NFT comprimidos. El programa de votación es un ejemplo de cuenta de programa nativo. Se utiliza para crear y gestionar cuentas que registran el estado de votación y las recompensas de los validadores. Explicaremos la diferencia entre las cuentas de programa y de programa nativo en la sección ¿Qué son los programas?. Por ahora, es importante saber que existen distintos tipos de cuentas ejecutables en Solana.

Además, todas las cuentas no ejecutables pueden clasificarse como cuentas de datos. Algunos ejemplos de cuentas de datos son:

Estructura de las cuentas

Las cuentas se estructuran según la estructura AccountInfo:

Código
pub struct AccountInfo<'a> {
    pub key: &'a Pubkey,
    pub lamports: Rc>,
    pub data: Rc>,
    pub owner: &'a Pubkey,
    pub rent_epoch: Epoch,
    pub is_signer: bool,
    pub is_writable: bool,
    pub executable: bool,
}

Las cuentas se identifican mediante su dirección (key), que es una clave pública única de 32 bytes.

El campo lamports contiene la cantidad de lamports que posee esta cuenta. Un lamport equivale a una milmillonésima parte de un SOL, el token nativo de Solana.

data se refiere al arreglo de bytes de datos sin procesar que almacena esta cuenta. Puede almacenar desde los metadatos de un activo digital hasta saldos de tokens, y los programas pueden modificarlo.

El campo owner contiene al propietario de esta cuenta, representado por la dirección de una cuenta de programa. La propiedad de las cuentas se rige por algunas reglas:

  • Solo el propietario de una cuenta puede modificar sus datos y retirar lamports
  • Cualquiera puede depositar lamports en una cuenta
  • El propietario de una cuenta puede transferir su propiedad a un nuevo propietario, siempre que los datos de la cuenta se restablezcan a cero

El campo is_signer es un booleano que indica si el propietario de la cuenta en cuestión firmó una transacción. En otras palabras, indica a los programas involucrados en la transacción si la cuenta es firmante. Ser firmante significa que la cuenta posee la clave privada correspondiente a la clave pública y tiene autoridad para aprobar la transacción propuesta.

El campo is_writable es un booleano que indica si pueden modificarse los datos de la cuenta. Solana permite que las transacciones especifiquen cuentas como de solo lectura para facilitar el procesamiento en paralelo. Aunque el runtime permite que distintos programas accedan simultáneamente a cuentas de solo lectura, gestiona los posibles conflictos de escritura en las cuentas modificables mediante un orden de procesamiento de transacciones. Esto garantiza que solo se procesen en paralelo las transacciones que no presentan conflictos.

El campo executable es un booleano que indica si una cuenta puede procesar instrucciones. Sí, esto significa que los programas se almacenan en cuentas, algo que analizaremos en la siguiente sección. Primero debemos explicar el concepto de renta.

El campo rent_epoch indica la siguiente época en la que esta cuenta deberá pagar renta. Una época es el número de slots durante el cual es válida una programación de líderes. A diferencia de los archivos tradicionales de un sistema operativo, las cuentas de Solana tienen una vida útil expresada como una cantidad de lamports. La idea de que la existencia continua de una cuenta depende de su saldo de lamports nos lleva al concepto de renta.

Renta

La renta es un costo de almacenamiento necesario para mantener activas las cuentas en Solana y garantizar que permanezcan en la memoria de los validadores. El cobro de la renta se calcula por épocas, una unidad de tiempo definida por los slots durante los cuales una programación de líderes es válida. Así funciona la renta:

  • Cobro de renta: la renta se cobra una vez por época. También puede cobrarse cuando una transacción hace referencia a una cuenta
  • Distribución de la renta: una parte de la renta cobrada se quema, es decir, se retira de la circulación de forma permanente. El resto se distribuye entre las cuentas de votación después de cada slot
  • Pago de la renta: si una cuenta no tiene suficientes lamports para pagar la renta, sus datos se eliminan y se libera la cuenta mediante un proceso conocido como recolección de basura
  • Exención de renta: las cuentas pueden quedar exentas de renta si mantienen un saldo mínimo equivalente a dos años de pagos de renta. Todas las cuentas nuevas deben cumplir este umbral de exención, que depende del tamaño de la cuenta
  • Recuperación de la renta: los usuarios pueden cerrar una cuenta para recuperar los lamports restantes. Esto permite recuperar la renta almacenada en una cuenta

La renta puede estimarse mediante el endpoint RPC getMinimumBalanceForRentExemption para un tamaño de cuenta determinado. Test Drive simplifica el proceso al aceptar la longitud de los datos de una cuenta en usize. También puedes utilizar el subcomando de renta de la CLI de Solana para estimar la cantidad mínima de SOL necesaria para que una cuenta quede exenta de renta. Por ejemplo, al momento de escribir este artículo, ejecutar el comando solana rent 20000 devuelve Rent-exempt minimum: 0.14009088 SOL.

Direcciones en Solana

En realidad, existen dos «tipos» de direcciones en Solana. Para crear direcciones, Solana utiliza ed25519, un esquema de firma EdDSA que emplea SHA-512 (SHA-2) y la curva elíptica Curve22519. Esto produce claves públicas de 32 bytes, que funcionan como el formato de dirección principal. Pueden utilizarse directamente porque no se someten a hash.

Para que una dirección sea válida, debe ser un punto de la curva ed25519. Sin embargo, no todas las direcciones deben derivarse de esta curva. Las direcciones derivadas de programas (PDA) se generan fuera de la curva, lo que significa que no tienen una clave privada correspondiente y no pueden utilizarse para firmar. Las PDA se crean mediante el programa del sistema y se utilizan cuando los programas necesitan gestionar cuentas. Este es solo un comentario adicional para que tú, como lector, conozcas los distintos tipos de direcciones de Solana. Explicaremos las PDA en otro artículo.

¿En qué se diferencian las cuentas de Solana de las cuentas de Ethereum?

Ethereum tiene dos tipos principales de cuentas: cuentas de propiedad externa (EOA) y cuentas de contrato. Las claves privadas controlan las EOA, mientras que el código de contrato rige las cuentas de contrato, que no pueden iniciar transacciones por sí mismas.

Tanto las EOA como las cuentas de contrato siguen la misma estructura de cuenta:

  • Saldo: cada cuenta tiene un saldo medido en Ether
  • Nonce: para las EOA, es el número de transacciones enviadas desde la cuenta. Para los contratos, es el número de contratos creados por la cuenta
  • Raíz de almacenamiento: un hash de 256 bits del nodo raíz de un árbol de Merkle Patricia, que codifica el contenido de almacenamiento de la cuenta
  • CodeHash: el hash del código de la Ethereum Virtual Machine (EVM) del contrato. Es inmutable, lo que significa que su código no cambia una vez creado, aunque su estado sí puede hacerlo. Es importante señalar que existen excepciones para actualizar contratos en Ethereum, como el uso de patrones proxy, pero esto queda fuera del alcance del artículo. Para las EOA, es el hash de una cadena vacía, ya que las EOA no contienen código

Solana adopta un modelo de cuentas más uniforme, en el que cualquier cuenta tiene el potencial de ser un programa. La separación del código y los datos fomenta un entorno más eficiente y flexible. Los programas de Solana no tienen estado e interactúan con distintas cuentas de datos sin implementaciones redundantes. Esto resulta especialmente ventajoso para las aplicaciones de finanzas descentralizadas (DeFi), donde un usuario quiere interactuar con varios protocolos sin mover activos entre distintos programas. En cambio, el modelo de programación de Ethereum combina el código y el estado en una sola entidad. Esto aumenta la complejidad de las interacciones y puede elevar los costos debido al gas necesario para cambiar el estado.

Antes, las cuentas de Solana debían pagar renta y mantener un saldo mínimo para permanecer activas. Esto garantiza que la red termine recuperando las cuentas sin uso o con fondos insuficientes, lo que reduce la acumulación excesiva de estado. Las actualizaciones recientes eliminaron las cuentas que pagan renta en mainnet: ahora las cuentas deben estar exentas de renta. En comparación, Ethereum utiliza gas para gestionar la asignación de recursos. Con este modelo, el almacenamiento de un contrato persiste indefinidamente a menos que se borre de forma explícita. El enfoque de Solana ofrece una estructura de costos más predecible para almacenar estado, mientras que los costos de Ethereum pueden variar y volverse prohibitivos durante periodos de congestión de la red.

En la siguiente sección, examinaremos cómo Solana separa la lógica de sus programas del estado. En comparación con el modelo de programación de Ethereum, verás cómo este enfoque modular facilita operaciones on-chain más eficientes y proporciona a los desarrolladores una estructura de costos transparente y predecible.

¿Qué son los programas de Solana?

Los programas son cuentas ejecutables que pertenecen al BPF Loader. Los ejecuta el runtime de Solana, diseñado para procesar transacciones y lógica de programas.

Una de las características distintivas del modelo de programación de Solana es la separación del código y los datos. Los programas no tienen estado, lo que significa que no almacenan ningún estado internamente. En cambio, todos los datos que necesitan para operar se guardan en cuentas separadas, que las transacciones pasan a los programas por referencia. Este diseño permite que una única implementación genérica de un programa interactúe con distintas cuentas.

Los programas de Solana pueden:

  • Poseer cuentas adicionales
  • Leer otras cuentas o acreditarlas
  • Modificar datos o debitar las cuentas que poseen

Existen dos tipos de programas:

  • Programas on-chain: programas escritos por usuarios e implementados en Solana. Su autoridad de actualización, que suele ser la cuenta que implementó el programa, puede actualizarlos
  • Programas nativos: programas integrados en el núcleo de Solana. Proporcionan la funcionalidad fundamental que los validadores necesitan para operar. Los programas nativos solo pueden actualizarse mediante actualizaciones de software para toda la red. Algunos ejemplos habituales son el programa del sistema, el programa BPF Loader y el programa de votación.

Tanto los usuarios como otros programas pueden invocar los programas on-chain y los nativos. La principal diferencia está en sus mecanismos de actualización: su autoridad de actualización puede actualizar los programas on-chain, mientras que los programas nativos solo pueden actualizarse como parte de las actualizaciones del clúster.

Solana Labs mantiene un grupo selecto de programas on-chain conocido como la biblioteca de programas de Solana. Esta biblioteca facilita diversas operaciones on-chain, incluidos los préstamos de tokens y la creación de pools de stake. Por ejemplo, el programa de cuentas de tokens asociadas establece un estándar y un mecanismo para vincular la billetera de un usuario con sus respectivas cuentas de tokens. Además, SPL es dinámica. Programas como Token-2022 amplían y desarrollan las funcionalidades que proporciona el programa de tokens.

El desarrollo de programas en Solana suele realizarse en Rust con la ayuda de Anchor, un framework con convenciones definidas que simplifica la creación de programas al reducir el código repetitivo y agilizar la serialización y deserialización. Aunque Rust es la opción preferida, los desarrolladores no están limitados a este lenguaje: pueden utilizar C, C++ y cualquier lenguaje que tenga como destino el backend BPF de LLVM (es decir, un componente de LLVM que permite compilar programas en bytecode BPF). Los desarrollos recientes de Solang y Neon Labs permiten usar Solidity para desarrollar programas.

Los programas suelen desarrollarse y probarse en Localhost y Devnet antes de implementarse en Testnet o Mainnet Beta. Los desarrolladores pueden implementar su programa mediante la CLI de Solana con el comando solana program deploy <path to program>. Una vez compilado como un objeto compartido ELF que contiene el bytecode BPF, el programa se carga en el clúster de Solana indicado. Los programas implementados residen en cuentas marcadas como executable, y la dirección de la cuenta funciona como program_id.

Al principio, los programas de Solana se implementaban con cuentas que tenían el doble del tamaño del programa. La actualización 1.16 de Solana incorpora compatibilidad con cuentas redimensionables para ofrecer a los desarrolladores mayor flexibilidad y una mejor asignación de recursos. Ahora, un desarrollador puede implementar su programa con una cuenta más pequeña y ampliar su tamaño después.

Como mencionamos, se considera que los programas no tienen estado porque todos los datos con los que interactúan se almacenan en cuentas separadas que se pasan por referencia. Todos los programas tienen un único punto de entrada donde se procesan las instrucciones. Este recibe un program_id, un arreglo de cuentas y los datos de la instrucción como un arreglo de bytes. El runtime de Solana ejecuta los programas cuando una transacción los invoca.

¿Qué son las transacciones?

Las transacciones son la base de la actividad on-chain. Funcionan como el mecanismo para invocar programas y efectuar cambios de estado. Una transacción de Solana es un conjunto de instrucciones que indica a los validadores qué acciones deben realizar, sobre qué cuentas y si tienen los permisos necesarios para hacerlo.

Una transacción consta de tres partes principales:

  • Un arreglo de cuentas desde las que se leerá o en las que se escribirá
  • Una o más instrucciones
  • Una o más firmas

Las transacciones de Solana siguen la estructura Transaction. Esta proporciona la información necesaria para que la red procese y valide las acciones. Se define de la siguiente manera:

Código
pub struct Transaction {
    pub signatures: Vec,
    pub message: Message,
}

El campo signatures contiene un conjunto de firmas que corresponden al Message serializado. Cada firma está asociada con una clave de cuenta de la lista account_keys de Message, empezando por quien paga la comisión. Esta es la cuenta responsable de cubrir las comisiones incurridas al procesar una transacción. Por lo general, es la cuenta que inicia la transacción. El número de firmas necesarias es igual a num_required_signatures, que se define en el MessageHeader del mensaje.

El propio message es una estructura de tipo Message. Se define así:

Código
pub struct Message {
    pub header: MessageHeader,
    pub account_keys: Vec,
    pub recent_blockhash: Hash,
    pub instructions: Vec,
}

El header del mensaje contiene tres enteros sin signo de 8 bits: el número de firmas necesarias (es decir, num_required_signatures), el número de firmantes de solo lectura y el número de no firmantes de solo lectura.

El campo account_keys enumera todas las direcciones de las cuentas involucradas en la transacción. Primero aparecen las cuentas que solicitan acceso de lectura y escritura, seguidas de las cuentas de solo lectura.

recent_blockhash es un blockhash reciente que contiene un hash SHA-256 de 32 bytes. Es necesario para indicar cuándo observó el cliente por última vez el libro mayor y funciona como periodo de validez para las transacciones recientes. Los validadores rechazan las transacciones con un blockhash antiguo. Además, incluir un blockhash reciente ayuda a evitar transacciones duplicadas, ya que se rechaza cualquier transacción que sea idéntica a una anterior. Si, por cualquier motivo, una transacción debe firmarse mucho antes de enviarse a la red, puede utilizarse un nonce de transacción duradero en lugar de un blockhash reciente para garantizar que sea única.

El campo instructions contiene una o más estructuras CompiledInstruction, cada una de las cuales determina una acción específica que deben realizar los validadores de la red.

Instrucciones

Una instrucción es una directiva para una única invocación de un programa de Solana. Es la unidad más pequeña de lógica de ejecución de un programa y funciona como la unidad operativa más básica de Solana. Los programas interpretan los datos que pasa una instrucción y operan sobre las cuentas especificadas. La estructura Instruction se define así:

Código
pub struct Instruction {
    pub program_id: Pubkey,
    pub accounts: Vec,
    pub data: Vec,
}

El campo program_id especifica la clave pública del programa que se ejecutará. Es la dirección del programa que procesará la instrucción. El propietario de la cuenta del programa, indicado por esta clave pública, especifica el loader responsable de inicializar y ejecutar el programa. El loader marca como ejecutables los programas on-chain en Solana Bytecode Format (SBF) después de su implementación. El runtime de Solana rechaza cualquier transacción que intente invocar cuentas que no estén marcadas como ejecutables.

El campo accounts enumera las cuentas que la instrucción puede leer o modificar. Estas cuentas deben proporcionarse como valores AccountMeta. Cualquier cuenta cuyos datos pueda modificar la instrucción debe indicarse como modificable; de lo contrario, la transacción fallará. Esto se debe a que los programas no pueden escribir en cuentas que no poseen o para las que no tienen los permisos necesarios. También se aplica a la modificación de los lamports de una cuenta: restar lamports de una cuenta que no pertenece al programa provoca que la transacción falle, mientras que se permite añadir lamports a cualquier cuenta. El campo accounts también puede especificar cuentas que el programa no lee ni modifica. Esto se hace para influir en la programación de la ejecución del programa por parte del runtime, pero estas cuentas se ignoran en los demás aspectos.

data es un vector de uso general de enteros sin signo de 8 bits que funciona como la entrada que se pasa al programa. Este campo es crucial porque contiene las instrucciones codificadas que ejecutará el programa.

Solana no depende del formato de los datos de las instrucciones. Sin embargo, ofrece compatibilidad integrada con la serialización mediante bincode e borsh (Binary Object Representation Serializer for Hashing). La serialización es el proceso de convertir estructuras de datos complejas en una secuencia plana de bytes que puede transmitirse o almacenarse. Al elegir cómo codificar los datos, debe considerarse el costo adicional de la decodificación, ya que todo ocurre on-chain. La serialización de Borsh suele preferirse frente a bincode porque cuenta con una especificación estable, una implementación en JavaScript y suele ser más eficiente.

Los programas utilizan funciones auxiliares para agilizar la construcción de las instrucciones compatibles. Por ejemplo, el programa del sistema proporciona una función auxiliar para construir la instrucción SystemInstruction::Assign:

Código
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
    let account_metas = vec![AccountMeta::new(*pubkey, true)];
    Instruction::new(
        system_program::id(),
        &SystemInstruction::Assign { owner: *owner },
        account_metas,
    )
}

Esta función construye una instrucción que, cuando se procesa, cambia el propietario de la cuenta especificada por el nuevo propietario proporcionado.

Una sola transacción puede contener varias instrucciones, que se ejecutan de forma secuencial y atómica en el orden indicado. Esto significa que todas las instrucciones se completan correctamente o ninguna lo hace. También implica que el orden de las instrucciones puede ser crucial. Los programas deben reforzarse para procesar de forma segura cualquier secuencia posible de instrucciones y evitar potenciales ataques.

Por ejemplo, durante la desinicialización, un programa puede intentar desinicializar una cuenta estableciendo su saldo de lamports en cero. Esto supone que el runtime de Solana eliminará la cuenta. Esta suposición es válida entre transacciones, pero no entre instrucciones o invocaciones entre programas (explicaremos las invocaciones entre programas en otro artículo). El programa debe poner explícitamente en cero los datos de la cuenta para protegerse contra esta posible falla del proceso de desinicialización. De lo contrario, un atacante podría emitir una instrucción posterior para aprovechar la supuesta eliminación, por ejemplo, reutilizando la cuenta antes de que finalice la transacción.

¿Qué son las transacciones versionadas?

Las transacciones de Solana utilizan los estándares de IPv6 para la unidad máxima de transmisión (MTU), a fin de garantizar una transmisión de datos rápida y confiable en todo el clúster. La pila de red de Solana utiliza un tamaño MTU conservador de 1280 bytes. Después de reservar espacio para los encabezados, quedan 1232 bytes disponibles para los datos del paquete. Por tanto, las transacciones de Solana están limitadas a este tamaño.

Esta restricción de tamaño facilita diversas mejoras de la red, pero también limita la complejidad de las operaciones que pueden realizarse en una sola transacción. Dado que cada dirección de cuenta ocupa 32 bytes de almacenamiento, una transacción puede almacenar hasta 35 cuentas sin instrucciones. Esta restricción plantea dificultades para los casos de uso que requieren más de 35 cuentas sin firma en una sola transacción.

Para resolverlo, se introdujo un nuevo formato de transacción compatible con varias versiones. Actualmente, el runtime de Solana admite dos versiones de transacciones:

  • legacy: el formato de transacción original
  • 0 (versión 0): el formato de transacción más reciente, que admite tablas de consulta de direcciones

La versión 0 se lanzó para admitir las tablas de consulta de direcciones (ALT). En esencia, almacenan direcciones de cuentas on-chain en una estructura de datos similar a una tabla. Estas tablas son cuentas separadas que almacenan direcciones de cuentas y permiten referenciarlas en una transacción mediante un índice u8 de 1 byte. Esto reduce considerablemente el tamaño de una transacción, ya que cada cuenta incluida solo necesita 1 byte en lugar de 32. Las ALT son especialmente útiles para operaciones complejas que involucran muchas cuentas, como las habituales en las aplicaciones DeFi.

Este diagrama se adaptó de la sección sobre transacciones versionadas del Solana Cookbook

El término «transacciones versionadas» se refiere a la forma en que Solana admite tanto el formato de transacción heredado como el de la versión 0. Este enfoque garantiza la componibilidad y, al mismo tiempo, incorpora mejoras del runtime.

Estructura de una transacción versionada

Un VersionedTransaction se define así:

Código
pub struct VersionedTransaction {
    pub signatures: Vec,
    pub message: VersionedMessage,
}

El campo signatures es una lista de firmas de los firmantes de la transacción. Sirven para autenticar la transacción y mantener su integridad. message es el contenido real de la transacción. Está encapsulado por el tipo VersionedMessage, un wrapper enum ligero que gestiona tanto los mensajes heredados como los de la versión 0:

Código
pub enum VersionedMessage {
    Legacy(Message),
    V0(Message),
}

La versión del mensaje se determina mediante el primer bit del proceso de serialización. Si el primer bit está establecido, los 7 bits restantes se utilizan para determinar qué versión de Message se serializa, empezando por la versión 0. Si el primer bit no está establecido, se utilizan todos los bytes para codificar el formato heredado de Message. Esto se debe a que hay dos estructuras Message con nombres idénticos, pero se encuentran en módulos distintos: legacy y v0.

Un Message representa el formato interno condensado de una transacción. Se utiliza para la transmisión por la red y la manipulación por parte del runtime. Incluye una lista lineal de todas las cuentas utilizadas por las instrucciones de la transacción, un MessageHeader que detalla la estructura del arreglo de cuentas, un blockhash reciente y una codificación compacta de las instrucciones del mensaje. Esta es la estructura de Message de v0:

Código
pub struct Message {
    pub header: MessageHeader,
    pub account_keys: Vec,
    pub recent_blockhash: Hash,
    pub instructions: Vec,
    pub address_table_lookups: Vec,
}

La diferencia entre un mensaje heredado y uno de v0 es la inclusión del campo address_table_lookups.

Integración del modelo de programación con el flujo de transacciones de Solana

El modelo de programación de Solana está profundamente integrado con sus sistemas de cuentas y transacciones. Así se relacionan estos conceptos:

  • Cuentas como estado: las cuentas de Solana funcionan como contenedores de estado para los programas. El modelo de programación gira en torno a modificar los datos almacenados en estos contenedores en respuesta a instrucciones
  • Instrucciones: los programas definen la lógica para procesar las instrucciones incluidas en una transacción. Estas instrucciones son los componentes ejecutables que interactúan con los datos de las cuentas
  • Serialización y procesamiento: cuando se serializa una transacción, las instrucciones del programa determinan los cambios en el estado de las cuentas. El proceso de serialización respeta el diseño del programa, ya sea que utilice el formato de transacción heredado o el de la versión 0
  • Atomicidad: el modelo de programación de Solana garantiza el procesamiento atómico de las instrucciones. Los programas deben diseñarse para gestionar transacciones simultáneas de forma segura y eficiente
  • Escalabilidad: el modelo de programación de Solana permite escalar mediante funciones como las tablas de consulta de direcciones (ALT). Estas tablas reducen el tamaño de las transacciones y aumentan el número de cuentas que una transacción puede referenciar

El modelo de programación de Solana no consiste solo en escribir código, sino en entender cómo interactúa ese código dentro del ecosistema más amplio. Las cuentas son esenciales para este modelo, ya que constituyen el principal medio para almacenar y modificar datos en la red. Las transacciones posibilitan la actividad on-chain al indicar a los validadores qué datos deben crearse, actualizarse o eliminarse. Comprender a fondo estos aspectos es fundamental para que los desarrolladores creen aplicaciones optimizadas para el rendimiento y la sinergia dentro del ecosistema de Solana.

Conclusión

¡Felicitaciones! En este artículo, recorrimos las complejidades de la arquitectura del sistema de Solana y exploramos el concepto de los clústeres como heaps monolíticos de datos. Descubrimos cómo este heap se organiza en regiones de memoria independientes llamadas cuentas, que forman la base del modelo de programación de Solana. Las cuentas almacenan desde los tokens de los usuarios hasta los propios programas que definen el comportamiento de la red, y todo se modifica mediante transacciones.

Para los desarrolladores, es crucial comprender el enfoque de Solana para la computación descentralizada. Entender los detalles de las cuentas, los programas y las transacciones es necesario para crear aplicaciones que aprovechen al máximo las capacidades de Solana. Se trata de comprender un sistema en el que el código está desacoplado del estado. El resultado son programas sin estado que interactúan con los datos mediante cuentas, con un nivel sin precedentes de componibilidad y capacidad de actualización.

Tanto para los inversionistas como para los usuarios ocasionales, comprender cómo el diseño de Solana crea un ecosistema sólido, flexible y eficiente es crucial para apreciar la viabilidad de la plataforma y su capacidad para impulsar aplicaciones innovadoras que solo son posibles en Solana.

Si llegaste hasta aquí, anon, ¡gracias! ¿Quieres profundizar? Únete hoy a nuestro Discord para empezar a programar en Solana.

Recursos adicionales / Lecturas complementarias

Suscríbete a Helius

Mantente al día con las novedades del desarrollo en Solana y recibe actualizaciones cuando publiquemos

Imagen ampliada