Opt-in Slashing protection + interchange (#1643)

* Slashing protection + interchange initial commit

* Restrict the when UseSlashingProtection dance in other modules

* Integrate slashing tests in other all_tests

* Add attestation slashing protection support

* Add a message that mention if built with/without slashing protection

* no op the initialization proc

* test slashing protection in Jenkins (temp)

* where to configure NIMFLAGS in Jenkins ...

* Jenkins -> ensure Built with slashing protection

* Add slashing protection complete import

* use Opt.get(otherwise)

* Don't use negation in proc name

* Turn slashing protection on by default
This commit is contained in:
Mamy Ratsimbazafy 2020-09-16 13:30:03 +02:00
parent 8b492ed1ce
commit 702b346965
12 changed files with 1901 additions and 50 deletions

14
Jenkinsfile vendored
View File

@ -35,7 +35,9 @@ def runStages() {
sh """#!/bin/bash
set -e
make -j${env.NPROC} V=1
make -j${env.NPROC} V=1 LOG_LEVEL=TRACE NIMFLAGS='-d:testnet_servers_image' beacon_node
make -j${env.NPROC} V=1 LOG_LEVEL=TRACE NIMFLAGS='-d:UseSlashingProtection=true -d:testnet_servers_image' beacon_node
# Miracl fallback
# make -j${env.NPROC} V=1 LOG_LEVEL=TRACE NIMFLAGS='-d:BLS_FORCE_BACKEND=miracl -d:UseSlashingProtection=true -d:testnet_servers_image' beacon_node
"""
}
},
@ -47,18 +49,11 @@ def runStages() {
// EXECUTOR_NUMBER will be 0 or 1, since we have 2 executors per Jenkins node
sh """#!/bin/bash
set -e
export NIMFLAGS='-d:UseSlashingProtection=true'
./scripts/launch_local_testnet.sh --testnet 0 --nodes 4 --stop-at-epoch 5 --log-level DEBUG --disable-htop --enable-logtrace --data-dir local_testnet0_data --base-port \$(( 9000 + EXECUTOR_NUMBER * 100 )) --base-rpc-port \$(( 7000 + EXECUTOR_NUMBER * 100 )) --base-metrics-port \$(( 8008 + EXECUTOR_NUMBER * 100 )) -- --verify-finalization --discv5:no
./scripts/launch_local_testnet.sh --testnet 1 --nodes 4 --stop-at-epoch 5 --log-level DEBUG --disable-htop --enable-logtrace --data-dir local_testnet1_data --base-port \$(( 9000 + EXECUTOR_NUMBER * 100 )) --base-rpc-port \$(( 7000 + EXECUTOR_NUMBER * 100 )) --base-metrics-port \$(( 8008 + EXECUTOR_NUMBER * 100 )) -- --verify-finalization --discv5:no
"""
}
// stage("testnet finalization - Miracl/Milagro fallback") {
// // EXECUTOR_NUMBER will be 0 or 1, since we have 2 executors per Jenkins node
// sh """#!/bin/bash
// set -e
// NIMFLAGS="-d:BLS_FORCE_BACKEND=miracl" ./scripts/launch_local_testnet.sh --testnet 0 --nodes 4 --stop-at-epoch 5 --log-level INFO --disable-htop --data-dir local_testnet0_data --base-port \$(( 9000 + EXECUTOR_NUMBER * 100 )) --base-rpc-port \$(( 7000 + EXECUTOR_NUMBER * 100 )) --base-metrics-port \$(( 8008 + EXECUTOR_NUMBER * 100 )) -- --verify-finalization
// NIMFLAGS="-d:BLS_FORCE_BACKEND=miracl" ./scripts/launch_local_testnet.sh --testnet 1 --nodes 4 --stop-at-epoch 5 --log-level INFO --disable-htop --data-dir local_testnet1_data --base-port \$(( 9000 + EXECUTOR_NUMBER * 100 )) --base-rpc-port \$(( 7000 + EXECUTOR_NUMBER * 100 )) --base-metrics-port \$(( 8008 + EXECUTOR_NUMBER * 100 )) -- --verify-finalization
// """
// }
}
)
}
@ -100,4 +95,3 @@ parallel(
}
},
)

View File

@ -61,10 +61,17 @@ task test, "Run all tests":
# Just the part of minimal config which explicitly differs from mainnet
buildAndRunBinary "test_fixture_const_sanity_check", "tests/official/", """-d:const_preset=minimal -d:chronicles_sinks="json[file]""""
# Generic SSZ test, doesn't use consensus objects minimal/mainnet presets
buildAndRunBinary "test_fixture_ssz_generic_types", "tests/official/", """-d:chronicles_log_level=TRACE -d:chronicles_sinks="json[file]""""
# Consensus object SSZ tests
buildAndRunBinary "test_fixture_ssz_consensus_objects", "tests/official/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
# EF tests
buildAndRunBinary "all_fixtures_require_ssz", "tests/official/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
# Mainnet config
buildAndRunBinary "proto_array", "beacon_chain/fork_choice/", """-d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
buildAndRunBinary "fork_choice", "beacon_chain/fork_choice/", """-d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
buildAndRunBinary "all_tests", "tests/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
buildAndRunBinary "all_tests", "tests/", """-d:UseSlashingProtection=true -d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
# Check Miracl/Milagro fallback on select tests
buildAndRunBinary "test_interop", "tests/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:BLS_FORCE_BACKEND=miracl -d:chronicles_sinks="json[file]""""
@ -74,14 +81,6 @@ task test, "Run all tests":
buildAndRunBinary "test_attestation_pool", "tests/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:BLS_FORCE_BACKEND=miracl -d:chronicles_sinks="json[file]""""
buildAndRunBinary "test_block_pool", "tests/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:BLS_FORCE_BACKEND=miracl -d:chronicles_sinks="json[file]""""
# Generic SSZ test, doesn't use consensus objects minimal/mainnet presets
buildAndRunBinary "test_fixture_ssz_generic_types", "tests/official/", """-d:chronicles_log_level=TRACE -d:chronicles_sinks="json[file]""""
# Consensus object SSZ tests
buildAndRunBinary "test_fixture_ssz_consensus_objects", "tests/official/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
buildAndRunBinary "all_fixtures_require_ssz", "tests/official/", """-d:chronicles_log_level=TRACE -d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
# State and block sims; getting to 4th epoch triggers consensus checks
buildAndRunBinary "state_sim", "research/", "-d:const_preset=mainnet -d:chronicles_log_level=INFO", "--validators=3000 --slots=128"
# buildAndRunBinary "state_sim", "research/", "-d:const_preset=mainnet -d:BLS_FORCE_BACKEND=miracl -d:chronicles_log_level=INFO", "--validators=3000 --slots=128"

View File

@ -32,6 +32,7 @@ import
mainchain_monitor, version, ssz/[merkleization], merkle_minimal,
sync_protocol, request_manager, keystore_management, interop, statusbar,
sync_manager, validator_duties, validator_api,
validator_slashing_protection,
./eth2_processor
const
@ -258,7 +259,6 @@ proc init*(T: type BeaconNode,
netKeys: netKeys,
db: db,
config: conf,
attachedValidators: ValidatorPool.init(),
chainDag: chainDag,
quarantine: quarantine,
attestationPool: attestationPool,
@ -271,6 +271,16 @@ proc init*(T: type BeaconNode,
topicAggregateAndProofs: topicAggregateAndProofs,
)
res.attachedValidators = ValidatorPool.init(
SlashingProtectionDB.init(
chainDag.headState.data.data.genesis_validators_root,
when UseSlashingProtection:
kvStore SqStoreRef.init(conf.validatorsDir(), "slashing_protection").tryGet()
else:
KvStoreRef()
)
)
proc getWallTime(): BeaconTime = res.beaconClock.now()
res.processor = Eth2Processor.new(
@ -1312,4 +1322,3 @@ programMain:
of WalletsCmd.restore:
restoreWalletInteractively(rng[], config)

View File

@ -5,7 +5,8 @@ import
stew/endians2,
spec/[datatypes, crypto],
block_pools/block_pools_types,
fork_choice/fork_choice_types
fork_choice/fork_choice_types,
validator_slashing_protection
export block_pools_types
@ -105,5 +106,6 @@ type
ValidatorPool* = object
validators*: Table[ValidatorPubKey, AttachedValidator]
slashingProtection*: SlashingProtectionDB
func shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)

View File

@ -16,14 +16,16 @@ import
json_serialization/std/[options, sets, net],
# Local modules
spec/[datatypes, digest, crypto, helpers, network],
spec/[datatypes, digest, crypto, helpers, network, signatures],
conf, time, version,
eth2_network, eth2_discovery, validator_pool, beacon_node_types,
nimbus_binary_common,
version, ssz/merkleization,
sync_manager, keystore_management,
spec/eth2_apis/callsigs_types,
eth2_json_rpc_serialization
eth2_json_rpc_serialization,
validator_slashing_protection,
eth/db/[kvstore, kvstore_sqlite3]
logScope: topics = "vc"
@ -132,22 +134,35 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
# check if we have a validator which needs to propose on this slot
if vc.proposalsForCurrentEpoch.contains slot:
let public_key = vc.proposalsForCurrentEpoch[slot]
let notSlashable = vc.attachedValidators
.slashingProtection
.checkSlashableBlockProposal(public_key, slot)
if notSlashable.isOk:
let validator = vc.attachedValidators.validators[public_key]
info "Proposing block", slot = slot, public_key = public_key
let randao_reveal = await validator.genRandaoReveal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
var newBlock = SignedBeaconBlock(
message: await vc.client.get_v1_validator_block(slot, vc.graffitiBytes, randao_reveal)
)
newBlock.root = hash_tree_root(newBlock.message)
# TODO: signing_root is recomputed in signBlockProposal just after
let signing_root = compute_block_root(vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
vc.attachedValidators
.slashingProtection
.registerBlock(public_key, slot, signing_root)
newBlock.signature = await validator.signBlockProposal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
discard await vc.client.post_v1_validator_block(newBlock)
else:
warn "Slashing protection activated for block proposal",
validator = public_key,
slot = slot,
existingProposal = notSlashable.error
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#attesting
# A validator should create and broadcast the attestation to the associated
@ -167,12 +182,31 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
let validator = vc.attachedValidators.validators[a.public_key]
let ad = await vc.client.get_v1_validator_attestation(slot, a.committee_index)
let notSlashable = vc.attachedValidators
.slashingProtection
.checkSlashableAttestation(
a.public_key,
ad.source.epoch,
ad.target.epoch)
if notSlashable.isOk():
# TODO signing_root is recomputed in produceAndSignAttestation/signAttestation just after
let signing_root = compute_attestation_root(
vc.fork, vc.beaconGenesis.genesis_validators_root, ad)
vc.attachedValidators
.slashingProtection
.registerAttestation(
a.public_key, ad.source.epoch, ad.target.epoch, signing_root)
# TODO I don't like these (u)int64-to-int conversions...
let attestation = await validator.produceAndSignAttestation(
ad, a.committee_length.int, a.validator_committee_index.int,
vc.fork, vc.beaconGenesis.genesis_validators_root)
discard await vc.client.post_v1_beacon_pool_attestations(attestation)
else:
warn "Slashing protection activated for attestation",
validator = a.public_key,
badVoteDetails = $notSlashable.error
except CatchableError as err:
warn "Caught an unexpected error", err = err.msg, slot = shortLog(slot)
@ -230,6 +264,13 @@ programMain:
vc.beaconGenesis = waitFor vc.client.get_v1_beacon_genesis()
vc.beaconClock = BeaconClock.init(vc.beaconGenesis.genesis_time)
when UseSlashingProtection:
vc.attachedValidators.slashingProtection =
SlashingProtectionDB.init(
vc.beaconGenesis.genesis_validators_root,
kvStore SqStoreRef.init(config.validatorsDir(), "slashing_protection").tryGet()
)
let
curSlot = vc.beaconClock.now().slotOrZero()
nextSlot = curSlot + 1 # No earlier than GENESIS_SLOT + 1

View File

@ -7,7 +7,7 @@
import
# Standard library
std/[os, tables, strutils, sequtils, osproc, streams],
std/[os, tables, sequtils, osproc, streams],
# Nimble packages
stew/[objects], stew/shims/macros,
@ -18,13 +18,14 @@ import
eth/[keys, async_utils], eth/p2p/discoveryv5/[protocol, enr],
# Local modules
spec/[datatypes, digest, crypto, helpers, validator, network],
spec/[datatypes, digest, crypto, helpers, validator, network, signatures],
spec/state_transition,
conf, time, validator_pool,
attestation_pool, block_pools/[spec_cache, chain_dag, clearance],
eth2_network, keystore_management, beacon_node_common, beacon_node_types,
nimbus_binary_common, mainchain_monitor, version, ssz/merkleization, interop,
attestation_aggregation, sync_manager, sszdump
attestation_aggregation, sync_manager, sszdump,
validator_slashing_protection
# Metrics for tracking attestation and beacon block loss
declareCounter beacon_attestations_sent,
@ -120,6 +121,8 @@ proc isSynced*(node: BeaconNode, head: BlockRef): bool =
beaconTime = node.beaconClock.now()
wallSlot = beaconTime.toSlot()
# TODO: MaxEmptySlotCount should likely involve the weak subjectivity period.
# TODO if everyone follows this logic, the network will not recover from a
# halt: nobody will be producing blocks because everone expects someone
# else to do it
@ -293,6 +296,16 @@ proc proposeBlock(node: BeaconNode,
slot = shortLog(slot)
return head
let notSlashable = node.attachedValidators
.slashingProtection
.checkSlashableBlockProposal(validator.pubkey, slot)
if notSlashable.isErr:
warn "Slashing protection activated",
validator = validator.pubkey,
slot = slot,
existingProposal = notSlashable.error
return head
let valInfo = ValidatorInfoForMakeBeaconBlock(kind: viValidator, validator: validator)
let beaconBlockTuple = await makeBeaconBlockForHeadAndSlot(
node, valInfo, validator_index, node.graffitiBytes, head, slot)
@ -304,6 +317,14 @@ proc proposeBlock(node: BeaconNode,
)
newBlock.root = hash_tree_root(newBlock.message)
# TODO: recomputed in block proposal
let signing_root = compute_block_root(
beaconBlockTuple.fork, beaconBlockTuple.genesis_validators_root, slot, newBlock.root)
node.attachedValidators
.slashingProtection
.registerBlock(validator.pubkey, slot, signing_root)
newBlock.signature = await validator.signBlockProposal(
beaconBlockTuple.fork, beaconBlockTuple.genesis_validators_root, slot, newBlock.root)
@ -368,9 +389,21 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
attestations.add((ad, committee.len, index_in_committee, validator))
for a in attestations:
let notSlashable = node.attachedValidators
.slashingProtection
.checkSlashableAttestation(
a.validator.pubkey,
a.data.source.epoch,
a.data.target.epoch)
if notSlashable.isOk():
traceAsyncErrors createAndSendAttestation(
node, fork, genesis_validators_root, a.validator, a.data,
a.committeeLen, a.indexInCommittee, num_active_validators)
else:
warn "Slashing protection activated for attestation",
validator = a.validator.pubkey,
badVoteDetails = $notSlashable.error
proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
Future[BlockRef] {.async.} =

View File

@ -3,10 +3,18 @@ import
chronos, chronicles,
spec/[datatypes, crypto, digest, signatures, helpers],
beacon_node_types,
json_serialization/std/[sets, net]
json_serialization/std/[sets, net],
validator_slashing_protection,
eth/db/[kvstore, kvstore_sqlite3]
func init*(T: type ValidatorPool): T =
func init*(T: type ValidatorPool,
slashingProtectionDB: SlashingProtectionDB): T =
## Initialize the validator pool and the slashing protection service
## `genesis_validator_root` is used as an unique ID for the
## blockchain
## `backend` is the KeyValue Store backend
result.validators = initTable[ValidatorPubKey, AttachedValidator]()
result.slashingProtection = slashingProtectionDB
template count*(pool: ValidatorPool): int =
pool.validators.len

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,9 @@ import # Unit test
./test_sync_manager,
./test_honest_validator,
./test_interop,
./fork_choice/tests_fork_choice
./fork_choice/tests_fork_choice,
./slashing_protection/test_slashing_interchange,
./slashing_protection/test_slashing_protection_db
import # Refactor state transition unit tests
# In mainnet these take 2 minutes and are empty TODOs

1
tests/slashing_protection/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.json

View File

@ -0,0 +1,97 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[unittest, os],
# Status lib
eth/db/kvstore,
stew/results,
nimcrypto/utils,
# Internal
../../beacon_chain/validator_slashing_protection,
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
# Test utilies
../testutil
static: doAssert UseSlashingProtection, "The test was compiled without slashing protection, pass -d:UseSlashingProtection=true"
template wrappedTimedTest(name: string, body: untyped) =
# `check` macro takes a copy of whatever it's checking, on the stack!
block: # Symbol namespacing
proc wrappedTest() =
timedTest name:
body
wrappedTest()
func fakeRoot(index: SomeInteger): Eth2Digest =
## Create fake roots
## Those are just the value serialized in big-endian
## We prevent zero hash special case via a power of 2 prefix
result.data[0 ..< 8] = (1'u64 shl 32 + index.uint64).toBytesBE()
func fakeValidator(index: SomeInteger): ValidatorPubKey =
## Create fake validator public key
result = ValidatorPubKey(kind: OpaqueBlob)
result.blob[0 ..< 8] = (1'u64 shl 48 + index.uint64).toBytesBE()
func hexToDigest(hex: string): Eth2Digest =
result = Eth2Digest.fromHex(hex)
suiteReport "Slashing Protection DB - Interchange" & preset():
# https://hackmd.io/@sproul/Bk0Y0qdGD#Format-1-Complete
wrappedTimedTest "Smoke test - Complete format" & preset():
let genesis_validators_root = hexToDigest"0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673"
block: # export
let db = SlashingProtectionDB.init(genesis_validators_root, kvStore MemStoreRef.init())
let pubkey = ValidatorPubKey
.fromHex"0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed"
.get()
db.registerBlock(
pubkey,
Slot 81952,
hexToDigest"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
)
# db.registerBlock(
# pubkey,
# Slot 81951,
# fakeRoot(65535)
# )
db.registerAttestation(
pubkey,
source = Epoch 2290,
target = Epoch 3007,
hexToDigest"0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
)
db.registerAttestation(
pubkey,
source = Epoch 2290,
target = Epoch 3008,
fakeRoot(65535)
)
db.toSPDIF(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
block: # import - zero root db
let db2 = SlashingProtectionDB.init(Eth2Digest(), kvStore MemStoreRef.init())
doAssert db2.fromSPDIF(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
db2.toSPDIF(currentSourcePath.parentDir/"test_complete_export_slashing_protection_roundtrip1.json")
block: # import - same root db
let db3 = SlashingProtectionDB.init(genesis_validators_root, kvStore MemStoreRef.init())
doAssert db3.fromSPDIF(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")
db3.toSPDIF(currentSourcePath.parentDir/"test_complete_export_slashing_protection_roundtrip2.json")
block: # import - invalid root db
let invalid_genvalroot = hexToDigest"0x1234"
let db3 = SlashingProtectionDB.init(invalid_genvalroot, kvStore MemStoreRef.init())
doAssert not db3.fromSPDIF(currentSourcePath.parentDir/"test_complete_export_slashing_protection.json")

View File

@ -0,0 +1,566 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/unittest,
# Status lib
eth/db/kvstore,
stew/results,
# Internal
../../beacon_chain/validator_slashing_protection,
../../beacon_chain/spec/[datatypes, digest, crypto, presets],
# Test utilies
../testutil
static: doAssert UseSlashingProtection, "The test was compiled without slashing protection, pass -d:UseSlashingProtection=true"
template wrappedTimedTest(name: string, body: untyped) =
# `check` macro takes a copy of whatever it's checking, on the stack!
block: # Symbol namespacing
proc wrappedTest() =
timedTest name:
body
wrappedTest()
func fakeRoot(index: SomeInteger): Eth2Digest =
## Create fake roots
## Those are just the value serialized in big-endian
## We prevent zero hash special case via a power of 2 prefix
result.data[0 ..< 8] = (1'u64 shl 32 + index.uint64).toBytesBE()
func fakeValidator(index: SomeInteger): ValidatorPubKey =
## Create fake validator public key
result = ValidatorPubKey(kind: OpaqueBlob)
result.blob[0 ..< 8] = (1'u64 shl 48 + index.uint64).toBytesBE()
suiteReport "Slashing Protection DB" & preset():
wrappedTimedTest "Empty database" & preset():
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
check:
db.checkSlashableBlockProposal(
fakeValidator(1234),
slot = Slot 1
).isOk()
db.checkSlashableAttestation(
fakeValidator(1234),
source = Epoch 1,
target = Epoch 2
).isOk()
db.checkSlashableAttestation(
fakeValidator(1234),
source = Epoch 2,
target = Epoch 1
).error.kind == TargetPrecedesSource
db.close()
wrappedTimedTest "SP for block proposal - linear append":
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
db.registerBlock(
fakeValidator(100),
Slot 10,
fakeRoot(100)
)
db.registerBlock(
fakeValidator(111),
Slot 15,
fakeRoot(111)
)
check:
# Slot occupied by same validator
db.checkSlashableBlockProposal(
fakeValidator(100),
slot = Slot 10
).isErr()
# Slot occupied by another validator
db.checkSlashableBlockProposal(
fakeValidator(111),
slot = Slot 10
).isOk()
# Slot occupied by another validator
db.checkSlashableBlockProposal(
fakeValidator(100),
slot = Slot 15
).isOk()
# Slot occupied by same validator
db.checkSlashableBlockProposal(
fakeValidator(111),
slot = Slot 15
).isErr()
# Slot inoccupied
db.checkSlashableBlockProposal(
fakeValidator(255),
slot = Slot 20
).isOk()
db.registerBlock(
fakeValidator(255),
slot = Slot 20,
fakeRoot(4321)
)
check:
# Slot now occupied
db.checkSlashableBlockProposal(
fakeValidator(255),
slot = Slot 20
).isErr()
wrappedTimedTest "SP for block proposal - backtracking append":
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
# last finalized block
db.registerBlock(
fakeValidator(0),
Slot 0,
fakeRoot(0)
)
db.registerBlock(
fakeValidator(100),
Slot 10,
fakeRoot(10)
)
db.registerBlock(
fakeValidator(100),
Slot 20,
fakeRoot(20)
)
for i in 0 ..< 30:
if i notin {10, 20}:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isOk()
else:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isErr()
db.registerBlock(
fakeValidator(100),
Slot 15,
fakeRoot(15)
)
for i in 0 ..< 30:
if i notin {10, 15, 20}:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isOk()
else:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isErr()
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
db.registerBlock(
fakeValidator(100),
Slot 12,
fakeRoot(12)
)
db.registerBlock(
fakeValidator(100),
Slot 17,
fakeRoot(17)
)
for i in 0 ..< 30:
if i notin {10, 12, 15, 17, 20}:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isOk()
else:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isErr()
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
db.registerBlock(
fakeValidator(100),
Slot 9,
fakeRoot(9)
)
db.registerBlock(
fakeValidator(100),
Slot 1,
fakeRoot(1)
)
db.registerBlock(
fakeValidator(100),
Slot 3,
fakeRoot(3)
)
for i in 0 ..< 30:
if i notin {1, 3, 9, 10, 12, 15, 17, 20}:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isOk()
else:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isErr()
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
db.registerBlock(
fakeValidator(100),
Slot 29,
fakeRoot(29)
)
db.registerBlock(
fakeValidator(100),
Slot 2,
fakeRoot(2)
)
for i in 0 ..< 30:
if i notin {1, 2, 3, 9, 10, 12, 15, 17, 20, 29}:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isOk()
else:
check:
db.checkSlashableBlockProposal(
fakeValidator(100),
Slot i
).isErr()
db.checkSlashableBlockProposal(
fakeValidator(0xDEADBEEF),
Slot i
).isOk()
wrappedTimedTest "SP for same epoch attestation target - linear append":
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 10,
fakeRoot(100)
)
db.registerAttestation(
fakeValidator(111),
Epoch 0, Epoch 15,
fakeRoot(111)
)
check:
# Epoch occupied by same validator
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 10,
).error.kind == DoubleVote
# Epoch occupied by another validator
db.checkSlashableAttestation(
fakeValidator(111),
Epoch 0, Epoch 10
).isOk()
# Epoch occupied by another validator
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 15
).isOk()
# Epoch occupied by same validator
db.checkSlashableAttestation(
fakeValidator(111),
Epoch 0, Epoch 15
).error.kind == DoubleVote
# Epoch inoccupied
db.checkSlashableAttestation(
fakeValidator(255),
Epoch 0, Epoch 20
).isOk()
db.registerAttestation(
fakeValidator(255),
Epoch 0, Epoch 20,
fakeRoot(4321)
)
check:
# Epoch now occupied
db.checkSlashableAttestation(
fakeValidator(255),
Epoch 0, Epoch 20
).error.kind == DoubleVote
wrappedTimedTest "SP for same epoch attestation target - backtracking append":
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
# last finalized block
db.registerAttestation(
fakeValidator(0),
Epoch 0, Epoch 0,
fakeRoot(0)
)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 10,
fakeRoot(10)
)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 20,
fakeRoot(20)
)
for i in 0 ..< 30:
if i notin {10, 20}:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).isOk()
else:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).error.kind == DoubleVote
db.checkSlashableAttestation(
fakeValidator(0xDEADBEEF),
Epoch 0, Epoch i
).isOk()
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 15,
fakeRoot(15)
)
for i in 0 ..< 30:
if i notin {10, 15, 20}:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).isOk()
else:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).error.kind == DoubleVote
db.checkSlashableAttestation(
fakeValidator(0xDEADBEEF),
Epoch 0, Epoch i
).isOk()
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 12,
fakeRoot(12)
)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 17,
fakeRoot(17)
)
for i in 0 ..< 30:
if i notin {10, 12, 15, 17, 20}:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).isOk()
else:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).error.kind == DoubleVote
db.checkSlashableAttestation(
fakeValidator(0xDEADBEEF),
Epoch 0, Epoch i
).isOk()
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 9,
fakeRoot(9)
)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 1,
fakeRoot(1)
)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 3,
fakeRoot(3)
)
for i in 0 ..< 30:
if i notin {1, 3, 9, 10, 12, 15, 17, 20}:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).isOk()
else:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).error.kind == DoubleVote
db.checkSlashableAttestation(
fakeValidator(0xDEADBEEF),
Epoch 0, Epoch i
).isOk()
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 29,
fakeRoot(29)
)
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 2,
fakeRoot(2)
)
for i in 0 ..< 30:
if i notin {1, 2, 3, 9, 10, 12, 15, 17, 20, 29}:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).isOk()
else:
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch i
).error.kind == DoubleVote
db.checkSlashableAttestation(
fakeValidator(0xDEADBEEF),
Epoch 0, Epoch i
).isOk()
wrappedTimedTest "SP for surrounded attestations":
block:
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 19
).error.kind == SurroundedVote
db.checkSlashableAttestation(
fakeValidator(200),
Epoch 11, Epoch 19
).isOk
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 21
).isOk
# TODO: is that possible?
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 19
).isOk
block:
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 1,
fakeRoot(0)
)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 19
).error.kind == SurroundedVote
db.checkSlashableAttestation(
fakeValidator(200),
Epoch 11, Epoch 19
).isOk
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 11, Epoch 21
).isOk
# TODO: is that possible?
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 19
).isOk
wrappedTimedTest "SP for surrounding attestations":
block:
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 21
).error.kind == SurroundingVote
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 21
).error.kind == SurroundingVote
block:
let db = SlashingProtectionDB.init(default(Eth2Digest), kvStore MemStoreRef.init())
db.registerAttestation(
fakeValidator(100),
Epoch 0, Epoch 1,
fakeRoot(20)
)
db.registerAttestation(
fakeValidator(100),
Epoch 10, Epoch 20,
fakeRoot(20)
)
check:
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 9, Epoch 21
).error.kind == SurroundingVote
db.checkSlashableAttestation(
fakeValidator(100),
Epoch 0, Epoch 21
).error.kind == SurroundingVote