Skip to content

Commit 16e3136

Browse files
rjl493456442karalabe
authored andcommitted
cmd/puppeth: integrate blockscout (#18261)
* cmd/puppeth: integrate blockscout * cmd/puppeth: expose debug namespace for blockscout * cmd/puppeth: fix dbdir * cmd/puppeth: run explorer in archive mode * cmd/puppeth: ensure node is synced * cmd/puppeth: fix explorer docker alignment + drop unneeded exec * cmd/puppeth: polish up config saving and reloading * cmd/puppeth: check both web and p2p port for explorer service
1 parent fa538ee commit 16e3136

File tree

4 files changed

+109
-124
lines changed

4 files changed

+109
-124
lines changed

cmd/puppeth/module_explorer.go

Lines changed: 78 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -30,108 +30,86 @@ import (
3030

3131
// explorerDockerfile is the Dockerfile required to run a block explorer.
3232
var explorerDockerfile = `
33-
FROM puppeth/explorer:latest
34-
35-
ADD ethstats.json /ethstats.json
36-
ADD chain.json /chain.json
33+
FROM puppeth/blockscout:latest
3734
35+
ADD genesis.json /genesis.json
3836
RUN \
39-
echo '(cd ../eth-net-intelligence-api && pm2 start /ethstats.json)' > explorer.sh && \
40-
echo '(cd ../etherchain-light && npm start &)' >> explorer.sh && \
41-
echo 'exec /parity/parity --chain=/chain.json --port={{.NodePort}} --tracing=on --fat-db=on --pruning=archive' >> explorer.sh
37+
echo 'geth --cache 512 init /genesis.json' > explorer.sh && \
38+
echo $'geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcapi "net,web3,eth,shh,debug" --rpccorsdomain "*" --rpcvhosts "*" --ws --wsorigins "*" --exitwhensynced' >> explorer.sh && \
39+
echo $'exec geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcapi "net,web3,eth,shh,debug" --rpccorsdomain "*" --rpcvhosts "*" --ws --wsorigins "*" &' >> explorer.sh && \
40+
echo '/usr/local/bin/docker-entrypoint.sh postgres &' >> explorer.sh && \
41+
echo 'sleep 5' >> explorer.sh && \
42+
echo 'mix do ecto.drop --force, ecto.create, ecto.migrate' >> explorer.sh && \
43+
echo 'mix phx.server' >> explorer.sh
4244
4345
ENTRYPOINT ["/bin/sh", "explorer.sh"]
4446
`
4547

46-
// explorerEthstats is the configuration file for the ethstats javascript client.
47-
var explorerEthstats = `[
48-
{
49-
"name" : "node-app",
50-
"script" : "app.js",
51-
"log_date_format" : "YYYY-MM-DD HH:mm Z",
52-
"merge_logs" : false,
53-
"watch" : false,
54-
"max_restarts" : 10,
55-
"exec_interpreter" : "node",
56-
"exec_mode" : "fork_mode",
57-
"env":
58-
{
59-
"NODE_ENV" : "production",
60-
"RPC_HOST" : "localhost",
61-
"RPC_PORT" : "8545",
62-
"LISTENING_PORT" : "{{.Port}}",
63-
"INSTANCE_NAME" : "{{.Name}}",
64-
"CONTACT_DETAILS" : "",
65-
"WS_SERVER" : "{{.Host}}",
66-
"WS_SECRET" : "{{.Secret}}",
67-
"VERBOSITY" : 2
68-
}
69-
}
70-
]`
71-
7248
// explorerComposefile is the docker-compose.yml file required to deploy and
7349
// maintain a block explorer.
7450
var explorerComposefile = `
7551
version: '2'
7652
services:
77-
explorer:
78-
build: .
79-
image: {{.Network}}/explorer
80-
container_name: {{.Network}}_explorer_1
81-
ports:
82-
- "{{.NodePort}}:{{.NodePort}}"
83-
- "{{.NodePort}}:{{.NodePort}}/udp"{{if not .VHost}}
84-
- "{{.WebPort}}:3000"{{end}}
85-
volumes:
86-
- {{.Datadir}}:/root/.local/share/io.parity.ethereum
87-
environment:
88-
- NODE_PORT={{.NodePort}}/tcp
89-
- STATS={{.Ethstats}}{{if .VHost}}
90-
- VIRTUAL_HOST={{.VHost}}
91-
- VIRTUAL_PORT=3000{{end}}
92-
logging:
93-
driver: "json-file"
94-
options:
95-
max-size: "1m"
96-
max-file: "10"
97-
restart: always
53+
explorer:
54+
build: .
55+
image: {{.Network}}/explorer
56+
container_name: {{.Network}}_explorer_1
57+
ports:
58+
- "{{.EthPort}}:{{.EthPort}}"
59+
- "{{.EthPort}}:{{.EthPort}}/udp"{{if not .VHost}}
60+
- "{{.WebPort}}:4000"{{end}}
61+
environment:
62+
- ETH_PORT={{.EthPort}}
63+
- ETH_NAME={{.EthName}}
64+
- BLOCK_TRANSFORMER={{.Transformer}}{{if .VHost}}
65+
- VIRTUAL_HOST={{.VHost}}
66+
- VIRTUAL_PORT=4000{{end}}
67+
volumes:
68+
- {{.Datadir}}:/opt/app/.ethereum
69+
- {{.DBDir}}:/var/lib/postgresql/data
70+
logging:
71+
driver: "json-file"
72+
options:
73+
max-size: "1m"
74+
max-file: "10"
75+
restart: always
9876
`
9977

10078
// deployExplorer deploys a new block explorer container to a remote machine via
10179
// SSH, docker and docker-compose. If an instance with the specified network name
10280
// already exists there, it will be overwritten!
103-
func deployExplorer(client *sshClient, network string, chainspec []byte, config *explorerInfos, nocache bool) ([]byte, error) {
81+
func deployExplorer(client *sshClient, network string, bootnodes []string, config *explorerInfos, nocache bool, isClique bool) ([]byte, error) {
10482
// Generate the content to upload to the server
10583
workdir := fmt.Sprintf("%d", rand.Int63())
10684
files := make(map[string][]byte)
10785

10886
dockerfile := new(bytes.Buffer)
10987
template.Must(template.New("").Parse(explorerDockerfile)).Execute(dockerfile, map[string]interface{}{
110-
"NodePort": config.nodePort,
88+
"NetworkID": config.node.network,
89+
"Bootnodes": strings.Join(bootnodes, ","),
90+
"Ethstats": config.node.ethstats,
91+
"EthPort": config.node.port,
11192
})
11293
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
11394

114-
ethstats := new(bytes.Buffer)
115-
template.Must(template.New("").Parse(explorerEthstats)).Execute(ethstats, map[string]interface{}{
116-
"Port": config.nodePort,
117-
"Name": config.ethstats[:strings.Index(config.ethstats, ":")],
118-
"Secret": config.ethstats[strings.Index(config.ethstats, ":")+1 : strings.Index(config.ethstats, "@")],
119-
"Host": config.ethstats[strings.Index(config.ethstats, "@")+1:],
120-
})
121-
files[filepath.Join(workdir, "ethstats.json")] = ethstats.Bytes()
122-
95+
transformer := "base"
96+
if isClique {
97+
transformer = "clique"
98+
}
12399
composefile := new(bytes.Buffer)
124100
template.Must(template.New("").Parse(explorerComposefile)).Execute(composefile, map[string]interface{}{
125-
"Datadir": config.datadir,
126-
"Network": network,
127-
"NodePort": config.nodePort,
128-
"VHost": config.webHost,
129-
"WebPort": config.webPort,
130-
"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
101+
"Network": network,
102+
"VHost": config.host,
103+
"Ethstats": config.node.ethstats,
104+
"Datadir": config.node.datadir,
105+
"DBDir": config.dbdir,
106+
"EthPort": config.node.port,
107+
"EthName": config.node.ethstats[:strings.Index(config.node.ethstats, ":")],
108+
"WebPort": config.port,
109+
"Transformer": transformer,
131110
})
132111
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
133-
134-
files[filepath.Join(workdir, "chain.json")] = chainspec
112+
files[filepath.Join(workdir, "genesis.json")] = config.node.genesis
135113

136114
// Upload the deployment files to the remote server (and clean up afterwards)
137115
if out, err := client.Upload(files); err != nil {
@@ -149,30 +127,28 @@ func deployExplorer(client *sshClient, network string, chainspec []byte, config
149127
// explorerInfos is returned from a block explorer status check to allow reporting
150128
// various configuration parameters.
151129
type explorerInfos struct {
152-
datadir string
153-
ethstats string
154-
nodePort int
155-
webHost string
156-
webPort int
130+
node *nodeInfos
131+
dbdir string
132+
host string
133+
port int
157134
}
158135

159136
// Report converts the typed struct into a plain string->string map, containing
160137
// most - but not all - fields for reporting to the user.
161138
func (info *explorerInfos) Report() map[string]string {
162139
report := map[string]string{
163-
"Data directory": info.datadir,
164-
"Node listener port ": strconv.Itoa(info.nodePort),
165-
"Ethstats username": info.ethstats,
166-
"Website address ": info.webHost,
167-
"Website listener port ": strconv.Itoa(info.webPort),
140+
"Website address ": info.host,
141+
"Website listener port ": strconv.Itoa(info.port),
142+
"Ethereum listener port ": strconv.Itoa(info.node.port),
143+
"Ethstats username": info.node.ethstats,
168144
}
169145
return report
170146
}
171147

172148
// checkExplorer does a health-check against a block explorer server to verify
173149
// whether it's running, and if yes, whether it's responsive.
174150
func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
175-
// Inspect a possible block explorer container on the host
151+
// Inspect a possible explorer container on the host
176152
infos, err := inspectContainer(client, fmt.Sprintf("%s_explorer_1", network))
177153
if err != nil {
178154
return nil, err
@@ -181,13 +157,13 @@ func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
181157
return nil, ErrServiceOffline
182158
}
183159
// Resolve the port from the host, or the reverse proxy
184-
webPort := infos.portmap["3000/tcp"]
185-
if webPort == 0 {
160+
port := infos.portmap["4000/tcp"]
161+
if port == 0 {
186162
if proxy, _ := checkNginx(client, network); proxy != nil {
187-
webPort = proxy.port
163+
port = proxy.port
188164
}
189165
}
190-
if webPort == 0 {
166+
if port == 0 {
191167
return nil, ErrNotExposed
192168
}
193169
// Resolve the host from the reverse-proxy and the config values
@@ -196,17 +172,23 @@ func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
196172
host = client.server
197173
}
198174
// Run a sanity check to see if the devp2p is reachable
199-
nodePort := infos.portmap[infos.envvars["NODE_PORT"]]
200-
if err = checkPort(client.server, nodePort); err != nil {
201-
log.Warn(fmt.Sprintf("Explorer devp2p port seems unreachable"), "server", client.server, "port", nodePort, "err", err)
175+
p2pPort := infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"]
176+
if err = checkPort(host, p2pPort); err != nil {
177+
log.Warn("Explorer node seems unreachable", "server", host, "port", p2pPort, "err", err)
178+
}
179+
if err = checkPort(host, port); err != nil {
180+
log.Warn("Explorer service seems unreachable", "server", host, "port", port, "err", err)
202181
}
203182
// Assemble and return the useful infos
204183
stats := &explorerInfos{
205-
datadir: infos.volumes["/root/.local/share/io.parity.ethereum"],
206-
nodePort: nodePort,
207-
webHost: host,
208-
webPort: webPort,
209-
ethstats: infos.envvars["STATS"],
184+
node: &nodeInfos{
185+
datadir: infos.volumes["/opt/app/.ethereum"],
186+
port: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
187+
ethstats: infos.envvars["ETH_NAME"],
188+
},
189+
dbdir: infos.volumes["/var/lib/postgresql/data"],
190+
host: host,
191+
port: port,
210192
}
211193
return stats, nil
212194
}

cmd/puppeth/wizard_dashboard.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (w *wizard) deployDashboard() {
7777
}
7878
case "explorer":
7979
if infos, err := checkExplorer(client, w.network); err == nil {
80-
port = infos.webPort
80+
port = infos.port
8181
}
8282
case "wallet":
8383
if infos, err := checkWallet(client, w.network); err == nil {

cmd/puppeth/wizard_explorer.go

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ func (w *wizard) deployExplorer() {
3535
log.Error("No ethstats server configured")
3636
return
3737
}
38-
if w.conf.Genesis.Config.Ethash == nil {
39-
log.Error("Only ethash network supported")
40-
return
41-
}
4238
// Select the server to interact with
4339
server := w.selectServer()
4440
if server == "" {
@@ -50,50 +46,57 @@ func (w *wizard) deployExplorer() {
5046
infos, err := checkExplorer(client, w.network)
5147
if err != nil {
5248
infos = &explorerInfos{
53-
nodePort: 30303, webPort: 80, webHost: client.server,
49+
node: &nodeInfos{port: 30303},
50+
port: 80,
51+
host: client.server,
5452
}
5553
}
5654
existed := err == nil
5755

58-
chainspec, err := newParityChainSpec(w.network, w.conf.Genesis, w.conf.bootnodes)
59-
if err != nil {
60-
log.Error("Failed to create chain spec for explorer", "err", err)
61-
return
62-
}
63-
chain, _ := json.MarshalIndent(chainspec, "", " ")
56+
infos.node.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
57+
infos.node.network = w.conf.Genesis.Config.ChainID.Int64()
6458

6559
// Figure out which port to listen on
6660
fmt.Println()
67-
fmt.Printf("Which port should the explorer listen on? (default = %d)\n", infos.webPort)
68-
infos.webPort = w.readDefaultInt(infos.webPort)
61+
fmt.Printf("Which port should the explorer listen on? (default = %d)\n", infos.port)
62+
infos.port = w.readDefaultInt(infos.port)
6963

7064
// Figure which virtual-host to deploy ethstats on
71-
if infos.webHost, err = w.ensureVirtualHost(client, infos.webPort, infos.webHost); err != nil {
65+
if infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host); err != nil {
7266
log.Error("Failed to decide on explorer host", "err", err)
7367
return
7468
}
7569
// Figure out where the user wants to store the persistent data
7670
fmt.Println()
77-
if infos.datadir == "" {
78-
fmt.Printf("Where should data be stored on the remote machine?\n")
79-
infos.datadir = w.readString()
71+
if infos.node.datadir == "" {
72+
fmt.Printf("Where should node data be stored on the remote machine?\n")
73+
infos.node.datadir = w.readString()
74+
} else {
75+
fmt.Printf("Where should node data be stored on the remote machine? (default = %s)\n", infos.node.datadir)
76+
infos.node.datadir = w.readDefaultString(infos.node.datadir)
77+
}
78+
// Figure out where the user wants to store the persistent data for backend database
79+
fmt.Println()
80+
if infos.dbdir == "" {
81+
fmt.Printf("Where should postgres data be stored on the remote machine?\n")
82+
infos.dbdir = w.readString()
8083
} else {
81-
fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.datadir)
82-
infos.datadir = w.readDefaultString(infos.datadir)
84+
fmt.Printf("Where should postgres data be stored on the remote machine? (default = %s)\n", infos.dbdir)
85+
infos.dbdir = w.readDefaultString(infos.dbdir)
8386
}
8487
// Figure out which port to listen on
8588
fmt.Println()
86-
fmt.Printf("Which TCP/UDP port should the archive node listen on? (default = %d)\n", infos.nodePort)
87-
infos.nodePort = w.readDefaultInt(infos.nodePort)
89+
fmt.Printf("Which TCP/UDP port should the archive node listen on? (default = %d)\n", infos.node.port)
90+
infos.node.port = w.readDefaultInt(infos.node.port)
8891

8992
// Set a proper name to report on the stats page
9093
fmt.Println()
91-
if infos.ethstats == "" {
94+
if infos.node.ethstats == "" {
9295
fmt.Printf("What should the explorer be called on the stats page?\n")
93-
infos.ethstats = w.readString() + ":" + w.conf.ethstats
96+
infos.node.ethstats = w.readString() + ":" + w.conf.ethstats
9497
} else {
95-
fmt.Printf("What should the explorer be called on the stats page? (default = %s)\n", infos.ethstats)
96-
infos.ethstats = w.readDefaultString(infos.ethstats) + ":" + w.conf.ethstats
98+
fmt.Printf("What should the explorer be called on the stats page? (default = %s)\n", infos.node.ethstats)
99+
infos.node.ethstats = w.readDefaultString(infos.node.ethstats) + ":" + w.conf.ethstats
97100
}
98101
// Try to deploy the explorer on the host
99102
nocache := false
@@ -102,7 +105,7 @@ func (w *wizard) deployExplorer() {
102105
fmt.Printf("Should the explorer be built from scratch (y/n)? (default = no)\n")
103106
nocache = w.readDefaultYesNo(false)
104107
}
105-
if out, err := deployExplorer(client, w.network, chain, infos, nocache); err != nil {
108+
if out, err := deployExplorer(client, w.network, w.conf.bootnodes, infos, nocache, w.conf.Genesis.Config.Clique != nil); err != nil {
106109
log.Error("Failed to deploy explorer container", "err", err)
107110
if len(out) > 0 {
108111
fmt.Printf("%s\n", out)

cmd/puppeth/wizard_network.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (w *wizard) deployComponent() {
174174
fmt.Println(" 1. Ethstats - Network monitoring tool")
175175
fmt.Println(" 2. Bootnode - Entry point of the network")
176176
fmt.Println(" 3. Sealer - Full node minting new blocks")
177-
fmt.Println(" 4. Explorer - Chain analysis webservice (ethash only)")
177+
fmt.Println(" 4. Explorer - Chain analysis webservice")
178178
fmt.Println(" 5. Wallet - Browser wallet for quick sends")
179179
fmt.Println(" 6. Faucet - Crypto faucet to give away funds")
180180
fmt.Println(" 7. Dashboard - Website listing above web-services")

0 commit comments

Comments
 (0)