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