# 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
  std/[strutils, parseutils, tables, typetraits],
  chronos/timer,
  stew/[byteutils], stint, web3/primitives as web3types,
  ./datatypes/constants

export constants

export stint, web3types.toHex, web3types.`==`

const
  # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#withdrawal-prefixes
  BLS_WITHDRAWAL_PREFIX*: byte = 0
  ETH1_ADDRESS_WITHDRAWAL_PREFIX*: byte = 1

  # Constants from `validator.md` not covered by config/presets in the spec
  TARGET_AGGREGATORS_PER_COMMITTEE*: uint64 = 16

  # Not used anywhere; only for network preset checking
  EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: uint64 = 256
  TTFB_TIMEOUT* = 5'u64
  MESSAGE_DOMAIN_INVALID_SNAPPY*: array[4, byte] = [0x00, 0x00, 0x00, 0x00]
  MESSAGE_DOMAIN_VALID_SNAPPY*: array[4, byte] = [0x01, 0x00, 0x00, 0x00]

type
  Version* = distinct array[4, byte]
  Eth1Address* = web3types.Address

  RuntimeConfig* = object
    ## https://github.com/ethereum/consensus-specs/tree/v1.4.0-beta.4/configs
    PRESET_BASE*: string
    CONFIG_NAME*: string

    # Transition
    TERMINAL_TOTAL_DIFFICULTY*: UInt256
    TERMINAL_BLOCK_HASH*: BlockHash
    TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH*: Epoch  # Not actively used, but part of the spec

    # Genesis
    MIN_GENESIS_ACTIVE_VALIDATOR_COUNT*: uint64
    MIN_GENESIS_TIME*: uint64
    GENESIS_FORK_VERSION*: Version
    GENESIS_DELAY*: uint64

    # Forking
    ALTAIR_FORK_VERSION*: Version
    ALTAIR_FORK_EPOCH*: Epoch
    BELLATRIX_FORK_VERSION*: Version
    BELLATRIX_FORK_EPOCH*: Epoch
    CAPELLA_FORK_VERSION*: Version
    CAPELLA_FORK_EPOCH*: Epoch
    DENEB_FORK_VERSION*: Version
    DENEB_FORK_EPOCH*: Epoch
    EIP7594_FORK_VERSION*: Version
    EIP7594_FORK_EPOCH*: Epoch
    ELECTRA_FORK_VERSION*: Version
    ELECTRA_FORK_EPOCH*: Epoch

    # Time parameters
    # TODO SECONDS_PER_SLOT*: uint64
    SECONDS_PER_ETH1_BLOCK*: uint64
    MIN_VALIDATOR_WITHDRAWABILITY_DELAY*: uint64
    SHARD_COMMITTEE_PERIOD*: uint64
    ETH1_FOLLOW_DISTANCE*: uint64

    # Validator cycle
    INACTIVITY_SCORE_BIAS*: uint64
    INACTIVITY_SCORE_RECOVERY_RATE*: uint64
    EJECTION_BALANCE*: uint64
    MIN_PER_EPOCH_CHURN_LIMIT*: uint64
    CHURN_LIMIT_QUOTIENT*: uint64
    MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT*: uint64
    MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA*: uint64
    MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT*: uint64

    # Fork choice
    # TODO PROPOSER_SCORE_BOOST*: uint64
    # TODO REORG_HEAD_WEIGHT_THRESHOLD*: uint64
    # TODO REORG_PARENT_WEIGHT_THRESHOLD*: uint64
    # TODO REORG_MAX_EPOCHS_SINCE_FINALIZATION*: uint64

    # Deposit contract
    DEPOSIT_CHAIN_ID*: uint64
    DEPOSIT_NETWORK_ID*: uint64
    DEPOSIT_CONTRACT_ADDRESS*: Eth1Address

    # Networking
    # TODO GOSSIP_MAX_SIZE*: uint64
    # TODO MAX_REQUEST_BLOCKS*: uint64
    # TODO EPOCHS_PER_SUBNET_SUBSCRIPTION*: uint64
    MIN_EPOCHS_FOR_BLOCK_REQUESTS*: uint64
    # TODO MAX_CHUNK_SIZE*: uint64
    # TODO TTFB_TIMEOUT*: uint64
    # TODO RESP_TIMEOUT*: uint64
    # TODO ATTESTATION_PROPAGATION_SLOT_RANGE*: uint64
    # TODO MAXIMUM_GOSSIP_CLOCK_DISPARITY*: uint64
    # TODO MESSAGE_DOMAIN_INVALID_SNAPPY*: array[4, byte]
    # TODO MESSAGE_DOMAIN_VALID_SNAPPY*: array[4, byte]
    # TODO SUBNETS_PER_NODE*: uint64
    # TODO ATTESTATION_SUBNET_COUNT*: uint64
    # TODO ATTESTATION_SUBNET_EXTRA_BITS*: uint64
    # TODO ATTESTATION_SUBNET_PREFIX_BITS*: uint64

    # Deneb
    # TODO MAX_REQUEST_BLOCKS_DENEB*: uint64
    # TODO MAX_REQUEST_BLOB_SIDECARS*: uint64
    MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS*: uint64

    # EIP-7594
    # TODO MAX_REQUEST_BLOCKS_DENEB*: uint64
    # TODO MAX_REQUEST_DATA_COLUMN_SIDECARS*: uint64
    MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS*: uint64

    # TODO BLOB_SIDECAR_SUBNET_COUNT*: uint64

  PresetFile* = object
    values*: Table[string, string]
    missingValues*: seq[string]

  PresetFileError* = object of CatchableError
  PresetIncompatibleError* = object of CatchableError

const
  const_preset* {.strdefine.} = "mainnet"

  # No-longer used values from legacy config files
  ignoredValues = [
    "TRANSITION_TOTAL_DIFFICULTY", # Name that appears in some altair alphas, obsolete, remove when no more testnets
    "MIN_ANCHOR_POW_BLOCK_DIFFICULTY", # Name that appears in some altair alphas, obsolete, remove when no more testnets
    "RANDOM_SUBNETS_PER_VALIDATOR",    # Removed in consensus-specs v1.4.0
  ]

when const_preset == "mainnet":
  import ./presets/mainnet
  export mainnet

  # TODO Move this to RuntimeConfig
  const SECONDS_PER_SLOT* {.intdefine.}: uint64 = 12

  # The default run-time config specifies the default configuration values
  # that will be used if a particular run-time config is missing specific
  # confugration values (which will be then taken from this config object).
  # It mostly matches the mainnet config with the exception of few properties
  # such as `CONFIG_NAME`, `TERMINAL_TOTAL_DIFFICULTY`, `*_FORK_EPOCH`, etc
  # which must be effectively overriden in all network (including mainnet).
  const defaultRuntimeConfig* = RuntimeConfig(
    # Mainnet config

    # Extends the mainnet preset
    PRESET_BASE: "mainnet",

    # Free-form short name of the network that this configuration applies to - known
    # canonical network names include:
    # * 'mainnet' - there can be only one
    # * 'sepolia' - testnet
    # * 'holesky' - testnet
    # Must match the regex: [a-z0-9\-]
    CONFIG_NAME: "",

    # Transition
    # ---------------------------------------------------------------
    # TBD, 2**256-2**10 is a placeholder
    TERMINAL_TOTAL_DIFFICULTY:
      u256"115792089237316195423570985008687907853269984665640564039457584007913129638912",
    # By default, don't use these params
    TERMINAL_BLOCK_HASH: BlockHash.fromHex(
      "0x0000000000000000000000000000000000000000000000000000000000000000"),

    # Genesis
    # ---------------------------------------------------------------
    # `2**14` (= 16,384)
    MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384,
    # Dec 1, 2020, 12pm UTC
    MIN_GENESIS_TIME: 1606824000,
    # Mainnet initial fork version, recommend altering for testnets
    GENESIS_FORK_VERSION: Version [byte 0x00, 0x00, 0x00, 0x00],
    # 604800 seconds (7 days)
    GENESIS_DELAY: 604800,

    # Forking
    # ---------------------------------------------------------------
    # Some forks are disabled for now:
    #  - These may be re-assigned to another fork-version later
    #  - Temporarily set to max uint64 value: 2**64 - 1

    # Altair
    ALTAIR_FORK_VERSION: Version [byte 0x01, 0x00, 0x00, 0x00],
    ALTAIR_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Bellatrix
    BELLATRIX_FORK_VERSION: Version [byte 0x02, 0x00, 0x00, 0x00],
    BELLATRIX_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Capella
    CAPELLA_FORK_VERSION: Version [byte 0x03, 0x00, 0x00, 0x00],
    CAPELLA_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Deneb
    DENEB_FORK_VERSION: Version [byte 0x04, 0x00, 0x00, 0x00],
    DENEB_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Electra
    ELECTRA_FORK_VERSION: Version [byte 0x05, 0x00, 0x00, 0x00],
    ELECTRA_FORK_EPOCH: FAR_FUTURE_EPOCH,

    # Time parameters
    # ---------------------------------------------------------------
    # 12 seconds
    # TODO SECONDS_PER_SLOT: 12,
    # 14 (estimate from Eth1 mainnet)
    SECONDS_PER_ETH1_BLOCK: 14,
    # 2**8 (= 256) epochs ~27 hours
    MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256,
    # 2**8 (= 256) epochs ~27 hours
    SHARD_COMMITTEE_PERIOD: 256,
    # 2**11 (= 2,048) Eth1 blocks ~8 hours
    ETH1_FOLLOW_DISTANCE: 2048,


    # Validator cycle
    # ---------------------------------------------------------------
    # 2**2 (= 4)
    INACTIVITY_SCORE_BIAS: 4,
    # 2**4 (= 16)
    INACTIVITY_SCORE_RECOVERY_RATE: 16,
    # 2**4 * 10**9 (= 16,000,000,000) Gwei
    EJECTION_BALANCE: 16000000000'u64,
    # 2**2 (= 4)
    MIN_PER_EPOCH_CHURN_LIMIT: 4,
    # 2**16 (= 65,536)
    CHURN_LIMIT_QUOTIENT: 65536,
    # [New in Deneb:EIP7514] 2**3 (= 8)
    MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8,
    # [New in Electra:EIP7251] 2**7 * 10**9 (= 128,000,000,000)
    MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000'u64,
    # [New in Electra:EIP7251] 2**8 * 10**9 (= 256,000,000,000)
    MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000'u64,

    # Deposit contract
    # ---------------------------------------------------------------
    # Ethereum PoW Mainnet
    DEPOSIT_CHAIN_ID: 1,
    DEPOSIT_NETWORK_ID: 1,
    DEPOSIT_CONTRACT_ADDRESS: default(Eth1Address),

    # Networking
    # ---------------------------------------------------------------
    # `10 * 2**20` (= 10485760, 10 MiB)
    # TODO GOSSIP_MAX_SIZE: 10485760,
    # `2**10` (= 1024)
    # TODO MAX_REQUEST_BLOCKS: 1024,
    # `2**8` (= 256)
    # TODO EPOCHS_PER_SUBNET_SUBSCRIPTION: 256,
    # `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months)
    MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024,
    # `10 * 2**20` (=10485760, 10 MiB)
    # TODO MAX_CHUNK_SIZE: 10485760,
    # 5s
    # TODO TTFB_TIMEOUT: 5,
    # 10s
    # TODO RESP_TIMEOUT: 10,
    # TODO ATTESTATION_PROPAGATION_SLOT_RANGE: 32,
    # 500ms
    # TODO MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500,
    # TODO MESSAGE_DOMAIN_INVALID_SNAPPY: [byte 0x00, 0x00, 0x00, 0x00],
    # TODO MESSAGE_DOMAIN_VALID_SNAPPY: [byte 0x01, 0x00, 0x00, 0x00],
    # 2 subnets per node
    # TODO SUBNETS_PER_NODE: 2,
    # 2**8 (= 64)
    # TODO ATTESTATION_SUBNET_COUNT: 64,
    # TODO ATTESTATION_SUBNET_EXTRA_BITS: 0,
    # ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS
    # TODO ATTESTATION_SUBNET_PREFIX_BITS: 6,

    # Deneb
    # `2**7` (=128)
    # TODO MAX_REQUEST_BLOCKS_DENEB: 128,
    # MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
    # TODO MAX_REQUEST_BLOB_SIDECARS: 768,
    # `2**12` (= 4096 epochs, ~18 days)
    MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
    # `6`
    # TODO BLOB_SIDECAR_SUBNET_COUNT: 6,

    # EIP-7594
    # `2**7` (=128)
    # TODO MAX_REQUEST_BLOCKS_DENEB: 128,
    # MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS
    # TODO MAX_REQUEST_BLOB_SIDECARS: 768,
    # `2**12` (= 4096 epochs, ~18 days)
    MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096
  )

elif const_preset == "gnosis":
  import ./presets/gnosis
  export gnosis

  # TODO Move this to RuntimeConfig
  const SECONDS_PER_SLOT* {.intdefine.}: uint64 = 5

  # The default run-time config specifies the default configuration values
  # that will be used if a particular run-time config is missing specific
  # confugration values (which will be then taken from this config object).
  # It mostly matches the gnosis config with the exception of few properties
  # such as `CONFIG_NAME`, `TERMINAL_TOTAL_DIFFICULTY`, `*_FORK_EPOCH`, etc
  # which must be effectively overriden in all network (including mainnet).
  const defaultRuntimeConfig* = RuntimeConfig(
    # Mainnet config

    # Extends the mainnet preset
    PRESET_BASE: "gnosis",

    # Free-form short name of the network that this configuration applies to - known
    # canonical network names include:
    # * 'mainnet' - there can be only one
    # * 'sepolia' - testnet
    # * 'holesky' - testnet
    # Must match the regex: [a-z0-9\-]
    CONFIG_NAME: "",

    # Transition
    # ---------------------------------------------------------------
    # TBD, 2**256-2**10 is a placeholder
    TERMINAL_TOTAL_DIFFICULTY:
      u256"115792089237316195423570985008687907853269984665640564039457584007913129638912",
    # By default, don't use these params
    TERMINAL_BLOCK_HASH: BlockHash.fromHex(
      "0x0000000000000000000000000000000000000000000000000000000000000000"),

    # Genesis
    # ---------------------------------------------------------------
    # `2**14` (= 16,384)
    MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 4096,
    # Dec 1, 2020, 12pm UTC
    MIN_GENESIS_TIME: 1638968400,
    # Mainnet initial fork version, recommend altering for testnets
    GENESIS_FORK_VERSION: Version [byte 0x00, 0x00, 0x00, 0x64],
    # 604800 seconds (7 days)
    GENESIS_DELAY: 604800,

    # Forking
    # ---------------------------------------------------------------
    # Some forks are disabled for now:
    #  - These may be re-assigned to another fork-version later
    #  - Temporarily set to max uint64 value: 2**64 - 1

    # Altair
    ALTAIR_FORK_VERSION: Version [byte 0x01, 0x00, 0x00, 0x64],
    ALTAIR_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Bellatrix
    BELLATRIX_FORK_VERSION: Version [byte 0x02, 0x00, 0x00, 0x64],
    BELLATRIX_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Capella
    CAPELLA_FORK_VERSION: Version [byte 0x03, 0x00, 0x00, 0x64],
    CAPELLA_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Deneb
    DENEB_FORK_VERSION: Version [byte 0x04, 0x00, 0x00, 0x64],
    DENEB_FORK_EPOCH: FAR_FUTURE_EPOCH,
    # Electra
    ELECTRA_FORK_VERSION: Version [byte 0x05, 0x00, 0x00, 0x64],
    ELECTRA_FORK_EPOCH: FAR_FUTURE_EPOCH,


    # Time parameters
    # ---------------------------------------------------------------
    # 12 seconds
    # TODO SECONDS_PER_SLOT: 12,
    # 14 (estimate from Eth1 mainnet)
    SECONDS_PER_ETH1_BLOCK: 5,
    # 2**8 (= 256) epochs ~27 hours
    MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256,
    # 2**8 (= 256) epochs ~27 hours
    SHARD_COMMITTEE_PERIOD: 256,
    # 2**11 (= 2,048) Eth1 blocks ~8 hours
    ETH1_FOLLOW_DISTANCE: 2048,


    # Validator cycle
    # ---------------------------------------------------------------
    # 2**2 (= 4)
    INACTIVITY_SCORE_BIAS: 4,
    # 2**4 (= 16)
    INACTIVITY_SCORE_RECOVERY_RATE: 16,
    # 2**4 * 10**9 (= 16,000,000,000) Gwei
    EJECTION_BALANCE: 16000000000'u64,
    # 2**2 (= 4)
    MIN_PER_EPOCH_CHURN_LIMIT: 4,
    # 2**16 (= 65,536)
    CHURN_LIMIT_QUOTIENT: 4096,
    # [New in Deneb:EIP7514] 2**3 (= 8)
    MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8,
    # [New in Electra:EIP7251] 2**7 * 10**9 (= 128,000,000,000) (copied from EF mainnet)
    MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000'u64,
    # [New in Electra:EIP7251] 2**8 * 10**9 (= 256,000,000,000) (copied from EF mainnet)
    MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000'u64,

    # Deposit contract
    # ---------------------------------------------------------------
    # Gnosis PoW Mainnet
    DEPOSIT_CHAIN_ID: 100,
    DEPOSIT_NETWORK_ID: 100,
    DEPOSIT_CONTRACT_ADDRESS: default(Eth1Address),

    # Networking
    # ---------------------------------------------------------------
    # `10 * 2**20` (= 10485760, 10 MiB)
    # TODO GOSSIP_MAX_SIZE: 10485760,
    # `2**10` (= 1024)
    # TODO MAX_REQUEST_BLOCKS: 1024,
    # `2**8` (= 256)
    # TODO EPOCHS_PER_SUBNET_SUBSCRIPTION: 256,
    # `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months)
    MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024,
    # `10 * 2**20` (=10485760, 10 MiB)
    # TODO MAX_CHUNK_SIZE: 10485760,
    # 5s
    # TODO TTFB_TIMEOUT: 5,
    # 10s
    # TODO RESP_TIMEOUT: 10,
    # TODO ATTESTATION_PROPAGATION_SLOT_RANGE: 32,
    # 500ms
    # TODO MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500,
    # TODO MESSAGE_DOMAIN_INVALID_SNAPPY: [byte 0x00, 0x00, 0x00, 0x00],
    # TODO MESSAGE_DOMAIN_VALID_SNAPPY: [byte 0x01, 0x00, 0x00, 0x00],
    # 2 subnets per node
    # TODO SUBNETS_PER_NODE: 2,
    # 2**8 (= 64)
    # TODO ATTESTATION_SUBNET_COUNT: 64,
    # TODO ATTESTATION_SUBNET_EXTRA_BITS: 0,
    # ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS
    # TODO ATTESTATION_SUBNET_PREFIX_BITS: 6,

    # Deneb
    # `2**7` (=128)
    # TODO MAX_REQUEST_BLOCKS_DENEB: 128,
    # MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
    # TODO MAX_REQUEST_BLOB_SIDECARS: 768,
    # `2**12` (= 4096 epochs, ~18 days)
    MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 16384,
    # `6`
    # TODO BLOB_SIDECAR_SUBNET_COUNT: 6,
  )

elif const_preset == "minimal":
  import ./presets/minimal
  export minimal

  const SECONDS_PER_SLOT* {.intdefine.}: uint64 = 6

  # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/configs/minimal.yaml
  const defaultRuntimeConfig* = RuntimeConfig(
    # Minimal config

    # Extends the minimal preset
    PRESET_BASE: "minimal",

    # Free-form short name of the network that this configuration applies to - known
    # canonical network names include:
    # * 'mainnet' - there can be only one
    # * 'sepolia' - testnet
    # * 'holesky' - testnet
    # Must match the regex: [a-z0-9\-]
    CONFIG_NAME: "minimal",

    # Transition
    # ---------------------------------------------------------------
    # 2**256-2**10 for testing minimal network
    TERMINAL_TOTAL_DIFFICULTY:
      u256"115792089237316195423570985008687907853269984665640564039457584007913129638912",
    # By default, don't use these params
    TERMINAL_BLOCK_HASH: BlockHash.fromHex(
      "0x0000000000000000000000000000000000000000000000000000000000000000"),


    # Genesis
    # ---------------------------------------------------------------
    # [customized]
    MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64,
    # Jan 3, 2020
    MIN_GENESIS_TIME: 1578009600,
    # Highest byte set to 0x01 to avoid collisions with mainnet versioning
    GENESIS_FORK_VERSION: Version [byte 0x00, 0x00, 0x00, 0x01],
    # [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis
    GENESIS_DELAY: 300,


    # Forking
    # ---------------------------------------------------------------
    # Values provided for illustrative purposes.
    # Individual tests/testnets may set different values.

    # Altair
    ALTAIR_FORK_VERSION: Version [byte 0x01, 0x00, 0x00, 0x01],
    ALTAIR_FORK_EPOCH: Epoch(uint64.high),
    # Bellatrix
    BELLATRIX_FORK_VERSION: Version [byte 0x02, 0x00, 0x00, 0x01],
    BELLATRIX_FORK_EPOCH: Epoch(uint64.high),
    # Capella
    CAPELLA_FORK_VERSION: Version [byte 0x03, 0x00, 0x00, 0x01],
    CAPELLA_FORK_EPOCH: Epoch(uint64.high),
    # Deneb
    DENEB_FORK_VERSION: Version [byte 0x04, 0x00, 0x00, 0x01],
    DENEB_FORK_EPOCH: Epoch(uint64.high),
    # Electra
    ELECTRA_FORK_VERSION: Version [byte 0x05, 0x00, 0x00, 0x01],
    ELECTRA_FORK_EPOCH: Epoch(uint64.high),


    # Time parameters
    # ---------------------------------------------------------------
    # [customized] Faster for testing purposes
    # TODO SECONDS_PER_SLOT: 6,
    # 14 (estimate from Eth1 mainnet)
    SECONDS_PER_ETH1_BLOCK: 14,
    # 2**8 (= 256) epochs
    MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256,
    # [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit
    SHARD_COMMITTEE_PERIOD: 64,
    # [customized] process deposits more quickly, but insecure
    ETH1_FOLLOW_DISTANCE: 16,


    # Validator cycle
    # ---------------------------------------------------------------
    # 2**2 (= 4)
    INACTIVITY_SCORE_BIAS: 4,
    # 2**4 (= 16)
    INACTIVITY_SCORE_RECOVERY_RATE: 16,
    # 2**4 * 10**9 (= 16,000,000,000) Gwei
    EJECTION_BALANCE: 16000000000'u64,
    # [customized] more easily demonstrate the difference between this value and the activation churn limit
    MIN_PER_EPOCH_CHURN_LIMIT: 2,
    # [customized] scale queue churn at much lower validator counts for testing
    CHURN_LIMIT_QUOTIENT: 32,
    # [New in Deneb:EIP7514] [customized]
    MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4,
    # [New in Electra:EIP7251] 2**6 * 10**9 (= 64,000,000,000)
    MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000'u64,
    # [New in Electra:EIP7251] 2**7 * 10**9 (= 128,000,000,000)
    MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000'u64,


    # Deposit contract
    # ---------------------------------------------------------------
    # Ethereum Goerli testnet
    DEPOSIT_CHAIN_ID: 5,
    DEPOSIT_NETWORK_ID: 5,
    # Configured on a per testnet basis
    DEPOSIT_CONTRACT_ADDRESS: default(Eth1Address),

    # Networking
    # ---------------------------------------------------------------
    # `10 * 2**20` (= 10485760, 10 MiB)
    # TODO GOSSIP_MAX_SIZE: 10485760,
    # `2**10` (= 1024)
    # TODO MAX_REQUEST_BLOCKS: 1024,
    # `2**8` (= 256)
    # TODO EPOCHS_PER_SUBNET_SUBSCRIPTION: 256,
    # [customized] `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 272)
    MIN_EPOCHS_FOR_BLOCK_REQUESTS: 272,
    # `10 * 2**20` (=10485760, 10 MiB)
    # TODO MAX_CHUNK_SIZE: 10485760,
    # 5s
    # TODO TTFB_TIMEOUT: 5,
    # 10s
    # TODO RESP_TIMEOUT: 10,
    # TODO ATTESTATION_PROPAGATION_SLOT_RANGE: 32,
    # 500ms
    # TODO MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500,
    # TODO MESSAGE_DOMAIN_INVALID_SNAPPY: [byte 0x00, 0x00, 0x00, 0x00],
    # TODO MESSAGE_DOMAIN_VALID_SNAPPY: [byte 0x01, 0x00, 0x00, 0x00],
    # 2 subnets per node
    # TODO SUBNETS_PER_NODE: 2,
    # 2**8 (= 64)
    # TODO ATTESTATION_SUBNET_COUNT: 64,
    # TODO ATTESTATION_SUBNET_EXTRA_BITS: 0,
    # ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS
    # TODO ATTESTATION_SUBNET_PREFIX_BITS: 6,

    # Deneb
    # `2**7` (=128)
    # TODO MAX_REQUEST_BLOCKS_DENEB: 128,
    # MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
    # TODO MAX_REQUEST_BLOB_SIDECARS: 768,
    # `2**12` (= 4096 epochs, ~18 days)
    MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
    # `6`
    # TODO BLOB_SIDECAR_SUBNET_COUNT: 6,

    # EIP-7594
    # `2**7` (=128)
    # TODO MAX_REQUEST_BLOCKS_DENEB: 128,
    # MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS
    # TODO MAX_REQUEST_BLOB_SIDECARS: 768,
    # `2**12` (= 4096 epochs, ~18 days)
    MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096
  )

else:
  {.error: "Only mainnet, gnosis, and minimal presets supported".}
  # macro createConstantsFromPreset*(path: static string): untyped =
  #   result = newStmtList()

  #   let preset = try: readPresetFile(path)
  #                except CatchableError as err:
  #                  error err.msg # TODO: This should be marked as noreturn
  #                  return

  #   for name, value in preset.values:
  #     let
  #       typ = getType(name)
  #       value = if typ in ["int64", "uint64", "byte"]: typ & "(" & value & ")"
  #               else: "parse(" & typ & ", \"" & value & "\")"
  #     try:
  #       result.add parseStmt("const $1* {.intdefine.} = $2" % [$name, value])
  #     except ValueError:
  #       doAssert false, "All values in the presets are printable"

  #   if preset.missingValues.card > 0:
  #     warning "Missing constants in preset: " & $preset.missingValues

  # createConstantsFromPreset const_preset

const SLOTS_PER_SYNC_COMMITTEE_PERIOD* =
  SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/phase0/p2p-interface.md#configuration
func safeMinEpochsForBlockRequests*(cfg: RuntimeConfig): uint64 =
  cfg.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + cfg.CHURN_LIMIT_QUOTIENT div 2

func parse(T: type uint64, input: string): T {.raises: [ValueError].} =
  var res: BiggestUInt
  if input.len > 2 and input[0] == '0' and input[1] == 'x':
    if parseHex(input, res) != input.len:
      raise newException(ValueError, "The constant value should be a valid hex integer")
  else:
    if parseBiggestUInt(input, res) != input.len:
      raise newException(ValueError, "The constant value should be a valid unsigned integer")

  uint64(res)

template parse(T: type byte, input: string): T =
  byte parse(uint64, input)

func parse(T: type array[4, byte], input: string): T
           {.raises: [ValueError].} =
  hexToByteArray(input, 4)

func parse(T: type Version, input: string): T
           {.raises: [ValueError].} =
  Version hexToByteArray(input, 4)

template parse(T: type Slot, input: string): T =
  Slot parse(uint64, input)

template parse(T: type Epoch, input: string): T =
  Epoch parse(uint64, input)

template parse(T: type string, input: string): T =
  input.strip(chars = {'"', '\''})

template parse(T: type Eth1Address, input: string): T =
  Eth1Address.fromHex(input)

template parse(T: type BlockHash, input: string): T =
  BlockHash.fromHex(input)

template parse(T: type UInt256, input: string): T =
  parse(input, UInt256, 10)

func parse(T: type DomainType, input: string): T
           {.raises: [ValueError].} =
  DomainType hexToByteArray(input, 4)

proc readRuntimeConfig*(
    fileContent: string, path: string): (RuntimeConfig, seq[string]) {.
    raises: [PresetFileError, PresetIncompatibleError].} =
  var
    lineNum = 0
    cfg = defaultRuntimeConfig

  template lineinfo: string =
    try: "$1($2) " % [path, $lineNum]
    except ValueError: path

  template fail(msg) =
    raise newException(PresetFileError, lineinfo() & msg)

  var names: seq[string]
  for name, field in cfg.fieldPairs():
    names.add name

  var values: Table[string, string]
  for line in splitLines(fileContent):
    inc lineNum
    if line.len == 0 or line[0] == '#': continue
    # remove any trailing comments
    let line = line.split("#")[0]
    let lineParts = line.split(":")
    if lineParts.len != 2:
      fail "Invalid syntax: A preset file should include only assignments in the form 'ConstName: Value'"

    if lineParts[0] in ignoredValues: continue

    values[lineParts[0]] = lineParts[1].strip

  values.withValue("DENEB_FORK_EPOCH", v):
    values["EIP7594_FORK_EPOCH"] = v[]
  values.withValue("DENEB_FORK_VERSION", v):
    values["EIP7594_FORK_VERSION"] = v[]

  # Certain config keys are baked into the binary at compile-time
  # and cannot be overridden via config.
  template checkCompatibility(
      constValue: untyped, name: string, operator: untyped = `==`): untyped =
    if values.hasKey(name):
      const opDesc = astToStr(operator)
      try:
        let value = parse(typeof(constValue), values[name])
        when constValue is distinct:
          if not operator(distinctBase(value), distinctBase(constValue)):
            raise (ref PresetFileError)(msg:
              "Cannot override config" &
              " (required: " & name & opDesc & $distinctBase(constValue) &
              " - config: " & name & "=" & values[name] & ")")
        else:
          if not operator(value, constValue):
            raise (ref PresetFileError)(msg:
              "Cannot override config" &
              " (required: " & name & opDesc & $constValue &
              " - config: " & name & "=" & values[name] & ")")
        values.del name
      except ValueError:
        raise (ref PresetFileError)(msg: "Unable to parse " & name)

  template checkCompatibility(
      constValue: untyped, operator: untyped = `==`): untyped =
    block:
      const name = astToStr(constValue)
      checkCompatibility(constValue, name, operator)

  checkCompatibility SECONDS_PER_SLOT

  checkCompatibility BLS_WITHDRAWAL_PREFIX

  checkCompatibility MAX_COMMITTEES_PER_SLOT
  checkCompatibility TARGET_COMMITTEE_SIZE
  checkCompatibility MAX_VALIDATORS_PER_COMMITTEE
  checkCompatibility SHUFFLE_ROUND_COUNT
  checkCompatibility HYSTERESIS_QUOTIENT
  checkCompatibility HYSTERESIS_DOWNWARD_MULTIPLIER
  checkCompatibility HYSTERESIS_UPWARD_MULTIPLIER
  checkCompatibility MIN_DEPOSIT_AMOUNT
  checkCompatibility MAX_EFFECTIVE_BALANCE
  checkCompatibility EFFECTIVE_BALANCE_INCREMENT
  checkCompatibility MIN_ATTESTATION_INCLUSION_DELAY
  checkCompatibility SLOTS_PER_EPOCH
  checkCompatibility MIN_SEED_LOOKAHEAD
  checkCompatibility MAX_SEED_LOOKAHEAD
  checkCompatibility EPOCHS_PER_ETH1_VOTING_PERIOD
  checkCompatibility SLOTS_PER_HISTORICAL_ROOT
  checkCompatibility MIN_EPOCHS_TO_INACTIVITY_PENALTY
  checkCompatibility EPOCHS_PER_HISTORICAL_VECTOR
  checkCompatibility EPOCHS_PER_SLASHINGS_VECTOR
  checkCompatibility HISTORICAL_ROOTS_LIMIT
  checkCompatibility VALIDATOR_REGISTRY_LIMIT
  checkCompatibility BASE_REWARD_FACTOR
  checkCompatibility WHISTLEBLOWER_REWARD_QUOTIENT
  checkCompatibility PROPOSER_REWARD_QUOTIENT
  checkCompatibility INACTIVITY_PENALTY_QUOTIENT
  checkCompatibility MIN_SLASHING_PENALTY_QUOTIENT
  checkCompatibility PROPORTIONAL_SLASHING_MULTIPLIER
  checkCompatibility MAX_PROPOSER_SLASHINGS
  checkCompatibility MAX_ATTESTER_SLASHINGS
  checkCompatibility MAX_ATTESTATIONS
  checkCompatibility MAX_DEPOSITS
  checkCompatibility MAX_VOLUNTARY_EXITS

  checkCompatibility TARGET_AGGREGATORS_PER_COMMITTEE
  checkCompatibility EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION

  checkCompatibility DOMAIN_BEACON_PROPOSER
  checkCompatibility DOMAIN_BEACON_ATTESTER
  checkCompatibility DOMAIN_RANDAO
  checkCompatibility DOMAIN_DEPOSIT
  checkCompatibility DOMAIN_VOLUNTARY_EXIT
  checkCompatibility DOMAIN_SELECTION_PROOF
  checkCompatibility DOMAIN_AGGREGATE_AND_PROOF
  checkCompatibility DOMAIN_SYNC_COMMITTEE
  checkCompatibility DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF
  checkCompatibility DOMAIN_CONTRIBUTION_AND_PROOF

  checkCompatibility GOSSIP_MAX_SIZE
  checkCompatibility MAX_REQUEST_BLOCKS
  checkCompatibility EPOCHS_PER_SUBNET_SUBSCRIPTION
  checkCompatibility MAX_CHUNK_SIZE
  checkCompatibility TTFB_TIMEOUT
  checkCompatibility RESP_TIMEOUT
  checkCompatibility ATTESTATION_PROPAGATION_SLOT_RANGE
  checkCompatibility MAXIMUM_GOSSIP_CLOCK_DISPARITY.milliseconds.uint64,
                     "MAXIMUM_GOSSIP_CLOCK_DISPARITY"
  checkCompatibility MESSAGE_DOMAIN_INVALID_SNAPPY
  checkCompatibility MESSAGE_DOMAIN_VALID_SNAPPY
  checkCompatibility SUBNETS_PER_NODE
  checkCompatibility ATTESTATION_SUBNET_COUNT
  checkCompatibility ATTESTATION_SUBNET_EXTRA_BITS
  checkCompatibility ATTESTATION_SUBNET_PREFIX_BITS

  checkCompatibility MAX_REQUEST_BLOCKS_DENEB
  checkCompatibility MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK,
                     "MAX_REQUEST_BLOB_SIDECARS"
  checkCompatibility BLOB_SIDECAR_SUBNET_COUNT

  # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/phase0/fork-choice.md#configuration
  # Isn't being used as a preset in the usual way: at any time, there's one correct value
  checkCompatibility PROPOSER_SCORE_BOOST
  checkCompatibility REORG_HEAD_WEIGHT_THRESHOLD
  checkCompatibility REORG_PARENT_WEIGHT_THRESHOLD
  checkCompatibility REORG_MAX_EPOCHS_SINCE_FINALIZATION

  for name, field in cfg.fieldPairs():
    if name in values:
      try:
        field = parse(typeof(field), values[name])
        values.del name
      except ValueError:
        raise (ref PresetFileError)(msg: "Unable to parse " & name)

  if cfg.PRESET_BASE != const_preset:
    raise (ref PresetIncompatibleError)(
      msg: "Config not compatible with binary, compile with -d:const_preset=" & cfg.PRESET_BASE)

  # Requires initialized `cfg`
  checkCompatibility cfg.safeMinEpochsForBlockRequests(),
                     "MIN_EPOCHS_FOR_BLOCK_REQUESTS", `>=`

  var unknowns: seq[string]
  for name in values.keys:
    unknowns.add name

  (cfg, unknowns)

proc readRuntimeConfig*(
    path: string): (RuntimeConfig, seq[string]) {.
    raises: [IOError, PresetFileError, PresetIncompatibleError].} =
  readRuntimeConfig(readFile(path), path)

template name*(cfg: RuntimeConfig): string =
  if cfg.CONFIG_NAME.len() > 0:
    cfg.CONFIG_NAME
  else:
    const_preset

func defaultLightClientDataMaxPeriods*(cfg: RuntimeConfig): uint64 =
  const epochsPerPeriod = EPOCHS_PER_SYNC_COMMITTEE_PERIOD
  let maxEpochs = cfg.MIN_EPOCHS_FOR_BLOCK_REQUESTS
  (maxEpochs + epochsPerPeriod - 1) div epochsPerPeriod