⚠️ ¡Advertencia! El siguiente artículo es un resumen sobre Mastering Ethereum, en consecuencia, me tomé la libertad de quitar las partes que no me parecieron relevantes y profundizar en lo que sí. Si tenés alguna sugerencia o corrección, no dudes en contactarme.
Transacciones - Parte 1
Podemos definir una transición como un mensaje originado por una EOA, transmitido a la red de Ethereum y registrado en la Ethereum blockchain. Otra forma de ver las transacciones es verlas como algo que pueden generar un cambio en el estado o causar la ejecución de un contrato en la EVM.
Nota: es importante entender que ningún contrato se ejecuta de forma autónoma, todo empieza con una transacción.
En este capítulo vamos a indagar en como funcionan las transacciones y examinar en detalle su funcionamiento y composición.
La estructura de una transacción
Vamos a hablar de la estructura de una transacción serializada, ya que es la única forma estándar, aunque algunos clientes pueden implementar versiones aumentadas que agregan metadata adicional.
Nonce: un número secuencia, determinado por la EOA usado para evitar reenvío de mensajes.
Gas price: el precio que el que envía la transacción está dispuesto a pagar
Gas limit: la máxima cantidad de gas que el que envía la transacción está dispuesto a comprar para ejecutar esta transacción
Recipient: la dirección de destino
Value: la cantidad de ethers a enviar a destino
Data: un payload de largo variable binario
v,r,s: los tres componentes de la firma digital originaria de la EOA.
La transacción está estructurada usando Recursive Length Prefix (RLP) como esquema de encoding.
Nonce de transición
El nonce es un valor que no se almacena en la cuenta, sino que se calcula contando el número de transacciones confirmadas que saliendo desde la cuenta.
Hay dos escenarios en los que la existencia de un nonce para contar transacciones es importante: — que las transacciones se incluyan en el orden de creación — protección contra la duplicación de transacciones
Si no tuviéramos el nonce y enviáramos dos transacciones, A (nonce =0
) y B (nonce=1
), algunos nodos podría procesar la B, otro la y, por lo tanto, podría generar un comportamiento no deseado en el orden de las transacciones. También hay que tomar en cuenta que la transacción B no va a ser procesada hasta que pasen todas las transacciones que tengan un número de nonce anterior a esta, en este caso la A
Ahora en el segundo caso, imaginemos que mandamos una transacción A por 1 ETH, si esta no tuviera un nonce, el receptor o cualquier actor malicioso podría perfectamente tomar la transacción y retransmitirla en la red. De esta forma podría hacer que la transacción se ejecute dos veces, y ahora tener 2 ETH, cuando la intención del sender fue solo mandar un 1 ETH. El uso del nonce evita eso, funcionando básicamente con un número de idempotencia.
En resumen, es importante tener en cuenta que el uso del nonce es vital para un protocolo account-based como Ethereum, en contraste con el mecanismo de UTXO del protocolo Bitcoin.
Revisando un nonce de ejemplo
El nonce es un contador basado en cero, lo que significa que la primera transacción tiene un nonce de 0.
Cuando creas una nueva transacción, asignas el siguiente nonce en la secuencia. Pero hasta que no se confirme, no se contará hacia el total de getTransactionCount. Por lo tanto, si tu getTransactionCount actual es 40 se mantendrá así hasta que se apruebe la última transacción que enviaste recién siendo ahí el momento en el que suba a 41.
Por lo tanto, al momento de usar getTransactionCount en el momento de enviar varias transacciones seguidas debemos manejar el incremento de dónde ya sea guardándolo de otra forma(local por ejemplo vario wallets almacenan el nonce en el local coso del navegador) o contando las traslaciones e incrementando acorde.
Brechas en los nonces, Nonces duplicados y Confirmación
Como mencionamos antes es importante realizar un seguimiento de los nonces si estás creando transacciones programáticamente, especialmente si estás haciendo esto desde múltiples procesos independientes simultáneamente.
La red de Ethereum procesa las transacciones secuencialmente, basándose en el nonce. Esto significa que si transmites una transacción con nonce 0 y luego transmites una transacción con nonce 2, la segunda transacción no se incluirá en ningún bloque. Se almacenará en el mempool, mientras que la red de Ethereum espera que aparezca el nonce faltante.
Si luego trasmitimos una transacción con el nonce faltante 1, ambas transacciones (nonces 1 y 2) se procesarán e incluirán (si son válidas, por supuesto). Una vez que completes la brecha, la red puede extraer la transacción fuera de secuencia que tenía en el mempool.
Esto significa que si creas varias transacciones en secuencia y una de ellas no se incluye oficialmente en ningún bloque, todas las transacciones subsiguientes quedarán “atrapadas”, esperando el nonce faltante. Una transacción puede crear una “brecha” inadvertida en la secuencia de nonces porque es inválida o tiene gas insuficiente.
Para hacer que las cosas avancen nuevamente, debes transmitir una transacción válida con el nonce faltante. También debes tener en cuenta que una vez que una transacción con el nonce “faltante” es validada por la red, todas las transacciones difundidas con nonces subsiguientes se volverán incrementalmente válidas; ¡no es posible “recuperar” una transacción!
Si, por otro lado, duplicas accidentalmente un nonce, por ejemplo, al transmitir dos transacciones con el mismo nonce, pero destinatarios o valores diferentes, entonces una de ellas será confirmada y la otra será rechazada.
Concurrencia, Origen de Transacciones y Nonces
La concurrencia es un desafío en sistemas como Ethereum, donde múltiples aplicaciones pueden generar transacciones simultáneamente. Por ejemplo, en un intercambio, varios procesos pueden intentar retirar fondos de la misma wallet al mismo tiempo.
Una solución podría ser utilizar una sola computadora para asignar nonces, pero esto crea un punto único de falla.
Otra opción es generar transacciones sin asignar nonces y luego firmarlas y asignarles nonces en un nodo centralizado. Sin embargo, esto también puede ser un cuello de botella (recordá que el nonce es una parte integral de los datos de la transacción y, por lo tanto, necesita ser incluido en la firma digital que autentica la transacción).
Al final, estos problemas de concurrencia, junto con la dificultad de realizar un seguimiento de los saldos de cuentas y las confirmaciones de transacciones en procesos independientes, llevan a la mayoría de las implementaciones a evitar la concurrencia y crear cuellos de botella.
Y terminan preponderando soluciones como un único proceso que maneje todas las transacciones de retiro en un intercambio, o configurar múltiples billeteras calientes que puedan funcionar completamente de forma independiente para los retiros y solo necesiten ser re-balanceadas intermitentemente.
Gas
El gas es el combustible de Ethereum y se utiliza para controlar el uso de recursos en una transacción. El gasPrice permite al sender de la transacción establecer el precio que está dispuesto a pagar por el gas, medido en wei por unidad de gas. Al momento de enviar la transacción en una wallet se puede ajustar el gasPrice para lograr una confirmación más rápida de las transacciones. La rapidez de la confirmación está relacionada con el gasPrice: cuanto más alto sea, más rápida será la confirmación, y viceversa.
Podemos revisar el precio del gas en weis y en USD en este sitio
Otro concepto relevante relacionado con el gas es el gasLimit. En pocas palabras, gasLimit indica el máximo número de unidades de gas que el iniciador de la transacción está dispuesto a comprar para completar la transacción. Para pagos simples, es decir, transacciones que transfieren ether de un EOA a otro EOA, la cantidad de gas necesaria está fijada en 21,000 unidades de gas. Para calcular cuánto costará eso en ether, multiplicas 21,000 por el gasPrice que estás dispuesto a pagar.
Si el destino de tu transición es un smart contract, se puede estimar el gas, pero no va a ser de forma precisa, ya que el contrato en su interior puede evaluar diferentes condiciones que bifurquen a diferentes costos de ejecución en su interior.
En resumen, podemos pensar en el gas como una cuenta abierta la cual tenemos que pagar al momento de finalizar la ejecución, y se nos cobra tomando en cuenta cuanto gas usamos. Cuando se transmite la transacción a la red, uno de los primeros pasos que se ejecuta es validar que hay suficiente ether para pagar el gasPrice * gasLimit
.
Pero en realidad eso no se cobra de la cuenta hasta que la transacción termina y solo se nos cobra el gas que efectivamente consumo la transacción.
Referencias
1.Antonopoulos, A. M., & Wood, G. (2018). “Mastering Ethereum: Building Smart Contracts and DApps.” O’Reilly Media.