Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions chain/chain/src/spice/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ impl SpiceChainReader {
&self,
start_hash: &CryptoHash,
) -> Result<Arc<BlockHeader>, 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)? {
Expand All @@ -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())?;
}
Expand Down
16 changes: 12 additions & 4 deletions chain/client/src/client_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,11 +777,19 @@ impl Handler<SpanWrapped<Status>, Result<StatusResponse, StatusError>> for Clien
fn handle(&mut self, msg: SpanWrapped<Status>) -> Result<StatusResponse, StatusError> {
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
Expand Down
14 changes: 1 addition & 13 deletions chain/client/src/spice/chunk_executor_actor/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 6 additions & 8 deletions chain/client/src/spice/chunk_executor_actor/per_shard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 1 addition & 13 deletions chain/client/src/spice/data_distributor_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
22 changes: 22 additions & 0 deletions test-loop-tests/src/tests/account_cost_increase_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions test-loop-tests/src/tests/deterministic_account_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading