Хабрахабр

Как мы пилили IoT-платежи на хакатоне в Гонконге

А последние 26 часов мы провели почти без сна, разрабатывая прототип проекта под рабочим названием SensorPay на первом этапе хакатона EOS Global с общим призовым фондом полтора миллиона долларов. 10 июня шёл уже третий день нашей акклиматизации в Гонконге. Близился момент демонстрации проекта перед судьями.

А мы пока начнём планомерно рассказывать о технологиях EOS и о том, как мы пришли к идее привязать к EOS платежи для IoT. Если вам не терпится узнать, чем закончилась эта история, загляните сразу в последнюю часть. Сразу после этого будет подробное описание технической начинки проекта.

Если вдруг вы не знаете, что такое блокчейн или Ethereum, Гугл в помощь. EOS — блокчейн нового поколения, некоторые даже считают его убийцей Ethereum. А мы, так получилось, начали раскапывать EOS ещё около года назад, в том числе изучив предшествующие продукты его авторов BitShares и Steem.

Недостатки: опасения в централизации, потенциально более уязвимый консенсус DPoS, сырой код и более крутая кривая обучения для разработчиков. Преимущества EOS по сравнению с Ethereum: пропускная способность транзакций на три порядка выше; развитая permission-система для смарт-контрактов; возможность восстановить утерянный доступ и исправить ошибки в блокчейне; управление сетью onchain.

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

А где оно требуется? Итак, EOS — единственное известное нам рабочее решение для публичного блокчейна, где можно делать МНОГО транзакций. Ведь если каждый тостер станет микроплатежами сам оплачивать каждый кусок хлеба холодильнику (а это круто по умолчанию, как вы понимаете), транзакций будет очень много. Конечно, в IoT! Не говоря уж о всяких других применениях в медицине, производстве и быту.

Мы сравнивали идеи на основе известных критериев судейства: применения возможностей EOS, креативности, общественного воздействия (impact), масштабируемости. За несколько недель до хакатона периодически всплывали альтернативные идеи, проводились небольшие мозговые штурмы. В итоге остановились на IoT + EOS — решении, которое бы снимало данные с датчиков и отправляло много платёжных транзакций в EOS.

Но это всё никак не умещается в статью, поэтому вернёмся к нашему основному проекту. Кстати, нам очень хотелось рассказать тут ещё и о том, как мы поднимали свой Block Producer для EOS; как планировали запилить для него конструктор ERC721-like токенов и поддержку константных функций; как запилили-таки Merkle Root ACL протокол.

1.1. IoT

В роли RFID-считывателя был выбран RC522, работающий по шине SPI: он популярен, и его просто использовать. Подготовка IoT-части проекта — это выбор подходящего железа.

5». При поиске счётчика мы ориентировались на наличие импульсного выхода, так как он позволяет очень просто считывать данные: один импульс — это X кВт⋅ч (где X зависит от модели), в итоге остановились на счётчике «Меркурий 201.

Соответственно, нам требовалось устройство с сетевым модулем, которое могло бы подписать транзакцию с помощью ECDSA (в данном случае на эллиптической кривой secp256k1, так как в EOS для подписи используется именно она). Сложнее всего было определиться с контроллером, который должен собрать данные с датчиков, сформировать транзакцию, подписать её своим приватным ключом и отправить в сеть.

В то же время он очень компактный. Изначально выбор пал на микроконтроллер ESP8266, в нём есть модуль Wi-Fi и все нужные интерфейсы для подключения наших датчиков. Написать свою реализацию возможно, но это не задача для хакатона. Но ни в одной из прошивок нет нативной реализации эллиптических примитивов. В итоге для прототипа был выбран Raspberry Pi 3 B, a для генерации и подписи транзакций — библиотека eosjs.

1.2. Инфраструктура

Установка зависимостей, сборка и тесты прошли на удивление гладко для столь молодого проекта (при использовании документации). За пару дней до хакатона подготовили локально и на сервере eos-hackathon.smartz.io сборку EOS (исходники). На прочую подготовку инфраструктуры не хватило времени, и пришлось заниматься этим уже в ходе хакатона.

1.3. Архитектура

Мы собирались реализовать следующие основные кейсы: расчёты за электроэнергию и оплата покупок, помеченных RFID-метками. Накануне хакатона мы обсудили архитектуру и уточнили детали продукта. Также планировали сделать продукт легко расширяемым и использовать его в других областях.

У каждого потребителя есть свой баланс, который можно пополнять, с него на основании сигналов датчиков списываются средства. Идея архитектуры в том, что поставщик услуг (Producer) создаёт один контракт — центральную точку взаимодействия поставщика и потребителей. Все данные — пользователи, датчики, статистика — хранятся в контракте поставщика.

С потребителем могут быть связаны несколько датчиков, для каждого из них указывается контракт и настройки биллинга (billing_meta). С потребителем связаны пользовательские настройки, или флаги (например, льготная категория пользователя) — user_meta. Также заложена возможность разной логики биллинга, т. е. Так можно получить неизменяемый контракт биллинга без состояния, используемый для большого количества потребителей; необходимые данные будут появляться в ходе вызова метода bill(payload, user_meta, billing_meta). У каждого датчика есть «указатель» на свой контракт биллинга. разных контрактов: например, один считает электричество, другой — товары.

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

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

  • Авансовые платежи или постоплата? Пополни счёт и пользуйся (как сотовая связь) — или пользуйся, а потом расплатись (как AWS)? Правильного и неправильного ответа здесь нет: разные бизнесы предпочитают разные модели. Мы решили для простоты взять авансовые платежи.
  • Пользователь должен держать отдельный счёт для каждого поставщика или все списания идут с одного счёта? Опять же — правильного и неправильного решения нет; кроме того, ответ тесно связан с ответом на предыдущий вопрос. Авансовые платежи хорошо дружат с отдельными счетами потребителей — их и взяли.
  • Взимать плату в EOS, токене поставщика услуг или stable coin (привязанной к фиатной валюте)? Варианты, кроме stable coin, безусловно, неудобны для потребителя из-за волатильности, а stable coin в рамках EOS пока что не существует. На тот момент даже основной сети EOS ещё не было! Для простоты взяли условный токен.

Прежде всего специфицировали API и каркас контракта поставщика, чтобы параллельно начать разработку фронтенда, кода устройств, биллингов и основного контракта (исходники).

2.1. IoT

Для работы с GPIO (пины общего назначения) использовалась JS-библиотека onoff. Первым реализовали код для считывания импульсов со счётчика. Аналогично разработали схему и код для считывания RFID-меток, с единственным отличием: считывание происходило по шине SPI с использованием библиотеки MFRC522-python. Позже к схеме для наглядности добавили два светодиода: первый мигал при получении сигнала из счётчика, а второй — когда от ноды EOS приходил ответ об успешной транзакции. Как оказалось, настройка SPI для Raspberry Pi 3 отличается от настройки в более ранних моделях платы; нам помогла разобраться эта инструкция.

Устройства запитали с power bank, удачно подаренного всем участникам хакатона, а интернет пришлось расшаривать самим с iPhone 5, так как Wi-Fi хакатона работал исключительно на частоте 5 GHz, это не подошло для Raspberry Pi.

2.2. Инфраструктура и утилиты

На сервере продолжили работать с подготовленной сборкой, а локально, чтобы избежать установки EOS системно, использовали eos-dev. Организаторы советовали взять docker-образ eos-dev, однако нас смущало отсутствие описания и документации образа.

Идеальный вариант: собирать и запускать локально исполняемый файл. Сразу же остро потребовалась возможность быстрой сборки и тестов. Нужные параметры компиляции можно было подсмотреть в eosiocpp.in, однако мы решили не играть в эту игру. Однако нельзя было игнорировать тот факт, что после сборки на выходе требовалось получить WebAssembly и в окружении EOS — с соответствующими boost, библиотеками, системными контрактами. Поэтому для сборки взяли eoscpp, находящийся в контейнере eos-dev. Предсказуемый результат, пусть и чуть медленнее, важнее быстрого решения с потенциальными граблями.

Только софт. Сложнее получилось с запуском, пришлось поднимать локальный блокчейн EOS, причём готового решения опять же не было. Идея в том, чтобы спрятать нюансы монтирования и конфигурирования и получить самосогласованный набор из четырёх-пяти «кнопок» для типичных действий. Так появилась первая версия инфраструктуры запуска. Меньше контроля, но и меньше возможности ошибиться, плюс экономия времени.

К основным компонентам EOS относятся демоны nodeos, keosd, консольная утилита cleos и компилятор eoscpp:

  • nodeos — нода EOS, демон — участник сети, обеспечивает доступ к блокчейну и опционально производит новые блоки;
  • keosd — демон для управления локальными кошельками, хранящими пары ключей;
  • cleos предоставляет команды от получения информации о транзакциях до работы с ключами, реализуется на основе вызовов в nodeos и keosd по HTTP API;
  • eoscpp компилирует контракты в WebAssembly, а также позволяет получить Application Binary Interface на основе исходного кода.

Поскольку выдавалась ошибка, указывающая на сетевую недоступность keosd, мы потратили время на диагностику сетевых проблем в docker-сети. Сразу стало понятно, что не работают команды cleos, связанные с обращениями в keosd. Был диагностирован баг в cleos: проверка доступности keosd, которая выполняется перед любой командой, связанной с кошельками, учитывает порт, переданный в аргументах, но не учитывает адрес. Однако strace показал, что дело не в сети: cleos обращается по неверному адресу, всегда на localhost (а в случае нашей инфраструктуры у различных демонов различные сетевые адреса в отдельной docker-сети). В условиях хакатона в качестве обходного решения перешли на host-сеть в docker.

Входные и выходные каталоги монтировались. Следующим шагом стала утилита компиляции контрактов с использованием компилятора в контейнере (commit). Опять же — утилиты в согласованном стиле, простые «кнопки». И наконец, возможность загрузить контракт в блокчейн и посылать транзакции (commit). На этом с базовой инфраструктурой было покончено, но сюрпризы продолжились: мы наткнулись на проблему C-функций работы с памятью (более подробно дальше).

Одну копию этого окружения и развернули на eos-hackathon.smartz.io. В завершение в одном файле стали задавать аккаунты (каждый контракт и участник требуют отдельных аккаунтов) прямо вместе с парами ключей, создаваемые автоматически при запуске блокчейна, чтобы одной командой можно было поднять тестовое окружение.

2.3. Смарт-контракты

2.3.1. Контракт поставщика и биллинг электричества

Система состояла из следующих контрактов: После старта хакатона мы начали накидывать структуру контрактов по схеме выше.

  • supplier — контракт поставщика;
  • billing_electricity — контракт для расчёта платежа за электричество на каждый тик счётчика.

Более сложные методы отвечали за приём данных от счётчика, вызов контракта для расчёта платежа (биллинга), списание средств с лицевого счёта пользователя после обратного вызова из контракта биллинга. В контракте supplier большую часть занимают обычные CRUD-операции: добавление пользователей, тарифов, счётчиков, увеличение или уменьшение баланса пользователя. Нужный контракт для биллинга определялся на основе тарифа пользователя.

Накидав основную логику, мы даже подумали, не слишком ли простые контракты делаем. В контракте для биллинга после вызова рассчитывался платёж и вызывался метод для списания платежа с лицевого счёта пользователя. 🙂 Чуть позже, после деплоя контрактов в ноду и их тестирования, стало понятно, что контракты, может быть, и простые, но есть нюансы.

Не хватает прав. После деплоя выяснилось, что ожидаемые вызовы контрактов друг из друга не работают. При вызове контракта из контракта проверяется, разрешил ли инициатор контракту это делать. В отличие от смарт-контрактов в Ethereum, где вызов контракта из контракта происходит от имени вызывающего контракта, в EOS вся цепочка начинается с инициатора транзакции.

Права добавляются следующим образом (через вызов системного смарт-контракта eosio): Менторы сразу подсказали, как действовать в простом случае.

./cleos push action eosio updateauth '],"threshold":1,"accounts":[{"permission":{"actor":"supplier","permission":"eosio.code"},"weight":1}],"waits":[]}}' -p electricity

Более подробно о правах можно почитать в Technical WP EOS. В данном случае аккаунт electricity разрешает контракту supplier вызывать другие контракты от его имени. Добавление по аналогии прав в таком виде не срабатывало: У нас же контракт supplier вызывал контракт billing, а тот, в свою очередь, опять вызывал supplier.

./cleos push action eosio updateauth '{"account":"electricity","permission":"active","parent":"owner","auth":{"keys":[{"key":"EOS7oPdzdvbHcJ4k9iZaDuG4Foh9YsjQffTGniLP28FC8fbpCDgr5","weight":1}],"threshold":1,"accounts":[{"permission":{"actor":"supplier","permission":"eosio.code"},"weight":1},{"permission":{"actor":"billelectro","permission":"eosio.code"},"weight":1}],"waits":[]}}' -p electricity

Тут у менторов уже не вышло нам помочь: они сказали, что сами такого не делали. Выдавалась ошибка: Invalid authority. Может быть, только Дэн Лаример. А кто делал? Красиво сделать мешало то, что механизм вызовов других контрактов в EOS также отличается от эфира. Мы не смогли быстро найти причину ошибки в коде EOS и уже начали обдумывать альтернативные варианты, без цепочки вызовов. Не получится вызвать контракт и после вызова прочитать записанные этим контрактом данные. При вызове другого контракта этот вызов ставится в очередь и будет выполнен только после выполнения текущего вызова.

Оказывается, аккаунты в списке прав должны быть отсортированы по аккаунту: Makes sure all keys are unique and sorted and all account permissions are unique and sorted (authority.hpp). Позже всё-таки нашли в коде EOS причину ошибки при установке прав для двух контрактов. После изменения порядка аккаунтов обновление прав сработало — и наша система контрактов стала действовать.

2.3.2. Проблема C-функций работы с памятью

Вслед за std::istringstream подтягивалась функция env.calloc, что довольно странно. Смешно сказать, но нам в итоге не удалось использовать готовые функции разбора чисел (!) для чтения конфигурации биллинга. Упомянутые функции работы с памятью стандартной библиотеки C почему-то не находились в ходе загрузки кода в nodeos. А при использовании atof и подобных, а также sscanf — подтягивалась env.realloc. Функции C++ работы с памятью при этом работали.

Также довольно давно реализована поддержка C-функций работы с памятью поверх этой песочницы, их реализации есть в стандартных контрактах EOS. Конечно, при исполнении WebAssembly контракта используется не стандартный аллокатор памяти, а своя «песочница», предоставляемая каждой транзакции на определённых условиях. Вероятно, что-то шло не так на этапе линковки.

Механизм datastream EOS нам не подошёл: требовалась возможность сохранять пакеты данных разной структуры в одном поле и формировать их руками (те самые конфигурации биллингов). Потратив около часа на поиски выхода, в том числе с помощью одного из менторов, мы решили не продолжать и сделать обходное решение: написать свой код, решающий задачу разбора чисел.

2.3.3. Биллинг покупок

Общая схема работы такова: На втором дыхании, которое открылось благодаря то ли энергетикам, то ли раннему завтраку, написали биллинг для покупок в магазине.

  1. Поставщик создаёт контракт биллинга и прописывает его в своём общем контракте.
  2. Поставщик устанавливает на выходе из магазина рамки, которые умеют считывать RFID, взаимодействовать с EOS и имеют свои аккаунты, прописываемые в контракте биллинга.
  3. Каждый товар в магазине оснащается RFID-меткой, все метки прописываются в контракте биллинга.
  4. Покупатель оплачивает товар, сканируя его RFID, и товар удаляется из контракта биллинга.
  5. На выходе из магазина рамки дополнительно считывают RFID покупок. Если товар всё ещё в магазине — транзакция не проходит, и рамка должна бить тревогу (да, на самом деле можно даже не посылать транзакцию, а просто читать таблицу).

Лучше скажут в EOSIO Wiki и EOSIO Developer Portal. На самом коде контракта останавливаться нет смысла: это стандартный C++14 с некоторыми специфичными для EOS конструкциями и библиотеками.

2.3.4. Фронтенд

Вместо привычного многим Redux решили использовать MobX, который значительно ускоряет разработку и позволяет управлять глобальным состоянием без головной боли. Frontend-часть проекта реализовали с помощью React.

Пакет eosjs дорабатывается очень активно, вслед за ним и EOS-кошелёк для браузера Scatter. Стадия интеграции front-blockchain прошла не так гладко, как ожидалось. И не факт, что код, работавший вчера, будет отлично работать и сегодня. В данной связке это часто вызывает неполадки. Час попыток и дебага в полусонном состоянии — проблема решена. На эти грабли мы и наступили (и не первый раз).

Для этого понадобятся библиотека eosjs и браузерное расширение Scatter. Рассмотрим упрощённую схему взаимодействия клиентской части приложения с eos.

Scatter активно обновляется вслед за eosjs, не забывайте обновлять библиотеку. Напоминаем!

Существует два способа общения со смарт-контрактами в EOS: отправка транзакций (она вызывает модификацию блокчейна, при этом никаких возвращаемых значений не предусмотрено) и чтение таблиц (read-only действие). Далее кратко рассмотрим чтение и запись.

Рассмотрим код отправки транзакции:

sendTransaction(funcName, data) { return this.scatter .suggestNetwork(this.network) .then(() => this.scatter.getIdentity({ accounts: [this.network] })) .then((identity) => { let accountName = this.getAccountName(identity); // wrap eos instance const eos = this.scatter.eos(this.network, Eos, this.configEosInstance); return eos.transaction(accountName, (contract) => { contract[funcName](data, { authorization: accountName }); }); }); }

Третья строка: подтверждаем сеть, через которую взаимодействуем с EOS. Аргументы на входе: имя функции и объект, его значения — аргументы этой функции. Функция getAccountName возвращает первый аккаунт из полученного списка аккаунтов (в объекте identity). Четвёртая строка: получаем identity, параметр — объект с полем accounts (для данной сети).

Scatter — это обертка над экземпляром класса Eos. В данном примере Scatter используется для подписи транзакции. На строке 9 вызываем метод eos, его параметры:

  1. this.network — объект с параметрами сети.
  2. Eos — экземпляр eosjs.
  3. this.configEosInstance — объект с параметрами для экземпляра Eos (см. доку eosjs).

В callback’e вызываем метод полученного контракта с объектом, его ключи — аргументы вызываемого метода. Последний метод transaction на вход принимает accountName и callback, аргумент callback’а — контракт, находящийся в аккаунте accountName.

Рассмотрим метод чтения таблиц:

readTable(data) { this.eos = this.scatter.eos(this.network, Eos, this.configEosInstance); return this.eos.getTableRows({ json: true, limit: 1, ...data, }); }

Тут для чтения нам необходим только экземпляр eos.

В последние часы хакатона появилась идея оживить UI, добавить визуализацию процесса. Для оформления интерфейса мы, отбросив Materialize, Semantic и Ant, на скорую руку накидали стили сами. Улучшение значительно повысило привлекательность проекта и стало заключительным этапом построения UI. Подсветили новые строки таблицы на две секунды зелёным цветом и получили крутой эффект а-ля биржевые котировки.

Всё электричество стола шло через «Меркурий». За три часа до окончания времени у нас был Raspberry Pi с подключёнными к нему счётчиком электричества «Меркурий» и считывателем RFID, а также световая индикация. На каждые потраченные 0,3125 Вт⋅ч, а также на каждую «покупку» Raspberry Pi отправлял транзакции в наш блокчейн, а поставщик услуг мог управлять пользователями, датчиками, биллингом и видеть статистику потребления.

За два часа до окончания времени мы получили целостный продукт с двумя реализованными кейсами, полностью иллюстрирующий концепт, и даже никаких коммитов в последние минуты! Ещё час мы спокойно занимались проверками и добавляли последние штрихи.

На первом этапе 69 команд-участниц разделили на четыре группы, каждая из них питчилась отдельно перед двумя судьями и без зрителей. Демонстрации проектов (ака питчи) состояли из двух этапов. Эти команды получали возможность выступить с проектом на большой сцене перед зрителями и всеми восемью судьями. Судьи выставляли оценки (четыре критерия по 5 баллов каждый), и на основании этих оценок отбирался топ-10 команд на второй этап.

На выступление отводилось три минуты, их строго отслеживали по таймеру. Мы оказались в первой группе, нашими судьями были CEO и президент (интересно, чем эти должности отличаются) компании Everipedia. Судьи что-то поверхностно и достаточно коротко спросили, и демонстрация была окончена. Мы свою сбивчивую, но призванную впечатлить речь закончили на 30 секунд раньше срока.

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

Всё было разыграно как по нотам: мы описали проблему, боль, своё решение, показали, как оно работает, и описали планы развития проекта. Возникло ощущение, что с презентацией проекта мы провалились, поскольку рассчитывали произвести впечатление фактическим решением, прототипом, а не просто красочным описанием социально значимого и амбициозного проекта. Зная заранее о методах судейства, мы бы сделали многое иначе.

После началось оглашение победителей. Судьи из четырёх потоков первого раунда свели свои оценки и обменялись мнениями за 15 минут после окончания питчей. И мы это знали — и ждали результатов. В зале царила нервная обстановка: уставшие после 26-часового марафона люди хотели выиграть, сильных команд было много, и они знали, что могут претендовать на победу.

Первые четыре финалиста, потом ещё три, затем ещё три. Чтобы зрители не расслаблялись, результаты объявляли тремя частями. Мы не попали в топ-10 и не получили возможности выйти на большую сцену. Между объявлениями и в конце — выступления. Поздравляем победителей, они достойны своих призовых мест. В десятку пробились две русскоязычные команды, одна из которых в итоге стала третьей.

Хакатон прошёл на высшем уровне, практически без нареканий. Организатор хакатона команда AngelHack великолепно выполнила свою работу. Да, мы ожидали более технического судейства, но в любом случае мы прекрасно понимали, что наш шанс на победу — это только шанс. Наши призы на нём — приятное путешествие, полезный опыт, общение с сильными разработчиками и менторами.

Мы гордимся этим результатом и уверены в его ценности, применимости и масштабируемости. За 26 часов мы успели подготовить полностью рабочую модель фреймворка IoT-платежей через блокчейн EOS.

Если кто-то знает, как наладить массовое производство blockchain-ready счётчиков электричества, воды и газа, — нам будет интересно пообщаться. В дальнейших планах — снабдить решение полным и удобным UI (в помощь — наша кросс-блокчейн-платформа Smartz), поработать над конкретными кейсами применения. 🙂

п. Мы примерно прикинули, исходя из некоторых инсайдов, что только в России в организациях типа условных «РосГорГаз», «МосОблСвет» и т. По крупной прикидке, наша система позволит сэкономить на этой работе около 100 млн долларов, а уж количество потенциально сбережённых нервных клеток не поддаётся исчислению. более 5 тысяч человек чуть ли не вручную сверяют и уточняют коммунальные платежи. Так что не за горами проникновение нашего решения под рабочим названием SensorPay в ваш электрощиток!

Команда проекта и соавторы статьи:

Юрий Yuvasee Васильчиков (entrepreneur)
Алгыс algs Иевлев (hardware & backend developer)
Алексей therealal Макеев (architect, backend developer, devops)
Вячеслав bolbu Мельников (frontend developer)
Владимир quantum Храмов (blockchain & backend developer)

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»