Gestionando la Aleatoriedad On-Chain: Desarrollando Contratos Inteligentes con Verifiable Random Functions (VRF) de Chainlink
Este tutorial te guiará a través del proceso de integrar funciones de aleatoriedad verificable (VRF) de Chainlink en tus contratos inteligentes de Solidity. Descubrirás cómo la aleatoriedad on-chain segura es crucial para aplicaciones descentralizadas justas, desde juegos hasta NFTs y sorteos, y cómo implementarla paso a paso.
🚀 Introducción a la Aleatoriedad Verificable On-Chain
En el mundo de las aplicaciones descentralizadas (dApps), la aleatoriedad es un componente fundamental para una amplia gama de casos de uso. Desde la creación de características únicas para NFTs generativos hasta la determinación de ganadores en sorteos o la lógica de combate en juegos blockchain, la capacidad de introducir un elemento de azar es crucial para la equidad y la imprevisibilidad. Sin embargo, generar aleatoriedad on-chain de manera segura y confiable presenta desafíos significativos.
¿Por qué la aleatoriedad es un desafío en Blockchain? 🤯
Las blockchains son sistemas deterministas. Esto significa que cada nodo de la red debe poder replicar el mismo resultado de una transacción para verificar su validez. Si la aleatoriedad dependiera de factores internos del contrato o de datos fácilmente manipulables (como block.timestamp o block.difficulty), un atacante podría predecir o influir en el resultado, comprometiendo la seguridad y la justicia de la aplicación. Esto se conoce como el problema de la aleatoriedad predecible o manipulable.
La solución: Funciones de Aleatoriedad Verificable (VRF) ✨
Aquí es donde entran en juego las Verifiable Random Functions (VRF). Chainlink VRF es un servicio de oráculo descentralizado que proporciona números aleatorios verificables y a prueba de manipulaciones a los contratos inteligentes. Funciona generando pruebas criptográficas que demuestran que el número aleatorio fue generado correctamente y sin sesgos, y que no fue manipulado. Estas pruebas se verifican on-chain, asegurando la integridad del proceso.
🎯 ¿Cómo funciona Chainlink VRF? Una mirada a fondo
Chainlink VRF opera utilizando un sistema de criptografía de curva elíptica para generar números aleatorios junto con una prueba criptográfica que verifica su autenticidad.
El proceso paso a paso 👣
- Solicitud del Contrato Inteligente: Tu contrato inteligente solicita un número aleatorio a Chainlink VRF. Para ello, llama a la función
requestRandomWordsen el contratoVRFCoordinatorV2Interfacede Chainlink, pasando unkeyHash, la tarifa de gas, un número de palabras aleatorias y uncallbackGasLimit. - Generación Off-Chain: Un nodo Chainlink VRF recibe la solicitud. Utiliza su clave privada para generar un número aleatorio y una prueba criptográfica. La aleatoriedad se deriva de una combinación de su clave secreta y un seed generado a partir del
requestIdde la solicitud y elblockhashdel bloque donde se generó la respuesta. - Transacción de Cumplimiento: El nodo Chainlink envía una transacción de vuelta a la blockchain. Esta transacción contiene el número aleatorio generado y la prueba criptográfica. Llama a la función
rawFulfillRandomWordsen el contratoVRFCoordinatorV2. - Verificación On-Chain: El contrato
VRFCoordinatorV2verifica la prueba criptográfica utilizando la clave pública del nodo Chainlink. Si la prueba es válida, el número aleatorio se considera auténtico. - Entrega al Contrato Solicitante: Finalmente, el contrato
VRFCoordinatorV2llama a la funciónfulfillRandomWordsen tu contrato inteligente, pasándole elrequestIdoriginal y los números aleatorios generados. Tu contrato implementa esta función para recibir y utilizar el valor aleatorio.
🛠️ Requisitos previos para la implementación
Antes de sumergirnos en el código, asegúrate de tener lo siguiente:
- Conocimientos básicos de Solidity y desarrollo de contratos inteligentes.
- Un entorno de desarrollo Solidity: Remix IDE, Hardhat, Truffle, etc.
- MetaMask o una billetera compatible para interactuar con la red de prueba (testnet).
- Fondos de prueba (ETH) en una testnet: Sepolia es la testnet recomendada por Ethereum.
- Tokens LINK de prueba en la misma testnet: Necesarios para pagar las tarifas del servicio VRF de Chainlink.
- Puedes obtener ETH de prueba de un faucet de Sepolia.
- Puedes obtener LINK de prueba de un faucet de Chainlink.
Configuración de Chainlink VRF 📌
Para interactuar con Chainlink VRF, necesitarás algunos parámetros específicos para la red de prueba que estés utilizando. Estos se obtienen de la documentación oficial de Chainlink VRF. Para Sepolia, necesitarás:
VRF_COORDINATOR_V2: La dirección del contratoVRFCoordinatorV2en Sepolia.KEY_HASH: UnkeyHashes el ID del tipo de clave pública del nodo Chainlink VRF que utilizará la red Chainlink para cumplir tu solicitud. Esto impacta el costo de gas del cumplimiento. Selecciona uno adecuado de la documentación.SUBSCRIPTION_ID: Si utilizas el modelo de financiación de suscripción (recomendado), necesitarás una suscripción. Crearemos una en el siguiente paso. Si usas financiación directa, el proceso es ligeramente diferente y más complejo.MINIMUM_CONSUMER_GAS_LIMIT: Un límite de gas mínimo recomendado para la funciónfulfillRandomWordsen tu contrato.
📝 Creando y Financiando una Suscripción VRF (Modelo de Suscripción)
El modelo de suscripción es la forma más sencilla y recomendada de financiar tus solicitudes de VRF. Un contrato de SubscriptionManager retiene tokens LINK y los distribuye entre los contratos consumidores (tus contratos inteligentes) según sus solicitudes.
Pasos para crear y financiar una suscripción:
- Visita la UI de Chainlink VRF: Navega a la interfaz de usuario de Chainlink VRF para la red de prueba Sepolia.
- Conecta tu billetera: Asegúrate de que MetaMask esté configurado para la red Sepolia.
- Crea una nueva suscripción: Haz clic en 'Create new subscription'. Esto generará un
Subscription IDúnico para ti. Anótalo, lo necesitarás más adelante en tu contrato. - Financia la suscripción: Una vez creada, selecciona tu nueva suscripción y haz clic en 'Add funds'. Envía una cantidad suficiente de tokens LINK (por ejemplo, 1 LINK) desde tu billetera a la dirección de tu suscripción. Esto garantizará que tus solicitudes de VRF puedan ser cumplidas.
- Añade tu contrato como consumidor: Cuando despliegues tu contrato inteligente, necesitarás añadir su dirección como un 'Consumer' a tu suscripción. Lo haremos después de desplegar nuestro contrato.
✍️ Desarrollando el Contrato Inteligente Solicitante
Ahora vamos a escribir el contrato Solidity que solicitará y recibirá los números aleatorios de Chainlink VRF. Usaremos Hardhat para este ejemplo.
1. Inicializar el proyecto Hardhat
Si aún no tienes un proyecto Hardhat, créalo:
npx hardhat init
Selecciona 'Create a basic sample project'.
2. Instalar el contrato VRFCoordinatorV2Interface
Necesitamos instalar los contratos de interfaz de Chainlink VRF a través de npm para poder importarlos en nuestro contrato.
npm install @chainlink/contracts
3. Crear el contrato RandomNumberConsumer.sol
Crea un nuevo archivo contracts/RandomNumberConsumer.sol y añade el siguiente código. Este contrato será un Consumer de VRF. Heredará de VRFConsumerBaseV2 para manejar la lógica de la suscripción y el callback.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
contract RandomNumberConsumer is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
// Guarda la ID de la suscripción Chainlink VRF que usamos para financiar las solicitudes.
uint64 s_subscriptionId;
// keyHash es el ID del tipo de clave pública del nodo VRF.
// Consulta la documentación de Chainlink VRF para la red que usas.
bytes32 s_keyHash;
// El límite de gas para el callback de fulfillRandomWords.
// Cuanto más alto, más operaciones puede realizar tu contrato en el callback.
uint32 s_callbackGasLimit;
// El precio de la tarifa de gas para el cumplimiento, ajustado por Chainlink.
uint32 s_requestConfirmations;
// El número de palabras aleatorias a solicitar.
uint32 numWords;
// Almacena las últimas palabras aleatorias generadas.
uint256[] public s_randomWords;
// El ID de la última solicitud de aleatoriedad.
uint256 public s_requestId;
// Evento para emitir cuando se reciben palabras aleatorias.
event RequestSent(uint256 requestId, uint32 numWords);
event RequestFulfilled(uint256 requestId, uint256[] randomWords);
constructor(
uint64 subscriptionId,
address vrfCoordinator,
bytes32 keyHash,
uint32 callbackGasLimit,
uint32 requestConfirmations,
uint32 wordsToRequest
) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
s_subscriptionId = subscriptionId;
s_keyHash = keyHash;
s_callbackGasLimit = callbackGasLimit;
s_requestConfirmations = requestConfirmations;
numWords = wordsToRequest;
}
// Solicita palabras aleatorias del Chainlink VRFCoordinator.
function requestRandomWords() public returns (uint256 requestId) {
// Will revert if subscription is not funded or if there are any other errors.
s_requestId = COORDINATOR.requestRandomWords(
s_keyHash,
s_subscriptionId,
s_requestConfirmations,
s_callbackGasLimit,
numWords
);
emit RequestSent(s_requestId, numWords);
return s_requestId;
}
// Callback que recibe las palabras aleatorias del Chainlink VRFCoordinator.
// Solo puede ser llamado por el VRFCoordinator.
function fulfillRandomWords(
uint256 requestId,
uint256[] memory randomWords
) internal override {
require(s_requestId == requestId, "Request ID mismatch");
s_randomWords = randomWords;
emit RequestFulfilled(requestId, randomWords);
}
// Para propósitos de depuración, permite al propietario ver el saldo de LINK de la suscripción.
function getSubscriptionBalance() public view returns (uint256) {
// Esto solo es válido si el contrato es también el propietario de la suscripción.
// En este ejemplo, el contrato es un consumidor, no el propietario.
// Para ver el saldo real de la suscripción, usa la UI de Chainlink VRF.
return COORDINATOR.getSubscription(s_subscriptionId).balance;
}
}
Explicación del Código 📖
import: Importa las interfaces necesarias de los contratos Chainlink.VRFCoordinatorV2Interface COORDINATOR: Una instancia del contratoVRFCoordinatorV2de Chainlink, usado para interactuar con el servicio VRF.s_subscriptionId: La ID de la suscripción VRF que creaste y financiaste.s_keyHash: ElkeyHashde VRF para la red Sepolia.s_callbackGasLimit: El límite de gas para la funciónfulfillRandomWords. Asegúrate de que sea lo suficientemente alto para cubrir todas las operaciones que tu contrato realizará al recibir el número aleatorio.s_requestConfirmations: El número de confirmaciones de bloque que el oráculo Chainlink esperará antes de responder. Un valor más alto (3es común) aumenta la seguridad a expensas de la latencia.numWords: Cuántos números aleatorios quieres recibir en una sola solicitud.s_randomWords: Un array público para almacenar los números aleatorios generados.constructor: Inicializa las variables con los parámetros específicos de Chainlink VRF y la suscripción.requestRandomWords(): Esta es la función que tu contrato llamará para solicitar aleatoriedad. Internamente, llama arequestRandomWordsen elVRFCoordinatorV2de Chainlink.fulfillRandomWords(): Esta es una funcióninternal overrideque tu contrato debe implementar. ElVRFCoordinatorV2la llamará para entregarte los números aleatorios generados. Aquí es donde tu lógica para usar los números aleatorios debe ir. Es crucial que esta función sea eficiente en gas, ya que elcallbackGasLimitla restringe.
4. Compilar el contrato
npx hardhat compile
5. Script de despliegue deploy.js
Crea un archivo scripts/deploy.js con el siguiente contenido. Asegúrate de reemplazar los placeholders con tus valores reales.
const { ethers } = require("hardhat");
async function main() {
// Parámetros de Sepolia para Chainlink VRF V2. Consulta la documentación oficial de Chainlink.
const VRF_COORDINATOR_V2 = "0x8103525829D64A7822501a5eA4F8bC75D859664D"; // VRF Coordinator para Sepolia
const KEY_HASH = "0x474e34a077df58807dbe9c96d3c009b23bc3c605ce5fa17bb22698aef2794eed"; // Key Hash para Sepolia
const CALLBACK_GAS_LIMIT = 500000; // Un límite de gas generoso para la función fulfillRandomWords
const REQUEST_CONFIRMATIONS = 3; // Número de confirmaciones de bloque a esperar
const WORDS_TO_REQUEST = 1; // Número de palabras aleatorias a generar por solicitud
// ⚠️ Reemplaza con tu Subscription ID real de Chainlink VRF UI
const SUBSCRIPTION_ID = YOUR_SUBSCRIPTION_ID; // EJEMPLO: 1234
if (!SUBSCRIPTION_ID) {
throw new Error("Por favor, establece tu SUBSCRIPTION_ID. Visita https://vrf.chain.link/sepolia");
}
console.log("Desplegando contrato RandomNumberConsumer...");
const RandomNumberConsumer = await ethers.getContractFactory("RandomNumberConsumer");
const randomNumberConsumer = await RandomNumberConsumer.deploy(
SUBSCRIPTION_ID,
VRF_COORDINATOR_V2,
KEY_HASH,
CALLBACK_GAS_LIMIT,
REQUEST_CONFIRMATIONS,
WORDS_TO_REQUEST
);
await randomNumberConsumer.deployed();
console.log(`RandomNumberConsumer desplegado en: ${randomNumberConsumer.address}`);
// 📌 IMPORTANTE: Después de desplegar, añade la dirección del contrato como 'Consumer'
// en tu suscripción en la UI de Chainlink VRF (https://vrf.chain.link/sepolia).
// La dirección es: ${randomNumberConsumer.address}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
6. Desplegar el contrato
Para desplegar en Sepolia, asegúrate de tener configurado Sepolia en tu hardhat.config.js y de que tu variable de entorno ALCHEMY_API_KEY o INFURA_API_KEY y PRIVATE_KEY estén configuradas.
// hardhat.config.js (ejemplo)
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
module.exports = {
solidity: "0.8.19",
networks: {
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
accounts: [PRIVATE_KEY],
},
},
};
Despliega el contrato:
npx hardhat run scripts/deploy.js --network sepolia
Una vez desplegado, la consola te dará la dirección de tu contrato. Copia esta dirección.
✅ Añadir el Contrato como Consumidor en Chainlink VRF UI
Ahora que tu contrato está desplegado, vuelve a la UI de Chainlink VRF, selecciona tu suscripción y haz clic en 'Add consumer'. Pega la dirección de tu contrato desplegado y confírmalo. Esto permite que tu contrato utilice los fondos de tu suscripción para sus solicitudes de VRF.
🧪 Interactuando con el Contrato y Solicitando Aleatoriedad
Ahora que todo está configurado, podemos interactuar con nuestro contrato para solicitar y recibir números aleatorios.
1. Solicitar números aleatorios
Puedes interactuar con tu contrato desplegado a través de Etherscan (si el contrato ha sido verificado) o directamente con un script de Hardhat.
Ejemplo de script para solicitar y leer:
Crea un nuevo script scripts/interact.js:
const { ethers } = require("hardhat");
async function main() {
// Reemplaza con la dirección de tu contrato desplegado
const CONTRACT_ADDRESS = "YOUR_DEPLOYED_CONTRACT_ADDRESS";
if (!CONTRACT_ADDRESS) {
throw new Error("Por favor, establece la dirección de tu contrato desplegado.");
}
const randomNumberConsumer = await ethers.getContractAt("RandomNumberConsumer", CONTRACT_ADDRESS);
console.log("Solicitando números aleatorios...");
const tx = await randomNumberConsumer.requestRandomWords();
const receipt = await tx.wait();
console.log("Solicitud de números aleatorios enviada. Transaction Hash:", receipt.transactionHash);
// Espera un poco a que el oráculo de Chainlink procese la solicitud y envíe el callback.
// Esto puede tardar de unos segundos a un par de minutos dependiendo de la congestión de la red.
console.log("Esperando a que el oráculo de Chainlink cumpla la solicitud...");
// Puedes configurar un listener para el evento RequestFulfilled si estás en un script de larga ejecución
// o simplemente consultar el estado del contrato después de un tiempo.
let randomWords = await randomNumberConsumer.s_randomWords();
while (randomWords.length === 0) {
await new Promise(resolve => setTimeout(resolve, 5000)); // Espera 5 segundos
randomWords = await randomNumberConsumer.s_randomWords();
console.log("Aún esperando palabras aleatorias...");
}
console.log("Números aleatorios recibidos:", randomWords.map(word => word.toString()));
// Para propósitos de demostración, imprimimos el saldo de LINK de la suscripción (solo disponible si el contrato es dueño de la suscripción)
// Para el saldo real, consulta la UI de Chainlink VRF.
// const subscriptionBalance = await randomNumberConsumer.getSubscriptionBalance();
// console.log("Saldo de LINK de la suscripción (desde el contrato):", ethers.utils.formatEther(subscriptionBalance));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Ejecuta el script de interacción:
npx hardhat run scripts/interact.js --network sepolia
Este script primero solicitará un número aleatorio. Luego, esperará (consultando cada 5 segundos) hasta que la función fulfillRandomWords en tu contrato sea llamada por el VRFCoordinatorV2 y tu variable s_randomWords se actualice con el resultado.
Consideraciones sobre el gas y los límites de callback ⛽
Cuando solicitas aleatoriedad, debes especificar un callbackGasLimit. Este es el límite máximo de gas que el VRFCoordinatorV2 usará para ejecutar tu función fulfillRandomWords. Es crucial que todas las operaciones dentro de fulfillRandomWords se mantengan dentro de este límite. Si excede el límite, la transacción de cumplimiento fallará, pero tu suscripción seguirá siendo cargada por la solicitud.
💡 Casos de uso de VRF en dApps
La aleatoriedad segura y verificable abre un mundo de posibilidades para las dApps. Algunos ejemplos incluyen:
- Juegos Blockchain (GameFi): Determinación de recompensas aleatorias, asignación de atributos a personajes/ítems, resultados de batallas, generación de mazmorras.
- NFTs Generativos: Creación de rasgos únicos o rarezas para NFTs en el momento de la acuñación.
- Sorteos y Loterías: Selección de ganadores de forma justa y transparente.
- Asignación de Recursos: Distribución equitativa de recursos escasos o roles en DAOs.
- Muestreo Estadístico: Para protocolos que necesitan seleccionar un subconjunto aleatorio de usuarios o datos.
⚠️ Mejores Prácticas y Seguridad
- Gas
fulfillRandomWords: Diseña tu funciónfulfillRandomWordspara ser lo más eficiente posible en gas. Evita operaciones complejas o que consuman mucho gas dentro de ella. - Manejo de Reentradas: Aunque VRF no es directamente susceptible a reentradas en el sentido clásico, asegúrate de que cualquier lógica que se active después de recibir la aleatoriedad esté protegida contra reentradas si interactúa con otros contratos.
- Verificación de
requestId: Siempre verifica que elrequestIdenfulfillRandomWordscoincide con elrequestIdde tu solicitud original. Esto asegura que estás procesando la respuesta correcta. - Control de Acceso: Restringe quién puede llamar a
requestRandomWordsen tu contrato si no quieres que sea público. - Financiación de Suscripción: Monitorea el saldo de tu suscripción LINK. Si se agota, las solicitudes de VRF fallarán.
- Manejo de Errores: Aunque VRF es robusto, considera qué sucede si una solicitud falla (por ejemplo, por falta de LINK o
callbackGasLimitinsuficiente). Podrías querer un mecanismo de fallback o reintento.
Conclusión ✨
La integración de Chainlink VRF en tus contratos inteligentes es un paso crucial para construir aplicaciones descentralizadas justas, transparentes e impredecibles. Al aprovechar la aleatoriedad verificable, puedes desbloquear nuevos casos de uso y mejorar la seguridad y la confianza en tus dApps. Espero que este tutorial te haya proporcionado una base sólida para empezar a usar Chainlink VRF en tus propios proyectos Solidity.
¿Qué pasa si mi solicitud VRF falla?
Si tu solicitud VRF falla (por ejemplo, por falta de LINK en tu suscripción o por un `callbackGasLimit` insuficiente), la transacción de `requestRandomWords` puede revertir directamente, o la transacción de cumplimiento del oráculo puede fallar. En el segundo caso, se te cobrará por la solicitud, pero tu contrato no recibirá el número aleatorio. Es importante monitorear tus transacciones y los eventos para detectar estos fallos.¿Puedo solicitar múltiples números aleatorios a la vez?
Sí, puedes especificar `numWords` en el constructor o en la llamada a `requestRandomWords` para solicitar múltiples números aleatorios en una sola solicitud. Estos se te entregarán como un array en la función `fulfillRandomWords`.¿Hay alternativas a Chainlink VRF?
Existen otras soluciones de aleatoriedad en el espacio blockchain, como drand o soluciones basadas en *commit-reveal*. Sin embargo, Chainlink VRF es una de las opciones más utilizadas y seguras debido a su enfoque en la verificabilidad criptográfica on-chain y su amplia adopción.Tutoriales relacionados
- Explorando y Mitigando Ataques de Reentrada en Contratos Inteligentes Solidityintermediate15 min
- Asegurando la Interoperabilidad: Desarrollando Contratos Inteligentes EIP-712 en Solidityintermediate18 min
- Desarrollando Oráculos Descentralizados en Ethereum: Conectando Contratos Inteligentes al Mundo Real con Chainlinkintermediate20 min
- Explorando la Programación Orientada a Aspectos (AOP) en Solidity con Proxies y Patronesadvanced20 min
- Decodificando el Calldata Raw: Interacciones de Contratos Inteligentes a Bajo Nivel en Solidityadvanced20 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!