Работа с Ethereum

Использование возможностей библиотеки Web3 и среды разработки Truffle

Показывать лекцию целиком

Создание рабочей среды для приложения

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

Веб-сайт отображает баланс и пользователей, способных распоряжаться эфиром. На второй странице будет отображаться список всех событий, запрограммированных в Angular 1. Для программирования основного кода используем среду Truffle, установленную локально. Пользуясь материалами предыдущей лекции, необходимо настроить Truffle для работы с Angular. Затем напишем контракт, а после этого проведем ряд тестов с помощью инструментов Ethereumjs-Testrpc и Ether-Pudding в среде Mocha, включенную в Truffle. Далее напишем HTML-код для веб-страницы и протестируем код JavaScript. Научимся отслеживать возникающие события и отображать баланс счета и другую информацию на нашем веб-сайте. В конечном итоге разместим контракт в реальном блокчейне.

Разработка ведется локально. Добавляем новый каталог Truffle с названием truffle2. В настоящий момент он пуст. Инициализируем стандартный проект Truffle, чтобы начать разработку с нуля. Стандартный проект Truffle инициализировался в этом каталоге.

Откроем его в среде разработки. Обратим внимание на четыре каталога: app, contracts, migrations и test.

Первым делом стоит добавить поддержку Angular, для чего необходимо инициализировать bower. Справа открыта командная строка Git Bash, а слева - обычная командная строка Windows. Использование командной строки обусловлено тем, что инструменту bower необходима интерактивная оболочка.

Bower запустился.

Вносим некоторые коррективы для установки зависимых объектов в подходящие каталоги. Это можно сделать путем редактирования файла bowerrc.

В нем можно добавлять каталоги и определять каталоги для установки компонентов Bower. Выберем подкаталог в каталоге app, bower_components.

Теперь можно устанавливать Angular.

Включим Angular в файл truffle.js.

Остальной код JavaScript можно оставить без изменений. Теперь перейдем в контракт и начнем его создание. Для начала удалим MetaCoin, а также ConvertLib.

Создадим SimpleWallet

Зададим адрес в сети Ethereum, который будет считаться владельцем счета. Для определения, какие пользователи могут распоряжаться эфиром на счете, будем использовать специальную ассоциацию. А владелец будет назначен, как только кошелек будет создан. Нужно будет получить доступ к глобальной переменной msg, а объект-отправитель sender получит по умолчанию значение того пользователя, который создал кошелек.

Кроме того, запланировано наличие нескольких событий. Одно событие, Deposit, будет ассоциировано с зачислением эфира на счет, а другое, Withdrawl, со списанием эфира.

Закончив разработку контракта, перейдем к описанию реализации различных функций. Рассмотрим наш конструктор.

Как только кошелек будет создан, владелец счета будет определен по значению переменной msg.sender, так что становится известно, кто создал этот контракт. Анонимная функция вызывается каждый раз, когда контракт получает эфир или вызывается без передачи эфира или выполнения какой-либо функции. В этом случае производится проверка, является ли отправитель сообщения, msg.sender, владельцем (owner), или находится в списке пользователей, способных распоряжаться эфиром, и затем выдается разрешение на перечисление эфира. Кроме того, создается событие Deposit, которому сообщаются значения переменных msg.sender и value. Функция sendFunds используется, когда пользователь, наделенный правами распоряжаться эфиром, отправляет средства. Для ее работы необходимо задать количество и получателя эфира. Напомним, что отправкой эфира могут распоряжаться только пользователи с соответствующими правами, что проверяется совпадением отправителя сообщения с владельцем счета или наличием отметки "true" напротив его адреса в соответствующей ассоциации. Затем производится проверка достаточности баланса, и в случае успеха средства отсылаются получателю. Если что-то пошло не так, создается исключение, и все действия возвращаются до изначального состояния, затем создается событие, а веб-сайт отображает новую величину баланса. Наконец, есть еще несколько функций поменьше. С помощью первой, allowAddressToSendMoney, предоставляем новому адресу права распоряжаться эфиром на счете, и изменяем отметку напротив этого адреса в соответствующей ассоциации на "true". Вторая, disallowAddressToSendMoney, является функцией проверки, возвращающей булево значение. Третья функция называется killWallet. Она проверит, что пользователь, вызывающий эту функцию, является владельцем кошелька, уничтожит контракт и вернет все оставшиеся средства владельцу.

Создание тестов для приложения

Эта лекция посвящена созданию сценариев тестирования для простого веб-кошелька. С помощью анонимной функции будет производиться начисление эфира на счет, и она будет принимать эфир только от выбранных отправителей или владельца счета. Если отправитель не находится в соответствующем списке, функция вернет ошибку. Другая функция - sendFunds. Разумеется, только авторизованные пользователи могут отправлять средства. Две другие функции наделяют (allowAddressToSend) или лишают (disallowAddressToSend) пользователей права отправлять средства, и функция killWallet позволяет уничтожить кошелек.

Еще есть два события: Deposit и Withdrawl. Стоит упомянуть, что в настоящий момент отслеживание событий - непростая задача. В скором времени выходит новая версия Ether-Pudding, облегчающая слежение за событиями в тестах. Прямо сейчас Ether-Pudding построена на основе Web3. Для отслеживания события нужно дождаться пока оно не произойдет, то есть практически следить за всеми событиями в блокчейне.

Вновь откроем два окна с командными строками Git Bash.

Если вы работаете на Mac или Linux, то уже располагаете доступом к подходящей командной строке. При работе с Windows рекомендуется использовать Git Bash, хотя можно использовать обычную командную строку Windows. Для целей тестирования будем пользоваться сетью testrpc. Откроем среду программирования, в том месте, на котором остановились в последний раз. Откроем SimpleWallet.

Необходимо отредактировать данные в каталоге migrations, т.к. потребуется разместить в блокчейне SimpleWallet.

Тесты для кошелька SimpleWallet будем размещать в каталоге test. Создадим новый файл и назовем его simplewallet.js.

После запуска теста, Ether-Padding предоставит доступ к списку счетов accounts.

Для начала проверим, имеет ли владелец счета возможность распоряжаться эфиром. Затем получим доступ к контракту с помощью вызова SimpleWallet.deployed, потом вызовем функцию, которая вернет булево значение в зависимости от прав адреса, который ей сообщим. Зададим этой функции первый счет, который по совместительству является счетом, разместившим контракт в блокчейне. С помощью ключевого слова assert функция проверит, что параметр isAllowed имеет значение "true", и если нет, то вернет соответствующее сообщение, и, наконец, возвращаем результат.

Удалим прежний вариант теста..

Перейдем в консоль. Запустим сетьtestrpc. Введем команду truffle test.

Вернемся назад и удалим ссылку на ConvertLib.

Запустим команду truffle test еще раз. Теперь все работает.

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

С помощью второго теста проверим, что некоторая учетная запись, не авторизованная на отправку средств, действительно не сможет это сделать, и функция isAllowedToSend вернет значение "false".

Попробуем запустить этот тест. Он не сработал. Значение isAllowed - "правда", даже несмотря на то, что счет не должен иметь прав на распоряжение средствами.

Вернемся в код кошелька и разберемся, в чем дело. Переключимся на метод isAllowedToSend. Он получает адрес, затем проверяет, что в соответствующей ассоциации для isAllowedToSend стоит значение "true", или отправитель сообщения является владельцем счета. В нашем случае msg.sender действительно является владельцем счета, поскольку тест был запущен в блокчейне от лица первой учетной записи - собственно владельца. Поэтому значения msg.sender и owner совпадают. Необходимо подправить код теста для отражения этого факта.

Проведем тест заново. Теперь все проходит успешно.

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

Для начала проведем проверку, что счет accounts[1] может распоряжаться эфиром. Ожидаем, что это не удастся. Затем позволим этому адресу, accounts[1] , отправлять эфир. После этого проверяем, что отправка действительно разрешена, и ожидаем возврата значения "true". Теперь убираем права этого адреса на распоряжение эфиром. Проверяем способность к отправке и ожидаем, что вернется значение "false". Теперь запустим тест.

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

Запустим тест для проверки функции пополнения счета. Для начала отправим эфир контракту с первого счета. Выбираем адрес контракта и отправляем одну единицу эфира.

Если переключимся на контракт, то увидим, что было создано событие пополнения счета.

В текущем тесте встроим функцию проверки, что имя события - Deposit, пополнение произошло на величину в одну единицу эфира, и было произведено со счета accounts[0] .

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

Проверим, работает ли этот тест. Да, все сработало.

Добавим еще один тест: безымянная функция, ответственная за получение эфира, выдает ошибку, если отправка средств осуществляется счетом, не внесенным в число авторизованных на отправку эфира.

Необходимо отправить эфир со счета accounts[1]. Отправляем эту транзакцию в контракт SimpleWallet. Счет accounts[1] не наделен полномочиями отправлять средства со счета. В контракте SimpleWallet есть безымянная функция, проверяющая, что отправитель сообщения msg.sender является владельцем (owner), или что он наделен правами на распоряжение средствами - в этих случаях функция допустит перечисление эфира и создаст соответствующее событие; в противном случае она выдаст сообщение об исключении.

Давайте используем функцию callback из sendTransaction, и проверим, что переменная error имеет значение "true", в этом случае можно вызвать функцию done без дополнительных параметров. Если переменная error не имеет значения "true", вызовем done с параметром в виде переменной result. А, как мы помним, если done вызывается с параметром, это означает, что тест провален. Поэтому ожидаем увидеть сообщение об ошибке. Выполняем тест.

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

Определение страниц веб-кошелька: Стартовая страница

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

Для начала потребуется новый каталог views, в котором создадим главную страницу (main.html) и журнал событий (events.html) .

Перейдем в наше приложение и удалим содержимое.

Для работы функции sendFunds потребуется дополнительная страница. Начнем с добавление библиотеки Angular.

Зададим контроллеры: MainController, ShoweventsController и SendfundsController. Теперь приступим к конфигурации.

Большую часть настроек для веб-приложений на базе Angular можно свободно загрузить из Интернета: там хранятся и шаблоны, и инструменты для использования в веб-приложениях. Настоятельно рекомендуется воспользоваться поиском и подобрать самый подходящий шаблон веб-страницы на базе Angular.

Стоит отметить, что при сборке проектов в среде Truffle окну приложения ставится в соответствие объект Web3. Настроим объект routeProvider. Существует также опция, которая срабатывает при отсутствии указанных маршрутов - она перенаправляет пользователя на главную страницу.

Оставляем переменную myVar, описанную в ранее заданных контроллерах.

Затем на главной странице index поменяем заголовок на SimpleWallet.

В файле app не потребуется использовать таблицы CSS, достаточно будет простого языка HTML. Создали якорь отображения, и ng-app будет соответствовать mySimpleWalletDapp.

Теперь перейдем в консоль. Отправим команду truffle serve. Она вернет ошибку.

Эта ошибка просто говорит, что где-то в проекте Truffle было обнаружено неожиданное исключение. Чаще всего, вернувшись в файл truffle.js, можно обнаружить проблему с путями к файлам, в нашем случае забыли поставить пару косых черт. Добавим их и перезапустим сборку

Теперь можно отдать команду truffle serve, и Truffle выполнит сборку проекта и запустит сервер HTTP. Кроме того, автоматически будут обработаны любые изменения в файлах HTML или JavaScript, а проект будет пересобран заново.

Вот наша HTML-страница на базе Angular. На главной странице выведем надпись "Main" из контроллера MainController.

Если набрать в адресной строке "events", то переключимся на контроллер ShoweventsController, а если отправиться на страницу Sendfunds, то заработает контроллер SendfundsController.

Вот так работает Angular.

Отображение транзакций пользователей

В этой лекции поговорим о том, как отображаются пользовательские транзакции, покажем снятие средств со счета на главной странице приложения, дополним контракт для отображения снятий со счета.

Откроем среду разработки. Перейдем к контракту SimpleWallet и перепишем его так, чтобы ассоциация isAllowedToSendFunds связывала не адреса и булевы значения, а адреса и структуры.

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

Рассмотрим структуру отправителей Senders. В ней будет булево поле allowed, показывающее, что отправитель авторизован распоряжаться эфиром. Еще потребуется ключевое значение и связка со следующей структурой WithdrawlStruct, которую тоже нужно задать. Определяем эту структуру. Здесь пригодится адресное поле to и величина снятия amount. Теперь перенесем ассоциацию, связывающую адреса и отправителей.

Теперь нужно немного расширить спектр используемых функций. Начнем с функций allow и disallow. Для получения доступа к структуре пригодится булево расширение этих функций - в одном случае оно получит значение "true", в другом - "false".

Теперь допишем к этой функции allowed. Теперь обе функции получают доступ к структуре, а не просто к булеву значению из массива. Затем можно расширить следующую функцию, например, isAllowedToSend. Вместо того, чтобы просто создавать событие Withdrawl, описывающее снятие средств, можно расширить его для регистрации всех снятий. Получим доступ к структуре, добавим сюда ключ, который нужно увеличить для корректного получения доступа к структуре. Затем добавим параметров для доступа к структуре withdrawls. И, наконец, объявим, что в поле to будет записан получатель средств, receiver, а в поле amount - их объем.

Когда кто-либо отправляет средства со счета и имеет на это полномочия, а на счету достаточно средств и функция отправки успешно срабатывает, создается событие Withdrawl, записывающее снятие средств в ассоциацию isAllowedToSendFundsMapping.

Перейдем к коду JavaScript, который содержит три разных страницы. Одна из них - главная страница. На ней хотелось бы отображать баланс счета, а также все снятия с конкретного адреса. Страницу с событиями рассмотрим в следующей лекции. А на странице отправки средств должна быть возможность отправки средств на адрес контракта и их снятия в пользу заданного адреса.

Обратимся для начала к странице для отправки средств. Но перед этим добавим к нашему распределенному приложению загрузчик. Откроем index.html и выполним набор действий для включения загрузчика. Сначала скопируем файлы CSS, затем код JavaScript. Теперь при обработке веб-страницы будет задействован загрузчик, и можно будет воспользоваться всеми его функциями.

Настоятельно рекомендуется ознакомиться с документацией по getbootstrap (загрузчик Bootstrap).

Добавим к нашему приложению небольшое меню: кнопку SimpleWallet, бренд нашего приложения, кнопку для перехода к событиям и еще одну для отправки средств. Также добавим в контейнер якорь для отображения. Теперь все отцентрировано, а якорь для отображения находится в контейнере.

Перейдем к странице отправки средств. Будем использовать две функции. Одна будет отвечать за отправку средств контракту. Откроем список всех локальных счетов в клиенте Geth или другом узле APC. Выберем один из этих счетов, определим количество эфира для отправки и воспользуемся кнопкой для отправки средств.

Другой функцией станет отправка средств контракта на конкретный адрес. Форма Deposit начинается с простого заголовка, а затем идет раскрывающийся список из нескольких счетов и, конечно, поле для ввода объема средств для пересылки. И кнопка отправки команды.

Перейдем к контроллеру и посмотрим, как выглядит функция depositFunds изнутри. В контроллере SendfundsController удалим переменную myVar и начнем работу. Для начала создадим список доступных счетов. Объект Web3 привязан к активному окну, поэтому список счетов можно получить с помощью команды web3.eth.accounts. Еще одна функция - depositFunds, которая требует адреса (address) и объема средств (amount) в качестве входных параметров.

Далее получим доступ к контракту и запустим транзакцию. Поле from используется для задания адреса, откуда будут списываться средства, to - адрес нашего контракта, а value - это количество эфира, которое нужно конвертировать в веи. Затем идет функция обратного вызова, с помощью которой будем контролировать успешность операции. Если операция завершится с ошибкой, то ее классифицируем и выведем сообщение об ошибке, а если все пройдет хорошо, то отобразим короткое сообщение об успешности перевода средств. Поскольку работаем вне стандартных инструментов обнаружения ошибок Angular, потребуется внести в них изменения вручную.

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

Перейдем в папку truffle2 и введем команду truffle serve.

Исправляем ошибки.

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

Перейдем на адрес http://localhost:8080 и посмотрим на наш веб-кошелек. Здесь есть короткое меню, отображается секция для зачисления средств. Откроем консоль JavaScript. Вот список счетов из сети testrpc. Выберем первый счет и введем величину в одну единицу эфира.

Исправляем еще одну ошибку: simpleWallet, а не simpleContract.

Выполним сборку контракта еще раз.

Перезагрузим страницу. Теперь отправка средств прошла успешно. В консоли testrpc тоже показывается, что транзакция завершилась успешно.

Теперь перейдем на главную страницу и добавим код по запросу баланса. Добавим баланс в единицах эфира.

Переменной scope.balance соответствует web3.eth.getBalance. Конвертируем эту величину в число и задаем переменной scope.balanceInEther значение web3.fromWei, выраженное в единицах эфира.

Вернемся на адрес localhost:8080 . На счету контракта одна единица эфира.

Вернемся к механизму отправки средств и реализуем функцию снятия средств со счета. Добавим дополнительную форму с функцией снятия средств. Добавим функцию Withdraw. Команда withdrawFunds требует адреса и объема средств в качестве аргументов. Можно указать адрес, на который вы желаете перевести средства, объем в единицах эфира, и щелкнуть по кнопке Submit.

Перейдем к нашему контроллеру JavaScript и добавим эту функцию. Она требует один адрес и одно число - величину средств для перевода. Добавим контракт SimpleWallet и возможность вызова функции sendFunds из контракта SimpleWallet.

Если я перейду в контракт SimpleWallet, функция sendFunds будет требовать указания объема средств и адреса.

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

Далее опишем действия в случае возникновения ошибки.

Посмотрим, как вся эта логика выглядит со стороны веб-страницы. Вот функции пополнения и снятия средств.

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

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

Вот что нужно были сделать на самом деле.

Теперь добавим на счет одну единицу эфира. И теперь перешлем единицу эфира. Повторим эту транзакцию несколько раз.

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

Откроем MainController. Здесь нужно будет задать массив, который назовем withdrawls.

В нем можно будет перечислить все списания, упомянутые в ассоциации isAllowedToSendFunds. Для этого в первую очередь нужно вернуть количество списаний, произведенных с этого адреса. Давайте назовем эту функцию getAmountOfWithdrawls с параметром адресного типа. Это - постоянная функция, возвращающая число типа uint. В данном случае она возвращает isAllowedToSendFundsMapping[_address].amount_sends. Теперь зададим еще одну функцию, возвращающую информацию по конкретному списанию. Вернуть всю структуру нельзя, но можно вернуть несколько значений. Зададим функцию getWithdrawlForAddress с аргументами типов _address, uint и index. Эта функция возвращает адрес и объем переданного эфира. Настроим возврат процедуры. Важно отметить, что все эти значения инициализируются автоматически. Закончим задавать параметры для функции.

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

Затем подадим команду truffle serve, которая заново создаст HTTP-сервер и порт 8080.

Теперь, когда кошелек полностью обновлен и подключен к блокчейну, попробуем отправить средства со счета. На счету у нас три единицы эфира. Скопируем второй адрес и отправим одну единицу эфира, а потом повторим процедуру. Теперь посмотрим, как отобразить все процедуры списания. Для начала нам потребуется функция contract.getAmountOfWithdrawls.call, вызовем ее с указанием параметра web3.eth.accounts[0] , то есть запросим отображение всех списаний по первому счету. Запишем результат в переменную numberOfWithdrawls, и последовательно пройдем по списку всех списаний эфира с этого адреса с помощью цикла for. Установим счетчик i, равный единице, и будем выполнять функцию contract.getWithdrawlForAddress.call, пока i не станет равен числу всех списаний, numberOfWithdrawls, увеличивая i на единицу на каждом шаге и отображая это на веб-странице с помощью команды $scope.withdrawls.push(result_withdrawl) . Добавим команды для корректной работы Angular и Ether-Padding - $scope.$apply() и return this.

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

Посмотрим, успешно ли это работает. Нет, разумеется, доступ запрещен.

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

Проверим. Теперь объем средств для списания отображается в единицах эфира, и остается только убедиться, что данные поступают в численном формате.

Подведем краткие итоги этой лекции. Главной задачей было обеспечить корректную работу модуля списания эфира со счета. Во-первых, включили загрузчик Bootstrap в приложение и разобрались, как настроить его базовую конфигурацию путем копирования общедоступных участков кода. Данный загрузчик устанавливает тему страницы, поддерживает таблицы стилей CSS, библиотеку jQuery и bootstrap.jsВо-вторых, включили меню. Можно найти разные варианты реализации меню в свободном доступе, например, на сайте, посвященном загрузчику Bootstrap, на странице Components в разделе Navbar. Для данной лекции просто скопировали с сайта часть кода и удалили ненужные участки. В-третьих, установили якорь для отображения в контейнер, чтобы контент веб-кошелька не занимал всю ширину страницы, а был отцентрирован и умещался в 960 пикселов.

Вторая часть лекции представляла собой подготовку к настройке модуля снятия средств. Добавили на веб-страницу sendfunds.html две формы: для зачисления (Deposit) и снятия (Withdrawal) средств. По щелчку по кнопке Submit эти формы вызывают из контроллера функции зачисления или списания средств. Адреса для зачисления и списания также берутся из этих модулей путем предоставления выбора из списка адресов, доступных в нашей тестовой сети testrpc. Список счетов можно всегда получить с помощью функции web3.eth.accounts. Разумеется, результат будет различаться в зависимости от того, к какому узлу вы подключены. Например, если у вас запущен кошелек Mist или работает узел Geth, допускающий RPC-подключения по протоколу HTTP, вы увидите доступные в этих сетях счета в веб-кошельке. В данном случае приложение подключено к сетиtestrpc, и можно видеть существующие в ней счета. Если вернуться к файлу app.js и контроллеру SendFundsController, можно увидеть, в каком участке кода получаем список счетов и можем проходиться по их списку с помощью функции Withdraw, требующей указания целевого адреса и объема средств для передачи. Функция зачисления средств создает транзакцию для перевода средств со счета, находящегося в кошельке, на адрес контракта, также на заданную величину единиц эфира. В функции отправки вызываем функцию sendFunds из контракта, и она так же требует указания объема средств и адреса получателя, после чего пересылает средства из контракта получателю.

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

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

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

Обнаружение событий и реагирование на них

В этой лекции разберем схемы работы с событиями. Откроем среду разработки. Преимущественно будем работать с файлом events.html и контроллером ShoweventsController. Для начала необходимо определить, с каким контрактом будем работать. Укажем SimpleWallet.deployed().

Теперь прервемся и посмотрим, где можно ознакомиться с документацией по работе с событиями в среде Web3. Есть два важных способа работы с событиями, которые будут реализованы в этом приложении. Одной из важнейших функций является allEvents.

Ее можно вызывать прямо из контракта. Затем можно следить за событиями с помощью команды events.watch, а после получения необходимой информации отслеживание можно остановить с помощью команды stopWatching(). Это первый метод, который используем. Находясь в контракте, можно получить список всех событий с помощью команды _myContract.allEvents(). После этого можно запустить отображение событий, events.watch, что позволит создать описание ошибки error или результат result, а затем отобразить результат в консоли.

Теперь в кошельке на странице с журналом событий в консоли отображается одно событие списания средств.

Разумеется, необходимо отображать здесь полный список событий, начиная с самого первого блока. Для этого нужно еще немного отредактировать код. Необходимые параметры для отображения всех событий можно найти в документации.

Посмотрим, что происходит в кошельке. Видим, что произошло два события по зачислению средств, и одно - по списанию.

Информация по списанию средств также отображается и на главной странице. Теперь займемся отображением этих событий. Необходимо будет добавить информацию о них в отображаемый контент. Однако следует помнить, что планировали отслеживать все события, и когда покинем контроллер, то события все равно продолжат отслеживаться в фоновом режиме. Разумеется, стоит прекратить отслеживание событий, как только покинем контроллер. При покидании контроллера Angular создает событие под названием destroy. Данное событие можно отследить, и как только оно создастся, останавливаем отслеживание остальных событий.

Перейдем к файлу events.html. Укажем, откуда брать контент для отображения (ourEvents) , как отображать название события (Name: {{event.event}}) и объем эфира (Amount: {{event.args.amount}}).

Посмотрим, что произойдет при перезагрузке страницы.

Мы видим два события по зачислению средств и одно - по списанию.

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

Если есть потребность отслеживать одно конкретное событие, это также можно реализовать. С документацией на эту тему можно ознакомиться wiki-пространстве.

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

Выполним сходные процедуры для событий по начислению средств.

Но на этот раз вместо использования объекта событий events и наблюдения за его созданием применим мгновенный вызов функции обратного вызова. Рассмотрим функцию depositEvents. Для этой функции нет каких-либо фильтров, она работает с первого блока (fromBlock: 0) до текущего (toBlock: 'latest') и обращается к функции обратного вызова. Теперь отобразим полученное значение на странице ($scope.ourDepositEvents.push(result) и $scope.$apply()). При выходе из контроллера, как и в прошлом случае, остановим отслеживание событий.

Посмотрим, как это все работает в приложении. Как видно, в списке All Events перечислены все события.

К сожалению, в сети testrpc события по списанию средств отображаются как события по зачислению средств. Позднее проверим, не повторяется ли это поведение при работе с реальным блокчейном.

Задание №4: Использование возможностей библиотеки Web3 и среды разработки Truffle

Создайте контракт в среде Truffle и реализуйте его функционал на основе разрешений. Контракт должен быть способен вызывать функции allowAddressToSendMoney и disallowAddressToSendMoney

Пример приложения, которое должно было получиться

В данной лекции будет проведен полный обзор распределенного приложения. Загрузим приложение в частную сеть. Но перед этим развернем саму частную сеть, как делали в предыдущих лекциях, и воспользуемся командой truffle migrate для размещения приложения в блокчейне, а затем применим команду truffle serve для работы с приложением в частной сети.

Открываем две консоли на своем компьютере под управлением Windows. Слева открыта командная строка Windows, а справа - Git Bash. Специально проводим демонстрацию на системе Windows, поскольку настройка частной сети в этой среде несколько более проблематична, чем в Linux или OS X. Если вы работаете в OS X, потребуется только открыть консоль, установить менеджер программных пакетов для узлов сети Ethereum и среду Truffle. В командной строке перейдем в каталог, где расположен Geth.

Предварительно загрузили изначальный файл JSON для генерации блокчейна. Этот файл можно найти на веб-сайте http://github.com/ethereum/go-ethereum/wiki/Command-Line-Options, он расположен в самом низу страницы, откуда его легко скопировать.

Теперь используем команду для инициализации изначального узла. Создадим первый блок. Теперь запустим Geth с поддержкой HTTP и RPC. Обратите внимание, что в качестве одного из параметров указан datadir, показывающий Geth, в каком каталоге создавать узел сети. Создали узлы IPC и HTTP. Теперь перейдем во вторую консоль. Мы находимся в том же каталоге, но по некоторым причинам, связанным с работой моего компьютера под управлением Windows, я вынужден использовать командную строку для работы с Geth. Подключим Geth к нашему узлу.

Первое, что необходимо сделать после подключения к частной сети - это создать несколько счетов. Зададим пароль, скажем, test123, и после этого основной счет для хранения эфира автоматически подключается к этому счету. Теперь можно начать майнинг. Запустим процесс командой miner.start().

Теперь контракты в этой сети будут обсчитываться, и можно размещать в ней нужный нам код. Откроем третью консоль. Перейдем в ней в каталог, в котором ведется работа с Truffle, в нашем случае это /c/ethereum/truffle2. Теперь попробуем выполнить миграцию проекта Truffle. Она должна завершится неудачей, поскольку предварительно нужно разблокировать счет. Тем не менее, стоит продемонстрировать, что происходит в этом случае. Truffle попробовала смигрировать контракты, но попытка оказалась неуспешной, получили сообщение об ошибке, связанной с блокировкой счета - "Error: account is locked".

Поэтому необходимо разблокировать счет. Воспользуемся командой personal.unlockAccount(eth.coinbase), то есть выберем для разблокировки основной счет. Введем пароль, и после того, как консоль ответит "true" (что ожидается, если пароль был указан верно), станет возможным размещение контракта в блокчейне.

Если было получено еще одно сообщение об ошибке вида quot;Exceeds block gas limit quot;, обратите внимание на верхний предел объема газа для контрактов. Это можно узнать с помощью команды eth.getBlock('latest') - видно, что в данном случае верхний предел газа составляет около трех миллионов шестисот тысяч единиц газа.

Если вы получите такое сообщение об ошибке (что маловероятно при размещении контракта в реальном блокчейне), отредактируйте верхний предел объема газа в файле truffle.js.

Попробуем снова. В этот раз размещение контракта в блокчейне прошло успешно.

Теперь отдадим команду .

Перед нами веб-кошелек. Сейчас на балансе нет эфира. Отображается адрес кошелька, массив ABI (на случай, если потребуется добавить дополнительный контракт в кошелек), и единственный счет, авторизованный на распоряжение эфиром, он же - владелец кошелька.

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

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

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

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

Итак, мы продемонстрировали снятие и зачисление средств. Теперь наделим счет правами распоряжаться эфиром (потребуется немного времени на обсчет соответствующего блока). Теперь оба счета могут распоряжаться средствами в кошельке. Разумеется, можно отозвать ранее предоставленные права.

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

Следующие шаги после окончания курса

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

Вернуться к учебному плану