diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 8752fa02..10759bef 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -24,6 +24,14 @@ pub enum TransactionValidatorError { }, #[error("The resource bounds mapping is missing a resource {resource:?}.")] MissingResource { resource: Resource }, + #[error( + "Calldata length exceeded maximum: length {calldata_length} + (allowed length: {max_calldata_length})." + )] + CalldataTooLong { + calldata_length: usize, + max_calldata_length: usize, + }, } pub type TransactionValidatorResult = Result; diff --git a/crates/gateway/src/starknet_api_test_utils.rs b/crates/gateway/src/starknet_api_test_utils.rs index 6d1975ad..f2310eab 100644 --- a/crates/gateway/src/starknet_api_test_utils.rs +++ b/crates/gateway/src/starknet_api_test_utils.rs @@ -4,7 +4,7 @@ use starknet_api::external_transaction::{ ExternalDeployAccountTransactionV3, ExternalInvokeTransaction, ExternalInvokeTransactionV3, ExternalTransaction, }; -use starknet_api::transaction::{ResourceBounds, ResourceBoundsMapping}; +use starknet_api::transaction::{Calldata, ResourceBounds, ResourceBoundsMapping}; // Utils. pub fn create_external_declare_tx_for_testing( @@ -29,6 +29,7 @@ pub fn create_external_declare_tx_for_testing( pub fn create_external_deploy_account_tx_for_testing( resource_bounds: ResourceBoundsMapping, + constructor_calldata: Calldata, ) -> ExternalTransaction { ExternalTransaction::DeployAccount(ExternalDeployAccountTransaction::V3( ExternalDeployAccountTransactionV3 { @@ -36,7 +37,7 @@ pub fn create_external_deploy_account_tx_for_testing( tip: Default::default(), contract_address_salt: Default::default(), class_hash: Default::default(), - constructor_calldata: Default::default(), + constructor_calldata, nonce: Default::default(), signature: Default::default(), nonce_data_availability_mode: DataAvailabilityMode::L1, @@ -48,6 +49,7 @@ pub fn create_external_deploy_account_tx_for_testing( pub fn create_external_invoke_tx_for_testing( resource_bounds: ResourceBoundsMapping, + calldata: Calldata, ) -> ExternalTransaction { ExternalTransaction::Invoke(ExternalInvokeTransaction::V3(ExternalInvokeTransactionV3 { resource_bounds, @@ -55,7 +57,7 @@ pub fn create_external_invoke_tx_for_testing( signature: Default::default(), nonce: Default::default(), sender_address: Default::default(), - calldata: Default::default(), + calldata, nonce_data_availability_mode: DataAvailabilityMode::L1, fee_data_availability_mode: DataAvailabilityMode::L1, paymaster_data: Default::default(), diff --git a/crates/gateway/src/stateless_transaction_validator.rs b/crates/gateway/src/stateless_transaction_validator.rs index ab75f0b8..1b29add8 100644 --- a/crates/gateway/src/stateless_transaction_validator.rs +++ b/crates/gateway/src/stateless_transaction_validator.rs @@ -15,6 +15,8 @@ pub struct TransactionValidatorConfig { // If true, validates that the reousrce bounds are not zero. pub validate_non_zero_l1_gas_fee: bool, pub validate_non_zero_l2_gas_fee: bool, + + pub max_calldata_length: usize, } pub struct TransactionValidator { @@ -25,9 +27,9 @@ impl TransactionValidator { pub fn validate(&self, tx: &ExternalTransaction) -> TransactionValidatorResult<()> { // TODO(Arni, 1/5/2024): Add a mechanism that validate the sender address is not blocked. // TODO(Arni, 1/5/2024): Validate transaction version. - // TODO(Arni, 4/4/2024): Validate tx signature and calldata are not too long. self.validate_fee(tx)?; + self.validate_tx_size(tx)?; Ok(()) } @@ -72,4 +74,40 @@ impl TransactionValidator { Ok(()) } + + fn validate_tx_size(&self, tx: &ExternalTransaction) -> TransactionValidatorResult<()> { + self.validate_tx_calldata_size(tx)?; + + // TODO(Arni, 4/4/2024): Validate tx signature is not too long. + + Ok(()) + } + + fn validate_tx_calldata_size( + &self, + tx: &ExternalTransaction, + ) -> TransactionValidatorResult<()> { + let calldata = match tx { + ExternalTransaction::Declare(_) => { + // Declare transaction has no calldata. + return Ok(()); + } + ExternalTransaction::DeployAccount(tx) => match tx { + ExternalDeployAccountTransaction::V3(tx) => &tx.constructor_calldata, + }, + ExternalTransaction::Invoke(tx) => match tx { + ExternalInvokeTransaction::V3(tx) => &tx.calldata, + }, + }; + + let calldata_length = calldata.0.len(); + if calldata_length > self.config.max_calldata_length { + return Err(TransactionValidatorError::CalldataTooLong { + calldata_length, + max_calldata_length: self.config.max_calldata_length, + }); + } + + Ok(()) + } } diff --git a/crates/gateway/src/stateless_transaction_validator_test.rs b/crates/gateway/src/stateless_transaction_validator_test.rs index bf379715..88acd084 100644 --- a/crates/gateway/src/stateless_transaction_validator_test.rs +++ b/crates/gateway/src/stateless_transaction_validator_test.rs @@ -1,7 +1,9 @@ use rstest::rstest; +use starknet_api::calldata; use starknet_api::external_transaction::ExternalTransaction; -use starknet_api::transaction::{Resource, ResourceBounds, ResourceBoundsMapping}; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::{Calldata, Resource, ResourceBounds, ResourceBoundsMapping}; use crate::starknet_api_test_utils::{ create_external_declare_tx_for_testing, create_external_deploy_account_tx_for_testing, @@ -16,47 +18,73 @@ use crate::stateless_transaction_validator::{ const VALIDATOR_CONFIG_FOR_TESTING: TransactionValidatorConfig = TransactionValidatorConfig { validate_non_zero_l1_gas_fee: true, validate_non_zero_l2_gas_fee: false, + max_calldata_length: 1, }; -const VALIDATOR_CONFIG_FOR_L2_GAS_TESTING: TransactionValidatorConfig = - TransactionValidatorConfig { - validate_non_zero_l1_gas_fee: false, - validate_non_zero_l2_gas_fee: true, - }; - #[rstest] #[case::ignore_resource_bounds( TransactionValidatorConfig{ validate_non_zero_l1_gas_fee: false, validate_non_zero_l2_gas_fee: false, + ..VALIDATOR_CONFIG_FOR_TESTING }, - create_external_invoke_tx_for_testing(zero_resource_bounds_mapping()), + create_external_invoke_tx_for_testing(zero_resource_bounds_mapping(), calldata![]), Ok(()) )] #[case::missing_l1_gas_resource_bounds( - VALIDATOR_CONFIG_FOR_TESTING, - create_external_invoke_tx_for_testing(ResourceBoundsMapping::default()), + TransactionValidatorConfig{ + validate_non_zero_l1_gas_fee: true, + ..VALIDATOR_CONFIG_FOR_TESTING + }, + create_external_invoke_tx_for_testing(ResourceBoundsMapping::default(), calldata![]), Err(TransactionValidatorError::MissingResource { resource: Resource::L1Gas }) )] #[case::missing_l2_gas_resource_bounds( - VALIDATOR_CONFIG_FOR_L2_GAS_TESTING, - create_external_invoke_tx_for_testing(ResourceBoundsMapping::default()), + TransactionValidatorConfig{ + validate_non_zero_l1_gas_fee: false, + validate_non_zero_l2_gas_fee: true, + ..VALIDATOR_CONFIG_FOR_TESTING + }, + create_external_invoke_tx_for_testing(ResourceBoundsMapping::default(), calldata![]), Err(TransactionValidatorError::MissingResource { resource: Resource::L2Gas }) )] #[case::zero_l1_gas_resource_bounds( - VALIDATOR_CONFIG_FOR_TESTING, - create_external_invoke_tx_for_testing(zero_resource_bounds_mapping()), + TransactionValidatorConfig{ + validate_non_zero_l1_gas_fee: true, + ..VALIDATOR_CONFIG_FOR_TESTING + }, + create_external_invoke_tx_for_testing(zero_resource_bounds_mapping(), calldata![]), Err(TransactionValidatorError::ZeroFee{ resource: Resource::L1Gas, resource_bounds: ResourceBounds::default() }) )] #[case::zero_l2_gas_resource_bounds( - VALIDATOR_CONFIG_FOR_L2_GAS_TESTING, - create_external_invoke_tx_for_testing(non_zero_l1_resource_bounds_mapping()), + TransactionValidatorConfig{ + validate_non_zero_l1_gas_fee: false, + validate_non_zero_l2_gas_fee: true, + ..VALIDATOR_CONFIG_FOR_TESTING + }, + create_external_invoke_tx_for_testing(non_zero_l1_resource_bounds_mapping(), calldata![]), Err(TransactionValidatorError::ZeroFee{ resource: Resource::L2Gas, resource_bounds: ResourceBounds::default() }) )] +#[case::deploy_account_calldata_too_long( + VALIDATOR_CONFIG_FOR_TESTING, + create_external_deploy_account_tx_for_testing( + non_zero_l1_resource_bounds_mapping(), + calldata![StarkFelt::from_u128(1),StarkFelt::from_u128(2)], + ), + Err(TransactionValidatorError::CalldataTooLong { calldata_length: 2, max_calldata_length: 1 }) +)] +#[case::invoke_calldata_too_long( + VALIDATOR_CONFIG_FOR_TESTING, + create_external_invoke_tx_for_testing( + non_zero_l1_resource_bounds_mapping(), + calldata![StarkFelt::from_u128(1),StarkFelt::from_u128(2)], + ), + Err(TransactionValidatorError::CalldataTooLong { calldata_length: 2, max_calldata_length: 1 }) +)] #[case::valid_declare_tx( VALIDATOR_CONFIG_FOR_TESTING, create_external_declare_tx_for_testing(non_zero_l1_resource_bounds_mapping()), @@ -64,17 +92,21 @@ const VALIDATOR_CONFIG_FOR_L2_GAS_TESTING: TransactionValidatorConfig = )] #[case::valid_deploy_account_tx( VALIDATOR_CONFIG_FOR_TESTING, - create_external_deploy_account_tx_for_testing(non_zero_l1_resource_bounds_mapping(),), + create_external_deploy_account_tx_for_testing(non_zero_l1_resource_bounds_mapping(), calldata![]), Ok(()) )] #[case::valid_invoke_tx( VALIDATOR_CONFIG_FOR_TESTING, - create_external_invoke_tx_for_testing(non_zero_l1_resource_bounds_mapping()), + create_external_invoke_tx_for_testing(non_zero_l1_resource_bounds_mapping(), calldata![]), Ok(()) )] #[case::valid_l2_gas_invoke_tx( - VALIDATOR_CONFIG_FOR_L2_GAS_TESTING, - create_external_invoke_tx_for_testing(non_zero_l2_resource_bounds_mapping()), + TransactionValidatorConfig{ + validate_non_zero_l1_gas_fee: false, + validate_non_zero_l2_gas_fee: true, + ..VALIDATOR_CONFIG_FOR_TESTING + }, + create_external_invoke_tx_for_testing(non_zero_l2_resource_bounds_mapping(), calldata![]), Ok(()) )] fn test_transaction_validator(