Мост между централизованным и децентрализованным мирами: разбираемся, что такое оракулы
Оракул — это сервис (или устройство), который предоставляет определенный набор данных для использования внутри блокчейна. После размещения данных в сети, они становятся доступны для использования на смарт-контрактах.
По сути, оракулы являются мостом между двумя мирами. Между миром децентрализованным и централизованным. Между смарт-контрактом и сторонним поставщиком данных. Основная задача оракулов — это предоставление надежной информации для смарт-контрактов, которой можно доверять.
Дальше поговорим про оракулы для EVM-совместимых сетей.
Зачем нужны оракулы?
Блокчейн Ethereum спроектирован так, чтобы быть полностью детерминированным. Это означает, что если кто-то загрузит историю сети и будет ее воспроизводить, то сеть всегда будет проигрывать одно и тоже состояние. Детерминизм необходим для того, чтобы узлы блокчейна могли прийти к консенсусу. Для реализации механизма консенсуса сеть является закрытой и не может получать информацию извне.
Таким образом оракулы должны решать следующую проблему — безопасное получение off-chain данных и сохранение их в сети. Важно отметить, что это не противоречит основам децентрализованной сети. Поскольку информация, хранящаяся в этой сети, неизменяема и общедоступна, узлы Ethereum могут безопасно использовать предоставленные оракулом данные, не нарушая консенсуса.
Можно выделить несколько ключевых предметных областей, где используются оракулы:
Финансы. Всем продуктам сферы финансов требуются надежные потоки данных для выполнения операций в сети. В основном оракулы поставляют стоимость активов относительно друг друга.
Страхование. Оракулы служат для проверки наступления страховых случаев. Примером данных может служить информация о задержке рейса, получение травмы или потере застрахованных активов.
Грузоперевозки. Например, отслеживание GPS данных. В этом случае оракул поставляет в сеть контрольные точки местоположения различных объектов.
Кредитование и стейблкоины. Продуктам этой сферы нужен доступ к информации о стоимости залога для принятия решения о ликвидации. На базе информации из оракулов рассчитывается достаточность залогового обеспечения, вычисляется максимально возможная сумма для займа.
Прогнозы. Оракулы служат для расчетов вне сети. Так как для совершения прогноза необходимо обработать огромное количество информации и делать это on-chain дорого.
Устройство оракула
Для реализации прослойки между off-chain и on-chain логично, что оракул является составным и обычно включает в себя две части: смарт-контракт и сервис за пределами сети.
Полная модель реализации оракула состоит из четырех основных компонентов:
Client SC. Смарт-контракт, которому необходимы off-chain данные
Oracle SC. Смарт-контракт, который будет агрегировать в себе off-chain данные в сети
Data Source. Поставщик off-chain данных
Oracle node. Программное решение, которое является вспомогательным связующим звеном между Data Source и on-chain пространством
Общий процесс работы оракула можно описать следующим алгоритмом:
Исходный смарт-контракт Client SC запрашивает набор off-chain данных у смарт-контракта оракула Oracle SC.
Oracle SC бросает событие за пределы блокчейна. Событие означает, что были запрошены данные.
Промежуточное решение Oracle node постоянно слушает события от Oracle SC. И отлавливает событие о необходимости поставки off-chain данных, которое было отправлено.
Oracle node получает данные из Data source и обновляет данные на смарт-контракте Oracle SC
Обновление данных на Oracle SC автоматически проводит обратный вызов для исходного смарт-контракта Client SC
Таким образом мы можем получить off-chain данные, которые будут являться частью сети. За запись таких данных будет отвечать специальный смарт-контракт оракул (Oracle SC), который и будет хранить данные в сети.
Реализация смарт-контрактов Oracle SC и Client SC может выглядеть следующим образом:
Oracle.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
import {Counters} from "openzeppelin-contracts/utils/Counters.sol";
import {IOracle} from "./interfaces/IOracle.sol";
/**
* @notice Пример контракта Oracle, через который другие контракты могут получать off-chain данные
* @dev Получает on-chain запрос от контракта Client и генерирует запрос к оракл node на получение off-chain данных
* Ожидается, что вызов функции executeRequest() будет доставлять off-chain данные до запросившего контракта
*/
contract Oracle is IOracle, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _requestIds;
/// @notice Список запросов на получение off-chain данных
mapping(uint256 => Request) private _requests;
/// @notice Список адресов от имени которых oracle nodes смогут взаимодействовать с контрактом Oracle
mapping(address => bool) private _oracleNodes;
function getRequestById(uint256 requestId) external view returns (Request memory) {
return _requests[requestId];
}
/**
* @notice Создает запрос на получение данных и бросает событие, которое будет поймано oracle node off-chain
* @param oracleNode Адрес от имени которого oracle node может взаимодействовать с контрактом Oracle
* @param data Данные для запроса
* @param callback Данные для переадресации ответа
*/
function createRequest(address oracleNode, bytes memory data, Callback memory callback)
external
returns (uint256 requestId)
{
bool isTrusted = isOracleNodeTrusted(oracleNode);
if (!isTrusted) {
revert OracleNodeNotTrusted(oracleNode);
}
_requestIds.increment();
requestId = _requestIds.current();
_requests[requestId] = Request({
oracleNode: oracleNode,
callback: callback
});
emit RequestCreated(requestId, data);
}
/**
* @notice Выполнение запроса на получение off-chain данных
* @dev Только адрес установленный для соответствующего запроса сможет вызвать функцию выполнения запроса
* @param requestId Идентификатор запроса на получение off-chain данных
* @param data Off-chain данные
*/
function executeRequest(uint256 requestId, bytes memory data) external {
Request memory request = _requests[requestId];
if (request.oracleNode == address(0)) {
revert RequestNotFound(requestId);
}
if (msg.sender != request.oracleNode) {
revert SenderShouldBeEqualOracleNodeRequest();
}
(bool success,) = request.callback.to
.call(abi.encodeWithSelector(request.callback.functionSelector, data));
if (!success) {
revert ExecuteFailed(requestId);
}
emit RequestExecuted(requestId);
}
function isOracleNodeTrusted(address account) public view returns (bool) {
return _oracleNodes[account];
}
function setOracleNode(address account) external onlyOwner {
_oracleNodes[account] = true;
emit OracleNodeSet(account);
}
function removeOracleNode(address account) external onlyOwner {
delete _oracleNodes[account];
emit OracleNodeRemoved(account);
}
}
Client.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {IOracle} from "./interfaces/IOracle.sol";
import {RequestType} from "./utils/Constants.sol";
/**
* @notice Пример контракта, которому необходимо получать информацию о прайсе off-chain
* @dev Получает off-chain данные через специальный контракт Oracle
*/
contract Client {
/// @notice Экземпляр контракта Oracle
IOracle private _oracle;
/// @notice Приватная переменная для записи off-chain информации о прайсе
uint256 private _price;
event OracleSet(address oracle);
event PriceSet(uint256 price);
error OnlyOracle();
modifier onlyOracle {
if (msg.sender != address(_oracle)) {
revert OnlyOracle();
}
_;
}
constructor(address oracle) {
_oracle = IOracle(oracle);
emit OracleSet(oracle);
}
/**
* @notice Делает запрос за off-chain данными на контракт Oracle
* @param oracleNode Адрес от имени которого oracle node может взаимодействовать с контрактом Oracle
* Oracle node находится в off-chain пространстве
*/
function requestPrice(address oracleNode) external {
bytes memory data = abi.encode(RequestType.GET_PRICE);
_oracle.createRequest(
oracleNode,
data,
IOracle.Callback({
to: address(this),
functionSelector: Client.setPrice.selector
})
);
}
function getPrice() external view returns (uint256) {
return _price;
}
/**
* @notice Функция которая будет вызвана контрактом Oracle для обновления информации о прайсе
* @param data Набор закодированных данных, который содержат информацию о прайсе
*/
function setPrice(bytes memory data) external onlyOracle {
uint256 price = abi.decode(data, (uint256));
_price = price;
emit PriceSet(price);
}
}
Полный код смарт-контрактов с интерфейсами можно найти тут.
Однако, не все так просто! Подумай, какую опасность ты здесь видишь? Что может стать узким местом? Конечно, oracle node. Согласно схеме, в случае непредвиденного отказа oracle node смарт-контракт оракула (Oracle SC) никогда не сможет получить данные, записать их в сеть и передать исходному Client SC. Это значит, что запрос на данные останется без ответа. Решить эту проблему можно добавив несколько oracle nodes, которые смогут подстраховать друг друга. Смотри схему ниже.
Теперь мы можем добавить логику на контракте Oracle SC и обрабатывать ответ от любого из доверенных узлов оракула (oracle node). Как говорится, кто первый пришлет нам данные, тот молодец. Но на сколько спокойнее тебе стало теперь? Насколько мы можем доверять первому ответившему узлу? Что если один из узлов (Oracle node) будет присылать данные из неверного источника, как на схеме ниже.
Это можно решить добавив дополнительную логику для контракта Oracle SC. Сделаем так, чтобы Oracle SC ждал ответы от всех узлов (Oracle node). И после этого, аппроксимировал данные, выбирая среднее или наиболее часто встречающееся значение.
Важно! Такой подход позволяет использовать даже несколько источников данных с целью децентрализации самого Data Source, что на самом деле тоже является важной составляющей.
Однако можно снова возмутиться. Допустим источник данных мы децентрализуем, а как же сами узлы оракула (oracle nodes)? Как сделать так, чтобы им можно было доверять на 100%? Ответ на этот вопрос есть. Мы можем сделать децентрализованную сеть оракулов. Название такой сети DON (decentralized oracle networks). Подробнее про DON тут на примере Chainlink.
Важно! Оракулы сами по себе являются потенциально уязвимым местом. Только совокупность мер безопасности может минимизировать риск взлома оракула. Несколько Data Source, сеть oracle nodes, изоляция oracle nodes друг от друга, шифрование передаваемых данных и многие другие приемы и подходы необходимо использовать, чтобы снизить риск злонамеренных действий в отношении оракула.
Важно! Не стоит забывать про операционные расходы на обратный вызов функций от Oracle SC. Кто-то должен будет платить за газ, когда оракул делает обратный вызов на клиентский смарт-контракт.
Типы оракулов
Выше мы рассмотрели всего лишь один из возможных вариант работы оракула. Однако есть и другие подходы. Ниже попробуем их обсудить.
Все оракулы можно разделить на две группы по уровню безопасности:
Централизованные. Часто такими оракулами владеют в рамках одной компании и одного продукта. С таким решением мы доверяем данным, которые будут поставлены таким оракулом.
Децентрализованные. Используется DON или сеть децентрализованных оракулов.
Также можно разделить оракулов по способу взаимодействия off-chain и on-chain составляющих:
Immediate read oracles. Оракул всегда владеет актуальной информацией. Информация обновляется с заданной периодичностью. Можно моментально прочитать данные с такого оракула.
Publish-subscribe oracles. В основе такого оракула лежит паттерн Observer. По сути это механизм подписки. Можно подписаться на данные из оракула и получать данные на момент их обновления в самом оракуле.
Request-response oracles. Оракул основан на системе запрос-ответ. Именно такой оракул мы рассматривали в примере выше.
По виду работы с данными:
Input oracles. Такой оракул извлекает off-chain данные и записывает их в сеть для использования на смарт-контрактах. Например, такой оракул может поставлять информацию о стоимости активов в сеть.
Output oracles. Позволяет отправлять от имени смарт-контрактов команды автономным системам. Эти команды могут запускать определенные действия. Например, информирование банковского сервиса о совершение платежа.
Cross-chain oracles. Такие оракулы могут считывать и записывать информацию между разными блокчейнами. Например, можно реализовать задачу межсетевого учета объема определенных активов.
Compute-enabled oracles. Такие оракулы служат для выполнения вычислительной работы вне сети из-за технических, юридических или финансовых ограничений. Например, генерация случайного числа. Это одна из самых частых задач, не считаю получения котировок для активов.
Проблема оракулов
Можно ли доверять off-chain данным? Насколько эти данные целостные и всегда доступны? На эти вопросы необходимо ответить прежде чем начать использовать существующий оракул.
При разработке необходимо решить основную проблему, которая заключается в том, что делать, если оракул скомпрометирован. Ведь в таком случае смарт-контракт, на который он опирается, также будет скомпрометирован. Это часто называют главной проблемой Oracle. Нужно любыми доступными способами не допустить подобной ситуации. Хороший опыт собрала компания Chainlink в реализации безопасных и надежных оракулов.
Манипуляции с оракулами различного рода до сих пор входят в топ 10 возможных способов взлома смарт-контрактов. Если хочется побольше ознакомиться с тем, как это происходит, то можно прочитать статью «So you want to use a price oracle».
Chainlink
Как я говорил выше, использование одного оракула — это огромный риск. Со своей стороны, компания chainlink предлагает сеть децентрализованных оракулов, которые образуют фантастическую экосистему для получения данных из-за пределов сети. Сегодня это наиболее широко распространенное решение среди оракулов.
Ну и Market Cap у них самый большой среди оракулов.
В настоящее время существует большое количество price feeds, которые работают в ряде сетей блокчейнов (Ethereum, Polygon, BSC, Avalanche и другие). По мимо price feeds chainlink предоставляет не только децентрализованные потоки данных, но и широкий спектр безопасных сервисов вычислений вне сети. Например, VRF (генерация случайного числа), Keepers (или по новому этот раздел переименован в Automation) и другие различные формы вычислений вне сети.
Оракулы chainlink могут быть разделены на две группы:
Проверенные на безопасность командой chainlink с публичными идентификаторами.
Непроверенные на безопасность. Могут управляться, как известными, так и неизвестными организациями.
Важно! Чтобы стимулировать оракулов вести себя добросовестно в сети разработана специальная система репутации. Система репутации обеспечивает дополнительную безопасность за счет подотчетности действий оракулов. Оракулы с низким рейтингом могут быть удалены из сети.
Basic request model
Базовая модель запроса раскрывает секрет модели взаимодействия контрактов оракула с клиентскими контрактами и off-chain узлами.
Согласно схеме от Chainlink базовая модель очень похожа на общие схемы устройства оракулов. Про общую схему я также говорил выше. Отличительной чертой является участие в модели собственного токена Link.
Расшифровать схему можно следующим образом:
Контракт ChainlinkClient. На контракте инициируется transfer Link токена согласно стандарту ERC-1363 (это стандарт про transferAndCall).
Контракт Oracle. Принимает токены и реализует интерфейс transferAndCall. Согласно интерфейсу вызывает собственную функцию
oracleRequest()
. Эта функция бросает событиеOracleRequest
за пределы блокчейна. Это событие будет отслежено на oracle node.Oracle node. Проводит вычисление или запрос к данным и вызывает функцию
fulfillOracleRequest()
на контракте OracleКонтракт Oracle. Делает обратный вызов
callback()
и поставляет запрашиваемые данные контракту ChainlinkClient.
Подробнее про этот процесс можно прочитать в официальной документации chainlink.
Важно! C версии chainlink 0.7 контракт Oracle заменен на контракт Operator и является рекомендованным. Контракт ChainlinkClient в новой версии поддерживает запросы и к Oracle, и к Operator. Для этого реализованы функции sendChainlinkRequest()
и sendOperatorRequest()
.
Преимущества контракта Operator перед Oracle:
Отменено ограничение размера ответов от oracle node. Раньше ответ был ограничен bytes32.
Деплой. Чтобы развернуть контракт Oracle для каждого узла необходимо было вручную скомпилировать и задеплоить контракт в сеть. Поэтому для контракта Operator добавили контракт OperatorFactory.
Гибкая система финансирования своих адресов. Можно использовать несколько EOA аккаунтов на chainlink nodes. Это позволяет настраивать несколько стратегий отправки обратных транзакций.
Обзор контрактов
ChainlinkClient. Это родительский контракт, который позволяет смарт-контрактам получать данные от оракулов. Он доступен в библиотеке смарт-контрактов Chainlink. Понадобится наследоваться от этого контракта, чтобы безопасно сделать запрос на получение данных к контракту Oracle или Operator.
LINK Token. Это токен, совместимый с ERC-677 для реализации
transferAndCall
механизма. Имеет три основных функции: оплата за работу для chainlink nodes, механизм вознаграждения для оракулов, форма безопасности, когда оракулы оставляют Link токен в качестве залога (гарантия честной работы).Operator Contract. Новая версия контракта Oracle. В основном аналогичен контракту Oracle c небольшими улучшениями.
AggregatorV3Interface. Интерфейс при помощи которого можно взаимодействовать с контрактом, который агрегирует данные. Пример использования контракта Aggregator для получения стоимости токена можно посмотреть тут. Или в официальной документации.
Off-Chain Reporting — OCR
Это шаблон оракула, который повышает децентрализацию и масштабируемость сети chainlink. Строится на основе базовой модели запроса (Basic Request Model). Базовая модель позволяет поставить данные из одного chainlink node в сеть. В свою очередь OCR использует несколько chainlink nodes, позволяя им связываться напрямую через одноранговую сеть (P2P) с последующей агрегацией данных вне сети без каких-либо затрат.
Во время реализации процесса получения данных из chainlink nodes запускается облегченный алгоритм консенсуса, который следит за поставкой данных и проверяет подпись каждого узла. Среди всех chainlink nodes выбирается лидирующий узел, который будет управлять протоколом и собирать данные со всех остальных узлов. В конце LEADER сделает одну результирующую транзакцию с агрегированными данными.
Chainlink node
Управление chainlink node позволит тебе стать частью сети и поможет создавать гибридные смарт-контракты, предоставляя им доступ к реальным off-chain данным.
Хороший гайд по работе с chainlink node. Ты сможешь развернуть chainlink node локально, развернуть контракт Operator в тестовой сети, сделать первый запрос на локально развернутый узел и получить от него ответ.
Chainlink продукты и услуги
Благодаря собственным возможностям сети chainlink предоставляет широкий спектр услуг оракула для смарт-контрактов:
Price feeds. Источник данных финансового рынка. Включает в себя курсы криптовалют, стейблкоинов, товаров и индексов.
Любой APY. Позволят интегрировать смарт-контракты с любым API или источником данных вне сети. Например для получения данных о погоде. Подробнее тут.
Randomness. Verifiable Random Function (VRF) предоставляет безопасную возможность смарт-контрактам интегрировать генерацию случайного числа, которым невозможно манипулировать ни оракулу, ни разработчикам, ни конечным пользовтаелям.
Asset Collateralization PoR или доказательство резерва позволяет смарт-контрактам проверять информацию о текущем обеспечение активов в сети.
Chainlink function. Functions предоставляет смарт-контрактам доступ к вычислительной инфраструктуре с минимальным доверием.
Keepers. Или теперь это automation позволяет автоматизировать внутрисетевые транзакции на основе заранее определенных условий.
Валидаторы 2-го уровня. Могут предоставить услуги проверки для решений масштабирования блокчейнов 2-го уровня. Например Arbitrum Rollups, который включает в себя вычисления вне сети, путем создания пакетов транзакций.
Межсетевая коммуникация. Могут передавать данные из одной среды блокчейн в другую.
А есть кто кроме chainlink?
Witnet. Witnet это децентрализованный оракул для связи смарт-контрактов с реальным миром. Работает на собственной цепочке блоков и полагается на собственный токен WIT.
UMA Oracle. UMA — оптимистичный оракул, который позволяет смарт-контрактам быстро получать любые данные. Имеет свою собственную систему разрешения споров и механизма проверки данных.
Tellor — это прозрачный и не требующий разрешений протокол оракула для вашего смарт-контракта, позволяющий легко получать любые данные, когда это необходимо.
Band Protocol. Это кроссчейн-платформа оракула данных, которая объединяет и связывает реальные данные и API со смарт-контрактами
Provable. Это протокол, который соединяет децентрализованные приложения блокчейна с любым внешним API и использует доказательства TLSNotary, Trusted Execution Environments (TEE) и безопасные криптографические примитивы, чтобы гарантировать подлинность данных.
Pyth network. Это оракул, который реализует неклассическую модель поставки данных в каждую сеть. Модель называется «On-Demand Updates». Обновление по запросу. Это подразумевает возможность обновлять данные в сети любым пользователям.
Paralink. Paralink предоставляет децентрализованную платформу оракула с открытым исходным кодом для смарт-контрактов, работающих на Ethereum и других популярных блокчейнах.
Рейтинг оракулов можно посмотреть на coinmarketcap. При выборе оракула изучи функциональные возможности сервиса.
Примеры использования оракулов в реальной жизни
Compound use chainlink
Средневзвешенные цены от Uniswap. Подробнее про это можно найти в нашей отдельной небольшой статье
Oracle module
Если тебе все еще недостаточно примеров, то можно найти больше 77+ вариантов использования тут.
Links
Oracles
Хорошая статья Julien Thevenard «Decentralized Oracles: a comprehensive overview»
Сборник шаблонов и лучших практик для языка программирования смарт-контрактов Solidity
Implementing a Blockchain Oracle on Ethereum
Smart Contract Security Guidelines #3: The Dangers of Price Oracles. Я бы не сказал, что эта статья про безопасность. Здесь ставятся требования к оракулу и разбираются общие принципы популярных вариантов оракулов.
Completing The God Protocols: A Comprehensive Overview of Chainlink in 2021. Это большая статья, ее можно прочитать всю. Здесь неплохо рассказано про оракулы от chainlink.
Спасибо, что дочитали эту статью до конца! Буду рад любой обратной связи:)