使用Docker搭建以太坊私有鏈并部署合約

閱讀完本文,你將可以在一臺(tái)物理機(jī)上搭建起一個(gè)完整的以太坊私鏈,并且部署自己的合約。用以開(kāi)發(fā)、調(diào)試、學(xué)習(xí)以太坊。

本文涉及的知識(shí)和技術(shù)有:

  • Docker:一種時(shí)下流行的容器
  • geth:以太坊客戶(hù)端的go實(shí)現(xiàn)
  • truffle:以太坊合約

Docker環(huán)境搭建

關(guān)于如何搭建Docker環(huán)境,可以參考我之前的一篇文章。以下的內(nèi)容都是基于該文搭建的環(huán)境。如果你已經(jīng)有了可以運(yùn)行的Docker環(huán)境,可以忽略。

文章地址:http://m.itdecent.cn/p/7ca57e4f66c5

主機(jī)配置

此處需要補(bǔ)充一點(diǎn)的是,如果您使用的是虛擬機(jī)、云主機(jī)或者老破小機(jī)器,需要確保分配給主機(jī)的內(nèi)存至少2GB或以上。因?yàn)橐蕴粸榱说钟忍卮箨戇@樣的ASIC礦機(jī)對(duì)算力的壟斷,采用了和比特幣完全不同的PoW算法——ethash。該算法的特點(diǎn)是算力不敏感,內(nèi)存敏感。該算法目前需要在內(nèi)存創(chuàng)建大約1GB的DAG用來(lái)做PoW運(yùn)算,且DAG會(huì)隨著區(qū)塊的增加呈階梯狀增長(zhǎng),因此建議至少給挖礦節(jié)點(diǎn)的宿主機(jī)器分配至少2GB的內(nèi)存,且保留擴(kuò)大內(nèi)存容量的靈活性。

獲取geth鏡像

docker hub上有現(xiàn)成的geth鏡像。直接獲取。

docker pull ethereum/client-go:v1.8.12

試運(yùn)行一下

docker run -it --rm -v /workspace:/workspace --entrypoint /bin/sh ethereum/client-go:v1.8.12

此處修改鏡像默認(rèn)的entrypoint是為了不讓節(jié)點(diǎn)自動(dòng)運(yùn)行。我們稍后會(huì)對(duì)節(jié)點(diǎn)進(jìn)行自定義配置使其成為私有鏈節(jié)點(diǎn)。
-v將本地的/workspace目錄掛載成容器的/workspace目錄,用來(lái)在容器和宿主機(jī)器之間共享文件

創(chuàng)建Docker網(wǎng)絡(luò)

舊版本的docker容器相互之間是依靠link建立關(guān)系。
新版本docker推薦創(chuàng)建自有網(wǎng)路,再將需要互聯(lián)的容器配置到相同的網(wǎng)絡(luò)中。
于是,我們創(chuàng)建一個(gè)名為“ethnet“的網(wǎng)絡(luò)。該網(wǎng)絡(luò)配置如下:

  • 子網(wǎng)172.18.0.0/16
    • IP段172.18.0.0
    • 掩碼255.255.0.0
    • IP范圍172.18.0.1~172.18.255.254
    • IP廣播172.18.255.255
docker network create -d bridge --subnet=172.18.0.0/16 ethnet
docker network ls

配置以太坊網(wǎng)絡(luò)

運(yùn)行如下命令進(jìn)入一個(gè)容器:

docker run -it --rm --network ethnet --ip 172.18.0.50 -v /workspace:/workspace --entrypoint /bin/sh ethereum/client-go:v1.8.12

--network ethnet參數(shù)指定了該容器加入剛才創(chuàng)建的ethnet網(wǎng)絡(luò)
--ip 172.18.0.5指定了一個(gè)固定IP給該容器。

創(chuàng)建賬戶(hù)

首先,在容器內(nèi)的/workspace目錄創(chuàng)建如下目錄結(jié)構(gòu)和文件:

dapp\
dapp\miner\
dapp\data\
dapp\genesis.json

然后運(yùn)行如下命令創(chuàng)建賬戶(hù):

cd /workspace/dapp/miner
geth -datadir ./data account new

輸入兩次password,獲得地址。將地址記錄下來(lái),后面要用到。

重復(fù)如上步驟可以創(chuàng)建多個(gè)賬戶(hù)。

創(chuàng)建創(chuàng)世區(qū)塊

編輯剛才創(chuàng)建的文件dapp\data\genesis.json

{
  "config": {
    "chainId": 88,
    "homesteadBlock": 0,
    "eip155Block": 0,
    "eip158Block": 0
  },
  "alloc"      : {
    "0x4c283287839fd441b8c8d18771321bc06a81edae": {"balance": "100000000000000000000"},
    "0x8dadb9ec6a82413c389e677baa9e7b589fe92b68": {"balance": "1000000000000000000"},
    "0x39c70ad710e07953dea150b1a5f1d0534bf0f135": {"balance": "1000000000000000000"},
    "0xb9592b51d40d66bc583f8ed2755861372fb4afb0": {"balance": "1000000000000000000"}
  },
  "coinbase"   : "0x0000000000000000000000000000000000000000",
  "difficulty" : "0x400",
  "extraData"  : "",
  "gasLimit"   : "0x2fefd8",
  "nonce"      : "0x0000000000000000",
  "mixhash"    :
  "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash" :
  "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp"  : "0x00"
}
  • genesis.json是用來(lái)創(chuàng)建創(chuàng)世區(qū)塊的配置文件
  • 加入同一私鏈的節(jié)點(diǎn)必須使用同一配置文件
  • chainid是私鏈網(wǎng)絡(luò)的標(biāo)識(shí),可以是任意數(shù)字。
  • 即使chainid相同,如果genesis.json配置不一樣,也將是兩個(gè)不兼容的網(wǎng)絡(luò)
  • alloc下面列舉了4個(gè)賬戶(hù)地址,分別是上一步驟創(chuàng)建并記錄下來(lái)的地址。
  • balance是創(chuàng)世區(qū)塊為每個(gè)賬戶(hù)分配的初始以太幣。這里看似分配了很多,其實(shí)單位是wei。1eth=10^18wei。也就是除了第一個(gè)賬戶(hù)給了100eth外,其它幾個(gè)賬戶(hù)分別只擁有1eth。這里給第一個(gè)賬戶(hù)多分點(diǎn),是因?yàn)槲覀冎笮枰盟鼇?lái)部署合約。

完成以太坊網(wǎng)絡(luò)配置

此時(shí)可以退出剛才的容器。由于我們運(yùn)行容器是加了--rm參數(shù),剛才的容器會(huì)被刪除,但宿主機(jī)的/workspace下的文件會(huì)被保存下來(lái)。

挖礦

上述步驟只是配置好了一個(gè)以太坊私有網(wǎng)絡(luò),并沒(méi)有真正創(chuàng)建網(wǎng)絡(luò)。我們知道,以太坊網(wǎng)絡(luò)是一個(gè)分布式的網(wǎng)絡(luò),有了礦工,才有的網(wǎng)絡(luò)。于是,我們首先得有一個(gè)礦工。

創(chuàng)建“主”礦工節(jié)點(diǎn)

我們接下來(lái)打算創(chuàng)建的礦工節(jié)點(diǎn),成為“主”礦工,因?yàn)樗枰獡碛腥缦绿匦裕?/p>

  • 它是一個(gè)容器,并且是持久的容器
  • 它會(huì)自動(dòng)讀取genesis.json文件,并初始化以太坊網(wǎng)絡(luò)
  • 它能夠連接其它節(jié)點(diǎn)(容器)
  • 它能夠接受各種rpc調(diào)用,并能夠部署合約
  • 它已經(jīng)配置好挖礦賬戶(hù),可以一鍵挖礦

于是,我們按照這個(gè)要求,開(kāi)始一步步創(chuàng)建礦工節(jié)點(diǎn)。

創(chuàng)建entrypoint腳本

創(chuàng)建一個(gè)文件:/workspace/dapp/init.sh
文件內(nèi)容如下:

#!/bin/sh
geth -datadir ~/data/ init /workspace/traceability/genesis.json

if [  $# -lt 1 ]; then 
  exec "/bin/sh"
else
  exec /bin/sh -c "$@"
fi

該腳本的功能是讓以太坊節(jié)點(diǎn)(容器)自動(dòng)初始化以太坊網(wǎng)絡(luò),并且接受一個(gè)自動(dòng)運(yùn)行腳本作為輸入。

創(chuàng)建自動(dòng)運(yùn)行腳本

創(chuàng)建一個(gè)文件:/workspace/dapp/mine.sh
內(nèi)容如下:

#!/bin/sh
cp -r /workspace/dapp/miner/data/keystore/* ~/data/keystore/
geth -datadir ~/data/ --networkid 88 --rpc --rpcaddr "172.18.0.50" --rpcapi admin,eth,miner,web3,personal,net,txpool --unlock "0x4c283287839fd441b8c8d18771321bc06a81edae" --etherbase "0x4c283287839fd441b8c8d18771321bc06a81edae" console
  • 第一行命令是將剛才生成的賬戶(hù)私鑰文件拷貝到容器的home目錄下。因?yàn)?workspace是宿主目錄掛載的,并不是linux文件系統(tǒng),直接將datadir指定到該目錄會(huì)導(dǎo)致geth報(bào)錯(cuò)。
  • 第二行命令是啟動(dòng)以太坊節(jié)點(diǎn)的命令。
    • --networkid 88指定了networkid,這個(gè)必須與genesis.json內(nèi)設(shè)置保持一致
    • --rpc --rpcaddr "172.18.0.50" --rpcapi .... 這些參數(shù)表示該節(jié)點(diǎn)接受rpc,并且指定了rpc的協(xié)議
    • --unlock "0x..." 加入該參數(shù)會(huì)需要用戶(hù)輸入賬戶(hù)密碼。密碼校驗(yàn)后會(huì)解鎖該賬戶(hù)。賬戶(hù)解鎖后,該節(jié)點(diǎn)就能使用此賬戶(hù)的私鑰進(jìn)行簽名加密等動(dòng)作,用以進(jìn)行交易、發(fā)布合約等。
    • --etherbase 參數(shù)指定了挖礦收益賬戶(hù)

創(chuàng)建容器

docker run -it --name=miner --network ethnet --ip 172.18.0.50 --hostname node -v /workspace:/workspace --entrypoint /workspace/dapp/init.sh ethereum/client-go:v1.8.12 /workspace/dapp/mine.sh

該命令會(huì)創(chuàng)建一個(gè)持久化的容器。容器的entrypoint和自動(dòng)運(yùn)行腳本指定為我們剛創(chuàng)建的那兩個(gè)腳本。

創(chuàng)建“從”礦工節(jié)點(diǎn)

只有一個(gè)節(jié)點(diǎn)的網(wǎng)絡(luò),怎么看都不像“分布式”網(wǎng)絡(luò)。所以我們需要?jiǎng)?chuàng)建更多的節(jié)點(diǎn)來(lái)形成一個(gè)“分布式網(wǎng)絡(luò)”。我們稱(chēng)這些節(jié)點(diǎn)叫做“從”礦工。
這類(lèi)礦工不需要交易,不需要發(fā)布合約,因此不需要unlock賬戶(hù),也不需要接受rpc。它們只知道埋頭挖礦。

創(chuàng)建自動(dòng)運(yùn)行腳本

“從”礦工節(jié)點(diǎn)和“主”礦工節(jié)點(diǎn)共享entrypoint,以保證它們創(chuàng)建出完全相同的網(wǎng)絡(luò)。
只有自動(dòng)運(yùn)行腳本不太一樣,/workspace/dapp/node.sh:

#!/bin/sh
cp -r /workspace/dapp/miner/data/keystore/* ~/data/keystore/
geth -datadir ~/data/ --networkid 88 console

創(chuàng)建容器

docker run -it --name=node1 --network ethnet --ip 172.18.0.51 --hostname node1 -v /workspace:/workspace --entrypoint /workspace/dapp/init.sh ethereum/client-go:v1.8.12 /workspace/dapp/node.sh

操作節(jié)點(diǎn)

以上創(chuàng)建出了多個(gè)以太坊節(jié)點(diǎn),運(yùn)行在同一網(wǎng)絡(luò)下。每個(gè)節(jié)點(diǎn)都可以執(zhí)行如下操作。供參考。

節(jié)點(diǎn)發(fā)現(xiàn)

查看節(jié)點(diǎn)信息

geth -datadir ~/data/ --networkid 88 console
>admin.nodeInfo.enode

配置靜態(tài)節(jié)點(diǎn)文件

~/data/geth/static-nodes.json

[
    "enode://<node public key>@<node IP address>:<node port>",
    ...
]

查看連接上的節(jié)點(diǎn)

geth -datadir ~/data/ --networkid 88 console
>admin.peers

動(dòng)態(tài)添加節(jié)點(diǎn)

>admin.addPeer("enode://<node public key>@<node IP address>:<node port>")

挖礦

啟動(dòng)miner容器

>miner.start(1)
  • 參數(shù)1指定了挖礦的線程數(shù)。
  • 首次啟動(dòng)節(jié)點(diǎn)會(huì)消耗大約20~30分鐘產(chǎn)生DAG
  • 某開(kāi)始挖礦后,其它節(jié)點(diǎn)將會(huì)收到新區(qū)塊并打印

部署合約

創(chuàng)建truffle鏡像

由于沒(méi)有找到好用的truffle鏡像,我自己創(chuàng)建了一個(gè)。Dockerfile內(nèi)容如下:

FROM alpine:3.8
MAINTAINER Cary Tan tx-cary@163.com
ENV PS1='[truffle@docker $PWD]\$ '
RUN echo "http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.8/main" > /etc/apk/repositories \
      && echo "http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.8/community" >> /etc/apk/repositories \
      && apk update \
      && apk add npm \
      && mkdir -p /workspace \
      && npm config set registry https://registry.npm.taobao.org \
      && npm install -g truffle
WORKDIR /workspace
CMD /bin/sh

該鏡像我已經(jīng)上傳到dockerhub了,您也可以直接下載使用:

docker pull txcary/truffle:180806

啟動(dòng)truffle容器

docker run -it --rm -v /workspace:/workspace --network ethnet txcary/truffle:180806

測(cè)試節(jié)點(diǎn)RPC

curl 172.18.0.50:8545 -X POST --data '{"id":1,"jsonrpc":"2.0","method":"eth_accounts", "params":[]}' -H "Content-Type: application/json"

curl 172.18.0.50:8545 -X POST --data '{"id":1,"jsonrpc":"2.0","method":"eth_getBalance", "params":["0x4c2832
87839fd441b8c8d18771321bc06a81edae","latest"]}' -H "Content-Type: application/json"
  • 第一條命令獲取節(jié)點(diǎn)上的賬戶(hù)
  • 第二條命令獲取賬戶(hù)余額
  • 如果這兩條命令成功了,說(shuō)明以太坊私有網(wǎng)絡(luò)搭建成功,并且節(jié)點(diǎn)rpc調(diào)用成功

新建truffle工程

truffle init

修改truffle.js

module.exports = {
        networks: {
            development: {
                host: "172.18.0.50",
                port: 8545,
                network_id: 88,
                gas: 2900000,
                gasPrice: 10000000000
            }
        }
};
  • gas使用默認(rèn)值會(huì)導(dǎo)致超限錯(cuò)誤,研究半天不知道為什么。要是搞明白的可以給我留言,謝謝!
  • gasPrice為默認(rèn)值

編寫(xiě)合約Yourname.sol

  • 合約名稱(chēng)必須和文件名一致,不然會(huì)部署失敗
    如果你還沒(méi)有自己的合約,可以上truffle官網(wǎng)下載一個(gè)做練習(xí)。
    我也做了一個(gè)供應(yīng)鏈追蹤的合約放在github上:https://github.com/txcary/traceability。該合約通過(guò)remix調(diào)試并成功部署在我的私鏈上,供參考。

新建部署腳本

2_deploy_contracts.js

var Yourname = artifacts.require("./Yourname.sol");

module.exports = function(deployer) {
  deployer.deploy(Yourname);
};

編譯合約

truffle compile

部署合約

truffle migrate --network development --verbose-rpc
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文來(lái)自:https://github.com/ethereum/wiki/wiki/%5B%E4%B8%AD%E...
    MaxZing閱讀 5,803評(píng)論 3 8
  • 原文:Smart contracts 正如我們?cè)赱intro]中看到的那樣,以太坊中有兩種不同類(lèi)型的帳戶(hù):外部擁有...
    Jisen閱讀 5,138評(píng)論 1 7
  • 新婚 16.6.5 煙花吉日迎新人, 四海賓客齊來(lái)賀。 歡笑滿堂春風(fēng)起, 婚姻美滿百年合。
    閑垂散人閱讀 236評(píng)論 2 5
  • 得先有節(jié)奏,才能遇到合拍的,不然就亂拍了。 懷著喜歡熱愛(ài)去做事情,找準(zhǔn)平衡,那不是任務(wù),沒(méi)必要打卡。和小伙伴一起開(kāi)...
    梁自由閱讀 111評(píng)論 0 0
  • 詳細(xì):Android 異步消息處理機(jī)制 讓你深入理解 Looper、Handler、Message三者關(guān)系 從源...
    君莫看閱讀 992評(píng)論 0 0

友情鏈接更多精彩內(nèi)容