Skip to content
Open
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ lint-smart-contracts:

.PHONY: audit-rs
audit-rs:
$(CARGO) audit --ignore RUSTSEC-2024-0437 --ignore RUSTSEC-2025-0022 --ignore RUSTSEC-2025-0055 --ignore RUSTSEC-2026-0001 --ignore RUSTSEC-2026-0007 --ignore RUSTSEC-2026-0049 --ignore RUSTSEC-2026-0068 --ignore RUSTSEC-2026-0067
$(CARGO) audit --ignore RUSTSEC-2024-0437 --ignore RUSTSEC-2025-0022 --ignore RUSTSEC-2025-0055 --ignore RUSTSEC-2026-0001 --ignore RUSTSEC-2026-0007 --ignore RUSTSEC-2026-0049 --ignore RUSTSEC-2026-0068 --ignore RUSTSEC-2026-0067 --ignore RUSTSEC-2026-0098 --ignore RUSTSEC-2026-0099

.PHONY: audit
audit: audit-rs
Expand Down
9 changes: 6 additions & 3 deletions node/src/components/block_synchronizer/block_acquisition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ impl BlockAcquisitionState {
) => {
if false == is_historical {
Err(BlockAcquisitionError::InvalidStateTransition)
} else if transaction_state.needs_transaction() {
} else if block.transaction_count() == 0 || transaction_state.needs_transaction() {
// There is an execution result checksum and there is a derived utilization
// score that is meaningfull even when there are no transactions.
BlockAcquisitionAction::maybe_execution_results(
block,
peer_list,
Expand Down Expand Up @@ -506,7 +508,8 @@ impl BlockAcquisitionState {
}
BlockAcquisitionState::HaveStrictFinalitySignatures(block, ..) => {
if is_historical {
// we have enough signatures; need to make sure we've stored the necessary bits
// we have enough signatures; need to make sure we've
// stored the necessary bits
Ok(BlockAcquisitionAction::block_marked_complete(
*block.hash(),
block.height(),
Expand Down Expand Up @@ -1199,7 +1202,7 @@ impl BlockAcquisitionState {
Ok(())
}

/// Register a transactions for this block.
/// Register a transaction for this block.
pub(super) fn register_transaction(
&mut self,
txn_id: TransactionId,
Expand Down
138 changes: 121 additions & 17 deletions node/src/components/block_synchronizer/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2360,13 +2360,15 @@ fn historical_state(block_synchronizer: &BlockSynchronizer) -> &BlockAcquisition
.block_acquisition_state()
}

/// When there is no deploy, the state goes from `HaveGlobalState` to `HaveStrictFinalitySignature`
/// directly, skipping `HaveAllExecutionResults`, `HaveApprovalsHashes` and `HaveAllTransactions`.
/// Even if a block has no transaction it needs to go through "the regular" states becasue in those
/// states we calculate utilization tracking for the block (even if it's empty we still need to
/// calculate it's utilization).
#[tokio::test]
async fn historical_sync_skips_exec_results_and_deploys_if_block_empty() {
async fn historical_sync_does_not_skip_exec_results_if_block_empty() {
let rng = &mut TestRng::new();
let mock_reactor = MockReactor::new();
let test_env = TestEnv::random(rng);
let test_env =
TestEnv::random(rng).with_block(TestBlockBuilder::new().era(1).build(rng).into());
let peers = test_env.peers();
let block = test_env.block();
let validator_matrix = test_env.gen_validator_matrix();
Expand All @@ -2381,8 +2383,6 @@ async fn historical_sync_skips_exec_results_and_deploys_if_block_empty() {
assert!(block_synchronizer.forward.is_none());
block_synchronizer.register_peers(*block.hash(), peers.clone());

// Skip steps HaveBlockHeader, HaveWeakFinalitySignature, HaveBlock

let historical_builder = block_synchronizer
.historical
.as_mut()
Expand Down Expand Up @@ -2422,24 +2422,24 @@ async fn historical_sync_skips_exec_results_and_deploys_if_block_empty() {
Event::GlobalStateSynchronizer(global_state_synchronizer::Event::Request(request)),
);

// ----- HaveBlock -----
assert_matches!(
historical_state(&block_synchronizer),
BlockAcquisitionState::HaveBlock { .. }
);

// Those effects are handled directly and not through the reactor:
let events = effects
.try_one()
.expect("there should be only one effect")
.await;
let events = effects.one().await;
assert_matches!(
events.try_one(),
Some(Event::GlobalStateSynchronizer(
GlobalStateSynchronizerEvent::GetPeers(_)
))
);

// ----- HaveBlock -----
assert_matches!(
historical_state(&block_synchronizer),
BlockAcquisitionState::HaveBlock { .. }
);

// Let's not test the detail of the global synchronization event,
// since it is already tested in its unit tests.

let effects = block_synchronizer.handle_event(
mock_reactor.effect_builder(),
rng,
Expand All @@ -2457,11 +2457,115 @@ async fn historical_sync_skips_exec_results_and_deploys_if_block_empty() {
historical_state(&block_synchronizer),
BlockAcquisitionState::HaveGlobalState { .. }
);

let events = mock_reactor.process_effects(effects).await;

match events.try_one() {
Some(MockReactorEvent::ContractRuntimeRequest(
ContractRuntimeRequest::GetExecutionResultsChecksum {
state_root_hash,
responder,
},
)) => responder.respond(ExecutionResultsChecksumResult::Success { checksum: state_root_hash }).await,
other => panic!("Event should be of type `ContractRuntimeRequest(ContractRuntimeRequest::GetExecutionResultsChecksum) but it is {:?}", other),
}

let effects = block_synchronizer.handle_event(
mock_reactor.effect_builder(),
rng,
Event::GotExecutionResultsChecksum {
block_hash: *block.hash(),
result: ExecutionResultsChecksumResult::Success {
checksum: Digest::SENTINEL_NONE,
},
},
);
let events = mock_reactor.process_effects(effects).await;

for event in events {
assert_matches!(
event,
MockReactorEvent::BlockExecutionResultsOrChunkFetcherRequest(FetcherRequest { .. })
);
}

let execution_results = BlockExecutionResultsOrChunk::new_empty_value(*block.hash());
let effects = block_synchronizer.handle_event(
mock_reactor.effect_builder(),
rng,
Event::ExecutionResultsFetched {
block_hash: *block.hash(),
result: Ok(FetchedData::from_storage(Box::new(execution_results))),
},
);

let mut events = mock_reactor.process_effects(effects).await;

assert_matches!(
historical_state(&block_synchronizer),
BlockAcquisitionState::HaveGlobalState { .. }
);

assert_matches!(
events.remove(0),
MockReactorEvent::StorageRequest(StorageRequest::PutExecutionResults { .. })
);
for event in events {
assert_matches!(
event,
MockReactorEvent::ApprovalsHashesFetcherRequest(FetcherRequest { .. })
);
}

let effects = block_synchronizer.handle_event(
mock_reactor.effect_builder(),
rng,
Event::ExecutionResultsStored(*block.hash()),
);
// ----- HaveAllExecutionResults -----
assert_matches!(
historical_state(&block_synchronizer),
BlockAcquisitionState::HaveAllExecutionResults(_, _, _, checksum) if checksum.is_checkable()
);

let events = mock_reactor.process_effects(effects).await;

for event in events {
assert_matches!(event, MockReactorEvent::FinalitySignatureFetcherRequest(..));
assert_matches!(
event,
MockReactorEvent::ApprovalsHashesFetcherRequest(FetcherRequest { .. })
);
}

let effects = block_synchronizer.handle_event(
mock_reactor.effect_builder(),
rng,
Event::ApprovalsHashesFetched(Ok(FetchedData::from_storage(Box::new(
ApprovalsHashes::new(*block.hash(), vec![], dummy_merkle_proof()),
)))),
);
// ----- HaveApprovalsHashes -----
assert_matches!(
historical_state(&block_synchronizer),
BlockAcquisitionState::HaveApprovalsHashes(_, _, _)
);

let events = mock_reactor.process_effects(effects).await;
assert!(!events.is_empty());
// Since the block doesn't have any transactions,
// the next step should be to fetch the finality signatures for strict finality.
for event in events {
assert_matches!(
event,
MockReactorEvent::FinalitySignatureFetcherRequest(FetcherRequest {
id,
peer,
..
}) if peers.contains(&peer) && id.block_hash() == block.hash() && id.era_id() == block.era_id()
);
}

// The rest would be fetching finality signatures which is covered by other tests
}

#[tokio::test]
Expand Down
5 changes: 5 additions & 0 deletions node/src/components/consensus/era_supervisor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,11 @@ impl EraSupervisor {
let initial_era_height = self.era(era_id).start_height;
initial_era_height.saturating_add(block_context.ancestor_values().len() as u64)
}

// What is the block height of the next block we expect to execute?
pub(crate) fn next_executed_height(&self) -> u64 {
self.next_executed_height
}
}

/// A serialized consensus network message.
Expand Down
27 changes: 20 additions & 7 deletions node/src/components/contract_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use crate::{
effect::{
announcements::{
ContractRuntimeAnnouncement, FatalAnnouncement, MetaBlockAnnouncement,
UnexecutedBlockAnnouncement,
NonExecutableBlockAnnouncement, UnexecutedBlockAnnouncement,
},
incoming::{TrieDemand, TrieRequest as TrieRequestMessage, TrieRequestIncoming},
requests::{ContractRuntimeRequest, NetworkRequest, StorageRequest},
Expand All @@ -81,7 +81,7 @@ pub(crate) use types::{
BlockAndExecutionArtifacts, ExecutionArtifact, ExecutionPreState, SpeculativeExecutionResult,
StepOutcome,
};
use utils::{exec_or_requeue, run_intensive_task};
use utils::{exec_and_check_next, run_intensive_task};

const COMPONENT_NAME: &str = "contract_runtime";

Expand Down Expand Up @@ -196,7 +196,7 @@ impl ContractRuntime {
})
}

pub(crate) fn set_initial_state(&mut self, sequential_block_state: ExecutionPreState) {
pub(crate) fn set_execution_pre_state(&mut self, sequential_block_state: ExecutionPreState) {
let next_block_height = sequential_block_state.next_block_height();
let mut execution_pre_state = self.execution_pre_state.lock().unwrap();
*execution_pre_state = sequential_block_state;
Expand All @@ -208,6 +208,12 @@ impl ContractRuntime {
debug!(next_block_height, "ContractRuntime: set initial state");
}

/// Returns the current execution prestate.
pub(crate) fn execution_pre_state(&self) -> ExecutionPreState {
let execution_pre_state = self.execution_pre_state.lock().unwrap();
execution_pre_state.clone()
}

fn new_data_access_layer(
storage_dir: &Path,
contract_runtime_config: &Config,
Expand Down Expand Up @@ -314,6 +320,7 @@ impl ContractRuntime {
+ From<MetaBlockAnnouncement>
+ From<UnexecutedBlockAnnouncement>
+ From<FatalAnnouncement>
+ From<NonExecutableBlockAnnouncement>
+ Send,
{
match request {
Expand Down Expand Up @@ -540,7 +547,7 @@ impl ContractRuntime {
}
ContractRuntimeRequest::UpdatePreState { new_pre_state } => {
let next_block_height = new_pre_state.next_block_height();
self.set_initial_state(new_pre_state);
self.set_execution_pre_state(new_pre_state);
let current_price = self.current_gas_price.gas_price();
async move {
let block_header = match effect_builder
Expand Down Expand Up @@ -635,7 +642,7 @@ impl ContractRuntime {
let current_pre_state = self.execution_pre_state.lock().unwrap();
let next_block_height = current_pre_state.next_block_height();
match finalized_block_height.cmp(&next_block_height) {
// An old block: it won't be executed:
// An old block: it won't be enqueued:
Ordering::Less => {
debug!(
%era_id,
Expand All @@ -645,7 +652,7 @@ impl ContractRuntime {
);
effects.extend(
effect_builder
.announce_unexecuted_block(finalized_block_height)
.announce_not_enqueuing_old_executable_block(finalized_block_height)
.ignore(),
);
}
Expand Down Expand Up @@ -683,8 +690,13 @@ impl ContractRuntime {
let chainspec = Arc::clone(&self.chainspec);
let metrics = Arc::clone(&self.metrics);
let shared_pre_state = Arc::clone(&self.execution_pre_state);
// the way this works is inobvious. if the current executable block
// executes and its child is enqueued the underlying logic will
// update the pre-state to refer to the child, pop the child from the queue,
// and send a new event of this kind with the child. it will then get into
// this match arm and get executed without being re-enqueued.
effects.extend(
exec_or_requeue(
exec_and_check_next(
data_access_layer,
execution_engine_v1,
execution_engine_v2,
Expand Down Expand Up @@ -843,6 +855,7 @@ where
+ From<MetaBlockAnnouncement>
+ From<UnexecutedBlockAnnouncement>
+ From<FatalAnnouncement>
+ From<NonExecutableBlockAnnouncement>
+ Send,
{
type Event = Event;
Expand Down
8 changes: 5 additions & 3 deletions node/src/components/contract_runtime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ impl Unhandled for NetworkRequest<Message> {}

impl Unhandled for UnexecutedBlockAnnouncement {}

impl Unhandled for NonExecutableBlockAnnouncement {}

struct TestConfig {
config: Config,
fixture_name: Option<String>,
Expand Down Expand Up @@ -261,7 +263,7 @@ async fn should_not_set_shared_pre_state_to_lower_block_height() {
.reactor_mut()
.inner_mut()
.contract_runtime
.set_initial_state(initial_pre_state);
.set_execution_pre_state(initial_pre_state);

// Create the genesis immediate switch block.
let block_0 = ExecutableBlock::from_finalized_block_and_transactions(
Expand Down Expand Up @@ -398,7 +400,7 @@ async fn should_not_set_shared_pre_state_to_lower_block_height() {
.reactor_mut()
.inner_mut()
.contract_runtime
.set_initial_state(ExecutionPreState::new(
.set_execution_pre_state(ExecutionPreState::new(
next_block_height,
Digest::hash(rng.next_u64().to_le_bytes()),
BlockHash::random(rng),
Expand Down Expand Up @@ -535,7 +537,7 @@ async fn should_correctly_manage_entity_version_calls() {
.reactor_mut()
.inner_mut()
.contract_runtime
.set_initial_state(initial_pre_state);
.set_execution_pre_state(initial_pre_state);

// Create the genesis immediate switch block.
let block_0 = ExecutableBlock::from_finalized_block_and_transactions(
Expand Down
Loading
Loading