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:
parent
6e463257f4
commit
52548f079b
|
@ -35,7 +35,9 @@ def runStages() {
|
||||||
sh """#!/bin/bash
|
sh """#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
make -j${env.NPROC} V=1
|
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
|
// EXECUTOR_NUMBER will be 0 or 1, since we have 2 executors per Jenkins node
|
||||||
sh """#!/bin/bash
|
sh """#!/bin/bash
|
||||||
set -e
|
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 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
|
./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(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,17 @@ task test, "Run all tests":
|
||||||
# Just the part of minimal config which explicitly differs from mainnet
|
# 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]""""
|
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
|
# Mainnet config
|
||||||
buildAndRunBinary "proto_array", "beacon_chain/fork_choice/", """-d:const_preset=mainnet -d:chronicles_sinks="json[file]""""
|
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 "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
|
# 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]""""
|
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_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]""""
|
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
|
# 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: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"
|
# buildAndRunBinary "state_sim", "research/", "-d:const_preset=mainnet -d:BLS_FORCE_BACKEND=miracl -d:chronicles_log_level=INFO", "--validators=3000 --slots=128"
|
||||||
|
|
|
@ -32,6 +32,7 @@ import
|
||||||
mainchain_monitor, version, ssz/[merkleization], merkle_minimal,
|
mainchain_monitor, version, ssz/[merkleization], merkle_minimal,
|
||||||
sync_protocol, request_manager, keystore_management, interop, statusbar,
|
sync_protocol, request_manager, keystore_management, interop, statusbar,
|
||||||
sync_manager, validator_duties, validator_api,
|
sync_manager, validator_duties, validator_api,
|
||||||
|
validator_slashing_protection,
|
||||||
./eth2_processor
|
./eth2_processor
|
||||||
|
|
||||||
const
|
const
|
||||||
|
@ -258,7 +259,6 @@ proc init*(T: type BeaconNode,
|
||||||
netKeys: netKeys,
|
netKeys: netKeys,
|
||||||
db: db,
|
db: db,
|
||||||
config: conf,
|
config: conf,
|
||||||
attachedValidators: ValidatorPool.init(),
|
|
||||||
chainDag: chainDag,
|
chainDag: chainDag,
|
||||||
quarantine: quarantine,
|
quarantine: quarantine,
|
||||||
attestationPool: attestationPool,
|
attestationPool: attestationPool,
|
||||||
|
@ -271,6 +271,16 @@ proc init*(T: type BeaconNode,
|
||||||
topicAggregateAndProofs: topicAggregateAndProofs,
|
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()
|
proc getWallTime(): BeaconTime = res.beaconClock.now()
|
||||||
|
|
||||||
res.processor = Eth2Processor.new(
|
res.processor = Eth2Processor.new(
|
||||||
|
@ -1312,4 +1322,3 @@ programMain:
|
||||||
|
|
||||||
of WalletsCmd.restore:
|
of WalletsCmd.restore:
|
||||||
restoreWalletInteractively(rng[], config)
|
restoreWalletInteractively(rng[], config)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ import
|
||||||
stew/endians2,
|
stew/endians2,
|
||||||
spec/[datatypes, crypto],
|
spec/[datatypes, crypto],
|
||||||
block_pools/block_pools_types,
|
block_pools/block_pools_types,
|
||||||
fork_choice/fork_choice_types
|
fork_choice/fork_choice_types,
|
||||||
|
validator_slashing_protection
|
||||||
|
|
||||||
export block_pools_types
|
export block_pools_types
|
||||||
|
|
||||||
|
@ -105,5 +106,6 @@ type
|
||||||
|
|
||||||
ValidatorPool* = object
|
ValidatorPool* = object
|
||||||
validators*: Table[ValidatorPubKey, AttachedValidator]
|
validators*: Table[ValidatorPubKey, AttachedValidator]
|
||||||
|
slashingProtection*: SlashingProtectionDB
|
||||||
|
|
||||||
func shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)
|
func shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)
|
||||||
|
|
|
@ -16,14 +16,16 @@ import
|
||||||
json_serialization/std/[options, sets, net],
|
json_serialization/std/[options, sets, net],
|
||||||
|
|
||||||
# Local modules
|
# Local modules
|
||||||
spec/[datatypes, digest, crypto, helpers, network],
|
spec/[datatypes, digest, crypto, helpers, network, signatures],
|
||||||
conf, time, version,
|
conf, time, version,
|
||||||
eth2_network, eth2_discovery, validator_pool, beacon_node_types,
|
eth2_network, eth2_discovery, validator_pool, beacon_node_types,
|
||||||
nimbus_binary_common,
|
nimbus_binary_common,
|
||||||
version, ssz/merkleization,
|
version, ssz/merkleization,
|
||||||
sync_manager, keystore_management,
|
sync_manager, keystore_management,
|
||||||
spec/eth2_apis/callsigs_types,
|
spec/eth2_apis/callsigs_types,
|
||||||
eth2_json_rpc_serialization
|
eth2_json_rpc_serialization,
|
||||||
|
validator_slashing_protection,
|
||||||
|
eth/db/[kvstore, kvstore_sqlite3]
|
||||||
|
|
||||||
logScope: topics = "vc"
|
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
|
# check if we have a validator which needs to propose on this slot
|
||||||
if vc.proposalsForCurrentEpoch.contains slot:
|
if vc.proposalsForCurrentEpoch.contains slot:
|
||||||
let public_key = vc.proposalsForCurrentEpoch[slot]
|
let public_key = vc.proposalsForCurrentEpoch[slot]
|
||||||
let validator = vc.attachedValidators.validators[public_key]
|
|
||||||
|
|
||||||
info "Proposing block", slot = slot, public_key = public_key
|
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)
|
||||||
|
|
||||||
let randao_reveal = await validator.genRandaoReveal(
|
# TODO: signing_root is recomputed in signBlockProposal just after
|
||||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
|
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)
|
||||||
|
|
||||||
var newBlock = SignedBeaconBlock(
|
newBlock.signature = await validator.signBlockProposal(
|
||||||
message: await vc.client.get_v1_validator_block(slot, vc.graffitiBytes, randao_reveal)
|
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
|
||||||
)
|
|
||||||
|
|
||||||
newBlock.root = hash_tree_root(newBlock.message)
|
discard await vc.client.post_v1_validator_block(newBlock)
|
||||||
newBlock.signature = await validator.signBlockProposal(
|
else:
|
||||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)
|
warn "Slashing protection activated for block proposal",
|
||||||
|
validator = public_key,
|
||||||
discard await vc.client.post_v1_validator_block(newBlock)
|
slot = slot,
|
||||||
|
existingProposal = notSlashable.error
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#attesting
|
# 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
|
# 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 validator = vc.attachedValidators.validators[a.public_key]
|
||||||
let ad = await vc.client.get_v1_validator_attestation(slot, a.committee_index)
|
let ad = await vc.client.get_v1_validator_attestation(slot, a.committee_index)
|
||||||
|
|
||||||
# TODO I don't like these (u)int64-to-int conversions...
|
let notSlashable = vc.attachedValidators
|
||||||
let attestation = await validator.produceAndSignAttestation(
|
.slashingProtection
|
||||||
ad, a.committee_length.int, a.validator_committee_index.int,
|
.checkSlashableAttestation(
|
||||||
vc.fork, vc.beaconGenesis.genesis_validators_root)
|
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)
|
||||||
|
|
||||||
discard await vc.client.post_v1_beacon_pool_attestations(attestation)
|
# 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:
|
except CatchableError as err:
|
||||||
warn "Caught an unexpected error", err = err.msg, slot = shortLog(slot)
|
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.beaconGenesis = waitFor vc.client.get_v1_beacon_genesis()
|
||||||
vc.beaconClock = BeaconClock.init(vc.beaconGenesis.genesis_time)
|
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
|
let
|
||||||
curSlot = vc.beaconClock.now().slotOrZero()
|
curSlot = vc.beaconClock.now().slotOrZero()
|
||||||
nextSlot = curSlot + 1 # No earlier than GENESIS_SLOT + 1
|
nextSlot = curSlot + 1 # No earlier than GENESIS_SLOT + 1
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
std/[os, tables, strutils, sequtils, osproc, streams],
|
std/[os, tables, sequtils, osproc, streams],
|
||||||
|
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
stew/[objects], stew/shims/macros,
|
stew/[objects], stew/shims/macros,
|
||||||
|
@ -18,13 +18,14 @@ import
|
||||||
eth/[keys, async_utils], eth/p2p/discoveryv5/[protocol, enr],
|
eth/[keys, async_utils], eth/p2p/discoveryv5/[protocol, enr],
|
||||||
|
|
||||||
# Local modules
|
# Local modules
|
||||||
spec/[datatypes, digest, crypto, helpers, validator, network],
|
spec/[datatypes, digest, crypto, helpers, validator, network, signatures],
|
||||||
spec/state_transition,
|
spec/state_transition,
|
||||||
conf, time, validator_pool,
|
conf, time, validator_pool,
|
||||||
attestation_pool, block_pools/[spec_cache, chain_dag, clearance],
|
attestation_pool, block_pools/[spec_cache, chain_dag, clearance],
|
||||||
eth2_network, keystore_management, beacon_node_common, beacon_node_types,
|
eth2_network, keystore_management, beacon_node_common, beacon_node_types,
|
||||||
nimbus_binary_common, mainchain_monitor, version, ssz/merkleization, interop,
|
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
|
# Metrics for tracking attestation and beacon block loss
|
||||||
declareCounter beacon_attestations_sent,
|
declareCounter beacon_attestations_sent,
|
||||||
|
@ -120,6 +121,8 @@ proc isSynced*(node: BeaconNode, head: BlockRef): bool =
|
||||||
beaconTime = node.beaconClock.now()
|
beaconTime = node.beaconClock.now()
|
||||||
wallSlot = beaconTime.toSlot()
|
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
|
# TODO if everyone follows this logic, the network will not recover from a
|
||||||
# halt: nobody will be producing blocks because everone expects someone
|
# halt: nobody will be producing blocks because everone expects someone
|
||||||
# else to do it
|
# else to do it
|
||||||
|
@ -293,6 +296,16 @@ proc proposeBlock(node: BeaconNode,
|
||||||
slot = shortLog(slot)
|
slot = shortLog(slot)
|
||||||
return head
|
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 valInfo = ValidatorInfoForMakeBeaconBlock(kind: viValidator, validator: validator)
|
||||||
let beaconBlockTuple = await makeBeaconBlockForHeadAndSlot(
|
let beaconBlockTuple = await makeBeaconBlockForHeadAndSlot(
|
||||||
node, valInfo, validator_index, node.graffitiBytes, head, slot)
|
node, valInfo, validator_index, node.graffitiBytes, head, slot)
|
||||||
|
@ -304,6 +317,14 @@ proc proposeBlock(node: BeaconNode,
|
||||||
)
|
)
|
||||||
|
|
||||||
newBlock.root = hash_tree_root(newBlock.message)
|
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(
|
newBlock.signature = await validator.signBlockProposal(
|
||||||
beaconBlockTuple.fork, beaconBlockTuple.genesis_validators_root, slot, newBlock.root)
|
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))
|
attestations.add((ad, committee.len, index_in_committee, validator))
|
||||||
|
|
||||||
for a in attestations:
|
for a in attestations:
|
||||||
traceAsyncErrors createAndSendAttestation(
|
let notSlashable = node.attachedValidators
|
||||||
node, fork, genesis_validators_root, a.validator, a.data,
|
.slashingProtection
|
||||||
a.committeeLen, a.indexInCommittee, num_active_validators)
|
.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):
|
proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
|
||||||
Future[BlockRef] {.async.} =
|
Future[BlockRef] {.async.} =
|
||||||
|
|
|
@ -3,10 +3,18 @@ import
|
||||||
chronos, chronicles,
|
chronos, chronicles,
|
||||||
spec/[datatypes, crypto, digest, signatures, helpers],
|
spec/[datatypes, crypto, digest, signatures, helpers],
|
||||||
beacon_node_types,
|
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.validators = initTable[ValidatorPubKey, AttachedValidator]()
|
||||||
|
result.slashingProtection = slashingProtectionDB
|
||||||
|
|
||||||
template count*(pool: ValidatorPool): int =
|
template count*(pool: ValidatorPool): int =
|
||||||
pool.validators.len
|
pool.validators.len
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -30,7 +30,9 @@ import # Unit test
|
||||||
./test_sync_manager,
|
./test_sync_manager,
|
||||||
./test_honest_validator,
|
./test_honest_validator,
|
||||||
./test_interop,
|
./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
|
import # Refactor state transition unit tests
|
||||||
# In mainnet these take 2 minutes and are empty TODOs
|
# In mainnet these take 2 minutes and are empty TODOs
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*.json
|
|
@ -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")
|
|
@ -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
|
Loading…
Reference in New Issue