nimbus-eth2/tests/test_validator_client.nim

961 lines
39 KiB
Nim

# 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: [].}
{.used.}
import std/strutils
import httputils
import chronos/apps/http/httpserver
import chronos/unittest2/asynctests
import ../beacon_chain/spec/eth2_apis/eth2_rest_serialization,
../beacon_chain/validator_client/[api, common, scoring, fallback_service]
const
HostNames = [
"[2001:db8::1]",
"127.0.0.1",
"hostname.com",
"localhost",
"username:password@[2001:db8::1]",
"username:password@127.0.0.1",
"username:password@hostname.com",
"username:password@localhost",
]
GoodTestVectors = [
("http://$1",
"ok(http://$1)"),
("http://$1?q=query",
"ok(http://$1?q=query)"),
("http://$1?q=query#anchor",
"ok(http://$1?q=query#anchor)"),
("http://$1/subpath/",
"ok(http://$1/subpath/)"),
("http://$1/subpath/q=query",
"ok(http://$1/subpath/q=query)"),
("http://$1/subpath/q=query#anchor",
"ok(http://$1/subpath/q=query#anchor)"),
("http://$1/subpath",
"ok(http://$1/subpath)"),
("http://$1/subpath?q=query",
"ok(http://$1/subpath?q=query)"),
("http://$1/subpath?q=query#anchor",
"ok(http://$1/subpath?q=query#anchor)"),
("https://$1",
"ok(https://$1)"),
("https://$1?q=query",
"ok(https://$1?q=query)"),
("https://$1?q=query#anchor",
"ok(https://$1?q=query#anchor)"),
("https://$1/subpath/",
"ok(https://$1/subpath/)"),
("https://$1/subpath/q=query",
"ok(https://$1/subpath/q=query)"),
("https://$1/subpath/q=query#anchor",
"ok(https://$1/subpath/q=query#anchor)"),
("https://$1/subpath",
"ok(https://$1/subpath)"),
("https://$1/subpath?q=query",
"ok(https://$1/subpath?q=query)"),
("https://$1/subpath?q=query#anchor",
"ok(https://$1/subpath?q=query#anchor)"),
("$1:5052",
"ok(http://$1:5052)"),
("$1:5052?q=query",
"ok(http://$1:5052?q=query)"),
("$1:5052?q=query#anchor",
"ok(http://$1:5052?q=query#anchor)"),
("$1:5052/subpath/",
"ok(http://$1:5052/subpath/)"),
("$1:5052/subpath/q=query",
"ok(http://$1:5052/subpath/q=query)"),
("$1:5052/subpath/q=query#anchor",
"ok(http://$1:5052/subpath/q=query#anchor)"),
("$1:5052/subpath",
"ok(http://$1:5052/subpath)"),
("$1:5052/subpath?q=query",
"ok(http://$1:5052/subpath?q=query)"),
("$1:5052/subpath?q=query#anchor",
"ok(http://$1:5052/subpath?q=query#anchor)"),
("bnode://$1:5052",
"err(Unknown scheme value)"),
("bnode://$1:5052?q=query",
"err(Unknown scheme value)"),
("bnode://$1:5052?q=query#anchor",
"err(Unknown scheme value)"),
("bnode://$1:5052/subpath/",
"err(Unknown scheme value)"),
("bnode://$1:5052/subpath/q=query",
"err(Unknown scheme value)"),
("bnode://$1:5052/subpath/q=query#anchor",
"err(Unknown scheme value)"),
("bnode://$1:5052/subpath",
"err(Unknown scheme value)"),
("bnode://$1:5052/subpath?q=query",
"err(Unknown scheme value)"),
("bnode://$1:5052/subpath?q=query#anchor",
"err(Unknown scheme value)"),
("//$1:5052",
"ok(http://$1:5052)"),
("//$1:5052?q=query",
"ok(http://$1:5052?q=query)"),
("//$1:5052?q=query#anchor",
"ok(http://$1:5052?q=query#anchor)"),
("//$1:5052/subpath/",
"ok(http://$1:5052/subpath/)"),
("//$1:5052/subpath/q=query",
"ok(http://$1:5052/subpath/q=query)"),
("//$1:5052/subpath/q=query#anchor",
"ok(http://$1:5052/subpath/q=query#anchor)"),
("//$1:5052/subpath",
"ok(http://$1:5052/subpath)"),
("//$1:5052/subpath?q=query",
"ok(http://$1:5052/subpath?q=query)"),
("//$1:5052/subpath?q=query#anchor",
"ok(http://$1:5052/subpath?q=query#anchor)"),
("//$1", "err(Missing port number)"),
("//$1?q=query", "err(Missing port number)"),
("//$1?q=query#anchor", "err(Missing port number)"),
("//$1/subpath/", "err(Missing port number)"),
("//$1/subpath/q=query", "err(Missing port number)"),
("//$1/subpath/q=query#anchor", "err(Missing port number)"),
("//$1/subpath", "err(Missing port number)"),
("//$1/subpath?q=query", "err(Missing port number)"),
("//$1/subpath?q=query#anchor", "err(Missing port number)"),
("$1", "err(Missing port number)"),
("$1?q=query", "err(Missing port number)"),
("$1?q=query#anchor", "err(Missing port number)"),
("$1/subpath/", "err(Missing port number)"),
("$1/subpath/q=query", "err(Missing port number)"),
("$1/subpath/q=query#anchor", "err(Missing port number)"),
("$1/subpath", "err(Missing port number)"),
("$1/subpath?q=query", "err(Missing port number)"),
("$1/subpath?q=query#anchor", "err(Missing port number)"),
("", "err(Missing hostname)")
]
ObolBeaconRequestTestVector = """
[
{
"validator_index": "1",
"slot": "1",
"selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"slot": "2",
"validator_index": "2",
"selection_proof": "0x2b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "3",
"selection_proof": "0x3b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
"slot": "3"
},
{
"selection_proof": "0x4b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
"validator_index": "4",
"slot": "4"
}
]"""
ObolBeaconResponseTestVector = """
{
"data": [
{
"validator_index": "1",
"slot": "1",
"selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "2",
"slot": "2",
"selection_proof": "0x2b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "3",
"slot": "3",
"selection_proof": "0x3b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "4",
"slot": "4",
"selection_proof": "0x4b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
}
]
}"""
ObolBeaconResponseTestVectorObject = [
(
validator_index: RestValidatorIndex(1),
slot: Slot(1),
selection_proof: "1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
),
(
validator_index: RestValidatorIndex(2),
slot: Slot(2),
selection_proof: "2b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
),
(
validator_index: RestValidatorIndex(3),
slot: Slot(3),
selection_proof: "3b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
),
(
validator_index: RestValidatorIndex(4),
slot: Slot(4),
selection_proof: "4b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
)
]
ObolSyncRequestTestVector = """
[
{
"validator_index": "1",
"slot": "1",
"subcommittee_index": "1",
"selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "2",
"subcommittee_index": "2",
"slot": "2",
"selection_proof": "0x2b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"subcommittee_index": "3",
"validator_index": "3",
"slot": "3",
"selection_proof": "0x3b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "4",
"slot": "4",
"selection_proof": "0x4b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
"subcommittee_index": "4"
}
]"""
ObolSyncResponseTestVector = """
{
"data": [
{
"validator_index": "1",
"slot": "1",
"subcommittee_index": "1",
"selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "2",
"subcommittee_index": "2",
"slot": "2",
"selection_proof": "0x2b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"subcommittee_index": "3",
"validator_index": "3",
"slot": "3",
"selection_proof": "0x3b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
},
{
"validator_index": "4",
"slot": "4",
"selection_proof": "0x4b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
"subcommittee_index": "4"
}
]
}"""
ObolSyncResponseTestVectorObject = [
(
validator_index: RestValidatorIndex(1),
slot: Slot(1),
subcommittee_index: 1'u64,
selection_proof: "1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
),
(
validator_index: RestValidatorIndex(2),
slot: Slot(2),
subcommittee_index: 2'u64,
selection_proof: "2b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
),
(
validator_index: RestValidatorIndex(3),
slot: Slot(3),
subcommittee_index: 3'u64,
selection_proof: "3b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
),
(
validator_index: RestValidatorIndex(4),
slot: Slot(4),
subcommittee_index: 4'u64,
selection_proof: "4b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
)
]
type
TestDecodeTypes = seq[RestBeaconCommitteeSelection] |
seq[RestSyncCommitteeSelection]
AttestationDataTuple* = tuple[
slot: uint64,
index: uint64,
beacon_block_root: string,
source: uint64,
target: uint64
]
AttestationBitsObject = object
data: CommitteeValidatorsBits
SyncCommitteeBitsObject = object
data: SyncCommitteeAggregationBits
RestJson.useDefaultSerializationFor(
AttestationBitsObject,
SyncCommitteeBitsObject
)
const
AttestationDataVectors = [
# Attestation score with block monitoring enabled (perfect).
((6002798'u64, 10'u64, "22242212", 187586'u64, 187587'u64),
("22242212", 6002798'u64), "<perfect>"),
((6002811'u64, 24'u64, "26ec78d6", 187586'u64, 187587'u64),
("26ec78d6", 6002811'u64), "<perfect>"),
((6002821'u64, 11'u64, "10c6d1a2", 187587'u64, 187588'u64),
("10c6d1a2", 6002821'u64), "<perfect>"),
((6002836'u64, 15'u64, "42354ded", 187587'u64, 187588'u64),
("42354ded", 6002836'u64), "<perfect>"),
((6002859'u64, 10'u64, "97d8ac69", 187588'u64, 187589'u64),
("97d8ac69", 6002859'u64), "<perfect>"),
# Attestation score with block monitoring enabled #1 (not perfect).
((6002871'u64, 25'u64, "524a9e2b", 187588'u64, 187589'u64),
("524a9e2b", 6002870'u64), "375177.5000"),
((6002871'u64, 25'u64, "524a9e2b", 187588'u64, 187589'u64),
("524a9e2b", 6002869'u64), "375177.3333"),
((6002871'u64, 25'u64, "524a9e2b", 187588'u64, 187589'u64),
("524a9e2b", 6002868'u64), "375177.2500"),
((6002871'u64, 25'u64, "524a9e2b", 187588'u64, 187589'u64),
("524a9e2b", 6002867'u64), "375177.2000"),
((6002871'u64, 25'u64, "524a9e2b", 187588'u64, 187589'u64),
("524a9e2b", 6002866'u64), "375177.1667"),
# Attestation score with block monitoring enabled #2 (not perfect).
((6002962'u64, 14'u64, "22a19d87", 187591'u64, 187592'u64),
("22a19d87", 6002961'u64), "375183.5000"),
((6002962'u64, 14'u64, "22a19d87", 187591'u64, 187592'u64),
("22a19d87", 6002960'u64), "375183.3333"),
((6002962'u64, 14'u64, "22a19d87", 187591'u64, 187592'u64),
("22a19d87", 6002959'u64), "375183.2500"),
((6002962'u64, 14'u64, "22a19d87", 187591'u64, 187592'u64),
("22a19d87", 6002958'u64), "375183.2000"),
((6002962'u64, 14'u64, "22a19d87", 187591'u64, 187592'u64),
("22a19d87", 6002957'u64), "375183.1667"),
# Attestation score with block monitoring disabled #1.
((6003217'u64, 52'u64, "5e945218", 187599'u64, 187600'u64),
("00000000", 0'u64), "375199.0000"),
((6003217'u64, 52'u64, "5e945218", 187598'u64, 187600'u64),
("00000000", 0'u64), "375198.0000"),
((6003217'u64, 52'u64, "5e945218", 187597'u64, 187600'u64),
("00000000", 0'u64), "375197.0000"),
((6003217'u64, 52'u64, "5e945218", 187596'u64, 187600'u64),
("00000000", 0'u64), "375196.0000"),
((6003217'u64, 52'u64, "5e945218", 187595'u64, 187600'u64),
("00000000", 0'u64), "375195.0000"),
# Attestation score with block monitoring disabled #2.
((6003257'u64, 9'u64, "7bfa464e", 187600'u64, 187601'u64),
("00000000", 0'u64), "375201.0000"),
((6003257'u64, 9'u64, "7bfa464e", 187599'u64, 187601'u64),
("00000000", 0'u64), "375200.0000"),
((6003257'u64, 9'u64, "7bfa464e", 187598'u64, 187601'u64),
("00000000", 0'u64), "375199.0000"),
((6003257'u64, 9'u64, "7bfa464e", 187597'u64, 187601'u64),
("00000000", 0'u64), "375198.0000"),
((6003257'u64, 9'u64, "7bfa464e", 187596'u64, 187601'u64),
("00000000", 0'u64), "375197.0000"),
]
AggregatedDataVectors = [
("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01", "<perfect>"),
("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", "0.2500"),
("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", "0.5000"),
("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", "0.7500"),
("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01", "0.9995"),
("0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101", "0.0005"),
]
ContributionDataVectors = [
("0xffffffffffffffffffffffffffff7f7f", "0.9844"),
("0xffffffffffffffffffffffff7f7f7f7f", "0.9688"),
("0xffffffffffffffffffff7f7f7f7f7f7f", "0.9531"),
("0xffffffffffffffff7f7f7f7f7f7f7f7f", "0.9375"),
("0xffffffffffff7f7f7f7f7f7f7f7f7f7f", "0.9219"),
("0xffffffff7f7f7f7f7f7f7f7f7f7f7f7f", "0.9062"),
("0xffff7f7f7f7f7f7f7f7f7f7f7f7f7f7f", "0.8906"),
("0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", "0.8750"),
("0xffffffffffffffffffffffffffffffff", "<perfect>")
]
SyncMessageDataVectors = [
# Sync committee messages score with block monitoring enabled (perfect)
(6002798'u64, "22242212", "22242212", 6002798'u64, Opt.some(false),
"<perfect>"),
(6002811'u64, "26ec78d6", "26ec78d6", 6002811'u64, Opt.some(false),
"<perfect>"),
(6002836'u64, "42354ded", "42354ded", 6002836'u64, Opt.some(false),
"<perfect>"),
(6002859'u64, "97d8ac69", "97d8ac69", 6002859'u64, Opt.some(false),
"<perfect>"),
# Sync committee messages score when beacon node is optimistically synced
(6002798'u64, "22242212", "22242212", 6002798'u64, Opt.some(true),
"<bad>"),
(6002811'u64, "26ec78d6", "26ec78d6", 6002811'u64, Opt.some(true),
"<bad>"),
(6002836'u64, "42354ded", "42354ded", 6002836'u64, Opt.some(true),
"<bad>"),
(6002859'u64, "97d8ac69", "97d8ac69", 6002859'u64, Opt.some(true),
"<bad>"),
# Sync committee messages score with block monitoring enabled (not perfect)
(6002797'u64, "22242212", "22242212", 6002798'u64, Opt.some(false),
"1.5000"),
(6002809'u64, "26ec78d6", "26ec78d6", 6002811'u64, Opt.some(false),
"1.3333"),
(6002826'u64, "42354ded", "42354ded", 6002836'u64, Opt.some(false),
"1.0909"),
(6002819'u64, "97d8ac69", "97d8ac69", 6002859'u64, Opt.some(false),
"1.0244"),
# Sync committee messages score with block monitoring disabled
(6002797'u64, "00000000", "22242212", 6002798'u64, Opt.some(false),
"0.1334"),
(6002809'u64, "00000000", "26ec78d6", 6002811'u64, Opt.some(false),
"0.1520"),
(6002826'u64, "00000000", "42354ded", 6002836'u64, Opt.some(false),
"0.2586"),
(6002819'u64, "00000000", "97d8ac69", 6002859'u64, Opt.some(false),
"0.5931"),
]
AttestationBitsVectors = [
([("0xff01", Slot(0), 0'u64), ("0xff01", Slot(0), 0'u64)], 8),
([("0xff01", Slot(0), 0'u64), ("0xff01", Slot(1), 0'u64)], 16),
([("0xff01", Slot(0), 0'u64), ("0xff01", Slot(0), 1'u64)], 16)
]
UInt256ScoreVectors = [
("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0"),
("0x10",
"0x10",
"32")
]
proc init(t: typedesc[Eth2Digest], data: string): Eth2Digest =
let length = len(data)
var dst = Eth2Digest()
try:
hexToByteArray(data.toOpenArray(0, len(data) - 1),
dst.data.toOpenArray(0, (length div 2) - 1))
except ValueError:
discard
dst
proc init(t: typedesc[ProduceAttestationDataResponse],
ad: AttestationDataTuple): ProduceAttestationDataResponse =
ProduceAttestationDataResponse(data: AttestationData(
slot: Slot(ad.slot), index: ad.index,
beacon_block_root: Eth2Digest.init(ad.beacon_block_root),
source: Checkpoint(epoch: Epoch(ad.source)),
target: Checkpoint(epoch: Epoch(ad.target))
))
proc init(t: typedesc[phase0.Attestation], bits: string,
slot: Slot = GENESIS_SLOT, index: uint64 = 0'u64): phase0.Attestation =
let
jdata = "{\"data\":\"" & bits & "\"}"
bits =
try:
RestJson.decode(jdata, AttestationBitsObject)
except SerializationError as exc:
raiseAssert "Serialization error from [" & $exc.name & "]: " & $exc.msg
phase0.Attestation(aggregation_bits: bits.data,
data: AttestationData(slot: slot, index: index))
proc init(t: typedesc[GetAggregatedAttestationResponse],
bits: string): GetAggregatedAttestationResponse =
GetAggregatedAttestationResponse(data: phase0.Attestation.init(bits))
proc init(t: typedesc[ProduceSyncCommitteeContributionResponse],
bits: string): ProduceSyncCommitteeContributionResponse =
let
jdata = "{\"data\":\"" & bits & "\"}"
bits =
try:
RestJson.decode(jdata, SyncCommitteeBitsObject)
except SerializationError as exc:
raiseAssert "Serialization error from [" & $exc.name & "]: " & $exc.msg
ProduceSyncCommitteeContributionResponse(data: SyncCommitteeContribution(
aggregation_bits: bits.data
))
proc init(t: typedesc[GetBlockRootResponse],
optimistic: Opt[bool], root: Eth2Digest): GetBlockRootResponse =
let optopt =
if optimistic.isNone():
none[bool]()
else:
some(optimistic.get())
GetBlockRootResponse(data: RestRoot(root: root), execution_optimistic: optopt)
proc createRootsSeen(
root: tuple[root: string, slot: uint64]): Table[Eth2Digest, Slot] =
var res: Table[Eth2Digest, Slot]
res[Eth2Digest.init(root.root)] = Slot(root.slot)
res
suite "Validator Client test suite":
proc decodeBytes[T: TestDecodeTypes](
t: typedesc[T],
value: openArray[byte],
contentType: Opt[ContentTypeData] = Opt.none(ContentTypeData)
): RestResult[T] =
let mediaType =
if contentType.isNone():
ApplicationJsonMediaType
else:
if isWildCard(contentType.get().mediaType):
return err("Incorrect Content-Type")
contentType.get().mediaType
if mediaType == ApplicationJsonMediaType:
try:
ok RestJson.decode(value, T,
requireAllFields = true,
allowUnknownFields = true)
except SerializationError:
err("Serialization error")
else:
err("Content-Type not supported")
proc submitBeaconCommitteeSelectionsPlain(
body: seq[RestBeaconCommitteeSelection]
): RestPlainResponse {.
rest, endpoint: "/eth/v1/validator/beacon_committee_selections",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Validator/submitBeaconCommitteeSelections
proc submitSyncCommitteeSelectionsPlain(
body: seq[RestSyncCommitteeSelection]
): RestPlainResponse {.
rest, endpoint: "/eth/v1/validator/sync_committee_selections",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Validator/submitSyncCommitteeSelections
proc createServer(address: TransportAddress,
process: HttpProcessCallback, secure: bool): HttpServerRef =
let
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
res = HttpServerRef.new(address, process, socketFlags = socketFlags)
res.get()
test "normalizeUri() test vectors":
for hostname in HostNames:
for vector in GoodTestVectors:
let expect = vector[1] % (hostname)
check $normalizeUri(parseUri(vector[0] % (hostname))) == expect
asyncTest "/eth/v1/validator/beacon_committee_selections " &
"serialization/deserialization test":
var clientRequest: seq[byte]
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
if r.isOk():
let request = r.get()
case request.uri.path
of "/eth/v1/validator/beacon_committee_selections":
clientRequest = await request.getBody()
let headers = HttpTable.init([("Content-Type", "application/json")])
return await request.respond(Http200, ObolBeaconResponseTestVector,
headers)
else:
return await request.respond(Http404, "Page not found")
else:
return defaultResponse()
let server = createServer(initTAddress("127.0.0.1:0"), process, false)
server.start()
defer:
await server.stop()
await server.closeWait()
let
serverAddress = server.instance.localAddress
flags = {RestClientFlag.CommaSeparatedArray}
remoteUri = "http://" & $serverAddress
client =
block:
let res = RestClientRef.new(remoteUri, flags = flags)
check res.isOk()
res.get()
selections =
block:
let res = decodeBytes(
seq[RestBeaconCommitteeSelection],
ObolBeaconRequestTestVector.toOpenArrayByte(
0, len(ObolBeaconRequestTestVector) - 1))
check res.isOk()
res.get()
defer:
await client.closeWait()
let resp = await client.submitBeaconCommitteeSelectionsPlain(selections)
check:
resp.status == 200
resp.contentType == MediaType.init("application/json")
let request =
block:
let res = decodeBytes(
seq[RestBeaconCommitteeSelection],
clientRequest)
check res.isOk()
res.get()
let response = block:
let res = decodeBytes(SubmitBeaconCommitteeSelectionsResponse,
resp.data, resp.contentType)
check res.isOk()
res.get()
check:
len(request) == len(selections)
len(response.data) == len(ObolBeaconResponseTestVectorObject)
# Checking response
for index, item in response.data.pairs():
check:
item.validator_index ==
ObolBeaconResponseTestVectorObject[index].validator_index
item.slot ==
ObolBeaconResponseTestVectorObject[index].slot
item.selection_proof.toHex() ==
ObolBeaconResponseTestVectorObject[index].selection_proof
# Checking request
for index, item in selections.pairs():
check:
item.validator_index == request[index].validator_index
item.slot == request[index].slot
item.selection_proof.toHex() == request[index].selection_proof.toHex()
asyncTest "/eth/v1/validator/sync_committee_selections " &
"serialization/deserialization test":
var clientRequest: seq[byte]
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
if r.isOk():
let request = r.get()
case request.uri.path
of "/eth/v1/validator/sync_committee_selections":
clientRequest = await request.getBody()
let headers = HttpTable.init([("Content-Type", "application/json")])
return await request.respond(Http200, ObolSyncResponseTestVector,
headers)
else:
return await request.respond(Http404, "Page not found")
else:
return defaultResponse()
let server = createServer(initTAddress("127.0.0.1:0"), process, false)
server.start()
defer:
await server.stop()
await server.closeWait()
let
serverAddress = server.instance.localAddress
flags = {RestClientFlag.CommaSeparatedArray}
remoteUri = "http://" & $serverAddress
client =
block:
let res = RestClientRef.new(remoteUri, flags = flags)
check res.isOk()
res.get()
selections =
block:
let res = decodeBytes(
seq[RestSyncCommitteeSelection],
ObolSyncRequestTestVector.toOpenArrayByte(
0, len(ObolSyncRequestTestVector) - 1))
check res.isOk()
res.get()
defer:
await client.closeWait()
let resp = await client.submitSyncCommitteeSelectionsPlain(selections)
check:
resp.status == 200
resp.contentType == MediaType.init("application/json")
let request =
block:
let res = decodeBytes(
seq[RestSyncCommitteeSelection],
clientRequest)
check res.isOk()
res.get()
let response = block:
let res = decodeBytes(SubmitSyncCommitteeSelectionsResponse,
resp.data, resp.contentType)
check res.isOk()
res.get()
check:
len(request) == len(selections)
len(response.data) == len(ObolSyncResponseTestVectorObject)
# Checking response
for index, item in response.data.pairs():
check:
item.validator_index ==
ObolSyncResponseTestVectorObject[index].validator_index
item.slot ==
ObolSyncResponseTestVectorObject[index].slot
item.selection_proof.toHex() ==
ObolSyncResponseTestVectorObject[index].selection_proof
item.subcommittee_index == request[index].subcommittee_index
# Checking request
for index, item in selections.pairs():
check:
item.validator_index == request[index].validator_index
item.slot == request[index].slot
item.subcommittee_index == request[index].subcommittee_index
item.selection_proof.toHex() == request[index].selection_proof.toHex()
test "getAttestationDataScore() test vectors":
for vector in AttestationDataVectors:
let
adata = ProduceAttestationDataResponse.init(vector[0])
roots = createRootsSeen(vector[1])
score = shortScore(roots.getAttestationDataScore(adata))
check score == vector[2]
test "getAggregatedAttestationDataScore() test vectors":
for vector in AggregatedDataVectors:
let
adata = GetAggregatedAttestationResponse.init(vector[0])
score = shortScore(getAggregatedAttestationDataScore(adata))
check score == vector[1]
test "getAggregatedAttestationDataScore() default test":
let
adata = GetAggregatedAttestationResponse(
data: LowestScoreAggregatedAttestation)
score = shortScore(getAggregatedAttestationDataScore(adata))
check:
score == "0.0000"
isLowestScoreAggregatedAttestation(adata.data) == true
test "getProduceBlockResponseV3Score() default test":
let
bdata1 = ProduceBlockResponseV3()
bdata2 = ProduceBlockResponseV3(
consensusValue: Opt.some(UInt256.zero)
)
bdata3 = ProduceBlockResponseV3(
executionValue: Opt.some(UInt256.zero),
)
bdata4 = ProduceBlockResponseV3(
consensusValue: Opt.some(UInt256.zero),
executionValue: Opt.some(UInt256.zero)
)
check:
shortScore(getProduceBlockResponseV3Score(bdata1)) == "0"
shortScore(getProduceBlockResponseV3Score(bdata2)) == "0"
shortScore(getProduceBlockResponseV3Score(bdata3)) == "0"
shortScore(getProduceBlockResponseV3Score(bdata4)) == "0"
test "getSyncCommitteeContributionDataScore() test vectors":
for vector in ContributionDataVectors:
let
adata = ProduceSyncCommitteeContributionResponse.init(vector[0])
score = shortScore(getSyncCommitteeContributionDataScore(adata))
check score == vector[1]
test "getSyncCommitteeMessageDataScore() test vectors":
for vector in SyncMessageDataVectors:
let
roots = createRootsSeen((vector[1], vector[0]))
rdata = GetBlockRootResponse.init(vector[4], Eth2Digest.init(vector[2]))
currentSlot = Slot(vector[3])
score = shortScore(getSyncCommitteeMessageDataScore(roots, currentSlot,
rdata))
check:
score == vector[5]
test "getProduceBlockResponseV3Score() test vectors":
for vector in UInt256ScoreVectors:
let
value1 = strictParse(vector[0], UInt256, 16).get()
value2 = strictParse(vector[1], UInt256, 16).get()
bdata = ProduceBlockResponseV3(
executionValue: Opt.some(value1),
consensusValue: Opt.some(value2)
)
check shortScore(getProduceBlockResponseV3Score(bdata)) == vector[2]
test "getUniqueVotes() test vectors":
for vector in AttestationBitsVectors:
let
a1 = phase0.Attestation.init(vector[0][0][0], vector[0][0][1],
vector[0][0][2])
a2 = phase0.Attestation.init(vector[0][1][0], vector[0][1][1],
vector[0][1][2])
check getUniqueVotes([a1, a2]) == vector[1]
asyncTest "firstSuccessParallel() API timeout test":
let
uri = parseUri("http://127.0.0.1/")
beaconNodes = @[BeaconNodeServerRef.init(uri, 0).tryGet()]
vconf = ValidatorClientConf.load(
cmdLine = mapIt(["--beacon-node=http://127.0.0.1"], it))
epoch = Epoch(1)
strategy = ApiStrategyKind.Priority
var gotCancellation = false
var vc = ValidatorClientRef(config: vconf, beaconNodes: beaconNodes)
vc.fallbackService = await FallbackServiceRef.init(vc)
proc getTestDuties(client: RestClientRef,
epoch: Epoch): Future[RestPlainResponse] {.async.} =
try:
await sleepAsync(1.seconds)
except CancelledError as exc:
gotCancellation = true
raise exc
const
RequestName = "getTestDuties"
let response = vc.firstSuccessParallel(
RestPlainResponse,
uint64,
100.milliseconds,
AllBeaconNodeStatuses,
{BeaconNodeRole.Duties},
getTestDuties(it, epoch)):
check:
apiResponse.isErr()
apiResponse.error ==
"Timeout exceeded while awaiting for the response"
ApiResponse[uint64].err(apiResponse.error)
check:
response.isErr()
gotCancellation == true
asyncTest "bestSuccess() API timeout test":
let
uri = parseUri("http://127.0.0.1/")
beaconNodes = @[BeaconNodeServerRef.init(uri, 0).tryGet()]
vconf = ValidatorClientConf.load(
cmdLine = mapIt(["--beacon-node=http://127.0.0.1"], it))
epoch = Epoch(1)
strategy = ApiStrategyKind.Priority
var gotCancellation = false
var vc = ValidatorClientRef(config: vconf, beaconNodes: beaconNodes)
vc.fallbackService = await FallbackServiceRef.init(vc)
proc getTestDuties(client: RestClientRef,
epoch: Epoch): Future[RestPlainResponse] {.async.} =
try:
await sleepAsync(1.seconds)
except CancelledError as exc:
gotCancellation = true
raise exc
proc getTestScore(data: uint64): float64 = Inf
const
RequestName = "getTestDuties"
let response = vc.bestSuccess(
RestPlainResponse,
uint64,
float64,
100.milliseconds,
AllBeaconNodeStatuses,
{BeaconNodeRole.Duties},
getTestDuties(it, epoch),
getTestScore(itresponse)):
check:
apiResponse.isErr()
apiResponse.error ==
"Timeout exceeded while awaiting for the response"
ApiResponse[uint64].err(apiResponse.error)
check:
response.isErr()
gotCancellation == true
test "getLiveness() response deserialization test":
proc generateLivenessResponse(T: typedesc[string],
start, count, modv: int): string =
var res: seq[string]
for index in start ..< (start + count):
let
validator = Base10.toString(uint64(index))
visibility = if index mod modv == 0: "true" else: "false"
res.add("{\"index\":\"" & validator & "\",\"is_live\":" &
visibility & "}")
"{\"data\":[" & res.join(",") & "]}"
proc generateLivenessResponse(
T: typedesc[RestLivenessItem],
start, count, modv: int
): seq[RestLivenessItem] =
var res: seq[RestLivenessItem]
for index in start ..< (start + count):
let visibility = if index mod modv == 0: true else: false
res.add(RestLivenessItem(index: ValidatorIndex(uint64(index)),
is_live: visibility))
res
const Tests = [(0, 2_000_000, 3)]
for test in Tests:
let
datastr = string.generateLivenessResponse(
test[0], test[1], test[2])
data = stringToBytes(datastr)
contentType = getContentType("application/json").get()
res = decodeBytes(GetValidatorsLivenessResponse,
data, Opt.some(contentType))
expect = RestLivenessItem.generateLivenessResponse(
test[0], test[1], test[2])
check:
res.isOk()
res.get().data == expect
let vector = stringToBytes(
"{\"data\":[{\"index\":\"100000\",\"epoch\":\"202919\",\"is_live\":true}]}")
let contentType = getContentType("application/json").tryGet()
let res = decodeBytes(
GetValidatorsLivenessResponse, vector, Opt.some(contentType))
check:
res.isOk()
len(res.get().data) == 1
res.get().data[0].index == 100000
res.get().data[0].is_live == true