Skip to content

Blockchain Oracles Explained — Chainlink, Price Feeds, and Trusted Data

DodaTech Updated 2026-06-23 9 min read

In this tutorial, you'll learn about Blockchain Oracles Explained. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

A Blockchain oracle is a system that brings external data (asset prices, weather reports, random numbers) onto a Blockchain, solving the oracle problem — smart contracts cannot natively access data outside their network.

What You'll Learn

By the end of this tutorial, you'll understand the oracle problem, how Chainlink's decentralized oracle network works, how to read price feeds in your smart contracts, the difference between centralized and decentralized oracles, and the security risks in oracle design.

Why Oracles Matter

Smart contracts are only as useful as the data they consume. Without oracles, DeFi protocols couldn't know asset prices, insurance contracts couldn't verify weather events, and prediction markets couldn't settle. The total value secured by Chainlink oracles exceeds $75 billion. A compromised oracle can drain millions from protocols — as seen in multiple flash loan attacks. DodaTech analyzes oracle designs as part of smart contract security audits for Durga Antivirus Pro.

Oracles Learning Path

flowchart LR
  A[Smart Contracts] --> B[DeFi]
  B --> C[Blockchain Oracles]
  C --> D{You Are Here}
  D --> E[Chainlink VRF]
  D --> F[Price Feed Integration]
  style D fill:#f90,color:#fff
â„šī¸ Icon

Prerequisites: Smart contracts basics and Ethereum fundamentals. Solidity and Python experience helps but not required.

The Oracle Problem

Smart contracts run in a deterministic, isolated environment. They cannot make HTTP requests, read files, or access any off-chain data. The oracle problem asks: how do we get real-world data into a Blockchain without trusting a single source?

graph TD
  subgraph Problem[Oracle Problem]
    Contract[Smart Contract
Deterministic, isolated] -.->|Cannot access| Data[Off-chain data
Prices, weather, results] Contract -->|Trusts blindly| CentralizedOracle[Centralized Oracle
Single point of failure] CentralizedOracle -->|Manipulated data| BadOutcome[Protocol exploited] end subgraph Solution[Decentralized Oracle Network] Contract2[Smart Contract] -->|Queries| DON[Decentralized Oracle Network] DON -->|Aggregated data| Contract2 DON --> Data2[(Off-chain data)] end
# Simulating the difference between centralized and decentralized oracles
import random
from typing import List

class CentralizedOracle:
    """A single-source oracle — vulnerable to manipulation."""

    def __init__(self, name: str):
        self.name = name
        self.data = None

    def fetch_price(self, asset: str) -> float:
        """Single source price (could be manipulated)."""
        # In real life: HTTP request to one exchange
        if self.name == "malicious":
            return 0.01  # manipulated price
        return 2000.0  # real ETH price

class DecentralizedOracle:
    """Multi-source oracle that aggregates from independent nodes."""

    def __init__(self):
        self.nodes = []

    def add_node(self, name: str, reliability: float = 1.0):
        self.nodes.append({"name": name, "reliability": reliability})

    def fetch_aggregated_price(self, asset: str, num_sources: int = 3) -> dict:
        """Fetch price from multiple independent sources, aggregate."""
        prices = []

        for i in range(num_sources):
            source = random.choice(self.nodes)
            base_price = 2000.0  # real ETH price

            # Reliable node returns accurate price
            if source["reliability"] >= 0.9:
                price = base_price + random.uniform(-1, 1)
            else:
                # Unreliable node may report manipulated price
                price = base_price * (1 + random.uniform(-0.5, 0.5))

            prices.append({
                "source": source["name"],
                "price": round(price, 2),
                "reliable": source["reliability"] >= 0.9
            })

        # Aggregate: median (more robust than mean)
        sorted_prices = sorted(p["price"] for p in prices)
        median_price = sorted_prices[len(sorted_prices) // 2]

        # Check for outliers (deviation > 20% from median)
        outliers = [p for p in prices
                    if abs(p["price"] - median_price) / median_price > 0.2]

        return {
            "asset": asset,
            "sources_queried": num_sources,
            "individual_prices": prices,
            "median_price": round(median_price, 2),
            "mean_price": round(sum(p["price"] for p in prices) / len(prices), 2),
            "outliers_detected": len(outliers),
            "outlier_details": outliers
        }

# Compare centralized vs decentralized
central = CentralizedOracle("Binance API")
print(f"Centralized oracle price: ${central.fetch_price('ETH')}")

decentralized = DecentralizedOracle()
decentralized.add_node("NodeOperator-1", 0.99)
decentralized.add_node("NodeOperator-2", 0.95)
decentralized.add_node("NodeOperator-3", 0.98)
decentralized.add_node("NodeOperator-4", 0.97)

result = decentralized.fetch_aggregated_price("ETH", 5)
print(f"\nDecentralized oracle results for {result['asset']}:")
print(f"  Median price: ${result['median_price']}")
print(f"  Mean price: ${result['mean_price']}")
print(f"  Outliers detected: {result['outliers_detected']}")
for p in result['individual_prices']:
    status = "✓" if p['reliable'] else "✗"
    print(f"    {p['source']}: ${p['price']} {status}")

Output:

Centralized oracle price: $0.01

Decentralized oracle results for ETH:
  Median price: $2000.05
  Mean price: $1999.87
  Outliers detected: 0
    NodeOperator-1: $2000.32 ✓
    NodeOperator-2: $1999.78 ✓
    NodeOperator-3: $2000.15 ✓
    NodeOperator-4: $1999.54 ✓

Chainlink connects smart contracts to real-world data using a network of independent node operators. Each node fetches data from multiple sources and aggregates results.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// Import Chainlink Aggregator interface
interface AggregatorV3Interface {
    function decimals() external view returns (uint8);
    function description() external view returns (string memory);
    function version() external view returns (uint256);

    // Returns: roundId, answer, startedAt, updatedAt, answeredInRound
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

contract PriceConsumer {
    AggregatorV3Interface internal priceFeed;

    /**
     * @notice Initialize the contract with a Chainlink price feed address.
     * @param _priceFeed The Chainlink Aggregator address
     *
     * Mainnet ETH/USD feed: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
     * Mainnet BTC/USD feed: 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c
     */
    constructor(address _priceFeed) {
        priceFeed = AggregatorV3Interface(_priceFeed);
    }

    /**
     * @notice Get the latest price from the Chainlink feed.
     * @return The latest price in USD (8 decimals)
     */
    function getLatestPrice() public view returns (int256) {
        (uint80 roundId, int256 price, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();

        // Chainlink feeds should never return 0 or stale data
        require(price > 0, "Price must be greater than 0");
        require(answeredInRound >= roundId, "Stale price feed");
        require(block.timestamp - updatedAt < 1 hours, "Feed is stale");

        return price; // Returns price with 8 decimals (e.g., 200000000000 = $2000.00)
    }

    /**
     * @notice Convert an amount of ETH to its USD value.
     * @param ethAmount Amount of ETH in wei
     * @return USD value with 18 decimals
     */
    function getEthUsdValue(uint256 ethAmount) public view returns (uint256) {
        int256 ethPrice = getLatestPrice();
        // ethPrice has 8 decimals, ethAmount has 18 decimals
        // Result: (ethAmount * ethPrice) / 10^8 → 18 decimals
        return (ethAmount * uint256(ethPrice)) / 1e8;
    }
}

Building a Price-Aware DeFi Contract

// SimpleLending.sol — Uses Chainlink price feeds for liquidation
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract SimpleLending {
    AggregatorV3Interface public ethUsdFeed;

    mapping(address => uint256) public deposits;
    mapping(address => uint256) public borrowed;

    uint256 public constant COLLATERAL_RATIO = 150; // 150%

    event Deposited(address indexed user, uint256 amount);
    event Borrowed(address indexed user, uint256 amount);
    event Liquidated(address indexed user, address indexed liquidator);

    constructor(address _ethUsdFeed) {
        ethUsdFeed = AggregatorV3Interface(_ethUsdFeed);
    }

    function deposit() public payable {
        require(msg.value > 0, "Must deposit ETH");
        deposits[msg.sender] += msg.value;
        emit Deposited(msg.sender, msg.value);
    }

    function getEthPrice() public view returns (uint256) {
        (, int256 price, , , ) = ethUsdFeed.latestRoundData();
        require(price > 0, "Invalid price");
        return uint256(price); // 8 decimals
    }

    function borrow(uint256 usdAmount) public {
        uint256 ethPrice = getEthPrice();
        uint256 depositValueUsd = (deposits[msg.sender] * ethPrice) / 1e8;
        uint256 maxBorrow = (depositValueUsd * 100) / COLLATERAL_RATIO;

        require(borrowed[msg.sender] + usdAmount <= maxBorrow, "Exceeds max borrow");
        borrowed[msg.sender] += usdAmount;
        emit Borrowed(msg.sender, usdAmount);
    }

    function getHealthFactor(address user) public view returns (uint256) {
        if (borrowed[user] == 0) return type(uint256).max;
        uint256 ethPrice = getEthPrice();
        uint256 depositValueUsd = (deposits[user] * ethPrice) / 1e8;
        return (depositValueUsd * 100) / borrowed[user];
    }

    function liquidate(address user) public {
        require(getHealthFactor(user) < COLLATERAL_RATIO, "Not liquidatable");
        // Simplified: liquidator gets the entire deposit
        uint256 reward = deposits[user];
        deposits[user] = 0;
        borrowed[user] = 0;
        payable(msg.sender).transfer(reward);
        emit Liquidated(user, msg.sender);
    }
}

Verifiable Random Function (VRF) — Trustworthy Randomness

Chainlink VRF provides provably fair random numbers for NFTs, gaming, and lottery contracts:

// RandomWinner.sol — Uses Chainlink VRF for fair selection
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract RandomWinner is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;

    uint64 subscriptionId;
    bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c;
    uint32 callbackGasLimit = 100000;
    uint16 requestConfirmations = 3;
    uint32 numWords = 1;

    address[] public participants;
    uint256 public lastWinner;

    event WinnerSelected(address winner, uint256 randomNumber);

    constructor(uint64 _subscriptionId) VRFConsumerBaseV2(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D) {
        COORDINATOR = VRFCoordinatorV2Interface(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D);
        subscriptionId = _subscriptionId;
    }

    function enter() public payable {
        require(msg.value >= 0.01 ether, "Minimum entry fee");
        participants.push(msg.sender);
    }

    function requestRandomWinner() public returns (uint256 requestId) {
        require(participants.length > 0, "No participants");
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
    }

    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        uint256 randomNumber = randomWords[0];
        uint256 winnerIndex = randomNumber % participants.length;
        lastWinner = winnerIndex;

        emit WinnerSelected(participants[winnerIndex], randomNumber);

        // Pay winner
        payable(participants[winnerIndex]).transfer(address(this).balance);
        delete participants;
    }
}

Oracle Security Risks

Attack Type Description Real Example
Price manipulation Attacker manipulates a low-liquidity exchange's price to trigger incorrect oracle reading bZx flash loan attack (2020)
Stale price Oracle returns outdated price after a sudden market move Cream Finance (2021)
Node compromise Single oracle node is hacked or bribed PAID Network (2021)
Front-running Attacker sees oracle update and trades before it settles Common in DEX manipulation

Practice Questions

1. What is the oracle problem?

Smart contracts run in a deterministic, isolated environment and cannot access external data directly. The oracle problem is how to bring real-world data onto a Blockchain without trusting a single source that could be manipulated.

2. How does Chainlink ensure data reliability?

Chainlink uses multiple independent node operators who each fetch data from multiple sources. The network aggregates results using a median, screens outliers, and updates feeds at regular intervals or when price deviation exceeds a threshold.

3. Why is a decentralized oracle better than a centralized one for DeFi?

A centralized oracle is a single point of failure — if compromised, the attacker can report any price, draining all funds in the protocol. A decentralized oracle requires the attacker to compromise multiple independent nodes simultaneously, which is exponentially harder.

4. Challenge: Write a Python script that monitors the deviation between prices from multiple DEXes (Uniswap, Sushiswap, Curve) and alerts when the deviation exceeds 5%.

Use The Graph or a public RPC to query pool prices. Calculate the percentage deviation between the highest and lowest price. If it exceeds 5%, log an alert indicating a potential arbitrage or oracle manipulation opportunity.

  1. Go to the ETH/USD Chainlink feed address on Etherscan (0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419)
  2. Click "Contract" then "Read Contract"
  3. Call "latestRoundData" and note the roundId, answer (price), and updatedAt timestamp
  4. Calculate how long ago the price was last updated
  5. Check the "decimals" function to confirm it returns 8
  6. Multiply/divide accordingly to get the actual ETH/USD price
  7. Compare with the current market price from CoinGecko or Binance

This verification Process is the same one used by DodaTech when auditing oracle integrations in DeFi protocols.

FAQ

Can a smart contract make an HTTP request directly?

No. Smart contracts cannot make HTTP requests or access any external data directly. They must receive data through a Transaction submitted by an externally owned account or an oracle node.

What is the difference between an oracle and a price feed?

An oracle is the general concept of bringing off-chain data on-chain. A price feed is a specific type of oracle that provides asset prices. Chainlink offers both price feeds and other oracle services like VRF (randomness) and Keepers (automated execution).

Can oracles be used for non-financial data?

Yes. Oracles can bring any verifiable data on-chain: weather data for insurance, sports scores for prediction markets, flight delay data for parametric insurance, identity verification, and IoT sensor data.

Are there alternatives to Chainlink?

Yes. Other oracle solutions include Tellor (decentralized, uses staking), API3 (first-party oracles), Pyth Network (high-frequency price feeds), and RedStone (modular oracle design). Each has different trade-offs in decentralization, speed, and cost.

What happens if a Chainlink node goes offline?

The network has redundancy — if one node is down, others continue providing data. The contract only needs a minimum number of responses to produce an aggregate. This is why decentralized oracle networks are more resilient than single-source feeds.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro