Главная
предыдущий
четвёртый урок
следующий

Как использовать QuePasa в простом проекте на Tezos

В прошлом уроке мы показали как получать и использовать ончейн-данные с публичного индексера TzKT. Это простой, но не совсем безопасный способ: разработчики публичного индексера могут изменить API-запросы, ограничить количество запросов в секунду, на время перестать давать информацию из-за технических неполадок или передавать некорректные данные.

В этом уроке мы покажем, как захостить индексер на своей машине или сервере, чтобы не зависеть от третьих лиц. Будем показывать на примере селективного индексера Que Pasa и базы данных PostgreSQL.

Что мы будем делать

Сделаем небольшой проект с отображением информации о Signum — сервисе для выдачи займов под залог NFT. Для этого проиндексируем смарт-контракт Signum и будем отображать данные из него на странице. Например, последние предложения по займам, одобренные предложения и ссылки на NFT в залоге.

Алгоритм работы следующий:

  1. что будет в результате, например отображение предложений по займам.
  2. Проверим, какие контракты хранят нужные данные. В нашем случае Signum использует только один контракт, так что будет просто.
  3. Изучим структуру хранилища смарт-контракта, в частности big_map. Например, в контракте Signum есть big_map "offers" с предложениями по займам, в которую еще встроен список "object" с информацией об NFT в залоге.
    1
  4. Проиндексируем хранилище контракта.
  5. Проверим структуру полученной базы данных и спланируем, как будем получать нужные данные.
  6. Напишем проект.

Установка и настройка PostgreSQL и Que Pasa

Перейдите на сайт PostgreSQL и скачайте версию для своей ОС. Установщик запросит выбрать папку для установки, а также указать пароль для пользователя-админа Postgres — не забудьте этот пароль.

После установки на компьютере появится несколько приложений, связанных с Postgre. Нам будут нужны pgAdmin — графический интерфейс для работы с базами данных, и SQL Shell — шорткат для работы с PostgreSQL в терминале.

2

Для начала работы с Postgres откройте SQL Shell. Терминал запросит параметры для подключения к базе данных: адрес сервера, название базы данных, порт для подключения, имя пользователя и пароль. Нажмите Enter четыре раза, затем введите пароль, который задавали при установке.

3

Теперь нужно установить Que Pasa. Первый шаг — установка Rust и Cargo, надо просто следовать инструкции. Второй — сборка Que Pasa из исходного кода. Надо выполнить в терминале следующие команды:

git clone https://github.com/tzConnectBerlin/que-pasa.git
cd que-pasa
cargo install --path .

Остается настроить Que Pasa и PostgreSQL:

  1. Создать новую базу данных в Postgres для хранения данных и индексов.
  2. Подключить Que Pasa к созданной базе данных.
  3. Указать http-адрес ноды, у которой Que Pasa будет получать ончейн-данные.
  4. Указать адреса смарт-контрактов для индексации.

Создание новой базы данных

Откройте SQL Shell, подключитесь к дефолтной базе данных (посмотрите туториал), введите пароль. Затем выполните команду

CREATE DATABASE liquidity;

Терминал выдаст ответ CREATE DATABASE. Затем выполните команду \l, чтобы проверить результат. Вверху списка должна отобразиться новая база данных liquidity.

4

В колонке Access Privileges напротив БД liquidity пусто — нужно дать пользователю postgres (или другому, если вы задали другое имя) право с ней работать. Для этого выполните команду:

GRANT ALL PRIVILEGES ON DATABASE "liquidity" to postgres;

5

Подключение базы данных к Que Pasa

Откройте терминал и перейдите в папку с установленым Que Pasa. Это можно сделать командой cd ~/que-pasa в Unix-системах, или же ввести команду cd и перетащить в терминал папку que-pasa, чтобы автоматически прописать путь.

В терминале в этой папке выполните команды.

Сначала мы создадим файл конфигурации .env с помощью команды touch и проверить что в нем с помощью cat:

touch .env
cat .env

Затем создадим переменную для RPC URL, от которой Que Pasa будет получать ончейн-данные.

export NODE_URL=https://mainnet.tezos.marigold.dev/

Создадим еще одну переменную с данными для подключения к базе данных:

export DATABASE_URL="host=localhost dbname=liquidity user=postgres password=1234 port=5432"

Теперь можно запустить индексер командой que-pasa, чтобы проверить, все ли работает. Если терминал выдаст ошибку "zero contracts to index…" — все хорошо.

6

Добавление контрактов для индексирования

В папке с Que Pasa откроем файл settings.yaml и запишем в него внутреннее название контракта для Que Pasa и его адрес. В примере мы индексируем только один контракт, но можно указать несколько и индексировать их одновременно.

в поле "name" запишите "Liquidity", в поле "address" — адрес "KT1TxqZ8QtKvLu3V3JH7Gx58n7Co8pgtpQU5".

7

Запуск индексирования

В терминале, из которого вы работали с Que Pasa, выполните команду:

que-pasa --contract-settings settings.yaml

Que Pasa начнет записывать в базу данных операции по указанному контракту начиная с последнего созданного блока вплоть до genesis-блока (если не указать уровень, на котором нужно остановить индексирование). Индексер автоматически конвертирует все содержание хранилища контракта в таблицы: простые типы, списки, мапы, big_map, и другие. Однако, чтобы разобраться, где хранятся какие данные, нужно хорошо изучить получившиюся базу данных.

Кроме того, Que Pasa через Postgres автоматически создает базовые b-tree индексы для каждой таблицы, чтобы пользователь быстро получал данные по запросам. Если нужно, в Postgres можно создать дополнительные индексы для колонок в psql shell или PgAdmin.

Полная индексация операций и изменения состояния хранилища контракта может занять несколько часов или даже дней. Сейчас мы будем использовать только данные из последних блоков, поэтому индексацию можно прервать нажатием Ctrl+C. Проиндексированные данные сохранятся в базе данных, но Que Pasa не будет индексировать новые блоки. Если вы планируете использовать Que Pasa в продакшене и получать данные с новых блоков — не останавливайте индексирование.

8

Просмотр содержимого базы данных

Посмотрим что мы будем делать с полученными данными. Сначала нужно узнать что вообще у нас есть, а для этого — изучить базу данных, таблицы и их содержимое.

В Postgres для просмотра содержимого таблиц в базе данных используется команда \d и ее производные. Попробуем это сделать. Сначала подключимся к базе данных liquidity командой \c liquidity, затем выполним \d.

9

Получим ошибку "Did not find any relations". Эта ошибка возникает из-за неправильно настроенного пути поиска таблиц и схем. Настроим его с помощью команды:

ALTER ROLE postgres IN DATABASE liquidity SET search_path TO "Liquidity";

Так мы заставим Postgres искать данные в базе данных liquidity по схеме "Liquidity". Звучит сложно, но в итоге \d будет отображать все таблицы и связи между ними.

Que Pasa создает отдельную таблицу для каждой точки входа, элемента storage и сложных структур данных внутри storage. В таблицах, которые начинаются с "entry." хранятся вызовы соответствующих точек входа и их параметры, а в "storage." — содержание хранилища.

10

Чтобы просмотреть содержание таблиц, можно воспользоваться командой select * from "table_name". Для примера получим транзакции по внесению ликвидности в Sirius DEX:

select * from "entry.addLiquidity";

11

Названия колонок "owner" и "minLqMinted" сразу объясняют что в них хранится, но есть две колонки, содержание которых нужно объяснить:

  • tx_context_id — внутренний id операций, включенных в конкретный вызов смарт-контракта. Например, если пользователь батчит две транзакции в одну, то у обеих будет одинаковый tx_context_id;
  • id — внутренний id конкретной операции. Каждая строка любой таблицы базы данных будет иметь уникальный id.

Полная индексация контракта и _live

Полная индексация всех операций со смарт-контрактом может занять несколько дней. Так что для следующих примеров мы будем использовать контракт NFT-лендингового сервиса Signum. Этот dApp позволяет пользователям брать в займы tez используя их NFT в качестве залога. Мы проиндексируем его хранилище и вызовы точек входа, а затем используем SQL для фильтрации содержимого. Например, запросим самые крупные одобренные заявки на выдачу займа или заявки с наибольшим APY для займодателей, и даже сгенерируем ссылки на заложенные NFT.

Есть еще одна причина, почему мы используем Signum в примере. Команда задеплоила его контракт за несколько дней до написания этого урока, и Que Pasa полностью проиндексирует его за несколько минут.

Чтобы переключиться на Signum, нужно заменить название базы данных и адреса контракта KT1CXwDvsxboyXuX77ohut9KmoVfwuD4A8FK в файле settings.yaml.

12

Затем в терминале с Que Pasa запустим индексацию контракта командой:

que-pasa --contract-settings settings.yaml

Когда индексер закончит считывать информацию из старых блоков, он синхронизируется с сетью — начнет загружать и обрабатывать новые блоки.

13

Теперь вернемся к базе данных и просмотрим таблицы. Сначала сменим поисковый путь командой ALTER ROLE postgres IN DATABASE liquidity SET search_path TO "Signum";. Затем откроем список таблиц командой \d.

14

Мы получили те самые таблицы для каждой точки входа, хранилища и встроенных big_map. Однако у таблиц storage* есть дубликаты с разными окончаниями и содержимым:

  • просто storage — таблица со всеми данными с предыдущих блоков;
  • storage_live — таблица только с данными только с последнего блока;
  • storage_ordered — таблица отсортированная в порядке, в котором сеть Tezos исполняла операции;
  • storage_id_seq — служебная таблица с id первой строки.

Немного примеров: в "storage.offers" хранятся данные обо всех предложениях о займах, которые были опубликованы на Signum, а в "storage.offers_live" — данные об актуальных предложениях в контракте Signum.

В нашем примере мы будем использовать две таблицы: "storage_live" и "storage.offers.collateral". Первая содержит данные обо всех актуальных заявках, вроде адресов заемщиков и запрашиваемых сумм, а вторая — данные о заложенных NFT. Обе таблицы связаны внутренними id.

В общем, чтобы посмотреть текущее состояние хранилища или big_map внутри него, нужно пользоваться таблицами _live. Чтобы получить данные за предыдущие блоки — обычными таблицами. Например, команда select * from storage_live; вернет состояние хранилища контракта, уровень текущего блока и время включения блока в блокчейн (level_timestamp).

15

Подключение Postgres к сайту для запроса данных

Для запроса данных мы будем использовать PHP-сервер на локальной машине, чтобы не подключать API к базе данных.

Сначала нужно установить PHP по инструкции. Затем создать папку, в которой будем хранить скрипт, для отображения данных из Singum, например signum_test. После этого открыть терминал, перейти в папку и выполнить команду:

php -S localhost:8000

Команда запустит локальных PHP-сервер. На него уже можно перейти по ссылке http://localhost:8000/

16

Строки с таймштампами отображают запросы и ответы к серверу.

Теперь можно начать работу над страницей. Создадим в папке файл signum.php и начнем писать код. Сначала — основа: head, body, название страницы и текст для теста.

17

Сохраним скрипт в папке signum_test и перейдем на него в браузере по ссылке http://localhost:8000/signum.php.

18

Подключимся к базе данных. Для этого зададим переменные $dbhost, $dbname, $dbuser и $dbpass с соответствующими значениями: адресом localhost, названием базы данных, именем пользователя postgres и паролем.

Создадим переменную $dbconn, через которую будем управлять подключением. Сначала выполним функцию pg_coonnect() для подключения к PostgreSQL с данными пользователя и добавим функцию or die() для отлова ошибок. В конце добавим еще один фрагмент php для отключения от PostgreSQL по завершении загрузки страницы.

19

Перезагрузим страницу signum.php. Если нет ошибок — все хорошо.

Прежде чем запрашивать данные из базы данных, изучим хранилище контракта Signum и выберем, что именно будем запрашивать. В таблице storage.offers хранятся данные о заявках заемщиков: сумма займа (amount), процент по займу (premium), срок возвращения займа (deadline), адреса заемщика и кредитора. Для примера запросим суммы займов нескольких заявок.

20

Для этого добавим в скрипт SQL-запрос:

SELECT "amount" FROM "storage.offers_live" WHERE "amount" IS NOT NULL LIMIT 10

Команда означает следующее:

  • SELECT "amount" — получить записи из колонки amount;
  • FROM "storage.offers_live" — указатель на таблицу storage.offers_live;
  • WHERE "amount" IS NOT NULL — условие, что в выбранной строке должно присутствовать значение amount;
  • LIMIT 10 — ограничить результат только 10 значениями.

Присвоим этот запрос переменной $sql. Затем выполним функцию для этого запроса — $result = pg_query(). В pg_query() передадим два параметра: $dbconn с конфигурацией для подключения к базе данных и $sql с самим запросом.

В конце посмотрим результат. Так как pg_query() вернет данные в виде ряда, мы с помощью петли while и функции pg_fetch_array() перенесем все элементы в массив и отобразим их по очереди. Сначала присвоим переменной $row результат выполнения функции pg_fetch_array() на полученном результате. Затем в петле будем выводить каждый элемент массива $row["amount"] с помощью команды echo и разделять элементы запятой с пробелом (", ").

21

Проверим, что получилось. Для этого перезагрузим страницу.

22

Усложним запрос. Мы можем получить данные о займах, которые уже одобрил заемщик. Для этого добавим фильтр WHERE "amount" IS NOT NULL AND "lender" IS NOT NULL.

Также посчитаем прибыль кредиторов в годовых процентах (APR). Для этого нам нужно получить сумму займа, процент за пользование средствами (premium) и срок займа (deadline).

В итоге сделаем такой запрос:

23

Результат:

24

Оформим результат в более удобную для чтения таблицу. Для этого воспользуемся php-элементом

. Разделим значения amount и premium на 1000000, чтобы показать нормальное количество tez, так как блокчейн хранит балансы в миллионных долях tez — mutez.

25

В результате будет табличка:

26

Теперь посчитаем APR займов. Для этого добавим в таблицу колонку «Lender's APR», а в петлю while — формулу расчета APR. Назначим переменные для каждой итерации: $p — премия, $a — сумма займа, $d — срок возвращения займа, $dd — соотношение срока займа к количеству дней в году, $apr — значение APR по формуле.

27

Перезагрузим страницу, чтобы посмотреть результат.

28

Теперь можно экспериментировать. Например, изменить фильтр в SQL-запросе, чтобы получить данные о самых крупных активных заявках:

SELECT "amount", "premium", "deadline" FROM "storage.offers_live" WHERE "amount" IS NOT NULL AND "lender" IS NULL ORDER BY "amount" DESC LIMIT 10

29

Теперь используем реляционную базу данных как нужно: получим связанные данные с двух таблиц по внутренним id.

В базе данных контракта есть таблица storage.offers.collateral, в которой хранятся адреса контрактов и Token ID заложенных NFT. Мы можем использовать внутренний id (значение tx_context_id) из таблицы storage.offers, чтобы найти соответствующие им строки в таблице storage.offers.collateral. А дальше, зная адрес и Token ID, мы можем сгенерировать ссылку на токен в обозревателе TzKT или Objkt.com.

Итак, в первом SQL-запросе нам нужно получить tx_context_id, чтобы затем использовать эти значения в запросе к таблице storage.offers.collateral.

Добавим в запрос "tx_context_id".

30

В петлю while добавим переменную $id, которой присвоим значение tx_context_id. Затем напишем SQL-запрос $sql2, который будет запрашивать адрес контракта и id токена из таблицы storage.offers.collateral с теми же значениями tx_context_id, что мы получили из первого запроса. В конце выполним запрос функцией pg_querry() и запишем его в переменную $row2.

Использовать несколько SQL-запросов — не лучшая практика, так как это можно сделать с помощью команды JOIN, но мы решили сделать так для наглядности и простоты примера.

31

Наконец добавим в таблицу колонку "Link to Token", а ниже в функцию — строку со ссылкой на TzKT. Структура следующая:

tzkt.io/ + адрес контракта + /tokens/ + token id + /transfers

32

Сохраним страницу и проверим результат.

33

По ссылке мы перейдем на историю операций токенов на TzKT, среди которых будет и операция с контрактом Signum.

34

Код скрипта на Gist.

Домашнее задание

  1. Измените в таблице ссылку на TzKT на ссылку на Objkt.com. Для этого изучите структуру ссылки на NFT на Objkt.com и отредактируйте ссылку в таблице.
  2. Отсортируйте первый SQL-запрос по сумме займа, от большего к меньшему.

Решение

Ссылка на NFT на Objkt.com простая: она состоит из objkt.com/asssets/, адреса контракта и id токена.

35

Заменим ссылку в таблице на такую.

36

Затем в SQL-запросе добавьте фильтр ORDER BY "amount" DESC.

37

Поделиться в соцсетях: