📝

GethとDocker Composeの複数コンテナでプライベットネットワークを構築する

2022/08/01に公開

docker-composeでGethのノードを複数起動しプライベートネットワークを構築します。
1コンテナ1ノードで、コンテナ同士で接続するイメージです。
送金できるようにするところまでを目指します。

今回は4コンテナを用意します。

docker-compose.yml
version: "3"
services:
  app1:
    image: ethereum/client-go
    entrypoint: /bin/sh
    tty: true
    volumes:
      - ./private_net:/geth
  app2:
    image: ethereum/client-go
    entrypoint: "/bin/sh"
    tty: true
    volumes:
      - ./private_net2:/geth
  app3:
    image: ethereum/client-go
    entrypoint: "/bin/sh"
    tty: true
    volumes:
      - ./private_net3:/geth
  app4:
    image: ethereum/client-go
    entrypoint: "/bin/sh"
    tty: true
    volumes:
      - ./private_net4:/geth

使うイメージはこちら。
https://hub.docker.com/r/ethereum/client-go/tags

ディレクトリ構成もあったほうがいいかな。tree表示。

app1コンテナでGethを起動させる

まずはapp1のコンテナでGethを起動させます。

docker-compose up
docker-compose exec app1 /bin/sh

ジェネシスブロックの生成

Genesisブロックを作成します。
private_net/genesis.jsonを作成します。

genesis.json
{
  "config": {
    "chainId": 22,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "berlinBlock": 0,
    "londonBlock": 0
  },
  "alloc": {},
  "coinbase": "0x0000000000000000000000000000000000000000",
  "difficulty": "0x20000",
  "extraData": "",
  "gasLimit": "0x2fefd8",
  "nonce": "0x00000000000000059",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp": "0x00"
}

https://github.com/ethereum/go-ethereum#operating-a-private-network
を参考に作成しました。

chainIdは適当に22。
nonceも適当な値にしています。

初期化を実行します。

/geth # geth --datadir /geth/ init /geth/genesis.json
INFO [07-27|12:10:03.031] Maximum peer count                       ETH=50 LES=0 total=50
INFO [07-27|12:10:03.032] Smartcard socket not found, disabling    err="stat /run/pcscd/pcscd.comm: no such file or directory"
INFO [07-27|12:10:03.049] Set global gas cap                       cap=50,000,000
INFO [07-27|12:10:03.052] Allocated cache and file handles         database=/geth/geth/chaindata cache=16.00MiB handles=16
INFO [07-27|12:10:03.131] Opened ancient database                  database=/geth/geth/chaindata/ancient readonly=false
INFO [07-27|12:10:03.131] Writing custom genesis block 
INFO [07-27|12:10:03.131] Persisted trie from memory database      nodes=0 size=0.00B time="3.995µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [07-27|12:10:03.147] Successfully wrote genesis state         database=chaindata                    hash=119a56..09b937
INFO [07-27|12:10:03.148] Allocated cache and file handles         database=/geth/geth/lightchaindata    cache=16.00MiB handles=16
INFO [07-27|12:10:03.226] Opened ancient database                  database=/geth/geth/lightchaindata/ancient readonly=false
INFO [07-27|12:10:03.226] Writing custom genesis block 
INFO [07-27|12:10:03.226] Persisted trie from memory database      nodes=0 size=0.00B time="3.262µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [07-27|12:10:03.238] Successfully wrote genesis state         database=lightchaindata                    hash=119a56..09b937

これでジェネシスブロックが生成されました。

Gethを起動します。

/geth # geth --networkid "22" --nodiscover --datadir /geth console 2>> /geth/info.log
Welcome to the Geth JavaScript console!

instance: Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4
at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC))
 datadir: /geth
 modules: admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d or type exit

以下で1つ目のブロックが生成されていることを確認できます。

> eth.getBlock(0)
{
  baseFeePerGas: 1000000000,
  difficulty: 131072,
  extraData: "0x",
  gasLimit: 3141592,
  gasUsed: 0,
  hash: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x0000000000000000000000000000000000000000",
  mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  nonce: "0x0000000000000031",
  number: 0,
  parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 512,
  stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  timestamp: 0,
  totalDifficulty: 131072,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}

app2コンテナでGethを起動させてapp1のGethと接続する

ジェネシスブロック生成

残りの3コンテナに対してもジェネシスブロックを生成しておく必要があります。
初期化してないとpeerできないようなので。
https://geth.ethereum.org/docs/interface/private-network#running-member-nodes

また、1つ目と同じgenesis.jsonを使う必要があります。
private_net/genesis.jsonprivate_net/2genesis.json, private_net3/genesis.json, private_net4/genesis.jsonにコピーしておきます。

app2, app3, app4の各コンテナ内でGethの初期化コマンドを実行し、ジェネシスブロックを生成しておきます。

docker-compose exec app2 /bin/sh

/geth # geth --datadir /geth/ init /geth/genesis.json

bootnodeを使ってGethを起動

Geth実行中のapp1コンテナで接続情報を取得します。

# app1コンテナ
> admin.nodeInfo
{
  enode: "enode://73fede78d501324558c617209c50b8a5bc8621fee7e2649c69d149529307e952e8c1f5d9f19f06214eccc8a51919231ffb6ad329e2059c7e61f99d2ea60048a0@127.0.0.1:30303?discport=0",
  enr: "enr:-Jy4QCCMqPvwcXdXU4nYgETymfMy4U7w7X1RuSGs5hV_-oWMEAWOrQQoiKVQ3edJs9WWNRydPd3TDBSqhOTfenOYNkuGAYI_liUog2V0aMfGhL7GGDCAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQJz_t541QEyRVjGFyCcULilvIYh_ufiZJxp0UlSkwfpUoRzbmFwwIN0Y3CCdl8",
  id: "b898c20aa7c1283c0c16088875226e89697e6ebcb2189d64c5de10c2f5dc86e7",
  ip: "127.0.0.1",
  listenAddr: "[::]:30303",
  name: "Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4",
  ports: {
    discovery: 0,
    listener: 30303
  },
  protocols: {
    eth: {
      config: {
        berlinBlock: 0,
        byzantiumBlock: 0,
        chainId: 22,
        constantinopleBlock: 0,
        eip150Block: 0,
        eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000",
        eip155Block: 0,
        eip158Block: 0,
        homesteadBlock: 0,
        istanbulBlock: 0,
        londonBlock: 0,
        petersburgBlock: 0
      },
      difficulty: 131072,
      genesis: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
      head: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
      network: 22
    },
    snap: {}
  }
}

上記の
enode: "enode://73fede78d501324558c617209c50b8a5bc8621fee7e2649c69d149529307e952e8c1f5d9f19f06214eccc8a51919231ffb6ad329e2059c7e61f99d2ea60048a0@127.0.0.1:30303?discport=0"を使用します。

ただ、今回はコンテナ同士の接続ということで、
127.0.0.1:30303?discport=0の部分を
app1:30303
とします。
(30303ポートはGeth起動時のデフォルトポート)

まずはapp2コンテナでGethを起動させてみます。

/ # geth --networkid "22" --datadir /geth --bootnodes "enode://73fede78d501324558c617209c50b8a5bc8621fee7e2649c69d149529307e952e8c1f5d9f19f06214eccc8a51919231ffb6ad329e2059c7e61f99d2ea60048a0@app1:30303" console 2>> /geth/info.log

app1のGethとapp2のGethが接続されているか確認します。
app2コンテナでadmin.peersを実行。

# app2コンテナ
> admin.peers
[{
    caps: ["eth/66", "eth/67", "snap/1"],
    enode: "enode://73fede78d501324558c617209c50b8a5bc8621fee7e2649c69d149529307e952e8c1f5d9f19f06214eccc8a51919231ffb6ad329e2059c7e61f99d2ea60048a0@172.22.0.4:30303",
    id: "b898c20aa7c1283c0c16088875226e89697e6ebcb2189d64c5de10c2f5dc86e7",
    name: "Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4",
    network: {
      inbound: false,
      localAddress: "172.22.0.3:56428",
      remoteAddress: "172.22.0.4:30303",
      static: false,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 131072,
        head: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
        version: 67
      },
      snap: {
        version: 1
      }
    }
}]

enode://73fede78d501324558c617209c50b8a5bc8621fee7e2649c69d149529307e952e8c1f5d9f19f06214eccc8a51919231ffb6ad329e2059c7e61f99d2ea60048a0を見ると、app1admin.nodeInfo.enodeと一致しています。

また、app1コンテナでadmin.peersを実行すると

# app1コンテナ
> admin.peers
[{
    caps: ["eth/66", "eth/67", "snap/1"],
    enode: "enode://24fc738ee506109b85a4072b3a22b9d41e65be47a4d8d771bc6532cdcde929ff67d5e898a27acb23f43e791602c7301b9e30ff366a780f18e7ffe5539ce73883@172.22.0.3:56428",
    id: "597444e34fd9a17d0dca5f23e0e8e6017af3518112cb5d30f05bf112136f7ba9",
    name: "Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4",
    network: {
      inbound: true,
      localAddress: "172.22.0.4:30303",
      remoteAddress: "172.22.0.3:56428",
      static: false,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 131072,
        head: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
        version: 67
      },
      snap: {
        version: 1
      }
    }
}]

app2コンテナでadmin.nodeInfo.enodeを実行すると

> admin.nodeInfo.enode
"enode://24fc738ee506109b85a4072b3a22b9d41e65be47a4d8d771bc6532cdcde929ff67d5e898a27acb23f43e791602c7301b9e30ff366a780f18e7ffe5539ce73883@127.0.0.1:30303"

enodeの@以前が一致しています。

app2とapp3を接続する

app1app2がつながったので、次はapp2app3をつなげてみます。

app3でも同様にジェネシスブロックを生成した後、bootnodes。

geth --networkid "22" --datadir /geth --bootnodes "enode://24fc738ee506109b85a4072b3a22b9d41e65be47a4d8d771bc6532cdcde929ff67d5e898a27acb23f43e791602c7301b9e30ff366a780f18e7ffe5539ce73883@app2:30303" console 2>> /geth/info.log

app3admin.peersを確認してみます。

# app3コンテナ
> admin.peers
[{
    caps: ["eth/66", "eth/67", "snap/1"],
    enode: "enode://24fc738ee506109b85a4072b3a22b9d41e65be47a4d8d771bc6532cdcde929ff67d5e898a27acb23f43e791602c7301b9e30ff366a780f18e7ffe5539ce73883@172.22.0.3:30303",
    id: "597444e34fd9a17d0dca5f23e0e8e6017af3518112cb5d30f05bf112136f7ba9",
    name: "Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4",
    network: {
      inbound: false,
      localAddress: "172.22.0.5:50870",
      remoteAddress: "172.22.0.3:30303",
      static: false,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 131072,
        head: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
        version: 67
      },
      snap: {
        version: 1
      }
    }
}]

app2コンテナのenodeが表示されています。
app3コンテナのenodeを確認しておくと、

> admin.nodeInfo.enode
"enode://b65ffdace6debb20ffad982672b098c1d26144c122f7504c6df805dae1da70a346226a2df0506efa282ec41479316b618150a491be83537ef017d3c35606e95a@127.0.0.1:30303"

です。

app2コンテナのadmin.peersを確認してみます。

# app2コンテナ
> admin.peers
[{
    caps: ["eth/66", "eth/67", "snap/1"],
    enode: "enode://b65ffdace6debb20ffad982672b098c1d26144c122f7504c6df805dae1da70a346226a2df0506efa282ec41479316b618150a491be83537ef017d3c35606e95a@172.22.0.5:50870",
    id: "7f4154f1463bb921cfaa6b995bc9a7ca42244a8487e163111713e5eabc6222f6",
    name: "Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4",
    network: {
      inbound: true,
      localAddress: "172.22.0.3:30303",
      remoteAddress: "172.22.0.5:50870",
      static: false,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 131072,
        head: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
        version: 67
      },
      snap: {
        version: 1
      }
    }
}, {
    caps: ["eth/66", "eth/67", "snap/1"],
    enode: "enode://73fede78d501324558c617209c50b8a5bc8621fee7e2649c69d149529307e952e8c1f5d9f19f06214eccc8a51919231ffb6ad329e2059c7e61f99d2ea60048a0@172.22.0.4:30303",
    id: "b898c20aa7c1283c0c16088875226e89697e6ebcb2189d64c5de10c2f5dc86e7",
    name: "Geth/v1.10.21-unstable-14b0eeda-20220727/linux-amd64/go1.18.4",
    network: {
      inbound: false,
      localAddress: "172.22.0.3:56428",
      remoteAddress: "172.22.0.4:30303",
      static: false,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 131072,
        head: "0x119a5666fc2fc8879b3d6da3ac58335c6f78cfd5be0ae85909acfc98ed09b937",
        version: 67
      },
      snap: {
        version: 1
      }
    }
}]

配列の中身が2つに増えて、enodeの@マーク以前がそれぞれapp1app3のものと一致しているのが確認できます。

app3とapp4を接続する

これまで同様なので割愛します。

これで
app1 - app2 - app3 - app4と繋がりました。

アカウント作成とマイニング

app1のアカウントからapp4のアカウントへ送金してみます。
まずアカウントを作成します。

# app1
> personal.newAccount("hoge")
"0x3b701e006aed28e7b98bb1112438e380b5399760"
# app4
> personal.newAccount("hoge")
"0x709f545704f037f0342e94c04387046bbc434f74"

マイニング用のアカウントも必要なので作成して、マイニングを開始します。
今回はapp2に作ってみます。

# app2コンテナ
> personal.newAccount("hoge")
"0xc077acc1d8d5f910c5e7d7c339b80004f8221efd"
> miner.start(1)
null

しばらくするとマイニングが開始されて、ブロックの数が増えていきます。

# app2コンテナ
> eth.blockNumber
31

他のコンテナでも同様にブロック数が増えているのが確認できると思います。

送金

それでは送金してみます。
ただ、app1のアカウントは残高が0なので、app2のアカウントのeth(マイニング報酬で得られている)をapp1のアカウントに送金します。

送金時はアカウントのアンロックが必要です。

# app2
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xc077acc1d8d5f910c5e7d7c339b80004f8221efd
Passphrase: 
true

app1のアカウントのアドレスを確認して送金します。

# app2
> eth.sendTransaction({from: eth.accounts[0], to: "0x3b701e006aed28e7b98bb1112438e380b5399760", value: 300000000000000})
"0x868a0f18698a9ed81d3159d290d4e2de1ab4593fdbbee9a05e3ed11db6b27d49"

app1のアカウントの残高を確認してみます。

# app1
> eth.getBalance(eth.accounts[0])
300000000000000

送金されていました。

それでは、app1のアカウントからapp4のアカウントへ送金してみます。
app4のアカウントのアドレスは"0x4e7489808fa24f2f6297a651d420895d8497069da7370710593230b835d112bdです。

# app1コンテナ
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x3b701e006aed28e7b98bb1112438e380b5399760
Passphrase: 
true

> eth.sendTransaction({from: eth.accounts[0], to: "0x709f545704f037f0342e94c04387046bbc434f74", value: 100000000000000})
"0x4e7489808fa24f2f6297a651d420895d8497069da7370710593230b835d112bd"

> eth.getBalance(eth.accounts[0])
178999999853000

送金後に残高が減っています。
送金にはガス代がかかるのでその分も減っています。

app4のアカウントを確認します。

# app4コンテナ
> eth.getBalance(eth.accounts[0])
100000000000000

送金できていました。

Discussion