From 81538fcb70fb6fb4d70b28cdac03a710a1cd0f10 Mon Sep 17 00:00:00 2001 From: Razvan Barbascu Date: Thu, 23 Jan 2025 19:45:35 +0000 Subject: [PATCH] feat(optimistic_block): produce (#12761) We continue the implementation of Optimistic block #10584, by adding the logic to produce the block as soon as the previous block is done. If available, the optimistic block will be used in the production of the block to use the same timestamp. --------- Co-authored-by: Aleksandr Logunov --- chain/chain/src/tests/simple_chain.rs | 1 + chain/client/src/client.rs | 189 +++++++++++++++--- chain/client/src/client_actor.rs | 92 ++++++--- chain/client/src/metrics.rs | 8 + chain/client/src/sync/header.rs | 1 + chain/client/src/test_utils/client.rs | 1 + chain/client/src/tests/process_blocks.rs | 31 +++ chain/client/src/tests/query_client.rs | 1 + .../network/src/network_protocol/testonly.rs | 1 + core/primitives-core/src/version.rs | 6 +- core/primitives/benches/serialization.rs | 1 + core/primitives/src/block.rs | 27 ++- core/primitives/src/optimistic_block.rs | 17 +- core/primitives/src/test_utils.rs | 1 + core/primitives/src/utils.rs | 25 +++ .../src/tests/client/challenges.rs | 2 + .../src/tests/client/process_blocks.rs | 3 + 17 files changed, 325 insertions(+), 82 deletions(-) diff --git a/chain/chain/src/tests/simple_chain.rs b/chain/chain/src/tests/simple_chain.rs index 09babc82ad7..25f917abbf0 100644 --- a/chain/chain/src/tests/simple_chain.rs +++ b/chain/chain/src/tests/simple_chain.rs @@ -92,6 +92,7 @@ fn build_chain_with_orphans() { CryptoHash::default(), clock, None, + None, ); assert_matches!(chain.process_block_test(&None, block).unwrap_err(), Error::Orphan); assert_matches!( diff --git a/chain/client/src/client.rs b/chain/client/src/client.rs index 759d051d76b..c15cbd963c9 100644 --- a/chain/client/src/client.rs +++ b/chain/client/src/client.rs @@ -57,6 +57,7 @@ use near_primitives::errors::EpochError; use near_primitives::hash::CryptoHash; use near_primitives::merkle::{merklize, MerklePath, PartialMerkleTree}; use near_primitives::network::PeerId; +use near_primitives::optimistic_block::OptimisticBlock; use near_primitives::receipt::Receipt; use near_primitives::sharding::{ EncodedShardChunk, PartialEncodedChunk, ShardChunk, ShardChunkHeader, StateSyncInfo, @@ -198,6 +199,8 @@ pub struct Client { chunk_distribution_network: Option, /// Upgrade schedule which determines when the client starts voting for new protocol versions. upgrade_schedule: ProtocolUpgradeVotingSchedule, + /// Produced optimistic block. + last_optimistic_block_produced: Option, } impl AsRef for Client { @@ -399,6 +402,7 @@ impl Client { partial_witness_adapter, chunk_distribution_network, upgrade_schedule, + last_optimistic_block_produced: None, }) } @@ -541,6 +545,137 @@ impl Client { Ok(true) } + fn pre_block_production_check( + &self, + prev_header: &BlockHeader, + height: BlockHeight, + validator_signer: &Arc, + ) -> Result<(), Error> { + // Check that we are were called at the block that we are producer for. + let epoch_id = + self.epoch_manager.get_epoch_id_from_prev_block(&prev_header.hash()).unwrap(); + let next_block_proposer = self.epoch_manager.get_block_producer(&epoch_id, height)?; + + let protocol_version = self + .epoch_manager + .get_epoch_protocol_version(&epoch_id) + .expect("Epoch info should be ready at this point"); + if protocol_version > PROTOCOL_VERSION { + panic!("The client protocol version is older than the protocol version of the network. Please update nearcore. Client protocol version:{}, network protocol version {}", PROTOCOL_VERSION, protocol_version); + } + + if !self.can_produce_block( + &prev_header, + height, + validator_signer.validator_id(), + &next_block_proposer, + )? { + debug!(target: "client", me=?validator_signer.validator_id(), ?next_block_proposer, "Should reschedule block"); + return Err(Error::BlockProducer("Should reschedule".to_string())); + } + + let (validator_stake, _) = self.epoch_manager.get_validator_by_account_id( + &epoch_id, + &prev_header.hash(), + &next_block_proposer, + )?; + + let validator_pk = validator_stake.take_public_key(); + if validator_pk != validator_signer.public_key() { + debug!(target: "client", + local_validator_key = ?validator_signer.public_key(), + ?validator_pk, + "Local validator key does not match expected validator key, skipping optimistic block production"); + let err = Error::BlockProducer("Local validator key mismatch".to_string()); + #[cfg(not(feature = "test_features"))] + return Err(err); + #[cfg(feature = "test_features")] + match self.adv_produce_blocks { + None | Some(AdvProduceBlocksMode::OnlyValid) => return Err(err), + Some(AdvProduceBlocksMode::All) => {} + } + } + Ok(()) + } + + pub fn is_optimistic_block_done(&self, next_height: BlockHeight) -> bool { + self.last_optimistic_block_produced + .as_ref() + .filter(|ob| ob.inner.block_height == next_height) + .is_some() + } + + pub fn save_optimistic_block(&mut self, optimistic_block: &OptimisticBlock) { + if let Some(old_block) = self.last_optimistic_block_produced.as_ref() { + if old_block.inner.block_height == optimistic_block.inner.block_height { + warn!(target: "client", + height=old_block.inner.block_height, + old_previous_hash=?old_block.inner.prev_block_hash, + new_previous_hash=?optimistic_block.inner.prev_block_hash, + "Optimistic block already exists, replacing"); + } + } + self.last_optimistic_block_produced = Some(optimistic_block.clone()); + } + + /// Produce optimistic block for given `height` on top of chain head. + /// Either returns optimistic block or error. + pub fn produce_optimistic_block_on_head( + &mut self, + height: BlockHeight, + ) -> Result, Error> { + let _span = + tracing::debug_span!(target: "client", "produce_optimistic_block_on_head", height) + .entered(); + + let head = self.chain.head()?; + assert_eq!( + head.epoch_id, + self.epoch_manager.get_epoch_id_from_prev_block(&head.prev_block_hash).unwrap() + ); + + let prev_hash = head.last_block_hash; + let prev_header = self.chain.get_block_header(&prev_hash)?; + + let validator_signer: Arc = + self.validator_signer.get().ok_or_else(|| { + Error::BlockProducer("Called without block producer info.".to_string()) + })?; + + if let Err(err) = self.pre_block_production_check(&prev_header, height, &validator_signer) { + debug!(target: "client", height, ?err, "Skipping optimistic block production."); + return Ok(None); + } + + debug!( + target: "client", + validator=?validator_signer.validator_id(), + height=height, + prev_height=prev_header.height(), + prev_hash=format_hash(prev_hash), + "Producing optimistic block", + ); + + #[cfg(feature = "sandbox")] + let sandbox_delta_time = Some(self.sandbox_delta_time()); + #[cfg(not(feature = "sandbox"))] + let sandbox_delta_time = None; + + // TODO(#10584): Add debug information about the block production in self.block_production_info + + let optimistic_block = OptimisticBlock::produce( + &prev_header, + height, + &*validator_signer, + self.clock.clone(), + sandbox_delta_time, + ); + + metrics::OPTIMISTIC_BLOCK_PRODUCED_TOTAL.inc(); + + Ok(Some(optimistic_block)) + } + /// Produce block if we are block producer for given block `height`. /// Either returns produced block (not applied) or error. pub fn produce_block(&mut self, height: BlockHeight) -> Result, Error> { @@ -583,49 +718,38 @@ impl Client { let validator_signer = self.validator_signer.get().ok_or_else(|| { Error::BlockProducer("Called without block producer info.".to_string()) })?; - + let optimistic_block = self + .last_optimistic_block_produced + .as_ref() + .filter(|ob| { + // Make sure that the optimistic block is produced on the same previous block. + if ob.inner.prev_block_hash == prev_hash { + return true; + } + warn!(target: "client", + height=height, + prev_hash=?prev_hash, + optimistic_block_prev_hash=?ob.inner.prev_block_hash, + "Optimistic block was constructed on different block, discarding it"); + false + }) + .cloned(); // Check that we are were called at the block that we are producer for. let epoch_id = self.epoch_manager.get_epoch_id_from_prev_block(&prev_hash).unwrap(); - let next_block_proposer = self.epoch_manager.get_block_producer(&epoch_id, height)?; let prev = self.chain.get_block_header(&prev_hash)?; let prev_height = prev.height(); let prev_epoch_id = *prev.epoch_id(); let prev_next_bp_hash = *prev.next_bp_hash(); - // Check and update the doomslug tip here. This guarantees that our endorsement will be in the - // doomslug witness. Have to do it before checking the ability to produce a block. - let _ = self.check_and_update_doomslug_tip()?; - - if !self.can_produce_block( - &prev, - height, - validator_signer.validator_id(), - &next_block_proposer, - )? { - debug!(target: "client", me=?validator_signer.validator_id(), ?next_block_proposer, "Should reschedule block"); + if let Err(err) = self.pre_block_production_check(&prev, height, &validator_signer) { + debug!(target: "client", height, ?err, "Skipping block production"); return Ok(None); } - let (validator_stake, _) = self.epoch_manager.get_validator_by_account_id( - &epoch_id, - &prev_hash, - &next_block_proposer, - )?; - let validator_pk = validator_stake.take_public_key(); - if validator_pk != validator_signer.public_key() { - debug!(target: "client", - local_validator_key = ?validator_signer.public_key(), - ?validator_pk, - "Local validator key does not match expected validator key, skipping block production"); - #[cfg(not(feature = "test_features"))] - return Ok(None); - #[cfg(feature = "test_features")] - match self.adv_produce_blocks { - None | Some(AdvProduceBlocksMode::OnlyValid) => return Ok(None), - Some(AdvProduceBlocksMode::All) => {} - } - } + // Check and update the doomslug tip here. This guarantees that our endorsement will be in the + // doomslug witness. Have to do it before checking the ability to produce a block. + let _ = self.check_and_update_doomslug_tip()?; let new_chunks = self .chunk_inclusion_tracker @@ -812,6 +936,7 @@ impl Client { block_merkle_root, self.clock.clone(), sandbox_delta_time, + optimistic_block, ); // Update latest known even before returning block out, to prevent race conditions. diff --git a/chain/client/src/client_actor.rs b/chain/client/src/client_actor.rs index 75c1904dd97..429f6f6f340 100644 --- a/chain/client/src/client_actor.rs +++ b/chain/client/src/client_actor.rs @@ -1117,42 +1117,58 @@ impl ClientActorInner { } } + let prev_block_hash = &head.last_block_hash; for height in latest_known.height + 1..=self.client.doomslug.get_largest_height_crossing_threshold() { let next_block_producer_account = self.client.epoch_manager.get_block_producer(&epoch_id, height)?; - if me == next_block_producer_account { - self.client.chunk_inclusion_tracker.prepare_chunk_headers_ready_for_inclusion( - &head.last_block_hash, - &mut self.client.chunk_endorsement_tracker, - )?; - let num_chunks = self - .client - .chunk_inclusion_tracker - .num_chunk_headers_ready_for_inclusion(&epoch_id, &head.last_block_hash); - let shard_ids = self.client.epoch_manager.shard_ids(&epoch_id).unwrap(); - let have_all_chunks = head.height == 0 || num_chunks == shard_ids.len(); + if me != next_block_producer_account { + continue; + } - if self.client.doomslug.ready_to_produce_block( - height, - have_all_chunks, - log_block_production_info, - ) { - self.client - .chunk_inclusion_tracker - .record_endorsement_metrics(&head.last_block_hash, &shard_ids); - if let Err(err) = self.produce_block(height, signer) { - // If there is an error, report it and let it retry on the next loop step. - error!(target: "client", height, "Block production failed: {}", err); - } else { - self.post_block_production(); - } + self.client.chunk_inclusion_tracker.prepare_chunk_headers_ready_for_inclusion( + prev_block_hash, + &mut self.client.chunk_endorsement_tracker, + )?; + let num_chunks = self + .client + .chunk_inclusion_tracker + .num_chunk_headers_ready_for_inclusion(&epoch_id, prev_block_hash); + let shard_ids = self.client.epoch_manager.shard_ids(&epoch_id).unwrap(); + let have_all_chunks = head.height == 0 || num_chunks == shard_ids.len(); + + if self.client.doomslug.ready_to_produce_block( + height, + have_all_chunks, + log_block_production_info, + ) { + self.client + .chunk_inclusion_tracker + .record_endorsement_metrics(prev_block_hash, &shard_ids); + if let Err(err) = self.produce_block(height, signer) { + // If there is an error, report it and let it retry on the next loop step. + error!(target: "client", height, "Block production failed: {}", err); + } else { + self.post_block_production(); } } } + let protocol_version = self.client.epoch_manager.get_epoch_protocol_version(&epoch_id)?; + if !ProtocolFeature::ProduceOptimisticBlock.enabled(protocol_version) { + return Ok(()); + } + let optimistic_block_height = self.client.doomslug.get_timer_height(); + if me != self.client.epoch_manager.get_block_producer(&epoch_id, optimistic_block_height)? { + return Ok(()); + } + if let Err(err) = self.produce_optimistic_block(optimistic_block_height) { + // If there is an error, report it and let it retry. + error!(target: "client", optimistic_block_height, ?err, "Optimistic block production failed!"); + } + Ok(()) } @@ -1372,6 +1388,32 @@ impl ClientActorInner { } } + /// Produce optimistic block if we are block producer for given `next_height` height. + fn produce_optimistic_block(&mut self, next_height: BlockHeight) -> Result<(), Error> { + let _span = tracing::debug_span!(target: "client", "produce_optimistic_block", next_height) + .entered(); + // Check if optimistic block is already produced + if self.client.is_optimistic_block_done(next_height) { + return Ok(()); + } + + let Some(optimistic_block) = self.client.produce_optimistic_block_on_head(next_height)? + else { + return Ok(()); + }; + + /* TODO(#10584): If we produced the optimistic block, send it out before we save it. + self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests( + NetworkRequests::OptimisticBlock { optimistic_block: block.clone() }, + )); + */ + + // We’ve produced the optimistic block, mark it as done so we don't produce it again. + self.client.save_optimistic_block(&optimistic_block); + + Ok(()) + } + fn send_chunks_metrics(&mut self, block: &Block) { let chunks = block.chunks(); for (chunk, &included) in chunks.iter_deprecated().zip(block.header().chunk_mask().iter()) { diff --git a/chain/client/src/metrics.rs b/chain/client/src/metrics.rs index 971225cc790..c0e4b854772 100644 --- a/chain/client/src/metrics.rs +++ b/chain/client/src/metrics.rs @@ -14,6 +14,14 @@ pub(crate) static BLOCK_PRODUCED_TOTAL: LazyLock = LazyLock::new(|| .unwrap() }); +pub(crate) static OPTIMISTIC_BLOCK_PRODUCED_TOTAL: LazyLock = LazyLock::new(|| { + try_create_int_counter( + "near_optimistic_block_produced_total", + "Total number of optimistic blocks produced since starting this node", + ) + .unwrap() +}); + pub(crate) static CHUNK_PRODUCED_TOTAL: LazyLock = LazyLock::new(|| { try_create_int_counter( "near_chunk_produced_total", diff --git a/chain/client/src/sync/header.rs b/chain/client/src/sync/header.rs index 78ee10be416..850b2be0e1d 100644 --- a/chain/client/src/sync/header.rs +++ b/chain/client/src/sync/header.rs @@ -815,6 +815,7 @@ mod test { block_merkle_tree.root(), clock.clock(), None, + None, ); block_merkle_tree.insert(*block.hash()); chain2.process_block_header(block.header(), &mut Vec::new()).unwrap(); // just to validate diff --git a/chain/client/src/test_utils/client.rs b/chain/client/src/test_utils/client.rs index 05796b71fca..337eb59b434 100644 --- a/chain/client/src/test_utils/client.rs +++ b/chain/client/src/test_utils/client.rs @@ -276,6 +276,7 @@ pub fn create_chunk( block_merkle_tree.root(), client.clock.clone(), None, + None, ); ( ProduceChunkResult { diff --git a/chain/client/src/tests/process_blocks.rs b/chain/client/src/tests/process_blocks.rs index 01b34005e24..70e2ead29f3 100644 --- a/chain/client/src/tests/process_blocks.rs +++ b/chain/client/src/tests/process_blocks.rs @@ -8,6 +8,7 @@ use near_network::types::{NetworkRequests, PeerManagerMessageRequest}; use near_primitives::block::Block; use near_primitives::congestion_info::CongestionInfo; use near_primitives::network::PeerId; +use near_primitives::optimistic_block::OptimisticBlock; use near_primitives::sharding::ShardChunkHeader; use near_primitives::sharding::ShardChunkHeaderV3; use near_primitives::test_utils::create_test_signer; @@ -287,3 +288,33 @@ fn test_bad_congestion_info_corrupt_allowed_shard() { fn test_bad_congestion_info_none() { test_bad_congestion_info_impl(BadCongestionInfoMode::None); } + +// Helper function to check that a block was produced from an optimistic block +fn check_block_produced_from_optimistic_block(block: &Block, optimistic_block: &OptimisticBlock) { + assert_eq!(block.header().height(), optimistic_block.inner.block_height, "height"); + assert_eq!( + block.header().prev_hash(), + &optimistic_block.inner.prev_block_hash, + "previous hash" + ); + assert_eq!(block.header().raw_timestamp(), optimistic_block.inner.block_timestamp, "timestamp"); + assert_eq!(block.header().random_value(), &optimistic_block.inner.random_value, "random value"); +} + +// Testing the production and application of optimistic blocks +#[test] +fn test_process_optimistic_block() { + let mut env = TestEnv::default_builder().num_shards(4).mock_epoch_managers().build(); + let prev_block = env.clients[0].produce_block(1).unwrap().unwrap(); + env.process_block(0, prev_block, Provenance::PRODUCED); + assert!(!env.clients[0].is_optimistic_block_done(2), "Optimistic block should not be ready"); + let optimistic_block = env.clients[0].produce_optimistic_block_on_head(2).unwrap().unwrap(); + // Store optimistic block to be used at block production. + env.clients[0].save_optimistic_block(&optimistic_block); + assert!(env.clients[0].is_optimistic_block_done(2), "Optimistic block should be ready"); + + // TODO(#10584): Process chunks with optimistic block + + let block = env.clients[0].produce_block(2).unwrap().unwrap(); + check_block_produced_from_optimistic_block(&block, &optimistic_block); +} diff --git a/chain/client/src/tests/query_client.rs b/chain/client/src/tests/query_client.rs index 256b0850969..6c0e2a839f4 100644 --- a/chain/client/src/tests/query_client.rs +++ b/chain/client/src/tests/query_client.rs @@ -97,6 +97,7 @@ fn query_status_not_crash() { block_merkle_tree.root(), Clock::real(), None, + None, ); let timestamp = next_block.header().timestamp(); next_block diff --git a/chain/network/src/network_protocol/testonly.rs b/chain/network/src/network_protocol/testonly.rs index 8d8ac625a52..ae6bdb46657 100644 --- a/chain/network/src/network_protocol/testonly.rs +++ b/chain/network/src/network_protocol/testonly.rs @@ -70,6 +70,7 @@ pub fn make_block( CryptoHash::default(), clock, None, + None, ) } diff --git a/core/primitives-core/src/version.rs b/core/primitives-core/src/version.rs index 2f1745c051a..c07d294407b 100644 --- a/core/primitives-core/src/version.rs +++ b/core/primitives-core/src/version.rs @@ -195,6 +195,8 @@ pub enum ProtocolFeature { ExcludeExistingCodeFromWitnessForCodeLen, /// Use the block height instead of the block hash to calculate the receipt ID. BlockHeightForReceiptId, + /// Enable optimistic block production. + ProduceOptimisticBlock, GlobalContracts, } @@ -283,7 +285,9 @@ impl ProtocolFeature { ProtocolFeature::CurrentEpochStateSync => 144, ProtocolFeature::SimpleNightshadeV4 => 146, ProtocolFeature::ExcludeExistingCodeFromWitnessForCodeLen => 148, - ProtocolFeature::BlockHeightForReceiptId => 149, + ProtocolFeature::BlockHeightForReceiptId | ProtocolFeature::ProduceOptimisticBlock => { + 149 + } // Place features that are not yet in Nightly below this line. ProtocolFeature::GlobalContracts => 200, } diff --git a/core/primitives/benches/serialization.rs b/core/primitives/benches/serialization.rs index 4eb90e831a8..6dd6f16361b 100644 --- a/core/primitives/benches/serialization.rs +++ b/core/primitives/benches/serialization.rs @@ -82,6 +82,7 @@ fn create_block() -> Block { CryptoHash::default(), Clock::real(), None, + None, ) } diff --git a/core/primitives/src/block.rs b/core/primitives/src/block.rs index 0d0f1ebf602..a0b5193a95f 100644 --- a/core/primitives/src/block.rs +++ b/core/primitives/src/block.rs @@ -11,6 +11,7 @@ use crate::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo}; use crate::hash::CryptoHash; use crate::merkle::{merklize, verify_path, MerklePath}; use crate::num_rational::Rational32; +use crate::optimistic_block::OptimisticBlock; use crate::sharding::{ChunkHashHeight, ShardChunkHeader, ShardChunkHeaderV1}; use crate::types::{Balance, BlockHeight, EpochId, Gas}; use crate::version::{ProtocolVersion, SHARD_CHUNK_HEADER_UPGRADE_VERSION}; @@ -305,12 +306,14 @@ impl Block { block_merkle_root: CryptoHash, clock: near_time::Clock, sandbox_delta_time: Option, + optimistic_block: Option, ) -> Self { use itertools::Itertools; use near_primitives_core::version::ProtocolFeature; use crate::{ - hash::hash, stateless_validation::chunk_endorsements_bitmap::ChunkEndorsementsBitmap, + stateless_validation::chunk_endorsements_bitmap::ChunkEndorsementsBitmap, + utils::get_block_metadata, }; // Collect aggregate of validators and gas usage/limits from chunks. let mut prev_validator_proposals = vec![]; @@ -340,15 +343,19 @@ impl Block { ); let new_total_supply = prev.total_supply() + minted_amount.unwrap_or(0) - balance_burnt; - let now = clock.now_utc().unix_timestamp_nanos() as u64; - #[cfg(feature = "sandbox")] - let now = now + sandbox_delta_time.unwrap().whole_nanoseconds() as u64; - #[cfg(not(feature = "sandbox"))] - debug_assert!(sandbox_delta_time.is_none()); - let time = if now <= prev.raw_timestamp() { prev.raw_timestamp() + 1 } else { now }; - - let (vrf_value, vrf_proof) = signer.compute_vrf_with_proof(prev.random_value().as_ref()); - let random_value = hash(vrf_value.0.as_ref()); + + // Use the optimistic block data if available, otherwise compute it. + let (time, vrf_value, vrf_proof, random_value) = optimistic_block + .as_ref() + .map(|ob| { + ( + ob.inner.block_timestamp, + ob.inner.vrf_value, + ob.inner.vrf_proof, + ob.inner.random_value, + ) + }) + .unwrap_or_else(|| get_block_metadata(prev, signer, clock, sandbox_delta_time)); let last_ds_final_block = if height == prev.height() + 1 { prev.hash() } else { prev.last_ds_final_block() }; diff --git a/core/primitives/src/optimistic_block.rs b/core/primitives/src/optimistic_block.rs index 3b90edde385..a102ae4e361 100644 --- a/core/primitives/src/optimistic_block.rs +++ b/core/primitives/src/optimistic_block.rs @@ -41,21 +41,10 @@ impl OptimisticBlock { clock: near_time::Clock, sandbox_delta_time: Option, ) -> Self { + use crate::utils::get_block_metadata; let prev_block_hash = *prev_block_header.hash(); - let (vrf_value, vrf_proof) = - signer.compute_vrf_with_proof(prev_block_header.random_value().as_ref()); - let random_value = hash(vrf_value.0.as_ref()); - - let now = clock.now_utc().unix_timestamp_nanos() as u64; - #[cfg(feature = "sandbox")] - let now = now + sandbox_delta_time.unwrap().whole_nanoseconds() as u64; - #[cfg(not(feature = "sandbox"))] - debug_assert!(sandbox_delta_time.is_none()); - let time = if now <= prev_block_header.raw_timestamp() { - prev_block_header.raw_timestamp() + 1 - } else { - now - }; + let (time, vrf_value, vrf_proof, random_value) = + get_block_metadata(prev_block_header, signer, clock, sandbox_delta_time); let inner = OptimisticBlockInner { prev_block_hash, diff --git a/core/primitives/src/test_utils.rs b/core/primitives/src/test_utils.rs index b14f8519395..d31cfd5003f 100644 --- a/core/primitives/src/test_utils.rs +++ b/core/primitives/src/test_utils.rs @@ -867,6 +867,7 @@ impl TestBlockBuilder { self.block_merkle_root, self.clock, None, + None, ) } } diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 36518f8ce09..afca65946cd 100644 --- a/core/primitives/src/utils.rs +++ b/core/primitives/src/utils.rs @@ -9,6 +9,7 @@ use near_primitives_core::types::BlockHeight; use near_primitives_core::version::ProtocolFeature; use serde; +use crate::block::BlockHeader; use crate::hash::{hash, CryptoHash}; use crate::transaction::SignedTransaction; use crate::types::{NumSeats, NumShards, ShardId}; @@ -511,6 +512,30 @@ pub fn derive_eth_implicit_account_id(public_key: &Secp256K1PublicKey) -> Accoun format!("0x{}", hex::encode(&pk_hash[12..32])).parse().unwrap() } +/// Returns the block metadata used to create an optimistic block. +pub fn get_block_metadata( + prev_block_header: &BlockHeader, + signer: &crate::validator_signer::ValidatorSigner, + clock: near_time::Clock, + sandbox_delta_time: Option, +) -> (u64, near_crypto::vrf::Value, near_crypto::vrf::Proof, CryptoHash) { + let now = clock.now_utc().unix_timestamp_nanos() as u64; + #[cfg(feature = "sandbox")] + let now = now + sandbox_delta_time.unwrap().whole_nanoseconds() as u64; + #[cfg(not(feature = "sandbox"))] + debug_assert!(sandbox_delta_time.is_none()); + let time = if now <= prev_block_header.raw_timestamp() { + prev_block_header.raw_timestamp() + 1 + } else { + now + }; + + let (vrf_value, vrf_proof) = + signer.compute_vrf_with_proof(prev_block_header.random_value().as_ref()); + let random_value = hash(vrf_value.0.as_ref()); + (time, vrf_value, vrf_proof, random_value) +} + #[cfg(test)] mod tests { use super::*; diff --git a/integration-tests/src/tests/client/challenges.rs b/integration-tests/src/tests/client/challenges.rs index ec98ad57338..5402b49db61 100644 --- a/integration-tests/src/tests/client/challenges.rs +++ b/integration-tests/src/tests/client/challenges.rs @@ -118,6 +118,7 @@ fn test_verify_block_double_sign_challenge() { block_merkle_tree.root(), Clock::real(), None, + None, ); let epoch_id = *b1.header().epoch_id(); let valid_challenge = Challenge::produce( @@ -445,6 +446,7 @@ fn test_verify_chunk_invalid_state_challenge() { block_merkle_tree.root(), Clock::real(), None, + None, ); let challenge_body = client diff --git a/integration-tests/src/tests/client/process_blocks.rs b/integration-tests/src/tests/client/process_blocks.rs index 1154af3af59..27d2f657a0b 100644 --- a/integration-tests/src/tests/client/process_blocks.rs +++ b/integration-tests/src/tests/client/process_blocks.rs @@ -346,6 +346,7 @@ fn receive_network_block() { block_merkle_tree.root(), Clock::real(), None, + None, ); actor_handles.client_actor.do_send( BlockResponse { block, peer_id: PeerInfo::random().id, was_requested: false } @@ -434,6 +435,7 @@ fn produce_block_with_approvals() { block_merkle_tree.root(), Clock::real(), None, + None, ); actor_handles.client_actor.do_send( BlockResponse { @@ -631,6 +633,7 @@ fn invalid_blocks_common(is_requested: bool) { block_merkle_tree.root(), Clock::real(), None, + None, ); // Send block with invalid chunk mask let mut block = valid_block.clone();