Ir al contenido principal

Oráculos

Última edición: , Invalid DateTime

Los oráculos son feeds de datos que conectan Ethereum a información del mundo real, externa a la blockchain (off-chain) para que puedas consultar datos en tus contratos inteligentes. Por ejemplo, las dapps de mercados de predicciones utilizan oráculos para establecer pagos basados en eventos. Un mercado de predicción podría pedirte que apostases tu ETH sobre el próximo presidente de los Estados Unidos. Utilizarán un oráculo para confirmar el resultado y pagar a los ganadores.

Pre requisitos

Asegúrate de estar familiarizado con los nodos, los mecanismos de consenso y la anatomía de los contratos inteligentes, especialmente con los eventos.

¿Qué es un oráculo?

Un oráculo es un puente entre la blockchain y el mundo real. Los oráculos actúan como API internas a la cadena de bloques (on-chain), que puedes consultar a fin de aportar información a los contratos inteligentes. Esta información podría ser muy variada: desde datos de precios hasta informes climáticos. Los oráculos pueden también ser bidireccionales y usarse para "enviar" datos al mundo real.

Ver explicación de Patrick sobre los oráculos:

¿Por qué son necesarios?

En una cadena de bloques como Ethereum, es necesario que cada nodo de la red sea capaz de replicar cada transacción y obtener el mismo resultado de manera garantizada. Las API introducen información potencialmente variable. Si le enviara una cierta cantidad de ETH a alguien según un valor de $USD acordado y para ello utilizara una API, la consulta arrojaría un resultado diferente de un día para otro. Esto sin mencionar que la API podría ser hackeada o tornarse obsoleta. Si esto ocurriera, los nodos de la red no podrán lograr un acuerdo sobre el estado actual de Ethereum, lo que llevaría a una ruptura del consenso.

Los oráculos resuelven este problema publicando la información en la cadena de bloques. Así, cualquier nodo que replique la transacción usará los mismos datos inmutables que se publicaron en la red. Para ello, un oráculo típicamente constará de un contrato inteligente y algunos componentes externos a la cadena que consultan diversas API y posteriormente envían transacciones para actualizar los datos de los contratos inteligentes.

El problema de los oráculos

Como hemos mencionado, las transacciones de Ethereum no pueden acceder a información fuera de la cadena directamente. Al mismo tiempo, confiar en una sola fuente de confianza para proporcionar la información es inseguro e invalida la descentralización de un contrato inteligente. Esto es conocido como el problema de los oráculos.

Podemos evitar este problema usando un oráculo descentralizado que extraiga información de diferentes fuentes; si una fuente de información es hackeada o falla, el contrato inteligente continuará funcionando según lo previsto.

Seguridad

Un oráculo es tan seguro como las fuentes de datos que utiliza. Si una dapp usa Uniswap como oráculo para el feed de precio ETH/DAI, un atacante podría modificar dicho precio en Uniswap para manipular la comprensión de precio actual de la dapp. Un ejemplo de cómo combatir esto es un sistema de feed(opens in a new tab) como el utilizado por MakerDAO, el cual coteja precios desde muchos feeds de datos externos en vez de confiar solo en una fuente de datos.

Arquitectura

Este es un ejemplo de una arquitectura simple de un oráculo, pero hay más formas de activar el cálculo fuera de la cadena.

  1. Emite un registro con su evento de contrato inteligente.
  2. Un servicio fuera de cadena se suscribió (generalmente usando algo como el comando JSON-RPC eth_subscribe) a estos registros específicos.
  3. El servicio fuera de cadena realiza algunas tareas según se define en el registro.
  4. El servicio fuera de cadena responde con los datos solicitados en una transacción secundaria al contrato inteligente.

Esta es la forma de obtener datos de una manera de 1 a 1. Sin embargo, para mejorar la seguridad, es posible que desee descentralizar la forma en que recopila sus datos fuera de cadena.

El siguiente paso podría ser tener una red de estos nodos haciendo estas llamadas a diferentes API y fuentes, y agregando los datos en cadena.

Off-Chain Reporting de Chainlink(opens in a new tab) (Chainlink OCR) ha mejorado esta metodología haciendo que la red de oráculos fuera de cadena se comunique entre sí. firmen criptográficamente sus respuestas, agreguen sus respuestas fuera de cadena y envíen solo una transacción en cadena con el resultado. De esta manera, se consume menos gas, pero aún obtiene la garantía de datos descentralizados, ya que cada nodo ha firmado su parte de la transacción, lo que hace que el nodo que envía la transacción no pueda modificarla. La política de escalamiento entra en acción si el nodo no realiza transacciones y lo hace el siguiente nodo.

Uso

Usando servicios como Chainlink, puede referenciar datos descentralizados en cadena que se hayan sacado del mundo real y agregado. Algo así como un patrimonio público, pero para datos descentralizados. También puede crear sus propias redes de oráculos modulares para obtener cualquier dato personalizado que esté buscando. Además, puede hacer cómputo fuera de la cadena y enviar información al mundo real también. Chainlink tiene infraestructura para:

Este es un ejemplo de cómo obtener el precio más reciente de ETH en su contrato inteligente usando un feed de precios de Chainlink:

1pragma solidity ^0.6.7;
2
3import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
4
5contract PriceConsumerV3 {
6
7 AggregatorV3Interface internal priceFeed;
8
9 /**
10 * Network: Kovan
11 * Aggregator: ETH/USD
12 * Address: 0x9326BFA02ADD2366b30bacB125260Af641031331
13 */
14 constructor() public {
15 priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
16 }
17
18 /**
19 * Returns the latest price
20 */
21 function getLatestPrice() public view returns (int) {
22 (
23 uint80 roundID,
24 int price,
25 uint startedAt,
26 uint timeStamp,
27 uint80 answeredInRound
28 ) = priceFeed.latestRoundData();
29 return price;
30 }
31}
32
Mostrar todo
📋 Copiar

Puede probar esto en remix con este enlace(opens in a new tab)

Ver la documentación(opens in a new tab)

Chainlink FAV (del acrónimo en inglés de función aleatoria verificable) es una fuente justa y verificable de aleatoriedad diseñada para contratos inteligentes. Los desarrolladores de contratos inteligentes pueden utilizar Chainlink VRF como un generador de números aleatorios (GNA) a prueba de manipulaciones para crear contratos inteligentes confiables para cualquier aplicación que esté basada en resultados impredecibles:

  • NFT y juegos de cadena de bloques
  • Asignación aleatoria de tareas y recursos (por ejemplo, asignación aleatoria de jueces a casos)
  • Elección de una muestra representativa para mecanismos de consenso

Los números aleatorios son difíciles de lograr porque las cadenas de bloques son deterministas.

El trabajo con oráculos de Chainlink fuera de los feeds de datos sigue el ciclo de solicitud y recepción(opens in a new tab) de trabajar con Chainlink. Utilizan el token LINK para enviar gas de oráculo a los proveedores de oráculos para mostrar respuestas. El token LINK está diseñado específicamente para funcionar con oráculos y se basa en el token ERC-677 actualizado, el cual ofrece compatibilidad con versiones anteriores de ERC-20. El siguiente código, si se implementa en la red de prueba de Kovan, recuperará un número aleatorio comprobado de forma criptográfica. Para hacer la solicitud, financie el contrato con algún token LINK de la red de prueba, que puede obtener del Faucet de LINK de Kovan(opens in a new tab).

1
2pragma solidity 0.6.6;
3
4import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
5
6contract RandomNumberConsumer is VRFConsumerBase {
7
8 bytes32 internal keyHash;
9 uint256 internal fee;
10
11 uint256 public randomResult;
12
13 /**
14 * Constructor inherits VRFConsumerBase
15 *
16 * Network: Kovan
17 * Chainlink VRF Coordinator address: 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9
18 * LINK token address: 0xa36085F69e2889c224210F603D836748e7dC0088
19 * Key Hash: 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4
20 */
21 constructor()
22 VRFConsumerBase(
23 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator
24 0xa36085F69e2889c224210F603D836748e7dC0088 // LINK Token
25 ) public
26 {
27 keyHash = 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4;
28 fee = 0.1 * 10 ** 18; // 0.1 LINK (varies by network)
29 }
30
31 /**
32 * Requests randomness from a user-provided seed
33 */
34 function getRandomNumber(uint256 userProvidedSeed) public returns (bytes32 requestId) {
35 require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
36 return requestRandomness(keyHash, fee, userProvidedSeed);
37 }
38
39 /**
40 * Callback function used by VRF Coordinator
41 */
42 function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
43 randomResult = randomness;
44 }
45}
46
Mostrar todo

Los contratos inteligentes no pueden activar o iniciar sus propias funciones en momentos arbitrarios o bajo condiciones arbitrarias. Los cambios de estado solo pueden ocurrir cuando otra cuenta inicia una transacción (como un usuario, un oráculo o un contrato). La Red Chainlink Keepers(opens in a new tab) proporciona opciones para contratos inteligentes a fin de externalizar tareas de mantenimiento regular de manera minimalizada y descentralizada.

Para usar Ckainlink Keepers, un contrato inteligente debe implementar KeeperCompatibleInterface(opens in a new tab), que consta de dos funciones:

  • checkUpkeep: verifica si el contrato requiere trabajo.
  • performUpkeep: realiza el trabajo en el contrato, si se lo indica checkUpkeep.

El siguiente ejemplo es un contrato de contador simple. La variable counter se incrementa en uno por cada llamada a performUpkeep. Puede revisar el siguiente código usando Remix(opens in a new tab)

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.7.0;
3
4// KeeperCompatible.sol imports the functions from both ./KeeperBase.sol and
5// ./interfaces/KeeperCompatibleInterface.sol
6import "@chainlink/contracts/src/v0.7/KeeperCompatible.sol";
7
8contract Counter is KeeperCompatibleInterface {
9 /**
10 * Public counter variable
11 */
12 uint public counter;
13
14 /**
15 * Use an interval in seconds and a timestamp to slow execution of Upkeep
16 */
17 uint public immutable interval;
18 uint public lastTimeStamp;
19
20 constructor(uint updateInterval) {
21 interval = updateInterval;
22 lastTimeStamp = block.timestamp;
23
24 counter = 0;
25 }
26
27 function checkUpkeep(bytes calldata /* checkData */) external override returns (bool upkeepNeeded, bytes memory /* performData */) {
28 upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
29 // We don't use the checkData in this example. Los datos de verificación se definen cuando se registró el Upkeep.
30 }
31
32 function performUpkeep(bytes calldata /* performData */) external override {
33 lastTimeStamp = block.timestamp;
34 counter = counter + 1;
35 // We don't use the performData in this example. Los datos de rendimiento son generados por la llamada de Keeper a su función checkUpkeep
36 }
37}
38
Mostrar todo

Después de implementar un contrato compatible con Keeper, debe registrar el contrato para Upkeep(opens in a new tab) y depositar fondos con LINK para notificar a la Red de Keepers sobre su contrato, de modo que su trabajo se realice continuamente.

Proyectos de Keepers

Las llamadas a la API de Chainlink(opens in a new tab) son la forma más fácil de obtener datos del mundo fuera de la cadena en la forma tradicional en que funciona la Web: llamadas a la API. Una sola instancia de esto y teniendo un solo oráculo hacen que el proceso sea centralizado por naturaleza. Para mantener una verdadera descentralización, una plataforma de contratos inteligentes necesitaría usar numerosos nodos encontrados en un mercado externo de datos(opens in a new tab).

Implementar el siguiente código en remix en la red kovan para probar(opens in a new tab)

Esto también sigue el ciclo de solicitud y recepción de los oráculos y necesita que el contrato reciba fondos de Kovan LINK (el gas de los oráculos) para funcionar.

1pragma solidity ^0.6.0;
2
3import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";
4
5contract APIConsumer is ChainlinkClient {
6
7 uint256 public volume;
8
9 address private oracle;
10 bytes32 private jobId;
11 uint256 private fee;
12
13 /**
14 * Network: Kovan
15 * Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e
16 * Job ID: 29fa9aa13bf1468788b7cc4a500a45b8
17 * Fee: 0.1 LINK
18 */
19 constructor() public {
20 setPublicChainlinkToken();
21 oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;
22 jobId = "29fa9aa13bf1468788b7cc4a500a45b8";
23 fee = 0.1 * 10 ** 18; // 0.1 LINK
24 }
25
26 /**
27 * Create a Chainlink request to retrieve API response, find the target
28 * data, then multiply by 1000000000000000000 (to remove decimal places from data).
29 */
30 function requestVolumeData() public returns (bytes32 requestId)
31 {
32 Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
33
34 // Set the URL to perform the GET request on
35 request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
36
37 // Set the path to find the desired data in the API response, where the response format is:
38 // {"RAW":
39 // {"ETH":
40 // {"USD":
41 // {
42 // "VOLUME24HOUR": xxx.xxx,
43 // }
44 // }
45 // }
46 // }
47 request.add("path", "RAW.ETH.USD.VOLUME24HOUR");
48
49 // Multiply the result by 1000000000000000000 to remove decimals
50 int timesAmount = 10**18;
51 request.addInt("times", timesAmount);
52
53 // Sends the request
54 return sendChainlinkRequestTo(oracle, request, fee);
55 }
56
57 /**
58 * Receive the response in the form of uint256
59 */
60 function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)
61 {
62 volume = _volume;
63 }
64}
65
Mostrar todo

Puede obtener más información acerca de las aplicaciones de Chainlink leyendo el blog para desarrolladores de Chainlink(opens in a new tab).

Servicios de oráculos

Creación de un contrato inteligente de oráculo

Este es un ejemplo de un contrato inteligente de oráculo diseñado por Pedro Costa. Puede encontrar anotaciones adicionales en su artículo: Implementación de un oráculo de cadena de bloques en Ethereum(opens in a new tab).

1pragma solidity >=0.4.21 <0.6.0;
2
3contract Oracle {
4 Request[] requests; //list of requests made to the contract
5 uint currentId = 0; //increasing request id
6 uint minQuorum = 2; //minimum number of responses to receive before declaring final result
7 uint totalOracleCount = 3; // Hardcoded oracle count
8
9 // defines a general api request
10 struct Request {
11 uint id; //request id
12 string urlToQuery; //API url
13 string attributeToFetch; //json attribute (key) to retrieve in the response
14 string agreedValue; //value from key
15 mapping(uint => string) anwers; //answers provided by the oracles
16 mapping(address => uint) quorum; //oracles which will query the answer (1=oracle hasn't voted, 2=oracle has voted)
17 }
18
19 //event that triggers oracle outside of the blockchain
20 event NewRequest (
21 uint id,
22 string urlToQuery,
23 string attributeToFetch
24 );
25
26 //triggered when there's a consensus on the final result
27 event UpdatedRequest (
28 uint id,
29 string urlToQuery,
30 string attributeToFetch,
31 string agreedValue
32 );
33
34 function createRequest (
35 string memory _urlToQuery,
36 string memory _attributeToFetch
37 )
38 public
39 {
40 uint length = requests.push(Request(currentId, _urlToQuery, _attributeToFetch, ""));
41 Request storage r = requests[length-1];
42
43 // Hardcoded oracles address
44 r.quorum[address(0x6c2339b46F41a06f09CA0051ddAD54D1e582bA77)] = 1;
45 r.quorum[address(0xb5346CF224c02186606e5f89EACC21eC25398077)] = 1;
46 r.quorum[address(0xa2997F1CA363D11a0a35bB1Ac0Ff7849bc13e914)] = 1;
47
48 // launch an event to be detected by oracle outside of blockchain
49 emit NewRequest (
50 currentId,
51 _urlToQuery,
52 _attributeToFetch
53 );
54
55 // increase request id
56 currentId++;
57 }
58
59 //called by the oracle to record its answer
60 function updateRequest (
61 uint _id,
62 string memory _valueRetrieved
63 ) public {
64
65 Request storage currRequest = requests[_id];
66
67 //check if oracle is in the list of trusted oracles
68 //and if the oracle hasn't voted yet
69 if(currRequest.quorum[address(msg.sender)] == 1){
70
71 //marking that this address has voted
72 currRequest.quorum[msg.sender] = 2;
73
74 //iterate through "array" of answers until a position if free and save the retrieved value
75 uint tmpI = 0;
76 bool found = false;
77 while(!found) {
78 //find first empty slot
79 if(bytes(currRequest.anwers[tmpI]).length == 0){
80 found = true;
81 currRequest.anwers[tmpI] = _valueRetrieved;
82 }
83 tmpI++;
84 }
85
86 uint currentQuorum = 0;
87
88 //iterate through oracle list and check if enough oracles(minimum quorum)
89 //have voted the same answer has the current one
90 for(uint i = 0; i < totalOracleCount; i++){
91 bytes memory a = bytes(currRequest.anwers[i]);
92 bytes memory b = bytes(_valueRetrieved);
93
94 if(keccak256(a) == keccak256(b)){
95 currentQuorum++;
96 if(currentQuorum >= minQuorum){
97 currRequest.agreedValue = _valueRetrieved;
98 emit UpdatedRequest (
99 currRequest.id,
100 currRequest.urlToQuery,
101 currRequest.attributeToFetch,
102 currRequest.agreedValue
103 );
104 }
105 }
106 }
107 }
108 }
109}
110
Mostrar todo
📋 Copiar

Nos encantaría tener más documentación sobre la creación de un contrato inteligente de oráculo. Si puede colaborar, cree una PR.

Para profundizar sobre el tema

Artículos

Videos

Tutoriales

¿Le ha resultado útil este artículo?