Skip to content

Commit 14dc6b0

Browse files
apollo_gateway: make StatefulTransactionValidatorTrait async
1 parent 1445e7c commit 14dc6b0

File tree

4 files changed

+140
-259
lines changed

4 files changed

+140
-259
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: 85 additions & 71 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,104 +84,70 @@ 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.validate_state_preconditions(executable_tx, account_nonce)?;
154-
runtime.block_on(validate_by_mempool(
155-
executable_tx,
156-
account_nonce,
157-
mempool_client.clone(),
158-
))?;
159-
self.run_validate_entry_point(executable_tx, account_nonce, mempool_client, runtime)?;
122+
let (account_nonce, skip_validate) =
123+
self.run_pre_validation_checks(executable_tx, mempool_client).await?;
124+
self.run_validate_entry_point(executable_tx, skip_validate).await?;
160125
Ok(account_nonce)
161126
}
162127
}
163128

164-
impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
165-
fn validate_state_preconditions(
129+
impl StatefulTransactionValidator {
130+
async fn validate_state_preconditions(
166131
&self,
167132
executable_tx: &ExecutableTransaction,
168133
account_nonce: Nonce,
169134
) -> StatefulTransactionValidatorResult<()> {
170-
self.validate_resource_bounds(executable_tx)?;
135+
self.validate_resource_bounds(executable_tx).await?;
171136
self.validate_nonce(executable_tx, account_nonce)?;
172137
Ok(())
173138
}
174139

175-
fn validate_resource_bounds(
140+
async fn validate_resource_bounds(
176141
&self,
177142
executable_tx: &ExecutableTransaction,
178143
) -> StatefulTransactionValidatorResult<()> {
179144
// Skip this validation during the systems bootstrap phase.
180145
if self.config.validate_resource_bounds {
181146
// TODO(Arni): getnext_l2_gas_price from the block header.
182-
// TODO(Itamar): Replace usage of `blockifier_stateful_tx_validator.block_info()` with
183-
// the GW fixed-block provider and then remove `block_info()` from
184-
// blockifier::{StatefulValidatorTrait, StatefulValidator}.
185147
let previous_block_l2_gas_price = self
186-
.blockifier_stateful_tx_validator
187-
.block_info()
148+
.gateway_fixed_block_state_reader
149+
.get_block_info()
150+
.await?
188151
.gas_prices
189152
.strk_gas_prices
190153
.l2_gas_price;
@@ -254,23 +217,52 @@ impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
254217
}
255218

256219
#[sequencer_latency_histogram(GATEWAY_VALIDATE_TX_LATENCY, true)]
257-
fn run_validate_entry_point(
220+
async fn run_validate_entry_point(
258221
&mut self,
259222
executable_tx: &ExecutableTransaction,
260-
account_nonce: Nonce,
261-
mempool_client: SharedMempoolClient,
262-
runtime: tokio::runtime::Handle,
223+
skip_validate: bool,
263224
) -> StatefulTransactionValidatorResult<()> {
264-
let skip_validate =
265-
skip_stateful_validations(executable_tx, account_nonce, mempool_client, runtime)?;
266225
let only_query = false;
267226
let charge_fee = enforce_fee(executable_tx, only_query);
268227
let strict_nonce_check = false;
269228
let execution_flags =
270229
ExecutionFlags { only_query, charge_fee, validate: !skip_validate, strict_nonce_check };
271230

272231
let account_tx = AccountTransaction { tx: executable_tx.clone(), execution_flags };
273-
self.blockifier_stateful_tx_validator.validate(account_tx).map_err(|e| StarknetError {
232+
233+
// Build block context here.
234+
let mut versioned_constants = VersionedConstants::get_versioned_constants(
235+
self.config.versioned_constants_overrides.clone(),
236+
);
237+
// The validation of a transaction is not affected by the casm hash migration.
238+
versioned_constants.enable_casm_hash_migration = false;
239+
240+
let mut block_info = self.gateway_fixed_block_state_reader.get_block_info().await?;
241+
block_info.block_number = block_info.block_number.unchecked_next();
242+
let block_context = BlockContext::new(
243+
block_info,
244+
self.chain_info.clone(),
245+
versioned_constants,
246+
BouncerConfig::max(),
247+
);
248+
249+
// Move state into the blocking task and run CPU-heavy validation.
250+
let state_reader_and_contract_manager =
251+
self.state_reader_and_contract_manager.take().expect("Validator was already consumed");
252+
253+
tokio::task::spawn_blocking(move || {
254+
let state = CachedState::new(state_reader_and_contract_manager);
255+
let mut blockifier = BlockifierStatefulValidator::create(state, block_context);
256+
blockifier.validate(account_tx)
257+
})
258+
.await
259+
.map_err(|e| StarknetError {
260+
code: StarknetErrorCode::UnknownErrorCode(
261+
"StarknetErrorCode.InternalError".to_string(),
262+
),
263+
message: format!("Blocking task join error: {e}"),
264+
})?
265+
.map_err(|e| StarknetError {
274266
code: StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::ValidateFailure),
275267
message: e.to_string(),
276268
})?;
@@ -310,6 +302,28 @@ impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
310302
}
311303
Ok(())
312304
}
305+
306+
async fn run_pre_validation_checks(
307+
&self,
308+
executable_tx: &ExecutableTransaction,
309+
mempool_client: SharedMempoolClient,
310+
) -> StatefulTransactionValidatorResult<(Nonce, bool)> {
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+
// TODO(noamsp): Fix this. Need to map the errors better.
315+
StarknetError::internal_with_signature_logging(
316+
format!("Failed to get nonce for sender address {address}"),
317+
&executable_tx.signature(),
318+
e,
319+
)
320+
})?;
321+
self.validate_state_preconditions(executable_tx, account_nonce).await?;
322+
validate_by_mempool(executable_tx, account_nonce, mempool_client.clone()).await?;
323+
let skip_validate =
324+
skip_stateful_validations(executable_tx, account_nonce, mempool_client.clone()).await?;
325+
Ok((account_nonce, skip_validate))
326+
}
313327
}
314328

315329
/// Perform transaction validation by the mempool.
@@ -328,11 +342,10 @@ async fn validate_by_mempool(
328342
/// Check if validation of an invoke transaction should be skipped due to deploy_account not being
329343
/// processed yet. This feature is used to improve UX for users sending deploy_account + invoke at
330344
/// once.
331-
fn skip_stateful_validations(
345+
async fn skip_stateful_validations(
332346
tx: &ExecutableTransaction,
333347
account_nonce: Nonce,
334348
mempool_client: SharedMempoolClient,
335-
runtime: tokio::runtime::Handle,
336349
) -> StatefulTransactionValidatorResult<bool> {
337350
if let ExecutableTransaction::Invoke(ExecutableInvokeTransaction { tx, .. }) = tx {
338351
// check if the transaction nonce is 1, meaning it is post deploy_account, and the
@@ -344,8 +357,9 @@ fn skip_stateful_validations(
344357
// to check if the account exists in the mempool since it means that either it has a
345358
// deploy_account transaction or transactions with future nonces that passed
346359
// validations.
347-
return runtime
348-
.block_on(mempool_client.account_tx_in_pool_or_recent_block(tx.sender_address()))
360+
return mempool_client
361+
.account_tx_in_pool_or_recent_block(tx.sender_address())
362+
.await
349363
.map_err(|err| mempool_client_err_to_deprecated_gw_err(&tx.signature(), err))
350364
.inspect(|exists| {
351365
if *exists {

0 commit comments

Comments
 (0)