Automated beacon constant checks (#583)
* Fix SSZ bitlist deserialization super silly bug * Add an automated sanity checks of the beacon chain constants * Remove SSZ consensus skipping procs [skip ci] * Add phase 1 domains * Fix mainnet constants * Add missing phase 1 constants on minimal (they are not needed somehow on mainnet) * Rebase artifact: constants were defined twice
This commit is contained in:
parent
edfd65fd5d
commit
1938379bcd
|
@ -72,6 +72,23 @@ template maxSize*(n: int) {.pragma.}
|
||||||
type
|
type
|
||||||
Bytes = seq[byte]
|
Bytes = seq[byte]
|
||||||
|
|
||||||
|
# Domains
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_beacon-chain.md#domain-types
|
||||||
|
DomainType* {.pure.} = enum
|
||||||
|
DOMAIN_BEACON_PROPOSER = 0
|
||||||
|
DOMAIN_BEACON_ATTESTER = 1
|
||||||
|
DOMAIN_RANDAO = 2
|
||||||
|
DOMAIN_DEPOSIT = 3
|
||||||
|
DOMAIN_VOLUNTARY_EXIT = 4
|
||||||
|
# Phase 1 - Custody game
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/1_custody-game.md#signature-domain-types
|
||||||
|
DOMAIN_CUSTODY_BIT_CHALLENGE = 6
|
||||||
|
# Phase 1 - Sharding
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/1_shard-data-chains.md#signature-domain-types
|
||||||
|
DOMAIN_SHARD_PROPOSER = 128
|
||||||
|
DOMAIN_SHARD_ATTESTER = 129
|
||||||
|
|
||||||
# https://github.com/nim-lang/Nim/issues/574 and be consistent across
|
# https://github.com/nim-lang/Nim/issues/574 and be consistent across
|
||||||
# 32-bit and 64-bit word platforms.
|
# 32-bit and 64-bit word platforms.
|
||||||
# TODO VALIDATOR_REGISTRY_LIMIT is 1 shl 40 in 0.8.3, and
|
# TODO VALIDATOR_REGISTRY_LIMIT is 1 shl 40 in 0.8.3, and
|
||||||
|
|
|
@ -151,13 +151,34 @@ const
|
||||||
MAX_DEPOSITS* = 2^4
|
MAX_DEPOSITS* = 2^4
|
||||||
MAX_VOLUNTARY_EXITS* = 2^4
|
MAX_VOLUNTARY_EXITS* = 2^4
|
||||||
|
|
||||||
type
|
# Fork choice
|
||||||
# Domains
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/configs/mainnet.yaml#L138
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_fork-choice.md#configuration
|
||||||
DomainType* {.pure.} = enum
|
SAFE_SLOTS_TO_UPDATE_JUSTIFIED* = 8 # 96 seconds
|
||||||
DOMAIN_BEACON_PROPOSER = 0
|
|
||||||
DOMAIN_BEACON_ATTESTER = 1
|
# Validators
|
||||||
DOMAIN_RANDAO = 2
|
# ---------------------------------------------------------------
|
||||||
DOMAIN_DEPOSIT = 3
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/validator/0_beacon-chain-validator.md#misc
|
||||||
DOMAIN_VOLUNTARY_EXIT = 4
|
ETH1_FOLLOW_DISTANCE* = 1024 # blocks ~ 4 hours
|
||||||
|
TARGET_AGGREGATORS_PER_COMMITTEE* = 16 # validators
|
||||||
|
RANDOM_SUBNETS_PER_VALIDATOR* = 1 # subnet
|
||||||
|
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION* = 256 # epochs ~ 27 hours
|
||||||
|
|
||||||
|
# Phase 1 - Sharding
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/1_shard-data-chains.md#time-parameters
|
||||||
|
# TODO those are included in minimal.yaml but not mainnet.yaml
|
||||||
|
# Why?
|
||||||
|
# SHARD_SLOTS_PER_BEACON_SLOT* = 2 # spec: SHARD_SLOTS_PER_EPOCH
|
||||||
|
# EPOCHS_PER_SHARD_PERIOD* = 4
|
||||||
|
# PHASE_1_FORK_EPOCH* = 8
|
||||||
|
# PHASE_1_FORK_SLOT* = 64
|
||||||
|
|
||||||
|
# Phase 1 - Custody game
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/1_custody-game.md#constants
|
||||||
|
# TODO those are included in minimal.yaml but not mainnet.yaml
|
||||||
|
# Why?
|
||||||
|
# EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS* = 4096 # epochs
|
||||||
|
# EPOCHS_PER_CUSTODY_PERIOD* = 4
|
||||||
|
# CUSTODY_PERIOD_TO_RANDAO_PADDING* = 4
|
||||||
|
|
|
@ -34,7 +34,7 @@ const
|
||||||
# Changed
|
# Changed
|
||||||
SHUFFLE_ROUND_COUNT* = 10
|
SHUFFLE_ROUND_COUNT* = 10
|
||||||
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT* {.intdefine.} = 64
|
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT* {.intdefine.} = 64
|
||||||
MIN_GENESIS_TIME* {.intdefine.} = 0
|
MIN_GENESIS_TIME* {.intdefine.} = 1578009600 # 3 Jan, 2020
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
@ -121,14 +121,40 @@ const
|
||||||
MAX_DEPOSITS* = 2^4
|
MAX_DEPOSITS* = 2^4
|
||||||
MAX_VOLUNTARY_EXITS* = 2^4
|
MAX_VOLUNTARY_EXITS* = 2^4
|
||||||
|
|
||||||
|
# Fork choice
|
||||||
type
|
|
||||||
# Domains
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_beacon-chain.md#domain-types
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_fork-choice.md#configuration
|
||||||
DomainType* {.pure.} = enum
|
|
||||||
DOMAIN_BEACON_PROPOSER = 0
|
# Changed
|
||||||
DOMAIN_BEACON_ATTESTER = 1
|
SAFE_SLOTS_TO_UPDATE_JUSTIFIED* = 2
|
||||||
DOMAIN_RANDAO = 2
|
|
||||||
DOMAIN_DEPOSIT = 3
|
# Validators
|
||||||
DOMAIN_VOLUNTARY_EXIT = 4
|
# ---------------------------------------------------------------
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/validator/0_beacon-chain-validator.md#misc
|
||||||
|
|
||||||
|
# Changed
|
||||||
|
ETH1_FOLLOW_DISTANCE* = 16 # blocks
|
||||||
|
|
||||||
|
# Unchanged
|
||||||
|
TARGET_AGGREGATORS_PER_COMMITTEE* = 16 # validators
|
||||||
|
RANDOM_SUBNETS_PER_VALIDATOR* = 1 # subnet
|
||||||
|
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION* = 256 # epochs ~ 27 hours
|
||||||
|
|
||||||
|
# Phase 1 - Sharding
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/1_shard-data-chains.md#time-parameters
|
||||||
|
# TODO those are included in minimal.yaml but not mainnet.yaml
|
||||||
|
# Why?
|
||||||
|
SHARD_SLOTS_PER_BEACON_SLOT* = 2 # spec: SHARD_SLOTS_PER_EPOCH
|
||||||
|
EPOCHS_PER_SHARD_PERIOD* = 4
|
||||||
|
PHASE_1_FORK_EPOCH* = 8
|
||||||
|
PHASE_1_FORK_SLOT* = 64
|
||||||
|
|
||||||
|
# Phase 1 - Custody game
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/1_custody-game.md#constants
|
||||||
|
# TODO those are included in minimal.yaml but not mainnet.yaml
|
||||||
|
# Why?
|
||||||
|
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS* = 4096 # epochs
|
||||||
|
EPOCHS_PER_CUSTODY_PERIOD* = 4
|
||||||
|
CUSTODY_PERIOD_TO_RANDAO_PADDING* = 4
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
import # Official constants
|
||||||
|
./official/test_fixture_const_sanity_check
|
||||||
|
|
||||||
import # Unit test
|
import # Unit test
|
||||||
./test_attestation_pool,
|
./test_attestation_pool,
|
||||||
./test_beacon_chain_db,
|
./test_beacon_chain_db,
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Sanity check on constants
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
|
||||||
|
import
|
||||||
|
# Standard library
|
||||||
|
macros, os, strutils, tables, math, json, streams,
|
||||||
|
strformat, unittest,
|
||||||
|
# Third party
|
||||||
|
yaml,
|
||||||
|
# Status libraries
|
||||||
|
stew/[byteutils, endians2],
|
||||||
|
# Internals
|
||||||
|
../../beacon_chain/spec/[datatypes, digest],
|
||||||
|
# Test utilities
|
||||||
|
../testutil
|
||||||
|
|
||||||
|
const
|
||||||
|
SpecDir = currentSourcePath.rsplit(DirSep, 1)[0] /
|
||||||
|
".."/".."/"beacon_chain"/"spec"
|
||||||
|
FixturesDir = currentSourcePath.rsplit(DirSep, 1)[0] / "fixtures"
|
||||||
|
Config = FixturesDir/"tests-v0.9.2"/const_preset/"config.yaml"
|
||||||
|
|
||||||
|
type
|
||||||
|
CheckedType = SomeInteger or Slot or Epoch
|
||||||
|
# Only test numerical types, constants
|
||||||
|
# defined for other type will get a placeholder low(int64) value
|
||||||
|
|
||||||
|
macro parseNumConsts(file: static string): untyped =
|
||||||
|
## Important: All const are assumed to be top-level
|
||||||
|
## i.e. not hidden behind a "when" statement
|
||||||
|
|
||||||
|
var constsToCheck: seq[(string, NimNode)]
|
||||||
|
# We can't create a table directly and quote do it
|
||||||
|
# Nim complains about the "data" field not being accessible
|
||||||
|
|
||||||
|
let fileAST = parseStmt(slurp(file))
|
||||||
|
for statement in fileAST:
|
||||||
|
if statement.kind == nnkConstSection:
|
||||||
|
for constDef in statement:
|
||||||
|
if constDef.len == 0 or
|
||||||
|
constDef[0].kind notin {nnkPragmaExpr, nnkPostfix}:
|
||||||
|
# Comments in a const section need to be skipped.
|
||||||
|
# And we only want exported constants.
|
||||||
|
continue
|
||||||
|
# Note: we assume that all const with a pragma are exported
|
||||||
|
# 1. Simple statement
|
||||||
|
#
|
||||||
|
# ConstDef
|
||||||
|
# Postfix
|
||||||
|
# Ident "*"
|
||||||
|
# Ident "HISTORICAL_ROOTS_LIMIT"
|
||||||
|
# Empty
|
||||||
|
# IntLit 16777216
|
||||||
|
#
|
||||||
|
# 2. with intdefine pragma
|
||||||
|
#
|
||||||
|
# ConstDef
|
||||||
|
# PragmaExpr
|
||||||
|
# Postfix
|
||||||
|
# Ident "*"
|
||||||
|
# Ident "MIN_GENESIS_ACTIVE_VALIDATOR_COUNT"
|
||||||
|
# Pragma
|
||||||
|
# Ident "intdefine"
|
||||||
|
# Empty
|
||||||
|
# IntLit 99
|
||||||
|
let name = if constDef[0].kind == nnkPostfix: $constDef[0][1]
|
||||||
|
else: $constDef[0][0][1]
|
||||||
|
|
||||||
|
# ConstsToCheck["HISTORICAL_ROOTS_LIMIT"} = uint64(16777216)
|
||||||
|
# Put a placeholder values for strings
|
||||||
|
let value = block:
|
||||||
|
let node = constDef[2]
|
||||||
|
quote do:
|
||||||
|
when `node` is CheckedType:
|
||||||
|
uint64(`node`)
|
||||||
|
else:
|
||||||
|
high(uint64)
|
||||||
|
constsToCheck.add (name, value)
|
||||||
|
|
||||||
|
result = quote do: `constsToCheck`
|
||||||
|
|
||||||
|
const
|
||||||
|
datatypesConsts = @(parseNumConsts(SpecDir/"datatypes.nim"))
|
||||||
|
mainnetConsts = @(parseNumConsts(SpecDir/"presets"/"mainnet.nim"))
|
||||||
|
minimalConsts = @(parseNumConsts(SpecDir/"presets"/"minimal.nim"))
|
||||||
|
|
||||||
|
const IgnoreKeys = [
|
||||||
|
# Ignore all non-numeric types
|
||||||
|
"DEPOSIT_CONTRACT_ADDRESS"
|
||||||
|
]
|
||||||
|
|
||||||
|
func parseU32LEHex(hexValue: string): uint32 =
|
||||||
|
## Parse little-endian uint32 hex string
|
||||||
|
result = uint32.fromBytesLE hexToByteArray[4](hexValue)
|
||||||
|
|
||||||
|
proc checkConfig() =
|
||||||
|
let ConstsToCheck = toTable(
|
||||||
|
when const_preset == "minimal":
|
||||||
|
minimalConsts & datatypesConsts
|
||||||
|
else:
|
||||||
|
mainnetConsts & datatypesConsts
|
||||||
|
)
|
||||||
|
|
||||||
|
var yamlStream = openFileStream(Config)
|
||||||
|
defer: yamlStream.close()
|
||||||
|
var config = yamlStream.loadToJson()
|
||||||
|
doAssert config.len == 1
|
||||||
|
for constant, value in config[0]:
|
||||||
|
test &"{constant:<50}{value:<20}{preset()}":
|
||||||
|
if constant in IgnoreKeys:
|
||||||
|
echo &" ↶↶ Skipping {constant}"
|
||||||
|
continue
|
||||||
|
if constant.startsWith("DOMAIN"):
|
||||||
|
let domain = parseEnum[DomainType](constant)
|
||||||
|
let value = parseU32LEHex(value.getStr())
|
||||||
|
check: uint32(domain) == value
|
||||||
|
else:
|
||||||
|
check: ConstsToCheck[constant] == value.getBiggestInt().uint64()
|
||||||
|
|
||||||
|
suite "Official - 0.9.2 - constants & config " & preset():
|
||||||
|
checkConfig()
|
|
@ -43,50 +43,16 @@ setDefaultValue(SSZHashTreeRoot, signing_root, "")
|
||||||
# Checking the values against the yaml file is TODO (require more flexible Yaml parser)
|
# Checking the values against the yaml file is TODO (require more flexible Yaml parser)
|
||||||
const Unsupported = toHashSet([
|
const Unsupported = toHashSet([
|
||||||
"AggregateAndProof", # Type for signature aggregation - not implemented
|
"AggregateAndProof", # Type for signature aggregation - not implemented
|
||||||
# "Attestation", #
|
|
||||||
# "AttestationData", #
|
|
||||||
# "AttesterSlashing", #
|
|
||||||
# "BeaconBlock", #
|
|
||||||
# "BeaconBlockBody", #
|
|
||||||
# "BeaconBlockHeader", #
|
|
||||||
# "BeaconState", #
|
|
||||||
# "Checkpoint", #
|
|
||||||
# "Deposit", #
|
|
||||||
# "DepositData", #
|
|
||||||
# "Eth1Data", #
|
|
||||||
# "Fork", #
|
|
||||||
# "HistoricalBatch", #
|
|
||||||
# "IndexedAttestation", #
|
|
||||||
# "PendingAttestation", #
|
|
||||||
# "ProposerSlashing", #
|
|
||||||
# "Validator", # HashTreeRoot KO
|
|
||||||
# "VoluntaryExit" #
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const UnsupportedMainnet = toHashSet([
|
proc checkSSZ(T: typedesc, dir: string, expectedHash: SSZHashTreeRoot) =
|
||||||
"PendingAttestation", # HashTreeRoot KO
|
|
||||||
"BeaconState",
|
|
||||||
"AttesterSlashing",
|
|
||||||
"BeaconBlockBody",
|
|
||||||
"IndexedAttestation",
|
|
||||||
"Attestation",
|
|
||||||
"BeaconBlock"
|
|
||||||
])
|
|
||||||
|
|
||||||
type Skip = enum
|
|
||||||
SkipNone
|
|
||||||
SkipHashTreeRoot
|
|
||||||
SkipSigningRoot
|
|
||||||
|
|
||||||
proc checkSSZ(T: typedesc, dir: string, expectedHash: SSZHashTreeRoot, skip = SkipNone) =
|
|
||||||
# Deserialize into a ref object to not fill Nim stack
|
# Deserialize into a ref object to not fill Nim stack
|
||||||
var deserialized: ref T
|
var deserialized: ref T
|
||||||
new deserialized
|
new deserialized
|
||||||
deserialized[] = SSZ.loadFile(dir/"serialized.ssz", T)
|
deserialized[] = SSZ.loadFile(dir/"serialized.ssz", T)
|
||||||
|
|
||||||
if not(skip == SkipHashTreeRoot):
|
check: expectedHash.root == "0x" & toLowerASCII($deserialized.hashTreeRoot())
|
||||||
check: expectedHash.root == "0x" & toLowerASCII($deserialized.hashTreeRoot())
|
if expectedHash.signing_root != "":
|
||||||
if expectedHash.signing_root != "" and not(skip == SkipSigningRoot):
|
|
||||||
check: expectedHash.signing_root == "0x" & toLowerASCII($deserialized[].signingRoot())
|
check: expectedHash.signing_root == "0x" & toLowerASCII($deserialized[].signingRoot())
|
||||||
|
|
||||||
# TODO check the value
|
# TODO check the value
|
||||||
|
@ -104,17 +70,11 @@ proc runSSZtests() =
|
||||||
for pathKind, sszType in walkDir(SSZDir, relative = true):
|
for pathKind, sszType in walkDir(SSZDir, relative = true):
|
||||||
doAssert pathKind == pcDir
|
doAssert pathKind == pcDir
|
||||||
if sszType in Unsupported:
|
if sszType in Unsupported:
|
||||||
test &" Skipping {sszType:20} consensus object ✗✗✗":
|
test &" Skipping {sszType:20} ✗✗✗":
|
||||||
discard
|
discard
|
||||||
continue
|
continue
|
||||||
|
|
||||||
when const_preset == "mainnet":
|
test &" Testing {sszType}":
|
||||||
if sszType in UnsupportedMainnet:
|
|
||||||
test &" Skipping {sszType:20} consensus object ✗✗✗ (skipped on mainnet-only)":
|
|
||||||
discard
|
|
||||||
continue
|
|
||||||
|
|
||||||
test &" Testing {sszType:20} consensus object ✓✓✓":
|
|
||||||
let path = SSZDir/sszType
|
let path = SSZDir/sszType
|
||||||
for pathKind, sszTestKind in walkDir(path, relative = true):
|
for pathKind, sszTestKind in walkDir(path, relative = true):
|
||||||
doAssert pathKind == pcDir
|
doAssert pathKind == pcDir
|
||||||
|
@ -146,5 +106,5 @@ proc runSSZtests() =
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "Unsupported test: " & sszType)
|
raise newException(ValueError, "Unsupported test: " & sszType)
|
||||||
|
|
||||||
suite "Official - 0.9.1 - SSZ consensus objects " & preset():
|
suite "Official - 0.9.2 - SSZ consensus objects " & preset():
|
||||||
runSSZtests()
|
runSSZtests()
|
||||||
|
|
Loading…
Reference in New Issue