# beacon_chain
# Copyright (c) 2018-2024 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: [].}

import
  chronos,
  results,
  ../consensus_object_pools/block_dag,
  ../beacon_clock,
  "."/[validator_pool]

export chronos, results, block_dag, beacon_clock

## The validator_duties module contains logic and utilities related to performing
## validator duties that are shared between beacon node and validator client.

type
  RegisteredAttestation* = object
    # A registered attestation is one that has been successfully registered in
    # the slashing protection database and is therefore ready to be signed and
    # sent
    validator*: AttachedValidator
    committee_index*: CommitteeIndex
    index_in_committee*: uint64
    committee_len*: int
    data*: AttestationData

proc toAttestation*(
    registered: RegisteredAttestation, signature: ValidatorSig):
    phase0.Attestation =
  phase0.Attestation.init(
    [registered.index_in_committee], registered.committee_len,
    registered.data, signature).expect("valid data")

proc toElectraAttestation*(
    registered: RegisteredAttestation, signature: ValidatorSig):
    electra.Attestation =
  electra.Attestation.init(
    registered.committee_index, [registered.index_in_committee],
    registered.committee_len, registered.data, signature).expect("valid data")

proc waitAfterBlockCutoff*(clock: BeaconClock, slot: Slot,
                           head: Opt[BlockRef] = Opt.none(BlockRef))
                           {.async: (raises: [CancelledError]).} =
  # 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)