diff --git a/cmd/puppeth/module_explorer.go b/cmd/puppeth/module_explorer.go index e465fa04ad..bd4c1998cd 100644 --- a/cmd/puppeth/module_explorer.go +++ b/cmd/puppeth/module_explorer.go @@ -30,108 +30,80 @@ import ( // explorerDockerfile is the Dockerfile required to run a block explorer. var explorerDockerfile = ` -FROM puppeth/explorer:latest - -ADD ethstats.json /ethstats.json -ADD chain.json /chain.json +FROM puppeth/blockscout:latest +ADD genesis.json /genesis.json RUN \ - echo '(cd ../eth-net-intelligence-api && pm2 start /ethstats.json)' > explorer.sh && \ - echo '(cd ../etherchain-light && npm start &)' >> explorer.sh && \ - echo 'exec /parity/parity --chain=/chain.json --port={{.NodePort}} --tracing=on --fat-db=on --pruning=archive' >> explorer.sh + echo 'geth --cache 512 init /genesis.json' > explorer.sh && \ + echo $'exec geth --networkid {{.NetworkID}} --syncmode "full" --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpccorsdomain "*" --rpcvhosts "*" --ws --wsorigins "*" &' >> explorer.sh && \ + echo '/usr/local/bin/docker-entrypoint.sh postgres &' >> explorer.sh && \ + echo 'sleep 5' >> explorer.sh && \ + echo 'mix do ecto.drop --force, ecto.create, ecto.migrate' >> explorer.sh && \ + echo 'mix phx.server' >> explorer.sh ENTRYPOINT ["/bin/sh", "explorer.sh"] ` -// explorerEthstats is the configuration file for the ethstats javascript client. -var explorerEthstats = `[ - { - "name" : "node-app", - "script" : "app.js", - "log_date_format" : "YYYY-MM-DD HH:mm Z", - "merge_logs" : false, - "watch" : false, - "max_restarts" : 10, - "exec_interpreter" : "node", - "exec_mode" : "fork_mode", - "env": - { - "NODE_ENV" : "production", - "RPC_HOST" : "localhost", - "RPC_PORT" : "8545", - "LISTENING_PORT" : "{{.Port}}", - "INSTANCE_NAME" : "{{.Name}}", - "CONTACT_DETAILS" : "", - "WS_SERVER" : "{{.Host}}", - "WS_SECRET" : "{{.Secret}}", - "VERBOSITY" : 2 - } - } -]` - // explorerComposefile is the docker-compose.yml file required to deploy and // maintain a block explorer. var explorerComposefile = ` version: '2' services: - explorer: - build: . - image: {{.Network}}/explorer - container_name: {{.Network}}_explorer_1 - ports: - - "{{.NodePort}}:{{.NodePort}}" - - "{{.NodePort}}:{{.NodePort}}/udp"{{if not .VHost}} - - "{{.WebPort}}:3000"{{end}} - volumes: - - {{.Datadir}}:/root/.local/share/io.parity.ethereum - environment: - - NODE_PORT={{.NodePort}}/tcp - - STATS={{.Ethstats}}{{if .VHost}} - - VIRTUAL_HOST={{.VHost}} - - VIRTUAL_PORT=3000{{end}} - logging: - driver: "json-file" - options: - max-size: "1m" - max-file: "10" - restart: always + explorer: + build: . + image: {{.Network}}/explorer + container_name: {{.Network}}_explorer_1 + ports: + - "{{.NodePort}}:{{.NodePort}}" + - "{{.NodePort}}:{{.NodePort}}/udp"{{if not .VHost}} + - "{{.WebPort}}:4000"{{end}} + environment: + - NETWORK={{.NetworkName}}{{if .VHost}} + - VIRTUAL_HOST={{.VHost}} + - VIRTUAL_PORT=4000{{end}} + - NODE_PORT={{.NodePort}}/tcp + - STATS={{.Ethstats}} + volumes: + - {{.Datadir}}:/root/.ethereum + - {{.DBDir}}:/var/lib/postgresql/data + logging: + driver: "json-file" + options: + max-size: "1m" + max-file: "10" + restart: always ` // deployExplorer deploys a new block explorer container to a remote machine via // SSH, docker and docker-compose. If an instance with the specified network name // already exists there, it will be overwritten! -func deployExplorer(client *sshClient, network string, chainspec []byte, config *explorerInfos, nocache bool) ([]byte, error) { +func deployExplorer(client *sshClient, network string, bootnodes []string, config *explorerInfos, nocache bool) ([]byte, error) { // Generate the content to upload to the server workdir := fmt.Sprintf("%d", rand.Int63()) files := make(map[string][]byte) dockerfile := new(bytes.Buffer) template.Must(template.New("").Parse(explorerDockerfile)).Execute(dockerfile, map[string]interface{}{ - "NodePort": config.nodePort, + "NetworkID": config.networkId, + "Bootnodes": strings.Join(bootnodes, ","), + "Ethstats": config.ethstats, + "NodePort": config.nodePort, }) files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() - ethstats := new(bytes.Buffer) - template.Must(template.New("").Parse(explorerEthstats)).Execute(ethstats, map[string]interface{}{ - "Port": config.nodePort, - "Name": config.ethstats[:strings.Index(config.ethstats, ":")], - "Secret": config.ethstats[strings.Index(config.ethstats, ":")+1 : strings.Index(config.ethstats, "@")], - "Host": config.ethstats[strings.Index(config.ethstats, "@")+1:], - }) - files[filepath.Join(workdir, "ethstats.json")] = ethstats.Bytes() - composefile := new(bytes.Buffer) template.Must(template.New("").Parse(explorerComposefile)).Execute(composefile, map[string]interface{}{ - "Datadir": config.datadir, - "Network": network, - "NodePort": config.nodePort, - "VHost": config.webHost, - "WebPort": config.webPort, - "Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")], + "NetworkName": network, + "VHost": config.webHost, + "Ethstats": config.ethstats, + "Datadir": config.datadir, + "DBDir": config.dbdir, + "Network": network, + "NodePort": config.nodePort, + "WebPort": config.webPort, }) files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() - - files[filepath.Join(workdir, "chain.json")] = chainspec + files[filepath.Join(workdir, "genesis.json")] = config.genesis // Upload the deployment files to the remote server (and clean up afterwards) if out, err := client.Upload(files); err != nil { @@ -149,11 +121,14 @@ func deployExplorer(client *sshClient, network string, chainspec []byte, config // explorerInfos is returned from a block explorer status check to allow reporting // various configuration parameters. type explorerInfos struct { - datadir string - ethstats string - nodePort int - webHost string - webPort int + genesis []byte + datadir string + dbdir string + ethstats string + nodePort int + webHost string + webPort int + networkId int64 } // Report converts the typed struct into a plain string->string map, containing @@ -172,7 +147,7 @@ func (info *explorerInfos) Report() map[string]string { // checkExplorer does a health-check against a block explorer server to verify // whether it's running, and if yes, whether it's responsive. func checkExplorer(client *sshClient, network string) (*explorerInfos, error) { - // Inspect a possible block explorer container on the host + // Inspect a possible explorer container on the host infos, err := inspectContainer(client, fmt.Sprintf("%s_explorer_1", network)) if err != nil { return nil, err @@ -181,7 +156,7 @@ func checkExplorer(client *sshClient, network string) (*explorerInfos, error) { return nil, ErrServiceOffline } // Resolve the port from the host, or the reverse proxy - webPort := infos.portmap["3000/tcp"] + webPort := infos.portmap["4000/tcp"] if webPort == 0 { if proxy, _ := checkNginx(client, network); proxy != nil { webPort = proxy.port @@ -202,7 +177,8 @@ func checkExplorer(client *sshClient, network string) (*explorerInfos, error) { } // Assemble and return the useful infos stats := &explorerInfos{ - datadir: infos.volumes["/root/.local/share/io.parity.ethereum"], + dbdir: infos.volumes["/var/lib/postgresql/data"], + datadir: infos.volumes["/root/.ethereum"], nodePort: nodePort, webHost: host, webPort: webPort, diff --git a/cmd/puppeth/wizard_explorer.go b/cmd/puppeth/wizard_explorer.go index a128fb9fb5..fccc294c72 100644 --- a/cmd/puppeth/wizard_explorer.go +++ b/cmd/puppeth/wizard_explorer.go @@ -35,10 +35,6 @@ func (w *wizard) deployExplorer() { log.Error("No ethstats server configured") return } - if w.conf.Genesis.Config.Ethash == nil { - log.Error("Only ethash network supported") - return - } // Select the server to interact with server := w.selectServer() if server == "" { @@ -55,12 +51,8 @@ func (w *wizard) deployExplorer() { } existed := err == nil - chainspec, err := newParityChainSpec(w.network, w.conf.Genesis, w.conf.bootnodes) - if err != nil { - log.Error("Failed to create chain spec for explorer", "err", err) - return - } - chain, _ := json.MarshalIndent(chainspec, "", " ") + infos.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ") + infos.networkId = w.conf.Genesis.Config.ChainID.Int64() // Figure out which port to listen on fmt.Println() @@ -75,12 +67,21 @@ func (w *wizard) deployExplorer() { // Figure out where the user wants to store the persistent data fmt.Println() if infos.datadir == "" { - fmt.Printf("Where should data be stored on the remote machine?\n") + fmt.Printf("Where should node data be stored on the remote machine?\n") infos.datadir = w.readString() } else { - fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.datadir) + fmt.Printf("Where should node data be stored on the remote machine? (default = %s)\n", infos.datadir) infos.datadir = w.readDefaultString(infos.datadir) } + // Figure out where the user wants to store the persistent data for backend database + fmt.Println() + if infos.dbdir == "" { + fmt.Printf("Where should postgres data be stored on the remote machine?\n") + infos.dbdir = w.readString() + } else { + fmt.Printf("Where should postgres data be stored on the remote machine? (default = %s)\n", infos.dbdir) + infos.dbdir = w.readDefaultString(infos.datadir) + } // Figure out which port to listen on fmt.Println() fmt.Printf("Which TCP/UDP port should the archive node listen on? (default = %d)\n", infos.nodePort) @@ -102,7 +103,7 @@ func (w *wizard) deployExplorer() { fmt.Printf("Should the explorer be built from scratch (y/n)? (default = no)\n") nocache = w.readDefaultYesNo(false) } - if out, err := deployExplorer(client, w.network, chain, infos, nocache); err != nil { + if out, err := deployExplorer(client, w.network, w.conf.bootnodes, infos, nocache); err != nil { log.Error("Failed to deploy explorer container", "err", err) if len(out) > 0 { fmt.Printf("%s\n", out) diff --git a/cmd/puppeth/wizard_network.go b/cmd/puppeth/wizard_network.go index 83b10cf375..97302c0df8 100644 --- a/cmd/puppeth/wizard_network.go +++ b/cmd/puppeth/wizard_network.go @@ -174,7 +174,7 @@ func (w *wizard) deployComponent() { fmt.Println(" 1. Ethstats - Network monitoring tool") fmt.Println(" 2. Bootnode - Entry point of the network") fmt.Println(" 3. Sealer - Full node minting new blocks") - fmt.Println(" 4. Explorer - Chain analysis webservice (ethash only)") + fmt.Println(" 4. Explorer - Chain analysis webservice") fmt.Println(" 5. Wallet - Browser wallet for quick sends") fmt.Println(" 6. Faucet - Crypto faucet to give away funds") fmt.Println(" 7. Dashboard - Website listing above web-services")