В предыдущей статье я рассказал как установить окружение для ознакомления с библиотекой web3.js. Тогда мы использовали ethereum-блокчейн Ganache и библиотеку Truffle. В данной статье я покажу как формировать ethereum-транзакцию используя только библиотеку web3.js и Ganache, без использования библиотеки Truffle. Мы подключимся к локальному блокчейну Ganache, я покажу как создать новый аккаунт в дополнение к стандартным 10-ти аккаунтам. Далее мы сформируем транзакцию вручную, выполним её подписание и отправим в сеть. Посмотрим на тело сериализованной и подписанной транзакции в том виде, в котором она передаётся в ethereum-блокчейн (raw transaction). Так как команд будет много, то все эти шаги мы выполним в форме js-скрипта. Напомню, что Truffle это фреймворк, который внутри себя содержит библиотеку web3.js, но помимо web3.js Truffle предлагает множество других инструментов для тестирования, деплоя и отладки смарт-контрактов. Truffle пригодится нам в следующих статьях, когда речь пойдёт о написании смарт-контрактов, и отправки сообщений в их адрес, а пока спустимся на этаж ниже и посмотрим более детально на процесс формирования транзакции. Что нам понадобится? Node.js Web3.js Ganache Итак, приступим! Для начала запустим наш локальный блокчейн Ganache: Далее, создадим рабочую директорию и установим в ней библиотеку web3: Запустим консоль node.js командой: В консоли настроим подключение к Ganache: Если вы увидели вывод, представленный ниже, значит вы успешно подключились: Проверим соединение ещё раз, запросив Ganache показать нам предустановленные аккаунты: Вывод: Поскольку getAccounts() возвращает объект Promise, то необходимо дописать .then(console.log). Тот же самый вывод можно получить используя await перед вызовом: Создание нового аккаунта в Ganache Теперь я хочу продемонстрировать, как к существующим 10-ти аккаунтам мы можем создавать новые аккаунты, и эти аккаунты ничем не будут отличаться от предустановленных аккаунтов, ну почти ничем, ниже я расскажу про их небольшое различие. Создадим новый аккаунт: Вывод: В консоли мы видим данные по новому аккаунту, в частности адрес и связанный с ним приватный ключ. А вот теперь про небольшое отличие между аккаунтами созданными вручную, и теми, которые были сгенерированы при старте Ganache. Если вы откроете Ganache, то вы не увидите вновь созданный аккаунт. Почему так происходит? На самом деле Ganache при запуске создаёт аккаунты в отдельном объекте, назовём его массив. При отображении аккаунтов в GUI он использует этот массив, точно так же как и метод web3.eth.getAccounts(). Созданный нами аккаунт хоть и отсутствует в этом массиве, но существует в том же самом блокчейне. Поэтому с технической точки зрения они идентичны. Чтобы в этом убедиться, давайте отправим несколько Ether c предустановленного аккаунта на наш новый аккаунт. Перед отправкой Ether, проверим баланс нашего нового аккаунта: Видим, что вновь созданный аккаунт не имеет средств на балансе. Теперь отправим на этот аккаунт 70 Ether с аккаунта, который Ganache создал при старте, а заодно убедимся, что все аккаунты существуют в рамках одного блокчейна: Проверим новый аккаунт: Вывод показан в Wei. Если мы хотим получить вывод в Ether, то можем применить следующий вызов: Здесь мы передали методу getBalance() вторым аргументом функцию, которая сконвертировала полученный результат в Ether и сохранила его в переменной balance. Отлично, теперь откроем Ganache GUI и убедимся, что с аккаунта-отправителя списались средства. Теперь у нас достаточно средств на новом аккаунте, и мы убедились что не имеет значения каким образом был создан аккаунт, вручную или же при старте Ganache. Формируем транзакцию по шагам Теперь я покажу как отдельно сформировать транзакцию, отдельно её подписать и отдельно отправить подписанную транзакцию в сеть Ethereum.  Для отправки подписанной транзакции мы будем использовать метод sendSignedTransaction(). Это метод берёт подписанную сериализованную транзакцию, и отправляет в сеть Ethereum как есть. Но перед этим я хотел бы рассказать, что же происходило под капотом предыдущего метода отправки. Ранее для отправки транзакции мы использовали метод sendTransaction() библиотеки web3. Данный метод под капотом совершает множество операций за нас. Вот лишь некоторые из них: Находит приватный ключ отправителя Генерирует поле nonce Определяет значения полей gasPrice и gas Создаёт объект транзакции Подписывает эту транзакцию Сериализует транзакцию используя RLP кодирование Отправляет подписанную и сериализованную транзакцию в сеть Ethereum Может возникнуть вопрос - для чего нам нужно отдельно вручную выполнять эти шаги, если уже есть готовый метод, который за нас всё сделает? Дело в том, что иногда при написании Dapp приложений, нам действительно нужен ручной контроль над этапами формирования транзакции. Вот лишь один из примеров. Представьте, что вы имеете аккаунт с очень большим количеством криптовалюты на нём, и вам бы не очень хотелось каким-то образом скомпрометировать приватный ключ от этого аккаунта. Самый верный способ обеспечить безопасность приватного ключа - это хранить его на offline устройстве, там же осуществлять формирование и подписание транзакции. В роли такого устройства может выступать полностью изолированный от сети компьютер. После формирования, подписания, и сериализации транзакции на offline устройстве, необходимо перенести её на online компьютер. Компьютер, подключённый к сети будет выступать в роли гейта к блокчейн-сети, и отправит транзакцию в сеть как есть. В таком случае никто не будет иметь онлайн-доступа к нашему приватному ключу. Перенести транзакцию можно как при помощи флеш-накопителя, так и при помощи QR-кода, который считывается устройством-отправителем. Кстати, такой вид оффлайн-устройства называется ещё холодный кошелёк, или cold-wallet, а кошелёк, который хранит приватные ключи на онлайн-устройстве называют горячий кошелёк, или hot-wallet. В качестве cold-wallet на рынке присутствуют готовые решения в виде usb-устройств. Примером hot-wallet может служить расширение к браузеру MetaMask. Как правило, на hot-wallet рекомендуют хранить приватные ключи от тех аккаунтов, на которых хранятся незначительные средства. Hot-wallet крайне не рекомендуется использовать для хранения приватных ключей от аккаунтов с крупными суммами на балансе. Можно провести аналогию с банковской картой, на которой хранят небольшую сумму денег для повседневных платежей, а основная сумма лежит на счетах не привязанных к банковской карте. В приведённом списке параметров существует ещё загадочное поле nonce, чтобы вас не утруждать техническими подробностями, и быстрее перейти к практике, скажу лишь что это целочисленный счётчик транзакций, который есть в каждом аккаунте и прописывается в каждую транзакцию. В конце статьи я к нему ещё вернусь, и опишу его назначение, а пока примем этот параметр как данность. Про назначение полей gas и gasPrice я писал в предыдущей статье. Напомню лишь, что это плата за пользование вычислительными ресурсами сети Ethereum. Итак, практика В качестве участников транзакции я советую выбрать первый и второй аккаунты из Ganache, чтобы было удобнее смотреть изменения на балансах после многократного выполнения скрипта. Но для интереса вы можете потом поэкспериментировать и с тем аккаунтом, который мы создали ранее. Для того чтобы взять приватный ключ, кликаем на значок ключа в строке аккаунта-отправителя. Поскольку команд будет много, то удобнее создать js-скрипт. Создадим внутри нашей папки web3-transfer файл transfer.js и поместим туда скрипт (имя файла не имеет значения, вы можете назвать как вам больше нравится).  Наш скрипт transfer.js : В скрипте я добавил логи, чтобы можно было отслеживать изменение поля nonce и видеть транзакцию в чистом виде. Выйдем из консоли node (Ctrl + D) и запустим транзакцию в терминале: Вывод: Видим, что nonce у адреса-отправителя равен 1, так как до этого мы уже производили с него транзакцию в пользу вновь созданного аккаунта. Так же видим транзакцию в том виде, в котором она понятна протоколу Ethereum: Зайдём в Ganache и посмотрим на изменения: Отлично, балансы изменились, транзакция прошла успешно. Можете несколько раз позапускать этот скрипт и увидеть, как изменяется nonce и баланс.  В выводе скрипта мы видим так же хэш нашей транзакции: По этому хэшу находят транзакцию в блокчейне. Если мы откроем вкладку Transactions в GUI Ganache, то увидим все наши транзакции (я запустил скрипт несколько раз): Аналогично во вкладке Blocks мы можем обнаружить блоки в котором находятся наши транзакции. По одной транзакции на блок. В реальном мире в одном блоке содержатся сотни транзакций, а сами блоки формируются нодами-майнерами, за что получают финансовое вознаграждение, так как выступают в роли формирователей сети blockchain. Ну и в заключение, несколько слов про поле Nonce Nonce, он же "number only used once", предназначен для защиты от двух видов атак: replay-attack и double-spending attack. Про механизм этих атак можно почитать здесь, а если вкратце, то во-первых, без поля nonce в сети Ethereum мы могли бы многократно повторить транзакцию, которая уже была осуществлена, причём выполнить её без ведома самого отправителя (replay-attack), а во-вторых сеть Ethereum не обладала бы информацией об очередности обработки транзакций с одного и того же аккаунта (double-spending attack). Как же работает nonce? Nonce это дополнительное целочисленное поле внутри каждого аккаунта, которое проставляется в отправляемую транзакцию. Иными словами, это счётчик отправленных транзакций с конкретного аккаунта. С каждой новой транзакцией с одного и того же аккаунта, данный параметр увеличивается на один. Допустим мы создали новый аккаунт, поле nonce у него будет равняться нулю, отправили транзакцию, поле nonce у этой транзакции тоже будет равняться нулю. После отправки транзакции, аккаунт увеличит это поле на 1, таким образом в следующей транзакции это поле будет равняться 1, и так далее. И если мы захотим отправить точно такую же транзакцию на такой же адрес и с тем же количеством Ether, то nonce изменит хэш этой транзакции, а следовательно и её подпись. Забегу немного вперёд - у аккаунта на котором задеплоен смарт контракт, поле nonce инициализируется не нулём, а единицей (EIP-161). Таким образом, с полем nonce хэш SHA-3 (а точнее Keccak-256) любой транзакции всегда будет разный, следовательно подпись никогда не будет повторяться, каждая транзакция будет уникальной, а сеть Ethereum может учитывать порядок обработки транзакций. На этом всё. Мы научились новым методам библиотеки web3.js, в частности: создание новых аккаунтов, подписание транзакции и отправки её в сеть. Посмотрели из чего состоит транзакция и увидели её сырое представление. Надеюсь информация была полезной :)