github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/cmd/puppeth/module_node.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:33</date> 10 //</624450069529104384> 11 12 13 package main 14 15 import ( 16 "bytes" 17 "encoding/json" 18 "fmt" 19 "math/rand" 20 "path/filepath" 21 "strconv" 22 "strings" 23 "text/template" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/log" 27 ) 28 29 // 30 var nodeDockerfile = ` 31 FROM ethereum/client-go:latest 32 33 ADD genesis.json /genesis.json 34 {{if .Unlock}} 35 ADD signer.json /signer.json 36 ADD signer.pass /signer.pass 37 {{end}} 38 RUN \ 39 echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}} 40 echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}} 41 echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --nat extip:{{.IP}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--miner.etherbase {{.Etherbase}} --mine --miner.threads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --miner.gastarget {{.GasTarget}} --miner.gaslimit {{.GasLimit}} --miner.gasprice {{.GasPrice}}' >> geth.sh 42 43 ENTRYPOINT ["/bin/sh", "geth.sh"] 44 ` 45 46 // 47 // 48 var nodeComposefile = ` 49 version: '2' 50 services: 51 {{.Type}}: 52 build: . 53 image: {{.Network}}/{{.Type}} 54 container_name: {{.Network}}_{{.Type}}_1 55 ports: 56 - "{{.Port}}:{{.Port}}" 57 - "{{.Port}}:{{.Port}}/udp" 58 volumes: 59 - {{.Datadir}}:/root/.ethereum{{if .Ethashdir}} 60 - {{.Ethashdir}}:/root/.ethash{{end}} 61 environment: 62 - PORT={{.Port}}/tcp 63 - TOTAL_PEERS={{.TotalPeers}} 64 - LIGHT_PEERS={{.LightPeers}} 65 - STATS_NAME={{.Ethstats}} 66 - MINER_NAME={{.Etherbase}} 67 - GAS_TARGET={{.GasTarget}} 68 - GAS_LIMIT={{.GasLimit}} 69 - GAS_PRICE={{.GasPrice}} 70 logging: 71 driver: "json-file" 72 options: 73 max-size: "1m" 74 max-file: "10" 75 restart: always 76 ` 77 78 // 79 //Docker和Docker组合。如果具有指定网络名称的实例 80 //已经存在,将被覆盖! 81 func deployNode(client *sshClient, network string, bootnodes []string, config *nodeInfos, nocache bool) ([]byte, error) { 82 kind := "sealnode" 83 if config.keyJSON == "" && config.etherbase == "" { 84 kind = "bootnode" 85 bootnodes = make([]string, 0) 86 } 87 //生成要上载到服务器的内容 88 workdir := fmt.Sprintf("%d", rand.Int63()) 89 files := make(map[string][]byte) 90 91 lightFlag := "" 92 if config.peersLight > 0 { 93 lightFlag = fmt.Sprintf("--lightpeers=%d --lightserv=50", config.peersLight) 94 } 95 dockerfile := new(bytes.Buffer) 96 template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{ 97 "NetworkID": config.network, 98 "Port": config.port, 99 "IP": client.address, 100 "Peers": config.peersTotal, 101 "LightFlag": lightFlag, 102 "Bootnodes": strings.Join(bootnodes, ","), 103 "Ethstats": config.ethstats, 104 "Etherbase": config.etherbase, 105 "GasTarget": uint64(1000000 * config.gasTarget), 106 "GasLimit": uint64(1000000 * config.gasLimit), 107 "GasPrice": uint64(1000000000 * config.gasPrice), 108 "Unlock": config.keyJSON != "", 109 }) 110 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 111 112 composefile := new(bytes.Buffer) 113 template.Must(template.New("").Parse(nodeComposefile)).Execute(composefile, map[string]interface{}{ 114 "Type": kind, 115 "Datadir": config.datadir, 116 "Ethashdir": config.ethashdir, 117 "Network": network, 118 "Port": config.port, 119 "TotalPeers": config.peersTotal, 120 "Light": config.peersLight > 0, 121 "LightPeers": config.peersLight, 122 "Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")], 123 "Etherbase": config.etherbase, 124 "GasTarget": config.gasTarget, 125 "GasLimit": config.gasLimit, 126 "GasPrice": config.gasPrice, 127 }) 128 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 129 130 files[filepath.Join(workdir, "genesis.json")] = config.genesis 131 if config.keyJSON != "" { 132 files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON) 133 files[filepath.Join(workdir, "signer.pass")] = []byte(config.keyPass) 134 } 135 //将部署文件上载到远程服务器(然后清理) 136 if out, err := client.Upload(files); err != nil { 137 return out, err 138 } 139 defer client.Run("rm -rf " + workdir) 140 141 //构建和部署引导或密封节点服务 142 if nocache { 143 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network)) 144 } 145 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network)) 146 } 147 148 // 149 //各种配置参数。 150 type nodeInfos struct { 151 genesis []byte 152 network int64 153 datadir string 154 ethashdir string 155 ethstats string 156 port int 157 enode string 158 peersTotal int 159 peersLight int 160 etherbase string 161 keyJSON string 162 keyPass string 163 gasTarget float64 164 gasLimit float64 165 gasPrice float64 166 } 167 168 //报表将类型化结构转换为纯字符串->字符串映射,其中包含 169 //大多数(但不是全部)字段用于向用户报告。 170 func (info *nodeInfos) Report() map[string]string { 171 report := map[string]string{ 172 "Data directory": info.datadir, 173 "Listener port": strconv.Itoa(info.port), 174 "Peer count (all total)": strconv.Itoa(info.peersTotal), 175 "Peer count (light nodes)": strconv.Itoa(info.peersLight), 176 "Ethstats username": info.ethstats, 177 } 178 if info.gasTarget > 0 { 179 // 180 report["Gas price (minimum accepted)"] = fmt.Sprintf("%0.3f GWei", info.gasPrice) 181 report["Gas floor (baseline target)"] = fmt.Sprintf("%0.3f MGas", info.gasTarget) 182 report["Gas ceil (target maximum)"] = fmt.Sprintf("%0.3f MGas", info.gasLimit) 183 184 if info.etherbase != "" { 185 // 186 report["Ethash directory"] = info.ethashdir 187 report["Miner account"] = info.etherbase 188 } 189 if info.keyJSON != "" { 190 // 191 var key struct { 192 Address string `json:"address"` 193 } 194 if err := json.Unmarshal([]byte(info.keyJSON), &key); err == nil { 195 report["Signer account"] = common.HexToAddress(key.Address).Hex() 196 } else { 197 log.Error("Failed to retrieve signer address", "err", err) 198 } 199 } 200 } 201 return report 202 } 203 204 // 205 // 206 func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) { 207 kind := "bootnode" 208 if !boot { 209 kind = "sealnode" 210 } 211 // 212 infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, kind)) 213 if err != nil { 214 return nil, err 215 } 216 if !infos.running { 217 return nil, ErrServiceOffline 218 } 219 // 220 totalPeers, _ := strconv.Atoi(infos.envvars["TOTAL_PEERS"]) 221 lightPeers, _ := strconv.Atoi(infos.envvars["LIGHT_PEERS"]) 222 gasTarget, _ := strconv.ParseFloat(infos.envvars["GAS_TARGET"], 64) 223 gasLimit, _ := strconv.ParseFloat(infos.envvars["GAS_LIMIT"], 64) 224 gasPrice, _ := strconv.ParseFloat(infos.envvars["GAS_PRICE"], 64) 225 226 // 227 var out []byte 228 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.enode --cache=16 attach", network, kind)); err != nil { 229 return nil, ErrServiceUnreachable 230 } 231 enode := bytes.Trim(bytes.TrimSpace(out), "\"") 232 233 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /genesis.json", network, kind)); err != nil { 234 return nil, ErrServiceUnreachable 235 } 236 genesis := bytes.TrimSpace(out) 237 238 keyJSON, keyPass := "", "" 239 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.json", network, kind)); err == nil { 240 keyJSON = string(bytes.TrimSpace(out)) 241 } 242 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.pass", network, kind)); err == nil { 243 keyPass = string(bytes.TrimSpace(out)) 244 } 245 //运行健全性检查以查看是否可以访问devp2p 246 port := infos.portmap[infos.envvars["PORT"]] 247 if err = checkPort(client.server, port); err != nil { 248 log.Warn(fmt.Sprintf("%s devp2p port seems unreachable", strings.Title(kind)), "server", client.server, "port", port, "err", err) 249 } 250 //收集并返回有用的信息 251 stats := &nodeInfos{ 252 genesis: genesis, 253 datadir: infos.volumes["/root/.ethereum"], 254 ethashdir: infos.volumes["/root/.ethash"], 255 port: port, 256 peersTotal: totalPeers, 257 peersLight: lightPeers, 258 ethstats: infos.envvars["STATS_NAME"], 259 etherbase: infos.envvars["MINER_NAME"], 260 keyJSON: keyJSON, 261 keyPass: keyPass, 262 gasTarget: gasTarget, 263 gasLimit: gasLimit, 264 gasPrice: gasPrice, 265 } 266 stats.enode = string(enode) 267 268 return stats, nil 269 } 270