以下、理由でまとめました。
- 公式ドキュメントでは、環境構築~ノード構成~トランザクション生成まで、フローとしてまとまってなかった
- 実行スクリプトなど冗長だった
- ネット上漁っても、一気通貫でまとまっているものがなかった
作るもののイメージ
- transaction(Tx)をnode1に送信
- node1は、TxManager1にTxを送信
- TxManager1からTxManager2にpayload/hash/keyを送信、TxManager2はTxManager1にACK or Nackを送信
- ③が成功したら、TxManager1からnode1にhashを送信
- node1からnode2にTxを送信
大まかな手順下
前提条件
- 手順としてまとめたので、少し冗長かもしれません・・・
- 実行環境
- OS:ubuntsu 18.04
- tessera:v0.10.4
- git: v2.17.1
tesseraのインストール
- ダウンロード
$ wget https://oss.sonatype.org/service/local/repositories/releases/content/com/jpmorgan/quorum/tessera-app/0.10.4/tessera-app-0.10.4-app.jar
- リネーム
$ mv tessera-app-0.10.4-app.jar tessera.jar
tessera node1の構成作成
- tessera node1のディレクトリを作成、及び鍵の生成
$ mkdir node1-t
$ cd node1-t
$ java -jar ../tessera.jar -keygen -filename node1(=鍵名)
Enter a password if you want to lock the private key or leave blank
⇒任意のパスワードを入力
Please re-enter the password (or lack of) to confirm
⇒パスワード再入力
- コンフィグファイルの作成
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:/<yourpath>/node1-t/db1;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs": [
{
"app": "ThirdParty",
"enabled": true,
"serverAddress": "http://localhost:9081",
"communicationType": "REST"
},
{
"app": "Q2T",
"enabled": true,
"serverAddress": "unix:/<yourpath>/node1-t/tm.ipc",
"communicationType": "REST"
},
{
"app": "P2P",
"enabled": true,
"serverAddress": "http://localhost:9001",
"sslConfig": {
"tls": "OFF"
},
"communicationType": "REST"
}
],
"peer": [
{
"url": "http://localhost:9001"
},
{
"url": "http://localhost:9003"
}
],
"keys": {
"passwordFile": "/<yourpath>/node1-t/pass",
"keyData": [
{
"privateKeyPath": "/<yourpath>/node1-t/node1.key",
"publicKeyPath": "/<yourpath>/node1-t/node1.pub"
}
]
},
"alwaysSendTo": []
}
ここで、公式では"passwordFile": "pass"の代わりに、"passwords": []で記載があります。
しかし、これをすると、「標準入力からパスワードを入れるな」と怒られるので、対応をとりました。
passファイルを別途用意し、"passwordFile": ""で対応しています。
- パスワードファイルの作成
<node1作成時に入力したパスワード>
- tessera node2のディレクトリを作成、及び鍵の生成
$ mkdir node2-t
$ cd node2-t
$ java -jar ../tessera.jar -keygen -filename node2(=鍵名)
Enter a password if you want to lock the private key or leave blank
⇒任意のパスワードを入力(※node1のパスワードとは別)
Please re-enter the password (or lack of) to confirm
⇒パスワード再入力
- コンフィグファイルの作成
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:/<yourpath>/node2-t/db1;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs": [
{
"app": "ThirdParty",
"enabled": true,
"serverAddress": "http://localhost:9083",
"communicationType": "REST"
},
{
"app": "Q2T",
"enabled": true,
"serverAddress": "unix:/<yourpath>/node2-t/tm.ipc",
"communicationType": "REST"
},
{
"app": "P2P",
"enabled": true,
"serverAddress": "http://localhost:9003",
"sslConfig": {
"tls": "OFF"
},
"communicationType": "REST"
}
],
"peer": [
{
"url": "http://localhost:9001"
},
{
"url": "http://localhost:9003"
}
],
"keys": {
"passwordFile": "/<yourpath>/node2-t/pass",
"keyData": [
{
"privateKeyPath": "/<yourpath>/node2-t/node2.key",
"publicKeyPath": "/<yourpath>/node2-t/node2.pub"
}
]
},
"alwaysSendTo": []
}
node1同様、passファイルを別途用意し、"passwordFile": ""で対応しています。
- パスワードファイルの作成
<node2作成時に入力したパスワード>
- tessera nodeの起動スクリプト作成
#!/bin/bash
java -jar tessera.jar --configfile node1-t/config.json >> tessera1.log 2>&1 &
java -jar tessera.jar --configfile node2-t/config.json >> tessera2.log 2>&1 &
スクリプトを作ったほうが楽なので作りました。
- tessera nodeの起動
$ sudo chmod +x start_tessera.sh
$ ./start_tessera.sh
- 起動確認
$ ls node1-t/ & ls node2-t/
[実行結果]
config.json db1.mv.db db1.trace.db node1.key node1.pub pass tm.ipc
config.json db1.mv.db db1.trace.db node2.key node2.pub pass tm.ipc
quorumのインストール
- quorumをgit clone
$ git clone https://github.com/jpmorganchase/quorum.git
- quorumのビルド
$ cd quorum
$ make all
- buildバイナリのパス設定
$ export PATH=$(pwd)/build/bin:$PATH
毎回実行を前提とする場合は、~/.bashrcにパスを通すほうが楽です。
quorum nodeの構成作成
- node1のディレクトリ作成、及び管理アカウントの作成
$ mkdir node1
$ geth --datadir node1 account new
[実行結果]
Password:
⇒任意のパスワードを入力
Repeat password:
⇒パスワード再入力
- genesis.jsonファイルの作成
{
"alloc": {
"0xアドレス": {
"balance": "1000000000000000000000000000"
},
"0xc5c7b431e1629fb992eb18a79559f667228cd055": {
"balance": "2000000000000000000000000000"
}
},
"coinbase": "0x0000000000000000000000000000000000000000",
"config": {
"homesteadBlock": 0,
"byzantiumBlock": 0,
"chainId": 10,
"eip150Block": 0,
"eip155Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip158Block": 0,
"isQuorum": true
},
"difficulty": "0x0",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0xE0000000",
"mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"nonce": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00"
}
- nodekey、enodeの作成
$ bootnode --genkey=nodekey
$ cp nodekey node1
$ bootnode --nodekey=node1/nodekey --writeaddress > node1/enode
$ cat node1/enode
- node1のstatic-nodes.jsonの作成
[
"enode://<手順3で確認したenodeのハッシュ値@127.0.0.1:21000?discport=0&raftport=50000"
]
- node1のstatic-nodes.jsonファイルの配置
$ cp static-nodes.json node1
- node1の初期化
$ geth --datadir node1 init genesis.json
[実行結果]
INFO [07-02|18:27:29.226] Maximum peer count ETH=50 LES=0 total=50
INFO [07-02|18:27:29.226] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file
・
・
・
- node1の起動スクリプトの作成
#!/bin/bash
PRIVATE_CONFIG=ignore nohup geth --datadir node1\
--nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50000\
--rpc --rpcaddr 0.0.0.0 --rpcport 22000\
--rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft\
--emitcheckpoints --port 21000 >> node.log 2>&1 &
- node1の起動
$ sudo chmod +x startnode.sh
$ ./startnode.sh
- 起動確認
$ geth attach node1/geth.ipc
(以下はgethインタープリタ内の操作)
>raft.cluster
[実行結果]
[{
hostname: "127.0.0.1",
nodeActive: true,
nodeId: "6b7294b1ad3a6f086f730b4f408ca0791c61c10447d3969734df1b4ac4ad6add86a09052974ce1685e8f7a0f320019a078d19fe96abba50dd0511a5c58bd4779",
p2pPort: 21000,
raftId: 1,
raftPort: 50000,
role: "minter"
}]
raftクラスタに参加しているnodeは、自分自身のnodeであることnodeIDから確認できます。
- node2のディレクトリ作成、及びnodekey、enodeの作成
node2に関しても、同じような手順で作成します
$ mkdir nocde2
$ bootnode --genkey=nodekey2
$ cp nodekey2 node2/nodekey
$ bootnode --nodekey=node2/nodekey --writeaddress > node2/enode
$ cat node2/enode
- node2のstatic-nodes.jsonの配置
$ cp static-nodes.json node2
- node2の初期化
$ geth --datadir node2 init genesis.json
[実行結果]
INFO [07-02|18:56:56.532] Maximum peer count ETH=50 LES=0 total=50
INFO [07-02|18:56:56.532] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
INFO [07-02|18:56:56.535] Allocated cache and file handles
・
・
・
- node2の起動スクリプトの作成
#!/bin/bash
PRIVATE_CONFIG=ignore nohup geth --datadir node2 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50001 --raftjoinexisting 2 --rpc --rpcaddr 0.0.0.0 --rpcport 22001 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21001 >> node2.log 2>&1 &
- node2の起動
$ sudo chmod +x startnode2.sh
$ ./startnode2.sh
- node2の追加
>raft.addPeer("enode://[node2のenodeハッシュ値]@127.0.0.1:21001?discport=0&raftport=50001")
2
>raft.cluster
[{
hostname: "127.0.0.1",
nodeActive: true,
nodeId: "c7234394cc370f9444d1b0bc60d553f9a5cf18da677aafdc0cedf99993bf07ac247d1d827c87e666c01ade7996ce26a4a101dc17782b3a493db71cbce1bfb2a5",
p2pPort: 21001,
raftId: 2,
raftPort: 50001,
role: "verifier"
}, {
hostname: "127.0.0.1",
nodeActive: true,
nodeId: "6b7294b1ad3a6f086f730b4f408ca0791c61c10447d3969734df1b4ac4ad6add86a09052974ce1685e8f7a0f320019a078d19fe96abba50dd0511a5c58bd4779",
p2pPort: 21000,
raftId: 1,
raftPort: 50000,
role: "minter"
}]
もちろん、追加されたnode2のnodeIDも確認できます。
- node1, node2のgethプロセス停止
$ ps(以下のような状態になっている)
15392 pts/0 00:00:00 geth
15430 pts/0 00:00:00 geth
・
・
・
(上の例だと)
$ kill -9 15392 & kill -9 15430
gethプロセスをkillしないと、次に行うgeth.ipcの起動ができません。
理由は、種々のportを使いまわしているため、portが既にbindされているよ、と怒られます。
プライベートトランザクション生成、確認
- コントラクトデプロイ用のスクリプト作成
a = eth.accounts[0]
web3.eth.defaultAccount = a;
// abi and bytecode generated from simplestorage.sol:
// > solcjs --bin --abi simplestorage.sol
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" }], "payable": false, "type": "constructor" }];
var bytecode = "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029";
var simpleContract = web3.eth.contract(abi);
var simple = simpleContract.new(10, { from: web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["3eAOnJSpWwRE2zC+GsPFHGJhKkg6mIEtePeoabhtVjo="] }, function (e, contract) {
if (e) {
console.log("err creating contract", e);
} else {
if (!contract.address) {
console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
} else {
console.log("Contract mined! Address: " + contract.address);
console.log(contract);
}
}
});
simpleContract.new()引数の、privateForフィールドにtessera node2の公開鍵を設定する。
- node1、node2の起動スクリプトを作成
#!/bin/bash
PRIVATE_CONFIG=/<yourpath>/node1-t/tm.ipc
nohup geth --datadir node/node1 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50000
--raftjoinexisting 1 --rpc --rpcaddr 0.0.0.0 --rpcport 22000 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft
--emitcheckpoints --port 21000 >> node1.log 2>&1 &
これも、スクリプトを作ったほうが楽なので作ります。
- node1、node2の起動
$ sudo chmod +x start.sh
$ ./start.sh
- 起動確認
$ ps
[実行結果]
5737 pts/0 00:00:46 java ⇒ tessera node1
5738 pts/0 00:00:45 java ⇒ tessera node2
6363 pts/0 00:00:03 geth ⇒ quorum node1
6364 pts/0 00:00:03 geth ⇒ quorum node2
- トランザクション生成、確認
$ geth attach --datadir node/node1
<node1のアカウントロックを解除>
> personal.unlockAccount(eth.accounts[0])
true
>loadScript(“private-contract.js”)
[出力結果]
Contract transaction send: TransactionHash: 0x0456febcd5f6aca1eb2789164cf41bde08d7204aa30ca37a61c6aa02cd3a86ba waiting to be mined... ①トランザクションハッシュ値
true
> Contract mined! Address: 0xe347834bc2abead836693f8972912db7a14e7dd2
[object Object]
>eth.getTransaction("①トランザクションハッシュ値")
[出力結果]
{
blockHash: "0x064f126b250ef49a2bf2b65a4053422fc602265cf5290eb4de5ad64a5ea48c21",
blockNumber: 1,
from: "0x95d18ba75f2bbd35420522f80c1abc265aeae3c3",
gas: 4700000,
gasPrice: 0,
hash: "0x0456febcd5f6aca1eb2789164cf41bde08d7204aa30ca37a61c6aa02cd3a86ba",
input: "0x79691bdd774a6c9f4ef99a73271a0df92fd6acc5d6a5ad04e598b07ad5bceddd5614b466f0811697e66f0a4ecb2da8eb249f194ebece2fc1424b9dbdddfe002e",
nonce: 0,
r: "0x3a2887245cbd4051ca4d3b0b876988414ce525f40d69af540df0720ec7ddee3f",
s: "0x657dd7a6d3ff74dab7f93636c87bc686f57d9c5c2a9d574e30f4530788388ed0",
to: null,
transactionIndex: 0,
v: "0x25",
value: 0
}
>eth.getTransactionReceipt("①トランザクションハッシュ値")
[実行結果]
{
blockHash: "0x064f126b250ef49a2bf2b65a4053422fc602265cf5290eb4de5ad64a5ea48c21",
blockNumber: 1,
contractAddress: "0xe347834bc2abead836693f8972912db7a14e7dd2",
cumulativeGasUsed: 0, ②コントラクトアドレス
from: "0x95d18ba75f2bbd35420522f80c1abc265aeae3c3",
gasUsed: 0,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
to: null,
transactionHash: "0x0456febcd5f6aca1eb2789164cf41bde08d7204aa30ca37a61c6aa02cd3a86ba",
transactionIndex: 0
}
>var addr = “②コントラクトアドレス”
>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" }], "payable": false, "type": "constructor" }];
>var private = eth.contract(abi).at(addr)
>private.get()
[出力結果]
10
⇒private-contract.jsで定義した値
node1, node2で"10"という値が共有できていることが確認できます。
まとめ
- プライベートトランザクションを、フルスクラッチで実行する手順をまとめました。
- ブロックチェーンは、ここらへんのインフラ設定周りで、どうも導入のハードルが高い気がしてました。
- ここらへがカチッと決まれば、コンテナでネットワークを組めばよいですが、その前段階では特に手数が多いのがなんとかなればいいです。