Статьи

Как отправить транзакции Ethereum с помощью Java

После того, как я выразил свое беспокойство по поводу технологии блокчейна , давайте немного поработаем с блокчейном. В частности, с Ethereum.

Мне нужно было отправить транзакцию с Java, поэтому я посмотрел на EthereumJ . У вас есть три варианта:

  • Полный узел — вы включаете синхронизацию, что означает, что весь блокчейн загружается. Это занимает много времени, поэтому я отказался от такого подхода
  • «Легкий» узел — вы отключаете синхронизацию, поэтому вы просто становитесь частью сети, но не выбираете какие-либо части цепочки. Не совсем уверен, но я думаю, что это соответствует «легкому» способу получения гетов (ethereum CLI). Вы можете отправлять сообщения (например, сообщения о транзакциях) другим партнерам для обработки и хранения в блокчейне, но у вас нет блокчейна.
  • Автономный режим (без узла) — просто создайте и подпишите транзакцию, вычислите ее необработанное представление (в формате ethereum RLP) и отправьте его в блокчейн через централизованный API, например API etherscan.io . Etherscan сам по себе является узлом в сети и может выполнять все операции (поэтому он служит прокси-сервером)

Прежде чем идти дальше, возможно, стоит указать несколько общих свойств блокчейна (по крайней мере, ethereum one и популярных криптовалют) — это распределенная база данных, основанная на одноранговой (оверлейной) сети, сформированной кем бы то ни было клиентское программное обеспечение работает (кошелек или иным образом). Транзакции имеют вид «Я (владелец закрытого ключа) хочу отправить эту сумму на этот адрес». Транзакции могут иметь дополнительные данные, хранящиеся внутри них, например, представляющие, о чем они. Затем транзакции проверяются одноранговыми узлами (в настоящее время используется консенсус, основанный на проверке работы) и сохраняются в цепочке блоков, что означает, что каждый подключенный узел получает вновь созданные блоки (каждый блок состоит из нескольких транзакций). Короче говоря, это блокчейн, и Ethereum не исключение.

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

В моем конкретном случае меня больше интересовало хранение определенного фрагмента данных как части транзакции, а не самой транзакции, поэтому у меня было два узла, которые отправляли друг другу очень маленькие транзакции (случайным образом выбирая отправителя и получателя). Я знаю, что мог бы сделать это с помощью умного контракта, но «по одному шагу за раз». Исходный код можно найти здесь , и он в значительной степени основан на образцах EthereumJ. Так как EthereumJ использует Spring внутри себя, а мое приложение использует spring, потребовалось дополнительное усилие, чтобы учесть два узла, но это не так важно для поставленной задачи. Самый важный фрагмент кода можно увидеть ниже в этом посте, только слегка измененный.

У вас должен быть файл user.conf в classpath с некоторыми значениями по умолчанию, и он может быть основан на конфигурации ethereumj по умолчанию . Более важной частью являются внешние файлы user1 и user2 conf (которые в общем сценарии могут быть просто одним файлом conf). Вот пример , со следующими важными параметрами:

  • peer.networkId — используете ли вы реальную производственную сеть (= 1) или тестовую сеть (= 3). Очевидно, что для чего-либо, кроме производства, вам нужна тестовая сеть. В тестовых сетях вы можете получить бесплатный эфир, используя кран . Для использования тестовой сети ниже есть еще два параметра — blockchain.config.name = ropsten и genesis = ropsten.json . Обратите внимание, что в настоящее время существует больше тестовых сетей для экспериментов с альтернативами проверке работы.
  • peer.privateKey — это самый важный бит. Это ваш секретный ключ, который дает вам контроль над «учетной записью» блокчейна. Только используя этот закрытый ключ, вы можете подписывать транзакции (используя алгоритм эллиптической кривой). Закрытый ключ имеет соответствующий открытый ключ, который в основном является вашим адресом в сети — если кто-то хочет отправить средства, он отправляет их на ваш открытый ключ. Но только тогда вы можете отправлять средства со своего счета, поскольку никто другой не владеет закрытым ключом. Что означает, что вы должны защитить его. В этом случае он находится в открытом тексте в файле, что может быть не идеально, если вы работаете с большим количеством эфира. Рассмотрите возможность использования некоторого решения для управления ключами (как описано здесь )
  • peer.ip.list — это необязательно, но предпочтительно — вам нужно иметь список пиров, к которым нужно подключиться, чтобы загрузить клиента и сделать его частью сети. Узлы там подключены к другим узлам, и так далее, и так далее, так что, в конце концов, это одна взаимосвязанная сеть. Обратите внимание, что в сочетании с номером порта, который требует дополнительной конфигурации сети, если вы используете ее на сервере / кластере / стеке — вам придется открывать некоторые порты и разрешать исходящие и входящие соединения.
  • database.dir — это каталог, в котором будет храниться блокчейн и список обнаруженных пиров. Он использует leveldb, и я обнаружил, что ethereumj использует устаревший leveldb, который не работал на моей машине. Поэтому я исключил их и вручную использовал более новые версии
  • sync.enabled — хотите ли вы получить блокчейн или нет. Обычно это не нужно, так как это занимает много времени, но таким образом вы не являетесь полным узлом и не участвуете в работе сети.

Как я отмечал ранее, мне не нужен был полный узел, мне просто нужно было отправить транзакцию. Легкий узел подойдет (разница должна заключаться в простом переключении sync.enabled с true на false), но после первоначального успешного подключения к пирам я начал получать странные исключения, в которые у меня не было времени войти, поэтому я не мог присоединиться сеть больше (возможно из-за дерьмового Wi-Fi, который я в настоящее время использую).

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
byte[] nonce = ByteUtil.intToBytes(getTransactionCount(senderAddress) + 1);
byte[] gasPrice = getGasPrice();
Transaction tx = new Transaction(
    nonce,
    gasPrice,
    ByteUtil.longToBytesNoLeadZeroes(200000),
    receiverAddress,
    ByteUtil.bigIntegerToBytes(BigInteger.valueOf(1)),  // 1 gwei
    data.getBytes(StandardCharsets.UTF_8),
    CHAIN_ID);
             
tx.sign(ECKey.fromPrivate(senderPrivateKey));
             
byte[] rawTx = tx.getEncoded();
             
restTemplate.getForObject(etherscanUrl, String.class, "0x" + BaseEncoding.base16().encode(rawTx));

В этом примере я использую API-интерфейс Etherscan.io (также есть тестовый для сети Ropsten) . Он также имеет ручную форму ввода для проверки ваших транзакций (ссылка для тестовой сети Ropsten).

Какие параметры выше?

  • nonce — это порядковый номер для транзакций на пользователя (= на закрытый ключ). Каждая последующая транзакция должна иметь одноразовый номер, который является одноразовым номером предыдущего +1. Таким образом, никто не может воспроизвести ту же транзакцию и истощить средства отправителя (транзакция, которая подписана, содержит одноразовый номер, поэтому вы не можете использовать ту же необработанную транзакцию представление и просто повторно представить его). Как получить одноразовый номер? Если вы подключены к сети Ethereum, есть ethereum.getRepository().getNonce(fromAddress); , Однако в отключенном сценарии вам необходимо получить текущее количество транзакций для отправителя, а затем увеличить его. Это делается через eth_getTransactionCount точку eth_getTransactionCount . Обратите внимание, что он возвращается как шестнадцатеричный, поэтому вы должны проанализировать его, например, {"jsonrpc":"2.0","result":"0x1","id":73}
  • цена на газ, максимальная цена на газ — они используются для покрытия транзакционных издержек (отправка не бесплатная). Вы можете прочитать больше здесь . Вы можете узнать текущую цену на газ, вызвав конечную точку API eth_gasPrice. Вероятно, это хорошая идея — периодически запрашивать цену на газ и кэшировать ее в течение короткого периода времени, а не извлекать ее для каждой транзакции. Если вы подключены к сети, вы можете получить цену на газ автоматически.
  • receiverAddress — байтовый массив, представляющий открытый ключ получателя
  • value — сколько эфира вы хотите отправить. Самая маленькая единица измерения — это «gwei», а значение указывается в gweis (часть 1 ETH)
  • data — любые дополнительные данные, которые вы хотите поместить в транзакцию.
  • chainId — это снова связано с тем, какую сеть вы используете. Производство = 1, тестовая сеть Ropsten = 3. Если вам интересно, почему вы должны кодировать его в транзакции, вы можете прочитать здесь .

После этого вы подписываете необработанное представление транзакции своим закрытым ключом (необработанным представлением является RLP (Рекурсивный префикс длины) ). А затем вы отправляете его в API (для этого вам понадобится ключ, который вы можете получить в Etherscan и включить его в URL). Это почти идентично тому, что вы сделали бы, если бы вы были на связи. Но теперь вы полагаетесь на центральную партию (Etherscan) вместо того, чтобы стать частью сети.

Это может выглядеть «легко», и когда вы уже сделали это и поняли, это звучит как кусок пирога, но слишком много деталей, которые никто не отвлекает от вас, поэтому вы должны иметь полную картину, прежде чем даже быть способен выдвинуть одну транзакцию. Что такое одноразовый номер, что такое chainId, что такое тестовая сеть, как получить тестовый эфир (лучший результат Google для ropsten faucet в данный момент не работает, так что вам также нужно это выяснить), затем Выясните, хотите ли вы синхронизировать цепочку или нет, быть частью сети или нет, чтобы решить странные проблемы с подключением и конфигурацией сети. И это даже не говоря об умных контрактах. Я не говорю, что это плохо, это просто не достаточно просто и это барьер для более широкого принятия. Это, вероятно, относится к большей части программирования, хотя. В любом случае, я надеюсь, что приведенные выше примеры помогут людям легче начать работу.

Ссылка: Как отправить транзакции Ethereum с Java от нашего партнера по JCG Божидара Божанова в блоге Bozho’s tech blog.