КулЛиб - Классная библиотека! Скачать книги бесплатно 

Блокчейн. Разработка приложений [Нараян Прасти] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
Нараян Прасти

Санкт-Петербург
«БХВ-Петербург»
2018

УДК 004.75
ББК 32.973.26-018
П70

Прасти Н.
П70

Блокчейн. Разработка приложений: Пер. с англ. — СПб.: БХВ-Петербург,
2018. — 256 с.: ил.
ISBN 978-5-9775-3976-0
Рассказано о том, что такое децентрализованные приложения и как они работают. Рассмотрены принципы работы платформы Ethereum. Показано, как писать
смарт-контракты и использовать интерактивную консоль Geth для размещения и
передачи транзакций. Описана библиотека web3.js, ее импорт, подключение к Geth
и использование в среде Node.js или на стороне клиента. Продемонстрировано, как
создать сервис кошелька и управлять им, как компилировать смарт-контракты и
развертывать их при помощи web3.js и EthereumJS. Описаны язык программирования Solidity и среда разработки Truffle. Приведено руководство по разработке собственного блокчейна и децентрализованных приложений корпоративного уровня.

Для программистов, преподавателей и студентов,
а также специалистов отделов развития компаний и банков
УДК 004.75
ББК 32.973.26-018

Группа подготовки издания:
Руководитель проекта
Зав. редакцией
Перевод с английского
Компьютерная верстка
Оформление обложки

Евгений Рыбаков
Екатерина Капалыгина
Валерия Яценкова
Ольги Сергиенко
Марины Дамбиевой

© Packt Publishing 2017. First published in the English language under the title ‘Building Blockchain Projects (9781787122147)’
© Packt Publishing 2017. Впервые опубликовано на английском языке под названием ‘Building Blockchain Projects (9781787122147)’

"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20.

ISBN 978-1-78712-214-7 (англ.)
ISBN 978-5-9775-3976-0 (рус.)

© Packt Publishing 2017
© Перевод на русский язык, оформление.
ООО "БХВ-Петербург", ООО "БХВ", 2018

Оглавление
Предисловие к русскому изданию .............................................................................. 11
Об авторе ......................................................................................................................... 13
О рецензентах ................................................................................................................. 15
Издательство «Packt».................................................................................................... 17
Предисловие ................................................................................................................... 19
О чем эта книга? ............................................................................................................................ 19
Что понадобится в дополнение к этой книге?............................................................................. 20
Для кого эта книга? ....................................................................................................................... 20
Обозначения ................................................................................................................................... 20
Обратная связь ............................................................................................................................... 21
Поддержка читателей .................................................................................................................... 22
Скачивание исходных кодов программ ............................................................................... 22
Цветные иллюстрации ...........................................................................................................22
Электронный архив файлов для русского издания ............................................................. 23
Исправления ................................................................................................................................... 23
Пиратство ....................................................................................................................................... 23
Вопросы .......................................................................................................................................... 23
Глава 1. Децентрализованные приложения ............................................................. 25
Что такое ДП? ................................................................................................................................ 25
Достоинства децентрализованных приложений ......................................................................... 27
Недостатки децентрализованных приложений ........................................................................... 27
Децентрализованная автономная организация ........................................................................... 27
Личность пользователя в ДП ........................................................................................................ 28
Учетные записи пользователей .................................................................................................... 30
Доступ к централизованным приложениям ................................................................................ 30
Внутренняя валюта приложений ..................................................................................................31
Недостатки внутренней валюты ................................................................................................... 32

Оглавление

Приложения с контролируемым доступом ................................................................................. 32
Популярные приложения .............................................................................................................. 32
Bitcoin ..................................................................................................................................... 32
Что такое регистр? ........................................................................................................ 33
Что такое блокчейн? ..................................................................................................... 33
Легален ли биткойн? .................................................................................................... 33
Почему мы используем биткойн? ............................................................................... 34
Ethereum .................................................................................................................................. 34
Hyperledger ............................................................................................................................. 35
IPFS ......................................................................................................................................... 35
Как работает IPFS? ....................................................................................................... 36
Filecoin .................................................................................................................................... 36
Namecoin ................................................................................................................................. 37
Домены в зоне .bit ......................................................................................................... 37
Dash ......................................................................................................................................... 38
Децентрализованное управление и бюджетирование ............................................... 39
Децентрализованные услуги ........................................................................................ 39
BigChainDB ............................................................................................................................ 40
OpenBazaar ............................................................................................................................. 40
Ripple ...................................................................................................................................... 40
Заключение..................................................................................................................................... 43

Глава 2. Принципы работы Ethereum ....................................................................... 44
Знакомство с Ethereum .................................................................................................................. 44
Учетная запись Ethereum .............................................................................................................. 45
Транзакции ..................................................................................................................................... 45
Консенсус ....................................................................................................................................... 46
Метка времени ............................................................................................................................... 48
Число nonce .................................................................................................................................... 48
Время блока.................................................................................................................................... 49
Ветвление ....................................................................................................................................... 51
Генезис............................................................................................................................................ 52
Деноминация эфира....................................................................................................................... 52
Виртуальная машина Ethereum .................................................................................................... 53
Газ ................................................................................................................................................... 53
Обнаружение узлов ....................................................................................................................... 54
Протоколы Whisper и Swarm ........................................................................................................ 55
Geth ................................................................................................................................................. 55
Установка Geth ....................................................................................................................... 56
OS X ............................................................................................................................... 56
Ubuntu ............................................................................................................................ 56
Windows ......................................................................................................................... 56
JSON-RPC и консоль JavaScript............................................................................................ 57
Подкоманды и опции ............................................................................................................. 57
Подключение к сети mainnet ....................................................................................... 57
Создание частной сети ................................................................................................. 57
Создание аккаунта ................................................................................................................. 58
Майнинг......................................................................................................................... 58
Быстрая синхронизация ............................................................................................... 59
____________________________________

[6]

___________________________________

Оглавление

Ethereum Wallet .............................................................................................................................. 59
Mist .................................................................................................................................................. 61
Уязвимости Ethereum .................................................................................................................... 62
Атака Сибиллы ...................................................................................................................... 62
Атака 51% ............................................................................................................................... 62
Обновление Serenity ...................................................................................................................... 62
Платежные каналы и каналы состояния .............................................................................. 63
Протокол консенсуса Casper ................................................................................................. 63
Разделение данных ................................................................................................................ 64
Заключение..................................................................................................................................... 64

Глава 3. Разработка смарт-контрактов .................................................................... 65
Файлы исходного кода Solidity .................................................................................................... 65
Структура смарт-контракта .......................................................................................................... 66
Расположение данных ................................................................................................................... 67
Что такое типы данных?........................................................................................................68
Массивы.................................................................................................................................. 69
Строки ..................................................................................................................................... 70
Структуры .............................................................................................................................. 71
Перечисление ......................................................................................................................... 72
Сопоставление ....................................................................................................................... 72
Оператор delete ...................................................................................................................... 73
Преобразование элементарных типов.................................................................................. 74
Ключевое слово var ............................................................................................................... 74
Управляющие структуры .............................................................................................................. 75
Оператор new и создание контракта ............................................................................................ 76
Исключения.................................................................................................................................... 77
Вызов внешних функций .............................................................................................................. 77
Свойства контракта ....................................................................................................................... 79
Видимость .............................................................................................................................. 79
Модификаторы ....................................................................................................................... 81
Резервная функция................................................................................................................. 83
Наследование ......................................................................................................................... 83
Ключевое слово super ................................................................................................... 85
Абстрактные контракты ............................................................................................... 86
Библиотеки ..................................................................................................................................... 86
Конструкция using ... for ....................................................................................................... 88
Возврат нескольких значений ...................................................................................................... 89
Импорт файлов исходных кодов Solidity .................................................................................... 89
Глобальные переменные ............................................................................................................... 90
Свойства блока и транзакции ............................................................................................... 90
Свойства, связанные с адресом ............................................................................................ 91
Переменные, связанные с контрактом ................................................................................. 91
Единицы эфира .............................................................................................................................. 91
Доказательство наличия, целостности и принадлежности файла ............................................. 91
Компиляция и развертывание контракта ..................................................................................... 93
Заключение..................................................................................................................................... 96
____________________________________

[7]

___________________________________

Оглавление

Глава 4. Учимся работать с web3.js ........................................................................... 97
Введение в web3.js ......................................................................................................................... 97
Импортирование web3.js .......................................................................................................98
Подключение к узлу .............................................................................................................. 98
Структура API ........................................................................................................................ 99
Библиотека BigNumber.js .................................................................................................... 100
Конвертация денежных единиц .......................................................................................... 101
Запрос цены газа, баланса и деталей транзакции ............................................................. 101
Отправка эфира .................................................................................................................... 103
Работа с контрактами ..........................................................................................................104
Отслеживание событий контракта ..................................................................................... 106
Разработка клиентского приложения для контракта ................................................................ 109
Структура проекта ............................................................................................................... 110
Разработка серверной части ................................................................................................ 110
Разработка клиентской части.............................................................................................. 112
Тестирование клиентской части ......................................................................................... 116
Заключение................................................................................................................................... 119
Глава 5. Разработка сервиса кошелька................................................................... 120
Различие между онлайн- и оффлайн-кошельками.................................................................... 120
Библиотеки hooked-web3-provider и ethereumjs-tx.................................................................... 121
Что такое HD-кошелек? .............................................................................................................. 124
Введение в функции формирования ключа............................................................................... 125
Знакомство с LightWallet ............................................................................................................ 126
Путь вывода HD-кошелька ................................................................................................. 127
Разработка сервиса кошелька ..................................................................................................... 127
Предварительная подготовка .............................................................................................. 127
Структура проекта ............................................................................................................... 128
Разработка серверной части ................................................................................................ 128
Разработка клиентской части.............................................................................................. 129
Тестирование ........................................................................................................................ 136
Заключение................................................................................................................................... 142
Глава 6. Разработка платформы для смарт-контрактов ..................................... 143
Вычисление nonce для транзакции ............................................................................................ 144
Знакомство с solcjs ...................................................................................................................... 145
Установка solcjs ................................................................................................................... 145
API solcjs............................................................................................................................... 146
Использование различных версий компилятора............................................................... 147
Связывание библиотек ........................................................................................................ 148
Обновление ABI................................................................................................................... 149
Разработка платформы для развертывания контрактов ........................................................... 149
Структура проекта ............................................................................................................... 150
Разработка серверной части ................................................................................................ 150
Разработка клиентской части.............................................................................................. 156
Тестирование ........................................................................................................................ 160
Заключение................................................................................................................................... 161
____________________________________

[8]

___________________________________

Оглавление

Глава 7. Приложение для ставок на результат матча.......................................... 162
Знакомство с Oraclize .................................................................................................................. 163
Как работает Oraclize? ......................................................................................................... 163
Источники данных ............................................................................................................... 163
Доказательство подлинности .............................................................................................. 164
Стоимость услуг Oraclize .................................................................................................... 166
Основы работы с API Oraclize ............................................................................................ 167
Настройка типа и места хранения доказательства ................................................... 167
Отправка запросов ...................................................................................................... 167
Отложенные запросы ................................................................................................. 168
Расходование газа ....................................................................................................... 168
Функции обратного вызова........................................................................................ 169
Синтаксический разбор результатов ......................................................................... 170
Получение цены запроса ............................................................................................ 171
Шифрование запросов ......................................................................................................... 171
Расшифровка источника данных ............................................................................... 171
IDE Oraclize .......................................................................................................................... 172
Работа со строками ...................................................................................................................... 172
Разработка контракта для ставок на спорт ................................................................................ 174
Разработка приложения для ставок............................................................................................ 177
Разработка структуры приложения .................................................................................... 178
Разработка серверной части ................................................................................................ 178
Разработка клиентской части.............................................................................................. 181
Тестирование приложения .................................................................................................. 189
Заключение................................................................................................................................... 193
Глава 8. Разработка смарт-контрактов уровня предприятия ............................ 194
Знакомство с ethereumjs-testrpc .................................................................................................. 195
Установка и использование ethereumjs-testrpc .................................................................. 195
Приложение командной строки testrpc ..................................................................... 195
Использование ethereumjs-testrpc в качестве провайдера web3
или HTTP-сервера....................................................................................................... 197
Доступные методы RPC ...................................................................................................... 198
Что такое заголовки событий? ................................................................................................... 199
Знакомство с пакетом truffle-contract ........................................................................................ 201
Установка и импорт truffle-contract ................................................................................... 202
Настройка тестового окружения ........................................................................................ 203
API truffle-contract ............................................................................................................... 203
API абстракции контракта ......................................................................................... 204
Создание экземпляра контракта ................................................................................ 209
API экземпляра контракта.......................................................................................... 211
Введение в Truffle ........................................................................................................................ 212
Установка Truffle ................................................................................................................. 212
Инициализация Truffle ........................................................................................................212
Компиляция контрактов ...................................................................................................... 214
Файлы конфигурации .......................................................................................................... 214
Развертывание контрактов .................................................................................................. 215
Файлы переноса .......................................................................................................... 216
Написание кода переноса ........................................................................................... 216
____________________________________

[9]

___________________________________

Оглавление

Юнит-тесты контрактов ...................................................................................................... 218
Написание тестов на JavaScript ................................................................................. 219
Написание тестов на Solidity ..................................................................................... 221
Как перевести валюту на тестовый контракт? ......................................................... 224
Запуск тестов ............................................................................................................... 224
Управление пакетами ..........................................................................................................225
Управление пакетами через NPM ............................................................................. 225
Управление пакетами через EthPM ........................................................................... 225
Использование контрактов из пакета ....................................................................... 227
Использование артефактов пакета в коде JavaScript ............................................... 227
Доступ к адресам развернутых контрактов пакета в Solidity ................................. 227
Работа с консолью Truffle ................................................................................................... 228
Запуск внешних скриптов в контексте Truffle .................................................................. 229
Создание клиента в Truffle.................................................................................................. 229
Запуск внешних команд ............................................................................................. 230
Запуск пользовательских функций ........................................................................... 230
Конструктор Truffle по умолчанию .......................................................................... 231
Создание клиента ........................................................................................................ 233
Сервер Truffle ....................................................................................................................... 236
Заключение................................................................................................................................... 238

Глава 9. Разработка блокчейна для консорциума ................................................ 239
Что такое блокчейн консорциума?............................................................................................. 240
Что такое консенсус с доказательством полномочий?............................................................. 240
Введение в Parity ......................................................................................................................... 241
Принципы работы Aura ....................................................................................................... 241
Начинаем работу с Parity .................................................................................................... 243
Установка Rust ............................................................................................................ 243
Скачивание, установка и запуск Parity ..................................................................... 244
Создание частной сети ........................................................................................................244
Создание аккаунтов .................................................................................................... 244
Создание файла спецификации ................................................................................. 244
Запуск узлов ................................................................................................................ 248
Подключение узлов .................................................................................................... 249
Полномочия и приватность................................................................................................. 249
Заключение................................................................................................................................... 250
Приложение. Описание электронного файлового архива ................................... 251
Предметный указатель ............................................................................................... 253

____________________________________

[ 10 ] ___________________________________

Предисловие
к русскому изданию
Уважаемые читатели!
Нам повезло жить в интересное время. На наших глазах и при нашем участии рождаются новые технологии, которые изменят мир: блокчейн и децентрализованные
приложения. Следует понимать, что сам по себе блокчейн ничего не значит. Революционные перемены зависят лишь от того, каким способом и в каких областях
мы будем его применять. Книга, которую вы держите в руках, — один из редких
образцов руководства по практическому применению технологии блокчейна.
Блокчейн вошел в нашу жизнь совсем недавно. Для этой технологии пока нет общепринятых законов и правил. Не удивительно, что возникают причудливые сочетания языков программирования и сред разработки, буквально в течение года успевают родиться и умереть новые протоколы и сервисы. Но уже настало время
делиться опытом и говорить об устоявшихся подходах. Автор этой книги, Нараян
Прасти, знает технологию блокчейна изнутри, потому что работает предметным
экспертом по блокчейну в Национальном банке Арабских Эмиратов и последние
пять лет занимается прикладной разработкой децентрализованных приложений.
Для продуктивной работы с материалом этой книги вам потребуются базовые знания и навыки в области программирования:
 основы объектно-ориентированного и функционального программирования.
Следует знать, что такое объекты, классы, методы, конструкторы, функции. Полезно иметь навыки программирования на языке C#;
 навык программирования на языке JavaScript. Это обязательное условие для
продуктивной работы с материалом книги. Программированию на JavaScript посвящено много хороших книг, и для вас не составит большого труда получить
необходимые знания;
 умение работать со средой разработки и выполнения скриптов Node.js. Вы можете приступить к освоению Node.js параллельно с чтением двух первых глав
этой книги.

Предисловие к русскому изданию

При подготовке перевода мы тщательно проверили доступность всех ссылок, номера версий программ и обновили их по мере необходимости. В сносках указаны
дополнительные ссылки и примечания, которые помогут в изучении материала.
В сопровождающем книгу файловом архиве вы найдете исходные коды упражнений к главам книги, предоставленные издательством «Paсkt», и листинги исходных
кодов примеров программ из текста.
Желаем успеха в практическом освоении новейшей технологии!
Переводчик и научный редактор русского издания
Валерий Яценков

____________________________________

[ 12 ] ___________________________________

Об авторе
Нараян Прасти (Narayan Prusty) — разработчик полного цикла, который в течение
последних пяти лет специализируется на технологиях блокчейн и JavaScript. С присущей ему целеустремленностью он разрабатывал масштабируемые приложения
для стартапов, правительства и предприятий в Индии, Сингапуре, США и ОАЭ.
Сегодня Нараян Прасти регулярно создает децентрализованные приложения на основе Ethereum, Bitcoin, Hyperledger, IPFS, Ripple и других протоколов. Он работает
предметным экспертом по технологии блокчейн в Национальном банке Арабских
Эмиратов, Дубаи.
Нараян Прасти уже написал две книги: Learning ECMAScript 6 и Modern JavaScript
Applications. Обе они опубликованы издательством «Packt»1.
Он немедленно приступает к работе, если видит интересную захватывающую проблему. В возрасте 18 лет он разработал поисковый движок для файлов MP3 и до
сегодняшнего дня создал много различных приложений, которыми пользуются
люди по всей Земле. Его отличает уникальное умение разрабатывать масштабные
проекты от начала до конца.
Сейчас Нараян видит свое предназначение в том, чтобы делать многие вещи проще,
быстрее и дешевле при помощи технологии блокчейн. Он также ищет возможности
предотвратить коррупцию, мошенничество и добивается открытости во всем мире
при помощи блокчейн-приложений.
Вы можете больше узнать о авторе в его блоге: http://qnimate.com и найти его
в сообществе LinkedIn: https://www.linkedin.com/in/narayanprusty/.

1

На русском языке издана одна из этих книг: Введение в ECMAScript 6. М.: ДМК Пресс, 2016. —
Здесь и далее примечания переводчика, если не указано иное.

О рецензентах
Имран Башир (Imran Bashir) получил степень магистра информационной безопасности в Королевском колледже Холлоуэй (Royal Holloway) при Лондонском университете и обладает опытом разработки программного обеспечения, управления инфраструктурой и ИТ-службами. Он входит в сообщества Института инженеров по
электротехнике и электронике (Institute of Electrical and Electronics Engineers, IEEE)
и Британского компьютерного общества (British Computer Society, BSC). Имран
имеет 16-летний опыт работы в государственном и финансовом секторах. Прежде
чем перейти в индустрию финансовых услуг, он работал над крупными ИТ-проектами для государственного сектора и занимал разные технические должности
в Лондоне — экономической столице Европы. Сейчас он вице-президент по технологиям в одном из инвестиционных банков Лондона.
Дэниел Крафт (Daniel Kraft) изучал математику и физику и получил степень доктора прикладной математики в Университете Граца, Австрия. Он подключился
к разработке криптовалют в 2013-м году, был ведущим разработчиком и научным
руководителем проектов Namecoin и Huntercoin в 2014-м году и опубликовал две
исследовательских статьи в рецензируемых журналах. Дэниел работает инженеромпрограммистом и является соучредителем компании Crypto Realities Ltd, которая
создает многопользовательские игровые миры на основе технологии блокчейн.
Гуран Торвекар (Gaurang Torvekar) получил степень доктора по информационным
системам в Сингапурском университете управления. Он соучредитель и технический директор компании Attores, расположенной в Сингапуре и обслуживающей
смарт-контракты. Гуран имеет богатый опыт разработки приложений для Ethereum
и Hyperledger. Он был докладчиком на нескольких конференциях по блокчейну,
провел множество курсов по технологии блокчейн в Политехническом университете Сингапура и является ментором по блокчейну хакатона1 Angelhack.
1

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

Издательство «Packt»
Знаете ли вы, что издательство «Packt» для каждой опубликованной книги предлагает электронные версии в форматах PDF и ePub? Купив печатную книгу, вы
сможете на сайте www.packtpub.com получить ее электронное издание со скидкой для владельцев печатной версии1. С вопросами обращайтесь по адресу;
customercare@packtpub.com.
На сайте www.packtpub.com вы также можете найти множество бесплатных технических публикаций, подписаться на бесплатные рассылки и получать уникальные скидки и предложения.
Наиболее востребованные навыки разработки программного обеспечения вы
можете получить на странице «Mapt» сайта издательства «Packt» по адресу:
https://www.packtpub.com/mapt.
«Mapt» предоставляет полный доступ ко всем книгам и видеокурсам издательства
«Paсkt», а также передовые инструменты, которые помогут вам в личном развитии
и карьерном росте.
Что дает подписка?
 Полнотекстовый поиск по всем книгам издательства «Packt».
 Возможность ставить закладки, копировать и распечатывать фрагменты текста

из книг.

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

1

Разумеется, это относится только к покупателям исходного, английского издания книги. — Ред.

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

О чем эта книга?
 Глава 1. Децентрализованные приложения — рассказывает о том, что такое

децентрализованные приложения и как они работают.
 Глава 2. Принципы работы Ethereum — рассказывает о том, как работает

Ethereum.
 Глава 3. Разработка смарт-контрактов — показывает, как писать смарт-

контракты и использовать интерактивную консоль Geth для размещения и передачи транзакций.
 Глава 4. Учимся работать с web3.js — знакомит с библиотекой web3.js, рас-

сказывает о том, как импортировать библиотеку, подключить ее к Geth и
использовать в среде Node.js или на стороне клиента.

Предисловие

 Глава 5. Разработка сервиса кошелька — демонстрирует, как создать сервис

кошелька и легко управлять Ethereum Wallets даже в режиме оффлайн. Мы воспользуемся для этого библиотекой LightWallet.
 Глава 6. Разработка платформы для смарт-контрактов — рассказывает

о том, как компилировать смарт-контракты при помощи web3.js и развертывать
их при помощи web3.js и EthereumJS.
 Глава 7. Приложение для ставок на результат матча — поясняет, как можно

использовать сервис Oraclize для выполнения HTTP-запросов из смарт-контрактов Ethereum, чтобы получить доступ к данным в Интернете. Мы также узнаем, как получать доступ к файлам, сохраненным в IPFS, научимся использовать
строковую библиотеку и получим другие полезные навыки.
 Глава 8. Разработка смарт-контрактов уровня предприятия — подробно

рассказывает, как использовать среду Truffle, которая значительно упрощает
разработку децентрализованных приложений корпоративного уровня.
 Глава 9. Разработка блокчейна для консорциума — рассказывает о специфи-

ческих особенностях разработки блокчейна для консорциумов.

Что понадобится
в дополнение к этой книге?
Вам понадобится персональный компьютер с операционной системой Windows 7
SP1+, 8, 10 или Mac OS X 10.8+.

Для кого эта книга?
Эта книга предназначена для JavaScript-разработчиков, которые хотят создавать
защищенные приложения для управления транзакциями и данными на основе
блокчейна и Ethereum. Читатели, которые интересуются криптовалютами и доверенными хранилищами данных, найдут эту книгу чрезвычайно полезной.

Обозначения
В этой книге вы найдете несколько вариантов оформления текста, которые соответствуют различным типам информации. Продемонстрируем несколько стилей текста
и поясним, что они обозначают.
Отдельные директивы программного кода, имена таблиц баз данных, имена файлов, расширения имен файлов, вводимые пользователем данные и строки Twitter
обозначены шрифтом Courier. Например:
Затем запустите приложение при помощи команды node app.js внутри каталога Final.
____________________________________

[ 20 ] ___________________________________

Предисловие

Фрагменты кода выглядят так:
var solc = require("solc");
var input = "contract x { function g() {} }";
var output = solc.compile(input, 1); // 1 activates the optimizer
for (var contractName in output.contracts) {
// logging code and ABI
console.log(contractName + ": " +
output.contracts[contractName].bytecode);
console.log(contractName + "; " +
JSON.parse(output.contracts[contractName].interface));
}

Текст, вводимый и выводимый в командной строке терминальных программ, обозначен полужирным шрифтом Courier, например:
# cp /usr/src/asterisk-addons/configs/cdr_mysql.conf.sample
/etc/asterisk/cdr_mysql.conf

Надписи на кнопках и элементах меню программ обозначены полужирным
шрифтом, например:
Снова выберите тот же самый файл и нажмите кнопку Get Info.
Таким значком обозначены важные примечания и комментарии.

Таким значком обозначены полезные советы и подсказки.

Обратная связь
Мы всегда благодарны читателям за отзывы. Расскажите нам, что вы думаете об
этой книге, что вам понравилось или не понравилось. Отзывы читателей помогают
нам готовить издания, которые действительно будут для вас полезны.
Для отправки отзыва общего плана достаточно написать нам по адресу электронной почты feedback@packtpub.com, указав название книги в теме письма.
Если вы хорошо разбираетесь в какой-либо теме и хотели бы написать книгу или
стать соавтором, прочтите руководство для авторов: www.packtpub.com/authors.
Для читателей русского издания
Читатели русского издания могут оставлять свои отзывы на странице книги на
сайте издательства «БХВ-Петербург» по адресу: www.bhv.ru или писать в издательство по адресу электронной почты: mail@bhv.ru.

____________________________________

[ 21 ] ___________________________________

Предисловие

Поддержка читателей
Поскольку вы стали правомочным обладателем книги издательства «Packt», мы
поможем вам извлечь максимальную пользу из покупки.

Скачивание исходных кодов программ
Вы можете скачать исходные коды программ после регистрации на сайте
www.packtpub.com. Независимо от места приобретения книги, зарегистрируйтесь
по адресу: https://www.packtpub.com/books/content/support и получите файлы
непосредственно на свою электронную почту1.
Для скачивания исходных кодов с сайта издательства «Packt» выполните следующие шаги:
1. Войдите под своим именем или зарегистрируйтесь на сайте.
2. Наведите указатель мыши на закладку SUPPORT в верхней части сайта.
3. Щелкните на пункте Code Download & Errata.
4. Введите название книги или часть названия в поле Search.
5. Выберите нужную книгу в результатах поиска.
6. Выберите в раскрывающемся поле место покупки книги.
7. Щелкните на ссылке Code Download, которая появится ниже этого поля.
После скачивания воспользуйтесь одним из следующих архиваторов для извлечения файлов из архива:
 WinRAR или 7-ZIP — для Windows;
 Zipeg, iZip или UnRarX — для Mac;
 7-Zip или PeaZip — для Linux.
Набор исходных кодов для этой книги также хранится на сайте GitHub по адресу:
https://github.com/PacktPublishing/Building-Blockchain-Projects.
У издательства «Packt» есть и другие пакеты исходных кодов для обширного каталога их книг и видеокурсов. Они доступны по адресу:
https://github.com/PacktPublishing/.

Цветные иллюстрации
Мы также предоставляем вам PDF-файл с цветными изображениями скриншотов
и диаграмм, приведенных в этой книге. Цветные изображения помогут вам лучше
понять, что имеется в виду. Вы можете скачать этот файл по ссылке:
https://www.packtpub.com/sites/default/files/downloads/
BuildingBlockchainProjects_ColorImages.pdf.
1

Напомним, что эти предложения относятся только к покупателям исходного, английского издания
книги. — Ред.

____________________________________

[ 22 ] ___________________________________

Предисловие

Электронный архив файлов для русского издания
Электронный архив с материалами к этой книге можно скачать с FTP-сервера издательства «БХВ-Петербург» по ссылке ftp://ftp.bhv.ru/9785977539760.zip или со
страницы книги на сайте www.bhv.ru (см. приложение).

Исправления
Несмотря на все усилия и аккуратную работу над книгами, ошибки все-таки иногда
проникают в текст. Если вы нашли ошибку в одной из наших книг — в тексте или
в программе — мы будем признательны за сообщение о ней. Сделав это, вы убережете других читателей от огорчения и поможете нам улучшить следующее издание
книги. Если вы нашли ошибку, пожалуйста, сообщите нам, зайдя на сайт по адресу:
http://www.packtpub.com/submit-errata. Выберите вашу книгу, щелкните на ссылке: Errata Submission Form и введите текст своего замечания. После проверки ваша поправка будет одобрена и размещена на сайте в разделе для соответствующей
книги.
Для просмотра ранее внесенных поправок перейдите по адресу: www.packtpub.com/
books/content/support и введите название книги в поле поиска. Информация
о правках размещена под заголовком Errata.

Пиратство
Хищение авторских материалов в Интернете стало общей проблемой для всех
средств массовой информации. В издательстве «Packt» очень серьезно относятся
к защите своих авторских прав и лицензий. Если вы обнаружили незаконную копию одного из наших изданий в любой форме, пожалуйста, незамедлительно сообщите нам физический адрес этого места или адрес веб-сайта, чтобы мы смогли
принять меры.
Связаться с нами для отправки ссылки на нелегальные материалы можно по адресу
электронной почты: copyright@packtpub.com.
Мы благодарны вам за помощь в защите наших авторов и наших усилий по разработке полезных материалов, которые мы создаем для вас.

Вопросы
Если у вас возникли затруднения с любыми аспектами использования этой книги,
обращайтесь по адресу: questions@packtpub.com, и мы постараемся переадресовать ваш вопрос для наилучшего решения проблемы.
Читатели русского перевода книги могут обращаться с вопросами и пожеланиями
по адресу издательства «БХВ-Петербург»: mail@bhv.ru.
____________________________________

[ 23 ] ___________________________________



Децентрализованные
приложения

Почти все интернет-приложения, с которыми мы имели дело, являются централизованными, то есть серверы такого приложения принадлежат определенной компании или лицу. В течение длительного времени разработчики создавали централизованные приложения, а пользователи их применяли. Но у такого подхода есть проблемы, мешающие создавать определенные типы приложений. Централизованные
приложения менее прозрачны, имеют конкретную точку отказа, не могут противостоять сетевой цензуре и т. д. На фоне этих проблем возникла новая технология
разработки интернет-приложений, которые называются децентрализованными приложениями (Decentralized Applications, DApps).
В этой главе мы будем говорить о децентрализованных приложениях (ДП) и рассмотрим следующие темы:
 что такое ДП?
 в чем разница между децентрализованными, централизованными и распределенными приложениями?
 преимущества и недостатки централизованных и децентрализованных приложений;
 обзор структуры данных, алгоритмов и протоколов, применяемых в наиболее
популярных ДП;
 некоторые популярные ДП, выстроенные поверх других децентрализованных
приложений.

Что такое ДП?
Децентрализованные приложения — это особая разновидность интернет-приложений, основанных на одноранговой сети (peer-to-peer network) и имеющих открытый исходный код. Ни один узел сети не имеет полного контроля над ДП.

Глава 1

Структура и способ хранения данных ДП зависят от его функционального назначения. Например, Bitcoin1 использует структуру данных в виде блокчейна.
Узлом одноранговой сети может стать любой компьютер, подключенный к Интернету. Это создает серьезную проблему, связанную с необходимостью обнаруживать
и блокировать узлы, вносящие ошибочные изменения в данные и распространяющие недостоверную информацию остальным узлам. Следовательно, мы должны
прийти к соглашению между узлами относительно того, являются ли достоверными
данные, опубликованные определенным узлом. В структуре ДП нет главного сервера, который координирует узлы и принимает решение о достоверности данных.
Для решения этой сложной задачи предназначены особые протоколы консенсуса.
Протоколы консенсуса разрабатывают под конкретную структуру данных ДП. Например, Bitcoin для достижения консенсуса использует протокол доказательства
работы (proof-of-work protocol, PoW).
Каждое ДП имеет клиентскую часть (программа-клиент), с которой работает пользователь. Чтобы получить доступ к ДП, мы должны стать узлом одноранговой сети
и запустить на своем компьютере специальный сервер узла, а затем подключить
к этому серверу клиент. Узлы приложения предоставляют только API (Application
Programming Interface, интерфейс прикладногопрограммирования) и позволяют
сообществу разработчиков создавать различные клиентские программы. Некоторые
разработчики ДП предоставляют пользователям официальную версию клиента.
Клиенты должны иметь открытый исходный код и быть доступными для скачивания без ограничений. В противном случае вся идея децентрализации теряет смысл.
Однако клиент-серверная архитектура может оказаться трудной для установки,
особенно если пользователь не имеет навыков разработчика. Поэтому обычно клиент и/или сервер просто запускаются как службы.
Что такое распределенное приложение?
Распределенные приложения работают на множестве серверов одновременно.
Это необходимо при наличии большого сетевого трафика и объема данных, особенно если отказ в обслуживании приложения недопустим. В распределенных
приложениях данные реплицируются между разными серверами, чтобы обеспечить высокую доступность данных. Централизованные приложения могут быть
распределенными, а могут и не быть таковыми. Но децентрализованные приложения всегда распределенные. Например, Google, Facebook, Slack, Dropbox и им
подобные являются распределенными, а простой сайт с портфолио или персональный блог — нет, пока трафик не слишком большой.

1

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

____________________________________

[ 26 ] ___________________________________

Децентрализованные приложения

Достоинства
децентрализованных приложений
Приведем некоторые достоинства ДП:
 устойчивость к отказам, поскольку уязвимая точка отказа отсутствует по опре-

делению;
 неуязвимость от сетевой цензуры, поскольку нет центрального органа, на кото-

рый правительство могло бы надавить и потребовать удалить данные. Правительство не может даже заблокировать домен или IP-адреса приложения, поскольку ДП не нуждается в конкретных адресах или доменах. Конечно же, правительство может отследить отдельного пользователя сети по IP-адресу и
отключить его. Но если одноранговая сеть достаточно велика, то полное отключение приложения становится непосильной задачей, особенно если узлы разбросаны по разным странам;
 пользователям легче доверять приложению, которое не управляется одним

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

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

обновлено на каждом узле одноранговой сети;
 иногда приложения требуют подтвердить личность пользователя (user identity).

Поскольку нет центрального органа, заверяющего личность пользователя, разработка некоторых ДП превращается в серьезную проблему;
 трудность разработки — необходимо применять очень сложные протоколы дос-

тижения консенсуса и предусмотреть масштабирование с самого начала. Иначе
говоря, мы не можем наспех реализовать идею, надеясь когда-нибудь потом
добавлять новые функции и расширять приложение;
 обычно приложения не нуждаются в сторонних API для сохранения или получе-

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

Децентрализованная автономная организация
Мы привыкли к тому, что организацию олицетворяет наличие подписанных документов, и правительство может на нее влиять. В зависимости от типа организации,
она может иметь или не иметь акционеров.
____________________________________

[ 27 ] ___________________________________

Глава 1

Децентрализованная автономная организация (Decentralized Autonomous Organization, DAO, ДАО) — это организация, которая представлена обществу компьютерной программой (то есть действует в соответствии с правилами, заложенными
в программу), полностью прозрачна, полностью подконтрольна акционерам и не
зависит от правительства.
Для достижения этих целей мы должны разрабатывать ДАО как децентрализованное приложение. Поэтому можно сказать, что ДАО является подклассом ДП.
Что такое децентрализованная автономная корпорация (ДАК, DAC)?
Между понятиями ДАО и ДАК нет явного различия. Одни утверждают, что это
одно и то же, другие считают, что ДАК — это разновидность ДАО, созданная специально для получения прибыли акционерами.

Личность пользователя в ДП
Одним из основных преимуществ ДП является гарантия анонимности пользователя. Но многие приложения нуждаются в проверке личности пользователя перед началом работы. Поскольку центрального органа верификации не существует, подтверждение личности пользователя становится проблемой.
В централизованных приложениях человек подтверждает свою личность отправкой
отсканированных документов, звонком по телефону и т. д. Этот процесс получил
название «знай своего клиента» (Know Your Customer, KYC). Но в структуре ДП
нет человека, который бы проверял личность. Поэтому приложение вынуждено делать это самостоятельно. Очевидно, ДП не может понять и проверить отсканированные документы2 или отправить SMS. Поэтому мы должны предоставить приложению цифровой идентификатор, который ДП сможет понять и проверить. Основная проблема сегодня заключается в том, что не каждое ДП работает с цифровой
идентификацией, и лишь редкие пользователи знают, как получить цифровой идентификатор.
Существуют различные формы цифровых идентификаторов. На сегодняшний день
наиболее популярной и рекомендованной формой является цифровой сертификат,
который также называют сертификатом открытого ключа (public key certificate)
или удостоверением личности (identity certificate). Это электронный документ,
устанавливающий право собственности на открытый ключ. В общем случае пользователь имеет закрытый ключ, открытый ключ и цифровой сертификат. Пользователь хранит закрытый ключ в тайне и никому его не показывает. Открытый ключ
может быть показан кому угодно. Цифровой сертификат содержит публичный
ключ и информацию о том, кто владелец ключа. Изготовить такой сертификат не
2

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

____________________________________

[ 28 ] ___________________________________

Децентрализованные приложения

составляет труда. Поэтому цифровой сертификат всегда выдается уполномоченным
лицом, которому вы можете доверять, — заверяющим органом. Цифровой сертификат содержит поле, зашифрованное закрытым ключом заверяющего органа. Чтобы удостовериться в подлинности сертификата, нам достаточно расшифровать поле
открытым ключом заверяющего органа. Если расшифровка прошла успешно, мы
убеждаемся в подлинности сертификата.
Даже если пользователь успешно получил цифровой идентификатор, и он принят
приложением, это не решает основную проблему. Существуют различные заверяющие органы, выпускающие свои сертификаты. В самом деле, трудно вовремя
добавлять или обновлять ключи, чтобы поддерживать актуальный набор ключей
всех заверяющих органов. По этой причине проверку цифрового идентификатора
обычно реализуют на стороне клиента — клиентское приложение проще обновить.
Простой перенос проверки на сторону клиента не полностью решает проблему, потому что существует множество заверяющих органов, и механизм добавления их
ключей на клиентской стороне получается довольно громоздким.
Почему пользователи не могут проверять
идентичность друг друга?
Зачастую, совершая торговые сделки в реальной жизни, мы самостоятельно проверяем идентичность другого лица или доверяем эту работу другим. Такой же
подход можно применить и к ДП. Пользователи перед тем, как совершить сделку,
могут проверить идентификаторы друг друга. Этот подход работает лишь для
определенных типов ДП, в которых люди торгуют друг с другом. Например, если
ДП является децентрализованной социальной сетью, то идентификатор не может
быть проверен подобным образом. Но если ДП предназначен для людей, которые что-то покупают/продают, то перед оплатой покупатель и продавец могут
взаимно проверить идентификаторы. Эта идея привлекательно выглядит, но с
трудом реализуется на практике, потому что вам не захочется выполнять процедуру проверки при каждой сделке, и не каждый знает, как это делается. Например, если ДП используется для заказа такси, то вы, очевидно, не захотите
выполнять проверку при каждом бронировании. Но если вы изредка совершаете
торговые сделки и знаете, как проверять идентификатор, будет вполне приемлемо следовать этой процедуре.

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

Фиатные деньги также носят наименование деньги декретные. Происходит это название от латинского слова fiat — указ, декрет, дословное значение «Да будет так». Существуют в различных видах:
бумажные банкноты и безналичные деньги, деньги электронные и монеты. — Ред.
____________________________________

[ 29 ] ___________________________________

Глава 1

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

Учетные записи пользователей
Приложения часто используют механизм учетных записей. Данные, связанные
с учетной записью, должны быть доступны для редактирования только владельцу
учетной записи. Децентрализованные приложения не могут обеспечить эту функциональность на основе пары логин-пароль, потому что наличие пароля еще не доказывает, что изменение учетной записи было запрошено владельцем.
Существует несколько способов реализации учетных записей пользователей в ДП.
Но самый популярный путь — это использование пары открытого и закрытого
ключей для представления владельца учетной записи. Хеш открытого ключа является уникальным идентификатором учетной записи. Чтобы внести правки в учетную запись, пользователь должен подписать изменения своим закрытым ключом.
Мы должны полагать, что пользователи хранят свои закрытые ключи надежно
и безопасно. Если пользователи теряют свои секретные ключи, они навсегда теряют доступ к своей учетной записи.

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

[ 30 ] ___________________________________

Децентрализованные приложения

Итак, в некоторых случаях ДП вынуждено получать данные из централизованного
приложения. Но вот проблема — как ДП может убедиться, что данные не подменили при передаче, и они являются актуальным ответом на запрос? Есть несколько
способов решить эту проблему в зависимости от архитектуры ДП. Например,
в Ethereum смарт-контракты, которые нуждаются в доступе к централизованным
API, могут в качестве посредника воспользоваться сервисом Oraclize, потому что
смарт-контракты не выполняют прямые HTTP-запросы. А сервис Oraclize предоставляет доказательство подлинности TLSNotary для данных, собираемых с централизованных сервисов.

Внутренняя валюта приложений
Чтобы централизованное приложение сохраняло работоспособность в течение длительного времени, его владельцу нужно как-то извлекать прибыль. У ДП нет владельца, но все равно узлы ДП нуждаются в оборудовании и сетевых ресурсах4 для
поддержания работы. То есть узлы ДП должны приносить какую-то выгоду в обмен
на усилия по поддержанию их работоспособности. Именно здесь вступает в игру
внутренняя валюта. Большинство ДП имеют встроенную внутреннюю валюту.
Точнее, большинство успешных ДП имеют встроенную внутреннюю валюту.
Протокол консенсуса определяет, сколько валюты получает узел. В зависимости от
протокола, только определенные узлы получают валюту. Мы также можем сказать,
что лишь те узлы, которые способствуют поддержанию и управлению ДП, зарабатывают валюту. Узлы, которые только считывают данные, не получают ничего.
Например, в Bitcoin только майнеры зарабатывают биткойны за успешно добытые
блоки.
Самый важный вопрос о цифровой валюте — почему ее вообще кто-то ценит? Согласно экономической теории, все, что имеет достаточный спрос и недостаточное
количество, имеет ценность.
Заставляя пользователей расплачиваться внутренней валютой приложения, мы решаем проблему спроса. Чем больше пользователей используют ДП, тем больше
спрос на валюту и тем выше она ценится.
Установление фиксированного объема выпускаемой валюты приводит к ее нехватке и повышает стоимость5. Обычно валюта вводится в оборот постепенно, чтобы
вновь подключившиеся узлы тоже имели возможность заработать.

4

А также в обслуживающем персонале, который в общем случае не согласен работать бесплатно.

5

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

____________________________________

[ 31 ] ___________________________________

Глава 1

Недостатки внутренней валюты
Единственным недостатком внутренней валюты является то, что ДП не могут быть
бесплатными для всех. Это как раз тот случай, когда централизованные приложения берут верх, потому что они могут быть монетизированы за счет рекламы, предоставления премиальных API для сторонних приложений и т. п. Поэтому они могут быть бесплатными для обычных пользователей. Мы не можем интегрировать
рекламу в ДП, потому что некому проверять рекламу на соответствие стандартам6.
Клиенты могут не отображать рекламу, потому что не имеют выгоды от показа
объявлений.

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

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

Bitcoin
Биткойн — это децентрализованная валюта. Кроме того, это децентрализованное
приложение, успех которого показал, насколько успешным может быть ДП, и побудил других людей к созданию собственных приложений.

6

Технически не трудно учредить доверенного рекламного агента в области ДП. Однако не ясно, как
обеспечить показ рекламы в клиентах с открытым исходным кодом, где любой желающий может удалить модуль показа рекламы.

____________________________________

[ 32 ] ___________________________________

Децентрализованные приложения

Прежде, чем мы начнем подробно обсуждать, как работает протокол Bitcoin, и
почему люди и правительства считают биткойн валютой, мы должны узнать, что
такое регистр (ledger) и блокчейн (blockchain).

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

Что такое блокчейн?
Блокчейн — это особая структура данных, применяемая для создания децентрализованного регистра7. Блокчейн состоит из блоков (block), особым образом соединенных в цепочку (chain). Блок содержит набор транзакций, хеш предыдущего блока, метку времени (время создания блока), сумму отчисления майнеру за блок
и т. д. Поскольку каждый блок содержит хеш предыдущего блока, они связаны
в цепочку. Каждый узел сети хранит полную копию блокчейна.
Для поддержания безопасности блокчейна применяют протоколы с доказательством выполнения работы (proof-of-work, PoW), с доказательством владения долей
(proof-of-stake, PoS) и некоторые другие. Добавление блока происходит поразному — в зависимости от протокола. В случае с протоколом proof-of-work блок
создается при помощи процедуры, которая называется майнингом и обеспечивает
безопасность добавления блока. Майнер должен решить сложную математическую
задачу и затратить дорогостоящие вычислительные ресурсы. Далее в этой книге мы
более подробно поговорим о блокчейне и протоколах консенсуса.
В сети Bitcoin блокчейн хранит транзакции Bitcoin. Биткойны (монеты) вводятся
в оборот путем выплаты вознаграждения майнерам, успешно создавшим новые
блоки.
Основным преимуществом блокчейна является автоматизация контроля над
безопасностью транзакций. Блокчейн предотвращает мошенничество и злоупотребления и может решить множество других проблем, в зависимости от способа
реализации и использования.

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

7

Децентрализованный распределенный регистр (decentralized and distributed ledger) определяют как
способ реализации списка транзакций, хранящихся в децентрализованной форме без привязки к географической или иной принадлежности узлов сети.

____________________________________

[ 33 ] ___________________________________

Глава 1

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

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

денег людьми;
 используя виртуальные валюты, можно легко уходить от налогов.

Почему мы используем биткойн?
Cеть Bitcoin предназначена исключительно для того, чтобы отправлять/получать
биткойны, и ни для чего иного. Вы можете недоумевать — почему вообще существует потребность в биткойне?
Вот несколько причин, по которым люди используют сеть Bitcoin:
 основная выгода от использования биткойна — это простой и быстрый способ

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

в сети Bitcoin;
 хакеры могут украсть вашу платежную информацию у оператора обычной пла-

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

Ethereum
Ethereum — это децентрализованная платформа, на которой можно запускать приложения в виде смарт-контрактов (smart contract, умный контракт). Приложение
может состоять из одного или нескольких смарт-контрактов.
Смарт-контракт Ethereum — это программа, которая выполняется в сети Ethereum и
работает исключительно так, как запрограммировано, без риска простоя, цензуры,
мошенничества и вмешательства третьей стороны.
Главное преимущество платформы Ethereum для выполнения смарт-контрактов
заключается в том, что контракты могут легко взаимодействовать друг с другом.
Более того, вам не надо беспокоиться об интеграции протокола консенсуса и других вещах — напротив, вам всего лишь нужно написать логику приложения. Оче____________________________________

[ 34 ] ___________________________________

Децентрализованные приложения

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

Hyperledger
Hyperledger — это проект, посвященный разработке технологии контролируемых
ДП. Hyperledger Fabric (или просто Fabric) — реализация проекта Hyperledger.
К другим реализациям относятся Intel Sawtooth и R3 Corda.
Fabric — это контролируемая децентрализованная платформа, которая позволяет
выполнять КДП, именуемые чейнкодами (chaincode). Мы должны развернуть собственную единицу Fabric и запустить поверх нее КДП. На каждом узле сети работает единица Fabric. Она действует по принципу plug-and-play (подключай и пользуйся) и позволяет с легкостью подключать различные функции и протоколы консенсуса.
Hyperledger использует структуру данных блокчейна. На данный момент блокчейны проекта Hyperledger позволяют выбрать вариант без консенсуса (протокол
NoOps) или протокол консенсуса PBFT (Practical Byzantine Fault Tolerance, решение
устойчивое к византийской ошибке8). Hyperledger имеет специальный удостоверяющий узел (certificate authority), который решает, кто может подключиться к сети
и на какие действия имеет право.

IPFS
IPFS (InterPlanetary File System, Межпланетная файловая система) — это децентрализованная файловая система. В основу IPFS заложены принципы DHT (distributed
hash table, распределенная хеш-таблица) и Merkle DAG (directed acyclic graph,
направленный ациклический граф). IPFS использует такой же протокол, как
BitTorrent, чтобы распределять данные по сети. Одной из ключевых опций IPFS
является поддержка версий, реализованная на основе механизма Git.
Хотя IPFS называют децентрализованной файловой системой, она не обладает
главным свойством файловой системы. Если мы сохранили что-то в обычную файловую систему, то уверены, что файл будет храниться, пока мы его не удалим. Но

8

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

____________________________________

[ 35 ] ___________________________________

Глава 1

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

Как работает IPFS?
Когда мы сохраняем файл в IPFS, он разбивается на фрагменты (chunks) размером
менее 256 Кбайт, и для каждого фрагмента вычисляется хеш. Узлы сети хранят
файлы, которые им нужны, а также таблицу хешей.
Существует четыре типа файлов IPFS: блоб9 (blob), список (list), дерево (tree) и
снимок состояния (commit). Блоб содержит фрагмент файла, сохраненного в IPFS.
Список представляет собой законченный файл, содержащий список блобов и другие списки. Так как список может содержать другие списки, это помогает сжать
данные в сети. Дерево представляет собой каталог, который содержит список блобов, списки, другие деревья и снимки состояния. И, наконец, снимок состояния содержит снимок (snapshot) истории изменений любого другого файла. Поскольку
списки, деревья и снимки связаны с другими файлами IPFS, они формируют граф
Merkle DAG.
Если мы хотим скачать файл из сети, нам нужно знать только хеш файла списка.
Или, если мы хотим скачать каталог, нам просто нужен хеш файла дерева.
Поскольку каждый файл идентифицируется хешем, имена невозможно запомнить.
Если мы обновим файл, то должны поделиться новым хешем со всеми, кто хочет
загрузить этот файл. Чтобы справиться с проблемой, файловая система использует
функцию IPNS (InterPlanetary Name System, Межпланетная система имен), которая
позволяет присваивать файлам в IPFS имена с самоподписанными сертификатами
или имена, удобные для людей.

Filecoin
Главная причина, которая мешает IPFS стать децентрализованной файловой системой, заключается в том, что узлы сети хранят только те файлы, которые им нужны.
Filecoin — это децентрализованная файловая система, аналогичная IPFS, но имеющая внутреннюю валюту. Эта валюта поощряет узлы за хранение файлов, что повышает доступность данных и делает Filecoin более похожей на файловую систему.
Узлы сети добывают монеты Filecoin, предоставляя в аренду дисковое пространство, а для хранения/извлечения файлов вы должны заплатить этой валютой10.
9

Binary Large Object Block — большой двоичный блок.
По схожему принципу работает файловая система проекта Storj.io.

10

____________________________________

[ 36 ] ___________________________________

Децентрализованные приложения

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

Namecoin
Namecoin — это децентрализованная база данных ключ-значение. Она тоже имеет
внутреннюю валюту Namecoin, использует блокчейн и протокол доказательства
работы.
В базе данных Namecoin вы можете хранить пары данных ключ-значение. Для регистрации пары вы должны потратить валюту. Зарегистрировавшись, вы должны
обновлять регистрацию каждые 35 999 блоков, иначе связь ключей с данными устареет. За обновление вы также должны заплатить внутренней валютой. Нет необходимости обновлять сами ключи, то есть вам не надо тратить валюту на сохранность ключей после регистрации.
Namecoin поддерживает пространство имен, и пользователи могут упорядочить
различные типы ключей. Кто угодно может создать свое пространство имен или
использовать имеющееся для упорядочивания ключей.
К популярным пространствам имен относятся a (application-specific data, прикладные данные), d (domain name, имя домена), ds (secure domain name, имя безопасного домена), id (identity, личность), is (secure identity, подтвержденная личность),
p (product, продукт) и некоторые другие.

Домены в зоне .bit
Для доступа к сайту браузер первым делом находит IP-адрес, связанный с именем
домена. Таблица связей между доменными именами и фактическими IP-адресами
хранится на серверах DNS, которые контролируются большими компаниями и правительствами. Следовательно, доменные имена уязвимы для цензуры. Правительства и компании обычно блокируют доменные имена, если сайт связан с чем-то незаконным, наносит какой-либо ущерб или по иным причинам.
В связи с этим существует потребность в децентрализованной базе данных доменных имен. Поскольку Namecoin хранит пары ключ-значение наподобие сервера
DNS, то может применяться для создания децентрализованной службы DNS. Так
оно и есть на самом деле. Пространства имен d и ds содержат ключи, которые
заканчиваются на .bit и представляют доменные имена в зоне .bit. Пространство
имен не содержит никаких технических соглашений для именования ключей, но
все узлы и клиенты сети Namecoin согласились поддерживать это именование. Если
вы попытаетесь сохранить в пространствах имен d и ds неправильные ключи, то
клиенты их отфильтруют.
Браузер, который поддерживает домены .bit, должен провести поиск в пространствах d и ds, чтобы найти IP-адрес, соответствующий имени домена в зоне .bit.
____________________________________

[ 37 ] ___________________________________

Глава 1

Разница между пространствами имен d и ds заключается в том, что ds содержит
домены, которые поддерживают сертификат TLS (Transport Layer Security, безопасность на уровне передачи), а пространство d содержит домены, которые не поддерживают TLS. Мы сделали сервис DNS децентрализованным, и аналогичным способом мы можем выпускать децентрализованные сертификаты TLS.
Как TLS работает в Namecoin? Пользователи выпускают самоподписанные сертификаты и сохраняют хеш сертификата в Namecoin. Когда клиент с поддержкой доменов .bit пытается получить доступ к безопасному домену, он сравнивает хеш
сертификата, возвращенного сервером с хешем, который хранится в Namecoin.
Если хеши совпали, браузер продолжает поддерживать связь с сервером.
Создание децентрализованной службы DNS на основе Namecoin является первым решением для так называемого треугольника Зуко (Zooko triangle), согласно
которому приложения могут иметь три качества: децентрализованность, полез11
ность для человека и безопасность . Цифровой идентификатор может представлять не только личность, но также домен, компанию или что-то еще.

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

совершать транзакции мгновенно. Причина в том, что сложность майнинга в сети Bitcoin автоматически настраивается таким образом, чтобы новый блок создавался в среднем один раз в 10 минут. Далее в этой книге мы поговорим о майнинге более подробно;
 несмотря на то, что счета Bitcoin не связаны с личностями, торговля биткойнами

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

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

____________________________________

[ 38 ] ___________________________________

Децентрализованные приложения

В сети Bitcoin существуют два вида узлов: майнеры и обычные узлы. Однако в сети
Dash существуют три типа узлов: майнеры, главные узлы (master node) и обычные
узлы (ordinary node). Наличие главных узлов является отличительной особенностью
Dash.

Децентрализованное управление и бюджетирование
Чтобы развернуть главный узел, вам нужно иметь 1000 монет Dash и статический
IP-адрес. В сети Dash как майнеры, так и главные узлы могут зарабатывать валюту.
Когда блок вычислен, 45% вознаграждения получает майнер, 45% — главный узел
и оставшиеся 10% уходят в систему бюджетирования.
Главные узлы осуществляют децентрализованное управление и бюджетирование.
Исходя из этого, мы можем сказать, что Dash — это децентрализованная автономная организация.
Главные узлы сети выступают в качестве акционеров. Они имеют право решать,
куда потратить 10% валюты. Обычно эти 10% монет направляют на финансирование других проектов путем голосования. У каждого главного узла есть один голос.
Обсуждение предлагаемых проектов ведется за пределами сети Dash. Но голосование происходит непосредственно в сети.
Главные узлы могут являться вариантом решения проблемы личности пользователя. Главный узел может выбрать узел, которому доверяет проверку идентичности пользователя. Лицо или организация, которая владеет этим узлом, может
вручную проверить документы пользователя и получить за это определенное
вознаграждение. Если узел не обеспечивает хорошее качество сервиса идентификации, главные узлы могут проголосовать за другой узел. Это могло бы стать
хорошим решением проблемы идентификации пользователей.

Децентрализованные услуги
Главные узлы не только утверждают или отвергают предложения, но и образуют
сервисный уровень, который предоставляет различные услуги. Причина, по которой главные узлы предоставляют доступ к услугам, состоит в том, что чем больше
услуг они предоставляют, тем функциональнее становится сеть, тем больше пользователей приходят в сеть со своими транзакциями, а это повышает ценность монет
Dash и увеличивает размер вознаграждения. Таким образом, главные узлы увеличивают свой доход.
Главные узлы предоставляют такие услуги, как PrivateSend (услуга анонимных переводов в смешанных валютах), InstantSend (услуга практически мгновенных транзакций), DAPI (услуга децентрализованного API, благодаря которому обычный
пользователь может не запускать собственный узел) и т. д.
В настоящее время могут быть одновременно задействованы только 10 главных
узлов. Алгоритм выбора использует хеш текущего блока, чтобы выбрать главные
узлы. Затем он запрашивает у них услугу. Одинаковый ответ, полученный от боль____________________________________

[ 39 ] ___________________________________

Глава 1

шинства узлов, подтверждает корректность услуги. Таким образом достигается
консенсус относительно услуг, предоставляемых главными узлами.
Протокол подтверждения услуги (proof-of-service) гарантирует, что главные узлы
доступны в сети, отвечают и содержат актуальный блокчейн.

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

OpenBazaar
OpenBazaar — это децентрализованная платформа для электронной коммерции.
Используя OpenBazaar, вы можете покупать и продавать товары. Пользователи сети
OpenBazaar не являются анонимными, потому что их IP-адреса записываются. Узел
может быть покупателем, продавцом или арбитром (модератором).
OpenBazaar использует структуру распределенной хеш-таблицы по типу проекта
Kademlia. Продавец должен постоянно поддерживать работу своего узла, чтобы его
товары были видны в сети.
Чтобы предотвратить массовое создание аккаунтов, применяется протокол доказательства работы. OpenBazaar препятствует массовой «накрутке» рейтингов и обзоров при помощи протоколов доказательства уничтожения (proof-of-burn) и доказательства залога (proof-of-timelock).
Для взаиморасчетов между продавцом и покупателем используют биткойны. Покупатель может пригласить арбитра при соглашении покупки. Арбитр отвечает за
рассмотрение споров, которые могут возникнуть между продавцом и покупателем.
Арбитром может стать любой желающий. За вынесение решения по спору арбитр
получает вознаграждение12.

Ripple
Ripple — это децентрализованная платежная платформа, позволяющая переводить
фиатные деньги, криптовалюты и даже товары13 (commodity). Она использует блок12

В виде небольшой доли от суммы сделки.
Предметом транзакции может быть любая единица ценности (value), например баррели нефти, мили
для пассажиров или минуты мобильной связи.
13

____________________________________

[ 40 ] ___________________________________

Децентрализованные приложения

чейн и собственный протокол консенсуса. В документации Ripple вы не найдете
терминов «блок» и «блокчейн». Вместо них используется термин «реестр».
Транзакция Ripple проводится через цепочку доверия (trust chain) наподобие созданной ранее сети Hawala. В сети Ripple существуют два типа узлов: шлюзы и
обычные узлы. Шлюзы поддерживают депозит и списание в одной или нескольких
валютах и/или активах. Чтобы стать шлюзом в сети Ripple, вы должны обладать
разрешением должного уровня для формирования цепочки доверия. Шлюзы обычно создаются финансовыми организациями, биржами, продавцами и т. п.
Каждый пользователь и шлюз имеет адрес учетной записи. Пользователь должен
сформировать список шлюзов, которым он доверяет, путем добавления адресов
шлюзов в доверительный список (trust list). Протокол консенсуса для выбора доверенных шлюзов не предусмотрен. Каждый пользователь на свой риск выбирает
шлюзы, которым будет доверять. Шлюзы тоже могут составить список шлюзов,
которым они доверяют.
Давайте рассмотрим на примере, как пользователь X, живущий в Индии, может перевести 500 долларов пользователю Y, проживающему в США. Допустим, в Индии
есть шлюз XX, который принимает деньги (физические наличные или платеж банковской картой через веб-сайт) и формирует ripple-баланс только в индийских
рупиях. Пользователь X приходит в офис XX или посещает веб-сайт и вносит на
депозит 30 тысяч рупий. После чего шлюз XX запускает в сеть широковещательное
сообщение: «Я должен X 30 тысяч рупий». Теперь допустим, что в США есть
шлюз YY, который обслуживает только долларовые транзакции и которому доверяет пользователь Y. Однако шлюзы XX и YY не доверяют друг другу14. Итак, X и
Y не имеют общего доверенного шлюза, XX и YY не доверяют друг другу, и вдобавок XX и YY поддерживают разные валюты. Следовательно, если X хочет перевести деньги Y, ему нужен шлюз-посредник, чтобы сформировать цепочку доверия.
Пусть это будет шлюз ZZ, который пользуется доверием XX и YY и работает как
с долларами, так и с рупиями. Теперь X может провести транзакцию по переводу
30 тысяч рупий из XX в ZZ, который конвертирует рупии в доллары. Затем ZZ
переводит деньги в YY и требует отдать их Y. Теперь получается, что вовсе не X
должен 500 долларов Y, а YY должен 500 долларов Y, ZZ должен 500 долларов YY
и XX должен 30 тысяч рупий ZZ. Но это вполне нормально, потому что каждый из
них доверяет своему контрагенту, даже если X и Y не доверяют друг другу. При
этом XX, YY и ZZ могут фактически перевести деньги за пределами ripple-сети
когда захотят, либо ждать, пока обратные транзакции обнулят эти суммы15.
Ripple тоже имеет внутреннюю валюту, обозначаемую как XPR (ripple, рипл). Каждая транзакция, отправленная в сеть, обходится в некоторое количество риплов.
14
Это не значит, что владельцы шлюзов не верят друг другу. Просто их адреса по какой-то причине
оказались не внесены в доверительные списки.
15
Важный момент — в сети Ripple не пересылаются деньги, а происходит обмен долговыми расписками между звеньями доверительной цепочки. Погашение долговых расписок может происходить как
угодно. Например, с привлечением обычных платежных систем и банков.

____________________________________

[ 41 ] ___________________________________

Глава 1

Будучи внутренней валютой, XPR может быть отправлена кому угодно в сети без
доверия. Валюту XPR можно использовать при формировании цепочки доверия.
Помните, что каждый шлюз имеет собственный обменный курс. Риплы не добывают майнингом. Напротив, при запуске проекта было сгенерировано 100 миллиардов
монет, которые принадлежат самой компании. Монеты вводятся в оборот вручную
в зависимости от разных факторов.
Все транзакции хранятся в децентрализованном реестре, который формирует неизменяемую историю. Консенсус гарантирует, что в определенный момент времени
все узлы имеют одинаковый реестр. В протоколе Ripple предусмотрен третий тип
узла — валидаторы, которые являются частью протокола консенсуса. Валидаторы
отвечают за проверку транзакций. Любой узел имеет право стать валидатором, но
другие узлы хранят список валидаторов, которым доверяют. Этот список называется UNL (unique node list, уникальный список узлов). Валидаторы тоже хранят UNL,
то есть узлы, которые являются валидаторами, должны достичь консенсус между
собой. В настоящее время Ripple назначает список доверенных валидаторов, но
если сеть решает, что кто-то недостоин доверия, любой узел может модифицировать свой экземпляр списка.
Вы можете сформировать реестр, взяв предыдущий реестр и применив все транзакции, которые произошли с тех пор. Чтобы принять текущий реестр, узлы должны
принять предыдущий реестр и набор транзакций. После созданиянового реестра
обычные узлы и валидаторы запускают таймер (приблизительно на 5 секунд) и собирают транзакции, которые поступили с момента создания предыдущего реестра.
Когда время таймера истекло, узлы берут те транзакции, с которыми согласны не
менее 80% валидаторов из списка UNL, и формируют новый реестр. Валидаторы
рассылают по сети предложение (список транзакций, которые по их мнению можно
включить в следующий реестр). Валидаторы могут рассылать предложения несколько раз подряд и с разным набором транзакций, если они вдруг решили изменить список действительных транзакций в зависимости от предложений других
членов списка UNL или других факторов. Иными словами, вам надо подождать
5–10 секунд, пока ваша транзакция будет подтверждена сетью.
Некоторые люди задаются вопросом: если каждый узел может иметь свою версию
UNL, не приведет ли это к появлению множества версий реестра? До тех пор, пока
существует минимальная связь между разными UNL, консенсус будет быстро достигнут. По большей части эта уверенность основана на том, что первейшей целью
каждого честного узла является достижение консенсуса16.

16
Критики протокола Ripple считают, что опасность ветвления реестра заложена в саму основу протокола и ссылаются на пример компании Stellar Foundation, которая в 2014 году пережила крах сети.
Если консенсус не достигнут, то может возникнуть ответвление реестра, созданное частью сети, несогласной с транзакциями.

____________________________________

[ 42 ] ___________________________________

Децентрализованные приложения

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

____________________________________

[ 43 ] ___________________________________



Принципы работы
Ethereum

Из предыдущей главы мы узнали, что такое децентрализованные приложения,
и познакомились с обзором некоторых популярных приложений. Одним из них является Ethereum. Сегодня Ethereum занимает второе место по популярности после
Bitcoin. В этой главе мы детально разберемся в том, как работает Ethereum, и какие
приложения мы можем разработать на его основе. Мы также рассмотрим наиболее
важные клиентские программы и реализации узлов платформы Ethereum.
В этой главе будут раскрыты следующие темы:
 учетные записи пользователей Ethereum;
 что такое смарт-контракты и как они работают?
 виртуальная машина Ethereum;
 как работает майнинг в протоколе консенсуса?
 использование команд консольного приложения Geth;
 настройка приложений-клиентов Ethereum Wallet и Mist;
 обзор протокола связи Whisper и платформы для хранения данных Swarm;
 перспективы Ethereum.

Знакомство с Ethereum
Ethereum — это децентрализованная платформа, поверх которой можно разворачивать децентрализованные приложения. Смарт-контракт (smart contract, умный контракт) — это программа, которая выполняется исключительно так, как запрограммировано, без какой-либо возможности простоя, цензуры, мошенничества и вмешательства третьей стороны. Смарт-контракты для платформы Ethereum могут быть
написаны на различных языках программирования, включая Solidity, LLL и Serpent.

Принципы работы Ethereum

Наиболее популярен язык Solidity. Ethereum имеет внутреннюю валюту, которая
называется эфир (ether). Для того чтобы развернуть на платформе смарт-контракт
или вызвать его методы, нам нужен эфир. Может существовать несколько экземпляров смарт-контракта или приложения, и каждый экземпляр идентифицируется по
его уникальному адресу. Как счета пользователей, так и смарт-контракты могут
хранить эфир.
Ethereum основан на структуре данных блокчейна и протоколе консенсуса с доказательством выполнения работы. Метод смарт-контракта может быть вызван через
транзакцию или через другой метод. В сети есть два типа узлов: майнеры и обычные узлы. Обычные узлы просто хранят копию блокчейна, а майнеры строят блокчейн, вырабатывая блоки.

Учетная запись Ethereum
Для создания учетной записи Ethereum нам требуется пара асимметричных ключей.
Ключи шифрования могут генерироваться на основе различных алгоритмов.
Ethereum использует криптографию на эллиптических кривых (Elliptic Curve
Cryptography, ECC). Алгоритм ECC имеет различные параметры, от которых зависят скорость и безопасность. В Ethereum использована эллиптическая кривая
secp256k1. Погружение в ECC и его параметры требует наличия серьезных математических знаний, но в этом нет необходимости, если мы хотим строить децентрализованные приложения на основе Ethereum.
Ethereum использует 256-битное шифрование. Открытый и закрытый ключи
Ethereum представляют собой 256-битные числа. Поскольку процессор не может
обработать настолько большие числа целиком, их представляют в виде шестнадцатеричной строки из 64 символов.
Каждая учетная запись представлена адресом. Когда у нас есть ключи, мы должны
сгенерировать адрес. Процедура генерации адреса происходит следующим образом:
1. Генерируем хеш открытого ключа по алгоритму keccak-256. Это дает вам 256битное число.
2. Отбрасываем первые 96 битов (12 байтов) — теперь у вас должно остаться
160 битов (20 байтов).
3. Затем кодируем адрес в шестнадцатеричную строку. Итак, вы получили строку
из 40 символов — это и есть ваш адрес.
Теперь любой желающий может перевести эфир на этот адрес.

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

[ 45 ] ___________________________________

Глава 2

ECDSA (Elliptic Curve Digital Signature Algorithm, алгоритм цифровой подписи на
эллиптических кривых). Транзакция содержит указатель на получателя сообщения,
подпись отправителя, подтверждающую его личность и намерения, количество
эфира для передачи, максимальное количество вычислительных шагов, разрешенных для выполнения транзакции (так называемый лимит газа), и стоимость, которую отправитель транзакции готов заплатить за каждый вычислительный шаг (так
называемая цена газа). Если назначение транзакции заключается в вызове метода
контракта, она также содержит входные данные, а если она предназначена для развертывания контракта, то может содержать код инициализации. Количество газа и
его цена называются сбором за транзакцию. Чтобы отправить эфир или выполнить
метод контракта, вам необходимо транслировать транзакцию в сеть. Отправитель
должен подписать транзакцию при помощи закрытого ключа.
Транзакция считается подтвержденной, если мы уверены, что она навсегда оста1
лась в блокчейне . Рекомендуется подождать 15 подтвержденных блоков, прежде чем считать транзакцию подтвержденной.

Консенсус
Каждый узел в сети Ethereum хранит копию реестра транзакций. Мы должны быть
уверены, что узел не вмешался в блокчейн, и нам нужен механизм проверки достоверности блоков. А также, если мы столкнемся с двумя разными действительными
блокчейнами, у нас должен быть способ узнать, какой из них выбрать.
Ethereum использует протокол с доказательством выполнения работы, чтобы защитить блокчейн от фальсификации. Принцип доказательства работы предусматривает при создании нового блока решение сложной вычислительной задачи. Решение
задачи должно требовать расходования значительных вычислительных ресурсов,
что делает создание нового блока трудной работой. Процесс создания нового блока
называется майнингом. Майнеры — это узлы сети, которые вырабатывают новые
блоки. Все децентрализованные приложения, которые используют доказательство
выполнения работы, не используют абсолютно одинаковый набор алгоритмов. Они
могут различаться в деталях относительно того, какую задачу должен решить майнер, насколько сложной должна быть задача, сколько времени занимает решение
и т. п. Мы будем рассматривать алгоритм доказательства работы применительно
к Ethereum.
Любой участник сети может стать майнером. Каждый майнер решает задачу индивидуально. Первый майнер, который решил задачу, становится победителем и получает пять эфиров и сборы от всех транзакций, вошедших в блок. Если ваш процессор мощнее, чем у остальных узлов сети, это не означает, что вы всегда будете

1

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

____________________________________

[ 46 ] ___________________________________

Принципы работы Ethereum

победителем, потому что параметры задачи не одинаковые для всех майнеров. Однако, если ваш процессор мощнее, у вас больше шансов на успех. Алгоритм доказательства работы похож на лотерею, а вычислительная мощность процессора соответствует количеству купленных лотерейных билетов. Безопасность сети зависит
не от количества майнеров, а от совокупной вычислительной мощности сети.
Не существует ограничения по количеству блоков в блокчейне и по количеству
эфира, который можно выработать. Как только майнер создал блок, он транслирует
его всем остальным узлам сети. Блок содержит заголовок и набор транзакций. Каждый блок содержит хеш предыдущего блока, образуя неразрывную цепь.
Давайте разберемся, какую задачу должен решить майнер и как она решается в общем виде. Чтобы создать блок, майнер первым делом собирает необработанные
(сырые) транзакции, которые выложены в сеть, проверяет их и отбрасывает некорректные. Корректные транзакции должны быть правильно подписаны с использованием закрытого ключа, счет отправителя должен содержать достаточную сумму
на балансе, чтобы провести транзакцию, и т. д. Далее майнер создает блок, у которого есть заголовок и содержимое. Содержимое состоит из списка транзакций, которые включены в блок. Заголовок содержит такие объекты, как хеш предыдущего
блока, номер блока, одноразовое число (nonce2), целевое число (target), метку времени (timestamp), сложность (difficulty), адрес майнера и некоторые другие объекты. Метка времени содержит время добавления блока. Nonce здесь — это ничего не
значащее число, которое майнер должен найти путем перебора, чтобы получить
решение задачи. Обычно задача заключается в том, чтобы найти такое число nonce,
при котором хеш готового блока меньше целевого числа или равен ему. Ethereum
использует алгоритм хеширования Ethash3 (Ethereum hash). Единственный способ
решения задачи заключается в переборе всех возможных значений nonce. Целевое
число — это 256-битное число, которое вычисляется исходя из различных факторов. Значение сложности в заголовке является другим представлением целевого
числа. Чем меньше целевое число, тем больше времени требуется для нахождения
подходящего значения nonce, и наоборот, чем больше целевое число, тем быстрее
находится nonce. Так выглядит формула для вычисления сложности задачи:
current_block_difficulty = previous_block_difficulty +
previous_block_difficulty / 2048 * max(1 - (current_block_timestamp previous_blocktimestamp) / 10, -99) + int(2 ** ((current_block_number /
100000) - 2))

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

Производное от «number used only once» — одноразовое число.
См. https://github.com/ethereum/wiki/wiki/Ethash.

____________________________________

[ 47 ] ___________________________________

Глава 2
Если узел сети получает два действительных блокчейна, то принимается версия,
у которой совокупная сложность блоков выше.

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

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

Число nonce
Nonce — это 64–битное беззнаковое целое число. Оно является решением задачи.
Майнер продолжает последовательно увеличивать число, пока не найдет решение.
Сейчас вы можете предположить, что майнер, располагающий самой большой вычислительной мощью, всегда будет побеждать в гонке. Нет, это не так.
Хеш блока, который добывает майнер, всегда разный для разных майнеров, потому
что он зависит от таких параметров, как метка времени, адрес майнера и прочих,
которые не могут быть одинаковыми у всех майнеров. Следовательно, это не гонка
за решением. Скорее, это лотерея. Разумеется, шансы майнера на победу зависят от
вычислительной мощности, но это не значит, что самый мощный майнер всегда
будет находить следующий блок.

____________________________________

[ 48 ] ___________________________________

Принципы работы Ethereum

Время блока
Формула вычисления сложности, которую вы видели раньше, содержит 10-секундную задержку, которая гарантирует, что разница времени майнинга между родительским и дочерним блоком будет находиться в пределах 10–20 секунд. Но почему
именно 10–20 секунд и никакое другое значение? И зачем вводить ограничение
с постоянной разностью времени вместо того, чтобы установить постоянную сложность?
Представьте, что мы имеем постоянную сложность, и майнерам просто нужно найти число nonce, при котором хеш блока меньше или равен значению target. Предположим, сложность задачи высокая. В таком случае пользователи сети не смогут даже приблизительно предположить, сколько времени займет передача эфира от одного пользователя другому. Если вычислительной мощности сети недостаточно,
поиск решения (и проводка транзакции) может длиться весьма долго. Иногда сети
может повезти, и тогда решение будет найдено очень быстро. Но такой сети будет
очень сложно привлекать пользователей, потому что люди всегда хотят знать,
сколько времени займет транзакция, аналогично тому, как при переводе с одного
банковского счета на другой банковский счет мы представляем, сколько времени
занимает процедура перевода. Если сложность задачи низкая, это угрожает безопасности блокчейна, потому что мощные майнеры будут добывать блоки намного
быстрее слабых майнеров и могут захватить управление децентрализованным приложением. Невозможно найти постоянное значение сложности, которое гарантирует стабильность сети, потому что вычислительная мощность сети непостоянна.
Теперь вы понимаете, почему мы всегда должны знать среднее значение времени,
в течение которого сеть вырабатывает новый блок. Вопрос в том, какое усредненное время считать приемлемым в диапазоне от одной секунды до бесконечности?
Маленькое усредненное время достигается снижением сложности, большое усредненное время достигается увеличением сложности. Но каковы достоинства и
недостатки маленького и большого усредненного времени? Прежде, чем говорить
об этом, мы должны понять, что такое устаревшие блоки.
Что произойдет, если два майнера добудут следующий блок приблизительно в одно
и то же время? Оба блока будут вполне корректными, но блокчейн не может содержать два блока с одинаковым номером, и два майнера не могут получить вознаграждение. Хотя это частая проблема, у нее есть простое решение. Сетью будет
принят блок, у которого больше сложность. Соответственно, отвергнутый блок будет назван устаревшим блоком.
Общее количество устаревших блоков, производимых сетью, обратно пропорционально средней длительности выработки нового блока. Маленькая длительность
выработки блока означает, что новые блоки будут чаще транслироваться в сеть, и
возрастает вероятность того, что решение задачи будет найдено более чем одним
майнером. Иначе говоря, за время, пока блок распространяется по сети, другие
майнеры могут найти решение и тоже его разослать, но это будут устаревшие блоки. Но если средняя длительность выработки блока велика, это уменьшает вероят____________________________________

[ 49 ] ___________________________________

Глава 2

ность того, что несколько майнеров смогут найти решение. Даже если это возможно, то между решениями будет разрыв по времени, в течение которого первый
успешный блок будет распространен по сети, другие майнеры узнают об этом, прекратят добывать этот блок и примутся за работу над следующим блоком. Если
устаревшие блоки слишком часто появляются в сети, они вызывают серьезные проблемы, но если они появляются редко, то не навредят.
Но в чем заключается проблема, которую создают устаревшие блоки? Они тормозят подтверждение транзакции. Если два майнера выработали блоки приблизительно одновременно, они могут содержать разный набор транзакций, и если наши
транзакции содержатся в одном из них, то мы не можем считать их подтвержденными, потому что блок могут признать устаревшим. Нам придется ждать, пока не
будет найдено несколько новых блоков. По вине устаревших блоков среднее время
подтверждения транзакции не равно среднему времени выработки блока.
Снижают ли устаревшие блоки безопасность блокчейна? Да, снижают. Мы знаем,
что безопасность сети определяется совокупной вычислительной мощностью всех
майнеров, работающих в сети. Когда вычислительная мощность растет, сложность
задачи возрастает, чтобы гарантировать, что блоки не вырабатываются быстрее,
чем за установленное среднее время. Следовательно, более высокая сложность означает более высокую безопасность, потому что вмешательство злонамеренного
узла в блокчейн потребует наличия у злоумышленника значительной мощности
хеширования. Если два блока предложены приблизительно одновременно, они
делят сеть пополам, где каждая половина проверяет свою версию блокчейна. Но
будет принята только одна версия, а вторая половина сети напрасно потратит вычислительную мощность на выполнение бесполезной работы. Это выглядит так,
словно понизилась полезная вычислительная мощность сети, поэтому время выработки следующего блока будет сокращено за счет снижения сложности. Снижение
сложности уменьшает общий уровень безопасности сети. Если устаревших блоков
много, это чрезвычайно плохо влияет на безопасность блокчейна.
Ethereum борется с проблемой устаревших блоков при помощи протокола под названием GHOST (Greedy Heaviest Observed SubTree), используя модифицированную версию этого протокола. Протокол GHOST решает проблему добавлением устаревших блоков в основной блокчейн. Тем самым он увеличивает общую сложность блокчейна, т. к. совокупная сложность блокчейна включает в себя значения
сложности устаревших блоков. Но как устаревшие блоки вставляют в блокчейн без
конфликта между транзакциями? Суть в том, что любой блок может содержать
ноль или больше устаревших блоков. Чтобы побудить майнеров включать в блокчейн устаревшие блоки, им выплачивают вознаграждение. Кроме этого, майнеры
устаревших блоков тоже получают вознаграждение. Транзакции устаревших блоков не участвуют в расчете подтверждения, и майнеры устаревших блоков не получают комиссию за транзакции. Отметим, что в протоколе Ethereum старые блоки
называются uncle4 blocks.
4

Игра слов. Кроме указания на родственную связь второго уровня (дядя-племянник) одно из переносных значений слова uncle — старик, пожилой человек, что намекает на старость блока.

____________________________________

[ 50 ] ___________________________________

Принципы работы Ethereum

Существует специальная формула, по которой вычисляется вознаграждение майнеру за прием устаревшего блока в переработку. Остаток вознаграждения переходит
к блоку-племяннику (nephew block), который вбирает в себя блок-дядюшку (uncle
block):
(uncle_block_number + 8 - block_number) * 5 / 8

Вы можете спросить: если отсутствие вознаграждения майнерам устаревших блоков никак не влияет на безопасность, почему они получают вознаграждение? Существует еще одна проблема, связанная с частым появлением устаревших блоков.
Майнер должен получать вознаграждение пропорционально вычислительной мощности, которую он вносит в систему. Если два блока найдены приблизительно одновременно, то, вероятнее всего, в финальный блокчейн будет включен блок, добытый более мощным майнером, а слабый майнер останется без награды. Если доля старых блоков невелика, то и проблемы нет — мощный майнер получит лишь
немного больше. Однако если доля устаревших блоков велика, то мощный майнер
будет забирать себе намного больше, чем заслуживает, исходя из вычислительной
мощности5. Протокол GHOST устраняет дисбаланс, выдавая вознаграждение майнерам старых блоков. Мощный майнер не получает вознаграждение целиком, но
все же заметно больше, чем вообще без протокола GHOST. Поэтому мы можем
и майнерам старых блоков не выдавать полное вознаграждение, а лишь часть от
обычного вознаграждения6. Приведенная ранее формула прекрасно поддерживает
баланс.
Протокол GHOST ограничивает максимальное количество старых блоков, которые
может вобрать в себя блок-племянник, поэтому майнеры не могут просто вхолостую генерировать старые блоки, тормозя блокчейн.
Таким образом, если в сети появляется старый блок, он так или иначе влияет на
сеть. Чем чаще появляются старые блоки, тем сильнее они влияют на сеть.

Ветвление
Если случается конфликт между узлами, подтвердившими подлинность блокчейна,
и возникает больше одного подлинного блокчейна в сети, причем все они подтверждены несколькими майнерами, то говорят, что произошло ветвление (forking).
Принято различать три вида ветвления: простое (regular fork), мягкое (soft fork,
софтфорк) и жесткое (hard fork, хардфорк).
Простое ветвление возникает, если два или более майнеров выработали блок почти
одновременно. Проблема решается за счет того, что сложность одного блока больше, чем у остальных.
5

При этом мелкие майнеры просто потеряют всякий стимул к работе, а картель «жирных майнеров»
может захватить контроль над сетью.
6
На момент работы над переводом майнер старого блока получал 87,5% вознаграждения, а майнер
блока, поглотившего старый блок, получал оставшиеся 12,5% дополнительно к вознаграждению за
новый блок.
____________________________________

[ 51 ] ___________________________________

Глава 2

После смены исходного кода узла могут возникнуть конфликты версий узлов.
В зависимости от типа конфликта может потребоваться обязательное обновление
узлов у майнеров, чья совокупная вычислительная мощность превышает 50% мощности сети, либо обновление абсолютно всех узлов сети. Первый тип конфликта
называется мягким ветвлением, а второй — жестким. Примером мягкого ветвления является такое обновление программного обеспечения (реализации протокола),
при котором старые блоки/транзакции теряют валидность. Но если у майнеров, которые обновили свои узлы, совокупная мощность более 50%, то их новая версия
блокчейна будет иметь более высокую сложность и в конечном итоге будет принята всей сетью. Примером жесткого ветвления является такое обновление исходного
кода, при котором меняется механизм расчета вознаграждения, и для разрешения
конфликта все майнеры должны обновиться.
Ethereum с момента запуска прошел через несколько жестких и мягких ветвлений7.

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

Деноминация эфира
Эфир, как и любая другая валюта, имеет свою шкалу деноминации:
 1 Ether = 1000000000000000000 Wei;
 1 Ether = 1000000000000000 Kwei;
 1 Ether = 1000000000000 Mwei;
 1 Ether = 1000000000 Gwei;
 1 Ether = 1000000 Szabo;
 1 Ether = 1000 Finney;
7

Во время работы над переводом книги был активирован плановый хардфорк Ethereum, который
стартовал 16 октября 2017 г. в 5:22 UTC. В результате успешного обновления в работу введен новый
протокол под названием Byzantium. Это пятый по счету хардфорк Ethereum (предыдущий состоялся
16 ноября 2016 года).

____________________________________

[ 52 ] ___________________________________

Принципы работы Ethereum

 1 Ether = 0.001 Kether;
 1 Ether = 0.000001 Mether;
 1 Ether = 0.000000001 Gether;
 1 Ether = 0.000000000001 Tether.

Виртуальная машина Ethereum
Виртуальная машина Ethereum (Ethereum Virtual Machine, EVM) — это среда выполнения байт-кода контрактов. EVM работает на каждом узле сети. Все узлы сети
выполняют все транзакции, на которые указывают смарт-контракты, поэтому каждый узел выполняет одни и те же вычисления и хранит одинаковые значения. Транзакции, которые только перемещают эфир, тоже требуют некоторых вычислений,
таких как определение достаточности баланса на счете или вычитание суммы из
баланса.
Каждый узел выполняет транзакции и хранит окончательное состояние по нескольким причинам. Например, существует смарт-контракт, который хранит имена и
данные гостей, приглашенных на вечеринку. Каждый раз, когда добавляют нового
гостя, новая транзакция транслируется в сеть. Любой узел может ознакомиться
с данными каждого приглашенного на вечеринку. Для этого достаточно прочитать
финальное состояние контракта.
Для любой транзакции необходимы вычисления и хранилище в сети. Следовательно, должны существовать затраты на транзакцию, иначе вся сеть будет переполнена
транзакционным спамом, а у майнеров не будет стимула включать транзакции
в блок, и они начнут генерировать пустые блоки. Каждая транзакция требует разных ресурсов для обработки и хранения, следовательно, разные транзакции должны
иметь различную стоимость.
Существуют две реализации виртуальной машины: на исполняемом байт-коде и
8
JIT-VM . На момент подготовки этой книги JIT-VM была доступна для использования, но оставалась в состоянии разработки. В обоих случаях исходный код
Solidity компилируется в байт-код. В случае JIT-VM байт-код подвергается дополнительной компиляции непосредственно в исполняемый код, поэтому JIT-VM работает быстрее своего коллеги.

Газ
Газ (gas) — это единица измерения вычислительных шагов. Каждая транзакция
должна содержать лимит газа и величину вознаграждения за газ (за единичное вычисление). У майнера есть возможность выбирать транзакции и собирать вознаграждение. Если количество затраченного газа меньше или равно лимиту газа, то тран8

Just-in-Time, компиляция на ходу.

____________________________________

[ 53 ] ___________________________________

Глава 2

закция выполняется. Если затраты газа превышают лимит, то все изменения отменяются, кроме валидности транзакции, но вознаграждение за транзакцию майнером
может быть получено.
Майнеры самостоятельно устанавливают стоимость газа (цену за единичное
вычисление на своем узле). Если транзакция предлагает меньшую цену газа, чем
запросил майнер, то майнер отвергает транзакцию. Стоимость газа измеряется
в единицах Wei (см. ранее разд. «Деноминация эфира»). Иными словами, майнер
может отказаться включать транзакцию в блок, если предложенная стоимость газа
меньше, чем ему нужно.
С каждой операцией виртуальной машины Ethereum связано количество газа,
необходимое для выполнения операции.

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

Обнаружение узлов
Чтобы узел стал частью сети, он должен установить соединение с остальными
узлами сети, благодаря чему сможет транслировать транзакции/блоки и слушать
трансляцию транзакций/блоков. Узел не нуждается в соединении со всеми существующими узлами сети. Напротив, узел соединяется лишь с несколькими узлами.
В свою очередь, каждый из таких узлов соединяется с несколькими другими узлами. Так формируется сеть взаимосвязанных узлов.
Но каким образом узел может найти в сети другие узлы, если не существует центральный сервер, с которым любой желающий мог бы обменяться информацией?
Ethereum использует для решения этой проблемы собственный протокол обнаружения узлов, основанный на протоколе Kademlia. В соответствии с этим протоколом, в сети есть специальные узлы, которые называют загрузочными (bootstrap
node). Загрузочный узел формирует список узлов, которые подключались к нему
в течение определенного периода времени. Загрузочный узел не хранит блокчейн.
Когда узел пользователя подключается к сети, он первым делом соединяется с загрузочным узлом и получает список узлов, которые соединялись с ним в течение
заранее заданного периода времени. Затем новый узел соединяется и синхронизируется с узлами из списка.
Могут существовать различные экземпляры Ethereum, т. е. различные сети, каждая
из которых имеет свой идентификатор. Две основные сети Ethereum называются
____________________________________

[ 54 ] ___________________________________

Принципы работы Ethereum
mainnet (главная сеть) и testnet (тестовая сеть). Сеть mainnet служит для торговли
эфиром на биржах, а сеть testnet используется разработчиками для тестирования.
Все, что мы изучили до сих пор, относится к блокчейну сети mainnet.
Bootnode — это наиболее популярная реализация загрузочного узла Ethereum.
Если вы хотите создать собственный загрузочный узел, то можете использовать
Bootnode.

Протоколы Whisper и Swarm
Whisper (шепот) и Swarm (рой) — это, соответственно, децентрализованный протокол связи и децентрализованная платформа для хранения данных, созданные разработчиками Ethereum.
Whisper помогает узлам общаться друг с другом. Он поддерживает широковещательные сообщения, связь пользователь-пользователь, шифрованные сообщения и
многое другое. Он не предназначен для передачи большого объема данных.
Вы можете прочитать больше о протоколе Whisper по адресу: https://github.com/
ethereum/wiki/wiki/Whisper и ознакомиться с обзором примеров кода по адресу:
https://github.com/ethereum/wiki/wiki/Whisper-Overview.
Swarm похожа на Filecoin и отличается, в основном, техническими деталями и поощрением. Filecoin не оплачивает хранение, тогда как Swarm выплачивает вознаграждение — это увеличивает доступность информации. Вы можете спросить, как
реализовано поощрение в Swarm? Имеет ли она внутреннюю валюту? На самом
деле, у Swarm нет для выплаты вознаграждения другой валюты, кроме эфира.
Существует смарт-контракт Ethereum, который отслеживает поощрения. Очевидно,
что смарт-контракт не может общаться со Swarm. Наоборот, Swarm может обращаться к контракту. В общем, вы платите хранилищу через смарт-контракт, и платеж зачисляется хранилищу по наступлении заданной даты. Вы также можете сообщить контракту о пропаже файла, и контракт взыщет штраф с соответствующего
хранилища.
Вы можете узнать больше о различиях между Swarm и IPFS/Filecoin по адресу:
https://github.com/ethersphere/go-ethereum/wiki/IPFS-&-SWARM и получить исходный код смарт-контракта по адресу: https://github.com/ethersphere/go-ethereum/
blob/bzz-config/bzz/bzzcontract/swarm.sol.
На момент работы над книгой Whisper и Swarm находились в разработке, поэтому
многие тонкости пока остаются нераскрытыми.

Geth
Geth (или Go-Ethereum) — это реализация узлов Ethereum, Whisper и Swarm. Geth
может быть использован как компонент всех трех протоколов или только для любого из них. Причина использования Geth заключается в потребности сделать так,
____________________________________

[ 55 ] ___________________________________

Глава 2

чтобы все три децентрализованных приложения вместе выглядели как единое целое, а клиент имел доступ ко всем трем приложениям через один узел сети.
Geth — это консольное приложение, которое написано на языке Go. Оно доступно
для всех основных операционных систем. Текущая версия Geth не поддерживает
Swarm и поддерживает только некоторые функции Whisper.

Установка Geth
Geth доступен для OS X, Linux и Windows. Он поддерживает два типа установки:
бинарный файл и установка при помощи сценария (установочного скрипта). Во
время подготовки книги актуальной была версия Geth 1.4.139. Давайте рассмотрим
установку приложения на разные операционные системы при помощи исполняемого бинарного файла. Сценарий установки применяется, если вы внесли изменения
в исходный код. Мы не собираемся вносить изменения в исходный код, поэтому
воспользуемся бинарным файлом.

OS X
Рекомендуем устанавливать Geth под OS X при помощи установщика Brew. Выполните две команды в окне терминала, чтобы установить Geth:
brew tap ethereum/ethereum
brew install ethereum

Ubuntu
Для установки под Ubuntu рекомендуем использовать apt-get. Выполните эти
команды в окне терминала, чтобы установить Geth:
sudo
sudo
sudo
sudo

apt-get install software-properties-common
add-apt-repository -y ppa:ethereum/ethereum
apt-get update
apt-get install ethereum

Windows
Geth для Windows распространяется в виде исполняемого файла. Скачайте ZIPархив по адресу: https://github.com/ethereum/go-ethereum/wiki/Installationinstructions-for-Windows и распакуйте его. Внутри вы найдете исполняемый файл
geth.exe.
Узнать больше про установку Geth для различных операционных систем можно
по адресу: https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum.

9

Во время работы над переводом была доступна версия Geth 1.7.2.

____________________________________

[ 56 ] ___________________________________

Принципы работы Ethereum

JSON-RPC и консоль JavaScript
Geth предоставляет API JSON-RPC10 для взаимодействия с другими приложениями
и обслуживает API через HTTP, WebSocket и другие протоколы. API JSON-RPC
подразделяется на следующие категории: admin, debug, eth, miner, net, personal, shh,
txpool и web3. Вы можете найти более подробную информацию по адресу:
https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.
Geth также предоставляет интерактивную консоль JavaScript для программного
взаимодействия через API JavaScript. Эта интерактивная консоль использует JSONRPC поверх IPC (Inter Process Communication, связь между процессами) для связи
с Geth. Мы расскажем подробнее про JSON-RPC и API JavaScript в следующих
главах.

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

Подключение к сети mainnet
Узлы сети Ethereum по умолчанию общаются через порт 30303. Но узлы имеют
право выбрать любой другой порт.
Для подключения к сети mainnet вам достаточно выполнить команду geth. Вот
пример того, как в явном виде указать идентификатор сети и пользовательский
каталог, в котором будет храниться скачанный блокчейн:
geth --datadir "/users/packt/ethereum" --networkid 1

Опция --datadir указывает путь к месту хранения блокчейна. Если он не указан, то
по умолчанию используется путь $HOME/.ethereum.
Опция --networkid указывает идентификатор сети. Номер 1 соответствует сети
mainnet. Если номер не указан, то по умолчанию используется 1. Идентификатор
сети testnet — 2.

Создание частной сети
Для создания частной сети вам достаточно указать случайный идентификатор.
Частные сети обычно создают для нужд отладки. Geth располагает различными
флагами для логирования и отладки, что полезно во время разработки. Поэтому,
10

Remote Procedure Call (RPC) — протокол удаленного вызова процедур, не сохраняющий содержание вызова.
____________________________________

[ 57 ] ___________________________________

Глава 2

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

Создание аккаунта
Geth позволяет создавать счета (аккаунты), т. е. генерировать ключи и связанные
с ними адреса. Для создания счета используйте команду:
geth account new

Когда вы запустите эту команду, вас попросят ввести пароль для шифрования счета. Если вы забудете ваш пароль, не останется ни малейшего шанса вернуть доступ
к своему счету.
Чтобы получить список всех счетов в вашем локальном кошельке, используйте
команду:
geth account list

Эта команда выведет на печать список адресов всех счетов. Ключи по умолчанию
хранятся в каталоге, заданном опцией --datadir, но вы можете использовать
опцию –keystore, чтобы назначить другой каталог.

Майнинг
По умолчанию Geth не запускает процесс майнинга. Чтобы дать Geth команду начать майнинг, нужно использовать опцию --mine. Вот еще несколько опций, относящихся к майнингу:
geth --mine --minerthreads 16 --minergpus '0,1,2' --etherbase
'489b4e22aab35053ecd393b9f9c35f4f1de7b194' --unlock
'489b4e22aab35053ecd393b9f9c35f4f1de7b194'

Здесь после опции --mine мы указали несколько других опций:
 опция --minerthreads означает общее количество потоков, задействованных при

хешировании. По умолчанию используются восемь потоков11;

 опция --etherbase означает адрес (счет), на который будет поступать вознагра-

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

Несколько адресов разделяются запятыми;
11

В версии 1.7.2 по умолчанию запускаются четыре потока.

____________________________________

[ 58 ] ___________________________________

Принципы работы Ethereum

 опция --minergpus используется, чтобы перечислить видеокарты (GPU), которые

заняты в майнинге. Чтобы получить список доступных видеокарт, используйте
команду geth gpuinfo. На каждую видеокарту должно приходиться 1–2 Гбайт
оперативной памяти компьютера. По умолчанию используются не видеокарты,
а центральный процессор компьютера (CPU).

Быстрая синхронизация
На момент подготовки этой книги размер блокчейна составлял примерно 30 Гбайт12.
Скачивание такого объема данных может занять несколько часов или даже дней,
если у вас медленное подключение к Интернету. Для Ethereum реализован алгоритм быстрой синхронизации, который может ускорить скачивание блокчейна.
Для быстрой синхронизации не требуется скачивание полных блоков — загружаются только заголовки блоков, квитанции транзакций и недавняя база данных состояний. Иными словами, нам не надо скачивать и воспроизводить все транзакции.
Для проверки целостности блокчейна алгоритм скачивает один полный блок через
каждое определенное количество блоков. Вы можете прочитать больше про алгоритм быстрой синхронизации по адресу: https://github.com/ethereum/go-ethereum/
pull/1889.
Чтобы воспользоваться быстрой синхронизацией при скачивании блокчейна, следует при запуске Geth воспользоваться опцией --fast13.
В целях безопасности быстрая синхронизация разрешена только при начальной
синхронизации (т. е., когда собственный блокчейн узла пуст). После того как узел
успешно синхронизировался с сетью, быстрая синхронизация навсегда отключается. В качестве дополнительного средства безопасности, если сбой синхронизации
произошел рядом с точкой ветвления или сразу после нее, быстрая синхронизация
отключается, и узел переходит в режим полной синхронизации с обработкой всех
блоков.

Ethereum Wallet
Ethereum Wallet (бумажник) — это приложение-клиент пользовательского интерфейса Ethereum, позволяющее создавать аккаунт, отправлять эфир, разворачивать
контракты, вызывать методы контрактов и многое другое. На рис. 2.1 показано, как
выглядит окно Ethereum Wallet.
Ethereum Wallet работает в паре с Geth. Когда вы запускаете Ethereum Wallet, он
пытается найти локальный экземпляр Geth и соединиться с ним. Если он не находит работающий Geth, то запускает собственный узел Geth. Ethereum Wallet обме12
На момент подготовки перевода размер блокчейна вырос до 108 Гбайт. Данные получены на сайте:
https://bitinfocharts.com/ethereum/.
13
В новой версии Geth следует использовать опцию --syncmode "fast".

____________________________________

[ 59 ] ___________________________________

Глава 2

нивается данными с Geth через IPC (Inter-Process Communication, связь между процессами). Geth поддерживает файловый вариант IPC14.
Если вы меняете каталог хранения данных при работающем Geth, то должны
также изменить путь к файлу IPC. Чтобы Ethereum Wallet подключился к экземпляру Geth, вам следует использовать опцию --ipcpath, указывающую на расположение файла IPC по умолчанию. В противном случае Ethereum Wallet не сможет найти файл обмена и запустит собственный экземпляр Geth. Чтобы узнать
путь к файлу IPC по умолчанию, выполните команду geth help, и она покажет
путь по умолчанию после опции --ipcpath.

Ethereum Wallet доступен для операционных систем Linux, OS X и Windows. Вы
можете скачать его по адресу: https://github.com/ethereum/mist/releases.
По аналогии с Geth, у него есть два режима установки: бинарный файл и установка
с помощью сценария.

Рис. 2.1. Окно клиента Ethereum Wallet

14

Обмен данными между процессами через общий файл.

____________________________________

[ 60 ] ___________________________________

Принципы работы Ethereum

Mist
Mist (туман) — это приложение-клиент для Ethereum, Whisper и Swarm. Mist позволяет выполнять транзакции, отправлять сообщения Whisper, проверятьблокчейн
и т. д. На рис. 2.2 показано, как выглядит окно Mist.

Рис. 2.2. Окно клиента Mist со встроенным браузером блоков

Взаимодействие между Mist и Geth устроено так же, как между Ethereum Wallet
и Geth.
Самая популярная функция Mist — встроенный браузер блоков. В настоящее время
внешний интерфейс JavaScript, работающий в браузере, может обращаться к API
web3 с помощью узла Geth, используя библиотеку web3.js (библиотека, которая
предоставляет интерфейсы консоли JavaScript Ethereum другим приложениям для
связи с Geth).
Главная идея Mist заключается в создании третьего поколения Web (Web 3.0),
которое должно ликвидировать потребность в обладании серверами за счет использования вместо централизованных серверов Ethereum, Whisper и Swarm.
____________________________________

[ 61 ] ___________________________________

Глава 2

Уязвимости Ethereum
Любая система имеет уязвимости. Ethereum тоже не лишена проблем с безопасностью. Очевидно, как и любая другая программа, исходный код Ethereum может
содержать ошибки. И как любое другое сетевое приложение, Ethereum подвержена
DoS-атакам. Но давайте обратим внимание на уникальные и более важные уязвимости Ethereum.

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

Атака 51%
Если атакующий контролирует больше половины вычислительной мощности сети,
он может генерировать блоки быстрее, чем остальная сеть. Атакующий может просто организовать ветвление блокчейна и сохранять свою частную ветку до тех пор,
пока она не станет длиннее (и сложнее), чем ветка добропорядочной сети, а затем
распространить ее.
Обладая мощностью более 50%, майнер может отменять транзакции, мешать всем
или некоторым транзакциям быть обработанными и препятствовать включению
в блокчейн блоков от других майнеров.

Обновление Serenity
Serenity — название следующего основного обновления Ethereum. Во время работы
над этой книгой обновление Serenity оставалось в разработке. Для реализации обновления необходим хардфорк. Serenity изменит протокол консенсуса на Casper и
будет включать в себя каналы состояния и разделение данных (sharding). Точные
детали того, как это будет работать, в настоящее время остаются неясными. Давайте ограничимся поверхностным обзором обновления.
Уточнение от переводчика
Реализация обновлений отстала от графика, который был объявлен во время
работы автора над книгой. Очередность основных обновлений во время работы
над переводом книги выглядела так: Frontier — Homestead — Metropolis — Constantinople — Serenity. Ожидалось, что обновление Serenity будет реализовано
в 2017 году. Фактически, в октябре 2017 года был проведен успешный хардфорк,
который соответствует лишь половине пути к обновлению Metropolis. Таким обра____________________________________

[ 62 ] ___________________________________

Принципы работы Ethereum
зом, протокол Serenity вряд ли будет введен в действие ранее 2019 года. Ключевым отличием Serenity будет замена доказательства работы proof-of-work на доказательство владения долей proof-of-stake в протоколе консенсуса. Как обещают
разработчики, новый протокол значительно снизит потребление энергии и распахнет двери в сеть Ethereum всем желающим, включая школьников и домохозяек.

Платежные каналы и каналы состояния
Прежде, чем говорить о каналах состояния, следует знать, что представляют собой
платежные каналы. Платежный канал — это функция, которая позволяет нам объединить более двух транзакций по отправке эфира и передать их всего двумя транзакциями. Вот как это работает. Допустим, X — владелец веб-сайта с платными
видеоканалами, а Y — пользователь. X списывает один эфир за каждую минуту
видео. Соответственно, X хочет, чтобы Y платил ему каждую минуту во время просмотра видео. Разумеется, Y может транслировать транзакции каждую минуту, но
при этом возникают проблемы. Например, X может ждать подтверждения транзакции, и на это время будет прерываться трансляция видео. Платежные каналы решают эту проблему. Используя платежные каналы, пользователь Y может путем
отправки блокирующей транзакции депонировать несколько эфиров (допустим,
даже 100 эфиров) на определенный срок (допустим, на 24 часа) в пользу X. Теперь,
после просмотра одной минуты видео, пользователь Y отправляет заверенную
запись с указанием разблокировать резерв и отдать один эфир на счет X. Спустя
еще одну минуту пользователь Y транслирует еще одну заверенную запись, что
можно разблокировать счет и отправить на счет X два эфира. Этот процесс продолжается до тех пор, пока Y смотрит видео на сайте. В результате, если пользователь Y просмотрел 100 минут видео и исчерпал резерв (или прошли 24 часа), владелец сайта X отправляет заверенный запрос на списание финальной суммы со счета Y. Если X не смог подать запрос на списание в оговоренные ранее 24 часа, то вся
сумма резерва возвращается пользователю Y. Таким образом, сеть фактически обрабатывает всего две транзакции: блокирование и разблокирование.
Как можно видеть, платежные каналы относятся к переводам эфира. А каналы состояния позволяют объединять транзакции, относящиеся к смарт-контрактам.

Протокол консенсуса Casper
Говоря о протоколе консенсуса под названием Casper, мы должны понимать, как
работает протокол подтверждения доли (подтверждение участия, proof-of-stake).
Подтверждение доли — это наиболее распространенная замена протокола с подтверждением работы, потому что подтверждение работы напрасно расходует
огромные вычислительные ресурсы. Для подтверждения доли майнеру не нужно
решать сложную вычислительную задачу. Чтобы создать блок, он должен подтвердить владение долей активов сети. В системе с подтверждением доли количество
эфира на счете майнера рассматривается как доля, а вероятность нахождения ново____________________________________

[ 63 ] ___________________________________

Глава 2

го блока прямо пропорциональна величине доли. Поэтому, если майнер владеет
10% доли эфира в сети, он будет добывать 10% блоков.
Но вот вопрос: как мы узнаем, кто будет майнером следующего блока? Ведь нельзя
позволить майнеру с наибольшей долей всегда добывать следующий блок, потому
что это породит централизацию. Существуют различные алгоритмы выбора майнера следующего блока — такие, как случайный выбор блока и выбор по возрасту
монет (coin-age-based selection).
Casper — это модифицированная версия протокола с подтверждением доли, которая исключает различные проблемы базовой версии.

Разделение данных
В настоящее время каждый узел должен загружать все транзакции, объем которых
огромен. Если учесть, что размер блокчейна быстро увеличивается, через несколько лет станет трудно загружать весь блокчейн и поддерживать синхронизацию.
Если вы знакомы с архитектурой распределенных баз данных, то должны быть знакомы с разделением данных (sharding, фрагментация/репликация данных). Если
нет, то вам достаточно знать, что это метод распределения данных по множеству
компьютеров. Ethereum будет делить блокчейн на части и распределять его по
узлам.
Вы можете подробнее прочитать про распределение блокчейна по адресу:
https://github.com/ethereum/wiki/wiki/Sharding-FAQ.

Заключение
В этой главе мы детально изучили, как работает Ethereum. Мы узнали, как время
выработки блока влияет на безопасность и в чем заключаются уязвимости
Ethereum. Мы поговорили про Mist и Ethereum Wallet и узнали, как их установить.
Мы познакомились с наиболее важными командами Geth. Наконец, мы узнали,
какие изменения принесет в Ethereum новый протокол Serenity.
В следующей главе мы поговорим о различных способах хранения и защиты валюты Ethereum — эфира.

____________________________________

[ 64 ] ___________________________________



Разработка
смарт-контрактов

Из предыдущей главы мы узнали, как работает блокчейн и как консенсус с подтверждением работы (PoW) сохраняет его безопасность. Теперь пришло время начать писать смарт-контракты, потому что мы уже достаточно хорошо разобрались
в том, как работает Ethereum. Для написания смарт-контрактов Ethereum могут использоваться разные языки, но самым популярным является язык программирования Solidity, и в этой главе мы изучим его основы. А в завершение главы напишем
децентрализованное приложение для доказательства наличия, целостности и права
собственности на файл в заданное время. Иными словами, это будет децентрализованное приложение, которое может доказать, что файл принадлежал определенному владельцу в указанное время.
В этой главе мы рассмотрим следующие темы:
 расположение данных Solidity;
 типы данных Solidity;
 специальные переменные и функции контракта;
 управляющие структуры;
 строение и свойства контрактов;
 компиляция и развертывание контрактов.

Файлы исходного кода Solidity
Файлы исходного кода Solidity обозначаются расширением .sol. Как и любой другой язык программирования, Solidity имеет разные версии. Во время подготовки
книги самая свежая версия имела номер 0.4.21.
1

К моменту начала работы над переводом была доступна версия 0.4.18.

Глава 3

При помощи директивы pragma Solidity можно указать, для какой версии компилятора предназначен исходный код. Например, рассмотрим такую строку:
pragma Solidity ^0.4.2;

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

Структура смарт-контракта
Контракт похож на класс. Он содержит статические переменные, функции, модификаторы функций, события, структуры и перечисления. Контракт также поддерживает наследование, которое осуществляется копированием кода при компиляции. Смарт-контракты также поддерживают полиморфизм.
Давайте рассмотрим пример смарт-контракта, чтобы получить представление
о том, как он выглядит (листинг 3.1).
Листинг 3.1. Пример смарт-контракта
contract Sample
{
//статические переменные
uint256 data;
address owner;
//определение события
event logData(uint256 dataToLog);
//модификатор
modifier onlyOwner() {
if (msg.sender != owner) throw;
_;
}
//конструктор
function Sample(uint256 initData, address initOwner){
data = initData;
owner = initOwner;
}

____________________________________

[ 66 ] ___________________________________

Разработка смарт-контрактов
//функции
function getData() returns (uint256 returnedData){
return data;
}
function setData(uint256 newData) onlyOwner{
logData(newData);
data = newData;
}
}

Поясним, как работает код из листинга 3.1:
1. Мы объявляем контракт при помощи ключевого слова contract.
2. Затем объявляем две статические переменные: переменная data хранит некоторые данные, а переменная owner — адрес бумажника Ethereum Wallet владельца.
Это адрес, по которому будет развернут контракт.
3. Далее мы объявляем событие. События нужны для уведомления клиента о чемлибо. Мы будем запускать событие каждый раз, когда меняется значение data.
Все события хранятся в блокчейне.
4. Затем мы объявляем модификатор функции. Модификаторы применяются для
автоматической проверки условия перед выполнением функции. В данном случае модификатор проверяет, ссылается ли владелец контракта на функцию. Если
нет, это порождает исключение.
5. Далее у нас расположен конструктор контракта. При развертывании контракта
вызывается конструктор. Конструктор служит для инициализации переменных
состояния.
6. Наконец, мы определяем два метода: первый метод получает значение переменной data, а второй — изменяет значение этой переменной.
Прежде, чем мы погрузимся в изучение функций смарт-контрактов, следует рассмотреть несколько других важных понятий, имеющих отношение к Solidity. А затем мы снова вернемся к контрактам.

Расположение данных
Все языки программирования, которые вы когда-либо изучали, хранят данные
в памяти. Но Solidity хранит данные в памяти, а файловую систему — в зависимости
от ситуации.
В зависимости от контекста, всегда есть расположение по умолчанию. Но для
сложных типов данных, таких как строки, массивы и структуры, его можно переопределить в явном виде, добавив к объявлению типа ключевое слово storage или
memory. По умолчанию для параметров функции (включая возвращаемые парамет____________________________________

[ 67 ] ___________________________________

Глава 3

ры) — это memory, по умолчанию для локальных переменных — это storage. Для
статических переменных обычно также назначается расположение storage.
Расположение данных имеет значение, поскольку от него зависит поведение операторов присваивания:
 присваивание между переменными в хранилище (storage) и переменными в па-

мяти (memory) всегда создает независимую копию данных. Но операция присваивания между сложными типами данных, если они оба расположены в памяти, не
приводит к созданию копии;
 присваивание статической переменной (даже если источником является другая

статическая переменная) всегда создает независимую копию данных;
 вы не можете присвоить данные сложного типа, находящиеся в памяти, статиче-

ской переменной, находящейся в локальном хранилище;
 в случае, когда статическая переменная присваивается локальной переменной,

локальная переменная получает указатель на статическую переменную. Это значит, что локальная переменная становится указателем.

Что такое типы данных?
Solidity — это язык со статической типизацией, то есть типы переменных должны
быть заранее определены. По умолчанию все биты переменных сброшены
в ноль. Если переменная объявлена внутри функции, областью видимости переменной является вся функция, независимо от места объявления.
Рассмотрим различные типы данных в языке Solidity:
 самый простой тип данных — bool. Он может хранить только булевы значения
true или false;

 типы uint8, uint16, uint24 … uint256 применяются для хранения беззнаковых

целых чисел длиной 8 битов, 16 битов, 24 бита … 256 битов соответственно.
Аналогично, int8, int16 … int256 хранят целые числа со знаком длиной 8 битов,
16 битов … 256 битов соответственно. Служебные слова uint и int являются
псевдонимами для uint256 и int256. Служебные слова ufixed и fixed обозначают
дробные числа. Типы ufixed0x8, ufixed0x16 … ufixed0x256 — это беззнаковые
дробные числа длиной 8 битов, 16 битов … 256 битов соответственно. Если требуется сохранить число с разрядностью большей, чем 256, то используется
256-битовый тип, и в этом случае хранится приближенное (усеченное) значение;
 тип address применяется для хранения шестнадцатеричного литерала длиной до

20 байтов, используемого для адресов Ethereum. Тип address расширяется свойствами balance и send: свойство balance применяется при проверке баланса определенного адреса Ethereum, а свойство send — при переводе эфира на указанный
адрес. Метод send получает сумму в Wei, которую надо перевести, и возвращает
значение true или false в зависимости от того, был ли перевод успешно выпол____________________________________

[ 68 ] ___________________________________

Разработка смарт-контрактов

нен. Сумма в Wei вычитается из контракта, который вызвал метод send. Вы можете использовать в коде Solidity префикс 0x для обозначения шестнадцатеричного представления значений переменных.

Массивы
Solidity поддерживает обычные и байтовые массивы. Размер массива может быть
постоянным или динамически изменяемым. Также поддерживаются многомерные
массивы.
bytes1, bytes2, bytes3, ..., bytes32 —
псевдонимом типа bytes1.

это типы байтовых массивов. byte является

Рассмотрим пример использования обычных массивов (листинг 3.2).
Листинг 3.2. Пример использования массивов
contract sample{
//массив с динамическим размером
//везде, где виден литерал массива, создается новый массив
//здесь myArray хранит массив с размером [0, 0].
//тип массива определяется типом его значений,
//следовательно, вы не можете назначить пустой литерал массива
int[] myArray = [0, 0];
function sample(uint index, int value){
//индекс массива должен иметь тип uint256
myArray[index] = value;
//myArray2 хранит указатель на myArray
int[] myArray2 = myArray;
//массив постоянного размера в памяти
//здесь мы вынуждены применить uint24, потому что
//максимальное значение 99999,
//и требуется 24 бита для хранения
//данное ограничение действует для литералов в памяти,
//потому что стоимость памяти высока.
//так как [1, 2, 99999] - это тип uint24, следовательно
//myArray3 тоже
//должен иметь такой же тип, чтобы хранить указатель
uint24[3] memory myArray3 = [1, 2, 99999];

____________________________________

[ 69 ] ___________________________________

Глава 3
//вызовет исключение во время компиляции,
//так как myArray4 не может быть
//назначен комплексному типу, хранящемуся в памяти
uint8[2] myArray4 = [1, 2];
}
}

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

чтобы изменить размер массива. Однако вы не можете изменить размер
массива, хранящегося в памяти, или размер статического массива;

 если вы попытаетесь обратиться к несуществующему индексу, это вызовет ис-

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

Строки
В Solidity существует два способа создать строки: используя объявления bytes
и string. Объявление bytes применяется для создания неформатированной строки,
а string — для создания строки в кодировке UTF-8. Длина строки всегда динамическая.
В листинге 3.3 приведен пример синтаксиса строк.
Листинг 3.3. Пример синтаксиса при использовании строк
contract sample{
//создаем пустую строку myString
string myString = "";
//создаем неформатированную строку
bytes myRawString;
function sample(string initString, bytes rawStringInit){
myString = initString;
//myString2 содержит указатель на myString
string myString2 = myString;
//myString3 - это строка в памяти
string memory myString3 = "ABCDE";

____________________________________

[ 70 ] ___________________________________

Разработка смарт-контрактов
//меняем длину и содержимое строки
myString3 = "XYZ";
//присваиваем значение неформатированной строке
myRawString = rawStringInit;
//увеличиваем длину myRawString
myRawString.length++;
//вызовет исключение при компиляции,
//потому что строка расположена не в памяти
string myString4 = "Example";
//вызовет исключение при компиляции
string myString5 = initString;
}
}

Структуры
Solidity поддерживает структуры. В листинге 3.4 приведен пример синтаксиса при
использовании структур.
Листинг 3.4. Пример синтаксиса при использовании структур
contract sample{
struct myStruct {
bool myBool;
string myString;
}
myStruct s1;
myStruct s2 = myStruct(true, ""); //синтаксис структурного метода
function sample(bool initBool, string initString){
//создаем экземпляр структуры в хранилище (storage)
s1 = myStruct(initBool, initString);
//создаем экземпляр в памяти (memory)
myStruct memory s3 = myStruct(initBool, initString);
}
}
Учтите, что параметр функции не может быть структурой, и функция не может
возвращать структуру.
____________________________________

[ 71 ] ___________________________________

Глава 3

Перечисление
Solidity поддерживает перечисления. В листинге 3.5 приведен пример синтаксиса
при использовании перечислений.
Листинг 3.5. Пример синтаксиса при использовании перечислений
contract sample {
enum OS { Windows, Linux, OSX, UNIX }
OS choice;
function sample(OS chosen){
choice = chosen;
}
function setLinuxOS(){
choice = OS.Linux;
}
function getChoice() returns (OS chosenOS){
return choice;
}
}

Сопоставление
Сопоставление (отображение, mapping) представляет собой хеш-таблицу2. Сопоставление может находиться только в хранилище, но не в памяти, поэтому оно
может быть объявлено только как статическая переменная. Сопоставление можно
рассматривать как набор данных, состоящий из пар ключ-значение. Но ключ на самом деле не хранится. Для поиска значений используется хеш ключа, вычисленный
по алгоритму keccak256. Сопоставления не имеют длины и не могут быть присвоены
другому сопоставлению.
В листинге 3.6 показан пример создания и использования сопоставления.
Листинг 3.6. Пример создания и использования сопоставления
contract sample{
mapping (int => string) myMap;

2

Сопоставление (Mapping) реализует ассоциативный массив. В С++ это map, в языке Python это называется dict (словарь).
____________________________________

[ 72 ] ___________________________________

Разработка смарт-контрактов
function sample(int key, string value){
myMap[key] = value;
//myMap2 это ссылка на myMap
mapping (int => string) myMap2 = myMap;
}
}
Если вы попытаетесь получить доступ к несуществующему ключу, все биты результата будут нулевыми.

Оператор delete
Оператор delete можно применить к любой переменной, чтобы сбросить ее в состояние по умолчанию, когда все биты равны нулю.
Если мы применяем оператор delete к динамическому массиву, он удаляет все элементы массива, и длина массива становится равной нулю. Но если применить его
к статическому массиву, то обнуляются все элементы массива.
Если применить оператор delete к сопоставлению целиком, то ничего не произойдет. Но если вы примените delete к ключу сопоставления, то значение, связанное
с ключом, будет удалено (обнулено).
В листинге 3.7 приведен пример, демонстрирующий применение оператора delete.
Листинг 3.7. Пример использования оператора delete
contract sample {
struct Struct {
mapping (int => int) myMap;
int myNumber;
}
int[] myArray;
Struct myStruct;
function sample(int key, int value, int number, int[] array) {
//сопоставления не могут быть присвоены, поэтому при
//конструировании структуры мы игнорируем сопоставления
myStruct = Struct(number);
//задаем соответствие key/value
myStruct.myMap[key] = value;
myArray = array;
}
____________________________________

[ 73 ] ___________________________________

Глава 3
function reset(){
//теперь длина myArray равна нулю
delete myArray;
//myNumber теперь будет обнулен,
//но myMap не изменится
delete myStruct;
}
function deleteKey(int key){
//здесь мы удаляем ключ
delete myStruct.myMap[key];
}
}

Преобразование элементарных типов
Все сущности языка Solidity, которые не являются массивами, строками, структурами, перечислениями и сопоставлениями, относятся к элементарным типам.
Если в операторе встречаются два разных элементарных типа, то компилятор пытается выполнить неявное преобразование типа одного из операндов к типу другого
операнда. В общем случае неявное преобразование возможно, если оно имеет семантический смысл и не приводит к потере информации. Например, uint8 можно
преобразовать в uint16, а uint128 можно преобразовать в uint256. Но int8 нельзя
преобразовать в uint256, потому что uint256 не может хранить отрицательные числа. Кроме того, целые числа без знака могут быть преобразованы в байты того же
или большего размера, но не наоборот. Любой тип, который может быть преобразован в uint160, также может быть преобразован в тип address.
Solidity поддерживает и явное преобразование типов. Если компилятор не допускает неявное преобразование типов, вы можете попробовать применить явное преобразование. Тем не менее, настоятельно рекомендуется избегать явного преобразования, потому что оно может дать неожиданные результаты.
Давайте рассмотрим пример явного преобразования:
uint32 a = 0x12345678;
uint16 b = uint16(a); // теперь b = 0x5678

Здесь мы преобразовываем uint32 в uint16 в явном виде, то есть превращаем больший тип в меньший. Следовательно, старшие разряды числа отбрасываются.

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

[ 74 ] ___________________________________

Разработка смарт-контрактов

своено, тип переменной фиксируется. Если вы захотите присвоить этой переменной
значение другого типа, это вызовет преобразование типа.
Вот простой пример, который демонстрирует применение var:
int256 x = 12;
//тип переменной y будет тоже int256
var y = x;
uint256 z= 9;
//здесь возникнет исключение, потому что
//неявное преобразование типа int в uint невозможно
y = z;
Помните, что ключевое слово var нельзя использовать при объявлении массивов
и сопоставлений, а также при объявлении параметров функции и статических
переменных.

Управляющие структуры
Solidity поддерживает управляющие структуры if, else, while, for, break, continue,
return и конструкцию ? : 3.
В листинге 3.8 приведены примеры использования управляющих структур.
Листинг 3.8. Примеры управляющих структур
contract sample{
int a = 12;
int[] b;
function sample()
{
//оператор "==" вызовет исключение для сложных типов данных
if(a == 12)
{
}
else if(a == 34)
{
}
else
{
}
var temp = 10;

3

Тернарный оператор условия.

____________________________________

[ 75 ] ___________________________________

Глава 3
while(temp < 20)
{
if(temp == 17)
{
break;
}
else
{
continue;
}
temp++;
}
for(var iii = 0; iii < b.length; iii++)
{
//операторы цикла for
}
}
}

Оператор new и создание контракта
Контракт может создать новый контракт при помощи оператора new. Должен быть
известен полный код создаваемого контракта.
Далее приведен пример создания контракта. Мы не станем выделять этот простой
пример в отдельный листинг.
contract sample1
{
int a;
function assign(int b)
{
a = b;
}
}
contract sample2{
function sample2()
{
sample1 s = new sample1();
s.assign(12);
}
}
____________________________________

[ 76 ] ___________________________________

Разработка смарт-контрактов

Исключения
В некоторых случаях исключения генерируются автоматически. Вы можете использовать оператор throw, чтобы принудительно вбросить исключение. Действие
исключения состоит в том, что выполнение всех текущих вызовов прекращается
и ревертируется (возвращается к исходному состоянию, какие-либо изменения
баланса и статуса счета не происходят). Перехват исключений невозможен. Так
выглядит пример принудительного вброса исключения:
contract sample
{
function myFunction()
{
throw;
}
}

Вызов внешних функций
В Solidity существуют два вида вызова функций: внутренний и внешний. Внутренний вызов происходит, когда одна функция вызывает другую функцию внутри того
же самого контракта.
При внешнем вызове происходит вызов функции другого контракта. Давайте рассмотрим пример, приведенный в листинге 3.9.
Листинг 3.9. Пример вызова внешней функции
contract sample1
{
int a;
//"payable" - это встроенный модификатор
//этот модификатор требуется, если другой контракт отправляет эфир,
//когда вызывает метод
function sample1(int b) payable
{
a = b;
}
function assign(int c)
{
a = c;
}

____________________________________

[ 77 ] ___________________________________

Глава 3
function makePayment(int d) payable
{
a = d;
}
}
contract sample2{
function hello()
{
}
function sample2(address addressOfContract)
{
//отправка 12 wei при создании контракта
sample1 s = (new sample1).value(12)(23);
s.makePayment(22);
//также отправляем эфир
s.makePayment.value(45)(12);
//назначаем стоимость газа
s.makePayment.gas(895)(12);
//отправляем эфир, а также назначаем стоимость газа
s.makePayment.value(4).gas(900)(12);
//hello() - это внутренний вызов, this.hello() - это
//внешний вызов
this.hello();
//указатель на контракт, который уже развернут
sample1 s2 = sample1(addressOfContract);
s2.makePayment(112);
}
}
Вызовы с ключевым словом this выполняются как внешние. Ключевое слово
this внутри функции ссылается на текущий экземпляр контракта.

____________________________________

[ 78 ] ___________________________________

Разработка смарт-контрактов

Свойства контракта
Пришло время подробнее изучить контракты. Мы узнаем о некоторых новых их
свойствах, а также более детально рассмотрим уже знакомые опции.

Видимость
Видимость статических переменных или функций определяет, кто их может видеть.
Существует четыре типа видимости функций и статических переменных: external,
public, internal и private.
По умолчанию функции имеют видимость public, а статические переменные —
видимость internal. Давайте разберемся, что означает каждая разновидность видимости:
 external — внешние функции (external functions) могут быть вызваны только из

другого контракта или через транзакцию. Внешняя функция f не может быть
вызвана внутренним вызовом. Это означает, что вызов f() не будет работать, но
вызов this.f() сработает. Вы не можете применить видимость external к статическим переменным;
 public — общие функции и статические переменные могут быть доступны лю-

быми возможными способами. Созданные компилятором функции доступа
включают все статические переменные. Вы не можете создавать собственные
функции доступа (аксессоры). Фактически компилятор генерирует только геттеры4, но не сеттеры5;
 internal — внутренние функции и статические переменные могут быть доступ-

ны только внутри текущего контракта и контрактов, наследующих его. Вы не
можете использовать для доступа ключевое слово this;
 private — частные функции и статические переменные похожи на внутренние,

но они недоступны для наследующих контрактов.
В листинге 3.10 приведен пример, демонстрирующий видимость и функции доступа.
Листинг 3.10. Пример различной видимости и функций доступа
contract sample1
{
int public b = 78;
int internal c = 90;

4
5

Геттер (getter) — метод, возвращающий значение переменной.
Сеттер (setter) — метод, присваивающий значение переменной.

____________________________________

[ 79 ] ___________________________________

Глава 3
function sample1()
{
//внешний доступ
this.a();
//ошибка компиляции
a();
//внутренний доступ
b = 21;
//внешний доступ
this.b;
//внешний вызов
this.b();
//ошибка компиляции
this.b(8);
//ошибка компиляции
this.c();
//внутренний доступ
c = 9;
}
function a() external
{
}
}
contract sample2
{
int internal d = 9;
int private e = 90;
}
//sample3 наследует sample2
contract sample3 is sample2
{
sample1 s;
function sample3()
{
s = new sample1();
____________________________________

[ 80 ] ___________________________________

Разработка смарт-контрактов
//внешний доступ
s.a();
//внешний доступ
var f = s.b;
//ошибка компиляции, так как аксессор нельзя использовать
//для присвоения значения (в качестве сеттера)
s.b = 18;
//ошибка компиляции
s.c();
//внутренний доступ
d = 8;
//ошибка компиляции
e = 7;
}
}

Модификаторы
Мы уже видели раньше модификатор6 функции и даже использовали его встроенный вариант (см. листинг 3.9). Теперь изучим модификаторы подробнее.
Модификаторы наследуются дочерними контрактами и могут быть переназначены
ими. К одной функции можно применить несколько модификаторов, разделенных
пробелами, при этом модификаторы будут задействованы в порядке перечисления.
Вы также можете передавать аргументы в модификаторы.
Внутри модификатора следующий модификатор (или тело функции, в зависимости
от того, что будет дальше) начинается после символов _;.
Давайте рассмотрим более сложный пример модификаторов функций (листинг 3.11).
Листинг 3.11. Пример использования модификаторов функций
contract sample
{
int a = 90;.
modifier myModifier1(int b) {
int c = b;
_;

6

Модификаторы служат для изменения объявлений типов и их членов.

____________________________________

[ 81 ] ___________________________________

Глава 3
c = a;
a = 8;
}
modifier myModifier2 {
int c = a;
_;
}
modifier myModifier3 {
a = 96;
return;
_;
a = 99;
}
modifier myModifier4 {
int c = a;
_;
}
function myFunction() myModifier1(a) myModifier2 myModifier3 returns (int d)
{
a = 1;
return a;
}
}

В этом примере мы описали несколько модификаторов и применили эти модификаторы к функции myFunction(). Вот как будет выполняться функция myFunction() на
самом деле:
int c = b;
int c = a;
a = 96;
return;
int c = a;
a = 1;
return a;
a = 99;
c = a;
a = 8;

В данном случае вызов myFunction() возвратит ноль. Но если затем вы прочитаете
значение статической переменной a, то окажется, что ее значение равно восьми.
Оператор return в модификаторе или теле функции приводит к немедленному выходу из функции и возвращает значения переменных в том виде, как есть.
____________________________________

[ 82 ] ___________________________________

Разработка смарт-контрактов

Но в случае нашей функции с модификаторами код после оператора return продолжает выполняться даже после того, как завершен вызываемый код. То есть, при
наличии модификатора, код после метки _; в предыдущем модификаторе выполнен
уже после завершения вызываемого кода. В предыдущем фрагменте (который соответствует фактическому коду функции) строки 5, 6 и 7 никогда не выполняются.
После строки номер 4 будут выполнены строки 8, 9 и 10.
Оператор return внутри модификатора не может иметь связанное значение — он
всегда возвращает только ноль.

Резервная функция
Контракт может содержать только одну безымянную функцию, которая называется
резервной функцией (fallback function). Эта функция не может иметь аргументы и
ничего не возвращает. Она выполняется при обращении к контракту, если ни одно
имя функции в контракте не совпадает с указанным при вызове идентификатором.
Эта функция также выполняется, как только контракт получает эфир без обращения к функциям (транзакция переводит эфир контракту, но не вызывает ни одного
метода контракта). В данном случае для вызова функции доступно очень мало газа
(точнее, 2300 единиц), поэтому важно делать резервные функции как можно
дешевле7.
Вот небольшой пример резервной функции:
contract sample
{
function() payable
{
//например, код для уведомления о том,
//сколько эфиров было получено
}
}

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

На практике резервную функцию часто используют в простых смарт-контрактах, единственное назначение которых — получать или отправлять дискретные платежи. Например, это может быть контракт для первоначального размещения токенов, который получает оплату за токен и создает запись
в реестре акционеров. Прочитать больше о резервной функции можно по адресу:
https://github.com/ConsenSys/Ethereum-Development-Best-Practices/wiki/Fallback-functionsand-the-fundamental-limitations-of-using-send()-in-Ethereum-&-Solidity.

____________________________________

[ 83 ] ___________________________________

Глава 3

В листинге 3.12 приведен пример наследования.
Листинг 3.12. Пример наследования контрактов
contract sample1
{
function a(){}
function b(){}
}
//контракт sample2 наследует sample1
//и переопределяет функцию b()
contract sample2 is sample1
{
function b(){}
}
contract sample3
{
function sample3(int b)
{
//код функции
}
}
//контракт sample4 наследует sample1 и sample2,
//но контракт sample1 - это родитель для sample2, по сути мы получаем
//лишь реализацию сущности sample1
contract sample4 is sample1, sample2
{
function a(){}
function c(){
//выполняем метод a() контракта sample4
a();
//выполняем метод a() контракта sample1
sample1.a();
//выполняем метод sample2.b(), потому что он последний
//в списке родителей и перекрывает собой sample1.b()
b();
}
}

____________________________________

[ 84 ] ___________________________________

Разработка смарт-контрактов
//если конструктор содержит аргумент, он должен быть представлен
//в конструкторе дочернего контракта,
//но в Solidity дочерний конструктор не вызывает родительский
//конструктор, а инициализирует и копирует родителя
contract sample5 is sample3(122)
{
}

Ключевое слово super
Ключевое слово super применяется для ссылки на следующий контракт в окончательной последовательности наследования. Для более глубокого понимания рассмотрим пример из листинга 3.13.
Листинг 3.13. Пример использования ключевого слова super
contract sample1
{
}
contract sample2
{
}
contract sample3 is sample2
{
}
contract sample4 is sample2
{
}
contract sample5 is sample4
{
function myFunc()
{
}
}
contract sample6 is sample1, sample2, sample3, sample5
{
function myFunc()
{
//соответствует ссылке sample5.myFunc()
super.myFunc();
}
}
____________________________________

[ 85 ] ___________________________________

Глава 3

По отношению к контракту sample6 цепочка наследования имеет вид:
sample6, sample5, sample4, sample2, sample3, sample1

Цепочка наследования начинается с самого младшего контракта и заканчивается
самым старшим.

Абстрактные контракты
Контракты, которые содержат только прототипы функций, но не их реализацию,
называются абстрактными контрактами. Такие контракты не могут быть скомпилированы (даже если они содержат реализованные функции наряду с нереализованными функциями). Если контракт наследуется от абстрактного контракта и не
реализует все нереализованные функции путем переопределения, то такой контракт
сам становится абстрактным.
Абстрактные контракты объявляются только для того, чтобы сделать интерфейс
известным компилятору. Это полезно, когда вы обращаетесь к развернутому
контракту и вызываете его функции. Вот простой пример, который демонстрирует
такую ситуацию:
contract sample1
{
function a() returns (int b);
}
contract sample2
{
function myFunc()
{
sample1 s = sample1(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
//без абстрактного контракта эта часть кода
//вызовет ошибку компиляции
s.a();
}
}

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

[ 86 ] ___________________________________

Разработка смарт-контрактов

если доступ предоставлен в явном виде (в противном случае не будет иного способа обращения).
Библиотеки не могут иметь свои статические переменные. Они не поддерживают
наследование и не могут получать эфир. Библиотеки могут содержать структуры и
перечисления.
Как только библиотека Solidity размещена в блокчейне, ей может воспользоваться
любой желающий, — если знает ее адрес и имеет на руках исходный код (только
прототипы или полную реализацию). Исходный код нужен компилятору Solidity,
чтобы убедиться, что методы, которые вы собираетесь вызывать, действительно
содержатся в библиотеке.
Ознакомьтесь с небольшим примером кода:
library math
{
function addInt(int a, int b) returns (int c)
{
return a + b;
}
}
contract sample
{
function data() returns (int d)
{
return math.addInt(1, 2);
}
}

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

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

[ 87 ] ___________________________________

Глава 3

 библиотеки могут быть использованы для добавления функций принадлежности

к типам данных.

Конструкция using ... for ...
Конструкцию using ... for ... можно использовать для подключения библиотечных функций (из библиотеки A к любому типу B). Эти функции будут получать
объект, который их вызвал, в качестве своего первого параметра.
Результатом работы команды вида using A for *; будет присоединение функций из
библиотеки A ко всем типам.
В листинге 3.14 приведен пример использования конструкции using ... for ...
Листинг 3.14. Пример использования конструкции using … for …
library math
{
struct myStruct1 {
int a;
}
struct myStruct2 {
int a;
}
//Здесь мы должны обратиться к хранилищу s, чтобы получить ссылку,
//в противном случае addInt будет обращаться не к тому
//экземпляру myStruct1, который его вызывает
function addInt(myStruct1 storage s, int b) returns (int c)
{
return s.a + b;
}
function subInt(myStruct2 storage s, int b) returns (int c)
{
return s.a + b;
}
}
contract sample
{
//"*" присоединяет функции библиотеки ко всем структурам
using math for *;
math.myStruct1 s1;
math.myStruct2 s2;
____________________________________

[ 88 ] ___________________________________

Разработка смарт-контрактов
function sample()
{
s1 = math.myStruct1(9);
s2 = math.myStruct2(9);
s1.addInt(2);
//ошибка компиляции, потому что функция addInt
//не подключена к myStruct2
s2.addInt(1);
}
}

Возврат нескольких значений
Solidity позволяет функциям возвращать несколько значений. Вот небольшой пример, который это демонстрирует:
contract sample
{
function a() returns (int a, string c)
{
return (1, "ss");
}
function b()
{
int A;
string memory B;
//A получит 1 и B получит "ss"
(A, B) = a();
//A получит 1
(A,) = a();
//B получит "ss"
(, B) = a();
}
}

Импорт файлов исходных кодов Solidity
Solidity позволяет импортировать в исходный код содержимое других файлов с исходным кодом. Вот короткий пример, который демонстрирует эту возможность:
//Этот оператор импортирует все глобальные символы из "filename"
//(и символы, импортированные там) в текущую глобальную область.
____________________________________

[ 89 ] ___________________________________

Глава 3
//"filename" может содержать абсолютный или относительный путь.
//Это может быть только HTTP-адрес
import "filename";
//создает новый глобальный символ symbolName, который является членом
//глобального пространства символов "filename"
import * as symbolName from "filename";
//creates new global symbols alias and symbol2 which reference symbol1 and symbol2
from "filename", respectively.
import {symbol1 as alias, symbol2} from "filename";
//this is equivalent to import * as symbolName from "filename";.
import "filename" as symbolName;

Глобальные переменные
Существуют особые переменные и функции, которые всегда доступны глобально.
Они рассмотрены в следующих разделах.

Свойства блока и транзакции
Блок и транзакция имеют следующие стандартные свойства:
 block.blockhash(uint blockNumber) returns (bytes32) — хеш данного блока, рабо-

тает только для 256 последних блоков;
 block.coinbase (address) — адрес майнера текущего блока;
 block.difficulty (uint) — сложность текущего блока;
 block.gaslimit (uint) — лимит газа текущего блока. Определяет максимальное

количество газа, которое можно потратить на все транзакции блока. Его назначение заключается в поддержании низкого времени распространения и обработки блока. Майнеры имеют право принять лимит газа для текущего блока
в диапазоне ~0,0975% (1/1024) от лимита газа последнего блока, поэтому лимит
газа должен быть усредненным относительно требований майнеров;
 block.number (uint) — номер текущего блока;
 block.timestamp (uint) — метка времени текущего блока;
 msg.data (bytes) — полные данные вызова. Содержат функцию, вызываемую

транзакцией, и ее аргументы;
 msg.gas (uint) — остаток газа;
 msg.sender (address) — адрес отправителя сообщения (текущего вызова);
____________________________________

[ 90 ] ___________________________________

Разработка смарт-контрактов

 msg.sig (bytes4) — четыре первых байта вызова(идентификатор функции);
 msg.value (uint) — количество Wei, переданных с сообщением;
 now (uint) — метка времени текущего блока (псевдоним для block.timestamp);
 tx.gasprice (uint) — цена газа для транзакции;
 tx.origin (address) — адрес отправителя транзакции (полная цепочка вызовов).

Свойства, связанные с адресом
Свойства, связанные с адресом, входят в следующий перечень:
 .balance (uint256) — баланс заданного адреса в Wei;
 .send(uint256 amount) returns (bool) — отправка8 заданного количества

Wei на заданный address. Возвращает false при неудаче.

Переменные, связанные с контрактом
Переменные, связанные с контрактом:
 this — указатель на текущий контракт, в явном виде преобразуется в тип
address;

 selfdestruct(address recipient) — уничтожает текущий контракт и переводит

все средства контракта на указанный адрес9.

Единицы эфира
Сумма валюты может иметь суффикс wei, finney, szabo или Ether для конвертации
между субноминациями эфира, где сумма без указания суффикса номинирована
в wei. Например, сопоставление 2 Ether == 2000 finney вернет true.

Доказательство наличия, целостности
и принадлежности файла
Давайте напишем на языке Solidity контракт, который может подтвердить принадлежность файла, не раскрывая содержимое самого файла. Он может доказать, что
файл существовал в определенное время, и проверить целостность документа.
8

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

[ 91 ] ___________________________________

Глава 3

При этом мы получим доказательство владения, сохранив пару, состоящую из хеша
файла и имени владельца. Мы получим доказательство существования, сохранив
пару, состоящую из хеша файла и метки времени блока. Наконец, сохранение самого хеша доказывает целостность файла. Если файл изменить, то его хеш тоже изменится, и контракт больше не сможет найти файл, что послужит доказательством
изменения файла.
В листинге 3.15 приведен исходный код контракта, который решает эту задачу10.
Листинг 3.15. Пример контракта, доказывающего владение файлом
contract Proof
{
struct FileDetails
{
uint timestamp;
string owner;
}
mapping (string => FileDetails) files;
event logFileAddedStatus(bool status, uint timestamp, string owner,
string fileHash);
//функция для сохранения владельца файла и метки времени блока
function set(string owner, string fileHash)
{
//не существует корректного способа проверить,
//есть ли уже такой ключ,
//поэтому мы проверяем значение по умолчанию (все биты нулевые)
if(files[fileHash].timestamp == 0)
{
files[fileHash] = FileDetails(block.timestamp, owner);
//мы создаем событие, благодаря которому программа-оболочка
//узнает от контракта, что наличие файла
//и принадлежности сохранено
logFileAddedStatus(true, block.timestamp, owner, fileHash);
}

10

Дополнительно напомним начинающим разработчикам, что смарт-контракт сам по себе не общается с пользователем. Это back-end (серверная часть). Пользователь же имеет дело с приложением frontend, то есть с приложением уровня клиентского интерфейса. Например, это может быть оболочка кошелька, приложение для голосования или веб-интерфейс. Приложения front-end вызывают методы
контракта и получают от него ответные данные.
____________________________________

[ 92 ] ___________________________________

Разработка смарт-контрактов
else
{
//здесь мы сообщаем программе-оболочке, что
//подтверждение существования
//и принадлежности не могут быть сохранены, потому
//что данные файла
//уже были сохранены раньше
logFileAddedStatus(false, block.timestamp, owner, fileHash);
}
}
//функция для получения сведений о файле
function get(string fileHash) returns (uint timestamp, string owner)
{
return (files[fileHash].timestamp, files[fileHash].owner);
}
}

Компиляция и развертывание контракта
Ethereum имеет собственный компилятор SOLC (SOLidity Compiler), взаимодействие с которым происходит через командную строку. По адресу: http://solidity.
readthedocs.io/en/develop/installing-solidity.html#binary-packages вы найдете руководство по установке, а по адресу: https://solidity.readthedocs.io/en/develop/
using-the-compiler.html получите инструкции по использованию компилятора.
Но мы не станем работать с компилятором напрямую. Мы воспользуемся инструментами, которые называются solcjs и браузер Solidity. Утилита solcjs позволяет
компилировать код Solidity в среде Node.js, а браузер Solidity — это среда разработки (IDE), которая подходит для небольших контрактов.
Давайте прямо сейчас скомпилируем контракт из листинга 3.15 при помощи браузера Solidity, который нам предоставляет платформа Ethereum. Прочитать больше
об этом браузере можно по адресу: https://Ethereum.github.io/browser-Solidity/11.
Вы также можете по ссылке: https://github.com/Ethereum/browser-Solidity/tree/
gh-pages скачать исходный код браузера и использовать его локально, без подключения к сети.
Главное преимущество браузера Solidity заключается в том, что он имеет встроенный редактор и генерирует код для развертывания контракта.
Итак, скопируйте и вставьте в редактор исходный код из листинга 3.15 — вы увидите, что контракт компилируется и получается код web3.js для развертывания контракта при помощи интерактивной консоли Geth (см. главу 2).
11

На момент подготовки перевода ссылка не работала. Воспользуйтесь общей ссылкой: https://github.com/
ethereum/, чтобы выбрать нужную тему.

____________________________________

[ 93 ] ___________________________________

Глава 3

Вы получите на выходе следующий код:
var proofContract =
web3.eth.contract([{"constant":false,"inputs":[{"name":"fileHash","type":"s
tring"}],"name":"get","outputs":[{"name":"timestamp","type":"uint256"},{"na
me":"owner","type":"string"}],"payable":false,"type":"function"},{"constant
":false,"inputs":[{"name":"owner","type":"string"},{"name":"fileHash","type
":"string"}],"name":"set","outputs":[],"payable":false,"type":"function"},{
"anonymous":false,"inputs":[{"indexed":false,"name":"status","type":"bool"}
,{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"na
me":"owner","type":"string"},{"indexed":false,"name":"fileHash","type":"str
ing"}],"name":"logFileAddedStatus","type":"event"}]);
var proof = proofContract.new(
{
from: web3.eth.accounts[0],
data: '60606040526......,
gas: 4700000
}, function (e, contract){
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + '
transactionHash: ' + contract.transactionHash);
}
})

Блок data (выделен полужирным шрифтом) представляет собой байт-код (скомпилированную версию контракта), который понятен виртуальной машине Ethereum.
Исходный код сначала конвертируется в операционный код (opcode, мнемонические коды операций), а затем в байт-код виртуальной машины. Каждый операционный код имеет связанный с ним газ.
Первый аргумент в web3.eth.contract — это определение ABI (Application Binary
Interface, двоичный интерфейс приложения). Определение ABI применяется при
создании транзакций и содержит объявление прототипов всех методов.
Теперь запустите Geth в режиме разработчика, обязательно с включенным майнингом. Для этого введите в окне командной строки12:
geth --dev --mine --etherbase 'здесь подставьте ваш адрес Ethereum'

Откройте еще одно окно командной строки и введите команду запуска интерактивной консоли JavaScript с подключением к работающему узлу:
geth attach

После этого консоль JavaScript должна подключиться к процессу, запущенному
в другом окне.
12

Новая версия Geth не запустит режим майнинга даже в отладочном режиме без указания вашего
адреса в опции –etherbase.
____________________________________

[ 94 ] ___________________________________

Разработка смарт-контрактов

В правой панели браузера Solidity скопируйте содержимое текстового поля web3
deploy, вставьте его в интерактивную консоль и нажмите клавишу . Сначала вы получите хеш транзакции, а после некоторого ожидания, которое займет
майнинг транзакции, вы получите адрес контракта. Каждый развернутый контракт имеет уникальный адрес, по которому определяется в блокчейне.
Адрес контракта определенным образом вычисляется из адреса создателя (адрес
from) и количества отправленных транзакций (transaction nonce). Два этих числа
подвергаются RLP-кодированию13, а затем хешируются по алгоритму Keccak-256.
Мы рассмотрим понятие transaction nonce позже. Про RLP-кодирование можно
подробнее прочитать по адресу: https://github.com/ethereum/wiki/wiki/RLP.
Теперь давайте сохраним информацию о файле и запросим ее обратно.
Вставьте в консоль код из листинга 3.16, чтобы передать транзакцию сохранения
информации о файле.
Листинг 3.16. Код для отправки транзакции с данными файла
var contract_obj =
proofContract.at("0x9220c8ec6489a4298b06c2183cf04fb7e8fbd6d4");
contract_obj.set.sendTransaction("Owner Name",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", {
from: web3.eth.accounts[0],
}, function(error, transactionHash){
if (!err)
console.log(transactionHash);
})

В этом листинге замените адрес контракта на адрес, который вы только что получили. Первый аргумент метода proofContract.at() — это и есть адрес контракта.
Здесь мы не указали количество газа, в таком случае это значение вычисляется
автоматически.
Теперь давайте извлечем информацию о файле. Введите в консоль этот код14:
contract_obj.get.call("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852
b855");

Вы получите следующий ответ:
[1477591434, "Owner Name"]

Метод call() применяется для вызова метода контракта из текущего расположения.
Он не транслирует транзакцию. Для чтения данных нам не требуется трансляция,
потому что у нас есть собственная копия блокчейна.
Более подробно о пакете библиотек web3.js вы узнаете в следующих главах.
13
14

Recursive Length Prefix, основной метод кодирования для сериализации объектов Ethereum.
Вы можете скопировать хеш файла из листинга 3.16.

____________________________________

[ 95 ] ___________________________________

Глава 3

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

____________________________________

[ 96 ] ___________________________________



Учимся работать с web3.js

Из предыдущей главы мы узнали, как писать смарт-контракты и использовать интерактивную консоль Geth для размещения и трансляции транзакций при помощи
пакета библиотек web3.js. В этой главе мы более детально изучим web3.js и узнаем,
как подключаться к Geth и использовать библиотеки web3.js в среде Node.js и в
программах JavaScript на стороне клиента. Мы также узнаем, как при помощи
web3.js создать клиентскую оболочку для смарт-контракта, разработанного нами
в главе 3.
В этой главе мы рассмотрим следующие темы:
 импорт web3.js в среду Node.js и клиентские программы JavaScript;
 подключение к Geth;
 обзор возможностей, которые дает нам web3.js;
 исследование наиболее востребованных API web3.js;
 разработка в среде Node.js приложения для смарт-контракта.

Введение в web3.js
web3.js — это пакет библиотек, которые предоставляют нам API для взаимодействия с Geth на уровне протокола JSON-RPC1. Библиотеки web3.js могут также работать с любой другой реализацией узла Ethereum при условии поддержки протокола JSON-RPC. web3.js поддерживает не только API, относящиеся к узлу сети
Ethereum, но и API приложений Whisper и Swarm.

1

Remote Procedure Call (RPC) — протокол удаленного вызова процедур, не сохраняющий содержание
вызова.

Глава 4

По мере того, как мы станем разрабатывать различные проекты, вы будете узнавать
про web3.js все больше и больше. Но сейчас мы обсудим наиболее востребованные
API пакета web3.js, а затем разработаем оболочку для нашего смарт-контракта
с применением web3.js.
На момент работы над книгой актуальной была версия web3.js 0.16.02. В дальнейшем мы будем подразумевать использование этой версии.
Пакет web3.js находится по адресу: https://github.com/ethereum/web3.js, а полная
документация к нему доступна по адресу: https://github.com/ethereum/wiki/
wiki/JavaScript-API.

Импортирование web3.js
Чтобы использовать web3.js в среде Node.js, вам достаточно, находясь внутри каталога проекта, выполнить команду:
npm install web3

В исходном же коде вы можете выполнить импорт web3.js при помощи директивы:
require("web3");

Чтобы использовать web3.js на стороне клиентского приложения JavaScript, вы можете подключить файл web3.js, который находится в каталоге dist исходного кода
проекта. Теперь у вас есть объект Web3, доступный глобально.

Подключение к узлу
web3.js может взаимодействовать с узлами по протоколу HTTP или IPC. Для установления связи с узлом мы воспользуемся протоколом HTTP. Допускается установление связи с несколькими узлами, при этом каждый экземпляр web3 представляет соединение с узлом и предоставляет набор API.
Когда приложение запущено внутри приложения-клиента Mist, оно автоматически
создает доступный экземпляр web3, подключенный к узлу Mist. Имя переменной
экземпляра web3.
Так выглядит базовый код для подключения к узлу:
if (typeof web3 !== 'undefined') {
web3 = new Web3(new
Web3.providers.HttpProvider("http://localhost:8545"));
}

2

Вероятно, автор имел в виду версию API web3 Ethereum, которая в период работы над переводом
книги имела номер 0.20.0. Сам пакет web3.js во время подготовки книги имел версию 0.13.0, а во время работы над переводом была актуальна уже версия 0.14.0.

____________________________________

[ 98 ] ___________________________________

Учимся работать с web3.js

Сначала мы уточняем, запущен ли код внутри Mist. Для этого мы проверяем тип
web3 — является ли он неопределенным (undefined). Если тип web3 определен, мы
воспользуемся уже существующим экземпляром. В ином случае мы создаем экземпляр путем подключения к нашему пользовательскому узлу. Если вы хотите подключиться к узлу независимо от наличия работающего приложения внутри Mist,
просто удалите условие if из приведенного здесь кода. В данном примере мы подразумеваем, что узел доступен локально через порт 8545.
Объект Web3.providers предоставляет конструкторы (в данном контексте называемые провайдерами), чтобы устанавливать соединения и передавать сообщения
с использованием различных протоколов:
 Web3.providers.HttpProvider — устанавливает HTTP-соединение;
 Web3.providers.IpcProvider — устанавливает IPC-соединение.

Свойству Web3.providers.IpcProvider автоматически присваивается текущий экземпляр провайдера. После создания экземпляра web3 вы можете изменить его провайдера при помощи метода web3.setProvider(). Он получает единственный аргумент — экземпляр нового провайдера.
По умолчанию при запуске Geth протокол HTTP-RPC отключен. Чтобы включить
его, добавьте в строку запуска Geth опцию --rpc. По умолчанию HTTP-RPC использует порт 8545.

Метод isConnected() применяется для проверки наличия подключения к узлу и возвращает true или false.

Структура API
Экземпляр web3 содержит объект eth (web3.eth), предназначенный специально для
взаимодействия с блокчейном Ethereum, и объект shh (web3.shh) — для взаимодействия с Whisper. Большая часть API пакета web3.js представлена внутри двух этих
объектов.
Все API по умолчанию синхронные. Если вы хотите выполнить асинхронный
запрос, то для большинства функций API должны добавить функцию обратного
вызова (callback-функцию) в качестве последнего параметра. Все функции обратного вызова используют формат вызова «error-first»3.
Некоторые API имеют псевдоним для асинхронных запросов. Например,
web3.eth.coinbase() является синхронным, а web3.eth.getCoinbase() — асинхронным.

3

Простое описание формата вызова «error-first» читатели могут найти по адресу: http://fredkschott.com/
post/2014/03/understanding-error-first-callbacks-in-node-js. Если коротко, первый аргумент функции
обратного вызова зарезервирован под объект ошибки err. Когда случается ошибка, функция возвращает объект err, который можно использовать для получения описания ошибки.

____________________________________

[ 99 ] ___________________________________

Глава 4

Приведем простой пример:
//синхронный запрос
try
{
console.log(web3.eth.getBlock(48));
}
catch(e)
{
console.log(e);
}
//асинхронный запрос
web3.eth.getBlock(48, function(error, result){
if(!error)
console.log(result)
else
console.error(error);
})

Метод getBlock() возвращает информацию о блоке по его порядковому номеру или
хешу. Также можно вместо номера использовать ключевые слова earliest (генезисный блок), latest (последний добавленный блок) или pending (блок в майнинге).
Если вы опускаете аргумент, по умолчанию запрашивается web3.eth.defaultBlock,
которому соответствует псевдоним latest.
Все API, которые нуждаются в идентификации блока, могут принимать в качестве
входного аргумента число, хеш или строку. Если аргумент отсутствует, все эти API
по умолчанию используют web3.eth.defaultBlock.

Библиотека BigNumber.js
Язык JavaScript изначально плохо работает с большими числами. Поэтому приложения, которым приходится иметь дело с большими числами и делать точные
вычисления, используют библиотеку BigNumber.js.
Пакет web3.js тоже зависит от BigNumber.js (эта библиотека добавляется автоматически). web3.js всегда возвращает объект типа BigNumber для числовых значений. Он
может принимать числа JavaScript, строковые представления чисел и экземпляры
BigNumber в качестве входного параметра.
Вот пример, который это демонстрирует:
web3.eth.getBalance("0x27E829fB34d14f3384646F938165dfcD30cFfB7c").toString();

Здесь мы используем метод web3.eth.getBalance(), чтобы получить баланс указанного адреса (счета) Ethereum. Этот метод возвращает объект типа BigNumber. Мы
должны вызвать метод toString() объекта BigNumber, чтобы конвертировать его
в строковое представление числа.
____________________________________

[ 100 ] ___________________________________

Учимся работать с web3.js

Библиотека BigNumber.js не может обрабатывать числа, у которых более 20 знаков
после запятой, поэтому рекомендуется хранить баланс в единицах Wei, а для
отображения конвертировать в другие единицы. По умолчанию web3.js всегда возвращает и получает баланс в Wei. Например, метод getBalance() возвратит баланс
счета в Wei.

Конвертация денежных единиц
web3.js предоставляет возможность конвертировать Wei в другие номиналы эфира
и, наоборот, любой другой номинал эфира конвертировать в Wei.
Метод web3.fromWei() предназначен для конвертации суммы, номинированной в Wei
в другой номинал, а метод web3.toWei() выполняет обратную операцию. Вот два
примера, которые демонстрируют конвертацию:
web3.fromWei("1000000000000000000", "ether");
web3.toWei("0.000000000000000001", "ether");

В первой строке мы конвертируем Wei в Ether (один эфир), а во второй строке конвертируем Ether в Wei. Второй аргумент в обоих методах может быть одной из следующих строк:
 kwei/ada;

 ether;

 mwei/babbage;

 kether/grand/einstein;

 gwei/shannon;

 mether;

 szabo;

 gether;

 finney;

 tether.

Запрос цены газа, баланса и деталей транзакции
Теперь давайте рассмотрим API, применяемые для запроса цены газа, баланса на
счете и информации о транзакции (квитанции):
//Это синхронный запрос.
//Для асинхронного запроса используйте getGasPrice
console.log(web3.eth.gasPrice.toString());
console.log(web3.eth.getBalance("0x407d73d8a49eeb85d32cf465507dd71d507100c1",
45).toString());
console.log(web3.eth.getTransactionReceipt("0x9fc76417374aa880d4449a1f7f31ec597f00b
1f6f3dd2d66f4c9c6c445836d8b"));

Возвращаемая этим запросом информация имеет следующий вид:
20000000000
30000000000
____________________________________

[ 101 ] ___________________________________

Глава 4
{
"transactionHash":
"0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b ",
"transactionIndex": 0,
"blockHash":
"0xef95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46",
"blockNumber": 3,
"contractAddress": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"cumulativeGasUsed": 314159,
"gasUsed": 30234
}

Рассмотрим подробнее, как работает приведенный здесь метод:
 web3.eth.gasPrice() — возвращает цену газа, как медианное значение цены для
x последних

блоков;

 web3.ethgetBalance() — возвращает баланс указанного счета (адреса). Все хеши

должны быть отправлены в API web3.js как шестнадцатеричные строки, а не шестнадцатеричные литералы. Входные значения для типа address в Solidity тоже
должны быть шестнадцатеричными строками;
 web3.eth.getTransactionReceipt() — применяется для получения информации о

транзакции (квитанции) с использованием хеша. Он возвращает объект квитанции, если транзакция была найдена в блокчейне. В противном случае метод возвращает null. Объект квитанции содержит следующие свойства:
• blockHash — хеш блока, в который помещена транзакция;
• blockNumber — номер блока, в который помещена транзакция;
• transactionHash — хеш транзакции;
• transactionIndex — целое число, индекс позиции в блоке;
• from — адрес отправителя;
• to — адрес получателя, null — если это транзакция создания блока;
• cumulativeGasUsed — совокупное количество газа, которое расходуется при
выполнении этой транзакции в блоке;
• gasUsed — количество газа, расходуемое при выполнении данной одиночной
транзакции;
• contractAddress — адрес контракта, созданного транзакцией, если это была
транзакция, создающая контракт. В противном случае null;
• logs — массив объектов лога, который сгенерировала данная транзакция.

____________________________________

[ 102 ] ___________________________________

Учимся работать с web3.js

Отправка эфира
Давайте научимся отправлять эфир на любой адрес. Для перевода эфира вам потребуется метод web3.eth.sendTransaction(). Этот метод можно использовать для отправки любых транзакций, но в основном его применяют для перевода эфира, потому что развертывание контракта или вызов метода контракта при помощи этого
метода являются громоздкими и требуют самостоятельной генерации данных транзакции вместо автоматического создания. Метод принимает объект транзакции,
который обладает следующими свойствами:
 from — адрес счета отправителя. Если не указан, по умолчанию используется

свойство web3.eth.defaultAccount;
 to — необязательное свойство. Остается неопределенным для транзакций созда-

ния контракта;
 value — необязательное свойство. Сумма перевода в Wei либо сумма вознаграж-

дения при создании контракта;
 gas — необязательное свойство. Количество газа, расходуемого на транзакцию

(неиспользованный газ возвращается). Если не указано, задается автоматически;
 gasPrice — необязательное свойство. Цена газа для данной транзакции в Wei,

которая определяется по значению цены газа в сети;
 data — необязательное свойство. Это либо байтовая строка, содержащая при-

соединенное сообщение, либо — в случае создания контракта — код инициализации;
 nonce — необязательное свойство. Каждая транзакция имеет связанное с ней це-

лое число nonce. Это счетчик, который показывает число транзакций, переданных отправителем транзакции. Если свойство не определено, то задается автоматически. Это свойство позволяет предотвратить атаку массовыми транзакциями. Напомним, что данное свойство nonce — это вовсе не число nonce,
которое имеет отношение к майнингу блока. Если мы используем значение
nonce, которое больше, чем должна была бы иметь транзакция, то эта транзакция
встает в очередь, пока не поступят другие транзакции. Например, если свойство
nonce следующей транзакции должно иметь значение 4, а мы установили значение 10, то Geth будет ждать шесть промежуточных транзакций, прежде чем
транслировать данную транзакцию. Поэтому наша транзакция, у которой nonce
равно десяти, называется отложенной транзакцией (queued transaction), и не
является транзакцией в обработке (pending transaction).
Вот простой пример того, как отправляют эфир по заданному адресу:
var txnHash = web3.eth.sendTransaction({
from: web3.eth.accounts[0],
to: web3.eth.accounts[1],
value: web3.toWei("1", "ether")
});
____________________________________

[ 103 ] ___________________________________

Глава 4

Здесь мы переводим один эфир со счета номер 0 на счет номер 1. Убедитесь, что
оба счета разблокированы опцией --unlock при запуске Geth. В интерактивной консоли Geth будут запрошены пароли, но API web3.js вне интерактивной консоли
вернут ошибку, если счет заблокирован. Этот метод возвращает хеш транзакции.
Затем вы при помощи метода getTransactionReceipt() можете проверить, обработана ли транзакция майнером.
Вы

также

можете

использовать
API:
web3.personal.listAccounts(),
и web3.personal.newAccount(pwd) для динамического управления счетами.

web3.personal.unlockAccount(addr, pwd)

Работа с контрактами
Теперь давайте рассмотрим работу с контрактами — развертывание нового контракта, получение ссылки на контракт по его адресу, отправку эфира на контракт,
отправку транзакции, вызывающей метод контракта, и оценку количества газа,
нужного для вызова метода.
Для развертывания нового контракта или для получения ссылки на уже развернутый контракт вам надо, прежде всего, создать объект контракта при помощи метода
web3.eth.contract(). Он получает ABI контракта как аргумент и возвращает объект
контракта.
Так выглядит код4 для создания объекта контракта:
var proofContract =
web3.eth.contract([{"constant":false,"inputs":[{"name":"fileHash","type":"s
tring"}],"name":"get","outputs":[{"name":"timestamp","type":"uint256"},{"na
me":"owner","type":"string"}],"payable":false,"type":"function"},{"constant
":false,"inputs":[{"name":"owner","type":"string"},{"name":"fileHash","type
":"string"}],"name":"set","outputs":[],"payable":false,"type":"function"},{
"anonymous":false,"inputs":[{"indexed":false,"name":"status","type":"bool"}
,{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"na
me":"owner","type":"string"},{"indexed":false,"name":"fileHash","type":"str
ing"}],"name":"logFileAddedStatus","type":"event"}]);

Когда ваш контракт готов, вы можете развернуть его при помощи метода new()
объекта контракта или получить ссылку на уже развернутый контракт при помощи
метода at().
Давайте рассмотрим пример того, как развертывают новый контракт:
var proof = proofContract.new({
from: web3.eth.accounts[0],
data: "0x606060405261068...",
gas: "4700000"
},
4

О том, как получить этот код, рассказано в главе 3.

____________________________________

[ 104 ] ___________________________________

Учимся работать с web3.js
function (e, contract){
if(e)
{
console.log("Error " + e);
}
else if(contract.address != undefined)
{
console.log("Contract Address: " + contract.address);
}
else
{
console.log("Txn Hash: " + contract.transactionHash)
}
})

В данном случае метод new() вызывается асинхронно, поэтому, если транзакция
создана и успешно передана в сеть, обратный вызов срабатывает дважды: сначала — когда транзакция передана, а затем — когда она обработана майнером. Если
вы не предусмотрели обратный вызов, то поле address переменной proof будет
установлено в undefined. Как только майнинг контракта завершен, присваивается
значение полю address.
В нашем контракте proof нет конструктора, но если конструктор имеется, то аргументы конструктора должны быть помещены в начало метода new(). Объект, который мы передали, содержит адрес from, байт-код контракта и доступное количество
газа. Эти три параметра должны быть обязательно заданы, иначе транзакция не будет создана. В данном случае data — это байт-код контракта, а свойство to игнорируется.
Для получения ссылки на развернутый контракт вы можете воспользоваться методом at(). Пример кода, который демонстрирует получение ссылки:
var proof = proofContract.at("0xd45e541ca2622386cd820d1d3be74a86531c14a1");

Теперь посмотрим, как отправить транзакцию, которая вызывает метод контракта.
Вот простой пример, демонстрирующий эту процедуру:
proof.set.sendTransaction("Owner Name",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", {
from: web3.eth.accounts[0],
}, function(error, transactionHash){
if (!err)
console.log(transactionHash);
})
____________________________________

[ 105 ] ___________________________________

Глава 4

Здесь мы вызываем метод sendTransaction(). Объект, переданный этому методу, имеет такие же параметры, как и объект, передаваемый методу
web3.eth.sendTransaction(), за исключением того, что игнорируются параметры data
и to.
Если вы хотите вызывать некий метод непосредственно на своем узле, вместо того,
чтобы создавать и транслировать транзакцию, то вместо sendTransaction() используйте call(), например:
var returnValue =
proof.get.call("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");

Иногда необходимо узнать, какое количество газа потребуется для вызова метода,
чтобы принять решение, стоит ли вообще вызывать этот метод. Для решения такой
оценочной задачи можно использовать web3.eth.estimateGas(). Однако прямое использование этого метода вынуждает вас генерировать данные для транзакции.
Следовательно, мы можем воспользоваться методом estimateGas() нашего объекта,
например:
var estimatedGas =
proof.get.estimateGas("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852
b855");
Если вы хотите просто перевести несколько эфиров на контракт без вызова каких-либо
методов,
то
можете
напрямую
воспользоваться
методом
web3.eth.sendTransaction.

Отслеживание событий контракта
Сейчас мы узнаем, как отслеживать события, которые генерирует контракт. Отслеживание событий имеет большое значение, потому что результаты вызова методов
обычно возвращаются путем переключения триггеров событий.
Прежде, чем приступить к изучению отслеживания событий, нам следует узнать,
что такое индексированные параметры событий. Иметь атрибут indexed могут
не более трех параметров события. Этот атрибут применяется для уведомления
узла о том, что клиентское приложение может искать события по совпадению
возвращаемых значений. Если вы не используете атрибут indexed, вам придется
запрашивать все события узла и отфильтровывать нужные. Например, вы можете описать событие logFileAddedStatus таким образом:
event logFileAddedStatus(bool indexed status, uint indexed timestamp,
string owner, string indexed fileHash);

Пример, приведенный в листинге 4.1, демонстрирует, как прослушивать события
контракта.
____________________________________

[ 106 ] ___________________________________

Учимся работать с web3.js
Листинг 4.1. Прослушивание событий контракта
var event = proof.logFileAddedStatus(null, {
fromBlock: 0,
toBlock: "latest"
});
event.get(function(error, result){
if(!error)
{
console.log(result);
}
else
{
console.log(error);
}
})
event.watch(function(error, result){
if(!error)
{
console.log(result.args.status);
}
else
{
console.log(error);
}
})
setTimeout(function(){
event.stopWatching();
}, 60000)
var events = proof.allEvents({
fromBlock: 0,
toBlock: "latest"
});
events.get(function(error, result){
if(!error)
{
console.log(result);
}
else
{
console.log(error);
}
})
____________________________________

[ 107 ] ___________________________________

Глава 4
events.watch(function(error, result){
if(!error)
{
console.log(result.args.status);
}
else
{
console.log(error);
}
})
setTimeout(function(){
events.stopWatching();
}, 60000)

Давайте рассмотрим подробнее, как работает этот код:
1. Сначала мы получаем объект события путем вызова метода, одноименного с экземпляром контракта. Этот метод получает два объекта как аргументы, которые
использует для фильтрации событий:
• первый объект служит для фильтрации событий по индексированным значениям — например: {'valueA': 1, 'valueB': [myFirstAddress, mySecondAddress]}.
По умолчанию все значения фильтров установлены в null. Это означает, что
они будут совпадать со всеми событиями данного типа, поступающими от
этого контракта;
• второй объект может содержать три свойства: fromBlock (самый старый блок,
по умолчанию "latest"), toBlock (самый новый блок, по умолчанию "latest")
и address (список адресов, откуда следует получать логи событий, по умолчанию это адрес контракта).
2. Объект event предоставляет нам три метода: get, watch и stopWatching. Метод get
применяется для получения всех событий в заданном диапазоне блоков. Метод
watch подобен методу get, но следит за изменениями после получения событий.
Метод stopWatching прекращает отслеживание изменений.
3. Далее, у нас есть метод allEvents экземпляра контракта. Он служит для извлечения всех событий контракта.
4. Каждое событие представлено объектом, который имеет следующие свойства:
• args — объект аргументов, которые предоставило событие;
• event — строка, содержащая имя события;
• logIndex — целое число, представляющее позицию индекса (указателя) лога
в блоке;
• transactionIndex — целое число, представляющее транзакцию, по которой
был сгенерирован лог;
____________________________________

[ 108 ] ___________________________________

Учимся работать с web3.js

• transactionHash — строка, представляющая хеш транзакции, по которой был
сгенерирован данный лог;
• address — строка, представляющая адрес, из которого поступил лог;
• blockHash — строка, представляющая хеш блока, в котором был данный лог,
и null, если блок в ожидании подтверждения;
• blockNumber — номер блока, в котором был лог, и null, если блок в ожидании
подтверждения.
Пакет web3.js предоставляет API web3.eth.filter для извлечения и просмотра
событий. Вы можете использовать этот API, но описанный ранее метод намного
проще. Про это можно прочитать больше по адресу:
https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethfilter.

Разработка клиентского приложения
для контракта
В главе 3 мы написали код на языке Solidity, а также на протяжении двух последних
глав мы изучаем web3.js и вызов методов при помощи web3.js. Настало время написать клиентскую оболочку для нашего смарт-контракта, чтобы пользователям было
легче им пользоваться.
Мы разработаем клиент, в котором пользователь выбирает файл, вводит данные
владельца файла и нажимает кнопку Submit (Отправить), чтобы передать в сеть
транзакцию, вызывающую метод set c хешем и данными владельца. Когда транзакция успешно передана, мы выведем на дисплей ее хеш. Кроме того, пользователь
сможет выбрать файл и получить для него сведения о владельце из смартконтракта. А еще клиент будет показывать последние транзакции set, подтвержденные в режиме реального времени.
Мы воспользуемся при разработке следующими фреймворками и модулями:
 sha1.js — для получения хеша файла на стороне клиента;
 jQuery — для манипуляции объектами DOM;
 Bootstrap 4 — для создания адаптивного макета.

На серверной стороне мы задействуем express.js и web3.js. При этом нам надо сделать так, чтобы серверная часть отправляла подтвержденные транзакции клиентскому интерфейсу без постоянных запросов со стороны клиента. Для этого мы воспользуемся сервисом socket.io.
Пакет web3.js может быть задействован и на клиентской стороне. Но в данном
случае это создаст проблемы с безопасностью. Мы пользуемся счетами, сохраненными в Geth, и раскрываем URL-адрес узла Geth клиентскому интерфейсу,
который создает угрозу для эфира, хранящегося на других счетах.
____________________________________

[ 109 ] ___________________________________

Глава 4

Структура проекта
В папке файлов упражнений, дополняющих эту главу (см. приложение), вы найдете
два каталога: Final и Initial. Каталог Final содержит окончательный исходный код
проекта, в то время как Initial содержит пустые файлы исходного кода и библиотеки, что позволяет быстро начать работу над проектом.
Для проверки каталога Final вам понадобится выполнить команду терминала npm
install внутри каталога и заменить заданный в коде файла app.js адрес контракта на адрес, полученный вами после развертывания вашего контракта. После этого запустите приложение командой терминала node app.js внутри каталога Final.

В каталоге Initial находится вложенный каталог public и два файла: app.js и
package.json. Второй файл содержит зависимости для вашего приложения, а файл
app.js — это место, в котором будет храниться серверная часть проекта.
Каталог public содержит файлы, относящиеся к пользовательскому интерфейсу
(клиентская часть). Внутри public/css вы найдете библиотеку фреймворка Bootstrap
bootstrap.min.css, а внутрь public/html вы поместите HTML-код вашего клиентского приложения. В каталоге public/js находятся JavaScript-файлы для jQuery, sha1
и socket.io. Также внутри этого каталога вы найдете файл main.js, в который вы поместите код JavaScript для клиентской части проекта.

Разработка серверной части
Давайте сначала создадим серверную часть нашего приложения. Первым делом
выполним команду npm install внутри каталога Initial — чтобы установить необходимые зависимости для серверной части. Прежде чем приступить к написанию
кода серверной части, убедитесь, что Geth работает, а RPC включен. Если вы запустили Geth в частной сети, также убедитесь, что включен режим майнинга. Наконец,
удостоверьтесь, что счет 0 существует и разблокирован. Вы можете запустить Geth
в частной сети с включенным RPC и майнингом и разблокированным счетом 0 при
помощи единственной команды:
geth --dev --mine --rpc --unlock=0

Осталось лишь развернуть в сети контракт, о котором мы говорили в главе 3, и скопировать адрес контракта.
Теперь создадим автономный сервер (single server), который будет обслуживать
HTML-запросы браузера и принимать подключения сервиса socket.io:
var express = require("express");
var app = express();
var server = require("http").createServer(app);
var io = require("socket.io")(server);
server.listen(8080);

Здесь мы объединили серверы express и socket.io в один сервер с портом 8080.
____________________________________

[ 110 ] ___________________________________

Учимся работать с web3.js

Далее мы объявим пути к статическим файлам и домашней странице приложения.
Вот код, который это делает:
app.use(express.static("public"));
app.get("/", function(req, res){
res.sendFile(__dirname + "/public/html/index.html");
})

Здесь мы используем промежуточный интерфейс express.static для доступа к статическим файлам и указываем ему искать статические файлы в каталоге public.
Теперь подключимся к узлу Geth и получим ссылку на развернутый контракт, благодаря чему сможем отправлять транзакции и следить за событиями. Вот соответствующий фрагмент кода:
var Web3 = require("web3");
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
var proofContract =
web3.eth.contract([{"constant":false,"inputs":[{"name":"fileHash","type":"s
tring"}],"name":"get","outputs":[{"name":"timestamp","type":"uint256"},{"na
me":"owner","type":"string"}],"payable":false,"type":"function"},{"constant
":false,"inputs":[{"name":"owner","type":"string"},{"name":"fileHash","type
":"string"}],"name":"set","outputs":[],"payable":false,"type":"function"},{
"anonymous":false,"inputs":[{"indexed":false,"name":"status","type":"bool"}
,{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"na
me":"owner","type":"string"},{"indexed":false,"name":"fileHash","type":"str
ing"}],"name":"logFileAddedStatus","type":"event"}]);
var proof = proofContract.at("0xf7f02f65d5cd874d180c3575cb8813a9e7736066");

Мы уже несколько раз упоминали эту часть кода раньше, и она не требует пояснений. Просто замените номер контракта на тот, который получили сами.
Сформируем пути для вещания транзакций и получения сведений о файле:
app.get("/submit", function(req, res){
var fileHash = req.query.hash;
var owner = req.query.owner;
proof.set.sendTransaction(owner, fileHash, {
from: web3.eth.accounts[0],
}, function(error, transactionHash){
if (!error)
{
res.send(transactionHash);
}
else
{
res.send("Error");
}
})
})
____________________________________

[ 111 ] ___________________________________

Глава 4
app.get("/getInfo", function(req, res){
var fileHash = req.query.hash;
var details = proof.get.call(fileHash);
res.send(details);
})

В данном случае для создания и трансляции транзакции мы используем путь
/submit. Как только станет известен хеш транзакции, мы сразу отправим его в клиентскую часть приложения, — мы не ждем, пока транзакция находится в процессе
майнинга. Путь /getInfo служит для вызова метода контракта, расположенного на
нашем узле, вместо того, чтобы создавать транзакцию. Он просто пересылает ответ,
который получил от узла.
Теперь приступим к слежению за событиями контракта и трансляции их всем подключившимся клиентам:
proof.logFileAddedStatus().watch(function(error, result){
if(!error)
{
if(result.args.status == true)
{
io.send(result);
}
}
})

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

Разработка клиентской части
Приступим к разработке HTML-кода приложения. Поместите в файл index.html код
из листинга 4.2.
Листинг 4.2. HTML-код клиентского интерфейса










____________________________________

[ 112 ] ___________________________________

Учимся работать с web3.js

Upload any file









Введите имя владельца


Submit
Get Info


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







Транзакции, подтвержденные недавно

Транзакции не найдены










Кратко поясним, как работает этот код:
1. Сначала мы отображаем поле ввода, в котором пользователь может выбрать
файл.
____________________________________

[ 113 ] ___________________________________

Глава 4

2. Затем мы отображаем текстовое поле, в которое пользователь может ввести данные владельца файла.
3. Далее, у нас есть две кнопки: первая кнопка сохраняет хеш файла и данные
пользователя в контракт, а вторая кнопка предназначена для получения сведений о файле из контракта. Нажатие на кнопку Submit вызывает метод submit(),
а нажатие на кнопку Get Info, соответственно, вызывает метод getInfo().
4. В окне приложения предусмотрена область, в которой отображаются сообщения.
5. Наконец, мы отображаем упорядоченный список транзакций контракта, которые
были подтверждены, пока пользователь находится на странице приложения.
Теперь напишем реализации для методов getInfo() и submit(), установления соединения с socket.io и прослушивания сообщений socket.io, поступающих с сервера.
Далее приведен код, который выполняет указанные задачи. Он должен находиться
в файле main.js (листинг 4.3).
Листинг 4.3. Содержимое файла main.js
function submit()
{
var file = document.getElementById("file").files[0];
if(file)
{
var owner = document.getElementById("owner").value;
if(owner == "")
{
alert("Введите имя владельцаа");
}
else
{
var reader = new FileReader();
reader.onload = function (event) {
var hash = sha1(event.target.result);
$.get("/submit?hash=" + hash + "&owner=" + owner, function(data){
if(data == "Error")
{
$("#message").text("Произошла ошибка.");
}
else
{
$("#message").html("Хеш транзакции: " + data);
}
});
};
____________________________________

[ 114 ] ___________________________________

Учимся работать с web3.js
reader.readAsArrayBuffer(file);
}
}
else
{
alert("Выберите файл.");
}
}
function getInfo()
{
var file = document.getElementById("file").files[0];
if(file)
{
var reader = new FileReader();
reader.onload = function (event) {
var hash = sha1(event.target.result);
$.get("/getInfo?hash=" + hash, function(data){
if(data[0] == 0 && data[1] == "")
{
$("#message").html("Файл не найден");
}
else
{
$("#message").html("Метка времени: " + data[0] + " Владелец: " +
data[1]);
}
});
};
reader.readAsArrayBuffer(file);
}
else
{
alert("Please select a file");
}
}
var socket = io("http://localhost:8080");
socket.on("connect", function () {
socket.on("message", function (msg) {
if($("#events_list").text() == "Транзакции не найдены.")
{
$("#events_list").html("Txn Hash: " + msg.transactionHash +
"nOwner: " + msg.args.owner + "nFile Hash: " + msg.args.fileHash +
"");
}
____________________________________

[ 115 ] ___________________________________

Глава 4
else
{
$("#events_list").prepend("Txn Hash: " + msg.transactionHash +
"nOwner: " + msg.args.owner + "nFile Hash: " + msg.args.fileHash +
"");
}
});
});

Код из листинга 4.3 работает следующим образом:
1. Сначала мы определяем метод submit(). В этом методе мы убеждаемся, что выбранный файл существует, и текстовое поле не пустое. Затем мы считываем содержимое файла в буферный массив и передаем этот массив методу sha1(), предоставленному библиотекой sha1.js, чтобы получить хеш содержимого буферного массива. Когда получен хеш, мы используем фреймворк jQuery для
формирования AJAX-запроса по адресу /route, после чего отображаем хеш транзакции в блоке сообщений.
2. Затем мы определяем метод getInfo(). Прежде всего он проверяет, выбран ли
файл. Далее он генерирует хеш аналогично тому, как это делалось в первый раз,
и отправляет запрос по адресу /getInfo для получения сведений о файле.
3. Наконец, мы устанавливаем соединение с сервисом socket.io при помощи метода
io() из библиотеки socket.io. Ждем, пока сработает триггер по событиюсоединения. Это событие означает, что соединение установлено. Теперь мы слушаем
сообщения сервера и отображаем пользователю информацию о транзакциях.
Мы не сохраняем файл в блокчейн Ethereum, потому что хранение файлов обходится очень дорого и расходует большое количество газа. В данном случае нам и
не нужно хранить файл, потому что каждый узел сети сможет его увидеть. Следовательно, вы не сможете сохранить в секрете содержимое файла, если выложите его в блокчейн. Назначение нашего приложения — удостоверять сведения
о владельце, а не хранить файлы в облачном сервисе.

Тестирование клиентской части
Запустите приложение app.js в качестве серверной части приложения. Откройте
ваш любимый браузер и перейдите по адресу: http://localhost:8080/. В окне браузера отобразится интерфейс пользователя (рис. 4.1).
Теперь выберите файл, введите имя владельца и нажмите кнопку Submit. Содержимое экрана изменится и будет выглядеть, как показано на рис. 4.2.
Вы можете видеть, что отобразился хеш транзакции. Теперь подождите, пока транзакция не пройдет процесс майнинга. Когда майнинг завершен, вы можете увидеть
ее хеш в «живом» списке обработанных транзакций (рис. 4.3).
____________________________________

[ 116 ] ___________________________________

Учимся работать с web3.js

Рис. 4.1. Интерфейс пользователя: начальная страница

Рис. 4.2. Отображение хеша транзакции после отправки

____________________________________

[ 117 ] ___________________________________

Глава 4

Рис. 4.3. Отображение хеша и сведений о владельце файла

Рис. 4.4. Сведения о файле, хранящиеся в блокчейне

____________________________________

[ 118 ] ___________________________________

Учимся работать с web3.js

Теперь снова выберите тот же файл и нажмите кнопку Get Info — вы увидите
сведения о файле (рис. 4.4). Эти сведения содержат метку времени и сведения
о владельце.
Итак, мы закончили работу над клиентской частью нашего первого децентрализованного приложения!

Заключение
В этой главе мы на примерах изучили основы web3.js. Мы узнали, как подключиться к узлу, освоили базовые API и отправку различных типов транзакций, а также
научились отслеживать события. В завершение главы мы разработали клиентское
приложение для нашего контракта, заверяющего владение файлом. Теперь вам
будет проще писать смарт-контракты и разрабатывать для них интерфейсные оболочки, которые значительно облегчают пользование контрактами.
В следующей главе мы разработаем сервис бумажника (wallet), благодаря которому
пользователям будет проще создавать бумажники Ethereum Wallet и управлять ими
даже без доступа к сети.

____________________________________

[ 119 ] ___________________________________



Разработка
сервиса кошелька

Сервис кошелька предназначен для отправки и получения средств. Главной задачей
при создании такого сервиса является обеспечение безопасности и доверия: пользователь должен быть уверен, что его средства находятся в безопасности и что их
не украдет администратор сервиса. Приложение, которое мы с вами разработаем,
решает обе проблемы.
В этой главе мы рассмотрим следующие темы:
 различие между онлайн- и оффлайн-кошельком;
 использование библиотек hooked-web3-provider и ethereumjs-tx для простого

создания и подписания транзакций из аккаунтов, которые не расположены непосредственно на узле Ethereum;
 понимание того, что такое HD-кошелек и как им пользоваться;
 создание HD-кошелька и заверителя транзакций при помощи библиотеки

LightWallet;
 создание сервиса кошелька.

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

Разработка сервиса кошелька

кованно. Более того — в зависимости от того, где хранится сам кошелек, — возможно, придется довериться третьей стороне.
Например, многие популярные сервисы кошельков хранят закрытые ключи
кошельков у себя и предоставляют вам доступ при помощи электронной почты и
пароля. По сути, вы не имеете полного контроля над своим кошельком, и они могут
украсть ваши деньги, когда захотят.
И наоборот, если кошелек не имеет соединения с Интернетом, мы называем его
оффлайновым. Это, например, кошельки, которые хранятся на флэш-накопителе,
в виде записей на бумаге, в виде текстовых файлов и т. п.1. Оффлайн-кошельки еще
называют «холодными» (cold wallet). Оффлайн-кошельки более безопасны, потому
что для кражи средств необходимо получить физический доступ к хранилищу.
Проблема использования оффлайн-кошелька состоит в том, что вы должны найти
такие место и способ хранения, которые исключают случайное его уничтожение,
утерю или доступ к нему посторонних. Многие люди хранят данные кошелька на
бумаге и кладут эти бумаги в сейф, который обеспечивает безопасное хранение в
течение длительного времени. Если вам надо часто отправлять средства со своего
счета, вы записываете файлы кошелька на защищенный паролем флэш-накопитель,
который все равно кладете в сейф. Риск хранения кошелька на цифровом носителе
немного выше, потому что электронное устройство может в любой момент сломаться, и вы потеряете доступ к данным на нем. Вот почему имеет смысл хранить
в сейфе также и копию устройства. Вы можете найти и лучший способ, исходя из
своих потребностей, но в любом случае надо быть уверенным, что кошелек лежит
в безопасном месте, а вы не потеряете доступ к нему.

Библиотеки hooked-web3-provider
и ethereumjs-tx
До сих пор все примеры метода sendTransaction() из библиотеки web3.js использовали адрес from, который находится в узле Ethereum, — следовательно, узел сети
Ethereum мог подписывать транзакции перед трансляцией в сеть. Но если ваш закрытый персональный ключ хранится где-то в другом месте, Geth не сможет его
найти. Значит, в данном случае для передачи транзакции вы должны использовать
метод web3.eth.sendRawTransaction().
Метод web3.eth.sendRawTransaction() применяется для передачи «сырой», необработанной, транзакции, т. е. вам придется написать код для создания и подписания такой транзакции. А узел сети Ethereum напрямую передаст ее в сеть, не совершая
над ней никаких иных действий. Однако написать код для передачи транзакции ме1

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

____________________________________

[ 121 ] ___________________________________

Глава 5

тодом web3.eth.sendRawTransaction() достаточно трудно, потому что надо сгенерировать блок данных, создать сырую транзакцию и подписать ее.
hooked-web3-provider — это библиотека, предоставляющая нам прикладной провайдер2, который общается с Geth по протоколу HTTP. И уникальность данного провайдера состоит в том, что он позволяет нам подписывать вызовы sendTransaction()
при помощи наших ключей. Следовательно, нам больше не надо создавать блок
данных транзакции. Провайдер, по сути, переопределяет реализацию метода
web3.eth.sendTransaction(). В общем, он позволяет нам подписывать как вызов
sendTransaction(), так и вызов web3.eth.sendTransaction().
Метод sendTransaction() экземпляра контракта осуществляет внутреннее формирование данных транзакции, а метод web3.eth.sendTransaction() вещает транзакцию
в сеть.
ethereumjs — это коллекция библиотек JavaScript, относящихся к Ethereum. В свою
очередь, ethereumjs-tx — это одна из библиотек коллекции. Она предоставляет API,
относящиеся к транзакциям. Например, она помогает создать необработанную
транзакцию, подписать эту транзакцию, проверить, правильным ли ключом подписана эта транзакция, и т. д.
Обе упомянутых библиотеки доступны и как компоненты Node.js, и как клиентская
часть JavaScript. Скачайте hooked-web3-provider по адресу: https://www.npmjs.com/
package/hooked-web3-provider, а ethereumjs-tx — по адресу: https://www.npmjs.com/
package/ethereumjs-tx.
На момент подготовки книги была доступна версия hooked-web3-provider 1.0.0 и
версия ethereumjs-tx 1.1.43.
Давайте на небольшом примере посмотрим, как использовать обе эти библиотеки
вместе и отправлять транзакции без прямого участия Geth:
var provider = new HookedWeb3Provider({
host: "http://localhost:8545",
transaction_signer: {
hasAddress: function(address, callback){
callback(null, true);
},
signTransaction: function(tx_params, callback){
var rawTx = {
gasPrice: web3.toHex(tx_params.gasPrice),
gasLimit: web3.toHex(tx_params.gas),
value: web3.toHex(tx_params.value)
from: tx_params.from,
2

От англ. custom provider — интерфейс-посредник, средство доступа к функциям и методам другой
программы или процесса.
3
Во время работы над переводом книги версия Hooked-Web3-Provider оставалась прежней, а версия
EthereumJS-TX обновилась до 1.3.3.
____________________________________

[ 122 ] ___________________________________

Разработка сервиса кошелька
to: tx_params.to,
nonce: web3.toHex(tx_params.nonce)
};
var privateKey =
EthJS.Util.toBuffer('0x1a56e47492bf3df9c9563fa7f66e4e032c661de9d68c3f36f358
e6bc9a9f69f2', 'hex');
var tx = new EthJS.Tx(rawTx);
tx.sign(privateKey);
callback(null, tx.serialize().toString('hex'));
}
}
});
var web3 = new Web3(provider);
web3.eth.sendTransaction({
from: "0xba6406ddf8817620393ab1310ab4d0c2deda714d",
to: "0x2bdbec0ccd70307a00c66de02789e394c2c7d549",
value: web3.toWei("0.1", "ether"),
gasPrice: "20000000000",
gas: "21000"
}, function(error, result){
console.log(error, result)
})

Этот код работает следующим образом:
1. Сначала мы создаем экземпляр объекта HookedWeb3Provider — он предоставлен
библиотекой hooked-web3-provider. Конструктор создает объект с двумя свойствами:
• host — это HTTP-адрес узла;
• transaction_signer — ссылка на объект, с которым взаимодействует прикладной провайдер, чтобы подписать транзакцию.
2. В свою очередь, объект transaction_signer содержит два метода:
• hasAddress() — служит для проверки, подписана ли транзакция (имеет ли
подписант транзакции персональный закрытый ключ для аккаунта с адресом
from). Этот метод принимает адрес и функцию обратного вызова. Обратный
вызов должен сработать, если первый параметр ответа — это сообщение об
ошибке, а второй параметр имеет значение false (закрытый ключ по указанному адресу не существует). Если ключ найден, первый параметр ответа имеет значение null, а второй — true;
____________________________________

[ 123 ] ___________________________________

Глава 5

• signTransaction() — этот метод вызывается для подписания транзакции, если
найден закрытый ключ по указанному адресу. Метод принимает два аргумента: параметры транзакции и функцию обратного вызова. Внутри метода параметры нашей транзакции сначала преобразуются в параметры сырой транзакции, а затем значения этих параметров кодируются в шестнадцатеричные
строки. Затем мы создаем буфер для хранения закрытого ключа. Буфер создается при помощи метода EthJS.Util.toBuffer(), который является частью библиотеки ethereumjs-util. Библиотека ethereumjs-util импортируется библиотекой ethereumjs-tx. Далее мы создаем и подписываем сырую транзакцию, сериализуем ее и конвертируем в шестнадцатеричную строку. Наконец, нам
надо передать шестнадцатеричную строку подписанной сырой транзакции
в прикладной провайдер при помощи функции обратного вызова. В случае,
если внутри метода произошла ошибка, первым аргументом обратного вызова будет сообщение об ошибке.
3. Далее провайдер берет сырую транзакцию и транслирует ее в сеть при помощи
метода web3.eth.sendRawTransaction().
4. Наконец, мы вызываем функцию web3.eth.sendTransaction для отправки некоторой суммы эфира на другой счет. Здесь мы должны предоставить все параметры
транзакции, кроме nonce, потому что провайдер может сам вычислить nonce.
Раньше многие из параметров были необязательными, потому что мы доверяли
их формирование узлу Ethereum. Но теперь, поскольку мы сами подписываем
транзакцию, мы должны предоставить все параметры. Параметр gas всегда имеет значение 21000, если транзакция не несет связанные с ней данные.
А где же открытый ключ?
В приведенном здесь коде нет упоминания об открытом ключе, который связан
с адресом отправителя, подписавшего транзакцию. Вы должны были задуматься:
как майнер проверит аутентичность транзакции без публичного ключа? Дело
4
в том, что майнеры используют уникальное свойство ECDSA , которое позволяет
им вычислить открытый ключ из сообщения и подписи. В транзакции сообщение
указывает на назначение транзакции, а подпись используется для определения
того, была ли транзакция подписана корректным закрытым ключом. Это является
особенностью ECDSA. Библиотека ethereumjs-tx предоставляет API для проверки
транзакций.

Что такое HD-кошелек?
HD-Wallet (Hierarchical Deterministic Wallet, иерархический детерминированный
кошелек) — это система извлечения адресов и ключей из единственного источника,
называемого сидом (от англ. seed — семя). Детерминированность означает, что из
одинаковых сидов всегда будут сгенерированы одинаковые адреса и ключи. Иерар4

Elliptic Curve Digital Signature Algorithm (ECDSA) — алгоритм построения цифровой подписи с использованием эллиптических кривых.

____________________________________

[ 124 ] ___________________________________

Разработка сервиса кошелька

хия означает, что адреса и ключи будут генерироваться в одинаковом порядке. Такой подход упрощает резервное копирование и хранение набора счетов — ведь вам
достаточно сохранить только сид, а не набор ключей и адресов.
Почему пользователи нуждаются в нескольких счетах?
Вам интересно, зачем пользователям несколько счетов? Обычно причина заключается в необходимости скрыть свое богатство. Баланс счета публично доступен
через блокчейн. Таким образом, если пользователь A сообщает пользователю
B адрес для получения некоторой суммы, то пользователь B может проверить,
какая сумма эфира хранится по этому адресу. Поэтому пользователи обычно
распределяют свое состояние по различным счетам.

Существуют разные типы HD-кошельков, которые различаются форматом сидов и
алгоритмом генерации адресов и ключей, — например: BIP32, Armory, Coinkite,
Coinb.in и др.
Что такое BIP32, BIP44 и BIP39?
BIP (Bitcoin Improvement Proposal, предложение по улучшению Bitcoin) — это проектный документ, предоставляющий информацию сообществу Bitcoin или описывающий новые функции Bitcoin, а также процессы и окружение. BIP должен излагать краткую техническую спецификацию функции и логическое обоснование этой
5
функции. На момент подготовки этой книги имелось 152 предложения по улучшению Bitcoin. BIP32 и BIP39 содержат, соответственно, информацию об алгоритме реализации HD-кошелька и спецификацию сида. Вы можете прочитать об
этом больше по адресу: https://github.com/bitcoin/bips.

Введение в функции формирования ключа
Асимметричные криптографические алгоритмы определяют характер ключей и то,
как они должны генерироваться, поскольку ключи должны быть связаны. Например, для генерации ключа RSA применяется детерминированный алгоритм.
Симметричные криптографические алгоритмы определяют только размер ключей,
а генерировать ключи мы должны сами. Для генерации ключей существуют различные алгоритмы, одним из которых является KDF (Key Derivation Function,
функция формирования ключа) — детерминированный алгоритм формирования
симметричного ключа на основе секретного значения (мастер-ключа, пароля или
парольной фразы). Существуют различные типы KDF, такие как bcrypt, crypt,
PBKDF2, scrypt, HKDF и др. Вы можете прочитать больше про KDF по адресу:
https://en.wikipedia.org/wiki/Key_derivation_function6.
Основанная на пароле функция формирования ключа берет пароль и генерирует
симметричный ключ. Примером парольной функции формирования ключа является
5

На момент подготовки перевода было уже 174 предложения.
На русском языке подобную информацию можно найти по адресу:
https://ru.wikipedia.org/wiki/Функция_формирования_ключа.

6

____________________________________

[ 125 ] ___________________________________

Глава 5

тип PBKDF2. Как известно, пользователи обычно придумывают слабые пароли,
поэтому парольные функции формирования ключей специально делают медленными и занимающими много памяти. Это осложняет атаку перебором паролей и другие типы атак. Основанные на пароле функции формирования ключа применяются
так широко, потому что простой пароль запомнить намного проще, чем длинный
криптоключ. Но если где-то хранить записанный криптоключ, то есть риск, что его
похитят.
Мастер-ключ или парольная фраза труднее поддаются атаке перебором. Следовательно, для генерации симметричного ключа на основе мастер-ключа или парольной фразы вы можете использовать беспарольную функцию формирования ключа,
например HKDF. Эта функция работает намного быстрее.
Почему вместо KDF не используют хеш-функцию?
Выходное значение хеш-функции можно использовать в качестве симметричного
ключа. Поэтому вам должно быть интересно, зачем тогда существуют KDF? Если
вы гарантированно используете парольную фразу или сложный пароль, вам достаточно применить к нему хеш-функцию. Например, HKDF просто использует
хеш-функцию для генерации ключа. Но если вы не уверены, что пользователь
придумает сложный пароль, то лучше задействовать KDF.

Знакомство с LightWallet
LightWallet — это HD-кошелек, в котором реализованы предложения BIP32, BIP39
и BIP44. LightWallet предоставляет API для создания и подписания транзакций или
шифрования и расшифровки данных при помощи адресов и ключей на их основе.
API LightWallet подразделяется на четыре пространства имен7: keystore, signing,
encryption и txutils.
Пространства имен signing, encryption и txutils предоставляют, соответственно,
API для подписания, несимметричного шифрования и создания транзакций.
Пространство keystore используется при создании объекта keystore, генерации сидов и тому подобных операций. keystore — это объект, который содержит сид
и ключи в зашифрованном виде. Пространство имен keystore реализует методы подписывания транзакций, которые требуют подписывать вызовы
we3.eth.sendTransaction(), если мы используем библиотеку hooked-web3-provider.
Поэтому пространство имен keystore способно автоматически создавать и подписывать транзакции для адресов, которые может в нем найти. В общем-то,
LightWallet в первую очередь и создан в качестве подписывающего провайдера для
hooked-web3-provider.
7

Пространство имен (namespace) — это способ организации множества идентификаторов (имен),
схожий с папками в файловой системе. Имя пространства формирует уникальное имя сущности. Например, Signing.test и Encryption.test — это разные классы test, потому что они относятся к разным пространствам имен.

____________________________________

[ 126 ] ___________________________________

Разработка сервиса кошелька

Экземпляр keystore может быть настроен как для создания и подписания транзакций, так и для шифрования и дешифровки данных. Для подписания транзакций он
использует параметр secp256k, а для шифрования/дешифровки — параметр
curve25519.
Сидом для LightWallet служит мнемокод из 12 слов, который легко запомнить, но
трудно взломать. Но это не могут быть любые 12 слов — сид должен быть сгенерирован самим LightWallet. Сгенерированный сид соответствует требованиям к подбору слов и другим параметрам.

Путь вывода HD-кошелька
Путь вывода HD-кошелька — это строка, которая упрощает обработку нескольких
криптовалют (при допущении, что все они используют одинаковые алгоритмы
подписи), множественных блокчейнов, множественных счетов и т. д.
Путь вывода может содержать столько параметров, сколько нам понадобится. Используя разные значения параметров, мы можем создать разные группы адресов и
связанных с ними ключей.
По умолчанию LightWallet использует путь вывода: m/0'/0'/0'. Здесь /n является
параметром, а n — значением этого параметра.
Каждый путь вывода содержит параметры curve и purpose. В свою очередь, purpose
может принимать значения sign или asymEncrypt: sign показывает, что путь используется для подписания транзакций, а asymEncrypt — что путь используется для
шифрования/дешифрования. curve обозначает параметр ECC (Elliptic Curve
Cryptography). Для подписания транзакций это secp256k, а для асимметричного
шифрования — curve25519, потому что LightWallet требует использовать именно
эти параметры.

Разработка сервиса кошелька
Итак, мы в достаточной мере изучили основы LightWallet и настало время разработать сервис кошелька, используя LightWallet и hooked-web3-provider. Наш сервис
предоставит пользователю возможности генерировать уникальный сид, отображать
адреса и связанные с ними балансы, и, наконец, сервис позволит пользователю отправлять эфир на другие счета. Все операции выполняются на клиентской стороне,
поэтому пользователь может доверять сервису как самому себе. Пользователь
может запомнить сид или сохранить в надежном месте.

Предварительная подготовка
Прежде, чем приступить к разработке сервиса кошелька, убедитесь, что Geth запущен в режиме разработчика, майнинг включен, сервер HTTP-RPC запущен и при____________________________________

[ 127 ] ___________________________________

Глава 5

нимает клиентские запросы с любого домена, а аккаунт 0 разблокирован. Для этого
вы можете воспользоваться следующей строкой запуска в терминале:
geth --dev --rpc --rpccorsdomain "*" --rpcaddr "0.0.0.0" --rpcport "8545"
--mine --unlock=0

Здесь опция --rpccorsdomain разрешает доступ с определенного домена. Мы должны
предоставить ей список доменов, разделенных пробелом, — например:
"http://localhost:8080 https://mySite.com *". Можно использовать символ подстановки "*", чтобы разрешить любые домены. Опция --rpcaddr показывает, по какому
адресу доступен сервер Geth. По умолчанию это адрес 127.0.0.1. Следовательно,
если сервер расположен на стороннем хостинге, вы не сможете использовать публичный IP-адрес сервера. Поэтому мы указали адрес 0.0.0.0, который означает, что
сервер может быть расположен и доступен по любому адресу.

Структура проекта
В файлах папки упражнений, дополняющих эту главу (см. приложение), вы найдете
два каталога: Final и Initial. Каталог Final содержит окончательный исходный код
проекта, в то время как Initial содержит пустые файлы исходного кода и библиотеки, что позволяет быстро начать работу над проектом.
Для тестирования содержимого каталога Final следует выполнить команду терминала npm install внутри каталога. После этого запустите приложение командой терминала node app.js внутри каталога Final.

В каталоге Initial находится вложенный каталог public и два файла: app.js и
package.json. Второй файл содержит зависимости для вашего приложения, а файл
app.js — это место, в котором будет храниться серверная часть проекта.
Каталог public содержит файлы, относящиеся к пользовательскому интерфейсу
(клиентская часть). Внутри public/css вы найдете библиотеку фреймворка Bootstrap
bootstrap.min.css, а внутрь public/html вы поместите HTML-код вашего клиентского приложения. В каталоге public/js находятся JavaScript-файлы для hooked-web3provider, web3js и LightWallet. Также внутри этого каталога вы найдете файл
main.js, в который вы поместите код JavaScript для клиентской части проекта.

Разработка серверной части
Приступим к разработке серверной части приложения. Прежде всего выполните
команду npm install внутри каталога Initial, чтобы установить зависимости для
нашей серверной части.
Далее приведен простой и короткий код, который запускает службу express и готовит к выдаче файл index.html и статические файлы:
var express = require("express");
var app = express();
____________________________________

[ 128 ] ___________________________________

Разработка сервиса кошелька
app.use(express.static("public"));
app.get("/", function(req, res){
res.sendFile(__dirname + "/public/html/index.html");
})
app.listen(8080);

Этот код не требует дополнительных пояснений.

Разработка клиентской части
Теперь создадим клиентскую часть приложения. Она будет поддерживать все
основные функции: генерацию сида, отображение адресов сида и отправку эфира.
Вставьте HTML-код из листинга 5.1 в файл index.html.
Листинг 5.1. Содержимое файла index.html














Создание или использование кошелька



Введите сид (12 слов)


Генерировать адреса
Генерировать сид

____________________________________

[ 129 ] ___________________________________

Глава 5

Адреса, ключи и балансы сида



Отправить эфир


Исходящий адрес



Адрес получателя



Эфир


Отправить эфир









Код из листинга 5.1 работает следующим образом:
1. Сначала мы запрашиваем набор стилей Bootstrap 4.
2. Затем отображаем информационное поле, в котором будем показывать пользователю различные сообщения.
3. Далее, у нас отображается форма, которая содержит поле ввода и две кнопки.
В поле ввода мы вводим имеющийся сид. Это же поле используется для отображения нового сида, если мы запросили генерацию сида.
4. Кнопка Генерировать адреса предназначена для отображения адресов, а кнопка
Генерировать сид — для генерации нового уникального сида. Нажатие кнопки
____________________________________

[ 130 ] ___________________________________

Разработка сервиса кошелька

Генерировать адреса вызывает метод generate_addresses(), а нажатие кнопки
Генерировать сид — метод generate_seed().
5. Далее у нас расположен неупорядоченный список. Пока он пустой. Здесь мы будем динамически отображать адреса, их балансы и связанные закрытые ключи
сидов, когда пользователь нажмет кнопку Генерировать адреса.
6. Наконец, у нас есть еще одна форма, которая содержит адрес отправителя, адрес
получателя и количество эфира для перевода. Адрес отправителя должен быть
одним из адресов, которые отображаются в неупорядоченном списке.
Теперь напишем реализацию каждой функции, которую вызывает HTML-код. Сначала создадим код, который генерирует новый сид. Поместите этот короткий код
в файл main.js:
function generate_seed()
{
var new_seed = lightwallet.keystore.generateRandomSeed();
document.getElementById("seed").value = new_seed;
generate_addresses(new_seed);
}

Метод generateRandomSeed() из пространства имен keystore служит для генерации
случайных сидов. Он может получать дополнительный строковый параметр, который содержит внешний источник энтропии (случайных данных).
8

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

Для генерации уникального сида нам требуется по-настоящему высокая энтропия.
LightWallet содержит встроенные методы генерации уникальных сидов, причем
алгоритм LightWallet генерирует энтропию на основе окружающей среды. Но если
вы хотите получить энтропию получше, то можете передать сгенерированную энтропию в метод generateRandomSeed(), и она будет сложена с энтропией, которая
создана внутри метода generateRandomSeed().
После генерации случайного сида мы вызываем метод generate_addresses(). Этот
метод берет сид и показывает принадлежащие ему адреса. Прежде чем генерировать адреса, метод спрашивает у пользователя, сколько адресов ему нужно.
В листинге 5.2 содержится код реализации метода generate_addresses(). Добавьте
этот код в файл main.js.
8

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

____________________________________

[ 131 ] ___________________________________

Глава 5
Листинг 5.2. Реализация метода generate_addresses()
var totalAddresses = 0;
function generate_addresses(seed)
{
if(seed == undefined)
{
seed = document.getElementById("seed").value;
}
if(!lightwallet.keystore.isSeedValid(seed))
{
document.getElementById("info").innerHTML = "Пожалуйста, введите правильный
сид";
eturn;
}
totalAddresses = prompt("Сколько адресов вы хотите сгенерировать?");
if(!Number.isInteger(parseInt(totalAddresses)))
{
document.getElementById("info").innerHTML = "Пожалуйста, введите
допустимое число адресов";
return;
}
var password = Math.random().toString();
lightwallet.keystore.createVault({
password: password,
seedPhrase: seed
}, function (err, ks) {
ks.keyFromPassword(password, function (err, pwDerivedKey) {
if(err)
{
document.getElementById("info").innerHTML = err;
}
else
{
ks.generateNewAddress(pwDerivedKey, totalAddresses);
var addresses = ks.getAddresses();
var web3 = new Web3(new
Web3.providers.HttpProvider("http://localhost:8545"));
var html = "";
for(var count = 0; count < addresses.length; count++)
____________________________________

[ 132 ] ___________________________________

Разработка сервиса кошелька
{
var address = addresses[count];
var private_key = ks.exportPrivateKey(address, pwDerivedKey);
var balance = web3.eth.getBalance("0x" + address);
html = html + "";
html = html + "Address: 0x" + address + "";
html = html + "Private Key: 0x" + private_key + "";
html = html + "Balance: " + web3.fromWei(balance, "ether") +
" ether";
html = html + "";
}
document.getElementById("list").innerHTML = html;
}
});
});
}

Код из листинга 5.2 работает следующим образом:
1. Сначала мы получаем значение переменной totalAddresses — она содержит
количество адресов, которые мы хотим сгенерировать.
2. Затем мы проверяем, определен ли параметр seed. Если он определен, то считываем значение из поля ввода. Мы это делаем, чтобы иметь возможность использовать метод generate_addressess() для отображения информации о сиде при
генерации нового сида, а также, если пользователь нажимает кнопку Генерировать адреса.
3. Далее мы проверяем правильность сида при помощи метода isSeedValid() из
пространства имен keystore.
4. Спрашиваем у пользователя, сколько адресов надо создать, и проверяем введенное значение.
5. Закрытые ключи в пространстве имен keystore всегда хранятся в зашифрованном виде. При генерации ключей мы должны их зашифровать, а при подписании
транзакций расшифровать обратно. Пароль для формирования симметричного
ключа шифрования может быть придуман пользователем, а может быть сгенерирован в виде случайной строки. Для удобства пользователей мы генерируем
случайную строку и используем ее в качестве пароля. Симметричный ключ не
хранится в пространстве имен keystore. Поэтому мы должны генерировать ключ
из пароля всякий раз, когда мы выполняем операции, связанные с закрытым
ключом: генерацию ключей, получение доступа к ключам и т. д.
6. Затем мы используем метод createVault(), чтобы создать экземпляр keystore.
Метод createVault() получает объект и функцию обратного вызова. Объект может иметь четыре свойства: password, seedPhrase, salt и hdPathString. Обязательным является только password, а остальные три свойства не обязательны. Если вы
____________________________________

[ 133 ] ___________________________________

Глава 5

не задали параметр seedPhrase, метод сгенерирует и будет использовать случайный сид. Параметр salt конкатенируется (сцепляется) с паролем для повышения безопасности симметричного ключа, поскольку атакующему придется найти не только пароль, но и значение salt. Если значение salt не задано, оно
генерируется случайным образом. Пространство имен keystore хранит salt
в открытом виде. Параметр hdPathString задает путь вывода для пространства
keystore, т. е. при генерации адресов, подписании транзакций и т. д. Если вы не
задали параметр hdPathString, то значением по умолчанию будет m/0'/0'/0'. Назначением по умолчанию для данного пути является sign. Вы можете создать
новый путь вывода или изменить назначение текущего пути при помощи метода addHdDerivationPath() экземпляра keystore. Также вы можете сменить путь
по умолчанию при помощи метода setDefaultHdDerivationPath() экземпляра
keystore. Когда создано пространство имен keystore, экземпляр возвращается
через обратный вызов. Таким образом, мы создали keystore лишь на основе пароля и сида.
7. Теперь мы должны сгенерировать для пользователя нужное количество адресов
и связанных с ними ключей. Мы можем сгенерировать миллионы адресов на
основе одного сида, поэтому keystore не генерирует никакие адреса до тех пор,
пока пользователь не укажет количество. После создания экземпляра keystore
мы генерируем симметричный ключ из пароля при помощи метода
keyFromPassword. А затем вызываем метод generateNewAddress() для формирования адресов и связанных с ними ключей.
8. Метод generateNewAddress() получает три аргумента: ключ, сформированный из
пароля, количество адресов для генерации и путь вывода. Так как мы не задали
путь вывода, метод использует путь по умолчанию для хранилища ключей.
Если вы вызываете метод generateNewAddress() несколько раз, то он возобновляет
работу с адресов, созданных при последнем вызове. Например, если вы вызывали метод дважды и каждый раз создавали по два адреса, у вас получится
четыре адреса.
9. Затем мы вызываем метод getAddresses(), чтобы получить все адреса, хранящиеся в keystore.
10. Мы расшифровываем и извлекаем закрытые ключи адресов при помощи метода
exportPrivateKey.
11. При помощи метода web3.eth.getBalance() получаем балансы адресов.
12. И, наконец, отображаем всю информацию в виде списка.
Теперь мы знаем, как генерировать адреса и закрытые ключи из сида. Давайте напишем реализацию метода send_ether(), который будет отправлять эфир с одного
из адресов, которые мы сгенерировали из сида.
Возьмите код из листинга 5.3 и добавьте его в файл main.js.
____________________________________

[ 134 ] ___________________________________

Разработка сервиса кошелька
Листинг 5.3. Реализация метода send_ether()
function send_ether()
{
var seed = document.getElementById("seed").value;
if(!lightwallet.keystore.isSeedValid(seed))
{
document.getElementById("info").innerHTML = "Введите правильное значение";
return;
}
var password = Math.random().toString();
lightwallet.keystore.createVault({
password: password,
seedPhrase: seed
}, function (err, ks) {
ks.keyFromPassword(password, function (err, pwDerivedKey) {
if(err)
{
document.getElementById("info").innerHTML = err;
}
else
{
ks.generateNewAddress(pwDerivedKey, totalAddresses);
ks.passwordProvider = function (callback) {
callback(null, password);
};
var provider = new HookedWeb3Provider({
host: "http://localhost:8545",
transaction_signer: ks
});
var web3 = new Web3(provider);
var from = document.getElementById("address1").value;
var to = document.getElementById("address2").value;
var value = web3.toWei(document.getElementById("ether").value, "ether");
web3.eth.sendTransaction({
from: from,
to: to,
value: value,
____________________________________

[ 135 ] ___________________________________

Глава 5
gas: 21000
}, function(error, result){
if(error)
{
document.getElementById("info").innerHTML = error;
}
else
{
document.getElementById("info").innerHTML = "Txn hash: " + result;
}
})
}
});
});
}

Фрагмент кода, который выполняет генерацию адресов из сида, не нуждается в пояснениях. Затем мы присоединяем обратный вызов к свойству passwordProvider.
Этот вызов срабатывает с целью получения пароля и расшифровки закрытого ключа во время подписания транзакции. Если мы не задали этот пароль, LightWallet
запросит пароль у пользователя. Далее мы создаем экземпляр HookedWeb3Provider,
передавая экземпляр keystore в качестве заверителя транзакции. Теперь, если провайдеру надо подписать транзакцию, он вызывает методы hasAddress и
signTransactions. Если адрес подписываемой транзакции не является тем адресом,
который мы создавали, провайдеру будет возвращена ошибка. И, наконец, мы
пересылаем какое-то количество эфира при помощи метода web3.eth.sendTransaction.

Тестирование
Мы закончили разработку нашего сервиса кошелька. Настало время проверить, работает ли он так, как задумано. Выполните команду терминала node app.js внутри
начального каталога и введите http://localhost:8080 в адресную строку вашего
любимого браузера. Вы должны увидеть окно, аналогичное изображенному на
рис. 5.1.
Теперь нажмите кнопку Generate New Seed (Генерировать сид), чтобы создать новый сид. Вам придется ввести количество адресов, которые нужно сгенерировать, — вы можете ввести любое число, но для нужд тестирования введите число
больше единицы. После этого окно приложения должно приобрести вид, показанный на рис. 5.2.

____________________________________

[ 136 ] ___________________________________

Разработка сервиса кошелька

Рис. 5.1. Начальное окно клиентской части приложения

Теперь для тестирования перевода валюты вам надо перечислить какую-то сумму
на один из вновь созданных адресов со своего основного счета. Когда вы это сделаете, нажмите кнопку Generate Details (Генерировать адреса), чтобы обновить
информацию интерфейса, хотя это и не обязательно. Удостоверьтесь, что из сида
развернуты (сгенерированы) те же самые адреса, что и на предыдущем шаге.
Теперь окно приложения должно выглядеть приблизительно так, как показано на
рис. 5.3.
В адресное поле From address (адрес отправителя) введите адрес, с которого хотите перевести средства. В поле To address (адрес получателя) введите адрес, на который должна быть зачислена валюта. Для тестирования мы укажем один из наших
собственных адресов. Введите сумму перевода, которая меньше или равна балансу
отправителя. Теперь окно приложения должно выглядеть, как показано на рис. 5.4.
____________________________________

[ 137 ] ___________________________________

Глава 5

Рис. 5.2. Окно приложения после генерации адресов

____________________________________

[ 138 ] ___________________________________

Разработка сервиса кошелька

Рис. 5.3. Окно приложения после получения эфира на счет

____________________________________

[ 139 ] ___________________________________

Глава 5

Рис. 5.4. Приложение готово к переводу валюты со счета на счет

Нажмите кнопку Send Ether (отправить эфир). В информационном блоке вы должны увидеть хеш транзакции. Подождите некоторое время, пока она обрабатывается.
Вы можете проверить, завершен ли майнинг, нажимая кнопку Generate Details
через короткие промежутки времени. Как только майнинг транзакции завершен, вы
увидите окно приложения, аналогичное показанному на рис. 5.5.
____________________________________

[ 140 ] ___________________________________

Разработка сервиса кошелька

Рис. 5.5. Окно приложения после завершения майнинга транзакции

____________________________________

[ 141 ] ___________________________________

Глава 5

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

Заключение
В этой главе вы узнали про три важных библиотеки Ethereum: hooked-web3provider, ethereumjs-tx и LightWallet. Эти библиотеки используются для управления
счетами и подписания транзакций вне узла Ethereum. Занимаясь разработкой клиентской части различных децентрализованных приложений, вы убедитесь в полезности этих библиотек.
В завершение главы мы создали сервис кошелька, который помогает пользователю
управлять счетами и пересылать закрытые ключи и другую информацию кошелька
в серверную часть сервиса.
В следующей главе мы построим платформу для разработки и развертывания
смарт-контрактов.

____________________________________

[ 142 ] ___________________________________



Разработка платформы
для смарт-контрактов

Клиентам некоторых приложений нужно компилировать и размещать контракты
«на ходу», в режиме реального времени. В нашем приложении для подтверждения
владения файлом (см. главы 3 и 4) мы размещали контракт вручную и жестко вписывали адрес контракта в код клиентской части. Но некоторым клиентам это не
подходит. Например, если клиентская часть приложения позволяет отмечать посещаемость учащихся в блокчейне, то приложению придется развертывать смартконтракт каждый раз, когда зарегистрирована новая школа, потому что каждая
школа должна иметь полный доступ к смарт-контракту. Из материала этой главы
мы узнаем, как скомпилировать смарт-контракты с помощью web3.js и развернуть
их с помощью web3.js и ethereumjs.
В этой главе будут рассмотрены следующие темы:
1

 вычисление nonce для транзакции;
 использование пула транзакций API JSON-RPC;
 формирование данных транзакции для создания контракта;
 оценка количества газа, необходимого для транзакции;
 нахождение текущего доступного баланса счета;
 компиляция смарт-контракта при помощи solcjs;
 разработка платформы для написания, компиляции и развертывания смарт-

контрактов.
1

Напомним, что число nonce для транзакции — это не то число nonce, которое определяется в процессе майнинга (см. главу 2).

Глава 6

Вычисление nonce для транзакции
Если счета размещены на узле под управлением Geth, мы не заботимся о вычислении nonce, потому что Geth сам добавляет корректное значение nonce к транзакции
и подписывает ее. Если же мы имеем дело со счетами, работающими независимо от
Geth, то должны вычислять nonce самостоятельно.
Для самостоятельного вычисления nonce мы можем использовать принадлежащий
Geth метод getTransactionCount(). Первый аргумент метода представляет собой адрес транзакции, для которой производится вычисление nonce. Второй аргумент —
это блок, в который помещена транзакция. Мы можем применить строку "pending"
для обозначения блока, который пока находится в процессе майнинга. Как мы уже
говорили в предыдущих главах, Geth формирует накопитель транзакций (transaction
pool), в котором он хранит ожидающие и поставленные в очередь транзакции. Для
майнинга блока Geth берет из накопителя ожидающие транзакции и запускает
выработку нового блока. До тех пор, пока майнинг блока не завершен, транзакции
остаются в накопителе. По завершении майнинга транзакции из накопителя удаляются. Новые транзакции, поступившие во время майнинга блока, помещаются
в накопитель и обрабатываются в следующем блоке. Следовательно, если мы указываем строку "pending" в качестве второго аргумента вызова getTransactionCount(),
метод не заглядывает внутрь накопителя, а рассматривает транзакции в обрабатываемом блоке.
Итак, если вы отправляете транзакции со счета, который не обслуживается на узле
Geth, то вычисляете общее количество транзакций данного счета в блокчейне и
складываете его с количеством транзакций, ждущих в накопителе. Если вы попытаетесь учесть ожидающие транзакции из обрабатываемого блока, то не получите
корректное значение nonce в случае, если транзакции отправлены в Geth с интервалом в несколько секунд, потому что для включения транзакции в блокчейн требуется в среднем 12 секунд.
В предыдущей главе для добавления nonce к транзакции мы обращались к провайдеру hooked-web3-provider. К сожалению, этот провайдер не умеет получать значение nonce правильным способом: он создает счетчик для каждого счета и инкрементирует его каждый раз, когда вы отправляете транзакцию со счета. Но если транзакция оказалась ошибочной (например, если транзакция пытается перевести денег
больше, чем есть на счете), то обратное уменьшение счетчика не происходит.
Следовательно, остальные транзакции счета будут поставлены в очередь, но не будут обработаны майнером до тех пор, пока не сброшен hooked-web3-provider, т. е.
пока не перезапущена клиентская часть приложения. Если вы создали несколько
экземпляров hooked-web3-provider, они не способны синхронизировать текущее
значение nonce между собой, поэтому вы легко можете получить некорректное значение. Но перед тем как включить nonce в транзакцию, hooked-web3-provider всегда
получает количество транзакций, которые еще не попали в ожидающий майнинга
блок, сравнивает его со своим счетчиком и использует большее значение. Следовательно, если транзакция со счета, управляемого hooked-web3-provider, отправлена
____________________________________

[ 144 ] ___________________________________

Разработка платформы для смарт-контрактов

с другого узла сети и уже включена в ожидающий блок, провайдер увидит это. Но
мы не можем целиком полагаться на hooked-web3-provider в задаче вычисления
nonce. Он прекрасно подходит лишь для быстрого прототипирования клиентской
части приложений или для тех приложений, в которых пользователь может видеть
и повторно отправлять транзакции, если они не отправлены в сеть, а провайдер часто перезапускается. Например, в нашем сервисе кошелька (см. главу 5) пользователь часто перезагружает страницу, соответственно часто создается новый объект
hooked-web3-provider. Если транзакция не передана, ошибочна или необработана,
пользователь может обновить страницу и отправить транзакцию заново.

Знакомство с solcjs
solcjs — это библиотека Node.js и инструмент командной строки, который предназначен для компиляции программ на языке Solidity. solcjs не использует компилятор Solc. Напротив, он выполняет компиляцию исключительно при помощи
JavaScript, поэтому заметно проще в установке, чем Solc.
Solc — это компилятор для языка Solidity, написанный на C++. Затем код C++
скомпилирован в JavaScript при помощи межплатформенного компилятора
Emscripten2. Каждая версия solc компилируется в JavaScript. По адресу:
https://github.com/ethereum/solc-bin/tree/gh-pages/bin вы можете найти компиляторы на базе JavaScript для любой версии Solidity. Инструмент solcjs просто использует один из этих компиляторов для компиляции исходного кода Solidity.
JavaScript-компиляторы могут быть запущены как в браузере Solidity, так и в среде
Node.js.
Браузер Solidity использует упомянутые компиляторы на основе JavaScript для
компиляции исходного кода Solidity.

Установка solcjs
Компилятор solcjs доступен в виде NPM-пакета под именем solc. Вы можете выполнить установку пакета локально или глобально, аналогично другим пакетам
NPM. Если пакет установлен глобально, то будет доступен инструмент командной
строки solcjs. Поэтому для установки инструмента командной строки выполните
следующую команду терминала:
npm install -g solc

Теперь выполните следующую команду, чтобы узнать, как компилировать файлы
Solidity при помощи командной строки:
solcjs -help
2

Об Emscripten — компиляторе из LLVM-байт-кода в JavaScript — можно прочитать здесь:
https://en.wikipedia.org/wiki/Emscripten.

____________________________________

[ 145 ] ___________________________________

Глава 6

В этой книге мы не будем использовать инструмент командной строки, а воспользуемся API solcjs для компиляции файлов Solidity.
По умолчанию solcjs использует компилятор, версия которого совпадает с его
собственной версией. Например, если вы используете solcjs версии 0.4.8, то по
умолчанию он применит компилятор версии 0.4.8. Но solcjs можно настроить для
использования другой версии компилятора. На момент работы над книгой последней была версия solcjs 0.4.8.

API solcjs
solcjs предоставляет метод compile(), который применяется для компиляции кода
Solidity. Этот метод может быть использован двумя разными способами в зависимости от того, содержит ли исходный код импорты. Если исходный код не содержит ни одного импорта, то метод получает два аргумента: первый аргумент — это
собственно исходный код Solidity в виде строки, а второй — булева переменная,
которая указывает, оптимизировать байт-код или нет. Если строка исходного кода
содержит несколько контрактов, то все они будут скомпилированы.
Вот простой пример, который это демонстрирует:
var
var
var
for

solc = require("solc");
input = "contract x { function g() {} }";
output = solc.compile(input, 1); // 1 activates the optimiser
(var contractName in output.contracts) {
//выводим в консоль код и ABI
console.log(contractName + ": " +
output.contracts[contractName].bytecode);
console.log(contractName + "; " +
JSON.parse(output.contracts[contractName].interface));
}

Если исходный код содержит импорты, то первым аргументом будет объект, чьи
ключи являются именами файлов, а значения — содержимым файлов. Поэтому
всякий раз, когда компилятор видит оператор import, он не ищет файл в файловой
системе. Вместо этого он ищет содержимое файла в объекте, сопоставляя имя файла с ключами. Вот пример, который это демонстрирует:
var solc = require("solc");
var input = {
"lib.sol": "library L { function f() returns (uint) { return 7; } }",
"cont.sol": "import 'lib.sol'; contract x { function g() { L.f(); } }"
};
var output = solc.compile({sources: input}, 1);
for (var contractName in output.contracts)
console.log(contractName + ": " +
output.contracts[contractName].bytecode);
____________________________________

[ 146 ] ___________________________________

Разработка платформы для смарт-контрактов

Если вы хотите прочитать содержимое импортированного файла из файловой системы во время компиляции или распознать содержимое файла при компиляции, то
для таких случаев метод compile() поддерживает третий аргумент, который сам
является ссылкой на метод, получающий имя файла и возвращающий содержимое
этого файла.
Вот пример, который демонстрирует такой вызов метода:
var solc = require("solc");
var input = {
"cont.sol": "import 'lib.sol'; contract x { function g() { L.f(); } }"
};
function findImports(path) {
if (path === "lib.sol")
return { contents: "library L { function f() returns (uint) {
return 7; } }" }
else
return { error: "Файл не найден" }
}
var output = solc.compile({sources: input}, 1, findImports);
for (var contractName in output.contracts)
console.log(contractName + ": " +
output.contracts[contractName].bytecode);

Использование различных версий компилятора
Для компиляции контракта с использованием различных версий Solidity вам следует применять метод useVersion(), который возвращает ссылку на другой компилятор. Метод useVersion() получает строку c именем файла JavaScript, содержащего
компилятор, и ищет файл в каталоге /node_modules/solc/bin.
solcjs также содержит метод с именем loadRemoteVersion(), который получает имя
компилятора, ищет файл с таким именем в репозитории: https://github.com/
ethereum/solc-bin/tree/gh-pages/bin, скачивает этот файл и использует его.
Наконец, solcjs предоставляет метод setupMethods(), который аналогичен методу
useVersion(), но может загружать компилятор из любого каталога.
Рассмотрим пример, который демонстрирует все три метода (листинг 6.1).
Листинг 6.1. Демонстрация методов для работы с версиями компилятора
var solc = require("solc");
var solcV047 = solc.useVersion("v0.4.7.commit.822622cf");
var output = solcV011.compile("contract t { function g() {} }", 1);
____________________________________

[ 147 ] ___________________________________

Глава 6
solc.loadRemoteVersion('soljson-v0.4.5.commit.b318366e', function(err,
solcV045) {
if (err) {
// Сообщаем об ошибке и завершаем работу
}
var output = solcV045.compile("contract t { function g() {} }", 1);
});
var solcV048 = solc.setupMethods(require("/my/local/0.4.8.js"));
var output = solcV048.compile("contract t { function g() {} }", 1);
solc.loadRemoteVersion('latest', function(err, latestVersion) {
if (err) {
// Сообщаем об ошибке и завершаем работу
}
var output = latestVersion.compile("contract t { function g() {} }",
1);
});

Чтобы выполнить код из листинга 6.1, вы сначала должны скачать файл
v0.4.7.commit.822622cf.js из репозитория и поместить его в каталог node_modules/
solc/bin. Затем скачать файл компилятора Solidity версии 0.4.8, поместить его
где угодно в файловой системе и указать путь к нему в аргументе метода
setupMethods().

Связывание библиотек
Если исходный код Solidity ссылается на библиотеки, то сгенерированный байт-код
будет содержать поля-заполнители (placeholders) для реальных адресов библиотек,
на которые имеются ссылки. Перед развертыванием контракта заполнители должны быть обновлены при помощи процесса, который называется связыванием.
Для связывания библиотек solcjs предоставляет метод linkByteCode(), выполняющий привязку адресов библиотек к байт-коду.
Вот пример, который демонстрирует применение метода linkByteCode():
var solc = require("solc");
var input = {
"lib.sol": "library L { function f() returns (uint) { return 7; } }",
"cont.sol": "import 'lib.sol'; contract x { function g() { L.f(); } }"
};
var output = solc.compile({sources: input}, 1);
var finalByteCode = solc.linkBytecode(output.contracts["x"].bytecode, {
'L': '0x123456...' });
____________________________________

[ 148 ] ___________________________________

Разработка платформы для смарт-контрактов

Обновление ABI
ABI (Application Binary Interface, двоичный интерфейс приложения) контракта кроме реализации методов предоставляет и другие виды информации. ABI, созданные
двумя разными версиями компилятора, могут различаться. Новая версия компилятора поддерживает больше возможностей Solidity, чем старая, и расширяет набор
опций ABI. Например, резервная функция (см. главу 3) была заявлена в версии 0.4.0
языка Solidity. Если использовать компилятор, версия которого меньше, чем 0.4.0,
то в ABI не будет включена информация о резервной функции, и контракт будет
выглядеть так, словно резервная функция имеет пустое тело и модификатор payable.
Следовательно, ABI нашего контракта должен быть обновлен, чтобы приложения,
которые созданы для новой версии Solidity, могли получить более полную информацию о контракте.
solcjs располагает средствами для обновления ABI. Ознакомьтесь с примером,
который демонстрирует эту возможность:
var abi = require("solc/abi");
var inputABI =
[{"constant":false,"inputs":[],"name":"hello","outputs":[{"name":"","type":
"string"}],"payable":false,"type":"function"}];
var outputABI = abi.update("0.3.6", inputABI)

Здесь строка "0.3.6" показывает, что ABI был сгенерирован компилятором версии
0.3.6. Поскольку мы используем solcjs версии 0.4.8, то ABI будет обновлен, чтобы
соответствовать версии 0.4.8 (но не выше).
Результат компиляции приведенного здесь кода будет следующим:
[{"constant":false,"inputs":[],"name":"hello","outputs":[{"name":"","type":"string"
}],"payable":true,"type":"function"},{"type":"fallback","payable":true}]

Разработка платформы
для развертывания контрактов
Теперь, когда мы знаем, как применять solcjs для компиляции исходного кода
Solidity, пришло время создать платформу для написания, компиляции и развертывания контрактов. Наша платформа позволит пользователям вводить свой адрес и
закрытый ключ, с помощью которых платформа будет размещать контракты в сети.
Прежде чем приступить к разработке сервиса кошелька, убедитесь, что Geth запущен в режиме разработчика, майнинг включен, RPC работает и предоставляет API
eth, web3 и txpool через сервер HTTP-RPC. Для этого вы можете воспользоваться
следующей строкой запуска в терминале:
geth --dev --rpc --rpccorsdomain "*" --rpcaddr "0.0.0.0" --rpcport "8545" --mine
--rpcapi "eth,txpool,web3"
____________________________________

[ 149 ] ___________________________________

Глава 6

Структура проекта
В файлах папки упражнений, дополняющих эту главу (см. приложение), вы найдете
два каталога: Final и Initial. Каталог Final содержит окончательный исходный код
проекта, в то время как Initial содержит пустые файлы исходного кода и библиотеки, что позволяет быстро начать самостоятельную работу над проектом.
Для тестирования содержимого каталога Final следует выполнить команду терминала npm install внутри каталога. После этого запустите приложение командой терминала node app.js внутри каталога Final.

В каталоге Initial находится вложенный каталог public и два файла: app.js и
package.json. Второй файл содержит зависимости для вашего приложения, а файл
app.js — это место, в котором будет храниться серверная часть проекта.
Каталог public содержит файлы, относящиеся к пользовательскому интерфейсу
(клиентская часть). Внутри public/css вы найдете библиотеку фреймворка Bootstrap
bootstrap.min.css, а также файл codemirror.css, который содержит таблицу стилей
библиотеки Codemirror. Внутри public/html находится файл index.html, в который
вы поместите HTML-код вашего клиентского приложения. В каталоге public/js
находятся JavaScript-файлы для web3js и Codemirror. Также внутри этого каталога
вы найдете файл main.js, в который вы поместите код JavaScript для клиентской
части проекта.

Разработка серверной части
Приступим к разработке серверной части приложения. Прежде всего выполните
команду npm install внутри каталога Initial, чтобы установить зависимости для
нашей серверной части.
Далее приведен простой и короткий код, который запускает службу express и готовит к выдаче файл index.html и статические файлы:
var express = require("express");
var app = express();
app.use(express.static("public"));
app.get("/", function(req, res){
res.sendFile(__dirname + "/public/html/index.html");
})
app.listen(8080);

Этот код не требует дополнительных пояснений. Теперь пойдем дальше. У нашего
приложения будет две кнопки: Compile (Компилировать) и Deploy (Развернуть).
Соответственно, когда пользователь нажимает кнопку Compile, контракт компилируется, а когда нажимает кнопку Deploy — контракт размещается в сети.
____________________________________

[ 150 ] ___________________________________

Разработка платформы для смарт-контрактов

Мы будем выполнять компиляцию и развертывание контрактов на серверной стороне приложения. Несмотря на то, что эти операции можно выполнить и на клиентской стороне, мы воспользуемся именно серверной частью, потому что solcjs доступен только для Node.js (в то время как JavaScript-компиляторы, которые он использует, работают на клиентской стороне).
Чтобы узнать, как выполнять компиляцию на стороне клиента, вам следует разобраться с исходным кодом solcjs, в котором вы найдете упоминания об API компиляторов, основанных на JavaScript.

Когда пользователь нажимает кнопку Compile, клиентская часть формирует GETзапрос по пути /compile и передает исходный код контракта:
var solc = require("solc");
app.get("/compile", function(req, res){
var output = solc.compile(req.query.code, 1);
res.send(output);
})

Сначала мы импортируем библиотеку solcjs. Затем задаем путь /compile и внутри
функции обратного вызова просто компилируем с включенным оптимизатором
отправленный клиентом код. Далее получаем результат метода solc.compile(),
отправляем его в клиентскую часть и даем пользователю возможность проверить,
была ли компиляция успешной.
Когда пользователь нажимает на кнопку Deploy, клиентская часть формирует GETзапрос по пути /deploy и передает исходный код контракта и аргументы конструктора: адрес и закрытый ключ. После нажатия этой кнопки происходит развертывание контракта, а пользователю возвращается хеш транзакции.
В листинге 6.2 приведен код, который выполняет эту работу.
Листинг 6.2. Код обработки нажатия кнопки Deploy и развертывания контракта
var
var
var
var

Web3 = require("web3");
BigNumber = require("bignumber.js");
ethereumjsUtil = require("ethereumjs-util");
ethereumjsTx = require("ethereumjs-tx");

var web3 = new Web3(new
Web3.providers.HttpProvider("http://localhost:8545"));
function etherSpentInPendingTransactions(address, callback)
{
web3.currentProvider.sendAsync({
method: "txpool_content",
params: [],
jsonrpc: "2.0",
____________________________________

[ 151 ] ___________________________________

Глава 6
id: new Date().getTime()
}, function (error, result) {
if(result.result.pending)
{
if(result.result.pending[address])
{
var txns = result.result.pending[address];
var cost = new BigNumber(0);
for(var txn in txns)
{
cost = cost.add((new BigNumber(parseInt(txns[txn].value))).add((new
BigNumber(parseInt(txns[txn].gas))).mul(new
BigNumber(parseInt(txns[txn].gasPrice)))));
}
callback(null, web3.fromWei(cost, "ether"));
}
else
{
callback(null, "0");
}
}
else
{
callback(null, "0");
}
})
}
function getNonce(address, callback)
{
web3.eth.getTransactionCount(address, function(error, result){
var txnsCount = result;
web3.currentProvider.sendAsync({
method: "txpool_content",
params: [],
jsonrpc: "2.0",
id: new Date().getTime()
}, function (error, result) {
if(result.result.pending)
{
if(result.result.pending[address])
{
txnsCount = txnsCount +
Object.keys(result.result.pending[address]).length;
callback(null, txnsCount);
}
____________________________________

[ 152 ] ___________________________________

Разработка платформы для смарт-контрактов
else
{
callback(null, txnsCount);
}
}
else
{
callback(null, txnsCount);
}
})
})
}
app.get("/deploy", function(req, res){
var code = req.query.code;
var arguments = JSON.parse(req.query.arguments);
var address = req.query.address;
var output = solc.compile(code, 1);
var contracts = output.contracts;
for(var contractName in contracts)
{
var abi = JSON.parse(contracts[contractName].interface);
var byteCode = contracts[contractName].bytecode;
var contract = web3.eth.contract(abi);
var data = contract.new.getData.call(null, ...arguments, {
data: byteCode
});
var gasRequired = web3.eth.estimateGas({
data: "0x" + data
});
web3.eth.getBalance(address, function(error, balance){
var etherAvailable = web3.fromWei(balance, "ether");
etherSpentInPendingTransactions(address, function(error, balance){
etherAvailable = etherAvailable.sub(balance)
if(etherAvailable.gte(web3.fromWei(new
BigNumber(web3.eth.gasPrice).mul(gasRequired), "ether")))
{
getNonce(address, function(error, nonce){
var rawTx = {
gasPrice: web3.toHex(web3.eth.gasPrice),
____________________________________

[ 153 ] ___________________________________

Глава 6
gasLimit: web3.toHex(gasRequired),
from: address,
nonce: web3.toHex(nonce),
data: "0x" + data
};
var privateKey = ethereumjsUtil.toBuffer(req.query.key, 'hex');
var tx = new ethereumjsTx(rawTx);
tx.sign(privateKey);
web3.eth.sendRawTransaction("0x" + tx.serialize().toString('hex'),
function(err, hash) {
res.send({result: {
hash: hash,
}});
});
})
}
else
{
res.send({error: "Недостаточно средств на балансе"});
}
})
})
break;
}
})

Давайте пошагово рассмотрим, как работает код из листинга 6.2:
1. Сначала мы импортируем библиотеки web3.js, BigNumber.js, ethereumjs-util и
ethereumjs-tx. Затем создаем экземпляр web3.
2. Определяем функцию с именем etherInSpentPendingTransactions(). Эта функция
вычисляет общую сумму эфира, которая будет израсходована ожидающими
транзакциями данного адреса. Библиотека web3.js не предоставляет JavaScript
API для пула транзакций, поэтому мы выполняем непосредственный вызов
JSON-RPC посредством web3.currentProvider.sendAsync(). Использованный здесь
метод sendAsync() применяется для асинхронных вызовов JSON-RPC. Если вы
хотите выполнить синхронный вызов, то используйте метод send(). Во время
вычисления общей суммы эфира всех ждущих транзакций данного адреса мы
ищем ожидающие транзакции в пуле транзакций, но не в ожидающем блоке, по
____________________________________

[ 154 ] ___________________________________

Разработка платформы для смарт-контрактов

причине проблемы, которую уже обсудили в этой главе ранее3. При вычислении
общей суммы мы прибавляем к ней газ каждой транзакции, потому что газ тоже
вычитается из баланса счета.
3. Далее мы определяем функцию getNonce(). Она получает значение nonce по
методике, которую мы обсудили ранее. Эта функция просто складывает общее
количество обработанных транзакций с общим количеством ожидающих транзакций.
4. Наконец, мы объявляем конечную точку /deploy. Сначала мы компилируем контракт. Затем размещаем только первый контракт. Наша платформа разработана
для размещения первого контракта, если в исходном коде обнаружено несколько
контрактов. Потом вы можете доработать приложение, чтобы развернуть все
скомпилированные контракты, а не только первый. Далее мы создаем объект
контракта при помощи web3.eth.contract.
5. Мы не используем hooked-web3-provider или другие средства для перехвата вызова sendTransactions и преобразования его в вызов sendRawTransaction, поэтому
для развертывания контракта мы теперь должны сгенерировать блок данных
транзакции, который будет содержать байт-код контракта и аргументы конструктора, представленные в виде шестнадцатеричной строки. Объект контракта
позволяет нам получить блок данных транзакции. Мы достигаем этого вызовом
метода getData(). Если вы хотите развернуть контракт, то выполняете вызов
contract.new.getData(), а если хотите вызвать функцию контракта, то вызываете
contract.functionName.getData(). В обоих случаях не забудьте указать аргументы
метода getData(). Итак, для формирования блока данных транзакции вам нужен
только ABI контракта. Для более глубокого изучения вопроса, как имя и аргументы функции упаковываются в блок данных, посетите страницу по адресу:
https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples,
но
в этом нет необходимости, если вам доступен ABI контракта или вы знаете, как
создать его собственноручно.
6. Затем мы применяем web3.eth.estimateGas(), чтобы вычислить количество газа,
которое необходимо для развертывания контракта.
7. Далее мы проверяем, хватает ли на счете средств для оплаты газа, который
будет затрачен на развертывание контракта. Для этого мы запрашиваем наличие
средств на балансе адреса, вычитаем из него средства, потраченные ожидающими транзакциями, и убеждаемся, что остаток на балансе больше или равен стоимости газа.
8. В завершение процесса мы получаем nonce, подписываем и отправляем транзакции, а затем просто возвращаем хеш транзакции в клиентскую часть приложения.
3

См. ранее разд. «Вычисление nonce для транзакции».

____________________________________

[ 155 ] ___________________________________

Глава 6

Разработка клиентской части
Теперь приступим к разработке клиентской части приложения. Она будет содержать редактор, и пользователи смогут писать исходный код контракта. А когда
пользователь нажмет кнопку Compile, мы будем динамически отображать ему поля
ввода, которые соответствуют аргументам конструктора. По нажатию кнопки
Deploy значения аргументов конструктора считываются из этих полей. Пользователь должен вводить в эти поля строку в формате JSON.
Для добавления редактора в клиентскую часть мы используем библиотеку
Codemirror. Вы можете узнать о ней больше по адресу: http://codemirror.net/.

В листинге 6.3 приведен HTML-код клиентской части приложения. Поместите его
в файл index.html.
Листинг 6.3. HTML-код клиентской части приложения для файла index.html









.CodeMirror
{
height: auto;
}










Compile


____________________________________

[ 156 ] ___________________________________

Разработка платформы для смарт-контрактов



Address



Private Key





Deploy









В этом коде только тег требует отдельного комментария. В этом теге
располагается редактор Codemirror, в котором пользователь будет работать с кодом
контракта. Остальной код страницы понятен без пояснений.
В листинге 6.4 приведен код JavaScript для клиентской части. Этот код следует
поместить в файл main.js.
Листинг 6.4. Код JavaScript для файла main.js
var editor = CodeMirror.fromTextArea(document.getElementById("editor"), {
lineNumbers: true,
});
var argumentsCount = 0;
document.getElementById("compile").addEventListener("click", function(){
editor.save();
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
____________________________________

[ 157 ] ___________________________________

Глава 6
if(JSON.parse(xhttp.responseText).errors != undefined)
{
document.getElementById("errors").innerHTML =
JSON.parse(xhttp.responseText).errors + "";
}
else
{
document.getElementById("errors").innerHTML = "";
}
var contracts = JSON.parse(xhttp.responseText).contracts;
for(var contractName in contracts)
{
var abi = JSON.parse(contracts[contractName].interface);
document.getElementById("arguments").innerHTML = "";
for(var count1 = 0; count1 < abi.length; count1++)
{
if(abi[count1].type == "constructor")
{
argumentsCount = abi[count1].inputs.length;
document.getElementById("arguments").innerHTML =
'Arguments';
for(var count2 = 0; count2 < abi[count1].inputs.length; count2++)
{
var inputElement = document.createElement("input");
inputElement.setAttribute("type", "text");
inputElement.setAttribute("class", "form-control");
inputElement.setAttribute("placeholder",
abi[count1].inputs[count2].type);
inputElement.setAttribute("id", "arguments-" + (count2 + 1));
var br = document.createElement("br");
document.getElementById("arguments").appendChild(br);
document.getElementById("arguments").appendChild(inputElement);
}
break;
}
}
break;
}
}
};
xhttp.open("GET", "/compile?code=" +
encodeURIComponent(document.getElementById("editor").value), true);
____________________________________

[ 158 ] ___________________________________

Разработка платформы для смарт-контрактов
xhttp.send();
})
document.getElementById("deploy").addEventListener("click", function(){
editor.save();
var arguments = [];
for(var count = 1; count uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
function MetaCoin() {
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Transfer(msg.sender, receiver, amount);
return true;
}
function getBalanceInEth(address addr) returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) returns(uint) {
return balances[addr];
}
}

Смарт-контракт MetaCoin помещает 10 000 метакоинов на счет по адресу, на котором развернут контракт. Теперь пользователь может отправить эти метакоины
кому-нибудь при помощи функции sendCoin(). В любой момент вы можете узнать
текущий баланс при помощи функции getBalance(). Можно получить баланс в эфирах при помощи функции getBalanceInEth(). При этом считается, что курс метакоина фиксированный и равен двум эфирам.
Библиотека ConvertLib применяется для конвертации суммы метакоинов в эфир.
Для этой цели доступен метод convert().
____________________________________

[ 213 ] ___________________________________

Глава 8

Компиляция контрактов
Компиляция контрактов завершается созданием артефактов, содержащих набор из
abi и unlinked_binary. Для компиляции введите команду:
truffle compile

Truffle будет компилировать только те контракты, которые были изменены с момента последней компиляции. Если вы хотите изменить такое поведение, добавьте
к предыдущей команде опцию: --all.
После компиляции вы найдете артефакты в каталоге build/contracts. Вы вправе редактировать эти файлы по своему усмотрению. Файлы модифицируются во время
выполнения команд compile и migrate.
Есть несколько нюансов, на которые следует обратить внимание перед компиляцией:
 Truffle ожидает, что имя файла совпадает с именем контракта. Например, если
у вас есть файл с именем MyContract.sol, то он должен содержать контракт
MyContract{} или библиотеку myContract{};
 имена файлов чувствительны к регистру. Например, если имя файла не содержит заглавные буквы, то и имя контракта не может содержать заглавных букв;
 вы можете объявить зависимости контракта при помощи команды import. Truffle
будет компилировать контракты в правильном порядке и подключать библиотеки по мере необходимости. Зависимости должны быть объявлены относительно
текущего расположения файла Solidity и начинаться с пути ./ или ../.
Truffle версии 3.1.2 использует компилятор версии 0.4.8. В настоящее время
Truffle не поддерживает изменение версии компилятора.

Файлы конфигурации
Файл truffle.js предназначен для конфигурирования проекта. Этот файл может
содержать любой код, необходимый для создания конфигурации. Он должен
экспортировать объект, представляющий конфигурацию вашего проекта. По умолчанию файл содержит следующий исходный код:
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
}
}
};

Объект может содержать различные свойства. Но наиболее востребованным является свойство networks. Оно определяет как сеть, доступную для развертывания
____________________________________

[ 214 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

контракта, так и специальные параметры транзакции (gasPrice, from, gas и т. д.)
По умолчанию gasPrice = 100 000 000 000 Wei, gas = 4712388, адрес from определяется по адресу первого доступного контракта в клиенте.
Вы можете описать свойства любого количества сетей. Отредактируйте файл конфигурации следующим образом:
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "10"
},
live: {
host: "localhost",
port: 8545,
network_id: "1"
}
}
};

В этом примере кода мы определили две сети с именами: development и live.
При использовании стандартной командной строки Windows имя файла конфигурации, заданное по умолчанию, может вызвать конфликт с исполняемым файлом
Truffle. В таком случае рекомендуем использовать Windows PowerShell или Git
BASH, т. к. в этих оболочках конфликт не возникает. В качестве альтернативного
варианта можно изменить имя файла конфигурации на truffle-config.js.

Развертывание контрактов
Даже самый маленький проект будет взаимодействовать, по крайней мере, с двумя
блокчейнами: один — на машине разработчика, такой как TestRPC EthereumJS, и
другой в сети, в которой будет развернуто приложение. Это может быть, например,
главная сеть Ethereum или частная корпоративная сеть.
Поскольку рабочая сеть определяется автоматически абстракциями контракта во
время выполнения, это означает, что вам достаточно один раз развернуть контракт
или клиентскую часть. Когда приложение запущено, работающий клиент сети
Ethereum будет определять, какой артефакт контракта используется, и это придаст
вашему приложению большую гибкость.
Файлы JavaScript, которые содержат код для развертывания контракта в сети
Ethereum, называются файлами переноса (migration files). Эти файлы отвечают за
поэтапную постановку задач развертывания и пишутся исходя из предположения,
что ваши требования к развертыванию будут меняться со временем. По мере развития вашего проекта вы будете создавать новые скрипты переноса и вносить изме____________________________________

[ 215 ] ___________________________________

Глава 8

нения в блокчейн. История всех запусков переноса записывается в блокчейн при
помощи специального контракта Migrations. Если вы просматривали содержимое
каталогов contracts и build/contracts, то должны были заметить, что там уже есть
контракт Migrations. Этот контракт всегда должен там находиться, и его не следует
трогать без необходимости и ясного понимания своих действий.

Файлы переноса
В каталоге migrations вы можете видеть, что имена файлов начинаются с числового префикса — например, 1_initial_migration.js и 2_deploy_contracts.js. Числовой
префикс необходим, чтобы контролировать порядок выполнения переноса.
Контракт Migrations сохраняет в last_completed_migration номер, соответствующий
последнему использованному скрипту переноса из каталога migrations. Контракт
Migrations всегда выполняется первым. Для имен файлов принято соглашение вида
x_script_name.js, где значения x начинаются с 1. Контракты вашего приложения
обычно идут в скриптах, начиная с номера 2.
Поскольку контракт Migrations хранит номер последнего выполненного скрипта,
Truffle не может выполнить этот скрипт повторно. С другой стороны, в будущем
ваше приложение может нуждаться в доработке или развертывании нового контракта. В таком случае вы можете создать новый скрипт со следующим порядковым номером. После выполнения этот скрипт тоже станет недоступным для повторного запуска.

Написание кода переноса
В начале файла переноса мы сообщаем Truffle, какой контракт будет взаимодействовать с ним через метод artifacts.require(). Этот метод совпадает с методом
require() Node.js, но в нашем случае он особым образом возвращает абстракцию
контракта, которую мы будем использовать далее, вплоть до окончания скрипта.
Все скрипты миграции должны экспортировать (предоставлять нам для использования) функцию через module.exports. Функция должна получать объект deployer
в качестве первого аргумента. Этот объект помогает нам в развертывании и предоставляет простой и понятный API для развертывания смарт-контрактов, а также выполняет обычную работу — сохранение артефактов в файлах для последующего
использования, связывание библиотек и т. д. Объект deployer — это ваш основной
интерфейс для постановки задач развертывания.
Объект deployer предоставляет нам несколько методов. Все методы синхронные:
 deployer.deploy(contractAbstraction, args..., options) — развертывает контракт,
определяемый объектом абстракции контракта с необязательными аргументами
конструктора. Это удобно для одиночных контрактов, когда для вашего децентрализованного приложения существует только один экземпляр этого контракта.
Метод определит адрес контракта после развертывания (т. е. свойство address
в файле артефакта получит значение нового адреса), и будут переписаны любые
____________________________________

[ 216 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

предыдущие значения адреса. Вы можете передать в метод массив контрактов
(или массив массивов), чтобы ускорить развертывание контрактов. Дополнительно, последний аргумент является необязательным объектом, состоящим из
единственного ключа overwrite. Если ключ overwrite установлен в состояние
false, то метод не станет развертывать контракт заново, если он уже был развернут. Метод возвращает обещание (подтверждаемое ожидание выполнения);
 deployer.link(library, destinations) — подключает ранее размещенную библио-

теку к одному или нескольким контрактам. Аргумент destinations может быть
одиночной абстракцией контракта или массивом абстракций нескольких контрактов. Если контракт не ссылается на подключаемую библиотеку, deployer игнорирует этот контракт. Метод возвращает обещание;
 deployer.then(function(){}) — метод применяется для запуска произвольного

шага развертывания. В процессе выполнения скрипта переноса вы можете вызвать определенные функции контракта для добавления, редактирования или реорганизации данных контракта. Для развертывания и подключения контрактов
внутри функции обратного вызова вы должны использовать API абстракции
контракта.
Существует возможность выполнять условные шаги развертывания, которые зависят от сети назначения. Чтобы организовать условие выполнения шагов развертывания, ваш скрипт переноса должен получать второй параметр — network. Например, в сети mainnet уже развернуто множество популярных библиотек. Следовательно, при развертывании контракта в этой библиотеке вам не надо развертывать
библиотеки — достаточно лишь подключить их. Вот небольшой пример такого
подхода:
module.exports = function(deployer, network) {
if (network != "live") {
// Выполняем различные действия, если имя сети не "live"
} else {
// Делаем что-то специальное для сети с именем "live"
}
}

В заготовке проекта вы найдете два файла переноса: 1_initial_migration.js и
Первый файл не следует трогать, если вы не имеете четкого
понимания, что делаете. Второй файл можно редактировать по своему усмотрению.
Код файла 2_deploy_contracts.js выглядит так:

2_deploy_contracts.js.

var ConvertLib = artifacts.require("./ConvertLib.sol");
var MetaCoin = artifacts.require("./MetaCoin.sol");
module.exports = function(deployer) {
deployer.deploy(ConvertLib);
deployer.link(ConvertLib, MetaCoin);
deployer.deploy(MetaCoin);
};
____________________________________

[ 217 ] ___________________________________

Глава 8

В этом коде мы создаем абстракции для библиотеки ConvertLib и контракта
MetaCoin. Независимо от того, какую сеть будем использовать, мы размещаем библиотеку ConvertLib, подключаем ее к контракту MetaCoin и, наконец, развертываем
контракт MetaCoin.
Для запуска скрипта переноса выполните команду:
truffle migrate --network development

В данном случае мы указываем Truffle выполнить перенос в сеть development. Впрочем, если не указать опцию --network, то сеть development будет использована по
умолчанию.
После запуска команды вы получите уведомление о том, что Truffle автоматически
обновит адреса библиотеки ConvertLib и контракта MetaCoin в файлах артефактов,
а также обновит ссылки.
Есть еще две важные опции, на которые стоит обратить внимание:
 --reset — выполняет все скрипты переноса с самого начала, а не с последнего

завершенного переноса;
 -f — выполняет скрипт, начиная с указанного номера.
Вы можете в любое время найти адреса контрактов и библиотек вашего проекта
при помощи команды truffle networks.

Юнит-тесты контрактов
Юнит-тест (поузловой тест) — один из методов тестирования приложения. Это
процесс, при котором приложение разбивается на минимально возможные функциональные фрагменты, именуемые юнитами (unit), а затем каждый юнит подвергается тестированию по отдельности. Юнит-тестирование можно выполнять вручную, хотя обычно его автоматизируют.
Truffle по умолчанию распространяется с фреймворком для автоматизации тестирования ваших контрактов. Он обеспечивает чистую среду выполнения для запуска
тестовых файлов — Truffle будет перезапускать ваши скрипты переноса для каждого тестового файла, чтобы поддерживать свежий набор контрактов, подвергаемых
тестированию.
Truffle позволяет нам писать простые и управляемые тесты двумя способами:
 на языке JavaScript — чтобы проверять контракт из клиентской части прило-

жения;
 на языке Solidity — чтобы проверять ваш контракт из других контрактов.

Оба подхода имеют свои достоинства и недостатки, поэтому мы изучим два способа разработки тестов.
____________________________________

[ 218 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

Все тесты должны располагаться в каталоге /test. Truffle будет выполнять только
файлы с расширениями js, es, es6, jsx и sol. Все остальные файлы игнорируются.
При запуске автоматических тестов ethereumjs-testrpc работает заметно быстрее, чем другие клиенты. Более того, testrpc содержит специальные опции, которые повышают скорость выполнения тестов Truffle почти на 90 процентов.
В общем, мы рекомендуем использовать testrpc в процессе обычной разработки
и тестирования, а потом непосредственно перед развертыванием еще раз однократно выполнить тесты при помощи go-ethereum или иного официального клиента Ethereum.

Написание тестов на JavaScript
Тестовый фреймворк Truffle является надстройкой над Mocha. В свою очередь,
Mocha — это среда JavaScript для написания тестов, а Chai — библиотека проверки
утверждений (assertion library).
Тестовые фреймворки применяются для организации и выполнения тестов, а библиотеки проверки утверждений содержат утилиты для проверки правильности различных утверждений. Библиотеки значительно упрощают тестирование вашего
кода, потому что вам не приходится выполнять тысячи проверок оператором if.
Большинство тестовых фреймворков не содержат библиотек проверки утверждений
и разрешают пользователю подключать их по мере необходимости.
Прежде чем продолжить чтение, вам следует научиться писать тесты с использованием Mocha и Chai. Для изучения Mocha посетите адрес: https://
mochajs.org/. Для изучения Chai посетите адрес: http://chaijs.com/.

Ваши тесты должны располагаться в каталоге ./test и названия файлов должны
заканчиваться расширением js.
Абстракции контракта являются основой возможного взаимодействия с JavaScript.
Поскольку у Truffle нет способа заранее определить, для каких контрактов вам потребуется взаимодействие с тестами, вам придется запросить контракты в явном
виде. Это делается при помощи метода artifacts.require(). Итак, первое, что нужно сделать в тестовых файлах, — это создать абстракции для контрактов, которые
вы хотите проверить.
Затем следует написать тесты. Ваши тесты должны перейти из Mocha почти без
структурных изменений. Файлы тестов должны содержать код, который Mocha
распознает как автоматизированный тест. Тесты Truffle отличаются от Mocha наличием функции contract(). Эта функция в целом работает аналогично describe(), за
исключением того, что указывает Truffle выполнить все скрипты переноса. Функция contract() работает следующим образом:
 перед выполнением каждой функции contract() ваши контракты заново разворачиваются на узле Ethereum, поэтому все тесты запускаются для исходного состояния контракта;
 функция contract() предоставляет перечень счетов, доступных на вашем узле
и пригодных для написания тестов.
____________________________________

[ 219 ] ___________________________________

Глава 8
Поскольку «под капотом» у Truffle скрывается Mocha, вы можете продолжать использовать describe() для запуска обычных тестов Mocha, когда не нужны функции Truffle.

В листинге 8.5 приведен исходный код теста, который по умолчанию генерирует
Truffle для тестирования контракта MetaCoin. Вы найдете этот код в файле
metacoin.js.
Листинг 8.5. Исходный код файла теста metacoin.js
// Запрашиваем абстракцию для MetaCoin.sol
var MetaCoin = artifacts.require("./MetaCoin.sol");
contract('MetaCoin', function(accounts) {
it("на первом счете должно быть 10000 MetaCoin", function() {
return MetaCoin.deployed().then(function(instance) {
return instance.getBalance.call(accounts[0]);
}).then(function(balance) {
assert.equal(balance.valueOf(), 10000, "10000 не оказалось на первом счете");
});
});
it("следует корректно отправить средства", function() {
var meta;
// Получаем начальные балансы первого и второго счета.
var account_one = accounts[0];
var account_two = accounts[1];
var account_one_starting_balance;
var account_two_starting_balance;
var account_one_ending_balance;
var account_two_ending_balance;
var amount = 10;
return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_starting_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_starting_balance = balance.toNumber();
return meta.sendCoin(account_two, amount, {from: account_one});
}).then(function() {
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_ending_balance = balance.toNumber();
____________________________________

[ 220 ] ___________________________________

Разработка смарт-контрактов уровня предприятия
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_ending_balance = balance.toNumber();
assert.equal(account_one_ending_balance, account_one_starting_balance - amount,
"Сумма некорректно получена от отправителя");
assert.equal(account_two_ending_balance, account_two_starting_balance +
amount, "Сумма некорректно отправлена получателю");
});
});
});

В коде листинга 8.5 вы можете видеть, что взаимодействие контрактов полностью
реализовано на основе библиотеки truffle-contract. В остальном код очевиден и не
требует дополнительных пояснений.
Наконец, Truffle дает нам доступ к конфигурации Mocha, поэтому мы можем
управлять поведением Mocha. Конфигурация Mocha находится в свойстве mocha
в файле truffle.js экспортированного объекта. Например, конфигурация может
выглядеть так:
mocha: {
useColors: true
}

Написание тестов на Solidity
Тестовые файлы на языке Solidity имеют расширение sol. Перед тем как приступить к написанию тестов на языке Solidity, обратите внимание на некоторые особенности:
 тесты Solidity не должны являться расширением какого-либо контракта. Это минимизирует ваши тесты насколько возможно и дает вам полный контроль над
контрактами, которые вы пишете;
 Truffle содержит готовую библиотеку проверки утверждений, но вы можете изменять и дорабатывать эту библиотеку по мере необходимости;
 вы должны иметь возможность запустить ваши тесты Solidity в любом клиенте
Ethereum.
Чтобы научиться писать тесты на Solidity, рассмотрите исходный код стандартного
теста, который по умолчанию генерирует Truffle. Код хранится в файле
TestMetacoin.sol и показан в листинге 8.6.
Листинг 8.6. Исходный код файла теста TestMetacoin.sol
pragma Solidity ^0.4.2;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";
____________________________________

[ 221 ] ___________________________________

Глава 8
contract TestMetacoin {
function testInitialBalanceUsingDeployedContract() {
MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Владелец счета должен
иметь 10000 MetaCoin");
}
function testInitialBalanceWithNewMetaCoin() {
MetaCoin meta = new MetaCoin();
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Владелец счета должен
иметь 10000 MetaCoin");
}
}

Код из листинга 8.6 работает следующим образом:
1. Функции утверждений, такие как Assert.equal(), представлены в библиотеке
truffle/Assert.sol. Это стандартная библиотека проверки утверждений, но вы
можете подключить собственную библиотеку, если она свободно интегрируется
со средой выполнения тестов Truffle и вызывает правильные события утверждений. Функции утверждений вызывают события, которые затем Truffle перехватывает и отображает соответствующую информацию. Такова архитектура
библиотек проверки утверждений Solidity в среде Truffle. Вы можете найти
полный перечень функций утверждений в файле Assert.sol по адресу:
https://github.com/ConsenSys/truffle/blob/beta/lib/testing/Assert.sol.
2. Если мы используем путь импорта truffle/Assert.sol, то truffle — это имя
пакета. Мы будем изучать пакеты немного позже.
3. Адреса ваших развернутых контрактов (т. е. контрактов, которые были развернуты в процессе переноса) доступны при помощи библиотеки
truffle/DeployedAddresses.sol. Эта библиотека тоже входит в состав Truffle. Она
заново компилируется и подключается перед каждым запуском тестового пакета. Библиотека предоставляет доступ ко всем вашим развернутым контрактам
в виде DeployedAddresses.(). Вызов возвращает адрес контракта,
который в дальнейшем можно использовать для доступа к контракту.
4. Чтобы использовать развернутый контракт, вам придется импортировать код
контракта в свой тестовый пакет. Обратите внимание в рассматриваемом примере на строку:
import "../contracts/MetaCoin.sol";
____________________________________

[ 222 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

Этот импорт принадлежит тестовому контракту, который расположен в каталоге
./test, но вынужден выйти за пределы каталога ./test, чтобы найти контракт
MetaCoin. Затем он использует этот контракт для преобразования адреса в тип
MetaCoin.
5. Имена всех тестовых контрактов должны начинаться со слова Test (с заглавной
буквы T). Это помогает отличить файл теста от вспомогательных файлов и контрактов, подвергаемых тестированию, и указывает среде тестирования, какой
файл содержит набор тестов.
6. По аналогии с именем файла все тестовые функции должны начинаться со слова
test, но в нижнем регистре. Каждая функция выполняется как одиночная транзакция и в том порядке, в каком встречается в тестовом файле. Функции
утверждений из библиотеки truffle/Assert.sol инициируют события, которые
среда тестирования использует для определения результата теста. Функции утверждения возвращают логическое (булево) значение, которое представляет результат утверждения. Вы можете использовать его для выхода из теста раньше,
чем возникнет ошибка выполнения (ошибка, которую сгенерирует testrpc).
7. Вам доступно несколько средств тестирования (test hooks, хуков), которые показаны в следующем примере. Это хуки с названиями: beforeAll, beforeEach,
afterAll и afterEach, которые не отличаются от таких же средств Mocha в тестах
на JavaScript. Вы можете использовать эти хуки для назначения и отмены действий до и после каждого теста, либо до и после каждого наборатестов. Аналогично функции, каждый хук выполняется через одиночную транзакцию. Имейте
в виду, что некоторые сложные тесты могут нуждаться в большом количестве
действий по настройке событий и превышать лимит газа транзакции. Вы можете
обойти лимит газа, создав несколько хуков с различными суффиксами, как показано в примере:
import "truffle/Assert.sol";
contract TestHooks {
uint someValue;
function beforeEach() {
someValue = 5;
}
function beforeEachAgain() {
someValue += 1;
}
function testSomeValueIsSix() {
uint expected = 6;
Assert.equal(someValue, expected, "someValue должно быть = 6");
}
}

8. Вы можете задать данные контракта перед выполнением теста и сбросить их
в ходе подготовки к следующему тесту. Аналогично тестам на JavaScript, ваша
____________________________________

[ 223 ] ___________________________________

Глава 8

следующая функция тестирования продолжит работу с тем состоянием контракта, которое осталось после завершения работы предыдущей функции.
Truffle не располагает возможностью проверить, должен ли ваш контракт вбрасывать исключения (речь идет о контрактах, которые используют вбрасывание
исключений для уведомления о предсказуемых ошибках). Но вы можете найти
оригинальное решение проблемы по адресу:
http://truffleframework.com/tutorials/testing-for-throws-in-Solidity-tests.

Как перевести валюту на тестовый контракт?
Чтобы перевести валюту на тестовый контракт, в нем должна быть публичная
функция initialBalance(), которая возвращает значение uint. Она может быть написана непосредственно как функция или как публичная переменная. Когда ваш тестовый контракт развернут в сети, Truffle отправит указанную сумму с тестового
счета на тестовый контракт. Теперь ваш тестовый контракт может использовать
полученную валюту для проведения операций с контрактом, который вы тестируете. Функция initialBalance() не является обязательной.
В качестве примера рассмотрим следующий небольшой код:
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MyContract.sol";
contract TestContract {
// Truffle отправит на TestContract один Эфир после развертывания контракта
public uint initialBalance = 1 ether;
function testInitialBalanceUsingDeployedContract() {
MyContract myContract = MyContract(DeployedAddresses.MyContract());
// выполняем отправку суммы на myContract, затем проверяем результат
myContract.send(...);
}
function () {
// Эта функция не выполняется, если валюта успешно отправлена
}
}
Truffle переводит валюту таким способом, который не выполняет вызов резервной функции. Поэтому вы можете продолжать использовать резервную функцию
для нужд тестирования в сложных случаях.

Запуск тестов
Для запуска тестовых скриптов достаточно выполнить команду:
truffle test
____________________________________

[ 224 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

Можно указать путь к определенному файлу, который вы хотите запустить, например:
truffle test ./path/to/test/file.js

Управление пакетами
Пакет представляет собой набор смарт-контрактов и их артефактов. Пакет может
зависеть от других пакетов, из которых вы используете смарт-контракты или артефакты. При использовании пакетов в своем проекте важно учитывать, что существуют два места, в которых мы будем использовать контракты и их артефакты:
внутри контрактов проекта и внутри кода JavaScript проекта (скрипты переноса
и тесты).
Проекты, созданные Truffle, по умолчанию имеют определенную структуру, которая позволяет использовать их в качестве пакетов. Наиболее важными каталогами
в пакете truffle являются следующие каталоги:
 /contracts;
 /build/contracts (создается Truffle).

Первый каталог — это каталог ваших контрактов, где хранятся «сырые» контракты
Solidity. Во втором каталоге хранятся артефакты сборки в виде файлов .json.
Truffle поддерживает два варианта управления пакетами: NPM и EthPM. Вы уже
должны знать, что такое пакеты NPM, но пакеты EthPM следует пояснить отдельно.
Это конфигурация пакетов в сети Ethereum. Вы можете прочитать больше о EthPM
по адресу: https://www.ethpm.com/. Стандарт пакетов основан на спецификации
ERC190, описывающей публикацию и применение пакетов смарт-контрактов:
https://github.com/ethereum/EIPs/issues/190

Управление пакетами через NPM
Truffle по умолчанию поставляется с поддержкой NPM, поэтому правильно трактует назначение каталога node_modules, если он имеется в вашем проекте. Это означает, что вы можете распространять контракты и библиотеки при помощи механизма
NPM и сделать свой код доступным для окружающих, а вам будет доступен их код.
В своем проекте вы можете использовать файл package.json. Вы можете просто
установить произвольный NPM-пакет в свой проект и импортировать его в любые
файлы JavaScript, но если он содержит два упомянутых ранее каталога, то обязательно должен называться truffle. Установка NPM-пакета в проект Truffle ничем
не отличается от установки NPM-пакета в любое приложение Node.js.

Управление пакетами через EthPM
При установке пакетов EthPM автоматически создается каталог installed_contracts,
если он не существовал ранее. Этот каталог можно использовать аналогично каталогу node_modules, о котором мы только что говорили.
____________________________________

[ 225 ] ___________________________________

Глава 8

Установка пакетов при помощи EthPM столь же проста, как установка NPM-пакетов. Достаточно выполнить команду:
truffle install

Можно установить пакет определенной версии:
truffle install @

Нумерация пакетов NPM и EthPM соответствует спецификации SemVer10. В вашем
проекте может присутствовать файл ethpm.json, который совпадает с файлом
package.json для NPM-пакетов. Для установки всех зависимостей, упомянутых
в файле ethpm.json, выполните команду:
truffle install

Содержимое файла ethpm.json может выглядеть приблизительно так, как показано
в этом примере:
{
"package_name": "adder",
"version": "0.0.3",
"description": "Simple contract to add two numbers",
"authors": [
"Tim Coulter "
],
"keywords": [
"ethereum",
"addition"
],
"dependencies": {
"owned": "^0.0.1"
},
"license": "MIT"
}
Создание и публикация NPM-пакетов для Truffle ничем не отличается от процесса создания любых других NPM-пакетов. Чтобы узнать, как создавать и публиковать EthPM-пакеты, прочтите руководство по адресу:
http://truffleframework.com/docs/getting_started/packages-ethpm#publishingyour-own-package.
Независимо от того, какой пакет вы создаете: NPM или EthPM — необходимо
выполнить команду:
truffle networks --clean
Запуск этой команды приводит к удалению всех артефактов для тех идентификаторов сети, которые имеют обобщающий символ * в файле конфигурации. Скорее всего, эти сети являются частными (закрытыми) и используются только для
10

SemVer (Semantic Versioning, семантическая версификация) — спецификация и механизм управления версиями пакетов в Node.js (см. http://nodesource.com/blog/semver-a-primer/).
____________________________________

[ 226 ] ___________________________________

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

Использование контрактов из пакета
Чтобы задействовать контракты из состава пакета в своем контракте, достаточно
использовать оператор import языка Solidity. Когда путь импорта не является относительным или абсолютным в явном виде, Truffle полагает, что вы ищете файл из
определенного именованного пакета. Рассмотрим пример из example-truffle-library
по адресу: https://github.com/ConsenSys/example-truffle-library:
import "example-truffle-library/contracts/SimpleNameRegistry.sol";

Поскольку путь не начинается с символов ./, Truffle понимает, что надо заглянуть
в папки node_modules или installed_contracts каталога example-truffle-library. Таким образом Truffle находит путь к контракту, который вы запросили.

Использование артефактов пакета в коде JavaScript
Для обращения к артефактам пакета в коде JavaScript нужно запросить файл .json
соответствующего пакета при помощи оператора require и указать на trufflecontract, чтобы преобразовать его в прикладную абстракцию:
var data = require("example-trufflelibrary/build/contracts/SimpleNameRegistry.json");
var contract = require("truffle-contract");
var SimpleNameRegistry = contract(data);

Доступ к адресам развернутых контрактов пакета в Solidity
Может случиться так, что вашему контракту понадобится взаимодействовать с пакетами ранее развернутых контрактов. Поскольку адреса развернутых контрактов
находятся внутри файлов .json соответствующих пакетов, код Solidity не может
напрямую прочитать содержимое этих файлов. Чтобы код Solidity получил доступ
к адресам в файлах .json, можно поступить следующим образом: определить в коде
Solidity функции, которые определяют адреса контрактов-зависимостей, а когда
контракт развернут, вызывать эти функции в коде JavaScript.
Например, вы можете написать код контракта наподобие этого:
import "example-truffle-library/contracts/SimpleNameRegistry.sol";
contract MyContract {
SimpleNameRegistry registry;
address public owner;
function MyContract {
owner = msg.sender;
}
____________________________________

[ 227 ] ___________________________________

Глава 8
// Простой пример использования реестра из пакета
function getModule(bytes32 name) returns (address) {
return registry.names(name);
}
// Назначить реестр, если вы его владелец
function setRegistry(address addr) {
if (msg.sender != owner) throw;
registry = SimpleNameRegistry(addr);
}
}

Так будет выглядеть соответствующий скрипт переноса:
var SimpleNameRegistry = artifacts.require("example-trufflelibrary/
contracts/SimpleNameRegistry.sol");
module.exports = function(deployer) {
// Разворачиваем контракт и определяем адрес реестра
deployer.deploy(MyContract).then(function() {
return MyContract.deployed();
}).then(function(deployed) {
return deployed.setRegistry(SimpleNameRegistry.address);
});
};

Работа с консолью Truffle
Иногда бывает полезно тестировать и отлаживать контракт в интерактивном режиме или вручную выполнять транзакции. Благодаря Truffle, у нас есть простой способ выполнять эти действия в интерактивной консоли, когда ваш контракт готов
к использованию.
Чтобы открыть консоль, выполните команду:
truffle console

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

соли команду:
migrate --reset

она будет интерпретирована так же, как команда:
truffle migrate --reset
____________________________________

[ 228 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

 введенная за пределами консоли;
 все ваши контракты должны быть доступны и готовы к использованию;
 после каждой команды (такой, как migrate --reset) ваши контракты обновляют-

ся, т. е. вы можете немедленно начинать использование новых адресов и бинарного кода;
 все объекты web3 доступны и настроены на подключение к вашему узлу

Ethereum;
 все команды, которые возвращают обещание, будут автоматически завершены,

а результат выведен на печать. Это делает ненужным использование оператора
.then() для простых команд. Например, вы можете ввести код наподобие такого:
MyContract.at("0xabcd...").getValue.call();

Запуск внешних скриптов в контексте Truffle
Часто возникает необходимость запустить внешние скрипты, которые будут взаимодействовать с вашими контрактами. Благодаря Truffle, у нас есть простой способ
это сделать: загрузить контракты в нужной сети и автоматически подключиться
к узлу Ethereum в соответствии с конфигурацией нашего проекта.
Для запуска внешнего скрипта выполните команду:
truffle exec

Для правильного выполнения внешних скриптов Truffle ожидает, что они будут
экспортировать функцию, которая принимает один параметр в качестве обратного
вызова. Вы можете делать что угодно в этом скрипте, пока выполняется обратный
вызов и пока не завершится скрипт. Обратный вызов принимает ошибку как первый и единственный параметр. Если получена ошибка, выполнение остановится,
и процесс вернет ненулевой код выхода.
Структура, которой должны соответствовать внешние скрипты, выглядит следующим образом:
module.exports = function(callback) {
// здесь выполняем действия
callback();
}

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

[ 229 ] ___________________________________

Глава 8

Создание клиента при помощи Truffle означает интеграцию артефактов в исходный
код клиента и подготовку исходного кода клиента к развертыванию.
Для создания клиента следует выполнить команду:
truffle build

При выполнении этой команды Truffle обращается к свойству build в файле конфигурации проекта.

Запуск внешних команд
Для создания клиента можно использовать инструмент командной строки. Если
свойство build является строкой, Truffle предполагает, что мы хотим запустить
команду для создания клиента, поэтому он выполняет строку как команду. Команде
доступен обширный набор переменных среды, которые можно интегрировать
с Truffle.
Вы можете заставить Truffle запустить инструмент командной строки для создания
клиента при помощи кода конфигурации, аналогичного следующему примеру:
module.exports = {
// This will run the &grave;webpack&grave; command on each build.
//
// The following environment variables will be set when running the
command:
// WORKING_DIRECTORY: root location of the project
// BUILD_DESTINATION_DIRECTORY: expected destination of built assets
// BUILD_CONTRACTS_DIRECTORY: root location of your build contract files
(.sol.js)
//
build: "webpack"
}

Запуск пользовательских функций
Для создания клиента можно использовать функцию JavaScript. Если свойство build
является функцией, Truffle будет выполнять эту функцию всякий раз при создании
клиента. Функция дает много информации о проекте, которая может быть интегрирована c Truffle.
Вы можете заставить Truffle выполнить функцию для создания клиента при помощи кода конфигурации, аналогичного следующему примеру:
module.exports = {
build: function(options, callback) {
// Do something when a build is required. &grave;options&grave;
contains these values:
//
____________________________________

[ 230 ] ___________________________________

Разработка смарт-контрактов уровня предприятия
// working_directory: root location of the project
// contracts_directory: root directory of .sol files
// destination_directory: directory where truffle expects the built
assets (important for &grave;truffle serve&grave;)
}
}
Вы также можете создать объект, который содержит метод создания, наподобие
показанного здесь. Это отлично подходит для тех, кто хочет опубликовать пакет
для создания клиента.

Конструктор Truffle по умолчанию
Truffle содержит NPM-пакет truffle-default-builder, который является конструктором
Truffle по умолчанию. Этот конструктор экспортирует объект, содержащий метод
создания клиента, работающий полностью идентично методу, о котором шла речь
в предыдущем разделе.
Конструктор по умолчанию можно использовать для создания веб-клиента вашего
децентрализованного приложения, когда сервер обслуживает только статичные
файлы, а вся функциональность обеспечена клиентской частью.
Прежде, чем приступить к углубленному изучению конструктора по умолчанию,
следует установить его командой:
npm install truffle-default-builder --save

Теперь измените файл конфигурации, чтобы он выглядел следующим образом:
var DefaultBuilder = require("truffle-default-builder");
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "10"
},
live: {
host: "localhost",
port: 8545,
network_id: "1"
}
},
build: new DefaultBuilder({
"index.html": "index.html",
"app.js": [
"javascripts/index.js"
],
____________________________________

[ 231 ] ___________________________________

Глава 8
"bootstrap.min.css": "stylesheets/bootstrap.min.css"
})
};

Конструктор по умолчанию дает вам полный контроль над тем, как организована
структура файлов и папок в вашем клиенте.
Эта конфигурация описывает пункт назначения targets (левая часть) с указанием
содержимого в виде файлов, папок и массивов файлов (правая часть). Каждый
пункт назначения создается обработкой файлов, указанных в правой части, с учетом их расширений, конкатенации результатов и сохранения результирующего
файла (target file) в место создания. В нашем случае вместо массива указана строка,
этот файл будет обработан при необходимости, а затем скопирован в указанное место. Если строка заканчивается символом «/», он будет интерпретирован как указание на каталог, и этот каталог будет просто скопирован без обработки. Все пути
в правой части указываются относительно каталога app/.
Вы можете в любое время изменить этот файл конфигурации и структуру каталогов. Вовсе не обязательно иметь каталоги javascripts и stylesheets, но позаботьтесь
соответствующим образом исправить вашу конфигурацию.
Если вы хотите использовать конструктор по умолчанию для интеграции Truffle с
клиентской частью вашего веб-приложения, позаботьтесь о создании целевого
файла app.js, к которому конструктор будет присоединять код. Любое другое
имя файла не подойдет.

Конструктор по умолчанию обладает следующими возможностями:
 автоматически импортирует артефакты скомпилированных контрактов, инфор-

мацию о развернутых контрактах и конфигурацию узла Ethereum в исходный
код клиента;
 подключает рекомендованные зависимости, включая web3 и truffle-contract;
 компилирует файлы ES6 и JSX;
 компилирует файлы SASS;
 минифицирует11 файлы asset.
Для отслеживания изменений в каталогах contracts, app и файле конфигурации
можно использовать команду:
truffle watch
Как только обнаружено изменение, команда перекомпилирует контракты, создает
новые файлы артефактов и пересобирает клиент. Но при этом она не запускает
скрипты переноса и тестирования.

11

Минификация — уменьшение размера исходного кода путем удаления ненужных символов без
потери функциональности.
____________________________________

[ 232 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

Создание клиента
Давайте напишем клиентскую часть нашего децентрализованного приложения, а
затем выполним его компоновку при помощи конструктора Truffle. Сначала создайте файлы и каталоги на основе рассмотренной ранее конфигурации: создайте
каталог app и внутри него создайте файл index.html, а также каталоги javascripts и
styelsheets. Внутри каталога javascripts создайте файл index.js, а внутрь каталога
stylesheets скачайте и сохраните файл CSS для Bootstrap 4. Вы можете найти его по
адресу: https://v4-alpha.getbootstrap.com/getting-started/download/#bootstrap-cssand-js.
В файл index.html поместите код из листинга 8.7.
Листинг 8.7. Код файла index.html










Отправить Метакоины



Выбрать адрес счета




Сколько метакоинов вы хотите отправить?




Введите адрес, на который хотите отправить
метакоины


____________________________________

[ 233 ] ___________________________________

Глава 8
Отправить




Найти баланс



Выбрать адрес счета












В коде из листинга 8.7 мы загружаем файлы bootstrap.min.css и app.js. Далее, у нас
есть две формы: одна из них — для отправки метакоинов на другой счет, а вторая — для проверки баланса своего счета. В первой форме пользователь должен
выбрать счет, ввести количество метакоинов и адрес получателя. Во второй форме
пользователь просто выбирает адрес счета, баланс которого хочет проверить.
В файл index.js поместите код из листинга 8.8.
Листинг 8.8. Код файла index.js
window.addEventListener("load", function(){
var accounts = web3.eth.accounts;
var html = "";
for(var count = 0; count < accounts.length; count++)
{
html = html + "" + accounts[count] + "";
}
document.getElementById("fromAddress").innerHTML = html;
document.getElementById("address").innerHTML = html;
____________________________________

[ 234 ] ___________________________________

Разработка смарт-контрактов уровня предприятия
MetaCoin.detectNetwork();
})
document.getElementById("sendForm").addEventListener("submit", function(e){
e.preventDefault();
MetaCoin.deployed().then(function(instance){
return
instance.sendCoin(document.getElementById("toAddress").value,
document.getElementById("amount").value, {
from:
document.getElementById("fromAddress").options[document.getElementById("from
Address").selectedIndex].value
});
}).then(function(result){
alert("Транзакция успешно обработана. Txn Hash: " + result.tx);
}).catch(function(e){
alert("Произошла ошибка");
})
})
document.getElementById("findBalanceForm").addEventListener("submit",
function(e){
e.preventDefault();
MetaCoin.deployed().then(function(instance){
return
instance.getBalance.call(document.getElementById("address").value);
}).then(function(result){
console.log(result);
alert("Баланс: " + result.toString() + " метакоинов");
}).catch(function(e){
alert("Произошла ошибка");
})
})

Код из листинга 8.8 работает так:
1. Конструктор по умолчанию создает артефакты, доступные через глобальный
объект __contracts__.
2. Также конструктор делает доступными абстракции всех контрактов. Они доступны как глобальные переменные, имена которых совпадают с именами контрактов.
3. Конструктор предоставляет объект web3 через уже существующий провайдер,
а также определяет провайдера абстракций контракта. Он подключает объект
web3 к сети с именем development и адресом подключения по умолчанию
http://localhost:8545 (если не задан иной адрес в явном виде).
____________________________________

[ 235 ] ___________________________________

Глава 8

4. Мы дожидаемся полной загрузки страницы. Когда загрузка завершена, мы
запрашиваем список счетов подключенного узла и отображаем его в обеих формах, а также вызываем метод detectNetwork() абстракции MetaCoin.
5. Далее, у нас имеется обработчик событий кнопки Отправить в обеих формах.
Они делают именно то, чего от них ожидают, а результат отображается во
всплывающем окне.
6. Когда передано содержимое первой формы, мы обращаемся к экземпляру развернутого контракта MetaCoin и вызываем метод sendCoin() с корректными аргументами.
7. Когда передано содержимое второй формы, мы запрашиваем баланс выбранного
счета вызовом метода getBalance() на виртуальной машине Ethereum, вместо того, чтобы транслировать транзакцию в сеть.
Теперь идем дальше и выполняем команду: truffle build. Вы увидите, что Truffle
создаст файлы index.html, app.js и bootstrap.min.css в каталоге build и поместит
в них окончательный код развертывания клиента.

Сервер Truffle
Truffle распространяется со встроенным веб-сервером. Этот сервер лишь выдает
файлы из каталога build с правильным заголовком типа MIME. Помимо этого, он
не приспособлен к выполнению чего-либо еще.
Для запуска сервера выполните команду:
truffle serve

По умолчанию сервер доступен через порт 8080, но вы можете использовать опцию -p для назначения другого номера порта.
По аналогии с командой truffle watch, этот веб-сервер также отслеживает изменения в каталогах contracts, app и в файле конфигурации. Если обнаружено изменение, он перекомпилирует контракты, генерирует новые файлы артефактов и пересобирает клиента. Но веб-сервер не запускает скрипты переноса и тесты.
Поскольку конструктор truffle-default-builder помещает финальный код развертывания в каталог build, для получения доступа к этим файлам через сеть достаточно выполнить команду запуска сервера: truffle serve.
Давайте протестируем наш веб-клиент. Наберите в адресной строке браузера адрес
http://localhost:8080, и вы должны увидеть окно, показанное на рис. 8.1.
Разумеется, у вас будут отображаться другие адреса счетов. При развертывании
контракта он помещает все метакоины по адресу развертывания этого контракта.
По этой причине на первом счете у нас имеется 10 000 метакоинов. Теперь давайте
отправим пять метакоинов с первого счета на второй счет и нажмем кнопку Submit.
Вы должны увидеть окно, аналогичное показанному на рис. 8.2.
Теперь проверьте баланс второго счета. Для этого в списке правой формы выберите
второй счет и нажмите кнопку Check Balance (рис. 8.3).
____________________________________

[ 236 ] ___________________________________

Разработка смарт-контрактов уровня предприятия

Рис. 8.1. Начальный экран веб-клиента

Рис. 8.2. Окно клиента после отправки метакоинов

Рис. 8.3. Окно проверки баланса второго счета

____________________________________

[ 237 ] ___________________________________

Глава 8

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

____________________________________

[ 238 ] ___________________________________



Разработка блокчейна
для консорциума

Консорциумы (профильные объединения, обычно состоящие из нескольких участников, таких как банки, сайты электронной коммерции, правительственные
учреждения, больницы и т. п.) могут использовать технологию блокчейна, чтобы
решить ряд проблем и сделать свою работу быстрее и дешевле. Хотя они и представляют в общих чертах, как им может помочь блокчейн, реализация блокчейна
Ethereum подходит им не во всех случаях. Тем не менее, несмотря на то, что
существуют варианты блокчейна специально для консорциумов (например,
Hyperledger), в этой книге мы рассматриваем прежде всего Ethereum. Поэтому сейчас мы расскажем, как приспособить Ethereum для создания блокчейна консорциума.
В основу нашего блокчейна будет заложен сетевой узел под названием Parity. Существуют и альтернативные решения, такие как J.P. Morgan Quorum, но мы воспользуемся именно Parity, потому что к моменту работы над книгой он уже достаточно долго существовал, и его использовали многие предприятия, а другие решения применялись лишь ограниченным кругом организаций. Однако имейте в виду,
что Parity не обязательно станет лучшим выбором для вашего предприятия. Обязательно изучите другие варианты, чтобы выбрать наиболее подходящее решение.
В этой главе будут рассмотрены следующие темы:
 почему Ethereum плохо подходит для блокчейна консорциума?
 что такое узел Parity и каковы его особенности?
 что такое консенсус с доказательством полномочий, и какие типы консенсуса
поддерживает Parity?
 как работает протокол консенсуса Aura?
 скачивание и установка Parity;
 создание блокчейна консорциума на основе Parity.

Глава 9

Что такое блокчейн консорциума?
Чтобы лучше понять, что такое блокчейн консорциума, или, вернее, какая разновидность блокчейна нужна консорциуму, рассмотрим простой пример. Банк хочет
создать блокчейн, чтобы сделать денежные переводы проще, быстрее и дешевле.
В этом случае банк выдвигает такие требования:
 скорость — сеть, в которой работает блокчейн, должна подтверждать транзакции практически в режиме реального времени. Однако в сети Ethereum блокирующий интервал между транзакциями составляет 12 секунд, вдобавок клиент
обычно ждет подтверждения транзакции несколько минут;
 контроль доступа — банки хотят, чтобы доступ к блокчейну был строго ограничен, и контролируемый доступ подразумевает различные ограничения. Например, необходимость получить разрешение на подключение к сети, иметь
полномочия на создание блоков, полномочия на отправку определенных транзакций и т. п.;
 безопасность — консенсус на основе доказательства выполнения работы не
гарантирует безопасность блокчейна в частных сетях, потому что они состоят
из слишком маленького числа участников и не обладают достаточно большой
совокупной вычислительной мощностью. Следовательно, нам нужен другой
протокол консенсуса;
 приватность — несмотря на то, что сеть является частной, внутри этой сети все
равно надо сохранять приватность. Существуют два типа приватности:
• приватность участника — остальные участники сети не должны иметь возможности отследить кого-либо1 через его транзакции. В сети Ethereum эту
проблему решают созданием нескольких аккаунтов, но тогда смартконтракты не смогут правильно работать, потому что нет способа доказать
контракту, что несколько аккаунтов принадлежат одному владельцу;
• приватность данных — иногда нам нужно, чтобы некоторые данные могли
видеть только строго заданные узлы, а не каждый узел сети.
В этой главе мы разберемся, как можно решить указанные проблемы в сети
Ethereum.

Что такое консенсус
с доказательством полномочий?
Доказательство полномочий (Proof-of-Authority, PoA) — это механизм достижения
консенсуса, при котором происходит обращение к списку валидаторов2. Валидато1

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

2

____________________________________

[ 240 ] ___________________________________

Разработка блокчейна для консорциума

ры — это особая группа аккаунтов/узлов, которым разрешено формировать консенсус. Они заверяют транзакции и блоки.
В протоколе PoA не задействован механизм майнинга. Существуют разные типы
протоколов PoA, и они различаются принципами работы. Например, Hyperledger
и Parity основаны на PoA, но главное различие между ними состоит в том, что
Hyperledger использует PBFT, а Parity — итеративный процесс.

Введение в Parity
Parity — это узел Ethereum, написанный с нуля специально для обеспечения корректности/проверяемости, модульности, низкой нагрузки и высокой производительности. Он написан на языке Rust — это гибридный язык3 с акцентом на производительность, специально разработанный Parity Technologies Ltd. На момент подготовки книги самой новой была версия Parity 1.7.0, и далее будет рассмотрена
именно эта версия4. Мы изучим Parity настолько, насколько это потребуется для
создания блокчейна консорциума. Для более глубокого изучения обратитесь к официальной документации на сайте https://www.parity.io/.
По сравнению с Go-Ethereum, Parity обладает более обширным набором инструментов, например браузером приложений web3, развитыми средствами управления
аккаунтами и другими подобными опциями. Но ключевой особенностью Parity является использование протокола доказательства полномочий (PoA) вместо
протокола доказательства работы (PoW). На данный момент Parity поддерживает
PoA-протоколы Aura и Tendermint. Рекомендуем использовать протокол Aura, т. к.
Tendermint находится на стадии разработки.
Aura намного лучше, чем PoW, подходит для блокчейна с ограниченным доступом,
потому что обеспечивает быструю обработку блоков и значительно более высокую
безопасность в частных сетях.

Принципы работы Aura
Давайте рассмотрим в общих чертах, как работает Aura. Прежде всего, необходимо,
чтобы для каждого узла сети был назначен один и тот же список валидаторов —
это список адресов аккаунтов, которые достигают консенсуса. Узел может быть
или не быть валидатором. Но даже единственный узел-валидатор должен хранить
список валидаторов и поэтому может достигать консенсуса с самим собой.
Если список валидаторов должен всегда оставаться неизменным, то его можно предоставить в статическом виде внутри генезисного блока. Динамически изменяемый
3

Rust — это гибрид концепций императивного, объектно-ориентированного и функционального подхода в одном языке программирования.
4
Во время работы над русским переводом была выпущена версия 1.8.3. Версии обычно обновляются
каждые шесть недель и пока поддерживают обратную совместимость.
____________________________________

[ 241 ] ___________________________________

Глава 9

список можно предоставить в смарт-контракте, и каждый узел будет знать об изменениях. В смарт-контракте можно реализовать различные стратегии добавления
новых валидаторов.
Время генерации блока задается в генезисном файле и полностью зависит от вашего решения. Для частных сетей хорошо подходит интервал в три секунды. В протоколе Aura каждые три секунды выбирается новый валидатор, и он отвечает за создание, проверку, подписание и трансляцию блока в сеть. Нам нет нужды глубоко
вникать в алгоритм выбора валидаторов, потому что он никак не связан с разработкой децентрализованных приложений. Но в общем виде формула выбора нового
валидатора имеет следующий вид:
UNIX_TIMESTAMP / BLOCK_TIME %NUMBER_OF_TOTAL_VALIDATORS

Алгоритм выбора достаточно разумен и предоставляет всем валидаторам равные
шансы. Когда другой узел получает блок, он проверяет, является ли действующий
валидатор отправителем этого блока. Если нет, то блок отвергается. В отличие от
алгоритма PoW, когда валидатор создает блок, он не получает вознаграждение.
В протоколе Aura только от нашего решения зависит, генерировать ли пустой блок,
если к моменту генерации нет ожидающих транзакций.
Вы можете спросить, что произойдет, если очередной валидатор по какой-то причине не сможет создать и транслировать следующий блок. В качестве примера допустим, что A — валидатор пятого блока и B — валидатор шестого блока. Интервал между блоками пять секунд. Если A не смог создать и передать блок, то через
пять секунд придет валидатор B и создаст этот блок. Так что не случится ничего
серьезного. Особенности создания блока будут отражены в метке времени.
Также вы можете спросить, существует ли вероятность того, что в сети возникнут
несколько разных блокчейнов, как это случается в протоколе PoW, если два майнера выработали блоки одновременно. Да, это может случиться в силу разных причин. В качестве примера рассмотрим один из вариантов развития событий и покажем, как сеть автоматически решает проблему. Итак, существуют пять валидаторов: A, B, C, D и E. Интервал между блоками пять секунд. Допустим, валидатор A
был выбран первым, и передал готовый блок в сеть. Но этот блок по какой-то причине не дошел до D и E, поэтому они решили, что A не передал блок. Далее алгоритм выбрал валидатора B для генерации следующего блока. Валидатор B генерирует следующий блок, вставляет его в блокчейн после блока, созданного узлом A,
и передает в сеть. Теперь D и E получили новый блок, но отвергают его, потому что
не получили предыдущий блок. По этой причине D и E сформируют свою цепочку
блоков, которая отличается от цепочки, сформированной узлами A, B и С. Теперь
узлы A, B и C будут отвергать блоки, созданные узлами D и E, и наоборот. Для решения этой проблемы применяется понятие уровень точности (accuracy score).
Уровень точности блокчейна валидаторов A, B и C будет выше, поэтому валидаторы D и E откажутся от своей версии и обновят блокчейн до версии, заверенной
узлами A, B и C. Например, когда валидатор B передает в сеть свой блок, вместе
с ним он передает уровень точности нового блокчейна. Если точность этого блок____________________________________

[ 242 ] ___________________________________

Разработка блокчейна для консорциума

чейна выше, то D и E будут должны заменить свои блокчейны на версию, предложенную узлом B. Таким образом, проблема решена. Уровень точности блокчейна
вычисляется по формуле:
U128_max * BLOCK_NUMBER_OF_LATEST_BLOCK (UNIX_TIMESTAMP_OF_LATEST_BLOCK / BLOCK_TIME)

Сначала точность блокчейна сравнивается по его длине — чем больше блоков, тем
лучше. Если длина совпадает, то предпочтение отдается версии с более старым
последним блоком.
Вы можете более глубоко изучить протокол Aura, перейдя по ссылке:
https://github.com/paritytech/parity/wiki/Aura.

Начинаем работу с Parity
Для работы с Parity потребуется Rust версии 1.16.05.

Установка Rust
Способы установки Rust зависят от операционной системы вашего компьютера.
Для Linux
В Linux-системах выполните команду терминала:
curl https://sh.rustup.rs -sSf | sh

Кроме этого, для Parity необходимо установить пакеты gcc, g++, libssl-dev/openssl,
libudev-dev, и pkg-config, если они не были установлены ранее.
Для OS X
В операционной системе OS X выполните команду терминала:
curl https://sh.rustup.rs -sSf | sh

Кроме этого, для Parity потребуется компилятор Clang, который поставляется вместе с инструментом командной строки Xcode или может быть установлен отдельно
при помощи Homebrew.
Для Windows
Прежде всего, у вас должна быть установлена среда разработки Visual Studio 2015
с поддержкой C++. Затем скачайте и запустите установщик, доступный по адресу:
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustupinit.exe.
Запустите приложение VS2015 x64 Native Tools Command Prompt и выполните следующую команду для установки и настройки пакета средств MSVC:
rustup default stable-x86_64-pc-windows-msvc

5

Для работы с Parity 1.8.3 требуется Rust не ниже 1.21.0.

____________________________________

[ 243 ] ___________________________________

Глава 9

Скачивание, установка и запуск Parity
После установки Rust в своей операционной системе вы можете выполнить однострочную команду установки Parity:
cargo install --git https://github.com/paritytech/parity.git parity

Для проверки установки Parity введите команду:
parity --help

Если установка прошла успешно, вы увидите полный список команд и опций.

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

Создание аккаунтов
Откройте два терминала командной строки: первый терминал — для первого валидатора, а второй — для второго валидатора. Первый узел будет содержать два
аккаунта, а второй узел — только один аккаунт. Второй аккаунт первого узла будет
располагать некоторой начальной суммой в эфирах, следовательно в сети будет
обращаться некоторое количество валюты.
В первом терминале дважды выполните такую команду:
parity account new -d ./validator0

Оба раза вам будет предложено ввести пароль. В данном случае просто введите
одинаковый пароль.
Во втором терминале однократно выполните команду:
parity account new -d ./validator1

Введите пароль для этого аккаунта.

Создание файла спецификации
Узлы любой сети обращаются к общему файлу спецификации. Этот файл рассказывает узлу о генезисном блоке, кто является валидаторами и т. п. Мы создадим
смарт-контракт, который содержит список валидаторов. Существуют два типа таких контрактов: рапортующий (reporting contract) и не рапортующий (non-reporting
contract) — мы должны выбрать только один вариант.
Различие между этими двумя типами контракта состоит в следующем. Контракт
без рапорта просто возвращает список валидаторов. Рапортующий же контракт
может принимать меры против неумышленного неправильного поведения узла (например, такой узел может просто не получать блок от назначенного валидатора)
____________________________________

[ 244 ] ___________________________________

Разработка блокчейна для консорциума

или против злоумышленного неправильного поведения (например, выпуска двух
разных блоков для одного и того же шага).
Интерфейс контракта без рапорта должен иметь, как минимум, следующий интерфейс:
{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"a
ddress[]"}],"payable":false,"type":"function"}

Функция getValidators будет вызываться для определения списка валидаторов при
появлении каждого блока. Правила переключения между валидаторами определяются контрактом, реализующим эту функцию.
Рапортующий контракт должен иметь, как минимум, следующий интерфейс:
[
{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"a
ddress[]"}],"payable":false,"type":"function"},
{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMa
licioгs","outputs":[],"payable":false,"type":"function"},
{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBe
nign","outputs":[],"payable":false,"type":"function"}
]

При обнаружении неумышленных или злоумышленных отклонений поведения
механизм консенсуса вызывает, соответственно, функцию reportBenign или
reportMalicious.
Мы создадим рапортующий контракт. Базовый пример такого контракта приведен
в листинге 9.1.
Листинг 9.1. Базовый пример рапортующего контракта
contract ReportingContract {
address[] public validators =
[0x831647ec69be4ca44ea4bd1b9909debfbaaef55c,
0x12a6bda0d5f58538167b2efce5519e316863f9fd];
mapping(address => uint) indices;
address public disliked;
function ReportingContract() {
for (uint i = 0; i < validators.length; i++) {
indices[validators[i]] = i;
}
}
// Вызывается при каждом блоке для обновления списка валидаторов
function getValidators() constant returns (address[]) {
return validators;
}
____________________________________

[ 245 ] ___________________________________

Глава 9
// Расширяет список валидаторов
function addValidator(address validator) {
validators.push(validator);
}
// Удаляет валидатора из списка
function reportMalicious(address validator) {
validators[indices[validator]] = validators[validators.length-1];
delete indices[validator];
delete validators[validators.length-1];
validators.length--;
}
function reportBenign(address validator) {
disliked = validator;
}
}

Этот код не требует пояснений. Убедитесь, что в массиве адресов этого кода вы
заменили исходные адреса на реальные адреса узлов validator1 и validator2, поскольку мы будем использовать эти адреса для заверения. Теперь скомпилируйте
этот контракт при помощи любого из изученных ранее компиляторов.
Теперь создадим файл конфигурации. Создайте файл с именем spec.json и поместите в него код из листинга 9.2.
Листинг 9.2. Исходный код файла spec.json
{
"name": "ethereum",
"engine": {
"authorityRound": {
"params": {
"gasLimitBoundDivisor": "0x400",
"stepDuration": "5",
"validators" : {
"contract": "0x0000000000000000000000000000000000000005"
}
}
}
},
"params": {
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2323"
},
____________________________________

[ 246 ] ___________________________________

Разработка блокчейна для консорциума
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x00000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"gasLimit": "0x5B8D80"
},
"accounts": {
"0x0000000000000000000000000000000000000001": { "balance": "1",
"builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000,
"word": 0 } } } },
"0x0000000000000000000000000000000000000002": { "balance": "1",
"builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word":
12 } } } },
"0x0000000000000000000000000000000000000003": { "balance": "1",
"builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600,
"word": 120 } } } },
"0x0000000000000000000000000000000000000004": { "balance": "1",
"builtin": { "name": "identity", "pricing": { "linear": { "base": 15,
"word": 3 } } } },
"0x0000000000000000000000000000000000000005": { "balance": "1",
"constructor" : "0x606060405260406040519081016040528073831647" },
"0x004ec07d2329997267Ec62b4166639513386F32E": { "balance":
"10000000000000000000000" }
}
}

Код из листинга 9.2 работает следующим образом:
1. Свойство engine служит для задания протокола и его специфических параметров.
Наш движок протокола называется authorityRound, что соответствует протоколу
Aura. Свойство gasLimitBoundDivisor определяет настройку лимита газа и содержит обычное значение ethereum. В свойство validators вложен параметр contract,
который содержит адрес уведомляющего контракта. Параметр stepDuration —
это интервал между блоками в секундах.
2. В свойстве params имеет значение только идентификатор сети. Остальные параметры стандартные для всех цепочек.
3. Свойство genesis имеет несколько стандартных значений для консенсуса
authorityRound.
____________________________________

[ 247 ] ___________________________________

Глава 9

4. Свойство accounts применяется для перечисления исходных аккаунтов и контрактов, которые находятся в сети. Первые четыре — это стандартные встроенные контракты Ethereum. Они должны быть добавлены для использования
контрактов на языке Solidity. Пятый контракт — это уведомляющий контракт.
Убедитесь, что вы не забыли подставить байт-код своего контракта в параметр constructor. Последний аккаунт сгенерирован в терминале, относящемся
к validator1. Онприменяется для отправки эфиров в сеть. Подставьте туда свой
номер.
Прежде, чем мы продолжим, создайте файл с именем node.pwds. Поместите в этот
файл пароль к созданным вами аккаунтам. Этот файл будут использовать валидаторы, чтобы разблокировать аккаунты и подписывать блоки.

Запуск узлов
Теперь у нас есть все необходимое для запуска узлов-валидаторов. В первом терминале выполните следующую команду для запуска первого валидатора:
parity --chain spec.json -d ./validator0 --force-sealing --engine-signer
"0x831647ec69be4ca44ea4bd1b9909debfbaaef55c" --port 30300 --jsonrpc-port
8540 --ui-port 8180 --dapps-port 8080 --ws-port 8546 --jsonrpc-apis
web3,eth,net,personal,parity,parity_set,traces,rpc,parity_accounts -password "node.pwds"

Команда устроена следующим образом:
 опция --chain указывает путь к файлу спецификации;
 опция -d указывает на каталог данных;
 опция --force-sealing обеспечивает создание блоков, даже если они не содержат

транзакции;
 опция --engine-signer определяет адрес аккаунта, который будет применять узел

для подписания блоков, т. е. адрес валидатора. В случае злонамеренного заверения пригодится опция --force-sealing. Она гарантирует, что правильная цепочка
длиннее. Убедитесь, что вы подставили в команду свой сгенерированный
адрес. Это должен быть первый адрес, сгенерированный ранее в терминале;
 опция --password задает файл пароля.

Во втором терминале выполните команду для запуска второго валидатора:
parity --chain spec.json -d ./validator1 --force-sealing --engine-signer
"0x12a6bda0d5f58538167b2efce5519e316863f9fd" --port 30301 --jsonrpc-port
8541 --ui-port 8181 --dapps-port 8081 --ws-port 8547 --jsonrpc-apis
web3,eth,net,personal,parity,parity_set,traces,rpc,parity_accounts -password "/Users/narayanprusty/Desktop/node.pwds"

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

[ 248 ] ___________________________________

Разработка блокчейна для консорциума

Подключение узлов
Теперь нам надо соединить наши узлы между собой. Откройте новое окно терминала и выполните следующую команду:
curl --data '{"jsonrpc":"2.0","method":"parity_enode","params":[],"id":0}' -H
"Content-Type: application/json" -X POST localhost:8541

Эта команда должна найти URL для подключения второго узла. Вывод команды
будет выглядеть приблизительно так:
{"jsonrpc":"2.0","result":"enode://7bac3c8cf914903904a408ecd71635966331990c5c9f7c7a
291b531d5912ac3b52e8b174994b93cab1bf14118c2f24a16f75c49e83b93e0864eb099996ec1af9@
[::0.0.1.0]:30301","id":0}

Далее выполните следующую команду, заменив URL-адрес и IP-адрес enode на
127.0.0.1:
curl --data '{"jsonrpc":"2.0","method":"parity_addReservedPeer","params":["enode:
//7ba..."],"id":0}' -H "Content-Type: application/json" -X POST localhost:8540

Вы должны получить следующий ответ:
{"jsonrpc":"2.0","result":true,"id":0}

В консоли узлы должны выводить сообщение 0/1/25 peers (рис. 9.1), которое означает, что узлы соединились друг с другом.

Рис. 9.1. Рабочая консоль узла после подключения

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

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

[ 249 ] ___________________________________

Глава 9

сервера каждого узла и разрешения подключений только с заданных IP-адресов.
Даже если IP-адрес не заблокирован, для подключения к узлу сети новый узел
должен знать адрес enode, как было показано ранее (и это вполне очевидно). По
сути, это элементы базовой защиты, но от них мало пользы. Каждый узел сети
должен самостоятельно обеспечивать безопасность. Разрешения для тех, кто
имеет право создавать блоки, могут быть определены через смарт-контракт.
Наконец, на сегодняшний день вообще не предусмотрена настройка того, какие
типы транзакций имеет право отправлять конкретный узел.
 Защита личной информации — cуществует механизм обеспечения защиты

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

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

Заключение
На протяжении этой главы мы изучали, как использовать Parity, как работает Aura,
а также некоторые механизмы обеспечения конфиденциальности и безопасности.
Теперь вы достаточно подготовлены для реализации, как минимум, протокола
Proof-of-Concept в блокчейне корпоративного уровня. Вы можете работать над собой далее и перейти к изучению других решений, таких как Hyperledger или
Quorum. В настоящее время разработчики Ethereum трудятся над более полной
официальной поддержкой консорциумов. Поэтому вы должны внимательно следить за всеми новостями в области блокчейна и не пропустить новинки на рынке
новых технологий.

____________________________________

[ 250 ] ___________________________________

ПРИЛОЖЕНИЕ

Описание электронного
файлового архива
Электронный архив с материалами к этой книге можно скачать с FTP-сервера издательства «БХВ-Петербург» по ссылке ftp://ftp.bhv.ru/9785977539760.zip или со
страницы книги на сайте www.bhv.ru.
Электронный архив состоит из двух частей:
 в папке OriginalPaktCode находятся файлы упражнений к главам 4–9. Каждое
упражнение разбито на два подкаталога с названиями Initial и Final. В папке
Initial расположены пустые файлы-заготовки, которые читатель может заполнить самостоятельно. На случай, если у читателя недостаточно квалификации
для самостоятельного написания кода или проект работает неправильно, в папке
Final находятся полностью готовые и проверенные автором книги файлы проекта соответствующей главы;
 в корневом каталоге архива помещены файлы листингов примеров из текста
книги.
Перечень файлов (папок) приведен в табл. П.1.
Таблица П.1. Содержание файлового архива книги
Имя файла (папки)

Описание файла (папки)

OriginalPactCode

Папка с примерами упражнений к главам 4–9

listing_3.1.sol

Пример смарт-контракта

listing_3.2.sol

Пример использования массивов

listing_3.3.sol

Пример синтаксиса при использовании строк

listing_3.4.sol

Пример синтаксиса при использовании структур

listing_3.5.sol

Пример синтаксиса при использовании перечислений

listing_3.6.sol

Пример создания и использования сопоставления

listing_3.7.sol

Пример использования оператора delete

Приложение
Таблица П.1 (окончание)
Имя файла (папки)

Описание файла (папки)

listing_3.8.sol

Примеры управляющих структур

listing_3.9.sol

Пример вызова внешней функции

listing_3.10.sol

Пример различной видимости и функций доступа

listing_3.11.sol

Пример использования модификаторов функций

listing_3.12.sol

Пример наследования контрактов

listing_3.13.sol

Пример использования ключевого слова super

listing_3.14.sol

Пример использования конструкции using … for …

listing_3.15.sol

Пример контракта, доказывающего владение файлом

listing_3.16.sol

Код для отправки транзакции с данными файла

listing_4.1.js

Прослушивание событий контракта

listing_4.2.html

HTML-код клиентского интерфейса

listing_4.3.js

Содержимое файла main.js

listing_5.1.html

Содержимое файла index.html

listing_5.2.js

Реализация метода generate_addresses()

listing_5.3.js

Реализация метода send_ether()

listing_6.1.js

Демонстрация методов для работы с версиями компилятора

listing_6.2.js

Код обработки нажатия кнопки Deploy и развертывания контракта

listing_6.3.html

HTML-код клиентской части приложения для файла index.html

listing_6.4.js

Код JavaScript для файла main.js

listing_7.1.sol

Контракт для заключения пари на результат матча

listing_7.2.ejs

Полный код файла визуализации matches.ejs

listing_7.3.html

Исходный код файла index.html

listing_7.4.js

Исходный код файла main.js

listing_8.1.sol

Исходный код контракта для изучения абстракции

listing_8.2.html

Исходный код примера абстракции контракта

listing_8.3.sol

Развертывание контракта и получение его экземпляра

listing_8.4.sol

Исходный код контракта MetaCoin

listing_8.5.js

Исходный код файла теста metacoin.js

listing_8.6.sol

Исходный код файла теста TestMetacoin.sol

listing_8.7.html

Код файла index.html

listing_8.8.js

Код файла index.js

listing_9.1.sol

Базовый пример рапортующего контракта

listing_9.2.json

Исходный код файла spec.json

____________________________________

[ 252 ] ___________________________________

Предметный указатель
A
ABI, Application Binary Interface 149
Aura 241
AWS, Amazon Web Services 166

B
BigChainDB 40
BIP 125
Bitcoin 32
Bootstrap node См. Загрузочный узел

C
Casper 62, 64
Codemirror 150

D
Dash 38
DHT, Distributed Hash Table 35

E
ECC, Elliptic Curve Criptography 45
ECDSA, Elliptic Curve Digital Signature
Algorithm 46, 124
Error-first, формат вызова 99
Ethereum 34, 45
Ethereum Wallet 59
EVM См. Виртуальная машина Ethereum

F
Fabric 35
Filecoin 36
Fork 51
◊ hard fork 51
◊ regular fork 51
◊ soft fork 51

G
Gas См. Газ
Genesis 52
Geth 55
GHOST,
Greedy Heaviest Observed SubTree 50

H
HD-Wallet 124
Hyperledger 35

I
IPFS 35

J
JSON-RPC 97

Предметный указатель

K
Kademlia 54
KDF, Key Derivation Function 125

L
LightWallet 126

M
MAC, Message Authetication Code 165
Mist 61
Mocha 219

N
Namecoin 37
Nephew block 51
Nonce 47

O
OpenBazaar 40
Oraclize 162

P
Parity 239
Parsing helper 170
Proof-of-Authority 240

Q
Quorum 239

R
Ripple 40
◊ валидатор 42
◊ доверительный список 41
◊ транзакция 41
◊ цепочка доверия 41
◊ шлюз 41
Rust 241
◊ установка 243

S
Serenity 62
Sharding См. Разделение данных
SOLC 145
SOLCJS 145
Solidity 45, 65
◊ исключения 77
◊ массивы 69
◊ оператор delete 73
◊ перечисление 72
◊ преобразование типов 74
◊ расположение данных 67
◊ создание контракта 76
◊ сопоставление 72
◊ строки 70
◊ структура контракта 66
◊ структуры 71
◊ типы данных 68
◊ файлы исходного кода 65
Swarm 55

T
Tendermint 241
testRPC 197
TLSNotary 164
Truffle 194
◊ truffle-contract, API 203
◊ truffle-contract, пакет 201
◊ внешние команды 230
◊ инициализация 212
◊ компиляция контракта 214
◊ консоль 228
◊ конструктор по умолчанию 231
◊ сервер 236
◊ установка 212
◊ файлы конфигурации 214

U
Uncle block 50

W
Whisper 55
WolframAlpha 163

Z
Zooko triangle См. Треугольник Зуко
____________________________________

[ 254 ] ___________________________________

Предметный указатель

А
Абстракция контракта 203
Альткоин 194
Арбитр 40
Атака 51% 62
◊ Сибиллы 62

Б
Библиотеки 86
Биткойн См. Bitcoin
Блокчейн 33

В
Валидатор 240
Ветвление 51
Виртуальная машина Ethereum 53
Время блока 49

Г
Газ 53
Генезис См. Genesis

Д
Деноминация 52
Децентрализованная автономная
организация 28
Доказательство полномочий
См. Proof-of-Authority

З
Заголовок события 199
Загрузочный узел 54

И
Индексированные параметры событий 106

К
Канал состояния 63
Конвертация денежных единиц 101
Консенсус 46
Консорциум 239
____________________________________

Контракт
◊ абстрактные контракты 86
◊ резервная функция 83
◊ свойства контракта 79

М
Майнер 46
Майнинг 46, 58
Метка времени 47
Модификатор функции 81

Н
Накопитель транзакций 144

П
Платежный канал 63
Поузловой тест См. Юнит-тест
Прикладной провайдер 122
Приложение
◊ внутренняя валюта 31
◊ децентрализованное 25
◊ распределенное 26
◊ централизованное 25
Пространство имен 126
Протокол
◊ доказательства работы 26
◊ доказательства владения долей 33
◊ доказательства залога 40
◊ доказательства уничтожения 40
◊ консенсуса 26
◊ подтверждения услуги 40
Путь вывода 127

Р
Разделение данных 64
Рапортующий контракт 244
Реестр 33

С
Сертификат, цифровой 28
Сигнатура события 199
Синхронизация 59
Сложность 47
Средства разбора строк 170
Срез 172

[ 255 ] ___________________________________

Предметный указатель

Т
Транзакция 45
◊ в обработке 103
◊ отложенная 103
Треугольник Зуко 38

Ц
Целевое число 47

Ч
Частная сеть 244

У
Удостоверение личности 28
Умный контракт 34
Управление пакетами 225
Устаревший блок 49
Учетная запись 30

Ф
Файлы переноса 216

____________________________________

Э
Экземпляр контракта 209
◊ API экземпляра 211
Эфир 45
Эфириум См. Ethereum

Ю
Юнит-тест 218

[ 256 ] ___________________________________