Skip to content

Commit

Permalink
feat(optimistic_block): shuffle incoming receipts based on prev hash (#…
Browse files Browse the repository at this point in the history
…12777)

#10584

There is another unexpected dependency on block hash during chunk
application - it is used in `shuffle_receipt_proofs` to shuffle new
receipts targeting our shard. As block hash is unknown in optimistic
block, I replace it with prev block hash with a protocol upgrade.

Additionally, use `Chunks` instead of `Block` in
`collect_incoming_receipts_from_chunks` - it will be useful for
optimistic block execution flow later.

## Security

Some block producer can brute force hashes to get salt which gives more
desirable order. But block hash is prone to that as well, prev hash has
equivalent safety.

## Upgrade

I use `BlockHeightForReceiptId` feature because it has similar goal and
it is going to be released soon. Adding separate feature makes code
harder to read I think.

## Testing

IMO it makes sense only to check consistency of the shuffling, I don't
see much value in checking that specific salt is used. So I claim that
running existing tests is enough to test that change.
  • Loading branch information
Longarithm authored Jan 24, 2025
1 parent 81538fc commit 532d157
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 20 deletions.
46 changes: 32 additions & 14 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::orphan::{Orphan, OrphanBlockPool};
use crate::rayon_spawner::RayonAsyncComputationSpawner;
use crate::resharding::manager::ReshardingManager;
use crate::resharding::types::ReshardingSender;
use crate::sharding::shuffle_receipt_proofs;
use crate::sharding::{get_receipts_shuffle_salt, shuffle_receipt_proofs};
use crate::signature_verification::{
verify_block_header_signature_with_epoch_manager, verify_block_vrf,
verify_chunk_header_signature_with_epoch_manager,
Expand Down Expand Up @@ -55,7 +55,7 @@ use near_chain_primitives::error::{BlockKnownError, Error, LogTransientStorageEr
use near_epoch_manager::shard_tracker::ShardTracker;
use near_epoch_manager::EpochManagerAdapter;
use near_primitives::bandwidth_scheduler::BandwidthRequests;
use near_primitives::block::{genesis_chunks, Block, BlockValidityError, Chunks, Tip};
use near_primitives::block::{genesis_chunks, Block, BlockValidityError, Chunks, MaybeNew, Tip};
use near_primitives::block_header::BlockHeader;
use near_primitives::challenge::{
BlockDoubleSign, Challenge, ChallengeBody, ChallengesResult, ChunkProofs, ChunkState,
Expand Down Expand Up @@ -1418,28 +1418,30 @@ impl Chain {
Ok(false)
}

/// Collect all incoming receipts generated in `block`, return a map from target shard id to the
/// list of receipts that the target shard receives.
/// Collect all incoming receipts from chunks included in some block,
/// return a map from target shard id to the list of receipts that the
/// target shard receives.
/// The receipts are sorted by the order that they will be processed.
/// Note that the receipts returned in this function do not equal all receipts that will be
/// processed as incoming receipts in this block, because that may include incoming receipts
/// generated in previous blocks too, if some shards in the previous blocks did not produce
/// new chunks.
pub fn collect_incoming_receipts_from_block(
pub fn collect_incoming_receipts_from_chunks(
&self,
me: &Option<AccountId>,
block: &Block,
chunks: &Chunks,
prev_block_hash: &CryptoHash,
shuffle_salt: &CryptoHash,
) -> Result<HashMap<ShardId, Vec<ReceiptProof>>, Error> {
if !self.care_about_any_shard_or_part(me, *block.header().prev_hash())? {
if !self.care_about_any_shard_or_part(me, *prev_block_hash)? {
return Ok(HashMap::new());
}
let block_height = block.header().height();
let mut receipt_proofs_by_shard_id = HashMap::new();

for chunk_header in block.chunks().iter_deprecated() {
if !chunk_header.is_new_chunk(block_height) {
for chunk_header in chunks.iter() {
let MaybeNew::New(chunk_header) = chunk_header else {
continue;
}
};
let partial_encoded_chunk =
self.chain_store.get_partial_chunk(&chunk_header.chunk_hash()).unwrap();
for receipt in partial_encoded_chunk.prev_outgoing_receipts().iter() {
Expand All @@ -1453,7 +1455,7 @@ impl Chain {
}
// sort the receipts deterministically so the order that they will be processed is deterministic
for (_, receipt_proofs) in receipt_proofs_by_shard_id.iter_mut() {
shuffle_receipt_proofs(receipt_proofs, block.hash());
shuffle_receipt_proofs(receipt_proofs, shuffle_salt);
}

Ok(receipt_proofs_by_shard_id)
Expand Down Expand Up @@ -2345,7 +2347,14 @@ impl Chain {
}

self.ping_missing_chunks(me, prev_hash, block)?;
let incoming_receipts = self.collect_incoming_receipts_from_block(me, block)?;

let receipts_shuffle_salt = get_receipts_shuffle_salt(self.epoch_manager.as_ref(), &block)?;
let incoming_receipts = self.collect_incoming_receipts_from_chunks(
me,
&block.chunks(),
&prev_hash,
receipts_shuffle_salt,
)?;

// Check if block can be finalized and drop it otherwise.
self.check_if_finalizable(header)?;
Expand Down Expand Up @@ -3165,8 +3174,17 @@ impl Chain {
for pending_block in blocks_catch_up_state.pending_blocks.drain(..) {
let block = self.chain_store.get_block(&pending_block)?.clone();
let prev_block = self.chain_store.get_block(block.header().prev_hash())?.clone();
let prev_hash = *prev_block.hash();

let receipts_shuffle_salt =
get_receipts_shuffle_salt(self.epoch_manager.as_ref(), &block)?;
let receipts_by_shard = self.collect_incoming_receipts_from_chunks(
me,
&block.chunks(),
&prev_hash,
receipts_shuffle_salt,
)?;

let receipts_by_shard = self.collect_incoming_receipts_from_block(me, &block)?;
let work = self.apply_chunks_preprocessing(
me,
&block,
Expand Down
22 changes: 20 additions & 2 deletions chain/chain/src/sharding.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
use near_epoch_manager::EpochManagerAdapter;
use near_primitives::block::Block;
use near_primitives::errors::EpochError;
use near_primitives::hash::CryptoHash;
use near_primitives::version::ProtocolFeature;
use rand::seq::SliceRandom;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;

/// Gets salt for shuffling receipts grouped by **source shards** before
/// processing them in the target shard.
pub fn get_receipts_shuffle_salt<'a>(
epoch_manager: &dyn EpochManagerAdapter,
block: &'a Block,
) -> Result<&'a CryptoHash, EpochError> {
let protocol_version = epoch_manager.get_epoch_protocol_version(&block.header().epoch_id())?;
if ProtocolFeature::BlockHeightForReceiptId.enabled(protocol_version) {
Ok(block.header().prev_hash())
} else {
Ok(block.hash())
}
}

pub fn shuffle_receipt_proofs<ReceiptProofType>(
receipt_proofs: &mut Vec<ReceiptProofType>,
block_hash: &CryptoHash,
shuffle_salt: &CryptoHash,
) {
let mut slice = [0u8; 32];
slice.copy_from_slice(block_hash.as_ref());
slice.copy_from_slice(shuffle_salt.as_ref());
let mut rng: ChaCha20Rng = SeedableRng::from_seed(slice);
receipt_proofs.shuffle(&mut rng);
}
Expand Down
5 changes: 3 additions & 2 deletions chain/chain/src/stateless_validation/chunk_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::chain::{
use crate::rayon_spawner::RayonAsyncComputationSpawner;
use crate::resharding::event_type::ReshardingEventType;
use crate::resharding::manager::ReshardingManager;
use crate::sharding::shuffle_receipt_proofs;
use crate::sharding::{get_receipts_shuffle_salt, shuffle_receipt_proofs};
use crate::stateless_validation::processing_tracker::ProcessingDoneTracker;
use crate::store::filter_incoming_receipts_for_shard;
use crate::types::{
Expand Down Expand Up @@ -546,7 +546,8 @@ fn validate_source_receipt_proofs(
);

// Arrange the receipts in the order in which they should be applied.
shuffle_receipt_proofs(&mut block_receipt_proofs, block.hash());
let receipts_shuffle_salt = get_receipts_shuffle_salt(epoch_manager, block)?;
shuffle_receipt_proofs(&mut block_receipt_proofs, receipts_shuffle_salt);
for proof in block_receipt_proofs {
receipts_to_apply.extend(proof.0.iter().cloned());
}
Expand Down
5 changes: 3 additions & 2 deletions tools/replay-archive/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use near_chain::chain::{
ShardContext, StorageContext,
};
use near_chain::migrations::check_if_block_is_first_with_chunk_of_version;
use near_chain::sharding::shuffle_receipt_proofs;
use near_chain::sharding::{get_receipts_shuffle_salt, shuffle_receipt_proofs};
use near_chain::stateless_validation::chunk_endorsement::validate_chunk_endorsements_in_block;
use near_chain::stateless_validation::chunk_validation::apply_result_to_chunk_extra;
use near_chain::types::StorageDataSource;
Expand Down Expand Up @@ -492,8 +492,9 @@ impl ReplayController {
}

let mut store_update = self.chain_store.store_update();
let receipts_shuffle_salt = get_receipts_shuffle_salt(self.epoch_manager.as_ref(), block)?;
for (shard_id, mut receipts) in receipt_proofs_by_shard_id.into_iter() {
shuffle_receipt_proofs(&mut receipts, block_hash);
shuffle_receipt_proofs(&mut receipts, receipts_shuffle_salt);
store_update.save_incoming_receipt(&block_hash, shard_id, Arc::new(receipts));
}
store_update.commit().unwrap();
Expand Down

0 comments on commit 532d157

Please sign in to comment.