Allow running Nimbus as a Windows service (--run-as-service)
This commit is contained in:
parent
cdeae90806
commit
e6723ddb24
|
@ -44,6 +44,11 @@ const
|
|||
defaultAdminListenAddress* = (static ValidIpAddress.init("127.0.0.1"))
|
||||
defaultSigningNodeRequestTimeout* = 60
|
||||
|
||||
when defined(windows):
|
||||
{.pragma: windowsOnly.}
|
||||
else:
|
||||
{.pragma: windowsOnly, hidden.}
|
||||
|
||||
type
|
||||
BNStartUpCmd* {.pure.} = enum
|
||||
noCommand
|
||||
|
@ -66,9 +71,6 @@ type
|
|||
# status = "Displays status information about all deposits"
|
||||
exit = "Submits a validator voluntary exit"
|
||||
|
||||
VCStartUpCmd* = enum
|
||||
VCNoCommand
|
||||
|
||||
SNStartUpCmd* = enum
|
||||
SNNoCommand
|
||||
|
||||
|
@ -193,6 +195,12 @@ type
|
|||
defaultValue: BNStartUpCmd.noCommand }: BNStartUpCmd
|
||||
|
||||
of BNStartUpCmd.noCommand:
|
||||
runAsServiceFlag* {.
|
||||
windowsOnly
|
||||
defaultValue: false,
|
||||
desc: "Run as a Windows service"
|
||||
name: "run-as-service" }: bool
|
||||
|
||||
bootstrapNodes* {.
|
||||
desc: "Specifies one or more bootstrap nodes to use when connecting to the network"
|
||||
abbr: "b"
|
||||
|
@ -743,25 +751,20 @@ type
|
|||
desc: "A file specifying the authorizition token required for accessing the keymanager API"
|
||||
name: "keymanager-token-file" }: Option[InputFile]
|
||||
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: VCNoCommand }: VCStartUpCmd
|
||||
graffiti* {.
|
||||
desc: "The graffiti value that will appear in proposed blocks. " &
|
||||
"You can use a 0x-prefixed hex encoded string to specify " &
|
||||
"raw bytes"
|
||||
name: "graffiti" }: Option[GraffitiBytes]
|
||||
|
||||
of VCNoCommand:
|
||||
graffiti* {.
|
||||
desc: "The graffiti value that will appear in proposed blocks. " &
|
||||
"You can use a 0x-prefixed hex encoded string to specify " &
|
||||
"raw bytes"
|
||||
name: "graffiti" }: Option[GraffitiBytes]
|
||||
stopAtEpoch* {.
|
||||
desc: "A positive epoch selects the epoch at which to stop"
|
||||
defaultValue: 0
|
||||
name: "stop-at-epoch" }: uint64
|
||||
|
||||
stopAtEpoch* {.
|
||||
desc: "A positive epoch selects the epoch at which to stop"
|
||||
defaultValue: 0
|
||||
name: "stop-at-epoch" }: uint64
|
||||
|
||||
beaconNodes* {.
|
||||
desc: "URL addresses to one or more beacon node HTTP REST APIs",
|
||||
name: "beacon-node" }: seq[string]
|
||||
beaconNodes* {.
|
||||
desc: "URL addresses to one or more beacon node HTTP REST APIs",
|
||||
name: "beacon-node" }: seq[string]
|
||||
|
||||
SigningNodeConf* = object
|
||||
configFile* {.
|
||||
|
@ -812,35 +815,30 @@ type
|
|||
defaultValue: defaultSigningNodeRequestTimeout
|
||||
name: "request-timeout" }: int
|
||||
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: SNNoCommand }: SNStartUpCmd
|
||||
bindPort* {.
|
||||
desc: "Port for the REST (BETA version) HTTP server"
|
||||
defaultValue: DefaultEth2RestPort
|
||||
defaultValueDesc: "5052"
|
||||
name: "bind-port" }: Port
|
||||
|
||||
of SNNoCommand:
|
||||
bindPort* {.
|
||||
desc: "Port for the REST (BETA version) HTTP server"
|
||||
defaultValue: DefaultEth2RestPort
|
||||
defaultValueDesc: "5052"
|
||||
name: "bind-port" }: Port
|
||||
bindAddress* {.
|
||||
desc: "Listening address of the REST (BETA version) HTTP server"
|
||||
defaultValue: defaultAdminListenAddress
|
||||
defaultValueDesc: "127.0.0.1"
|
||||
name: "bind-address" }: ValidIpAddress
|
||||
|
||||
bindAddress* {.
|
||||
desc: "Listening address of the REST (BETA version) HTTP server"
|
||||
defaultValue: defaultAdminListenAddress
|
||||
defaultValueDesc: "127.0.0.1"
|
||||
name: "bind-address" }: ValidIpAddress
|
||||
tlsEnabled* {.
|
||||
desc: "Use secure TLS communication for REST (BETA version) server"
|
||||
defaultValue: false
|
||||
name: "tls" }: bool
|
||||
|
||||
tlsEnabled* {.
|
||||
desc: "Use secure TLS communication for REST (BETA version) server"
|
||||
defaultValue: false
|
||||
name: "tls" }: bool
|
||||
tlsCertificate* {.
|
||||
desc: "Path to SSL certificate file"
|
||||
name: "tls-cert" }: Option[InputFile]
|
||||
|
||||
tlsCertificate* {.
|
||||
desc: "Path to SSL certificate file"
|
||||
name: "tls-cert" }: Option[InputFile]
|
||||
|
||||
tlsPrivateKey* {.
|
||||
desc: "Path to SSL ceritificate's private key"
|
||||
name: "tls-key" }: Option[InputFile]
|
||||
tlsPrivateKey* {.
|
||||
desc: "Path to SSL ceritificate's private key"
|
||||
name: "tls-key" }: Option[InputFile]
|
||||
|
||||
AnyConf* = BeaconNodeConf | ValidatorClientConf | SigningNodeConf
|
||||
|
||||
|
@ -1006,6 +1004,9 @@ func outWalletFile*(config: BeaconNodeConf): Option[OutFile] =
|
|||
func databaseDir*(config: AnyConf): string =
|
||||
config.dataDir / "db"
|
||||
|
||||
func runAsService*(config: BeaconNodeConf): bool =
|
||||
config.cmd == noCommand and config.runAsServiceFlag
|
||||
|
||||
template writeValue*(writer: var JsonWriter,
|
||||
value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) =
|
||||
writer.writeValue(string value)
|
||||
|
|
|
@ -29,6 +29,64 @@ from
|
|||
import
|
||||
TopicParams, validateParameters, init
|
||||
|
||||
when defined(windows):
|
||||
import winlean
|
||||
|
||||
type
|
||||
LPCSTR* = cstring
|
||||
LPSTR* = cstring
|
||||
|
||||
SERVICE_STATUS* {.final, pure.} = object
|
||||
dwServiceType*: DWORD
|
||||
dwCurrentState*: DWORD
|
||||
dwControlsAccepted*: DWORD
|
||||
dwWin32ExitCode*: DWORD
|
||||
dwServiceSpecificExitCode*: DWORD
|
||||
dwCheckPoint*: DWORD
|
||||
dwWaitHint*: DWORD
|
||||
|
||||
SERVICE_STATUS_HANDLE* = DWORD
|
||||
LPSERVICE_STATUS* = ptr SERVICE_STATUS
|
||||
LPSERVICE_MAIN_FUNCTION* = proc (para1: DWORD, para2: LPSTR) {.stdcall.}
|
||||
|
||||
SERVICE_TABLE_ENTRY* {.final, pure.} = object
|
||||
lpServiceName*: LPSTR
|
||||
lpServiceProc*: LPSERVICE_MAIN_FUNCTION
|
||||
|
||||
LPSERVICE_TABLE_ENTRY* = ptr SERVICE_TABLE_ENTRY
|
||||
LPHANDLER_FUNCTION* = proc (para1: DWORD): WINBOOL{.stdcall.}
|
||||
|
||||
const
|
||||
SERVICE_WIN32_OWN_PROCESS = 16
|
||||
SERVICE_RUNNING = 4
|
||||
SERVICE_STOPPED = 1
|
||||
SERVICE_START_PENDING = 2
|
||||
SERVICE_STOP_PENDING = 3
|
||||
SERVICE_CONTROL_STOP = 1
|
||||
SERVICE_CONTROL_PAUSE = 2
|
||||
SERVICE_CONTROL_CONTINUE = 3
|
||||
SERVICE_CONTROL_INTERROGATE = 4
|
||||
SERVICE_ACCEPT_STOP = 1
|
||||
NO_ERROR = 0
|
||||
SERVICE_NAME = LPCSTR "NIMBUS_BEACON_NODE"
|
||||
|
||||
var
|
||||
gSvcStatusHandle: SERVICE_STATUS_HANDLE
|
||||
gSvcStatus: SERVICE_STATUS
|
||||
|
||||
proc reportServiceStatus*(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD) {.gcsafe.}
|
||||
|
||||
proc StartServiceCtrlDispatcher*(lpServiceStartTable: LPSERVICE_TABLE_ENTRY): WINBOOL{.
|
||||
stdcall, dynlib: "advapi32", importc: "StartServiceCtrlDispatcherA".}
|
||||
|
||||
proc SetServiceStatus*(hServiceStatus: SERVICE_STATUS_HANDLE,
|
||||
lpServiceStatus: LPSERVICE_STATUS): WINBOOL{.stdcall,
|
||||
dynlib: "advapi32", importc: "SetServiceStatus".}
|
||||
|
||||
proc RegisterServiceCtrlHandler*(lpServiceName: LPCSTR,
|
||||
lpHandlerProc: LPHANDLER_FUNCTION): SERVICE_STATUS_HANDLE{.
|
||||
stdcall, dynlib: "advapi32", importc: "RegisterServiceCtrlHandlerA".}
|
||||
|
||||
type
|
||||
RpcServer = RpcHttpServer
|
||||
|
||||
|
@ -1088,6 +1146,10 @@ proc onSlotStart(
|
|||
# Check before any re-scheduling of onSlotStart()
|
||||
checkIfShouldStopAtEpoch(wallSlot, node.config.stopAtEpoch)
|
||||
|
||||
when defined(windows):
|
||||
if node.config.runAsService:
|
||||
reportServiceStatus(SERVICE_RUNNING, NO_ERROR, 0)
|
||||
|
||||
beacon_slot.set wallSlot.toGaugeValue
|
||||
beacon_current_epoch.set wallSlot.epoch.toGaugeValue
|
||||
|
||||
|
@ -1727,7 +1789,96 @@ proc doSlashingInterchange(conf: BeaconNodeConf) {.raises: [Defect, CatchableErr
|
|||
of SlashProtCmd.`import`:
|
||||
conf.doSlashingImport()
|
||||
|
||||
proc handleStartUpCmd(config: var BeaconNodeConf) {.raises: [Defect, CatchableError].} =
|
||||
# Single RNG instance for the application - will be seeded on construction
|
||||
# and avoid using system resources (such as urandom) after that
|
||||
let rng = keys.newRng()
|
||||
|
||||
case config.cmd
|
||||
of BNStartUpCmd.createTestnet: doCreateTestnet(config, rng[])
|
||||
of BNStartUpCmd.noCommand: doRunBeaconNode(config, rng)
|
||||
of BNStartUpCmd.deposits: doDeposits(config, rng[])
|
||||
of BNStartUpCmd.wallets: doWallets(config, rng[])
|
||||
of BNStartUpCmd.record: doRecord(config, rng[])
|
||||
of BNStartUpCmd.web3: doWeb3Cmd(config)
|
||||
of BNStartUpCmd.slashingdb: doSlashingInterchange(config)
|
||||
of BNStartupCmd.trustedNodeSync:
|
||||
let
|
||||
network = loadEth2Network(config)
|
||||
cfg = network.cfg
|
||||
genesis =
|
||||
if network.genesisData.len > 0:
|
||||
newClone(readSszForkedHashedBeaconState(
|
||||
cfg,
|
||||
network.genesisData.toOpenArrayByte(0, network.genesisData.high())))
|
||||
else: nil
|
||||
|
||||
waitFor doTrustedNodeSync(
|
||||
cfg,
|
||||
config.databaseDir,
|
||||
config.trustedNodeUrl,
|
||||
config.blockId,
|
||||
config.backfillBlocks,
|
||||
genesis)
|
||||
|
||||
{.pop.} # TODO moduletests exceptions
|
||||
|
||||
when defined(windows):
|
||||
proc reportServiceStatus*(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD) {.gcsafe.} =
|
||||
gSvcStatus.dwCurrentState = dwCurrentState
|
||||
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode
|
||||
gSvcStatus.dwWaitHint = dwWaitHint
|
||||
if dwCurrentState == SERVICE_START_PENDING:
|
||||
gSvcStatus.dwControlsAccepted = 0
|
||||
else:
|
||||
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
|
||||
|
||||
# TODO
|
||||
# We can use non-zero values for the `dwCheckPoint` parameter to report
|
||||
# progress during lengthy operations such as start-up and shut down.
|
||||
gSvcStatus.dwCheckPoint = 0
|
||||
|
||||
# Report the status of the service to the SCM.
|
||||
let status = SetServiceStatus(gSvcStatusHandle, addr gSvcStatus)
|
||||
debug "Service status updated", status
|
||||
|
||||
proc serviceControlHandler(dwCtrl: DWORD): WINBOOL {.stdcall.} =
|
||||
case dwCtrl
|
||||
of SERVICE_CONTROL_STOP:
|
||||
# We re reporting that we plan stop the service in 10 seconds
|
||||
reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 10_000)
|
||||
bnStatus = BeaconNodeStatus.Stopping
|
||||
of SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE:
|
||||
warn "The Nimbus service cannot be paused and resimed"
|
||||
of SERVICE_CONTROL_INTERROGATE:
|
||||
# The default behavior is correct.
|
||||
# The service control manager will report our last status.
|
||||
discard
|
||||
else:
|
||||
debug "Service received an unexpected user-defined control message",
|
||||
msg = dwCtrl
|
||||
|
||||
proc serviceMainFunction(dwArgc: DWORD, lpszArgv: LPSTR) {.stdcall.} =
|
||||
# The service is launched in a fresh thread created by Windows, so
|
||||
# we must initialize the Nim GC here
|
||||
setupForeignThreadGc()
|
||||
|
||||
gSvcStatusHandle = RegisterServiceCtrlHandler(
|
||||
SERVICE_NAME,
|
||||
serviceControlHandler)
|
||||
|
||||
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS
|
||||
gSvcStatus.dwServiceSpecificExitCode = 0
|
||||
reportServiceStatus(SERVICE_RUNNING, NO_ERROR, 0)
|
||||
|
||||
info "Service thread started"
|
||||
|
||||
var config = makeBannerAndConfig(clientId, BeaconNodeConf)
|
||||
handleStartUpCmd(config)
|
||||
|
||||
info "Service thread stopped"
|
||||
reportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0) # we have to report back when we stopped!
|
||||
|
||||
programMain:
|
||||
var
|
||||
config = makeBannerAndConfig(clientId, BeaconNodeConf)
|
||||
|
@ -1762,33 +1913,18 @@ programMain:
|
|||
quit 0
|
||||
c_signal(SIGTERM, exitImmediatelyOnSIGTERM)
|
||||
|
||||
# Single RNG instance for the application - will be seeded on construction
|
||||
# and avoid using system resources (such as urandom) after that
|
||||
let rng = keys.newRng()
|
||||
when defined(windows):
|
||||
if config.runAsService:
|
||||
var dispatchTable = [
|
||||
SERVICE_TABLE_ENTRY(lpServiceName: SERVICE_NAME, lpServiceProc: serviceMainFunction),
|
||||
SERVICE_TABLE_ENTRY(lpServiceName: nil, lpServiceProc: nil) # last entry must be nil
|
||||
]
|
||||
|
||||
case config.cmd
|
||||
of BNStartUpCmd.createTestnet: doCreateTestnet(config, rng[])
|
||||
of BNStartUpCmd.noCommand: doRunBeaconNode(config, rng)
|
||||
of BNStartUpCmd.deposits: doDeposits(config, rng[])
|
||||
of BNStartUpCmd.wallets: doWallets(config, rng[])
|
||||
of BNStartUpCmd.record: doRecord(config, rng[])
|
||||
of BNStartUpCmd.web3: doWeb3Cmd(config)
|
||||
of BNStartUpCmd.slashingdb: doSlashingInterchange(config)
|
||||
of BNStartupCmd.trustedNodeSync:
|
||||
let
|
||||
network = loadEth2Network(config)
|
||||
cfg = network.cfg
|
||||
genesis =
|
||||
if network.genesisData.len > 0:
|
||||
newClone(readSszForkedHashedBeaconState(
|
||||
cfg,
|
||||
network.genesisData.toOpenArrayByte(0, network.genesisData.high())))
|
||||
else: nil
|
||||
|
||||
waitFor doTrustedNodeSync(
|
||||
cfg,
|
||||
config.databaseDir,
|
||||
config.trustedNodeUrl,
|
||||
config.blockId,
|
||||
config.backfillBlocks,
|
||||
genesis)
|
||||
let status = StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY(addr dispatchTable[0]))
|
||||
if status == 0:
|
||||
fatal "Failed to start Windows service", errorCode = getLastError()
|
||||
quit 1
|
||||
else:
|
||||
handleStartUpCmd(config)
|
||||
else:
|
||||
handleStartUpCmd(config)
|
||||
|
|
|
@ -332,17 +332,15 @@ programMain:
|
|||
SigningNodeConf)
|
||||
setupLogging(config.logLevel, config.logStdout, config.logFile)
|
||||
|
||||
case config.cmd
|
||||
of SNNoCommand:
|
||||
var sn = SigningNode.init(config)
|
||||
notice "Launching signing node", version = fullVersionStr,
|
||||
cmdParams = commandLineParams(), config,
|
||||
validators_count = sn.attachedValidators.count()
|
||||
sn.installApiHandlers()
|
||||
sn.start()
|
||||
try:
|
||||
runForever()
|
||||
finally:
|
||||
waitFor sn.stop()
|
||||
waitFor sn.close()
|
||||
discard sn.stop()
|
||||
var sn = SigningNode.init(config)
|
||||
notice "Launching signing node", version = fullVersionStr,
|
||||
cmdParams = commandLineParams(), config,
|
||||
validators_count = sn.attachedValidators.count()
|
||||
sn.installApiHandlers()
|
||||
sn.start()
|
||||
try:
|
||||
runForever()
|
||||
finally:
|
||||
waitFor sn.stop()
|
||||
waitFor sn.close()
|
||||
discard sn.stop()
|
||||
|
|
|
@ -165,37 +165,35 @@ programMain:
|
|||
|
||||
setupLogging(config.logLevel, config.logStdout, config.logFile)
|
||||
|
||||
case config.cmd
|
||||
of VCNoCommand:
|
||||
let beaconNodes =
|
||||
block:
|
||||
var servers: seq[BeaconNodeServerRef]
|
||||
let flags = {RestClientFlag.CommaSeparatedArray}
|
||||
for url in config.beaconNodes:
|
||||
let res = RestClientRef.new(url, flags = flags)
|
||||
if res.isErr():
|
||||
warn "Unable to resolve remote beacon node server's hostname",
|
||||
url = url
|
||||
else:
|
||||
servers.add(BeaconNodeServerRef(client: res.get(), endpoint: url))
|
||||
servers
|
||||
let beaconNodes =
|
||||
block:
|
||||
var servers: seq[BeaconNodeServerRef]
|
||||
let flags = {RestClientFlag.CommaSeparatedArray}
|
||||
for url in config.beaconNodes:
|
||||
let res = RestClientRef.new(url, flags = flags)
|
||||
if res.isErr():
|
||||
warn "Unable to resolve remote beacon node server's hostname",
|
||||
url = url
|
||||
else:
|
||||
servers.add(BeaconNodeServerRef(client: res.get(), endpoint: url))
|
||||
servers
|
||||
|
||||
if len(beaconNodes) == 0:
|
||||
fatal "Not enough beacon nodes in command line"
|
||||
quit 1
|
||||
if len(beaconNodes) == 0:
|
||||
fatal "Not enough beacon nodes in command line"
|
||||
quit 1
|
||||
|
||||
notice "Launching validator client", version = fullVersionStr,
|
||||
cmdParams = commandLineParams(),
|
||||
config,
|
||||
beacon_nodes_count = len(beaconNodes)
|
||||
notice "Launching validator client", version = fullVersionStr,
|
||||
cmdParams = commandLineParams(),
|
||||
config,
|
||||
beacon_nodes_count = len(beaconNodes)
|
||||
|
||||
var vc = ValidatorClientRef(
|
||||
config: config,
|
||||
beaconNodes: beaconNodes,
|
||||
graffitiBytes: config.graffiti.get(defaultGraffitiBytes()),
|
||||
nodesAvailable: newAsyncEvent(),
|
||||
forksAvailable: newAsyncEvent()
|
||||
)
|
||||
var vc = ValidatorClientRef(
|
||||
config: config,
|
||||
beaconNodes: beaconNodes,
|
||||
graffitiBytes: config.graffiti.get(defaultGraffitiBytes()),
|
||||
nodesAvailable: newAsyncEvent(),
|
||||
forksAvailable: newAsyncEvent()
|
||||
)
|
||||
|
||||
waitFor asyncInit(vc)
|
||||
waitFor asyncRun(vc)
|
||||
waitFor asyncInit(vc)
|
||||
waitFor asyncRun(vc)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 0fc26c5b25a931fdd15a74f2d9028112ffd621ba
|
||||
Subproject commit 0a88d30e0035ad761b41886251149d54e65a916a
|
Loading…
Reference in New Issue