gitee.com/liu-zhao234568/cntest@v1.0.0/cmd/puppeth/module_faucet.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum 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-ethereum 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-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "html/template" 24 "math/rand" 25 "path/filepath" 26 "strconv" 27 "strings" 28 29 "gitee.com/liu-zhao234568/cntest/common" 30 "gitee.com/liu-zhao234568/cntest/log" 31 ) 32 33 // faucetDockerfile is the Dockerfile required to build a faucet container to 34 // grant crypto tokens based on GitHub authentications. 35 var faucetDockerfile = ` 36 FROM ethereum/client-go:alltools-latest 37 38 ADD genesis.json /genesis.json 39 ADD account.json /account.json 40 ADD account.pass /account.pass 41 42 EXPOSE 8080 30303 30303/udp 43 44 ENTRYPOINT [ \ 45 "faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \ 46 "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ 47 "--account.json", "/account.json", "--account.pass", "/account.pass" \ 48 {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ 49 {{if .TwitterToken}}, "--twitter.token.v1", "{{.TwitterToken}}"{{end}} \ 50 ]` 51 52 // faucetComposefile is the docker-compose.yml file required to deploy and maintain 53 // a crypto faucet. 54 var faucetComposefile = ` 55 version: '2' 56 services: 57 faucet: 58 build: . 59 image: {{.Network}}/faucet 60 container_name: {{.Network}}_faucet_1 61 ports: 62 - "{{.EthPort}}:{{.EthPort}}" 63 - "{{.EthPort}}:{{.EthPort}}/udp"{{if not .VHost}} 64 - "{{.ApiPort}}:8080"{{end}} 65 volumes: 66 - {{.Datadir}}:/root/.faucet 67 environment: 68 - ETH_PORT={{.EthPort}} 69 - ETH_NAME={{.EthName}} 70 - FAUCET_AMOUNT={{.FaucetAmount}} 71 - FAUCET_MINUTES={{.FaucetMinutes}} 72 - FAUCET_TIERS={{.FaucetTiers}} 73 - CAPTCHA_TOKEN={{.CaptchaToken}} 74 - CAPTCHA_SECRET={{.CaptchaSecret}} 75 - TWITTER_TOKEN={{.TwitterToken}} 76 - NO_AUTH={{.NoAuth}}{{if .VHost}} 77 - VIRTUAL_HOST={{.VHost}} 78 - VIRTUAL_PORT=8080{{end}} 79 logging: 80 driver: "json-file" 81 options: 82 max-size: "1m" 83 max-file: "10" 84 restart: always 85 ` 86 87 // deployFaucet deploys a new faucet container to a remote machine via SSH, 88 // docker and docker-compose. If an instance with the specified network name 89 // already exists there, it will be overwritten! 90 func deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos, nocache bool) ([]byte, error) { 91 // Generate the content to upload to the server 92 workdir := fmt.Sprintf("%d", rand.Int63()) 93 files := make(map[string][]byte) 94 95 dockerfile := new(bytes.Buffer) 96 template.Must(template.New("").Parse(faucetDockerfile)).Execute(dockerfile, map[string]interface{}{ 97 "NetworkID": config.node.network, 98 "Bootnodes": strings.Join(bootnodes, ","), 99 "Ethstats": config.node.ethstats, 100 "EthPort": config.node.port, 101 "CaptchaToken": config.captchaToken, 102 "CaptchaSecret": config.captchaSecret, 103 "FaucetName": strings.Title(network), 104 "FaucetAmount": config.amount, 105 "FaucetMinutes": config.minutes, 106 "FaucetTiers": config.tiers, 107 "NoAuth": config.noauth, 108 "TwitterToken": config.twitterToken, 109 }) 110 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 111 112 composefile := new(bytes.Buffer) 113 template.Must(template.New("").Parse(faucetComposefile)).Execute(composefile, map[string]interface{}{ 114 "Network": network, 115 "Datadir": config.node.datadir, 116 "VHost": config.host, 117 "ApiPort": config.port, 118 "EthPort": config.node.port, 119 "EthName": config.node.ethstats[:strings.Index(config.node.ethstats, ":")], 120 "CaptchaToken": config.captchaToken, 121 "CaptchaSecret": config.captchaSecret, 122 "FaucetAmount": config.amount, 123 "FaucetMinutes": config.minutes, 124 "FaucetTiers": config.tiers, 125 "NoAuth": config.noauth, 126 "TwitterToken": config.twitterToken, 127 }) 128 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 129 130 files[filepath.Join(workdir, "genesis.json")] = config.node.genesis 131 files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON) 132 files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass) 133 134 // Upload the deployment files to the remote server (and clean up afterwards) 135 if out, err := client.Upload(files); err != nil { 136 return out, err 137 } 138 defer client.Run("rm -rf " + workdir) 139 140 // Build and deploy the faucet service 141 if nocache { 142 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)) 143 } 144 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network)) 145 } 146 147 // faucetInfos is returned from a faucet status check to allow reporting various 148 // configuration parameters. 149 type faucetInfos struct { 150 node *nodeInfos 151 host string 152 port int 153 amount int 154 minutes int 155 tiers int 156 noauth bool 157 captchaToken string 158 captchaSecret string 159 twitterToken string 160 } 161 162 // Report converts the typed struct into a plain string->string map, containing 163 // most - but not all - fields for reporting to the user. 164 func (info *faucetInfos) Report() map[string]string { 165 report := map[string]string{ 166 "Website address": info.host, 167 "Website listener port": strconv.Itoa(info.port), 168 "Ethereum listener port": strconv.Itoa(info.node.port), 169 "Funding amount (base tier)": fmt.Sprintf("%d Ethers", info.amount), 170 "Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes), 171 "Funding tiers": strconv.Itoa(info.tiers), 172 "Captha protection": fmt.Sprintf("%v", info.captchaToken != ""), 173 "Using Twitter API": fmt.Sprintf("%v", info.twitterToken != ""), 174 "Ethstats username": info.node.ethstats, 175 } 176 if info.noauth { 177 report["Debug mode (no auth)"] = "enabled" 178 } 179 if info.node.keyJSON != "" { 180 var key struct { 181 Address string `json:"address"` 182 } 183 if err := json.Unmarshal([]byte(info.node.keyJSON), &key); err == nil { 184 report["Funding account"] = common.HexToAddress(key.Address).Hex() 185 } else { 186 log.Error("Failed to retrieve signer address", "err", err) 187 } 188 } 189 return report 190 } 191 192 // checkFaucet does a health-check against a faucet server to verify whether 193 // it's running, and if yes, gathering a collection of useful infos about it. 194 func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { 195 // Inspect a possible faucet container on the host 196 infos, err := inspectContainer(client, fmt.Sprintf("%s_faucet_1", network)) 197 if err != nil { 198 return nil, err 199 } 200 if !infos.running { 201 return nil, ErrServiceOffline 202 } 203 // Resolve the port from the host, or the reverse proxy 204 port := infos.portmap["8080/tcp"] 205 if port == 0 { 206 if proxy, _ := checkNginx(client, network); proxy != nil { 207 port = proxy.port 208 } 209 } 210 if port == 0 { 211 return nil, ErrNotExposed 212 } 213 // Resolve the host from the reverse-proxy and the config values 214 host := infos.envvars["VIRTUAL_HOST"] 215 if host == "" { 216 host = client.server 217 } 218 amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"]) 219 minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"]) 220 tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"]) 221 222 // Retrieve the funding account information 223 var out []byte 224 keyJSON, keyPass := "", "" 225 if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.json", network)); err == nil { 226 keyJSON = string(bytes.TrimSpace(out)) 227 } 228 if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.pass", network)); err == nil { 229 keyPass = string(bytes.TrimSpace(out)) 230 } 231 // Run a sanity check to see if the port is reachable 232 if err = checkPort(host, port); err != nil { 233 log.Warn("Faucet service seems unreachable", "server", host, "port", port, "err", err) 234 } 235 // Container available, assemble and return the useful infos 236 return &faucetInfos{ 237 node: &nodeInfos{ 238 datadir: infos.volumes["/root/.faucet"], 239 port: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"], 240 ethstats: infos.envvars["ETH_NAME"], 241 keyJSON: keyJSON, 242 keyPass: keyPass, 243 }, 244 host: host, 245 port: port, 246 amount: amount, 247 minutes: minutes, 248 tiers: tiers, 249 captchaToken: infos.envvars["CAPTCHA_TOKEN"], 250 captchaSecret: infos.envvars["CAPTCHA_SECRET"], 251 noauth: infos.envvars["NO_AUTH"] == "true", 252 twitterToken: infos.envvars["TWITTER_TOKEN"], 253 }, nil 254 }