LoginSignup
1
3

More than 3 years have passed since last update.

Quorumでprivate transactionをスクラッチで生成する

Last updated at Posted at 2020-07-03

以下、理由でまとめました。

  • 公式ドキュメントでは、環境構築~ノード構成~トランザクション生成まで、フローとしてまとまってなかった
  • 実行スクリプトなど冗長だった
  • ネット上漁っても、一気通貫でまとまっているものがなかった

作るもののイメージ

quorum_test.png

  1. transaction(Tx)をnode1に送信
  2. node1は、TxManager1にTxを送信
  3. TxManager1からTxManager2にpayload/hash/keyを送信、TxManager2はTxManager1にACK or Nackを送信
  4. ③が成功したら、TxManager1からnode1にhashを送信
  5. node1からnode2にTxを送信

大まかな手順下

  1. tesseraのインストール
  2. tesseraノードの構成作成
  3. quorumのインストール
  4. quorumノードの構成作成
  5. プライベートトランザクション生成、確認

前提条件

  • 手順としてまとめたので、少し冗長かもしれません・・・
  • 実行環境
    • OS:ubuntsu 18.04
    • tessera:v0.10.4
    • git: v2.17.1

tesseraのインストール

  • ダウンロード
console
$ 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
  • リネーム
console
$ mv tessera-app-0.10.4-app.jar tessera.jar

tessera node1の構成作成

  • tessera node1のディレクトリを作成、及び鍵の生成
console
$ 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
⇒パスワード再入力
  • コンフィグファイルの作成
node1-t/config.json
{
    "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-t/pass
<node1作成時に入力したパスワード>
  • tessera node2のディレクトリを作成、及び鍵の生成
console
$ 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
⇒パスワード再入力
  • コンフィグファイルの作成
node2-t/config.json
{
    "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": ""で対応しています。

  • パスワードファイルの作成
node1-t/pass
<node2作成時に入力したパスワード>
  • tessera nodeの起動スクリプト作成
start_tessera.sh
#!/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 &

スクリプトを作ったほうが楽なので作りました。

  1. tessera nodeの起動
console
$ sudo chmod +x start_tessera.sh
$ ./start_tessera.sh
  • 起動確認
console
$ 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
console
$ git clone https://github.com/jpmorganchase/quorum.git
  • quorumのビルド
console
$ cd quorum
$ make all
  • buildバイナリのパス設定
console
$ export PATH=$(pwd)/build/bin:$PATH

毎回実行を前提とする場合は、~/.bashrcにパスを通すほうが楽です。

quorum nodeの構成作成

  • node1のディレクトリ作成、及び管理アカウントの作成
console
$ mkdir node1
$ geth --datadir node1 account new

[実行結果]
Password: 
⇒任意のパスワードを入力
Repeat password:
⇒パスワード再入力
  • genesis.jsonファイルの作成
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の作成
console
$ bootnode --genkey=nodekey
$ cp nodekey node1
$ bootnode --nodekey=node1/nodekey --writeaddress > node1/enode
$ cat node1/enode
  • node1のstatic-nodes.jsonの作成
static-nodes.json
[
    "enode://<手順3で確認したenodeのハッシュ値@127.0.0.1:21000?discport=0&raftport=50000"
]
  • node1のstatic-nodes.jsonファイルの配置
console
$ cp static-nodes.json node1
  • node1の初期化
console
$ 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の起動スクリプトの作成
startnodes.json
#!/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の起動
console
$ sudo chmod +x startnode.sh
$ ./startnode.sh
  • 起動確認
console
$ 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に関しても、同じような手順で作成します

console
$ mkdir nocde2
$ bootnode --genkey=nodekey2
$ cp nodekey2 node2/nodekey
$ bootnode --nodekey=node2/nodekey --writeaddress > node2/enode
$ cat node2/enode
  • node2のstatic-nodes.jsonの配置
console
$ cp static-nodes.json node2
  • node2の初期化
console
$ 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の起動スクリプトの作成
startnodes2.sh
#!/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の起動
console
$ sudo chmod +x startnode2.sh
$ ./startnode2.sh
  • node2の追加
console
>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プロセス停止
console
$ 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されているよ、と怒られます。

プライベートトランザクション生成、確認

  • コントラクトデプロイ用のスクリプト作成
private-contract.js
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の起動スクリプトを作成
start.sh
#!/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
  • 起動確認
console
$ 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
  • トランザクション生成、確認
console
$ 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"という値が共有できていることが確認できます。

まとめ

  • プライベートトランザクションを、フルスクラッチで実行する手順をまとめました。
  • ブロックチェーンは、ここらへんのインフラ設定周りで、どうも導入のハードルが高い気がしてました。
    • ここらへがカチッと決まれば、コンテナでネットワークを組めばよいですが、その前段階では特に手数が多いのがなんとかなればいいです。

参考

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3