Я люблю JavaScript, и надеюсь, вы тоже. Одна из величайших вещей в нем — это сообщество, библиотеки и вселенная фреймворков. Как разработчик, я могу найти библиотеку JS буквально для всего. Блокчейн и Ethereum, в частности, не являются исключением. Web3, EthersJS, контракт Truffle — фантастические примеры. Их легко настроить и использовать, они предоставляют мне все необходимое для разработки децентрализованных приложений.

Вы, вероятно, делали или видели что-то подобное много раз:

Сладкий! Запрос чтения к смарт-контакту Ethereum Solidity с помощью всего нескольких строк кода. И все данные приходят в стандартном человекочитаемом формате.

Или это:

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

Куда я веду с этим? Как инженеры-программисты, мы должны не только знать, как использовать инструменты (например, библиотеки выше), но и понимать, как они работают. И иметь возможность отвечать на такие вопросы, как:
Что происходит внутри этих фреймворков и библиотек?
Как можно делать запросы к смарт-контракту, используя только собственный JavaScript, без каких-либо библиотек?
Как правильно построить эти запросы?
Как закодировать параметры метода и расшифровать результаты ответа?
Как подписаться и слушать события Ethereum WebSocket?
Есть ли проблемы позади?

Это те вопросы, которые я задал себе после довольно долгого времени работы с Web3JS и контрактом Truffle. Решил разобраться в этом вопросе повнимательнее и поделиться с вами результатами. Написав это, я предполагаю, что у вас есть базовое понимание мира Ethereum/Solidity/DApps и, конечно же, немного JavaScript.

Чтобы она была короткой, легкой для чтения и восприятия, я решил разделить статью на три части:
Часть 1. Запросы на чтение Ethereum RPC с чистым ванильным JavaScript.
Часть 2. События Ethereum WebSocket с чистым ванильным JavaScript.
Часть 3. Кодирование/декодирование параметров и результатов Ethereum RPC.

Часть 1. Запросы на чтение Ethereum RPC с чистым ванильным JavaScript

На мой взгляд, объяснение на основе примеров является наиболее понятным, поэтому я создал децентрализованное приложение Ethereum, чтобы сделать приведенные ниже примеры кода осмысленными и интересными.
Идея - это система обзора/отзывов для супергероев.< br /> Таким образом, пользователь может:
- Получить всех супергероев.
- Получить одного супергероя по идентификатору.
- Получить обзоры супергероев по идентификатору героя.
- Дать обзор на супергероя по ID.
- Номинировать/добавить супергероя в список.
Рабочий пример доступен здесь.
А страница на GitHub — здесь.

Сетевой уровень Ethereum позволяет нам взаимодействовать со смарт-контрактами Solidity из внешних приложений JavaScript с использованием JSON-RPC.

В двух словах, JSON-RPC — это стандарт, описывающий, как две удаленные программы могут общаться друг с другом, не разбираясь в деталях базовой сети. Для приложений JavaScript это означает отправку специально отформатированного запроса POST через HTTP.

Вот суть Ethereum JSON-RPC.

Вызов JSON-RPC должен содержать четыре следующих свойства:

1. метод — сообщает EVM, какой тип операции мы хотим выполнить.
Например:
eth_call — для выполнения запросов на чтение.
eth_send — для отправки транзакции, выполнения операции записи в блокчейне .
eth_subscribe — подписаться на событие WebSocket.

2. id — случайное число, заданное клиентом, практически любое целое число.

3. params — все, что мне нужно передать в качестве аргументов. Они должны быть закодированы в шестнадцатеричном формате, совместимом с Ethereum.

4. jsonrpc– «2.0». Бонус один. Он всегда остается прежним.

Откуда мы берем данные для запроса? Ответ - из файла контракта ABI. Поскольку он довольно большой, я добавлю сюда только соответствующие фрагменты. Полная версия находится здесь.

Мы сосредоточимся на 2 свойствах, отмеченных оранжевым цветом ниже.

  1. abi — массив, содержащий описания методов контракта.
  2. сети, а точнее - адрес контракта в сети Ethereum, в данном примере тестовой сети Ropsten.

Скажем, я хочу получить всех героев. Для этого я войду в свойство массива abi и разверну описание метода getSuperHeroes. Я вижу, что входных данных нет, потому что метод не требует никаких параметров для возврата полного списка супергероев; это делает его простым. Итак, чтобы сделать запрос, мне нужно только:

1. Свойство signature, представляющее собой уникальный идентификатор метода в шестнадцатеричном коде.
2. адрессвойство из массива сетей выше.

Теперь я просто передаю подпись в свойство data и адрес контракта в свойство to.

Поскольку это простой запрос на чтение, вы можете попробовать это прямо в консоли инструментов разработчика новой вкладки браузера (Ethereum не самый быстрый блокчейн, и запрос может занять некоторое время, если вы передали это в консоль, подождите несколько секунд для обещания решить). Круто, это работает! Но возвращаемые данные выглядят неудобными и нечитаемыми. Что-то вроде этого:

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

Метод Web3JS decodeParameters требует двух параметров:
— массив типов, в основном свойство outputs из ABI метода. Чтобы получить его, я использую метод JS Array find для свойства массива abi с getSuperHeroes в качестве имени метода. Я хочу позвонить.
- шестнадцатеричная строка результата, которую я получил из запроса.

И все, console.log выше выводит список супергероев в удобочитаемом формате.
Это был запрос без каких-либо параметров. Если не упоминать часть декодирования, я надеюсь, что это довольно просто и понятно.

Итак, у нас есть список супергероев, который можно было бы отобразить пользователям ресурса. Его пользователи могут захотеть нажать на супергероя, чтобы узнать больше о нем/ней. Для этого мы должны сделать запрос с конкретным идентификатором героя в качестве параметра. В этом примере имя метода — getHero.

Запрос с параметрами будет более сложным, так как все параметры должны быть в шестнадцатеричном формате Ethereum и должным образом объединены в одну строку. Чтобы найти необходимые параметры запроса, давайте снова взглянем на ABI.

Из приведенного выше ABI метода нам нужны:
 – значение свойства signature, как и в предыдущем примере.
 – вводит свойство. Обратите внимание, что на этот раз он не пустой, как раньше, и вы можете видеть, что запрос должен содержать идентификатор героя, который представляет собой 256-битное целое число без знака.

Необработанный запрос героя с ID=1 выглядит так:

Все остается таким же, как и в предыдущем примере, за исключением свойства data.
Теперь это довольно длинная строка 0x21d80111000000000000000000000000000000000000000000000000000000000000000000001. На первый взгляд кажется ерундой, но при ближайшем рассмотрении обнаруживается простая закономерность. Это конкатенация сигнатуры метода 0x21d80111 и числа 1, закодированного в Ethereum шестнадцатеричное беззнаковое 256-битное целое 000000000000000000000000000000000000000000000000000000000000000001

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

Фу! Это работает! И достаточно для этой части. Теперь мы должны понять, как:
- сделать вызов RPC к смарт-контракту Solidity без параметров.
- декодировать результаты вызова из шестнадцатеричного в человекочитаемый формат.
- сделать вызов RPC вызов смарт-контракта Solidity, требующего параметров.
— кодировать параметры в шестнадцатеричный формат Ethereum.

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

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

P.S. Так же, как вы, возможно, узнали здесь что-то новое для себя, я учусь, исследуя тему и пишу эту статью. Буду рад любым отзывам (положительным и конструктивным), предложениям и обсуждению ниже. Если у вас есть идеи, как улучшить пример приложения — просто откройте тему, или сделайте PR, любая помощь приветствуется.