Skip to content

Conversation

@itamar-starkware
Copy link
Contributor

@itamar-starkware itamar-starkware commented Dec 7, 2025

What changed?

  • Removed the ProcessTxBlockingTask struct and simplified the transaction validation flow in Gateway::add_tx
  • Changed StatefulTransactionValidator to get the nonce directly from the state reader instead of the blockifier
  • Added a get_nonce method to the GatewayFixedBlockStateReader trait and its implementations
  • Renamed extract_state_nonce_and_run_validations to run_transaction_validations and simplified its implementation
  • Updated tests to match the new validation flow

Why make this change?

This refactoring simplifies the transaction validation flow by:

  1. Eliminating unnecessary code complexity around the blocking task
  2. Improving error handling and propagation
  3. Making the nonce retrieval more direct by getting it from the state reader
  4. Reducing the number of state transitions and method calls in the validation process

The changes maintain the same validation logic while making the code more maintainable and easier to understand.

@reviewable-StarkWare
Copy link

This change is Reviewable

@itamar-starkware itamar-starkware self-assigned this Dec 7, 2025
Copy link
Contributor Author

itamar-starkware commented Dec 7, 2025

@itamar-starkware itamar-starkware marked this pull request as ready for review December 7, 2025 10:11
@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch from 8790404 to bfe5849 Compare December 7, 2025 12:08
@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/stateful_transaction_validator.rs line 165 at r1 (raw file):

impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
    fn validate_state_preconditions(

I know this is not the main topic, but I am distracted by this.

Suggestion:

        let account_nonce = runtime
            .block_on(self.gateway_fixed_block_state_reader.get_nonce(address))
            .map_err(|e| {
                // TODO(noamsp): Fix this. Need to map the errors better.
                StarknetError::internal_with_signature_logging(
                    format!("Failed to get nonce for sender address {address}"),
                    &executable_tx.signature(),
                    e,
                )
            })?;
        self.run_transaction_validations(executable_tx, account_nonce, mempool_client, runtime)?;
        Ok(account_nonce)
    }
}

impl<B: BlockifierStatefulValidatorTrait> StatefulTransactionValidator<B> {
    fn run_transaction_validations(
        &mut self,
        executable_tx: &ExecutableTransaction,
        account_nonce: Nonce,
        mempool_client: SharedMempoolClient,
        runtime: tokio::runtime::Handle,
    ) -> StatefulTransactionValidatorResult<()> {
        self.validate_state_preconditions(executable_tx, account_nonce)?;
        runtime.block_on(validate_by_mempool(
            executable_tx,
            account_nonce,
            mempool_client.clone(),
        ))?;
        self.run_validate_entry_point(executable_tx, account_nonce, mempool_client, runtime)?;
        Ok(())
    }

    fn validate_state_preconditions(

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 7 files reviewed, 1 unresolved discussion (waiting on @itamar-starkware and @TzahiTaub)


crates/apollo_gateway/src/stateful_transaction_validator.rs line 165 at r1 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

I know this is not the main topic, but I am distracted by this.

This comment is still relevant.

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/stateful_transaction_validator.rs line 165 at r1 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

This comment is still relevant.

I'm missing the issue here. Please explain.

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 7 files reviewed, 1 unresolved discussion (waiting on @itamar-starkware and @TzahiTaub)


crates/apollo_gateway/src/stateful_transaction_validator.rs line 165 at r1 (raw file):

Previously, itamar-starkware wrote…

I'm missing the issue here. Please explain.

  1. Don't change unnecessary things in this PR. Which leads to the second point.
  2. I like that the function called extract_state_nonce_and_run_validations does:
  • extract the state nonce (one method).
  • runs validations (second method). * This is the issue I refer to.

@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/gateway.rs line 176 at r2 (raw file):

            ),
            message: format!("Blocking task join error: {e}"),
        })??;

Join error? Blocking task?
Neither are relevant anymore (or is the spawn_blocking considered a join?)

Code quote:

            message: format!("Blocking task join error: {e}"),
        })??;

@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/gateway.rs line 170 at r2 (raw file):

                    &tx_signature, starknet_err
                );
                metric_counters.record_add_tx_failure(&starknet_err);

What happened to the metric? IIUC, it should be handled as one of the two ? operations of the spawn_blocking.

Code quote:

metric_counters.record_add_tx_failure(&starknet_err);

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 7 files reviewed, 4 unresolved discussions (waiting on @itamar-starkware and @TzahiTaub)


crates/apollo_gateway/src/stateful_transaction_validator_test.rs line 492 at r2 (raw file):

    .map_err(|err| err.code);
    assert_eq!(result, expected_result_code);
}

This was a unit test for the deleted method run_transaction_validations.

In this thread. I requested you bring it back.
This would make the changes to this test unnecessary.

Code quote:

async fn run_transaction_validation_test(
    executable_tx: AccountTransaction,
    account_nonce: Nonce,
    max_allowed_nonce_gap: u32,
    expected_result_code: Result<(), StarknetErrorCode>,
) {
    let mut mock_blockifier_validator = MockBlockifierStatefulValidatorTrait::new();
    mock_blockifier_validator.expect_validate().return_once(|_| Ok(()));
    mock_blockifier_validator.expect_block_info().return_const(BlockInfo::default());

    let mut stateful_validator = StatefulTransactionValidator {
        config: StatefulTransactionValidatorConfig { max_allowed_nonce_gap, ..Default::default() },
        blockifier_stateful_tx_validator: mock_blockifier_validator,
        gateway_fixed_block_state_reader: {
            let mut mock = MockGatewayFixedBlockStateReader::new();
            mock.expect_get_nonce().return_once(move |_| Ok(account_nonce));
            Box::new(mock)
        },
    };

    let result = tokio::task::spawn_blocking(move || {
        let mut mempool_client = MockMempoolClient::new();
        mempool_client.expect_validate_tx().returning(|_| Ok(()));
        stateful_validator.extract_state_nonce_and_run_validations(
            &executable_tx,
            Arc::new(mempool_client),
            tokio::runtime::Handle::current(),
        )
    })
    .await
    .unwrap()
    .map(|_| ())
    .map_err(|err| err.code);
    assert_eq!(result, expected_result_code);
}

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 7 files reviewed, 4 unresolved discussions (waiting on @itamar-starkware and @TzahiTaub)


crates/apollo_gateway/src/stateful_transaction_validator_test.rs line 492 at r2 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

This was a unit test for the deleted method run_transaction_validations.

In this thread. I requested you bring it back.
This would make the changes to this test unnecessary.

Ditto other tests.

@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/stateful_transaction_validator.rs line 141 at r2 (raw file):

        let address = executable_tx.contract_address();
        let account_nonce =
            self.blockifier_stateful_tx_validator.get_nonce(address).map_err(|e| {

Go to crates/blockifier/src/blockifier/stateful_validator.rs.
Delete the method get_nonce. If this isn't simple, add a todo to delete it.

#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait StatefulValidatorTrait {
    #[allow(clippy::result_large_err)]
    fn validate(&mut self, account_tx: AccountTransaction) -> StatefulValidatorResult<()>;
    fn block_info(&self) -> &BlockInfo;
    #[allow(clippy::result_large_err)]
    fn get_nonce(&mut self, account_address: ContractAddress) -> StatefulValidatorResult<Nonce>;
}

Code quote:

self.blockifier_stateful_tx_validator.get_nonce(address)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 7 files at r1.
Reviewable status: 1 of 7 files reviewed, 5 unresolved discussions (waiting on @itamar-starkware and @TzahiTaub)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 7 files at r1.
Reviewable status: 2 of 7 files reviewed, 5 unresolved discussions (waiting on @itamar-starkware and @TzahiTaub)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 4 files at r2.
Reviewable status: 3 of 7 files reviewed, 5 unresolved discussions (waiting on @itamar-starkware and @TzahiTaub)

@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/gateway_fixed_block_state_reader.rs line 83 at r2 (raw file):

            Err(StateSyncClientError::StateSyncError(StateSyncError::ContractNotFound(_))) => {
                Ok(Nonce::default())
            }

What is the precedent?

Code quote:

            Err(StateSyncClientError::StateSyncError(StateSyncError::ContractNotFound(_))) => {
                Ok(Nonce::default())
            }

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway.rs line 170 at r2 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

What happened to the metric? IIUC, it should be handled as one of the two ? operations of the spawn_blocking.

Fixed.

@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch from bfe5849 to 1445e7c Compare December 9, 2025 10:17
@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway.rs line 176 at r2 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

Join error? Blocking task?
Neither are relevant anymore (or is the spawn_blocking considered a join?)

spawn_blocking return a JoinHandle, but I don't think we should refer to it in our message.
I changed to something that describes the issue better IMO.

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway_fixed_block_state_reader.rs line 83 at r2 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

What is the precedent?

On the get_nonce_at for the blockifier:

fn get_nonce_at(&self, contract_address: ContractAddress) -> StateResult<Nonce> {
let res = self
.runtime
.block_on(self.state_sync_client.get_nonce_at(self.block_number, contract_address));
match res {
Ok(value) => Ok(value),
Err(StateSyncClientError::StateSyncError(StateSyncError::ContractNotFound(_))) => {
Ok(Nonce::default())
}
Err(e) => Err(StateError::StateReadError(e.to_string())),
}
}

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/stateful_transaction_validator.rs line 141 at r2 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

Go to crates/blockifier/src/blockifier/stateful_validator.rs.
Delete the method get_nonce. If this isn't simple, add a todo to delete it.

#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait StatefulValidatorTrait {
    #[allow(clippy::result_large_err)]
    fn validate(&mut self, account_tx: AccountTransaction) -> StatefulValidatorResult<()>;
    fn block_info(&self) -> &BlockInfo;
    #[allow(clippy::result_large_err)]
    fn get_nonce(&mut self, account_address: ContractAddress) -> StatefulValidatorResult<Nonce>;
}

Added a todo, this PR is already too long.

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/stateful_transaction_validator.rs line 165 at r1 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…
  1. Don't change unnecessary things in this PR. Which leads to the second point.
  2. I like that the function called extract_state_nonce_and_run_validations does:
  • extract the state nonce (one method).
  • runs validations (second method). * This is the issue I refer to.

In the next PR we revert back to something close to that.

  • pre-validation call, which extracts the nonce.
  • validation

@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/stateful_transaction_validator.rs line 165 at r1 (raw file):

Previously, itamar-starkware wrote…

In the next PR we revert back to something close to that.

  • pre-validation call, which extracts the nonce.
  • validation

I would argue we can have three steps:

  • extract_nonce
  • run prevalidation checks
  • run validate entry point

@ArniStarkware
Copy link
Contributor

crates/apollo_gateway/src/gateway.rs line 170 at r2 (raw file):

Previously, itamar-starkware wrote…

Fixed.

Sorry for saying "one of the two ?", because any failure should be registered as a record_add_tx_failure.

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/stateful_transaction_validator_test.rs line 366 at r4 (raw file):

    .await
    .unwrap();
    assert_eq!(result.map(|_| ()), expected_result);

Done

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway.rs line 170 at r2 (raw file):

Previously, ArniStarkware (Arnon Hod) wrote…

Yes, you should.

Done.

@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch from cf08661 to fb3507f Compare December 9, 2025 14:03
@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/stateful_transaction_validator_test.rs line 476 at r4 (raw file):

            Box::new(mock)
        },
    };

Done. I should have seen it by myself.

@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch from fb3507f to 6987806 Compare December 9, 2025 14:11
Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 2 files at r3.
Reviewable status: 4 of 8 files reviewed, all discussions resolved (waiting on @TzahiTaub)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 7 files at r1, 1 of 2 files at r4, 1 of 2 files at r5.
Reviewable status: 7 of 8 files reviewed, 1 unresolved discussion (waiting on @itamar-starkware and @TzahiTaub)


crates/apollo_gateway/src/stateful_transaction_validator_test.rs line 117 at r6 (raw file):

    };
    let expected_run_result =
        expected_result.as_ref().map(|_| account_nonce).map_err(|blockifier_error| StarknetError {

why do we need this?

Code quote:

.map(|_| account_nonce)

@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch 3 times, most recently from 087eff1 to e77c3e5 Compare December 9, 2025 19:25
Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 2 files at r7, all commit messages.
Reviewable status: 7 of 8 files reviewed, all discussions resolved (waiting on @TzahiTaub)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed 1 of 1 files at r9.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @TzahiTaub)

@ArniStarkware
Copy link
Contributor

Please wait for @TzahiTaub's review.

Copy link
Contributor

@TzahiTaub TzahiTaub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TzahiTaub reviewed 3 of 7 files at r1, 1 of 2 files at r3, 1 of 2 files at r4, 1 of 2 files at r5, all commit messages.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @itamar-starkware)


crates/apollo_gateway/src/gateway.rs line 168 at r8 (raw file):

                    mempool_client,
                    tokio::runtime::Handle::current(),
                )

Suggestion:

        let nonce = tokio::task::spawn_blocking(move || {
            curr_span.in_scope(|| {
                stateful_transaction_validator.extract_state_nonce_and_run_validations(
                    &executable_tx,
                    self.mempool_client.clone(),
                    tokio::runtime::Handle::current(),
                )

crates/apollo_gateway/src/gateway.rs line 173 at r8 (raw file):

        .await
        .map_err(|e| {
            let err = StarknetError {

IIUC, this helps to understand why there are two consecutive error handlers.

Suggestion:

        .map_err(|e| {
            // Handle panics in the spawned thread (see tokio::task::JoinHandle).
            let err = StarknetError {

Copy link
Contributor

@TzahiTaub TzahiTaub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TzahiTaub reviewed 1 of 2 files at r7, 1 of 1 files at r9.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @itamar-starkware)

@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway.rs line 173 at r8 (raw file):

Previously, TzahiTaub (Tzahi) wrote…

IIUC, this helps to understand why there are two consecutive error handlers.

Done

@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch from e77c3e5 to 52d9004 Compare December 10, 2025 15:28
@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway.rs line 168 at r8 (raw file):

                    mempool_client,
                    tokio::runtime::Handle::current(),
                )

Can't be done.
We have lifetime issues here.
spawn_blocking create a closure which take ownership on all object get inside him, so working with self.mempool_client will require taking ownership on self.
The issue here, IIUC, is lifetime requiremnets. The spawn_blocking can live longer than the self object.

The compiler error here is:

borrowed data escapes outside of method
`self` escapes the method body here

This is the cursor summary of the case:

The blocking validation runs in a thread via spawn_blocking, 
whose closure must be 'static (no non-'static borrows).

Inlining self.mempool_client.clone() would capture &self, 
a non-'static borrow; instead, clone the Arc outside and
 move the owned clone into the closure.

The local mempool_client variable isn’t 'static itself, 
but moving it into the closure makes the closure 'static 
because it owns its captures.

Copy link
Contributor

@TzahiTaub TzahiTaub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 7 of 8 files reviewed, 2 unresolved discussions (waiting on @ArniStarkware and @itamar-starkware)


crates/apollo_gateway/src/gateway.rs line 168 at r8 (raw file):

Previously, itamar-starkware wrote…

Can't be done.
We have lifetime issues here.
spawn_blocking create a closure which take ownership on all object get inside him, so working with self.mempool_client will require taking ownership on self.
The issue here, IIUC, is lifetime requiremnets. The spawn_blocking can live longer than the self object.

The compiler error here is:

borrowed data escapes outside of method
`self` escapes the method body here

This is the cursor summary of the case:

The blocking validation runs in a thread via spawn_blocking, 
whose closure must be 'static (no non-'static borrows).

Inlining self.mempool_client.clone() would capture &self, 
a non-'static borrow; instead, clone the Arc outside and
 move the owned clone into the closure.

The local mempool_client variable isn’t 'static itself, 
but moving it into the closure makes the closure 'static 
because it owns its captures.

Right, then just remove the clone from &executable_tx

Copy link
Contributor

@TzahiTaub TzahiTaub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TzahiTaub reviewed 1 of 1 files at r10.
Reviewable status: all files reviewed (commit messages unreviewed), 2 unresolved discussions (waiting on @itamar-starkware)

@itamar-starkware itamar-starkware force-pushed the 12-07-apollo_gateway_remove_processing_tx_block branch from 52d9004 to 500b3b7 Compare December 10, 2025 15:58
@itamar-starkware
Copy link
Contributor Author

crates/apollo_gateway/src/gateway.rs line 168 at r8 (raw file):

Previously, TzahiTaub (Tzahi) wrote…

Right, then just remove the clone from &executable_tx

Done

Copy link
Contributor

@TzahiTaub TzahiTaub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

@TzahiTaub reviewed 1 of 1 files at r11, all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @itamar-starkware)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ArniStarkware reviewed all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @itamar-starkware)

Copy link
Contributor

@ArniStarkware ArniStarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @itamar-starkware)

@itamar-starkware itamar-starkware added this pull request to the merge queue Dec 11, 2025
Merged via the queue into main-v0.14.1 with commit 15427c2 Dec 11, 2025
36 of 39 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Dec 12, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants