github.com/waltonchain/waltonchain_gwtc_src@v1.1.4-0.20201225072101-8a298c95a819/cmd/puppeth/module_node.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of go-wtc. 3 // 4 // go-wtc is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-wtc is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-wtc. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bytes" 21 "fmt" 22 "math/rand" 23 "path/filepath" 24 "strconv" 25 "strings" 26 "text/template" 27 28 "github.com/wtc/go-wtc/log" 29 ) 30 31 // nodeDockerfile is the Dockerfile required to run an Wtc node. 32 var nodeDockerfile = ` 33 FROM wtc/client-go:latest 34 35 ADD genesis.json /genesis.json 36 {{if .Unlock}} 37 ADD signer.json /signer.json 38 ADD signer.pass /signer.pass 39 {{end}} 40 RUN \ 41 echo 'gwtc init /genesis.json' > gwtc.sh && \{{if .Unlock}} 42 echo 'mkdir -p /root/.wtc/keystore/ && cp /signer.json /root/.wtc/keystore/' >> gwtc.sh && \{{end}} 43 echo $'gwtc --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .BootV4}}--bootnodesv4 {{.BootV4}}{{end}} {{if .BootV5}}--bootnodesv5 {{.BootV5}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine{{end}}{{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> gwtc.sh 44 45 ENTRYPOINT ["/bin/sh", "gwtc.sh"] 46 ` 47 48 // nodeComposefile is the docker-compose.yml file required to deploy and maintain 49 // an Wtc node (bootnode or miner for now). 50 var nodeComposefile = ` 51 version: '2' 52 services: 53 {{.Type}}: 54 build: . 55 image: {{.Network}}/{{.Type}} 56 ports: 57 - "{{.FullPort}}:{{.FullPort}}" 58 - "{{.FullPort}}:{{.FullPort}}/udp"{{if .Light}} 59 - "{{.LightPort}}:{{.LightPort}}/udp"{{end}} 60 volumes: 61 - {{.Datadir}}:/root/.wtc 62 environment: 63 - FULL_PORT={{.FullPort}}/tcp 64 - LIGHT_PORT={{.LightPort}}/udp 65 - TOTAL_PEERS={{.TotalPeers}} 66 - LIGHT_PEERS={{.LightPeers}} 67 - STATS_NAME={{.Ethstats}} 68 - MINER_NAME={{.Etherbase}} 69 - GAS_TARGET={{.GasTarget}} 70 - GAS_PRICE={{.GasPrice}} 71 logging: 72 driver: "json-file" 73 options: 74 max-size: "1m" 75 max-file: "10" 76 restart: always 77 ` 78 79 // deployNode deploys a new Wtc node container to a remote machine via SSH, 80 // docker and docker-compose. If an instance with the specified network name 81 // already exists there, it will be overwritten! 82 func deployNode(client *sshClient, network string, bootv4, bootv5 []string, config *nodeInfos) ([]byte, error) { 83 kind := "sealnode" 84 if config.keyJSON == "" && config.etherbase == "" { 85 kind = "bootnode" 86 bootv4 = make([]string, 0) 87 bootv5 = make([]string, 0) 88 } 89 // Generate the content to upload to the server 90 workdir := fmt.Sprintf("%d", rand.Int63()) 91 files := make(map[string][]byte) 92 93 lightFlag := "" 94 if config.peersLight > 0 { 95 lightFlag = fmt.Sprintf("--lightpeers=%d --lightserv=50", config.peersLight) 96 } 97 dockerfile := new(bytes.Buffer) 98 template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{ 99 "NetworkID": config.network, 100 "Port": config.portFull, 101 "Peers": config.peersTotal, 102 "LightFlag": lightFlag, 103 "BootV4": strings.Join(bootv4, ","), 104 "BootV5": strings.Join(bootv5, ","), 105 "Ethstats": config.ethstats, 106 "Etherbase": config.etherbase, 107 "GasTarget": uint64(1000000 * config.gasTarget), 108 "GasPrice": uint64(1000000000 * config.gasPrice), 109 "Unlock": config.keyJSON != "", 110 }) 111 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 112 113 composefile := new(bytes.Buffer) 114 template.Must(template.New("").Parse(nodeComposefile)).Execute(composefile, map[string]interface{}{ 115 "Type": kind, 116 "Datadir": config.datadir, 117 "Network": network, 118 "FullPort": config.portFull, 119 "TotalPeers": config.peersTotal, 120 "Light": config.peersLight > 0, 121 "LightPort": config.portFull + 1, 122 "LightPeers": config.peersLight, 123 "Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")], 124 "Etherbase": config.etherbase, 125 "GasTarget": config.gasTarget, 126 "GasPrice": config.gasPrice, 127 }) 128 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 129 130 //genesisfile, _ := json.MarshalIndent(config.genesis, "", " ") 131 files[filepath.Join(workdir, "genesis.json")] = []byte(config.genesis) 132 133 if config.keyJSON != "" { 134 files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON) 135 files[filepath.Join(workdir, "signer.pass")] = []byte(config.keyPass) 136 } 137 // Upload the deployment files to the remote server (and clean up afterwards) 138 if out, err := client.Upload(files); err != nil { 139 return out, err 140 } 141 defer client.Run("rm -rf " + workdir) 142 143 // Build and deploy the boot or seal node service 144 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build", workdir, network)) 145 } 146 147 // nodeInfos is returned from a boot or seal node status check to allow reporting 148 // various configuration parameters. 149 type nodeInfos struct { 150 genesis []byte 151 network int64 152 datadir string 153 ethstats string 154 portFull int 155 portLight int 156 enodeFull string 157 enodeLight string 158 peersTotal int 159 peersLight int 160 etherbase string 161 keyJSON string 162 keyPass string 163 gasTarget float64 164 gasPrice float64 165 } 166 167 // String implements the stringer interface. 168 func (info *nodeInfos) String() string { 169 discv5 := "" 170 if info.peersLight > 0 { 171 discv5 = fmt.Sprintf(", portv5=%d", info.portLight) 172 } 173 return fmt.Sprintf("port=%d%s, datadir=%s, peers=%d, lights=%d, ethstats=%s, gastarget=%0.3f MGas, gasprice=%0.3f GWei", 174 info.portFull, discv5, info.datadir, info.peersTotal, info.peersLight, info.ethstats, info.gasTarget, info.gasPrice) 175 } 176 177 // checkNode does a health-check against an boot or seal node server to verify 178 // whether it's running, and if yes, whether it's responsive. 179 func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) { 180 kind := "bootnode" 181 if !boot { 182 kind = "sealnode" 183 } 184 // Inspect a possible bootnode container on the host 185 infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, kind)) 186 if err != nil { 187 return nil, err 188 } 189 if !infos.running { 190 return nil, ErrServiceOffline 191 } 192 // Resolve a few types from the environmental variables 193 totalPeers, _ := strconv.Atoi(infos.envvars["TOTAL_PEERS"]) 194 lightPeers, _ := strconv.Atoi(infos.envvars["LIGHT_PEERS"]) 195 gasTarget, _ := strconv.ParseFloat(infos.envvars["GAS_TARGET"], 64) 196 gasPrice, _ := strconv.ParseFloat(infos.envvars["GAS_PRICE"], 64) 197 198 // Container available, retrieve its node ID and its genesis json 199 var out []byte 200 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 gwtc --exec admin.nodeInfo.id attach", network, kind)); err != nil { 201 return nil, ErrServiceUnreachable 202 } 203 id := bytes.Trim(bytes.TrimSpace(out), "\"") 204 205 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /genesis.json", network, kind)); err != nil { 206 return nil, ErrServiceUnreachable 207 } 208 genesis := bytes.TrimSpace(out) 209 210 keyJSON, keyPass := "", "" 211 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.json", network, kind)); err == nil { 212 keyJSON = string(bytes.TrimSpace(out)) 213 } 214 if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.pass", network, kind)); err == nil { 215 keyPass = string(bytes.TrimSpace(out)) 216 } 217 // Run a sanity check to see if the devp2p is reachable 218 port := infos.portmap[infos.envvars["FULL_PORT"]] 219 if err = checkPort(client.server, port); err != nil { 220 log.Warn(fmt.Sprintf("%s devp2p port seems unreachable", strings.Title(kind)), "server", client.server, "port", port, "err", err) 221 } 222 // Assemble and return the useful infos 223 stats := &nodeInfos{ 224 genesis: genesis, 225 datadir: infos.volumes["/root/.wtc"], 226 portFull: infos.portmap[infos.envvars["FULL_PORT"]], 227 portLight: infos.portmap[infos.envvars["LIGHT_PORT"]], 228 peersTotal: totalPeers, 229 peersLight: lightPeers, 230 ethstats: infos.envvars["STATS_NAME"], 231 etherbase: infos.envvars["MINER_NAME"], 232 keyJSON: keyJSON, 233 keyPass: keyPass, 234 gasTarget: gasTarget, 235 gasPrice: gasPrice, 236 } 237 stats.enodeFull = fmt.Sprintf("enode://%s@%s:%d", id, client.address, stats.portFull) 238 if stats.portLight != 0 { 239 stats.enodeLight = fmt.Sprintf("enode://%s@%s:%d?discport=%d", id, client.address, stats.portFull, stats.portLight) 240 } 241 return stats, nil 242 }