在 Docker 上執行 Ethereum

September 19, 2018

這陣子在上 Ethereum 的線上課程,其中課程章節中有一段需要你安裝 Ethereum 的環境 - go-ethereum,也就是 Ethereum 的 Client 端,你可以透過 geth 指令與整個區塊鏈做互動,例如查詢區塊資訊等其他的功能。

在課程的內容中,請學生使用虛擬機安裝 Ubuntu,並在上面安裝 go-ethereum,對於像是 geth 這種指令操作介面的程式,安裝整個 GUI 真的是不太需要啊,後來發現 go-ethereum 有提供 Docker 的映像檔,讓你可以透過 Docker 來安裝 go-ethereum

本文主要是記錄如何在 Docker 上安裝 ethereum,並且同步區塊鏈上的資訊。

🐳 Dockerfile

雖然可以直接透過 docker pull 將映像檔直接抓下來使用,但是秉持著研究的精神,稍微理解一下官方的 Dockerfile 內容:

# Build Geth in a stock Go builder container
FROM golang:1.10-alpine as builder

RUN apk add --no-cache make gcc musl-dev linux-headers

ADD . /go-ethereum
RUN cd /go-ethereum && make geth

# Pull Geth into a second stage deploy alpine container
FROM alpine:latest

RUN apk add --no-cache ca-certificates
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/

EXPOSE 8545 8546 30303 30303/udp
ENTRYPOINT ["geth"]

go-ethereum 的 Dockerfile 使用了多階段構建(Multiple Stage Builds),有興趣可以參考我之前寫的「透過 Multiple Stage Builds 編譯出最小的 Docker Image」,前面四行主要是將整個 Geth 給 Builds 出來後,接著使用 alpine 執行前面構建後的結果,也就是 Build 出 Geth 這個 binary 的執行檔:geth,當容器執行後,會執行 geth 的命令。

⛓ 拉取映像檔並建立容器

請執行以下命令拉取映像檔:

$ docker pull ethereum/client-go

拉取完畢後,使用這個映像檔來建立一個新的 Container:

$ docker run -d --name eth-ropsten-node -v $HOME/geth/ropsten:/root \
  -p 8545:8545 -p 30303:30303 \
  ethereum/client-go \
  --testnet \
  --syncmode "fast" \
  --cache=1024

稍微說明一下這段指令:

  • docker run:執行一個新的 Container
  • -d:在背景執行 Container 並且列印出 Container 的 ID
  • --name eth-ropsten-node:Container 的名稱
  • -v:綁定 Volume 的位置,$HOME/get/ropsten 是本機端的目錄,對映到 Container 的 root 目錄
  • -p 8545:8545 -p 30303:30303:要 expose 的 port
  • ethereum/client-go:指定映像檔名稱,使用我們剛剛拉取回來的映像檔
  • --testnet--syncmode "fast"--cache=1024:當容器起來後,會執行 ENTRYPOINT 的指令,可以參考上面的 Dockerfile

因為針對課程內容需要,加入了一些額外的參數:

  • 使用測試鏈的資料,加上 --testnet(使用 Ropsten 測試鏈),預設會同步主鏈
  • 設定同步模式:--syncmode "fast"(模式有: fastfulllight
  • 設定區塊的 cache 大小為 1024(預設就是為 1024,可以省略不打)

⚠️ 注意:同步模式的參數請不要打成 --fast,因為在新的 geth 中已經使用 --syncmode 取代 --fast

這樣容器就已經建立成功了,使用 docker ps 查看:

screenshot-2018-09-19-12.02.56

🔁 查看區塊同步狀況

請輸入以下的指令:

$ docker exec -it eth-ropsten-node geth attach ipc:/root/.ethereum/testnet/geth.ipc

一般來說,如果你是使用主鏈的話,可以直接使用 geth attach,因為上方設定為測試鏈,所以必須加上後面的路徑。

接著你可以看到以下畫面,輸入 eth.syncing 來查看區塊同步的情況:

screenshot-2018-09-19-12.06.30

你也可以直接輸入 eth,可以看到提供許多的函式,這些函式都可以和區塊鏈做互動:

> eth
{
  accounts: [],
  blockNumber: 0,
  coinbase: undefined,
  compile: {
    lll: function(),
    serpent: function(),
    solidity: function()
  },
  defaultAccount: undefined,
  defaultBlock: "latest",
  gasPrice: 1000000000,
  hashrate: 0,
  mining: false,
  pendingTransactions: [],
  protocolVersion: "0x3f",
  syncing: {
    currentBlock: 3749807,
    highestBlock: 4069070,
    knownStates: 11312851,
    pulledStates: 11309555,
    startingBlock: 3322148
  },
  call: function(),
  contract: function(abi),
  estimateGas: function(),
  filter: function(options, callback, filterCreationErrorCallback),
  getAccounts: function(callback),
  getBalance: function(),
  getBlock: function(),
  getBlockNumber: function(callback),
  getBlockTransactionCount: function(),
  getBlockUncleCount: function(),
  getCode: function(),
  getCoinbase: function(callback),
  getCompilers: function(),
  getGasPrice: function(callback),
  getHashrate: function(callback),
  getMining: function(callback),
  getPendingTransactions: function(callback),
  getProtocolVersion: function(callback),
  getRawTransaction: function(),
  getRawTransactionFromBlock: function(),
  getStorageAt: function(),
  getSyncing: function(callback),
  getTransaction: function(),
  getTransactionCount: function(),
  getTransactionFromBlock: function(),
  getTransactionReceipt: function(),
  getUncle: function(),
  getWork: function(),
  iban: function(iban),
  icapNamereg: function(),
  isSyncing: function(callback),
  namereg: function(),
  resend: function(),
  sendIBANTransaction: function(),
  sendRawTransaction: function(),
  sendTransaction: function(),
  sign: function(),
  signTransaction: function(),
  submitTransaction: function(),
  submitWork: function()
}

例如取得第 99 個區塊的資訊:

$ > eth.getBlock(99)
{
  difficulty: 827352,
  extraData: "0xd783010502846765746887676f312e362e33856c696e7578",
  gasLimit: 15232192,
  gasUsed: 0,
  hash: "0x3dd4dc843801af12c0a6dd687642467a3ce835dca09159734dec03109a1c1f1f",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0xc2fa6dcef5a1fbf70028c5636e7f64cd46e7cfd4",
  mixHash: "0x9ccbc308c21deece51af7ba19af8815d4ca9156bf39fcf88de0b4e0893df4ff8",
  nonce: "0x11b446b03241d3cd",
  number: 99,
  parentHash: "0xc2dcbcebd1c6bcd47323e98aa3255d158b35be85a76955bb11136454fe03a5f7",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 535,
  stateRoot: "0x485756cd70dd968b133f60b8dafa3ee8db3ce014af59ea5bcaaa2042ea552273",
  timestamp: 1479653843,
  totalDifficulty: 84969728,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}

如何建立私鏈

#!/bin/sh

set -e

geth --datadir /root/.ethereum/net42 init /root/genesis42.json

geth --datadir /root/.ethereum/net42 --networkid 42
docker run -d --entrypoint "/root/run.sh" \
      --name private-chain-node \
      -v $HOME/private-chain-node:/root \
      -p 8541:8545 -p 30301:30303 \
      ethereum/client-go

Reference