最近在學(xué)習(xí)區(qū)塊鏈的技術(shù),初步打算從go-ethereum入手岳守,學(xué)習(xí)一下以太坊的設(shè)計(jì)思想順便把GoLang入個(gè)門(簡單凄敢、直接明了,是目前這個(gè)階段需要追求的東西)湿痢。
手上有一個(gè)臺阿里云的服務(wù)器(單核1G)涝缝,性能一般,平時(shí)就用來掛著自己的個(gè)站,工作不飽和拒逮,打算在上面搭一條以太坊的私鏈罐氨,方便學(xué)習(xí)。
環(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é)點(diǎn)有兩個(gè)版本,基于c++編寫的cpp-ethereum和基于go編寫的go-ethereum狠怨,這里選擇go語言版本约啊,主要還是希望能順便熟悉一下這門語言
選擇一個(gè)最新的release版本1.8,下載到服務(wù)器佣赖,位置隨意恰矩,這里我們選擇放在/usr/src(存放源碼)下。
cd /usr/src/
wget https://github.com/ethereum/go-ethereum/archive/release/1.8.zip
解壓zip包得到我們需要的源碼文件
unzip 1.8.zip
安裝go語言環(huán)境
yum install golang
編譯go-ethereum源碼
cd go-ethereum-release-1.8/
make geth
到這里我們就得到了需要的以太坊節(jié)點(diǎn)程序/go-ethereum-release-1.8/build/bin/geth
可以把編譯好的程序拷貝到/usr/bin目錄下憎蛤,方便運(yùn)行
創(chuàng)建初始區(qū)塊
以太坊私鏈的初始區(qū)塊需要手動創(chuàng)建起來外傅,否則整個(gè)區(qū)塊鏈沒法持續(xù)運(yùn)行,我們從官網(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)建一個(gè)文件俩檬,并復(fù)制初始配置進(jìn)去
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ù)存儲位置,要與初始化時(shí)保持一致
--rpcport PRC協(xié)議接口
--rpcapi RPC支持的API
--networkid 區(qū)塊鏈網(wǎng)絡(luò)ID棚辽,用于發(fā)現(xiàn)節(jié)點(diǎn)
最后把日志重定向到log.txt文件中技竟,方便我們查看
當(dāng)然還需要去阿里云的服務(wù)器配置中把TCP的8545端口打開
到這里節(jié)點(diǎn)就已經(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ù)機(jī)器自身的性能指定需要啟動的線程數(shù)。但是我在啟動miner之后并沒有開始挖礦联逻,也沒有報(bào)錯(cuò)搓扯,仔細(xì)閱讀了以太坊的相關(guān)資料之后發(fā)現(xiàn)正常情況下以太坊節(jié)點(diǎn)是通過POW(proof-of-work)的方式產(chǎn)生新的區(qū)塊,如果機(jī)器性能比較低可能并不會產(chǎn)生新的區(qū)塊包归,或者生成新區(qū)塊的速度會非常慢锨推。
如果單純是為了測試開發(fā)使用,可以通過--dev參數(shù)初始化一條測試私鏈公壤。--dev參數(shù)會創(chuàng)建一個(gè)使用POA(proof-of-authority)的共識網(wǎng)絡(luò)换可,默認(rèn)預(yù)分配一個(gè)開發(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還能正常運(yùn)行境钟,并能處理RPC請求必須要以守護(hù)進(jìn)程(daemon)的方式啟動锦担,這里有幾種方式:
- nohup 命令
- Systemd 工具
- pm2 工具
當(dāng)然還有其他的方式,這三種是我比較常用的慨削,因?yàn)闄C(jī)器上有個(gè)node的服務(wù)正在用pm2管理,這里也正好借用pm2工具管理一下geth
首先創(chuàng)建一個(gè)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
geth以daemon的方式啟動并通過pm2進(jìn)行監(jiān)控,接下來就可以通過RPC的方式通信了
RPC通信
以太坊RPC的接口列表可以參考:https://ethereum.gitbooks.io/frontier-guide/content/rpc.html
這里遇到一個(gè)巨大的坑缚态,感謝安全工程師何處不可憐系統(tǒng)化的方法的幫助磁椒,讓我能一步步定位到問題。
我們以eth_accounts試一下:
因?yàn)榉?wù)器IP地址綁定了域名玫芦,然后調(diào)用方式可以這樣寫:
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}' http://domain:8545
然后就不正常了浆熔,直接報(bào)錯(cuò)
invalid host specified
感覺應(yīng)該是域名解析的問題,但是試了一下其他端口的服務(wù)一切正常桥帆,去DNS解析服務(wù)器看過也沒問題医增。然后更換服務(wù)端口依舊報(bào)錯(cuò)。似乎問題不在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寓盗,居然通了晒杈∪乘總結(jié)一下嘗試的結(jié)果:
- DNS解析正常
- 端口正常
- IP訪問正常(服務(wù)正常)
- 域名訪問異常
在服務(wù)正常DNS正常的情況想通過域名請求不到粱哼,問題很可能是服務(wù)本身的限制崎苗。參考wiki些阅,---rpccorsdomain參數(shù)配置為“*”沒有問題伞剑,允許所有域名訪問。然后再找似乎也沒有可用的參數(shù)了市埋。這個(gè)時(shí)候只能讀一下源碼黎泣,看看是不是能找到思路。之后就是找到go-ethereumRPC模塊的代碼腰素,一點(diǎn)點(diǎn)讀聘裁。最后發(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的地方弓千,然后用了一個(gè)比較笨的辦法
[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
返回?cái)?shù)據(jù)
{"jsonrpc":"2.0","id":1,"result":["0x8018e73d7efc27297ea313e8bd250a02c6ca9f14","0xe3207f6fb2816fead3ccba99ebd2ea9f3ff22231"]}
到這里一切就都調(diào)通了姻政,后續(xù)的鏈上操作就可以通過RPC服務(wù)直接操作了呆抑。