WEB3DEV

Cover image for Oráculos da Blockchain
Panegali
Panegali

Posted on

Oráculos da Blockchain

Escrito com a ajuda de Rishabh Vasudevan.

Contratos inteligentes não podem acessar fontes de dados externas, como dados do mundo real da internet ou outros sistemas externos. Isso é crucial para o funcionamento de muitos contratos inteligentes. Por exemplo, considere um contrato inteligente projetado para pagar automaticamente benefícios de seguro quando houver atraso em voos. Para que este contrato funcione corretamente, ele precisa ser capaz de acessar dados de horários de voos de fontes externas.

Oráculo são nós especializados projetados para fornecer acesso confiável e preciso a fontes de dados externas para uso em contratos inteligentes. Dada a natureza autônoma dos contratos inteligentes, é essencial garantir a integridade dos dados fornecidos pelos oráculos.

Como a ChainLink resolve o problema dos oráculos?

A ChainLink é uma rede descentralizada de nós especializados chamados "Nós da ChainLink", que atuam como oráculos, fornecendo acesso seguro e confiável a fontes de dados externas. Esses nós são operados por operadores independentes. A ChainLink também utiliza um sistema de reputação descentralizado para garantir a confiabilidade e precisão dos dados fornecidos pelos nós. Um usuário pode conectar seu contrato inteligente a um nó da ChainLink para acessar fontes de dados externas.

Para atuar como um nó da ChainLink, é necessário executar o software da ChainLink e configurá-lo para se conectar à rede. O token nativo da ChainLink (LINK) precisa ser deixado em stake como colateral para garantir que os nós desempenhem suas funções de forma confiável. Uma vez que o nó está configurado e conectado à rede, ele pode começar a fornecer dados para contratos inteligentes. Os operadores de nós recebem recompensas na forma de tokens LINK por fornecer dados à rede. (YouTube, Blog)

Como funcionam os oráculos?

Os contratos inteligentes utilizam oráculos para buscar dados externos da seguinte forma:

  1. O contrato inteligente do usuário (USER-SC) envia uma solicitação para o contrato do oráculo (CHAINLINK-SC). Este é o contrato no qual os nós do oráculo (Core) monitoram de perto.
  2. Após a entrada da solicitação, o Core começa a executar a tarefa com base nos parâmetros da solicitação com a ajuda do Adaptador (Adapter).
  3. O Adaptador entra em contato com os provedores de dados externos e retorna o valor recebido ao Core, que escreve os dados no contrato do oráculo por meio de uma transação na blockchain.
  4. E por meio de uma função de retorno de chamada, os dados são então recebidos pelo contrato inteligente (cliente) que originou a solicitação.

Solicitação do contrato do oráculo

Vários nós participam para evitar a centralização da tomada de decisões. Os dados fornecidos pelos nós são agregados por um Contrato Inteligente (Chainlink-SC) usando métodos apropriados, como média/mediana, etc.

Arquitetura de nós descentralizada

A tarefa de agregação geralmente é abstraída em um contrato inteligente agregador. Existem contratos inteligentes de proxy que são tipicamente usados para permitir flexibilidade na troca de contratos inteligentes de agregação. O contrato inteligente de proxy armazena a localização do contrato do agregador. Os contratos inteligentes que usam o oráculo não precisam alterar o endereço de chamada sempre que houver alguma alteração no código dos agregadores. Ele também é usado para salvar dados históricos em diferentes agregadores.

Oráculo de Preços

Vamos revisar o oráculo de preços para entender o fluxo de trabalho acima: os feeds de preços são um oráculo muito especial fornecido pela ChainLink. A maioria dos tokens é negociada ativamente em corretoras centralizadas. É justo assumir que o valor em uma corretora centralizada é um bom indicador do valor de um determinado token.

O oráculo de preços permite que DApps usem os preços atuais de diferentes tokens. O uso de nós da ChainLink requer tokens LINK como incentivo. Ele agrega os valores recebidos de vários nós diferentes e, com base na reputação, retorna a média de todos os nós (a média é calculada pelo contrato de agregação).

Aqui está a captura de tela do feed de preço ETH/USD fornecido pelo oráculo da Chainlink:

Os nós que participam como oráculos nisso são nós oficiais. Para se tornar um nó oficial, você pode participar do "Node Olympics" ou tentar entrar em contato com a equipe da ChainLink. Este link documenta vários tipos diferentes de contratos de agregação (código padrão, código de média de preços, código de contrato de proxy, contrato do oráculo).

Seguindo um exemplo de código que obtém o preço do ETH em USD do CoinGecko. Ele utiliza o cliente ChainLink para fazer chamadas ao contrato do oráculo. O que ele requer é um ID de trabalho (que define o que o nó deve fazer com os dados e que tipo de dados deve retornar) e um endereço de oráculo. Isso é passado para o objeto chamado ChainLinkRequest. O objeto também inclui coisas como o objeto de solicitação de endereço (que contém todos os detalhes necessários para recuperar os dados), o endereço do contrato do oráculo e uma função de retorno de chamada. Uma vez que os dados são recuperados e agregados, a função de retorno de chamada é chamada com o valor resultante.

pragma solidity 0.6.0;

import https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/ChainlinkClient.sol";
import https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/vendor/Ownable.sol";

contract ExampleOracleClient is ChainlinkClient, Ownable {
 address constant private ORACLE = 0x83dA1beEb89Ffaf56d0B7C50aFB0A66Fb4DF8cB1;
string constant private JOB_ID = 93547cb3c6784ec08a366be6211caa24;
uint256 constant private ORACLE_PAYMENT = 1 * LINK / 10;

uint256 public currentPrice;

event RequestEthereumPriceFulfilled(
 bytes32 indexed requestId,
 uint256 indexed price
);

constructor() public Ownable() {
 setPublicChainlinkToken();
}

function requestEthereumPrice() public onlyOwner {
 Chainlink.Request memory req = buildChainlinkRequest(stringToBytes32(JOB_ID), address(this), this.fulfillEthereumPrice.selector);
 sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
}

function fulfillEthereumPrice(bytes32 _requestId, uint256 _price) public recordChainlinkFulfillment(_requestId) {
 emit RequestEthereumPriceFulfilled(_requestId, _price);
 currentPrice = _price;
}

function withdrawLink() public onlyOwner {
 LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
 require(link.transfer(msg.sender, link.balanceOf(address(this))), Unable to transfer);
}

function stringToBytes32(string memory source) private pure returns (bytes32 result) {
 bytes memory tempEmptyStringTest = bytes(source);
 if (tempEmptyStringTest.length == 0) {
 return 0x0;
 }

 assembly { // solhint-disable-line no-inline-assembly
 result := mload(add(source, 32))
 }
}
}
Enter fullscreen mode Exit fullscreen mode

Exemplo de criação de uma rede usando nós da ChainLink

A seguir, um exemplo de como um nó de oráculo funcionaria. Eu criei 2 contratos:

  1. O contrato Agregador e
  2. O contrato inteligente de proxy que chama o agregador.

Também usei um contrato do oráculo (contrato inteligente que o nó monitora e também possui). Usei este oráculo para fazer duas chamadas get>uint256, o que significa basicamente que eu queria um uint256 em retorno quando enviasse uma solicitação get. Uma solicitação chamou o volume da ação ETH e a outra solicitou seu preço em USD. Depois disso, em vez de calcular uma média ou mediana, pois isso era apenas um teste e uma prova de conceito, eu simplesmente multipliquei os dois e enviei isso como resultado.

//programa que chama o agregador
pragma solidity ^0.8.7;
// SPDX-License-Identifier: MIT

contract APIConsumer {
 uint256 public volume;
 uint256 public price;
 uint256 public marketcap;
 bytes32 private jobId;
 uint256 private fee;

 event RequestVolume(bytes32 indexed requestId, uint256 volume);
 event RequestPrice(bytes32 indexed requestId, uint256 price);
 event print(uint256 value);

 constructor(){}

 function requestprice() public returns (bytes32 requestId) {}
 function requestVolumeData() public returns (bytes32 requestId) {}

 function fulfill(
 bytes32 _requestId,
 uint256 _volume
 ) public {}

 function fulfill2(
 bytes32 _requestId,
 uint256 _price
 ) public {}

 function updateMarketCap() public{}

 function withdrawLink() public {}
}

contract Caller {
 APIConsumer aggregator;
 uint256 public marketcap;

 function getData(address _address) public returns(uint256){
 aggregator = APIConsumer(_address);
 marketcap = aggregator.marketcap();
 return marketcap;
 }
 function requestUpdateValue(address _address) public{
 aggregator = APIConsumer(_address);
 aggregator.updateMarketCap();
 }
}
Enter fullscreen mode Exit fullscreen mode
// Agregador
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@ChainLink/contracts/src/v0.8/ChainLinkClient.sol";
import "@ChainLink/contracts/src/v0.8/ConfirmedOwner.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

/**
* Solicite LINK e ETH da rede de testes aqui: https://faucets.chain.link/
* Encontre informações sobre os contratos do Token LINK e obtenha as torneiras mais recentes de ETH e LINK aqui: https://docs.chain.link/docs/link-token-contracts/
*/

/**
* ESTE É UM CONTRATO DE EXEMPLO QUE USA VALORES FIXOS PARA MAIOR CLAREZA.
* ESTE EXEMPLO USA CÓDIGO NÃO AUDITADO.
* NÃO USE ESTE CÓDIGO EM PRODUÇÃO.
*/

contract APIConsumer is ChainLinkClient, ConfirmedOwner {
 using ChainLink for ChainLink.Request;
 using SafeMath for uint256;

 uint256 public volume;
 uint256 public price;
 uint256 public marketcap;
 bytes32 private jobId;
 uint256 private fee;

 event RequestVolume(bytes32 indexed requestId, uint256 volume);
 event RequestPrice(bytes32 indexed requestId, uint256 price);
 event print(uint256 value);

 /**
 * @notice Inicialize o token LINK e o oráculo alvo
 *
 * Detalhes da rede de testes Goerli:
 * Token Link: 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
 * Oráculo: 0xCC79157eb46F5624204f47AB42b3906cAA40eaB7 (ChainLink DevRel)
 * jobId: ca98366cc7314957b8c012c72f05aeeb
 *
 */
 constructor() ConfirmedOwner(msg.sender) {
 setChainLinkToken(0x326C977E6efc84E512bB9C30f76E30c160eD06FB);
 setChainLinkOracle(0xCC79157eb46F5624204f47AB42b3906cAA40eaB7);
 jobId = "ca98366cc7314957b8c012c72f05aeeb";
 fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varia de acordo com a rede e o trabalho)
 }

function requestprice() public returns (bytes32 requestId) {
 ChainLink.Request memory req = buildChainLinkRequest(
 jobId,
 address(this),
 this.fulfill2.selector
 );

 // Define a URL para realizar a solicitação GET
 req.add(
 "get",
 "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD"
 );

 req.add("path", "RAW,ETH,USD,PRICE"); // Os nós da ChainLink 1.0.0 e posteriores suportam este formato

 // Multiplique o resultado por 1000000000000000000 para remover casas decimais
 int256 timesAmount = 10 ** 18;
 req.addInt("times", timesAmount);

 // Envia a solicitação
 return sendChainLinkRequest(req, fee);
 }

 /**
 * Crie uma solicitação ChainLink para obter a resposta da API, encontre os dados alvo e multiplique por 1000000000000000000 (para remover casas decimais dos dados).
 */
 function requestVolumeData() public returns (bytes32 requestId) {
 ChainLink.Request memory req = buildChainLinkRequest(
 jobId,
 address(this),
 this.fulfill.selector
 );

 // Define a URL para realizar a solicitação GET
 req.add(
 "get",
 "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD"
 );

 req.add("path", "RAW,ETH,USD,VOLUME24HOUR"); // Os nós da ChainLink 1.0.0 e posteriores suportam este formato

 // Multiplique o resultado por 1000000000000000000 para remover casas decimais
 int256 timesAmount = 10 ** 18;
 req.addInt("times", timesAmount);

 // Envia a solicitação
 return sendChainLinkRequest(req, fee);
 }

 /**
 * Receba a resposta na forma de uint256
 */
 function fulfill(
 bytes32 _requestId,
 uint256 _volume
 ) public recordChainLinkFulfillment(_requestId) {
 emit RequestVolume(_requestId, _volume);
 volume = _volume;
 }

 function fulfill2(
 bytes32 _requestId,
 uint256 _price
 ) public recordChainLinkFulfillment(_requestId) {
 emit RequestPrice(_requestId, _price);
 price = _price;
 marketcap = price.mul(volume);
 }

 function updateMarketCap() public{
 requestVolumeData();
 requestprice();
 }

 function withdrawLink() public onlyOwner {
 LinkTokenInterface link = LinkTokenInterface(ChainLinkTokenAddress());
 require(
 link.transfer(msg.sender, link.balanceOf(address(this))),
 "Unable to transfer"
 );
 }
}
Enter fullscreen mode Exit fullscreen mode

Como não há uma maneira de fazer o programa em Solidity esperar, primeiro precisamos fazer uma chamada para atualizar o valor e, somente quando os resultados retornarem dos nós, o que leva algum tempo, podemos fazer uma chamada para obter o valor que estamos procurando. Se fizermos isso antes de os valores do oráculo retornarem, o valor será o mesmo que o do ciclo anterior.

Passo a passo de como o programa é executado:

  1. O contrato inteligente possui a ABI (nesse caso, a estrutura do contrato de agregação sem a definição da função) do contrato de agregação. Isso significa que ele pode chamar as funções do contrato de agregação diretamente se tiver o endereço do contrato de agregação.
  2. Portanto, o contrato inteligente faz uma chamada ao agregador, que por sua vez faz chamadas aos nós de oráculos usando o ChainLinkClient. Isso é feito por meio de uma transação.
  3. O nó do oráculo recebe a chamada e, em seguida, emite um evento em seus registros; o nó que possui esse contrato do oráculo fica de olho nos registros para observar um evento específico.
  4. O próprio nó é dividido em duas partes: uma é o núcleo e a outra é o adaptador. A primeira parte, que fica de olho no contrato do oráculo, é feita pelo núcleo. Portanto, assim que os dados necessários para atender à solicitação são coletados pelo núcleo do nó, ele envia os dados para o adaptador.
  5. O adaptador verifica o ID do trabalho para entender o que deve ser feito com os dados e busca-os. Em seguida, ele os envia de volta para a blockchain para o contrato do oráculo com a ajuda de uma transação.
  6. O contrato do oráculo, com a ajuda da transação, envia os dados para o agregador, que por sua vez os envia para o contrato inteligente que estava inicialmente procurando por eles.

Diagrama de blocos mostrando o fluxo de trabalho

Exemplo do que é emitido em um evento quando o contrato do oráculo deseja chamar o nó.

Exemplo do nó enviando dados para o contrato do oráculo usando uma transação.

Exemplo de dados da transação do nó atualizando o valor anterior dos dados.

Você pode entrar em contato com a equipe da ChainLink em https://chain.link/data-feeds
se desejar criar uma rede pronta para produção.

Referências:

  1. https://www.leewayhertz.com/ChainLink-solving-blockchain-oracle-problem/
  2. https://arxiv.org/pdf/2004.07140.pdf
  3. Playlist da ChainLink para oráculos: https://www.youtube.com/playlist?list=PLVP9aGDn-X0QwJVbQvuKr-zrh2_DV5M6J

Artigo escrito por pankaj garg. Traduzido por Marcelo Panegali.

Oldest comments (0)