@ -30,108 +30,86 @@ 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 $ ' 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 && \
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 && \
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 :
- "{{.EthPort}}:{{.EthPort}}"
- "{{.EthPort}}:{{.EthPort}}/udp" { { if not . VHost } }
- "{{.WebPort}}:4000" { { end } }
environment :
- ETH_PORT = { { . EthPort } }
- ETH_NAME = { { . EthName } }
- BLOCK_TRANSFORMER = { { . Transformer } } { { if . VHost } }
- VIRTUAL_HOST = { { . VHost } }
- VIRTUAL_PORT = 4000 { { end } }
volumes :
- { { . Datadir } } : / opt / app / . 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 , isCliqu e 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 . node . network ,
"Bootnodes" : strings . Join ( bootnodes , "," ) ,
"Ethstats" : config . node . ethstats ,
"EthPort" : config . node . port ,
} )
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 ( )
transformer := "base"
if isClique {
transformer = "clique"
}
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 , ":" ) ] ,
"Network" : network ,
"VHost" : config . host ,
"Ethstats" : config . node . ethstats ,
"Datadir" : config . node . datadir ,
"DBDir" : config . dbdir ,
"EthPort" : config . node . port ,
"EthName" : config . node . ethstats [ : strings . Index ( config . node . ethstats , ":" ) ] ,
"WebPort" : config . port ,
"Transformer" : transformer ,
} )
files [ filepath . Join ( workdir , "docker-compose.yaml" ) ] = composefile . Bytes ( )
files [ filepath . Join ( workdir , "chain.json" ) ] = chainspec
files [ filepath . Join ( workdir , "genesis.json" ) ] = config . node . genesis
// Upload the deployment files to the remote server (and clean up afterwards)
if out , err := client . Upload ( files ) ; err != nil {
@ -149,22 +127,20 @@ 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
node * nodeInfos
dbdir string
host string
port int
}
// Report converts the typed struct into a plain string->string map, containing
// most - but not all - fields for reporting to the user.
func ( info * explorerInfos ) Report ( ) map [ string ] string {
report := map [ string ] string {
"Data directory" : info . datadir ,
"Node listener port " : strconv . Itoa ( info . nodePort ) ,
"Ethstats username" : info . ethstats ,
"Website address " : info . webHost ,
"Website listener port " : strconv . Itoa ( info . webPort ) ,
"Website address " : info . host ,
"Website listener port " : strconv . Itoa ( info . port ) ,
"Ethereum listener port " : strconv . Itoa ( info . node . port ) ,
"Ethstats username" : info . node . ethstats ,
}
return report
}
@ -172,7 +148,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,13 +157,13 @@ func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
return nil , ErrServiceOffline
}
// Resolve the port from the host, or the reverse proxy
webP ort := infos . portmap [ "3 000/tcp" ]
if webP ort == 0 {
p ort := infos . portmap [ "4 000/tcp" ]
if p ort == 0 {
if proxy , _ := checkNginx ( client , network ) ; proxy != nil {
webP ort = proxy . port
p ort = proxy . port
}
}
if webP ort == 0 {
if p ort == 0 {
return nil , ErrNotExposed
}
// Resolve the host from the reverse-proxy and the config values
@ -196,17 +172,23 @@ func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
host = client . server
}
// Run a sanity check to see if the devp2p is reachable
nodePort := infos . portmap [ infos . envvars [ "NODE_PORT" ] ]
if err = checkPort ( client . server , nodePort ) ; err != nil {
log . Warn ( fmt . Sprintf ( "Explorer devp2p port seems unreachable" ) , "server" , client . server , "port" , nodePort , "err" , err )
p2pPort := infos . portmap [ infos . envvars [ "ETH_PORT" ] + "/tcp" ]
if err = checkPort ( host , p2pPort ) ; err != nil {
log . Warn ( "Explorer node seems unreachable" , "server" , host , "port" , p2pPort , "err" , err )
}
if err = checkPort ( host , port ) ; err != nil {
log . Warn ( "Explorer service seems unreachable" , "server" , host , "port" , port , "err" , err )
}
// Assemble and return the useful infos
stats := & explorerInfos {
datadir : infos . volumes [ "/root/.local/share/io.parity.ethereum" ] ,
nodePort : nodePort ,
webHost : host ,
webPort : webPort ,
ethstats : infos . envvars [ "STATS" ] ,
node : & nodeInfos {
datadir : infos . volumes [ "/opt/app/.ethereum" ] ,
port : infos . portmap [ infos . envvars [ "ETH_PORT" ] + "/tcp" ] ,
ethstats : infos . envvars [ "ETH_NAME" ] ,
} ,
dbdir : infos . volumes [ "/var/lib/postgresql/data" ] ,
host : host ,
port : port ,
}
return stats , nil
}