Konstantin Narkhov
Pheix 0.8.96
@condemnedcell
konstantinnarkhov.pro

Частные PoA сети на базе Parity Ethereum — часть 3: смарт-контракт CRUD приложения

06 июня, 2019


CRUD - приложение, предоставляющее базовые функции взаимодействия с базой данных (создание/create, чтение/read, обновление/update, удаление/delete) и обеспечивающее доступ к хранилищу данных или СУБД. В терминах blockchain технологии CRUD обеспечивает доступ к данным, хранящимся в цепочке блоков.

В blockchain Ethereum CRUD приложением нижнего уровня является смарт-контракт, а CRUD приложением высокого - geth консоль или, например, веб-приложение на node.js или php.

Коснувшись вскользь использования веб-приложений, следует ненадолго на этом остановиться. В web важность децентрализованных приложений (и как следствие хранилищ данных) особенно актуальна. При этом blockchain предоставляет дополнительные средства, качественно увеличивающие функциональную ценность веб-приложения: полная история изменений хранилища данных, история доступа к данным на уровне узлов, ограничение доступа к методам смарт-контракта на уровне узлов - очень полезное свойство для ограничения доступа к rw-методам контракта, т.е. некоторый доверенный узел (т.н. owner смарт-контракта) может модифицировать хранилище данных в полном объеме, а остальным узлам предоставлен только доступ на чтение. Это схема, при которой работоспособность административной панели некоторого сайта переносится на защищенный узел.

Архитектура базы данных представлена на рисунке. Особенностью смарт-контракта является использование специального типа данных mapping, который обеспечивает доступ к данным по ключу. Ключи хранятся в динамических массивах: table_names[] и indexes[].

Для управления хранилищем данных (базой данных) смарт-контракт предоставляет следующие программисту базовые для CRUD приложения набор методов:

  • new_table(string tabname) — создание пустой таблицы с заданным именем;
  • drop_table(string tabname) — удаление таблицы с заданным именем;
  • set(string memory tabname, uint rowid, string memory rowdata) — модификация данных по идентификатору записи в таблице с заданным именем;
  • insert(string memory tabname, string memory rowdata, uint id) — вставка данных в таблицу, если таблицы с заданным именем не существует она создается, если в качестве идентификатора записи передан 0, то новой записи будет присвоен (max(ids[])+1) идентификатор для заданной таблицы;
  • select(string memory tabname, uint rowid) — чтение данных по идентификатору записи для таблицы с заданным именем;
  • remove(string memory tabname, uint rowid) — удаление записи по идентификатору для таблицы с заданным именем.

Помимо указанные методов в смарт-контракте реализованы утилитарные методы, обеспечивающие работу CRUD методов:

  • table_exists(string tabname) — проверка существования таблицы с заданным именем;
  • id_exists(string tabname, uint rowid) — проверка существования записи с заданным идентификатором в таблице с заданным именем;
  • count_tables() — подсчет количества таблиц в хранилище;
  • count_rows(string tabname) — подсчет количества записей в таблице с заданным именем;
  • get_tabname_byindex(uint index) — получение имени таблицы по индексу в массиве table_names[];
  • get_id_byindex(string tabname, uint index) — получение идентификатора записи по индексу в массиве indexes[];
  • table_index(string tabname) — получение индекса таблицы в массиве table_names[] по имени;
  • get_max_id(string tabname) — получение максимального идентификатора для таблицы с заданным именем;
  • init() — инициализация тестовой базы данных (три таблицы с несколькими записями в каждой).

Метод select_all() умышленно не реализован в смарт-контракте, так как возвращаемым значение этого метода является динамический массив (действительно, изначально нам неизвестно сколько записей вернет метод). Загвоздка в том, что solidity не позволяет возвращать динамические массивы по-умолчанию. Для этого нужно либо использовать pragma experimental ABIEncoderV2; (но стоит ли применять экспериментальный функционал в production?!), либо выполнять сериализацию строк. Поэтому select_all() выносится на уровень выше — CRUD-приложение верхнего уровня (например, node.js) реализует этот метод, используя count_tables(), get_tabname_byindex(), count_rows() и get_id_byindex().

Отметим, что для компиляции смарт-контракта требует компилятор solidity версии не ниже 0.5.1.

Репозиторий с рассматриваемым смарт-контрактом является публичным и доступен всем желающим. Кроме исходного текста собственно смарт-контракта в него входит набор unit-тестов и скрипт, автоматизирующий процесс компиляции смарт-контракта, а также скрипт генерирующий JS-сценарий для быстрого развертывания смарт-контракта в частной сети.

Для развертывания смарт-контракта в локальной частной сети (предполагаем, что сеть успешно настроена и запущена в docker контейнере) нужно:

  • Клонировать репозиторий смарта-контракта в файловую систему контейнера, например в каталог /sc;
  • Сгенерировать JS-сценарий для быстрого развертывания смарт-контракта с помощью команды cd /sc/t; ./compile-contract.sh pheix_database;
  • Открыть geth консоль любого из узлов сети;
  • Выполнить развертывание смарт-контракта командой loadScript("/sс/t/pheix_database/pheix_database.js");
  • После выполнения команды (код возврата true) в geth консоли становится доступен объект storage;
  • Для инициализации тестовой БД следует выполнить команду storage.init.sendTransaction({from:eth.accounts[0],gas:4700000}).

Blog entries

Check out latest blog entries: most interesting stories about past events & activities.

Quick feedback

Letters and spaces only