Skip to content

Commit

Permalink
feat: skip validation of an invoke transaction after deploy account
Browse files Browse the repository at this point in the history
  • Loading branch information
Yael-Starkware committed Jul 4, 2024
1 parent d9b06e3 commit 3dcdf26
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions crates/gateway/src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,8 @@ fn process_tx(
_ => None,
};

// TODO(Yael, 19/5/2024): pass the relevant deploy_account_hash.
let tx_hash =
stateful_tx_validator.run_validate(state_reader_factory, &tx, optional_class_info, None)?;
stateful_tx_validator.run_validate(state_reader_factory, &tx, optional_class_info)?;

// TODO(Arni): Add the Sierra and the Casm to the mempool input.
Ok(MempoolInput {
Expand Down
48 changes: 38 additions & 10 deletions crates/gateway/src/stateful_transaction_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use blockifier::bouncer::BouncerConfig;
use blockifier::context::BlockContext;
use blockifier::execution::contract_class::ClassInfo;
use blockifier::state::cached_state::CachedState;
use blockifier::state::state_api::StateReader;
use blockifier::versioned_constants::VersionedConstants;
use starknet_api::rpc_transaction::RPCTransaction;
use starknet_api::core::Nonce;
use starknet_api::hash::StarkFelt;
use starknet_api::rpc_transaction::{RPCInvokeTransaction, RPCTransaction};
use starknet_api::transaction::TransactionHash;

use crate::config::StatefulTransactionValidatorConfig;
Expand All @@ -27,12 +30,12 @@ impl StatefulTransactionValidator {
state_reader_factory: &dyn StateReaderFactory,
external_tx: &RPCTransaction,
optional_class_info: Option<ClassInfo>,
deploy_account_tx_hash: Option<TransactionHash>,
) -> StatefulTransactionValidatorResult<TransactionHash> {
// TODO(yael 6/5/2024): consider storing the block_info as part of the
// StatefulTransactionValidator and update it only once a new block is created.
let latest_block_info = get_latest_block_info(state_reader_factory)?;
let state_reader = state_reader_factory.get_state_reader(latest_block_info.block_number);
let prev_block_number = latest_block_info.block_number;
let state_reader = state_reader_factory.get_state_reader(prev_block_number);
let state = CachedState::new(state_reader);
let versioned_constants = VersionedConstants::latest_constants_with_overrides(
self.config.validate_max_n_steps,
Expand All @@ -52,23 +55,48 @@ impl StatefulTransactionValidator {
&versioned_constants,
);

let mut validator = BlockifierStatefulValidator::create(
state,
block_context,
self.config.max_nonce_for_validation_skip,
BouncerConfig::max(),
);
let mut validator =
BlockifierStatefulValidator::create(state, block_context, BouncerConfig::max());
let account_tx = external_tx_to_account_tx(
external_tx,
optional_class_info,
&self.config.chain_info.chain_id,
)?;
let tx_hash = get_tx_hash(&account_tx);
validator.perform_validations(account_tx, deploy_account_tx_hash)?;

let skip_validate = skip_stateful_validations(
external_tx,
&state_reader_factory.get_state_reader(prev_block_number),
self.config.max_nonce_for_validation_skip,
)?;
validator.perform_validations(account_tx, skip_validate)?;
Ok(tx_hash)
}
}

// Check if validation of an invoke transaction should be skipped due to deploy_account not being
// proccessed yet. This feature is used to improve UX for users sending deploy_account + invoke at
// once.
fn skip_stateful_validations(
tx: &RPCTransaction,
state_reader: &impl StateReader,
max_nonce_for_validation_skip: Nonce,
) -> StatefulTransactionValidatorResult<bool> {
if let RPCTransaction::Invoke(RPCInvokeTransaction::V3(tx)) = tx {
// check if the transaction nonce is bigger than 1, meaning it is post
// deploy_account, and the account nonce is zero, meaning the account was not deployed yet.
// The mempool will also verify that the deploy_account transaction exists.
let account_nonce = state_reader.get_nonce_at(tx.sender_address)?;
if tx.nonce >= Nonce(StarkFelt::ONE)
&& tx.nonce <= max_nonce_for_validation_skip
&& account_nonce == Nonce(StarkFelt::ZERO)
{
return Ok(true);
}
}
Ok(false)
}

pub fn get_latest_block_info(
state_reader_factory: &dyn StateReaderFactory,
) -> StatefulTransactionValidatorResult<BlockInfo> {
Expand Down
58 changes: 50 additions & 8 deletions crates/gateway/src/stateful_transaction_validator_test.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use assert_matches::assert_matches;
use blockifier::blockifier::stateful_validator::StatefulValidatorError;
use blockifier::context::BlockContext;
use blockifier::test_utils::dict_state_reader::DictStateReader;
use blockifier::test_utils::CairoVersion;
use blockifier::transaction::errors::{TransactionFeeError, TransactionPreValidationError};
use rstest::rstest;
use starknet_api::core::Nonce;
use starknet_api::hash::StarkFelt;
use starknet_api::rpc_transaction::RPCTransaction;
use starknet_api::transaction::TransactionHash;
use test_utils::invoke_tx_args;
use test_utils::starknet_api_test_utils::{
declare_tx, deploy_account_tx, invoke_tx, VALID_L1_GAS_MAX_AMOUNT,
declare_tx, deploy_account_tx, external_invoke_tx, invoke_tx, VALID_L1_GAS_MAX_AMOUNT,
VALID_L1_GAS_MAX_PRICE_PER_UNIT,
};

Expand All @@ -16,7 +20,7 @@ use crate::errors::{StatefulTransactionValidatorError, StatefulTransactionValida
use crate::gateway::compile_contract_class;
use crate::state_reader_test_utils::{
local_test_state_reader_factory, local_test_state_reader_factory_for_deploy_account,
TestStateReaderFactory,
TestStateReader, TestStateReaderFactory,
};
use crate::stateful_transaction_validator::StatefulTransactionValidator;

Expand Down Expand Up @@ -84,11 +88,49 @@ fn test_stateful_tx_validator(
_ => None,
};

let result = stateful_validator.run_validate(
&state_reader_factory,
&external_tx,
optional_class_info,
None,
);
let result =
stateful_validator.run_validate(&state_reader_factory, &external_tx, optional_class_info);
assert_eq!(format!("{:?}", result), format!("{:?}", expected_result));
}

#[rstest]
#[case::should_skip_validation(
external_invoke_tx(invoke_tx_args!{nonce: Nonce(StarkFelt::ONE)}),
true
)]
#[case::should_not_skip_validation_nonce_over_max_nonce_for_skip(
external_invoke_tx(invoke_tx_args!{nonce: Nonce(StarkFelt::TWO)}),
false
)]
#[case::should_not_skip_validation_non_invoke(deploy_account_tx(), false)]
fn test_skip_stateful_validation(
#[case] external_tx: RPCTransaction,
#[case] should_pass_validation: bool,
) {
let block_context = &BlockContext::create_for_testing();
// Initialize an empty state so that transactions would fail validation.
let empty_state_reader_factory = TestStateReaderFactory {
state_reader: TestStateReader {
blockifier_state_reader: DictStateReader::default(),
block_info: block_context.block_info().clone(),
},
};
let stateful_validator = StatefulTransactionValidator {
config: StatefulTransactionValidatorConfig {
max_nonce_for_validation_skip: Nonce(StarkFelt::ONE),
validate_max_n_steps: block_context.versioned_constants().validate_max_n_steps,
max_recursion_depth: block_context.versioned_constants().max_recursion_depth,
chain_info: block_context.chain_info().clone().into(),
},
};
let result = stateful_validator.run_validate(&empty_state_reader_factory, &external_tx, None);
if should_pass_validation {
assert_matches!(result, Ok(_));
} else {
// To be sure that the validations were actually skipped, we check that the error came from
// the blockifier stateful validations, and not from the pre validations since those are
// executed also when skip_validate is true.
assert_matches!(result, Err(StatefulTransactionValidatorError::StatefulValidatorError(err))
if !matches!(err, StatefulValidatorError::TransactionPreValidationError(_)));
}
}

0 comments on commit 3dcdf26

Please sign in to comment.