VC: Remote BN received block monitoring. (#4856)
* Initial commit with both methods enabled: `poll` and `event`. * Address review comments. * Address review comments. Fix copyright years. * After bump fixes.
This commit is contained in:
parent
e8c6af0636
commit
effe8b7f90
|
@ -120,6 +120,11 @@ type
|
||||||
Normal = "normal"
|
Normal = "normal"
|
||||||
SingleSalt = "single-salt"
|
SingleSalt = "single-salt"
|
||||||
|
|
||||||
|
BlockMonitoringType* {.pure.} = enum
|
||||||
|
Disabled = "disabled"
|
||||||
|
Poll = "poll"
|
||||||
|
Event = "event"
|
||||||
|
|
||||||
BeaconNodeConf* = object
|
BeaconNodeConf* = object
|
||||||
configFile* {.
|
configFile* {.
|
||||||
desc: "Loads the configuration from a TOML file"
|
desc: "Loads the configuration from a TOML file"
|
||||||
|
@ -944,6 +949,11 @@ type
|
||||||
defaultValueDesc: $defaultBeaconNodeUri
|
defaultValueDesc: $defaultBeaconNodeUri
|
||||||
name: "beacon-node" .}: seq[Uri]
|
name: "beacon-node" .}: seq[Uri]
|
||||||
|
|
||||||
|
monitoringType* {.
|
||||||
|
desc: "Enable block monitoring which are seen by beacon node (BETA)"
|
||||||
|
defaultValue: BlockMonitoringType.Disabled
|
||||||
|
name: "block-monitor-type".}: BlockMonitoringType
|
||||||
|
|
||||||
SigningNodeConf* = object
|
SigningNodeConf* = object
|
||||||
configFile* {.
|
configFile* {.
|
||||||
desc: "Loads the configuration from a TOML file"
|
desc: "Loads the configuration from a TOML file"
|
||||||
|
|
|
@ -3150,6 +3150,31 @@ proc decodeString*(t: typedesc[EventTopic],
|
||||||
else:
|
else:
|
||||||
err("Incorrect event's topic value")
|
err("Incorrect event's topic value")
|
||||||
|
|
||||||
|
proc encodeString*(value: set[EventTopic]): Result[string, cstring] =
|
||||||
|
var res: string
|
||||||
|
if EventTopic.Head in value:
|
||||||
|
res.add("head,")
|
||||||
|
if EventTopic.Block in value:
|
||||||
|
res.add("block,")
|
||||||
|
if EventTopic.Attestation in value:
|
||||||
|
res.add("attestation,")
|
||||||
|
if EventTopic.VoluntaryExit in value:
|
||||||
|
res.add("voluntary_exit,")
|
||||||
|
if EventTopic.FinalizedCheckpoint in value:
|
||||||
|
res.add("finalized_checkpoint,")
|
||||||
|
if EventTopic.ChainReorg in value:
|
||||||
|
res.add("chain_reorg,")
|
||||||
|
if EventTopic.ContributionAndProof in value:
|
||||||
|
res.add("contribution_and_proof,")
|
||||||
|
if EventTopic.LightClientFinalityUpdate in value:
|
||||||
|
res.add("light_client_finality_update,")
|
||||||
|
if EventTopic.LightClientOptimisticUpdate in value:
|
||||||
|
res.add("light_client_optimistic_update,")
|
||||||
|
if len(res) == 0:
|
||||||
|
return err("Topics set must not be empty")
|
||||||
|
res.setLen(len(res) - 1)
|
||||||
|
ok(res)
|
||||||
|
|
||||||
proc decodeString*(t: typedesc[ValidatorSig],
|
proc decodeString*(t: typedesc[ValidatorSig],
|
||||||
value: string): Result[ValidatorSig, cstring] =
|
value: string): Result[ValidatorSig, cstring] =
|
||||||
if len(value) != ValidatorSigSize + 2:
|
if len(value) != ValidatorSigSize + 2:
|
||||||
|
@ -3353,3 +3378,13 @@ proc decodeString*(t: typedesc[ConsensusFork],
|
||||||
of "capella": ok(ConsensusFork.Capella)
|
of "capella": ok(ConsensusFork.Capella)
|
||||||
of "deneb": ok(ConsensusFork.Deneb)
|
of "deneb": ok(ConsensusFork.Deneb)
|
||||||
else: err("Unsupported or invalid beacon block fork version")
|
else: err("Unsupported or invalid beacon block fork version")
|
||||||
|
|
||||||
|
proc decodeString*(t: typedesc[EventBeaconBlockObject],
|
||||||
|
value: string): Result[EventBeaconBlockObject, string] =
|
||||||
|
try:
|
||||||
|
ok(RestJson.decode(value, t,
|
||||||
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = true))
|
||||||
|
except SerializationError as exc:
|
||||||
|
err(exc.formatMsg("<data>"))
|
||||||
|
|
||||||
|
|
|
@ -110,11 +110,43 @@ proc getBlockHeaders*(slot: Option[Slot], parent_root: Option[Eth2Digest]
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders
|
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders
|
||||||
|
|
||||||
proc getBlockHeader*(block_id: BlockIdent): RestResponse[GetBlockHeaderResponse] {.
|
# proc getBlockHeader*(block_id: BlockIdent): RestResponse[GetBlockHeaderResponse] {.
|
||||||
|
# rest, endpoint: "/eth/v1/beacon/headers/{block_id}",
|
||||||
|
# meth: MethodGet.}
|
||||||
|
# ## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
|
||||||
|
|
||||||
|
proc getBlockHeaderPlain*(block_id: BlockIdent): RestPlainResponse {.
|
||||||
rest, endpoint: "/eth/v1/beacon/headers/{block_id}",
|
rest, endpoint: "/eth/v1/beacon/headers/{block_id}",
|
||||||
meth: MethodGet.}
|
meth: MethodGet.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
|
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
|
||||||
|
|
||||||
|
proc getBlockHeader*(
|
||||||
|
client: RestClientRef,
|
||||||
|
block_id: BlockIdent
|
||||||
|
): Future[Opt[GetBlockHeaderResponse]] {.async.} =
|
||||||
|
## https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
|
||||||
|
let resp = await client.getBlockHeaderPlain(block_id)
|
||||||
|
return
|
||||||
|
case resp.status
|
||||||
|
of 200:
|
||||||
|
let response = decodeBytes(GetBlockHeaderResponse, resp.data,
|
||||||
|
resp.contentType).valueOr:
|
||||||
|
raise newException(RestError, $error)
|
||||||
|
Opt.some(response)
|
||||||
|
of 404:
|
||||||
|
Opt.none(GetBlockHeaderResponse)
|
||||||
|
of 400, 500:
|
||||||
|
let error = decodeBytes(RestErrorMessage, resp.data,
|
||||||
|
resp.contentType).valueOr:
|
||||||
|
let msg = "Incorrect response error format (" & $resp.status &
|
||||||
|
") [" & $error & "]"
|
||||||
|
raise (ref RestResponseError)(msg: msg, status: resp.status)
|
||||||
|
let msg = "Error response (" & $resp.status & ") [" & error.message & "]"
|
||||||
|
raise (ref RestResponseError)(
|
||||||
|
msg: msg, status: error.code, message: error.message)
|
||||||
|
else:
|
||||||
|
raiseRestResponseError(resp)
|
||||||
|
|
||||||
proc publishBlock*(body: phase0.SignedBeaconBlock): RestPlainResponse {.
|
proc publishBlock*(body: phase0.SignedBeaconBlock): RestPlainResponse {.
|
||||||
rest, endpoint: "/eth/v1/beacon/blocks",
|
rest, endpoint: "/eth/v1/beacon/blocks",
|
||||||
meth: MethodPost.}
|
meth: MethodPost.}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import
|
||||||
rest_beacon_calls, rest_builder_calls, rest_config_calls, rest_debug_calls,
|
rest_beacon_calls, rest_builder_calls, rest_config_calls, rest_debug_calls,
|
||||||
rest_keymanager_calls, rest_light_client_calls,
|
rest_keymanager_calls, rest_light_client_calls,
|
||||||
rest_node_calls, rest_validator_calls,
|
rest_node_calls, rest_validator_calls,
|
||||||
rest_nimbus_calls, rest_common
|
rest_nimbus_calls, rest_event_calls, rest_common
|
||||||
]
|
]
|
||||||
|
|
||||||
export
|
export
|
||||||
|
@ -20,4 +20,4 @@ export
|
||||||
rest_beacon_calls, rest_builder_calls, rest_config_calls, rest_debug_calls,
|
rest_beacon_calls, rest_builder_calls, rest_config_calls, rest_debug_calls,
|
||||||
rest_keymanager_calls, rest_light_client_calls,
|
rest_keymanager_calls, rest_light_client_calls,
|
||||||
rest_node_calls, rest_validator_calls,
|
rest_node_calls, rest_validator_calls,
|
||||||
rest_nimbus_calls, rest_common
|
rest_nimbus_calls, rest_event_calls, rest_common
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed and distributed under either of
|
||||||
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||||
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
import
|
||||||
|
chronos, presto/client,
|
||||||
|
"."/[rest_types, eth2_rest_serialization]
|
||||||
|
|
||||||
|
proc subscribeEventStream*(topics: set[EventTopic]): RestHttpResponseRef {.
|
||||||
|
rest, endpoint: "/eth/v1/events", accept: "text/event-stream",
|
||||||
|
meth: MethodGet.}
|
||||||
|
## https://ethereum.github.io/beacon-APIs/#/Events/eventstream
|
|
@ -644,7 +644,7 @@ type
|
||||||
GetAggregatedAttestationResponse* = DataEnclosedObject[Attestation]
|
GetAggregatedAttestationResponse* = DataEnclosedObject[Attestation]
|
||||||
GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]]
|
GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]]
|
||||||
GetBlockAttestationsResponse* = DataEnclosedObject[seq[Attestation]]
|
GetBlockAttestationsResponse* = DataEnclosedObject[seq[Attestation]]
|
||||||
GetBlockHeaderResponse* = DataEnclosedObject[RestBlockHeaderInfo]
|
GetBlockHeaderResponse* = DataOptimisticObject[RestBlockHeaderInfo]
|
||||||
GetBlockHeadersResponse* = DataEnclosedObject[seq[RestBlockHeaderInfo]]
|
GetBlockHeadersResponse* = DataEnclosedObject[seq[RestBlockHeaderInfo]]
|
||||||
GetBlockRootResponse* = DataOptimisticObject[RestRoot]
|
GetBlockRootResponse* = DataOptimisticObject[RestRoot]
|
||||||
GetDebugChainHeadsResponse* = DataEnclosedObject[seq[RestChainHead]]
|
GetDebugChainHeadsResponse* = DataEnclosedObject[seq[RestChainHead]]
|
||||||
|
|
|
@ -622,7 +622,7 @@ proc getIndexedErrorMessage(response: RestPlainResponse): string =
|
||||||
else:
|
else:
|
||||||
"Unable to decode error response: [" & $res.error & "]"
|
"Unable to decode error response: [" & $res.error & "]"
|
||||||
|
|
||||||
proc getErrorMessage(response: RestPlainResponse): string =
|
proc getErrorMessage*(response: RestPlainResponse): string =
|
||||||
let res = decodeBytes(RestErrorMessage, response.data,
|
let res = decodeBytes(RestErrorMessage, response.data,
|
||||||
response.contentType)
|
response.contentType)
|
||||||
if res.isOk():
|
if res.isOk():
|
||||||
|
|
|
@ -9,7 +9,7 @@ import
|
||||||
std/sets,
|
std/sets,
|
||||||
chronicles,
|
chronicles,
|
||||||
../validators/activity_metrics,
|
../validators/activity_metrics,
|
||||||
"."/[common, api, block_service]
|
"."/[common, api]
|
||||||
|
|
||||||
const
|
const
|
||||||
ServiceName = "attestation_service"
|
ServiceName = "attestation_service"
|
||||||
|
@ -332,7 +332,7 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef,
|
||||||
duties: seq[DutyAndProof]) {.async.} =
|
duties: seq[DutyAndProof]) {.async.} =
|
||||||
let vc = service.client
|
let vc = service.client
|
||||||
# Waiting for blocks to be published before attesting.
|
# Waiting for blocks to be published before attesting.
|
||||||
await vc.waitForBlockPublished(slot, attestationSlotOffset)
|
await vc.waitForBlock(slot, attestationSlotOffset)
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let delay = vc.getDelay(slot.attestation_deadline())
|
let delay = vc.getDelay(slot.attestation_deadline())
|
||||||
|
@ -431,7 +431,7 @@ proc mainLoop(service: AttestationServiceRef) {.async.} =
|
||||||
try:
|
try:
|
||||||
let
|
let
|
||||||
# We use zero offset here, because we do waiting in
|
# We use zero offset here, because we do waiting in
|
||||||
# waitForBlockPublished(attestationSlotOffset).
|
# waitForBlock(attestationSlotOffset).
|
||||||
slot = await vc.checkedWaitForNextSlot(currentSlot,
|
slot = await vc.checkedWaitForNextSlot(currentSlot,
|
||||||
ZeroTimeDiff, false)
|
ZeroTimeDiff, false)
|
||||||
if slot.isNone():
|
if slot.isNone():
|
||||||
|
|
|
@ -9,10 +9,14 @@ import
|
||||||
chronicles,
|
chronicles,
|
||||||
".."/validators/activity_metrics,
|
".."/validators/activity_metrics,
|
||||||
".."/spec/forks,
|
".."/spec/forks,
|
||||||
common, api
|
common, api, fallback_service
|
||||||
|
|
||||||
const
|
const
|
||||||
ServiceName = "block_service"
|
ServiceName = "block_service"
|
||||||
|
BlockPollInterval = attestationSlotOffset.nanoseconds div 4
|
||||||
|
BlockPollOffset1 = TimeDiff(nanoseconds: BlockPollInterval)
|
||||||
|
BlockPollOffset2 = TimeDiff(nanoseconds: BlockPollInterval * 2)
|
||||||
|
BlockPollOffset3 = TimeDiff(nanoseconds: BlockPollInterval * 3)
|
||||||
|
|
||||||
logScope: service = ServiceName
|
logScope: service = ServiceName
|
||||||
|
|
||||||
|
@ -428,90 +432,263 @@ proc addOrReplaceProposers*(vc: ValidatorClientRef, epoch: Epoch,
|
||||||
res
|
res
|
||||||
vc.proposers[epoch] = ProposedData.init(epoch, dependentRoot, tasks)
|
vc.proposers[epoch] = ProposedData.init(epoch, dependentRoot, tasks)
|
||||||
|
|
||||||
proc waitForBlockPublished*(vc: ValidatorClientRef,
|
proc pollForEvents(service: BlockServiceRef, node: BeaconNodeServerRef,
|
||||||
slot: Slot, timediff: TimeDiff) {.async.} =
|
response: RestHttpResponseRef) {.async.} =
|
||||||
## This procedure will wait for all the block proposal tasks to be finished at
|
let vc = service.client
|
||||||
## slot ``slot``.
|
|
||||||
let
|
|
||||||
startTime = Moment.now()
|
|
||||||
pendingTasks =
|
|
||||||
block:
|
|
||||||
var res: seq[Future[void]]
|
|
||||||
let epochDuties = vc.proposers.getOrDefault(slot.epoch())
|
|
||||||
for task in epochDuties.duties:
|
|
||||||
if task.duty.slot == slot:
|
|
||||||
if not(task.future.finished()):
|
|
||||||
res.add(task.future)
|
|
||||||
res
|
|
||||||
waitTime = (start_beacon_time(slot) + timediff) - vc.beaconClock.now()
|
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
start_time = startTime
|
node = node
|
||||||
pending_tasks = len(pendingTasks)
|
|
||||||
slot = slot
|
|
||||||
timediff = timediff
|
|
||||||
|
|
||||||
# TODO (cheatfate): This algorithm should be tuned, when we will have ability
|
while true:
|
||||||
# to monitor block proposals which are not created by validators bundled with
|
let events =
|
||||||
# VC.
|
|
||||||
logScope: wait_time = waitTime
|
|
||||||
if waitTime.nanoseconds > 0'i64:
|
|
||||||
if len(pendingTasks) > 0:
|
|
||||||
# Block proposal pending
|
|
||||||
try:
|
|
||||||
await allFutures(pendingTasks).wait(nanoseconds(waitTime.nanoseconds))
|
|
||||||
trace "Block proposal awaited"
|
|
||||||
# The expected block arrived - in our async loop however, we might
|
|
||||||
# have been doing other processing that caused delays here so we'll
|
|
||||||
# cap the waiting to the time when we would have sent out attestations
|
|
||||||
# had the block not arrived. An opposite case is that we received
|
|
||||||
# (or produced) a block that has not yet reached our neighbours. To
|
|
||||||
# protect against our attestations being dropped (because the others
|
|
||||||
# have not yet seen the block), we'll impose a minimum delay of
|
|
||||||
# 2000ms. The delay is enforced only when we're not hitting the
|
|
||||||
# "normal" cutoff time for sending out attestations. An earlier delay
|
|
||||||
# of 250ms has proven to be not enough, increasing the risk of losing
|
|
||||||
# attestations, and with growing block sizes, 1000ms started to be
|
|
||||||
# risky as well. Regardless, because we "just" received the block,
|
|
||||||
# we'll impose the delay.
|
|
||||||
|
|
||||||
# Take into consideration chains with a different slot time
|
|
||||||
const afterBlockDelay = nanos(attestationSlotOffset.nanoseconds div 2)
|
|
||||||
let
|
|
||||||
afterBlockTime = vc.beaconClock.now() + afterBlockDelay
|
|
||||||
afterBlockCutoff = vc.beaconClock.fromNow(
|
|
||||||
min(afterBlockTime,
|
|
||||||
slot.attestation_deadline() + afterBlockDelay))
|
|
||||||
if afterBlockCutoff.inFuture:
|
|
||||||
debug "Got block, waiting to send attestations",
|
|
||||||
after_block_cutoff = shortLog(afterBlockCutoff.offset)
|
|
||||||
await sleepAsync(afterBlockCutoff.offset)
|
|
||||||
except CancelledError as exc:
|
|
||||||
let dur = Moment.now() - startTime
|
|
||||||
debug "Waiting for block publication interrupted", duration = dur
|
|
||||||
raise exc
|
|
||||||
except AsyncTimeoutError:
|
|
||||||
let dur = Moment.now() - startTime
|
|
||||||
debug "Block was not published in time", duration = dur
|
|
||||||
else:
|
|
||||||
# No pending block proposals.
|
|
||||||
try:
|
try:
|
||||||
await sleepAsync(nanoseconds(waitTime.nanoseconds))
|
await response.getServerSentEvents()
|
||||||
|
except RestError as exc:
|
||||||
|
debug "Unable to receive server-sent event", reason = $exc.msg
|
||||||
|
return
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
let dur = Moment.now() - startTime
|
|
||||||
debug "Waiting for block publication interrupted", duration = dur
|
|
||||||
raise exc
|
raise exc
|
||||||
except CatchableError as exc:
|
except CatchableError as exc:
|
||||||
let dur = Moment.now() - startTime
|
warn "Got an unexpected error, " &
|
||||||
error "Unexpected error occured while waiting for block publication",
|
"while reading server-sent event stream", reason = $exc.msg
|
||||||
err_name = exc.name, err_msg = exc.msg, duration = dur
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
case event.name
|
||||||
|
of "data":
|
||||||
|
let blck = EventBeaconBlockObject.decodeString(event.data).valueOr:
|
||||||
|
debug "Got invalid block event format", reason = error
|
||||||
|
return
|
||||||
|
vc.registerBlock(blck)
|
||||||
|
of "event":
|
||||||
|
if event.data != "block":
|
||||||
|
debug "Got unexpected event name field", event_name = event.name,
|
||||||
|
event_data = event.data
|
||||||
|
else:
|
||||||
|
debug "Got some unexpected event field", event_name = event.name
|
||||||
|
|
||||||
|
if len(events) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
proc runBlockEventMonitor(service: BlockServiceRef,
|
||||||
|
node: BeaconNodeServerRef) {.async.} =
|
||||||
|
let
|
||||||
|
vc = service.client
|
||||||
|
roles = {BeaconNodeRole.BlockProposalData}
|
||||||
|
statuses = {RestBeaconNodeStatus.Synced}
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
node = node
|
||||||
|
|
||||||
|
while true:
|
||||||
|
while node.status notin statuses:
|
||||||
|
await vc.waitNodes(nil, statuses, roles, false)
|
||||||
|
|
||||||
|
let response =
|
||||||
|
block:
|
||||||
|
var resp: HttpClientResponseRef
|
||||||
|
try:
|
||||||
|
resp = await node.client.subscribeEventStream({EventTopic.Block})
|
||||||
|
if resp.status == 200:
|
||||||
|
resp
|
||||||
|
else:
|
||||||
|
let body = await resp.getBodyBytes()
|
||||||
|
await resp.closeWait()
|
||||||
|
let
|
||||||
|
plain = RestPlainResponse(status: resp.status,
|
||||||
|
contentType: resp.contentType, data: body)
|
||||||
|
reason = plain.getErrorMessage()
|
||||||
|
debug "Unable to to obtain events stream", code = resp.status,
|
||||||
|
reason = reason
|
||||||
|
return
|
||||||
|
except RestError as exc:
|
||||||
|
if not(isNil(resp)): await resp.closeWait()
|
||||||
|
debug "Unable to obtain events stream", reason = $exc.msg
|
||||||
|
return
|
||||||
|
except CancelledError as exc:
|
||||||
|
if not(isNil(resp)): await resp.closeWait()
|
||||||
|
debug "Block monitoring loop has been interrupted"
|
||||||
|
raise exc
|
||||||
|
except CatchableError as exc:
|
||||||
|
if not(isNil(resp)): await resp.closeWait()
|
||||||
|
warn "Got an unexpected error while trying to establish event stream",
|
||||||
|
reason = $exc.msg
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await service.pollForEvents(node, response)
|
||||||
|
except CancelledError as exc:
|
||||||
|
raise exc
|
||||||
|
except CatchableError as exc:
|
||||||
|
warn "Got an unexpected error while receiving block events",
|
||||||
|
reason = $exc.msg
|
||||||
|
finally:
|
||||||
|
await response.closeWait()
|
||||||
|
|
||||||
|
proc pollForBlockHeaders(service: BlockServiceRef, node: BeaconNodeServerRef,
|
||||||
|
slot: Slot, waitTime: Duration,
|
||||||
|
index: int): Future[bool] {.async.} =
|
||||||
|
let vc = service.client
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
node = node
|
||||||
|
slot = slot
|
||||||
|
wait_time = waitTime
|
||||||
|
schedule_index = index
|
||||||
|
|
||||||
|
trace "Polling for block header"
|
||||||
|
|
||||||
|
let bres =
|
||||||
|
try:
|
||||||
|
await sleepAsync(waitTime)
|
||||||
|
await node.client.getBlockHeader(BlockIdent.init(slot))
|
||||||
|
except RestError as exc:
|
||||||
|
debug "Unable to obtain block header",
|
||||||
|
reason = $exc.msg, error = $exc.name
|
||||||
|
return false
|
||||||
|
except RestResponseError as exc:
|
||||||
|
debug "Got an error while trying to obtain block header",
|
||||||
|
reason = exc.message, status = exc.status
|
||||||
|
return false
|
||||||
|
except CancelledError as exc:
|
||||||
|
raise exc
|
||||||
|
except CatchableError as exc:
|
||||||
|
warn "Unexpected error encountered while receiving block header",
|
||||||
|
reason = $exc.msg, error = $exc.name
|
||||||
|
return false
|
||||||
|
|
||||||
|
if bres.isNone():
|
||||||
|
trace "Beacon node does not yet have block"
|
||||||
|
return false
|
||||||
|
|
||||||
|
let blockHeader = bres.get()
|
||||||
|
|
||||||
|
let eventBlock = EventBeaconBlockObject(
|
||||||
|
slot: blockHeader.data.header.message.slot,
|
||||||
|
block_root: blockHeader.data.root,
|
||||||
|
optimistic: blockHeader.execution_optimistic
|
||||||
|
)
|
||||||
|
vc.registerBlock(eventBlock)
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc runBlockPollMonitor(service: BlockServiceRef,
|
||||||
|
node: BeaconNodeServerRef) {.async.} =
|
||||||
|
let
|
||||||
|
vc = service.client
|
||||||
|
roles = {BeaconNodeRole.BlockProposalData}
|
||||||
|
statuses = {RestBeaconNodeStatus.Synced}
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
node = node
|
||||||
|
|
||||||
|
while true:
|
||||||
|
let currentSlot =
|
||||||
|
block:
|
||||||
|
let res = await vc.checkedWaitForNextSlot(ZeroTimeDiff, false)
|
||||||
|
if res.isNone(): continue
|
||||||
|
res.geT()
|
||||||
|
|
||||||
|
while node.status notin statuses:
|
||||||
|
await vc.waitNodes(nil, statuses, roles, false)
|
||||||
|
|
||||||
|
let
|
||||||
|
currentTime = vc.beaconClock.now()
|
||||||
|
afterSlot = currentTime.slotOrZero()
|
||||||
|
|
||||||
|
if currentTime > afterSlot.attestation_deadline():
|
||||||
|
# Attestation time already, lets wait for next slot.
|
||||||
|
continue
|
||||||
|
|
||||||
|
let
|
||||||
|
pollTime1 = afterSlot.start_beacon_time() + BlockPollOffset1
|
||||||
|
pollTime2 = afterSlot.start_beacon_time() + BlockPollOffset2
|
||||||
|
pollTime3 = afterSlot.start_beacon_time() + BlockPollOffset3
|
||||||
|
|
||||||
|
var pendingTasks =
|
||||||
|
block:
|
||||||
|
var res: seq[FutureBase]
|
||||||
|
if currentTime <= pollTime1:
|
||||||
|
let stime = nanoseconds((pollTime1 - currentTime).nanoseconds)
|
||||||
|
res.add(FutureBase(
|
||||||
|
service.pollForBlockHeaders(node, afterSlot, stime, 0)))
|
||||||
|
if currentTime <= pollTime2:
|
||||||
|
let stime = nanoseconds((pollTime2 - currentTime).nanoseconds)
|
||||||
|
res.add(FutureBase(
|
||||||
|
service.pollForBlockHeaders(node, afterSlot, stime, 1)))
|
||||||
|
if currentTime <= pollTime3:
|
||||||
|
let stime = nanoseconds((pollTime3 - currentTime).nanoseconds)
|
||||||
|
res.add(FutureBase(
|
||||||
|
service.pollForBlockHeaders(node, afterSlot, stime, 2)))
|
||||||
|
res
|
||||||
|
try:
|
||||||
|
while true:
|
||||||
|
let completedFuture = await race(pendingTasks)
|
||||||
|
let blockReceived =
|
||||||
|
block:
|
||||||
|
var res = false
|
||||||
|
for future in pendingTasks:
|
||||||
|
if not(future.done()): continue
|
||||||
|
if not(cast[Future[bool]](future).read()): continue
|
||||||
|
res = true
|
||||||
|
break
|
||||||
|
res
|
||||||
|
if blockReceived:
|
||||||
|
var pending: seq[Future[void]]
|
||||||
|
for future in pendingTasks:
|
||||||
|
if not(future.finished()): pending.add(future.cancelAndWait())
|
||||||
|
await allFutures(pending)
|
||||||
|
break
|
||||||
|
pendingTasks.keepItIf(it != completedFuture)
|
||||||
|
if len(pendingTasks) == 0: break
|
||||||
|
except CancelledError as exc:
|
||||||
|
var pending: seq[Future[void]]
|
||||||
|
for future in pendingTasks:
|
||||||
|
if not(future.finished()): pending.add(future.cancelAndWait())
|
||||||
|
await allFutures(pending)
|
||||||
|
raise exc
|
||||||
|
except CatchableError as exc:
|
||||||
|
warn "An unexpected error occurred while running block monitoring",
|
||||||
|
reason = $exc.msg, error = $exc.name
|
||||||
|
|
||||||
|
proc runBlockMonitor(service: BlockServiceRef) {.async.} =
|
||||||
|
let
|
||||||
|
vc = service.client
|
||||||
|
blockNodes = vc.filterNodes(AllBeaconNodeStatuses,
|
||||||
|
{BeaconNodeRole.BlockProposalData})
|
||||||
|
let pendingTasks =
|
||||||
|
case vc.config.monitoringType
|
||||||
|
of BlockMonitoringType.Disabled:
|
||||||
|
debug "Block monitoring disabled"
|
||||||
|
@[newFuture[void]("block.monitor.disabled")]
|
||||||
|
of BlockMonitoringType.Poll:
|
||||||
|
var res: seq[Future[void]]
|
||||||
|
for node in blockNodes:
|
||||||
|
res.add(service.runBlockPollMonitor(node))
|
||||||
|
res
|
||||||
|
of BlockMonitoringType.Event:
|
||||||
|
var res: seq[Future[void]]
|
||||||
|
for node in blockNodes:
|
||||||
|
res.add(service.runBlockEventMonitor(node))
|
||||||
|
res
|
||||||
|
|
||||||
|
try:
|
||||||
|
await allFutures(pendingTasks)
|
||||||
|
except CancelledError as exc:
|
||||||
|
var pending: seq[Future[void]]
|
||||||
|
for future in pendingTasks:
|
||||||
|
if not(future.finished()): pending.add(future.cancelAndWait())
|
||||||
|
await allFutures(pending)
|
||||||
|
raise exc
|
||||||
|
except CatchableError as exc:
|
||||||
|
warn "An unexpected error occurred while running block monitoring",
|
||||||
|
reason = $exc.msg, error = $exc.name
|
||||||
|
return
|
||||||
|
|
||||||
proc mainLoop(service: BlockServiceRef) {.async.} =
|
proc mainLoop(service: BlockServiceRef) {.async.} =
|
||||||
let vc = service.client
|
let vc = service.client
|
||||||
service.state = ServiceState.Running
|
service.state = ServiceState.Running
|
||||||
debug "Service started"
|
debug "Service started"
|
||||||
var future = newFuture[void]()
|
let future = service.runBlockMonitor()
|
||||||
try:
|
try:
|
||||||
# Future is not going to be completed, so the only way to exit, is to
|
# Future is not going to be completed, so the only way to exit, is to
|
||||||
# cancel it.
|
# cancel it.
|
||||||
|
|
|
@ -15,7 +15,9 @@ import
|
||||||
".."/spec/[eth2_merkleization, helpers, signatures, validator],
|
".."/spec/[eth2_merkleization, helpers, signatures, validator],
|
||||||
".."/spec/eth2_apis/[eth2_rest_serialization, rest_beacon_client,
|
".."/spec/eth2_apis/[eth2_rest_serialization, rest_beacon_client,
|
||||||
dynamic_fee_recipients],
|
dynamic_fee_recipients],
|
||||||
".."/validators/[keystore_management, validator_pool, slashing_protection],
|
".."/consensus_object_pools/block_pools_types,
|
||||||
|
".."/validators/[keystore_management, validator_pool, slashing_protection,
|
||||||
|
validator_duties],
|
||||||
".."/[conf, beacon_clock, version, nimbus_binary_common]
|
".."/[conf, beacon_clock, version, nimbus_binary_common]
|
||||||
|
|
||||||
from std/times import Time, toUnix, fromUnix, getTime
|
from std/times import Time, toUnix, fromUnix, getTime
|
||||||
|
@ -26,7 +28,7 @@ export
|
||||||
byteutils, presto_client, eth2_rest_serialization, rest_beacon_client,
|
byteutils, presto_client, eth2_rest_serialization, rest_beacon_client,
|
||||||
phase0, altair, helpers, signatures, validator, eth2_merkleization,
|
phase0, altair, helpers, signatures, validator, eth2_merkleization,
|
||||||
beacon_clock, keystore_management, slashing_protection, validator_pool,
|
beacon_clock, keystore_management, slashing_protection, validator_pool,
|
||||||
dynamic_fee_recipients, Time, toUnix, fromUnix, getTime
|
dynamic_fee_recipients, Time, toUnix, fromUnix, getTime, block_pools_types
|
||||||
|
|
||||||
const
|
const
|
||||||
SYNC_TOLERANCE* = 4'u64
|
SYNC_TOLERANCE* = 4'u64
|
||||||
|
@ -151,6 +153,14 @@ type
|
||||||
DoppelgangerAttempt* {.pure.} = enum
|
DoppelgangerAttempt* {.pure.} = enum
|
||||||
None, Failure, SuccessTrue, SuccessFalse
|
None, Failure, SuccessTrue, SuccessFalse
|
||||||
|
|
||||||
|
BlockWaiter* = object
|
||||||
|
future*: Future[seq[Eth2Digest]]
|
||||||
|
count*: int
|
||||||
|
|
||||||
|
BlockDataItem* = object
|
||||||
|
blocks: seq[Eth2Digest]
|
||||||
|
waiters*: seq[BlockWaiter]
|
||||||
|
|
||||||
ValidatorClient* = object
|
ValidatorClient* = object
|
||||||
config*: ValidatorClientConf
|
config*: ValidatorClientConf
|
||||||
metricsServer*: Opt[MetricsHttpServerRef]
|
metricsServer*: Opt[MetricsHttpServerRef]
|
||||||
|
@ -186,6 +196,7 @@ type
|
||||||
proposerTasks*: Table[Slot, seq[ProposerTask]]
|
proposerTasks*: Table[Slot, seq[ProposerTask]]
|
||||||
dynamicFeeRecipientsStore*: ref DynamicFeeRecipientsStore
|
dynamicFeeRecipientsStore*: ref DynamicFeeRecipientsStore
|
||||||
validatorsRegCache*: Table[ValidatorPubKey, SignedValidatorRegistrationV1]
|
validatorsRegCache*: Table[ValidatorPubKey, SignedValidatorRegistrationV1]
|
||||||
|
blocksSeen*: Table[Slot, BlockDataItem]
|
||||||
rng*: ref HmacDrbgContext
|
rng*: ref HmacDrbgContext
|
||||||
|
|
||||||
ApiStrategyKind* {.pure.} = enum
|
ApiStrategyKind* {.pure.} = enum
|
||||||
|
@ -224,6 +235,18 @@ const
|
||||||
BeaconNodeRole.SyncCommitteeData,
|
BeaconNodeRole.SyncCommitteeData,
|
||||||
BeaconNodeRole.SyncCommitteePublish,
|
BeaconNodeRole.SyncCommitteePublish,
|
||||||
}
|
}
|
||||||
|
AllBeaconNodeStatuses* = {
|
||||||
|
RestBeaconNodeStatus.Offline,
|
||||||
|
RestBeaconNodeStatus.Online,
|
||||||
|
RestBeaconNodeStatus.Incompatible,
|
||||||
|
RestBeaconNodeStatus.Compatible,
|
||||||
|
RestBeaconNodeStatus.NotSynced,
|
||||||
|
RestBeaconNodeStatus.OptSynced,
|
||||||
|
RestBeaconNodeStatus.Synced,
|
||||||
|
RestBeaconNodeStatus.UnexpectedCode,
|
||||||
|
RestBeaconNodeStatus.UnexpectedResponse,
|
||||||
|
RestBeaconNodeStatus.InternalError
|
||||||
|
}
|
||||||
|
|
||||||
proc `$`*(roles: set[BeaconNodeRole]): string =
|
proc `$`*(roles: set[BeaconNodeRole]): string =
|
||||||
if card(roles) > 0:
|
if card(roles) > 0:
|
||||||
|
@ -1084,3 +1107,127 @@ proc checkedWaitForNextSlot*(vc: ValidatorClientRef, curSlot: Opt[Slot],
|
||||||
nextSlot = currentSlot + 1
|
nextSlot = currentSlot + 1
|
||||||
|
|
||||||
vc.checkedWaitForSlot(nextSlot, offset, showLogs)
|
vc.checkedWaitForSlot(nextSlot, offset, showLogs)
|
||||||
|
|
||||||
|
proc checkedWaitForNextSlot*(vc: ValidatorClientRef, offset: TimeDiff,
|
||||||
|
showLogs: bool): Future[Opt[Slot]] =
|
||||||
|
let
|
||||||
|
currentTime = vc.beaconClock.now()
|
||||||
|
currentSlot = currentTime.slotOrZero()
|
||||||
|
nextSlot = currentSlot + 1
|
||||||
|
|
||||||
|
vc.checkedWaitForSlot(nextSlot, offset, showLogs)
|
||||||
|
|
||||||
|
proc expectBlock*(vc: ValidatorClientRef, slot: Slot,
|
||||||
|
confirmations: int = 1): Future[seq[Eth2Digest]] =
|
||||||
|
var
|
||||||
|
retFuture = newFuture[seq[Eth2Digest]]("expectBlock")
|
||||||
|
waiter = BlockWaiter(future: retFuture, count: confirmations)
|
||||||
|
|
||||||
|
proc cancellation(udata: pointer) =
|
||||||
|
vc.blocksSeen.withValue(slot, adata):
|
||||||
|
adata[].waiters.keepItIf(it.future != retFuture)
|
||||||
|
|
||||||
|
proc scheduleCallbacks(data: var BlockDataItem,
|
||||||
|
waiter: BlockWaiter) =
|
||||||
|
data.waiters.add(waiter)
|
||||||
|
for mitem in data.waiters.mitems():
|
||||||
|
if mitem.count <= len(data.blocks):
|
||||||
|
if not(mitem.future.finished()): mitem.future.complete(data.blocks)
|
||||||
|
|
||||||
|
vc.blocksSeen.mgetOrPut(slot, BlockDataItem()).scheduleCallbacks(waiter)
|
||||||
|
if not(retFuture.finished()): retFuture.cancelCallback = cancellation
|
||||||
|
retFuture
|
||||||
|
|
||||||
|
proc registerBlock*(vc: ValidatorClientRef, data: EventBeaconBlockObject) =
|
||||||
|
let
|
||||||
|
wallTime = vc.beaconClock.now()
|
||||||
|
delay = wallTime - data.slot.start_beacon_time()
|
||||||
|
|
||||||
|
debug "Block received", slot = data.slot,
|
||||||
|
block_root = shortLog(data.block_root), optimistic = data.optimistic,
|
||||||
|
delay = delay
|
||||||
|
|
||||||
|
proc scheduleCallbacks(data: var BlockDataItem,
|
||||||
|
blck: EventBeaconBlockObject) =
|
||||||
|
data.blocks.add(blck.block_root)
|
||||||
|
for mitem in data.waiters.mitems():
|
||||||
|
if mitem.count >= len(data.blocks):
|
||||||
|
if not(mitem.future.finished()): mitem.future.complete(data.blocks)
|
||||||
|
vc.blocksSeen.mgetOrPut(data.slot, BlockDataItem()).scheduleCallbacks(data)
|
||||||
|
|
||||||
|
proc pruneBlocksSeen*(vc: ValidatorClientRef, epoch: Epoch) =
|
||||||
|
var blocksSeen: Table[Slot, BlockDataItem]
|
||||||
|
for slot, item in vc.blocksSeen.pairs():
|
||||||
|
if (slot.epoch() + HISTORICAL_DUTIES_EPOCHS) >= epoch:
|
||||||
|
blocksSeen[slot] = item
|
||||||
|
else:
|
||||||
|
let blockRoot =
|
||||||
|
if len(item.blocks) == 0:
|
||||||
|
"<missing>"
|
||||||
|
elif len(item.blocks) == 1:
|
||||||
|
shortLog(item.blocks[0])
|
||||||
|
else:
|
||||||
|
"[" & item.blocks.mapIt(shortLog(it)).join(", ") & "]"
|
||||||
|
debug "Block data has been pruned", slot = slot, blocks = blockRoot
|
||||||
|
vc.blocksSeen = blocksSeen
|
||||||
|
|
||||||
|
proc waitForBlock*(
|
||||||
|
vc: ValidatorClientRef,
|
||||||
|
slot: Slot,
|
||||||
|
timediff: TimeDiff,
|
||||||
|
confirmations: int = 1
|
||||||
|
) {.async.} =
|
||||||
|
## This procedure will wait for a block proposal for a ``slot`` received
|
||||||
|
## by the beacon node.
|
||||||
|
let
|
||||||
|
startTime = Moment.now()
|
||||||
|
waitTime = (start_beacon_time(slot) + timediff) - vc.beaconClock.now()
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
slot = slot
|
||||||
|
timediff = timediff
|
||||||
|
wait_time = waitTime
|
||||||
|
|
||||||
|
debug "Waiting for block proposal"
|
||||||
|
|
||||||
|
if waitTime.nanoseconds <= 0'i64:
|
||||||
|
# We do not have time to wait for block.
|
||||||
|
return
|
||||||
|
|
||||||
|
let blocks =
|
||||||
|
try:
|
||||||
|
let timeout = nanoseconds(waitTime.nanoseconds)
|
||||||
|
await vc.expectBlock(slot, confirmations).wait(timeout)
|
||||||
|
except AsyncTimeoutError:
|
||||||
|
let dur = Moment.now() - startTime
|
||||||
|
debug "Block has not been received in time", duration = dur
|
||||||
|
return
|
||||||
|
except CancelledError as exc:
|
||||||
|
let dur = Moment.now() - startTime
|
||||||
|
debug "Block awaiting was interrupted", duration = dur
|
||||||
|
raise exc
|
||||||
|
except CatchableError as exc:
|
||||||
|
let dur = Moment.now() - startTime
|
||||||
|
error "Unexpected error occured while waiting for block publication",
|
||||||
|
err_name = exc.name, err_msg = exc.msg, duration = dur
|
||||||
|
return
|
||||||
|
|
||||||
|
let
|
||||||
|
dur = Moment.now() - startTime
|
||||||
|
blockRoot =
|
||||||
|
if len(blocks) == 0:
|
||||||
|
"<missing>"
|
||||||
|
elif len(blocks) == 1:
|
||||||
|
shortLog(blocks[0])
|
||||||
|
else:
|
||||||
|
"[" & blocks.mapIt(shortLog(it)).join(", ") & "]"
|
||||||
|
|
||||||
|
debug "Block proposal awaited", duration = dur,
|
||||||
|
block_root = blockRoot
|
||||||
|
|
||||||
|
try:
|
||||||
|
await waitAfterBlockCutoff(vc.beaconClock, slot)
|
||||||
|
except CancelledError as exc:
|
||||||
|
let dur = Moment.now() - startTime
|
||||||
|
debug "Waiting for block cutoff was interrupted", duration = dur
|
||||||
|
raise exc
|
||||||
|
|
|
@ -11,7 +11,7 @@ import
|
||||||
../spec/datatypes/[phase0, altair, bellatrix],
|
../spec/datatypes/[phase0, altair, bellatrix],
|
||||||
../spec/eth2_apis/rest_types,
|
../spec/eth2_apis/rest_types,
|
||||||
../validators/activity_metrics,
|
../validators/activity_metrics,
|
||||||
"."/[common, api, block_service]
|
"."/[common, api]
|
||||||
|
|
||||||
const
|
const
|
||||||
ServiceName = "sync_committee_service"
|
ServiceName = "sync_committee_service"
|
||||||
|
@ -340,7 +340,7 @@ proc publishSyncMessagesAndContributions(service: SyncCommitteeServiceRef,
|
||||||
async.} =
|
async.} =
|
||||||
let vc = service.client
|
let vc = service.client
|
||||||
|
|
||||||
await vc.waitForBlockPublished(slot, syncCommitteeMessageSlotOffset)
|
await vc.waitForBlock(slot, syncCommitteeMessageSlotOffset)
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let delay = vc.getDelay(slot.sync_committee_message_deadline())
|
let delay = vc.getDelay(slot.sync_committee_message_deadline())
|
||||||
|
@ -457,7 +457,7 @@ proc mainLoop(service: SyncCommitteeServiceRef) {.async.} =
|
||||||
try:
|
try:
|
||||||
let
|
let
|
||||||
# We use zero offset here, because we do waiting in
|
# We use zero offset here, because we do waiting in
|
||||||
# waitForBlockPublished(syncCommitteeMessageSlotOffset).
|
# waitForBlock(syncCommitteeMessageSlotOffset).
|
||||||
slot = await vc.checkedWaitForNextSlot(currentSlot, ZeroTimeDiff,
|
slot = await vc.checkedWaitForNextSlot(currentSlot, ZeroTimeDiff,
|
||||||
false)
|
false)
|
||||||
if slot.isNone():
|
if slot.isNone():
|
||||||
|
|
|
@ -1552,6 +1552,42 @@ proc updateValidators(
|
||||||
index: index, validator: validators[int index]
|
index: index, validator: validators[int index]
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
proc waitAfterBlockCutoff*(clock: BeaconClock, slot: Slot,
|
||||||
|
head: Opt[BlockRef] = Opt.none(BlockRef)) {.async.} =
|
||||||
|
# The expected block arrived (or expectBlock was called again which
|
||||||
|
# shouldn't happen as this is the only place we use it) - in our async
|
||||||
|
# loop however, we might have been doing other processing that caused delays
|
||||||
|
# here so we'll cap the waiting to the time when we would have sent out
|
||||||
|
# attestations had the block not arrived.
|
||||||
|
# An opposite case is that we received (or produced) a block that has
|
||||||
|
# not yet reached our neighbours. To protect against our attestations
|
||||||
|
# being dropped (because the others have not yet seen the block), we'll
|
||||||
|
# impose a minimum delay of 2000ms. The delay is enforced only when we're
|
||||||
|
# not hitting the "normal" cutoff time for sending out attestations.
|
||||||
|
# An earlier delay of 250ms has proven to be not enough, increasing the
|
||||||
|
# risk of losing attestations, and with growing block sizes, 1000ms
|
||||||
|
# started to be risky as well.
|
||||||
|
# Regardless, because we "just" received the block, we'll impose the
|
||||||
|
# delay.
|
||||||
|
|
||||||
|
# Take into consideration chains with a different slot time
|
||||||
|
const afterBlockDelay = nanos(attestationSlotOffset.nanoseconds div 2)
|
||||||
|
let
|
||||||
|
afterBlockTime = clock.now() + afterBlockDelay
|
||||||
|
afterBlockCutoff = clock.fromNow(
|
||||||
|
min(afterBlockTime, slot.attestation_deadline() + afterBlockDelay))
|
||||||
|
|
||||||
|
if afterBlockCutoff.inFuture:
|
||||||
|
if head.isSome():
|
||||||
|
debug "Got block, waiting to send attestations",
|
||||||
|
head = shortLog(head.get()), slot = slot,
|
||||||
|
afterBlockCutoff = shortLog(afterBlockCutoff.offset)
|
||||||
|
else:
|
||||||
|
debug "Got block, waiting to send attestations",
|
||||||
|
slot = slot, afterBlockCutoff = shortLog(afterBlockCutoff.offset)
|
||||||
|
|
||||||
|
await sleepAsync(afterBlockCutoff.offset)
|
||||||
|
|
||||||
proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
||||||
## Perform validator duties - create blocks, vote and aggregate existing votes
|
## Perform validator duties - create blocks, vote and aggregate existing votes
|
||||||
if node.attachedValidators[].count == 0:
|
if node.attachedValidators[].count == 0:
|
||||||
|
@ -1624,35 +1660,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
||||||
# Wait either for the block or the attestation cutoff time to arrive
|
# Wait either for the block or the attestation cutoff time to arrive
|
||||||
if await node.consensusManager[].expectBlock(slot)
|
if await node.consensusManager[].expectBlock(slot)
|
||||||
.withTimeout(attestationCutoff.offset):
|
.withTimeout(attestationCutoff.offset):
|
||||||
# The expected block arrived (or expectBlock was called again which
|
await waitAfterBlockCutoff(node.beaconClock, slot, Opt.some(head))
|
||||||
# shouldn't happen as this is the only place we use it) - in our async
|
|
||||||
# loop however, we might have been doing other processing that caused delays
|
|
||||||
# here so we'll cap the waiting to the time when we would have sent out
|
|
||||||
# attestations had the block not arrived.
|
|
||||||
# An opposite case is that we received (or produced) a block that has
|
|
||||||
# not yet reached our neighbours. To protect against our attestations
|
|
||||||
# being dropped (because the others have not yet seen the block), we'll
|
|
||||||
# impose a minimum delay of 2000ms. The delay is enforced only when we're
|
|
||||||
# not hitting the "normal" cutoff time for sending out attestations.
|
|
||||||
# An earlier delay of 250ms has proven to be not enough, increasing the
|
|
||||||
# risk of losing attestations, and with growing block sizes, 1000ms
|
|
||||||
# started to be risky as well.
|
|
||||||
# Regardless, because we "just" received the block, we'll impose the
|
|
||||||
# delay.
|
|
||||||
|
|
||||||
# Take into consideration chains with a different slot time
|
|
||||||
const afterBlockDelay = nanos(attestationSlotOffset.nanoseconds div 2)
|
|
||||||
let
|
|
||||||
afterBlockTime = node.beaconClock.now() + afterBlockDelay
|
|
||||||
afterBlockCutoff = node.beaconClock.fromNow(
|
|
||||||
min(afterBlockTime, slot.attestation_deadline() + afterBlockDelay))
|
|
||||||
|
|
||||||
if afterBlockCutoff.inFuture:
|
|
||||||
debug "Got block, waiting to send attestations",
|
|
||||||
head = shortLog(head),
|
|
||||||
afterBlockCutoff = shortLog(afterBlockCutoff.offset)
|
|
||||||
|
|
||||||
await sleepAsync(afterBlockCutoff.offset)
|
|
||||||
|
|
||||||
# Time passed - we might need to select a new head in that case
|
# Time passed - we might need to select a new head in that case
|
||||||
node.consensusManager[].updateHead(slot)
|
node.consensusManager[].updateHead(slot)
|
||||||
|
|
Loading…
Reference in New Issue