diff --git a/chain/chain/src/spice/chain.rs b/chain/chain/src/spice/chain.rs index ed28788ac8a..3fc1f654def 100644 --- a/chain/chain/src/spice/chain.rs +++ b/chain/chain/src/spice/chain.rs @@ -63,8 +63,7 @@ impl SpiceChainReader { &self, start_hash: &CryptoHash, ) -> Result, Error> { - // Absent on non-spice nodes (seeded only when SPICE is enabled at genesis). - let final_execution_head = self.chain_store.spice_final_execution_head().ok(); + let final_execution_head = self.chain_store.spice_final_execution_head()?; let mut header = self.chain_store.get_block_header(start_hash)?; loop { if self.is_block_executed(&header)? { @@ -73,10 +72,8 @@ impl SpiceChainReader { if header.is_genesis() { return Ok(header); } - if let Some(final_execution_head) = &final_execution_head { - if header.height() <= final_execution_head.height { - return Err(Error::DBNotFoundErr("no executed ancestor found".to_string())); - } + if header.height() <= final_execution_head.height { + return Err(Error::DBNotFoundErr("no executed ancestor found".to_string())); } header = self.chain_store.get_block_header(header.prev_hash())?; } diff --git a/chain/client/src/client_actor.rs b/chain/client/src/client_actor.rs index 1ad28a3290a..1b9ed6dd724 100644 --- a/chain/client/src/client_actor.rs +++ b/chain/client/src/client_actor.rs @@ -777,11 +777,19 @@ impl Handler, Result> for Clien fn handle(&mut self, msg: SpanWrapped) -> Result { let msg = msg.span_unwrap(); let head = self.client.chain.head()?; + let protocol_version = self + .client + .epoch_manager + .get_epoch_protocol_version(&head.epoch_id) + .into_chain_error()?; // For spice, walk back to find the latest executed block for the status - // response, since the consensus head may not be executed yet. - // In non-spice, this is just the head block as all blocks are considered executed. - let head_header = - self.spice_chain_reader.find_first_executed_ancestor(&head.last_block_hash)?; + // response, since the consensus head may not be executed yet. In non-spice, + // all blocks are considered executed, so the head block is the latest executed. + let head_header = if ProtocolFeature::Spice.enabled(protocol_version) { + self.spice_chain_reader.find_first_executed_ancestor(&head.last_block_hash)? + } else { + self.client.chain.get_block_header(&head.last_block_hash)? + }; let latest_block_time = head_header.raw_timestamp(); let head_protocol_version = self .client diff --git a/chain/client/src/spice/chunk_executor_actor/coordinator.rs b/chain/client/src/spice/chunk_executor_actor/coordinator.rs index 7f577952d69..06dfac9f028 100644 --- a/chain/client/src/spice/chunk_executor_actor/coordinator.rs +++ b/chain/client/src/spice/chunk_executor_actor/coordinator.rs @@ -308,19 +308,7 @@ impl ChunkExecutorActor { } fn process_all_ready_blocks(&mut self) -> Result<(), Error> { - let start_block = match self.chain_store.spice_final_execution_head() { - Ok(final_execution_head) => final_execution_head.last_block_hash, - Err(Error::DBNotFoundErr(_)) => { - let final_head_hash = self.chain_store.final_head()?.last_block_hash; - let mut header = self.chain_store.get_block_header(&final_head_hash)?; - // TODO(spice): Stop searching on the first non-spice block. - while !header.is_genesis() { - header = self.chain_store.get_block_header(header.prev_hash())?; - } - *header.hash() - } - Err(err) => return Err(err), - }; + let start_block = self.chain_store.spice_final_execution_head()?.last_block_hash; // Park every block above the final execution head into its tracked shards' // executors and try to drain. A freshly-started node catches up from disk; diff --git a/chain/client/src/spice/chunk_executor_actor/per_shard.rs b/chain/client/src/spice/chunk_executor_actor/per_shard.rs index 27467441a70..9f81dc48851 100644 --- a/chain/client/src/spice/chunk_executor_actor/per_shard.rs +++ b/chain/client/src/spice/chunk_executor_actor/per_shard.rs @@ -753,14 +753,12 @@ pub(crate) fn is_descendant_of_final_execution_head( chain_store: &ChainStoreAdapter, header: &BlockHeader, ) -> bool { - let final_execution_head = match chain_store.spice_final_execution_head() { - Ok(final_header) => final_header, - // Without final execution head we are either executing on genesis and don't have it yet or - // executing on top of non-spice blocks. In both cases we can assume that all blocks are on - // top of final_execution_head until it's set. - Err(Error::DBNotFoundErr(_)) => return true, - Err(err) => panic!("failed to find final execution head: {err:?}"), - }; + // The final execution head is seeded at genesis whenever spice is enabled, and + // this runs only on spice-gated paths, so its absence is a bug rather than a + // recoverable "not set yet" state — fail loud. + let final_execution_head = chain_store + .spice_final_execution_head() + .expect("spice final execution head is seeded at genesis when spice is enabled"); let mut height = header.height(); if height <= final_execution_head.height { return false; diff --git a/chain/client/src/spice/chunk_executor_actor/receipt_tracker.rs b/chain/client/src/spice/chunk_executor_actor/receipt_tracker.rs index e28d70859bd..bb9f16ec235 100644 --- a/chain/client/src/spice/chunk_executor_actor/receipt_tracker.rs +++ b/chain/client/src/spice/chunk_executor_actor/receipt_tracker.rs @@ -80,13 +80,7 @@ impl UnverifiedReceiptTracker { &mut self, chain_store: &ChainStoreAdapter, ) -> Result<(), Error> { - // Absent on non-spice nodes (seeded only when SPICE is enabled at genesis); - // nothing below it to prune then. - let final_head = match chain_store.spice_final_execution_head() { - Ok(head) => head, - Err(Error::DBNotFoundErr(_)) => return Ok(()), - Err(err) => return Err(err), - }; + let final_head = chain_store.spice_final_execution_head()?; let mut stale = Vec::new(); for source_block in self.buffer.keys().copied() { match chain_store.get_block_header(&source_block) { diff --git a/chain/client/src/spice/data_distributor_actor.rs b/chain/client/src/spice/data_distributor_actor.rs index b56ca113712..5314cc7f10e 100644 --- a/chain/client/src/spice/data_distributor_actor.rs +++ b/chain/client/src/spice/data_distributor_actor.rs @@ -1221,19 +1221,7 @@ impl SpiceDataDistributorActor { } fn start_waiting_on_missing_data(&mut self) -> Result<(), Error> { - let start_block = match self.chain_store.spice_final_execution_head() { - Ok(final_execution_head) => final_execution_head.last_block_hash, - Err(near_chain::Error::DBNotFoundErr(_)) => { - let final_head_hash = self.chain_store.final_head()?.last_block_hash; - let mut header = self.chain_store.get_block_header(&final_head_hash)?; - // TODO(spice): Stop searching on the first non-spice block. - while !header.is_genesis() { - header = self.chain_store.get_block_header(header.prev_hash())?; - } - *header.hash() - } - Err(err) => return Err(err.into()), - }; + let start_block = self.chain_store.spice_final_execution_head()?.last_block_hash; let mut next_block_hashes: VecDeque<_> = self.chain_store.get_all_next_block_hashes(&start_block).into(); diff --git a/test-loop-tests/src/tests/account_cost_increase_diff.rs b/test-loop-tests/src/tests/account_cost_increase_diff.rs index 81b804a6a55..18fc069a219 100644 --- a/test-loop-tests/src/tests/account_cost_increase_diff.rs +++ b/test-loop-tests/src/tests/account_cost_increase_diff.rs @@ -621,6 +621,8 @@ fn transfer_to(receiver: AccountId) -> ScenarioTx { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_create_account() { // A single `CreateAccount` action creating a fresh sub-account of the actor. run_cost_test( @@ -638,6 +640,8 @@ fn test_create_account() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_function_call() { // A single function call; creates no account, so cost is identical before and after. run_cost_test( @@ -650,6 +654,8 @@ fn test_function_call() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_transfer_to_named_account() { // Transfer to an existing named account; no account is created, so cost is unchanged. run_cost_test( @@ -659,6 +665,8 @@ fn test_transfer_to_named_account() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_transfer_creating_near_implicit_account() { // Transfer to a non-existent NEAR-implicit account, which creates it. run_cost_test( @@ -668,6 +676,8 @@ fn test_transfer_creating_near_implicit_account() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_transfer_to_existing_near_implicit_account() { // Transfer to a NEAR-implicit account that already exists (pre-created in setup), so no // account is created and no creation charge applies. @@ -685,6 +695,8 @@ fn test_transfer_to_existing_near_implicit_account() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_transfer_creating_eth_implicit_account() { // Transfer to a non-existent ETH-implicit account, which creates it. run_cost_test( @@ -694,6 +706,8 @@ fn test_transfer_creating_eth_implicit_account() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_create_account_then_failing_call() { // `CreateAccount` followed by a `FunctionCall` on the new (contract-less) account, which // fails - rolling back the whole receipt, so the account is NOT created and no creation @@ -714,6 +728,8 @@ fn test_create_account_then_failing_call() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_create_account_then_successful_call() { // `CreateAccount`, point the new account at the global contract with `UseGlobalContract`, // then call a method on it that succeeds. The account is created (charge applies); using a @@ -743,6 +759,8 @@ fn test_create_account_then_successful_call() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_create_two_accounts_via_call_promise() { // A single `call_promise` function call that spawns two promises, each creating a new // sub-account of the contract. Two accounts are created, so the creation charge applies @@ -774,6 +792,8 @@ fn test_create_two_accounts_via_call_promise() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_deterministic_state_init_creating_account() { // A `DeterministicStateInit` action that creates a new (zero-balance) deterministic account. run_cost_test( @@ -789,6 +809,8 @@ fn test_deterministic_state_init_creating_account() { } #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_deterministic_state_init_to_existing_account() { // `DeterministicStateInit` on a deterministic account that already exists (pre-created in // setup). Re-initialization creates no account, so no creation charge applies. diff --git a/test-loop-tests/src/tests/deterministic_account_id.rs b/test-loop-tests/src/tests/deterministic_account_id.rs index 6f5c79560db..4768666a25c 100644 --- a/test-loop-tests/src/tests/deterministic_account_id.rs +++ b/test-loop-tests/src/tests/deterministic_account_id.rs @@ -134,6 +134,8 @@ fn test_deterministic_state_init_via_meta_tx() { /// validation. The exploit is prevented by a following `validate_receipt` check /// when the meta transaction is unpacked. #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_deterministic_state_init_meta_tx_receiver_check_pre_fix() { let fix_version = ProtocolFeature::FixDelegatedDeterministicStateInit.protocol_version(); let outcome = try_meta_tx_deterministic_receiver_exploit(fix_version - 1) @@ -157,6 +159,8 @@ fn test_deterministic_state_init_meta_tx_receiver_check_pre_fix() { /// With `FixDelegatedDeterministicStateInit` in place, the exploit should /// already be caught at the first tx validation. #[test] +// Pins to a pre-spice protocol version; skipped under the spice feature. +#[cfg_attr(feature = "protocol_feature_spice", ignore)] fn test_deterministic_state_init_meta_tx_receiver_check() { let fix_version = ProtocolFeature::FixDelegatedDeterministicStateInit.protocol_version(); let err = try_meta_tx_deterministic_receiver_exploit(fix_version)