Skip to content

Commit

Permalink
daemon for jury (#18)
Browse files Browse the repository at this point in the history
* daemon for jury

* fix lint

* add validator key input confirm

* change to another PR. to fix
  • Loading branch information
abrahamcruise321 authored Apr 25, 2022
1 parent 0cc878e commit 4c6ea41
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 8 deletions.
7 changes: 6 additions & 1 deletion command/server/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package server

import (
"errors"
"github.com/hashicorp/go-hclog"
"net"

"github.com/dogechain-lab/jury/chain"
"github.com/dogechain-lab/jury/network"
"github.com/dogechain-lab/jury/secrets"
"github.com/dogechain-lab/jury/server"
"github.com/hashicorp/go-hclog"
"github.com/multiformats/go-multiaddr"
)

Expand All @@ -33,6 +33,7 @@ const (
devIntervalFlag = "dev-interval"
devFlag = "dev"
corsOriginFlag = "access-control-allow-origins"
daemonFlag = "daemon"
)

const (
Expand Down Expand Up @@ -68,6 +69,8 @@ type serverParams struct {
blockGasTarget uint64
devInterval uint64
isDevMode bool
isDaemon bool
validatorKey string

corsAllowedOrigins []string

Expand Down Expand Up @@ -160,5 +163,7 @@ func (p *serverParams) generateConfig() *server.Config {
RestoreFile: p.getRestoreFilePath(),
BlockTime: p.rawConfig.BlockTime,
LogLevel: hclog.LevelFromString(p.rawConfig.LogLevel),
Daemon: p.isDaemon,
ValidatorKey: p.validatorKey,
}
}
88 changes: 86 additions & 2 deletions command/server/server.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package server

import (
"bufio"
"fmt"
"strconv"

"github.com/dogechain-lab/jury/command"
"github.com/dogechain-lab/jury/crypto"
"github.com/dogechain-lab/jury/helper/daemon"
"github.com/howeyc/gopass"
"github.com/spf13/cobra"
"io/ioutil"
"log"
"os"
"strconv"
"strings"

"github.com/dogechain-lab/jury/command/helper"
"github.com/dogechain-lab/jury/network"
Expand Down Expand Up @@ -183,6 +190,13 @@ func setFlags(cmd *cobra.Command) {
"the CORS header indicating whether any JSON-RPC response can be shared with the specified origin",
)

cmd.Flags().BoolVar(
&params.isDaemon,
daemonFlag,
false,
"the flag indicating that the server ran as daemon",
)

setDevFlags(cmd)
}

Expand Down Expand Up @@ -235,9 +249,79 @@ func isConfigFileSpecified(cmd *cobra.Command) bool {
return cmd.Flags().Changed(configFlag)
}

func askForConfirmation() string {
reader := bufio.NewReader(os.Stdin)

for {
privateKeyRaw, err := gopass.GetPasswdPrompt("Enter ValidatorKey:", true, os.Stdin, os.Stdout)
if err != nil {
log.Println("Parent process ", os.Getpid(), " passwd prompt err:", err)
}

privateKey, err := crypto.BytesToPrivateKey(privateKeyRaw)
if err != nil {
log.Println("Parent process ", os.Getpid(), " input to private key, err:", err)
}

validatorKeyAddr := crypto.PubKeyToAddress(&privateKey.PublicKey)

log.Println("Parent process ", os.Getpid(), " passwd prompt, ValidatorKey len:", len(params.validatorKey),
", ValidatorKeyAddr: ", validatorKeyAddr.String())

fmt.Printf("ValidatorKey Address: %s [y/n]: ", validatorKeyAddr.String())

response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}

response = strings.ToLower(strings.TrimSpace(response))

if response == "y" || response == "yes" {
return string(privateKeyRaw)
} else if response == "n" || response == "no" {
continue
}
}
}

func runCommand(cmd *cobra.Command, _ []string) {
outputter := command.InitializeOutputter(cmd)

log.Println("Main process run isDaemon:", params.isDaemon)

// Launch daemons
if params.isDaemon {
// First time, daemonIdx is empty
daemonIdx := os.Getenv(daemon.EnvDaemonIdx)
if len(daemonIdx) == 0 {
params.validatorKey = askForConfirmation()
} else {
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
log.Println("Child process ", os.Getpid(), " read pipe data err: ", err)
} else {
log.Println("Child process ", os.Getpid(), " read pipe data: ", len(string(data)))
params.validatorKey = string(data)
}
}

// Create a daemon object
newDaemon := daemon.NewDaemon(daemon.DaemonLog)
newDaemon.MaxCount = daemon.MaxCount
newDaemon.ValidatorKey = params.validatorKey

// Execute daemon mode
newDaemon.Run()

// When params.isDaemon = true,
// the following code will only be executed by the final child process,
// and neither the main process nor the daemon will execute
log.Println("Child process ", os.Getpid(), "start...")
log.Println("Child process ", os.Getpid(), "isDaemon: ", params.isDaemon)
log.Println("Child process ", os.Getpid(), "ValidatorKey len: ", len(params.validatorKey))
}

if err := runServerLoop(params.generateConfig(), outputter); err != nil {
outputter.SetError(err)
outputter.WriteOutput()
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ require (
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/godbus/dbus/v5 v5.0.6 // indirect
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect
github.com/ipfs/go-cid v0.1.0 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZdu
github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
Expand Down Expand Up @@ -1384,6 +1386,7 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1i
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
12 changes: 12 additions & 0 deletions helper/daemon/attr_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !windows && !plan9
// +build !windows,!plan9

package daemon

import "syscall"

func NewSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
Setsid: true,
}
}
12 changes: 12 additions & 0 deletions helper/daemon/attr_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build windows
// +build windows

package daemon

import "syscall"

func NewSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
HideWindow: true,
}
}
171 changes: 171 additions & 0 deletions helper/daemon/daemon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package daemon

import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"time"
)

const EnvDaemonIdx = "XW_DAEMON_IDX"
const DaemonLog = "daemon.log"
const MaxCount = 5

// The number of times background was called at runtime
var runIdx int = 0

// Daemon
type Daemon struct {
// records the standard output and error output of daemons and child processes
LogFile string
// The maximum number of cycle restarts. If it is 0, it will restart indefinitely
MaxCount int
// The maximum number of consecutive startup failures or abnormal exits.
// Exceed this number, the daemon exits and the child process will not be restarted
MaxError int
// The minimum time (in seconds) for a child process to exit normally
// Less than this time is considered as abnormal exit
MinExitTime int64
// validator key for daemon
ValidatorKey string
}

// Turn the the main program into background running (start a child process, and then exit)
// logFile - If it is not empty, the standard output and error output of the child process will be recorded in this file
// isExit - Whether to exit the main program directly after the child process started.
// If false, the main program returns * OS Process, the child process returns nil.
// It needs to be handled by the caller.
func Background(logFile string, priKey string, isExit bool) (*exec.Cmd, error) {
// Check whether the child process or the parent process
runIdx++

envIdx, err := strconv.Atoi(os.Getenv(EnvDaemonIdx))

if err != nil {
envIdx = 0
}

if runIdx <= envIdx { //child process, exit
return nil, nil
}

// Setting child process environment variables
env := os.Environ()
env = append(env, fmt.Sprintf("%s=%d", EnvDaemonIdx, runIdx))

// Start child process
cmd, err := startProc(os.Args, env, logFile, priKey)
if err != nil {
log.Println(os.Getpid(), "Start child process failed :", err)

return nil, err
} else {
log.Println(os.Getpid(), ":", "Start child process succeed:", "->", cmd.Process.Pid, "\n ")
}

if isExit {
os.Exit(0)
}

return cmd, nil
}

func NewDaemon(logFile string) *Daemon {
return &Daemon{
LogFile: logFile,
MaxCount: 0,
MaxError: 3,
MinExitTime: 10,
}
}

// Run Start the background daemon
func (d *Daemon) Run() {
// Start a daemon and exit
_, _ = Background(d.LogFile, d.ValidatorKey, true)

// The daemon starts a child process and circularly monitors it
var t int64

count := 1
errNum := 0

for {
//daemon Information description
dInfo := fmt.Sprintf("Daemon (pid:%d; count:%d/%d; errNum:%d/%d):",
os.Getpid(), count, d.MaxCount, errNum, d.MaxError)
if errNum > d.MaxError {
log.Println(dInfo, "Failed to start the subprocess too many times, quit")
os.Exit(1)
}

if (d.MaxCount > 0) && (count > d.MaxCount) {
log.Println(dInfo, "Too many restarts, exit")
os.Exit(0)
}
count++

t = time.Now().Unix() // Start timestamp
cmd, err := Background(d.LogFile, d.ValidatorKey, false)

if err != nil {
log.Println(dInfo, "The child process failed to start, ", "err:", err)
errNum++

continue
}

// child process
if cmd == nil {
log.Printf("Child process pid=%d: start running...", os.Getpid())

break
}

// Parent process: wait for the child process to exit
err = cmd.Wait()
duration := time.Now().Unix() - t // Child process running seconds

if duration < d.MinExitTime { // Abnormal exit
errNum++
} else { // Normal exit
errNum = 0
}

log.Printf("%s Monitoring child process(%d) quit, it has been running for %d seconds: %v\n",
dInfo, cmd.ProcessState.Pid(), duration, err)
}
}

func startProc(args, env []string, logFile string, pipeData string) (*exec.Cmd, error) {
cmd := &exec.Cmd{
Path: args[0],
Args: args,
Env: env,
SysProcAttr: NewSysProcAttr(),
}

if logFile != "" {
stdout, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Println(os.Getpid(), ": Error opening log file:", err)

return nil, err
}

cmd.Stderr = stdout
cmd.Stdout = stdout
}

cmdIn, _ := cmd.StdinPipe()
_, _ = cmdIn.Write([]byte(pipeData))
_ = cmdIn.Close()

if err := cmd.Start(); err != nil {
return nil, err
}

return cmd, nil
}
Loading

0 comments on commit 4c6ea41

Please sign in to comment.