Skip to content

Commit 2719fd2

Browse files
authored
Merge of #5385
2 parents f33ce8c + 482a996 commit 2719fd2

File tree

5 files changed

+208
-19
lines changed

5 files changed

+208
-19
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,14 +413,14 @@ pub struct BeaconChain<T: BeaconChainTypes> {
413413
/// Maintains a record of slashable message seen over the gossip network or RPC.
414414
pub observed_slashable: RwLock<ObservedSlashable<T::EthSpec>>,
415415
/// Maintains a record of which validators have submitted voluntary exits.
416-
pub(crate) observed_voluntary_exits: Mutex<ObservedOperations<SignedVoluntaryExit, T::EthSpec>>,
416+
pub observed_voluntary_exits: Mutex<ObservedOperations<SignedVoluntaryExit, T::EthSpec>>,
417417
/// Maintains a record of which validators we've seen proposer slashings for.
418-
pub(crate) observed_proposer_slashings: Mutex<ObservedOperations<ProposerSlashing, T::EthSpec>>,
418+
pub observed_proposer_slashings: Mutex<ObservedOperations<ProposerSlashing, T::EthSpec>>,
419419
/// Maintains a record of which validators we've seen attester slashings for.
420-
pub(crate) observed_attester_slashings:
420+
pub observed_attester_slashings:
421421
Mutex<ObservedOperations<AttesterSlashing<T::EthSpec>, T::EthSpec>>,
422422
/// Maintains a record of which validators we've seen BLS to execution changes for.
423-
pub(crate) observed_bls_to_execution_changes:
423+
pub observed_bls_to_execution_changes:
424424
Mutex<ObservedOperations<SignedBlsToExecutionChange, T::EthSpec>>,
425425
/// Provides information from the Ethereum 1 (PoW) chain.
426426
pub eth1_chain: Option<Eth1Chain<T::Eth1Chain, T::EthSpec>>,

beacon_node/beacon_chain/src/observed_operations.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ impl<T: ObservableOperation<E>, E: EthSpec> ObservedOperations<T, E> {
153153
self.current_fork = head_fork;
154154
}
155155
}
156+
157+
/// Reset the cache. MUST ONLY BE USED IN TESTS.
158+
pub fn __reset_for_testing_only(&mut self) {
159+
self.observed_validator_indices.clear();
160+
}
156161
}
157162

158163
impl<T: ObservableOperation<E> + VerifyOperationAt<E>, E: EthSpec> ObservedOperations<T, E> {

beacon_node/beacon_chain/tests/op_verification.rs

Lines changed: 192 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
33
#![cfg(not(debug_assertions))]
44

5-
use beacon_chain::observed_operations::ObservationOutcome;
6-
use beacon_chain::test_utils::{
7-
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType,
5+
use beacon_chain::{
6+
observed_operations::ObservationOutcome,
7+
test_utils::{
8+
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType,
9+
},
10+
BeaconChainError,
811
};
912
use lazy_static::lazy_static;
1013
use sloggers::{null::NullLoggerBuilder, Build};
14+
use state_processing::per_block_processing::errors::{
15+
AttesterSlashingInvalid, BlockOperationError, ExitInvalid, ProposerSlashingInvalid,
16+
};
1117
use std::sync::Arc;
1218
use store::{LevelDB, StoreConfig};
1319
use tempfile::{tempdir, TempDir};
@@ -119,6 +125,75 @@ async fn voluntary_exit() {
119125
));
120126
}
121127

128+
#[tokio::test]
129+
async fn voluntary_exit_duplicate_in_state() {
130+
let db_path = tempdir().unwrap();
131+
let store = get_store(&db_path);
132+
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
133+
let spec = &harness.chain.spec;
134+
135+
harness
136+
.extend_chain(
137+
(E::slots_per_epoch() * (spec.shard_committee_period + 1)) as usize,
138+
BlockStrategy::OnCanonicalHead,
139+
AttestationStrategy::AllValidators,
140+
)
141+
.await;
142+
harness.advance_slot();
143+
144+
// Exit a validator.
145+
let exited_validator = 0;
146+
let exit =
147+
harness.make_voluntary_exit(exited_validator, Epoch::new(spec.shard_committee_period));
148+
let ObservationOutcome::New(verified_exit) = harness
149+
.chain
150+
.verify_voluntary_exit_for_gossip(exit.clone())
151+
.unwrap()
152+
else {
153+
panic!("exit should verify");
154+
};
155+
harness.chain.import_voluntary_exit(verified_exit);
156+
157+
// Make a new block to include the exit.
158+
harness
159+
.extend_chain(
160+
1,
161+
BlockStrategy::OnCanonicalHead,
162+
AttestationStrategy::AllValidators,
163+
)
164+
.await;
165+
166+
// Verify validator is actually exited.
167+
assert_ne!(
168+
harness
169+
.get_current_state()
170+
.validators()
171+
.get(exited_validator as usize)
172+
.unwrap()
173+
.exit_epoch,
174+
spec.far_future_epoch
175+
);
176+
177+
// Clear the in-memory gossip cache & try to verify the same exit on gossip.
178+
// It should still fail because gossip verification should check the validator's `exit_epoch`
179+
// field in the head state.
180+
harness
181+
.chain
182+
.observed_voluntary_exits
183+
.lock()
184+
.__reset_for_testing_only();
185+
186+
assert!(matches!(
187+
harness
188+
.chain
189+
.verify_voluntary_exit_for_gossip(exit)
190+
.unwrap_err(),
191+
BeaconChainError::ExitValidationError(BlockOperationError::Invalid(
192+
ExitInvalid::AlreadyExited(index)
193+
)) if index == exited_validator
194+
));
195+
}
196+
122197
#[test]
123198
fn proposer_slashing() {
124199
let db_path = tempdir().unwrap();
@@ -171,6 +246,63 @@ fn proposer_slashing() {
171246
));
172247
}
173248

249+
#[tokio::test]
250+
async fn proposer_slashing_duplicate_in_state() {
251+
let db_path = tempdir().unwrap();
252+
let store = get_store(&db_path);
253+
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
254+
255+
// Slash a validator.
256+
let slashed_validator = 0;
257+
let slashing = harness.make_proposer_slashing(slashed_validator);
258+
let ObservationOutcome::New(verified_slashing) = harness
259+
.chain
260+
.verify_proposer_slashing_for_gossip(slashing.clone())
261+
.unwrap()
262+
else {
263+
panic!("slashing should verify");
264+
};
265+
harness.chain.import_proposer_slashing(verified_slashing);
266+
267+
// Make a new block to include the slashing.
268+
harness
269+
.extend_chain(
270+
1,
271+
BlockStrategy::OnCanonicalHead,
272+
AttestationStrategy::AllValidators,
273+
)
274+
.await;
275+
276+
// Verify validator is actually slashed.
277+
assert!(
278+
harness
279+
.get_current_state()
280+
.validators()
281+
.get(slashed_validator as usize)
282+
.unwrap()
283+
.slashed
284+
);
285+
286+
// Clear the in-memory gossip cache & try to verify the same slashing on gossip.
287+
// It should still fail because gossip verification should check the validator's `slashed` field
288+
// in the head state.
289+
harness
290+
.chain
291+
.observed_proposer_slashings
292+
.lock()
293+
.__reset_for_testing_only();
294+
295+
assert!(matches!(
296+
harness
297+
.chain
298+
.verify_proposer_slashing_for_gossip(slashing)
299+
.unwrap_err(),
300+
BeaconChainError::ProposerSlashingValidationError(BlockOperationError::Invalid(
301+
ProposerSlashingInvalid::ProposerNotSlashable(index)
302+
)) if index == slashed_validator
303+
));
304+
}
305+
174306
#[test]
175307
fn attester_slashing() {
176308
let db_path = tempdir().unwrap();
@@ -241,3 +373,60 @@ fn attester_slashing() {
241373
ObservationOutcome::AlreadyKnown
242374
));
243375
}
376+
377+
#[tokio::test]
378+
async fn attester_slashing_duplicate_in_state() {
379+
let db_path = tempdir().unwrap();
380+
let store = get_store(&db_path);
381+
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
382+
383+
// Slash a validator.
384+
let slashed_validator = 0;
385+
let slashing = harness.make_attester_slashing(vec![slashed_validator]);
386+
let ObservationOutcome::New(verified_slashing) = harness
387+
.chain
388+
.verify_attester_slashing_for_gossip(slashing.clone())
389+
.unwrap()
390+
else {
391+
panic!("slashing should verify");
392+
};
393+
harness.chain.import_attester_slashing(verified_slashing);
394+
395+
// Make a new block to include the slashing.
396+
harness
397+
.extend_chain(
398+
1,
399+
BlockStrategy::OnCanonicalHead,
400+
AttestationStrategy::AllValidators,
401+
)
402+
.await;
403+
404+
// Verify validator is actually slashed.
405+
assert!(
406+
harness
407+
.get_current_state()
408+
.validators()
409+
.get(slashed_validator as usize)
410+
.unwrap()
411+
.slashed
412+
);
413+
414+
// Clear the in-memory gossip cache & try to verify the same slashing on gossip.
415+
// It should still fail because gossip verification should check the validator's `slashed` field
416+
// in the head state.
417+
harness
418+
.chain
419+
.observed_attester_slashings
420+
.lock()
421+
.__reset_for_testing_only();
422+
423+
assert!(matches!(
424+
harness
425+
.chain
426+
.verify_attester_slashing_for_gossip(slashing)
427+
.unwrap_err(),
428+
BeaconChainError::AttesterSlashingValidationError(BlockOperationError::Invalid(
429+
AttesterSlashingInvalid::NoSlashableIndices
430+
))
431+
));
432+
}

consensus/state_processing/src/per_block_processing/process_operations.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,9 @@ pub fn process_attester_slashings<T: EthSpec>(
231231
spec: &ChainSpec,
232232
) -> Result<(), BlockProcessingError> {
233233
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
234-
verify_attester_slashing(state, attester_slashing, verify_signatures, spec)
235-
.map_err(|e| e.into_with_index(i))?;
236-
237234
let slashable_indices =
238-
get_slashable_indices(state, attester_slashing).map_err(|e| e.into_with_index(i))?;
235+
verify_attester_slashing(state, attester_slashing, verify_signatures, spec)
236+
.map_err(|e| e.into_with_index(i))?;
239237

240238
for i in slashable_indices {
241239
slash_validator(state, i as usize, None, ctxt, spec)?;

consensus/state_processing/src/per_block_processing/verify_attester_slashing.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@ fn error(reason: Invalid) -> BlockOperationError<Invalid> {
1313
/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of
1414
/// the given state.
1515
///
16-
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for
16+
/// Returns `Ok(indices)` with `indices` being a non-empty vec of validator indices in ascending
17+
/// order if the `AttesterSlashing` is valid. Otherwise returns `Err(e)` with the reason for
1718
/// invalidity.
18-
///
19-
/// Spec v0.12.1
2019
pub fn verify_attester_slashing<T: EthSpec>(
2120
state: &BeaconState<T>,
2221
attester_slashing: &AttesterSlashing<T>,
2322
verify_signatures: VerifySignatures,
2423
spec: &ChainSpec,
25-
) -> Result<()> {
24+
) -> Result<Vec<u64>> {
2625
let attestation_1 = &attester_slashing.attestation_1;
2726
let attestation_2 = &attester_slashing.attestation_2;
2827

@@ -38,14 +37,12 @@ pub fn verify_attester_slashing<T: EthSpec>(
3837
is_valid_indexed_attestation(state, attestation_2, verify_signatures, spec)
3938
.map_err(|e| error(Invalid::IndexedAttestation2Invalid(e)))?;
4039

41-
Ok(())
40+
get_slashable_indices(state, attester_slashing)
4241
}
4342

4443
/// For a given attester slashing, return the indices able to be slashed in ascending order.
4544
///
46-
/// Returns Ok(indices) if `indices.len() > 0`.
47-
///
48-
/// Spec v0.12.1
45+
/// Returns Ok(indices) if `indices.len() > 0`
4946
pub fn get_slashable_indices<T: EthSpec>(
5047
state: &BeaconState<T>,
5148
attester_slashing: &AttesterSlashing<T>,

0 commit comments

Comments
 (0)