Абстрактный

Getline.in обратился ко мне с просьбой предоставить им полностью рабочий прототип приложения Ethereum для краудфандинговых займов. Задача заключалась в создании как back-end (Smart Contract), так и front-end (Dapp). Конечная цель - подтвердить рынок и понять стек технологий для дальнейшего развития. Поскольку это должно было быть быстрым и грязным, мы установили крайний срок в неделю - то, что мы создаем, это то, что у нас есть, без расширений. В итоге мы потратили ~ 20 часов, и конечный продукт можно было увидеть на etherloans.netlify.com. Для работы веб-сайта у вас должен быть плагин Metamask с разблокированным кошельком (другие узлы Ethereum, которые внедряют Web3 API, также должны работать) и быть в тестовой сети Ethereum.

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

  1. Запросите ссуду, указав цель финансирования, период инвестирования, срок окупаемости и целевую процентную ставку
  2. Кредит создается и перечисляется начиная с инвестиционного периода.
  3. В период инвестирования инвесторы могут вложить деньги в ссуду. Эта сумма еще не может быть снята ни одной из сторон.
  4. Если цель финансирования не достигнута, инвесторы должны иметь возможность снять свои деньги, и заем не пройдет.
  5. В противном случае должник может забрать ссуду и наступит срок окупаемости.
  6. Если долг не выплачен, ссуда считается дефолтной и несостоятельной.
  7. Если долг погашен, ссуда считается успешной.
  8. По истечении срока окупаемости и успешной выдачи кредита инвесторы могут отозвать свои вложения.

Стек технологий

После того, как мы спланировали основы, мы могли начать думать о том, какие технологии использовать.

Краткое объяснение того, что на самом деле представляет собой Dapp: Dapp - это обычный веб-сайт, который можно разместить где угодно. Используя Ethereum Javascript Web3 API, вы можете взаимодействовать с узлом Ethereum, установленным либо на вашем клиентском компьютере, либо с любым другим общедоступным узлом, установленным в Интернете. Этот узел действует как шлюз в сеть Ethereum и позволяет вам общаться с развернутыми смарт-контрактами. Поскольку нелокальные узлы не имеют информации о кошельке клиента, мы не могли использовать облачные для обеспечения инвестиций и снятия средств и выбрали установку узла на компьютере пользователя.

В Dapp серверная часть вместо того, чтобы жить на сервере, принадлежащем какой-то компании, - это ваш смарт-контракт, живущий на блокчейне. Они написаны на Solidity (есть и другие контрактные языки, но этот гораздо более популярный и в настоящее время предпочтительный способ написания контрактов). Хотя вам все еще нужно писать контракты Ethereum напрямую, такие фреймворки, как Truffle или Embark, обеспечивают дополнительную ценность за счет простого развертывания в сетях, миграции для обновления существующих контрактов и модульного тестирования в сети и вне сети. В итоге мы использовали Truffle, дополненный узлом TestRPC. TestRPC позволяет имитировать всю цепочку блоков, он имитирует все транзакции локально и быстро. Он идеально подходит для тестирования перед реальным развертыванием.

Что касается самого фронтенда, мы остановились на Angular 2. Многие участники сообщества используют Meteor.js, поскольку он прекрасно вписывается в модель Dapp, но мы были ограничены во времени и выбрали то, что знали лучше.

Смарт-контракт

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

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

В нашем случае каждая ссуда представляла собой автономную структуру, к которой можно получить доступ, предоставив ей уникальный идентификатор в списке всех ссуд.

Здесь есть несколько интересных моментов:

  • Структуры - это только часть языка Solidity, а не базовая виртуальная машина, поэтому для каждого свойства структуры требовался собственный дополнительный геттер. Контрактная чистая функция.
  • Виртуальная машина Ethereum не поддерживает числа с плавающей или фиксированной точностью, поэтому мы прибегли к использованию промилле, чтобы отслеживать дроби.
  • Существует константа block.timestamp, но майнеры могут заставить ее отклоняться от реального времени, поэтому безопасные контракты не должны так сильно полагаться на нее.
  • С другой стороны, блоки нельзя добывать быстрее или медленнее, чем необходимо, поэтому установка крайних сроков с помощью константы block.number - лучшее решение.
  • Невозможно перебирать сопоставление, требовалась альтернатива для получения всех ссуд.

Ethereum изначально поддерживает события, которые могут генерироваться, когда в контракте происходит что-то интересное. Более того, за этими событиями может следить внешний интерфейс, что позволяет нам автоматически обновлять данные о ссуде, когда это необходимо, вместо опроса блокчейна.

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

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

Отправка эфира из контракта - непростая задача. Поскольку адрес, на который мы отправляем деньги, также может быть Контрактом, его методы также должны запускаться при получении денег, и они запускают промежуточное выполнение метода первого Контракта, поэтому второй Контракт также может вызывать первый Контракт во время его выполнения. . Это свойство называется повторным входом. Это означает, что если контракт написан неправильно, его состояние может быть не таким, как вы ожидаете при вызове метода. Один из сценариев, в котором это может быть использовано, - это когда Контракт обновляет свое состояние только после отправки денег. Нетрудно представить себе злоумышленника, который пишет контракт, который вызывает ReceivePayment () каждый раз, когда получает деньги, выполнение зацикливается, и все средства контракта ссуды могут быть выведены. Вот почему рекомендуется заранее обновить состояние.

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

Тестирование

Truffle позволяет писать автоматические тесты для смарт-контрактов. Сначала он развертывает контракт в предоставленной сети. В большинстве случаев к поддельному, смоделированному TestRPC, но тесты также можно запускать в реальных сетях (и следует хотя бы один раз перед развертыванием).

Затем вы взаимодействуете с Контрактом так же, как Dapp - в Javascript с API Web3. У Truffle также есть небольшой слой абстракции сахара сверху, заключающий каждый вызов в обещание.

Самой большой проблемой при тестировании была возможность тестировать сроки наших займов - вы не можете ускорить время, а TestRPC не позволяет подскочить константу block.number вверх. В итоге мы обошли проблему, воспользовавшись внутренними механизмами TestRPC - для каждой транзакции добывается отдельный блок. Мы просто протолкнули столько пустых транзакций, сколько блоков нам нужно для продвижения вперед. Однако это решение не работает в реальных сетях. Я не думаю, что есть какой-либо способ проверить дедлайны в реальной сети, не изобретая путешествия во времени.

Внешний интерфейс

Фронтенд был на удивление довольно простым. Обычное приложение Angular 2 с другим способом получения данных внутри служб (или известным как модель в других фреймворках / шаблонах).

Здесь снова помог Truffle - при компиляции и развертывании контрактов он генерирует файл JSON со всеми данными, необходимыми для работы с контрактом через Web3. Поскольку Webpack позволяет напрямую импортировать файлы JSON внутри Javascript, мы можем просто получить их из каталога компиляции, разделив компиляцию Truffle и компиляцию внешнего интерфейса.

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

Развертывание

Эта часть была самой сложной, я хотел избежать ночевки, загружая весь блокчейн, который весит 50+ ГБ. Легкие клиенты позволят нам развернуть контракт, не загружая все. Однако большинство из них находятся в разработке. На данный момент только один - это Metamask, но это плагин Chrome, что означает, что он изолирован и не прослушивает обычные команды RPC. Единственный способ поговорить с ним - через Web3 API на веб-сайте. Truffle не может связаться с ним для развертывания и отправки JSON, необходимого для интерфейса!

В итоге мы развернули на узле TestRPC, а затем открыли новую вкладку Chrome, вручную развернули скомпилированный байт-код через Web3, обновили JSON до правильных значений и перекомпилировали интерфейс.

Чтобы интерфейс работал с JSON, нужно было изменить две вещи. Идентификатор сети и адрес контракта. Идентификатор сети достаточно прост: web3.version.network возвращает «3» для тестовой сети. Развертывание контракта было более длительным процессом, чтобы прочитать и понять, как это делать правильно.

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

Улучшение для следующих итераций

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

У нас закончилось время, поэтому не все функции смарт-контракта доступны в интерфейсе Dapp. Снятие средств инвесторами и владельцем контракта по-прежнему необходимо производить вручную с использованием API-интерфейсов Web3.

Хотя ссуды обновляются на интерфейсе должным образом, когда происходит что-то интересное, например, вложение или окупаемость, он не может определить, когда заканчивается период, и затем обновить его. Это связано с тем, что контракт выполняется только по запросу, а не постоянно. Есть несколько сетевых решений, таких как Ethereum Alarm Clock, но самым дешевым и простым способом было бы использовать таймеры Javascript во внешней оценке, когда период должен закончиться, и затем обновить ссуду. Одна техническая проблема заключается в том, что время добычи блока в Ethereum варьируется, поэтому мы можем только оценить время, когда будет добыт блок с крайним сроком. Однако эта проблема не будет видна пользователю. Мы могли бы проверить контракт еще раз после того, как таймер закончится за кулисами, и соответствующим образом настроить таймеры.

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

Выводы

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

Хотя это было очень весело!

Значит, вас действительно интересует Ethereum, а? Если у вас есть проект, напишите [email protected], и мы посмотрим, что мы сможем построить вместе!