在 Docker 上執行 Ethereum

這陣子在上 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