記事の内容
Enterprise版Ethereumと呼ばれるQuorumを使ってみよう。
ということで、環境構築からサンプルのコントラクトを動かすところまでをやってみました。
サンプルの内容は以下になります。
環境
OS:Windows10
Vagrant:2.2.7
環境構築
工程毎にやったことを書いていきます。
Quorum用のゲストOS起動
まずはgitからリポジトリをクローンし、ゲストOSを起動します。
git clone https://github.com/jpmorganchase/quorum-examples
cd quorum-examples
vagrant up
vagrant ssh
ここで、私の環境では「vagrnat up」に失敗しました。
原因はVagrantのバージョンが古かったからです。
前に使っていたバージョンを忘れてしまいましたが、2.1.XXだったはずです。
現時点の最新版(2.2.7)にアップデートすると動きました。
dockerを使用する環境であれば、docker-composeで起動出来るみたいです。
docker-compose up -d
nodeの起動
サンプルではnodeを7つ起動します。
$ cd quorum-examples/examples/7nodes
$ ./raft-init.sh
$ ./raft-start.sh
ここで、私の環境だと「raft-start.sh」が延々に先に進まないという事象に陥りました。
ログを確認(qdata/logs 配下)してみると、以下のメッセージが出力されていました。
a has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
どうも、コンパイルされたJavaのバージョンと実行しようとしているJavaのバージョンが異なるみたいです。
エラー内容を見る限りだと、以下の状態になっているみたいです。
Javaのクラスファイル:55.0(JDK 11)
Javaの実行環境 :52.0(JRE 8)
Javaのバージョンを11に上げます。
$ sudo add-apt-repository ppa:openjdk-r/ppa
$ sudo apt update
$ sudo apt install openjdk-11-jdk
これが終わってJavaのバージョンを確認すると上がっているはずです。
$ java -version
openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment (build 11.0.5+10-post-Ubuntu-2ubuntu116.04)
OpenJDK 64-Bit Server VM (build 11.0.5+10-post-Ubuntu-2ubuntu116.04, mixed mode, sharing)
無事にJavaのバージョンが上がっていることを確認出来たらもう一度、nodeの起動シェルを実行します。
$ ./raft-init.sh
$ ./raft-start.sh
起動が完了すると以下のログが出力されます。
All Tessera nodes started
[*] Starting 7 Ethereum nodes with ChainID and NetworkId of 10
ARGS="--nodiscover --verbosity 5 --networkid $NETWORK_ID --raft --rpc --rpccorsdomain=* --rpcvhosts=* --rpcaddr 0.0.0.0 --rpcapi admin,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft,quorumPermission --emitcheckpoints --unlock 0 --password passwords.txt $QUORUM_GETH_ARGS"
basePort=21000
baseRpcPort=22000
baseRaftPort=50401
for i in `seq 1 ${numNodes}`
do
port=$(($basePort + ${i} - 1))
rpcPort=$(($baseRpcPort + ${i} - 1))
raftPort=$(($baseRaftPort + ${i} - 1))
permissioned=
if [[ $i -le 4 ]]; then
permissioned="--permissioned"
elif ! [[ -z "${STARTPERMISSION+x}" ]] ; then
permissioned="--permissioned"
fi
PRIVATE_CONFIG=qdata/c${i}/tm.ipc nohup geth --datadir qdata/dd${i} ${ARGS} ${permissioned} --raftport ${raftPort} --rpcport ${rpcPort} --port ${port} 2>>qdata/logs/${i}.log &
done
seq 1 ${numNodes}
set +v
All nodes configured. See 'qdata/logs' for logs, and run e.g. 'geth attach qdata/dd1/geth.ipc' to attach to the first Geth node.
To test sending a private transaction from Node 1 to Node 7, run './runscript.sh private-contract.js'
サンプルコントラクトの実行
次に用意されているサンプルの[private-contract.js」を実行してみます。
$ ./runscript.sh private-contract.js
Contract transaction send: TransactionHash: 0x1cf8ab921c69c959f8d76fe134ea54af5b9536d9b95dff1fe57781f6743b30df waiting to be mined...
true
このような結果が表示されれば正常終了しています。
トランザクションの情報を確認してみます。
まずは、gethを使ってコンソールにログインします。
$ geth attach ipc:qdata/dd1/geth.ipc
ここではnode1に接続してみました。
nodeは1から7まで起動していますので、別のnodeに接続する場合、「dd1」の数字の部分を変更することで指定したnodeに接続することが可能です。
>eth.getTransaction('0x1cf8ab921c69c959f8d76fe134ea54af5b9536d9b95dff1fe57781f6743b30df')
{
blockHash: "0xcac3adee3bf5c8934bf6f9f03f84096f7ba68a4e990fb7d5d53e1391b6ebcc61",
blockNumber: 1,
from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
gas: 4700000,
gasPrice: 0,
hash: "0x1cf8ab921c69c959f8d76fe134ea54af5b9536d9b95dff1fe57781f6743b30df",
input: "0xfaf90963bbc0e7ed8f4ddb48b74b14d83dd19ebd65d85b95591679788379e56e14fb144ec926273f8430d34095c582666dfe9f7d8892fd97d3f6788538770bf7",
nonce: 0,
r: "0x7cea1fca3231c37b834a8f656b54e3f5940f72fa5b29ffe8f83f25be36f07d79",
s: "0x276afd259a492193f322d0b70232c3d24a23739120a8721bff8c9598070eb05c",
to: null,
transactionIndex: 0,
v: "0x26",
value: 0
}
トランザクションの情報を取得することが出来ました。
ついでに、ジェネシスブロックとトランザクションが取り込まれたブロックの情報も確認してみます。
> eth.getBlock(0)
{
difficulty: 0,
extraData: "0x0000000000000000000000000000000000000000000000000000000000000000",
gasLimit: 3758096384,
gasUsed: 0,
hash: "0x6a6605601e17bbfbc0a199104a05f222d11da37fe2320c023394ff1e516243a2",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x0000000000000000000000000000000000000000",
mixHash: "0x00000000000000000000000000000000000000647572616c65787365646c6578",
nonce: "0x0000000000000000",
number: 0,
parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 537,
stateRoot: "0x8e0eda84327cb810632f7d6ab845ab2dc0c1960156beb095384eb4bd0900eeb0",
timestamp: 0,
totalDifficulty: 0,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}
> eth.getBlock(1)
{
difficulty: 131072,
extraData: "0x0000000000000000000000000000000000000000000000000000000000000000f84434b841e4ac2bbe3afdc9357603d6d9c9b569cc689b8539dbd4cebcb6e436c0633585215f1449f756f12e95dbf1d21f352c1ce181c5f823b4b9e6ba9f9078cabd54dd6d01",
gasLimit: 3757178881,
gasUsed: 0,
hash: "0xcac3adee3bf5c8934bf6f9f03f84096f7ba68a4e990fb7d5d53e1391b6ebcc61",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x0000000000000000000000000000000000000000",
mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "0x0000000000000000",
number: 1,
parentHash: "0x6a6605601e17bbfbc0a199104a05f222d11da37fe2320c023394ff1e516243a2",
receiptsRoot: "0xb64408da6b8fe39ab764af88ece1e8cca1c35fd988db57806e99138c629365a0",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 763,
stateRoot: "0x90df70742c1e2848659187fce7b941351c913cfc693b09e51fd9c2c34edc1c87",
timestamp: 1582853824124984800,
totalDifficulty: 131072,
transactions: ["0x1cf8ab921c69c959f8d76fe134ea54af5b9536d9b95dff1fe57781f6743b30df"],
transactionsRoot: "0x305186990a1c2260ea7963cf030683025155da79b96f55b0b00f41218ba53624",
uncles: []
}
この辺りは、Ethereumと変わらないですね。
コントラクトの状態を確認する
gitのサンプルコードだと
> var address = "0x1932c48b2bf8102ba33b4a6b545c32236e342f34"; //replace with your contract address
この様になっています。
contract addressを変更する必要がありますが、確認方法です。
$ vi qdata/logs/1.log
このログを確認すると以下のログが出力されている箇所があります。
この「to=0x1932c48b2bF8102Ba33B4A6B545C32236e342f34」がコントラクトアドレスになります。
INFO [02-28|01:37:04.123] Submitted contract creation fullhash=0x1cf8ab921c69c959f8d76fe134ea54af5b9536d9b95dff1fe57781f6743b30df to=0x1932c48b2bF8102Ba33B4A6B545C32236e342f34
このアドレスを使ってサンプルを進めます。
> var address = '0x1932c48b2bF8102Ba33B4A6B545C32236e342f34'
undefined
> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"type":"constructor"}]
undefined
> var private = eth.contract(abi).at(address)
undefined
> private.get()
42
はい。ステータスの確認ができました。
この「42」というものが初期値の様です。
他のノードでも同じ手順で確認することが出来ます。
node4
> var address = '0x1932c48b2bF8102Ba33B4A6B545C32236e342f34'
undefined
> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"type":"constructor"}];
undefined
> var private = eth.contract(abi).at(address)
undefined
> private.get()
0
node7
> var address = '0x1932c48b2bF8102Ba33B4A6B545C32236e342f34'
undefined
> var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"type":"constructor"}];
undefined
> var private = eth.contract(abi).at(address)
undefined
> private.get()
42
サンプルと同じ結果になりました。
node1,7は上手くコントラクトがデプロイされているようですが、node4はデプロイされていないみたいです。
コントラクトの状態を変更する
node1でコントラクトの状態を変更する処理を動かしてみます。
> private.set(4,{from:eth.accounts[0],privateFor:["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]});
"0xbd96e94523639db82b57ee0cc6b5ca6d61d7e75923bc82a25b4d1723d242097a"
privateForの部分はaccount[0]の秘密鍵かなと思うのですが、調べる方法が分からず、サンプルのまま動かしたら動きました。
> private.get()
4
ステータスが変わりました。
他のnodeでも確認してみます。
node4
> private.get()
0
node7
> private.get()
4
node4は変わらず、node7のみ状態が変わっていることを確認できました。
ログにもprivate.set()を動かしたことによるブロック生成のログが出力されていました。
DEBUG[02-28|02:52:30.696] Persisted trie from memory database nodes=2 size=208.00B time=24.269µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
DEBUG[02-28|02:52:30.697] Inserted new block number=2 hash=5879c0…942ec2 uncles=0 txs=1 gas=0 elapsed=734.008µs
INFO [02-28|02:52:30.697] Imported new chain segment blocks=1 txs=1 mgas=0.000 elapsed=757.316µs mgasps=0.000 number=2 hash=5879c0…942ec2 cache=1.76kB
INFO [02-28|02:52:30.697] QUORUM-CHECKPOINT name=BLOCK-CREATED block=5879c002f3058227e4a48bfbf2caa182fe2361e357da80f4dc8f2f171b942ec2
node1で状態変更のコントラクトを実行したので、node7でブロック情報を確認してみます。
これで確認出来ればブロックも確実に伝播されています。
> eth.getBlock(2)
{
difficulty: 131072,
extraData: "0x0000000000000000000000000000000000000000000000000000000000000000f84434b8418f3ca04bcd67c4b4bcce8f5f097bcc05fe067d3dcf6d633db7612eeaa198c4fd503fd39fc52360799f5f51477c1806d2e0fbffa5aa96b1baac1d988acfe4fdf101",
gasLimit: 3756261602,
gasUsed: 0,
hash: "0x5879c002f3058227e4a48bfbf2caa182fe2361e357da80f4dc8f2f171b942ec2",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x0000000000000000000000000000000000000000",
mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "0x0000000000000000",
number: 2,
parentHash: "0xcac3adee3bf5c8934bf6f9f03f84096f7ba68a4e990fb7d5d53e1391b6ebcc61",
receiptsRoot: "0xb64408da6b8fe39ab764af88ece1e8cca1c35fd988db57806e99138c629365a0",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 783,
stateRoot: "0x332446fec20ad7da8549da8e6e25c013be5aed5ec8ec15b14084fbec4b8e55e6",
timestamp: 1582858350662357200,
totalDifficulty: 262144,
transactions: ["0xbd96e94523639db82b57ee0cc6b5ca6d61d7e75923bc82a25b4d1723d242097a"],
transactionsRoot: "0xb182a8536c568bc7adbb3a9d2374fa7926763f7d8ac3a06d6a872a38882ec0ba",
uncles: []
}
確認できました。
感想
Ethereumと互換性があるということで、gethの使い方は違いを意識することは必要ないのは良いですね。
ここで動かしたサンプルだけだとEthereumと比べた明確なメリットを理解するのは難しいので、実際に使うには更なる技術調査が必要です。