#前提
Ethereumのgethを使ったプライベート環境構築記事はほかにもあるので
個人メモ的なもの。JavaでのRPC実行まで。コントラクト実行は確認していない。
#環境構築
以下を参考に。
http://book.ethereum-jp.net/first_use/connect_to_private_net.html
ubuntu16LTS ベースとする。
###AWSのEC2にubuntuベースでインスタンス作成
microインスタンスのスペックではマイニングができなかった為、m4.largeで構築。(ある程度のCPUとメモリを要求する)
###事前準備からインストール
リポジトリを追加して、gethをインストールする。
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo add-apt-repository -y ppa:ethereum/ethereum-dev
sudo apt update
sudo apt upgrade
sudo apt install ethereum
cd /home/ubuntu
mkdir eth_private_net
cd eth_private_net
###Genesis.jsonを作る
nano Genesis.json
####Genesis.jsonの内容
{
"nonce": "0x0000000000000042",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x0",
"gasLimit": "0x8000000",
"difficulty": "0x4000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x3333333333333333333333333333333333333333",
"alloc": {}
}
###ジェネシスブロック生成
geth --datadir /home/ubuntu/eth_private_net init /home/ubuntu/eth_private_net/Genesis.json
I1116 05:15:12.361001 cmd/geth/main.go:256] successfully wrote genesis block and/or chain rule set: 3b3326d56983eec74bcd3c5757801dcd42e0bf2f169fc0c5d695e28e20f217d7
###コンソール起動
geth --identity "sampleNode" --rpc --datadir "/home/ubuntu/eth_private_net" --nodiscover --networkid 10 console 2>> /home/ubuntu/eth_private_net/geth.log
プロンプトが起動する。
###アカウント作成
> personal.newAccount()
パスフレーズを指定しなかった場合、入力を2回求められる。
Passphrase:
Repeat passphrase:
"0x02f35a58e4ad954834db125588b9e0c096d41aa7"
> personal.newAccount("パスフレーズ")
でも可能。
送信側、受信側で2つアカウントを作成。
###コンソールを一旦停止
> exit
###ディレクトリ内のkeystoreとGenesis.json以外削除
###Genesis.jsonのalloc部分を、先ほど作成したアカウントに書き換え、balance(残高)を追記
{
"nonce": "0x0000000000000042",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x0",
"gasLimit": "0x8000000",
"difficulty": "0x4000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x3333333333333333333333333333333333333333",
"alloc":
{
"0x02f35a58e4ad954834db125588b9e0c096d41aa7": {
"balance": "10000000000000000000"
}
}
}
###再度ジェネシスブロック生成
geth --datadir /home/ubuntu/eth_private_net init /home/ubuntu/eth_private_net/Genesis.json
###再度コンソール起動
geth --mine --minerthreads 2 --identity "sampleNode" --rpc --rpcport 8575 --rpcapi "web3,eth,net,personal" --rpccorsdomain "*" --rpcaddr "0.0.0.0" --datadir "/home/ubuntu/eth_private_net" --nodiscover --networkid 10 console 2>> /home/ubuntu/eth_private_net/geth.log
####geth起動オプション
コマンド | 内容 |
---|---|
--mine | マイニング実行可能にする |
--minerthreads スレッド数 | マイニング実行スレッド数 |
--identity "名前" | 名前を付ける |
--rpc | RPCを有効化 |
--rpcport ポート番号 | RPC受付ポート番号の指定 |
--rpcapi "機能名" | どの機能をRPCで使えるようにするか |
--rpccorsdomain "*" | クロスドメイン有効化 |
--rpcaddr "IPアドレス" | 指定せずにデフォルトだとローカルアクセスのみ許可されている。外部サーバからRPCアクセスする場合はそのIPを指定。どこからでもRPCアクセス可能にするには"0.0.0.0"を指定。 |
--datadir "データ保存パス" | データをどこに保存するかパスを指定 |
--nodiscover | デフォルトでは自動探索モードでほかのethereumノードを検索しに行く。プライベートブロックチェーンの場合は探しに行かないようにこのオプションを指定。 |
--networkid 任意の番号 | インターネット上のオープンなネットとは異なるネットワークを作る為、任意の数字(0~2以外)、例えば10を指定する。既存ネットワークのidは、0=Olympic, 1=Frontier, 2=Morden |
console | コンソールを同時に起動する。(attachであとからconsoleの起動も可能) |
###アドレス確認
> eth.accounts[0]
"0x02f35a58e4ad954834db125588b9e0c096d41aa7"
###Genesis.jsonで指定したアドレスの残高確認
> eth.getBalance(eth.accounts[0])
10000000000000000000
###アカウントロック解除
5分で再度ロックがかかるのでunlockしてから送金。
実際は送金側アカウントのみで可。
> personal.unlockAccount("0x02f35a58e4ad954834db125588b9e0c096d41aa7", "パスフレーズ")
true
> personal.unlockAccount("0x5c17f667c6d9f2278c6a960cfa8fd79f11c09e91", "パスフレーズ")
true
###送金トランザクション実行
> eth.sendTransaction({from: "0x02f35a58e4ad954834db125588b9e0c096d41aa7", to: "0x5c17f667c6d9f2278c6a960cfa8fd79f11c09e91", value: web3.toWei(5, "ether")})
fromとtoはeth.accounts[0]などの書き方でも可。
###マイニング実行
このままではトランザクションは実行されない。
ここで、マイニングを実行する必要がある。
> eth.pendingTransactions
で、ペンディングされたトランザクションを確認できる。
また、
> eth.
の状態で、タブキーを2回押すと、コマンドの一覧が表示される。
これは「eth.」で始まるコマンドに限らず、「personal.」で始まるコマンドなども同様。
###マイニング実行
> miner.start()
true
これでしばらく待つとスペックにもよるがマイニングが開始される。
> eth.hashrate
が0でなければ、マイニングされている。
マイニングで入る収入は最初に作ったアカウントに送られる。
> eth.accounts[0]
で表示されるアカウント。
現在のブロック番号を確認するには、以下のコマンド。
> eth.blockNumber
117
マイニングされたら、再度、待機状態のトランザクションを確認
> eth.pendingTransactions
[]
なければ、送金トランザクションは実行済み。
マイニングを停止する。
> miner.stop()
###送信側、受信側の残高確認
> eth.getBalance("0x5c17f667c6d9f2278c6a960cfa8fd79f11c09e91")
これも、eth.accounts[0]などの形式での指定でも可。
ここで、送信側からは、実際の送金額+手数料が引かれている。
この送金手数料が、採掘者の収入になる。
あと、通貨の単位が桁数によって4種類(ether、finney、szabo、wei)あるので、どの単位で扱っているかは注意する。
###RPC接続確認
RPCで外部から接続可能かどうか、とりあえずcurlで確認。
JSON-RPC 2.0形式でリクエストを投げられれば、どんな方式でもよい。
curl -X POST http://XX.XXX.XXX.XXX:8575/ --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}'
これで、応答があればOK。
AWSの場合はセキュリティグループでRPCのポートを開けておく。
###JSON-RPCで使えるAPI
JSON-RPC経由で使えるAPIは以下のページを参考に。
https://github.com/ethereum/wiki/blob/master/JSON-RPC.md
コマンドから実行できるpersonal.XXXX(アカウント作成など)はRPC経由では実行できない。また。マイニングの実行や停止もできない。
その為、すべてのオペレーションをRPC経由で実行するように作りこむことはできない。
クライアントの言語によらず、JSON-RPC2.0でのアクセスを行うための実装をすれば、RPC経由での処理実行と、結果の取得が可能。
###Javaでの実装クラス抜粋
実用レベルに作りこんでない。動かしてみたレベル。
(Error時のJSONを作ってない、返してない、パッケージてきとー、命名てきとー、定数外だしー、etc..)
package com.example.service;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.example.requestDto.NoArgsRequestDto;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
import com.thetransactioncompany.jsonrpc2.client.JSONRPC2Session;
import com.thetransactioncompany.jsonrpc2.client.JSONRPC2SessionException;
@Service
public class GetAccountsService {
private static final Logger LOGGER = LoggerFactory
.getLogger(GetAccountsService.class.getName());
private static final String HOST = "http://XX.XXX.XXX.XXX:8575/";
/**
* jsonrpc2-client版
* http://software.dzhuvinov.com/json-rpc-2.0-client.html
* @return
*/
public String getAccountsJ() {
URL serverURL = null;
JSONRPC2Session mySession = null;
try {
serverURL = new URL(HOST);
mySession = new JSONRPC2Session(serverURL);
mySession.getOptions().setRequestContentType("application/json+rpc");
} catch (MalformedURLException e) {
LOGGER.warn(e.getMessage());
return e.getMessage();
}
String method = "eth_accounts";
String id = "1";
Map<String, Object> params = null;
JSONRPC2Request req = new JSONRPC2Request(method, params, id);
JSONRPC2Response res = null;
try {
res = mySession.send(req);
} catch (JSONRPC2SessionException e) {
LOGGER.warn(e.getMessage());
return e.getMessage();
}
if (res.indicatesSuccess()){
return res.getResult().toString();
}else{
return res.getError().getMessage();
}
}
/**
* Unirest版
* http://unirest.io/java.html
* @return
*/
public String getAccounts() {
HttpResponse<JsonNode> jsonResponse = null;
NoArgsRequestDto requestDto = new NoArgsRequestDto();
requestDto.setId(1);
requestDto.setJsonrpc("2.0");
requestDto.setMethod("eth_accounts");
try {
jsonResponse = Unirest.post(HOST)
.header("accept", "application/json")
.header("Content-Type", "application/json")
.body(requestDto).asJson();
LOGGER.info(jsonResponse.getBody().toString());
} catch (UnirestException e) {
LOGGER.warn(e.getMessage());
return e.getMessage();
}
String result = jsonResponse.getBody().toString();
return result;
}
}
package com.example.service;
import java.io.IOException;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.mashape.unirest.http.ObjectMapper;
import com.mashape.unirest.http.Unirest;
@Service
public class UnirestInitService {
static {
Unirest.setObjectMapper(new ObjectMapper() {
private com.fasterxml.jackson.databind.ObjectMapper jacksonObjectMapper
= new com.fasterxml.jackson.databind.ObjectMapper();
public <T> T readValue(String value, Class<T> valueType) {
try {
return jacksonObjectMapper.readValue(value, valueType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String writeValue(Object value) {
try {
return jacksonObjectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});
}
}
package com.example.requestDto;
import java.io.Serializable;
public class BaseRequestDto implements Serializable {
private String jsonrpc;
private String method;
private String[] params;
private int id;
/**
* @return method
*/
public String getMethod() {
return method;
}
/**
* @param method セットする method
*/
public void setMethod(String method) {
this.method = method;
}
/**
* @return params
*/
public String[] getParams() {
return params;
}
/**
* @param params セットする params
*/
public void setParams(String[] params) {
this.params = params;
}
/**
* @return id
*/
public int getId() {
return id;
}
/**
* @param id セットする id
*/
public void setId(int id) {
this.id = id;
}
/**
* @return jsonrpc
*/
public String getJsonrpc() {
return jsonrpc;
}
/**
* @param jsonrpc セットする jsonrpc
*/
public void setJsonrpc(String jsonrpc) {
this.jsonrpc = jsonrpc;
}
}
package com.example.requestDto;
public class NoArgsRequestDto extends BaseRequestDto {
}