Skip to content

Commit

Permalink
Opt-in Slashing protection + interchange (#1643)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mratsim authored Sep 16, 2020
1 parent 6e46325 commit 52548f0
Show file tree
Hide file tree
Showing 12 changed files with 1,905 additions and 54 deletions.
14 changes: 4 additions & 10 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
}
},
Expand All @@ -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
// """
// }
}
)
}
Expand Down Expand Up @@ -100,4 +95,3 @@ parallel(
}
},
)

17 changes: 8 additions & 9 deletions beacon_chain.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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]""""
Expand All @@ -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"
Expand Down
13 changes: 11 additions & 2 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -258,7 +259,6 @@ proc init*(T: type BeaconNode,
netKeys: netKeys,
db: db,
config: conf,
attachedValidators: ValidatorPool.init(),
chainDag: chainDag,
quarantine: quarantine,
attestationPool: attestationPool,
Expand All @@ -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(
Expand Down Expand Up @@ -1312,4 +1322,3 @@ programMain:

of WalletsCmd.restore:
restoreWalletInteractively(rng[], config)

4 changes: 3 additions & 1 deletion beacon_chain/beacon_node_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -105,5 +106,6 @@ type

ValidatorPool* = object
validators*: Table[ValidatorPubKey, AttachedValidator]
slashingProtection*: SlashingProtectionDB

func shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)
87 changes: 64 additions & 23 deletions beacon_chain/validator_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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 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)
newBlock.signature = await validator.signBlockProposal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, newBlock.root)

discard await vc.client.post_v1_validator_block(newBlock)
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
Expand All @@ -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)

# 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)
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)
Expand Down Expand Up @@ -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
Expand Down
45 changes: 39 additions & 6 deletions beacon_chain/validator_duties.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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:
traceAsyncErrors createAndSendAttestation(
node, fork, genesis_validators_root, a.validator, a.data,
a.committeeLen, a.indexInCommittee, num_active_validators)
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.} =
Expand Down
12 changes: 10 additions & 2 deletions beacon_chain/validator_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 52548f0

Please sign in to comment.