以太坊私鏈初探

最近在學習區(qū)塊鏈的技術(shù),初步打算從go-ethereum入手,學習一下以太坊的設(shè)計思想順便把GoLang入個門(簡單、直接明了,是目前這個階段需要追求的東西)。

手上有一個臺阿里云的服務(wù)器(單核1G),性能一般,平時就用來掛著自己的個站,工作不飽和,打算在上面搭一條以太坊的私鏈,方便學習。

環(huán)境

服務(wù)器:

  • 單核CPU,1G內(nèi)存,40G磁盤
  • OS:CentOS
  • OS內(nèi)核:3.10.0-693.11.1.el7.x86_64
  • 軟件包管理工具:yum

go-ethereum源碼下載

以太坊的節(jié)點有兩個版本,基于c++編寫的cpp-ethereum和基于go編寫的go-ethereum,這里選擇go語言版本,主要還是希望能順便熟悉一下這門語言
選擇一個最新的release版本1.8,下載到服務(wù)器,位置隨意,這里我們選擇放在/usr/src(存放源碼)下。

cd /usr/src/
wget https://github.com/ethereum/go-ethereum/archive/release/1.8.zip
1521190470392.jpg

解壓zip包得到我們需要的源碼文件

unzip 1.8.zip
1521195373874.jpg

安裝go語言環(huán)境

yum install golang
1521195682289.jpg

編譯go-ethereum源碼

cd go-ethereum-release-1.8/
make geth
1521197127296.jpg

到這里我們就得到了需要的以太坊節(jié)點程序/go-ethereum-release-1.8/build/bin/geth
可以把編譯好的程序拷貝到/usr/bin目錄下,方便運行

創(chuàng)建初始區(qū)塊

以太坊私鏈的初始區(qū)塊需要手動創(chuàng)建起來,否則整個區(qū)塊鏈沒法持續(xù)運行,我們從官網(wǎng)得到一段初始配置

{
  "config": {
        "chainId": 0,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
  "alloc"      : {},
  "coinbase"   : "0x0000000000000000000000000000000000000000",
  "difficulty" : "0x20000",
  "extraData"  : "",
  "gasLimit"   : "0x2fefd8",
  "nonce"      : "0x0000000000000042",
  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp"  : "0x00"
}

創(chuàng)建一個文件,并復(fù)制初始配置進去

touch init.json

接下來就可以完成初始區(qū)塊的配置了

geth  --datadir chaindata  init init.json

--datadir 參數(shù)指定區(qū)塊鏈的數(shù)據(jù)存儲位置,可以根據(jù)需求自己指定

啟動區(qū)塊鏈并開放RPC接口

geth --rpc --rpcaddr "*.*.*.*" --rpccorsdomain "*" --datadir "chaindata" --rpcport "8545" --rpcapi "db,eth,net,web3" --networkid 31415926 console 2>>log.txt

--rpc 啟動RPC協(xié)議
--rpcaddr 指定服務(wù)器ip地址
--rpccorsdomain 設(shè)置允許訪問的域名
--datadir 區(qū)塊鏈數(shù)據(jù)存儲位置,要與初始化時保持一致
--rpcport PRC協(xié)議接口
--rpcapi RPC支持的API
--networkid 區(qū)塊鏈網(wǎng)絡(luò)ID,用于發(fā)現(xiàn)節(jié)點
最后把日志重定向到log.txt文件中,方便我們查看

當然還需要去阿里云的服務(wù)器配置中把TCP的8545端口打開


1521198807402.jpg

到這里節(jié)點就已經(jīng)啟動了

創(chuàng)建賬戶并啟動挖礦

創(chuàng)建賬戶,指定密碼
personal.newAccount('pwd')
綁定賬戶到挖礦程序
miner.setEtherbase(eth.accounts[0])
開啟挖礦,指定線程數(shù)
miner.start(2)
停止挖礦
miner.stop()

如果挖礦啟動失敗,可以檢查一下是否綁定過賬戶miner.setEtherbase(eth.accounts[0])
這里miner.start(2)可以根據(jù)機器自身的性能指定需要啟動的線程數(shù)。但是我在啟動miner之后并沒有開始挖礦,也沒有報錯,仔細閱讀了以太坊的相關(guān)資料之后發(fā)現(xiàn)正常情況下以太坊節(jié)點是通過POW(proof-of-work)的方式產(chǎn)生新的區(qū)塊,如果機器性能比較低可能并不會產(chǎn)生新的區(qū)塊,或者生成新區(qū)塊的速度會非常慢。

如果單純是為了測試開發(fā)使用,可以通過--dev參數(shù)初始化一條測試私鏈。--dev參數(shù)會創(chuàng)建一個使用POA(proof-of-authority)的共識網(wǎng)絡(luò),默認預(yù)分配一個開發(fā)者賬戶并且會自動開啟挖礦。
可以通過下面的方式直接創(chuàng)建:

geth --rpc --rpcaddr "*.*.*.*" --rpccorsdomain "*" --datadir "chaindata" --rpcport "8545" --rpcapi "db,eth,net,web3" --networkid 31415926 --dev console 2>>log.txt

用pm2監(jiān)控geth

為了保證終端回話關(guān)閉之后geth還能正常運行,并能處理RPC請求必須要以守護進程(daemon)的方式啟動,這里有幾種方式:

  • nohup 命令
  • Systemd 工具
  • pm2 工具
    當然還有其他的方式,這三種是我比較常用的,因為機器上有個node的服務(wù)正在用pm2管理,這里也正好借用pm2工具管理一下geth
    首先創(chuàng)建一個pm2啟動配置文件:
touch start.json

配置參數(shù)
{
    "name": "geth",
    "script": "geth",
    "args": "--rpc --rpcaddr '*.*.*.*' --rpccorsdomain '*' --datadir 'chaindata' --rpcport '8545' --rpcapi 'db,eth,net,web3' --networkid 31415926 --dev",
    "log_date_format": "YYYY-MM-DD HH:mm Z",
    "merge_logs": false,
    "watch": false,
    "max_restarts": 10,
    "exec_interpreter": "none",
    "exec_mode": "fork_mode"
}

pm2 start start.json
1521259248462.png

geth以daemon的方式啟動并通過pm2進行監(jiān)控,接下來就可以通過RPC的方式通信了

RPC通信

以太坊RPC的接口列表可以參考:https://ethereum.gitbooks.io/frontier-guide/content/rpc.html
這里遇到一個巨大的坑,感謝安全工程師何處不可憐系統(tǒng)化的方法的幫助,讓我能一步步定位到問題。
我們以eth_accounts試一下:
因為服務(wù)器IP地址綁定了域名,然后調(diào)用方式可以這樣寫:

curl  -X POST --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}' http://domain:8545

然后就不正常了,直接報錯

invalid host specified

感覺應(yīng)該是域名解析的問題,但是試了一下其他端口的服務(wù)一切正常,去DNS解析服務(wù)器看過也沒問題。然后更換服務(wù)端口依舊報錯。似乎問題不在DNS解析和端口上
前端抓包顯示403,
后端tcptump監(jiān)聽結(jié)果

14:54:50.651476 IP *.*.*.*.55747 > dawn.8545: Flags [P.], seq 2542259482:2542260075, ack 1963893059, win 4100, options [nop,nop,TS val 1412772142 ecr 4250673300], length 593
14:54:50.652009 IP dawn.8545 > *.*.*.*.55747: Flags [P.], seq 1:181, ack 593, win 255, options [nop,nop,TS val 4250708302 ecr 1412772142], length 180
14:54:50.657020 IP *.*.*.*.55747 > dawn.8545: Flags [.], ack 181, win 4094, options [nop,nop,TS val 1412772147 ecr 4250708302], length 0
14:54:50.685458 IP *.*.*.*.55747 > dawn.8545: Flags [P.], seq 593:1161, ack 181, win 4096, options [nop,nop,TS val 1412772176 ecr 4250708302], length 568
14:54:50.685546 IP dawn.8545 > *.*.*.*.55747: Flags [P.], seq 181:361, ack 1161, win 264, options [nop,nop,TS val 4250708336 ecr 1412772176], length 180
14:54:50.689829 IP *.*.*.*.55747 > dawn.8545: Flags [.], ack 361, win 4090, options [nop,nop,TS val 1412772179 ecr 4250708336], length

有來有往,似乎又沒啥問題。
修改請求地址,換ip直接訪問,200,居然通了??偨Y(jié)一下嘗試的結(jié)果:

  • DNS解析正常
  • 端口正常
  • IP訪問正常(服務(wù)正常)
  • 域名訪問異常

在服務(wù)正常DNS正常的情況想通過域名請求不到,問題很可能是服務(wù)本身的限制。參考wiki,---rpccorsdomain參數(shù)配置為“*”沒有問題,允許所有域名訪問。然后再找似乎也沒有可用的參數(shù)了。這個時候只能讀一下源碼,看看是不是能找到思路。之后就是找到go-ethereumRPC模塊的代碼,一點點讀。最后發(fā)現(xiàn)了這么一段代碼

// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler
func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // if r.Host is not set, we can continue serving since a browser would set the Host header
    if r.Host == "" {
        h.next.ServeHTTP(w, r)
        return
    }
    host, _, err := net.SplitHostPort(r.Host)
    if err != nil {
        // Either invalid (too many colons) or no port specified
        host = r.Host
    }
    if ipAddr := net.ParseIP(host); ipAddr != nil {
        // It's an IP address, we can serve that
        h.next.ServeHTTP(w, r)
        return

    }
    // Not an ip address, but a hostname. Need to validate
    if _, exist := h.vhosts["*"]; exist {
        h.next.ServeHTTP(w, r)
        return
    }
    if _, exist := h.vhosts[host]; exist {
        h.next.ServeHTTP(w, r)
        return
    }
    http.Error(w, "invalid host specified", http.StatusForbidden)
}
// DefaultConfig contains reasonable default settings.
var DefaultConfig = Config{
    DataDir:          DefaultDataDir(),
    HTTPPort:         DefaultHTTPPort,
    HTTPModules:      []string{"net", "web3"},
    HTTPVirtualHosts: []string{"localhost"},
    WSPort:           DefaultWSPort,
    WSModules:        []string{"net", "web3"},
    P2P: p2p.Config{
        ListenAddr: ":30303",
        MaxPeers:   25,
        NAT:        nat.Any(),
    },
}

到這里應(yīng)該差不多能猜到,有設(shè)置vhosts的地方,然后用了一個比較笨的辦法

[dawn@dawn ~]$ geth --help|grep vhosts
  --rpcvhosts value      Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost")
[dawn@dawn ~]$

果然是有--rpcvhosts參數(shù)可以設(shè)置,修改配置

"args": "--rpc --rpcaddr '*.*.*.*' --rpccorsdomain '*' --datadir 'chaindata' --rpcport '8545' --rpcapi 'db,eth,net,web3' --networkid 31415926 --dev --rpcvhosts '*'"

重啟服務(wù)

pm2 restart start.json

再次通過域名訪問,200,果然通了,但是返回:

invalid content type, only application/json is supported

這就好處理了,增加content-type設(shè)置

請求
curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}' http://domain:8545
返回數(shù)據(jù)
{"jsonrpc":"2.0","id":1,"result":["0x8018e73d7efc27297ea313e8bd250a02c6ca9f14","0xe3207f6fb2816fead3ccba99ebd2ea9f3ff22231"]}

到這里一切就都調(diào)通了,后續(xù)的鏈上操作就可以通過RPC服務(wù)直接操作了。

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

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

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