Четвертая часть SensorDApp tutorial продолжает углубляться в разработку смарт-контрактов.

Часть 4

Часть 1| Часть 2| Часть 3| Часть 5

В нашей предыдущей статье мы закончили определение заголовка контракта. В этом случае мы собираемся реализовать логику действий.

Начнем с создания нового файла с именем sensordapp.cpp, в котором мы собираемся реализовать логику.

После того, как мы создали файл, первым делом нужно импортировать заголовочный файл и библиотеку eosio и поместить тело действий, ранее определенных в заголовочный файл. Этот шаг выполняется в следующем фрагменте:

1. #include "sensordapp.hpp"
2. #include <eosiolib/eosio.hpp>
3.
4. using namespace eosio;
5.
6. ACTION sensordapp::grantuser( const name& user) {
7.
8.
9. }
10.
11. ACTION sensordapp::revokeuser( const name& user) {
12.
13.
14. }
15.
16. ACTION sensordapp::newsensor( const name& owner,
    const uint64_t& id, const std::string& location,
    const std::vector<std::string>& labels) {
17.
18.
19. }
20.
21. ACTION sensordapp::deletesensor( const name& owner,
    const uint64_t id) {
22.
23.
24. }
25.
26. ACTION sensordapp::multiupload( const name& owner,
    const uint64_t& id, const uint64_t& timestamp,
    const std::vector<double>& values) {
27.
28.
29. }

Теперь у нас есть тело основных действий, следующим шагом будет определение ядра действия grantuser:

1. ACTION sensordapp::grantuser( const name& user) {
2.   require_auth(_self);
3.   whitelist existing_whitelist(_code, _code.value);
4.   auto itr = existing_whitelist.find( user.value );
5.   eosio_assert(itr == existing_whitelist.end(),
     "User already exists");
6.
7.   existing_whitelist.emplace(_self, [&]( auto& w ) {
8.      w.user = user;
9.   });
10. }

В заголовке действия (строка 1) мы передаем имя учетной записи в качестве параметра. Эта учетная запись принадлежит пользователю, которому мы хотим предоставить разрешение на создание новых датчиков.

В строке 2 мы объявляем, что это действие разрешено только для владельца контракта, передав параметр _self, который относится к владельцу контракта, при вызове функции require_auth (предоставляется библиотекой eosio). Если это действие вызывается учетной записью, не являющейся владельцем, оно автоматически завершается ошибкой разрешения.

Как только мы установим, кто может вызывать эту функцию, следующим шагом будет объявление структуры белого списка (ранее определенной в заголовке контракта). В строке 3 мы создаем экземпляр белого списка под именем existing_whitelist, заполняя его значениями, хранящимися под учетной записью владельца контракта.

Далее мы должны проверить, находится ли пользователь в белом списке. Это выполняется в строках 4 и 5. Сначала мы повторяем структуру, проверяя, соответствует ли учетная запись пользователя, переданная в качестве параметра, какой-либо учетной записи, уже сохраненной в белом списке (строка 4). Затем мы вызываем функцию eosio_assert, предоставленную библиотекой eosio, и проверяем, находится ли итератор в конце белого списка, не найдя совпадений. Если какое-либо совпадение было найдено, действие завершается ошибкой, возвращая информационное сообщение «Пользователь уже существует» (строка 5).

Если пользователь передан в качестве параметра, который соответствует всем этим требованиям, следующим шагом будет добавление имени учетной записи в структуру белого списка. Это делается в строках с 7 по 9 с использованием метода мультииндекса emplace (строка 8).

После того, как мы уже определили метод grantuser, пришло время реализовать метод обратной логики revokeuser. Эта функция реализована в следующем фрагменте кода:

1. void sensordapp::revokeuser( const name& user) {
2.   require_auth(_self);
3.   whitelist existing_whitelist(_code, _code.value);
4.   auto itr = existing_whitelist.find( user.value );
5.   eosio_assert(itr != existing_whitelist.end(),
     "User does not exist");
6.
7.   existing_whitelist.erase(itr);
8. }

Как мы видим, параметр тот же, что и в grantuser, а также строки со 2 по 5, за исключением того, что условие утверждения изменяется путем поиска, если пользователь не существует.

Как только мы определяем, что пользователь действительно существует, мы удаляем его из структуры белого списка, вызывая многоиндексный метод erase, параметром которого является только что найденный пользователь.

Как видите, методы grantuser и revokeuser очень похожи. Один добавляет пользователей в белый список, а другой удаляет их, но по сути требования (строки со 2 по 5 в обоих сниппетах) практически одинаковы.

На данный момент мы уже определили методы, связанные с белым списком. Поэтому следующим шагом является определение методов, связанных с датчиками. Первая необходимая функция — newsensor. Используя эту функцию, мы можем добавить новый датчик в структуру датчиков. Код показан в следующем фрагменте:

1. void sensordapp::newsensor( const name& owner,
   const uint64_t& id, const std::string& location,
   const std::vector<std::string>& labels) {
2.    require_auth(owner);
3.    eosio_assert(labels.size() < 10, "Too many labels");
4.
5.    whitelist existing_whitelist(_code, _code.value);
6.    auto itr1 = existing_whitelist.find( owner.value );
7.    eosio_assert(itr1 != existing_whitelist.end(),
      "User not allowed");
8.
9.    sensors existing_sensors(_code, owner.value);
10.   auto itr2 = existing_sensors.find( id );
11.   eosio_assert(itr2 == existing_sensors.end(),
      "Sensor already exists");
12.
13.   existing_sensors.emplace(owner, [&]( auto& s ) {
14.      s.owner = owner;
15.      s.id = id;
16.      s.location = location;
17.      s.labels = labels;
18.   });
19. }

Как показано в объявлении действия, мы определяем, какие параметры будут переданы. Этими параметрами являются учетная запись владельца датчика, идентификатор датчика, местоположение датчика и вектор меток датчика.

При реализации, как и в случае с действиями белого списка, первым шагом является запрос авторизации с использованием функции require_auth, которая проверяет, совпадает ли учетная запись, вызывающая функцию, с той, которая передается в теле функции.

Затем мы проверяем, меньше ли длина вектора меток 10, чтобы предотвратить чрезмерные затраты оперативной памяти на пользователя (строка 3). Делая это, мы эффективно ограничиваем до 10 каналов данных на датчик.

Как только мы выполним первые требования в строках с 5 по 7, мы делаем то же самое, что и с revokeuser в строках с 3 по 5.

После проверки того, разрешен ли пользователь (через белый список), мы приступаем к импорту структуры датчиков (строка 9), очень похожей на строку 5, за исключением того, что хранилище этой структуры находится в ОЗУ учетной записи владельца датчика, а не в учетной записи владельца контракта, поэтому все затраты на оперативную память по понятным причинам перенаправляются на владельца датчика. Затем мы проверяем, существует ли уже датчик с помощью eosio_assert (строка 11), как мы это делали с предыдущими операторами eosio_assert.

Наконец, мы добавляем датчик в таблицу, используя метод emplace, как мы делали с белым списком.

Как только мы реализуем действие newsensor, необходимо реализовать обратное действие, как в структуре белого списка. Это действие, очевидно, deletesensor. Код очень похож на действие revokeuser, но с изменением имен структур. Вы можете увидеть это на этом фрагменте:

1. void sensordapp::deletesensor( const name& owner,
   const uint64_t id) {
2.    require_auth(owner);
3.
4.    sensors existing_sensors(_code, owner.value);
5.    auto itr = existing_sensors.find( id );
6.    eosio_assert(itr != existing_sensors.end(),
      "Sensor does not exist");
7.
8.    existing_sensors.erase(itr);
9. }

Последнее, что нам нужно реализовать в нашем контракте, — это действие, позволяющее датчику загружать данные в блокчейн. Это действие multiupload, и оно показано в следующем фрагменте:

1. void sensordapp::multiupload( const name& owner,
   const uint64_t& id, const uint64_t& timestamp,
   const std::vector<double>& values) {
2.    require_auth(owner);
3.
4.    eosio_assert(values.size() < 10, "Too many sensor values");
5.
6.    whitelist existing_whitelist(_code, _code.value);
7.    auto itr1 = existing_whitelist.find( owner.value );
8.    eosio_assert(itr1 != existing_whitelist.end(),
      "User not allowed");
9.
10.    sensors existing_sensors(_code, owner.value);
11.    auto itr2 = existing_sensors.find( id );
12.    eosio_assert(itr2 != existing_sensors.end(),
       "Sensor does not exist");
13.
14.    eosio_assert(itr2->labels.size() == values.size(),
       "Values/Labels array size mismatch");
15. }

Как обычно, первая строка действия — это вызов функции require_auth, чтобы проверить, является ли вызывающий объект тем же, что и учетная запись, переданная в качестве параметра владельца.

Следующие строки посвящены проверке правильности длины вектора значений (помните, что вектор меток имеет целых 9 меток, поэтому вектор значений не может быть больше 9), владелец датчиков находится в белом списке, датчик существует в структуре датчиков , а длина вектора значений равна длине вектора меток.

Теперь некоторые из вас, возможно, поняли, что нет отдельной строки, выделенной для хранения фактических значений. Только представьте, что было бы, если бы вам пришлось хранить все эти значения, поступающие от датчиков. Это будет означать недоступную стоимость оперативной памяти. Следовательно, мы должны принять какой-то другой метод, который позволит нам хранить значения, не неся при этом непропорциональных затрат на хранение.

Блокчейн Telos ведет запись каждого вызова действия, а также в нем хранятся параметры, с которыми мы вызывали действие.

Вы понимаете, почему нет необходимости хранить каждое значение в оперативной памяти? Например, если вы хотите запросить последние 1000 значений, вам нужно получить только запись истории последних 1000 вызовов.

Конечно, это базовый подход, и для повышения производительности можно использовать более продвинутые/оптимальные методы. Мы рассмотрим их в следующих статьях.

Чтобы закончить наш контракт, мы должны добавить строку, которая сообщает компилятору, какие действия мы публикуем:

EOSIO_DISPATCH( sensordapp, (grantuser)(revokeuser)(newsensor)(deletesensor)(multiupload))

Таким образом, вы достигли своего первого ориентира, определив код смарт-контракта. Все, что вам нужно сделать сейчас, это сделать его доступным для использования в сети блокчейна Telos. Вы можете узнать, как это сделать, в следующей главе.



А если вы хотите напрямую связаться с сообществом создателей и разработчиков DApps для Telos, присоединяйтесь к телеграммной группе Telos Dapp Development.



Это руководство было создано членами команды The Teloscope.

Вы можете поддержать нас, включив theteloscope в список BP, которые заслуживают вашего голоса.