Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
brentstone committed Mar 21, 2023
1 parent 190d373 commit 8bd9cb4
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 8 deletions.
17 changes: 17 additions & 0 deletions core/src/ledger/storage_api/collections/lazy_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,23 @@ where
storage.read(&self.get_data_key(index))
}

/// Read the first element
pub fn front<S>(&self, storage: &S) -> Result<Option<T>>
where
S: StorageRead,
{
self.get(storage, 0)
}

/// Read the last element
pub fn back<S>(&self, storage: &S) -> Result<Option<T>>
where
S: StorageRead,
{
let len = self.len(storage)?;
self.get(storage, len - 1)
}

/// An iterator visiting all elements. The iterator element type is
/// `Result<T>`, because iterator's call to `next` may fail with e.g. out of
/// gas or data decoding error.
Expand Down
12 changes: 6 additions & 6 deletions proof_of_stake/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ use crate::{
bond_tokens, bonds_and_unbonds, consensus_validator_set_handle,
copy_validator_sets_and_positions, find_validator_by_raw_hash,
get_num_consensus_validators, init_genesis,
insert_validator_into_validator_set,
insert_validator_into_validator_set, process_slashes,
read_below_capacity_validator_set_addresses_with_stake,
read_consensus_validator_set_addresses_with_stake, read_total_stake,
read_validator_delta_value, read_validator_stake, staking_token_address,
total_deltas_handle, unbond_handle, unbond_tokens, update_validator_deltas,
update_validator_set, validator_consensus_key_handle,
validator_set_update_tendermint, validator_state_handle, withdraw_tokens,
write_validator_address_raw_hash,
read_validator_delta_value, read_validator_stake, slash,
staking_token_address, total_deltas_handle, unbond_handle, unbond_tokens,
update_validator_deltas, update_validator_set,
validator_consensus_key_handle, validator_set_update_tendermint,
validator_state_handle, withdraw_tokens, write_validator_address_raw_hash,
};

proptest! {
Expand Down
145 changes: 143 additions & 2 deletions proof_of_stake/src/tests/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ use super::arb_genesis_validators;
use crate::parameters::testing::{arb_pos_params, arb_rate};
use crate::parameters::PosParams;
use crate::types::{
BondId, GenesisValidator, ReverseOrdTokenAmount, ValidatorState,
WeightedValidator,
BondId, GenesisValidator, ReverseOrdTokenAmount, Slash, SlashType,
ValidatorState, WeightedValidator,
};
use crate::{
consensus_validator_set_handle, enqueued_slashes_handle,
validator_state_handle,
};

prop_state_machine! {
Expand Down Expand Up @@ -61,6 +65,10 @@ struct AbstractPosState {
/// Unbonded bonds. The outer key for Epoch is pipeline + unbonding offset
/// from epoch in which the unbond is applied.
unbonds: BTreeMap<Epoch, HashMap<BondId, token::Amount>>,
/// Validator slashes post-processing
validator_slashes: BTreeMap<Address, Vec<Slash>>,
/// Enqueued slashes pre-processing
enqueued_slashes: BTreeMap<Epoch, Vec<Slash>>,
}

/// The PoS system under test
Expand Down Expand Up @@ -94,6 +102,12 @@ enum Transition {
Withdraw {
id: BondId,
},
Misbehavior {
address: Address,
slash_type: SlashType,
infraction_epoch: Epoch,
height: u64,
},
}

impl StateMachineTest for ConcretePosState {
Expand Down Expand Up @@ -345,6 +359,36 @@ impl StateMachineTest for ConcretePosState {
// equal to the withdrawn amount
assert_eq!(src_balance_post - src_balance_pre, withdrawn,);
}
Transition::Misbehavior {
address,
slash_type,
infraction_epoch,
height,
} => {
let current_epoch = state.current_epoch();
// Record the slash evidence
super::slash(
&mut state.s,
&params,
current_epoch,
infraction_epoch,
height,
slash_type,
&address,
)
.unwrap();

// Apply some post-conditions
state.check_misbehavior_post_conditions(
&params,
current_epoch,
infraction_epoch,
slash_type,
&address,
);

// Any others?
}
}
state
}
Expand Down Expand Up @@ -626,6 +670,51 @@ impl ConcretePosState {
.contains(&weighted);
assert!(in_consensus ^ in_bc);
}

fn check_misbehavior_post_conditions(
&self,
params: &PosParams,
detected_epoch: Epoch,
infraction_epoch: Epoch,
slash_type: SlashType,
validator: &Address,
) {
// Validator state jailed and validator removed from the consensus set
for offset in 0..=params.pipeline_len {
assert_eq!(
validator_state_handle(validator)
.get(&self.s, detected_epoch + offset, params)
.unwrap(),
Some(ValidatorState::Jailed)
);
let in_consensus = consensus_validator_set_handle()
.at(&(detected_epoch + offset))
.iter(&self.s)
.unwrap()
.any(|res| {
let (_, val_address) = res.unwrap();
val_address == validator.clone()
});
assert!(!in_consensus);
}

// `enqueued_slashes` contains the slash element just added
let processing_epoch = infraction_epoch + params.unbonding_len;
let slash = enqueued_slashes_handle()
.at(&processing_epoch)
.at(validator)
.back(&self.s)
.unwrap();
if let Some(slash) = slash {
assert_eq!(slash.epoch, infraction_epoch);
assert_eq!(slash.r#type, slash_type);
assert_eq!(slash.rate, slash_type.get_slash_rate(params));
} else {
panic!("Could not find the slash enqueued");
}

// TODO: Any others?
}
}

impl AbstractStateMachine for AbstractPosState {
Expand All @@ -650,6 +739,8 @@ impl AbstractStateMachine for AbstractPosState {
consensus_set: Default::default(),
below_capacity_set: Default::default(),
validator_states: Default::default(),
validator_slashes: Default::default(),
enqueued_slashes: Default::default(),
};

for GenesisValidator {
Expand Down Expand Up @@ -730,6 +821,7 @@ impl AbstractStateMachine for AbstractPosState {
Just(Transition::NextEpoch),
add_arb_bond_amount(state),
arb_delegation(state),
arb_slash(state),
(
address::testing::arb_established_address(),
key::testing::arb_common_keypair(),
Expand Down Expand Up @@ -863,6 +955,14 @@ impl AbstractStateMachine for AbstractPosState {
// Remove any epochs that have no unbonds left
state.unbonds.retain(|_epoch, unbonds| !unbonds.is_empty());
}
Transition::Misbehavior {
address,
slash_type,
infraction_epoch,
height,
} => {
todo!();
}
}
state
}
Expand Down Expand Up @@ -922,6 +1022,14 @@ impl AbstractStateMachine for AbstractPosState {
// The amount must be available to unbond
&& is_withdrawable
}
Transition::Misbehavior {
address,
slash_type,
infraction_epoch,
height,
} => {
todo!();
}
}
}
}
Expand Down Expand Up @@ -1203,3 +1311,36 @@ fn arb_delegation(
pub fn arb_bond_amount() -> impl Strategy<Value = token::Amount> {
(1_u64..10).prop_map(token::Amount::from)
}

/// Arbitrary validator misbehavior
pub fn arb_slash(
state: &AbstractPosState,
) -> impl Strategy<Value = Transition> {
let validators = state.consensus_set.iter().fold(
Vec::new(),
|mut acc, (_epoch, vals)| {
for vals in vals.values() {
for validator in vals {
acc.push(validator.clone());
}
}
acc
},
);
let arb_validator = prop::sample::select(validators);
let slash_types =
vec![SlashType::LightClientAttack, SlashType::DuplicateVote];
let arb_type = prop::sample::select(slash_types);
let current_epoch = state.epoch.0;
let arb_epoch = (current_epoch - state.params.unbonding_len
..=current_epoch)
.prop_map(Epoch::from);
(arb_validator, arb_type, arb_epoch).prop_map(
|(validator, slash_type, infraction_epoch)| Transition::Misbehavior {
address: validator,
slash_type,
infraction_epoch,
height: 0,
},
)
}

0 comments on commit 8bd9cb4

Please sign in to comment.