Comisiones en Solana

La cadena de bloques de Solana tiene diferentes tipos de comisiones y costes que se incurren para utilizar la red. Estos pueden segmentarse en unos pocos tipos específicos:

  • Comisiones de Transacción - Una comisión para validadores que procesan transacciones/instrucciones
  • Comisiones de prioridad - Una comisión opcional para mejorar el orden de procesamiento de transacciones
  • Renta - Un saldo retenido para mantener los datos almacenados en la cadena

Comisiones de Transacciones #

La pequeña comisión pagada para procesar la lógica (instrucción) dentro de un programa en Solana es conocido como "transaction fee".

Cuando una transacción que contiene una o más instrucciones se envía a través de la red, es procesada por el líder validador actual. Una vez confirmada como una transacción de estado global, esta transaction fee es pagada a la red para ayudar a apoyar el diseño económico de la cadena de bloques de Solana.

Info

Las comisiones de transacción son diferentes de la comisión de depósito de datos de la cuenta de renta. Mientras que las comisiones de transacción se pagan para procesar las instrucciones en la red de Solana, un depósito de renta es retenido en una cuenta para almacenar sus datos la el blockchain y luego puede ser reclamado.

Actualmente, la comisión base de transacción de Solana se establece en un valor estático de 5 mil lamports por firma. Además de esta comisión base, cualquier comisión de priorización adicional puede ser añadida.

¿Por qué pagar las comisiones de transacción? #

Las comisiones por transacción ofrecen muchas ventajas en el diseño económico de Solana, principalmente:

  • Compensar a la red de validadores por el consumo de recursos informáticos de CPU/GPU necesarios para procesar las transacciones
  • Reducir el spam en la red introduciendo un coste real en las transacciones
  • Proporcionar estabilidad económica a largo plazo a la red mediante un importe mínimo de comisión por transacción fijado por protocolo

Diseño económico básico #

Muchas redes de cadena de bloques (incluidas Bitcoin y Ethereum), se basan en incentivos inflacionarios a nivel del protocolo para asegurar la red a corto plazo. A largo plazo, estas redes dependerán cada vez más de las comisiones de transacción para mantener la seguridad.

Lo mismo puede decirse de Solana. Específicamente:

  • Una proporción fija (inicialmente el 50%) de cada comisión de transacción se quema (destruye), y el resto va al líder actual que procesa la transacción.
  • Una tasa de inflación global programada proporciona una fuente para las recompensas distribuidas a los validadores en Solana.

Cobro de comisiones #

Las transacciones requieren al menos una cuenta que haya firmado la transacción y que sea mutable. Estas cuentas firmantes mutables se serializan primero en la lista de cuentas y la primera de ellas siempre se utiliza como la "fee payer".

Antes de procesar cualquier instrucción de la transacción, se deducirá del balance de la cuenta del "fee payer" para pagar las comisiones de transacción. Si el balance del "fee payer" no es suficiente para cubrir las comisiones de la transacción, el procesamiento de la transacción se detendrá y dará lugar a una transacción fallida.

Si el balance fue suficiente, las comisiones se deducirán y las instrucciones de la transacción comenzarán a ejecutarse. Si alguna de las instrucciones da error, el procesamiento de la transacción se detendrá y, en última instancia, se registrará como una transacción fallida en Solana. El tiempo de ejecución sigue cobrando la comisión por estas transacciones fallidas.

Si alguna de las instrucciones devuelve un error o infringe las restricciones del tiempo de ejecución, se anularán todos los cambios en la cuenta excepto la deducción de la comisión de transacción. Esto se debe a que la red de validadores ya ha gastado recursos informáticos para recopilar transacciones y comenzar el procesamiento inicial.

Distribución de comisiones #

Las comisiones de las transacciones se queman parcialmente y las comisiones restantes las cobra el validador que produjo el bloque en el que se incluyeron las transacciones correspondientes. En concreto, el 50% se quema y el 50% se distribuye al validador que produjo el bloque.

¿Por qué quemar algunas comisiones? #

Como ya se ha mencionado, una proporción fija de cada comisión por transacción se quema (destruye). Con ello se pretende estabilizar el valor económico de SOL y sostener así la seguridad de la red. A diferencia de un esquema en el que las comisiones por transacciones se queman por completo, los líderes siguen estando incentivados a incluir tantas transacciones como sea posible en sus "slots" (oportunidad de crear un bloque).

Las comisiones quemadas también pueden ayudar a evitar que validadores maliciosos censuren transacciones al ser consideradas en la selección fork.

Ejemplo de un ataque: #

En el caso de una bifurcación Proof of History (PoH) con un líder malicioso o censurador:

  • Debido a las comisiones perdidas por la censura, esperaríamos que el total de comisiones quemadas fuera menor que un fork honesto equivalente.
  • Si el líder de la censura quiere compensar estas comisiones de protocolo perdidas, tendría que reponer él mismo las comisiones quemadas en su fork.
  • Reduciendo así potencialmente el incentivo para censurar en primer lugar.

Calculando las comisiones de transacción #

La comisión completa por una transacción determinada se calcula en función de dos partes principales:

  • una comisión base por firma fijada de forma estática, y
  • Los recursos computacionales utilizados durante la transacción, medidos en "unidades de cómputo"

Dado que cada transacción puede requerir una cantidad diferente de recursos computacionales, a cada una se le asigna un número máximo de unidades de cómputo por transacción como parte del presupuesto computacional.

Presupuesto Computacional #

Para evitar el abuso de los recursos informáticos, a cada transacción se le asigna un "presupuesto computacional". Este presupuesto especifica detalles sobre las unidades de cómputo e incluye:

  • El costo del cómputo asociado a los distintos tipos de operaciones que puede realizar la transacción (unidades de cómputo consumidas por operación)
  • El número máximo de unidades de cálculo que puede consumir una transacción (límite de unidades de cómputo),
  • Y los límites operativos que debe respetar la transacción (como los límites de tamaño de los datos de la cuenta)

Cuando la transacción consume todo su presupuesto computacional (agotamiento), excede el límite de tamaño de datos de profundidad máxima de llamadas apiladas o máximo número de cuentas, el tiempo de ejecución detiene el procesamiento de la transacción y devuelve un error. El resultado es una transacción fallida y ningún cambio de estado (aparte de la comisión de transacción cobrada).

Límite de tamaño de cuentas de datos #

Una transacción puede especificar un máximo de bytes que se pueden cargar en una cuenta incluyendo la instrucción SetLoadedAccountsDataSizeLimit (que no exceda el máximo absoluto del tiempo de ejecución). Si no se proporciona SetLoadedAccountsDataSizeLimit, la transacción utiliza por defecto el valor MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES del tiempo de ejecución.

La función ComputeBudgetInstruction::set_loaded_accounts_data_size_limit se puede utilizar para crear esta instrucción:

let instruction = ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(100_000);

Unidades de Cómputo #

Todas las operaciones realizadas dentro de una transacción requieren que los validadores gasten diferentes cantidades de recursos computacionales al procesarlas (coste computacional). La unidad de medida más pequeña para el consumo de estos recursos se denomina "unidad de cómputo".

A medida que se procesa una transacción, las unidades de cómputo se consumen de forma incremental por cada una de las instrucciones que se ejecutan en la cadena (consumiendo el presupuesto). Dado que cada instrucción ejecuta una lógica diferente (escribir en cuentas, cpi, realizar syscalls, etc), cada una puede consumir una cantidad diferente de unidades de cómputo.

Info

Un programa puede registrar detalles sobre su uso computacional, incluyendo cuánto queda en su presupuesto computacional. Vea depuración del programa para más información. También puedes encontrar más información en esta guía para optimizar el uso computacional.

A cada transacción se le asigna un límite de unidades de cómputo, ya sea con el límite por defecto establecido por el tiempo de ejecución o solicitando explícitamente un límite superior. Cuando una transacción supera su límite de unidades de cómputo, su procesamiento se detiene, lo que provoca un fallo de la transacción.

A continuación se enumeran algunas operaciones comunes que incurren en costes computacionales:

  • Ejecutar instrucciones
  • Pasar datos entre programas
  • Realizar syscalls
  • Usar sysvars
  • Registros con el macro msg!
  • Registro de llaves públicas
  • Crear direcciones derivadas de programas (PDAs)
  • Invocaciones entre programas (CPI)
  • Operaciones criptográficas
Info

Para invocaciones entre programas, la instrucción invocada hereda el presupuesto computacional y límites de su padre. Si una instrucción invocada consume el presupuesto restante de la transacción o excede un límite, toda la cadena de invocación y el procesamiento de la transacción es detenido.

Puede encontrar más detalles sobre todas las operaciones que consumen unidades de cómputo del tiempo de ejecución de Solana.

Límite de unidades de cómputo #

Cada transacción tiene un número máximo de unidades de cómputo (CU) que puede consumir denominado "límite de unidades de cómputo". Por transacción, el tiempo de ejecución de Solana tiene un límite máximo absoluto de unidades de cómputo de 1,4 millones de CU y establece un límite máximo solicitado por defecto de 200k CU por instrucción.

Una transacción puede solicitar un límite de unidades de cómputo más específico y óptimo incluyendo una única instrucción SetComputeUnitLimit. Un límite superior o inferior. Pero nunca podrá solicitar más del límite máximo absoluto por transacción.

Aunque el límite de unidades de cómputo por defecto de una transacción funcionará en la mayoría de los casos, a menudo son menos que óptimas (tanto para el tiempo de ejecución como para el usuario). Para transacciones más complejas, como la invocación de programas que realizan múltiples CPIs, puede ser necesario solicitar un límite de unidades de cómputo más alto para la transacción.

Solicitar los límites óptimos de unidades de cómputo para su transacción es esencial para ayudarle a pagar menos por su transacción y para ayudar a procesar su transacción en la red. Billeteras, dApps y otros servicios deben asegurarse de que sus solicitudes de unidades de cómputo son óptimas para ofrecer la mejor experiencia posible a sus usuarios.

Info

Para más detalles y mejores prácticas, lea esta guía sobre solicitar límites óptimos de cómputo.

Precio de las unidades de cómputo #

Cuando una transacción desea pagar una comisión más alta para aumentar su prioridad de procesamiento, puede establecer un "precio a la unidad de cómputo". Este precio, utilizado en combinación con el límite de unidades de cómputo, se utilizará para determinar la comisión de prioridad de una transacción.

Por defecto, no se establece un precio de unidad de cómputo lo que resulta en ninguna comisión adicional de priorización.

Comisiones de prioridad #

Como parte del presupuesto computacional, el tiempo de ejecución permite transacciones que pagan una comisión opcional conocida como "comisión de prioridad". El pago de esta comisión adicional ayuda a aumentar la prioridad que se da a una transacción frente a otras a la hora de procesarla, lo que se traduce en tiempos de ejecución más rápidos.

Cómo se calcula la comisión de prioridad #

La comisión de prioridad de una transacción se calcula multiplicando su límite de unidades de cómputo por el precio por unidad de cómputo (medido en micro-lamports). Estos valores pueden establecerse una vez por transacción incluyendo las siguientes instrucciones del programa de presupuesto computacional:

  • SetComputeUnitLimit - Establece el número máximo de unidades de cómputo que puede consumir la transacción
  • SetComputeUnitPrice - Establece la comisión adicional que la transacción está dispuesta a pagar para aumentar su prioridad

Si no se proporciona la instrucción SetComputeUnitLimit, se utilizará el default compute unit limit.

Si no se proporciona la instrucción SetComputeUnitPrice, la transacción se realizará por defecto sin comisión adicional y con la prioridad más baja (es decir, sin comisión de prioridad).

Cómo establecer la comisión de prioridad #

La comisión de prioridad de una transacción se establece incluyendo una instrucción SetComputeUnitPrice y, opcionalmente, una instrucción SetComputeUnitLimit`. El tiempo de ejecución utilizará estos valores para calcular la comisión de prioridad, que se utilizará para priorizar la transacción dentro del bloque.

Puedes elaborar cada una de estas instrucciones a través de sus funciones Rust o @solana/web3.js. A continuación, cada instrucción puede incluirse en la transacción y enviarse al clúster con normalidad. Vea también las mejores prácticas a continuación.

A diferencia de otras instrucciones dentro de una transacción Solana, las instrucciones de Presupuesto Computacional NO requieren ninguna cuenta. Una transacción con múltiples de cualquiera de las instrucciones fallará.

Caution

Las transacciones sólo pueden contener una de cada tipo de instrucción del presupuesto computacional. Los tipos de instrucción duplicados provocarán un error TransactionError::DuplicateInstruction y, en última instancia, el fallo de la transacción.

Rust #

El crate de rust solana-sdk incluye funciones dentro de ComputeBudgetInstruction para crear instrucciones para establecer el límite de unidades de cómputo y el precio por unidad de cómputo:

let instruction = ComputeBudgetInstruction::set_compute_unit_limit(300_000);
let instruction = ComputeBudgetInstruction::set_compute_unit_price(1);

Javascript #

La librería @solana/web3.js incluye funciones dentro de la clase ComputeBudgetProgram para elaborar instrucciones para establecer el límite de unidades de cómputo y el precio por unidad de cómputo:

const instruction = ComputeBudgetProgram.setComputeUnitLimit({
  units: 300_000,
});
const instruction = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: 1,
});

Mejores prácticas para comisiones de prioridad #

A continuación encontrará información general sobre las mejores prácticas en materia de comisión de prioridad. También puede encontrar información más detallada en esta guía sobre cómo solicitar cómputo óptimo, incluyendo cómo simular una transacción para determinar su uso aproximado de cómputo.

Solicitar las unidades de cómputo mínimas #

Las transacciones deben solicitar la cantidad mínima de unidades de cómputo necesarias para minimizar las comisiones. Tenga en cuenta también que las comisiones no se ajustan cuando el número de unidades de cómputo solicitadas supera el número de unidades de cómputo realmente consumidas por una transacción ejecutada.

Obtener comisiones de prioridad recientes #

Antes de enviar una transacción al clúster, puede utilizar el método RPC getRecentPrioritizationFees para obtener una lista de las últimas comisiones de prioridad pagadas dentro de los últimos bloques procesados por el nodo.

A continuación, podría utilizar estos datos para estimar una comisión de priorización adecuada para su transacción con el fin de (a) garantizar que sea procesada mejor por el clúster y (b) minimizar las comisiones pagadas.

Renta (Rent) #

La comisión depositada en cada cuenta de Solana para mantener su información disponible en la cadena de bloques se denomina "renta". Esta comisión se retiene en el balance normal de cada cuenta y puede reclamarse cuando se cierra la cuenta.

Info

La renta es diferente de las comisiones de transacción. La renta es "pagada" (se retiene en una cuenta) para mantener los datos almacenados en la cadena de bloques de Solana y se puede reclamar. Mientras que las comisiones por transacción se pagan por procesar instrucciones en la red.

Se requiere que todas las cuentas mantengan un balance en lamports lo suficientemente alto (en relación con su espacio asignado) para ser exento de renta y permanecer en la cadena de bloques de Solana. Cualquier transacción que intente reducir el balance de una cuenta por debajo de su respectivo balance mínimo para ser exento de renta fallará (a menos que el balance se reduzca exactamente a cero).

Cuando el propietario de una cuenta ya no desea mantener estos datos en la cadena de bloques y disponibles en el estado global, el propietario puede cerrar la cuenta y reclamar el depósito de la renta.

Esto se consigue retirando (transfiriendo) todo el balance de lamports de la cuenta a otra cuenta (es decir, a su billetera). Al reducir el saldo de la cuenta a exactamente 0, el tiempo de ejecución eliminará la cuenta y sus datos asociados de la red en el proceso de "recolección de basura".

Comisión de Renta #

La comisión de renta en Solana se fija para toda la red, basándose principalmente en un valor en el tiempo de ejecución "lamports por byte por año". Actualmente, la comisión de renta es una cantidad estática y se almacena en la variable renta del sistema.

Esta comisión de renta se utiliza para calcular el importe exacto de renta que debe tenerse en una cuenta dado su espacio en bytes (es decir, la cantidad de datos que pueden almacenarse en la cuenta). Cuanto más espacio asigne una cuenta, mayor será el depósito de renta retenido.

Exención de renta #

Las cuentas deben mantener un balance de lamports superior al mínimo requerido para almacenar sus respectivos datos en la cadena de bloques. Esto se denomina estar "exento de renta" y ese balance se denomina "balance mínimo para la exención de la renta".

Info

Las cuentas nuevas (y programas) en Solana REQUIEREN ser inicializadas con suficientes lamports para convertirse en exentas de renta. No siempre ha sido así. Antes, el tiempo de ejecución cobraba periódica y automáticamente una comisión a cada cuenta por debajo de su balance mínimo para la exención de renta. Finalmente, estas cuentas se reducen a cero y se eliminan del estado global (a menos que se recarguen manualmente).

En el proceso de creación de una cuenta nueva, debe asegurarse de depositar suficientes lamports para estar por encima de este balance mínimo. Todo lo que sea inferior a este umbral mínimo resultará en una transacción fallida.

Cada vez que se reduce el balance de una cuenta, el tiempo de ejecución realiza una comprobación para ver si la cuenta seguirá estando por encima de este balance mínimo para la exención de la renta. A menos que reduzcan el balance final exactamente a 0 (cerrando la cuenta), las operaciones que hagan que el balance de una cuenta descienda por debajo del umbral de exención de renta fracasarán.

El saldo mínimo específico para que una cuenta quede exenta de renta depende de la comisión de renta actual de la cadena de bloques y de la cantidad deseada de espacio de almacenamiento que una cuenta desee asignar (tamaño de la cuenta). Por lo tanto, se recomienda utilizar el endpoint RPC getMinimumBalanceForRentExemption para calcular el balance específico para un tamaño de cuenta determinado.

El monto del depósito de renta necesario también puede estimarse mediante el subcomando de CLI solana rent:

solana rent 15000
 
# salida
Rent per byte-year: 0.00000348 SOL
Rent per epoch: 0.000288276 SOL
Rent-exempt minimum: 0.10529088 SOL

Recolección de basura #

Las cuentas que no mantienen un saldo de lamports superior a cero se eliminan de la red en un proceso conocido como recolección de basura. Este proceso es realizado para ayudar a reducir el almacenamiento de datos que ya no son utilizados por la red.

Después de que una transacción reduzca con éxito el balance de una cuenta exactamente a 0, el tiempo de ejecución realiza automáticamente la recolección de basura. Cualquier transacción que intente reducir el balance de una cuenta por debajo de su balance mínimo para la exención de renta (que no sea exactamente cero) fallará.

Warning

Es importante tener en cuenta que la recolección de basura ocurre después de que la ejecución de la transacción se ha completado. Si hay una instrucción para «cerrar» una cuenta reduciendo el saldo de la cuenta a cero, la cuenta puede "reabrirse" dentro de la misma transacción mediante una instrucción posterior. Si el estado de la cuenta no se borró en la instrucción de "cierre", la instrucción posterior de "reapertura" tendrá el mismo estado de cuenta. Es un problema de seguridad, por lo que es bueno saber el momento exacto en que la recolección de basura tiene efecto.

Incluso después de que una cuenta haya sido eliminada de la red (a través de la recolección de basura), todavía puede tener transacciones asociadas a su dirección (ya sea en el pasado o en el futuro). Aunque el explorador de bloques de Solana muestre un mensaje del tipo "account not found" (cuenta no encontrada), es posible que pueda ver el historial de transacciones asociado a esa cuenta.

Puede leer la propuesta implementada del validador para la recolección de basura para obtener más información.