Merge pull request #264 from sigp/yaml-chain-tests
YAML-defined test_harness routines
This commit is contained in:
commit
0e1a14a628
@ -15,9 +15,7 @@ use state_processing::{
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use types::{
|
use types::{
|
||||||
readers::{BeaconBlockReader, BeaconStateReader},
|
readers::{BeaconBlockReader, BeaconStateReader},
|
||||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, BeaconStateError, ChainSpec,
|
*,
|
||||||
Crosslink, Deposit, Epoch, Eth1Data, FreeAttestation, Hash256, PublicKey, RelativeEpoch,
|
|
||||||
Signature, Slot,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -66,6 +64,9 @@ pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock, F: ForkChoice> {
|
|||||||
pub state_store: Arc<BeaconStateStore<T>>,
|
pub state_store: Arc<BeaconStateStore<T>>,
|
||||||
pub slot_clock: U,
|
pub slot_clock: U,
|
||||||
pub attestation_aggregator: RwLock<AttestationAggregator>,
|
pub attestation_aggregator: RwLock<AttestationAggregator>,
|
||||||
|
pub deposits_for_inclusion: RwLock<Vec<Deposit>>,
|
||||||
|
pub proposer_slashings_for_inclusion: RwLock<Vec<ProposerSlashing>>,
|
||||||
|
pub attester_slashings_for_inclusion: RwLock<Vec<AttesterSlashing>>,
|
||||||
canonical_head: RwLock<CheckPoint>,
|
canonical_head: RwLock<CheckPoint>,
|
||||||
finalized_head: RwLock<CheckPoint>,
|
finalized_head: RwLock<CheckPoint>,
|
||||||
pub state: RwLock<BeaconState>,
|
pub state: RwLock<BeaconState>,
|
||||||
@ -132,6 +133,9 @@ where
|
|||||||
state_store,
|
state_store,
|
||||||
slot_clock,
|
slot_clock,
|
||||||
attestation_aggregator,
|
attestation_aggregator,
|
||||||
|
deposits_for_inclusion: RwLock::new(vec![]),
|
||||||
|
proposer_slashings_for_inclusion: RwLock::new(vec![]),
|
||||||
|
attester_slashings_for_inclusion: RwLock::new(vec![]),
|
||||||
state: RwLock::new(genesis_state),
|
state: RwLock::new(genesis_state),
|
||||||
finalized_head,
|
finalized_head,
|
||||||
canonical_head,
|
canonical_head,
|
||||||
@ -364,6 +368,128 @@ where
|
|||||||
Ok(aggregation_outcome)
|
Ok(aggregation_outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Accept some deposit and queue it for inclusion in an appropriate block.
|
||||||
|
pub fn receive_deposit_for_inclusion(&self, deposit: Deposit) {
|
||||||
|
// TODO: deposits are not check for validity; check them.
|
||||||
|
self.deposits_for_inclusion.write().push(deposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a vec of deposits suitable for inclusion in some block.
|
||||||
|
pub fn get_deposits_for_block(&self) -> Vec<Deposit> {
|
||||||
|
// TODO: deposits are indiscriminately included; check them for validity.
|
||||||
|
self.deposits_for_inclusion.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of `Deposits` that were included in recent blocks and removes them from the
|
||||||
|
/// inclusion queue.
|
||||||
|
///
|
||||||
|
/// This ensures that `Deposits` are not included twice in successive blocks.
|
||||||
|
pub fn set_deposits_as_included(&self, included_deposits: &[Deposit]) {
|
||||||
|
// TODO: method does not take forks into account; consider this.
|
||||||
|
let mut indices_to_delete = vec![];
|
||||||
|
|
||||||
|
for included in included_deposits {
|
||||||
|
for (i, for_inclusion) in self.deposits_for_inclusion.read().iter().enumerate() {
|
||||||
|
if included == for_inclusion {
|
||||||
|
indices_to_delete.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let deposits_for_inclusion = &mut self.deposits_for_inclusion.write();
|
||||||
|
for i in indices_to_delete {
|
||||||
|
deposits_for_inclusion.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept some proposer slashing and queue it for inclusion in an appropriate block.
|
||||||
|
pub fn receive_proposer_slashing_for_inclusion(&self, proposer_slashing: ProposerSlashing) {
|
||||||
|
// TODO: proposer_slashings are not check for validity; check them.
|
||||||
|
self.proposer_slashings_for_inclusion
|
||||||
|
.write()
|
||||||
|
.push(proposer_slashing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a vec of proposer slashings suitable for inclusion in some block.
|
||||||
|
pub fn get_proposer_slashings_for_block(&self) -> Vec<ProposerSlashing> {
|
||||||
|
// TODO: proposer_slashings are indiscriminately included; check them for validity.
|
||||||
|
self.proposer_slashings_for_inclusion.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of `ProposerSlashings` that were included in recent blocks and removes them
|
||||||
|
/// from the inclusion queue.
|
||||||
|
///
|
||||||
|
/// This ensures that `ProposerSlashings` are not included twice in successive blocks.
|
||||||
|
pub fn set_proposer_slashings_as_included(
|
||||||
|
&self,
|
||||||
|
included_proposer_slashings: &[ProposerSlashing],
|
||||||
|
) {
|
||||||
|
// TODO: method does not take forks into account; consider this.
|
||||||
|
let mut indices_to_delete = vec![];
|
||||||
|
|
||||||
|
for included in included_proposer_slashings {
|
||||||
|
for (i, for_inclusion) in self
|
||||||
|
.proposer_slashings_for_inclusion
|
||||||
|
.read()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if included == for_inclusion {
|
||||||
|
indices_to_delete.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let proposer_slashings_for_inclusion = &mut self.proposer_slashings_for_inclusion.write();
|
||||||
|
for i in indices_to_delete {
|
||||||
|
proposer_slashings_for_inclusion.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept some attester slashing and queue it for inclusion in an appropriate block.
|
||||||
|
pub fn receive_attester_slashing_for_inclusion(&self, attester_slashing: AttesterSlashing) {
|
||||||
|
// TODO: attester_slashings are not check for validity; check them.
|
||||||
|
self.attester_slashings_for_inclusion
|
||||||
|
.write()
|
||||||
|
.push(attester_slashing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a vec of attester slashings suitable for inclusion in some block.
|
||||||
|
pub fn get_attester_slashings_for_block(&self) -> Vec<AttesterSlashing> {
|
||||||
|
// TODO: attester_slashings are indiscriminately included; check them for validity.
|
||||||
|
self.attester_slashings_for_inclusion.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of `AttesterSlashings` that were included in recent blocks and removes them
|
||||||
|
/// from the inclusion queue.
|
||||||
|
///
|
||||||
|
/// This ensures that `AttesterSlashings` are not included twice in successive blocks.
|
||||||
|
pub fn set_attester_slashings_as_included(
|
||||||
|
&self,
|
||||||
|
included_attester_slashings: &[AttesterSlashing],
|
||||||
|
) {
|
||||||
|
// TODO: method does not take forks into account; consider this.
|
||||||
|
let mut indices_to_delete = vec![];
|
||||||
|
|
||||||
|
for included in included_attester_slashings {
|
||||||
|
for (i, for_inclusion) in self
|
||||||
|
.attester_slashings_for_inclusion
|
||||||
|
.read()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if included == for_inclusion {
|
||||||
|
indices_to_delete.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let attester_slashings_for_inclusion = &mut self.attester_slashings_for_inclusion.write();
|
||||||
|
for i in indices_to_delete {
|
||||||
|
attester_slashings_for_inclusion.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
|
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
|
||||||
///
|
///
|
||||||
/// This could be a very expensive operation and should only be done in testing/analysis
|
/// This could be a very expensive operation and should only be done in testing/analysis
|
||||||
@ -412,6 +538,8 @@ where
|
|||||||
last_slot = slot;
|
last_slot = slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dump.reverse();
|
||||||
|
|
||||||
Ok(dump)
|
Ok(dump)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,6 +616,11 @@ where
|
|||||||
self.block_store.put(&block_root, &ssz_encode(&block)[..])?;
|
self.block_store.put(&block_root, &ssz_encode(&block)[..])?;
|
||||||
self.state_store.put(&state_root, &ssz_encode(&state)[..])?;
|
self.state_store.put(&state_root, &ssz_encode(&state)[..])?;
|
||||||
|
|
||||||
|
// Update the inclusion queues so they aren't re-submitted.
|
||||||
|
self.set_deposits_as_included(&block.body.deposits[..]);
|
||||||
|
self.set_proposer_slashings_as_included(&block.body.proposer_slashings[..]);
|
||||||
|
self.set_attester_slashings_as_included(&block.body.attester_slashings[..]);
|
||||||
|
|
||||||
// run the fork_choice add_block logic
|
// run the fork_choice add_block logic
|
||||||
self.fork_choice
|
self.fork_choice
|
||||||
.write()
|
.write()
|
||||||
@ -500,7 +633,7 @@ where
|
|||||||
if self.head().beacon_block_root == parent_block_root {
|
if self.head().beacon_block_root == parent_block_root {
|
||||||
self.update_canonical_head(block.clone(), block_root, state.clone(), state_root);
|
self.update_canonical_head(block.clone(), block_root, state.clone(), state_root);
|
||||||
// Update the local state variable.
|
// Update the local state variable.
|
||||||
*self.state.write() = state.clone();
|
*self.state.write() = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed))
|
Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed))
|
||||||
@ -541,10 +674,10 @@ where
|
|||||||
},
|
},
|
||||||
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
|
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
|
||||||
body: BeaconBlockBody {
|
body: BeaconBlockBody {
|
||||||
proposer_slashings: vec![],
|
proposer_slashings: self.get_proposer_slashings_for_block(),
|
||||||
attester_slashings: vec![],
|
attester_slashings: self.get_attester_slashings_for_block(),
|
||||||
attestations,
|
attestations,
|
||||||
deposits: vec![],
|
deposits: self.get_deposits_for_block(),
|
||||||
exits: vec![],
|
exits: vec![],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -553,7 +686,7 @@ where
|
|||||||
|
|
||||||
let result =
|
let result =
|
||||||
state.per_block_processing_without_verifying_block_signature(&block, &self.spec);
|
state.per_block_processing_without_verifying_block_signature(&block, &self.spec);
|
||||||
trace!(
|
debug!(
|
||||||
"BeaconNode::produce_block: state processing result: {:?}",
|
"BeaconNode::produce_block: state processing result: {:?}",
|
||||||
result
|
result
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,14 @@ version = "0.1.0"
|
|||||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "test_harness"
|
||||||
|
path = "src/bin.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "test_harness"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "state_transition"
|
name = "state_transition"
|
||||||
harness = false
|
harness = false
|
||||||
@ -18,6 +26,7 @@ beacon_chain = { path = "../../beacon_chain" }
|
|||||||
block_proposer = { path = "../../../eth2/block_proposer" }
|
block_proposer = { path = "../../../eth2/block_proposer" }
|
||||||
bls = { path = "../../../eth2/utils/bls" }
|
bls = { path = "../../../eth2/utils/bls" }
|
||||||
boolean-bitfield = { path = "../../../eth2/utils/boolean-bitfield" }
|
boolean-bitfield = { path = "../../../eth2/utils/boolean-bitfield" }
|
||||||
|
clap = "2.32.0"
|
||||||
db = { path = "../../db" }
|
db = { path = "../../db" }
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.7"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
@ -33,3 +42,4 @@ serde_json = "1.0"
|
|||||||
slot_clock = { path = "../../../eth2/utils/slot_clock" }
|
slot_clock = { path = "../../../eth2/utils/slot_clock" }
|
||||||
ssz = { path = "../../../eth2/utils/ssz" }
|
ssz = { path = "../../../eth2/utils/ssz" }
|
||||||
types = { path = "../../../eth2/types" }
|
types = { path = "../../../eth2/types" }
|
||||||
|
yaml-rust = "0.4.2"
|
||||||
|
150
beacon_node/beacon_chain/test_harness/README.md
Normal file
150
beacon_node/beacon_chain/test_harness/README.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Test Harness
|
||||||
|
|
||||||
|
Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects.
|
||||||
|
|
||||||
|
This environment bypasses networking and client run-times and connects the `Attester` and `Proposer`
|
||||||
|
directly to the `BeaconChain` via an `Arc`.
|
||||||
|
|
||||||
|
The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness`
|
||||||
|
instances. All of the `ValidatorHarness` instances work to advance the `BeaconChain` by
|
||||||
|
producing blocks and attestations.
|
||||||
|
|
||||||
|
The crate consists of a library and binary, examples for using both are
|
||||||
|
described below.
|
||||||
|
|
||||||
|
## YAML
|
||||||
|
|
||||||
|
Both the library and the binary are capable of parsing tests from a YAML file,
|
||||||
|
in fact this is the sole purpose of the binary.
|
||||||
|
|
||||||
|
You can find YAML test cases [here](specs/). An example is included below:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
title: Validator Registry Tests
|
||||||
|
summary: Tests deposit and slashing effects on validator registry.
|
||||||
|
test_suite: validator_registry
|
||||||
|
fork: tchaikovsky
|
||||||
|
version: 1.0
|
||||||
|
test_cases:
|
||||||
|
- config:
|
||||||
|
epoch_length: 64
|
||||||
|
deposits_for_chain_start: 1000
|
||||||
|
num_slots: 64
|
||||||
|
skip_slots: [2, 3]
|
||||||
|
deposits:
|
||||||
|
# At slot 1, create a new validator deposit of 32 ETH.
|
||||||
|
- slot: 1
|
||||||
|
amount: 32
|
||||||
|
# Trigger more deposits...
|
||||||
|
- slot: 3
|
||||||
|
amount: 32
|
||||||
|
- slot: 5
|
||||||
|
amount: 32
|
||||||
|
proposer_slashings:
|
||||||
|
# At slot 2, trigger a proposer slashing for validator #42.
|
||||||
|
- slot: 2
|
||||||
|
validator_index: 42
|
||||||
|
# Trigger another slashing...
|
||||||
|
- slot: 8
|
||||||
|
validator_index: 13
|
||||||
|
attester_slashings:
|
||||||
|
# At slot 2, trigger an attester slashing for validators #11 and #12.
|
||||||
|
- slot: 2
|
||||||
|
validator_indices: [11, 12]
|
||||||
|
# Trigger another slashing...
|
||||||
|
- slot: 5
|
||||||
|
validator_indices: [14]
|
||||||
|
results:
|
||||||
|
num_skipped_slots: 2
|
||||||
|
states:
|
||||||
|
- slot: 63
|
||||||
|
num_validators: 1003
|
||||||
|
slashed_validators: [11, 12, 13, 14, 42]
|
||||||
|
exited_validators: []
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks to [prsym](http://github.com/prysmaticlabs/prysm) for coming up with the
|
||||||
|
base YAML format.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
Wherever `slot` is used, it is actually the "slot height", or slots since
|
||||||
|
genesis. This allows the tests to disregard the `GENESIS_EPOCH`.
|
||||||
|
|
||||||
|
### Differences from Prysmatic's format
|
||||||
|
|
||||||
|
1. The detail for `deposits`, `proposer_slashings` and `attester_slashings` is
|
||||||
|
ommitted from the test specification. It assumed they should be valid
|
||||||
|
objects.
|
||||||
|
2. There is a `states` list in `results` that runs checks against any state
|
||||||
|
specified by a `slot` number. This is in contrast to the variables in
|
||||||
|
`results` that assume the last (highest) state should be inspected.
|
||||||
|
|
||||||
|
#### Reasoning
|
||||||
|
|
||||||
|
Respective reasonings for above changes:
|
||||||
|
|
||||||
|
1. This removes the concerns of the actual object structure from the tests.
|
||||||
|
This allows for more variation in the deposits/slashings objects without
|
||||||
|
needing to update the tests. Also, it makes it makes it easier to create
|
||||||
|
tests.
|
||||||
|
2. This gives more fine-grained control over the tests. It allows for checking
|
||||||
|
that certain events happened at certain times whilst making the tests only
|
||||||
|
slightly more verbose.
|
||||||
|
|
||||||
|
_Notes: it may be useful to add an extra field to each slashing type to
|
||||||
|
indicate if it should be valid or not. It also may be useful to add an option
|
||||||
|
for double-vote/surround-vote attester slashings. The `amount` field was left
|
||||||
|
on `deposits` as it changes the behaviour of state significantly._
|
||||||
|
|
||||||
|
## Binary Usage Example
|
||||||
|
|
||||||
|
Follow these steps to run as a binary:
|
||||||
|
|
||||||
|
1. Navigate to the root of this crate (where this readme is located)
|
||||||
|
2. Run `$ cargo run --release -- --yaml examples/validator_registry.yaml`
|
||||||
|
|
||||||
|
_Note: the `--release` flag builds the binary without all the debugging
|
||||||
|
instrumentation. The test is much faster built using `--release`. As is
|
||||||
|
customary in cargo, the flags before `--` are passed to cargo and the flags
|
||||||
|
after are passed to the binary._
|
||||||
|
|
||||||
|
### CLI Options
|
||||||
|
|
||||||
|
```
|
||||||
|
Lighthouse Test Harness Runner 0.0.1
|
||||||
|
Sigma Prime <contact@sigmaprime.io>
|
||||||
|
Runs `test_harness` using a YAML test_case.
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
test_harness --log-level <LOG_LEVEL> --yaml <FILE>
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information
|
||||||
|
-V, --version Prints version information
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--log-level <LOG_LEVEL> Logging level. [default: debug] [possible values: error, warn, info, debug, trace]
|
||||||
|
--yaml <FILE> YAML file test_case.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Library Usage Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use test_harness::BeaconChainHarness;
|
||||||
|
use types::ChainSpec;
|
||||||
|
|
||||||
|
let validator_count = 8;
|
||||||
|
let spec = ChainSpec::few_validators();
|
||||||
|
|
||||||
|
let mut harness = BeaconChainHarness::new(spec, validator_count);
|
||||||
|
|
||||||
|
harness.advance_chain_with_block();
|
||||||
|
|
||||||
|
let chain = harness.chain_dump().unwrap();
|
||||||
|
|
||||||
|
// One block should have been built on top of the genesis block.
|
||||||
|
assert_eq!(chain.len(), 2);
|
||||||
|
```
|
@ -0,0 +1,42 @@
|
|||||||
|
title: Validator Registry Tests
|
||||||
|
summary: Tests deposit and slashing effects on validator registry.
|
||||||
|
test_suite: validator_registry
|
||||||
|
fork: tchaikovsky
|
||||||
|
version: 1.0
|
||||||
|
test_cases:
|
||||||
|
- config:
|
||||||
|
epoch_length: 64
|
||||||
|
deposits_for_chain_start: 1000
|
||||||
|
num_slots: 64
|
||||||
|
skip_slots: [2, 3]
|
||||||
|
deposits:
|
||||||
|
# At slot 1, create a new validator deposit of 32 ETH.
|
||||||
|
- slot: 1
|
||||||
|
amount: 32
|
||||||
|
# Trigger more deposits...
|
||||||
|
- slot: 3
|
||||||
|
amount: 32
|
||||||
|
- slot: 5
|
||||||
|
amount: 32
|
||||||
|
proposer_slashings:
|
||||||
|
# At slot 2, trigger a proposer slashing for validator #42.
|
||||||
|
- slot: 2
|
||||||
|
validator_index: 42
|
||||||
|
# Trigger another slashing...
|
||||||
|
- slot: 8
|
||||||
|
validator_index: 13
|
||||||
|
attester_slashings:
|
||||||
|
# At slot 2, trigger an attester slashing for validators #11 and #12.
|
||||||
|
- slot: 2
|
||||||
|
validator_indices: [11, 12]
|
||||||
|
# Trigger another slashing...
|
||||||
|
- slot: 5
|
||||||
|
validator_indices: [14]
|
||||||
|
results:
|
||||||
|
num_skipped_slots: 2
|
||||||
|
states:
|
||||||
|
- slot: 63
|
||||||
|
num_validators: 1003
|
||||||
|
slashed_validators: [11, 12, 13, 14, 42]
|
||||||
|
exited_validators: []
|
||||||
|
|
@ -11,14 +11,9 @@ use log::debug;
|
|||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use slot_clock::TestingSlotClock;
|
use slot_clock::TestingSlotClock;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use types::{
|
use types::*;
|
||||||
BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, FreeAttestation, Hash256,
|
|
||||||
Keypair, Slot,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The beacon chain harness simulates a single beacon node with `validator_count` validators connected
|
/// The beacon chain harness simulates a single beacon node with `validator_count` validators connected
|
||||||
/// to it. Each validator is provided a borrow to the beacon chain, where it may read
|
/// to it. Each validator is provided a borrow to the beacon chain, where it may read
|
||||||
@ -245,6 +240,59 @@ impl BeaconChainHarness {
|
|||||||
debug!("Free attestations processed.");
|
debug!("Free attestations processed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Signs a message using some validators secret key with the `Fork` info from the latest state
|
||||||
|
/// of the `BeaconChain`.
|
||||||
|
///
|
||||||
|
/// Useful for producing slashable messages and other objects that `BeaconChainHarness` does
|
||||||
|
/// not produce naturally.
|
||||||
|
pub fn validator_sign(
|
||||||
|
&self,
|
||||||
|
validator_index: usize,
|
||||||
|
message: &[u8],
|
||||||
|
epoch: Epoch,
|
||||||
|
domain_type: u64,
|
||||||
|
) -> Option<Signature> {
|
||||||
|
let validator = self.validators.get(validator_index)?;
|
||||||
|
|
||||||
|
let domain = self
|
||||||
|
.beacon_chain
|
||||||
|
.state
|
||||||
|
.read()
|
||||||
|
.fork
|
||||||
|
.get_domain(epoch, domain_type);
|
||||||
|
|
||||||
|
Some(Signature::new(message, domain, &validator.keypair.sk))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submit a deposit to the `BeaconChain` and, if given a keypair, create a new
|
||||||
|
/// `ValidatorHarness` instance for this validator.
|
||||||
|
///
|
||||||
|
/// If a new `ValidatorHarness` was created, the validator should become fully operational as
|
||||||
|
/// if the validator were created during `BeaconChainHarness` instantiation.
|
||||||
|
pub fn add_deposit(&mut self, deposit: Deposit, keypair: Option<Keypair>) {
|
||||||
|
self.beacon_chain.receive_deposit_for_inclusion(deposit);
|
||||||
|
|
||||||
|
// If a keypair is present, add a new `ValidatorHarness` to the rig.
|
||||||
|
if let Some(keypair) = keypair {
|
||||||
|
let validator =
|
||||||
|
ValidatorHarness::new(keypair, self.beacon_chain.clone(), self.spec.clone());
|
||||||
|
self.validators.push(validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submit a proposer slashing to the `BeaconChain` for inclusion in some block.
|
||||||
|
pub fn add_proposer_slashing(&mut self, proposer_slashing: ProposerSlashing) {
|
||||||
|
self.beacon_chain
|
||||||
|
.receive_proposer_slashing_for_inclusion(proposer_slashing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submit an attester slashing to the `BeaconChain` for inclusion in some block.
|
||||||
|
pub fn add_attester_slashing(&mut self, attester_slashing: AttesterSlashing) {
|
||||||
|
self.beacon_chain
|
||||||
|
.receive_attester_slashing_for_inclusion(attester_slashing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the fork choice rule on the `BeaconChain`, selecting a new canonical head.
|
||||||
pub fn run_fork_choice(&mut self) {
|
pub fn run_fork_choice(&mut self) {
|
||||||
self.beacon_chain.fork_choice().unwrap()
|
self.beacon_chain.fork_choice().unwrap()
|
||||||
}
|
}
|
||||||
@ -253,12 +301,4 @@ impl BeaconChainHarness {
|
|||||||
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, BeaconChainError> {
|
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, BeaconChainError> {
|
||||||
self.beacon_chain.chain_dump()
|
self.beacon_chain.chain_dump()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the output of `chain_dump` to a JSON file.
|
|
||||||
pub fn dump_to_file(&self, filename: String, chain_dump: &[CheckPoint]) {
|
|
||||||
let json = serde_json::to_string(chain_dump).unwrap();
|
|
||||||
let mut file = File::create(filename).unwrap();
|
|
||||||
file.write_all(json.as_bytes())
|
|
||||||
.expect("Failed writing dump to file.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
69
beacon_node/beacon_chain/test_harness/src/bin.rs
Normal file
69
beacon_node/beacon_chain/test_harness/src/bin.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use clap::{App, Arg};
|
||||||
|
use env_logger::{Builder, Env};
|
||||||
|
use std::{fs::File, io::prelude::*};
|
||||||
|
use test_case::TestCase;
|
||||||
|
use yaml_rust::YamlLoader;
|
||||||
|
|
||||||
|
mod beacon_chain_harness;
|
||||||
|
mod test_case;
|
||||||
|
mod validator_harness;
|
||||||
|
|
||||||
|
use validator_harness::ValidatorHarness;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = App::new("Lighthouse Test Harness Runner")
|
||||||
|
.version("0.0.1")
|
||||||
|
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||||
|
.about("Runs `test_harness` using a YAML test_case.")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("yaml")
|
||||||
|
.long("yaml")
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("YAML file test_case.")
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("log")
|
||||||
|
.long("log-level")
|
||||||
|
.value_name("LOG_LEVEL")
|
||||||
|
.help("Logging level.")
|
||||||
|
.possible_values(&["error", "warn", "info", "debug", "trace"])
|
||||||
|
.default_value("debug")
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
if let Some(log_level) = matches.value_of("log") {
|
||||||
|
Builder::from_env(Env::default().default_filter_or(log_level)).init();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(yaml_file) = matches.value_of("yaml") {
|
||||||
|
let docs = {
|
||||||
|
let mut file = File::open(yaml_file).unwrap();
|
||||||
|
|
||||||
|
let mut yaml_str = String::new();
|
||||||
|
file.read_to_string(&mut yaml_str).unwrap();
|
||||||
|
|
||||||
|
YamlLoader::load_from_str(&yaml_str).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
for doc in &docs {
|
||||||
|
// For each `test_cases` YAML in the document, build a `TestCase`, execute it and
|
||||||
|
// assert that the execution result matches the test_case description.
|
||||||
|
//
|
||||||
|
// In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis
|
||||||
|
// and a new `BeaconChain` is built as per the test_case.
|
||||||
|
//
|
||||||
|
// After the `BeaconChain` has been built out as per the test_case, a dump of all blocks
|
||||||
|
// and states in the chain is obtained and checked against the `results` specified in
|
||||||
|
// the `test_case`.
|
||||||
|
//
|
||||||
|
// If any of the expectations in the results are not met, the process
|
||||||
|
// panics with a message.
|
||||||
|
for test_case in doc["test_cases"].as_vec().unwrap() {
|
||||||
|
let test_case = TestCase::from_yaml(test_case);
|
||||||
|
test_case.assert_result_valid(test_case.execute())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,32 @@
|
|||||||
|
//! Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects.
|
||||||
|
//!
|
||||||
|
//! This environment bypasses networking and client run-times and connects the `Attester` and `Proposer`
|
||||||
|
//! directly to the `BeaconChain` via an `Arc`.
|
||||||
|
//!
|
||||||
|
//! The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness`
|
||||||
|
//! instances. All of the `ValidatorHarness` instances work to advance the `BeaconChain` by
|
||||||
|
//! producing blocks and attestations.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! use test_harness::BeaconChainHarness;
|
||||||
|
//! use types::ChainSpec;
|
||||||
|
//!
|
||||||
|
//! let validator_count = 8;
|
||||||
|
//! let spec = ChainSpec::few_validators();
|
||||||
|
//!
|
||||||
|
//! let mut harness = BeaconChainHarness::new(spec, validator_count);
|
||||||
|
//!
|
||||||
|
//! harness.advance_chain_with_block();
|
||||||
|
//!
|
||||||
|
//! let chain = harness.chain_dump().unwrap();
|
||||||
|
//!
|
||||||
|
//! // One block should have been built on top of the genesis block.
|
||||||
|
//! assert_eq!(chain.len(), 2);
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod beacon_chain_harness;
|
mod beacon_chain_harness;
|
||||||
|
pub mod test_case;
|
||||||
mod validator_harness;
|
mod validator_harness;
|
||||||
|
|
||||||
pub use self::beacon_chain_harness::BeaconChainHarness;
|
pub use self::beacon_chain_harness::BeaconChainHarness;
|
||||||
|
109
beacon_node/beacon_chain/test_harness/src/test_case/config.rs
Normal file
109
beacon_node/beacon_chain/test_harness/src/test_case/config.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use super::yaml_helpers::{as_u64, as_usize, as_vec_u64};
|
||||||
|
use bls::create_proof_of_possession;
|
||||||
|
use types::*;
|
||||||
|
use yaml_rust::Yaml;
|
||||||
|
|
||||||
|
pub type DepositTuple = (u64, Deposit, Keypair);
|
||||||
|
pub type ProposerSlashingTuple = (u64, u64);
|
||||||
|
pub type AttesterSlashingTuple = (u64, Vec<u64>);
|
||||||
|
|
||||||
|
/// Defines the execution of a `BeaconStateHarness` across a series of slots.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Initial validators.
|
||||||
|
pub deposits_for_chain_start: usize,
|
||||||
|
/// Number of slots in an epoch.
|
||||||
|
pub epoch_length: Option<u64>,
|
||||||
|
/// Number of slots to build before ending execution.
|
||||||
|
pub num_slots: u64,
|
||||||
|
/// Number of slots that should be skipped due to inactive validator.
|
||||||
|
pub skip_slots: Option<Vec<u64>>,
|
||||||
|
/// Deposits to be included during execution.
|
||||||
|
pub deposits: Option<Vec<DepositTuple>>,
|
||||||
|
/// Proposer slashings to be included during execution.
|
||||||
|
pub proposer_slashings: Option<Vec<ProposerSlashingTuple>>,
|
||||||
|
/// Attester slashings to be including during execution.
|
||||||
|
pub attester_slashings: Option<Vec<AttesterSlashingTuple>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Load from a YAML document.
|
||||||
|
///
|
||||||
|
/// Expects to receive the `config` section of the document.
|
||||||
|
pub fn from_yaml(yaml: &Yaml) -> Self {
|
||||||
|
Self {
|
||||||
|
deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start")
|
||||||
|
.expect("Must specify validator count"),
|
||||||
|
epoch_length: as_u64(&yaml, "epoch_length"),
|
||||||
|
num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"),
|
||||||
|
skip_slots: as_vec_u64(yaml, "skip_slots"),
|
||||||
|
deposits: parse_deposits(&yaml),
|
||||||
|
proposer_slashings: parse_proposer_slashings(&yaml),
|
||||||
|
attester_slashings: parse_attester_slashings(&yaml),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the `attester_slashings` section of the YAML document.
|
||||||
|
fn parse_attester_slashings(yaml: &Yaml) -> Option<Vec<AttesterSlashingTuple>> {
|
||||||
|
let mut slashings = vec![];
|
||||||
|
|
||||||
|
for slashing in yaml["attester_slashings"].as_vec()? {
|
||||||
|
let slot = as_u64(slashing, "slot").expect("Incomplete attester_slashing (slot)");
|
||||||
|
let validator_indices = as_vec_u64(slashing, "validator_indices")
|
||||||
|
.expect("Incomplete attester_slashing (validator_indices)");
|
||||||
|
|
||||||
|
slashings.push((slot, validator_indices));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(slashings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the `proposer_slashings` section of the YAML document.
|
||||||
|
fn parse_proposer_slashings(yaml: &Yaml) -> Option<Vec<ProposerSlashingTuple>> {
|
||||||
|
let mut slashings = vec![];
|
||||||
|
|
||||||
|
for slashing in yaml["proposer_slashings"].as_vec()? {
|
||||||
|
let slot = as_u64(slashing, "slot").expect("Incomplete proposer slashing (slot)_");
|
||||||
|
let validator_index = as_u64(slashing, "validator_index")
|
||||||
|
.expect("Incomplete proposer slashing (validator_index)");
|
||||||
|
|
||||||
|
slashings.push((slot, validator_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(slashings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the `deposits` section of the YAML document.
|
||||||
|
fn parse_deposits(yaml: &Yaml) -> Option<Vec<DepositTuple>> {
|
||||||
|
let mut deposits = vec![];
|
||||||
|
|
||||||
|
for deposit in yaml["deposits"].as_vec()? {
|
||||||
|
let keypair = Keypair::random();
|
||||||
|
let proof_of_possession = create_proof_of_possession(&keypair);
|
||||||
|
|
||||||
|
let slot = as_u64(deposit, "slot").expect("Incomplete deposit (slot)");
|
||||||
|
let amount =
|
||||||
|
as_u64(deposit, "amount").expect("Incomplete deposit (amount)") * 1_000_000_000;
|
||||||
|
|
||||||
|
let deposit = Deposit {
|
||||||
|
// Note: `branch` and `index` will need to be updated once the spec defines their
|
||||||
|
// validity.
|
||||||
|
branch: vec![],
|
||||||
|
index: 0,
|
||||||
|
deposit_data: DepositData {
|
||||||
|
amount,
|
||||||
|
timestamp: 1,
|
||||||
|
deposit_input: DepositInput {
|
||||||
|
pubkey: keypair.pk.clone(),
|
||||||
|
withdrawal_credentials: Hash256::zero(),
|
||||||
|
proof_of_possession,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
deposits.push((slot, deposit, keypair));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(deposits)
|
||||||
|
}
|
215
beacon_node/beacon_chain/test_harness/src/test_case/mod.rs
Normal file
215
beacon_node/beacon_chain/test_harness/src/test_case/mod.rs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
//! Defines execution and testing specs for a `BeaconChainHarness` instance. Supports loading from
|
||||||
|
//! a YAML file.
|
||||||
|
|
||||||
|
use crate::beacon_chain_harness::BeaconChainHarness;
|
||||||
|
use beacon_chain::CheckPoint;
|
||||||
|
use log::{info, warn};
|
||||||
|
use types::*;
|
||||||
|
use types::{
|
||||||
|
attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder,
|
||||||
|
};
|
||||||
|
use yaml_rust::Yaml;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod results;
|
||||||
|
mod state_check;
|
||||||
|
mod yaml_helpers;
|
||||||
|
|
||||||
|
pub use config::Config;
|
||||||
|
pub use results::Results;
|
||||||
|
pub use state_check::StateCheck;
|
||||||
|
|
||||||
|
/// Defines the execution and testing of a `BeaconChainHarness` instantiation.
|
||||||
|
///
|
||||||
|
/// Typical workflow is:
|
||||||
|
///
|
||||||
|
/// 1. Instantiate the `TestCase` from YAML: `let test_case = TestCase::from_yaml(&my_yaml);`
|
||||||
|
/// 2. Execute the test_case: `let result = test_case.execute();`
|
||||||
|
/// 3. Test the results against the test_case: `test_case.assert_result_valid(result);`
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TestCase {
|
||||||
|
/// Defines the execution.
|
||||||
|
pub config: Config,
|
||||||
|
/// Defines tests to run against the execution result.
|
||||||
|
pub results: Results,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of executing a `TestCase`.
|
||||||
|
///
|
||||||
|
pub struct ExecutionResult {
|
||||||
|
/// The canonical beacon chain generated from the execution.
|
||||||
|
pub chain: Vec<CheckPoint>,
|
||||||
|
/// The spec used for execution.
|
||||||
|
pub spec: ChainSpec,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCase {
|
||||||
|
/// Load the test case from a YAML document.
|
||||||
|
pub fn from_yaml(test_case: &Yaml) -> Self {
|
||||||
|
Self {
|
||||||
|
results: Results::from_yaml(&test_case["results"]),
|
||||||
|
config: Config::from_yaml(&test_case["config"]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a `ChainSpec::foundation()`.
|
||||||
|
///
|
||||||
|
/// If specified in `config`, returns it with a modified `epoch_length`.
|
||||||
|
fn spec(&self) -> ChainSpec {
|
||||||
|
let mut spec = ChainSpec::foundation();
|
||||||
|
|
||||||
|
if let Some(n) = self.config.epoch_length {
|
||||||
|
spec.epoch_length = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
spec
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the test case, returning an `ExecutionResult`.
|
||||||
|
pub fn execute(&self) -> ExecutionResult {
|
||||||
|
let spec = self.spec();
|
||||||
|
let validator_count = self.config.deposits_for_chain_start;
|
||||||
|
let slots = self.config.num_slots;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Building BeaconChainHarness with {} validators...",
|
||||||
|
validator_count
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut harness = BeaconChainHarness::new(spec, validator_count);
|
||||||
|
|
||||||
|
info!("Starting simulation across {} slots...", slots);
|
||||||
|
|
||||||
|
// -1 slots because genesis counts as a slot.
|
||||||
|
for slot_height in 0..slots - 1 {
|
||||||
|
// Feed deposits to the BeaconChain.
|
||||||
|
if let Some(ref deposits) = self.config.deposits {
|
||||||
|
for (slot, deposit, keypair) in deposits {
|
||||||
|
if *slot == slot_height {
|
||||||
|
info!("Including deposit at slot height {}.", slot_height);
|
||||||
|
harness.add_deposit(deposit.clone(), Some(keypair.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed proposer slashings to the BeaconChain.
|
||||||
|
if let Some(ref slashings) = self.config.proposer_slashings {
|
||||||
|
for (slot, validator_index) in slashings {
|
||||||
|
if *slot == slot_height {
|
||||||
|
info!(
|
||||||
|
"Including proposer slashing at slot height {} for validator #{}.",
|
||||||
|
slot_height, validator_index
|
||||||
|
);
|
||||||
|
let slashing = build_proposer_slashing(&harness, *validator_index);
|
||||||
|
harness.add_proposer_slashing(slashing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed attester slashings to the BeaconChain.
|
||||||
|
if let Some(ref slashings) = self.config.attester_slashings {
|
||||||
|
for (slot, validator_indices) in slashings {
|
||||||
|
if *slot == slot_height {
|
||||||
|
info!(
|
||||||
|
"Including attester slashing at slot height {} for validators {:?}.",
|
||||||
|
slot_height, validator_indices
|
||||||
|
);
|
||||||
|
let slashing =
|
||||||
|
build_double_vote_attester_slashing(&harness, &validator_indices[..]);
|
||||||
|
harness.add_attester_slashing(slashing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a block or skip a slot.
|
||||||
|
match self.config.skip_slots {
|
||||||
|
Some(ref skip_slots) if skip_slots.contains(&slot_height) => {
|
||||||
|
warn!("Skipping slot at height {}.", slot_height);
|
||||||
|
harness.increment_beacon_chain_slot();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
info!("Producing block at slot height {}.", slot_height);
|
||||||
|
harness.advance_chain_with_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
harness.run_fork_choice();
|
||||||
|
|
||||||
|
info!("Test execution complete!");
|
||||||
|
|
||||||
|
info!("Building chain dump for analysis...");
|
||||||
|
|
||||||
|
ExecutionResult {
|
||||||
|
chain: harness.chain_dump().expect("Chain dump failed."),
|
||||||
|
spec: (*harness.spec).clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that the `ExecutionResult` is consistent with the specifications in `self.results`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics with a message if any result does not match exepectations.
|
||||||
|
pub fn assert_result_valid(&self, execution_result: ExecutionResult) {
|
||||||
|
info!("Verifying test results...");
|
||||||
|
let spec = &execution_result.spec;
|
||||||
|
|
||||||
|
if let Some(num_skipped_slots) = self.results.num_skipped_slots {
|
||||||
|
assert_eq!(
|
||||||
|
execution_result.chain.len(),
|
||||||
|
self.config.num_slots as usize - num_skipped_slots,
|
||||||
|
"actual skipped slots != expected."
|
||||||
|
);
|
||||||
|
info!(
|
||||||
|
"OK: Chain length is {} ({} skipped slots).",
|
||||||
|
execution_result.chain.len(),
|
||||||
|
num_skipped_slots
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref state_checks) = self.results.state_checks {
|
||||||
|
for checkpoint in &execution_result.chain {
|
||||||
|
let state = &checkpoint.beacon_state;
|
||||||
|
|
||||||
|
for state_check in state_checks {
|
||||||
|
let adjusted_state_slot =
|
||||||
|
state.slot - spec.genesis_epoch.start_slot(spec.epoch_length);
|
||||||
|
|
||||||
|
if state_check.slot == adjusted_state_slot {
|
||||||
|
state_check.assert_valid(state, spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `AttesterSlashing` for some `validator_indices`.
|
||||||
|
///
|
||||||
|
/// Signs the message using a `BeaconChainHarness`.
|
||||||
|
fn build_double_vote_attester_slashing(
|
||||||
|
harness: &BeaconChainHarness,
|
||||||
|
validator_indices: &[u64],
|
||||||
|
) -> AttesterSlashing {
|
||||||
|
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
|
||||||
|
harness
|
||||||
|
.validator_sign(validator_index as usize, message, epoch, domain)
|
||||||
|
.expect("Unable to sign AttesterSlashing")
|
||||||
|
};
|
||||||
|
|
||||||
|
AttesterSlashingBuilder::double_vote(validator_indices, signer, &harness.spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `ProposerSlashing` for some `validator_index`.
|
||||||
|
///
|
||||||
|
/// Signs the message using a `BeaconChainHarness`.
|
||||||
|
fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) -> ProposerSlashing {
|
||||||
|
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
|
||||||
|
harness
|
||||||
|
.validator_sign(validator_index as usize, message, epoch, domain)
|
||||||
|
.expect("Unable to sign AttesterSlashing")
|
||||||
|
};
|
||||||
|
|
||||||
|
ProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
use super::state_check::StateCheck;
|
||||||
|
use super::yaml_helpers::as_usize;
|
||||||
|
use yaml_rust::Yaml;
|
||||||
|
|
||||||
|
/// A series of tests to be carried out upon an `ExecutionResult`, returned from executing a
|
||||||
|
/// `TestCase`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Results {
|
||||||
|
pub num_skipped_slots: Option<usize>,
|
||||||
|
pub state_checks: Option<Vec<StateCheck>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Results {
|
||||||
|
/// Load from a YAML document.
|
||||||
|
///
|
||||||
|
/// Expects the `results` section of the YAML document.
|
||||||
|
pub fn from_yaml(yaml: &Yaml) -> Self {
|
||||||
|
Self {
|
||||||
|
num_skipped_slots: as_usize(yaml, "num_skipped_slots"),
|
||||||
|
state_checks: parse_state_checks(yaml),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the `state_checks` section of the YAML document.
|
||||||
|
fn parse_state_checks(yaml: &Yaml) -> Option<Vec<StateCheck>> {
|
||||||
|
let mut states = vec![];
|
||||||
|
|
||||||
|
for state_yaml in yaml["states"].as_vec()? {
|
||||||
|
states.push(StateCheck::from_yaml(state_yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(states)
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
use super::yaml_helpers::{as_u64, as_usize, as_vec_u64};
|
||||||
|
use log::info;
|
||||||
|
use types::*;
|
||||||
|
use yaml_rust::Yaml;
|
||||||
|
|
||||||
|
/// Tests to be conducted upon a `BeaconState` object generated during the execution of a
|
||||||
|
/// `TestCase`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StateCheck {
|
||||||
|
/// Checked against `beacon_state.slot`.
|
||||||
|
pub slot: Slot,
|
||||||
|
/// Checked against `beacon_state.validator_registry.len()`.
|
||||||
|
pub num_validators: Option<usize>,
|
||||||
|
/// A list of validator indices which have been penalized. Must be in ascending order.
|
||||||
|
pub slashed_validators: Option<Vec<u64>>,
|
||||||
|
/// A list of validator indices which have been exited. Must be in ascending order.
|
||||||
|
pub exited_validators: Option<Vec<u64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateCheck {
|
||||||
|
/// Load from a YAML document.
|
||||||
|
///
|
||||||
|
/// Expects the `state_check` section of the YAML document.
|
||||||
|
pub fn from_yaml(yaml: &Yaml) -> Self {
|
||||||
|
Self {
|
||||||
|
slot: Slot::from(as_u64(&yaml, "slot").expect("State must specify slot")),
|
||||||
|
num_validators: as_usize(&yaml, "num_validators"),
|
||||||
|
slashed_validators: as_vec_u64(&yaml, "slashed_validators"),
|
||||||
|
exited_validators: as_vec_u64(&yaml, "exited_validators"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs all checks against a `BeaconState`
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics with an error message if any test fails.
|
||||||
|
pub fn assert_valid(&self, state: &BeaconState, spec: &ChainSpec) {
|
||||||
|
let state_epoch = state.slot.epoch(spec.epoch_length);
|
||||||
|
|
||||||
|
info!("Running state check for slot height {}.", self.slot);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
self.slot,
|
||||||
|
state.slot - spec.genesis_epoch.start_slot(spec.epoch_length),
|
||||||
|
"State slot is invalid."
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(num_validators) = self.num_validators {
|
||||||
|
assert_eq!(
|
||||||
|
state.validator_registry.len(),
|
||||||
|
num_validators,
|
||||||
|
"State validator count != expected."
|
||||||
|
);
|
||||||
|
info!("OK: num_validators = {}.", num_validators);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref slashed_validators) = self.slashed_validators {
|
||||||
|
let actually_slashed_validators: Vec<u64> = state
|
||||||
|
.validator_registry
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, validator)| {
|
||||||
|
if validator.is_penalized_at(state_epoch) {
|
||||||
|
Some(i as u64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
actually_slashed_validators, *slashed_validators,
|
||||||
|
"Slashed validators != expected."
|
||||||
|
);
|
||||||
|
info!("OK: slashed_validators = {:?}.", slashed_validators);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref exited_validators) = self.exited_validators {
|
||||||
|
let actually_exited_validators: Vec<u64> = state
|
||||||
|
.validator_registry
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, validator)| {
|
||||||
|
if validator.is_exited_at(state_epoch) {
|
||||||
|
Some(i as u64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
actually_exited_validators, *exited_validators,
|
||||||
|
"Exited validators != expected."
|
||||||
|
);
|
||||||
|
info!("OK: exited_validators = {:?}.", exited_validators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
use yaml_rust::Yaml;
|
||||||
|
|
||||||
|
pub fn as_usize(yaml: &Yaml, key: &str) -> Option<usize> {
|
||||||
|
yaml[key].as_i64().and_then(|n| Some(n as usize))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_u64(yaml: &Yaml, key: &str) -> Option<u64> {
|
||||||
|
yaml[key].as_i64().and_then(|n| Some(n as u64))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_vec_u64(yaml: &Yaml, key: &str) -> Option<Vec<u64>> {
|
||||||
|
yaml[key].clone().into_vec().and_then(|vec| {
|
||||||
|
Some(
|
||||||
|
vec.iter()
|
||||||
|
.map(|item| item.as_i64().unwrap() as u64)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
@ -1,27 +1,16 @@
|
|||||||
use attester::Signer as AttesterSigner;
|
use attester::Signer as AttesterSigner;
|
||||||
use block_proposer::Signer as BlockProposerSigner;
|
use block_proposer::Signer as BlockProposerSigner;
|
||||||
use std::sync::RwLock;
|
|
||||||
use types::{Keypair, Signature};
|
use types::{Keypair, Signature};
|
||||||
|
|
||||||
/// A test-only struct used to perform signing for a proposer or attester.
|
/// A test-only struct used to perform signing for a proposer or attester.
|
||||||
pub struct LocalSigner {
|
pub struct LocalSigner {
|
||||||
keypair: Keypair,
|
keypair: Keypair,
|
||||||
should_sign: RwLock<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalSigner {
|
impl LocalSigner {
|
||||||
/// Produce a new TestSigner with signing enabled by default.
|
/// Produce a new TestSigner with signing enabled by default.
|
||||||
pub fn new(keypair: Keypair) -> Self {
|
pub fn new(keypair: Keypair) -> Self {
|
||||||
Self {
|
Self { keypair }
|
||||||
keypair,
|
|
||||||
should_sign: RwLock::new(true),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If set to `false`, the service will refuse to sign all messages. Otherwise, all messages
|
|
||||||
/// will be signed.
|
|
||||||
pub fn enable_signing(&self, enabled: bool) {
|
|
||||||
*self.should_sign.write().unwrap() = enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign some message.
|
/// Sign some message.
|
||||||
|
@ -41,6 +41,4 @@ fn it_can_produce_past_first_epoch_boundary() {
|
|||||||
let dump = harness.chain_dump().expect("Chain dump failed.");
|
let dump = harness.chain_dump().expect("Chain dump failed.");
|
||||||
|
|
||||||
assert_eq!(dump.len() as u64, blocks + 1); // + 1 for genesis block.
|
assert_eq!(dump.len() as u64, blocks + 1); // + 1 for genesis block.
|
||||||
|
|
||||||
harness.dump_to_file("/tmp/chaindump.json".to_string(), &dump);
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
|
use self::verify_slashable_attestation::verify_slashable_attestation;
|
||||||
use crate::SlotProcessingError;
|
use crate::SlotProcessingError;
|
||||||
use hashing::hash;
|
use hashing::hash;
|
||||||
use int_to_bytes::int_to_bytes32;
|
use int_to_bytes::int_to_bytes32;
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use ssz::{ssz_encode, TreeHash};
|
use ssz::{ssz_encode, TreeHash};
|
||||||
use types::{
|
use types::*;
|
||||||
AggregatePublicKey, Attestation, BeaconBlock, BeaconState, BeaconStateError, ChainSpec,
|
|
||||||
Crosslink, Epoch, Exit, Fork, Hash256, PendingAttestation, PublicKey, RelativeEpoch, Signature,
|
mod verify_slashable_attestation;
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: define elsehwere.
|
|
||||||
const DOMAIN_PROPOSAL: u64 = 2;
|
|
||||||
const DOMAIN_EXIT: u64 = 3;
|
|
||||||
const DOMAIN_RANDAO: u64 = 4;
|
|
||||||
const PHASE_0_CUSTODY_BIT: bool = false;
|
const PHASE_0_CUSTODY_BIT: bool = false;
|
||||||
const DOMAIN_ATTESTATION: u64 = 1;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -31,10 +26,13 @@ pub enum Error {
|
|||||||
BadRandaoSignature,
|
BadRandaoSignature,
|
||||||
MaxProposerSlashingsExceeded,
|
MaxProposerSlashingsExceeded,
|
||||||
BadProposerSlashing,
|
BadProposerSlashing,
|
||||||
|
MaxAttesterSlashingsExceed,
|
||||||
MaxAttestationsExceeded,
|
MaxAttestationsExceeded,
|
||||||
|
BadAttesterSlashing,
|
||||||
InvalidAttestation(AttestationValidationError),
|
InvalidAttestation(AttestationValidationError),
|
||||||
NoBlockRoot,
|
NoBlockRoot,
|
||||||
MaxDepositsExceeded,
|
MaxDepositsExceeded,
|
||||||
|
BadDeposit,
|
||||||
MaxExitsExceeded,
|
MaxExitsExceeded,
|
||||||
BadExit,
|
BadExit,
|
||||||
BadCustodyReseeds,
|
BadCustodyReseeds,
|
||||||
@ -89,7 +87,7 @@ impl BlockProcessable for BeaconState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn per_block_processing_signature_optional(
|
fn per_block_processing_signature_optional(
|
||||||
state: &mut BeaconState,
|
mut state: &mut BeaconState,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
verify_block_signature: bool,
|
verify_block_signature: bool,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
@ -113,7 +111,7 @@ fn per_block_processing_signature_optional(
|
|||||||
&block_proposer.pubkey,
|
&block_proposer.pubkey,
|
||||||
&block.proposal_root(spec)[..],
|
&block.proposal_root(spec)[..],
|
||||||
&block.signature,
|
&block.signature,
|
||||||
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_PROPOSAL)
|
get_domain(&state.fork, state.current_epoch(spec), spec.domain_proposal)
|
||||||
),
|
),
|
||||||
Error::BadBlockSignature
|
Error::BadBlockSignature
|
||||||
);
|
);
|
||||||
@ -127,7 +125,7 @@ fn per_block_processing_signature_optional(
|
|||||||
&block_proposer.pubkey,
|
&block_proposer.pubkey,
|
||||||
&int_to_bytes32(state.current_epoch(spec).as_u64()),
|
&int_to_bytes32(state.current_epoch(spec).as_u64()),
|
||||||
&block.randao_reveal,
|
&block.randao_reveal,
|
||||||
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_RANDAO)
|
get_domain(&state.fork, state.current_epoch(spec), spec.domain_randao)
|
||||||
),
|
),
|
||||||
Error::BadRandaoSignature
|
Error::BadRandaoSignature
|
||||||
);
|
);
|
||||||
@ -188,7 +186,7 @@ fn per_block_processing_signature_optional(
|
|||||||
.proposal_data_1
|
.proposal_data_1
|
||||||
.slot
|
.slot
|
||||||
.epoch(spec.epoch_length),
|
.epoch(spec.epoch_length),
|
||||||
DOMAIN_PROPOSAL
|
spec.domain_proposal
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Error::BadProposerSlashing
|
Error::BadProposerSlashing
|
||||||
@ -204,7 +202,7 @@ fn per_block_processing_signature_optional(
|
|||||||
.proposal_data_2
|
.proposal_data_2
|
||||||
.slot
|
.slot
|
||||||
.epoch(spec.epoch_length),
|
.epoch(spec.epoch_length),
|
||||||
DOMAIN_PROPOSAL
|
spec.domain_proposal
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Error::BadProposerSlashing
|
Error::BadProposerSlashing
|
||||||
@ -212,6 +210,17 @@ fn per_block_processing_signature_optional(
|
|||||||
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
|
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attester slashings
|
||||||
|
*/
|
||||||
|
ensure!(
|
||||||
|
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
|
||||||
|
Error::MaxAttesterSlashingsExceed
|
||||||
|
);
|
||||||
|
for attester_slashing in &block.body.attester_slashings {
|
||||||
|
verify_slashable_attestation(&mut state, &attester_slashing, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Attestations
|
* Attestations
|
||||||
*/
|
*/
|
||||||
@ -242,7 +251,27 @@ fn per_block_processing_signature_optional(
|
|||||||
Error::MaxDepositsExceeded
|
Error::MaxDepositsExceeded
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: process deposits.
|
// TODO: verify deposit merkle branches.
|
||||||
|
for deposit in &block.body.deposits {
|
||||||
|
debug!(
|
||||||
|
"Processing deposit for pubkey {:?}",
|
||||||
|
deposit.deposit_data.deposit_input.pubkey
|
||||||
|
);
|
||||||
|
state
|
||||||
|
.process_deposit(
|
||||||
|
deposit.deposit_data.deposit_input.pubkey.clone(),
|
||||||
|
deposit.deposit_data.amount,
|
||||||
|
deposit
|
||||||
|
.deposit_data
|
||||||
|
.deposit_input
|
||||||
|
.proof_of_possession
|
||||||
|
.clone(),
|
||||||
|
deposit.deposit_data.deposit_input.withdrawal_credentials,
|
||||||
|
None,
|
||||||
|
spec,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::BadDeposit)?;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Exits
|
* Exits
|
||||||
@ -276,7 +305,7 @@ fn per_block_processing_signature_optional(
|
|||||||
&validator.pubkey,
|
&validator.pubkey,
|
||||||
&exit_message,
|
&exit_message,
|
||||||
&exit.signature,
|
&exit.signature,
|
||||||
get_domain(&state.fork, exit.epoch, DOMAIN_EXIT)
|
get_domain(&state.fork, exit.epoch, spec.domain_exit)
|
||||||
),
|
),
|
||||||
Error::BadProposerSlashing
|
Error::BadProposerSlashing
|
||||||
);
|
);
|
||||||
@ -370,11 +399,7 @@ fn validate_attestation_signature_optional(
|
|||||||
);
|
);
|
||||||
let mut group_public_key = AggregatePublicKey::new();
|
let mut group_public_key = AggregatePublicKey::new();
|
||||||
for participant in participants {
|
for participant in participants {
|
||||||
group_public_key.add(
|
group_public_key.add(&state.validator_registry[participant as usize].pubkey)
|
||||||
state.validator_registry[participant as usize]
|
|
||||||
.pubkey
|
|
||||||
.as_raw(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ensure!(
|
ensure!(
|
||||||
attestation.verify_signature(
|
attestation.verify_signature(
|
||||||
@ -383,7 +408,7 @@ fn validate_attestation_signature_optional(
|
|||||||
get_domain(
|
get_domain(
|
||||||
&state.fork,
|
&state.fork,
|
||||||
attestation.data.slot.epoch(spec.epoch_length),
|
attestation.data.slot.epoch(spec.epoch_length),
|
||||||
DOMAIN_ATTESTATION,
|
spec.domain_attestation,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
AttestationValidationError::BadSignature
|
AttestationValidationError::BadSignature
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
use super::Error;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
macro_rules! ensure {
|
||||||
|
($condition: expr, $result: expr) => {
|
||||||
|
if !$condition {
|
||||||
|
return Err($result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
|
||||||
|
/// otherwise returns an `Err`.
|
||||||
|
pub fn verify_slashable_attestation(
|
||||||
|
state: &mut BeaconState,
|
||||||
|
attester_slashing: &AttesterSlashing,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
||||||
|
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
slashable_attestation_1.data != slashable_attestation_2.data,
|
||||||
|
Error::BadAttesterSlashing
|
||||||
|
);
|
||||||
|
ensure!(
|
||||||
|
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
||||||
|
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
||||||
|
Error::BadAttesterSlashing
|
||||||
|
);
|
||||||
|
ensure!(
|
||||||
|
state.verify_slashable_attestation(&slashable_attestation_1, spec),
|
||||||
|
Error::BadAttesterSlashing
|
||||||
|
);
|
||||||
|
ensure!(
|
||||||
|
state.verify_slashable_attestation(&slashable_attestation_2, spec),
|
||||||
|
Error::BadAttesterSlashing
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut slashable_indices = vec![];
|
||||||
|
for i in &slashable_attestation_1.validator_indices {
|
||||||
|
let validator = state
|
||||||
|
.validator_registry
|
||||||
|
.get(*i as usize)
|
||||||
|
.ok_or_else(|| Error::BadAttesterSlashing)?;
|
||||||
|
|
||||||
|
if slashable_attestation_1.validator_indices.contains(&i)
|
||||||
|
& !validator.is_penalized_at(state.current_epoch(spec))
|
||||||
|
{
|
||||||
|
slashable_indices.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure!(!slashable_indices.is_empty(), Error::BadAttesterSlashing);
|
||||||
|
|
||||||
|
for i in slashable_indices {
|
||||||
|
state.penalize_validator(*i as usize, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -4,6 +4,10 @@ use serde_derive::Serialize;
|
|||||||
use ssz_derive::{Decode, Encode, TreeHash};
|
use ssz_derive::{Decode, Encode, TreeHash};
|
||||||
use test_random_derive::TestRandom;
|
use test_random_derive::TestRandom;
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
pub use builder::AttesterSlashingBuilder;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
|
||||||
pub struct AttesterSlashing {
|
pub struct AttesterSlashing {
|
||||||
pub slashable_attestation_1: SlashableAttestation,
|
pub slashable_attestation_1: SlashableAttestation,
|
||||||
|
96
eth2/types/src/attester_slashing/builder.rs
Normal file
96
eth2/types/src/attester_slashing/builder.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use crate::*;
|
||||||
|
use ssz::TreeHash;
|
||||||
|
|
||||||
|
/// Builds an `AttesterSlashing`.
|
||||||
|
pub struct AttesterSlashingBuilder();
|
||||||
|
|
||||||
|
impl AttesterSlashingBuilder {
|
||||||
|
/// Builds an `AttesterSlashing` that is a double vote.
|
||||||
|
///
|
||||||
|
/// The `signer` function is used to sign the double-vote and accepts:
|
||||||
|
///
|
||||||
|
/// - `validator_index: u64`
|
||||||
|
/// - `message: &[u8]`
|
||||||
|
/// - `epoch: Epoch`
|
||||||
|
/// - `domain: u64`
|
||||||
|
///
|
||||||
|
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||||
|
pub fn double_vote<F>(
|
||||||
|
validator_indices: &[u64],
|
||||||
|
signer: F,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> AttesterSlashing
|
||||||
|
where
|
||||||
|
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
|
||||||
|
{
|
||||||
|
let double_voted_slot = Slot::new(0);
|
||||||
|
let shard = 0;
|
||||||
|
let justified_epoch = Epoch::new(0);
|
||||||
|
let epoch = Epoch::new(0);
|
||||||
|
let hash_1 = Hash256::from(&[1][..]);
|
||||||
|
let hash_2 = Hash256::from(&[2][..]);
|
||||||
|
|
||||||
|
let mut slashable_attestation_1 = SlashableAttestation {
|
||||||
|
validator_indices: validator_indices.to_vec(),
|
||||||
|
data: AttestationData {
|
||||||
|
slot: double_voted_slot,
|
||||||
|
shard,
|
||||||
|
beacon_block_root: hash_1,
|
||||||
|
epoch_boundary_root: hash_1,
|
||||||
|
shard_block_root: hash_1,
|
||||||
|
latest_crosslink: Crosslink {
|
||||||
|
epoch,
|
||||||
|
shard_block_root: hash_1,
|
||||||
|
},
|
||||||
|
justified_epoch,
|
||||||
|
justified_block_root: hash_1,
|
||||||
|
},
|
||||||
|
custody_bitfield: Bitfield::new(),
|
||||||
|
aggregate_signature: AggregateSignature::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut slashable_attestation_2 = SlashableAttestation {
|
||||||
|
validator_indices: validator_indices.to_vec(),
|
||||||
|
data: AttestationData {
|
||||||
|
slot: double_voted_slot,
|
||||||
|
shard,
|
||||||
|
beacon_block_root: hash_2,
|
||||||
|
epoch_boundary_root: hash_2,
|
||||||
|
shard_block_root: hash_2,
|
||||||
|
latest_crosslink: Crosslink {
|
||||||
|
epoch,
|
||||||
|
shard_block_root: hash_2,
|
||||||
|
},
|
||||||
|
justified_epoch,
|
||||||
|
justified_block_root: hash_2,
|
||||||
|
},
|
||||||
|
custody_bitfield: Bitfield::new(),
|
||||||
|
aggregate_signature: AggregateSignature::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let add_signatures = |attestation: &mut SlashableAttestation| {
|
||||||
|
for (i, validator_index) in validator_indices.iter().enumerate() {
|
||||||
|
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
|
||||||
|
data: attestation.data.clone(),
|
||||||
|
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
|
||||||
|
};
|
||||||
|
let message = attestation_data_and_custody_bit.hash_tree_root();
|
||||||
|
let signature = signer(
|
||||||
|
*validator_index,
|
||||||
|
&message[..],
|
||||||
|
epoch,
|
||||||
|
spec.domain_attestation,
|
||||||
|
);
|
||||||
|
attestation.aggregate_signature.add(&signature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add_signatures(&mut slashable_attestation_1);
|
||||||
|
add_signatures(&mut slashable_attestation_2);
|
||||||
|
|
||||||
|
AttesterSlashing {
|
||||||
|
slashable_attestation_1,
|
||||||
|
slashable_attestation_2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,11 @@
|
|||||||
use self::epoch_cache::EpochCache;
|
use self::epoch_cache::EpochCache;
|
||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::{
|
use crate::{validator::StatusFlags, validator_registry::get_active_validator_indices, *};
|
||||||
validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData,
|
|
||||||
Bitfield, ChainSpec, Crosslink, Deposit, DepositData, DepositInput, Epoch, Eth1Data,
|
|
||||||
Eth1DataVote, Fork, Hash256, PendingAttestation, PublicKey, Signature, Slot, Validator,
|
|
||||||
};
|
|
||||||
use bls::verify_proof_of_possession;
|
use bls::verify_proof_of_possession;
|
||||||
use honey_badger_split::SplitExt;
|
use honey_badger_split::SplitExt;
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
use rayon::prelude::*;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -199,10 +196,10 @@ impl BeaconState {
|
|||||||
let mut genesis_state =
|
let mut genesis_state =
|
||||||
BeaconState::genesis_without_validators(genesis_time, latest_eth1_data, spec)?;
|
BeaconState::genesis_without_validators(genesis_time, latest_eth1_data, spec)?;
|
||||||
|
|
||||||
trace!("Processing genesis deposits...");
|
debug!("Processing genesis deposits...");
|
||||||
|
|
||||||
let deposit_data = initial_validator_deposits
|
let deposit_data = initial_validator_deposits
|
||||||
.iter()
|
.par_iter()
|
||||||
.map(|deposit| &deposit.deposit_data)
|
.map(|deposit| &deposit.deposit_data)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -411,6 +408,8 @@ impl BeaconState {
|
|||||||
return Err(Error::InsufficientValidators);
|
return Err(Error::InsufficientValidators);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Shuffling {} validators...", active_validator_indices.len());
|
||||||
|
|
||||||
let committees_per_epoch =
|
let committees_per_epoch =
|
||||||
self.get_epoch_committee_count(active_validator_indices.len(), spec);
|
self.get_epoch_committee_count(active_validator_indices.len(), spec);
|
||||||
|
|
||||||
@ -420,8 +419,7 @@ impl BeaconState {
|
|||||||
committees_per_epoch
|
committees_per_epoch
|
||||||
);
|
);
|
||||||
|
|
||||||
let active_validator_indices: Vec<usize> =
|
let active_validator_indices: Vec<usize> = active_validator_indices.to_vec();
|
||||||
active_validator_indices.iter().cloned().collect();
|
|
||||||
|
|
||||||
let shuffled_active_validator_indices = shuffle_list(
|
let shuffled_active_validator_indices = shuffle_list(
|
||||||
active_validator_indices,
|
active_validator_indices,
|
||||||
@ -1000,6 +998,10 @@ impl BeaconState {
|
|||||||
whistleblower_reward
|
whistleblower_reward
|
||||||
);
|
);
|
||||||
self.validator_registry[validator_index].penalized_epoch = current_epoch;
|
self.validator_registry[validator_index].penalized_epoch = current_epoch;
|
||||||
|
debug!(
|
||||||
|
"Whistleblower {} penalized validator {}.",
|
||||||
|
whistleblower_index, validator_index
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1138,6 +1140,114 @@ impl BeaconState {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify ``bitfield`` against the ``committee_size``.
|
||||||
|
///
|
||||||
|
/// Spec v0.2.0
|
||||||
|
pub fn verify_bitfield(&self, bitfield: &Bitfield, committee_size: usize) -> bool {
|
||||||
|
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in committee_size..(bitfield.num_bytes() * 8) {
|
||||||
|
match bitfield.get(i) {
|
||||||
|
Ok(bit) => {
|
||||||
|
if bit {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify validity of ``slashable_attestation`` fields.
|
||||||
|
///
|
||||||
|
/// Spec v0.2.0
|
||||||
|
pub fn verify_slashable_attestation(
|
||||||
|
&self,
|
||||||
|
slashable_attestation: &SlashableAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> bool {
|
||||||
|
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if slashable_attestation.validator_indices.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
|
||||||
|
if slashable_attestation.validator_indices[i]
|
||||||
|
>= slashable_attestation.validator_indices[i + 1]
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.verify_bitfield(
|
||||||
|
&slashable_attestation.custody_bitfield,
|
||||||
|
slashable_attestation.validator_indices.len(),
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if slashable_attestation.validator_indices.len()
|
||||||
|
> spec.max_indices_per_slashable_vote as usize
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||||
|
let mut message_exists = vec![false; 2];
|
||||||
|
|
||||||
|
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
|
||||||
|
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
|
||||||
|
Ok(bit) => bit,
|
||||||
|
Err(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
message_exists[custody_bit as usize] = true;
|
||||||
|
|
||||||
|
match self.validator_registry.get(*v as usize) {
|
||||||
|
Some(validator) => {
|
||||||
|
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
||||||
|
}
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let message_0 = AttestationDataAndCustodyBit {
|
||||||
|
data: slashable_attestation.data.clone(),
|
||||||
|
custody_bit: false,
|
||||||
|
}
|
||||||
|
.hash_tree_root();
|
||||||
|
let message_1 = AttestationDataAndCustodyBit {
|
||||||
|
data: slashable_attestation.data.clone(),
|
||||||
|
custody_bit: true,
|
||||||
|
}
|
||||||
|
.hash_tree_root();
|
||||||
|
|
||||||
|
let mut messages = vec![];
|
||||||
|
let mut keys = vec![];
|
||||||
|
|
||||||
|
if message_exists[0] {
|
||||||
|
messages.push(&message_0[..]);
|
||||||
|
keys.push(&aggregate_pubs[0]);
|
||||||
|
}
|
||||||
|
if message_exists[1] {
|
||||||
|
messages.push(&message_1[..]);
|
||||||
|
keys.push(&aggregate_pubs[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
slashable_attestation.aggregate_signature.verify_multiple(
|
||||||
|
&messages[..],
|
||||||
|
spec.domain_attestation,
|
||||||
|
&keys[..],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the block root at a recent `slot`.
|
/// Return the block root at a recent `slot`.
|
||||||
///
|
///
|
||||||
/// Spec v0.2.0
|
/// Spec v0.2.0
|
||||||
|
@ -6,6 +6,10 @@ use serde_derive::Serialize;
|
|||||||
use ssz_derive::{Decode, Encode, TreeHash};
|
use ssz_derive::{Decode, Encode, TreeHash};
|
||||||
use test_random_derive::TestRandom;
|
use test_random_derive::TestRandom;
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
pub use builder::ProposerSlashingBuilder;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
|
||||||
pub struct ProposerSlashing {
|
pub struct ProposerSlashing {
|
||||||
pub proposer_index: u64,
|
pub proposer_index: u64,
|
||||||
|
59
eth2/types/src/proposer_slashing/builder.rs
Normal file
59
eth2/types/src/proposer_slashing/builder.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use crate::*;
|
||||||
|
use ssz::TreeHash;
|
||||||
|
|
||||||
|
/// Builds a `ProposerSlashing`.
|
||||||
|
pub struct ProposerSlashingBuilder();
|
||||||
|
|
||||||
|
impl ProposerSlashingBuilder {
|
||||||
|
/// Builds a `ProposerSlashing` that is a double vote.
|
||||||
|
///
|
||||||
|
/// The `signer` function is used to sign the double-vote and accepts:
|
||||||
|
///
|
||||||
|
/// - `validator_index: u64`
|
||||||
|
/// - `message: &[u8]`
|
||||||
|
/// - `epoch: Epoch`
|
||||||
|
/// - `domain: u64`
|
||||||
|
///
|
||||||
|
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||||
|
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
|
||||||
|
where
|
||||||
|
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
|
||||||
|
{
|
||||||
|
let slot = Slot::new(0);
|
||||||
|
let shard = 0;
|
||||||
|
|
||||||
|
let proposal_data_1 = ProposalSignedData {
|
||||||
|
slot,
|
||||||
|
shard,
|
||||||
|
block_root: Hash256::from(&[1][..]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let proposal_data_2 = ProposalSignedData {
|
||||||
|
slot,
|
||||||
|
shard,
|
||||||
|
block_root: Hash256::from(&[2][..]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let proposal_signature_1 = {
|
||||||
|
let message = proposal_data_1.hash_tree_root();
|
||||||
|
let epoch = slot.epoch(spec.epoch_length);
|
||||||
|
let domain = spec.domain_proposal;
|
||||||
|
signer(proposer_index, &message[..], epoch, domain)
|
||||||
|
};
|
||||||
|
|
||||||
|
let proposal_signature_2 = {
|
||||||
|
let message = proposal_data_2.hash_tree_root();
|
||||||
|
let epoch = slot.epoch(spec.epoch_length);
|
||||||
|
let domain = spec.domain_proposal;
|
||||||
|
signer(proposer_index, &message[..], epoch, domain)
|
||||||
|
};
|
||||||
|
|
||||||
|
ProposerSlashing {
|
||||||
|
proposer_index,
|
||||||
|
proposal_data_1,
|
||||||
|
proposal_signature_1,
|
||||||
|
proposal_data_2,
|
||||||
|
proposal_signature_2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield};
|
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec};
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use ssz_derive::{Decode, Encode, TreeHash};
|
use ssz_derive::{Decode, Encode, TreeHash};
|
||||||
@ -12,12 +12,107 @@ pub struct SlashableAttestation {
|
|||||||
pub aggregate_signature: AggregateSignature,
|
pub aggregate_signature: AggregateSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SlashableAttestation {
|
||||||
|
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
|
||||||
|
///
|
||||||
|
/// Spec v0.3.0
|
||||||
|
pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
|
||||||
|
self.data.slot.epoch(spec.epoch_length) == other.data.slot.epoch(spec.epoch_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
|
||||||
|
///
|
||||||
|
/// Spec v0.3.0
|
||||||
|
pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
|
||||||
|
let source_epoch_1 = self.data.justified_epoch;
|
||||||
|
let source_epoch_2 = other.data.justified_epoch;
|
||||||
|
let target_epoch_1 = self.data.slot.epoch(spec.epoch_length);
|
||||||
|
let target_epoch_2 = other.data.slot.epoch(spec.epoch_length);
|
||||||
|
|
||||||
|
(source_epoch_1 < source_epoch_2) && (target_epoch_2 < target_epoch_1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::chain_spec::ChainSpec;
|
||||||
|
use crate::slot_epoch::{Epoch, Slot};
|
||||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||||
use ssz::{ssz_encode, Decodable, TreeHash};
|
use ssz::{ssz_encode, Decodable, TreeHash};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_double_vote_true() {
|
||||||
|
let spec = ChainSpec::foundation();
|
||||||
|
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
|
||||||
|
let slashable_vote_second = create_slashable_attestation(1, 1, &spec);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_double_vote_false() {
|
||||||
|
let spec = ChainSpec::foundation();
|
||||||
|
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
|
||||||
|
let slashable_vote_second = create_slashable_attestation(2, 1, &spec);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_true() {
|
||||||
|
let spec = ChainSpec::foundation();
|
||||||
|
let slashable_vote_first = create_slashable_attestation(2, 1, &spec);
|
||||||
|
let slashable_vote_second = create_slashable_attestation(1, 2, &spec);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_true_realistic() {
|
||||||
|
let spec = ChainSpec::foundation();
|
||||||
|
let slashable_vote_first = create_slashable_attestation(4, 1, &spec);
|
||||||
|
let slashable_vote_second = create_slashable_attestation(3, 2, &spec);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_false_source_epoch_fails() {
|
||||||
|
let spec = ChainSpec::foundation();
|
||||||
|
let slashable_vote_first = create_slashable_attestation(2, 2, &spec);
|
||||||
|
let slashable_vote_second = create_slashable_attestation(1, 1, &spec);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_false_target_epoch_fails() {
|
||||||
|
let spec = ChainSpec::foundation();
|
||||||
|
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
|
||||||
|
let slashable_vote_second = create_slashable_attestation(2, 2, &spec);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_ssz_round_trip() {
|
pub fn test_ssz_round_trip() {
|
||||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||||
@ -40,4 +135,17 @@ mod tests {
|
|||||||
// TODO: Add further tests
|
// TODO: Add further tests
|
||||||
// https://github.com/sigp/lighthouse/issues/170
|
// https://github.com/sigp/lighthouse/issues/170
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_slashable_attestation(
|
||||||
|
slot_factor: u64,
|
||||||
|
justified_epoch: u64,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> SlashableAttestation {
|
||||||
|
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||||
|
let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng);
|
||||||
|
|
||||||
|
slashable_vote.data.slot = Slot::new(slot_factor * spec.epoch_length);
|
||||||
|
slashable_vote.data.justified_epoch = Epoch::new(justified_epoch);
|
||||||
|
slashable_vote
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,9 +54,19 @@ pub struct Validator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Validator {
|
impl Validator {
|
||||||
/// This predicate indicates if the validator represented by this record is considered "active" at `slot`.
|
/// Returns `true` if the validator is considered active at some epoch.
|
||||||
pub fn is_active_at(&self, slot: Epoch) -> bool {
|
pub fn is_active_at(&self, epoch: Epoch) -> bool {
|
||||||
self.activation_epoch <= slot && slot < self.exit_epoch
|
self.activation_epoch <= epoch && epoch < self.exit_epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the validator is considered exited at some epoch.
|
||||||
|
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
|
||||||
|
self.exit_epoch <= epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the validator is considered penalized at some epoch.
|
||||||
|
pub fn is_penalized_at(&self, epoch: Epoch) -> bool {
|
||||||
|
self.penalized_epoch <= epoch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
eth2/utils/bls/src/aggregate_public_key.rs
Normal file
24
eth2/utils/bls/src/aggregate_public_key.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use super::PublicKey;
|
||||||
|
use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey;
|
||||||
|
|
||||||
|
/// A single BLS signature.
|
||||||
|
///
|
||||||
|
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
|
||||||
|
/// serialization).
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct AggregatePublicKey(RawAggregatePublicKey);
|
||||||
|
|
||||||
|
impl AggregatePublicKey {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
AggregatePublicKey(RawAggregatePublicKey::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, public_key: &PublicKey) {
|
||||||
|
self.0.add(public_key.as_raw())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying signature.
|
||||||
|
pub fn as_raw(&self) -> &RawAggregatePublicKey {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
use super::{AggregatePublicKey, Signature};
|
use super::{AggregatePublicKey, Signature};
|
||||||
use bls_aggregates::AggregateSignature as RawAggregateSignature;
|
use bls_aggregates::{
|
||||||
|
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
|
||||||
|
};
|
||||||
use serde::ser::{Serialize, Serializer};
|
use serde::ser::{Serialize, Serializer};
|
||||||
use ssz::{
|
use ssz::{
|
||||||
decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
|
decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
|
||||||
@ -33,7 +35,38 @@ impl AggregateSignature {
|
|||||||
domain: u64,
|
domain: u64,
|
||||||
aggregate_public_key: &AggregatePublicKey,
|
aggregate_public_key: &AggregatePublicKey,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.0.verify(msg, domain, aggregate_public_key)
|
self.0.verify(msg, domain, aggregate_public_key.as_raw())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify this AggregateSignature against multiple AggregatePublickeys with multiple Messages.
|
||||||
|
///
|
||||||
|
/// All PublicKeys related to a Message should be aggregated into one AggregatePublicKey.
|
||||||
|
/// Each AggregatePublicKey has a 1:1 ratio with a 32 byte Message.
|
||||||
|
pub fn verify_multiple(
|
||||||
|
&self,
|
||||||
|
messages: &[&[u8]],
|
||||||
|
domain: u64,
|
||||||
|
aggregate_public_keys: &[&AggregatePublicKey],
|
||||||
|
) -> bool {
|
||||||
|
// TODO: the API for `RawAggregatePublicKey` shoudn't need to take an owned
|
||||||
|
// `AggregatePublicKey`. There is an issue to fix this, but in the meantime we need to
|
||||||
|
// clone.
|
||||||
|
//
|
||||||
|
// https://github.com/sigp/signature-schemes/issues/10
|
||||||
|
let aggregate_public_keys: Vec<RawAggregatePublicKey> = aggregate_public_keys
|
||||||
|
.iter()
|
||||||
|
.map(|pk| pk.as_raw())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Messages are concatenated into one long message.
|
||||||
|
let mut msg: Vec<u8> = vec![];
|
||||||
|
for message in messages {
|
||||||
|
msg.extend_from_slice(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.verify_multiple(&msg[..], domain, &aggregate_public_keys[..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
extern crate bls_aggregates;
|
extern crate bls_aggregates;
|
||||||
extern crate ssz;
|
extern crate ssz;
|
||||||
|
|
||||||
|
mod aggregate_public_key;
|
||||||
mod aggregate_signature;
|
mod aggregate_signature;
|
||||||
mod keypair;
|
mod keypair;
|
||||||
mod public_key;
|
mod public_key;
|
||||||
mod secret_key;
|
mod secret_key;
|
||||||
mod signature;
|
mod signature;
|
||||||
|
|
||||||
|
pub use crate::aggregate_public_key::AggregatePublicKey;
|
||||||
pub use crate::aggregate_signature::AggregateSignature;
|
pub use crate::aggregate_signature::AggregateSignature;
|
||||||
pub use crate::keypair::Keypair;
|
pub use crate::keypair::Keypair;
|
||||||
pub use crate::public_key::PublicKey;
|
pub use crate::public_key::PublicKey;
|
||||||
pub use crate::secret_key::SecretKey;
|
pub use crate::secret_key::SecretKey;
|
||||||
pub use crate::signature::Signature;
|
pub use crate::signature::Signature;
|
||||||
|
|
||||||
pub use self::bls_aggregates::AggregatePublicKey;
|
|
||||||
|
|
||||||
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
|
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
|
||||||
|
|
||||||
use ssz::ssz_encode;
|
use ssz::ssz_encode;
|
||||||
|
Loading…
Reference in New Issue
Block a user