Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: refactor the stateful tx validator to get executable tx as input #519

Merged
Merged
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: 2 additions & 0 deletions crates/gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ pub struct StatefulTransactionValidatorConfig {
pub max_nonce_for_validation_skip: Nonce,
pub validate_max_n_steps: u32,
pub max_recursion_depth: usize,
// TODO(Arni): Move this member out of the stateful transaction validator config. Move it into
// the gateway config. This is used during the transalation from external_tx to executable_tx.
pub chain_info: ChainInfo,
}

Expand Down
21 changes: 2 additions & 19 deletions crates/gateway/src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ fn process_tx(
// Perform stateless validations.
stateless_tx_validator.validate(&tx)?;

// TODO(Arni): remove copy_of_rpc_tx and use executable_tx directly as the mempool input.
let copy_of_rpc_tx = tx.clone();
let executable_tx = compile_contract_and_build_executable_tx(
tx,
&gateway_compiler,
Expand All @@ -128,29 +126,14 @@ fn process_tx(
}
}

let optional_class_info = match executable_tx {
starknet_api::executable_transaction::Transaction::Declare(tx) => {
Some(tx.class_info.try_into().map_err(|e| {
error!("Failed to convert Starknet API ClassInfo to Blockifier ClassInfo: {:?}", e);
GatewaySpecError::UnexpectedError { data: "Internal server error.".to_owned() }
})?)
}
_ => None,
};

let validator = stateful_tx_validator.instantiate_validator(state_reader_factory)?;
// TODO(Yael 31/7/24): refactor after IntrnalTransaction is ready, delete validate_info and
// compute all the info outside of run_validate.
let validate_info =
stateful_tx_validator.run_validate(&copy_of_rpc_tx, optional_class_info, validator)?;
let validate_info = stateful_tx_validator.run_validate(&executable_tx, validator)?;

// TODO(Arni): Add the Sierra and the Casm to the mempool input.
Ok(MempoolInput {
tx: Transaction::new_from_rpc_tx(
copy_of_rpc_tx,
validate_info.tx_hash,
validate_info.sender_address,
),
tx: executable_tx,
account: Account {
sender_address: validate_info.sender_address,
state: AccountState { nonce: validate_info.account_nonce },
Expand Down
49 changes: 14 additions & 35 deletions crates/gateway/src/gateway_test.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::sync::Arc;

use assert_matches::assert_matches;
use blockifier::context::ChainInfo;
use blockifier::test_utils::CairoVersion;
use mempool_test_utils::starknet_api_test_utils::{create_executable_tx, declare_tx, invoke_tx};
use mempool_test_utils::starknet_api_test_utils::{declare_tx, invoke_tx};
use mockall::predicate::eq;
use starknet_api::core::{CompiledClassHash, ContractAddress};
use starknet_api::core::{ChainId, CompiledClassHash, ContractAddress};
use starknet_api::executable_transaction::{InvokeTransaction, Transaction};
use starknet_api::rpc_transaction::{RpcDeclareTransaction, RpcTransaction};
use starknet_api::transaction::{TransactionHash, ValidResourceBounds};
use starknet_gateway_types::errors::GatewaySpecError;
use starknet_mempool_types::communication::{MempoolWrapperInput, MockMempoolClient};
use starknet_mempool_types::mempool_types::{Account, AccountState, MempoolInput};
Expand All @@ -19,7 +18,6 @@ use crate::gateway::{internal_add_tx, AppState, SharedMempoolClient};
use crate::state_reader_test_utils::{local_test_state_reader_factory, TestStateReaderFactory};
use crate::stateful_transaction_validator::StatefulTransactionValidator;
use crate::stateless_transaction_validator::StatelessTransactionValidator;
use crate::utils::rpc_tx_to_account_tx;

pub fn app_state(
mempool_client: SharedMempoolClient,
Expand Down Expand Up @@ -56,33 +54,31 @@ fn create_tx() -> (RpcTransaction, SenderAddress) {
// TODO: add test with Some broadcasted message metadata
#[tokio::test]
async fn test_add_tx() {
let (tx, sender_address) = create_tx();
let tx_hash = calculate_hash(&tx);
let (rpc_tx, sender_address) = create_tx();
let rpc_invoke_tx =
assert_matches!(rpc_tx.clone(), RpcTransaction::Invoke(rpc_invoke_tx) => rpc_invoke_tx);
let executable_tx = Transaction::Invoke(
InvokeTransaction::from_rpc_tx(rpc_invoke_tx, &ChainId::create_for_testing()).unwrap(),
);

let tx_hash = executable_tx.tx_hash();

let mut mock_mempool_client = MockMempoolClient::new();
mock_mempool_client
.expect_add_tx()
.once()
.with(eq(MempoolWrapperInput {
// TODO(Arni): Use external_to_executable_tx instead of `create_executable_tx`. Consider
// creating a `convertor for testing` that does not do the compilation.
mempool_input: MempoolInput {
tx: create_executable_tx(
sender_address,
tx_hash,
*tx.tip(),
*tx.nonce(),
ValidResourceBounds::AllResources(*tx.resource_bounds()),
),
account: Account { sender_address, state: AccountState { nonce: *tx.nonce() } },
tx: executable_tx,
account: Account { sender_address, state: AccountState { nonce: *rpc_tx.nonce() } },
},
message_metadata: None,
}))
.return_once(|_| Ok(()));
let state_reader_factory = local_test_state_reader_factory(CairoVersion::Cairo1, false);
let app_state = app_state(Arc::new(mock_mempool_client), state_reader_factory);

let response_tx_hash = internal_add_tx(app_state, tx).await.unwrap();
let response_tx_hash = internal_add_tx(app_state, rpc_tx).await.unwrap();

assert_eq!(tx_hash, response_tx_hash);
}
Expand All @@ -105,20 +101,3 @@ async fn test_compiled_class_hash_mismatch() {
let err = internal_add_tx(app_state, tx).await.unwrap_err();
assert_matches!(err, GatewaySpecError::CompiledClassHashMismatch);
}

fn calculate_hash(rpc_tx: &RpcTransaction) -> TransactionHash {
let optional_class_info = match &rpc_tx {
RpcTransaction::Declare(_declare_tx) => {
panic!("Declare transactions are not supported in this test")
}
_ => None,
};

let account_tx = rpc_tx_to_account_tx(
rpc_tx,
optional_class_info,
&ChainInfo::create_for_testing().chain_id,
)
.unwrap();
account_tx.tx_hash()
}
36 changes: 22 additions & 14 deletions crates/gateway/src/stateful_transaction_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ use blockifier::blockifier::stateful_validator::{
};
use blockifier::bouncer::BouncerConfig;
use blockifier::context::BlockContext;
use blockifier::execution::contract_class::ClassInfo;
use blockifier::state::cached_state::CachedState;
use blockifier::transaction::account_transaction::AccountTransaction;
use blockifier::versioned_constants::VersionedConstants;
#[cfg(test)]
use mockall::automock;
use starknet_api::core::{ContractAddress, Nonce};
use starknet_api::rpc_transaction::{RpcInvokeTransaction, RpcTransaction};
use starknet_api::executable_transaction::{
InvokeTransaction as ExecutableInvokeTransaction,
Transaction as ExecutableTransaction,
};
use starknet_api::transaction::TransactionHash;
use starknet_gateway_types::errors::GatewaySpecError;
use starknet_types_core::felt::Felt;
Expand All @@ -21,7 +23,6 @@ use tracing::error;
use crate::config::StatefulTransactionValidatorConfig;
use crate::errors::StatefulTransactionValidatorResult;
use crate::state_reader::{MempoolStateReader, StateReaderFactory};
use crate::utils::{get_sender_address, rpc_tx_to_account_tx};

#[cfg(test)]
#[path = "stateful_transaction_validator_test.rs"]
Expand Down Expand Up @@ -70,19 +71,25 @@ impl StatefulTransactionValidator {
// conversion is also relevant for the Mempool.
pub fn run_validate<V: StatefulTransactionValidatorTrait>(
&self,
rpc_tx: &RpcTransaction,
optional_class_info: Option<ClassInfo>,
executable_tx: &ExecutableTransaction,
mut validator: V,
) -> StatefulTransactionValidatorResult<ValidateInfo> {
let account_tx =
rpc_tx_to_account_tx(rpc_tx, optional_class_info, &self.config.chain_info.chain_id)?;
let tx_hash = account_tx.tx_hash();
let sender_address = get_sender_address(&account_tx);
let tx_hash = executable_tx.tx_hash();
let sender_address = executable_tx.contract_address();

let account_tx = AccountTransaction::try_from(
// TODO(Arni): create a try_from for &ExecutableTransaction.
executable_tx.clone(),
)
.map_err(|error| {
error!("Failed to convert executable transaction into account transaction: {}", error);
GatewaySpecError::UnexpectedError { data: "Internal server error".to_owned() }
})?;
let account_nonce = validator.get_nonce(sender_address).map_err(|e| {
error!("Failed to get nonce for sender address {}: {}", sender_address, e);
GatewaySpecError::UnexpectedError { data: "Internal server error.".to_owned() }
})?;
let skip_validate = skip_stateful_validations(rpc_tx, account_nonce);
let skip_validate = skip_stateful_validations(executable_tx, account_nonce);
validator
.validate(account_tx, skip_validate)
.map_err(|err| GatewaySpecError::ValidationFailure { data: err.to_string() })?;
Expand Down Expand Up @@ -120,15 +127,15 @@ impl StatefulTransactionValidator {
// 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, account_nonce: Nonce) -> bool {
fn skip_stateful_validations(tx: &ExecutableTransaction, account_nonce: Nonce) -> bool {
match tx {
RpcTransaction::Invoke(RpcInvokeTransaction::V3(tx)) => {
ExecutableTransaction::Invoke(ExecutableInvokeTransaction { tx, .. }) => {
// check if the transaction nonce is 1, meaning it is post deploy_account, and the
// account nonce is zero, meaning the account was not deployed yet. The mempool also
// verifies that the deploy_account transaction exists.
tx.nonce == Nonce(Felt::ONE) && account_nonce == Nonce(Felt::ZERO)
tx.nonce() == Nonce(Felt::ONE) && account_nonce == Nonce(Felt::ZERO)
}
RpcTransaction::DeployAccount(_) | RpcTransaction::Declare(_) => false,
ExecutableTransaction::DeployAccount(_) | ExecutableTransaction::Declare(_) => false,
}
}

Expand All @@ -142,6 +149,7 @@ pub fn get_latest_block_info(
})
}

// TODO(Arni): Remove irrelevant fields.
/// Holds members created by the stateful transaction validator, needed for
/// [`MempoolInput`](starknet_mempool_types::mempool_types::MempoolInput).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
51 changes: 31 additions & 20 deletions crates/gateway/src/stateful_transaction_validator_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ use blockifier::blockifier::stateful_validator::{
use blockifier::context::BlockContext;
use blockifier::test_utils::CairoVersion;
use blockifier::transaction::errors::{TransactionFeeError, TransactionPreValidationError};
use mempool_test_utils::invoke_tx_args;
use mempool_test_utils::starknet_api_test_utils::{
deploy_account_tx,
invoke_tx,
rpc_invoke_tx,
executable_invoke_tx as create_executable_invoke_tx,
TEST_SENDER_ADDRESS,
VALID_L1_GAS_MAX_AMOUNT,
VALID_L1_GAS_MAX_PRICE_PER_UNIT,
Expand All @@ -19,9 +16,11 @@ use num_bigint::BigUint;
use pretty_assertions::assert_eq;
use rstest::{fixture, rstest};
use starknet_api::core::{ContractAddress, Nonce, PatriciaKey};
use starknet_api::rpc_transaction::RpcTransaction;
use starknet_api::executable_transaction::Transaction;
use starknet_api::test_utils::deploy_account::executable_deploy_account_tx;
use starknet_api::test_utils::invoke::executable_invoke_tx;
use starknet_api::transaction::{Resource, TransactionHash};
use starknet_api::{contract_address, felt, patricia_key};
use starknet_api::{contract_address, deploy_account_tx_args, felt, invoke_tx_args, patricia_key};
use starknet_gateway_types::errors::GatewaySpecError;
use starknet_types_core::felt::Felt;

Expand Down Expand Up @@ -66,18 +65,19 @@ fn stateful_validator(block_context: BlockContext) -> StatefulTransactionValidat
// TODO(Arni): consider testing declare and deploy account.
#[rstest]
#[case::valid_tx(
invoke_tx(CairoVersion::Cairo1),
create_executable_invoke_tx(CairoVersion::Cairo1),
Ok(ValidateInfo{
tx_hash: TransactionHash(felt!(
"0x3c6546364f70395ad7c8d1ce64b990b698be21e786bee2efff57a694f37648e"
)),
tx_hash: TransactionHash::default(),
sender_address: contract_address!("0xc0020000"),
account_nonce: Nonce::default()
})
)]
#[case::invalid_tx(invoke_tx(CairoVersion::Cairo1), Err(STATEFUL_VALIDATOR_FEE_ERROR))]
#[case::invalid_tx(
create_executable_invoke_tx(CairoVersion::Cairo1),
Err(STATEFUL_VALIDATOR_FEE_ERROR)
)]
fn test_stateful_tx_validator(
#[case] rpc_tx: RpcTransaction,
#[case] executable_tx: Transaction,
#[case] expected_result: BlockifierStatefulValidatorResult<ValidateInfo>,
stateful_validator: StatefulTransactionValidator,
) {
Expand All @@ -90,7 +90,7 @@ fn test_stateful_tx_validator(
mock_validator.expect_validate().return_once(|_, _| expected_result.map(|_| ()));
mock_validator.expect_get_nonce().returning(|_| Ok(Nonce(Felt::ZERO)));

let result = stateful_validator.run_validate(&rpc_tx, None, mock_validator);
let result = stateful_validator.run_validate(&executable_tx, mock_validator);
assert_eq!(result, expected_result_as_stateful_transaction_result);
}

Expand Down Expand Up @@ -130,28 +130,39 @@ fn test_instantiate_validator() {

#[rstest]
#[case::should_skip_validation(
rpc_invoke_tx(invoke_tx_args!{nonce: Nonce(Felt::ONE)}),
Transaction::Invoke(executable_invoke_tx(invoke_tx_args!(nonce: Nonce(Felt::ONE)))),
Nonce::default(),
true
)]
#[case::should_not_skip_validation_nonce_over_max_nonce_for_skip(
rpc_invoke_tx(invoke_tx_args!{nonce: Nonce(Felt::TWO)}),
Transaction::Invoke(executable_invoke_tx(invoke_tx_args!(nonce: Nonce(Felt::ZERO)))),
Nonce::default(),
false
)]
#[case::should_not_skip_validation_non_invoke(deploy_account_tx(), Nonce::default(), false)]
#[case::should_not_skip_validation_non_invoke(
Transaction::DeployAccount(
executable_deploy_account_tx(deploy_account_tx_args!(), Nonce::default())
),
Nonce::default(),
false)]
#[case::should_not_skip_validation_account_nonce_1(
rpc_invoke_tx(invoke_tx_args!{sender_address: ContractAddress::from(TEST_SENDER_ADDRESS), nonce: Nonce(Felt::ONE)}),
Transaction::Invoke(executable_invoke_tx(
invoke_tx_args!(
nonce: Nonce(Felt::ONE),
sender_address: TEST_SENDER_ADDRESS.into()
)
)),
Nonce(Felt::ONE),
false
)]
fn test_skip_stateful_validation(
#[case] rpc_tx: RpcTransaction,
#[case] executable_tx: Transaction,
#[case] sender_nonce: Nonce,
#[case] should_skip_validate: bool,
stateful_validator: StatefulTransactionValidator,
) {
let sender_address = rpc_tx.calculate_sender_address().unwrap();
let sender_address = executable_tx.contract_address();

let mut mock_validator = MockStatefulTransactionValidatorTrait::new();
mock_validator
.expect_get_nonce()
Expand All @@ -161,5 +172,5 @@ fn test_skip_stateful_validation(
.expect_validate()
.withf(move |_, skip_validate| *skip_validate == should_skip_validate)
.returning(|_, _| Ok(()));
let _ = stateful_validator.run_validate(&rpc_tx, None, mock_validator);
let _ = stateful_validator.run_validate(&executable_tx, mock_validator);
}
Loading
Loading