Skip to content

Commit

Permalink
[stateless_validation] Add helper functions for chunk endorsement val…
Browse files Browse the repository at this point in the history
…idation (#10464)

Couple of awesome changes here...
- Expose validator_mandates_config as we need this to calculating total
stake. See function `does_chunk_have_enough_stake`
- `Vec<Option<Box<Signature>>>`??? Wth is this??? Type aliased it to
`ChunkEndorsementSignatures`
- Added `validate_signature` function to `ChunkEndorsement` that checks
whether a signature + public key is valid or not. This is used for
verifying chunk endorsement signature in header.
- `ChunkValidatorAssignments` just got coooooler!! Now we have a
`does_chunk_have_enough_stake` function that calculates whether we have
2/3rd stake and we are good to go?
- Some other minor improvements to `ChunkValidatorAssignments` like
adding `ordered_chunk_validators` function and `contains` function
  • Loading branch information
Shreyan Gupta authored Jan 18, 2024
1 parent de2a723 commit cd32e1a
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 19 deletions.
8 changes: 8 additions & 0 deletions chain/chain/src/test_utils/kv_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use near_primitives::types::{
AccountId, ApprovalStake, Balance, BlockHeight, EpochHeight, EpochId, Gas, Nonce, NumShards,
ShardId, StateChangesForResharding, StateRoot, StateRootNode, ValidatorInfoIdentifier,
};
use near_primitives::validator_mandates::ValidatorMandatesConfig;
use near_primitives::version::{ProtocolVersion, PROTOCOL_VERSION};
use near_primitives::views::{
AccessKeyInfoView, AccessKeyList, CallResult, ContractCodeView, EpochValidatorInfo,
Expand Down Expand Up @@ -706,6 +707,13 @@ impl EpochManagerAdapter for MockEpochManager {
Ok(chunk_producers[index].account_id().clone())
}

fn get_validator_mandates_config(
&self,
_epoch_id: &EpochId,
) -> Result<ValidatorMandatesConfig, EpochError> {
Ok(Default::default())
}

fn get_chunk_validator_assignments(
&self,
_epoch_id: &EpochId,
Expand Down
5 changes: 2 additions & 3 deletions chain/client/src/chunk_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl ChunkValidator {
chunk_header.shard_id(),
chunk_header.height_created(),
)?;
if !chunk_validator_assignments.chunk_validators.contains(my_signer.validator_id()) {
if !chunk_validator_assignments.contains(my_signer.validator_id()) {
return Err(Error::NotAChunkValidator);
}

Expand Down Expand Up @@ -517,8 +517,7 @@ impl Client {
chunk_header.shard_id(),
chunk_header.height_created(),
)?
.chunk_validators
.clone();
.ordered_chunk_validators();
let prev_chunk = self.chain.get_chunk(&prev_chunk_header.chunk_hash())?;
let (main_state_transition, implicit_transitions, applied_receipts_hash) =
self.collect_state_transition_data(&chunk_header, prev_chunk_header)?;
Expand Down
16 changes: 15 additions & 1 deletion chain/epoch-manager/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use near_primitives::types::{
AccountId, ApprovalStake, Balance, BlockHeight, EpochHeight, EpochId, ShardId,
ValidatorInfoIdentifier,
};
use near_primitives::validator_mandates::ValidatorMandatesConfig;
use near_primitives::version::ProtocolVersion;
use near_primitives::views::EpochValidatorInfo;
use near_store::{ShardUId, StoreUpdate};
Expand Down Expand Up @@ -188,6 +189,11 @@ pub trait EpochManagerAdapter: Send + Sync {
shard_id: ShardId,
) -> Result<AccountId, EpochError>;

fn get_validator_mandates_config(
&self,
epoch_id: &EpochId,
) -> Result<ValidatorMandatesConfig, EpochError>;

/// Gets the chunk validators for a given height and shard.
fn get_chunk_validator_assignments(
&self,
Expand Down Expand Up @@ -658,6 +664,14 @@ impl EpochManagerAdapter for EpochManagerHandle {
Ok(epoch_manager.get_chunk_producer_info(epoch_id, height, shard_id)?.take_account_id())
}

fn get_validator_mandates_config(
&self,
epoch_id: &EpochId,
) -> Result<ValidatorMandatesConfig, EpochError> {
let epoch_manager = self.read();
Ok(epoch_manager.get_epoch_info(epoch_id)?.get_validator_mandates_config())
}

fn get_chunk_validator_assignments(
&self,
epoch_id: &EpochId,
Expand Down Expand Up @@ -981,7 +995,7 @@ impl EpochManagerAdapter for EpochManagerHandle {
chunk_header.shard_id(),
chunk_header.height_created(),
)?;
if !chunk_validator_assignments.chunk_validators.contains(&endorsement.account_id) {
if !chunk_validator_assignments.contains(&endorsement.account_id) {
return Err(Error::NotAValidator);
}
let validator =
Expand Down
8 changes: 4 additions & 4 deletions chain/epoch-manager/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2678,10 +2678,10 @@ fn test_verify_chunk_endorsements() {
let epoch_id = epoch_manager.get_epoch_id(&h[1]).unwrap();

// verify if we have one chunk validator
let chunk_validators =
&epoch_manager.get_chunk_validator_assignments(&epoch_id, 0, 1).unwrap().chunk_validators;
assert_eq!(chunk_validators.len(), 1);
assert!(chunk_validators.contains(&account_id));
let chunk_validator_assignments =
&epoch_manager.get_chunk_validator_assignments(&epoch_id, 0, 1).unwrap();
assert_eq!(chunk_validator_assignments.ordered_chunk_validators().len(), 1);
assert!(chunk_validator_assignments.contains(&account_id));

// verify if the test signer has same public key as the chunk validator
let (validator, _) =
Expand Down
2 changes: 1 addition & 1 deletion chain/network/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ pub enum NetworkRequests {
/// A challenge to invalidate a block.
Challenge(Challenge),
/// A chunk's state witness.
ChunkStateWitness(HashSet<AccountId>, ChunkStateWitness),
ChunkStateWitness(Vec<AccountId>, ChunkStateWitness),
/// Message for a chunk endorsement, sent by a chunk validator to the block producer.
ChunkEndorsement(AccountId, ChunkEndorsement),
}
Expand Down
4 changes: 3 additions & 1 deletion core/primitives/src/block_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ pub struct BlockBodyV2 {
// If the chunk_validator did not endorse the chunk, the signature is None.
// For cases of missing chunk, we include the chunk endorsements from the previous
// block just like we do for chunks.
pub chunk_endorsements: Vec<Vec<Option<Box<Signature>>>>,
pub chunk_endorsements: Vec<ChunkEndorsementSignatures>,
}

pub type ChunkEndorsementSignatures = Vec<Option<Box<Signature>>>;

// For now, we only have one version of block body.
// Eventually with ChunkValidation, we would include ChunkEndorsement in BlockBodyV2
#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Eq, Debug)]
Expand Down
43 changes: 40 additions & 3 deletions core/primitives/src/chunk_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::validator_signer::ValidatorSigner;
use borsh::{BorshDeserialize, BorshSerialize};
use near_crypto::{PublicKey, Signature};
use near_primitives_core::hash::CryptoHash;
use near_primitives_core::types::AccountId;
use near_primitives_core::types::{AccountId, Balance};

/// The state witness for a chunk; proves the state transition that the
/// chunk attests to.
Expand Down Expand Up @@ -122,6 +122,16 @@ impl ChunkEndorsement {
pub fn chunk_hash(&self) -> &ChunkHash {
&self.inner.chunk_hash
}

pub fn validate_signature(
chunk_hash: ChunkHash,
signature: &Signature,
public_key: &PublicKey,
) -> bool {
let inner = ChunkEndorsementInner::new(chunk_hash);
let data = borsh::to_vec(&inner).unwrap();
signature.verify(&data, public_key)
}
}

/// This is the part of the chunk endorsement that is actually being signed.
Expand Down Expand Up @@ -160,13 +170,40 @@ pub struct StoredChunkStateTransitionData {

#[derive(Debug, Default)]
pub struct ChunkValidatorAssignments {
pub assignments: Vec<(AccountId, AssignmentWeight)>,
pub chunk_validators: HashSet<AccountId>,
assignments: Vec<(AccountId, AssignmentWeight)>,
chunk_validators: HashSet<AccountId>,
}

impl ChunkValidatorAssignments {
pub fn new(assignments: Vec<(AccountId, AssignmentWeight)>) -> Self {
let chunk_validators = assignments.iter().map(|(id, _)| id.clone()).collect();
Self { assignments, chunk_validators }
}

pub fn contains(&self, account_id: &AccountId) -> bool {
self.chunk_validators.contains(account_id)
}

pub fn ordered_chunk_validators(&self) -> Vec<AccountId> {
self.assignments.iter().map(|(id, _)| id.clone()).collect()
}

/// Returns true if the chunk has enough stake to be considered valid.
/// We require that at least 2/3 of the total stake of the chunk is endorsed by chunk_validators.
pub fn does_chunk_have_enough_stake(
&self,
endorsed_chunk_validators: &HashSet<AccountId>,
stake_per_mandate: Balance,
) -> bool {
let mut total_stake: Balance = 0;
let mut endorsed_stake: Balance = 0;
for (account_id, weight) in &self.assignments {
let stake = weight.num_mandates as Balance * stake_per_mandate + weight.partial_weight;
total_stake += stake;
if endorsed_chunk_validators.contains(account_id) {
endorsed_stake += stake;
}
}
endorsed_stake > total_stake * 2 / 3
}
}
15 changes: 11 additions & 4 deletions core/primitives/src/epoch_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,9 @@ pub mod epoch_info {
use crate::epoch_manager::ValidatorWeight;
use crate::types::validator_stake::{ValidatorStake, ValidatorStakeIter};
use crate::types::{BlockChunkValidatorStats, ValidatorKickoutReason};
use crate::validator_mandates::{ValidatorMandates, ValidatorMandatesAssignment};
use crate::validator_mandates::{
ValidatorMandates, ValidatorMandatesAssignment, ValidatorMandatesConfig,
};
use crate::version::PROTOCOL_VERSION;
use borsh::{BorshDeserialize, BorshSerialize};
use near_primitives_core::hash::CryptoHash;
Expand Down Expand Up @@ -1090,12 +1092,17 @@ pub mod epoch_info {
}
}

pub fn get_validator_mandates_config(&self) -> ValidatorMandatesConfig {
match &self {
Self::V1(_) | Self::V2(_) | Self::V3(_) => Default::default(),
Self::V4(v4) => v4.validator_mandates.config,
}
}

pub fn sample_chunk_validators(&self, height: BlockHeight) -> ValidatorMandatesAssignment {
// Chunk validator assignment was introduced with `V4`.
match &self {
Self::V1(_) => Default::default(),
Self::V2(_) => Default::default(),
Self::V3(_) => Default::default(),
Self::V1(_) | Self::V2(_) | Self::V3(_) => Default::default(),
Self::V4(v4) => {
let mut rng = Self::chunk_validate_rng(&v4.rng_seed, height);
v4.validator_mandates.sample(&mut rng)
Expand Down
4 changes: 2 additions & 2 deletions core/primitives/src/validator_mandates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use rand::{seq::SliceRandom, Rng};
)]
pub struct ValidatorMandatesConfig {
/// The amount of stake that corresponds to one mandate.
stake_per_mandate: Balance,
pub stake_per_mandate: Balance,
/// The minimum number of mandates required per shard.
min_mandates_per_shard: usize,
/// The number of shards for the referenced epoch.
Expand Down Expand Up @@ -53,7 +53,7 @@ impl ValidatorMandatesConfig {
)]
pub struct ValidatorMandates {
/// The configuration applied to the mandates.
config: ValidatorMandatesConfig,
pub config: ValidatorMandatesConfig,
/// Each element represents a validator mandate held by the validator with the given id.
///
/// The id of a validator who holds `n >= 0` mandates occurs `n` times in the vector.
Expand Down

0 comments on commit cd32e1a

Please sign in to comment.