Skip to content

Commit 84b5660

Browse files
apollo_gateway: make StatefulTransactionValidatorTrait async
1 parent 16492e8 commit 84b5660

File tree

4 files changed

+153
-271
lines changed

4 files changed

+153
-271
lines changed

crates/apollo_gateway/src/gateway.rs

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use starknet_api::rpc_transaction::{
3232
RpcDeclareTransaction,
3333
RpcTransaction,
3434
};
35-
use tracing::{debug, warn, Span};
35+
use tracing::{debug, warn};
3636

3737
use crate::errors::{
3838
mempool_client_result_to_deprecated_gw_result,
@@ -157,28 +157,10 @@ impl Gateway {
157157
.await
158158
.inspect_err(|e| metric_counters.record_add_tx_failure(e))?;
159159

160-
let curr_span = Span::current();
161-
let mempool_client = self.mempool_client.clone();
162-
let nonce = tokio::task::spawn_blocking(move || {
163-
curr_span.in_scope(|| {
164-
stateful_transaction_validator.extract_state_nonce_and_run_validations(
165-
&executable_tx.clone(),
166-
mempool_client,
167-
tokio::runtime::Handle::current(),
168-
)
169-
})
170-
})
171-
.await
172-
.map_err(|e| {
173-
let err = StarknetError {
174-
code: StarknetErrorCode::UnknownErrorCode(
175-
"StarknetErrorCode.InternalError".to_string(),
176-
),
177-
message: format!("Validation task failed to complete: {e}"),
178-
};
179-
metric_counters.record_add_tx_failure(&err);
180-
err
181-
})??;
160+
let nonce = stateful_transaction_validator
161+
.extract_state_nonce_and_run_validations(&executable_tx, self.mempool_client.clone())
162+
.await
163+
.inspect_err(|e| metric_counters.record_add_tx_failure(e))?;
182164

183165
let gateway_output = create_gateway_output(&internal_tx);
184166

crates/apollo_gateway/src/gateway_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ async fn add_tx_returns_error_when_run_transaction_validations_fails(
557557

558558
mock_stateful_transaction_validator
559559
.expect_extract_state_nonce_and_run_validations()
560-
.return_once(|_, _, _| Err(expected_error));
560+
.return_once(|_, _| Err(expected_error));
561561

562562
mock_stateful_transaction_validator_factory
563563
.expect_instantiate_validator()

crates/apollo_gateway/src/stateful_transaction_validator.rs

Lines changed: 93 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ use apollo_mempool_types::communication::SharedMempoolClient;
1111
use apollo_mempool_types::mempool_types::ValidationArgs;
1212
use apollo_proc_macros::sequencer_latency_histogram;
1313
use async_trait::async_trait;
14-
use blockifier::blockifier::stateful_validator::{
15-
StatefulValidator,
16-
StatefulValidatorTrait as BlockifierStatefulValidatorTrait,
17-
};
14+
use blockifier::blockifier::stateful_validator::{StatefulValidator, StatefulValidatorTrait};
1815
use blockifier::blockifier_versioned_constants::VersionedConstants;
1916
use blockifier::bouncer::BouncerConfig;
2017
use blockifier::context::{BlockContext, ChainInfo};
@@ -87,115 +84,71 @@ impl StatefulTransactionValidatorFactoryTrait for StatefulTransactionValidatorFa
8784
self.contract_class_manager.clone(),
8885
Some(GATEWAY_CLASS_CACHE_METRICS),
8986
);
90-
91-
let state = CachedState::new(state_reader_and_contract_manager);
92-
let mut versioned_constants = VersionedConstants::get_versioned_constants(
93-
self.config.versioned_constants_overrides.clone(),
94-
);
95-
// The validation of a transaction is not affected by the casm hash migration.
96-
versioned_constants.enable_casm_hash_migration = false;
97-
98-
let mut block_info = gateway_fixed_block_state_reader.get_block_info().await?;
99-
block_info.block_number = block_info.block_number.unchecked_next();
100-
let block_context = BlockContext::new(
101-
block_info,
102-
self.chain_info.clone(),
103-
versioned_constants,
104-
BouncerConfig::max(),
105-
);
106-
107-
let blockifier_stateful_tx_validator =
108-
BlockifierStatefulValidator::create(state, block_context);
10987
Ok(Box::new(StatefulTransactionValidator {
11088
config: self.config.clone(),
111-
blockifier_stateful_tx_validator,
89+
chain_info: self.chain_info.clone(),
90+
state_reader_and_contract_manager: Some(state_reader_and_contract_manager),
11291
gateway_fixed_block_state_reader,
11392
}))
11493
}
11594
}
11695

11796
#[cfg_attr(test, mockall::automock)]
97+
#[async_trait]
11898
pub trait StatefulTransactionValidatorTrait: Send {
119-
fn extract_state_nonce_and_run_validations(
99+
async fn extract_state_nonce_and_run_validations(
120100
&mut self,
121101
executable_tx: &ExecutableTransaction,
122102
mempool_client: SharedMempoolClient,
123-
runtime: tokio::runtime::Handle,
124103
) -> StatefulTransactionValidatorResult<Nonce>;
125104
}
126105

127-
pub struct StatefulTransactionValidator<B: BlockifierStatefulValidatorTrait> {
106+
pub struct StatefulTransactionValidator {
128107
config: StatefulTransactionValidatorConfig,
129-
blockifier_stateful_tx_validator: B,
108+
chain_info: ChainInfo,
109+
// Consumed when running the CPU-heavy blockifier validation.
110+
state_reader_and_contract_manager:
111+
Option<StateReaderAndContractManager<Box<dyn GatewayStateReaderWithCompiledClasses>>>,
130112
gateway_fixed_block_state_reader: Box<dyn GatewayFixedBlockStateReader>,
131113
}
132114

133-
impl<B: BlockifierStatefulValidatorTrait + Send> StatefulTransactionValidatorTrait
134-
for StatefulTransactionValidator<B>
135-
{
136-
fn extract_state_nonce_and_run_validations(
115+
#[async_trait]
116+
impl StatefulTransactionValidatorTrait for StatefulTransactionValidator {
117+
async fn extract_state_nonce_and_run_validations(
137118
&mut self,
138119
executable_tx: &ExecutableTransaction,
139120
mempool_client: SharedMempoolClient,
140-
runtime: tokio::runtime::Handle,
141121
) -> StatefulTransactionValidatorResult<Nonce> {
142-
let address = executable_tx.contract_address();
143-
let account_nonce = runtime
144-
.block_on(self.gateway_fixed_block_state_reader.get_nonce(address))
145-
.map_err(|e| {
146-
// TODO(noamsp): Fix this. Need to map the errors better.
147-
StarknetError::internal_with_signature_logging(
148-
format!("Failed to get nonce for sender address {address}"),
149-
&executable_tx.signature(),
150-
e,
151-
)
152-
})?;
153-
self.run_transaction_validation(executable_tx, account_nonce, mempool_client, runtime)?;
122+
let account_nonce = self.extract_nonce(executable_tx).await?;
123+
let skip_validate =
124+
self.run_pre_validation_checks(executable_tx, account_nonce, mempool_client).await?;
125+
self.run_validate_entry_point(executable_tx, skip_validate).await?;
154126
Ok(account_nonce)
155127
}
156128
}
157129

158-
impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
159-
fn run_transaction_validation(
160-
&mut self,
161-
executable_tx: &ExecutableTransaction,
162-
account_nonce: Nonce,
163-
mempool_client: SharedMempoolClient,
164-
runtime: tokio::runtime::Handle,
165-
) -> StatefulTransactionValidatorResult<()> {
166-
self.validate_state_preconditions(executable_tx, account_nonce)?;
167-
runtime.block_on(validate_by_mempool(
168-
executable_tx,
169-
account_nonce,
170-
mempool_client.clone(),
171-
))?;
172-
self.run_validate_entry_point(executable_tx, account_nonce, mempool_client, runtime)?;
173-
Ok(())
174-
}
175-
176-
fn validate_state_preconditions(
130+
impl StatefulTransactionValidator {
131+
async fn validate_state_preconditions(
177132
&self,
178133
executable_tx: &ExecutableTransaction,
179134
account_nonce: Nonce,
180135
) -> StatefulTransactionValidatorResult<()> {
181-
self.validate_resource_bounds(executable_tx)?;
136+
self.validate_resource_bounds(executable_tx).await?;
182137
self.validate_nonce(executable_tx, account_nonce)?;
183138
Ok(())
184139
}
185140

186-
fn validate_resource_bounds(
141+
async fn validate_resource_bounds(
187142
&self,
188143
executable_tx: &ExecutableTransaction,
189144
) -> StatefulTransactionValidatorResult<()> {
190145
// Skip this validation during the systems bootstrap phase.
191146
if self.config.validate_resource_bounds {
192147
// TODO(Arni): getnext_l2_gas_price from the block header.
193-
// TODO(Itamar): Replace usage of `blockifier_stateful_tx_validator.block_info()` with
194-
// the GW fixed-block provider and then remove `block_info()` from
195-
// blockifier::{StatefulValidatorTrait, StatefulValidator}.
196148
let previous_block_l2_gas_price = self
197-
.blockifier_stateful_tx_validator
198-
.block_info()
149+
.gateway_fixed_block_state_reader
150+
.get_block_info()
151+
.await?
199152
.gas_prices
200153
.strk_gas_prices
201154
.l2_gas_price;
@@ -265,23 +218,52 @@ impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
265218
}
266219

267220
#[sequencer_latency_histogram(GATEWAY_VALIDATE_TX_LATENCY, true)]
268-
fn run_validate_entry_point(
221+
async fn run_validate_entry_point(
269222
&mut self,
270223
executable_tx: &ExecutableTransaction,
271-
account_nonce: Nonce,
272-
mempool_client: SharedMempoolClient,
273-
runtime: tokio::runtime::Handle,
224+
skip_validate: bool,
274225
) -> StatefulTransactionValidatorResult<()> {
275-
let skip_validate =
276-
skip_stateful_validations(executable_tx, account_nonce, mempool_client, runtime)?;
277226
let only_query = false;
278227
let charge_fee = enforce_fee(executable_tx, only_query);
279228
let strict_nonce_check = false;
280229
let execution_flags =
281230
ExecutionFlags { only_query, charge_fee, validate: !skip_validate, strict_nonce_check };
282231

283232
let account_tx = AccountTransaction { tx: executable_tx.clone(), execution_flags };
284-
self.blockifier_stateful_tx_validator.validate(account_tx).map_err(|e| StarknetError {
233+
234+
// Build block context here.
235+
let mut versioned_constants = VersionedConstants::get_versioned_constants(
236+
self.config.versioned_constants_overrides.clone(),
237+
);
238+
// The validation of a transaction is not affected by the casm hash migration.
239+
versioned_constants.enable_casm_hash_migration = false;
240+
241+
let mut block_info = self.gateway_fixed_block_state_reader.get_block_info().await?;
242+
block_info.block_number = block_info.block_number.unchecked_next();
243+
let block_context = BlockContext::new(
244+
block_info,
245+
self.chain_info.clone(),
246+
versioned_constants,
247+
BouncerConfig::max(),
248+
);
249+
250+
// Move state into the blocking task and run CPU-heavy validation.
251+
let state_reader_and_contract_manager =
252+
self.state_reader_and_contract_manager.take().expect("Validator was already consumed");
253+
254+
tokio::task::spawn_blocking(move || {
255+
let state = CachedState::new(state_reader_and_contract_manager);
256+
let mut blockifier = BlockifierStatefulValidator::create(state, block_context);
257+
blockifier.validate(account_tx)
258+
})
259+
.await
260+
.map_err(|e| StarknetError {
261+
code: StarknetErrorCode::UnknownErrorCode(
262+
"StarknetErrorCode.InternalError".to_string(),
263+
),
264+
message: format!("Blocking task join error: {e}"),
265+
})?
266+
.map_err(|e| StarknetError {
285267
code: StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::ValidateFailure),
286268
message: e.to_string(),
287269
})?;
@@ -321,6 +303,35 @@ impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
321303
}
322304
Ok(())
323305
}
306+
307+
async fn extract_nonce(
308+
&self,
309+
executable_tx: &ExecutableTransaction,
310+
) -> StatefulTransactionValidatorResult<Nonce> {
311+
let address = executable_tx.contract_address();
312+
let account_nonce =
313+
self.gateway_fixed_block_state_reader.get_nonce(address).await.map_err(|e| {
314+
StarknetError::internal_with_signature_logging(
315+
format!("Failed to get nonce for sender address {address}"),
316+
&executable_tx.signature(),
317+
e,
318+
)
319+
})?;
320+
Ok(account_nonce)
321+
}
322+
323+
async fn run_pre_validation_checks(
324+
&self,
325+
executable_tx: &ExecutableTransaction,
326+
account_nonce: Nonce,
327+
mempool_client: SharedMempoolClient,
328+
) -> StatefulTransactionValidatorResult<bool> {
329+
self.validate_state_preconditions(executable_tx, account_nonce).await?;
330+
validate_by_mempool(executable_tx, account_nonce, mempool_client.clone()).await?;
331+
let skip_validate =
332+
skip_stateful_validations(executable_tx, account_nonce, mempool_client.clone()).await?;
333+
Ok(skip_validate)
334+
}
324335
}
325336

326337
/// Perform transaction validation by the mempool.
@@ -339,11 +350,10 @@ async fn validate_by_mempool(
339350
/// Check if validation of an invoke transaction should be skipped due to deploy_account not being
340351
/// processed yet. This feature is used to improve UX for users sending deploy_account + invoke at
341352
/// once.
342-
fn skip_stateful_validations(
353+
async fn skip_stateful_validations(
343354
tx: &ExecutableTransaction,
344355
account_nonce: Nonce,
345356
mempool_client: SharedMempoolClient,
346-
runtime: tokio::runtime::Handle,
347357
) -> StatefulTransactionValidatorResult<bool> {
348358
if let ExecutableTransaction::Invoke(ExecutableInvokeTransaction { tx, .. }) = tx {
349359
// check if the transaction nonce is 1, meaning it is post deploy_account, and the
@@ -355,8 +365,9 @@ fn skip_stateful_validations(
355365
// to check if the account exists in the mempool since it means that either it has a
356366
// deploy_account transaction or transactions with future nonces that passed
357367
// validations.
358-
return runtime
359-
.block_on(mempool_client.account_tx_in_pool_or_recent_block(tx.sender_address()))
368+
return mempool_client
369+
.account_tx_in_pool_or_recent_block(tx.sender_address())
370+
.await
360371
.map_err(|err| mempool_client_err_to_deprecated_gw_err(&tx.signature(), err))
361372
.inspect(|exists| {
362373
if *exists {

0 commit comments

Comments
 (0)