Oracles
Oracles are data feeds that connect Ethereum to off-chain, real-world information, so you can query data in your smart contracts. For example, prediction market dapps use oracles to settle payments based on events. A prediction market may ask you to bet your ETH on the next president of the United States. They'll use an oracle to confirm the outcome and pay out to the winners.
Prerequisites
Make sure you're familiar with nodes, consensus mechanisms, and smart contract anatomy, specifically events.
What is an oracle
An oracle is a bridge between the blockchain and the real world. They act as on-chain APIs you can query to get information into your smart contracts. This could be anything from price information to weather reports. Oracles can also be bi-directional, used to "send" data out to the real world.
Why are they needed?
With a blockchain like Ethereum you need every node in the network to be able to replay every transaction and end up with the same result, guaranteed. APIs introduce potentially variable data. If you were sending someone an amount of ETH based on an agreed $USD value using a price API, the query would return a different result from one day to the next. Not to mention, the API could be hacked or deprecated. If this happens, the nodes in the network wouldn't be able to agree on Ethereum's current state, effectively breaking consensus.
Oracles solve this problem by posting the data on the blockchain. So any node replaying the transaction will use the same immutable data that's posted for all to see. To do this, an oracle is typically made up of a smart contract and some off-chain components that can query APIs, then periodically send transactions to update the smart contract's data.
Security
An oracle is only as secure as its data source(s). If a dapp uses Uniswap as an oracle for its ETH/DAI price feed, it is possible for an attacker to move the price on Uniswap in order to manipulate the dapp's understanding of the current price. An example of how to combat this is a feed system like the one used by MakerDAO, which collates price data from a number of external price feeds instead of just relying on a single source.
Architecture
This is an example of a simple Oracle architecture, but there are more ways than this to trigger off-chain computation.
- Emit a log with your smart contract event
- An off-chain service has subscribed (usually using something like the JSON-RPC
eth_subscribe
command) to these specific logs. - The off-chain service proceeds to do some tasks as defined by the log.
- The off-chain service responds with the data requested in a secondary transaction to the smart contract.
This is how to get data in a 1 to 1 manner, however to improve security you may want to decentralize how you collect your off-chain data.
The next step might be to have a network of these nodes making these calls to different APIs and sources, and aggregating the data on-chain.
Chainlink Off-Chain Reporting (Chainlink OCR) has improved on this methodology by having the off-chain oracle network communicate with each other, cryptographically sign their responses, aggregate their responses off-chain, and send only 1 transaction on-chain with the result. This way, fewer gas is spent but you still get the guarantee of decentralized data since every node has signed their part of the transaction, making it unchangeable by the node sending the transaction. If the node doesn't transact, the escalation policy kicks in, and the next node sends the transaction.
Usage
Using services like Chainlink, you can reference decentralized data on-chain, that has already been pulled from the real world and aggregated. Sort of like a public commons, but for decentralized data. You can also build your own modular oracle networks to get any customized data you're looking for. In addition, you can do off-chain computation and send information to the real world as well. Chainlink has infrastructure in place to:
- Get crypto price feeds in your contract
- Generate verifiable random numbers (useful for gaming)
- Call external APIs – One novel use of this is Checking wBTC reserves
This is an example of how to get the latest ETH price in your smart contract using a Chainlink price feed:
Chainlink Data Feeds
1pragma solidity ^0.6.7;23import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";45contract PriceConsumerV3 {67 AggregatorV3Interface internal priceFeed;89 /**10 * Network: Kovan11 * Aggregator: ETH/USD12 * Address: 0x9326BFA02ADD2366b30bacB125260Af64103133113 */14 constructor() public {15 priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);16 }1718 /**19 * Returns the latest price20 */21 function getLatestPrice() public view returns (int) {22 (23 uint80 roundID,24 int price,25 uint startedAt,26 uint timeStamp,27 uint80 answeredInRound28 ) = priceFeed.latestRoundData();29 return price;30 }31}32Показати всеКопіювати
You can test this in remix with this link
Chainlink VRF
Chainlink VRF (Verifiable Random Function) is a provably-fair and verifiable source of randomness designed for smart contracts. Smart contract developers can use Chainlink VRF as a tamper-proof random number generation (RNG) to build reliable smart contracts for any applications which rely on unpredictable outcomes:
- Blockchain games and NFTs.
- Random assignment of duties and resources (e.g. randomly assigning judges to cases).
- Choosing a representative sample for consensus mechanisms.
Random numbers are difficult because blockchains are deterministic.
Working with Chainlink Oracles outside of data feeds follows the request and receive cycle of working with Chainlink. They use the LINK token to send oracle providers oracle gas for returning responses. The LINK token is specifically designed to work with oracles and are based on the upgraded ERC-677 token, which is backwards compatible with ERC-20. The following code, if deployed on the Kovan testnet will retrieve a cryptographically proven random number. To make the request, fund the contract with some testnet LINK token that you can get from the Kovan LINK Faucet.
12pragma solidity 0.6.6;34import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";56contract RandomNumberConsumer is VRFConsumerBase {78 bytes32 internal keyHash;9 uint256 internal fee;1011 uint256 public randomResult;1213 /**14 * Constructor inherits VRFConsumerBase15 *16 * Network: Kovan17 * Chainlink VRF Coordinator address: 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E918 * LINK token address: 0xa36085F69e2889c224210F603D836748e7dC008819 * Key Hash: 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f420 */21 constructor()22 VRFConsumerBase(23 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator24 0xa36085F69e2889c224210F603D836748e7dC0088 // LINK Token25 ) public26 {27 keyHash = 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4;28 fee = 0.1 * 10 ** 18; // 0.1 LINK (varies by network)29 }3031 /**32 * Requests randomness from a user-provided seed33 */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 }3839 /**40 * Callback function used by VRF Coordinator41 */42 function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {43 randomResult = randomness;44 }45}46Показати все
Chainlink API Call
Chainlink API Calls are the easiest way to get data from the off-chain world in the traditional way the web works: API calls. Doing a single instance of this and having only 1 oracle makes it centralized by nature. In order to keep it truly decentralized a smart contract platform would need to use numerous nodes, found in an external data market.
Deploy the following code in remix on the kovan network to test
This also follows the request and receive cycle of oracles, and needs the contract to be funded with Kovan LINK (the oracle gas) in order to work.
1pragma solidity ^0.6.0;23import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";45contract APIConsumer is ChainlinkClient {67 uint256 public volume;89 address private oracle;10 bytes32 private jobId;11 uint256 private fee;1213 /**14 * Network: Kovan15 * Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e16 * Job ID: 29fa9aa13bf1468788b7cc4a500a45b817 * Fee: 0.1 LINK18 */19 constructor() public {20 setPublicChainlinkToken();21 oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;22 jobId = "29fa9aa13bf1468788b7cc4a500a45b8";23 fee = 0.1 * 10 ** 18; // 0.1 LINK24 }2526 /**27 * Create a Chainlink request to retrieve API response, find the target28 * 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);3334 // Set the URL to perform the GET request on35 request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");3637 // 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");4849 // Multiply the result by 1000000000000000000 to remove decimals50 int timesAmount = 10**18;51 request.addInt("times", timesAmount);5253 // Sends the request54 return sendChainlinkRequestTo(oracle, request, fee);55 }5657 /**58 * Receive the response in the form of uint25659 */60 function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)61 {62 volume = _volume;63 }64}65Показати все
You can learn more about the applications of chainlink by reading the developers blog.
Oracle services
Build an oracle smart contract
Here's an example oracle contract by Pedro Costa. You can find further annotation in his article: Implementing a Blockchain Oracle on Ethereum.
1pragma solidity >=0.4.21 <0.6.0;23contract Oracle {4 Request[] requests; //list of requests made to the contract5 uint currentId = 0; //increasing request id6 uint minQuorum = 2; //minimum number of responses to receive before declaring final result7 uint totalOracleCount = 3; // Hardcoded oracle count89 // defines a general api request10 struct Request {11 uint id; //request id12 string urlToQuery; //API url13 string attributeToFetch; //json attribute (key) to retrieve in the response14 string agreedValue; //value from key15 mapping(uint => string) anwers; //answers provided by the oracles16 mapping(address => uint) quorum; //oracles which will query the answer (1=oracle hasn't voted, 2=oracle has voted)17 }1819 //event that triggers oracle outside of the blockchain20 event NewRequest (21 uint id,22 string urlToQuery,23 string attributeToFetch24 );2526 //triggered when there's a consensus on the final result27 event UpdatedRequest (28 uint id,29 string urlToQuery,30 string attributeToFetch,31 string agreedValue32 );3334 function createRequest (35 string memory _urlToQuery,36 string memory _attributeToFetch37 )38 public39 {40 uint lenght = requests.push(Request(currentId, _urlToQuery, _attributeToFetch, ""));41 Request storage r = requests[lenght-1];4243 // Hardcoded oracles address44 r.quorum[address(0x6c2339b46F41a06f09CA0051ddAD54D1e582bA77)] = 1;45 r.quorum[address(0xb5346CF224c02186606e5f89EACC21eC25398077)] = 1;46 r.quorum[address(0xa2997F1CA363D11a0a35bB1Ac0Ff7849bc13e914)] = 1;4748 // launch an event to be detected by oracle outside of blockchain49 emit NewRequest (50 currentId,51 _urlToQuery,52 _attributeToFetch53 );5455 // increase request id56 currentId++;57 }5859 //called by the oracle to record its answer60 function updateRequest (61 uint _id,62 string memory _valueRetrieved63 ) public {6465 Request storage currRequest = requests[_id];6667 //check if oracle is in the list of trusted oracles68 //and if the oracle hasn't voted yet69 if(currRequest.quorum[address(msg.sender)] == 1){7071 //marking that this address has voted72 currRequest.quorum[msg.sender] = 2;7374 //iterate through "array" of answers until a position if free and save the retrieved value75 uint tmpI = 0;76 bool found = false;77 while(!found) {78 //find first empty slot79 if(bytes(currRequest.anwers[tmpI]).length == 0){80 found = true;81 currRequest.anwers[tmpI] = _valueRetrieved;82 }83 tmpI++;84 }8586 uint currentQuorum = 0;8788 //iterate through oracle list and check if enough oracles(minimum quorum)89 //have voted the same answer has the current one90 for(uint i = 0; i < totalOracleCount; i++){91 bytes memory a = bytes(currRequest.anwers[i]);92 bytes memory b = bytes(_valueRetrieved);9394 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.agreedValue103 );104 }105 }106 }107 }108 }109}110Показати всеКопіювати
We'd love more documentation on creating an oracle smart contract. If you can help, create a PR!
Further reading
- What is a Blockchain Oracle? - Patrick Collins
- Decentralised Oracles: a comprehensive overview –Julien Thevenard
- Implementing a Blockchain Oracle on Ethereum –Pedro Costa
- Oracles –EthHub
Допоможіть нам покращити цю сторінку
Якщо ви добре розумієтеся на темі й хочете зробити внесок, відредагуйте цю сторінку й поділіться своїми знаннями.
Ми будемо вас вдячні від імені всієї спільноти Ethereum!
Використайте цей гнучкий шаблон для документів
Виникли запитання? Зв’яжіться з нами в каналі #контенту на нашому сервері Discord
Виправити сторінку