From c05d80685ecf8755faf5e082439c8e5712f4670c Mon Sep 17 00:00:00 2001 From: Arni Hod Date: Sun, 17 Mar 2024 09:49:34 +0200 Subject: [PATCH] feat: create skelaton of transaction validator --- crates/gateway/Cargo.toml | 9 ++ crates/gateway/src/lib.rs | 1 + crates/gateway/src/transaction_validator.rs | 94 ++++++++++++++++++ .../gateway/src/transaction_validator_test.rs | 99 +++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 crates/gateway/src/lib.rs create mode 100644 crates/gateway/src/transaction_validator.rs create mode 100644 crates/gateway/src/transaction_validator_test.rs diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index da116f2d5..4a50f0aad 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -2,3 +2,12 @@ name = "gateway" version = "0.1.0" edition = "2021" + +[dependencies] +thiserror = "1.0" +starknet_api = "0.8.0" +assert_matches = "1.5.0" +rstest = "0.17.0" + +[lib] +name = "gateway_lib" diff --git a/crates/gateway/src/lib.rs b/crates/gateway/src/lib.rs new file mode 100644 index 000000000..1e5a0a3ee --- /dev/null +++ b/crates/gateway/src/lib.rs @@ -0,0 +1 @@ +pub mod transaction_validator; diff --git a/crates/gateway/src/transaction_validator.rs b/crates/gateway/src/transaction_validator.rs new file mode 100644 index 000000000..8a976359a --- /dev/null +++ b/crates/gateway/src/transaction_validator.rs @@ -0,0 +1,94 @@ +use starknet_api::transaction::Transaction; +use starknet_api::transaction::TransactionVersion; + +use thiserror::Error; + +#[cfg(test)] +#[path = "transaction_validator_test.rs"] +mod transaction_validator_test; + +#[derive(Debug, Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum TransactionValidatorError { + #[error("Invalid transaction type")] + InvalidTransactionType, + #[error("Transactions of version {0:?} are not valid. {1}")] + InvalidTransactionVersion(TransactionVersion, String), + #[error("Blocked transaction version {0:?}. {1}")] + BlockedTransactionVersion(TransactionVersion, String), +} + +pub type TransactionValidatorResult = Result; + +#[derive(Default)] +pub struct TransactionValidatorConfig { + pub block_declare_cairo1: bool, + pub block_declare_cairo0: bool, + + pub min_allowed_tx_version: TransactionVersion, + pub max_allowed_tx_version: TransactionVersion, +} + +pub struct TransactionValidator { + pub config: TransactionValidatorConfig, +} + +impl TransactionValidator { + pub fn validate(&self, tx: Transaction) -> TransactionValidatorResult<()> { + // Deploy transactions are deprecated. + // L1Handler transactions are not supported in the gateway. + if matches!(tx, Transaction::Deploy(_) | Transaction::L1Handler(_)) { + return Err(TransactionValidatorError::InvalidTransactionType); + } + + // Check if the declaration of Cairo / Cairo-0 contracts are blocked. + if let Transaction::Declare(tx) = &tx { + if tx.version() < TransactionVersion::TWO && self.config.block_declare_cairo0 { + return Err(TransactionValidatorError::BlockedTransactionVersion( + tx.version(), + "Declare of Cairo 0 is blocked.".into(), + )); + } + if tx.version() >= TransactionVersion::TWO && self.config.block_declare_cairo1 { + return Err(TransactionValidatorError::BlockedTransactionVersion( + tx.version(), + "Transaction type is temporarily blocked.".into(), + )); + } + } + + // TODO(Arni, 1/4/2024): Add a mechanism that validate the sender address is not blocked. + let version = get_tx_version(&tx); + if version < self.config.min_allowed_tx_version { + return Err(TransactionValidatorError::InvalidTransactionVersion( + version, + format!( + "Minimal supported version is {:?}.", + self.config.min_allowed_tx_version + ), + )); + } + if version > self.config.max_allowed_tx_version { + return Err(TransactionValidatorError::InvalidTransactionVersion( + version, + format!( + "Maximal supported version is {:?}.", + self.config.max_allowed_tx_version + ), + )); + } + + // TODO(Arni, 1/4/2024): Validate fee and tx size. + Ok(()) + } +} + +fn get_tx_version(tx: &Transaction) -> TransactionVersion { + match tx { + Transaction::Declare(tx) => tx.version(), + Transaction::DeployAccount(tx) => tx.version(), + Transaction::Invoke(tx) => tx.version(), + Transaction::Deploy(_) => TransactionVersion::ZERO, + Transaction::L1Handler(_) => TransactionVersion::ZERO, + } +} diff --git a/crates/gateway/src/transaction_validator_test.rs b/crates/gateway/src/transaction_validator_test.rs new file mode 100644 index 000000000..5b0ff405a --- /dev/null +++ b/crates/gateway/src/transaction_validator_test.rs @@ -0,0 +1,99 @@ +use rstest::rstest; + +use starknet_api::transaction::InvokeTransaction; +use starknet_api::transaction::InvokeTransactionV1; +use starknet_api::transaction::Transaction; +use starknet_api::transaction::TransactionVersion; + +use crate::transaction_validator::{ + TransactionValidator, TransactionValidatorConfig, TransactionValidatorError, +}; + +#[rstest] +#[case::block_declare_on_cairo_version_0( + TransactionValidatorConfig { + block_declare_cairo0: true, + ..Default::default() + }, + Transaction::Declare(starknet_api::transaction::DeclareTransaction::V0( + starknet_api::transaction::DeclareTransactionV0V1 {..Default::default()} + )), + Some(TransactionValidatorError::BlockedTransactionVersion( + TransactionVersion::ZERO, + "Declare of Cairo 0 is blocked.".to_string() + )) +)] +#[case::block_declare_on_cairo_version_1( + TransactionValidatorConfig { + block_declare_cairo1: true, + ..Default::default() + }, + Transaction::Declare(starknet_api::transaction::DeclareTransaction::V2( + starknet_api::transaction::DeclareTransactionV2 {..Default::default()} + )), + Some(TransactionValidatorError::BlockedTransactionVersion( + TransactionVersion::TWO, + "Transaction type is temporarily blocked.".to_string() + )) +)] +#[case::tx_version_below_minimal( + TransactionValidatorConfig { + min_allowed_tx_version: TransactionVersion::THREE, + max_allowed_tx_version: TransactionVersion::THREE, + ..Default::default() + }, + Transaction::Invoke(InvokeTransaction::V1(InvokeTransactionV1 {..Default::default()})), + Some(TransactionValidatorError::InvalidTransactionVersion( + TransactionVersion::ONE, + format!{"Minimal supported version is {:?}.", TransactionVersion::THREE} + )) +)] +#[case::tx_version_above_maximal( + TransactionValidatorConfig { + min_allowed_tx_version: TransactionVersion::ZERO, + max_allowed_tx_version: TransactionVersion::ZERO, + ..Default::default() + }, + Transaction::Invoke(InvokeTransaction::V1(InvokeTransactionV1 {..Default::default()})), + Some(TransactionValidatorError::InvalidTransactionVersion( + TransactionVersion::ONE, + format!{"Maximal supported version is {:?}.", TransactionVersion::ZERO} + )) +)] +#[case::deprecated_deploy_tx( + TransactionValidatorConfig { + ..Default::default() + }, + Transaction::Deploy(starknet_api::transaction::DeployTransaction {..Default::default()}), + Some(TransactionValidatorError::InvalidTransactionType) +)] +#[case::unsupported_l1_handler_tx( + TransactionValidatorConfig { + ..Default::default() + }, + Transaction::L1Handler(starknet_api::transaction::L1HandlerTransaction {..Default::default()}), + Some(TransactionValidatorError::InvalidTransactionType) +)] +#[case::valid_tx( + TransactionValidatorConfig { + min_allowed_tx_version: TransactionVersion::ZERO, + max_allowed_tx_version: TransactionVersion::THREE, + ..Default::default() + }, + Transaction::Invoke(InvokeTransaction::V1(InvokeTransactionV1 {..Default::default()})), + None +)] +fn test_transaction_version( + #[case] config: TransactionValidatorConfig, + #[case] tx: Transaction, + #[case] expected_error: Option, +) { + let tx_validator = TransactionValidator { config }; + let result = tx_validator.validate(tx); + + if let Some(expected_error) = expected_error { + assert_eq!(result.unwrap_err(), expected_error); + } else { + assert!(result.is_ok()); + } +}