Работаем с биткоином на Эликсире

Автор: admin от 27-10-2017, 12:15, посмотрело: 597

Недавно меня с головой захватил волшебный мир биткоин. Жажде знаний не было предела, и утолить её помогла замечательная книга «Mastering Bitcoin» Андреаса Антонопулоса и полное погружение в биткоин-разработку. Книга подробно освещает технические основы биткоин, но ничто так не помогает в изучении нового дела, как практика.



Простенькое приложение на Эликсире для управления полной биткоин-нодой и связи с ней через интерфейс JSON-RPC, по-моему, – отличный «Hello, World!». Поехали!

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



Установим демона bitcoind и настроим его в файле bitcoin.config:



rpcuser=<username>
rpcpassword=<password>


Определяемые значения и будем использовать для аутентификации при отправке запросов к биткоин-ноде.



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



bitcoind -conf=<path to bitcoin.config> -daemon


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



Убедимся, что всё работает как нужно:



bitcoin-cli getinfo


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



Интерфейс JSON-RPC



Биткоин-нода работает через интерфейс JSON-RPC, который можно использовать для извлечения информации о блокчейне и взаимодействия с нодой.



Любопытно, что инструмент bitcoin-cli, который мы использовали ранее для получения информации о ноде работает поверх JSON-RPC API. Список всех возможных RPC-команд ноды можно увидеть, вызвав bitcoin-cli help или полистав Bitcoin Wiki.



Протокол JSON-RPC получает входящие команды через HTTP-сервер, а значит, можно обойтись без bitcoin-cli и прописать эти RPC-команды самостоятельно.



Например, запустим getinfo вручную с помощьюcurl`:



curl --data-binary '{"jsonrpc":"1.0","method":"getinfo","params":[]}'  
     http://<user>:<pass>@localhost:8332/


Аналогичным образом можно выполнять такие команды в любой среде программирования с HTTP-клиентом, например, в Эликсире!



Разработка Эликсир-приложения



Продумав стратегию взаимодействия с полной биткоин-нодой, займёмся Эликсир-приложением.



Создадим новый проект и обновим mix.exs, чтобы добавить в зависимости библиотеку poison, которая понадобится для шифрования и расшифровки JSON-объектов, и httpoison – один из лучших HTTP-клиентов для Эликсира.



defp deps do
  [
    {:httpoison, "~> 0.13"},
    {:poison, "~> 3.1"}
  ]
end


Теперь, когда мы закончили с частью, отвечающей за кодогенерацию, перейдём к реализации взаимодействия с биткоин-нодой.



Начнём работать с модулем HelloBitcoin и первым делом поставим заглушку для функции getinfo:



defmodule HelloBitcoin do

  def getinfo do
    raise "TODO: Implement getinfo"
  end

end


Для простоты будем взаимодействовать с этим модулем через iex -S mix. Прежде чем приступить к следующему шагу, давайте убедимся, что всё работает правильно.



Вызов заглушки HelloBitcoin.getinfo должен повлечь за собой исключение времени выполнения:



iex(1)> HelloBitcoin.getinfo
HelloBitcoin.getinfo
** (RuntimeError) TODO: Implement getinfo
    (hello_bitcoin) lib/hello_bitcoin.ex:4: HelloBitcoin.getinfo/0


Отлично. Ошибка. Как и должно быть.



Построение команды GetInfo



Теперь наполним функцию getinfo содержимым.



Повторюсь: нам необходимо послать HTTP-запрос методом POST к HTTP-серверу биткоин-ноды (обычно слушающему по http://localhost:8332) и передать JSON-объект, содержащий команду GetInfo и необходимые параметры.



Оказалось, что httpoison справляется с таким заданием в два счёта:



def getinfo do
  with url     <- Application.get_env(:hello_bitcoin, :bitcoin_url),
       command <- %{jsonrpc: "1.0", method: "getinfo", params: []},
       body    <- Poison.encode!(command),
       headers <- [{"Content-Type", "application/json"}] do
    HTTPoison.post!(url, body, headers)
  end
end


Сначала получим url из ключа bitcoin_url в конфигурации приложения. Адрес должен находиться в файле config/config.exs и указывать на локальную ноду:



config :hello_bitcoin, bitcoin_url: "http://<user>:<password>@localhost:8332"


Далее, создадим словарь, представляющий нашу JSON-RPC-команду. В данном случае в поле method прописываем "getinfo", а поле params оставляем пустым. И последнее, сформируем тело запроса, преобразовав команду в формат JSON с помощью Poison.encode!.



Вызов HelloBitcoin.getinfo должен возвратить успешный ответ от биткоин-ноды с кодом состояния 200, а также результат команды getinfo в формате JSON:



%HTTPoison.Response{
  body: "{"result":{"version":140200,"protocolversion":70015,"walletversion":130000,"balance":0.00000000,"blocks":482864,"timeoffset":-1,"connections":8,"proxy":"","difficulty":888171856257.3206,"testnet":false,"keypoololdest":1503512537,"keypoolsize":100,"paytxfee":0.00000000,"relayfee":0.00001000,"errors":""},"error":null,"id":null}n",
  headers: [{"Content-Type", "application/json"}, {"Date", "Thu, 31 Aug 2017 21:27:02 GMT"}, {"Content-Length", "328"}],
  request_url: "http://localhost:8332",
  status_code: 200
}


Прекрасно.



Расшифруем полученный JSON-текст в body и получим результат:



HTTPoison.post!(url, body)
|> Map.get(:body)
|> Poison.decode!


Теперь результаты вызова HelloBitcoin.getinfo, полученные от bitcoind, будут представлены в более удобном виде:



%{"error" => nil, "id" => nil,
  "result" => %{"balance" => 0.0, "blocks" => 483001, "connections" => 8,
    "difficulty" => 888171856257.3206, "errors" => "",
    "keypoololdest" => 1503512537, "keypoolsize" => 100, "paytxfee" => 0.0,
    "protocolversion" => 70015, "proxy" => "", "relayfee" => 1.0e-5,
    "testnet" => false, "timeoffset" => -1, "version" => 140200,
    "walletversion" => 130000}}


Обратите внимание, что необходимые нам данные ("result"), обернуты в словарь, содержащий метаданные о самом запросе. Эти метаданные содержат строку с возможной ошибкой и идентификатор запроса.



Перепишем функцию getinfo так, чтобы она включала обработку ошибок и возвращала фактические данные в случае безошибочного выполнения запроса:



with url <- Application.get_env(:hello_bitcoin, :bitcoin_url),
     command <- %{jsonrpc: "1.0", method: "getinfo", params: []},
     {:ok, body} <- Poison.encode(command),
     {:ok, response} <- HTTPoison.post(url, body),
     {:ok, metadata} <- Poison.decode(response.body),
     %{"error" => nil, "result" => result} <- metadata do
  result
else
  %{"error" => reason}  {:error, reason}
  error  error
end


Теперь при отсутствии ошибок функция getinfo будет возвращать кортеж {:ok, result}, содержащий результат RPC-вызова, а в обратном случае мы получим кортеж {:error, reason} с описанием ошибки.



Обобщение команд



В похожей манере можно реализовать и другие RPC-команды блокчейна, например, getblockhash:



def getblockhash(index) do
  with url <- Application.get_env(:hello_bitcoin, :bitcoin_url),
       command <- %{jsonrpc: "1.0", method: "getblockhash", params: [index]},
       {:ok, body} <- Poison.encode(command),
       {:ok, response} <- HTTPoison.post(url, body),
       {:ok, metadata} <- Poison.decode(response.body),
       %{"error" => nil, "result" => result} <- metadata do
    {:ok, result}
  else
    %{"error" => reason}  {:error, reason}
    error  error
  end
end


Вызвав getblockhash с нулевым индексом, получим первый блок цепочки.



HelloBitcoin.getblockhash(0)

{:ok, "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"}


Функция getblockhash работает верно, и она практически идентична функции getinfo.



Чтобы избежать дублирования кода, выделим общую функциональную часть в новую вспомогательную функцию bitcoin_rpc:



defp bitcoin_rpc(method, params  []) do
  with url <- Application.get_env(:hello_bitcoin, :bitcoin_url),
       command <- %{jsonrpc: "1.0", method: method, params: params},
       {:ok, body} <- Poison.encode(command),
       {:ok, response} <- HTTPoison.post(url, body),
       {:ok, metadata} <- Poison.decode(response.body),
       %{"error" => nil, "result" => result} <- metadata do
    {:ok, result}
  else
    %{"error" => reason}  {:error, reason}
    error  error
  end
end


Теперь переопределим функции getinfo и getblockhash в соответствии с функцией bitcoin_rpc:



def getinfo, do: bitcoin_rpc("getinfo")

def getblockhash(index), do: bitcoin_rpc("getblockhash", [index])


Можно видеть, что bitcoin_rpc представляет собой полноценный RPC-интерфейс для биткоин, позволяющий с лёгкостью выполнять любые RPC-команды.



Если вам интересно попробовать осуществить всё вышеперечисленное на своей машине, то исходники проекта можно найти на GitHub.



Заключение



Ну вот и подошла к концу достаточно длинная статья, объясняющая относительно простую идею. Полная нода биткоин предоставляет интерфейс JSON-RPC, доступ к которому можно получить, используя любой язык (например, Эликсир) или стек. Биткоин-разработка – удивительно занимательная вещь, в которую интересно углубиться ещё сильнее.



Следующая часть серии статей о работе с биткоином на Эликсире по ссылке.



Источник: Хабрахабр

Категория: Информационная безопасность » Криптография

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

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *