diff --git a/chain/chain/src/chain.rs b/chain/chain/src/chain.rs index 0024eca1381..097380ec6e1 100644 --- a/chain/chain/src/chain.rs +++ b/chain/chain/src/chain.rs @@ -70,7 +70,7 @@ use near_primitives::types::{ }; use near_primitives::unwrap_or_return; use near_primitives::utils::MaybeValidated; -use near_primitives::version::PROTOCOL_VERSION; +use near_primitives::version::{PROTOCOL_VERSION, ProtocolVersion}; use near_primitives::views::{ BlockStatusView, DroppedReason, ExecutionOutcomeWithIdView, ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionOutcomeWithReceiptView, FinalExecutionStatus, @@ -832,6 +832,72 @@ impl Chain { Ok(()) } + pub fn apply_chunk_for_post_state_root( + &self, + shard_id: ShardId, + epoch_id: &EpochId, + prev_state_root: StateRoot, + block_height: BlockHeight, + prev_block_header: &BlockHeader, + transactions: &[SignedTransaction], + last_validator_proposals: near_primitives::types::validator_stake::ValidatorStakeIter, + gas_price: Balance, + gas_limit: Gas, + last_chunk_height_included: BlockHeight, + ) -> Result { + let prev_block_hash = *prev_block_header.hash(); + let shard_layout = self.epoch_manager.get_shard_layout(epoch_id)?; + let is_first_block_with_chunk_of_version = check_if_block_is_first_with_chunk_of_version( + self.store(), + self.epoch_manager.as_ref(), + &prev_block_hash, + shard_id, + )?; + let mut new_receipts = Vec::new(); + for (from_shard_id, &chunk_included) in prev_block_header.chunk_mask().iter().enumerate() { + if chunk_included { + for receipt in self.store.get_outgoing_receipts(&prev_block_hash, from_shard_id as ShardId).unwrap().iter() { + if account_id_to_shard_id(&receipt.receiver_id, &shard_layout) == shard_id { + new_receipts.push(receipt.clone()); + } + } + } + } + // TODO(post-state-root): shuffle new_receipts + let old_receipts = + collect_receipts_from_response(&self.store.get_incoming_receipts_for_shard( + self.epoch_manager.as_ref(), + shard_id, + prev_block_hash, + last_chunk_height_included, + )?); + let receipts = [new_receipts, old_receipts].concat(); + // TODO(post-state-root): block-level fields + let block_timestamp = 0; + let block_hash = CryptoHash::default(); + let random_seed = CryptoHash::default(); + + self.runtime_adapter.apply_transactions( + shard_id, + &prev_state_root, + block_height, + block_timestamp, + &prev_block_hash, + &block_hash, + &receipts, + transactions, + last_validator_proposals, + gas_price, + gas_limit, + &vec![], + random_seed, + true, + is_first_block_with_chunk_of_version, + Default::default(), + true, + ) + } + pub fn save_orphan( &mut self, block: MaybeValidated, diff --git a/chain/client/src/client.rs b/chain/client/src/client.rs index f5aa7d9c25b..a4b89839ecc 100644 --- a/chain/client/src/client.rs +++ b/chain/client/src/client.rs @@ -21,6 +21,7 @@ use near_chain::flat_storage_creator::FlatStorageCreator; use near_chain::resharding::StateSplitRequest; use near_chain::state_snapshot_actor::MakeSnapshotCallback; use near_chain::test_utils::format_hash; +use near_chain::types::ApplyTransactionResult; use near_chain::types::RuntimeAdapter; use near_chain::types::{ChainConfig, LatestKnown}; use near_chain::{ @@ -55,6 +56,7 @@ use near_primitives::hash::CryptoHash; use near_primitives::merkle::{merklize, MerklePath, PartialMerkleTree}; use near_primitives::network::PeerId; use near_primitives::receipt::Receipt; +use near_primitives::shard_layout::account_id_to_shard_id; use near_primitives::sharding::StateSyncInfo; use near_primitives::sharding::{ ChunkHash, EncodedShardChunk, PartialEncodedChunk, ReedSolomonWrapper, ShardChunk, @@ -63,6 +65,8 @@ use near_primitives::sharding::{ use near_primitives::static_clock::StaticClock; use near_primitives::transaction::SignedTransaction; use near_primitives::types::chunk_extra::ChunkExtra; +use near_primitives::types::Gas; +use near_primitives::types::StateRoot; use near_primitives::types::{AccountId, ApprovalStake, BlockHeight, EpochId, NumBlocks, ShardId}; use near_primitives::unwrap_or_return; use near_primitives::utils::MaybeValidated; @@ -801,6 +805,35 @@ impl Client { validator_signer.validator_id() ); + let ret = self.produce_pre_state_root_chunk( + validator_signer.as_ref(), + prev_block_hash, + epoch_id, + last_header, + next_height, + shard_id, + )?; + + metrics::CHUNK_PRODUCED_TOTAL.inc(); + self.chunk_production_info.put( + (next_height, shard_id), + ChunkProduction { + chunk_production_time: Some(StaticClock::utc()), + chunk_production_duration_millis: Some(timer.elapsed().as_millis() as u64), + }, + ); + Ok(Some(ret)) + } + + fn produce_pre_state_root_chunk( + &mut self, + validator_signer: &dyn ValidatorSigner, + prev_block_hash: CryptoHash, + epoch_id: &EpochId, + last_header: ShardChunkHeader, + next_height: BlockHeight, + shard_id: ShardId, + ) -> Result<(EncodedShardChunk, Vec, Vec), Error> { let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, epoch_id)?; let chunk_extra = self .chain @@ -808,8 +841,12 @@ impl Client { .map_err(|err| Error::ChunkProducer(format!("No chunk extra available: {}", err)))?; let prev_block_header = self.chain.get_block_header(&prev_block_hash)?; - let transactions = - self.prepare_transactions(shard_uid, &chunk_extra, &prev_block_header)?; + let transactions = self.prepare_transactions( + shard_uid, + chunk_extra.gas_limit(), + *chunk_extra.state_root(), + &prev_block_header, + )?; let transactions = transactions; #[cfg(feature = "test_features")] let transactions = Self::maybe_insert_invalid_transaction( @@ -875,15 +912,122 @@ impl Client { outgoing_receipts.len(), ); - metrics::CHUNK_PRODUCED_TOTAL.inc(); - self.chunk_production_info.put( - (next_height, shard_id), - ChunkProduction { - chunk_production_time: Some(StaticClock::utc()), - chunk_production_duration_millis: Some(timer.elapsed().as_millis() as u64), - }, + Ok((encoded_chunk, merkle_paths, outgoing_receipts)) + } + + fn produce_post_state_root_chunk( + &mut self, + validator_signer: &dyn ValidatorSigner, + prev_block_hash: CryptoHash, + epoch_id: &EpochId, + last_header: ShardChunkHeader, + next_height: BlockHeight, + shard_id: ShardId, + ) -> Result, Vec)>, Error> { + let shard_uid = self.epoch_manager.shard_id_to_uid(shard_id, epoch_id)?; + let prev_block_header = self.chain.get_block_header(&prev_block_hash)?; + let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id)?; + let gas_limit; + let prev_gas_used; + let prev_state_root; + let prev_validator_proposals; + let prev_outcome_root; + match &last_header { + ShardChunkHeader::V3(near_primitives::sharding::ShardChunkHeaderV3 { + inner: near_primitives::sharding::ShardChunkHeaderInner::V3(header_inner), + .. + }) => { + gas_limit = 0; + prev_gas_used = 0; + prev_state_root = StateRoot::default(); + prev_validator_proposals = + near_primitives::types::validator_stake::ValidatorStakeIter::empty(); + prev_outcome_root = CryptoHash::default(); + } + _ => { + let chunk_extra = + self.chain.get_chunk_extra(&prev_block_hash, &shard_uid).map_err(|err| { + Error::ChunkProducer(format!("No chunk extra available: {}", err)) + })?; + gas_limit = chunk_extra.gas_limit(); + prev_gas_used = chunk_extra.gas_used(); + prev_state_root = *chunk_extra.state_root(); + prev_validator_proposals = chunk_extra.validator_proposals(); + prev_outcome_root = *chunk_extra.outcome_root(); + } + } + + let transactions = + self.prepare_transactions(shard_uid, gas_limit, prev_state_root, &prev_block_header)?; + #[cfg(feature = "test_features")] + let transactions = Self::maybe_insert_invalid_transaction( + transactions, + prev_block_hash, + self.produce_invalid_tx_in_chunks, ); - Ok(Some((encoded_chunk, merkle_paths, outgoing_receipts))) + let num_filtered_transactions = transactions.len(); + let (tx_root, _) = merklize(&transactions); + + // TODO(post-state-root): block-level field, temporary using the value from the prev block + let gas_price = prev_block_header.gas_price(); + let ApplyTransactionResult { outgoing_receipts, .. } = self.chain.apply_chunk_for_post_state_root( + shard_id, + epoch_id, + prev_state_root, + // TODO(post-state-root): block-level field, need to double check if using next_height is correct here + next_height, + &prev_block_header, + &transactions, + prev_validator_proposals, + gas_price, + gas_limit, + last_header.height_included(), + )?; + + // Receipts proofs root is calculating here + // + // For each subset of incoming_receipts_into_shard_i_from_the_current_one + // we calculate hash here and save it + // and then hash all of them into a single receipts root + // + // We check validity in two ways: + // 1. someone who cares about shard will download all the receipts + // and checks that receipts_root equals to all receipts hashed + // 2. anyone who just asks for one's incoming receipts + // will receive a piece of incoming receipts only + // with merkle receipts proofs which can be checked locally + let outgoing_receipts_root = self.calculate_receipts_root(epoch_id, &outgoing_receipts)?; + let protocol_version = self.epoch_manager.get_epoch_protocol_version(epoch_id)?; + #[cfg(feature = "test_features")] + let gas_used = if self.produce_invalid_chunks { gas_used + 1 } else { gas_used }; + + let (encoded_chunk, merkle_paths) = ShardsManager::create_encoded_shard_chunk( + prev_block_hash, + *chunk_extra.state_root(), + *chunk_extra.outcome_root(), + next_height, + shard_id, + gas_used, + chunk_extra.gas_limit(), + chunk_extra.balance_burnt(), + chunk_extra.validator_proposals().collect(), + transactions, + &outgoing_receipts, + outgoing_receipts_root, + tx_root, + &*validator_signer, + &mut self.rs_for_chunk_production, + protocol_version, + )?; + todo!() + } + + fn calculate_receipts_root(&self, epoch_id: &EpochId, receipts: &[Receipt]) -> Result { + let shard_layout = self.epoch_manager.get_shard_layout(epoch_id)?; + let receipts_hashes = + Chain::build_receipts_hashes(&receipts, &shard_layout); + let (receipts_root, _) = merklize(&receipts_hashes); + Ok(receipts_root) } #[cfg(feature = "test_features")] @@ -911,7 +1055,8 @@ impl Client { fn prepare_transactions( &mut self, shard_uid: ShardUId, - chunk_extra: &ChunkExtra, + gas_limit: Gas, + state_root: StateRoot, prev_block_header: &BlockHeader, ) -> Result, Error> { let Self { chain, sharded_tx_pool, epoch_manager, runtime_adapter: runtime, .. } = self; @@ -924,10 +1069,10 @@ impl Client { let transaction_validity_period = chain.transaction_validity_period; runtime.prepare_transactions( prev_block_header.gas_price(), - chunk_extra.gas_limit(), + gas_limit, &next_epoch_id, shard_id, - *chunk_extra.state_root(), + state_root, // while the height of the next block that includes the chunk might not be prev_height + 1, // passing it will result in a more conservative check and will not accidentally allow // invalid transactions to be included.